commit f06de361983f04daa074621595cf32bb0d097b36 Author: wehub-resource-sync Date: Mon Jul 13 12:58:31 2026 +0800 chore: import upstream snapshot with attribution diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..3d7704f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,15 @@ +# 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'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..ce97a7e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,83 @@ +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 diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..74b3807 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,28 @@ +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 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..13f8d2d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# 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" diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000..4e46246 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,82 @@ +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 \ No newline at end of file diff --git a/.github/workflows/android_foss.yml b/.github/workflows/android_foss.yml new file mode 100644 index 0000000..9371822 --- /dev/null +++ b/.github/workflows/android_foss.yml @@ -0,0 +1,57 @@ +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 \ No newline at end of file diff --git a/.github/workflows/android_market.yml b/.github/workflows/android_market.yml new file mode 100644 index 0000000..acbea17 --- /dev/null +++ b/.github/workflows/android_market.yml @@ -0,0 +1,57 @@ +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 \ No newline at end of file diff --git a/.github/workflows/tb_release.yml b/.github/workflows/tb_release.yml new file mode 100644 index 0000000..2653800 --- /dev/null +++ b/.github/workflows/tb_release.yml @@ -0,0 +1,113 @@ +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 }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d68a613 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +.DS_Store +/build +/app/release +/captures +.externalNativeBuild +.cxx +/.idea +/libs/nQuant/build +/.kotlin/errors/ +/.kotlin +/scripts/ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..25bcb4d --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,572 @@ +# 📐 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 +``` \ No newline at end of file diff --git a/ARCHITECTURE_2 b/ARCHITECTURE_2 new file mode 100644 index 0000000..20e77a4 --- /dev/null +++ b/ARCHITECTURE_2 @@ -0,0 +1,612 @@ +%%{ + 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 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..cfe0a37 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,89 @@ +# 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! \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4c4e2bd --- /dev/null +++ b/README.md @@ -0,0 +1,1229 @@ +
+
+ + +
+ +
+ +# Image Toolbox + +
+ +
+ +

+ API + Kotlin + Jetpack Compose + material +
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + +T8RIN%2FImageToolbox | Trendshift + + + + 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 330 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] Deuteranopia + - [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 + - [x] Distortion + - [x] VHS NTSC + - [x] Expand Image + - [x] Shader + - [x] Torn Edge + - [x] Drop Shadow + - [x] Flare + - [x] Distort Perspective + - [x] Java Look And Feel + - [x] Shear + - [x] Water Drop + - [x] High Pass + - [x] Color Mask + - [x] Adaptive Blur + - [x] Chrome + - [x] Dissolve + - [x] Feedback + - [x] Lens Blur + - [x] Levels + - [x] Light Effects + - [x] Rays + - [x] Ripple + - [x] Auto White Balance + + +
+ +- 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 +- Texture generation with more than 60 presets + +
+ Available textures +
+ + - Brushed Metal + - Caustics + - Cellular + - Checkerboard + - FBM + - Marble + - Plasma + - Quilt + - Wood + - Brick + - Camouflage + - Cell + - Cloud + - Crack + - Fabric + - Foliage + - Honeycomb + - Ice + - Lava + - Nebula + - Paper + - Rust + - Sand + - Smoke + - Stone + - Terrain + - Topography + - Water Ripple + - Advanced Wood + - Grass + - Dirt + - Leather + - Concrete + - Asphalt + - Moss + - Fire + - Aurora + - Oil slick + - Watercolor + - Abstract flow + - Opal + - Damascus Steel + - Lightning + - Velvet + - Ink Marbling + - Holographic Foil + - Bioluminescence + - Cosmic Vortex + - Lava Lamp + - Event Horizon + - Fractal Bloom + - Chromatic Tunnel + - Eclipse Corona + - Strange Attractor + - Ferrofluid Crown + - Supernova + - Iris + - Peacock Feather + - Nautilus Shell + - Ringed Planet + +
+ +- Fragment Shader creation +- 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 + - Tesseract or PaddleOCR (v5/v6) +- 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 + - YOLO + - MODNet +- 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) + - Remove Annotations +- Document Scanning +- AI tools (100+ 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 + - VVC + - AVIF (AV1/AV2) + - WEBP + - JPEG + - JPG + - PNG Lossless + - PNG Lossy + - OxiPNG + - ImageQuant + - 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 + - Batch rename by pattern + - Duplicate Finder + +**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 24 + +- [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. + +- [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. + +- [GPU Image](https://github.com/cats-oss/android-gpuimage) for creating and applying filters to the + images. + +- [DeJpeg](https://github.com/jeeneo/dejpeg) for start of AI tools + +- [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. +``` diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..f818e1c --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,7 @@ +# WeHub 来源说明 + +- 原始项目:`T8RIN/ImageToolbox` +- 原始仓库:https://github.com/T8RIN/ImageToolbox +- 导入方式:上游默认分支的最新快照 +- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准 +- 本文件仅用于记录来源,不代表 WeHub 是原项目作者 diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..f70d2bf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,10 @@ +/build/ +/release/ +/foss/ +/market/ +/jxl/ +/build +/release +/foss +/market +/jxl \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..0e800c0 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,202 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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") + } + } + + 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" + ) +} + +androidComponents { + beforeVariants(selector().all()) { variantBuilder -> + val flavorName = variantBuilder.productFlavors.firstOrNull()?.second.orEmpty() + val flavorCap = flavorName.replaceFirstChar(Char::uppercase) + + val gmsEnabled = android.productFlavors + .findByName(flavorName) + ?.extra + ?.get("gmsEnabled") == true + + tasks.configureEach { + val isTargetTask = listOf("GoogleServices", "Crashlytics").any { marker -> + name.contains(marker) + } && name.contains(flavorCap) + + if (isTargetTask) { + enabled = gmsEnabled + } + } + } +} + +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)) +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..85fda73 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,135 @@ +# 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.core.filters.domain.model.shader.** { *; } +-keep class com.t8rin.imagetoolbox.core.filters.domain.model.shader.** +-keep class com.t8rin.imagetoolbox.core.filters.domain.model.shader.* + +-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.* + +# Moshi reflects image export preset models when saving/loading presets. +-keep class com.t8rin.imagetoolbox.core.domain.image.model.ImageExportProfiles { *; } +-keep class com.t8rin.imagetoolbox.core.domain.image.model.ImageExportProfile { *; } +-keep class com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo { *; } +-keep class com.t8rin.imagetoolbox.core.domain.image.model.Preset { *; } +-keep class com.t8rin.imagetoolbox.core.domain.image.model.Preset$* { *; } +-keep class com.t8rin.imagetoolbox.core.domain.image.model.ResizeType { *; } +-keep class com.t8rin.imagetoolbox.core.domain.image.model.ResizeType$* { *; } +-keep class com.t8rin.imagetoolbox.core.domain.image.model.Quality { *; } +-keep class com.t8rin.imagetoolbox.core.domain.image.model.Quality$* { *; } +-keep class com.t8rin.imagetoolbox.core.domain.model.IntegerSize { *; } +-keep class com.t8rin.imagetoolbox.core.data.json.PresetJson { *; } +-keep class com.t8rin.imagetoolbox.core.data.json.ResizeTypeJson { *; } +-keep class com.t8rin.imagetoolbox.core.data.json.ImageScaleModeJson { *; } + +# Moshi reflects app history/statistics models for recent tools. +-keep class com.t8rin.imagetoolbox.core.domain.history.model.LastUsedTool { *; } +-keep class com.t8rin.imagetoolbox.core.domain.history.model.AppUsageStatistics { *; } +-keep class com.t8rin.imagetoolbox.core.data.history.LastUsedTools { *; } +-keep class com.t8rin.imagetoolbox.core.data.history.SavedFormatCounters { *; } + +# coil-resvg uses JNA/Uniffi generated names at runtime. +-keep class com.hashsequence.coilresvg.** { *; } +-keep class com.sun.jna.** { *; } +-dontwarn java.awt.Component +-dontwarn java.awt.GraphicsEnvironment +-dontwarn java.awt.HeadlessException +-dontwarn java.awt.Window + +-dontwarn javax.naming.NamingEnumeration +-dontwarn javax.naming.NamingException +-dontwarn javax.naming.directory.Attribute +-dontwarn javax.naming.directory.Attributes +-dontwarn javax.naming.directory.DirContext +-dontwarn javax.naming.directory.InitialDirContext +-dontwarn javax.naming.directory.SearchControls +-dontwarn javax.naming.directory.SearchResult + +-assumevalues public class androidx.compose.runtime.ComposeRuntimeFlags { + static boolean isLinkBufferComposerEnabled return true; +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e0e10a1 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,239 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/AppActivity.kt b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/AppActivity.kt new file mode 100644 index 0000000..e7ce4ef --- /dev/null +++ b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/AppActivity.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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) + +} \ No newline at end of file diff --git a/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/ImageToolboxApplication.kt b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/ImageToolboxApplication.kt new file mode 100644 index 0000000..6b70a40 --- /dev/null +++ b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/ImageToolboxApplication.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.remote.AnalyticsManager +import com.t8rin.imagetoolbox.core.domain.saving.KeepAliveService +import com.t8rin.imagetoolbox.core.resources.emoji.Emoji.initEmoji +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 + + @Inject + lateinit var analyticsManager: AnalyticsManager + + private var isSetupCompleted: Boolean = false + + override fun onCreate() { + super.onCreate() + runSetup() + } + + override fun runSetup() { + if (isSetupCompleted) return + + if (isMain()) { + setupFlags() + initAppContext() + initEmoji() + initOpenCV() + initNeuralTool() + initColorNames() + initQrScanner() + attachLogWriter() + applyGlobalExceptionHandler() + registerSecurityProviders() + initPdfBox() + injectBaseComponent() + initCollages() + + isSetupCompleted = true + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/AttachLogWriter.kt b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/AttachLogWriter.kt new file mode 100644 index 0000000..bab4d2a --- /dev/null +++ b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/AttachLogWriter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.resources.R +import com.t8rin.imagetoolbox.core.ui.utils.helper.DeviceInfo +import com.t8rin.imagetoolbox.core.utils.Logger +import com.t8rin.imagetoolbox.core.utils.attachLogWriter + + +internal fun ImageToolboxApplication.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 + ), + errorHandler = analyticsManager::sendReport, + isSyncCreate = false + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitCollages.kt b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitCollages.kt new file mode 100644 index 0000000..f0fbbb5 --- /dev/null +++ b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitCollages.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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() } \ No newline at end of file diff --git a/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitColorNames.kt b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitColorNames.kt new file mode 100644 index 0000000..791246b --- /dev/null +++ b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitColorNames.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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) } \ No newline at end of file diff --git a/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitNeuralTool.kt b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitNeuralTool.kt new file mode 100644 index 0000000..5988c95 --- /dev/null +++ b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitNeuralTool.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 +) \ No newline at end of file diff --git a/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitOpenCV.kt b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitOpenCV.kt new file mode 100644 index 0000000..215c0b9 --- /dev/null +++ b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitOpenCV.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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) \ No newline at end of file diff --git a/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitPdfBox.kt b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitPdfBox.kt new file mode 100644 index 0000000..e58859d --- /dev/null +++ b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitPdfBox.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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) \ No newline at end of file diff --git a/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitQrScanner.kt b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitQrScanner.kt new file mode 100644 index 0000000..add7513 --- /dev/null +++ b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitQrScanner.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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)) \ No newline at end of file diff --git a/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InjectBaseComponent.kt b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InjectBaseComponent.kt new file mode 100644 index 0000000..0bd2a37 --- /dev/null +++ b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InjectBaseComponent.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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) \ No newline at end of file diff --git a/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/RegisterSecurityProviders.kt b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/RegisterSecurityProviders.kt new file mode 100644 index 0000000..2bd7180 --- /dev/null +++ b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/RegisterSecurityProviders.kt @@ -0,0 +1,104 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.imagetoolbox.core.utils.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: Throwable) { + 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" \ No newline at end of file diff --git a/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/SetupFlags.kt b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/SetupFlags.kt new file mode 100644 index 0000000..085ad99 --- /dev/null +++ b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/SetupFlags.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 +import androidx.compose.material3.ComposeMaterial3Flags +import androidx.compose.runtime.ComposeRuntimeFlags +import androidx.compose.runtime.Composer +import androidx.compose.runtime.ExperimentalComposeApi +import androidx.compose.runtime.tooling.ComposeStackTraceMode +import com.arkivanov.decompose.DecomposeSettings + +@OptIn(ExperimentalComposeApi::class) +internal fun setupFlags() { + ComposeRuntimeFlags.isLinkBufferComposerEnabled = true + ComposeFoundationFlags.isPausableCompositionInPrefetchEnabled = true + ComposeMaterial3Flags.isCheckboxStylingFixEnabled = true + Composer.setDiagnosticStackTraceMode(ComposeStackTraceMode.GroupKeys) + DecomposeSettings.update { it.copy(duplicateConfigurationsEnabled = true) } +} \ No newline at end of file diff --git a/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/utils/GetProcessName.kt b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/utils/GetProcessName.kt new file mode 100644 index 0000000..5a9bd83 --- /dev/null +++ b/app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/utils/GetProcessName.kt @@ -0,0 +1,69 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.imagetoolbox.core.utils.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.classLoader + ) + 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 +} \ No newline at end of file diff --git a/app/src/main/res/resources.properties b/app/src/main/res/resources.properties new file mode 100644 index 0000000..20a99a4 --- /dev/null +++ b/app/src/main/res/resources.properties @@ -0,0 +1,17 @@ +# +# ImageToolbox is an image editor for android +# Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language 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 \ No newline at end of file diff --git a/app/src/market/debug/google-services.json b/app/src/market/debug/google-services.json new file mode 100644 index 0000000..cb5ff56 --- /dev/null +++ b/app/src/market/debug/google-services.json @@ -0,0 +1,39 @@ +{ + "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" +} \ No newline at end of file diff --git a/app/src/market/release/google-services.json b/app/src/market/release/google-services.json new file mode 100644 index 0000000..0757365 --- /dev/null +++ b/app/src/market/release/google-services.json @@ -0,0 +1,39 @@ +{ + "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" +} \ No newline at end of file diff --git a/benchmark/.gitignore b/benchmark/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/benchmark/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts new file mode 100644 index 0000000..35eae94 --- /dev/null +++ b/benchmark/build.gradle.kts @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" + } +} \ No newline at end of file diff --git a/benchmark/src/main/AndroidManifest.xml b/benchmark/src/main/AndroidManifest.xml new file mode 100644 index 0000000..79e2759 --- /dev/null +++ b/benchmark/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + \ No newline at end of file diff --git a/benchmark/src/main/java/com/t8rin/imagetoolbox/benchmark/BaselineProfileGenerator.kt b/benchmark/src/main/java/com/t8rin/imagetoolbox/benchmark/BaselineProfileGenerator.kt new file mode 100644 index 0000000..84c836a --- /dev/null +++ b/benchmark/src/main/java/com/t8rin/imagetoolbox/benchmark/BaselineProfileGenerator.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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() + } + ) +} \ No newline at end of file diff --git a/build-logic/.gitignore b/build-logic/.gitignore new file mode 100644 index 0000000..f211e70 --- /dev/null +++ b/build-logic/.gitignore @@ -0,0 +1,11 @@ +*.iml +.gradle +/local.properties +.DS_Store +/build +/app/release +/captures +.externalNativeBuild +.cxx +/.idea +/.kotlin/ diff --git a/build-logic/convention/.gitignore b/build-logic/convention/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/build-logic/convention/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts new file mode 100644 index 0000000..05b51c4 --- /dev/null +++ b/build-logic/convention/build.gradle.kts @@ -0,0 +1,71 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" + } + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/kotlin/ImageToolboxApplicationPlugin.kt b/build-logic/convention/src/main/kotlin/ImageToolboxApplicationPlugin.kt new file mode 100644 index 0000000..acde500 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/ImageToolboxApplicationPlugin.kt @@ -0,0 +1,79 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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()) + } + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/kotlin/ImageToolboxHiltPlugin.kt b/build-logic/convention/src/main/kotlin/ImageToolboxHiltPlugin.kt new file mode 100644 index 0000000..c802dd1 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/ImageToolboxHiltPlugin.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + ksp(libs.kotlin.metadata.jvm) + } + } + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/kotlin/ImageToolboxLibraryComposePlugin.kt b/build-logic/convention/src/main/kotlin/ImageToolboxLibraryComposePlugin.kt new file mode 100644 index 0000000..b305992 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/ImageToolboxLibraryComposePlugin.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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()) + } + } +} diff --git a/build-logic/convention/src/main/kotlin/ImageToolboxLibraryFeaturePlugin.kt b/build-logic/convention/src/main/kotlin/ImageToolboxLibraryFeaturePlugin.kt new file mode 100644 index 0000000..a5ac464 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/ImageToolboxLibraryFeaturePlugin.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + } + } + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/kotlin/ImageToolboxLibraryPlugin.kt b/build-logic/convention/src/main/kotlin/ImageToolboxLibraryPlugin.kt new file mode 100644 index 0000000..ad4afe8 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/ImageToolboxLibraryPlugin.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + } + } + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ConfigureCompose.kt b/build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ConfigureCompose.kt new file mode 100644 index 0000000..a762a57 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ConfigureCompose.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.compose.preview) + debugImplementation(libs.compose.tooling) + } + } + + extensions.configure { + stabilityConfigurationFiles.addAll( + rootProject.layout.projectDirectory.file("compose_compiler_config.conf") + ) + } +} diff --git a/build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ConfigureDetekt.kt b/build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ConfigureDetekt.kt new file mode 100644 index 0000000..15231d3 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ConfigureDetekt.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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) + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ConfigureKotlinAndroid.kt b/build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ConfigureKotlinAndroid.kt new file mode 100644 index 0000000..1a7580a --- /dev/null +++ b/build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ConfigureKotlinAndroid.kt @@ -0,0 +1,138 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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" + disable += "ConvertLongToDuration" + } + } + + 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) + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ProjectExtensions.kt b/build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ProjectExtensions.kt new file mode 100644 index 0000000..2571496 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ProjectExtensions.kt @@ -0,0 +1,96 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.debugImplementation( + dependency: Library +) = add("debugImplementation", dependency) + +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 +} \ No newline at end of file diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 0000000..baf2f55 --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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") \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..b4abfdb --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/compose_compiler_config.conf b/compose_compiler_config.conf new file mode 100644 index 0000000..c6cd494 --- /dev/null +++ b/compose_compiler_config.conf @@ -0,0 +1,36 @@ +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.* \ No newline at end of file diff --git a/core/crash/.gitignore b/core/crash/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/crash/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/crash/build.gradle.kts b/core/crash/build.gradle.kts new file mode 100644 index 0000000..cc6c10d --- /dev/null +++ b/core/crash/build.gradle.kts @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/core/crash/src/foss/java/com/t8rin/imagetoolbox/core/crash/data/AnalyticsManagerImpl.kt b/core/crash/src/foss/java/com/t8rin/imagetoolbox/core/crash/data/AnalyticsManagerImpl.kt new file mode 100644 index 0000000..b43ff34 --- /dev/null +++ b/core/crash/src/foss/java/com/t8rin/imagetoolbox/core/crash/data/AnalyticsManagerImpl.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + override fun pushMetric( + tag: String, + metric: String + ) = Unit + +} \ No newline at end of file diff --git a/core/crash/src/main/AndroidManifest.xml b/core/crash/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f51adf0 --- /dev/null +++ b/core/crash/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/di/CrashModule.kt b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/di/CrashModule.kt new file mode 100644 index 0000000..f598a97 --- /dev/null +++ b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/di/CrashModule.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/CrashActivity.kt b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/CrashActivity.kt new file mode 100644 index 0000000..8aa2470 --- /dev/null +++ b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/CrashActivity.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + +} \ No newline at end of file diff --git a/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashActionButtons.kt b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashActionButtons.kt new file mode 100644 index 0000000..87c3f4b --- /dev/null +++ b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashActionButtons.kt @@ -0,0 +1,190 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.AnimatedContent +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.tween +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.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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.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.Icons +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.utils.animation.springySpec +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedCircularProgressIndicator +import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText +import kotlinx.coroutines.delay + +@Composable +internal fun CrashActionButtons( + onCopyCrashInfo: () -> Unit, + onShareLogs: () -> Unit, + isSendingLogs: Boolean, + 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), + isLoading = isSendingLogs + ) + } + } +} + +@Composable +private fun LargeEnhancedButton( + onClick: () -> Unit, + containerColor: Color, + contentColor: Color, + icon: ImageVector, + text: String, + isLoading: Boolean = false, + modifier: Modifier = Modifier, +) { + 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) + ) + } + } + + EnhancedButton( + onClick = onClick, + modifier = modifier + .height(48.dp), + containerColor = containerColor, + contentColor = contentColor, + borderColor = MaterialTheme.colorScheme.outlineVariant( + onTopOf = containerColor + ), + contentPadding = ButtonDefaults.ButtonWithIconContentPadding + ) { + AnimatedContent(progress > 0) { showLoading -> + if (showLoading) { + EnhancedCircularProgressIndicator( + modifier = Modifier.size(24.dp * progress), + trackColor = MaterialTheme.colorScheme.onPrimary.copy(0.2f), + color = MaterialTheme.colorScheme.onPrimary, + strokeWidth = 3.dp + ) + } else { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Icon( + imageVector = icon, + contentDescription = text + ) + Spacer(modifier = Modifier.width(8.dp)) + AutoSizeText( + text = text, + maxLines = 1 + ) + } + } + } + } +} \ No newline at end of file diff --git a/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashAttentionCard.kt b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashAttentionCard.kt new file mode 100644 index 0000000..39c1ae8 --- /dev/null +++ b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashAttentionCard.kt @@ -0,0 +1,101 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.material3.Text +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.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 +import com.t8rin.imagetoolbox.core.ui.widget.other.EmojiItem + +@Composable +internal fun CrashAttentionCard( + title: String, + description: String, + emoji: String +) { + 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) + } + } + EmojiItem( + emoji = "", + fontSize = 0.sp, + animatedEmoji = emoji, + shape = ShapeDefaults.default, + modifier = Modifier + .size(80.dp) + .statusBarsPadding(), + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = title, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + fontSize = 22.sp, + lineHeight = 26.sp, + color = contentColor + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = description, + textAlign = TextAlign.Center, + fontSize = 16.sp, + lineHeight = 20.sp, + color = contentColor + ) + } +} \ No newline at end of file diff --git a/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashBottomButtons.kt b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashBottomButtons.kt new file mode 100644 index 0000000..e5c9c2e --- /dev/null +++ b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashBottomButtons.kt @@ -0,0 +1,84 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ContentCopy +import com.t8rin.imagetoolbox.core.resources.icons.RestartAlt +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)) + } + ) + onCopy?.let { + 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) + ) + } + } + } +} \ No newline at end of file diff --git a/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashHandler.kt b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashHandler.kt new file mode 100644 index 0000000..deb9811 --- /dev/null +++ b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashHandler.kt @@ -0,0 +1,121 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 isOutOfMemory: Boolean = listOf( + title, + body, + exceptionName, + stackTrace + ).any { text -> + text.contains( + other = "OutOfMemoryError", + ignoreCase = true + ) || text.contains( + other = "Failed to allocate", + ignoreCase = true + ) + } + + val isForegroundServiceDidNotStartInTime: Boolean = listOf( + title, + body, + exceptionName, + stackTrace + ).any { text -> + text.contains( + other = "ForegroundServiceDidNotStartInTimeException", + ignoreCase = true + ) + } + + val isNonReportable: Boolean = isOutOfMemory || isForegroundServiceDidNotStartInTime + + val emoji: String = if (isNonReportable) { + if (isOutOfMemory) { + "file:///android_asset/lottie/transportation/construction.lottie" + } else { + "file:///android_asset/lottie/objects/bomb.lottie" + } + } else { + "file:///android_asset/lottie/objects/siren.lottie" + } + + val textToSend = listOf(title, body).joinToString(DELIMITER) + + val githubLink = + "$ISSUE_TRACKER/new?title=${title.encodeEscaped()}&body=${body.encodeEscaped()}" +} + +private const val DELIMITER = "\n\n" \ No newline at end of file diff --git a/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashInfoCard.kt b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashInfoCard.kt new file mode 100644 index 0000000..aef075f --- /dev/null +++ b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashInfoCard.kt @@ -0,0 +1,73 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.BugReport +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) + ) + } + } + } + ) +} \ No newline at end of file diff --git a/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashRootContent.kt b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashRootContent.kt new file mode 100644 index 0000000..47975b2 --- /dev/null +++ b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashRootContent.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.crash.presentation.screenLogic.CrashComponent +import com.t8rin.imagetoolbox.core.resources.R +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 + ) { + when { + crashInfo.isOutOfMemory -> { + CrashAttentionCard( + title = stringResource(R.string.crash_oom_title), + description = stringResource(R.string.crash_oom_description), + emoji = crashInfo.emoji + ) + } + + crashInfo.isForegroundServiceDidNotStartInTime -> { + CrashAttentionCard( + title = stringResource(R.string.crash_foreground_service_timeout_title), + description = stringResource(R.string.crash_foreground_service_timeout_description), + emoji = crashInfo.emoji + ) + } + + else -> { + CrashAttentionCard( + title = stringResource(R.string.crash_title), + description = stringResource(R.string.crash_subtitle), + emoji = crashInfo.emoji + ) + Spacer(modifier = Modifier.height(24.dp)) + CrashActionButtons( + onCopyCrashInfo = copyCrashInfo, + onShareLogs = component::shareLogs, + isSendingLogs = component.isSendingLogs, + githubLink = crashInfo.githubLink + ) + } + } + Spacer(modifier = Modifier.height(24.dp)) + CrashInfoCard(crashInfo = crashInfo) + } + + CrashBottomButtons( + modifier = Modifier.align(Alignment.BottomCenter), + onCopy = copyCrashInfo.takeIf { !crashInfo.isNonReportable }, + onRestartApp = { + context.startActivity( + Intent(context, AppActivityClass) + ) + } + ) + } +} diff --git a/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/GlobalExceptionHandler.kt b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/GlobalExceptionHandler.kt new file mode 100644 index 0000000..6feb4d2 --- /dev/null +++ b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/GlobalExceptionHandler.kt @@ -0,0 +1,83 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.utils.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, +) \ No newline at end of file diff --git a/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/screenLogic/CrashComponent.kt b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/screenLogic/CrashComponent.kt new file mode 100644 index 0000000..8aa76c7 --- /dev/null +++ b/core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/screenLogic/CrashComponent.kt @@ -0,0 +1,85 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.domain.utils.runSuspendCatching +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.state.update +import com.t8rin.imagetoolbox.core.utils.makeLog +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 + + private val _isSendingLogs = mutableStateOf(false) + val isSendingLogs by _isSendingLogs + + init { + runBlocking { + _settingsState.value = settingsManager.getSettingsState() + } + settingsManager.settingsState.onEach { + _settingsState.value = it + }.launchIn(componentScope) + + crashInfo.makeLog("CrashComponent") + } + + fun shareLogs() { + componentScope.launch { + _isSendingLogs.update { true } + runSuspendCatching { + shareProvider.shareUri( + uri = settingsManager.createLogsExport(), + onComplete = {} + ) + } + _isSendingLogs.update { false } + } + } + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + crashInfo: CrashInfo + ): CrashComponent + } + +} \ No newline at end of file diff --git a/core/crash/src/market/java/com/t8rin/imagetoolbox/core/crash/data/AnalyticsManagerImpl.kt b/core/crash/src/market/java/com/t8rin/imagetoolbox/core/crash/data/AnalyticsManagerImpl.kt new file mode 100644 index 0000000..518cbc5 --- /dev/null +++ b/core/crash/src/market/java/com/t8rin/imagetoolbox/core/crash/data/AnalyticsManagerImpl.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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()) } + } + } + } + + override fun pushMetric( + tag: String, + metric: String + ) { + if (allowCollectAnalytics) { + analytics.logEvent(tag) { param(Param.CONTENT, metric) } + } + } + + 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 +} \ No newline at end of file diff --git a/core/data/.gitignore b/core/data/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/data/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts new file mode 100644 index 0000000..3a160d1 --- /dev/null +++ b/core/data/build.gradle.kts @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + implementation(libs.coilNetwork) + api(libs.ktor) + api(libs.ktor.logging) + implementation(libs.coilGif) + implementation(libs.coilSvg) + implementation(libs.coil.resvg) + implementation(libs.trickle) + + implementation(libs.androidx.compose.ui.graphics) + + api(libs.datastore.preferences.android) + api(libs.datastore.core.android) + + 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) + + 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) +} diff --git a/core/data/src/main/AndroidManifest.xml b/core/data/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1646c60 --- /dev/null +++ b/core/data/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/Base64Fetcher.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/Base64Fetcher.kt new file mode 100644 index 0000000..238dfd0 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/Base64Fetcher.kt @@ -0,0 +1,68 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/CoilLogger.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/CoilLogger.kt new file mode 100644 index 0000000..1a793dc --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/CoilLogger.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.utils.makeLog +import com.t8rin.imagetoolbox.core.utils.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 + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/HeifDecoder.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/HeifDecoder.kt new file mode 100644 index 0000000..d47e80e --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/HeifDecoder.kt @@ -0,0 +1,143 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.fetch.SourceFetchResult +import coil3.request.Options +import coil3.request.allowHardware +import coil3.request.allowRgb565 +import coil3.request.bitmapConfig +import coil3.size.Scale +import coil3.size.Size +import coil3.size.pxOrElse +import com.radzivon.bartoshyk.avif.coder.Coder +import com.radzivon.bartoshyk.avif.coder.PreferredColorConfig +import com.radzivon.bartoshyk.avif.coder.ScaleMode +import com.t8rin.imagetoolbox.core.utils.makeLog +import kotlinx.coroutines.runInterruptible +import okio.ByteString.Companion.encodeUtf8 + +class HeifDecoder( + private val source: SourceFetchResult, + private val options: Options, + private val exceptionLogger: ((Exception) -> Unit)? = null, +) : Decoder { + + private val coder = Coder() + + override suspend fun decode(): DecodeResult? = runInterruptible { + try { + // ColorSpace is preferred to be ignored due to lib is trying to handle all color profile by itself + val sourceData = source.source.source().readByteArray() + + var mPreferredColorConfig: PreferredColorConfig = when (options.bitmapConfig) { + Bitmap.Config.ALPHA_8 -> PreferredColorConfig.RGBA_8888 + Bitmap.Config.RGB_565 -> if (options.allowRgb565) PreferredColorConfig.RGB_565 else PreferredColorConfig.DEFAULT + Bitmap.Config.ARGB_8888 -> PreferredColorConfig.RGBA_8888 + else -> { + if (options.allowHardware) { + PreferredColorConfig.DEFAULT + } else { + PreferredColorConfig.RGBA_8888 + } + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && options.bitmapConfig == Bitmap.Config.RGBA_F16) { + mPreferredColorConfig = PreferredColorConfig.RGBA_F16 + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && options.bitmapConfig == Bitmap.Config.HARDWARE) { + mPreferredColorConfig = PreferredColorConfig.HARDWARE + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && options.bitmapConfig == Bitmap.Config.RGBA_1010102) { + mPreferredColorConfig = PreferredColorConfig.RGBA_1010102 + } + + if (options.size == Size.ORIGINAL) { + val originalImage = coder.decode( + byteArray = sourceData, + preferredColorConfig = mPreferredColorConfig + ) + + return@runInterruptible DecodeResult( + image = originalImage.asImage(), + isSampled = false + ) + } + + val dstWidth = options.size.width.pxOrElse { 0 } + val dstHeight = options.size.height.pxOrElse { 0 } + val scaleMode = when (options.scale) { + Scale.FILL -> ScaleMode.FILL + Scale.FIT -> ScaleMode.FIT + } + + val originalImage = coder.decodeSampled( + byteArray = sourceData, + scaledWidth = dstWidth, + scaledHeight = dstHeight, + preferredColorConfig = mPreferredColorConfig, + scaleMode = scaleMode, + ) + + return@runInterruptible DecodeResult( + image = originalImage.asImage(), + isSampled = true + ) + } catch (e: Exception) { + e.makeLog("HeifDecoder") + exceptionLogger?.invoke(e) + return@runInterruptible null + } + } + + class Factory : Decoder.Factory { + override fun create( + result: SourceFetchResult, + options: Options, + imageLoader: ImageLoader + ): Decoder? { + return if ( + AVAILABLE_BRANDS.any { + result.source.source().rangeEquals(4, it) + } + ) { + HeifDecoder( + source = result, + options = options + ) + } else null + } + + companion object { + private val MIF = "ftypmif1".encodeUtf8() + private val MSF = "ftypmsf1".encodeUtf8() + private val HEIC = "ftypheic".encodeUtf8() + private val HEIX = "ftypheix".encodeUtf8() + private val HEVC = "ftyphevc".encodeUtf8() + private val HEVX = "ftyphevx".encodeUtf8() + private val AVIF = "ftypavif".encodeUtf8() + private val AVIS = "ftypavis".encodeUtf8() + + private val AVAILABLE_BRANDS = listOf(MIF, MSF, HEIC, HEIX, HEVC, HEVX, AVIF, AVIS) + } + } +} diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/MemoryCache.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/MemoryCache.kt new file mode 100644 index 0000000..f3e5fe3 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/MemoryCache.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/NefDecoder.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/NefDecoder.kt new file mode 100644 index 0000000..f00314f --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/NefDecoder.kt @@ -0,0 +1,462 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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("MagicNumber", "ReturnCount") + +package com.t8rin.imagetoolbox.core.data.coil + +import android.graphics.Bitmap +import android.graphics.Matrix +import coil3.ImageLoader +import coil3.asImage +import coil3.decode.BitmapFactoryDecoder +import coil3.decode.DecodeResult +import coil3.decode.Decoder +import coil3.decode.ImageSource +import coil3.fetch.SourceFetchResult +import coil3.request.Options +import coil3.toBitmap +import okio.Buffer +import okio.BufferedSource +import okio.ByteString.Companion.toByteString +import java.io.File +import java.io.RandomAccessFile +import java.util.Locale + +internal class NefDecoder private constructor( + private val source: ImageSource, + private val options: Options +) : Decoder { + + override suspend fun decode(): DecodeResult? { + val preview = runCatching { + NefPreviewExtractor( + file = source.file().toFile() + ).extract() + }.getOrNull() ?: return null + + val result = BitmapFactoryDecoder( + source = ImageSource( + source = Buffer().write(preview.bytes), + fileSystem = options.fileSystem + ), + options = options + ).decode() + + return result.applyOrientation(preview.orientation) + } + + private fun DecodeResult.applyOrientation( + orientation: Int + ): DecodeResult { + if (orientation == ORIENTATION_NORMAL) return this + + val bitmap = image.toBitmap() + val matrix = Matrix().applyTiffOrientation(orientation) ?: return this + val rotated = Bitmap.createBitmap( + bitmap, + 0, + 0, + bitmap.width, + bitmap.height, + matrix, + true + ) + + if (rotated != bitmap) bitmap.recycle() + + return DecodeResult( + image = rotated.asImage(), + isSampled = isSampled + ) + } + + private fun Matrix.applyTiffOrientation( + orientation: Int + ): Matrix? { + val transformation = TIFF_ORIENTATION_TRANSFORMS[orientation] ?: return null + transformation(this) + return this + } + + class Factory : Decoder.Factory { + + override fun create( + result: SourceFetchResult, + options: Options, + imageLoader: ImageLoader + ): Decoder? { + return if (isTiff(result.source.source())) { + NefDecoder( + source = result.source, + options = options + ) + } else { + null + } + } + + private fun isTiff(source: BufferedSource): Boolean { + val littleEndianMagic = byteArrayOf(0x49, 0x49, 0x2a, 0x00) + val bigEndianMagic = byteArrayOf(0x4d, 0x4d, 0x00, 0x2a) + + return source.rangeEquals(0, littleEndianMagic.toByteString()) || + source.rangeEquals(0, bigEndianMagic.toByteString()) + } + } + + private class NefPreviewExtractor( + private val file: File + ) { + fun extract(): PreviewData? = RandomAccessFile(file, "r").use { raf -> + val reader = TiffReader.create(raf) ?: return null + val rootDirectory = reader.readDirectory(reader.firstDirectoryOffset) ?: return null + val make = rootDirectory.asciiValue(TAG_MAKE, reader) + ?.uppercase(Locale.ROOT) + .orEmpty() + + if (!make.contains("NIKON")) return null + + val subIfdOffsets = rootDirectory.longValues(TAG_SUB_IFD, reader) + if (subIfdOffsets.isEmpty()) return null + + val subIfds = subIfdOffsets.mapNotNull(reader::readDirectory) + if (!subIfds.any { it.isNikonRawImage(reader) }) return null + + val preview = subIfds + .mapNotNull { it.jpegPreview(reader) } + .filter(reader::isJpeg) + .maxByOrNull(JpegPreview::length) + ?: return null + + val bytes = reader.readBytes( + offset = preview.offset, + byteCount = preview.length + ) ?: return null + + PreviewData( + bytes = bytes, + orientation = rootDirectory.longValue(TAG_ORIENTATION, reader)?.toInt() + ?: ORIENTATION_NORMAL + ) + } + } + + private class TiffReader private constructor( + private val raf: RandomAccessFile, + private val byteOrder: ByteOrder, + val firstDirectoryOffset: Long + ) { + + fun readDirectory(offset: Long): TiffDirectory? { + if (offset <= 0 || offset >= raf.length()) return null + + raf.seek(offset) + val entryCount = readUShort() + if (entryCount > MAX_IFD_ENTRIES) return null + + val entries = buildMap { + repeat(entryCount) { + val bytes = ByteArray(IFD_ENTRY_SIZE) + raf.readFully(bytes) + + val entry = TiffEntry( + tag = readUShort(bytes, offset = 0), + type = readUShort(bytes, offset = 2), + count = readUInt(bytes, offset = 4), + valueOrOffset = readUInt(bytes, offset = 8), + inlineValue = bytes.copyOfRange(8, 12) + ) + put(entry.tag, entry) + } + } + + return TiffDirectory(entries) + } + + fun values(entry: TiffEntry): ByteArray? { + val typeSize = TYPE_SIZES[entry.type] ?: return null + val byteCount = entry.count * typeSize + if (byteCount <= 0 || byteCount > Int.MAX_VALUE) return null + + if (byteCount <= INLINE_VALUE_SIZE) { + return entry.inlineValue.copyOf(byteCount.toInt()) + } + + return readBytes( + offset = entry.valueOrOffset, + byteCount = byteCount + ) + } + + fun readBytes( + offset: Long, + byteCount: Long + ): ByteArray? { + if (offset < 0 || byteCount <= 0 || byteCount > Int.MAX_VALUE) return null + if (offset + byteCount > raf.length()) return null + + val bytes = ByteArray(byteCount.toInt()) + raf.seek(offset) + raf.readFully(bytes) + return bytes + } + + fun isJpeg(preview: JpegPreview): Boolean { + val bytes = readBytes( + offset = preview.offset, + byteCount = JPEG_SIGNATURE_SIZE + ) ?: return false + + return bytes[0] == JPEG_START_BYTE_0 && bytes[1] == JPEG_START_BYTE_1 + } + + fun readUShort(bytes: ByteArray, offset: Int): Int { + val first = bytes[offset].toInt() and 0xff + val second = bytes[offset + 1].toInt() and 0xff + + return when (byteOrder) { + ByteOrder.LittleEndian -> first or (second shl 8) + ByteOrder.BigEndian -> (first shl 8) or second + } + } + + fun readUInt(bytes: ByteArray, offset: Int): Long { + val first = bytes[offset].toLong() and 0xff + val second = bytes[offset + 1].toLong() and 0xff + val third = bytes[offset + 2].toLong() and 0xff + val fourth = bytes[offset + 3].toLong() and 0xff + + return when (byteOrder) { + ByteOrder.LittleEndian -> first or (second shl 8) or (third shl 16) or (fourth shl 24) + ByteOrder.BigEndian -> (first shl 24) or (second shl 16) or (third shl 8) or fourth + } + } + + private fun readUShort(): Int { + val bytes = ByteArray(2) + raf.readFully(bytes) + return readUShort(bytes, offset = 0) + } + + companion object { + + fun create(raf: RandomAccessFile): TiffReader? { + if (raf.length() < TIFF_HEADER_SIZE) return null + + raf.seek(0) + val header = ByteArray(TIFF_HEADER_SIZE) + raf.readFully(header) + + val byteOrder = when { + header[0] == LITTLE_ENDIAN_BYTE && header[1] == LITTLE_ENDIAN_BYTE -> { + ByteOrder.LittleEndian + } + + header[0] == BIG_ENDIAN_BYTE && header[1] == BIG_ENDIAN_BYTE -> { + ByteOrder.BigEndian + } + + else -> return null + } + + val reader = TiffReader( + raf = raf, + byteOrder = byteOrder, + firstDirectoryOffset = 0 + ) + val magic = reader.readUShort(header, offset = 2) + if (magic != TIFF_MAGIC) return null + + return TiffReader( + raf = raf, + byteOrder = byteOrder, + firstDirectoryOffset = reader.readUInt(header, offset = 4) + ) + } + } + } + + private data class TiffDirectory( + private val entries: Map + ) { + + fun longValue( + tag: Int, + reader: TiffReader + ): Long? = longValues(tag, reader).firstOrNull() + + fun longValues( + tag: Int, + reader: TiffReader + ): List { + val entry = entries[tag] ?: return emptyList() + val bytes = reader.values(entry) ?: return emptyList() + val count = entry.count.toIntOrNull() ?: return emptyList() + + return when (entry.type) { + TYPE_BYTE -> List(count) { index -> + bytes[index].toLong() and 0xff + } + + TYPE_SHORT -> List(count) { index -> + reader.readUShort(bytes, index * SHORT_SIZE).toLong() + } + + TYPE_LONG -> List(count) { index -> + reader.readUInt(bytes, index * LONG_SIZE) + } + + else -> emptyList() + } + } + + fun asciiValue( + tag: Int, + reader: TiffReader + ): String? { + val entry = entries[tag] ?: return null + if (entry.type != TYPE_ASCII) return null + + val bytes = reader.values(entry) ?: return null + return String(bytes, Charsets.US_ASCII).substringBefore(NULL_CHAR) + } + + fun isNikonRawImage(reader: TiffReader): Boolean { + val compression = longValue(TAG_COMPRESSION, reader) + val photometric = longValue(TAG_PHOTOMETRIC, reader) + val bitsPerSample = longValue(TAG_BITS_PER_SAMPLE, reader) + + return compression == COMPRESSION_NIKON_RAW || + (photometric == PHOTOMETRIC_CFA && bitsPerSample != null && bitsPerSample > 8) + } + + fun jpegPreview(reader: TiffReader): JpegPreview? { + val offset = longValue(TAG_JPEG_INTERCHANGE_FORMAT, reader) ?: return null + val length = longValue(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, reader) ?: return null + + if (length <= 0) return null + return JpegPreview( + offset = offset, + length = length + ) + } + } + + private class TiffEntry( + val tag: Int, + val type: Int, + val count: Long, + val valueOrOffset: Long, + val inlineValue: ByteArray + ) + + private data class JpegPreview( + val offset: Long, + val length: Long + ) + + private class PreviewData( + val bytes: ByteArray, + val orientation: Int + ) + + private enum class ByteOrder { + LittleEndian, + BigEndian + } + + private companion object { + private const val TIFF_HEADER_SIZE = 8 + private const val TIFF_MAGIC = 42 + private const val IFD_ENTRY_SIZE = 12 + private const val INLINE_VALUE_SIZE = 4 + private const val MAX_IFD_ENTRIES = 512 + private const val SHORT_SIZE = 2 + private const val LONG_SIZE = 4 + private const val JPEG_SIGNATURE_SIZE = 2L + + private const val LITTLE_ENDIAN_BYTE = 0x49.toByte() + private const val BIG_ENDIAN_BYTE = 0x4d.toByte() + private const val JPEG_START_BYTE_0 = 0xff.toByte() + private const val JPEG_START_BYTE_1 = 0xd8.toByte() + private const val NULL_CHAR = '\u0000' + + private const val TYPE_BYTE = 1 + private const val TYPE_ASCII = 2 + private const val TYPE_SHORT = 3 + private const val TYPE_LONG = 4 + + private const val TAG_SUB_IFD = 330 + private const val TAG_MAKE = 271 + private const val TAG_ORIENTATION = 274 + private const val TAG_BITS_PER_SAMPLE = 258 + private const val TAG_COMPRESSION = 259 + private const val TAG_PHOTOMETRIC = 262 + private const val TAG_JPEG_INTERCHANGE_FORMAT = 513 + private const val TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514 + + private const val COMPRESSION_NIKON_RAW = 34713L + private const val PHOTOMETRIC_CFA = 32803L + + private const val ORIENTATION_NORMAL = 1 + private const val ORIENTATION_FLIP_HORIZONTAL = 2 + private const val ORIENTATION_ROTATE_180 = 3 + private const val ORIENTATION_FLIP_VERTICAL = 4 + private const val ORIENTATION_TRANSPOSE = 5 + private const val ORIENTATION_ROTATE_90 = 6 + private const val ORIENTATION_TRANSVERSE = 7 + private const val ORIENTATION_ROTATE_270 = 8 + + private val TIFF_ORIENTATION_TRANSFORMS = mapOf Unit>( + ORIENTATION_FLIP_HORIZONTAL to { + postScale(-1f, 1f) + }, + ORIENTATION_ROTATE_180 to { + postRotate(180f) + }, + ORIENTATION_FLIP_VERTICAL to { + postScale(1f, -1f) + }, + ORIENTATION_TRANSPOSE to { + postRotate(90f) + postScale(1f, -1f) + }, + ORIENTATION_ROTATE_90 to { + postRotate(90f) + }, + ORIENTATION_TRANSVERSE to { + postRotate(90f) + postScale(-1f, 1f) + }, + ORIENTATION_ROTATE_270 to { + postRotate(270f) + } + ) + + private val TYPE_SIZES = mapOf( + TYPE_BYTE to 1L, + TYPE_ASCII to 1L, + TYPE_SHORT to SHORT_SIZE.toLong(), + TYPE_LONG to LONG_SIZE.toLong() + ) + + private fun Long.toIntOrNull(): Int? = takeIf { + it in 0..Int.MAX_VALUE.toLong() + }?.toInt() + } +} diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/OriginalUriMapper.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/OriginalUriMapper.kt new file mode 100644 index 0000000..7690634 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/OriginalUriMapper.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.map.Mapper +import coil3.request.Options +import coil3.toUri +import com.t8rin.imagetoolbox.core.utils.UriReplacements + +internal object OriginalUriMapper : Mapper { + override fun map( + data: Any, + options: Options + ): Any? = when (data) { + is String -> UriReplacements.resolve(data).takeIf { it != data } + is android.net.Uri -> UriReplacements.resolve(data).takeIf { it != data } + is coil3.Uri -> UriReplacements.resolve(data.toString()) + .takeIf { it != data.toString() } + ?.toUri() + + else -> null + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/PdfDecoder.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/PdfDecoder.kt new file mode 100644 index 0000000..9f8b82d --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/PdfDecoder.kt @@ -0,0 +1,152 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 android.graphics.Color +import android.graphics.Matrix +import android.graphics.pdf.PdfRenderer +import android.os.ParcelFileDescriptor +import androidx.core.graphics.createBitmap +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 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 = ParcelFileDescriptor.open( + file, + ParcelFileDescriptor.MODE_READ_ONLY + ).use { fileDescriptor -> + PdfRenderer(fileDescriptor).use { renderer -> + val pageIndex = options.pdfPage.coerceIn(0, renderer.pageCount - 1) + + renderer.openPage(pageIndex).use { page -> + val originalWidth = page.width + val originalHeight = page.height + + val targetSize = IntegerSize( + width = originalWidth, + height = originalHeight + ).flexibleResize( + w = options.size.width.pxOrElse { 0 }, + h = options.size.height.pxOrElse { 0 } + ) + + val scaleX = targetSize.width.toFloat() / originalWidth + val scaleY = targetSize.height.toFloat() / originalHeight + val scale = minOf(scaleX, scaleY).coerceAtMost(2f) + + val bitmap = createBitmap( + (originalWidth * scale).roundToInt().coerceAtLeast(1), + (originalHeight * scale).roundToInt().coerceAtLeast(1) + ).apply { + eraseColor(Color.WHITE) + } + + page.render( + bitmap, + null, + Matrix().apply { setScale(scale, scale) }, + PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY + ) + + 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, + size: Size? = null +): ImageRequest = ImageRequest.Builder(appContext) + .data(data) + .pdfPage(pdfPage) + .memoryCacheKey(data.toString() + pdfPage) + .diskCacheKey(data.toString() + pdfPage) + .apply { + size?.let(::size) + } + .build() + +fun ImageRequest.Builder.pdfPage(pdfPage: Int) = apply { + extras[pdfPageKey] = pdfPage +} + +val ImageRequest.pdfPage: Int + get() = getExtra(pdfPageKey) + +val Options.pdfPage: Int + get() = getExtra(pdfPageKey) + +val Extras.Key.Companion.pdfPage: Extras.Key + get() = pdfPageKey + +private val pdfPageKey = Extras.Key(default = 0) \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/SvgDecoderCompat.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/SvgDecoderCompat.kt new file mode 100644 index 0000000..3d0a2d8 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/SvgDecoderCompat.kt @@ -0,0 +1,114 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +import com.hashsequence.coilresvg.ResvgDecoder +import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching +import com.t8rin.imagetoolbox.core.utils.makeLog + +internal class SvgDecoderCompat( + private val source: ImageSource, + options: Options, + minimumSize: Int? = null +) : Decoder { + + private val options = minimumSize?.let { + options.copy( + size = options.size.coerceAtLeast(minimumSize) + ) + } ?: options + + private fun source() = ImageSource( + file = source.file(), + fileSystem = source.fileSystem, + metadata = source.metadata + ) + + override suspend fun decode(): DecodeResult { + if (!isResvgAvailable) return decodeDefault() + + return runSuspendCatching { + decodeNative() + }.onFailure { + if (it is LinkageError) isResvgAvailable = false + }.getOrNull() ?: decodeDefault() + } + + private suspend fun decodeNative() = ResvgDecoder( + source = source(), + options = options + ).decode() + + private suspend fun decodeDefault() = SvgDecoder( + source = source(), + options = options + ).decode().also { + "fallback coil-svg decoder".makeLog() + } + + private fun Size.coerceAtLeast(size: Int): Size = Size( + width = width.pxOrElse { 0 }.coerceAtLeast(size), + height = height.pxOrElse { 0 }.coerceAtLeast(size) + ) + + class Factory( + private val minimumSize: Int? = null + ) : Decoder.Factory { + + override fun create( + result: SourceFetchResult, + options: Options, + imageLoader: ImageLoader + ): Decoder? { + if (!isApplicable(result)) return null + + return SvgDecoderCompat( + source = result.source, + options = options, + minimumSize = minimumSize + ) + } + + private fun isApplicable(result: SourceFetchResult): Boolean { + return result.mimeType == MIME_TYPE_SVG || + result.mimeType == MIME_TYPE_XML || + DecodeUtils.isSvg(result.source.source()) + } + + private companion object { + private const val MIME_TYPE_SVG = "image/svg+xml" + private const val MIME_TYPE_XML = "text/xml" + } + } + + private companion object { + @Volatile + private var isResvgAvailable = true + } +} diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/TiffDecoder.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/TiffDecoder.kt new file mode 100644 index 0000000..7c8eeda --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/TiffDecoder.kt @@ -0,0 +1,105 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 = runCatching { + TiffBitmapFactory.decodeFile( + source.file().toFile() + ) + }.getOrNull() ?: 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()) + } + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/TimeMeasureInterceptor.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/TimeMeasureInterceptor.kt new file mode 100644 index 0000000..29e510d --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/TimeMeasureInterceptor.kt @@ -0,0 +1,49 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.utils.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 + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/VVCDecoder.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/VVCDecoder.kt new file mode 100644 index 0000000..6d212a4 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/VVCDecoder.kt @@ -0,0 +1,103 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.asImage +import coil3.decode.DecodeResult +import coil3.decode.Decoder +import coil3.fetch.SourceFetchResult +import coil3.request.Options +import coil3.size.Scale +import coil3.size.Size +import coil3.size.pxOrElse +import com.t8rin.trickle.VvcDecoder +import com.t8rin.trickle.VvcScaleMode +import kotlinx.coroutines.runInterruptible +import okio.ByteString.Companion.encodeUtf8 +import okio.ByteString.Companion.toByteString + +class VVCDecoder( + private val source: SourceFetchResult, + private val options: Options, + private val exceptionLogger: ((Exception) -> Unit)? = null +) : Decoder { + + override suspend fun decode(): DecodeResult? = runInterruptible { + try { + val sourceData = source.source.source().readByteArray() + + if (options.size == Size.ORIGINAL) { + return@runInterruptible DecodeResult( + image = VvcDecoder.decode(sourceData).asImage(), + isSampled = false + ) + } + + val width = options.size.width.pxOrElse { 0 } + val height = options.size.height.pxOrElse { 0 } + val scaleMode = when (options.scale) { + Scale.FILL -> VvcScaleMode.FILL + Scale.FIT -> VvcScaleMode.FIT + } + + DecodeResult( + image = VvcDecoder.decodeSampled( + encoded = sourceData, + scaledWidth = width, + scaledHeight = height, + scaleMode = scaleMode + ).asImage(), + isSampled = true + ) + } catch (e: Exception) { + exceptionLogger?.invoke(e) + null + } + } + + class Factory : Decoder.Factory { + override fun create( + result: SourceFetchResult, + options: Options, + imageLoader: ImageLoader + ): Decoder? { + val source = result.source.source() + val isRawVvc = RAW_VVC_START_CODES.any { source.rangeEquals(0, it) } + val isVvcInHeif = source.rangeEquals(4, FTYP) && + source.indexOf(VVC_ITEM_TYPE, 0, SEARCH_LIMIT) >= 0 + + return if (isRawVvc || isVvcInHeif) { + VVCDecoder( + source = result, + options = options + ) + } else null + } + + private companion object { + const val SEARCH_LIMIT = 64L * 1024L + val FTYP = "ftyp".encodeUtf8() + val VVC_ITEM_TYPE = "vvc1".encodeUtf8() + val RAW_VVC_START_CODES = listOf( + byteArrayOf(0, 0, 1).toByteString(), + byteArrayOf(0, 0, 0, 1).toByteString() + ) + } + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coroutines/AndroidDispatchersHolder.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coroutines/AndroidDispatchersHolder.kt new file mode 100644 index 0000000..33d2cae --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coroutines/AndroidDispatchersHolder.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coroutines/AppScopeImpl.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coroutines/AppScopeImpl.kt new file mode 100644 index 0000000..393696d --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coroutines/AppScopeImpl.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.utils.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() +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/CoroutinesModule.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/CoroutinesModule.kt new file mode 100644 index 0000000..19f8805 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/CoroutinesModule.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/ImageLoaderModule.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/ImageLoaderModule.kt new file mode 100644 index 0000000..35d8d8b --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/ImageLoaderModule.kt @@ -0,0 +1,138 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.util.Logger +import com.awxkee.jxlcoder.coil.AnimatedJxlDecoder +import com.gemalto.jp2.coil.Jpeg2000Decoder +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.HeifDecoder +import com.t8rin.imagetoolbox.core.data.coil.NefDecoder +import com.t8rin.imagetoolbox.core.data.coil.OriginalUriMapper +import com.t8rin.imagetoolbox.core.data.coil.PdfDecoder +import com.t8rin.imagetoolbox.core.data.coil.SvgDecoderCompat +import com.t8rin.imagetoolbox.core.data.coil.TiffDecoder +import com.t8rin.imagetoolbox.core.data.coil.TimeMeasureInterceptor +import com.t8rin.imagetoolbox.core.data.coil.VVCDecoder +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(OriginalUriMapper) + 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(SvgDecoderCompat.Factory()) + add(VVCDecoder.Factory()) + add(HeifDecoder.Factory()) + add(AnimatedJxlDecoder.Factory()) + add(Jpeg2000Decoder.Factory()) + add(NefDecoder.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() + +} diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/ImageModule.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/ImageModule.kt new file mode 100644 index 0000000..110f3e4 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/ImageModule.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.data.image.ImageExportProfilesUseCaseImpl +import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor +import com.t8rin.imagetoolbox.core.domain.image.ImageExportProfilesUseCase +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 + + @Singleton + @Binds + fun provideImageExportProfilesUseCase( + useCase: ImageExportProfilesUseCaseImpl + ): ImageExportProfilesUseCase + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/JsonModule.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/JsonModule.kt new file mode 100644 index 0000000..29fd514 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/JsonModule.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.ImageFormatJsonAdapter +import com.t8rin.imagetoolbox.core.data.json.ImageScaleModeJsonAdapter +import com.t8rin.imagetoolbox.core.data.json.MoshiParser +import com.t8rin.imagetoolbox.core.data.json.PresetJsonAdapter +import com.t8rin.imagetoolbox.core.data.json.ResizeTypeJsonAdapter +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.Heic::class.java, "heic") + .withSubtype(Quality.Vvc::class.java, "vvc") + .withSubtype(Quality.PngLossy::class.java, "png") + .withSubtype(Quality.PngQuant::class.java, "pngquant") + .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") + .withSubtype(ShapeType.Wavy::class.java, "wavy") + .withSubtype(ShapeType.Scoop::class.java, "scoop") + .withSubtype(ShapeType.Notch::class.java, "notch") + .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()) + ) + .add(ImageFormatJsonAdapter()) + .add(PresetJsonAdapter()) + .add(ResizeTypeJsonAdapter()) + .add(ImageScaleModeJsonAdapter()) + .addLast(KotlinJsonAdapterFactory()) + .build() + + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/LocalModule.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/LocalModule.kt new file mode 100644 index 0000000..e54016a --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/LocalModule.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) } + ) + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/RemoteModule.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/RemoteModule.kt new file mode 100644 index 0000000..46ef539 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/RemoteModule.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.utils.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 + } + } + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/ResourcesModule.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/ResourcesModule.kt new file mode 100644 index 0000000..8171dc9 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/ResourcesModule.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/SavingModule.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/SavingModule.kt new file mode 100644 index 0000000..5e37786 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/SavingModule.kt @@ -0,0 +1,80 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.history.AppHistoryRepositoryImpl +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.data.saving.FileControllerEventEmitter +import com.t8rin.imagetoolbox.core.data.saving.OriginalFileDeletionHelper +import com.t8rin.imagetoolbox.core.domain.history.AppHistoryRepository +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 + + @Binds + fun provideFileControllerEventEmitter( + impl: OriginalFileDeletionHelper + ): FileControllerEventEmitter + + @Singleton + @Binds + fun filenameCreator( + impl: AndroidFilenameCreator + ): FilenameCreator + + @Singleton + @Binds + fun service( + impl: AndroidKeepAliveService + ): KeepAliveService + + @Singleton + @Binds + fun history( + impl: AppHistoryRepositoryImpl + ): AppHistoryRepository + + companion object { + @Singleton + @Provides + fun provideMetadata( + impl: AndroidFileController + ): MetadataProvider = impl.toMetadataProvider() + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/history/AppHistoryRepositoryImpl.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/history/AppHistoryRepositoryImpl.kt new file mode 100644 index 0000000..f31adb3 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/history/AppHistoryRepositoryImpl.kt @@ -0,0 +1,211 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.history + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.core.longPreferencesKey +import androidx.datastore.preferences.core.stringPreferencesKey +import com.t8rin.imagetoolbox.core.domain.history.AppHistoryRepository +import com.t8rin.imagetoolbox.core.domain.history.model.AppUsageStatistics +import com.t8rin.imagetoolbox.core.domain.history.model.LastUsedTool +import com.t8rin.imagetoolbox.core.domain.json.JsonParser +import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching +import com.t8rin.imagetoolbox.core.settings.domain.SettingsProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import java.time.LocalDate +import javax.inject.Inject + +internal class AppHistoryRepositoryImpl @Inject constructor( + private val dataStore: DataStore, + private val jsonParser: JsonParser, + private val settingsProvider: SettingsProvider +) : AppHistoryRepository { + + private val rawLastUsedTools: Flow> + get() = dataStore.data.map { preferences -> + preferences[LAST_USED_TOOLS]?.let { lastTools -> + jsonParser.fromJson( + json = lastTools, + type = LastUsedTools::class.java + )?.tools + }.orEmpty() + } + + override fun lastUsedTools( + maxCount: Int + ): Flow> = settingsProvider.settingsState.flatMapLatest { settings -> + if (settings.showToolsHistory) { + rawLastUsedTools.map { rawList -> + rawList.sortedByDescending { it.openCount }.let { + if (maxCount < Int.MAX_VALUE) { + val last = rawList.lastOrNull() + val limited = it.take(maxCount) + + if (last != null) { + listOf(last) + limited.minus(last) + } else { + limited + } + } else { + it + } + } + } + } else { + flowOf(emptyList()) + } + } + + override fun toolUsageStatistics(): Flow> = rawLastUsedTools.map { rawList -> + rawList.sortedWith( + compareByDescending { it.openCount } + .thenByDescending { it.lastOpenedTimestamp } + ) + } + + override fun successfulSavesCount(): Flow = dataStore.data.map { preferences -> + preferences[SUCCESSFUL_SAVES_COUNT] ?: 0 + } + + override fun appUsageStatistics(): Flow = + dataStore.data.map { preferences -> + AppUsageStatistics( + successfulSavesCount = preferences[SUCCESSFUL_SAVES_COUNT] ?: 0, + savedBytes = preferences[SAVED_BYTES] ?: 0, + lastActivityDayEpoch = preferences[LAST_ACTIVITY_DAY_EPOCH] ?: 0, + currentActivityStreak = preferences[CURRENT_ACTIVITY_STREAK] ?: 0, + savedFormatCounts = preferences[SAVED_FORMAT_COUNTERS]?.let { counters -> + jsonParser.fromJson( + json = counters, + type = SavedFormatCounters::class.java + )?.counters + }.orEmpty() + ) + } + + override suspend fun pushLastTool(screenId: Int) { + dataStore.edit { preferences -> + val todayEpochDay = LocalDate.now().toEpochDay() + val lastActivityDayEpoch = preferences[LAST_ACTIVITY_DAY_EPOCH] + + if (lastActivityDayEpoch != todayEpochDay) { + preferences[CURRENT_ACTIVITY_STREAK] = + if (lastActivityDayEpoch == todayEpochDay - 1) { + (preferences[CURRENT_ACTIVITY_STREAK] ?: 0) + 1 + } else { + 1 + } + preferences[LAST_ACTIVITY_DAY_EPOCH] = todayEpochDay + } + + val current = preferences[LAST_USED_TOOLS]?.let { + jsonParser.fromJson( + json = it, + type = LastUsedTools::class.java + ) + } ?: LastUsedTools(emptyList()) + + val screenEntry = current.tools.find { + it.screenId == screenId + } ?: LastUsedTool( + screenId = screenId, + openCount = 0, + lastOpenedTimestamp = System.currentTimeMillis() + ) + + val newValue = current.copy( + tools = current.tools + .minus(screenEntry) + .plus( + screenEntry.copy( + openCount = screenEntry.openCount + 1, + lastOpenedTimestamp = System.currentTimeMillis() + ) + ) + ) + + preferences[LAST_USED_TOOLS] = jsonParser.toJson( + obj = newValue, + type = LastUsedTools::class.java + ) ?: preferences[LAST_USED_TOOLS].orEmpty() + } + } + + override suspend fun registerSuccessfulSave( + savedBytes: Long, + savedFormat: String + ) { + dataStore.edit { preferences -> + preferences[SUCCESSFUL_SAVES_COUNT] = (preferences[SUCCESSFUL_SAVES_COUNT] ?: 0) + 1 + preferences[SAVED_BYTES] = (preferences[SAVED_BYTES] ?: 0) + savedBytes.coerceAtLeast(0) + + val format = savedFormat.lowercase().trim() + if (format.isNotEmpty()) { + val current = preferences[SAVED_FORMAT_COUNTERS]?.let { + runSuspendCatching { + jsonParser.fromJson( + json = it, + type = SavedFormatCounters::class.java + ) + }.getOrNull() + } ?: SavedFormatCounters(emptyMap()) + + preferences[SAVED_FORMAT_COUNTERS] = jsonParser.toJson( + obj = current.copy( + counters = current.counters + (format to ((current.counters[format] + ?: 0) + 1)) + ), + type = SavedFormatCounters::class.java + ) ?: preferences[SAVED_FORMAT_COUNTERS].orEmpty() + } + } + } + + override suspend fun resetUsageStatistics() { + dataStore.edit { preferences -> + preferences.remove(LAST_USED_TOOLS) + preferences.remove(SUCCESSFUL_SAVES_COUNT) + preferences.remove(SAVED_BYTES) + preferences.remove(LAST_ACTIVITY_DAY_EPOCH) + preferences.remove(CURRENT_ACTIVITY_STREAK) + preferences.remove(SAVED_FORMAT_COUNTERS) + } + } + +} + +private data class LastUsedTools( + val tools: List +) + +private data class SavedFormatCounters( + val counters: Map +) + +private val LAST_USED_TOOLS = stringPreferencesKey("LAST_USED_TOOLS") +private val SUCCESSFUL_SAVES_COUNT = intPreferencesKey("SUCCESSFUL_SAVES_COUNT") +private val SAVED_BYTES = longPreferencesKey("SAVED_BYTES") +private val LAST_ACTIVITY_DAY_EPOCH = longPreferencesKey("LAST_ACTIVITY_DAY_EPOCH") +private val CURRENT_ACTIVITY_STREAK = intPreferencesKey("CURRENT_ACTIVITY_STREAK") +private val SAVED_FORMAT_COUNTERS = stringPreferencesKey("SAVED_FORMAT_COUNTERS") \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageCompressor.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageCompressor.kt new file mode 100644 index 0000000..d327230 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageCompressor.kt @@ -0,0 +1,174 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 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.ensureActive +import kotlinx.coroutines.withContext +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, + private val settingsProvider: SettingsProvider, + dispatchersHolder: DispatchersHolder +) : DispatchersHolder by dispatchersHolder, ImageCompressor { + + private val settingsState get() = settingsProvider.settingsState.value + + override suspend fun compress( + image: Bitmap, + imageFormat: ImageFormat, + quality: Quality + ): ByteArray = withContext(encodingDispatcher) { + val coercedQuality = quality.coerceIn(imageFormat) + val transformedImage = image.toSoftware().let { software -> + val enableForAlpha = settingsState.enableBackgroundColorForAlphaFormats + val isNonAlpha = imageFormat !in ImageFormat.alphaContainedEntries + + if (isNonAlpha || coercedQuality.isNonAlpha() || enableForAlpha) { + withContext(defaultDispatcher) { + Trickle.drawColorBehind( + color = settingsState.backgroundForNoAlphaImageFormats.colorInt, + input = software + ) + } + } else software + } + + ImageCompressorBackend.Factory() + .create( + imageFormat = imageFormat, + context = context, + imageScaler = imageScaler + ) + .compress( + image = transformedImage, + quality = coercedQuality + ) + } + + + override suspend fun compressAndTransform( + image: Bitmap, + imageInfo: ImageInfo, + onImageReadyToCompressInterceptor: suspend (Bitmap) -> Bitmap, + applyImageTransformations: Boolean + ): ByteArray = withContext(encodingDispatcher) { + val currentImage = if (applyImageTransformations) { + val size = imageInfo.originalUri?.let { + imageGetter.getImage( + data = it, + originalSize = true + )?.run { width sizeTo height } + } + ensureActive() + 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 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 + + ensureActive() + 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() + } + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageGetter.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageGetter.kt new file mode 100644 index 0000000..2e60409 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageGetter.kt @@ -0,0 +1,263 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.saving.FailureNotifier +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.imagetoolbox.core.utils.makeLog +import dagger.Lazy +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.ensureActive +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, + private val failureNotifier: FailureNotifier, + 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 ?: failureNotifier::send + )?.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() + + ensureActive() + + runSuspendCatching { + imageLoader.execute(request).image?.toBitmap() + }.onFailure { + it.makeLog("ImageGetter") + onFailure(it) + }.getOrNull() + } + +} diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImagePreviewCreator.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImagePreviewCreator.kt new file mode 100644 index 0000000..432bce2 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImagePreviewCreator.kt @@ -0,0 +1,135 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.ensureActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +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) + ensureActive() + onGetByteCount( + imageCompressor.calculateImageSize( + image = image, + imageInfo = imageInfo + ).toInt() + ) + } + + if (!settingsState.generatePreviews) return@withContext null + + if (imageInfo.height == 0 || imageInfo.width == 0) return@withContext image + + ensureActive() + val shouldTransform = transformations.isNotEmpty() + || (imageInfo.width != image.width) + || (imageInfo.height != image.height) + || !imageInfo.quality.isDefault() + || (imageInfo.rotationDegrees != 0f) + || imageInfo.isFlipped + + val targetImage = 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 + } + ensureActive() + 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 = { + ensureActive() + imageTransformer.transform( + image = it, + transformations = transformations + ) ?: it + } + ) + + ensureActive() + imageGetter.getImage(bytes) ?: image + } else { + image + } + + ensureActive() + 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 \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageScaler.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageScaler.kt new file mode 100644 index 0000000..547075e --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageScaler.kt @@ -0,0 +1,497 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.utils.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 + } + } + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageTransformer.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageTransformer.kt new file mode 100644 index 0000000..459dc04 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageTransformer.kt @@ -0,0 +1,189 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.utils.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) + } + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidMetadata.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidMetadata.kt new file mode 100644 index 0000000..5d1dc66 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidMetadata.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidShareProvider.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidShareProvider.kt new file mode 100644 index 0000000..a54e607 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidShareProvider.kt @@ -0,0 +1,321 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.history.AppHistoryRepository +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.imagetoolbox.core.utils.fileSize +import com.t8rin.imagetoolbox.core.utils.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, + private val appHistoryRepository: AppHistoryRepository, + 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().also { uri -> + if (filename.startsWith(PDF)) { + appHistoryRepository.registerSuccessfulSave( + savedBytes = uri.toUri().fileSize() ?: 0, + savedFormat = "PDF" + ) + } + } + } + + 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() + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/ImageExportProfilesUseCaseImpl.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/ImageExportProfilesUseCaseImpl.kt new file mode 100644 index 0000000..1c1ea88 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/ImageExportProfilesUseCaseImpl.kt @@ -0,0 +1,155 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.MutablePreferences +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import com.t8rin.imagetoolbox.core.domain.image.ImageExportProfilesUseCase +import com.t8rin.imagetoolbox.core.domain.image.ShareProvider +import com.t8rin.imagetoolbox.core.domain.image.model.ImageExportProfile +import com.t8rin.imagetoolbox.core.domain.image.model.ImageExportProfiles +import com.t8rin.imagetoolbox.core.domain.json.JsonParser +import com.t8rin.imagetoolbox.core.domain.saving.FileController +import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +internal class ImageExportProfilesUseCaseImpl @Inject constructor( + private val dataStore: DataStore, + private val fileController: FileController, + private val jsonParser: JsonParser, + private val shareProvider: ShareProvider +) : ImageExportProfilesUseCase { + + override val profiles: Flow> = dataStore.data.map { preferences -> + preferences.readPresets().presets.asReversed() + } + + override suspend fun upsert(profile: ImageExportProfile) { + val normalized = profile.copy(name = profile.name.trim()) + if (normalized.name.isBlank()) return + + dataStore.edit { preferences -> + val list = preferences.readPresets().presets.toMutableList() + val index = list.indexOfFirst { + it.name.equals(normalized.name, ignoreCase = true) + } + if (index >= 0) { + list[index] = normalized.copy(name = list[index].name) + } else { + list.add(normalized) + } + preferences.writePresets(ImageExportProfiles(list)) + } + } + + override suspend fun delete(profile: ImageExportProfile) { + dataStore.edit { preferences -> + val current = preferences.readPresets() + + preferences.writePresets( + current.copy( + presets = current.presets.filterNot { + it.name.equals(profile.name, ignoreCase = true) + } + ) + ) + } + } + + override suspend fun export( + profile: ImageExportProfile, + uri: String + ) { + profile.toJson()?.let { json -> + fileController.writeBytes(uri) { + it.writeBytes(json.encodeToByteArray()) + } + } + } + + override suspend fun share(profile: ImageExportProfile) { + profile.toJson()?.let { json -> + shareProvider.shareData( + filename = profile.fileName(), + writeData = { + it.writeBytes(json.encodeToByteArray()) + } + ) + } + } + + override suspend fun importProfile(uri: String) { + runSuspendCatching { + fileController.readBytes(uri).decodeToString() + }.mapCatching { json -> + jsonParser.fromJson( + json = json, + type = ImageExportProfile::class.java + ) + }.getOrNull()?.let { preset -> + upsert(preset) + } + } + + private fun Preferences.readPresets(): ImageExportProfiles { + val json = this[PresetsKey] + + return json?.let { + jsonParser.fromJson( + json = it, + type = ImageExportProfiles::class.java + ) + } ?: ImageExportProfiles() + } + + private fun MutablePreferences.writePresets( + presets: ImageExportProfiles + ) { + if (presets.presets.isEmpty()) { + remove(PresetsKey) + return + } + + jsonParser.toJson( + obj = presets, + type = ImageExportProfiles::class.java + )?.let { json -> + this[PresetsKey] = json + } + } + + private fun ImageExportProfile.toJson(): String? = jsonParser.toJson( + obj = this, + type = ImageExportProfile::class.java + ) + + private fun ImageExportProfile.fileName(): String = "${name.safePresetFileName()}.itpreset" + + private fun String.safePresetFileName(): String = trim() + .replace(Regex("""[^\w.-]+"""), "_") + .trim('_') + .ifBlank { "preset" } + +} + +private val PresetsKey = stringPreferencesKey("PRESETS_KEY") diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/BlendingModeExt.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/BlendingModeExt.kt new file mode 100644 index 0000000..bebe212 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/BlendingModeExt.kt @@ -0,0 +1,99 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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()) + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/CanvasUtils.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/CanvasUtils.kt new file mode 100644 index 0000000..0b9ebd2 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/CanvasUtils.kt @@ -0,0 +1,95 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + } + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/ColorUtils.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/ColorUtils.kt new file mode 100644 index 0000000..e1240f4 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/ColorUtils.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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() + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/ImageCompressorBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/ImageCompressorBackend.kt new file mode 100644 index 0000000..de08ac9 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/ImageCompressorBackend.kt @@ -0,0 +1,106 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.PngImageQuantBackend +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.VvcBackend +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.Png.ImageQuant -> PngImageQuantBackend + + 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.Heic.HeifLossless, + ImageFormat.Heic.Lossy, + ImageFormat.Heic.HeifLossy -> HeicBackend(isLossless = imageFormat.isLossless) + + ImageFormat.Heic.VvcLossless, + ImageFormat.Heic.VvcLossy -> VvcBackend(isLossless = imageFormat.isLossless) + + ImageFormat.Avif.LosslessAv1, + ImageFormat.Avif.LossyAv1, + ImageFormat.Avif.LosslessAv2, + ImageFormat.Avif.LossyAv2 -> AvifBackend( + isLossless = imageFormat.isLossless, + isAv1 = imageFormat == ImageFormat.Avif.LosslessAv1 || + imageFormat == ImageFormat.Avif.LossyAv1 + ) + + 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) + } + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/StaticOptions.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/StaticOptions.kt new file mode 100644 index 0000000..f46aa22 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/StaticOptions.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.t8rin.imagetoolbox.core.data.coil.SvgDecoderCompat + +fun ImageRequest.Builder.static() = repeatCount(0) + .enableJxlAnimation(false) + .decoderFactory(SvgDecoderCompat.Factory(minimumSize = 2048)) \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/AvifBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/AvifBackend.kt new file mode 100644 index 0000000..e111722 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/AvifBackend.kt @@ -0,0 +1,70 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.AvKind +import com.radzivon.bartoshyk.avif.coder.AvSpeed +import com.radzivon.bartoshyk.avif.coder.Coder +import com.radzivon.bartoshyk.avif.coder.PreciseMode +import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend +import com.t8rin.imagetoolbox.core.domain.image.model.AvifChromaSubsampling +import com.t8rin.imagetoolbox.core.domain.image.model.Quality +import com.radzivon.bartoshyk.avif.coder.AvifChromaSubsampling as BackendChromaSubsampling + +internal data class AvifBackend( + private val isLossless: Boolean, + private val isAv1: Boolean +) : ImageCompressorBackend { + + override suspend fun compress( + image: Bitmap, + quality: Quality + ): ByteArray { + val avifQuality = quality as? Quality.Avif ?: Quality.Avif() + + return Coder().encodeAvif( + bitmap = image, + quality = avifQuality.qualityValue, + preciseMode = if (isLossless) { + PreciseMode.LOSSLESS + } else { + PreciseMode.LOSSY + }, + avifChromaSubsampling = avifQuality.chromaSubsampling.toBackend(), + avKind = if (isAv1) { + AvKind.AV1 + } else { + AvKind.AV2 + }, + speed = AvSpeed.entries.firstOrNull { + it.ordinal == (3 - avifQuality.effort) + } ?: AvSpeed.FAST + ) + } + +} + +private fun AvifChromaSubsampling.toBackend(): BackendChromaSubsampling = when (this) { + AvifChromaSubsampling.Auto -> BackendChromaSubsampling.AUTO + AvifChromaSubsampling.Yuv420 -> BackendChromaSubsampling.YUV420 + AvifChromaSubsampling.Yuv422 -> BackendChromaSubsampling.YUV422 + AvifChromaSubsampling.Yuv444 -> BackendChromaSubsampling.YUV444 + AvifChromaSubsampling.Yuv400 -> BackendChromaSubsampling.YUV400 + AvifChromaSubsampling.Lossless -> BackendChromaSubsampling.LOSELESS +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/BmpBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/BmpBackend.kt new file mode 100644 index 0000000..c21292e --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/BmpBackend.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/HeicBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/HeicBackend.kt new file mode 100644 index 0000000..d476683 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/HeicBackend.kt @@ -0,0 +1,59 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.Coder +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.HeicChromaSubsampling +import com.t8rin.imagetoolbox.core.domain.image.model.Quality +import com.radzivon.bartoshyk.avif.coder.HeicChromaSubsampling as BackendChromaSubsampling + +internal data class HeicBackend( + private val isLossless: Boolean +) : ImageCompressorBackend { + + override suspend fun compress( + image: Bitmap, + quality: Quality + ): ByteArray { + val heicQuality = quality as? Quality.Heic ?: Quality.Heic( + qualityValue = quality.qualityValue + ) + + return Coder().encodeHeic( + bitmap = image, + quality = HeifQualityArg.Quality(heicQuality.qualityValue), + preciseMode = if (isLossless) { + PreciseMode.LOSSLESS + } else { + PreciseMode.LOSSY + }, + chromaSubsampling = heicQuality.chromaSubsampling.toBackend() + ) + } + +} + +private fun HeicChromaSubsampling.toBackend(): BackendChromaSubsampling = when (this) { + HeicChromaSubsampling.Yuv420 -> BackendChromaSubsampling.YUV420 + HeicChromaSubsampling.Yuv422 -> BackendChromaSubsampling.YUV422 + HeicChromaSubsampling.Yuv444 -> BackendChromaSubsampling.YUV444 +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/IcoBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/IcoBackend.kt new file mode 100644 index 0000000..945b810 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/IcoBackend.kt @@ -0,0 +1,109 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/Jpeg2000Backend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/Jpeg2000Backend.kt new file mode 100644 index 0000000..4656e28 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/Jpeg2000Backend.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/JpegliBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/JpegliBackend.kt new file mode 100644 index 0000000..3ce7ee0 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/JpegliBackend.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/JpgBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/JpgBackend.kt new file mode 100644 index 0000000..ccb626f --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/JpgBackend.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/JxlBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/JxlBackend.kt new file mode 100644 index 0000000..e69c24b --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/JxlBackend.kt @@ -0,0 +1,59 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 } + ) + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/MozJpegBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/MozJpegBackend.kt new file mode 100644 index 0000000..fca40b7 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/MozJpegBackend.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/OxiPngBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/OxiPngBackend.kt new file mode 100644 index 0000000..716473c --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/OxiPngBackend.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + ) + ) + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/PngImageQuantBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/PngImageQuantBackend.kt new file mode 100644 index 0000000..d1b1642 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/PngImageQuantBackend.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.ImageQuant + +internal data object PngImageQuantBackend : ImageCompressorBackend { + + override suspend fun compress( + image: Bitmap, + quality: Quality + ): ByteArray { + val pngQuantQuality = quality as? Quality.PngQuant ?: Quality.PngQuant() + return ImageQuant.compress( + bitmap = image, + options = ImageQuant.Options( + quality = pngQuantQuality.quality, + speed = pngQuantQuality.speed, + maxColors = pngQuantQuality.maxColors + ) + ) + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/PngLosslessBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/PngLosslessBackend.kt new file mode 100644 index 0000000..059e9ad --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/PngLosslessBackend.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/PngLossyBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/PngLossyBackend.kt new file mode 100644 index 0000000..f16a389 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/PngLossyBackend.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + ) + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/QoiBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/QoiBackend.kt new file mode 100644 index 0000000..83efc76 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/QoiBackend.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/StaticGifBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/StaticGifBackend.kt new file mode 100644 index 0000000..c108579 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/StaticGifBackend.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + .setSize( + width = image.width, + height = image.height + ) + .setRepeat(1) + .setQuality(quality.qualityValue) + .setTransparent(Color.Transparent.toArgb()).apply { + start(out) + addFrame(image) + finish() + } + + out.toByteArray() + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/TiffBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/TiffBackend.kt new file mode 100644 index 0000000..af4753a --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/TiffBackend.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + } + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/VvcBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/VvcBackend.kt new file mode 100644 index 0000000..9aa8ac6 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/VvcBackend.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.domain.image.model.VvcBitDepth +import com.t8rin.imagetoolbox.core.domain.image.model.VvcChroma +import com.t8rin.trickle.VvcContainer +import com.t8rin.trickle.VvcEncoder +import com.t8rin.trickle.VvcBitDepth as BackendBitDepth +import com.t8rin.trickle.VvcChroma as BackendChroma + +internal data class VvcBackend( + private val isLossless: Boolean +) : ImageCompressorBackend { + + override suspend fun compress( + image: Bitmap, + quality: Quality + ): ByteArray { + val vvcQuality = quality as? Quality.Vvc ?: Quality.Vvc( + qualityValue = quality.qualityValue.coerceIn(1..100) + ) + + return VvcEncoder.encode( + bitmap = image, + options = VvcEncoder.Options( + quality = vvcQuality.qualityValue, + lossless = isLossless, + chroma = vvcQuality.chroma.toBackend(), + bitDepth = vvcQuality.bitDepth.toBackend(), + container = VvcContainer.HEIF + ) + ) + } +} + +private fun VvcChroma.toBackend(): BackendChroma = when (this) { + VvcChroma.MONOCHROME -> BackendChroma.MONOCHROME + VvcChroma.YUV_420 -> BackendChroma.YUV_420 + VvcChroma.YUV_422 -> BackendChroma.YUV_422 + VvcChroma.YUV_444 -> BackendChroma.YUV_444 +} + +private fun VvcBitDepth.toBackend(): BackendBitDepth = when (this) { + VvcBitDepth.EIGHT -> BackendBitDepth.EIGHT + VvcBitDepth.TEN -> BackendBitDepth.TEN + VvcBitDepth.TWELVE -> BackendBitDepth.TWELVE +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/WebpBackend.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/WebpBackend.kt new file mode 100644 index 0000000..9490865 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/WebpBackend.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/json/ImageModelJsonAdapters.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/json/ImageModelJsonAdapters.kt new file mode 100644 index 0000000..5c2a438 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/json/ImageModelJsonAdapters.kt @@ -0,0 +1,176 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.FromJson +import com.squareup.moshi.ToJson +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.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 + +internal class ImageFormatJsonAdapter { + + @FromJson + fun fromJson(value: String?): ImageFormat = + ImageFormat.fromTitle(value) ?: ImageFormat.Default + + @ToJson + fun toJson(value: ImageFormat): String = value.title +} + +internal class PresetJsonAdapter { + + @FromJson + fun fromJson(value: PresetJson?): Preset = when (value?.type) { + Telegram -> Preset.Telegram + Percentage -> value.value?.let(Preset::Percentage) ?: Preset.None + AspectRatio -> Preset.AspectRatio( + ratio = value.ratio ?: 1f, + isFit = value.isFit ?: false + ) + + else -> Preset.None + } + + @ToJson + fun toJson(value: Preset): PresetJson = when (value) { + Preset.None -> PresetJson(type = None) + Preset.Telegram -> PresetJson(type = Telegram) + is Preset.Percentage -> PresetJson( + type = Percentage, + value = value.value + ) + + is Preset.AspectRatio -> PresetJson( + type = AspectRatio, + ratio = value.ratio, + isFit = value.isFit + ) + } + + private companion object { + const val None = "none" + const val Telegram = "telegram" + const val Percentage = "percentage" + const val AspectRatio = "aspect_ratio" + } +} + +internal class ResizeTypeJsonAdapter { + + @FromJson + fun fromJson(value: ResizeTypeJson?): ResizeType = when (value?.type) { + Flexible -> ResizeType.Flexible( + resizeAnchor = value.resizeAnchor + ) + + CenterCrop -> ResizeType.CenterCrop( + canvasColor = value.canvasColor, + blurRadius = value.blurRadius, + originalSize = value.originalSize, + scaleFactor = value.scaleFactor, + position = value.position + ) + + Fit -> ResizeType.Fit( + canvasColor = value.canvasColor, + blurRadius = value.blurRadius, + position = value.position + ) + + else -> ResizeType.Explicit + } + + @ToJson + fun toJson(value: ResizeType): ResizeTypeJson = when (value) { + ResizeType.Explicit -> ResizeTypeJson(type = Explicit) + + is ResizeType.Flexible -> ResizeTypeJson( + type = Flexible, + resizeAnchor = value.resizeAnchor + ) + + is ResizeType.CenterCrop -> ResizeTypeJson( + type = CenterCrop, + canvasColor = value.canvasColor, + blurRadius = value.blurRadius, + originalSize = value.originalSize, + scaleFactor = value.scaleFactor, + position = value.position + ) + + is ResizeType.Fit -> ResizeTypeJson( + type = Fit, + canvasColor = value.canvasColor, + blurRadius = value.blurRadius, + position = value.position + ) + } + + private companion object { + const val Explicit = "explicit" + const val Flexible = "flexible" + const val CenterCrop = "center_crop" + const val Fit = "fit" + } +} + +internal class ImageScaleModeJsonAdapter { + + @FromJson + fun fromJson(value: ImageScaleModeJson?): ImageScaleMode = ImageScaleMode + .fromInt(value?.value ?: ImageScaleMode.Default.value) + .copy( + scaleColorSpace = ScaleColorSpace.fromOrdinal( + value?.scaleColorSpace ?: ScaleColorSpace.Default.ordinal + ) + ) + + @ToJson + fun toJson(value: ImageScaleMode): ImageScaleModeJson = ImageScaleModeJson( + value = value.value, + scaleColorSpace = value.scaleColorSpace.ordinal + ) +} + +internal data class PresetJson( + val type: String = "none", + val value: Int? = null, + val ratio: Float? = null, + val isFit: Boolean? = null +) + +internal data class ResizeTypeJson( + val type: String = "explicit", + val resizeAnchor: ResizeAnchor = ResizeAnchor.Default, + val canvasColor: Int? = 0, + val blurRadius: Int = 35, + val originalSize: IntegerSize = IntegerSize.Undefined, + val scaleFactor: Float = 1f, + val position: Position = Position.Center +) + +internal data class ImageScaleModeJson( + val value: Int = ImageScaleMode.Default.value, + val scaleColorSpace: Int = ScaleColorSpace.Default.ordinal +) diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/json/MoshiParser.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/json/MoshiParser.kt new file mode 100644 index 0000000..a30b2cd --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/json/MoshiParser.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.utils.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() + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/remote/AndroidDownloadManager.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/remote/AndroidDownloadManager.kt new file mode 100644 index 0000000..5665f48 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/remote/AndroidDownloadManager.kt @@ -0,0 +1,193 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.utils.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 + ) + } + } + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/remote/AndroidRemoteResourcesStore.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/remote/AndroidRemoteResourcesStore.kt new file mode 100644 index 0000000..54295c0 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/remote/AndroidRemoteResourcesStore.kt @@ -0,0 +1,123 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.utils.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) + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/resource/AndroidResourceManager.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/resource/AndroidResourceManager.kt new file mode 100644 index 0000000..b0b292b --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/resource/AndroidResourceManager.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/AndroidFileController.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/AndroidFileController.kt new file mode 100644 index 0000000..f4ebf90 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/AndroidFileController.kt @@ -0,0 +1,621 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.media.MediaScannerConnection +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.history.AppHistoryRepository +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.UriReplacements +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.makeLog +import com.t8rin.imagetoolbox.core.utils.tryExtractOriginal +import com.t8rin.imagetoolbox.core.utils.uiPath +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 appHistoryRepository: AppHistoryRepository, + private val appScope: AppScope, + private val originalFileDeletionHelper: OriginalFileDeletionHelper, + 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") + + if (result is SaveResult.Success) { + appHistoryRepository.registerSuccessfulSave( + savedBytes = result.savedBytes, + savedFormat = if (saveTarget is ImageSaveTarget) { + saveTarget.imageFormat.title + } else { + saveTarget.extension + } + ) + } + + return result + } + + private suspend fun saveImpl( + saveTarget: SaveTarget, + keepOriginalMetadata: Boolean, + oneTimeSaveLocationUri: String?, + ): SaveResult = withContext(ioDispatcher) { + if (!context.isExternalStorageWritable()) { + return@withContext SaveResult.Error.MissingPermissions + } + + val shouldKeepMetadata = keepOriginalMetadata && !settingsState.isAlwaysClearExif + 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, + savedBytes = data.size.toLong() + ) + } + + val originalUri = UriReplacements.resolve(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(saveTarget.originalUri) + } + } + + if (settingsState.filenameBehavior is FilenameBehavior.Overwrite) { + val providedMetadata = (saveTarget as? ImageSaveTarget) + ?.metadata + ?.takeUnless { settingsState.isAlwaysClearExif } + val targetMetadata = if (shouldKeepMetadata) { + readMetadata(originalUri.toString()) ?: providedMetadata + } else { + providedMetadata + } + + val parcel = runCatching { + if (originalUri == Uri.EMPTY) throw IllegalStateException() + + context.openFileDescriptor( + uri = originalUri, + mode = FileMode.WriteTruncate + ) + }.getOrNull() + + if (parcel == null) { + settingsManager.setImagePickerMode(FILE_EXPLORER_PICKER_MODE) + return@withContext SaveResult.Error.Exception( + Throwable(getString(R.string.overwrite_file_requirements)) + ) + } + + parcel.use { + FileOutputStream(parcel.fileDescriptor).use { out -> + out.write(data) + + copyMetadata( + initialExif = targetMetadata, + fileUri = originalUri, + keepOriginalMetadata = shouldKeepMetadata, + 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, + savedBytes = data.size.toLong() + ) + } + } 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( + Throwable( + getString( + R.string.no_such_directory, + treeUri.toUri().uiPath(treeUri, context) + ) + ) + ) + } + } else { + documentFile = null + } + + var initialExif: Metadata? = null + + val newSaveTarget = if (saveTarget is ImageSaveTarget) { + initialExif = saveTarget.metadata.takeUnless { settingsState.isAlwaysClearExif } + + saveTarget.copy( + filename = filenameCreator.constructImageFilename( + saveTarget = saveTarget, + forceNotAddSizeInFilename = saveTarget.imageInfo.height <= 0 || saveTarget.imageInfo.width <= 0 + ) + ) + } else saveTarget + + val filename = newSaveTarget.filename + ?: throw IllegalArgumentException(getString(R.string.filename_is_not_set)) + + val savingFolder = SavingFolder.getInstance( + context = context, + treeUri = treeUri?.toUri(), + saveTarget = newSaveTarget, + saveToOriginalFolder = settingsState.saveToOriginalFolder + ) ?: throw IllegalArgumentException(getString(R.string.error_while_saving)) + + savingFolder.use { + it.writeBytes(data) + } + + copyMetadata( + initialExif = initialExif, + fileUri = savingFolder.fileUri, + keepOriginalMetadata = shouldKeepMetadata, + originalUri = saveTarget.originalUri.toUri() + ) + + if (settingsState.deleteOriginalsAfterSave && settingsState.filenameBehavior !is FilenameBehavior.Overwrite) { + originalFileDeletionHelper.deleteAfterSuccessfulSave( + originalUri = saveTarget.originalUri, + outputUri = savingFolder.fileUri + ) + } + + savingFolder.fileUri.path?.takeIf { + savingFolder.fileUri.scheme == "file" + }?.let { path -> + MediaScannerConnection.scanFile( + context, + arrayOf(path), + arrayOf(newSaveTarget.mimeType.entry), + null + ) + } + + val actualSavingPath = savingFolder.normalizedSavingPath ?: savingPath + + 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 (actualSavingPath.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, + actualSavingPath, + filename + ) + } else { + getString( + R.string.saved_to_without_filename, + actualSavingPath + ) + } + } else null, + savingPath = actualSavingPath, + savedBytes = data.size.toLong() + ) + } + }.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( + UriReplacements.resolve(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 { + val savedBytes = uri.toUri().fileSize() ?: 0 + + appHistoryRepository.registerSuccessfulSave( + savedBytes = savedBytes, + savedFormat = "" + ) + return@withContext SaveResult.Success( + message = null, + savingPath = "", + savedBytes = savedBytes + ) + }.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 = UriReplacements.resolve(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? + ) { + UriReplacements.resolve(imageUri.toUri()).let { resolvedUri -> + copyMetadata( + initialExif = metadata, + fileUri = resolvedUri, + keepOriginalMetadata = false, + originalUri = resolvedUri + ) + } + } + + override suspend fun readMetadata( + imageUri: String + ): Metadata? = runSuspendCatching { + context.contentResolver.openInputStream( + UriReplacements.resolve(imageUri.toUri()) + )?.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 && !settingsState.isAlwaysClearExif) { + 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 + ) +} + +private const val FILE_EXPLORER_PICKER_MODE = 3 \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/AndroidFilenameCreator.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/AndroidFilenameCreator.kt new file mode 100644 index 0000000..f7b4813 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/AndroidFilenameCreator.kt @@ -0,0 +1,331 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.ParentFolder +import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.ParentFolderUpper +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.Uuid +import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.UuidUpper +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.slice +import com.t8rin.imagetoolbox.core.domain.utils.timestamp +import com.t8rin.imagetoolbox.core.domain.utils.transliterate +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.imagetoolbox.core.utils.makeLog +import com.t8rin.imagetoolbox.core.utils.path +import dagger.hilt.android.qualifiers.ApplicationContext +import java.util.Date +import java.util.Locale +import javax.inject.Inject +import kotlin.random.Random +import kotlin.uuid.Uuid as UUID + +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") + } + + runCatching { + result = result.replace("""\\c\{([^}]*)\}""".toRegex()) { match -> + val params = match.groupValues[1].split(":") + val start = params.getOrNull(0)?.toIntOrNull() ?: 1 + val step = params.getOrNull(1)?.toIntOrNull() ?: 1 + val padding = params.getOrNull(2)?.toIntOrNull() + ?: if (params.size == 1) params[0].toIntOrNull() ?: 0 else 0 + + val current = start + ((saveTarget.sequenceNumber ?: 1) - 1) * step + current.toString().padStart(padding, '0') + } + }.onFailure { + it.makeLog("pattern sequence") + } + + runCatching { + result = result.replace("""\\o\{(-?\d+)?:(-?\d+)?\}""".toRegex()) { match -> + if (settingsState.addOriginalFilename && !isOriginalEmpty) { + val start = match.groupValues[1].toIntOrNull() + val end = match.groupValues[2].toIntOrNull() + originalName.slice(start, end) + } else { + "" + } + } + + result = result.replace("""\\O\{(-?\d+)?:(-?\d+)?\}""".toRegex()) { match -> + if (settingsState.addOriginalFilename && !isOriginalEmpty) { + val start = match.groupValues[1].toIntOrNull() + val end = match.groupValues[2].toIntOrNull() + originalName.uppercase().slice(start, end) + } else { + "" + } + } + + result = result.replace("""\\o\{t\}""".toRegex()) { + if (settingsState.addOriginalFilename && !isOriginalEmpty) { + originalName.transliterate() + } else { + "" + } + } + + result = result.replace("""\\O\{t\}""".toRegex()) { + if (settingsState.addOriginalFilename && !isOriginalEmpty) { + originalName.uppercase().transliterate() + } else { + "" + } + } + + result = result.replace("""\\o\{s/(.*?)/(.*?)/\}""".toRegex()) { match -> + if (settingsState.addOriginalFilename && !isOriginalEmpty) { + val old = match.groupValues[1] + val new = match.groupValues[2] + originalName.replace(old, new) + } else { + "" + } + } + + result = result.replace("""\\O\{s/(.*?)/(.*?)/\}""".toRegex()) { match -> + if (settingsState.addOriginalFilename && !isOriginalEmpty) { + val old = match.groupValues[1] + val new = match.groupValues[2] + originalName.uppercase().replace(old, new) + } else { + "" + } + } + }.onFailure { + it.makeLog("pattern original slice") + } + + val parentFolder = if (isOriginalEmpty) "" + else saveTarget.originalUri.toUri().path()?.let { path -> + if ('/' in path) { + path.substringBeforeLast('/').substringAfterLast('/') + } else "" + }.orEmpty() + + 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(ParentFolder, parentFolder) + .replace(Uuid, UUID.random().toString()) + .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()) + .replace(ParentFolderUpper, parentFolder.uppercase()) + .replace(UuidUpper, UUID.random().toString().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) ?: "" + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/AndroidKeepAliveService.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/AndroidKeepAliveService.kt new file mode 100644 index 0000000..5796601 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/AndroidKeepAliveService.kt @@ -0,0 +1,71 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.FailureNotifier +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, + failureNotifier: FailureNotifier +) : KeepAliveService, FailureNotifier by failureNotifier { + + 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) } + ) + } + +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/FileControllerEventEmitter.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/FileControllerEventEmitter.kt new file mode 100644 index 0000000..9bbf086 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/FileControllerEventEmitter.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.IntentSender +import kotlinx.coroutines.flow.Flow + +interface FileControllerEventEmitter { + val events: Flow + + suspend fun deleteFiles( + uris: List, + onResult: (FileDeletionResult) -> Unit + ) + + fun onDeleteOriginalsPermissionResult( + requestId: Long, + granted: Boolean + ) +} + +data class FileDeletionResult( + val deletedUris: List, + val failedUris: List +) + +sealed interface FileControllerEvent { + data class RequestDeleteOriginalsPermission( + val requestId: Long, + val intentSender: IntentSender, + val count: Int + ) : FileControllerEvent + + data class OriginalFilesDeleteResult( + val deleted: Int, + val failed: Int + ) : FileControllerEvent +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/KeepAliveForegroundService.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/KeepAliveForegroundService.kt new file mode 100644 index 0000000..cbfdd0c --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/KeepAliveForegroundService.kt @@ -0,0 +1,221 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.utils.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( + { + runCatching { + stopForegroundSafe() + stopSelfResult(startId) + }.onFailure { it.makeLog("KeepAliveForegroundService") } + }, + 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) + } + } + + override fun onTimeout(startId: Int, fgsType: Int) { + "Timeout: startId = $startId, fgsType = $fgsType".makeLog("KeepAliveForegroundService") + removeNotification = true + stopForegroundSafe() + } + + @Suppress("DEPRECATION") + private fun stopForegroundSafe() { + stopForeground( + if (removeNotification) { + STOP_FOREGROUND_REMOVE + } else { + STOP_FOREGROUND_DETACH + } + ) + 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" + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/OriginalFileDeletionHelper.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/OriginalFileDeletionHelper.kt new file mode 100644 index 0000000..502537c --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/OriginalFileDeletionHelper.kt @@ -0,0 +1,521 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.RecoverableSecurityException +import android.content.ContentResolver +import android.content.ContentUris +import android.content.Context +import android.content.IntentSender +import android.net.Uri +import android.os.Build +import android.os.ParcelFileDescriptor +import android.provider.DocumentsContract +import android.provider.MediaStore +import android.provider.OpenableColumns +import android.system.Os +import androidx.annotation.RequiresApi +import androidx.core.net.toUri +import androidx.documentfile.provider.DocumentFile +import com.t8rin.imagetoolbox.core.domain.coroutines.AppScope +import com.t8rin.imagetoolbox.core.utils.UriReplacements +import com.t8rin.imagetoolbox.core.utils.distinctUris +import com.t8rin.imagetoolbox.core.utils.tryExtractOriginal +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch +import java.io.File +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicLong +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +internal class OriginalFileDeletionHelper @Inject constructor( + @ApplicationContext private val context: Context, + private val appScope: AppScope +) : FileControllerEventEmitter { + + private val eventChannel = Channel(Channel.UNLIMITED) + override val events: Flow = eventChannel.receiveAsFlow() + + private val pendingDeleteUris = ConcurrentHashMap.newKeySet() + private val pendingDeleteRequests = ConcurrentHashMap>() + private val pendingDeleteOperations = ConcurrentHashMap() + private val nextDeleteRequestId = AtomicLong() + private val pendingDeleteLock = Any() + private var pendingDeleteJob: Job? = null + private val pendingDeletedCount = AtomicInteger() + private val pendingFailedCount = AtomicInteger() + private val deleteResultLock = Any() + private var deleteResultJob: Job? = null + + override suspend fun deleteFiles( + uris: List, + onResult: (FileDeletionResult) -> Unit + ) { + val distinctUris = uris + .map { it.toUri().tryExtractOriginal() } + .distinctUris() + .map(Uri::toString) + if (distinctUris.isEmpty()) { + onResult(FileDeletionResult(emptyList(), emptyList())) + return + } + + val batch = DeleteBatch( + size = distinctUris.size, + onResult = { result -> + emitDeleteResult( + deleted = result.deletedUris.size, + failed = result.failedUris.size + ) + onResult(result) + } + ) + appScope.launch { + distinctUris.forEach { source -> + val sourceUri = source.toUri() + val resolvedUri = UriReplacements.resolve(sourceUri) + deleteFile( + DeleteOperation( + originalUri = resolvedUri, + onResult = { deleted -> + batch.complete(source, deleted) + } + ) + ) + } + } + } + + fun deleteAfterSuccessfulSave( + originalUri: String, + outputUri: Uri + ) { + val sourceUri = originalUri + .takeIf(String::isNotBlank) + ?.toUri() + ?: return + val resolvedUri = UriReplacements.resolve(sourceUri) + + if (resolvedUri.scheme !in DELETABLE_URI_SCHEMES) return + if (resolvedUri == outputUri) return + + val mediaStoreUri = resolvedUri.toMediaStoreItemUri() + val outputMediaStoreUri = outputUri.toMediaStoreItemUri() + if (mediaStoreUri != null && outputMediaStoreUri != null) { + if (mediaStoreUri == outputMediaStoreUri) return + } else if (resolvedUri.refersToSameFileAs(outputUri)) { + return + } + + val replacement = UriReplacement( + sourceUri = sourceUri, + originalUri = resolvedUri, + outputUri = outputUri + ) + deleteFile( + DeleteOperation( + originalUri = resolvedUri, + onResult = { deleted -> + if (deleted) registerUriReplacement(replacement) + emitDeleteResult( + deleted = if (deleted) 1 else 0, + failed = if (deleted) 0 else 1 + ) + } + ) + ) + } + + private fun deleteFile(operation: DeleteOperation) { + val resolvedUri = operation.originalUri + + if (resolvedUri.scheme !in DELETABLE_URI_SCHEMES) { + operation.complete(deleted = false) + return + } + + if (resolvedUri.scheme == ContentResolver.SCHEME_FILE) { + val file = resolvedUri.path?.let(::File) + if (file == null) { + operation.complete(deleted = false) + return + } + + operation.complete(deleted = !file.exists() || file.delete() || !file.exists()) + return + } + + val mediaStoreUri = resolvedUri.toMediaStoreItemUri() + val documentDeleteResult = runCatching { + DocumentFile.fromSingleUri(context, resolvedUri)?.delete() == true + } + + if (documentDeleteResult.getOrDefault(false)) { + operation.complete(deleted = true) + return + } + + documentDeleteResult.exceptionOrNull()?.let { throwable -> + if (!resolvedUri.exists()) { + operation.complete(deleted = true) + return + } + + if (throwable is SecurityException) { + handleDeleteSecurityException( + throwable = throwable, + mediaStoreUri = mediaStoreUri, + operation = operation + ) + return + } + } + + try { + val deleted = context.contentResolver.delete( + mediaStoreUri ?: resolvedUri, + null, + null + ) + + if (deleted > 0 || !resolvedUri.exists()) { + operation.complete(deleted = true) + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && mediaStoreUri != null) { + enqueueDeletePermissionRequest( + uri = mediaStoreUri, + operation = operation + ) + } else { + operation.complete(deleted = false) + } + } catch (throwable: SecurityException) { + if (!resolvedUri.exists()) { + operation.complete(deleted = true) + } else { + handleDeleteSecurityException( + throwable = throwable, + mediaStoreUri = mediaStoreUri, + operation = operation + ) + } + } catch (_: Throwable) { + if (!resolvedUri.exists()) { + operation.complete(deleted = true) + } else { + operation.complete(deleted = false) + } + } + } + + private fun handleDeleteSecurityException( + throwable: SecurityException, + mediaStoreUri: Uri?, + operation: DeleteOperation + ) { + when { + Build.VERSION.SDK_INT == Build.VERSION_CODES.Q && + throwable is RecoverableSecurityException -> { + emitRecoverableDeletePermissionRequest( + uri = mediaStoreUri ?: operation.originalUri, + throwable = throwable, + operation = operation + ) + } + + Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && mediaStoreUri != null -> { + enqueueDeletePermissionRequest( + uri = mediaStoreUri, + operation = operation + ) + } + + else -> operation.complete(deleted = false) + } + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun enqueueDeletePermissionRequest( + uri: Uri, + operation: DeleteOperation + ) { + pendingDeleteUris.add(uri) + pendingDeleteOperations[uri] = operation + scheduleDeletePermissionRequest() + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun scheduleDeletePermissionRequest() { + synchronized(pendingDeleteLock) { + if (pendingDeleteJob?.isActive == true) return + + pendingDeleteJob = appScope.launch { + delay(500L) + + val uris = pendingDeleteUris.toList().distinct() + pendingDeleteUris.removeAll(uris.toSet()) + if (uris.isNotEmpty()) { + runCatching { + MediaStore.createDeleteRequest( + context.contentResolver, + uris + ).intentSender + }.onSuccess { intentSender -> + emitDeletePermissionRequest( + uris = uris, + intentSender = intentSender + ) + }.onFailure { + uris.forEach { uri -> + pendingDeleteOperations.remove(uri)?.complete(deleted = false) + } + } + } + + synchronized(pendingDeleteLock) { + pendingDeleteJob = null + } + if (pendingDeleteUris.isNotEmpty()) { + scheduleDeletePermissionRequest() + } + } + } + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun emitRecoverableDeletePermissionRequest( + uri: Uri, + throwable: RecoverableSecurityException, + operation: DeleteOperation + ) { + pendingDeleteOperations[uri] = operation + emitDeletePermissionRequest( + uris = listOf(uri), + intentSender = throwable.userAction.actionIntent.intentSender + ) + } + + private fun emitDeletePermissionRequest( + uris: List, + intentSender: IntentSender + ) { + val requestId = nextDeleteRequestId.incrementAndGet() + pendingDeleteRequests[requestId] = uris + eventChannel.trySend( + FileControllerEvent.RequestDeleteOriginalsPermission( + requestId = requestId, + intentSender = intentSender, + count = uris.size + ) + ) + } + + override fun onDeleteOriginalsPermissionResult( + requestId: Long, + granted: Boolean + ) { + val uris = pendingDeleteRequests.remove(requestId) ?: return + uris.forEach { uri -> + pendingDeleteOperations.remove(uri)?.complete(deleted = granted) + } + } + + private fun registerUriReplacement(replacement: UriReplacement) { + UriReplacements.register( + originalUri = replacement.sourceUri, + replacementUri = replacement.outputUri + ) + UriReplacements.register( + originalUri = replacement.originalUri, + replacementUri = replacement.outputUri + ) + } + + private fun emitDeleteResult( + deleted: Int, + failed: Int + ) { + pendingDeletedCount.addAndGet(deleted) + pendingFailedCount.addAndGet(failed) + + synchronized(deleteResultLock) { + if (deleteResultJob?.isActive == true) return + + deleteResultJob = appScope.launch { + delay(300L) + val deletedCount = pendingDeletedCount.getAndSet(0) + val failedCount = pendingFailedCount.getAndSet(0) + + if (deletedCount + failedCount > 0) { + eventChannel.send( + FileControllerEvent.OriginalFilesDeleteResult( + deleted = deletedCount, + failed = failedCount + ) + ) + } + + synchronized(deleteResultLock) { + deleteResultJob = null + } + if (pendingDeletedCount.get() + pendingFailedCount.get() > 0) { + emitDeleteResult(deleted = 0, failed = 0) + } + } + } + } + + private fun Uri.exists(): Boolean { + if (scheme == "file") return path?.let(::File)?.exists() == true + + val queryResult = runCatching { + context.contentResolver.query( + this, + arrayOf(OpenableColumns.DISPLAY_NAME), + null, + null, + null + )?.use { cursor -> cursor.moveToFirst() } + }.getOrNull() + + if (queryResult == true) { + return runCatching { + context.contentResolver.openFileDescriptor(this, "r")?.use { true } ?: false + }.getOrElse { it is SecurityException } + } + + return queryResult ?: runCatching { + DocumentFile.fromSingleUri(context, this)?.exists() + }.getOrNull() ?: true + } + + private fun Uri.toMediaStoreItemUri(): Uri? { + if (authority == MediaStore.AUTHORITY) { + return takeIf { lastPathSegment?.toLongOrNull() != null } + } + if (authority != MEDIA_DOCUMENTS_AUTHORITY) return null + + val documentId = runCatching { + DocumentsContract.getDocumentId(this) + }.getOrNull() ?: return null + val (type, idString) = documentId.split(':', limit = 2).takeIf { + it.size == 2 + } ?: return null + val id = idString.toLongOrNull() ?: return null + + val collection = when (type) { + "image" -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI + "video" -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI + "audio" -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + else -> return null + } + + return ContentUris.withAppendedId(collection, id) + } + + private fun Uri.refersToSameFileAs(other: Uri): Boolean { + if (this == other) return true + + val firstFile = takeIf { scheme == ContentResolver.SCHEME_FILE } + ?.path + ?.let(::File) + val secondFile = other.takeIf { it.scheme == ContentResolver.SCHEME_FILE } + ?.path + ?.let(::File) + if (firstFile != null && secondFile != null) { + return runCatching { + firstFile.canonicalFile == secondFile.canonicalFile + }.getOrDefault(false) + } + + return runCatching { + openFileDescriptor()?.use { firstDescriptor -> + other.openFileDescriptor()?.use { secondDescriptor -> + val firstStat = Os.fstat(firstDescriptor.fileDescriptor) + val secondStat = Os.fstat(secondDescriptor.fileDescriptor) + + firstStat.st_ino != 0L && + firstStat.st_dev == secondStat.st_dev && + firstStat.st_ino == secondStat.st_ino + } + } + }.getOrNull() == true + } + + private fun Uri.openFileDescriptor(): ParcelFileDescriptor? = when (scheme) { + ContentResolver.SCHEME_FILE -> path?.let(::File)?.let { + ParcelFileDescriptor.open(it, ParcelFileDescriptor.MODE_READ_ONLY) + } + + ContentResolver.SCHEME_CONTENT -> context.contentResolver.openFileDescriptor(this, "r") + else -> null + } +} + +private class DeleteBatch( + size: Int, + private val onResult: (FileDeletionResult) -> Unit +) { + private val remaining = AtomicInteger(size) + private val deletedUris = mutableListOf() + private val failedUris = mutableListOf() + + fun complete( + uri: String, + deleted: Boolean + ) { + val result = synchronized(this) { + if (deleted) deletedUris += uri else failedUris += uri + + if (remaining.decrementAndGet() == 0) { + FileDeletionResult( + deletedUris = deletedUris.toList(), + failedUris = failedUris.toList() + ) + } else null + } + result?.let(onResult) + } +} + +private data class DeleteOperation( + val originalUri: Uri, + val onResult: (Boolean) -> Unit +) { + fun complete(deleted: Boolean) = onResult(deleted) +} + +private const val MEDIA_DOCUMENTS_AUTHORITY = "com.android.providers.media.documents" + +private val DELETABLE_URI_SCHEMES = setOf( + ContentResolver.SCHEME_CONTENT, + ContentResolver.SCHEME_FILE +) + +private data class UriReplacement( + val sourceUri: Uri, + val originalUri: Uri, + val outputUri: Uri +) \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/SaveException.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/SaveException.kt new file mode 100644 index 0000000..78d4e29 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/SaveException.kt @@ -0,0 +1,20 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/SavingFolder.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/SavingFolder.kt new file mode 100644 index 0000000..306fd66 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/SavingFolder.kt @@ -0,0 +1,283 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.DocumentsContract +import android.provider.MediaStore +import androidx.annotation.RequiresApi +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.ImageSaveTarget +import com.t8rin.imagetoolbox.core.domain.saving.model.SaveTarget +import com.t8rin.imagetoolbox.core.utils.makeLog +import com.t8rin.imagetoolbox.core.utils.path +import com.t8rin.imagetoolbox.core.utils.tryExtractOriginal +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, + private val savingPath: String? = null +) : StreamWriteable by StreamWriteable(outputStream) { + + val normalizedSavingPath = savingPath?.removeSuffix("/")?.removePrefix("/storage/emulated/0/") + + companion object { + suspend fun getInstance( + context: Context, + treeUri: Uri?, + saveTarget: SaveTarget, + saveToOriginalFolder: Boolean + ): SavingFolder? = coroutineScope { + val originalFolder = if (saveToOriginalFolder) { + runCatching { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) { + createLegacyFile( + saveTarget = saveTarget, + parent = saveTarget.originalParentFile(context) + ) + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + saveTarget.originalRelativePath(context)?.let { + context.createViaMediaStore( + saveTarget = saveTarget, + relativePath = it + ) + } + } else { + createLegacyFile( + saveTarget = saveTarget, + parent = saveTarget.originalParentFile(context) + ) + } + }.onFailure { it.makeLog("saveToOriginalFolder") }.getOrNull() + } else null + + originalFolder ?: if (treeUri == null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + context.createViaMediaStore( + saveTarget = saveTarget, + relativePath = null + ) + } else { + createDefaultFolderFile(saveTarget) + } + } else if (DocumentFile.isDocumentUri(context, treeUri)) { + SavingFolder( + outputStream = context.contentResolver.openOutputStream(treeUri) + ?: return@coroutineScope null, + fileUri = treeUri, + savingPath = treeUri.path() + ) + } 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, + savingPath = documentFile.uri.path() + ) + } + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun Context.createViaMediaStore( + saveTarget: SaveTarget, + relativePath: String? + ): SavingFolder? { + val filename = saveTarget.filename ?: return null + val mimeType = saveTarget.mimeType.entry + + val contentValues = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, filename) + put(MediaStore.MediaColumns.MIME_TYPE, mimeType) + put( + MediaStore.MediaColumns.RELATIVE_PATH, + relativePath ?: "${Environment.DIRECTORY_DOCUMENTS}/ImageToolbox" + ) + } + + val primaryDirectory = relativePath + ?.trimStart('/') + ?.substringBefore('/') + + val collectionUri = when { + relativePath == null -> MediaStore.Files.getContentUri( + MediaStore.VOLUME_EXTERNAL_PRIMARY + ) + + primaryDirectory.equals(Environment.DIRECTORY_DOWNLOADS, ignoreCase = true) -> { + MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) + } + + primaryDirectory.equals(Environment.DIRECTORY_DOCUMENTS, ignoreCase = true) -> { + MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) + } + + mimeType.startsWith("image/") -> MediaStore.Images.Media.getContentUri( + MediaStore.VOLUME_EXTERNAL_PRIMARY + ) + + mimeType.startsWith("video/") -> MediaStore.Video.Media.getContentUri( + MediaStore.VOLUME_EXTERNAL_PRIMARY + ) + + mimeType.startsWith("audio/") -> MediaStore.Audio.Media.getContentUri( + MediaStore.VOLUME_EXTERNAL_PRIMARY + ) + + else -> MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) + } + + val uri = runCatching { + contentResolver.insert(collectionUri, contentValues) + }.getOrNull() ?: return null + + return SavingFolder( + outputStream = contentResolver.openOutputStream(uri) + ?: return null, + fileUri = uri, + savingPath = relativePath ?: "${Environment.DIRECTORY_DOCUMENTS}/ImageToolbox" + ) + } + + private fun createDefaultFolderFile( + saveTarget: SaveTarget + ): SavingFolder? = createLegacyFile( + saveTarget = saveTarget, + parent = File( + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOCUMENTS + ), + "ImageToolbox" + ) + ) + + private fun createLegacyFile( + saveTarget: SaveTarget, + parent: File? + ): SavingFolder? { + val filename = saveTarget.filename ?: return null + val dir = parent ?: return null + + if (!dir.exists()) dir.mkdirs() + if (!dir.exists() || !dir.isDirectory) return null + + val file = File(dir, filename) + + return SavingFolder( + outputStream = FileOutputStream(file), + fileUri = file.toUri(), + savingPath = dir.absolutePath + ) + } + + private fun SaveTarget.originalParentFile(context: Context): File? { + val originalUri = (this as? ImageSaveTarget) + ?.originalUri + ?.toUri() + ?: return null + + val originalPath = originalUri.externalStoragePath(context) ?: originalUri.path() + ?.takeIf { it.isNotBlank() } + ?: return null + + return File(originalPath) + .parentFile + ?.takeIf { it.exists() && it.isDirectory } + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun SaveTarget.originalRelativePath( + context: Context + ): String? { + val originalUri = (this as? ImageSaveTarget) + ?.originalUri + ?.toUri() + ?.tryExtractOriginal() + ?: return null + + originalUri.externalStorageRelativePath(context)?.let { return it } + + return context.contentResolver.query( + originalUri, + arrayOf(MediaStore.MediaColumns.RELATIVE_PATH), + null, + null, + null + )?.use { cursor -> + if (!cursor.moveToFirst()) return@use null + + cursor.getString( + cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.RELATIVE_PATH) + ) + }?.takeIf { + it.isNotBlank() && ".transforms" !in it + } + } + + private fun Uri.externalStorageRelativePath(context: Context): String? = runCatching { + if (!DocumentsContract.isDocumentUri(context, this)) return@runCatching null + + val documentId = DocumentsContract.getDocumentId(this) + val type = documentId.substringBefore(':') + val path = documentId.substringAfter(':', "") + + if (type.equals("primary", ignoreCase = true)) { + path.substringBeforeLast('/', "") + .takeIf { it.isNotBlank() } + ?.plus('/') + } else null + }.onFailure { it.makeLog("externalStorageRelativePath") }.getOrNull() + + private fun Uri.externalStoragePath(context: Context): String? = runCatching { + if (!DocumentsContract.isDocumentUri(context, this)) return@runCatching null + + val documentId = DocumentsContract.getDocumentId(this) + val type = documentId.substringBefore(':') + val path = documentId.substringAfter(':', "") + + if (type.equals("primary", ignoreCase = true) && path.isNotBlank()) { + File(Environment.getExternalStorageDirectory(), path).absolutePath + } else null + }.onFailure { it.makeLog("externalStoragePath") }.getOrNull() + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/io/FileWriteable.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/io/FileWriteable.kt new file mode 100644 index 0000000..a61af72 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/io/FileWriteable.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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()) \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/io/StreamWriteable.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/io/StreamWriteable.kt new file mode 100644 index 0000000..0efa96a --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/io/StreamWriteable.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.utils.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") + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/io/UriReadable.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/io/UriReadable.kt new file mode 100644 index 0000000..d072351 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/io/UriReadable.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.utils.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) +) \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/BitmapUtils.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/BitmapUtils.kt new file mode 100644 index 0000000..1fcdeba --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/BitmapUtils.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/ChecksumUtils.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/ChecksumUtils.kt new file mode 100644 index 0000000..82cccc5 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/ChecksumUtils.kt @@ -0,0 +1,149 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/CoilUtils.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/CoilUtils.kt new file mode 100644 index 0000000..77fd836 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/CoilUtils.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.ensureActive +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 = coroutineScope { + ensureActive() + 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 = coroutineScope { + ensureActive() + this@asDomain.transform(input, size.asCoil()) + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/ContextUtils.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/ContextUtils.kt new file mode 100644 index 0000000..da1c838 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/ContextUtils.kt @@ -0,0 +1,117 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/CoroutinesUtils.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/CoroutinesUtils.kt new file mode 100644 index 0000000..b972a76 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/CoroutinesUtils.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/FileUtils.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/FileUtils.kt new file mode 100644 index 0000000..2a9c8e2 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/FileUtils.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +fun File.observeHasChanges( + flags: Int = DEFAULT_FLAGS +): 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 { + @Suppress("DEPRECATION") + object : FileObserver(absolutePath, flags) { + override fun onEvent(event: Int, path: String?) { + trySend(Unit) + } + } + } + send(Unit) + observer.startWatching() + awaitClose { observer.stopWatching() } +} + +private const val DEFAULT_FLAGS = + FileObserver.CREATE or FileObserver.DELETE or FileObserver.DELETE_SELF or FileObserver.DELETE_SELF or FileObserver.MODIFY or FileObserver.MOVE_SELF or FileObserver.MOVED_TO or FileObserver.MOVED_FROM \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/HttpClientUtils.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/HttpClientUtils.kt new file mode 100644 index 0000000..ab692b3 --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/HttpClientUtils.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/WriteableUtils.kt b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/WriteableUtils.kt new file mode 100644 index 0000000..c118b5f --- /dev/null +++ b/core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/WriteableUtils.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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())) + } +} \ No newline at end of file diff --git a/core/di/.gitignore b/core/di/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/di/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/di/build.gradle.kts b/core/di/build.gradle.kts new file mode 100644 index 0000000..197aadd --- /dev/null +++ b/core/di/build.gradle.kts @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/core/di/src/main/AndroidManifest.xml b/core/di/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/core/di/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/core/di/src/main/java/com/t8rin/imagetoolbox/core/di/EntryPointUtils.kt b/core/di/src/main/java/com/t8rin/imagetoolbox/core/di/EntryPointUtils.kt new file mode 100644 index 0000000..20ed4a6 --- /dev/null +++ b/core/di/src/main/java/com/t8rin/imagetoolbox/core/di/EntryPointUtils.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) +) \ No newline at end of file diff --git a/core/di/src/main/java/com/t8rin/imagetoolbox/core/di/Qualifiers.kt b/core/di/src/main/java/com/t8rin/imagetoolbox/core/di/Qualifiers.kt new file mode 100644 index 0000000..7a77dc5 --- /dev/null +++ b/core/di/src/main/java/com/t8rin/imagetoolbox/core/di/Qualifiers.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 \ No newline at end of file diff --git a/core/domain/.gitignore b/core/domain/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/domain/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/domain/build.gradle.kts b/core/domain/build.gradle.kts new file mode 100644 index 0000000..e3446ba --- /dev/null +++ b/core/domain/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + testImplementation(libs.junit) +} \ No newline at end of file diff --git a/core/domain/src/main/AndroidManifest.xml b/core/domain/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/core/domain/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/Constants.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/Constants.kt new file mode 100644 index 0000000..6e0b43a --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/Constants.kt @@ -0,0 +1,61 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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_EMAIL = "open.t8rin@internet.ru" + +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 TEMPLATE_EXT = "imtbx_template" +const val SHADER_EXT = "itshader" +const val PDF = "pdf/" + + +const val WEBLATE_LINK = "https://hosted.weblate.org/engage/image-resizer/" +const val PARTNER_FREE_SOFTWARE = "tg://resolve?domain=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" \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/coroutines/AppScope.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/coroutines/AppScope.kt new file mode 100644 index 0000000..e4df60a --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/coroutines/AppScope.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/coroutines/DispatchersHolder.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/coroutines/DispatchersHolder.kt new file mode 100644 index 0000000..313b7ed --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/coroutines/DispatchersHolder.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/history/AppHistoryRepository.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/history/AppHistoryRepository.kt new file mode 100644 index 0000000..9626679 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/history/AppHistoryRepository.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.history + +import com.t8rin.imagetoolbox.core.domain.history.model.AppUsageStatistics +import com.t8rin.imagetoolbox.core.domain.history.model.LastUsedTool +import kotlinx.coroutines.flow.Flow + +interface AppHistoryRepository { + + fun lastUsedTools(maxCount: Int = 5): Flow> + + fun toolUsageStatistics(): Flow> + + fun successfulSavesCount(): Flow + + fun appUsageStatistics(): Flow + + suspend fun pushLastTool(screenId: Int) + + suspend fun registerSuccessfulSave( + savedBytes: Long, + savedFormat: String + ) + + suspend fun resetUsageStatistics() + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/history/model/AppUsageStatistics.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/history/model/AppUsageStatistics.kt new file mode 100644 index 0000000..833ef30 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/history/model/AppUsageStatistics.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.history.model + +data class AppUsageStatistics( + val successfulSavesCount: Int = 0, + val savedBytes: Long = 0, + val lastActivityDayEpoch: Long = 0, + val currentActivityStreak: Int = 0, + val savedFormatCounts: Map = emptyMap() +) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/history/model/LastUsedTool.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/history/model/LastUsedTool.kt new file mode 100644 index 0000000..c1847e4 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/history/model/LastUsedTool.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.history.model + +data class LastUsedTool( + val screenId: Int, + val openCount: Int, + val lastOpenedTimestamp: Long +) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageCompressor.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageCompressor.kt new file mode 100644 index 0000000..0daf3fe --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageCompressor.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageExportProfilesUseCase.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageExportProfilesUseCase.kt new file mode 100644 index 0000000..0e5760c --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageExportProfilesUseCase.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.ImageExportProfile +import kotlinx.coroutines.flow.Flow + +interface ImageExportProfilesUseCase { + + val profiles: Flow> + + suspend fun upsert(profile: ImageExportProfile) + + suspend fun delete(profile: ImageExportProfile) + + suspend fun export( + profile: ImageExportProfile, + uri: String + ) + + suspend fun share(profile: ImageExportProfile) + + suspend fun importProfile(uri: String) + +} diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageGetter.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageGetter.kt new file mode 100644 index 0000000..e239cd8 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageGetter.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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)? = null + ): 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? + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImagePreviewCreator.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImagePreviewCreator.kt new file mode 100644 index 0000000..5ef6717 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImagePreviewCreator.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageScaler.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageScaler.kt new file mode 100644 index 0000000..0931c39 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageScaler.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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? + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageShareProvider.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageShareProvider.kt new file mode 100644 index 0000000..57341f7 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageShareProvider.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageTransformer.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageTransformer.kt new file mode 100644 index 0000000..303e8ac --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageTransformer.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/Metadata.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/Metadata.kt new file mode 100644 index 0000000..c40c40d --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/Metadata.kt @@ -0,0 +1,107 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 = this as? TagMapMetadata ?: 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? +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ShareProvider.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ShareProvider.kt new file mode 100644 index 0000000..efbfdbe --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ShareProvider.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/BlendingMode.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/BlendingMode.kt new file mode 100644 index 0000000..29d28dc --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/BlendingMode.kt @@ -0,0 +1,544 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ChromaSubsampling.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ChromaSubsampling.kt new file mode 100644 index 0000000..1573d05 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ChromaSubsampling.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 AvifChromaSubsampling { + Auto, + Yuv420, + Yuv422, + Yuv444, + Yuv400, + Lossless +} + +enum class HeicChromaSubsampling { + Yuv420, + Yuv422, + Yuv444 +} + +enum class VvcChroma { + MONOCHROME, + YUV_420, + YUV_422, + YUV_444 +} + +enum class VvcBitDepth { + EIGHT, + TEN, + TWELVE +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageData.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageData.kt new file mode 100644 index 0000000..f80dcb6 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageData.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageExportProfile.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageExportProfile.kt new file mode 100644 index 0000000..957e401 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageExportProfile.kt @@ -0,0 +1,73 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.ColorModel + +data class ImageExportProfiles( + val presets: List = emptyList() +) + +data class ImageExportProfile( + val name: String = "", + val imageInfo: ImageInfo = ImageInfo(), + val preset: Preset = Preset.None, + val keepExif: Boolean? = null, + val backgroundColorForNoAlphaFormats: Int? = null, + val version: Int = 1 +) { + + fun toImageInfo(current: ImageInfo): ImageInfo = imageInfo.copy( + width = if (preset.isEmpty()) imageInfo.width else current.width, + height = if (preset.isEmpty()) imageInfo.height else current.height, + quality = imageInfo.quality.coerceIn(imageInfo.imageFormat), + originalUri = current.originalUri, + sizeInBytes = 0 + ) + + fun applyExportSettingsTo(imageInfo: ImageInfo): ImageInfo = imageInfo.copy( + imageFormat = this.imageInfo.imageFormat, + quality = this.imageInfo.quality.coerceIn(this.imageInfo.imageFormat), + resizeType = this.imageInfo.resizeType, + imageScaleMode = this.imageInfo.imageScaleMode, + rotationDegrees = this.imageInfo.rotationDegrees, + isFlipped = this.imageInfo.isFlipped, + sizeInBytes = 0 + ) + + fun backgroundColorModel(): ColorModel? = backgroundColorForNoAlphaFormats?.let(::ColorModel) + + companion object { + fun from( + name: String, + imageInfo: ImageInfo, + preset: Preset, + keepExif: Boolean? = null, + backgroundColorForNoAlphaFormats: ColorModel? = null + ): ImageExportProfile = ImageExportProfile( + name = name.trim(), + imageInfo = imageInfo.copy( + originalUri = null, + sizeInBytes = 0 + ), + preset = preset, + keepExif = keepExif, + backgroundColorForNoAlphaFormats = backgroundColorForNoAlphaFormats?.colorInt + ) + } +} diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageFormat.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageFormat.kt new file mode 100644 index 0000000..0eee363 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageFormat.kt @@ -0,0 +1,403 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 ImageQuant : Png( + title = "ImageQuant", + compressionTypes = listOf( + CompressionType.Quality(0..100) + ), + 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 LosslessAv1 : Avif( + title = "AV1 Lossless", + compressionTypes = listOf( + CompressionType.Effort(0..2) + ) + ), LosslessMarker + + data object LossyAv1 : Avif( + title = "AV1 Lossy", + compressionTypes = listOf( + CompressionType.Quality(1..100), + CompressionType.Effort(0..2) + ) + ) + + data object LosslessAv2 : Avif( + title = "AV2 Lossless", + compressionTypes = listOf( + CompressionType.Effort(0..2) + ) + ), LosslessMarker + + data object LossyAv2 : Avif( + title = "AV2 Lossy", + compressionTypes = listOf( + CompressionType.Quality(1..100), + CompressionType.Effort(0..2) + ) + ) + } + + sealed class Heic( + title: String, + compressionTypes: List, + extension: String = "heic", + mimeType: MimeType.Single = MimeType.Heic + ) : ImageFormat( + title = title, + extension = extension, + mimeType = mimeType, + compressionTypes = compressionTypes, + canChangeCompressionValue = true + ) { + data object Lossless : Heic( + title = "HEIC Lossless", + compressionTypes = listOf() + ), LosslessMarker + + data object Lossy : Heic( + title = "HEIC Lossy", + compressionTypes = listOf( + CompressionType.Quality(0..100) + ) + ) + + data object HeifLossless : Heic( + title = "HEIF Lossless", + compressionTypes = emptyList(), + extension = "heif", + mimeType = MimeType.Heif + ), LosslessMarker + + data object HeifLossy : Heic( + title = "HEIF Lossy", + compressionTypes = listOf( + CompressionType.Quality(0..100) + ), + extension = "heif", + mimeType = MimeType.Heif + ) + + data object VvcLossless : Heic( + title = "VVC Lossless", + extension = "heif", + mimeType = MimeType.Heif, + compressionTypes = emptyList() + ), LosslessMarker + + data object VvcLossy : Heic( + title = "VVC Lossy", + extension = "heif", + mimeType = MimeType.Heif, + compressionTypes = listOf( + CompressionType.Quality(1..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 } + + fun fromTitle(title: String?): ImageFormat? = when (title) { + "AVIF Lossless" -> Avif.LosslessAv2 + "AVIF Lossy" -> Avif.LossyAv2 + else -> entries.firstOrNull { it.title == title } + } + + 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.LosslessAv2 + typeString.contains("heif") -> Heic.HeifLossless + 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 entries by lazy { + listOf( + Jpg, + Jpeg, + MozJpeg, + Jpegli, + Png.Lossless, + Png.Lossy, + Png.OxiPNG, + Png.ImageQuant, + Bmp, + Webp.Lossless, + Webp.Lossy, + Avif.LosslessAv1, + Avif.LossyAv1, + Avif.LosslessAv2, + Avif.LossyAv2, + Heic.Lossless, + Heic.Lossy, + Heic.HeifLossless, + Heic.HeifLossy, + Heic.VvcLossless, + Heic.VvcLossy, + Jxl.Lossless, + Jxl.Lossy, + Tif, + Tiff, + Jpeg2000.Jp2, + Jpeg2000.J2k, + Qoi, + Ico, + Gif + ) + } + } +} + +val ImageFormat.isLossless get() = this is ImageFormat.LosslessMarker \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageFormatGroup.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageFormatGroup.kt new file mode 100644 index 0000000..838eade --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageFormatGroup.kt @@ -0,0 +1,153 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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, + ImageFormat.Png.ImageQuant + ) + ) + + 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.LosslessAv1, + ImageFormat.Avif.LossyAv1, + ImageFormat.Avif.LosslessAv2, + ImageFormat.Avif.LossyAv2 + ) + ) + + data object Heic : ImageFormatGroup( + title = "HEIC", + formats = listOf( + ImageFormat.Heic.Lossless, + ImageFormat.Heic.Lossy, + ImageFormat.Heic.HeifLossless, + ImageFormat.Heic.HeifLossy, + ImageFormat.Heic.VvcLossless, + ImageFormat.Heic.VvcLossy, + ) + ) + + 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 ImageFormat.Companion.alphaContainedEntries: List by lazy { + ImageFormatGroup.alphaContainedEntries.flatMap { it.formats } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageFrames.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageFrames.kt new file mode 100644 index 0000000..888d149 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageFrames.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageInfo.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageInfo.kt new file mode 100644 index 0000000..3ec3ca9 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageInfo.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageScaleMode.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageScaleMode.kt new file mode 100644 index 0000000..c79bdba --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageScaleMode.kt @@ -0,0 +1,643 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageWithSize.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageWithSize.kt new file mode 100644 index 0000000..8953aac --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageWithSize.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/MetadataTag.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/MetadataTag.kt new file mode 100644 index 0000000..832a096 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/MetadataTag.kt @@ -0,0 +1,477 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + ) + } + } +} diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/Preset.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/Preset.kt new file mode 100644 index 0000000..ff31c69 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/Preset.kt @@ -0,0 +1,59 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) } + } + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/Quality.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/Quality.kt new file mode 100644 index 0000000..39ce658 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/Quality.kt @@ -0,0 +1,187 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.Png.ImageQuant -> { + val value = this as? PngQuant + ?: return PngQuant() + value.copy( + maxColors = value.maxColors.coerceIn(2..1024), + quality = value.quality.coerceIn(0..100), + speed = value.speed.coerceIn(1..10) + ) + } + + is ImageFormat.Avif -> { + val value = this as? Avif + ?: return Avif() + value.copy( + qualityValue = qualityValue.coerceIn(1..100), + effort = effort.coerceIn(0..2) + ) + } + + ImageFormat.Heic.VvcLossless, + ImageFormat.Heic.VvcLossy -> { + val value = this as? Vvc + ?: Vvc(qualityValue = this@Quality.qualityValue.coerceIn(1..100)) + value.copy( + qualityValue = value.qualityValue.coerceIn(1..100) + ) + } + + is ImageFormat.Heic -> { + val value = this as? Heic + ?: Heic(qualityValue = this@Quality.qualityValue) + value.copy( + qualityValue = this@Quality.qualityValue.coerceIn(0..100) + ) + } + + 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(this@Quality.qualityValue.coerceIn(20..100)) + + else -> { + Base(this@Quality.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 Heic -> this == Heic() + is Vvc -> this == Vvc() + is Jxl -> this == Jxl() + is PngLossy -> this == PngLossy() + is Tiff -> this == Tiff() + is PngQuant -> this == PngQuant() + } + + 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 = 2) + val effort: Int = 0, + val chromaSubsampling: AvifChromaSubsampling = AvifChromaSubsampling.Auto + ) : Quality + + data class Heic( + @IntRange(from = 0, to = 100) + override val qualityValue: Int = 100, + val chromaSubsampling: HeicChromaSubsampling = HeicChromaSubsampling.Yuv420 + ) : Quality + + data class Vvc( + @IntRange(from = 1, to = 100) + override val qualityValue: Int = 50, + val chroma: VvcChroma = VvcChroma.YUV_420, + val bitDepth: VvcBitDepth = VvcBitDepth.EIGHT + ) : 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 PngQuant( + @IntRange(from = 0, to = 100) + val quality: Int = 85, + @IntRange(from = 1, to = 10) + val speed: Int = 3, + @IntRange(from = 2) + val maxColors: Int = 512 + ) : Quality { + override val qualityValue: Int = quality + } + + 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 + } + } + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ResizeAnchor.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ResizeAnchor.kt new file mode 100644 index 0000000..c0d07da --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ResizeAnchor.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ResizeType.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ResizeType.kt new file mode 100644 index 0000000..34d41ac --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ResizeType.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + ) + } + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/TiffCompressionScheme.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/TiffCompressionScheme.kt new file mode 100644 index 0000000..5fbb27b --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/TiffCompressionScheme.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 } + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/json/JsonParser.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/json/JsonParser.kt new file mode 100644 index 0000000..fcbedda --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/json/JsonParser.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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? + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/CipherType.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/CipherType.kt new file mode 100644 index 0000000..36d023b --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/CipherType.kt @@ -0,0 +1,138 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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", + "1.2.156.10197.1.104.11", + "1.2.156.10197.1.104.12" + ) + } + + 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 + ) + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/ColorModel.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/ColorModel.kt new file mode 100644 index 0000000..f4ffff3 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/ColorModel.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/DomainAspectRatio.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/DomainAspectRatio.kt new file mode 100644 index 0000000..e37e559 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/DomainAspectRatio.kt @@ -0,0 +1,92 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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!! + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/ExtraDataType.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/ExtraDataType.kt new file mode 100644 index 0000000..0f3da4d --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/ExtraDataType.kt @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 Template(val uri: String) : ExtraDataType + data class Text(val text: String) : ExtraDataType +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/FileModel.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/FileModel.kt new file mode 100644 index 0000000..dc02a7c --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/FileModel.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/FloatSize.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/FloatSize.kt new file mode 100644 index 0000000..0e34e76 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/FloatSize.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/HashingType.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/HashingType.kt new file mode 100644 index 0000000..748fab1 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/HashingType.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + } + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/ImageModel.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/ImageModel.kt new file mode 100644 index 0000000..ef2afda --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/ImageModel.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/IntegerSize.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/IntegerSize.kt new file mode 100644 index 0000000..e2d62d5 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/IntegerSize.kt @@ -0,0 +1,95 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/MimeType.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/MimeType.kt new file mode 100644 index 0000000..9be0edc --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/MimeType.kt @@ -0,0 +1,103 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/OffsetModel.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/OffsetModel.kt new file mode 100644 index 0000000..2a68773 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/OffsetModel.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/Outline.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/Outline.kt new file mode 100644 index 0000000..f63dc39 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/Outline.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/PerformanceClass.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/PerformanceClass.kt new file mode 100644 index 0000000..a8a5c61 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/PerformanceClass.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/Position.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/Position.kt new file mode 100644 index 0000000..173e735 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/Position.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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; + + companion object { + val entriesSorted by lazy { + listOf( + TopLeft, + TopCenter, + TopRight, + CenterLeft, + Center, + CenterRight, + BottomLeft, + BottomCenter, + BottomRight + ) + } + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/Pt.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/Pt.kt new file mode 100644 index 0000000..08fb167 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/Pt.kt @@ -0,0 +1,88 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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)) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/QrType.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/QrType.kt new file mode 100644 index 0000000..bce9050 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/QrType.kt @@ -0,0 +1,252 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/RectModel.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/RectModel.kt new file mode 100644 index 0000000..45cffcc --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/RectModel.kt @@ -0,0 +1,230 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/SecureAlgorithmsMapping.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/SecureAlgorithmsMapping.kt new file mode 100644 index 0000000..2dc9fed --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/SecureAlgorithmsMapping.kt @@ -0,0 +1,499 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + } + } +} diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/SortType.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/SortType.kt new file mode 100644 index 0000000..d3fd143 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/SortType.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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, + Reverse, Shuffle +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/SystemBarsVisibility.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/SystemBarsVisibility.kt new file mode 100644 index 0000000..b4c7cde --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/SystemBarsVisibility.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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] + } + } + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/VisibilityOwner.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/VisibilityOwner.kt new file mode 100644 index 0000000..1ee5595 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/VisibilityOwner.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/AnalyticsManager.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/AnalyticsManager.kt new file mode 100644 index 0000000..3b5236d --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/AnalyticsManager.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + + fun pushMetric( + tag: String, + metric: String + ) + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/Cache.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/Cache.kt new file mode 100644 index 0000000..4885258 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/Cache.kt @@ -0,0 +1,70 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } +} diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/DownloadManager.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/DownloadManager.kt new file mode 100644 index 0000000..3b19381 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/DownloadManager.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/DownloadProgress.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/DownloadProgress.kt new file mode 100644 index 0000000..f39acf5 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/DownloadProgress.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/RemoteResources.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/RemoteResources.kt new file mode 100644 index 0000000..beacb56 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/RemoteResources.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/RemoteResourcesStore.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/RemoteResourcesStore.kt new file mode 100644 index 0000000..b43888a --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/RemoteResourcesStore.kt @@ -0,0 +1,72 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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? + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/resource/ResourceManager.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/resource/ResourceManager.kt new file mode 100644 index 0000000..352980d --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/resource/ResourceManager.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/FailureNotifier.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/FailureNotifier.kt new file mode 100644 index 0000000..3446c13 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/FailureNotifier.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +fun interface FailureNotifier { + fun send(error: Throwable) +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/FileController.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/FileController.kt new file mode 100644 index 0000000..ee9bce0 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/FileController.kt @@ -0,0 +1,72 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 {} + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/FilenameCreator.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/FilenameCreator.kt new file mode 100644 index 0000000..6bce87b --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/FilenameCreator.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/KeepAliveService.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/KeepAliveService.kt new file mode 100644 index 0000000..f44ae22 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/KeepAliveService.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 : FailureNotifier { + 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 { + var isSuccess = false + + try { + initial() + action().also { + isSuccess = true + } + } catch (e: CancellationException) { + onCancel() + throw e + } catch (e: Throwable) { + onFailure(e) + send(e) + null + } finally { + onComplete(isSuccess) + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/ObjectSaver.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/ObjectSaver.kt new file mode 100644 index 0000000..34d09cd --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/ObjectSaver.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/RandomStringGenerator.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/RandomStringGenerator.kt new file mode 100644 index 0000000..9b53d38 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/RandomStringGenerator.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/io/IoCloseable.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/io/IoCloseable.kt new file mode 100644 index 0000000..e8881d4 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/io/IoCloseable.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/io/Readable.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/io/Readable.kt new file mode 100644 index 0000000..8983588 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/io/Readable.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/io/Writeable.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/io/Writeable.kt new file mode 100644 index 0000000..d9ebb07 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/io/Writeable.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/FileSaveTarget.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/FileSaveTarget.kt new file mode 100644 index 0000000..e376bde --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/FileSaveTarget.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/FilenamePattern.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/FilenamePattern.kt new file mode 100644 index 0000000..0458fcc --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/FilenamePattern.kt @@ -0,0 +1,108 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 ParentFolder = FilenamePattern("\\f") + val FileSize = FilenamePattern("\\z") + val Uuid = FilenamePattern("\\u") + + 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 ParentFolderUpper = FilenamePattern("\\f").upper() + val FileSizeUpper = FilenamePattern("\\z").upper() + val UuidUpper = FilenamePattern("\\u").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, + ParentFolder, + Uuid, + PresetInfo, + ScaleMode, + Suffix, + Extension + ) + } + + val upperEntries by lazy { + listOf( + PrefixUpper, + OriginalNameUpper, + DateUpper, + ParentFolderUpper, + UuidUpper, + PresetInfoUpper, + ScaleModeUpper, + SuffixUpper, + ExtensionUpper + ) + } + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/ImageSaveTarget.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/ImageSaveTarget.kt new file mode 100644 index 0000000..3bd1535 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/ImageSaveTarget.kt @@ -0,0 +1,72 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/SaveResult.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/SaveResult.kt new file mode 100644 index 0000000..6c28d99 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/SaveResult.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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, + val savedBytes: Long = 0, + ) : 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 class Skipped( + val uri: String? = null + ) : 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() +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/SaveTarget.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/SaveTarget.kt new file mode 100644 index 0000000..a37ee28 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/SaveTarget.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/transformation/ChainTransformation.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/transformation/ChainTransformation.kt new file mode 100644 index 0000000..e3d63de --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/transformation/ChainTransformation.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.ensureActive + +interface ChainTransformation : Transformation { + + fun getTransformations(): List> + + override suspend fun transform( + input: T, + size: IntegerSize + ): T = coroutineScope { + getTransformations().fold(input) { acc, filter -> + ensureActive() + filter.transform(acc, size) + } + } + +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/transformation/GenericTransformation.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/transformation/GenericTransformation.kt new file mode 100644 index 0000000..1e81a84 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/transformation/GenericTransformation.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 } +) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/transformation/Transformation.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/transformation/Transformation.kt new file mode 100644 index 0000000..4f4ec2b --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/transformation/Transformation.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/ByteUtils.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/ByteUtils.kt new file mode 100644 index 0000000..a8887aa --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/ByteUtils.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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", "") +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Delegates.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Delegates.kt new file mode 100644 index 0000000..d1111f2 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Delegates.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/FileMode.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/FileMode.kt new file mode 100644 index 0000000..14d9b60 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/FileMode.kt @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Flavor.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Flavor.kt new file mode 100644 index 0000000..1b89463 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Flavor.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/IntUtils.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/IntUtils.kt new file mode 100644 index 0000000..7a355c5 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/IntUtils.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/KotlinUtils.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/KotlinUtils.kt new file mode 100644 index 0000000..8a95300 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/KotlinUtils.kt @@ -0,0 +1,114 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 kotlinx.coroutines.flow.transformLatest +import java.io.Closeable +import kotlin.reflect.KClass +import kotlin.time.Duration + + +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 = if (delayMillis > 0) { + conflate().transform { + emit(it) + delay(delayMillis) + } +} else this + + +fun Flow.onEachDebounced( + timeout: Duration, + action: suspend (T) -> Unit +): Flow { + var isFirst = true + + return transformLatest { value -> + emit(value) + + if (isFirst) { + isFirst = false + action(value) + } else { + delay(timeout) + action(value) + } + } +} + +inline fun T.applyUse(block: T.() -> R): R = use(block) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/ListUtils.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/ListUtils.kt new file mode 100644 index 0000000..53e6f55 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/ListUtils.kt @@ -0,0 +1,95 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) } + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/ProgressInputStream.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/ProgressInputStream.kt new file mode 100644 index 0000000..e17611e --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/ProgressInputStream.kt @@ -0,0 +1,64 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Quad.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Quad.kt new file mode 100644 index 0000000..729c486 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Quad.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 +) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Rounding.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Rounding.kt new file mode 100644 index 0000000..f15058c --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Rounding.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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)) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/SmartJob.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/SmartJob.kt new file mode 100644 index 0000000..62cacd6 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/SmartJob.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/StringUtils.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/StringUtils.kt new file mode 100644 index 0000000..b0afe71 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/StringUtils.kt @@ -0,0 +1,114 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 "" } +} + +fun String.slice(start: Int?, end: Int?): String { + val len = length + val realStart = if (start == null) 0 + else if (start < 0) (len + start).coerceAtLeast(0) + else start.coerceAtLeast(0) + + val realEnd = if (end == null) len + else if (end < 0) (len + end).coerceAtLeast(0) + else end.coerceAtMost(len) + + if (realStart >= realEnd || realStart >= len) return "" + return substring(realStart, realEnd) +} + +fun String.transliterate(): String { + val cyrillicToLatin = mapOf( + 'а' to "a", 'б' to "b", 'в' to "v", 'г' to "g", 'д' to "d", 'е' to "e", 'ё' to "yo", + 'ж' to "zh", 'з' to "z", 'и' to "i", 'й' to "j", 'к' to "k", 'л' to "l", 'м' to "m", + 'н' to "n", 'о' to "o", 'п' to "p", 'р' to "r", 'с' to "s", 'т' to "t", 'у' to "u", + 'ф' to "f", 'х' to "kh", 'ц' to "ts", 'ч' to "ch", 'ш' to "sh", 'щ' to "shch", + 'ъ' to "", 'ы' to "y", 'ь' to "", 'э' to "e", 'ю' to "yu", 'я' to "ya", + 'А' to "A", 'Б' to "B", 'В' to "V", 'Г' to "G", 'Д' to "D", 'Е' to "E", 'Ё' to "Yo", + 'Ж' to "Zh", 'З' to "Z", 'И' to "I", 'Й' to "J", 'К' to "K", 'Л' to "L", 'М' to "M", + 'Н' to "N", 'О' to "O", 'П' to "P", 'Р' to "R", 'С' to "S", 'Т' to "T", 'У' to "U", + 'Ф' to "F", 'Х' to "Kh", 'Ц' to "Ts", 'Ч' to "Ch", 'Ш' to "Sh", 'Щ' to "Shch", + 'Ъ' to "", 'Ы' to "Y", 'Ь' to "", 'Э' to "E", 'Ю' to "Yu", 'Я' to "Ya" + ) + + return buildString { + for (char in this@transliterate) { + append(cyrillicToLatin[char] ?: char) + } + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/TimeUtils.kt b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/TimeUtils.kt new file mode 100644 index 0000000..ad6b232 --- /dev/null +++ b/core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/TimeUtils.kt @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() \ No newline at end of file diff --git a/core/domain/src/test/kotlin/com/t8rin/imagetoolbox/core/domain/utils/StringUtilsTest.kt b/core/domain/src/test/kotlin/com/t8rin/imagetoolbox/core/domain/utils/StringUtilsTest.kt new file mode 100644 index 0000000..70262cb --- /dev/null +++ b/core/domain/src/test/kotlin/com/t8rin/imagetoolbox/core/domain/utils/StringUtilsTest.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 org.junit.Assert.assertEquals +import org.junit.Test + +class StringUtilsTest { + + @Test + fun testSlice() { + val s = "0123456789" + assertEquals("0123456789", s.slice(null, null)) + assertEquals("01234", s.slice(0, 5)) + assertEquals("1234", s.slice(1, 5)) + assertEquals("56789", s.slice(5, null)) + assertEquals("01234", s.slice(null, 5)) + assertEquals("789", s.slice(-3, null)) + assertEquals("78", s.slice(-3, -1)) + assertEquals("012", s.slice(null, -7)) + assertEquals("", s.slice(5, 2)) + assertEquals("", s.slice(10, 15)) + assertEquals("9", s.slice(-1, null)) + assertEquals("", s.slice(-1, -1)) + } +} diff --git a/core/filters/.gitignore b/core/filters/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/filters/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/filters/build.gradle.kts b/core/filters/build.gradle.kts new file mode 100644 index 0000000..94e403d --- /dev/null +++ b/core/filters/build.gradle.kts @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} diff --git a/core/filters/src/main/AndroidManifest.xml b/core/filters/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/core/filters/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/data/SideFadePaint.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/data/SideFadePaint.kt new file mode 100644 index 0000000..bee6ed4 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/data/SideFadePaint.kt @@ -0,0 +1,89 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/FilterParamsInteractor.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/FilterParamsInteractor.kt new file mode 100644 index 0000000..6a13082 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/FilterParamsInteractor.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 getRecentFilters(): Flow>> + + suspend fun addRecentFilter(filter: Filter<*>) + + 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) + +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/FilterProvider.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/FilterProvider.kt new file mode 100644 index 0000000..3b7a69a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/FilterProvider.kt @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/ShaderPresetRepository.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/ShaderPresetRepository.kt new file mode 100644 index 0000000..4ae9534 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/ShaderPresetRepository.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.filters.domain.model.shader.ShaderPreset +import kotlinx.coroutines.flow.Flow + +interface ShaderPresetRepository { + + fun getPresets(): Flow> + + suspend fun savePreset( + preset: ShaderPreset, + replacingName: String? = null + ): Result + + suspend fun importPreset(json: String): Result + + suspend fun deletePreset(preset: ShaderPreset) + + fun exportPreset(preset: ShaderPreset): String + +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/Filter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/Filter.kt new file mode 100644 index 0000000..2b0b62e --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/Filter.kt @@ -0,0 +1,428 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.DistortPerspectiveParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.DropShadowParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.EnhancedZoomBlurParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.FlareParams +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.NtscParams +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.SeamCarvingParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.ShaderParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.ShearParams +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.TornEdgeParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.VoronoiCrystallizeParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.WaterDropParams +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 Deuteranopia : 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 Shader : Filter + 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 Distortion : FloatFilter + interface VHSNtsc : Filter + interface ExpandImage : QuadFloatFilter + interface DropShadow : Filter + interface TornEdge : Filter + interface Flare : Filter + interface DistortPerspective : Filter + interface JavaLookAndFeel : SimpleFilter + interface Shear : Filter + interface WaterDrop : Filter + interface HighPass : FloatFilter + interface ColorMask : ColorValueFilter + interface Chrome : PairFloatFilter + interface Dissolve : PairFloatFilter + interface Feedback : QuadFloatFilter + interface LensBlur : QuadFloatFilter + interface Levels : QuadFloatFilter + interface LightEffects : PairFloatFilter + interface Rays : TripleFloatFilter + interface Ripple : QuadFloatFilter + interface AdaptiveBlur : PairFloatFilter + interface AutoWhiteBalance : PairFloatFilter +} + +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 diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/FilterParam.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/FilterParam.kt new file mode 100644 index 0000000..eb08ab1 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/FilterParam.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +) \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/FilterUtils.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/FilterUtils.kt new file mode 100644 index 0000000..57173e3 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/FilterUtils.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/FilterValueWrapper.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/FilterValueWrapper.kt new file mode 100644 index 0000000..f545963 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/FilterValueWrapper.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/TemplateFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/TemplateFilter.kt new file mode 100644 index 0000000..3a1048f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/TemplateFilter.kt @@ -0,0 +1,79 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/BlurEdgeMode.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/BlurEdgeMode.kt new file mode 100644 index 0000000..d7a918f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/BlurEdgeMode.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/ColorMapType.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/ColorMapType.kt new file mode 100644 index 0000000..d622a41 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/ColorMapType.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/FadeSide.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/FadeSide.kt new file mode 100644 index 0000000..258471a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/FadeSide.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/MirrorSide.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/MirrorSide.kt new file mode 100644 index 0000000..565c46c --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/MirrorSide.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/PaletteTransferSpace.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/PaletteTransferSpace.kt new file mode 100644 index 0000000..24e4cc5 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/PaletteTransferSpace.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/PolarCoordinatesType.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/PolarCoordinatesType.kt new file mode 100644 index 0000000..e45d6e8 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/PolarCoordinatesType.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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; +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/PopArtBlendingMode.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/PopArtBlendingMode.kt new file mode 100644 index 0000000..6029aed --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/PopArtBlendingMode.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/SpotHealMode.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/SpotHealMode.kt new file mode 100644 index 0000000..fac3f27 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/SpotHealMode.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/TransferFunc.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/TransferFunc.kt new file mode 100644 index 0000000..cdd3922 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/TransferFunc.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ArcParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ArcParams.kt new file mode 100644 index 0000000..427ab46 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ArcParams.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/AsciiParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/AsciiParams.kt new file mode 100644 index 0000000..f83df07 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/AsciiParams.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/BilaterialBlurParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/BilaterialBlurParams.kt new file mode 100644 index 0000000..5e421bc --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/BilaterialBlurParams.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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, + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/BloomParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/BloomParams.kt new file mode 100644 index 0000000..39b61a0 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/BloomParams.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ChannelMixParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ChannelMixParams.kt new file mode 100644 index 0000000..1fcfbe6 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ChannelMixParams.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ClaheParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ClaheParams.kt new file mode 100644 index 0000000..27dafd3 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ClaheParams.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } + + } + +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/CropOrPerspectiveParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/CropOrPerspectiveParams.kt new file mode 100644 index 0000000..e94bf00 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/CropOrPerspectiveParams.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/DistortPerspectiveParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/DistortPerspectiveParams.kt new file mode 100644 index 0000000..5b8492a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/DistortPerspectiveParams.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 DistortPerspectiveParams( + val topLeft: FloatPair, + val topRight: FloatPair, + val bottomLeft: FloatPair, + val bottomRight: FloatPair, + val clip: Boolean +) { + companion object { + val Default = DistortPerspectiveParams( + topLeft = 0.2f to 0f, + topRight = 0.8f to 0f, + bottomLeft = 0f to 1f, + bottomRight = 1f to 1f, + clip = false + ) + } +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/DropShadowParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/DropShadowParams.kt new file mode 100644 index 0000000..f333619 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/DropShadowParams.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.domain.model.ColorModel + +data class DropShadowParams( + val color: ColorModel = ColorModel(0xFF000000.toInt()), + val offsetX: Float = 0f, + val offsetY: Float = 8f, + val blurRadius: Float = 18f +) { + companion object { + val Default = DropShadowParams() + } +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/EnhancedZoomBlurParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/EnhancedZoomBlurParams.kt new file mode 100644 index 0000000..d7d399f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/EnhancedZoomBlurParams.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/FlareParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/FlareParams.kt new file mode 100644 index 0000000..599157f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/FlareParams.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 FlareParams( + val radius: Float, + val baseAmount: Float, + val ringAmount: Float, + val rayAmount: Float, + val ringWidth: Float, + val centreX: Float, + val centreY: Float, + val color: ColorModel +) { + companion object { + val Default = FlareParams( + radius = 0.3f, + baseAmount = 1f, + ringAmount = 0.2f, + rayAmount = 0.1f, + ringWidth = 1.6f, + centreX = 0.5f, + centreY = 0.5f, + color = Color.White.toArgb().toColorModel() + ) + } +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/GlitchParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/GlitchParams.kt new file mode 100644 index 0000000..da56f44 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/GlitchParams.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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, +) \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/KaleidoscopeParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/KaleidoscopeParams.kt new file mode 100644 index 0000000..cd9c8bd --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/KaleidoscopeParams.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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, + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/LinearGaussianParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/LinearGaussianParams.kt new file mode 100644 index 0000000..700068b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/LinearGaussianParams.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/LinearTiltShiftParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/LinearTiltShiftParams.kt new file mode 100644 index 0000000..7f089f1 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/LinearTiltShiftParams.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/NtscParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/NtscParams.kt new file mode 100644 index 0000000..3b0c37f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/NtscParams.kt @@ -0,0 +1,86 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 NtscParams( + val amount: Float = 1f, + val chromaBleed: Float = 1f, + val tapeWear: Float = 1f, + val noise: Float = 1f, + val tracking: Float = 1f, + val seed: Int = 0, + val lumaSmear: Float = 0.5f, + val compositeSharpening: Float = 1f, + val ringing: Float = 4f, + val snow: Float = 1f, + val useField: Int = 4, + val filterType: Int = 1, + val inputLumaFilter: Int = 2, + val chromaLowpassIn: Int = 2, + val chromaDemodulation: Int = 1, + val videoScanlinePhaseShift: Int = 2, + val videoScanlinePhaseShiftOffset: Int = 0, + val headSwitchingEnabled: Boolean = true, + val headSwitchingHeight: Int = 8, + val headSwitchingOffset: Int = 3, + val headSwitchingHorizontalShift: Float = 72f, + val headSwitchingMidLinePosition: Float = 0.95f, + val headSwitchingMidLineJitter: Float = 0.03f, + val trackingNoiseEnabled: Boolean = true, + val trackingNoiseHeight: Int = 12, + val trackingNoiseWaveIntensity: Float = 15f, + val trackingNoiseSnowIntensity: Float = 0.025f, + val trackingNoiseSnowAnisotropy: Float = 0.25f, + val trackingNoiseNoiseIntensity: Float = 0.25f, + val compositeNoiseEnabled: Boolean = true, + val compositeNoiseFrequency: Float = 0.5f, + val compositeNoiseIntensity: Float = 0.05f, + val compositeNoiseDetail: Int = 1, + val ringingEnabled: Boolean = true, + val ringingFrequency: Float = 0.45f, + val ringingPower: Float = 4f, + val lumaNoiseEnabled: Boolean = true, + val lumaNoiseFrequency: Float = 0.5f, + val lumaNoiseIntensity: Float = 0.01f, + val lumaNoiseDetail: Int = 1, + val chromaNoiseEnabled: Boolean = true, + val chromaNoiseFrequency: Float = 0.05f, + val chromaNoiseIntensity: Float = 0.1f, + val chromaNoiseDetail: Int = 2, + val snowIntensity: Float = 0.00025f, + val snowAnisotropy: Float = 0.5f, + val chromaPhaseNoiseIntensity: Float = 0.001f, + val chromaPhaseError: Float = 0f, + val chromaDelayHorizontal: Float = 0f, + val chromaDelayVertical: Int = 0, + val vhsEnabled: Boolean = true, + val vhsTapeSpeed: Int = 2, + val vhsChromaLoss: Float = 0.000025f, + val vhsSharpenIntensity: Float = 0.25f, + val vhsSharpenFrequency: Float = 1f, + val vhsEdgeWaveIntensity: Float = 0.5f, + val vhsEdgeWaveSpeed: Float = 4f, + val vhsEdgeWaveFrequency: Float = 0.05f, + val vhsEdgeWaveDetail: Int = 2, + val chromaLowpassOut: Int = 2, + val scaleEnabled: Boolean = true, + val scaleHorizontal: Float = 1f, + val scaleVertical: Float = 1f, + val scaleFactorX: Float = 1f, + val scaleFactorY: Float = 1f +) \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/PinchParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/PinchParams.kt new file mode 100644 index 0000000..501bdb2 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/PinchParams.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/RadialTiltShiftParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/RadialTiltShiftParams.kt new file mode 100644 index 0000000..898c6da --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/RadialTiltShiftParams.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/RubberStampParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/RubberStampParams.kt new file mode 100644 index 0000000..cd8a931 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/RubberStampParams.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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(), + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/SeamCarvingParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/SeamCarvingParams.kt new file mode 100644 index 0000000..5bc3ed1 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/SeamCarvingParams.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.domain.model.FileModel +import com.t8rin.imagetoolbox.core.domain.model.IntegerSize + +data class SeamCarvingParams( + val size: IntegerSize = IntegerSize.Zero, + val maskFile: FileModel = FileModel(""), + val useBackwardEnergy: Boolean = false, + val useMaskAsRemoval: Boolean = false, +) \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ShaderParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ShaderParams.kt new file mode 100644 index 0000000..9f5a9b5 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ShaderParams.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.shader.ShaderPreset +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderValue + +data class ShaderParams( + val preset: ShaderPreset? = null, + val values: Map = emptyMap() +) { + val resolvedValues: Map + get() = preset?.params.orEmpty().associate { param -> + param.name to (values[param.name]?.takeIf { it.type == param.type } + ?: param.defaultValue) + } + + fun withPreset(preset: ShaderPreset?): ShaderParams = copy( + preset = preset, + values = preset?.defaultValues.orEmpty() + ) +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ShearParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ShearParams.kt new file mode 100644 index 0000000..7e2f8ff --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ShearParams.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 ShearParams( + val xAngle: Float = 15f, + val yAngle: Float = 0f, + val resize: Boolean = true +) diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/SideFadeParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/SideFadeParams.kt new file mode 100644 index 0000000..316610e --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/SideFadeParams.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/SmearParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/SmearParams.kt new file mode 100644 index 0000000..ce2ebca --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/SmearParams.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/SparkleParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/SparkleParams.kt new file mode 100644 index 0000000..f95927b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/SparkleParams.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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() + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ToneCurvesParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ToneCurvesParams.kt new file mode 100644 index 0000000..dfc91de --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ToneCurvesParams.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/TornEdgeParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/TornEdgeParams.kt new file mode 100644 index 0000000..e7df359 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/TornEdgeParams.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 TornEdgeParams( + val toothHeight: Int = 16, + val horizontalToothRange: Int = 22, + val verticalToothRange: Int = 22, + val top: Boolean = true, + val right: Boolean = true, + val bottom: Boolean = true, + val left: Boolean = true +) { + companion object { + val Default = TornEdgeParams() + } +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/VoronoiCrystallizeParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/VoronoiCrystallizeParams.kt new file mode 100644 index 0000000..073e4bb --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/VoronoiCrystallizeParams.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/WaterDropParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/WaterDropParams.kt new file mode 100644 index 0000000..6ebdbd1 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/WaterDropParams.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 WaterDropParams( + val wavelength: Float = 0.1f, + val amplitude: Float = 0.05f, + val phase: Float = 0f, + val centreX: Float = 0.5f, + val centreY: Float = 0.5f, + val radius: Float = 0.5f +) diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/WaterParams.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/WaterParams.kt new file mode 100644 index 0000000..2142d54 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/WaterParams.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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, +) \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/shader/ShaderParser.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/shader/ShaderParser.kt new file mode 100644 index 0000000..aa373e0 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/shader/ShaderParser.kt @@ -0,0 +1,468 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.shader + +import com.t8rin.imagetoolbox.core.domain.json.JsonParser +import javax.inject.Inject + +class ShaderParser @Inject constructor( + private val jsonParser: JsonParser +) { + fun parse(json: String): Result = runCatching { + val trimmedJson = json.trim() + + if (trimmedJson.isBlank()) { + throw ShaderParseException(ShaderParseError.EmptyFile) + } + if (!trimmedJson.isLikelyShaderPresetJson()) { + throw ShaderParseException(ShaderParseError.NotItShaderJson) + } + + val rawPreset = jsonParser.fromJson( + json = trimmedJson, + type = ShaderPresetJson::class.java + ) ?: throw ShaderParseException(ShaderParseError.InvalidJson) + + rawPreset.toShaderPreset() + } + + private fun ShaderPresetJson.toShaderPreset(): ShaderPreset { + val errors = mutableListOf() + val parsedParams = params.orEmpty().mapIndexedNotNull { index, param -> + param.toShaderParam(index, errors) + } + + val preset = ShaderPreset( + version = version ?: 0, + name = name?.trim().orEmpty(), + params = parsedParams, + shader = shader.orEmpty() + ) + + if (version == null) { + errors += ShaderParseError.MissingField("version") + } + if (name == null) { + errors += ShaderParseError.MissingField("name") + } + if (shader == null) { + errors += ShaderParseError.MissingField("shader") + } + + if (errors.isNotEmpty()) { + throw ShaderParseException(errors) + } + + return preset + } + + private fun ShaderParamJson.toShaderParam( + index: Int, + errors: MutableList + ): ShaderParam? { + val startErrorCount = errors.size + val path = "params[$index]" + val parsedName = name?.trim().orEmpty() + val parsedType = type?.trim() + ?.let(ShaderParamType::fromSerialName) + + if (name == null) { + errors += ShaderParseError.MissingParamField("$path.name") + } + if (type == null) { + errors += ShaderParseError.MissingParamField("$path.type") + } else if (parsedType == null) { + errors += ShaderParseError.UnsupportedParamType( + path = "$path.type", + type = type, + supportedTypes = supportedTypes() + ) + } + + val defaultValue = parsedType?.let { + parseValue( + value = default, + type = it, + path = "$path.default", + isRequired = true, + errors = errors + ) + } + val minValue = parsedType?.let { + parseValue( + value = min, + type = it, + path = "$path.min", + isRequired = false, + errors = errors + ) + } + val maxValue = parsedType?.let { + parseValue( + value = max, + type = it, + path = "$path.max", + isRequired = false, + errors = errors + ) + } + + return if ( + errors.size == startErrorCount && + parsedType != null && + defaultValue != null + ) { + ShaderParam( + name = parsedName, + type = parsedType, + defaultValue = defaultValue, + minValue = minValue, + maxValue = maxValue + ) + } else { + null + } + } + + private fun parseValue( + value: Any?, + type: ShaderParamType, + path: String, + isRequired: Boolean, + errors: MutableList + ): ShaderValue? { + if (value == null) { + if (isRequired) { + errors += ShaderParseError.MissingValue(path, type.serialName) + } + return null + } + + return when (type) { + ShaderParamType.Float -> parseFloat(value, path, errors) + ?.let(ShaderValue::FloatValue) + + ShaderParamType.Int -> parseInt(value, path, errors) + ?.let(ShaderValue::IntValue) + + ShaderParamType.Bool -> parseBool(value, path, errors) + ?.let(ShaderValue::BoolValue) + + ShaderParamType.Color -> parseColor(value, path, errors) + ShaderParamType.Vec2 -> parseVec2(value, path, errors) + } + } + + private fun parseFloat( + value: Any?, + path: String, + errors: MutableList + ): Float? { + val parsed = (value as? Number)?.toFloat() + return if (parsed != null && parsed.isFinite()) { + parsed + } else { + errors += ShaderParseError.FiniteNumberExpected(path) + null + } + } + + private fun parseInt( + value: Any?, + path: String, + errors: MutableList + ): Int? { + val number = value as? Number + val parsed = number?.toDouble() + + return if ( + parsed != null && + parsed.isFinite() && + parsed % 1.0 == 0.0 && + parsed in Int.MIN_VALUE.toDouble()..Int.MAX_VALUE.toDouble() + ) { + parsed.toInt() + } else { + errors += ShaderParseError.IntegerExpected(path) + null + } + } + + private fun parseBool( + value: Any?, + path: String, + errors: MutableList + ): Boolean? = value as? Boolean ?: run { + errors += ShaderParseError.BoolExpected(path) + null + } + + private fun parseColor( + value: Any, + path: String, + errors: MutableList + ): ShaderValue.ColorValue? = when (value) { + is String -> parseColorString(value, path, errors) + is List<*> -> parseColorList(value, path, errors) + is Map<*, *> -> parseColorMap(value, path, errors) + else -> { + errors += ShaderParseError.ColorExpected(path) + null + } + } + + private fun parseColorString( + value: String, + path: String, + errors: MutableList + ): ShaderValue.ColorValue? { + val hex = value.removePrefix("#") + if (hex.length != RGB_HEX_LENGTH && hex.length != RGBA_HEX_LENGTH) { + errors += ShaderParseError.ColorFormatExpected(path) + return null + } + + return runCatching { + val red = hex.substring(0, 2).toInt(radix = HEX_RADIX) + val green = hex.substring(2, 4).toInt(radix = HEX_RADIX) + val blue = hex.substring(4, 6).toInt(radix = HEX_RADIX) + val alpha = if (hex.length == RGBA_HEX_LENGTH) { + hex.substring(6, 8).toInt(radix = HEX_RADIX) + } else { + OPAQUE_CHANNEL + } + ShaderValue.ColorValue(red, green, blue, alpha) + }.getOrElse { + errors += ShaderParseError.HexColorExpected(path) + null + } + } + + private fun parseColorList( + value: List<*>, + path: String, + errors: MutableList + ): ShaderValue.ColorValue? { + if (value.size !in RGB_CHANNEL_COUNT..RGBA_CHANNEL_COUNT) { + errors += ShaderParseError.ColorChannelCountExpected(path) + return null + } + + val channels = value.mapIndexedNotNull { index, channel -> + parseColorChannel( + value = channel, + path = "$path[$index]", + errors = errors + ) + } + + return if (channels.size == value.size) { + ShaderValue.ColorValue( + red = channels[0], + green = channels[1], + blue = channels[2], + alpha = channels.getOrElse(3) { OPAQUE_CHANNEL } + ) + } else { + null + } + } + + private fun parseColorMap( + value: Map<*, *>, + path: String, + errors: MutableList + ): ShaderValue.ColorValue? { + val red = parseColorChannel(value["r"] ?: value["red"], "$path.r", errors) + val green = parseColorChannel(value["g"] ?: value["green"], "$path.g", errors) + val blue = parseColorChannel(value["b"] ?: value["blue"], "$path.b", errors) + val alpha = (value["a"] ?: value["alpha"])?.let { + parseColorChannel(it, "$path.a", errors) + } ?: OPAQUE_CHANNEL + + return if (red != null && green != null && blue != null) { + ShaderValue.ColorValue(red, green, blue, alpha) + } else { + null + } + } + + private fun parseColorChannel( + value: Any?, + path: String, + errors: MutableList + ): Int? { + val parsed = parseInt(value, path, errors) + return if (parsed != null && parsed in CHANNEL_RANGE) { + parsed + } else { + if (parsed != null) { + errors += ShaderParseError.ColorChannelRangeExpected(path) + } + null + } + } + + private fun parseVec2( + value: Any, + path: String, + errors: MutableList + ): ShaderValue.Vec2Value? = when (value) { + is List<*> -> parseVec2List(value, path, errors) + is Map<*, *> -> parseVec2Map(value, path, errors) + else -> { + errors += ShaderParseError.Vec2Expected(path) + null + } + } + + private fun parseVec2List( + value: List<*>, + path: String, + errors: MutableList + ): ShaderValue.Vec2Value? { + if (value.size != VEC2_COMPONENT_COUNT) { + errors += ShaderParseError.Vec2ComponentCountExpected(path) + return null + } + + val x = parseFloat(value[0], "$path[0]", errors) + val y = parseFloat(value[1], "$path[1]", errors) + + return if (x != null && y != null) { + ShaderValue.Vec2Value(x, y) + } else { + null + } + } + + private fun parseVec2Map( + value: Map<*, *>, + path: String, + errors: MutableList + ): ShaderValue.Vec2Value? { + val x = parseFloat(value["x"], "$path.x", errors) + val y = parseFloat(value["y"], "$path.y", errors) + + return if (x != null && y != null) { + ShaderValue.Vec2Value(x, y) + } else { + null + } + } + + private fun supportedTypes(): String = + ShaderParamType.entries.joinToString { it.serialName } + + private fun String.isLikelyShaderPresetJson(): Boolean = + startsWith("{") && + endsWith("}") && + REQUIRED_FIELDS.all { field -> contains("\"$field\"") } + + private companion object { + const val HEX_RADIX = 16 + const val RGB_HEX_LENGTH = 6 + const val RGBA_HEX_LENGTH = 8 + const val RGB_CHANNEL_COUNT = 3 + const val RGBA_CHANNEL_COUNT = 4 + const val VEC2_COMPONENT_COUNT = 2 + const val OPAQUE_CHANNEL = 255 + + val CHANNEL_RANGE = 0..255 + val REQUIRED_FIELDS = listOf("version", "name", "shader") + } +} + +class ShaderParseException( + val errors: List +) : IllegalArgumentException( + errors.joinToString( + separator = "\n", + transform = ShaderParseError::englishMessage + ) +) { + constructor(error: ShaderParseError) : this(listOf(error)) +} + +sealed interface ShaderParseError { + data object EmptyFile : ShaderParseError + data object NotItShaderJson : ShaderParseError + data object InvalidJson : ShaderParseError + data class MissingField(val field: String) : ShaderParseError + data class MissingParamField(val path: String) : ShaderParseError + data class UnsupportedParamType( + val path: String, + val type: String, + val supportedTypes: String + ) : ShaderParseError + + data class MissingValue(val path: String, val type: String) : ShaderParseError + data class FiniteNumberExpected(val path: String) : ShaderParseError + data class IntegerExpected(val path: String) : ShaderParseError + data class BoolExpected(val path: String) : ShaderParseError + data class ColorExpected(val path: String) : ShaderParseError + data class ColorFormatExpected(val path: String) : ShaderParseError + data class HexColorExpected(val path: String) : ShaderParseError + data class ColorChannelCountExpected(val path: String) : ShaderParseError + data class ColorChannelRangeExpected(val path: String) : ShaderParseError + data class Vec2Expected(val path: String) : ShaderParseError + data class Vec2ComponentCountExpected(val path: String) : ShaderParseError +} + +fun ShaderParseError.englishMessage(): String = when (this) { + ShaderParseError.EmptyFile -> "Shader file is empty." + ShaderParseError.NotItShaderJson -> + "Shader file must be a .itshader JSON object with version, name, and shader fields." + + ShaderParseError.InvalidJson -> + "Shader file is not valid JSON or does not match the .itshader format." + + is ShaderParseError.MissingField -> "Field '$field' is required." + is ShaderParseError.MissingParamField -> "$path is required." + is ShaderParseError.UnsupportedParamType -> + "$path '$type' is not supported. Supported types: $supportedTypes." + + is ShaderParseError.MissingValue -> "$path is required for '$type' parameters." + is ShaderParseError.FiniteNumberExpected -> "$path must be a finite number." + is ShaderParseError.IntegerExpected -> "$path must be an integer." + is ShaderParseError.BoolExpected -> "$path must be true or false." + is ShaderParseError.ColorExpected -> + "$path must be a color string, RGB/RGBA array, or color object." + + is ShaderParseError.ColorFormatExpected -> "$path must use #RRGGBB or #RRGGBBAA format." + is ShaderParseError.HexColorExpected -> "$path must contain valid hexadecimal color channels." + is ShaderParseError.ColorChannelCountExpected -> "$path must contain 3 or 4 color channels." + is ShaderParseError.ColorChannelRangeExpected -> "$path must be between 0 and 255." + is ShaderParseError.Vec2Expected -> "$path must be a two-number array or an object with x and y." + is ShaderParseError.Vec2ComponentCountExpected -> "$path must contain exactly 2 numbers." +} + +private data class ShaderPresetJson( + val version: Int? = null, + val name: String? = null, + val params: List? = null, + val shader: String? = null +) + +private data class ShaderParamJson( + val name: String? = null, + val type: String? = null, + val default: Any? = null, + val min: Any? = null, + val max: Any? = null +) diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/shader/ShaderPreset.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/shader/ShaderPreset.kt new file mode 100644 index 0000000..8361d9c --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/shader/ShaderPreset.kt @@ -0,0 +1,111 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.shader + +data class ShaderPreset( + val version: Int, + val name: String, + val params: List = emptyList(), + val shader: String +) { + val defaultValues: Map + get() = params.associate { it.name to it.defaultValue } +} + +data class ShaderParam( + val name: String, + val type: ShaderParamType, + val defaultValue: ShaderValue, + val minValue: ShaderValue? = null, + val maxValue: ShaderValue? = null +) + +enum class ShaderParamType( + val serialName: String, + val uniformType: String +) { + Float(serialName = "float", uniformType = "float"), + Int(serialName = "int", uniformType = "int"), + Bool(serialName = "bool", uniformType = "bool"), + Color(serialName = "color", uniformType = "vec4"), + Vec2(serialName = "vec2", uniformType = "vec2"); + + companion object { + fun fromSerialName(value: String): ShaderParamType? = + entries.firstOrNull { it.serialName == value.lowercase() } + } +} + +sealed interface ShaderValue { + val type: ShaderParamType + + data class FloatValue( + val value: Float + ) : ShaderValue { + init { + require(value.isFinite()) { "Float shader values must be finite." } + } + + override val type: ShaderParamType = ShaderParamType.Float + } + + data class IntValue( + val value: Int + ) : ShaderValue { + override val type: ShaderParamType = ShaderParamType.Int + } + + data class BoolValue( + val value: Boolean + ) : ShaderValue { + override val type: ShaderParamType = ShaderParamType.Bool + } + + data class ColorValue( + val red: Int, + val green: Int, + val blue: Int, + val alpha: Int = OPAQUE_CHANNEL + ) : ShaderValue { + init { + require(red in CHANNEL_RANGE) { "Red channel must be between 0 and 255." } + require(green in CHANNEL_RANGE) { "Green channel must be between 0 and 255." } + require(blue in CHANNEL_RANGE) { "Blue channel must be between 0 and 255." } + require(alpha in CHANNEL_RANGE) { "Alpha channel must be between 0 and 255." } + } + + override val type: ShaderParamType = ShaderParamType.Color + + companion object { + private const val OPAQUE_CHANNEL = 255 + private val CHANNEL_RANGE = 0..255 + } + } + + data class Vec2Value( + val x: Float, + val y: Float + ) : ShaderValue { + init { + require(x.isFinite()) { "Vec2 x value must be finite." } + require(y.isFinite()) { "Vec2 y value must be finite." } + } + + override val type: ShaderParamType = ShaderParamType.Vec2 + } +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/shader/ShaderPresetCodec.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/shader/ShaderPresetCodec.kt new file mode 100644 index 0000000..7d033ba --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/shader/ShaderPresetCodec.kt @@ -0,0 +1,84 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.shader + +fun ShaderPreset.toItShaderJson(): String = buildString { + appendLine("{") + appendLine(" \"version\": $version,") + appendLine(" \"name\": \"${name.escapeJson()}\",") + appendLine(" \"params\": [") + params.forEachIndexed { index, param -> + appendLine(" {") + appendLine(" \"name\": \"${param.name.escapeJson()}\",") + appendLine(" \"type\": \"${param.type.serialName}\",") + appendLine(" \"default\": ${param.defaultValue.toJsonValue()}${if (param.minValue != null || param.maxValue != null) "," else ""}") + param.minValue?.let { + appendLine(" \"min\": ${it.toJsonValue()}${if (param.maxValue != null) "," else ""}") + } + param.maxValue?.let { + appendLine(" \"max\": ${it.toJsonValue()}") + } + append(" }") + if (index != params.lastIndex) append(",") + appendLine() + } + appendLine(" ],") + appendLine(" \"shader\": \"${shader.escapeJson()}\"") + append("}") +} + +private fun ShaderValue.toJsonValue(): String = when (this) { + is ShaderValue.FloatValue -> value.toString() + is ShaderValue.IntValue -> value.toString() + is ShaderValue.BoolValue -> value.toString() + is ShaderValue.ColorValue -> "\"#${red.hex()}${green.hex()}${blue.hex()}${alpha.hex()}\"" + is ShaderValue.Vec2Value -> "[$x, $y]" +} + +private fun String.escapeJson(): String = buildString { + this@escapeJson.forEach { char -> + when (char) { + '\\' -> append("\\\\") + '"' -> append("\\\"") + '\b' -> append("\\b") + '\u000C' -> append("\\f") + '\n' -> append("\\n") + '\r' -> append("\\r") + '\t' -> append("\\t") + else -> { + if (char.code < CONTROL_CHAR_LIMIT) { + append("\\u") + append(char.code.toString(HEX_RADIX).padStart(UNICODE_ESCAPE_LENGTH, '0')) + } else { + append(char) + } + } + } + } +} + +private fun Int.hex(): String = coerceIn(COLOR_MIN, COLOR_MAX) + .toString(HEX_RADIX) + .padStart(HEX_CHANNEL_LENGTH, '0') + +private const val COLOR_MIN = 0 +private const val COLOR_MAX = 255 +private const val HEX_RADIX = 16 +private const val HEX_CHANNEL_LENGTH = 2 +private const val CONTROL_CHAR_LIMIT = 0x20 +private const val UNICODE_ESCAPE_LENGTH = 4 diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/shader/ShaderSourceSafety.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/shader/ShaderSourceSafety.kt new file mode 100644 index 0000000..271c209 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/shader/ShaderSourceSafety.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.shader + +fun String.glslUnsafeCharacters(): List = + asSequence() + .filterNot(Char::isGlslSafeCharacter) + .distinct() + .toList() + +fun String.isGlslSourceSafe(): Boolean = + all(Char::isGlslSafeCharacter) + +private fun Char.isGlslSafeCharacter(): Boolean = + this == '\n' || this == '\r' || this == '\t' || code in SAFE_PRINTABLE_ASCII_RANGE + +private val SAFE_PRINTABLE_ASCII_RANGE = 32..126 diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/shader/ShaderValidator.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/shader/ShaderValidator.kt new file mode 100644 index 0000000..06290b9 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/shader/ShaderValidator.kt @@ -0,0 +1,544 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.shader + +data object ShaderValidator { + fun validate(preset: ShaderPreset): List = buildList { + if (preset.version != SUPPORTED_VERSION) { + add(ShaderValidationError.UnsupportedVersion(preset.version, SUPPORTED_VERSION)) + } + if (preset.name.isBlank()) { + add(ShaderValidationError.BlankName) + } + if (preset.shader.isBlank()) { + add(ShaderValidationError.BlankSource) + } + validateShaderSourceCharacters(preset.shader) + + validateParams(preset.params) + + val shaderSource = preset.shader.withoutComments() + if (preset.shader.isNotBlank()) { + val shaderSignature = shaderSource.shaderSignature() + validateShaderContract(shaderSignature) + validateSingleInputTexture(shaderSignature.samplerUniforms) + validateUnsupportedShaderFeatures(shaderSource) + validateUniforms(shaderSignature.uniformTypes, preset.params) + } + } + + private fun MutableList.validateShaderSourceCharacters(shader: String) { + val unsafeCharacters = shader.glslUnsafeCharacters() + if (unsafeCharacters.isNotEmpty()) { + add( + ShaderValidationError.UnsupportedCharacters( + unsafeCharacters.joinToString { it.readableName() } + ) + ) + } + } + + private fun MutableList.validateParams(params: List) { + params.groupBy { it.name } + .filterValues { it.size > 1 } + .keys + .forEach { add(ShaderValidationError.DuplicateParameter(it)) } + + params.forEach { param -> + if (param.name.isBlank()) { + add(ShaderValidationError.BlankParameterName) + } + if (param.name in RESERVED_NAMES) { + add(ShaderValidationError.ReservedParameterName(param.name)) + } + if (param.name.isNotBlank() && !GLSL_IDENTIFIER.matches(param.name)) { + add(ShaderValidationError.InvalidParameterName(param.name)) + } + validateValueType(param, param.defaultValue, "default") + param.minValue?.let { validateValueType(param, it, "min") } + param.maxValue?.let { validateValueType(param, it, "max") } + validateBounds(param) + } + } + + private fun MutableList.validateValueType( + param: ShaderParam, + value: ShaderValue, + fieldName: String + ) { + if (value.type != param.type) { + add( + ShaderValidationError.InvalidValueType( + name = param.name, + fieldName = fieldName, + actualType = value.type.serialName, + expectedType = param.type.serialName + ) + ) + } + } + + private fun MutableList.validateBounds(param: ShaderParam) { + val minValue = param.minValue + val maxValue = param.maxValue + if (minValue == null && maxValue == null) return + if (minValue != null && minValue.type != param.type) return + if (maxValue != null && maxValue.type != param.type) return + + when (param.type) { + ShaderParamType.Float -> validateFloatBounds( + name = param.name, + defaultValue = (param.defaultValue as? ShaderValue.FloatValue)?.value, + minValue = (minValue as? ShaderValue.FloatValue)?.value, + maxValue = (maxValue as? ShaderValue.FloatValue)?.value + ) + + ShaderParamType.Int -> validateIntBounds( + name = param.name, + defaultValue = (param.defaultValue as? ShaderValue.IntValue)?.value, + minValue = (minValue as? ShaderValue.IntValue)?.value, + maxValue = (maxValue as? ShaderValue.IntValue)?.value + ) + + ShaderParamType.Vec2 -> validateVec2Bounds( + name = param.name, + defaultValue = param.defaultValue as? ShaderValue.Vec2Value, + minValue = minValue as? ShaderValue.Vec2Value, + maxValue = maxValue as? ShaderValue.Vec2Value + ) + + ShaderParamType.Color -> validateColorBounds( + name = param.name, + defaultValue = param.defaultValue as? ShaderValue.ColorValue, + minValue = minValue as? ShaderValue.ColorValue, + maxValue = maxValue as? ShaderValue.ColorValue + ) + + ShaderParamType.Bool -> { + add(ShaderValidationError.BoolBounds(param.name)) + } + } + } + + private fun MutableList.validateFloatBounds( + name: String, + defaultValue: Float?, + minValue: Float?, + maxValue: Float? + ) { + if (minValue != null && maxValue != null && minValue > maxValue) { + add(ShaderValidationError.MinGreaterThanMax(name)) + } + if (defaultValue != null && minValue != null && defaultValue < minValue) { + add(ShaderValidationError.DefaultLowerThanMin(name)) + } + if (defaultValue != null && maxValue != null && defaultValue > maxValue) { + add(ShaderValidationError.DefaultGreaterThanMax(name)) + } + } + + private fun MutableList.validateIntBounds( + name: String, + defaultValue: Int?, + minValue: Int?, + maxValue: Int? + ) { + if (minValue != null && maxValue != null && minValue > maxValue) { + add(ShaderValidationError.MinGreaterThanMax(name)) + } + if (defaultValue != null && minValue != null && defaultValue < minValue) { + add(ShaderValidationError.DefaultLowerThanMin(name)) + } + if (defaultValue != null && maxValue != null && defaultValue > maxValue) { + add(ShaderValidationError.DefaultGreaterThanMax(name)) + } + } + + private fun MutableList.validateVec2Bounds( + name: String, + defaultValue: ShaderValue.Vec2Value?, + minValue: ShaderValue.Vec2Value?, + maxValue: ShaderValue.Vec2Value? + ) { + validateFloatBounds( + name = "$name.x", + defaultValue = defaultValue?.x, + minValue = minValue?.x, + maxValue = maxValue?.x + ) + validateFloatBounds( + name = "$name.y", + defaultValue = defaultValue?.y, + minValue = minValue?.y, + maxValue = maxValue?.y + ) + } + + private fun MutableList.validateColorBounds( + name: String, + defaultValue: ShaderValue.ColorValue?, + minValue: ShaderValue.ColorValue?, + maxValue: ShaderValue.ColorValue? + ) { + validateIntBounds( + name = "$name.red", + defaultValue = defaultValue?.red, + minValue = minValue?.red, + maxValue = maxValue?.red + ) + validateIntBounds( + name = "$name.green", + defaultValue = defaultValue?.green, + minValue = minValue?.green, + maxValue = maxValue?.green + ) + validateIntBounds( + name = "$name.blue", + defaultValue = defaultValue?.blue, + minValue = minValue?.blue, + maxValue = maxValue?.blue + ) + validateIntBounds( + name = "$name.alpha", + defaultValue = defaultValue?.alpha, + minValue = minValue?.alpha, + maxValue = maxValue?.alpha + ) + } + + private fun MutableList.validateShaderContract(shaderSignature: ShaderSignature) { + if (shaderSignature.uniformTypes[INPUT_TEXTURE_NAME] != "sampler2D") { + add(ShaderValidationError.MissingInputTexture(INPUT_TEXTURE_NAME)) + } + if (!shaderSignature.hasTextureCoordinateVarying) { + add(ShaderValidationError.MissingTextureCoordinate(TEXTURE_COORDINATE_NAME)) + } + if (!shaderSignature.hasMainFunction) { + add(ShaderValidationError.MissingMainFunction) + } + if (!shaderSignature.hasFragmentColorOutput) { + add(ShaderValidationError.MissingFragmentColor) + } + } + + private fun MutableList.validateUnsupportedShaderFeatures(shaderSource: String) { + if (shaderSource.hasIdentifier("mainImage")) { + add(ShaderValidationError.ShaderToyMainImage) + } + if (shaderSource.hasIdentifier("iTime")) { + add(ShaderValidationError.ShaderToyITime) + } + if (shaderSource.hasIdentifier("iFrame")) { + add(ShaderValidationError.ShaderToyIFrame) + } + if (shaderSource.hasIdentifier("iResolution")) { + add(ShaderValidationError.ShaderToyIResolution) + } + if (shaderSource.hasIdentifierWithPrefix("iChannel")) { + add(ShaderValidationError.ShaderToyIChannel) + } + if (shaderSource.hasIdentifier("samplerExternalOES")) { + add(ShaderValidationError.ExternalTextures) + } + if (shaderSource.hasIdentifier("GL_OES_EGL_image_external")) { + add(ShaderValidationError.ExternalTextureExtensions) + } + if (shaderSource.lineSequence().any { it.trimStart().startsWith("#pragma parameter") }) { + add(ShaderValidationError.LibretroParameters) + } + } + + private fun MutableList.validateSingleInputTexture( + samplerUniforms: List> + ) { + samplerUniforms + .filterNot { (_, samplerName) -> samplerName == INPUT_TEXTURE_NAME } + .forEach { (samplerType, samplerName) -> + add( + ShaderValidationError.ExtraInputTexture(samplerType, samplerName) + ) + } + } + + private fun MutableList.validateUniforms( + uniformTypes: Map, + params: List + ) { + params.forEach { param -> + val uniformType = uniformTypes[param.name] + when { + uniformType == null -> add( + ShaderValidationError.MissingUniform( + name = param.name, + expectedType = param.type.uniformType + ) + ) + + uniformType != param.type.uniformType -> add( + ShaderValidationError.WrongUniformType( + name = param.name, + actualType = uniformType, + expectedType = param.type.uniformType + ) + ) + } + } + } + + private fun String.withoutComments(): String = buildString(length) { + var index = 0 + while (index < this@withoutComments.length) { + val current = this@withoutComments[index] + val next = this@withoutComments.getOrNull(index + 1) + when (current) { + '/' if next == '/' -> { + index += 2 + while (index < this@withoutComments.length && this@withoutComments[index] != '\n') { + index += 1 + } + } + + '/' if next == '*' -> { + index += 2 + while ( + index + 1 < this@withoutComments.length && + !(this@withoutComments[index] == '*' && this@withoutComments[index + 1] == '/') + ) { + index += 1 + } + index = (index + 2).coerceAtMost(this@withoutComments.length) + } + + else -> { + append(current) + index += 1 + } + } + } + } + + private fun String.shaderSignature(): ShaderSignature { + val uniformTypes = mutableMapOf() + val samplerUniforms = mutableListOf>() + var hasTextureCoordinateVarying = false + + splitToSequence(';').forEach { statement -> + val trimmed = statement.trim() + val uniform = trimmed.readDeclaration("uniform") + if (uniform != null) { + val (type, names) = uniform + names.forEach { name -> + uniformTypes[name] = type + if (type in SAMPLER_TYPES) { + samplerUniforms += type to name + } + } + } + + val varying = trimmed.readDeclaration("varying") + if (varying != null) { + val (type, names) = varying + if (type == "vec2" && TEXTURE_COORDINATE_NAME in names) { + hasTextureCoordinateVarying = true + } + } + } + + return ShaderSignature( + uniformTypes = uniformTypes, + samplerUniforms = samplerUniforms, + hasTextureCoordinateVarying = hasTextureCoordinateVarying, + hasMainFunction = MAIN_FUNCTION.containsMatchIn(this), + hasFragmentColorOutput = hasIdentifier("gl_FragColor") + ) + } + + private fun String.readDeclaration(keyword: String): Pair>? { + val tokens = trim().split(WHITESPACE).filter(String::isNotBlank) + if (tokens.firstOrNull() != keyword) return null + + var index = 1 + if (tokens.getOrNull(index) in PRECISION_QUALIFIERS) { + index += 1 + } + + val type = tokens.getOrNull(index) ?: return null + val names = tokens.drop(index + 1) + .joinToString(" ") + .split(",") + .mapNotNull { IDENTIFIER_PREFIX.find(it.trim())?.value } + + return type to names + } + + private fun String.hasIdentifier(identifier: String): Boolean { + var index = indexOf(identifier) + while (index >= 0) { + val before = getOrNull(index - 1) + val after = getOrNull(index + identifier.length) + if (!before.isGlslIdentifierPart() && !after.isGlslIdentifierPart()) { + return true + } + index = indexOf(identifier, startIndex = index + identifier.length) + } + return false + } + + private fun String.hasIdentifierWithPrefix(prefix: String): Boolean { + var index = indexOf(prefix) + while (index >= 0) { + val before = getOrNull(index - 1) + if (!before.isGlslIdentifierPart()) return true + index = indexOf(prefix, startIndex = index + prefix.length) + } + return false + } + + private fun Char?.isGlslIdentifierPart(): Boolean = + this != null && (isLetterOrDigit() || this == '_') + + private fun Char.readableName(): String = + if (isISOControl()) { + "U+${code.toString(16).uppercase().padStart(4, '0')}" + } else { + "'$this'" + } + + private data class ShaderSignature( + val uniformTypes: Map, + val samplerUniforms: List>, + val hasTextureCoordinateVarying: Boolean, + val hasMainFunction: Boolean, + val hasFragmentColorOutput: Boolean + ) + + const val SUPPORTED_VERSION = 1 + + val RESERVED_NAMES: Set = setOf( + "inputImageTexture", + "textureCoordinate", + "inputImageSize", + "outputImageSize" + ) + + private const val INPUT_TEXTURE_NAME = "inputImageTexture" + private const val TEXTURE_COORDINATE_NAME = "textureCoordinate" + + private val GLSL_IDENTIFIER = Regex("""[A-Za-z_][A-Za-z0-9_]*""") + private val PRECISION_QUALIFIERS = setOf("lowp", "mediump", "highp") + private val SAMPLER_TYPES = setOf("sampler2D", "samplerCube", "samplerExternalOES") + private val WHITESPACE = Regex("""\s+""") + private val IDENTIFIER_PREFIX = Regex("""^[A-Za-z_][A-Za-z0-9_]*""") + private val MAIN_FUNCTION = Regex( + pattern = """\bvoid\s+main\s*\(\s*\)""", + option = RegexOption.MULTILINE + ) +} + +sealed interface ShaderValidationError { + data class UnsupportedVersion(val version: Int, val supportedVersion: Int) : + ShaderValidationError + + data object BlankName : ShaderValidationError + data object BlankSource : ShaderValidationError + data class UnsupportedCharacters(val characters: String) : ShaderValidationError + data class DuplicateParameter(val name: String) : ShaderValidationError + data object BlankParameterName : ShaderValidationError + data class ReservedParameterName(val name: String) : ShaderValidationError + data class InvalidParameterName(val name: String) : ShaderValidationError + data class InvalidValueType( + val name: String, + val fieldName: String, + val actualType: String, + val expectedType: String + ) : ShaderValidationError + + data class BoolBounds(val name: String) : ShaderValidationError + data class MinGreaterThanMax(val name: String) : ShaderValidationError + data class DefaultLowerThanMin(val name: String) : ShaderValidationError + data class DefaultGreaterThanMax(val name: String) : ShaderValidationError + data class MissingInputTexture(val name: String) : ShaderValidationError + data class MissingTextureCoordinate(val name: String) : ShaderValidationError + data object MissingMainFunction : ShaderValidationError + data object MissingFragmentColor : ShaderValidationError + data object ShaderToyMainImage : ShaderValidationError + data object ShaderToyITime : ShaderValidationError + data object ShaderToyIFrame : ShaderValidationError + data object ShaderToyIResolution : ShaderValidationError + data object ShaderToyIChannel : ShaderValidationError + data object ExternalTextures : ShaderValidationError + data object ExternalTextureExtensions : ShaderValidationError + data object LibretroParameters : ShaderValidationError + data class ExtraInputTexture(val samplerType: String, val samplerName: String) : + ShaderValidationError + + data class MissingUniform(val name: String, val expectedType: String) : ShaderValidationError + data class WrongUniformType( + val name: String, + val actualType: String, + val expectedType: String + ) : ShaderValidationError +} + +fun ShaderValidationError.englishMessage(): String = when (this) { + is ShaderValidationError.UnsupportedVersion -> + "Unsupported shader version $version. Supported version: $supportedVersion." + + ShaderValidationError.BlankName -> "Shader name must not be blank." + ShaderValidationError.BlankSource -> "Shader source must not be blank." + is ShaderValidationError.UnsupportedCharacters -> + "Shader source contains unsupported characters: $characters. Use ASCII GLSL source only." + + is ShaderValidationError.DuplicateParameter -> "Parameter '$name' is declared more than once." + ShaderValidationError.BlankParameterName -> "Parameter names must not be blank." + is ShaderValidationError.ReservedParameterName -> "Parameter '$name' uses a reserved GPUImage name." + is ShaderValidationError.InvalidParameterName -> "Parameter '$name' must be a valid GLSL identifier." + is ShaderValidationError.InvalidValueType -> + "Parameter '$name' has $fieldName value type '$actualType', expected '$expectedType'." + + is ShaderValidationError.BoolBounds -> "Parameter '$name' cannot define min or max for bool values." + is ShaderValidationError.MinGreaterThanMax -> "Parameter '$name' has min greater than max." + is ShaderValidationError.DefaultLowerThanMin -> "Parameter '$name' default is lower than min." + is ShaderValidationError.DefaultGreaterThanMax -> "Parameter '$name' default is greater than max." + is ShaderValidationError.MissingInputTexture -> + "Shader must declare 'uniform sampler2D $name;'." + + is ShaderValidationError.MissingTextureCoordinate -> + "Shader must declare 'varying vec2 $name;'." + + ShaderValidationError.MissingMainFunction -> "Shader must define 'void main()'." + ShaderValidationError.MissingFragmentColor -> "Shader must write a color to gl_FragColor." + ShaderValidationError.ShaderToyMainImage -> + "ShaderToy mainImage shaders are not supported. Use GPUImage's void main() contract." + + ShaderValidationError.ShaderToyITime -> "Animated ShaderToy uniform 'iTime' is not supported." + ShaderValidationError.ShaderToyIFrame -> "Animated ShaderToy uniform 'iFrame' is not supported." + ShaderValidationError.ShaderToyIResolution -> "ShaderToy uniform 'iResolution' is not supported." + ShaderValidationError.ShaderToyIChannel -> "ShaderToy iChannel textures are not supported." + ShaderValidationError.ExternalTextures -> "External textures are not supported." + ShaderValidationError.ExternalTextureExtensions -> "External texture extensions are not supported." + ShaderValidationError.LibretroParameters -> "Libretro shader parameters are not supported." + is ShaderValidationError.ExtraInputTexture -> + "Only one input texture is supported. Remove 'uniform $samplerType $samplerName;'." + + is ShaderValidationError.MissingUniform -> + "Parameter '$name' must be declared as 'uniform $expectedType $name;'." + + is ShaderValidationError.WrongUniformType -> + "Parameter '$name' is declared as 'uniform $actualType $name;', expected 'uniform $expectedType $name;'." +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/FilterPreviewKey.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/FilterPreviewKey.kt new file mode 100644 index 0000000..76e8ee6 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/FilterPreviewKey.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 + +data class FilterPreviewKey( + val filterType: String?, + val value: Any?, + val isVisible: Boolean +) + +fun Filter<*>.previewKey(): FilterPreviewKey = FilterPreviewKey( + filterType = this::class.simpleName, + value = value.toPreviewKeyValue(), + isVisible = isVisible +) + +fun UiFilter<*>.hasSameValue(value: Any?): Boolean { + return this.value.toPreviewKeyValue() == value.toPreviewKeyValue() +} + +fun UiFilter<*>.hasSameState(other: UiFilter<*>): Boolean { + return previewKey() == other.previewKey() +} + +fun Any?.toPreviewKeyValue(): Any? = when (this) { + is FloatArray -> toList() + is IntArray -> toList() + is DoubleArray -> toList() + is LongArray -> toList() + is ShortArray -> toList() + is ByteArray -> toList() + is CharArray -> toList() + is BooleanArray -> toList() + is Array<*> -> map { it.toPreviewKeyValue() } + is Iterable<*> -> map { it.toPreviewKeyValue() } + is Map<*, *> -> entries.associate { (key, value) -> + key.toPreviewKeyValue() to value.toPreviewKeyValue() + } + + is Pair<*, *> -> first.toPreviewKeyValue() to second.toPreviewKeyValue() + is Triple<*, *, *> -> Triple( + first.toPreviewKeyValue(), + second.toPreviewKeyValue(), + third.toPreviewKeyValue() + ) + + else -> this +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAcesFilmicToneMappingFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAcesFilmicToneMappingFilter.kt new file mode 100644 index 0000000..23a1945 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAcesFilmicToneMappingFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAcesHillToneMappingFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAcesHillToneMappingFilter.kt new file mode 100644 index 0000000..aa3fb1d --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAcesHillToneMappingFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAchromatomalyFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAchromatomalyFilter.kt new file mode 100644 index 0000000..b819662 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAchromatomalyFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAchromatopsiaFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAchromatopsiaFilter.kt new file mode 100644 index 0000000..900e00b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAchromatopsiaFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAdaptiveBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAdaptiveBlurFilter.kt new file mode 100644 index 0000000..42728c6 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAdaptiveBlurFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 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 UiAdaptiveBlurFilter( + override val value: Pair = 15f to 35f +) : UiFilter>( + title = R.string.adaptive_blur, + paramsInfo = listOf( + FilterParam(R.string.radius, 1f..100f, 0), + FilterParam(R.string.threshold, 0f..255f, 0) + ), + value = value +), Filter.AdaptiveBlur diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAldridgeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAldridgeFilter.kt new file mode 100644 index 0000000..c17bd76 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAldridgeFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAmatorkaFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAmatorkaFilter.kt new file mode 100644 index 0000000..e120dae --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAmatorkaFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAnaglyphFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAnaglyphFilter.kt new file mode 100644 index 0000000..86f95ec --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAnaglyphFilter.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAnisotropicDiffusionFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAnisotropicDiffusionFilter.kt new file mode 100644 index 0000000..fb5c702 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAnisotropicDiffusionFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiArcFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiArcFilter.kt new file mode 100644 index 0000000..df38f96 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiArcFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAsciiFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAsciiFilter.kt new file mode 100644 index 0000000..88713df --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAsciiFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAtkinsonDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAtkinsonDitheringFilter.kt new file mode 100644 index 0000000..91c26ca --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAtkinsonDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutoCropFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutoCropFilter.kt new file mode 100644 index 0000000..fd07254 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutoCropFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutoPerspectiveFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutoPerspectiveFilter.kt new file mode 100644 index 0000000..3892fdf --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutoPerspectiveFilter.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutoRemoveRedEyesFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutoRemoveRedEyesFilter.kt new file mode 100644 index 0000000..9636dcf --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutoRemoveRedEyesFilter.kt @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutoWhiteBalanceFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutoWhiteBalanceFilter.kt new file mode 100644 index 0000000..73c1d20 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutoWhiteBalanceFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 UiAutoWhiteBalanceFilter( + override val value: Pair = 1f to 0.05f +) : UiFilter>( + title = R.string.auto_white_balance, + value = value, + paramsInfo = listOf( + FilterParam(R.string.strength, 0f..1f), + FilterParam(R.string.clipping, 0f..2f) + ) +), Filter.AutoWhiteBalance \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutumnTonesFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutumnTonesFilter.kt new file mode 100644 index 0000000..7db8656 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutumnTonesFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAverageDistanceFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAverageDistanceFilter.kt new file mode 100644 index 0000000..d9f9338 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAverageDistanceFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerEightDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerEightDitheringFilter.kt new file mode 100644 index 0000000..417aca3 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerEightDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerFourDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerFourDitheringFilter.kt new file mode 100644 index 0000000..9c9d302 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerFourDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerThreeDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerThreeDitheringFilter.kt new file mode 100644 index 0000000..48e9093 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerThreeDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerTwoDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerTwoDitheringFilter.kt new file mode 100644 index 0000000..a658f2c --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerTwoDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBilaterialBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBilaterialBlurFilter.kt new file mode 100644 index 0000000..876b35d --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBilaterialBlurFilter.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBlackAndWhiteFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBlackAndWhiteFilter.kt new file mode 100644 index 0000000..8046bc0 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBlackAndWhiteFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBlackHatFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBlackHatFilter.kt new file mode 100644 index 0000000..5d5bc0c --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBlackHatFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBleachBypassFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBleachBypassFilter.kt new file mode 100644 index 0000000..ad2078a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBleachBypassFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBlockGlitchFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBlockGlitchFilter.kt new file mode 100644 index 0000000..9b1d9bd --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBlockGlitchFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBloomFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBloomFilter.kt new file mode 100644 index 0000000..fe2eb6a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBloomFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBokehFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBokehFilter.kt new file mode 100644 index 0000000..54cf515 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBokehFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBorderFrameFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBorderFrameFilter.kt new file mode 100644 index 0000000..aace2be --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBorderFrameFilter.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBoxBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBoxBlurFilter.kt new file mode 100644 index 0000000..a8f0a56 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBoxBlurFilter.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBrightnessFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBrightnessFilter.kt new file mode 100644 index 0000000..46a0223 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBrightnessFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBrowniFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBrowniFilter.kt new file mode 100644 index 0000000..cd4d4fa --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBrowniFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBulgeDistortionFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBulgeDistortionFilter.kt new file mode 100644 index 0000000..16360c4 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBulgeDistortionFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBurkesDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBurkesDitheringFilter.kt new file mode 100644 index 0000000..1d5c2a0 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBurkesDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCGAColorSpaceFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCGAColorSpaceFilter.kt new file mode 100644 index 0000000..2b3ec6e --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCGAColorSpaceFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCandlelightFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCandlelightFilter.kt new file mode 100644 index 0000000..6d575a0 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCandlelightFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCannyFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCannyFilter.kt new file mode 100644 index 0000000..3625fcb --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCannyFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCaramelDarknessFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCaramelDarknessFilter.kt new file mode 100644 index 0000000..4895e83 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCaramelDarknessFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCelluloidFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCelluloidFilter.kt new file mode 100644 index 0000000..d225a83 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCelluloidFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiChannelMixFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiChannelMixFilter.kt new file mode 100644 index 0000000..4398ab0 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiChannelMixFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiChromeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiChromeFilter.kt new file mode 100644 index 0000000..c5aa65f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiChromeFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 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 UiChromeFilter( + override val value: Pair = 0.5f to 1f +) : UiFilter>( + title = R.string.chrome, + paramsInfo = listOf( + R.string.amount paramTo 0f..1f, + R.string.exposure paramTo 0f..5f + ), + value = value +), Filter.Chrome diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCircleBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCircleBlurFilter.kt new file mode 100644 index 0000000..2bb0286 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCircleBlurFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCirclePixelationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCirclePixelationFilter.kt new file mode 100644 index 0000000..c7f9338 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCirclePixelationFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheFilter.kt new file mode 100644 index 0000000..7dc726e --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheHSLFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheHSLFilter.kt new file mode 100644 index 0000000..ed306ba --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheHSLFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheHSVFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheHSVFilter.kt new file mode 100644 index 0000000..a822c54 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheHSVFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheJzazbzFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheJzazbzFilter.kt new file mode 100644 index 0000000..5afcf12 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheJzazbzFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheLABFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheLABFilter.kt new file mode 100644 index 0000000..c041261 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheLABFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheLUVFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheLUVFilter.kt new file mode 100644 index 0000000..3376219 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheLUVFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheOklabFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheOklabFilter.kt new file mode 100644 index 0000000..9b7c4a9 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheOklabFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheOklchFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheOklchFilter.kt new file mode 100644 index 0000000..febc096 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheOklchFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClosingFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClosingFilter.kt new file mode 100644 index 0000000..09f3eac --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClosingFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClustered2x2DitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClustered2x2DitheringFilter.kt new file mode 100644 index 0000000..53df03d --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClustered2x2DitheringFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClustered4x4DitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClustered4x4DitheringFilter.kt new file mode 100644 index 0000000..f12b945 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClustered4x4DitheringFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClustered8x8DitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClustered8x8DitheringFilter.kt new file mode 100644 index 0000000..f8bdaf7 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClustered8x8DitheringFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCodaChromeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCodaChromeFilter.kt new file mode 100644 index 0000000..c2d7c7f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCodaChromeFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCoffeeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCoffeeFilter.kt new file mode 100644 index 0000000..d3730bb --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCoffeeFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorAnomalyFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorAnomalyFilter.kt new file mode 100644 index 0000000..22b9649 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorAnomalyFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorBalanceFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorBalanceFilter.kt new file mode 100644 index 0000000..9127925 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorBalanceFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorExplosionFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorExplosionFilter.kt new file mode 100644 index 0000000..6efa424 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorExplosionFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorHalftoneFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorHalftoneFilter.kt new file mode 100644 index 0000000..3e6e42b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorHalftoneFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorMapFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorMapFilter.kt new file mode 100644 index 0000000..da43a62 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorMapFilter.kt @@ -0,0 +1,154 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorMaskFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorMaskFilter.kt new file mode 100644 index 0000000..d575b03 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorMaskFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 UiColorMaskFilter( + override val value: FilterValueWrapper = Color.Cyan.toModel().wrap() +) : UiFilter>( + title = R.string.color_mask, + value = value +), Filter.ColorMask diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorMatrix3x3Filter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorMatrix3x3Filter.kt new file mode 100644 index 0000000..a4130ee --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorMatrix3x3Filter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorMatrix4x4Filter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorMatrix4x4Filter.kt new file mode 100644 index 0000000..ae30b29 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorMatrix4x4Filter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorOverlayFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorOverlayFilter.kt new file mode 100644 index 0000000..71e3145 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorOverlayFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorPosterFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorPosterFilter.kt new file mode 100644 index 0000000..ada84d2 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorPosterFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorfulSwirlFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorfulSwirlFilter.kt new file mode 100644 index 0000000..a778271 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorfulSwirlFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiContourFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiContourFilter.kt new file mode 100644 index 0000000..dffc676 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiContourFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiContrastFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiContrastFilter.kt new file mode 100644 index 0000000..dca60c2 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiContrastFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiConvexFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiConvexFilter.kt new file mode 100644 index 0000000..e3c6a39 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiConvexFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiConvolution3x3Filter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiConvolution3x3Filter.kt new file mode 100644 index 0000000..3e49012 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiConvolution3x3Filter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCoolFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCoolFilter.kt new file mode 100644 index 0000000..f627e70 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCoolFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCopyMoveDetectionFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCopyMoveDetectionFilter.kt new file mode 100644 index 0000000..f196911 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCopyMoveDetectionFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCropOrPerspectiveFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCropOrPerspectiveFilter.kt new file mode 100644 index 0000000..caa9d6b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCropOrPerspectiveFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCropToContentFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCropToContentFilter.kt new file mode 100644 index 0000000..ecef12e --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCropToContentFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrossBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrossBlurFilter.kt new file mode 100644 index 0000000..3b95887 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrossBlurFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrossPixelizationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrossPixelizationFilter.kt new file mode 100644 index 0000000..61f00dc --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrossPixelizationFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrosshatchFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrosshatchFilter.kt new file mode 100644 index 0000000..0f9bc4a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrosshatchFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrtCurvatureFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrtCurvatureFilter.kt new file mode 100644 index 0000000..595915d --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrtCurvatureFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrystallizeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrystallizeFilter.kt new file mode 100644 index 0000000..8e3e66c --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrystallizeFilter.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCubeLutFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCubeLutFilter.kt new file mode 100644 index 0000000..1320ba8 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCubeLutFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCyberpunkFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCyberpunkFilter.kt new file mode 100644 index 0000000..bbdf005 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCyberpunkFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeepPurpleFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeepPurpleFilter.kt new file mode 100644 index 0000000..d853667 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeepPurpleFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDehazeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDehazeFilter.kt new file mode 100644 index 0000000..cf8e264 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDehazeFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeskewFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeskewFilter.kt new file mode 100644 index 0000000..1184c7a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeskewFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDespeckleFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDespeckleFilter.kt new file mode 100644 index 0000000..4c3b3b2 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDespeckleFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeutaromalyFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeutaromalyFilter.kt new file mode 100644 index 0000000..1be8c62 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeutaromalyFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeuteranopiaFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeuteranopiaFilter.kt new file mode 100644 index 0000000..3ccb936 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeuteranopiaFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 UiDeuteranopiaFilter : UiFilter( + title = R.string.deuteranopia, + value = Unit +), Filter.Deuteranopia \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDiamondPixelationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDiamondPixelationFilter.kt new file mode 100644 index 0000000..533947f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDiamondPixelationFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDiffuseFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDiffuseFilter.kt new file mode 100644 index 0000000..a19b6f2 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDiffuseFilter.kt @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDigitalCodeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDigitalCodeFilter.kt new file mode 100644 index 0000000..554811a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDigitalCodeFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDilationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDilationFilter.kt new file mode 100644 index 0000000..0ad4416 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDilationFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDissolveFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDissolveFilter.kt new file mode 100644 index 0000000..ddc7481 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDissolveFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 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 UiDissolveFilter( + override val value: Pair = 0.75f to 0.1f +) : UiFilter>( + title = R.string.dissolve, + paramsInfo = listOf( + R.string.density paramTo 0f..1f, + R.string.softness paramTo 0f..1f + ), + value = value +), Filter.Dissolve diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDistortPerspectiveFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDistortPerspectiveFilter.kt new file mode 100644 index 0000000..f379b15 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDistortPerspectiveFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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.DistortPerspectiveParams +import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject +import com.t8rin.imagetoolbox.core.resources.R + +@UiFilterInject(group = UiFilterInject.Groups.DISTORTION) +class UiDistortPerspectiveFilter( + override val value: DistortPerspectiveParams = DistortPerspectiveParams.Default +) : UiFilter( + title = R.string.distort_perspective, + paramsInfo = listOf( + FilterParam(R.string.top_left, 0f..1f), + FilterParam(R.string.top_right, 0f..1f), + FilterParam(R.string.bottom_left, 0f..1f), + FilterParam(R.string.bottom_right, 0f..1f), + FilterParam(R.string.clip, 0f..0f) + ), + value = value +), Filter.DistortPerspective diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDistortionFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDistortionFilter.kt new file mode 100644 index 0000000..0076529 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDistortionFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 UiDistortionFilter( + override val value: Float = 50f +) : UiFilter( + title = R.string.distortion_filter, + value = value, + paramsInfo = listOf( + FilterParam( + title = R.string.strength, + valueRange = 0f..95f, + roundTo = 0 + ) + ) +), Filter.Distortion diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDoGFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDoGFilter.kt new file mode 100644 index 0000000..2196efc --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDoGFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDragoFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDragoFilter.kt new file mode 100644 index 0000000..7e009d4 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDragoFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDropBluesFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDropBluesFilter.kt new file mode 100644 index 0000000..9b0927b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDropBluesFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDropShadowFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDropShadowFilter.kt new file mode 100644 index 0000000..92a530b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDropShadowFilter.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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.DropShadowParams +import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject +import com.t8rin.imagetoolbox.core.resources.R + +@UiFilterInject(group = UiFilterInject.Groups.EFFECTS) +class UiDropShadowFilter( + override val value: DropShadowParams = DropShadowParams.Default +) : UiFilter( + title = R.string.drop_shadow, + paramsInfo = listOf( + FilterParam(R.string.blur_radius, 0f..200f), + FilterParam(R.string.offset_x, -150f..150f), + FilterParam(R.string.offset_y, -150f..150f) + ), + value = value +), Filter.DropShadow diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEdgyAmberFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEdgyAmberFilter.kt new file mode 100644 index 0000000..d0d0496 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEdgyAmberFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiElectricGradientFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiElectricGradientFilter.kt new file mode 100644 index 0000000..d9f0aef --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiElectricGradientFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEmbossFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEmbossFilter.kt new file mode 100644 index 0000000..9f341ab --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEmbossFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedCirclePixelationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedCirclePixelationFilter.kt new file mode 100644 index 0000000..cbeed67 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedCirclePixelationFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedDiamondPixelationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedDiamondPixelationFilter.kt new file mode 100644 index 0000000..ee36799 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedDiamondPixelationFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedGlitchFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedGlitchFilter.kt new file mode 100644 index 0000000..46d3cd4 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedGlitchFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedOilFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedOilFilter.kt new file mode 100644 index 0000000..daaaf97 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedOilFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedPixelationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedPixelationFilter.kt new file mode 100644 index 0000000..cc6517b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedPixelationFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedZoomBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedZoomBlurFilter.kt new file mode 100644 index 0000000..e939637 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedZoomBlurFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeFilter.kt new file mode 100644 index 0000000..efa227d --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeFilter.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveFilter.kt new file mode 100644 index 0000000..d87402c --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveHSLFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveHSLFilter.kt new file mode 100644 index 0000000..f2589f1 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveHSLFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveHSVFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveHSVFilter.kt new file mode 100644 index 0000000..f1bd622 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveHSVFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveLABFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveLABFilter.kt new file mode 100644 index 0000000..de3ad42 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveLABFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveLUVFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveLUVFilter.kt new file mode 100644 index 0000000..c6f9568 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveLUVFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramFilter.kt new file mode 100644 index 0000000..3f96367 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramHSVFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramHSVFilter.kt new file mode 100644 index 0000000..971b729 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramHSVFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramPixelationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramPixelationFilter.kt new file mode 100644 index 0000000..1ba376f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramPixelationFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiErodeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiErodeFilter.kt new file mode 100644 index 0000000..55a70e8 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiErodeFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiErrorLevelAnalysisFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiErrorLevelAnalysisFilter.kt new file mode 100644 index 0000000..2363e66 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiErrorLevelAnalysisFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiExpandImageFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiExpandImageFilter.kt new file mode 100644 index 0000000..11ecadb --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiExpandImageFilter.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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.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 UiExpandImageFilter( + override val value: Quad = 64f to 64f qto (64f to 64f) +) : UiFilter>( + title = R.string.expand_image, + paramsInfo = listOf( + FilterParam( + title = R.string.top, + valueRange = 0f..1024f, + roundTo = 0 + ), + FilterParam( + title = R.string.start, + valueRange = 0f..1024f, + roundTo = 0 + ), + FilterParam( + title = R.string.end, + valueRange = 0f..1024f, + roundTo = 0 + ), + FilterParam( + title = R.string.bottom, + valueRange = 0f..1024f, + roundTo = 0 + ) + ), + value = value +), Filter.ExpandImage \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiExposureFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiExposureFilter.kt new file mode 100644 index 0000000..dda6c00 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiExposureFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFallColorsFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFallColorsFilter.kt new file mode 100644 index 0000000..ecb8e7f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFallColorsFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFalseColorFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFalseColorFilter.kt new file mode 100644 index 0000000..193d609 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFalseColorFilter.kt @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFalseFloydSteinbergDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFalseFloydSteinbergDitheringFilter.kt new file mode 100644 index 0000000..0124887 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFalseFloydSteinbergDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFantasyLandscapeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFantasyLandscapeFilter.kt new file mode 100644 index 0000000..05799a7 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFantasyLandscapeFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastBilaterialBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastBilaterialBlurFilter.kt new file mode 100644 index 0000000..e26bb40 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastBilaterialBlurFilter.kt @@ -0,0 +1,49 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastBlurFilter.kt new file mode 100644 index 0000000..baaa3a6 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastBlurFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastGaussianBlur2DFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastGaussianBlur2DFilter.kt new file mode 100644 index 0000000..190474c --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastGaussianBlur2DFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastGaussianBlur3DFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastGaussianBlur3DFilter.kt new file mode 100644 index 0000000..0c4f2fa --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastGaussianBlur3DFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastGaussianBlur4DFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastGaussianBlur4DFilter.kt new file mode 100644 index 0000000..3003472 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastGaussianBlur4DFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFeedbackFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFeedbackFilter.kt new file mode 100644 index 0000000..67aa715 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFeedbackFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 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.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 UiFeedbackFilter( + override val value: Quad = 10f to 0f qto (3f to 0.05f) +) : UiFilter>( + title = R.string.feedback, + paramsInfo = listOf( + FilterParam(R.string.distance, -100f..100f, 0), + FilterParam(R.string.angle, -180f..180f, 0), + FilterParam(R.string.rotation, -180f..180f, 0), + R.string.zoom paramTo -1f..1f + ), + value = value +), Filter.Feedback diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFilmStock50Filter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFilmStock50Filter.kt new file mode 100644 index 0000000..ee1e046 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFilmStock50Filter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFilter.kt new file mode 100644 index 0000000..6a4b850 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFilter.kt @@ -0,0 +1,232 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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.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.Icons +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.Bolt +import com.t8rin.imagetoolbox.core.resources.icons.Bookmark +import com.t8rin.imagetoolbox.core.resources.icons.Cube +import com.t8rin.imagetoolbox.core.resources.icons.Extension +import com.t8rin.imagetoolbox.core.resources.icons.FilterHdr +import com.t8rin.imagetoolbox.core.resources.icons.FloodFill +import com.t8rin.imagetoolbox.core.resources.icons.Gradient +import com.t8rin.imagetoolbox.core.resources.icons.Lightbulb +import com.t8rin.imagetoolbox.core.resources.icons.Schedule +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) + + val canUseTemplate by lazy { + listOf(this).filterCanUseTemplate().isNotEmpty() + } + + 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.filterCanUseTemplate() + } + + 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" + } + + class Recent( + data: List> + ) : Group( + icon = Icons.Rounded.Schedule, + title = R.string.recent, + data = data + ) { + override fun toString(): String = "Recent" + } + + data object Simple : Group( + icon = Icons.Rounded.Bolt, + 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 { + internal fun > List.filterCanUseTemplate() = filterIsNotInstance( + Filter.PaletteTransfer::class, + Filter.LUT512x512::class, + Filter.PaletteTransferVariant::class, + Filter.CubeLut::class, + Filter.Shader::class, + Filter.LensCorrection::class, + Filter.SeamCarving::class + ) + + 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 +) diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFlareFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFlareFilter.kt new file mode 100644 index 0000000..12e69fc --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFlareFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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.FlareParams +import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject +import com.t8rin.imagetoolbox.core.resources.R + +@UiFilterInject(group = UiFilterInject.Groups.LIGHT) +class UiFlareFilter( + override val value: FlareParams = FlareParams.Default +) : UiFilter( + title = R.string.flare, + paramsInfo = listOf( + R.string.radius paramTo 0.01f..1f, + R.string.base_amount paramTo 0f..2f, + R.string.ring_amount paramTo 0f..2f, + R.string.ray_amount paramTo 0f..2f, + R.string.ring_width paramTo 0.01f..5f, + R.string.center_x paramTo 0f..1f, + R.string.center_y paramTo 0f..1f, + R.string.color paramTo 0f..0f + ), + value = value +), Filter.Flare diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFloydSteinbergDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFloydSteinbergDitheringFilter.kt new file mode 100644 index 0000000..d0bd0be --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFloydSteinbergDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFoggyNightFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFoggyNightFilter.kt new file mode 100644 index 0000000..2abd33e --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFoggyNightFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFractalGlassFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFractalGlassFilter.kt new file mode 100644 index 0000000..b7bf8c3 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFractalGlassFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFuturisticGradientFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFuturisticGradientFilter.kt new file mode 100644 index 0000000..61e4dbc --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFuturisticGradientFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGammaFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGammaFilter.kt new file mode 100644 index 0000000..c823ad4 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGammaFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGaussianBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGaussianBlurFilter.kt new file mode 100644 index 0000000..be7e310 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGaussianBlurFilter.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGaussianBoxBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGaussianBoxBlurFilter.kt new file mode 100644 index 0000000..cf6d3a3 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGaussianBoxBlurFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlassSphereRefractionFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlassSphereRefractionFilter.kt new file mode 100644 index 0000000..f0872ff --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlassSphereRefractionFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlitchFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlitchFilter.kt new file mode 100644 index 0000000..bb8510e --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlitchFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlitchVariantFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlitchVariantFilter.kt new file mode 100644 index 0000000..e91bd46 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlitchVariantFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlowFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlowFilter.kt new file mode 100644 index 0000000..3cab5e0 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlowFilter.kt @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGoldenForestFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGoldenForestFilter.kt new file mode 100644 index 0000000..3f41371 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGoldenForestFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGoldenHourFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGoldenHourFilter.kt new file mode 100644 index 0000000..c2cca11 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGoldenHourFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGothamFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGothamFilter.kt new file mode 100644 index 0000000..13d307b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGothamFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGrainFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGrainFilter.kt new file mode 100644 index 0000000..63ac9b1 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGrainFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGrayscaleFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGrayscaleFilter.kt new file mode 100644 index 0000000..07d2805 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGrayscaleFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGreenSunFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGreenSunFilter.kt new file mode 100644 index 0000000..8f6054a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGreenSunFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGreenishFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGreenishFilter.kt new file mode 100644 index 0000000..dcf90f5 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGreenishFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHDRFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHDRFilter.kt new file mode 100644 index 0000000..5b385ea --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHDRFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHableFilmicToneMappingFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHableFilmicToneMappingFilter.kt new file mode 100644 index 0000000..cd43176 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHableFilmicToneMappingFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHalftoneFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHalftoneFilter.kt new file mode 100644 index 0000000..48ec85e --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHalftoneFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHazeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHazeFilter.kt new file mode 100644 index 0000000..7f6ea19 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHazeFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHejlBurgessToneMappingFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHejlBurgessToneMappingFilter.kt new file mode 100644 index 0000000..95a65b8 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHejlBurgessToneMappingFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHighPassFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHighPassFilter.kt new file mode 100644 index 0000000..60522b8 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHighPassFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 UiHighPassFilter( + override val value: Float = 10f +) : UiFilter( + title = R.string.high_pass, + valueRange = 1f..100f, + value = value +), Filter.HighPass diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHighlightsAndShadowsFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHighlightsAndShadowsFilter.kt new file mode 100644 index 0000000..feecf73 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHighlightsAndShadowsFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHorizontalWindStaggerFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHorizontalWindStaggerFilter.kt new file mode 100644 index 0000000..5975d8f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHorizontalWindStaggerFilter.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHotSummerFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHotSummerFilter.kt new file mode 100644 index 0000000..25666bf --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHotSummerFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHueFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHueFilter.kt new file mode 100644 index 0000000..fc362d3 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHueFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiJarvisJudiceNinkeDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiJarvisJudiceNinkeDitheringFilter.kt new file mode 100644 index 0000000..e449d37 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiJarvisJudiceNinkeDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiJavaLookAndFeelFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiJavaLookAndFeelFilter.kt new file mode 100644 index 0000000..ec5f5ac --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiJavaLookAndFeelFilter.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 UiJavaLookAndFeelFilter( + override val value: Unit = Unit +) : UiFilter( + title = R.string.java_look_and_feel, + value = value +), Filter.JavaLookAndFeel diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiKaleidoscopeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiKaleidoscopeFilter.kt new file mode 100644 index 0000000..a577403 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiKaleidoscopeFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiKodakFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiKodakFilter.kt new file mode 100644 index 0000000..f2ebada --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiKodakFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiKuwaharaFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiKuwaharaFilter.kt new file mode 100644 index 0000000..9a36316 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiKuwaharaFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLUT512x512Filter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLUT512x512Filter.kt new file mode 100644 index 0000000..889b299 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLUT512x512Filter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLaplacianFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLaplacianFilter.kt new file mode 100644 index 0000000..bf3ce8a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLaplacianFilter.kt @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLaplacianSimpleFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLaplacianSimpleFilter.kt new file mode 100644 index 0000000..e5cd655 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLaplacianSimpleFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLavenderDreamFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLavenderDreamFilter.kt new file mode 100644 index 0000000..5beba25 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLavenderDreamFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLeftToRightDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLeftToRightDitheringFilter.kt new file mode 100644 index 0000000..e4ae3e9 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLeftToRightDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLemonadeLightFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLemonadeLightFilter.kt new file mode 100644 index 0000000..7f3bbee --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLemonadeLightFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLensBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLensBlurFilter.kt new file mode 100644 index 0000000..3952951 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLensBlurFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 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.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 UiLensBlurFilter( + override val value: Quad = 10f to 5f qto (2f to 255f) +) : UiFilter>( + title = R.string.lens_blur, + paramsInfo = listOf( + FilterParam(R.string.radius, 0f..50f, 0), + FilterParam(R.string.sides, 3f..12f, 0), + R.string.bloom paramTo 0f..10f, + FilterParam(R.string.threshold, 0f..255f, 0) + ), + value = value +), Filter.LensBlur diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLensCorrectionFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLensCorrectionFilter.kt new file mode 100644 index 0000000..80a51b1 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLensCorrectionFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLevelsFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLevelsFilter.kt new file mode 100644 index 0000000..a608c1e --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLevelsFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 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.LIGHT) +class UiLevelsFilter( + override val value: Quad = 0f to 1f qto (0f to 1f) +) : UiFilter>( + title = R.string.levels, + paramsInfo = listOf( + R.string.low_input paramTo 0f..1f, + R.string.high_input paramTo 0f..1f, + R.string.low_output paramTo 0f..1f, + R.string.high_output paramTo 0f..1f + ), + value = value +), Filter.Levels diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLightEffectsFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLightEffectsFilter.kt new file mode 100644 index 0000000..1aad767 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLightEffectsFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 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 UiLightEffectsFilter( + override val value: Pair = 1f to 5f +) : UiFilter>( + title = R.string.light_effects, + paramsInfo = listOf( + R.string.bump_height paramTo 0f..10f, + R.string.bump_softness paramTo 0f..50f + ), + value = value +), Filter.LightEffects diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearBoxBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearBoxBlurFilter.kt new file mode 100644 index 0000000..302a376 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearBoxBlurFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearFastGaussianBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearFastGaussianBlurFilter.kt new file mode 100644 index 0000000..f231332 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearFastGaussianBlurFilter.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearFastGaussianBlurNextFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearFastGaussianBlurNextFilter.kt new file mode 100644 index 0000000..a3eb130 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearFastGaussianBlurNextFilter.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearGaussianBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearGaussianBlurFilter.kt new file mode 100644 index 0000000..18f9609 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearGaussianBlurFilter.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearGaussianBoxBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearGaussianBoxBlurFilter.kt new file mode 100644 index 0000000..beaceed --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearGaussianBoxBlurFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearStackBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearStackBlurFilter.kt new file mode 100644 index 0000000..6b0d72c --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearStackBlurFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearTentBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearTentBlurFilter.kt new file mode 100644 index 0000000..a186609 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearTentBlurFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearTiltShiftFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearTiltShiftFilter.kt new file mode 100644 index 0000000..14c97c7 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearTiltShiftFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLogarithmicToneMappingFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLogarithmicToneMappingFilter.kt new file mode 100644 index 0000000..a11c571 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLogarithmicToneMappingFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLookupFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLookupFilter.kt new file mode 100644 index 0000000..ca869c1 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLookupFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLowPolyFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLowPolyFilter.kt new file mode 100644 index 0000000..8bf78f9 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLowPolyFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLuminanceGradientFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLuminanceGradientFilter.kt new file mode 100644 index 0000000..97328a6 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLuminanceGradientFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMarbleFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMarbleFilter.kt new file mode 100644 index 0000000..427b6a5 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMarbleFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMedianBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMedianBlurFilter.kt new file mode 100644 index 0000000..47148c9 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMedianBlurFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMicroMacroPixelizationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMicroMacroPixelizationFilter.kt new file mode 100644 index 0000000..2d7512b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMicroMacroPixelizationFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMirrorFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMirrorFilter.kt new file mode 100644 index 0000000..38ae86a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMirrorFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMissEtikateFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMissEtikateFilter.kt new file mode 100644 index 0000000..f9a2410 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMissEtikateFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMobiusFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMobiusFilter.kt new file mode 100644 index 0000000..194ab72 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMobiusFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMoireFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMoireFilter.kt new file mode 100644 index 0000000..7f30592 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMoireFilter.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMonochromeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMonochromeFilter.kt new file mode 100644 index 0000000..cd6c8c2 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMonochromeFilter.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMorphologicalGradientFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMorphologicalGradientFilter.kt new file mode 100644 index 0000000..79b0232 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMorphologicalGradientFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMotionBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMotionBlurFilter.kt new file mode 100644 index 0000000..8ad740c --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMotionBlurFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNativeStackBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNativeStackBlurFilter.kt new file mode 100644 index 0000000..c0378f4 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNativeStackBlurFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNegativeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNegativeFilter.kt new file mode 100644 index 0000000..15b9a79 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNegativeFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNeonFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNeonFilter.kt new file mode 100644 index 0000000..258b83b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNeonFilter.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNightMagicFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNightMagicFilter.kt new file mode 100644 index 0000000..8b5c02b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNightMagicFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNightVisionFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNightVisionFilter.kt new file mode 100644 index 0000000..a5160c8 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNightVisionFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNoiseFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNoiseFilter.kt new file mode 100644 index 0000000..c45fdd6 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNoiseFilter.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNonMaximumSuppressionFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNonMaximumSuppressionFilter.kt new file mode 100644 index 0000000..1018969 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNonMaximumSuppressionFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNucleusPixelizationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNucleusPixelizationFilter.kt new file mode 100644 index 0000000..750d749 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNucleusPixelizationFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOffsetFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOffsetFilter.kt new file mode 100644 index 0000000..132ade2 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOffsetFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOilFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOilFilter.kt new file mode 100644 index 0000000..873aeb3 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOilFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOldTvFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOldTvFilter.kt new file mode 100644 index 0000000..ac0dfba --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOldTvFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOpacityFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOpacityFilter.kt new file mode 100644 index 0000000..c8aed24 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOpacityFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOpeningFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOpeningFilter.kt new file mode 100644 index 0000000..f0cca23 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOpeningFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOrangeHazeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOrangeHazeFilter.kt new file mode 100644 index 0000000..ddace96 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOrangeHazeFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOrbitalPixelizationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOrbitalPixelizationFilter.kt new file mode 100644 index 0000000..6cbf68b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOrbitalPixelizationFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPaletteTransferFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPaletteTransferFilter.kt new file mode 100644 index 0000000..3f4490b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPaletteTransferFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPaletteTransferVariantFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPaletteTransferVariantFilter.kt new file mode 100644 index 0000000..a9582c9 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPaletteTransferVariantFilter.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPastelFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPastelFilter.kt new file mode 100644 index 0000000..08c5fd3 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPastelFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPerlinDistortionFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPerlinDistortionFilter.kt new file mode 100644 index 0000000..12f8242 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPerlinDistortionFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPinchFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPinchFilter.kt new file mode 100644 index 0000000..a648102 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPinchFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPinkDreamFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPinkDreamFilter.kt new file mode 100644 index 0000000..3cd67b0 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPinkDreamFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPixelMeltFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPixelMeltFilter.kt new file mode 100644 index 0000000..e4d80dd --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPixelMeltFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPixelationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPixelationFilter.kt new file mode 100644 index 0000000..e36267a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPixelationFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPointillizeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPointillizeFilter.kt new file mode 100644 index 0000000..6741f33 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPointillizeFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPoissonBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPoissonBlurFilter.kt new file mode 100644 index 0000000..7a69441 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPoissonBlurFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPolarCoordinatesFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPolarCoordinatesFilter.kt new file mode 100644 index 0000000..2e4eed8 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPolarCoordinatesFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPolaroidFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPolaroidFilter.kt new file mode 100644 index 0000000..7c595e8 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPolaroidFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPolkaDotFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPolkaDotFilter.kt new file mode 100644 index 0000000..cfb03b9 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPolkaDotFilter.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPopArtFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPopArtFilter.kt new file mode 100644 index 0000000..0bedab9 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPopArtFilter.kt @@ -0,0 +1,56 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPosterizeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPosterizeFilter.kt new file mode 100644 index 0000000..db27a86 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPosterizeFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiProtanopiaFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiProtanopiaFilter.kt new file mode 100644 index 0000000..ad97399 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiProtanopiaFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiProtonomalyFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiProtonomalyFilter.kt new file mode 100644 index 0000000..3008f88 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiProtonomalyFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPulseGridPixelizationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPulseGridPixelizationFilter.kt new file mode 100644 index 0000000..dc4a1a8 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPulseGridPixelizationFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPurpleMistFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPurpleMistFilter.kt new file mode 100644 index 0000000..32c4775 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPurpleMistFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiQuantizierFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiQuantizierFilter.kt new file mode 100644 index 0000000..9f72f1b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiQuantizierFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRGBFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRGBFilter.kt new file mode 100644 index 0000000..75d177f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRGBFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRadialTiltShiftFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRadialTiltShiftFilter.kt new file mode 100644 index 0000000..46d78af --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRadialTiltShiftFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRadialWeavePixelizationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRadialWeavePixelizationFilter.kt new file mode 100644 index 0000000..5c25dc3 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRadialWeavePixelizationFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRainbowWorldFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRainbowWorldFilter.kt new file mode 100644 index 0000000..fa63f35 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRainbowWorldFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRandomDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRandomDitheringFilter.kt new file mode 100644 index 0000000..1665653 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRandomDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRaysFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRaysFilter.kt new file mode 100644 index 0000000..0650361 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRaysFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 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 UiRaysFilter( + override val value: Triple = Triple(0.75f, 0.25f, 1f) +) : UiFilter>( + title = R.string.rays, + paramsInfo = listOf( + R.string.opacity paramTo 0f..1f, + R.string.threshold paramTo 0f..1f, + R.string.strength paramTo 0f..5f + ), + value = value +), Filter.Rays diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRedSwirlFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRedSwirlFilter.kt new file mode 100644 index 0000000..f7ad91f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRedSwirlFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiReduceNoiseFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiReduceNoiseFilter.kt new file mode 100644 index 0000000..596fe56 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiReduceNoiseFilter.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRemoveColorFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRemoveColorFilter.kt new file mode 100644 index 0000000..9fc671a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRemoveColorFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiReplaceColorFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiReplaceColorFilter.kt new file mode 100644 index 0000000..b74fa7c --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiReplaceColorFilter.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRetroYellowFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRetroYellowFilter.kt new file mode 100644 index 0000000..a6349db --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRetroYellowFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRingBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRingBlurFilter.kt new file mode 100644 index 0000000..e10337e --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRingBlurFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRippleFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRippleFilter.kt new file mode 100644 index 0000000..5aef55a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRippleFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 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.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 UiRippleFilter( + override val value: Quad = 5f to 16f qto (5f to 16f) +) : UiFilter>( + title = R.string.ripple, + paramsInfo = listOf( + FilterParam(R.string.x_amplitude, 0f..100f, 1), + FilterParam(R.string.x_wavelength, 1f..200f, 1), + FilterParam(R.string.y_amplitude, 0f..100f, 1), + FilterParam(R.string.y_wavelength, 1f..200f, 1) + ), + value = value +), Filter.Ripple diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRubberStampFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRubberStampFilter.kt new file mode 100644 index 0000000..b939e49 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRubberStampFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSandPaintingFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSandPaintingFilter.kt new file mode 100644 index 0000000..7ab6401 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSandPaintingFilter.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSaturationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSaturationFilter.kt new file mode 100644 index 0000000..2dbf765 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSaturationFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSeamCarvingFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSeamCarvingFilter.kt new file mode 100644 index 0000000..6c95ea6 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSeamCarvingFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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.SeamCarvingParams +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: SeamCarvingParams = SeamCarvingParams() +) : UiFilter( + title = R.string.seam_carving, + paramsInfo = listOf( + FilterParam(R.string.width, 0f..0f), + FilterParam(R.string.height, 0f..0f), + ), + value = value +), Filter.SeamCarving \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSepiaFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSepiaFilter.kt new file mode 100644 index 0000000..b58cf83 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSepiaFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiShaderFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiShaderFilter.kt new file mode 100644 index 0000000..65762fa --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiShaderFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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.ShaderParams +import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject +import com.t8rin.imagetoolbox.core.resources.R + +@UiFilterInject(group = UiFilterInject.Groups.EFFECTS) +class UiShaderFilter( + override val value: ShaderParams = ShaderParams() +) : UiFilter( + title = R.string.shader, + value = value +), Filter.Shader diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSharpenFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSharpenFilter.kt new file mode 100644 index 0000000..7a55f5a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSharpenFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiShearFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiShearFilter.kt new file mode 100644 index 0000000..202b412 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiShearFilter.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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.ShearParams +import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject +import com.t8rin.imagetoolbox.core.resources.R + +@UiFilterInject(group = UiFilterInject.Groups.DISTORTION) +class UiShearFilter( + override val value: ShearParams = ShearParams() +) : UiFilter( + title = R.string.shear, + paramsInfo = listOf( + FilterParam(R.string.x_angle, -89f..89f, 0), + FilterParam(R.string.y_angle, -89f..89f, 0), + FilterParam(R.string.resize, 0f..0f) + ), + value = value +), Filter.Shear diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiShuffleBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiShuffleBlurFilter.kt new file mode 100644 index 0000000..f07c3f5 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiShuffleBlurFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSideFadeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSideFadeFilter.kt new file mode 100644 index 0000000..7bd2fea --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSideFadeFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSierraDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSierraDitheringFilter.kt new file mode 100644 index 0000000..6986b97 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSierraDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSierraLiteDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSierraLiteDitheringFilter.kt new file mode 100644 index 0000000..94157cf --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSierraLiteDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleOldTvFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleOldTvFilter.kt new file mode 100644 index 0000000..a6b76dd --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleOldTvFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleSketchFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleSketchFilter.kt new file mode 100644 index 0000000..63eedaf --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleSketchFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleSolarizeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleSolarizeFilter.kt new file mode 100644 index 0000000..67c6c7f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleSolarizeFilter.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleThresholdDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleThresholdDitheringFilter.kt new file mode 100644 index 0000000..4e6e6fd --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleThresholdDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleWeavePixelationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleWeavePixelationFilter.kt new file mode 100644 index 0000000..c8d86b3 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleWeavePixelationFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSketchFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSketchFilter.kt new file mode 100644 index 0000000..50f29f3 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSketchFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSmearFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSmearFilter.kt new file mode 100644 index 0000000..6fc87c1 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSmearFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSmoothToonFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSmoothToonFilter.kt new file mode 100644 index 0000000..736682f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSmoothToonFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSobelEdgeDetectionFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSobelEdgeDetectionFilter.kt new file mode 100644 index 0000000..d60bfc4 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSobelEdgeDetectionFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSobelSimpleFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSobelSimpleFilter.kt new file mode 100644 index 0000000..8e5be07 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSobelSimpleFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSoftEleganceFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSoftEleganceFilter.kt new file mode 100644 index 0000000..4586694 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSoftEleganceFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSoftEleganceVariantFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSoftEleganceVariantFilter.kt new file mode 100644 index 0000000..9015c10 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSoftEleganceVariantFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSoftSpringLightFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSoftSpringLightFilter.kt new file mode 100644 index 0000000..44ba915 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSoftSpringLightFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSolarizeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSolarizeFilter.kt new file mode 100644 index 0000000..24725ad --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSolarizeFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSpacePortalFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSpacePortalFilter.kt new file mode 100644 index 0000000..d73d744 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSpacePortalFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSparkleFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSparkleFilter.kt new file mode 100644 index 0000000..a81b8f3 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSparkleFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSpectralFireFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSpectralFireFilter.kt new file mode 100644 index 0000000..6638f49 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSpectralFireFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSphereLensDistortionFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSphereLensDistortionFilter.kt new file mode 100644 index 0000000..6ce3f1d --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSphereLensDistortionFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSphereRefractionFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSphereRefractionFilter.kt new file mode 100644 index 0000000..f7c7f0f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSphereRefractionFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStackBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStackBlurFilter.kt new file mode 100644 index 0000000..43142d3 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStackBlurFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStaggeredPixelizationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStaggeredPixelizationFilter.kt new file mode 100644 index 0000000..f73a2b2 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStaggeredPixelizationFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStarBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStarBlurFilter.kt new file mode 100644 index 0000000..7c141c4 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStarBlurFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStrokePixelationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStrokePixelationFilter.kt new file mode 100644 index 0000000..0baa771 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStrokePixelationFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStuckiDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStuckiDitheringFilter.kt new file mode 100644 index 0000000..10df1dc --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStuckiDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSunriseFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSunriseFilter.kt new file mode 100644 index 0000000..fea4c52 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSunriseFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSwirlDistortionFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSwirlDistortionFilter.kt new file mode 100644 index 0000000..643c2a2 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSwirlDistortionFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTentBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTentBlurFilter.kt new file mode 100644 index 0000000..a7dafbc --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTentBlurFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiThresholdFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiThresholdFilter.kt new file mode 100644 index 0000000..decdb9a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiThresholdFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiToneCurvesFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiToneCurvesFilter.kt new file mode 100644 index 0000000..61eea7b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiToneCurvesFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiToonFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiToonFilter.kt new file mode 100644 index 0000000..337d272 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiToonFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTopHatFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTopHatFilter.kt new file mode 100644 index 0000000..7014370 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTopHatFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTornEdgeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTornEdgeFilter.kt new file mode 100644 index 0000000..ebc1fb0 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTornEdgeFilter.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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.TornEdgeParams +import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject +import com.t8rin.imagetoolbox.core.resources.R + +@UiFilterInject(group = UiFilterInject.Groups.EFFECTS) +class UiTornEdgeFilter( + override val value: TornEdgeParams = TornEdgeParams.Default +) : UiFilter( + title = R.string.torn_edge, + paramsInfo = listOf( + FilterParam(R.string.tooth_height, 1f..96f, roundTo = 0), + FilterParam(R.string.horizontal_tooth_range, 2f..128f, roundTo = 0), + FilterParam(R.string.vertical_tooth_range, 2f..128f, roundTo = 0) + ), + value = value +), Filter.TornEdge diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTriToneFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTriToneFilter.kt new file mode 100644 index 0000000..a472b68 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTriToneFilter.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTritanopiaFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTritanopiaFilter.kt new file mode 100644 index 0000000..4520511 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTritanopiaFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTritonomalyFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTritonomalyFilter.kt new file mode 100644 index 0000000..a7aa16e --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTritonomalyFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTwirlFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTwirlFilter.kt new file mode 100644 index 0000000..96ab861 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTwirlFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTwoRowSierraDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTwoRowSierraDitheringFilter.kt new file mode 100644 index 0000000..81af33c --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTwoRowSierraDitheringFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiUchimuraFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiUchimuraFilter.kt new file mode 100644 index 0000000..9c1545d --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiUchimuraFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiUnsharpFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiUnsharpFilter.kt new file mode 100644 index 0000000..50c381f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiUnsharpFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVHSFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVHSFilter.kt new file mode 100644 index 0000000..228b993 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVHSFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVHSNtscFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVHSNtscFilter.kt new file mode 100644 index 0000000..9662b1a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVHSNtscFilter.kt @@ -0,0 +1,279 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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.NtscParams +import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject +import com.t8rin.imagetoolbox.core.resources.R + +@UiFilterInject(group = UiFilterInject.Groups.DISTORTION) +class UiVHSNtscFilter( + override val value: NtscParams = NtscParams() +) : UiFilter( + title = R.string.vhs_ntsc, + value = value, + paramsInfo = listOf( + FilterParam( + title = R.string.amount, + valueRange = 0f..3f, + roundTo = 2 + ), + FilterParam( + title = R.string.tape_wear, + valueRange = 0f..4f, + roundTo = 2 + ), + FilterParam( + title = R.string.chroma_bleed, + valueRange = 0f..4f, + roundTo = 2 + ), + FilterParam( + title = R.string.tracking, + valueRange = 0f..4f, + roundTo = 2 + ), + FilterParam( + title = R.string.noise, + valueRange = 0f..4f, + roundTo = 2 + ), + FilterParam( + title = R.string.snow, + valueRange = 0f..4f, + roundTo = 2 + ), + FilterParam( + title = R.string.luma_smear, + valueRange = 0f..8f, + roundTo = 2 + ), + FilterParam( + title = R.string.sharpen, + valueRange = 0f..8f, + roundTo = 2 + ), + FilterParam( + title = R.string.ringing, + valueRange = 0f..16f, + roundTo = 2 + ), + FilterParam( + title = R.string.seed, + valueRange = 0f..10_000f, + roundTo = 0 + ), + FilterParam( + title = R.string.ntsc_phase_offset, + valueRange = -16f..16f, + roundTo = 0 + ), + FilterParam( + title = R.string.ntsc_head_switching_height, + valueRange = 0f..160f, + roundTo = 0 + ), + FilterParam( + title = R.string.ntsc_head_switching_offset, + valueRange = -80f..80f, + roundTo = 0 + ), + FilterParam( + title = R.string.ntsc_head_switching_shift, + valueRange = -800f..800f, + roundTo = 1 + ), + FilterParam( + title = R.string.ntsc_midline_position, + valueRange = 0f..1f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_midline_jitter, + valueRange = 0f..2f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_tracking_noise_height, + valueRange = 0f..160f, + roundTo = 0 + ), + FilterParam( + title = R.string.ntsc_tracking_wave, + valueRange = 0f..160f, + roundTo = 1 + ), + FilterParam( + title = R.string.ntsc_tracking_snow, + valueRange = 0f..1f, + roundTo = 3 + ), + FilterParam( + title = R.string.ntsc_tracking_snow_anisotropy, + valueRange = 0f..1f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_tracking_noise_intensity, + valueRange = 0f..2f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_composite_noise_frequency, + valueRange = 0f..8f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_composite_noise_intensity, + valueRange = 0f..3f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_composite_noise_detail, + valueRange = 0f..16f, + roundTo = 0 + ), + FilterParam( + title = R.string.ntsc_ringing_frequency, + valueRange = 0f..8f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_ringing_power, + valueRange = 0f..20f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_luma_noise_frequency, + valueRange = 0f..8f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_luma_noise_intensity, + valueRange = 0f..3f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_luma_noise_detail, + valueRange = 0f..16f, + roundTo = 0 + ), + FilterParam( + title = R.string.ntsc_chroma_noise_frequency, + valueRange = 0f..8f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_chroma_noise_intensity, + valueRange = 0f..3f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_chroma_noise_detail, + valueRange = 0f..16f, + roundTo = 0 + ), + FilterParam( + title = R.string.ntsc_snow_intensity, + valueRange = 0f..0.1f, + roundTo = 4 + ), + FilterParam( + title = R.string.ntsc_snow_anisotropy, + valueRange = 0f..1f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_chroma_phase_noise, + valueRange = 0f..0.25f, + roundTo = 4 + ), + FilterParam( + title = R.string.ntsc_chroma_phase_error, + valueRange = -1f..1f, + roundTo = 3 + ), + FilterParam( + title = R.string.ntsc_chroma_delay_horizontal, + valueRange = -100f..100f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_chroma_delay_vertical, + valueRange = -40f..40f, + roundTo = 0 + ), + FilterParam( + title = R.string.ntsc_vhs_chroma_loss, + valueRange = 0f..0.01f, + roundTo = 5 + ), + FilterParam( + title = R.string.ntsc_vhs_sharpen_intensity, + valueRange = 0f..4f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_vhs_sharpen_frequency, + valueRange = 0f..12f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_vhs_edge_wave_intensity, + valueRange = 0f..6f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_vhs_edge_wave_speed, + valueRange = 0f..24f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_vhs_edge_wave_frequency, + valueRange = 0f..1f, + roundTo = 3 + ), + FilterParam( + title = R.string.ntsc_vhs_edge_wave_detail, + valueRange = 0f..16f, + roundTo = 0 + ), + FilterParam( + title = R.string.ntsc_scale_horizontal, + valueRange = 0.1f..4f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_scale_vertical, + valueRange = 0.1f..4f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_scale_factor_x, + valueRange = 0.1f..4f, + roundTo = 2 + ), + FilterParam( + title = R.string.ntsc_scale_factor_y, + valueRange = 0.1f..4f, + roundTo = 2 + ) + ) +), Filter.VHSNtsc diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVibranceFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVibranceFilter.kt new file mode 100644 index 0000000..15c0e90 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVibranceFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVignetteFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVignetteFilter.kt new file mode 100644 index 0000000..a23ad8a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVignetteFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVintageFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVintageFilter.kt new file mode 100644 index 0000000..7aced1b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVintageFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVoronoiCrystallizeFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVoronoiCrystallizeFilter.kt new file mode 100644 index 0000000..f3006a2 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVoronoiCrystallizeFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVortexPixelizationFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVortexPixelizationFilter.kt new file mode 100644 index 0000000..1032402 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVortexPixelizationFilter.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWarmFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWarmFilter.kt new file mode 100644 index 0000000..5d72729 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWarmFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWaterDropFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWaterDropFilter.kt new file mode 100644 index 0000000..54f1385 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWaterDropFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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.WaterDropParams +import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject +import com.t8rin.imagetoolbox.core.resources.R + +@UiFilterInject(group = UiFilterInject.Groups.DISTORTION) +class UiWaterDropFilter( + override val value: WaterDropParams = WaterDropParams() +) : UiFilter( + title = R.string.water_drop, + paramsInfo = listOf( + R.string.wavelength paramTo 0.01f..1f, + R.string.amplitude paramTo 0f..0.5f, + FilterParam(R.string.phase, 0f..360f, 0), + R.string.center_x paramTo 0f..1f, + R.string.center_y paramTo 0f..1f, + R.string.radius paramTo 0.01f..1f + ), + value = value +), Filter.WaterDrop diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWaterEffectFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWaterEffectFilter.kt new file mode 100644 index 0000000..88a5bff --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWaterEffectFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWeakPixelFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWeakPixelFilter.kt new file mode 100644 index 0000000..e94e45e --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWeakPixelFilter.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWeaveFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWeaveFilter.kt new file mode 100644 index 0000000..15fd37f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWeaveFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWhiteBalanceFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWhiteBalanceFilter.kt new file mode 100644 index 0000000..2c73c2b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWhiteBalanceFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiYililomaDitheringFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiYililomaDitheringFilter.kt new file mode 100644 index 0000000..f01bcc0 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiYililomaDitheringFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiZoomBlurFilter.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiZoomBlurFilter.kt new file mode 100644 index 0000000..c89a324 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiZoomBlurFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy 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 \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/utils/LamaLoader.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/utils/LamaLoader.kt new file mode 100644 index 0000000..e0198fc --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/utils/LamaLoader.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/utils/Mappings.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/utils/Mappings.kt new file mode 100644 index 0000000..4cf23a0 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/utils/Mappings.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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) + } \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/utils/ShaderValidationErrorUtils.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/utils/ShaderValidationErrorUtils.kt new file mode 100644 index 0000000..b095c5d --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/utils/ShaderValidationErrorUtils.kt @@ -0,0 +1,234 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderParseError +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderParseException +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderValidationError +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.utils.appContext + +fun ShaderParseException.localizedMessage(): String = + errors.joinToString(separator = "\n") { it.localizedMessage() } + +fun ShaderParseError.localizedMessage(): String = appContext.run { + when (this@localizedMessage) { + ShaderParseError.EmptyFile -> getString(R.string.shader_parse_error_empty_file) + ShaderParseError.NotItShaderJson -> getString(R.string.shader_parse_error_not_itshader_json) + ShaderParseError.InvalidJson -> getString(R.string.shader_parse_error_invalid_json) + is ShaderParseError.MissingField -> getString( + R.string.shader_parse_error_missing_field, + field + ) + + is ShaderParseError.MissingParamField -> getString( + R.string.shader_parse_error_missing_param_field, + path + ) + + is ShaderParseError.UnsupportedParamType -> getString( + R.string.shader_parse_error_unsupported_param_type, + path, + type, + supportedTypes + ) + + is ShaderParseError.MissingValue -> getString( + R.string.shader_parse_error_missing_value, + path, + type + ) + + is ShaderParseError.FiniteNumberExpected -> getString( + R.string.shader_parse_error_finite_number_expected, + path + ) + + is ShaderParseError.IntegerExpected -> getString( + R.string.shader_parse_error_integer_expected, + path + ) + + is ShaderParseError.BoolExpected -> getString( + R.string.shader_parse_error_bool_expected, + path + ) + + is ShaderParseError.ColorExpected -> getString( + R.string.shader_parse_error_color_expected, + path + ) + + is ShaderParseError.ColorFormatExpected -> getString( + R.string.shader_parse_error_color_format_expected, + path + ) + + is ShaderParseError.HexColorExpected -> getString( + R.string.shader_parse_error_hex_color_expected, + path + ) + + is ShaderParseError.ColorChannelCountExpected -> getString( + R.string.shader_parse_error_color_channel_count_expected, + path + ) + + is ShaderParseError.ColorChannelRangeExpected -> getString( + R.string.shader_parse_error_color_channel_range_expected, + path + ) + + is ShaderParseError.Vec2Expected -> getString( + R.string.shader_parse_error_vec2_expected, + path + ) + + is ShaderParseError.Vec2ComponentCountExpected -> getString( + R.string.shader_parse_error_vec2_component_count_expected, + path + ) + } +} + +fun ShaderValidationError.localizedMessage() = appContext.run { + when (this@localizedMessage) { + is ShaderValidationError.UnsupportedVersion -> getString( + R.string.shader_error_unsupported_version, + version, + supportedVersion + ) + + ShaderValidationError.BlankName -> getString(R.string.shader_error_blank_name) + ShaderValidationError.BlankSource -> getString(R.string.shader_error_blank_source) + is ShaderValidationError.UnsupportedCharacters -> getString( + R.string.shader_error_unsupported_characters, + characters + ) + + is ShaderValidationError.DuplicateParameter -> getString( + R.string.shader_error_duplicate_parameter, + name + ) + + ShaderValidationError.BlankParameterName -> getString( + R.string.shader_error_blank_parameter_name + ) + + is ShaderValidationError.ReservedParameterName -> getString( + R.string.shader_error_reserved_parameter_name, + name + ) + + is ShaderValidationError.InvalidParameterName -> getString( + R.string.shader_error_invalid_parameter_name, + name + ) + + is ShaderValidationError.InvalidValueType -> getString( + R.string.shader_error_invalid_value_type, + name, + fieldName, + actualType, + expectedType + ) + + is ShaderValidationError.BoolBounds -> getString( + R.string.shader_error_bool_bounds, + name + ) + + is ShaderValidationError.MinGreaterThanMax -> getString( + R.string.shader_error_min_greater_than_max, + name + ) + + is ShaderValidationError.DefaultLowerThanMin -> getString( + R.string.shader_error_default_lower_than_min, + name + ) + + is ShaderValidationError.DefaultGreaterThanMax -> getString( + R.string.shader_error_default_greater_than_max, + name + ) + + is ShaderValidationError.MissingInputTexture -> getString( + R.string.shader_error_missing_input_texture, + name + ) + + is ShaderValidationError.MissingTextureCoordinate -> getString( + R.string.shader_error_missing_texture_coordinate, + name + ) + + ShaderValidationError.MissingMainFunction -> getString( + R.string.shader_error_missing_main_function + ) + + ShaderValidationError.MissingFragmentColor -> getString( + R.string.shader_error_missing_fragment_color + ) + + ShaderValidationError.ShaderToyMainImage -> getString( + R.string.shader_error_shadertoy_main_image + ) + + ShaderValidationError.ShaderToyITime -> getString(R.string.shader_error_shadertoy_i_time) + ShaderValidationError.ShaderToyIFrame -> getString(R.string.shader_error_shadertoy_i_frame) + ShaderValidationError.ShaderToyIResolution -> getString( + R.string.shader_error_shadertoy_i_resolution + ) + + ShaderValidationError.ShaderToyIChannel -> getString( + R.string.shader_error_shadertoy_i_channel + ) + + ShaderValidationError.ExternalTextures -> getString( + R.string.shader_error_external_textures + ) + + ShaderValidationError.ExternalTextureExtensions -> getString( + R.string.shader_error_external_texture_extensions + ) + + ShaderValidationError.LibretroParameters -> getString( + R.string.shader_error_libretro_parameters + ) + + is ShaderValidationError.ExtraInputTexture -> getString( + R.string.shader_error_extra_input_texture, + samplerType, + samplerName + ) + + is ShaderValidationError.MissingUniform -> getString( + R.string.shader_error_missing_uniform, + name, + expectedType + ) + + is ShaderValidationError.WrongUniformType -> getString( + R.string.shader_error_wrong_uniform_type, + name, + actualType, + expectedType + ) + } +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/AddFilterButton.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/AddFilterButton.kt new file mode 100644 index 0000000..5a46111 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/AddFilterButton.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 com.t8rin.imagetoolbox.core.resources.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.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.AutoFixHigh +import com.t8rin.imagetoolbox.core.resources.icons.Extension +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)) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/CubeLutDownloadDialog.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/CubeLutDownloadDialog.kt new file mode 100644 index 0000000..02171cd --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/CubeLutDownloadDialog.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 com.t8rin.imagetoolbox.core.resources.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.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.TableChart +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.Rounded.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 + ) + } + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterItem.kt new file mode 100644 index 0000000..40fbb94 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterItem.kt @@ -0,0 +1,396 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.net.Uri +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.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.domain.model.shader.ShaderPreset +import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ContentCopy +import com.t8rin.imagetoolbox.core.resources.icons.DragHandle +import com.t8rin.imagetoolbox.core.resources.icons.Extension +import com.t8rin.imagetoolbox.core.resources.icons.KeyboardArrowDown +import com.t8rin.imagetoolbox.core.resources.icons.MoreVert +import com.t8rin.imagetoolbox.core.resources.icons.RemoveCircle +import com.t8rin.imagetoolbox.core.resources.icons.Visibility +import com.t8rin.imagetoolbox.core.resources.icons.VisibilityOff +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.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)?, + onDuplicate: (() -> Unit)? = null, + backgroundColor: Color = Color.Unspecified, + shape: Shape = MaterialTheme.shapes.extraLarge, + canHide: Boolean = true, + shaderPresets: List = emptyList(), + onImportShaderPreset: (suspend (Uri) -> ShaderPreset?)? = null, + additionalContent: @Composable (() -> Unit)? = null +) { + var isControlsExpanded by rememberSaveable { + mutableStateOf(true) + } + + val onCreateTemplateSafe = onCreateTemplate?.takeIf { filter.canUseTemplate } + + 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.Outlined.RemoveCircle, + contentDescription = stringResource(R.string.create_template) + ) + Spacer(Modifier.width(8.dp)) + Text(stringResource(R.string.remove)) + } + Spacer(Modifier.height(4.dp)) + onDuplicate?.let { + EnhancedButton( + modifier = Modifier.fillMaxWidth(), + onClick = { + onDuplicate() + showPopup = false + }, + shape = ShapeDefaults.center, + containerColor = takeColorFromScheme { + secondary.blend(primary) + }, + contentColor = takeColorFromScheme { + onSecondary.blend(onPrimary) + } + ) { + Icon( + imageVector = Icons.Rounded.ContentCopy, + contentDescription = stringResource(R.string.duplicate) + ) + Spacer(Modifier.width(8.dp)) + Text(stringResource(R.string.duplicate)) + } + Spacer(Modifier.height(4.dp)) + } + EnhancedButton( + modifier = Modifier.fillMaxWidth(), + onClick = { + filter.isVisible = !isVisible + onFilterChange(filter.value as Any) + showPopup = false + }, + shape = onCreateTemplateSafe?.let { + ShapeDefaults.center + } ?: ShapeDefaults.bottom, + containerColor = MaterialTheme.colorScheme.primary + ) { + Icon( + imageVector = if (isVisible) { + Icons.Rounded.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 + ) + ) + } + onCreateTemplateSafe?.let { + Spacer(Modifier.height(4.dp)) + EnhancedButton( + modifier = Modifier.fillMaxWidth(), + onClick = { + onCreateTemplateSafe() + 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.Outlined.RemoveCircle, + 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) } + val initialValue = rememberSaveable { sliderValue } + ValueText( + value = sliderValue, + onClick = { showValueDialog = true }, + onLongClick = { + sliderValue = initialValue + onFilterChange(initialValue) + } + ) + 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 + ) { + Column { + FilterItemContent( + filter = filter, + onFilterChange = onFilterChange, + shaderPresets = shaderPresets, + onImportShaderPreset = onImportShaderPreset, + previewOnly = previewOnly + ) + additionalContent?.invoke() + } + } + } + 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 diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterItemContent.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterItemContent.kt new file mode 100644 index 0000000..09112ec --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterItemContent.kt @@ -0,0 +1,471 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.net.Uri +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.DistortPerspectiveParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.DropShadowParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.EnhancedZoomBlurParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.FlareParams +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.NtscParams +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.SeamCarvingParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.ShaderParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.ShearParams +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.TornEdgeParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.VoronoiCrystallizeParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.WaterDropParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.WaterParams +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderPreset +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.DistortPerspectiveParamsItem +import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.DropShadowParamsItem +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.FlareParamsItem +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.NtscParamsItem +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.SeamCarvingParamsItem +import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.ShaderParamsItem +import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.ShearParamsItem +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.TornEdgeParamsItem +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.WaterDropParamsItem +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, + shaderPresets: List = emptyList(), + onImportShaderPreset: (suspend (Uri) -> ShaderPreset?)? = null, + previewOnly: Boolean = false, +) { + Column( + modifier = modifier + ) { + when (val value = filter.value) { + is FilterValueWrapper<*> -> { + FilterValueWrapperItem( + value = value, + filter = filter.cast(), + onFilterChange = onFilterChange, + previewOnly = previewOnly + ) + } + + is ShaderParams -> { + ShaderParamsItem( + value = value, + presets = shaderPresets, + onValueChange = onFilterChange, + onImportPreset = onImportShaderPreset, + 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 DistortPerspectiveParams -> { + DistortPerspectiveParamsItem( + value = value, + filter = filter.cast(), + onFilterChange = onFilterChange, + previewOnly = previewOnly + ) + } + + is FlareParams -> { + FlareParamsItem( + value = value, + filter = filter.cast(), + onFilterChange = onFilterChange, + previewOnly = previewOnly + ) + } + + is ShearParams -> { + ShearParamsItem( + value = value, + filter = filter.cast(), + onFilterChange = onFilterChange, + previewOnly = previewOnly + ) + } + + is WaterDropParams -> { + WaterDropParamsItem( + value = value, + filter = filter.cast(), + onFilterChange = onFilterChange, + previewOnly = previewOnly + ) + } + + is IntegerSize -> { + IntegerSizeParamsItem( + value = value, + filter = filter.cast(), + onFilterChange = onFilterChange, + previewOnly = previewOnly + ) + } + + is SeamCarvingParams -> { + SeamCarvingParamsItem( + value = value, + filter = filter.cast(), + onFilterChange = onFilterChange, + previewOnly = previewOnly + ) + } + + is BloomParams -> { + BloomParamsItem( + value = value, + filter = filter.cast(), + onFilterChange = onFilterChange, + previewOnly = previewOnly + ) + } + + is NtscParams -> { + NtscParamsItem( + value = value, + filter = filter.cast(), + onFilterChange = onFilterChange, + previewOnly = previewOnly + ) + } + + is DropShadowParams -> { + DropShadowParamsItem( + value = value, + filter = filter.cast(), + onFilterChange = onFilterChange, + previewOnly = previewOnly + ) + } + + is TornEdgeParams -> { + TornEdgeParamsItem( + value = value, + filter = filter.cast(), + onFilterChange = onFilterChange, + previewOnly = previewOnly + ) + } + } + } +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterPreviewPicture.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterPreviewPicture.kt new file mode 100644 index 0000000..c7d0a37 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterPreviewPicture.kt @@ -0,0 +1,188 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.Canvas +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer +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.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.scale +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.drawscope.withTransform +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.R +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 + +@Composable +internal fun FilterPreviewPicture( + model: Any?, + canShowImage: Boolean, + canOpenPreview: Boolean, + onOpenPreview: () -> Unit, + modifier: Modifier = Modifier +) { + Box( + contentAlignment = Alignment.Center, + modifier = modifier + ) { + if (canShowImage) { + Picture( + model = model, + contentScale = ContentScale.Crop, + contentDescription = null, + modifier = Modifier + .size(48.dp) + .scale(1.2f), + shape = MaterialTheme.shapes.medium + ) + } else { + Spacer( + modifier = Modifier + .size(48.dp) + .scale(1.2f) + .clip(MaterialTheme.shapes.medium) + .shimmer(true) + ) + } + if (canOpenPreview) { + val previewContentDescription = stringResource(R.string.image_preview) + val white = MaterialTheme.colorScheme.secondaryFixed + val black = MaterialTheme.colorScheme.onSecondaryFixed + + Canvas( + modifier = Modifier + .size(36.dp) + .clip(ShapeDefaults.small) + .hapticsClickable(onClick = onOpenPreview) + .semantics { + contentDescription = previewContentDescription + } + .padding(4.dp) + ) { + val strokeWidth = 2.dp.toPx() + val stroke = Stroke( + width = strokeWidth + ) + val dashedStroke = Stroke( + width = strokeWidth, + pathEffect = PathEffect.dashPathEffect( + intervals = floatArrayOf( + 3.dp.toPx(), + 3.dp.toPx() + ) + ) + ) + val cornerRadius = CornerRadius(8.dp.toPx()) + val borderTopLeft = Offset(strokeWidth / 2f, strokeWidth / 2f) + + val borderSize = Size( + width = size.width - strokeWidth, + height = size.height - strokeWidth + ) + + drawRoundRect( + color = black, + topLeft = borderTopLeft, + size = borderSize, + cornerRadius = cornerRadius, + style = stroke + ) + drawRoundRect( + color = black.copy(0.3f), + topLeft = borderTopLeft, + size = borderSize, + cornerRadius = cornerRadius + ) + drawRoundRect( + color = white, + topLeft = borderTopLeft, + size = borderSize, + cornerRadius = cornerRadius, + style = dashedStroke + ) + + val arrowWidth = size.width * 0.3f + val arrowBorderWidth = strokeWidth * 1.5f + + withTransform( + transformBlock = { + translate( + left = size.width / 2f, + top = size.height / 2f + ) + scale( + scaleX = arrowWidth, + scaleY = arrowWidth, + pivot = Offset.Zero + ) + } + ) { + drawPath( + path = PlayArrowPath, + color = black, + style = Stroke( + width = arrowBorderWidth / arrowWidth, + join = StrokeJoin.Round + ) + ) + drawPath( + path = PlayArrowPath, + color = white + ) + } + } + } + } +} + +private val PlayArrowPath: Path by lazy(LazyThreadSafetyMode.NONE) { + fun y(value: Float) = value * 1.1666666f + + Path().apply { + moveTo(-0.32f, y(0.47f)) + lineTo(0.45f, y(0.08f)) + quadraticTo(0.5f, y(0.05f), 0.5f, y(0f)) + quadraticTo(0.5f, y(-0.05f), 0.45f, y(-0.08f)) + lineTo(-0.32f, y(-0.47f)) + quadraticTo(-0.38f, y(-0.5f), -0.44f, y(-0.47f)) + quadraticTo(-0.5f, y(-0.45f), -0.5f, y(-0.39f)) + lineTo(-0.5f, y(0.39f)) + quadraticTo(-0.5f, y(0.45f), -0.44f, y(0.47f)) + quadraticTo(-0.38f, y(0.5f), -0.32f, y(0.47f)) + close() + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterPreviewSheet.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterPreviewSheet.kt new file mode 100644 index 0000000..9cb8f64 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterPreviewSheet.kt @@ -0,0 +1,280 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.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 androidx.lifecycle.compose.collectAsStateWithLifecycle +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.Done +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 + val shaderPresets by component.shaderPresets.collectAsStateWithLifecycle() + 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, + shaderPresets = shaderPresets, + onImportShaderPreset = component::importShaderPreset, + 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) + } + } + ) +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterReorderSheet.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterReorderSheet.kt new file mode 100644 index 0000000..7a53c7d --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterReorderSheet.kt @@ -0,0 +1,140 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.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)) + } + }, + ) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterSelectionCubeLutBottomContent.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterSelectionCubeLutBottomContent.kt new file mode 100644 index 0000000..699923d --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterSelectionCubeLutBottomContent.kt @@ -0,0 +1,399 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.Download +import com.t8rin.imagetoolbox.core.resources.icons.Search +import com.t8rin.imagetoolbox.core.resources.icons.SearchOff +import com.t8rin.imagetoolbox.core.resources.icons.TableChart +import com.t8rin.imagetoolbox.core.resources.icons.Update +import com.t8rin.imagetoolbox.core.resources.icons.Visibility +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.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.Outlined.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(160, 160) + .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.Outlined.SearchOff, + contentDescription = null, + modifier = Modifier + .weight(2f) + .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) + .fillMaxSize() + ) + Spacer(Modifier.weight(1f)) + } + } + } + } + } +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterSelectionItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterSelectionItem.kt new file mode 100644 index 0000000..daa8fe5 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterSelectionItem.kt @@ -0,0 +1,195 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.width +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.Shape +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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Bookmark +import com.t8rin.imagetoolbox.core.resources.icons.BookmarkRemove +import com.t8rin.imagetoolbox.core.resources.icons.WifiTetheringError +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.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 + +@Composable +internal fun FilterSelectionItem( + filter: UiFilter<*>, + isFavoritePage: Boolean, + canOpenPreview: Boolean, + showPreviewImage: Boolean, + isInFavorite: Boolean, + 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 showDownloadDialog by rememberSaveable { + mutableStateOf(false) + } + + var downloadOnlyNewData by rememberSaveable { + mutableStateOf(false) + } + + var forceUpdate by rememberSaveable { + mutableStateOf(false) + } + + PreferenceItemOverload( + title = stringResource(filter.title), + startIcon = { + Row(verticalAlignment = Alignment.CenterVertically) { + FilterPreviewPicture( + 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(160, 160) + .build() + }, + canShowImage = showPreviewImage, + canOpenPreview = canOpenPreview, + onOpenPreview = onOpenPreview + ) + 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) + ) { + AnimatedContent( + targetState = isInFavorite to isFavoritePage, + transitionSpec = { + (fadeIn() + scaleIn(initialScale = 0.85f)) + .togetherWith(fadeOut() + scaleOut(targetScale = 0.85f)) + } + ) { (isInFavorite, isFavPage) -> + val icon = remember(isInFavorite, isFavPage) { + when { + isFavPage && isInFavorite -> Icons.Rounded.BookmarkRemove + isInFavorite -> Icons.Rounded.Bookmark + else -> Icons.Outlined.Bookmark + } + } + 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.Rounded.WifiTetheringError, + duration = ToastDuration.Long + ) + } + }, + downloadOnlyNewData = downloadOnlyNewData, + cubeLutDownloadProgress = cubeLutDownloadProgress + ) +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterTemplateAddingGroup.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterTemplateAddingGroup.kt new file mode 100644 index 0000000..ba3c541 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterTemplateAddingGroup.kt @@ -0,0 +1,107 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FileOpen +import com.t8rin.imagetoolbox.core.resources.icons.QrCodeScanner +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.widget.enhanced.EnhancedButton +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton + +@SuppressLint("StringFormatInvalid") +@Composable +internal fun FilterTemplateAddingGroup( + component: FilterTemplateCreationSheetComponent, + onAddTemplateFilterFromString: (string: String) -> Unit, + onAddTemplateFilterFromUri: (uri: String) -> Unit +) { + val scanner = rememberBarcodeScanner { + onAddTemplateFilterFromString(it.raw) + } + + val importFromFileLauncher = rememberFilePicker { uri: Uri -> + onAddTemplateFilterFromUri(uri.toString()) + } + + 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 + ) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterTemplateCreationSheet.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterTemplateCreationSheet.kt new file mode 100644 index 0000000..64d317b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterTemplateCreationSheet.kt @@ -0,0 +1,505 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.Extension +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.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 + } + +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterTemplateInfoSheet.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterTemplateInfoSheet.kt new file mode 100644 index 0000000..8d9fefd --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterTemplateInfoSheet.kt @@ -0,0 +1,423 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.FilePresent +import com.t8rin.imagetoolbox.core.resources.icons.QrCode2 +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.AutoFixHigh +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.EditAlt +import com.t8rin.imagetoolbox.core.resources.icons.Extension +import com.t8rin.imagetoolbox.core.resources.icons.QrCode +import com.t8rin.imagetoolbox.core.resources.icons.Save +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.Outlined.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) + ) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/TemplateFilterSelectionItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/TemplateFilterSelectionItem.kt new file mode 100644 index 0000000..e769408 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/TemplateFilterSelectionItem.kt @@ -0,0 +1,116 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.width +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.graphics.Shape +import androidx.compose.ui.unit.dp +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Info +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.preferences.PreferenceItemOverload +import com.t8rin.imagetoolbox.core.utils.appContext + +@Composable +internal fun TemplateFilterSelectionItem( + templateFilter: TemplateFilter, + onClick: () -> Unit, + onLongClick: () -> Unit, + onRequestFilterMapping: (UiFilter<*>) -> Transformation, + onInfoClick: () -> Unit, + showPreviewImage: Boolean, + shape: Shape, + modifier: Modifier +) { + val previewModel = LocalFilterPreviewModelProvider.current.preview + + PreferenceItemOverload( + title = templateFilter.name, + startIcon = { + Row(verticalAlignment = Alignment.CenterVertically) { + FilterPreviewPicture( + 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(160, 160) + .build() + }, + canShowImage = showPreviewImage, + canOpenPreview = true, + onOpenPreview = onLongClick + ) + 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 + ) +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/AddFiltersSheet.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/AddFiltersSheet.kt new file mode 100644 index 0000000..98119ce --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/AddFiltersSheet.kt @@ -0,0 +1,540 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.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.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.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 androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.AutoFixHigh +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.Search +import com.t8rin.imagetoolbox.core.resources.icons.SearchOff +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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.collectAsStateWithLifecycle() + val recentFilters by component.recentFiltersFlow.collectAsStateWithLifecycle() + + val tabs: List = remember( + canAddTemplates, + favoriteFilters, + recentFilters + ) { + buildList { + if (canAddTemplates) { + add(UiFilter.Group.Template) + } + add(UiFilter.Group.Recent(recentFilters)) + add(UiFilter.Group.Favorite(favoriteFilters)) + addAll(UiFilter.groups) + } + } + val favoriteFilterKeys = remember(favoriteFilters) { + favoriteFilters.mapTo(HashSet()) { it::class.java.name } + } + + val haptics = LocalHapticFeedback.current + val pagerState = rememberPagerState( + pageCount = { tabs.size }, + initialPage = if (canAddTemplates) 3 else 2 + ) + + val pickFilter: (UiFilter<*>) -> Unit = { filter -> + component.addRecentFilter(filter) + onFilterPicked(filter) + } + val pickFilterWithParams: (UiFilter<*>) -> Unit = { filter -> + component.addRecentFilter(filter) + onFilterPickedWithParams(filter) + } + + var isSearching by rememberSaveable { + mutableStateOf(false) + } + var searchKeyword by rememberSaveable(isSearching) { + mutableStateOf("") + } + val allFilters = remember(canAddTemplates) { + UiFilter.groups.flatMap { group -> + group.filters(canAddTemplates).sortedBy { getString(it.title) } + } + } + var filtersForSearch by remember(allFilters) { + mutableStateOf(allFilters) + } + val resourceManager = LocalResourceManager.current + LaunchedEffect(searchKeyword, allFilters, resourceManager) { + delay(400L) + val keyword = searchKeyword.trim() + filtersForSearch = if (keyword.isEmpty()) { + allFilters + } else { + withContext(Dispatchers.Default) { + allFilters.filter { + resourceManager.getString(it.title).contains( + other = keyword, + ignoreCase = true + ) || resourceManager.getStringLocalized( + resId = it.title, + language = Locale.ENGLISH.language + ).contains( + other = keyword, + 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, + showPreviewImage = true, + isInFavorite = filter::class.java.name in favoriteFilterKeys, + onLongClick = { + component.setPreviewData(filter) + }, + onOpenPreview = { + component.setPreviewData(filter) + }, + onClick = { + onVisibleChange(false) + pickFilter(filter) + }, + onRequestFilterMapping = component::filterToTransformation, + 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.Outlined.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 -> + val showPreviewImages = page == pagerState.settledPage + + when (val group = tabs[page]) { + is UiFilter.Group.Template -> { + TemplatesContent( + component = component, + filterTemplateCreationSheetComponent = filterTemplateCreationSheetComponent, + onVisibleChange = onVisibleChange, + onFilterPickedWithParams = pickFilterWithParams, + showPreviewImages = showPreviewImages + ) + } + + is UiFilter.Group.Favorite -> { + FavoritesContent( + component = component, + favoriteFilters = favoriteFilters, + favoriteFilterKeys = favoriteFilterKeys, + onVisibleChange = onVisibleChange, + onFilterPickedWithParams = pickFilterWithParams, + onFilterPicked = pickFilter, + previewBitmap = previewBitmap, + showPreviewImages = showPreviewImages + ) + } + + is UiFilter.Group.Recent -> { + OtherContent( + component = component, + currentGroup = group, + filters = recentFilters, + favoriteFilterKeys = favoriteFilterKeys, + onVisibleChange = onVisibleChange, + onFilterPickedWithParams = pickFilterWithParams, + onFilterPicked = pickFilter, + previewBitmap = previewBitmap, + showPreviewImages = showPreviewImages + ) + } + + else -> { + val filters = remember(group, canAddTemplates) { + group.filters(canAddTemplates) + } + OtherContent( + component = component, + currentGroup = group, + filters = filters, + favoriteFilterKeys = favoriteFilterKeys, + onVisibleChange = onVisibleChange, + onFilterPickedWithParams = pickFilterWithParams, + onFilterPicked = pickFilter, + previewBitmap = previewBitmap, + showPreviewImages = showPreviewImages + ) + } + } + } + } + } + }, + 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.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.Outlined.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 = pickFilterWithParams, + 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 + ) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/AddFiltersSheetComponent.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/AddFiltersSheetComponent.kt new file mode 100644 index 0000000..e65df3d --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/AddFiltersSheetComponent.kt @@ -0,0 +1,479 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.TEMPLATE_EXT +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.resource.ResourceManager +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.ShaderPresetRepository +import com.t8rin.imagetoolbox.core.filters.domain.model.Filter +import com.t8rin.imagetoolbox.core.filters.domain.model.TemplateFilter +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderParseException +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.localizedMessage +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AutoFixHigh +import com.t8rin.imagetoolbox.core.resources.icons.QrCodeScanner +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.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +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 shaderPresetRepository: ShaderPresetRepository, + private val imageGetter: ImageGetter, + private val remoteResourcesStore: RemoteResourcesStore, + private val resourceManager: ResourceManager, + 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()}.${TEMPLATE_EXT}" + } + + fun reorderFavoriteFilters(value: List>) { + componentScope.launch { + favoriteInteractor.reorderFavoriteFilters(value) + } + } + + val favoritesFlow: StateFlow>> = favoriteInteractor.getFavoriteFilters() + .map { list -> + list.map { + it.toUiFilter() + } + }.stateIn( + scope = componentScope, + started = SharingStarted.Lazily, + initialValue = emptyList() + ) + + val recentFiltersFlow: StateFlow>> = favoriteInteractor.getRecentFilters() + .map { list -> + list.map { + it.toUiFilter() + } + }.stateIn( + scope = componentScope, + started = SharingStarted.Lazily, + initialValue = emptyList() + ) + + val shaderPresets = shaderPresetRepository.getPresets() + .stateIn( + scope = componentScope, + started = SharingStarted.Eagerly, + initialValue = emptyList() + ) + + suspend fun importShaderPreset(uri: Uri) = runCatching { + fileController.readBytes(uri.toString()).decodeToString() + }.mapCatching { + shaderPresetRepository.importPreset(it).getOrThrow() + }.onFailure { throwable -> + if (throwable is ShaderParseException) { + AppToastHost.showFailureToast(throwable.localizedMessage()) + } else { + AppToastHost.showFailureToast(throwable) + } + }.getOrNull() + + val templatesFlow: StateFlow> = favoriteInteractor.getTemplateFilters() + .map { list -> + list.sortedBy { it.name } + }.stateIn( + scope = componentScope, + started = SharingStarted.Lazily, + initialValue = emptyList() + ) + + fun toggleFavorite(filter: UiFilter<*>) { + componentScope.launch { + favoriteInteractor.toggleFavorite(filter) + } + } + + fun addRecentFilter(filter: UiFilter<*>) { + componentScope.launch { + favoriteInteractor.addRecentFilter(filter) + } + } + + fun removeTemplateFilter(templateFilter: TemplateFilter) { + componentScope.launch { + favoriteInteractor.removeTemplateFilter(templateFilter) + } + } + + suspend fun convertTemplateFilterToString( + templateFilter: TemplateFilter + ): String = favoriteInteractor.convertTemplateFilterToString(templateFilter) + + fun addTemplateFilterFromString(string: String) { + componentScope.launch { + favoriteInteractor.addTemplateFilterFromString( + string = string, + onSuccess = { filterName, filtersCount -> + AppToastHost.showToast( + message = resourceManager.getString( + R.string.added_filter_template, + filterName, + filtersCount + ), + icon = Icons.Outlined.AutoFixHigh + ) + }, + onFailure = { + AppToastHost.showToast( + message = resourceManager.getString(R.string.scanned_qr_code_isnt_filter_template), + icon = Icons.Rounded.QrCodeScanner + ) + } + ) + } + } + + fun addTemplateFilterFromUri(uri: String) { + componentScope.launch { + favoriteInteractor.addTemplateFilterFromUri( + uri = uri, + onSuccess = { filterName, filtersCount -> + AppToastHost.showToast( + message = resourceManager.getString( + R.string.added_filter_template, + filterName, + filtersCount + ), + icon = Icons.Outlined.AutoFixHigh + ) + }, + onFailure = { + AppToastHost.showToast( + message = resourceManager.getString(R.string.opened_file_have_no_filter_template), + icon = Icons.Outlined.AutoFixHigh + ) + } + ) + } + } + + 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 + } + +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/FavoritesContent.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/FavoritesContent.kt new file mode 100644 index 0000000..3f37813 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/FavoritesContent.kt @@ -0,0 +1,209 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.material3.Icon +import androidx.compose.material3.Text +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.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.widget.FilterSelectionItem +import com.t8rin.imagetoolbox.core.resources.Icons +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, + favoriteFilters: List>, + favoriteFilterKeys: Set, + onVisibleChange: (Boolean) -> Unit, + onFilterPickedWithParams: (UiFilter<*>) -> Unit, + onFilterPicked: (UiFilter<*>) -> Unit, + previewBitmap: Bitmap?, + showPreviewImages: Boolean +) { + val onRequestFilterMapping = component::filterToTransformation + + AnimatedContent( + targetState = favoriteFilters.isEmpty(), + modifier = Modifier.fillMaxSize() + ) { 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, + showPreviewImage = showPreviewImages, + isInFavorite = filter::class.java.name in favoriteFilterKeys, + 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 + ) + } + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/OtherContent.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/OtherContent.kt new file mode 100644 index 0000000..9e95eb3 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/OtherContent.kt @@ -0,0 +1,356 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.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.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.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.itemsIndexed +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.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.graphics.vector.ImageVector +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.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.widget.FilterSelectionItem +import com.t8rin.imagetoolbox.core.resources.HistoryToggleOff +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ImageSearch +import com.t8rin.imagetoolbox.core.resources.icons.Save +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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>, + favoriteFilterKeys: Set, + onVisibleChange: (Boolean) -> Unit, + onFilterPickedWithParams: (UiFilter<*>) -> Unit, + onFilterPicked: (UiFilter<*>) -> Unit, + previewBitmap: Bitmap?, + showPreviewImages: Boolean +) { + val onRequestFilterMapping = component::filterToTransformation + + if (currentGroup is UiFilter.Group.Recent && filters.isEmpty()) { + NoRecentFiltersSection( + icon = Icons.Outlined.HistoryToggleOff + ) + return + } + + 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, + showPreviewImage = showPreviewImages, + isInFavorite = filter::class.java.name in favoriteFilterKeys, + 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 + ) + } + ) + } + } +} + +@Composable +private fun NoRecentFiltersSection( + icon: ImageVector +) { + Column( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(0.5f), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Spacer(Modifier.weight(1f)) + Text( + text = stringResource(R.string.no_recent_filters), + fontSize = 18.sp, + textAlign = TextAlign.Center, + modifier = Modifier.padding(24.dp) + ) + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier + .weight(2f) + .size(140.dp) + .fillMaxSize() + ) + Spacer(Modifier.weight(1f)) + } +} + +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 + ) + ) + } + } + } + } + } +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/TemplatesContent.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/TemplatesContent.kt new file mode 100644 index 0000000..655ae1c --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/TemplatesContent.kt @@ -0,0 +1,179 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.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 androidx.lifecycle.compose.collectAsStateWithLifecycle +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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ExtensionOff +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, + showPreviewImages: Boolean +) { + val templateFilters by component.templatesFlow.collectAsStateWithLifecycle() + val onRequestFilterMapping = component::filterToTransformation + + AnimatedContent( + targetState = templateFilters.isEmpty(), + modifier = Modifier.fillMaxSize() + ) { 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, Alignment.CenterVertically), + contentPadding = PaddingValues(16.dp), + flingBehavior = enhancedFlingBehavior() + ) { + itemsIndexed( + items = templateFilters, + key = { index, f -> "$index-${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, + showPreviewImage = showPreviewImages, + 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 + ) + } + } + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ArcParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ArcParamsItem.kt new file mode 100644 index 0000000..d83d196 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ArcParamsItem.kt @@ -0,0 +1,114 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/AsciiParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/AsciiParamsItem.kt new file mode 100644 index 0000000..f46a389 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/AsciiParamsItem.kt @@ -0,0 +1,312 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.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.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 +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.Save +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 { + Gradient.entries.map { it.value } + } + + val customEntries = remember(settings.customAsciiGradients, defaultItems) { + settings.customAsciiGradients - defaultItems.toSet() + } + + val items = remember(defaultItems, customEntries) { + 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 = MaterialTheme.colorScheme.surfaceContainerHigh + ) + } + } + + 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(16.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 + ) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/BilaterialBlurParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/BilaterialBlurParamsItem.kt new file mode 100644 index 0000000..ca67e03 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/BilaterialBlurParamsItem.kt @@ -0,0 +1,111 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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() } + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/BloomParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/BloomParamsItem.kt new file mode 100644 index 0000000..3f34feb --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/BloomParamsItem.kt @@ -0,0 +1,114 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/BooleanItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/BooleanItem.kt new file mode 100644 index 0000000..e5101c0 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/BooleanItem.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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(16.dp), + enabled = !previewOnly + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ChannelMixParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ChannelMixParamsItem.kt new file mode 100644 index 0000000..4cd60b9 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ChannelMixParamsItem.kt @@ -0,0 +1,114 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ClaheParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ClaheParamsItem.kt new file mode 100644 index 0000000..148e93e --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ClaheParamsItem.kt @@ -0,0 +1,103 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/CropOrPerspectiveParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/CropOrPerspectiveParamsItem.kt new file mode 100644 index 0000000..73b9996 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/CropOrPerspectiveParamsItem.kt @@ -0,0 +1,191 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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(16.dp), + enabled = !previewOnly + ) + } + } + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/DistortPerspectiveParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/DistortPerspectiveParamsItem.kt new file mode 100644 index 0000000..400d512 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/DistortPerspectiveParamsItem.kt @@ -0,0 +1,201 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.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.roundTo +import com.t8rin.imagetoolbox.core.domain.utils.trimTrailingZero +import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam +import com.t8rin.imagetoolbox.core.filters.domain.model.params.DistortPerspectiveParams +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.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 DistortPerspectiveParamsItem( + value: DistortPerspectiveParams, + filter: UiFilter, + onFilterChange: (value: DistortPerspectiveParams) -> Unit, + previewOnly: Boolean +) { + Column( + modifier = Modifier.padding(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + PerspectivePointFields( + value = value.topLeft, + info = filter.paramsInfo[0], + enabled = !previewOnly, + onValueChange = { onFilterChange(value.copy(topLeft = it)) } + ) + PerspectivePointFields( + value = value.topRight, + info = filter.paramsInfo[1], + enabled = !previewOnly, + onValueChange = { onFilterChange(value.copy(topRight = it)) } + ) + PerspectivePointFields( + value = value.bottomLeft, + info = filter.paramsInfo[2], + enabled = !previewOnly, + onValueChange = { onFilterChange(value.copy(bottomLeft = it)) } + ) + PerspectivePointFields( + value = value.bottomRight, + info = filter.paramsInfo[3], + enabled = !previewOnly, + onValueChange = { onFilterChange(value.copy(bottomRight = it)) } + ) + PreferenceRowSwitch( + title = stringResource(filter.paramsInfo[4].title!!), + checked = value.clip, + onClick = { onFilterChange(value.copy(clip = it)) }, + modifier = Modifier.padding( + top = 8.dp, + start = 4.dp, + end = 4.dp + ), + applyHorizontalPadding = false, + startContent = {}, + resultModifier = Modifier.padding(16.dp), + enabled = !previewOnly + ) + } +} + +@Composable +private fun PerspectivePointFields( + value: FloatPair, + info: FilterParam, + enabled: Boolean, + onValueChange: (FloatPair) -> Unit +) { + var x by remember(value.first) { + mutableStateOf(value.first.toString().trimTrailingZero()) + } + var y by remember(value.second) { + mutableStateOf(value.second.toString().trimTrailingZero()) + } + + Column { + TitleItem( + text = "${stringResource(info.title!!)} (0–1)", + modifier = Modifier.padding(horizontal = 8.dp) + ) + Spacer(Modifier.height(8.dp)) + Row { + PerspectiveCoordinateField( + value = x, + label = "X", + shape = ShapeDefaults.smallStart, + info = info, + enabled = enabled, + modifier = Modifier + .weight(1f) + .padding( + start = 8.dp, + top = 8.dp, + bottom = 8.dp, + end = 2.dp + ), + onValueChange = { + x = it + it.toNormalizedFloatOrNull()?.let { newX -> + onValueChange(newX.roundTo(info.roundTo) to value.second) + } + } + ) + PerspectiveCoordinateField( + value = y, + label = "Y", + shape = ShapeDefaults.smallEnd, + info = info, + enabled = enabled, + modifier = Modifier + .weight(1f) + .padding( + start = 2.dp, + top = 8.dp, + bottom = 8.dp, + end = 8.dp + ), + onValueChange = { + y = it + it.toNormalizedFloatOrNull()?.let { newY -> + onValueChange(value.first to newY.roundTo(info.roundTo)) + } + } + ) + } + } +} + +@Composable +private fun PerspectiveCoordinateField( + value: String, + label: String, + shape: androidx.compose.ui.graphics.Shape, + info: FilterParam, + enabled: Boolean, + modifier: Modifier, + onValueChange: (String) -> Unit +) { + RoundedTextField( + value = value, + onValueChange = { + if (it.isValidCoordinate(info)) onValueChange(it) + }, + shape = shape, + enabled = enabled, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Decimal + ), + label = { + Text(label) + }, + modifier = modifier + ) +} + +private fun String.isValidCoordinate(info: FilterParam): Boolean { + if (isEmpty()) return true + if (!matches(Regex("""\d*(?:[.,]\d{0,${info.roundTo}})?"""))) return false + + return toNormalizedFloatOrNull()?.let { it in info.valueRange } ?: true +} + +private fun String.toNormalizedFloatOrNull(): Float? = replace(',', '.').toFloatOrNull() diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/DropShadowParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/DropShadowParamsItem.kt new file mode 100644 index 0000000..1eb255a --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/DropShadowParamsItem.kt @@ -0,0 +1,103 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.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.DropShadowParams +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.widget.controls.selection.ColorRowSelector +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem + +@Composable +internal fun DropShadowParamsItem( + value: DropShadowParams, + filter: UiFilter, + onFilterChange: (value: DropShadowParams) -> Unit, + previewOnly: Boolean +) { + Column( + modifier = Modifier.padding(8.dp) + ) { + ColorRowSelector( + value = value.color.toColor(), + onValueChange = { + if (!previewOnly) { + onFilterChange(value.copy(color = it.toArgb().toColorModel())) + } + }, + title = stringResource(R.string.shadow_color), + allowCustom = !previewOnly + ) + ShadowSlider( + value = value.blurRadius, + title = filter.paramsInfo[0].title!!, + valueRange = filter.paramsInfo[0].valueRange, + enabled = !previewOnly, + onValueChange = { + onFilterChange(value.copy(blurRadius = it.roundTo(2))) + } + ) + ShadowSlider( + value = value.offsetX, + title = filter.paramsInfo[1].title!!, + valueRange = filter.paramsInfo[1].valueRange, + enabled = !previewOnly, + onValueChange = { + onFilterChange(value.copy(offsetX = it.roundTo(2))) + } + ) + ShadowSlider( + value = value.offsetY, + title = filter.paramsInfo[2].title!!, + valueRange = filter.paramsInfo[2].valueRange, + enabled = !previewOnly, + onValueChange = { + onFilterChange(value.copy(offsetY = it.roundTo(2))) + } + ) + } +} + +@Composable +private fun ShadowSlider( + value: Float, + title: Int, + valueRange: ClosedFloatingPointRange, + enabled: Boolean, + onValueChange: (Float) -> Unit +) { + EnhancedSliderItem( + enabled = enabled, + value = value, + title = stringResource(title), + valueRange = valueRange, + onValueChange = onValueChange, + internalStateTransformation = { it.roundTo(2) }, + behaveAsContainer = false + ) +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/EdgeModeSelector.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/EdgeModeSelector.kt new file mode 100644 index 0000000..648f54d --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/EdgeModeSelector.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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]) + } + ) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/EnhancedZoomBlurParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/EnhancedZoomBlurParamsItem.kt new file mode 100644 index 0000000..15b7871 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/EnhancedZoomBlurParamsItem.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/FilterValueWrapperItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/FilterValueWrapperItem.kt new file mode 100644 index 0000000..2202580 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/FilterValueWrapperItem.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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()) + } + ) + } + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/FlareParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/FlareParamsItem.kt new file mode 100644 index 0000000..16dec32 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/FlareParamsItem.kt @@ -0,0 +1,95 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.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.FilterParam +import com.t8rin.imagetoolbox.core.filters.domain.model.params.FlareParams +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.ColorSelectionRow +import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem + +@Composable +internal fun FlareParamsItem( + value: FlareParams, + filter: UiFilter, + onFilterChange: (value: FlareParams) -> Unit, + previewOnly: Boolean +) { + Column( + modifier = Modifier.padding(8.dp) + ) { + FlareSlider(value.radius, filter.paramsInfo[0], !previewOnly) { + onFilterChange(value.copy(radius = it)) + } + FlareSlider(value.baseAmount, filter.paramsInfo[1], !previewOnly) { + onFilterChange(value.copy(baseAmount = it)) + } + FlareSlider(value.ringAmount, filter.paramsInfo[2], !previewOnly) { + onFilterChange(value.copy(ringAmount = it)) + } + FlareSlider(value.rayAmount, filter.paramsInfo[3], !previewOnly) { + onFilterChange(value.copy(rayAmount = it)) + } + FlareSlider(value.ringWidth, filter.paramsInfo[4], !previewOnly) { + onFilterChange(value.copy(ringWidth = it)) + } + FlareSlider(value.centreX, filter.paramsInfo[5], !previewOnly) { + onFilterChange(value.copy(centreX = it)) + } + FlareSlider(value.centreY, filter.paramsInfo[6], !previewOnly) { + onFilterChange(value.copy(centreY = it)) + } + ColorSelectionRow( + value = value.color.toColor(), + defaultColors = ColorSelectionRowDefaults.colorList, + allowScroll = !previewOnly, + onValueChange = { + onFilterChange(value.copy(color = it.toModel())) + }, + modifier = Modifier.padding(horizontal = 8.dp) + ) + } +} + +@Composable +private fun FlareSlider( + value: Float, + info: FilterParam, + enabled: Boolean, + onValueChange: (Float) -> Unit +) { + EnhancedSliderItem( + enabled = enabled, + value = value, + title = stringResource(info.title!!), + valueRange = info.valueRange, + onValueChange = onValueChange, + internalStateTransformation = { it.roundTo(info.roundTo) }, + behaveAsContainer = false + ) +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/FloatArrayItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/FloatArrayItem.kt new file mode 100644 index 0000000..d931a08 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/FloatArrayItem.kt @@ -0,0 +1,101 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 com.t8rin.imagetoolbox.core.resources.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 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.resources.icons.Done +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)) + } + ) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/FloatItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/FloatItem.kt new file mode 100644 index 0000000..acfba0d --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/FloatItem.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/GlitchParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/GlitchParamsItem.kt new file mode 100644 index 0000000..c90f1c6 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/GlitchParamsItem.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/IntegerSizeParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/IntegerSizeParamsItem.kt new file mode 100644 index 0000000..c9061ab --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/IntegerSizeParamsItem.kt @@ -0,0 +1,77 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/KaleidoscopeParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/KaleidoscopeParamsItem.kt new file mode 100644 index 0000000..b6fd464 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/KaleidoscopeParamsItem.kt @@ -0,0 +1,115 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/LinearGaussianParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/LinearGaussianParamsItem.kt new file mode 100644 index 0000000..50034ac --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/LinearGaussianParamsItem.kt @@ -0,0 +1,119 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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() } + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/LinearTiltShiftParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/LinearTiltShiftParamsItem.kt new file mode 100644 index 0000000..98da3b4 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/LinearTiltShiftParamsItem.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/MirrorSideSelector.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/MirrorSideSelector.kt new file mode 100644 index 0000000..703ea3d --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/MirrorSideSelector.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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]) + } + ) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/NtscParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/NtscParamsItem.kt new file mode 100644 index 0000000..51e2cfd --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/NtscParamsItem.kt @@ -0,0 +1,859 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.AnimatedVisibility +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.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.LocalTextStyle +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.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.utils.roundTo +import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam +import com.t8rin.imagetoolbox.core.filters.domain.model.params.NtscParams +import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Tune +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.other.ExpandableItem +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 com.t8rin.trickle.NtscSettings +import kotlin.math.roundToInt + +@Composable +internal fun NtscParamsItem( + value: NtscParams, + filter: UiFilter, + onFilterChange: (value: NtscParams) -> Unit, + previewOnly: Boolean +) { + var params by remember(value) { mutableStateOf(value) } + + LaunchedEffect(params) { + onFilterChange(params) + } + + Column( + modifier = Modifier.padding(8.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + NtscParamSlider( + value = params.amount, + info = filter.paramsInfo[0], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(amount = it) + } + ) + NtscParamSlider( + value = params.tapeWear, + info = filter.paramsInfo[1], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(tapeWear = it) + } + ) + NtscParamSlider( + value = params.chromaBleed, + info = filter.paramsInfo[2], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(chromaBleed = it) + } + ) + NtscParamSlider( + value = params.tracking, + info = filter.paramsInfo[3], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(tracking = it) + } + ) + NtscParamSlider( + value = params.noise, + info = filter.paramsInfo[4], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(noise = it) + } + ) + NtscParamSlider( + value = params.snow, + info = filter.paramsInfo[5], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(snow = it) + } + ) + NtscParamSlider( + value = params.lumaSmear, + info = filter.paramsInfo[6], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(lumaSmear = it) + } + ) + NtscParamSlider( + value = params.compositeSharpening, + info = filter.paramsInfo[7], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(compositeSharpening = it) + } + ) + NtscParamSlider( + value = params.ringing, + info = filter.paramsInfo[8], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(ringing = it) + } + ) + + ExpandableItem( + modifier = Modifier.padding(top = 4.dp), + visibleContent = { + TitleItem( + text = stringResource(R.string.ntsc_advanced_settings), + subtitle = stringResource(R.string.ntsc_advanced_settings_sub), + icon = Icons.Rounded.Tune, + iconEndPadding = 16.dp, + modifier = Modifier.padding(8.dp) + ) + }, + expandableContent = { + Column( + modifier = Modifier.padding(horizontal = 8.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + NtscIntSlider( + value = params.seed, + info = filter.paramsInfo[9], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(seed = it) + } + ) + NtscEnumSelector( + title = R.string.ntsc_use_field, + entries = NtscSettings.UseField.entries, + selectedIndex = params.useField, + previewOnly = previewOnly, + onValueChange = { + params = params.copy(useField = it) + } + ) + NtscEnumSelector( + title = R.string.ntsc_filter_type, + entries = NtscSettings.FilterType.entries, + selectedIndex = params.filterType, + previewOnly = previewOnly, + onValueChange = { + params = params.copy(filterType = it) + } + ) + NtscEnumSelector( + title = R.string.ntsc_input_luma_filter, + entries = NtscSettings.LumaLowpass.entries, + selectedIndex = params.inputLumaFilter, + previewOnly = previewOnly, + onValueChange = { + params = params.copy(inputLumaFilter = it) + } + ) + NtscEnumSelector( + title = R.string.ntsc_chroma_lowpass_in, + entries = NtscSettings.ChromaLowpass.entries, + selectedIndex = params.chromaLowpassIn, + previewOnly = previewOnly, + onValueChange = { + params = params.copy(chromaLowpassIn = it) + } + ) + NtscEnumSelector( + title = R.string.ntsc_chroma_demodulation, + entries = NtscSettings.ChromaDemodulationFilter.entries, + selectedIndex = params.chromaDemodulation, + previewOnly = previewOnly, + onValueChange = { + params = params.copy(chromaDemodulation = it) + } + ) + NtscEnumSelector( + title = R.string.ntsc_phase_shift, + entries = NtscSettings.PhaseShift.entries, + selectedIndex = params.videoScanlinePhaseShift, + previewOnly = previewOnly, + onValueChange = { + params = params.copy(videoScanlinePhaseShift = it) + } + ) + NtscEnumSelector( + title = R.string.ntsc_chroma_lowpass_out, + entries = NtscSettings.ChromaLowpass.entries, + selectedIndex = params.chromaLowpassOut, + previewOnly = previewOnly, + onValueChange = { + params = params.copy(chromaLowpassOut = it) + } + ) + NtscIntSlider( + value = params.videoScanlinePhaseShiftOffset, + info = filter.paramsInfo[10], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(videoScanlinePhaseShiftOffset = it) + } + ) + NtscToggleGroup( + title = R.string.ntsc_head_switching, + checked = params.headSwitchingEnabled, + previewOnly = previewOnly, + onCheckedChange = { + params = params.copy(headSwitchingEnabled = it) + }, + content = { + NtscIntSlider( + value = params.headSwitchingHeight, + info = filter.paramsInfo[11], + previewOnly = previewOnly, + shape = ShapeDefaults.top, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(headSwitchingHeight = it) + } + ) + NtscIntSlider( + value = params.headSwitchingOffset, + info = filter.paramsInfo[12], + previewOnly = previewOnly, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(headSwitchingOffset = it) + } + ) + NtscParamSlider( + value = params.headSwitchingHorizontalShift, + info = filter.paramsInfo[13], + previewOnly = previewOnly, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(headSwitchingHorizontalShift = it) + } + ) + NtscParamSlider( + value = params.headSwitchingMidLinePosition, + info = filter.paramsInfo[14], + previewOnly = previewOnly, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(headSwitchingMidLinePosition = it) + } + ) + NtscParamSlider( + value = params.headSwitchingMidLineJitter, + info = filter.paramsInfo[15], + previewOnly = previewOnly, + shape = ShapeDefaults.bottom, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(headSwitchingMidLineJitter = it) + } + ) + } + ) + NtscToggleGroup( + title = R.string.ntsc_tracking_noise, + checked = params.trackingNoiseEnabled, + previewOnly = previewOnly, + onCheckedChange = { + params = params.copy(trackingNoiseEnabled = it) + }, + content = { + NtscIntSlider( + value = params.trackingNoiseHeight, + info = filter.paramsInfo[16], + previewOnly = previewOnly, + shape = ShapeDefaults.top, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(trackingNoiseHeight = it) + } + ) + NtscParamSlider( + value = params.trackingNoiseWaveIntensity, + info = filter.paramsInfo[17], + previewOnly = previewOnly, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(trackingNoiseWaveIntensity = it) + } + ) + NtscParamSlider( + value = params.trackingNoiseSnowIntensity, + info = filter.paramsInfo[18], + previewOnly = previewOnly, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(trackingNoiseSnowIntensity = it) + } + ) + NtscParamSlider( + value = params.trackingNoiseSnowAnisotropy, + info = filter.paramsInfo[19], + previewOnly = previewOnly, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(trackingNoiseSnowAnisotropy = it) + } + ) + NtscParamSlider( + value = params.trackingNoiseNoiseIntensity, + info = filter.paramsInfo[20], + previewOnly = previewOnly, + shape = ShapeDefaults.bottom, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(trackingNoiseNoiseIntensity = it) + } + ) + } + ) + NtscToggleGroup( + title = R.string.ntsc_composite_noise, + checked = params.compositeNoiseEnabled, + previewOnly = previewOnly, + onCheckedChange = { + params = params.copy(compositeNoiseEnabled = it) + }, + content = { + NtscParamSlider( + value = params.compositeNoiseFrequency, + info = filter.paramsInfo[21], + previewOnly = previewOnly, + shape = ShapeDefaults.top, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(compositeNoiseFrequency = it) + } + ) + NtscParamSlider( + value = params.compositeNoiseIntensity, + info = filter.paramsInfo[22], + previewOnly = previewOnly, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(compositeNoiseIntensity = it) + } + ) + NtscIntSlider( + value = params.compositeNoiseDetail, + info = filter.paramsInfo[23], + previewOnly = previewOnly, + shape = ShapeDefaults.bottom, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(compositeNoiseDetail = it) + } + ) + } + ) + NtscToggleGroup( + title = R.string.ringing, + checked = params.ringingEnabled, + previewOnly = previewOnly, + onCheckedChange = { + params = params.copy(ringingEnabled = it) + }, + content = { + NtscParamSlider( + value = params.ringingFrequency, + info = filter.paramsInfo[24], + previewOnly = previewOnly, + shape = ShapeDefaults.top, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(ringingFrequency = it) + } + ) + NtscParamSlider( + value = params.ringingPower, + info = filter.paramsInfo[25], + previewOnly = previewOnly, + shape = ShapeDefaults.bottom, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(ringingPower = it) + } + ) + } + ) + NtscToggleGroup( + title = R.string.ntsc_luma_noise, + checked = params.lumaNoiseEnabled, + previewOnly = previewOnly, + onCheckedChange = { + params = params.copy(lumaNoiseEnabled = it) + }, + content = { + NtscParamSlider( + value = params.lumaNoiseFrequency, + info = filter.paramsInfo[26], + previewOnly = previewOnly, + shape = ShapeDefaults.top, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(lumaNoiseFrequency = it) + } + ) + NtscParamSlider( + value = params.lumaNoiseIntensity, + info = filter.paramsInfo[27], + previewOnly = previewOnly, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(lumaNoiseIntensity = it) + } + ) + NtscIntSlider( + value = params.lumaNoiseDetail, + info = filter.paramsInfo[28], + previewOnly = previewOnly, + shape = ShapeDefaults.bottom, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(lumaNoiseDetail = it) + } + ) + } + ) + NtscToggleGroup( + title = R.string.ntsc_chroma_noise, + checked = params.chromaNoiseEnabled, + previewOnly = previewOnly, + onCheckedChange = { + params = params.copy(chromaNoiseEnabled = it) + }, + content = { + NtscParamSlider( + value = params.chromaNoiseFrequency, + info = filter.paramsInfo[29], + previewOnly = previewOnly, + shape = ShapeDefaults.top, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(chromaNoiseFrequency = it) + } + ) + NtscParamSlider( + value = params.chromaNoiseIntensity, + info = filter.paramsInfo[30], + previewOnly = previewOnly, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(chromaNoiseIntensity = it) + } + ) + NtscIntSlider( + value = params.chromaNoiseDetail, + info = filter.paramsInfo[31], + previewOnly = previewOnly, + shape = ShapeDefaults.bottom, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(chromaNoiseDetail = it) + } + ) + } + ) + NtscParamSlider( + value = params.snowIntensity, + info = filter.paramsInfo[32], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(snowIntensity = it) + } + ) + NtscParamSlider( + value = params.snowAnisotropy, + info = filter.paramsInfo[33], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(snowAnisotropy = it) + } + ) + NtscParamSlider( + value = params.chromaPhaseNoiseIntensity, + info = filter.paramsInfo[34], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(chromaPhaseNoiseIntensity = it) + } + ) + NtscParamSlider( + value = params.chromaPhaseError, + info = filter.paramsInfo[35], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(chromaPhaseError = it) + } + ) + NtscParamSlider( + value = params.chromaDelayHorizontal, + info = filter.paramsInfo[36], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(chromaDelayHorizontal = it) + } + ) + NtscIntSlider( + value = params.chromaDelayVertical, + info = filter.paramsInfo[37], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(chromaDelayVertical = it) + } + ) + NtscToggleGroup( + title = R.string.vhs, + checked = params.vhsEnabled, + previewOnly = previewOnly, + onCheckedChange = { + params = params.copy(vhsEnabled = it) + }, + content = { + NtscEnumSelector( + title = R.string.ntsc_vhs_tape_speed, + entries = NtscSettings.VHSTapeSpeed.entries, + selectedIndex = params.vhsTapeSpeed, + previewOnly = previewOnly, + shape = ShapeDefaults.top, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(vhsTapeSpeed = it) + } + ) + NtscParamSlider( + value = params.vhsChromaLoss, + info = filter.paramsInfo[38], + previewOnly = previewOnly, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(vhsChromaLoss = it) + } + ) + NtscParamSlider( + value = params.vhsSharpenIntensity, + info = filter.paramsInfo[39], + previewOnly = previewOnly, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(vhsSharpenIntensity = it) + } + ) + NtscParamSlider( + value = params.vhsSharpenFrequency, + info = filter.paramsInfo[40], + previewOnly = previewOnly, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(vhsSharpenFrequency = it) + } + ) + NtscParamSlider( + value = params.vhsEdgeWaveIntensity, + info = filter.paramsInfo[41], + previewOnly = previewOnly, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(vhsEdgeWaveIntensity = it) + } + ) + NtscParamSlider( + value = params.vhsEdgeWaveSpeed, + info = filter.paramsInfo[42], + previewOnly = previewOnly, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(vhsEdgeWaveSpeed = it) + } + ) + NtscParamSlider( + value = params.vhsEdgeWaveFrequency, + info = filter.paramsInfo[43], + previewOnly = previewOnly, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(vhsEdgeWaveFrequency = it) + } + ) + NtscIntSlider( + value = params.vhsEdgeWaveDetail, + info = filter.paramsInfo[44], + previewOnly = previewOnly, + shape = ShapeDefaults.bottom, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(vhsEdgeWaveDetail = it) + } + ) + } + ) + NtscToggleGroup( + title = R.string.scale, + checked = params.scaleEnabled, + previewOnly = previewOnly, + onCheckedChange = { + params = params.copy(scaleEnabled = it) + }, + content = { + NtscParamSlider( + value = params.scaleHorizontal, + info = filter.paramsInfo[45], + previewOnly = previewOnly, + shape = ShapeDefaults.top, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(scaleHorizontal = it) + } + ) + NtscParamSlider( + value = params.scaleVertical, + info = filter.paramsInfo[46], + previewOnly = previewOnly, + shape = ShapeDefaults.bottom, + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + onValueChange = { + params = params.copy(scaleVertical = it) + } + ) + } + ) + NtscParamSlider( + value = params.scaleFactorX, + info = filter.paramsInfo[47], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(scaleFactorX = it) + } + ) + NtscParamSlider( + value = params.scaleFactorY, + info = filter.paramsInfo[48], + previewOnly = previewOnly, + onValueChange = { + params = params.copy(scaleFactorY = it) + } + ) + } + } + ) + } +} + +@Composable +private fun NtscParamSlider( + value: Float, + info: FilterParam, + previewOnly: Boolean, + shape: Shape = ShapeDefaults.default, + containerColor: Color = Color.Unspecified, + onValueChange: (Float) -> Unit +) { + val (title, valueRange, roundTo) = info + EnhancedSliderItem( + enabled = !previewOnly, + value = value, + title = stringResource(title!!), + valueRange = valueRange, + onValueChange = onValueChange, + internalStateTransformation = { + it.roundTo(roundTo) + }, + shape = shape, + containerColor = containerColor, + behaveAsContainer = containerColor != Color.Unspecified + ) +} + +@Composable +private fun NtscIntSlider( + value: Int, + info: FilterParam, + previewOnly: Boolean, + shape: Shape = ShapeDefaults.default, + containerColor: Color = Color.Unspecified, + onValueChange: (Int) -> Unit +) { + NtscParamSlider( + value = value.toFloat(), + info = info, + previewOnly = previewOnly, + shape = shape, + containerColor = containerColor, + onValueChange = { + onValueChange(it.roundToInt()) + } + ) +} + +@Composable +private fun > NtscEnumSelector( + title: Int, + entries: List, + selectedIndex: Int, + previewOnly: Boolean, + shape: Shape = ShapeDefaults.default, + containerColor: Color = Color.Unspecified, + onValueChange: (Int) -> Unit +) { + val labels = remember(entries) { + entries.map { it.name.ntscEnumName() } + } + if (labels.isEmpty()) return + + val safeIndex = selectedIndex.coerceIn(labels.indices) + + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .then( + if (containerColor != Color.Unspecified) { + Modifier.container( + shape = shape, + color = containerColor + ) + } else Modifier + ) + ) { + val shouldScroll = labels.estimatedWidth() > maxWidth.value + + EnhancedButtonGroup( + modifier = Modifier.fillMaxWidth(), + enabled = !previewOnly, + entries = labels, + value = labels[safeIndex], + itemContent = { + AutoSizeText( + text = it, + maxLines = 1, + style = LocalTextStyle.current + ) + }, + title = stringResource(title), + onValueChange = { + onValueChange(labels.indexOf(it)) + }, + inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainerHigh, + isScrollable = shouldScroll, + contentPadding = if (containerColor != Color.Unspecified) { + PaddingValues(horizontal = 8.dp, vertical = 4.dp) + } else { + PaddingValues(vertical = 4.dp) + } + ) + } +} + +@Composable +private fun NtscToggleGroup( + title: Int, + checked: Boolean, + previewOnly: Boolean, + onCheckedChange: (Boolean) -> Unit, + content: @Composable () -> Unit +) { + PreferenceRowSwitch( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp), + title = stringResource(title), + enabled = !previewOnly, + applyHorizontalPadding = false, + resultModifier = Modifier.padding(16.dp), + checked = checked, + onClick = onCheckedChange, + shape = ShapeDefaults.large, + containerColor = MaterialTheme.colorScheme.surface, + additionalContent = { + AnimatedVisibility( + visible = checked, + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier.padding(top = 8.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + content() + } + } + } + ) +} + +private fun String.ntscEnumName(): String { + if (length <= 3 && all { it.isUpperCase() }) return this + + return removePrefix("DEGREES_") + .lowercase() + .split('_') + .joinToString(" ") { part -> + part.replaceFirstChar { it.uppercase() } + } +} + +private fun List.estimatedWidth(): Float { + return sumOf { label -> + label.length * 8 + 32 + }.toFloat() +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/PairItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/PairItem.kt new file mode 100644 index 0000000..5648906 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/PairItem.kt @@ -0,0 +1,128 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/PinchParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/PinchParamsItem.kt new file mode 100644 index 0000000..cfae8d0 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/PinchParamsItem.kt @@ -0,0 +1,108 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/QuadItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/QuadItem.kt new file mode 100644 index 0000000..b62dd5f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/QuadItem.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/RadialTiltShiftParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/RadialTiltShiftParamsItem.kt new file mode 100644 index 0000000..f0e8a8c --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/RadialTiltShiftParamsItem.kt @@ -0,0 +1,108 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/RubberStampParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/RubberStampParamsItem.kt new file mode 100644 index 0000000..c2fc85e --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/RubberStampParamsItem.kt @@ -0,0 +1,143 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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) + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/SeamCarvingParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/SeamCarvingParamsItem.kt new file mode 100644 index 0000000..af5d014 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/SeamCarvingParamsItem.kt @@ -0,0 +1,64 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.filters.domain.model.params.SeamCarvingParams +import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter +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 +internal fun SeamCarvingParamsItem( + value: SeamCarvingParams, + filter: UiFilter, + onFilterChange: (value: SeamCarvingParams) -> Unit, + previewOnly: Boolean +) { + IntegerSizeParamsItem( + value = value.size, + filter = filter, + onFilterChange = { size -> + onFilterChange(value.copy(size = size)) + }, + previewOnly = previewOnly + ) + PreferenceRowSwitch( + title = stringResource(R.string.seam_carving_backward_energy), + subtitle = stringResource(R.string.seam_carving_backward_energy_sub), + checked = value.useBackwardEnergy, + onClick = { + onFilterChange(value.copy(useBackwardEnergy = it)) + }, + modifier = Modifier + .padding(horizontal = 8.dp) + .padding(bottom = 8.dp), + containerColor = Color.Unspecified, + shape = ShapeDefaults.extraLarge, + enabled = !previewOnly, + applyHorizontalPadding = false, + startContent = {}, + resultModifier = Modifier.padding(16.dp), + ) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ShaderParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ShaderParamsItem.kt new file mode 100644 index 0000000..af37623 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ShaderParamsItem.kt @@ -0,0 +1,501 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.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.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.itemsIndexed +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.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.Shape +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.domain.utils.roundTo +import com.t8rin.imagetoolbox.core.filters.domain.model.params.ShaderParams +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderParam +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderParamType +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderPreset +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderValue +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Code +import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRow +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.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.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.preferences.PreferenceItemOverload +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRow +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch +import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem +import kotlinx.coroutines.launch + +@Composable +fun ShaderParamsItem( + value: ShaderParams, + presets: List, + onValueChange: (ShaderParams) -> Unit, + onImportPreset: (suspend (Uri) -> ShaderPreset?)? = null, + modifier: Modifier = Modifier, + previewOnly: Boolean = false, + showPresetSelector: Boolean = true +) { + Column(modifier = modifier) { + if (showPresetSelector) { + ShaderPresetSelector( + selectedPreset = value.preset, + presets = presets, + enabled = !previewOnly, + onPresetSelected = { + onValueChange(value.withPreset(it)) + }, + onImportPreset = onImportPreset + ) + } + + Spacer(Modifier.height(4.dp)) + + val preset = value.preset?.takeIf { it.params.isNotEmpty() } ?: return@Column + + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.padding(bottom = 8.dp) + ) { + preset.params.forEachIndexed { index, param -> + ShaderParamItem( + param = param, + value = value.resolvedValues[param.name] ?: param.defaultValue, + enabled = !previewOnly, + shape = ShapeDefaults.byIndex(index, preset.params.size), + onValueChange = { shaderValue -> + onValueChange( + value.copy( + values = value.resolvedValues + (param.name to shaderValue) + ) + ) + } + ) + } + } + } +} + +@Composable +private fun ShaderPresetSelector( + selectedPreset: ShaderPreset?, + presets: List, + enabled: Boolean, + onPresetSelected: (ShaderPreset?) -> Unit, + onImportPreset: (suspend (Uri) -> ShaderPreset?)? +) { + var showSheet by rememberSaveable { mutableStateOf(false) } + val scope = rememberCoroutineScope() + val importPreset: (Uri) -> Unit = { uri -> + scope.launch { + onImportPreset?.invoke(uri)?.let(onPresetSelected) + } + } + + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp) + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier + .fillMaxWidth() + .height(intrinsicSize = IntrinsicSize.Max) + ) { + val hasPresets = presets.isNotEmpty() && onImportPreset != null + + FileSelector( + value = null, + title = stringResource(R.string.shader_file), + subtitle = selectedPreset?.name ?: stringResource(R.string.no_shader_selected), + onValueChange = importPreset, + shape = if (hasPresets) ShapeDefaults.start else ShapeDefaults.default, + modifier = Modifier + .weight(1f) + .fillMaxHeight(), + ) + + if (hasPresets) { + EnhancedIconButton( + enabled = enabled, + containerColor = MaterialTheme.colorScheme.secondaryContainer, + onClick = { showSheet = true }, + shape = ShapeDefaults.end, + modifier = Modifier.fillMaxHeight(), + forceMinimumInteractiveComponentSize = false + ) { + Icon( + imageVector = Icons.Rounded.Code, + contentDescription = stringResource(R.string.saved_shaders) + ) + } + } + } + } + + EnhancedModalBottomSheet( + visible = showSheet, + onDismiss = { showSheet = it }, + confirmButton = { + EnhancedButton( + onClick = { showSheet = false } + ) { + Text(stringResource(R.string.close)) + } + }, + title = { + TitleItem( + text = stringResource(R.string.saved_shaders), + icon = Icons.Rounded.Code + ) + } + ) { + LazyColumn( + contentPadding = PaddingValues(16.dp), + flingBehavior = enhancedFlingBehavior(), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + itemsIndexed( + items = presets, + key = { _, preset -> preset.name } + ) { index, preset -> + PreferenceItemOverload( + title = preset.name, + subtitle = stringResource(R.string.shader_params_count, preset.params.size), + onClick = { + showSheet = false + onPresetSelected(preset) + }, + shape = ShapeDefaults.byIndex(index, presets.size), + modifier = Modifier.fillMaxWidth() + ) + } + } + } +} + +@Composable +private fun ShaderParamItem( + param: ShaderParam, + value: ShaderValue, + enabled: Boolean, + shape: Shape, + onValueChange: (ShaderValue) -> Unit +) { + Column( + modifier = Modifier.padding(horizontal = 8.dp) + ) { + when (param.type) { + ShaderParamType.Float -> FloatShaderParamItem( + param = param, + value = value as? ShaderValue.FloatValue + ?: param.defaultValue as ShaderValue.FloatValue, + enabled = enabled, + shape = shape, + onValueChange = onValueChange + ) + + ShaderParamType.Int -> IntShaderParamItem( + param = param, + value = value as? ShaderValue.IntValue + ?: param.defaultValue as ShaderValue.IntValue, + enabled = enabled, + shape = shape, + onValueChange = onValueChange + ) + + ShaderParamType.Bool -> BoolShaderParamItem( + param = param, + value = value as? ShaderValue.BoolValue + ?: param.defaultValue as ShaderValue.BoolValue, + enabled = enabled, + shape = shape, + onValueChange = onValueChange + ) + + ShaderParamType.Color -> ColorShaderParamItem( + param = param, + value = value as? ShaderValue.ColorValue + ?: param.defaultValue as ShaderValue.ColorValue, + enabled = enabled, + shape = shape, + onValueChange = onValueChange + ) + + ShaderParamType.Vec2 -> Vec2ShaderParamItem( + param = param, + value = value as? ShaderValue.Vec2Value + ?: param.defaultValue as ShaderValue.Vec2Value, + enabled = enabled, + shape = shape, + onValueChange = onValueChange + ) + } + } +} + +@Composable +private fun FloatShaderParamItem( + param: ShaderParam, + value: ShaderValue.FloatValue, + enabled: Boolean, + shape: Shape, + onValueChange: (ShaderValue) -> Unit +) { + var sliderState by remember(param.name, value.value) { + mutableFloatStateOf(value.value) + } + + EnhancedSliderItem( + value = sliderState, + title = param.name, + valueRange = param.floatRange(value.value), + enabled = enabled, + shape = shape, + onValueChange = { + sliderState = it + onValueChange(ShaderValue.FloatValue(it.roundTo(3))) + }, + internalStateTransformation = { it.roundTo(3) } + ) +} + +@Composable +private fun IntShaderParamItem( + param: ShaderParam, + value: ShaderValue.IntValue, + enabled: Boolean, + shape: Shape, + onValueChange: (ShaderValue) -> Unit +) { + var sliderState by remember(param.name, value.value) { + mutableFloatStateOf(value.value.toFloat()) + } + val range = param.intRange(value.value) + + EnhancedSliderItem( + value = sliderState, + title = param.name, + valueRange = range.first.toFloat()..range.last.toFloat(), + enabled = enabled, + shape = shape, + steps = (range.last - range.first - 1).coerceIn(0, MAX_INT_STEPS), + onValueChange = { + sliderState = it + onValueChange(ShaderValue.IntValue(it.toInt())) + }, + internalStateTransformation = { it.toInt() } + ) +} + +@Composable +private fun BoolShaderParamItem( + param: ShaderParam, + value: ShaderValue.BoolValue, + enabled: Boolean, + shape: Shape, + onValueChange: (ShaderValue) -> Unit +) { + PreferenceRowSwitch( + title = param.name, + checked = value.value, + enabled = enabled, + shape = shape, + onClick = { onValueChange(ShaderValue.BoolValue(it)) }, + applyHorizontalPadding = false, + resultModifier = Modifier.padding(16.dp) + ) +} + +@Composable +private fun ColorShaderParamItem( + param: ShaderParam, + value: ShaderValue.ColorValue, + enabled: Boolean, + shape: Shape, + onValueChange: (ShaderValue) -> Unit +) { + PreferenceRow( + title = param.name, + enabled = enabled, + resultModifier = Modifier.padding( + start = 16.dp, + end = 16.dp, + top = 12.dp, + bottom = 8.dp + ), + shape = shape, + onClick = null, + applyHorizontalPadding = false, + additionalContent = { + ColorSelectionRow( + value = value.toComposeColor(), + onValueChange = { color -> + onValueChange(color.toShaderColorValue()) + }, + allowAlpha = true + ) + } + ) +} + +@Composable +private fun Vec2ShaderParamItem( + param: ShaderParam, + value: ShaderValue.Vec2Value, + enabled: Boolean, + shape: Shape, + onValueChange: (ShaderValue) -> Unit +) { + val min = param.minValue as? ShaderValue.Vec2Value + val max = param.maxValue as? ShaderValue.Vec2Value + + var xState by remember(param.name, value.x) { + mutableFloatStateOf(value.x) + } + var yState by remember(param.name, value.y) { + mutableFloatStateOf(value.y) + } + + Column( + modifier = Modifier + .container( + shape = shape, + resultPadding = 0.dp + ) + .padding( + horizontal = 8.dp, + vertical = 4.dp + ) + ) { + EnhancedSliderItem( + value = xState, + title = "${param.name}.x", + valueRange = rangeFor(value.x, min?.x, max?.x), + enabled = enabled, + onValueChange = { + xState = it + onValueChange(ShaderValue.Vec2Value(it.roundTo(3), yState)) + }, + internalStateTransformation = { it.roundTo(3) }, + behaveAsContainer = false, + titleFontWeight = FontWeight.Medium + ) + EnhancedSliderItem( + value = yState, + title = "${param.name}.y", + valueRange = rangeFor(value.y, min?.y, max?.y), + enabled = enabled, + onValueChange = { + yState = it + onValueChange(ShaderValue.Vec2Value(xState, it.roundTo(3))) + }, + internalStateTransformation = { it.roundTo(3) }, + behaveAsContainer = false, + titleFontWeight = FontWeight.Medium + ) + } +} + +@Composable +private fun ShaderParam.floatRange( + defaultValue: Float +): ClosedFloatingPointRange { + val min = (minValue as? ShaderValue.FloatValue)?.value + val max = (maxValue as? ShaderValue.FloatValue)?.value + + return rangeFor(defaultValue, min, max) +} + +@Composable +private fun ShaderParam.intRange( + defaultValue: Int +): IntRange = remember(minValue, maxValue, defaultValue) { + val min = (minValue as? ShaderValue.IntValue)?.value ?: (defaultValue - DEFAULT_INT_SPREAD) + val max = (maxValue as? ShaderValue.IntValue)?.value ?: (defaultValue + DEFAULT_INT_SPREAD) + + min.coerceAtMost(defaultValue)..max.coerceAtLeast(defaultValue) +} + +@Composable +private fun rangeFor( + defaultValue: Float, + minValue: Float?, + maxValue: Float? +): ClosedFloatingPointRange = remember(minValue, maxValue, defaultValue) { + val min = minValue ?: (defaultValue - DEFAULT_FLOAT_SPREAD) + val max = maxValue ?: (defaultValue + DEFAULT_FLOAT_SPREAD) + + min.coerceAtMost(defaultValue)..max.coerceAtLeast(defaultValue) +} + +@Composable +private fun ShaderValue.ColorValue.toComposeColor(): Color = remember(this) { + Color(red = red, green = green, blue = blue, alpha = alpha) +} + +private fun Color.toShaderColorValue(): ShaderValue.ColorValue { + val argb = toArgb() + return ShaderValue.ColorValue( + red = (argb shr RED_SHIFT) and COLOR_MASK, + green = (argb shr GREEN_SHIFT) and COLOR_MASK, + blue = argb and COLOR_MASK, + alpha = (argb shr ALPHA_SHIFT) and COLOR_MASK + ) +} + +private const val DEFAULT_FLOAT_SPREAD = 1f +private const val DEFAULT_INT_SPREAD = 10 +private const val MAX_INT_STEPS = 100 +private const val COLOR_MASK = 0xFF +private const val ALPHA_SHIFT = 24 +private const val RED_SHIFT = 16 +private const val GREEN_SHIFT = 8 diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ShearParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ShearParamsItem.kt new file mode 100644 index 0000000..333b473 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ShearParamsItem.kt @@ -0,0 +1,79 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.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.ShearParams +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 ShearParamsItem( + value: ShearParams, + filter: UiFilter, + onFilterChange: (value: ShearParams) -> Unit, + previewOnly: Boolean +) { + Column( + modifier = Modifier.padding(8.dp) + ) { + EnhancedSliderItem( + enabled = !previewOnly, + value = value.xAngle, + title = stringResource(filter.paramsInfo[0].title!!), + valueRange = filter.paramsInfo[0].valueRange, + onValueChange = { onFilterChange(value.copy(xAngle = it)) }, + internalStateTransformation = { + it.roundTo(filter.paramsInfo[0].roundTo) + }, + behaveAsContainer = false + ) + EnhancedSliderItem( + enabled = !previewOnly, + value = value.yAngle, + title = stringResource(filter.paramsInfo[1].title!!), + valueRange = filter.paramsInfo[1].valueRange, + onValueChange = { onFilterChange(value.copy(yAngle = it)) }, + internalStateTransformation = { + it.roundTo(filter.paramsInfo[1].roundTo) + }, + behaveAsContainer = false + ) + PreferenceRowSwitch( + title = stringResource(filter.paramsInfo[2].title!!), + checked = value.resize, + onClick = { onFilterChange(value.copy(resize = it)) }, + modifier = Modifier.padding( + top = 8.dp, + start = 4.dp, + end = 4.dp + ), + applyHorizontalPadding = false, + startContent = {}, + resultModifier = Modifier.padding(16.dp), + enabled = !previewOnly + ) + } +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/SideFadeRelativeItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/SideFadeRelativeItem.kt new file mode 100644 index 0000000..0b0c5e9 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/SideFadeRelativeItem.kt @@ -0,0 +1,101 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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] + } + ) + } +} + diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/SmearParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/SmearParamsItem.kt new file mode 100644 index 0000000..b962225 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/SmearParamsItem.kt @@ -0,0 +1,109 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/SparkleParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/SparkleParamsItem.kt new file mode 100644 index 0000000..ed7d04f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/SparkleParamsItem.kt @@ -0,0 +1,138 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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) + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ToneCurvesParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ToneCurvesParamsItem.kt new file mode 100644 index 0000000..978435f --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ToneCurvesParamsItem.kt @@ -0,0 +1,112 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 = {} + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/TornEdgeParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/TornEdgeParamsItem.kt new file mode 100644 index 0000000..b9c4bc3 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/TornEdgeParamsItem.kt @@ -0,0 +1,146 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.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.TornEdgeParams +import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch +import kotlin.math.roundToInt + +@Composable +internal fun TornEdgeParamsItem( + value: TornEdgeParams, + filter: UiFilter, + onFilterChange: (value: TornEdgeParams) -> Unit, + previewOnly: Boolean +) { + Column( + modifier = Modifier.padding(8.dp) + ) { + TornEdgeSlider( + value = value.toothHeight.toFloat(), + title = filter.paramsInfo[0].title!!, + valueRange = filter.paramsInfo[0].valueRange, + enabled = !previewOnly, + onValueChange = { + onFilterChange(value.copy(toothHeight = it.roundToInt())) + } + ) + TornEdgeSlider( + value = value.horizontalToothRange.toFloat(), + title = filter.paramsInfo[1].title!!, + valueRange = filter.paramsInfo[1].valueRange, + enabled = !previewOnly, + onValueChange = { + onFilterChange(value.copy(horizontalToothRange = it.roundToInt())) + } + ) + TornEdgeSlider( + value = value.verticalToothRange.toFloat(), + title = filter.paramsInfo[2].title!!, + valueRange = filter.paramsInfo[2].valueRange, + enabled = !previewOnly, + onValueChange = { + onFilterChange(value.copy(verticalToothRange = it.roundToInt())) + } + ) + TornEdgeSwitch( + title = R.string.top_edge, + checked = value.top, + enabled = !previewOnly, + onCheckedChange = { + onFilterChange(value.copy(top = it)) + } + ) + TornEdgeSwitch( + title = R.string.right_edge, + checked = value.right, + enabled = !previewOnly, + onCheckedChange = { + onFilterChange(value.copy(right = it)) + } + ) + TornEdgeSwitch( + title = R.string.bottom_edge, + checked = value.bottom, + enabled = !previewOnly, + onCheckedChange = { + onFilterChange(value.copy(bottom = it)) + } + ) + TornEdgeSwitch( + title = R.string.left_edge, + checked = value.left, + enabled = !previewOnly, + onCheckedChange = { + onFilterChange(value.copy(left = it)) + } + ) + } +} + +@Composable +private fun TornEdgeSlider( + value: Float, + title: Int, + valueRange: ClosedFloatingPointRange, + enabled: Boolean, + onValueChange: (Float) -> Unit +) { + EnhancedSliderItem( + enabled = enabled, + value = value, + title = stringResource(title), + valueRange = valueRange, + onValueChange = onValueChange, + internalStateTransformation = { it.roundTo(0) }, + behaveAsContainer = false + ) +} + +@Composable +private fun TornEdgeSwitch( + title: Int, + checked: Boolean, + enabled: Boolean, + onCheckedChange: (Boolean) -> Unit +) { + PreferenceRowSwitch( + title = stringResource(title), + checked = checked, + onClick = onCheckedChange, + modifier = Modifier.padding( + top = 8.dp, + start = 4.dp, + end = 4.dp + ), + applyHorizontalPadding = false, + startContent = {}, + resultModifier = Modifier.padding(16.dp), + enabled = enabled + ) +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/TransferFuncSelector.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/TransferFuncSelector.kt new file mode 100644 index 0000000..1689f2b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/TransferFuncSelector.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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]) + } + ) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/TripleItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/TripleItem.kt new file mode 100644 index 0000000..6b4b083 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/TripleItem.kt @@ -0,0 +1,118 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/VoronoiCrystallizeParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/VoronoiCrystallizeParamsItem.kt new file mode 100644 index 0000000..2f43cbd --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/VoronoiCrystallizeParamsItem.kt @@ -0,0 +1,149 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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) + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/WaterDropParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/WaterDropParamsItem.kt new file mode 100644 index 0000000..afa7639 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/WaterDropParamsItem.kt @@ -0,0 +1,79 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.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.FilterParam +import com.t8rin.imagetoolbox.core.filters.domain.model.params.WaterDropParams +import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem + +@Composable +internal fun WaterDropParamsItem( + value: WaterDropParams, + filter: UiFilter, + onFilterChange: (value: WaterDropParams) -> Unit, + previewOnly: Boolean +) { + Column( + modifier = Modifier.padding(8.dp) + ) { + WaterDropSlider(value.wavelength, filter.paramsInfo[0], !previewOnly) { + onFilterChange(value.copy(wavelength = it)) + } + WaterDropSlider(value.amplitude, filter.paramsInfo[1], !previewOnly) { + onFilterChange(value.copy(amplitude = it)) + } + WaterDropSlider(value.phase, filter.paramsInfo[2], !previewOnly) { + onFilterChange(value.copy(phase = it)) + } + WaterDropSlider(value.centreX, filter.paramsInfo[3], !previewOnly) { + onFilterChange(value.copy(centreX = it)) + } + WaterDropSlider(value.centreY, filter.paramsInfo[4], !previewOnly) { + onFilterChange(value.copy(centreY = it)) + } + WaterDropSlider(value.radius, filter.paramsInfo[5], !previewOnly) { + onFilterChange(value.copy(radius = it)) + } + } +} + +@Composable +private fun WaterDropSlider( + value: Float, + info: FilterParam, + enabled: Boolean, + onValueChange: (Float) -> Unit +) { + EnhancedSliderItem( + enabled = enabled, + value = value, + title = stringResource(info.title!!), + valueRange = info.valueRange, + onValueChange = onValueChange, + internalStateTransformation = { it.roundTo(info.roundTo) }, + behaveAsContainer = false + ) +} diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/WaterParamsItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/WaterParamsItem.kt new file mode 100644 index 0000000..ebeabec --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/WaterParamsItem.kt @@ -0,0 +1,108 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/ColorModelPairItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/ColorModelPairItem.kt new file mode 100644 index 0000000..e10efa9 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/ColorModelPairItem.kt @@ -0,0 +1,86 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/FloatColorModelPairItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/FloatColorModelPairItem.kt new file mode 100644 index 0000000..c70f618 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/FloatColorModelPairItem.kt @@ -0,0 +1,92 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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) + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/FloatFileModelPairItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/FloatFileModelPairItem.kt new file mode 100644 index 0000000..ac4a933 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/FloatFileModelPairItem.kt @@ -0,0 +1,121 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 com.t8rin.imagetoolbox.core.resources.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) + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/FloatImageModelPairItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/FloatImageModelPairItem.kt new file mode 100644 index 0000000..9acfa32 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/FloatImageModelPairItem.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberBlurEdgeModePairItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberBlurEdgeModePairItem.kt new file mode 100644 index 0000000..164ebb4 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberBlurEdgeModePairItem.kt @@ -0,0 +1,78 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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) + } + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberBooleanPairItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberBooleanPairItem.kt new file mode 100644 index 0000000..208ecbb --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberBooleanPairItem.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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(16.dp), + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberMirrorSidePairItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberMirrorSidePairItem.kt new file mode 100644 index 0000000..82b01b9 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberMirrorSidePairItem.kt @@ -0,0 +1,78 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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) + } + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberPairItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberPairItem.kt new file mode 100644 index 0000000..ad786c0 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberPairItem.kt @@ -0,0 +1,89 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberTransferFuncPairItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberTransferFuncPairItem.kt new file mode 100644 index 0000000..6e3f649 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberTransferFuncPairItem.kt @@ -0,0 +1,78 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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) + } + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/quad_components/NumberColorModelQuadItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/quad_components/NumberColorModelQuadItem.kt new file mode 100644 index 0000000..1147952 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/quad_components/NumberColorModelQuadItem.kt @@ -0,0 +1,126 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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) + ) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/quad_components/NumberQuadItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/quad_components/NumberQuadItem.kt new file mode 100644 index 0000000..fe99970 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/quad_components/NumberQuadItem.kt @@ -0,0 +1,103 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/ColorModelTripleItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/ColorModelTripleItem.kt new file mode 100644 index 0000000..fec0b90 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/ColorModelTripleItem.kt @@ -0,0 +1,118 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/FloatPaletteImageModelTripleItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/FloatPaletteImageModelTripleItem.kt new file mode 100644 index 0000000..b2b477b --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/FloatPaletteImageModelTripleItem.kt @@ -0,0 +1,149 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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)) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberColorModelColorModelTripleItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberColorModelColorModelTripleItem.kt new file mode 100644 index 0000000..5861640 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberColorModelColorModelTripleItem.kt @@ -0,0 +1,129 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberColorModelPopArtTripleItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberColorModelPopArtTripleItem.kt new file mode 100644 index 0000000..195b6d0 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberColorModelPopArtTripleItem.kt @@ -0,0 +1,142 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + ) + } + ) + } + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberNumberBlurEdgeModeTripleItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberNumberBlurEdgeModeTripleItem.kt new file mode 100644 index 0000000..c970da4 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberNumberBlurEdgeModeTripleItem.kt @@ -0,0 +1,102 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 } + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberNumberColorModelTripleItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberNumberColorModelTripleItem.kt new file mode 100644 index 0000000..6e5f162 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberNumberColorModelTripleItem.kt @@ -0,0 +1,115 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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) + ) +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberTransferFuncBlurEdgeModeTripleItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberTransferFuncBlurEdgeModeTripleItem.kt new file mode 100644 index 0000000..9ccd593 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberTransferFuncBlurEdgeModeTripleItem.kt @@ -0,0 +1,95 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 } + ) + } +} \ No newline at end of file diff --git a/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberTripleItem.kt b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberTripleItem.kt new file mode 100644 index 0000000..3aaed22 --- /dev/null +++ b/core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberTripleItem.kt @@ -0,0 +1,97 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } +} \ No newline at end of file diff --git a/core/ksp/.gitignore b/core/ksp/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/ksp/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/ksp/build.gradle.kts b/core/ksp/build.gradle.kts new file mode 100644 index 0000000..40ce57b --- /dev/null +++ b/core/ksp/build.gradle.kts @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/annotations/FilterInject.kt b/core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/annotations/FilterInject.kt new file mode 100644 index 0000000..eef0104 --- /dev/null +++ b/core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/annotations/FilterInject.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 \ No newline at end of file diff --git a/core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/annotations/UiFilterInject.kt b/core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/annotations/UiFilterInject.kt new file mode 100644 index 0000000..e916734 --- /dev/null +++ b/core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/annotations/UiFilterInject.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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" + } +} diff --git a/core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/processor/FilterInjectProcessor.kt b/core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/processor/FilterInjectProcessor.kt new file mode 100644 index 0000000..b722e1b --- /dev/null +++ b/core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/processor/FilterInjectProcessor.kt @@ -0,0 +1,100 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" + } +} \ No newline at end of file diff --git a/core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/processor/UiFilterInjectProcessor.kt b/core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/processor/UiFilterInjectProcessor.kt new file mode 100644 index 0000000..ce22529 --- /dev/null +++ b/core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/processor/UiFilterInjectProcessor.kt @@ -0,0 +1,317 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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("}.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("}.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.mapNotNull { it.resolve().declaration as? KSClassDeclaration } + .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 + ) + } +} diff --git a/core/ksp/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/core/ksp/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 0000000..25a9ca6 --- /dev/null +++ b/core/ksp/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1,19 @@ +# +# ImageToolbox is an image editor for android +# Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language 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 diff --git a/core/resources/.gitignore b/core/resources/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/resources/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/resources/build.gradle.kts b/core/resources/build.gradle.kts new file mode 100644 index 0000000..3cc7c76 --- /dev/null +++ b/core/resources/build.gradle.kts @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/core/resources/src/debug/ic_launcher-playstore.png b/core/resources/src/debug/ic_launcher-playstore.png new file mode 100644 index 0000000..bbb4c27 Binary files /dev/null and b/core/resources/src/debug/ic_launcher-playstore.png differ diff --git a/core/resources/src/debug/res/drawable-v31/ic_logo_animated.xml b/core/resources/src/debug/res/drawable-v31/ic_logo_animated.xml new file mode 100644 index 0000000..31c1dd1 --- /dev/null +++ b/core/resources/src/debug/res/drawable-v31/ic_logo_animated.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/resources/src/debug/res/drawable/ic_launcher_foreground.xml b/core/resources/src/debug/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..33e2a6f --- /dev/null +++ b/core/resources/src/debug/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,39 @@ + + + + + + + + + diff --git a/core/resources/src/debug/res/drawable/ic_logo_animated.xml b/core/resources/src/debug/res/drawable/ic_logo_animated.xml new file mode 100644 index 0000000..33e2a6f --- /dev/null +++ b/core/resources/src/debug/res/drawable/ic_logo_animated.xml @@ -0,0 +1,39 @@ + + + + + + + + + diff --git a/core/resources/src/debug/res/drawable/ic_notification_icon.xml b/core/resources/src/debug/res/drawable/ic_notification_icon.xml new file mode 100644 index 0000000..d1b48b9 --- /dev/null +++ b/core/resources/src/debug/res/drawable/ic_notification_icon.xml @@ -0,0 +1,39 @@ + + + + + + + + + diff --git a/core/resources/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml b/core/resources/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..a5e7837 --- /dev/null +++ b/core/resources/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/core/resources/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml b/core/resources/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..a5e7837 --- /dev/null +++ b/core/resources/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/core/resources/src/debug/res/mipmap-hdpi/ic_launcher.webp b/core/resources/src/debug/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..db3e110 Binary files /dev/null and b/core/resources/src/debug/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/core/resources/src/debug/res/mipmap-hdpi/ic_launcher_round.webp b/core/resources/src/debug/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b45efaa Binary files /dev/null and b/core/resources/src/debug/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/core/resources/src/debug/res/mipmap-mdpi/ic_launcher.webp b/core/resources/src/debug/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..9e1348f Binary files /dev/null and b/core/resources/src/debug/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/core/resources/src/debug/res/mipmap-mdpi/ic_launcher_round.webp b/core/resources/src/debug/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1051af8 Binary files /dev/null and b/core/resources/src/debug/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/core/resources/src/debug/res/mipmap-xhdpi/ic_launcher.webp b/core/resources/src/debug/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..37900d3 Binary files /dev/null and b/core/resources/src/debug/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/core/resources/src/debug/res/mipmap-xhdpi/ic_launcher_round.webp b/core/resources/src/debug/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..5802e06 Binary files /dev/null and b/core/resources/src/debug/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/core/resources/src/debug/res/mipmap-xxhdpi/ic_launcher.webp b/core/resources/src/debug/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..3d82826 Binary files /dev/null and b/core/resources/src/debug/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/core/resources/src/debug/res/mipmap-xxhdpi/ic_launcher_round.webp b/core/resources/src/debug/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..cb84419 Binary files /dev/null and b/core/resources/src/debug/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/core/resources/src/debug/res/mipmap-xxxhdpi/ic_launcher.webp b/core/resources/src/debug/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..5c0b231 Binary files /dev/null and b/core/resources/src/debug/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/core/resources/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.webp b/core/resources/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b4e12d3 Binary files /dev/null and b/core/resources/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/core/resources/src/debug/res/values-night-v31/colors.xml b/core/resources/src/debug/res/values-night-v31/colors.xml new file mode 100644 index 0000000..bc1f3d4 --- /dev/null +++ b/core/resources/src/debug/res/values-night-v31/colors.xml @@ -0,0 +1,21 @@ + + + + @android:color/system_accent2_800 + @android:color/system_accent1_100 + \ No newline at end of file diff --git a/core/resources/src/debug/res/values-night/colors.xml b/core/resources/src/debug/res/values-night/colors.xml new file mode 100644 index 0000000..72f7058 --- /dev/null +++ b/core/resources/src/debug/res/values-night/colors.xml @@ -0,0 +1,21 @@ + + + + #003131 + #00B1D3 + \ No newline at end of file diff --git a/core/resources/src/debug/res/values-v31/colors.xml b/core/resources/src/debug/res/values-v31/colors.xml new file mode 100644 index 0000000..338b010 --- /dev/null +++ b/core/resources/src/debug/res/values-v31/colors.xml @@ -0,0 +1,21 @@ + + + + @android:color/system_accent1_100 + @android:color/system_accent1_700 + \ No newline at end of file diff --git a/core/resources/src/debug/res/values/colors.xml b/core/resources/src/debug/res/values/colors.xml new file mode 100644 index 0000000..f9fa15d --- /dev/null +++ b/core/resources/src/debug/res/values/colors.xml @@ -0,0 +1,21 @@ + + + + #00B1D3 + #003131 + \ No newline at end of file diff --git a/core/resources/src/debug/res/values/ic_launcher_background.xml b/core/resources/src/debug/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..30f0b88 --- /dev/null +++ b/core/resources/src/debug/res/values/ic_launcher_background.xml @@ -0,0 +1,20 @@ + + + + @color/primary + \ No newline at end of file diff --git a/core/resources/src/main/AndroidManifest.xml b/core/resources/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/core/resources/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/core/resources/src/main/assets/lottie/emotions/aasparkles.lottie b/core/resources/src/main/assets/lottie/emotions/aasparkles.lottie new file mode 100644 index 0000000..80378bb Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/aasparkles.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/ahot-face.lottie b/core/resources/src/main/assets/lottie/emotions/ahot-face.lottie new file mode 100644 index 0000000..fd3f51d Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/ahot-face.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/alien.lottie b/core/resources/src/main/assets/lottie/emotions/alien.lottie new file mode 100644 index 0000000..4dbd830 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/alien.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/cat-crying.lottie b/core/resources/src/main/assets/lottie/emotions/cat-crying.lottie new file mode 100644 index 0000000..6593887 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/cat-crying.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/cat-grinning-with-smiling-eyes.lottie b/core/resources/src/main/assets/lottie/emotions/cat-grinning-with-smiling-eyes.lottie new file mode 100644 index 0000000..5383bdf Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/cat-grinning-with-smiling-eyes.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/cat-grinning.lottie b/core/resources/src/main/assets/lottie/emotions/cat-grinning.lottie new file mode 100644 index 0000000..0c63c7b Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/cat-grinning.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/cat-pouting.lottie b/core/resources/src/main/assets/lottie/emotions/cat-pouting.lottie new file mode 100644 index 0000000..5229a89 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/cat-pouting.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/cat-smiling-with-heart-eyes.lottie b/core/resources/src/main/assets/lottie/emotions/cat-smiling-with-heart-eyes.lottie new file mode 100644 index 0000000..18d878f Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/cat-smiling-with-heart-eyes.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/cat-with-wry-smile.lottie b/core/resources/src/main/assets/lottie/emotions/cat-with-wry-smile.lottie new file mode 100644 index 0000000..2e8f3ed Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/cat-with-wry-smile.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/devil-angry.lottie b/core/resources/src/main/assets/lottie/emotions/devil-angry.lottie new file mode 100644 index 0000000..876c1bb Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/devil-angry.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/devil-smiley.lottie b/core/resources/src/main/assets/lottie/emotions/devil-smiley.lottie new file mode 100644 index 0000000..1e9afa7 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/devil-smiley.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/distorted-face.lottie b/core/resources/src/main/assets/lottie/emotions/distorted-face.lottie new file mode 100644 index 0000000..a36ae4d Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/distorted-face.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/dotted-line-face.lottie b/core/resources/src/main/assets/lottie/emotions/dotted-line-face.lottie new file mode 100644 index 0000000..da29c8b Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/dotted-line-face.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/eyes.lottie b/core/resources/src/main/assets/lottie/emotions/eyes.lottie new file mode 100644 index 0000000..7e4c381 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/eyes.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-ablow.lottie b/core/resources/src/main/assets/lottie/emotions/face-ablow.lottie new file mode 100644 index 0000000..4d6f3fe Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-ablow.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-amelting.lottie b/core/resources/src/main/assets/lottie/emotions/face-amelting.lottie new file mode 100644 index 0000000..e12faee Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-amelting.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-apleading.lottie b/core/resources/src/main/assets/lottie/emotions/face-apleading.lottie new file mode 100644 index 0000000..7c36b7a Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-apleading.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-asaluting.lottie b/core/resources/src/main/assets/lottie/emotions/face-asaluting.lottie new file mode 100644 index 0000000..5d5955b Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-asaluting.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-ashaking.lottie b/core/resources/src/main/assets/lottie/emotions/face-ashaking.lottie new file mode 100644 index 0000000..68ef2b8 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-ashaking.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-athinking.lottie b/core/resources/src/main/assets/lottie/emotions/face-athinking.lottie new file mode 100644 index 0000000..bf3483d Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-athinking.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-azany.lottie b/core/resources/src/main/assets/lottie/emotions/face-azany.lottie new file mode 100644 index 0000000..6a9407b Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-azany.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-clown.lottie b/core/resources/src/main/assets/lottie/emotions/face-clown.lottie new file mode 100644 index 0000000..409132c Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-clown.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-cold.lottie b/core/resources/src/main/assets/lottie/emotions/face-cold.lottie new file mode 100644 index 0000000..2680234 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-cold.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-dnauseated.lottie b/core/resources/src/main/assets/lottie/emotions/face-dnauseated.lottie new file mode 100644 index 0000000..c5fb3b1 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-dnauseated.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-expressionless.lottie b/core/resources/src/main/assets/lottie/emotions/face-expressionless.lottie new file mode 100644 index 0000000..3028e30 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-expressionless.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-flushed.lottie b/core/resources/src/main/assets/lottie/emotions/face-flushed.lottie new file mode 100644 index 0000000..91e5840 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-flushed.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-in-clouds.lottie b/core/resources/src/main/assets/lottie/emotions/face-in-clouds.lottie new file mode 100644 index 0000000..03a3245 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-in-clouds.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-loudly-crying.lottie b/core/resources/src/main/assets/lottie/emotions/face-loudly-crying.lottie new file mode 100644 index 0000000..e38c4e9 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-loudly-crying.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-monkey-hear-no-evil.lottie b/core/resources/src/main/assets/lottie/emotions/face-monkey-hear-no-evil.lottie new file mode 100644 index 0000000..c0a805d Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-monkey-hear-no-evil.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-monkey-see-no-evil.lottie b/core/resources/src/main/assets/lottie/emotions/face-monkey-see-no-evil.lottie new file mode 100644 index 0000000..f9b6f5f Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-monkey-see-no-evil.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-sad-but-relieved.lottie b/core/resources/src/main/assets/lottie/emotions/face-sad-but-relieved.lottie new file mode 100644 index 0000000..da7f69f Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-sad-but-relieved.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-savoring-food.lottie b/core/resources/src/main/assets/lottie/emotions/face-savoring-food.lottie new file mode 100644 index 0000000..110cc82 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-savoring-food.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-screaming-in-fear.lottie b/core/resources/src/main/assets/lottie/emotions/face-screaming-in-fear.lottie new file mode 100644 index 0000000..f67c90b Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-screaming-in-fear.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-shaking-horizontally.lottie b/core/resources/src/main/assets/lottie/emotions/face-shaking-horizontally.lottie new file mode 100644 index 0000000..af96662 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-shaking-horizontally.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-shaking-vertically.lottie b/core/resources/src/main/assets/lottie/emotions/face-shaking-vertically.lottie new file mode 100644 index 0000000..c8e9d56 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-shaking-vertically.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-smirking.lottie b/core/resources/src/main/assets/lottie/emotions/face-smirking.lottie new file mode 100644 index 0000000..664978c Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-smirking.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-with-raised-eyebrow.lottie b/core/resources/src/main/assets/lottie/emotions/face-with-raised-eyebrow.lottie new file mode 100644 index 0000000..2fe3074 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-with-raised-eyebrow.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-with-spiral-eyes.lottie b/core/resources/src/main/assets/lottie/emotions/face-with-spiral-eyes.lottie new file mode 100644 index 0000000..83cb89c Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-with-spiral-eyes.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-with-steam-from-nose.lottie b/core/resources/src/main/assets/lottie/emotions/face-with-steam-from-nose.lottie new file mode 100644 index 0000000..1ab77b0 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-with-steam-from-nose.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-with-sunglasses.lottie b/core/resources/src/main/assets/lottie/emotions/face-with-sunglasses.lottie new file mode 100644 index 0000000..095b532 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-with-sunglasses.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-with-symbols-on-mouth.lottie b/core/resources/src/main/assets/lottie/emotions/face-with-symbols-on-mouth.lottie new file mode 100644 index 0000000..c456e98 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-with-symbols-on-mouth.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/face-with-tongue.lottie b/core/resources/src/main/assets/lottie/emotions/face-with-tongue.lottie new file mode 100644 index 0000000..a0f1c45 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/face-with-tongue.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/ghost.lottie b/core/resources/src/main/assets/lottie/emotions/ghost.lottie new file mode 100644 index 0000000..6ccab71 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/ghost.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart-aletter.lottie b/core/resources/src/main/assets/lottie/emotions/heart-aletter.lottie new file mode 100644 index 0000000..7c3bde3 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart-aletter.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart-growing.lottie b/core/resources/src/main/assets/lottie/emotions/heart-growing.lottie new file mode 100644 index 0000000..9830377 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart-growing.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart.lottie b/core/resources/src/main/assets/lottie/emotions/heart.lottie new file mode 100644 index 0000000..ed26d2b Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart_black.lottie b/core/resources/src/main/assets/lottie/emotions/heart_black.lottie new file mode 100644 index 0000000..fe433a6 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart_black.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart_blue.lottie b/core/resources/src/main/assets/lottie/emotions/heart_blue.lottie new file mode 100644 index 0000000..9bb2e4b Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart_blue.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart_blue_light.lottie b/core/resources/src/main/assets/lottie/emotions/heart_blue_light.lottie new file mode 100644 index 0000000..b42ce04 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart_blue_light.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart_broken.lottie b/core/resources/src/main/assets/lottie/emotions/heart_broken.lottie new file mode 100644 index 0000000..e54d4b2 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart_broken.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart_brown.lottie b/core/resources/src/main/assets/lottie/emotions/heart_brown.lottie new file mode 100644 index 0000000..82bef2c Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart_brown.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart_fire.lottie b/core/resources/src/main/assets/lottie/emotions/heart_fire.lottie new file mode 100644 index 0000000..2436fb8 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart_fire.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart_gauze.lottie b/core/resources/src/main/assets/lottie/emotions/heart_gauze.lottie new file mode 100644 index 0000000..cb9605e Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart_gauze.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart_green.lottie b/core/resources/src/main/assets/lottie/emotions/heart_green.lottie new file mode 100644 index 0000000..7fec3f5 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart_green.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart_grey.lottie b/core/resources/src/main/assets/lottie/emotions/heart_grey.lottie new file mode 100644 index 0000000..140985b Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart_grey.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart_orange.lottie b/core/resources/src/main/assets/lottie/emotions/heart_orange.lottie new file mode 100644 index 0000000..cb80ac9 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart_orange.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart_pink.lottie b/core/resources/src/main/assets/lottie/emotions/heart_pink.lottie new file mode 100644 index 0000000..aabc5bc Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart_pink.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart_purple.lottie b/core/resources/src/main/assets/lottie/emotions/heart_purple.lottie new file mode 100644 index 0000000..35f92e2 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart_purple.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart_white.lottie b/core/resources/src/main/assets/lottie/emotions/heart_white.lottie new file mode 100644 index 0000000..167afc0 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart_white.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/heart_yellow.lottie b/core/resources/src/main/assets/lottie/emotions/heart_yellow.lottie new file mode 100644 index 0000000..8a2203b Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/heart_yellow.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/hundred-points.lottie b/core/resources/src/main/assets/lottie/emotions/hundred-points.lottie new file mode 100644 index 0000000..de550e4 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/hundred-points.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/kiss-mark.lottie b/core/resources/src/main/assets/lottie/emotions/kiss-mark.lottie new file mode 100644 index 0000000..1bbd144 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/kiss-mark.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/moai.lottie b/core/resources/src/main/assets/lottie/emotions/moai.lottie new file mode 100644 index 0000000..fdd489f Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/moai.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/moon_a.lottie b/core/resources/src/main/assets/lottie/emotions/moon_a.lottie new file mode 100644 index 0000000..0766794 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/moon_a.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/moon_b.lottie b/core/resources/src/main/assets/lottie/emotions/moon_b.lottie new file mode 100644 index 0000000..379926e Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/moon_b.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/pile-of-poo.lottie b/core/resources/src/main/assets/lottie/emotions/pile-of-poo.lottie new file mode 100644 index 0000000..fe4fd5d Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/pile-of-poo.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/skull.lottie b/core/resources/src/main/assets/lottie/emotions/skull.lottie new file mode 100644 index 0000000..38e3982 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/skull.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/ssparty.lottie b/core/resources/src/main/assets/lottie/emotions/ssparty.lottie new file mode 100644 index 0000000..718b8d3 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/ssparty.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/zap.lottie b/core/resources/src/main/assets/lottie/emotions/zap.lottie new file mode 100644 index 0000000..8e25ab5 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/zap.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/zbang.lottie b/core/resources/src/main/assets/lottie/emotions/zbang.lottie new file mode 100644 index 0000000..71c63e9 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/zbang.lottie differ diff --git a/core/resources/src/main/assets/lottie/emotions/zfire.lottie b/core/resources/src/main/assets/lottie/emotions/zfire.lottie new file mode 100644 index 0000000..bc42212 Binary files /dev/null and b/core/resources/src/main/assets/lottie/emotions/zfire.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/1st-place-medal.lottie b/core/resources/src/main/assets/lottie/events/1st-place-medal.lottie new file mode 100644 index 0000000..c87f88a Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/1st-place-medal.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/2nd-place-medal.lottie b/core/resources/src/main/assets/lottie/events/2nd-place-medal.lottie new file mode 100644 index 0000000..a759633 Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/2nd-place-medal.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/3rd-place-medal.lottie b/core/resources/src/main/assets/lottie/events/3rd-place-medal.lottie new file mode 100644 index 0000000..f2daf7c Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/3rd-place-medal.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/admission-tickets.lottie b/core/resources/src/main/assets/lottie/events/admission-tickets.lottie new file mode 100644 index 0000000..4e1c503 Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/admission-tickets.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/alien_monster.lottie b/core/resources/src/main/assets/lottie/events/alien_monster.lottie new file mode 100644 index 0000000..ed166f7 Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/alien_monster.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/ball.lottie b/core/resources/src/main/assets/lottie/events/ball.lottie new file mode 100644 index 0000000..c25fe70 Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/ball.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/balloon.lottie b/core/resources/src/main/assets/lottie/events/balloon.lottie new file mode 100644 index 0000000..e578ec3 Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/balloon.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/bowling.lottie b/core/resources/src/main/assets/lottie/events/bowling.lottie new file mode 100644 index 0000000..3641149 Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/bowling.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/bullseye.lottie b/core/resources/src/main/assets/lottie/events/bullseye.lottie new file mode 100644 index 0000000..a5eb89b Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/bullseye.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/camera.lottie b/core/resources/src/main/assets/lottie/events/camera.lottie new file mode 100644 index 0000000..9897ad6 Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/camera.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/chess-pawn.lottie b/core/resources/src/main/assets/lottie/events/chess-pawn.lottie new file mode 100644 index 0000000..65afa67 Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/chess-pawn.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/disco.lottie b/core/resources/src/main/assets/lottie/events/disco.lottie new file mode 100644 index 0000000..cdd06c7 Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/disco.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/fireworks.lottie b/core/resources/src/main/assets/lottie/events/fireworks.lottie new file mode 100644 index 0000000..994668c Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/fireworks.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/gift.lottie b/core/resources/src/main/assets/lottie/events/gift.lottie new file mode 100644 index 0000000..ab2b017 Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/gift.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/performing-arts.lottie b/core/resources/src/main/assets/lottie/events/performing-arts.lottie new file mode 100644 index 0000000..2b3cccb Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/performing-arts.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/pool-8-ball.lottie b/core/resources/src/main/assets/lottie/events/pool-8-ball.lottie new file mode 100644 index 0000000..182f60c Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/pool-8-ball.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/rugby-football.lottie b/core/resources/src/main/assets/lottie/events/rugby-football.lottie new file mode 100644 index 0000000..d87a509 Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/rugby-football.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/slots.lottie b/core/resources/src/main/assets/lottie/events/slots.lottie new file mode 100644 index 0000000..099bd52 Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/slots.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/softball.lottie b/core/resources/src/main/assets/lottie/events/softball.lottie new file mode 100644 index 0000000..ef9ab3f Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/softball.lottie differ diff --git a/core/resources/src/main/assets/lottie/events/trophy.lottie b/core/resources/src/main/assets/lottie/events/trophy.lottie new file mode 100644 index 0000000..d319050 Binary files /dev/null and b/core/resources/src/main/assets/lottie/events/trophy.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/apple.lottie b/core/resources/src/main/assets/lottie/food/apple.lottie new file mode 100644 index 0000000..c9f57fa Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/apple.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/avocado.lottie b/core/resources/src/main/assets/lottie/food/avocado.lottie new file mode 100644 index 0000000..c355c91 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/avocado.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/bacon.lottie b/core/resources/src/main/assets/lottie/food/bacon.lottie new file mode 100644 index 0000000..f803128 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/bacon.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/beans.lottie b/core/resources/src/main/assets/lottie/food/beans.lottie new file mode 100644 index 0000000..4ab9cdf Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/beans.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/bell-pepper.lottie b/core/resources/src/main/assets/lottie/food/bell-pepper.lottie new file mode 100644 index 0000000..23d7efd Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/bell-pepper.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/beverage-box.lottie b/core/resources/src/main/assets/lottie/food/beverage-box.lottie new file mode 100644 index 0000000..df0953b Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/beverage-box.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/birthday-cake.lottie b/core/resources/src/main/assets/lottie/food/birthday-cake.lottie new file mode 100644 index 0000000..097ba41 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/birthday-cake.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/blueberries.lottie b/core/resources/src/main/assets/lottie/food/blueberries.lottie new file mode 100644 index 0000000..ba77bf5 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/blueberries.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/bottle.lottie b/core/resources/src/main/assets/lottie/food/bottle.lottie new file mode 100644 index 0000000..27cf0ec Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/bottle.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/bread.lottie b/core/resources/src/main/assets/lottie/food/bread.lottie new file mode 100644 index 0000000..0fa68de Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/bread.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/broccoli.lottie b/core/resources/src/main/assets/lottie/food/broccoli.lottie new file mode 100644 index 0000000..2c5bfc2 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/broccoli.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/bubble-tea.lottie b/core/resources/src/main/assets/lottie/food/bubble-tea.lottie new file mode 100644 index 0000000..955c411 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/bubble-tea.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/burrito.lottie b/core/resources/src/main/assets/lottie/food/burrito.lottie new file mode 100644 index 0000000..f6cd6f6 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/burrito.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/carrot.lottie b/core/resources/src/main/assets/lottie/food/carrot.lottie new file mode 100644 index 0000000..ec7c365 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/carrot.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/cheese-wedge.lottie b/core/resources/src/main/assets/lottie/food/cheese-wedge.lottie new file mode 100644 index 0000000..e20170a Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/cheese-wedge.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/cherries.lottie b/core/resources/src/main/assets/lottie/food/cherries.lottie new file mode 100644 index 0000000..2b8c837 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/cherries.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/clinking1.lottie b/core/resources/src/main/assets/lottie/food/clinking1.lottie new file mode 100644 index 0000000..be92cf3 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/clinking1.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/clinking2.lottie b/core/resources/src/main/assets/lottie/food/clinking2.lottie new file mode 100644 index 0000000..f6cfb0f Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/clinking2.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/cookie.lottie b/core/resources/src/main/assets/lottie/food/cookie.lottie new file mode 100644 index 0000000..8898cef Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/cookie.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/crab.lottie b/core/resources/src/main/assets/lottie/food/crab.lottie new file mode 100644 index 0000000..f354e06 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/crab.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/cucumber.lottie b/core/resources/src/main/assets/lottie/food/cucumber.lottie new file mode 100644 index 0000000..d4e77ab Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/cucumber.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/doughnut.lottie b/core/resources/src/main/assets/lottie/food/doughnut.lottie new file mode 100644 index 0000000..6078e65 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/doughnut.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/ear-of-corn.lottie b/core/resources/src/main/assets/lottie/food/ear-of-corn.lottie new file mode 100644 index 0000000..ae02603 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/ear-of-corn.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/garlic.lottie b/core/resources/src/main/assets/lottie/food/garlic.lottie new file mode 100644 index 0000000..5304ab8 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/garlic.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/grapes.lottie b/core/resources/src/main/assets/lottie/food/grapes.lottie new file mode 100644 index 0000000..11d02d2 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/grapes.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/green-salad.lottie b/core/resources/src/main/assets/lottie/food/green-salad.lottie new file mode 100644 index 0000000..032d6e6 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/green-salad.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/hamburger.lottie b/core/resources/src/main/assets/lottie/food/hamburger.lottie new file mode 100644 index 0000000..e6c6aea Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/hamburger.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/hotdog.lottie b/core/resources/src/main/assets/lottie/food/hotdog.lottie new file mode 100644 index 0000000..2098331 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/hotdog.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/kiwi.lottie b/core/resources/src/main/assets/lottie/food/kiwi.lottie new file mode 100644 index 0000000..2647ad5 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/kiwi.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/leafy-green.lottie b/core/resources/src/main/assets/lottie/food/leafy-green.lottie new file mode 100644 index 0000000..6749513 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/leafy-green.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/lemon.lottie b/core/resources/src/main/assets/lottie/food/lemon.lottie new file mode 100644 index 0000000..9838342 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/lemon.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/lobster.lottie b/core/resources/src/main/assets/lottie/food/lobster.lottie new file mode 100644 index 0000000..ac7b763 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/lobster.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/mango.lottie b/core/resources/src/main/assets/lottie/food/mango.lottie new file mode 100644 index 0000000..0377c4a Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/mango.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/onion.lottie b/core/resources/src/main/assets/lottie/food/onion.lottie new file mode 100644 index 0000000..287aa0f Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/onion.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/pancakes.lottie b/core/resources/src/main/assets/lottie/food/pancakes.lottie new file mode 100644 index 0000000..2c6cb3a Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/pancakes.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/pea-pod.lottie b/core/resources/src/main/assets/lottie/food/pea-pod.lottie new file mode 100644 index 0000000..c8fd4ef Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/pea-pod.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/pie.lottie b/core/resources/src/main/assets/lottie/food/pie.lottie new file mode 100644 index 0000000..eab0160 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/pie.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/pineapple.lottie b/core/resources/src/main/assets/lottie/food/pineapple.lottie new file mode 100644 index 0000000..1d90554 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/pineapple.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/pizza.lottie b/core/resources/src/main/assets/lottie/food/pizza.lottie new file mode 100644 index 0000000..58d948b Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/pizza.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/popcorn.lottie b/core/resources/src/main/assets/lottie/food/popcorn.lottie new file mode 100644 index 0000000..4093c11 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/popcorn.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/potato.lottie b/core/resources/src/main/assets/lottie/food/potato.lottie new file mode 100644 index 0000000..4a966b2 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/potato.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/poultry-leg.lottie b/core/resources/src/main/assets/lottie/food/poultry-leg.lottie new file mode 100644 index 0000000..f26223f Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/poultry-leg.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/salt.lottie b/core/resources/src/main/assets/lottie/food/salt.lottie new file mode 100644 index 0000000..b050135 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/salt.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/strawberry.lottie b/core/resources/src/main/assets/lottie/food/strawberry.lottie new file mode 100644 index 0000000..a17046b Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/strawberry.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/taco.lottie b/core/resources/src/main/assets/lottie/food/taco.lottie new file mode 100644 index 0000000..96d0779 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/taco.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/tangerine.lottie b/core/resources/src/main/assets/lottie/food/tangerine.lottie new file mode 100644 index 0000000..49cab5d Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/tangerine.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/tomato.lottie b/core/resources/src/main/assets/lottie/food/tomato.lottie new file mode 100644 index 0000000..f42e804 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/tomato.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/tropical-drink.lottie b/core/resources/src/main/assets/lottie/food/tropical-drink.lottie new file mode 100644 index 0000000..65e9f41 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/tropical-drink.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/watermelon.lottie b/core/resources/src/main/assets/lottie/food/watermelon.lottie new file mode 100644 index 0000000..8be03b5 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/watermelon.lottie differ diff --git a/core/resources/src/main/assets/lottie/food/wine.lottie b/core/resources/src/main/assets/lottie/food/wine.lottie new file mode 100644 index 0000000..d911726 Binary files /dev/null and b/core/resources/src/main/assets/lottie/food/wine.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/baby-chick.lottie b/core/resources/src/main/assets/lottie/nature/baby-chick.lottie new file mode 100644 index 0000000..e37fe62 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/baby-chick.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/bear.lottie b/core/resources/src/main/assets/lottie/nature/bear.lottie new file mode 100644 index 0000000..816b71c Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/bear.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/blossom.lottie b/core/resources/src/main/assets/lottie/nature/blossom.lottie new file mode 100644 index 0000000..9e94912 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/blossom.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/blowfish.lottie b/core/resources/src/main/assets/lottie/nature/blowfish.lottie new file mode 100644 index 0000000..4562367 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/blowfish.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/bone.lottie b/core/resources/src/main/assets/lottie/nature/bone.lottie new file mode 100644 index 0000000..24332b1 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/bone.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/bouquet.lottie b/core/resources/src/main/assets/lottie/nature/bouquet.lottie new file mode 100644 index 0000000..faf5a61 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/bouquet.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/cactus.lottie b/core/resources/src/main/assets/lottie/nature/cactus.lottie new file mode 100644 index 0000000..ca1665f Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/cactus.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/cherry-blossom.lottie b/core/resources/src/main/assets/lottie/nature/cherry-blossom.lottie new file mode 100644 index 0000000..37fe86d Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/cherry-blossom.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/cloud.lottie b/core/resources/src/main/assets/lottie/nature/cloud.lottie new file mode 100644 index 0000000..a3c82b4 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/cloud.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/clover.lottie b/core/resources/src/main/assets/lottie/nature/clover.lottie new file mode 100644 index 0000000..989a902 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/clover.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/comet.lottie b/core/resources/src/main/assets/lottie/nature/comet.lottie new file mode 100644 index 0000000..d703871 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/comet.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/crocodile.lottie b/core/resources/src/main/assets/lottie/nature/crocodile.lottie new file mode 100644 index 0000000..6785ea0 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/crocodile.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/dolphin.lottie b/core/resources/src/main/assets/lottie/nature/dolphin.lottie new file mode 100644 index 0000000..b1f7d37 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/dolphin.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/evergreen-tree.lottie b/core/resources/src/main/assets/lottie/nature/evergreen-tree.lottie new file mode 100644 index 0000000..b678bf3 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/evergreen-tree.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/face-rabbit.lottie b/core/resources/src/main/assets/lottie/nature/face-rabbit.lottie new file mode 100644 index 0000000..65ac3fb Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/face-rabbit.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/face-sun-with.lottie b/core/resources/src/main/assets/lottie/nature/face-sun-with.lottie new file mode 100644 index 0000000..19212b0 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/face-sun-with.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/fish.lottie b/core/resources/src/main/assets/lottie/nature/fish.lottie new file mode 100644 index 0000000..acae3e9 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/fish.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/fox.lottie b/core/resources/src/main/assets/lottie/nature/fox.lottie new file mode 100644 index 0000000..fec199b Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/fox.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/frog.lottie b/core/resources/src/main/assets/lottie/nature/frog.lottie new file mode 100644 index 0000000..dde7e85 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/frog.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/goose.lottie b/core/resources/src/main/assets/lottie/nature/goose.lottie new file mode 100644 index 0000000..0ded058 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/goose.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/herb.lottie b/core/resources/src/main/assets/lottie/nature/herb.lottie new file mode 100644 index 0000000..3a99c13 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/herb.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/hyacinth.lottie b/core/resources/src/main/assets/lottie/nature/hyacinth.lottie new file mode 100644 index 0000000..14cb9d2 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/hyacinth.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/jellyfish.lottie b/core/resources/src/main/assets/lottie/nature/jellyfish.lottie new file mode 100644 index 0000000..acf0bd1 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/jellyfish.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/lady-beetle.lottie b/core/resources/src/main/assets/lottie/nature/lady-beetle.lottie new file mode 100644 index 0000000..2ca171a Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/lady-beetle.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/leaf-fluttering-in-wind.lottie b/core/resources/src/main/assets/lottie/nature/leaf-fluttering-in-wind.lottie new file mode 100644 index 0000000..ed2ab9c Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/leaf-fluttering-in-wind.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/leafless-tree.lottie b/core/resources/src/main/assets/lottie/nature/leafless-tree.lottie new file mode 100644 index 0000000..4750c75 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/leafless-tree.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/lotus.lottie b/core/resources/src/main/assets/lottie/nature/lotus.lottie new file mode 100644 index 0000000..f5234f9 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/lotus.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/maple-leaf.lottie b/core/resources/src/main/assets/lottie/nature/maple-leaf.lottie new file mode 100644 index 0000000..d41a992 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/maple-leaf.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/microbe.lottie b/core/resources/src/main/assets/lottie/nature/microbe.lottie new file mode 100644 index 0000000..bfcfa14 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/microbe.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/milky-way.lottie b/core/resources/src/main/assets/lottie/nature/milky-way.lottie new file mode 100644 index 0000000..ac0d50b Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/milky-way.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/mushroom.lottie b/core/resources/src/main/assets/lottie/nature/mushroom.lottie new file mode 100644 index 0000000..a93973c Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/mushroom.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/orangutan.lottie b/core/resources/src/main/assets/lottie/nature/orangutan.lottie new file mode 100644 index 0000000..3a40349 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/orangutan.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/park.lottie b/core/resources/src/main/assets/lottie/nature/park.lottie new file mode 100644 index 0000000..e7b8177 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/park.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/paw-prints.lottie b/core/resources/src/main/assets/lottie/nature/paw-prints.lottie new file mode 100644 index 0000000..f3e0886 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/paw-prints.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/phoenix.lottie b/core/resources/src/main/assets/lottie/nature/phoenix.lottie new file mode 100644 index 0000000..9e9e5ba Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/phoenix.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/rainbow.lottie b/core/resources/src/main/assets/lottie/nature/rainbow.lottie new file mode 100644 index 0000000..be6b199 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/rainbow.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/rock.lottie b/core/resources/src/main/assets/lottie/nature/rock.lottie new file mode 100644 index 0000000..9f132d4 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/rock.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/rose.lottie b/core/resources/src/main/assets/lottie/nature/rose.lottie new file mode 100644 index 0000000..b545471 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/rose.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/sauropod.lottie b/core/resources/src/main/assets/lottie/nature/sauropod.lottie new file mode 100644 index 0000000..02ebff3 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/sauropod.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/shark.lottie b/core/resources/src/main/assets/lottie/nature/shark.lottie new file mode 100644 index 0000000..6194842 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/shark.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/snake.lottie b/core/resources/src/main/assets/lottie/nature/snake.lottie new file mode 100644 index 0000000..ee65b88 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/snake.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/snowflake.lottie b/core/resources/src/main/assets/lottie/nature/snowflake.lottie new file mode 100644 index 0000000..d8c66ff Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/snowflake.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/snowman.lottie b/core/resources/src/main/assets/lottie/nature/snowman.lottie new file mode 100644 index 0000000..9e07f50 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/snowman.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/sun-behind-cloud.lottie b/core/resources/src/main/assets/lottie/nature/sun-behind-cloud.lottie new file mode 100644 index 0000000..702ea5c Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/sun-behind-cloud.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/sunrise-over-mountains.lottie b/core/resources/src/main/assets/lottie/nature/sunrise-over-mountains.lottie new file mode 100644 index 0000000..838dc18 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/sunrise-over-mountains.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/sunrise.lottie b/core/resources/src/main/assets/lottie/nature/sunrise.lottie new file mode 100644 index 0000000..588782c Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/sunrise.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/sunset.lottie b/core/resources/src/main/assets/lottie/nature/sunset.lottie new file mode 100644 index 0000000..7b74cda Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/sunset.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/tornado.lottie b/core/resources/src/main/assets/lottie/nature/tornado.lottie new file mode 100644 index 0000000..44ac70c Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/tornado.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/turtle.lottie b/core/resources/src/main/assets/lottie/nature/turtle.lottie new file mode 100644 index 0000000..ddcfd86 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/turtle.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/umbrella-on-ground.lottie b/core/resources/src/main/assets/lottie/nature/umbrella-on-ground.lottie new file mode 100644 index 0000000..473a710 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/umbrella-on-ground.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/unicorn.lottie b/core/resources/src/main/assets/lottie/nature/unicorn.lottie new file mode 100644 index 0000000..67f265a Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/unicorn.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/volcano.lottie b/core/resources/src/main/assets/lottie/nature/volcano.lottie new file mode 100644 index 0000000..8e07527 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/volcano.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/wave.lottie b/core/resources/src/main/assets/lottie/nature/wave.lottie new file mode 100644 index 0000000..e9fa2ac Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/wave.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/wilted-flower.lottie b/core/resources/src/main/assets/lottie/nature/wilted-flower.lottie new file mode 100644 index 0000000..56edaca Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/wilted-flower.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/wolf.lottie b/core/resources/src/main/assets/lottie/nature/wolf.lottie new file mode 100644 index 0000000..968bb34 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/wolf.lottie differ diff --git a/core/resources/src/main/assets/lottie/nature/worm.lottie b/core/resources/src/main/assets/lottie/nature/worm.lottie new file mode 100644 index 0000000..1eb0495 Binary files /dev/null and b/core/resources/src/main/assets/lottie/nature/worm.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/battery.lottie b/core/resources/src/main/assets/lottie/objects/battery.lottie new file mode 100644 index 0000000..d87416e Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/battery.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/bomb.lottie b/core/resources/src/main/assets/lottie/objects/bomb.lottie new file mode 100644 index 0000000..1162935 Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/bomb.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/brain.lottie b/core/resources/src/main/assets/lottie/objects/brain.lottie new file mode 100644 index 0000000..8dc346e Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/brain.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/buoy.lottie b/core/resources/src/main/assets/lottie/objects/buoy.lottie new file mode 100644 index 0000000..5ffef49 Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/buoy.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/crown.lottie b/core/resources/src/main/assets/lottie/objects/crown.lottie new file mode 100644 index 0000000..c9df527 Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/crown.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/crystal_ball.lottie b/core/resources/src/main/assets/lottie/objects/crystal_ball.lottie new file mode 100644 index 0000000..e53c72d Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/crystal_ball.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/diamond.lottie b/core/resources/src/main/assets/lottie/objects/diamond.lottie new file mode 100644 index 0000000..df5a3f9 Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/diamond.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/die.lottie b/core/resources/src/main/assets/lottie/objects/die.lottie new file mode 100644 index 0000000..9ae5b16 Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/die.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/disk.lottie b/core/resources/src/main/assets/lottie/objects/disk.lottie new file mode 100644 index 0000000..418efa6 Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/disk.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/light_bulb.lottie b/core/resources/src/main/assets/lottie/objects/light_bulb.lottie new file mode 100644 index 0000000..cd1ba74 Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/light_bulb.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/locked.lottie b/core/resources/src/main/assets/lottie/objects/locked.lottie new file mode 100644 index 0000000..1fa07c4 Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/locked.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/money.lottie b/core/resources/src/main/assets/lottie/objects/money.lottie new file mode 100644 index 0000000..0c22fa8 Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/money.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/package.lottie b/core/resources/src/main/assets/lottie/objects/package.lottie new file mode 100644 index 0000000..8a55d66 Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/package.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/ring.lottie b/core/resources/src/main/assets/lottie/objects/ring.lottie new file mode 100644 index 0000000..62904bf Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/ring.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/shoe.lottie b/core/resources/src/main/assets/lottie/objects/shoe.lottie new file mode 100644 index 0000000..a016a91 Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/shoe.lottie differ diff --git a/core/resources/src/main/assets/lottie/objects/siren.lottie b/core/resources/src/main/assets/lottie/objects/siren.lottie new file mode 100644 index 0000000..217483a Binary files /dev/null and b/core/resources/src/main/assets/lottie/objects/siren.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/acancer.lottie b/core/resources/src/main/assets/lottie/symbols/acancer.lottie new file mode 100644 index 0000000..448606e Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/acancer.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/acapricorn.lottie b/core/resources/src/main/assets/lottie/symbols/acapricorn.lottie new file mode 100644 index 0000000..7e6d9f6 Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/acapricorn.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/agemini.lottie b/core/resources/src/main/assets/lottie/symbols/agemini.lottie new file mode 100644 index 0000000..9c146e2 Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/agemini.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/aleo.lottie b/core/resources/src/main/assets/lottie/symbols/aleo.lottie new file mode 100644 index 0000000..3ce9bc4 Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/aleo.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/alibra.lottie b/core/resources/src/main/assets/lottie/symbols/alibra.lottie new file mode 100644 index 0000000..85a6735 Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/alibra.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/aophiuchus.lottie b/core/resources/src/main/assets/lottie/symbols/aophiuchus.lottie new file mode 100644 index 0000000..9541f31 Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/aophiuchus.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/apisces.lottie b/core/resources/src/main/assets/lottie/symbols/apisces.lottie new file mode 100644 index 0000000..598e0f5 Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/apisces.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/aquarius.lottie b/core/resources/src/main/assets/lottie/symbols/aquarius.lottie new file mode 100644 index 0000000..1d052cc Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/aquarius.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/aries.lottie b/core/resources/src/main/assets/lottie/symbols/aries.lottie new file mode 100644 index 0000000..1a5f1d4 Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/aries.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/asagittarius.lottie b/core/resources/src/main/assets/lottie/symbols/asagittarius.lottie new file mode 100644 index 0000000..e7be851 Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/asagittarius.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/ascorpio.lottie b/core/resources/src/main/assets/lottie/symbols/ascorpio.lottie new file mode 100644 index 0000000..bab1aa8 Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/ascorpio.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/ataurus.lottie b/core/resources/src/main/assets/lottie/symbols/ataurus.lottie new file mode 100644 index 0000000..93ddf82 Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/ataurus.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/avirgo.lottie b/core/resources/src/main/assets/lottie/symbols/avirgo.lottie new file mode 100644 index 0000000..21c1a43 Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/avirgo.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/biohazard.lottie b/core/resources/src/main/assets/lottie/symbols/biohazard.lottie new file mode 100644 index 0000000..9ead2b8 Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/biohazard.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/cradioactive.lottie b/core/resources/src/main/assets/lottie/symbols/cradioactive.lottie new file mode 100644 index 0000000..7bead80 Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/cradioactive.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/cwarning.lottie b/core/resources/src/main/assets/lottie/symbols/cwarning.lottie new file mode 100644 index 0000000..2572a5f Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/cwarning.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/speech-balloon.lottie b/core/resources/src/main/assets/lottie/symbols/speech-balloon.lottie new file mode 100644 index 0000000..493950f Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/speech-balloon.lottie differ diff --git a/core/resources/src/main/assets/lottie/symbols/tick.lottie b/core/resources/src/main/assets/lottie/symbols/tick.lottie new file mode 100644 index 0000000..0e6f401 Binary files /dev/null and b/core/resources/src/main/assets/lottie/symbols/tick.lottie differ diff --git a/core/resources/src/main/assets/lottie/transportation/automobile.lottie b/core/resources/src/main/assets/lottie/transportation/automobile.lottie new file mode 100644 index 0000000..f3669e2 Binary files /dev/null and b/core/resources/src/main/assets/lottie/transportation/automobile.lottie differ diff --git a/core/resources/src/main/assets/lottie/transportation/bus.lottie b/core/resources/src/main/assets/lottie/transportation/bus.lottie new file mode 100644 index 0000000..1f7716d Binary files /dev/null and b/core/resources/src/main/assets/lottie/transportation/bus.lottie differ diff --git a/core/resources/src/main/assets/lottie/transportation/construction.lottie b/core/resources/src/main/assets/lottie/transportation/construction.lottie new file mode 100644 index 0000000..fffa315 Binary files /dev/null and b/core/resources/src/main/assets/lottie/transportation/construction.lottie differ diff --git a/core/resources/src/main/assets/lottie/transportation/delivery-truck.lottie b/core/resources/src/main/assets/lottie/transportation/delivery-truck.lottie new file mode 100644 index 0000000..ea6cfbf Binary files /dev/null and b/core/resources/src/main/assets/lottie/transportation/delivery-truck.lottie differ diff --git a/core/resources/src/main/assets/lottie/transportation/globe.lottie b/core/resources/src/main/assets/lottie/transportation/globe.lottie new file mode 100644 index 0000000..220fa8a Binary files /dev/null and b/core/resources/src/main/assets/lottie/transportation/globe.lottie differ diff --git a/core/resources/src/main/assets/lottie/transportation/motorcycle.lottie b/core/resources/src/main/assets/lottie/transportation/motorcycle.lottie new file mode 100644 index 0000000..c7847ed Binary files /dev/null and b/core/resources/src/main/assets/lottie/transportation/motorcycle.lottie differ diff --git a/core/resources/src/main/assets/lottie/transportation/racing-car.lottie b/core/resources/src/main/assets/lottie/transportation/racing-car.lottie new file mode 100644 index 0000000..79a622c Binary files /dev/null and b/core/resources/src/main/assets/lottie/transportation/racing-car.lottie differ diff --git a/core/resources/src/main/assets/lottie/transportation/ringed-planet.lottie b/core/resources/src/main/assets/lottie/transportation/ringed-planet.lottie new file mode 100644 index 0000000..63c8d6a Binary files /dev/null and b/core/resources/src/main/assets/lottie/transportation/ringed-planet.lottie differ diff --git a/core/resources/src/main/assets/lottie/transportation/rocket.lottie b/core/resources/src/main/assets/lottie/transportation/rocket.lottie new file mode 100644 index 0000000..99d675f Binary files /dev/null and b/core/resources/src/main/assets/lottie/transportation/rocket.lottie differ diff --git a/core/resources/src/main/assets/lottie/transportation/small-airplane.lottie b/core/resources/src/main/assets/lottie/transportation/small-airplane.lottie new file mode 100644 index 0000000..980a3b7 Binary files /dev/null and b/core/resources/src/main/assets/lottie/transportation/small-airplane.lottie differ diff --git a/core/resources/src/main/assets/lottie/transportation/taxi.lottie b/core/resources/src/main/assets/lottie/transportation/taxi.lottie new file mode 100644 index 0000000..adb0bfa Binary files /dev/null and b/core/resources/src/main/assets/lottie/transportation/taxi.lottie differ diff --git a/core/resources/src/main/assets/lottie/transportation/tractor.lottie b/core/resources/src/main/assets/lottie/transportation/tractor.lottie new file mode 100644 index 0000000..3099cbe Binary files /dev/null and b/core/resources/src/main/assets/lottie/transportation/tractor.lottie differ diff --git a/core/resources/src/main/assets/lottie/transportation/ufo.lottie b/core/resources/src/main/assets/lottie/transportation/ufo.lottie new file mode 100644 index 0000000..e92e3e7 Binary files /dev/null and b/core/resources/src/main/assets/lottie/transportation/ufo.lottie differ diff --git a/core/resources/src/main/assets/svg/emotions/aasparkles.svg b/core/resources/src/main/assets/svg/emotions/aasparkles.svg new file mode 100644 index 0000000..2e501cb --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/aasparkles.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/ahot-face.svg b/core/resources/src/main/assets/svg/emotions/ahot-face.svg new file mode 100644 index 0000000..b135c4d --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/ahot-face.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/alien.svg b/core/resources/src/main/assets/svg/emotions/alien.svg new file mode 100644 index 0000000..7151bbd --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/alien.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/cat-crying.svg b/core/resources/src/main/assets/svg/emotions/cat-crying.svg new file mode 100644 index 0000000..43a28b0 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/cat-crying.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/cat-grinning-with-smiling-eyes.svg b/core/resources/src/main/assets/svg/emotions/cat-grinning-with-smiling-eyes.svg new file mode 100644 index 0000000..7ed25e4 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/cat-grinning-with-smiling-eyes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/cat-grinning.svg b/core/resources/src/main/assets/svg/emotions/cat-grinning.svg new file mode 100644 index 0000000..5c7a309 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/cat-grinning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/cat-pouting.svg b/core/resources/src/main/assets/svg/emotions/cat-pouting.svg new file mode 100644 index 0000000..162039f --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/cat-pouting.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/cat-smiling-with-heart-eyes.svg b/core/resources/src/main/assets/svg/emotions/cat-smiling-with-heart-eyes.svg new file mode 100644 index 0000000..bfad272 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/cat-smiling-with-heart-eyes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/cat-with-wry-smile.svg b/core/resources/src/main/assets/svg/emotions/cat-with-wry-smile.svg new file mode 100644 index 0000000..4526070 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/cat-with-wry-smile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/devil-angry.svg b/core/resources/src/main/assets/svg/emotions/devil-angry.svg new file mode 100644 index 0000000..94bd570 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/devil-angry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/devil-smiley.svg b/core/resources/src/main/assets/svg/emotions/devil-smiley.svg new file mode 100644 index 0000000..3688390 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/devil-smiley.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/distorted-face.svg b/core/resources/src/main/assets/svg/emotions/distorted-face.svg new file mode 100644 index 0000000..9e4e293 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/distorted-face.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/dotted-line-face.svg b/core/resources/src/main/assets/svg/emotions/dotted-line-face.svg new file mode 100644 index 0000000..80d29c8 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/dotted-line-face.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/eyes.svg b/core/resources/src/main/assets/svg/emotions/eyes.svg new file mode 100644 index 0000000..6923b23 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/eyes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-ablow.svg b/core/resources/src/main/assets/svg/emotions/face-ablow.svg new file mode 100644 index 0000000..3ba4ac7 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-ablow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-amelting.svg b/core/resources/src/main/assets/svg/emotions/face-amelting.svg new file mode 100644 index 0000000..8cced61 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-amelting.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-apleading.svg b/core/resources/src/main/assets/svg/emotions/face-apleading.svg new file mode 100644 index 0000000..8c14daf --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-apleading.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-asaluting.svg b/core/resources/src/main/assets/svg/emotions/face-asaluting.svg new file mode 100644 index 0000000..c37f759 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-asaluting.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-ashaking.svg b/core/resources/src/main/assets/svg/emotions/face-ashaking.svg new file mode 100644 index 0000000..d9c24b2 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-ashaking.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-athinking.svg b/core/resources/src/main/assets/svg/emotions/face-athinking.svg new file mode 100644 index 0000000..80886db --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-athinking.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-azany.svg b/core/resources/src/main/assets/svg/emotions/face-azany.svg new file mode 100644 index 0000000..d9ca057 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-azany.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-clown.svg b/core/resources/src/main/assets/svg/emotions/face-clown.svg new file mode 100644 index 0000000..78b7e9c --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-clown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-cold.svg b/core/resources/src/main/assets/svg/emotions/face-cold.svg new file mode 100644 index 0000000..52e2625 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-cold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-dnauseated.svg b/core/resources/src/main/assets/svg/emotions/face-dnauseated.svg new file mode 100644 index 0000000..84722a0 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-dnauseated.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-expressionless.svg b/core/resources/src/main/assets/svg/emotions/face-expressionless.svg new file mode 100644 index 0000000..1ee25e1 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-expressionless.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-flushed.svg b/core/resources/src/main/assets/svg/emotions/face-flushed.svg new file mode 100644 index 0000000..377750c --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-flushed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-in-clouds.svg b/core/resources/src/main/assets/svg/emotions/face-in-clouds.svg new file mode 100644 index 0000000..cb41069 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-in-clouds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-loudly-crying.svg b/core/resources/src/main/assets/svg/emotions/face-loudly-crying.svg new file mode 100644 index 0000000..14ef5ad --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-loudly-crying.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-monkey-hear-no-evil.svg b/core/resources/src/main/assets/svg/emotions/face-monkey-hear-no-evil.svg new file mode 100644 index 0000000..e51bc73 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-monkey-hear-no-evil.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-monkey-see-no-evil.svg b/core/resources/src/main/assets/svg/emotions/face-monkey-see-no-evil.svg new file mode 100644 index 0000000..f10b3d5 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-monkey-see-no-evil.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-sad-but-relieved.svg b/core/resources/src/main/assets/svg/emotions/face-sad-but-relieved.svg new file mode 100644 index 0000000..84ba9f1 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-sad-but-relieved.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-savoring-food.svg b/core/resources/src/main/assets/svg/emotions/face-savoring-food.svg new file mode 100644 index 0000000..5cb5977 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-savoring-food.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-screaming-in-fear.svg b/core/resources/src/main/assets/svg/emotions/face-screaming-in-fear.svg new file mode 100644 index 0000000..667b021 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-screaming-in-fear.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-shaking-horizontally.svg b/core/resources/src/main/assets/svg/emotions/face-shaking-horizontally.svg new file mode 100644 index 0000000..f76ef54 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-shaking-horizontally.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-shaking-vertically.svg b/core/resources/src/main/assets/svg/emotions/face-shaking-vertically.svg new file mode 100644 index 0000000..602a67b --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-shaking-vertically.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-smirking.svg b/core/resources/src/main/assets/svg/emotions/face-smirking.svg new file mode 100644 index 0000000..b8b2315 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-smirking.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-with-raised-eyebrow.svg b/core/resources/src/main/assets/svg/emotions/face-with-raised-eyebrow.svg new file mode 100644 index 0000000..b1012ff --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-with-raised-eyebrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-with-spiral-eyes.svg b/core/resources/src/main/assets/svg/emotions/face-with-spiral-eyes.svg new file mode 100644 index 0000000..7ff74c4 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-with-spiral-eyes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-with-steam-from-nose.svg b/core/resources/src/main/assets/svg/emotions/face-with-steam-from-nose.svg new file mode 100644 index 0000000..048c6b3 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-with-steam-from-nose.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-with-sunglasses.svg b/core/resources/src/main/assets/svg/emotions/face-with-sunglasses.svg new file mode 100644 index 0000000..8c0d9eb --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-with-sunglasses.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-with-symbols-on-mouth.svg b/core/resources/src/main/assets/svg/emotions/face-with-symbols-on-mouth.svg new file mode 100644 index 0000000..936baa2 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-with-symbols-on-mouth.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/face-with-tongue.svg b/core/resources/src/main/assets/svg/emotions/face-with-tongue.svg new file mode 100644 index 0000000..5bc7ac2 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/face-with-tongue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/ghost.svg b/core/resources/src/main/assets/svg/emotions/ghost.svg new file mode 100644 index 0000000..c836475 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/ghost.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/goblin.svg b/core/resources/src/main/assets/svg/emotions/goblin.svg new file mode 100644 index 0000000..5950097 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/goblin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart-aletter.svg b/core/resources/src/main/assets/svg/emotions/heart-aletter.svg new file mode 100644 index 0000000..ad75afa --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart-aletter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart-growing.svg b/core/resources/src/main/assets/svg/emotions/heart-growing.svg new file mode 100644 index 0000000..bdb7d8a --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart-growing.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart.svg b/core/resources/src/main/assets/svg/emotions/heart.svg new file mode 100644 index 0000000..b4d25aa --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart_black.svg b/core/resources/src/main/assets/svg/emotions/heart_black.svg new file mode 100644 index 0000000..08b1c9d --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart_black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart_blue.svg b/core/resources/src/main/assets/svg/emotions/heart_blue.svg new file mode 100644 index 0000000..d45ce1c --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart_blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart_blue_light.svg b/core/resources/src/main/assets/svg/emotions/heart_blue_light.svg new file mode 100644 index 0000000..826f3ec --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart_blue_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart_broken.svg b/core/resources/src/main/assets/svg/emotions/heart_broken.svg new file mode 100644 index 0000000..8bfc3cf --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart_broken.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart_brown.svg b/core/resources/src/main/assets/svg/emotions/heart_brown.svg new file mode 100644 index 0000000..cea287f --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart_brown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart_fire.svg b/core/resources/src/main/assets/svg/emotions/heart_fire.svg new file mode 100644 index 0000000..5763463 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart_fire.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart_gauze.svg b/core/resources/src/main/assets/svg/emotions/heart_gauze.svg new file mode 100644 index 0000000..ac24f80 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart_gauze.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart_green.svg b/core/resources/src/main/assets/svg/emotions/heart_green.svg new file mode 100644 index 0000000..bc29aaf --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart_green.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart_grey.svg b/core/resources/src/main/assets/svg/emotions/heart_grey.svg new file mode 100644 index 0000000..9264a99 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart_grey.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart_orange.svg b/core/resources/src/main/assets/svg/emotions/heart_orange.svg new file mode 100644 index 0000000..8c379fe --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart_orange.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart_pink.svg b/core/resources/src/main/assets/svg/emotions/heart_pink.svg new file mode 100644 index 0000000..c04c61d --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart_pink.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart_purple.svg b/core/resources/src/main/assets/svg/emotions/heart_purple.svg new file mode 100644 index 0000000..011d651 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart_purple.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart_white.svg b/core/resources/src/main/assets/svg/emotions/heart_white.svg new file mode 100644 index 0000000..c1894c0 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart_white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/heart_yellow.svg b/core/resources/src/main/assets/svg/emotions/heart_yellow.svg new file mode 100644 index 0000000..98008a3 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/heart_yellow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/hundred-points.svg b/core/resources/src/main/assets/svg/emotions/hundred-points.svg new file mode 100644 index 0000000..0a7df02 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/hundred-points.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/kiss-mark.svg b/core/resources/src/main/assets/svg/emotions/kiss-mark.svg new file mode 100644 index 0000000..9cd7c6d --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/kiss-mark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/moai.svg b/core/resources/src/main/assets/svg/emotions/moai.svg new file mode 100644 index 0000000..0e0833b --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/moai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/moon_a.svg b/core/resources/src/main/assets/svg/emotions/moon_a.svg new file mode 100644 index 0000000..be9bbdc --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/moon_a.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/moon_b.svg b/core/resources/src/main/assets/svg/emotions/moon_b.svg new file mode 100644 index 0000000..ad14c80 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/moon_b.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/pile-of-poo.svg b/core/resources/src/main/assets/svg/emotions/pile-of-poo.svg new file mode 100644 index 0000000..c7002be --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/pile-of-poo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/skull.svg b/core/resources/src/main/assets/svg/emotions/skull.svg new file mode 100644 index 0000000..080eec7 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/skull.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/ssparty.svg b/core/resources/src/main/assets/svg/emotions/ssparty.svg new file mode 100644 index 0000000..2ab4585 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/ssparty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/zanger.svg b/core/resources/src/main/assets/svg/emotions/zanger.svg new file mode 100644 index 0000000..4338adb --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/zanger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/zap.svg b/core/resources/src/main/assets/svg/emotions/zap.svg new file mode 100644 index 0000000..f6fec15 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/zap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/zbang.svg b/core/resources/src/main/assets/svg/emotions/zbang.svg new file mode 100644 index 0000000..f94fb30 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/zbang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/zdizzy.svg b/core/resources/src/main/assets/svg/emotions/zdizzy.svg new file mode 100644 index 0000000..c3d98d0 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/zdizzy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/zdropplets.svg b/core/resources/src/main/assets/svg/emotions/zdropplets.svg new file mode 100644 index 0000000..cb23e27 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/zdropplets.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/zfire.svg b/core/resources/src/main/assets/svg/emotions/zfire.svg new file mode 100644 index 0000000..3949847 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/zfire.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/emotions/zzz.svg b/core/resources/src/main/assets/svg/emotions/zzz.svg new file mode 100644 index 0000000..7b98b05 --- /dev/null +++ b/core/resources/src/main/assets/svg/emotions/zzz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/1st-place-medal.svg b/core/resources/src/main/assets/svg/events/1st-place-medal.svg new file mode 100644 index 0000000..3e1a1fd --- /dev/null +++ b/core/resources/src/main/assets/svg/events/1st-place-medal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/2nd-place-medal.svg b/core/resources/src/main/assets/svg/events/2nd-place-medal.svg new file mode 100644 index 0000000..ea44ea6 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/2nd-place-medal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/3rd-place-medal.svg b/core/resources/src/main/assets/svg/events/3rd-place-medal.svg new file mode 100644 index 0000000..914bc4c --- /dev/null +++ b/core/resources/src/main/assets/svg/events/3rd-place-medal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/admission-tickets.svg b/core/resources/src/main/assets/svg/events/admission-tickets.svg new file mode 100644 index 0000000..5bfb837 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/admission-tickets.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/alien_monster.svg b/core/resources/src/main/assets/svg/events/alien_monster.svg new file mode 100644 index 0000000..a4c0d60 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/alien_monster.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/american-football.svg b/core/resources/src/main/assets/svg/events/american-football.svg new file mode 100644 index 0000000..fc9b434 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/american-football.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/ball.svg b/core/resources/src/main/assets/svg/events/ball.svg new file mode 100644 index 0000000..6771d96 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/ball.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/balloon.svg b/core/resources/src/main/assets/svg/events/balloon.svg new file mode 100644 index 0000000..db1c93f --- /dev/null +++ b/core/resources/src/main/assets/svg/events/balloon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/bowling.svg b/core/resources/src/main/assets/svg/events/bowling.svg new file mode 100644 index 0000000..a1a0187 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/bowling.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/boxing-glove.svg b/core/resources/src/main/assets/svg/events/boxing-glove.svg new file mode 100644 index 0000000..5b83d4a --- /dev/null +++ b/core/resources/src/main/assets/svg/events/boxing-glove.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/bullseye.svg b/core/resources/src/main/assets/svg/events/bullseye.svg new file mode 100644 index 0000000..8f93f59 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/bullseye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/camera.svg b/core/resources/src/main/assets/svg/events/camera.svg new file mode 100644 index 0000000..a3c4ec9 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/camera.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/chess-pawn.svg b/core/resources/src/main/assets/svg/events/chess-pawn.svg new file mode 100644 index 0000000..a1e70c2 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/chess-pawn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/christmas-tree.svg b/core/resources/src/main/assets/svg/events/christmas-tree.svg new file mode 100644 index 0000000..b24e93b --- /dev/null +++ b/core/resources/src/main/assets/svg/events/christmas-tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/curling-stone.svg b/core/resources/src/main/assets/svg/events/curling-stone.svg new file mode 100644 index 0000000..b9a69b1 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/curling-stone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/disco.svg b/core/resources/src/main/assets/svg/events/disco.svg new file mode 100644 index 0000000..c5c01d4 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/disco.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/firecracker.svg b/core/resources/src/main/assets/svg/events/firecracker.svg new file mode 100644 index 0000000..f86b8cd --- /dev/null +++ b/core/resources/src/main/assets/svg/events/firecracker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/fireworks.svg b/core/resources/src/main/assets/svg/events/fireworks.svg new file mode 100644 index 0000000..4537d05 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/fireworks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/gift.svg b/core/resources/src/main/assets/svg/events/gift.svg new file mode 100644 index 0000000..0754da6 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/gift.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/jack-o-lantern.svg b/core/resources/src/main/assets/svg/events/jack-o-lantern.svg new file mode 100644 index 0000000..ab5e42d --- /dev/null +++ b/core/resources/src/main/assets/svg/events/jack-o-lantern.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/joystick.svg b/core/resources/src/main/assets/svg/events/joystick.svg new file mode 100644 index 0000000..9163c11 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/joystick.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/palette.svg b/core/resources/src/main/assets/svg/events/palette.svg new file mode 100644 index 0000000..cb2e7f7 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/palette.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/performing-arts.svg b/core/resources/src/main/assets/svg/events/performing-arts.svg new file mode 100644 index 0000000..17d1981 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/performing-arts.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/picture.svg b/core/resources/src/main/assets/svg/events/picture.svg new file mode 100644 index 0000000..3131f47 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/picture.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/pool-8-ball.svg b/core/resources/src/main/assets/svg/events/pool-8-ball.svg new file mode 100644 index 0000000..7d545e2 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/pool-8-ball.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/puzzle.svg b/core/resources/src/main/assets/svg/events/puzzle.svg new file mode 100644 index 0000000..6405bbb --- /dev/null +++ b/core/resources/src/main/assets/svg/events/puzzle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/radio.svg b/core/resources/src/main/assets/svg/events/radio.svg new file mode 100644 index 0000000..5567def --- /dev/null +++ b/core/resources/src/main/assets/svg/events/radio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/red-envelope.svg b/core/resources/src/main/assets/svg/events/red-envelope.svg new file mode 100644 index 0000000..a872662 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/red-envelope.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/rugby-football.svg b/core/resources/src/main/assets/svg/events/rugby-football.svg new file mode 100644 index 0000000..b229fcf --- /dev/null +++ b/core/resources/src/main/assets/svg/events/rugby-football.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/slots.svg b/core/resources/src/main/assets/svg/events/slots.svg new file mode 100644 index 0000000..7c9e583 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/slots.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/softball.svg b/core/resources/src/main/assets/svg/events/softball.svg new file mode 100644 index 0000000..a301306 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/softball.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/sparkler.svg b/core/resources/src/main/assets/svg/events/sparkler.svg new file mode 100644 index 0000000..920a6b7 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/sparkler.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/sports-medal.svg b/core/resources/src/main/assets/svg/events/sports-medal.svg new file mode 100644 index 0000000..2d4de13 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/sports-medal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/ticket.svg b/core/resources/src/main/assets/svg/events/ticket.svg new file mode 100644 index 0000000..d0778ea --- /dev/null +++ b/core/resources/src/main/assets/svg/events/ticket.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/trophy.svg b/core/resources/src/main/assets/svg/events/trophy.svg new file mode 100644 index 0000000..f8d799e --- /dev/null +++ b/core/resources/src/main/assets/svg/events/trophy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/video-game.svg b/core/resources/src/main/assets/svg/events/video-game.svg new file mode 100644 index 0000000..4b089e2 --- /dev/null +++ b/core/resources/src/main/assets/svg/events/video-game.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/volleyball.svg b/core/resources/src/main/assets/svg/events/volleyball.svg new file mode 100644 index 0000000..26038ac --- /dev/null +++ b/core/resources/src/main/assets/svg/events/volleyball.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/events/water-pistol.svg b/core/resources/src/main/assets/svg/events/water-pistol.svg new file mode 100644 index 0000000..1da363e --- /dev/null +++ b/core/resources/src/main/assets/svg/events/water-pistol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/apple.svg b/core/resources/src/main/assets/svg/food/apple.svg new file mode 100644 index 0000000..870fb04 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/apple.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/avocado.svg b/core/resources/src/main/assets/svg/food/avocado.svg new file mode 100644 index 0000000..cbd2517 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/avocado.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/bacon.svg b/core/resources/src/main/assets/svg/food/bacon.svg new file mode 100644 index 0000000..9f4850d --- /dev/null +++ b/core/resources/src/main/assets/svg/food/bacon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/bagel.svg b/core/resources/src/main/assets/svg/food/bagel.svg new file mode 100644 index 0000000..683b7ec --- /dev/null +++ b/core/resources/src/main/assets/svg/food/bagel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/baguette-bread.svg b/core/resources/src/main/assets/svg/food/baguette-bread.svg new file mode 100644 index 0000000..51b6c28 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/baguette-bread.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/banana.svg b/core/resources/src/main/assets/svg/food/banana.svg new file mode 100644 index 0000000..72269c9 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/banana.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/beans.svg b/core/resources/src/main/assets/svg/food/beans.svg new file mode 100644 index 0000000..4818d81 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/beans.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/beef.svg b/core/resources/src/main/assets/svg/food/beef.svg new file mode 100644 index 0000000..7de402b --- /dev/null +++ b/core/resources/src/main/assets/svg/food/beef.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/beer.svg b/core/resources/src/main/assets/svg/food/beer.svg new file mode 100644 index 0000000..6b3202d --- /dev/null +++ b/core/resources/src/main/assets/svg/food/beer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/bell-pepper.svg b/core/resources/src/main/assets/svg/food/bell-pepper.svg new file mode 100644 index 0000000..33f631c --- /dev/null +++ b/core/resources/src/main/assets/svg/food/bell-pepper.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/bento-box.svg b/core/resources/src/main/assets/svg/food/bento-box.svg new file mode 100644 index 0000000..b8e106b --- /dev/null +++ b/core/resources/src/main/assets/svg/food/bento-box.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/beverage-box.svg b/core/resources/src/main/assets/svg/food/beverage-box.svg new file mode 100644 index 0000000..42f04b7 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/beverage-box.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/birthday-cake.svg b/core/resources/src/main/assets/svg/food/birthday-cake.svg new file mode 100644 index 0000000..8d4958f --- /dev/null +++ b/core/resources/src/main/assets/svg/food/birthday-cake.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/blueberries.svg b/core/resources/src/main/assets/svg/food/blueberries.svg new file mode 100644 index 0000000..c4fa62d --- /dev/null +++ b/core/resources/src/main/assets/svg/food/blueberries.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/bottle.svg b/core/resources/src/main/assets/svg/food/bottle.svg new file mode 100644 index 0000000..dbaf219 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/bottle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/bread.svg b/core/resources/src/main/assets/svg/food/bread.svg new file mode 100644 index 0000000..aac5a49 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/bread.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/broccoli.svg b/core/resources/src/main/assets/svg/food/broccoli.svg new file mode 100644 index 0000000..f54c21d --- /dev/null +++ b/core/resources/src/main/assets/svg/food/broccoli.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/brown-mushroom.svg b/core/resources/src/main/assets/svg/food/brown-mushroom.svg new file mode 100644 index 0000000..cf4042e --- /dev/null +++ b/core/resources/src/main/assets/svg/food/brown-mushroom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/bubble-tea.svg b/core/resources/src/main/assets/svg/food/bubble-tea.svg new file mode 100644 index 0000000..848afa2 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/bubble-tea.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/burrito.svg b/core/resources/src/main/assets/svg/food/burrito.svg new file mode 100644 index 0000000..3ac3836 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/burrito.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/candy.svg b/core/resources/src/main/assets/svg/food/candy.svg new file mode 100644 index 0000000..e371b46 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/candy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/carrot.svg b/core/resources/src/main/assets/svg/food/carrot.svg new file mode 100644 index 0000000..d8402a1 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/carrot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/cheese-wedge.svg b/core/resources/src/main/assets/svg/food/cheese-wedge.svg new file mode 100644 index 0000000..f553a9f --- /dev/null +++ b/core/resources/src/main/assets/svg/food/cheese-wedge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/cherries.svg b/core/resources/src/main/assets/svg/food/cherries.svg new file mode 100644 index 0000000..c9ac4cd --- /dev/null +++ b/core/resources/src/main/assets/svg/food/cherries.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/chestnut.svg b/core/resources/src/main/assets/svg/food/chestnut.svg new file mode 100644 index 0000000..9bc5e06 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/chestnut.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/chocolate-bar.svg b/core/resources/src/main/assets/svg/food/chocolate-bar.svg new file mode 100644 index 0000000..43babfb --- /dev/null +++ b/core/resources/src/main/assets/svg/food/chocolate-bar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/clinking1.svg b/core/resources/src/main/assets/svg/food/clinking1.svg new file mode 100644 index 0000000..aad0e72 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/clinking1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/clinking2.svg b/core/resources/src/main/assets/svg/food/clinking2.svg new file mode 100644 index 0000000..cc7fb51 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/clinking2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/cocktail.svg b/core/resources/src/main/assets/svg/food/cocktail.svg new file mode 100644 index 0000000..7b1cbc0 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/cocktail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/coconut.svg b/core/resources/src/main/assets/svg/food/coconut.svg new file mode 100644 index 0000000..d412fd1 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/coconut.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/cookie.svg b/core/resources/src/main/assets/svg/food/cookie.svg new file mode 100644 index 0000000..7190b1c --- /dev/null +++ b/core/resources/src/main/assets/svg/food/cookie.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/crab.svg b/core/resources/src/main/assets/svg/food/crab.svg new file mode 100644 index 0000000..d285b65 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/crab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/croissant.svg b/core/resources/src/main/assets/svg/food/croissant.svg new file mode 100644 index 0000000..24eb36d --- /dev/null +++ b/core/resources/src/main/assets/svg/food/croissant.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/cucumber.svg b/core/resources/src/main/assets/svg/food/cucumber.svg new file mode 100644 index 0000000..6ee3087 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/cucumber.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/doughnut.svg b/core/resources/src/main/assets/svg/food/doughnut.svg new file mode 100644 index 0000000..97d09c0 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/doughnut.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/ear-of-corn.svg b/core/resources/src/main/assets/svg/food/ear-of-corn.svg new file mode 100644 index 0000000..92728e5 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/ear-of-corn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/egg.svg b/core/resources/src/main/assets/svg/food/egg.svg new file mode 100644 index 0000000..2fa01ec --- /dev/null +++ b/core/resources/src/main/assets/svg/food/egg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/eggplant.svg b/core/resources/src/main/assets/svg/food/eggplant.svg new file mode 100644 index 0000000..b7f145b --- /dev/null +++ b/core/resources/src/main/assets/svg/food/eggplant.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/falafel.svg b/core/resources/src/main/assets/svg/food/falafel.svg new file mode 100644 index 0000000..d9a5e11 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/falafel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/fish-cake-with-swirl.svg b/core/resources/src/main/assets/svg/food/fish-cake-with-swirl.svg new file mode 100644 index 0000000..7db26de --- /dev/null +++ b/core/resources/src/main/assets/svg/food/fish-cake-with-swirl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/fortune-cookie.svg b/core/resources/src/main/assets/svg/food/fortune-cookie.svg new file mode 100644 index 0000000..ac221a4 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/fortune-cookie.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/french-fries.svg b/core/resources/src/main/assets/svg/food/french-fries.svg new file mode 100644 index 0000000..71b54c4 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/french-fries.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/fried-shrimp.svg b/core/resources/src/main/assets/svg/food/fried-shrimp.svg new file mode 100644 index 0000000..3c0d01d --- /dev/null +++ b/core/resources/src/main/assets/svg/food/fried-shrimp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/garlic.svg b/core/resources/src/main/assets/svg/food/garlic.svg new file mode 100644 index 0000000..7e503fd --- /dev/null +++ b/core/resources/src/main/assets/svg/food/garlic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/glass.svg b/core/resources/src/main/assets/svg/food/glass.svg new file mode 100644 index 0000000..b5db573 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/glass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/grapes.svg b/core/resources/src/main/assets/svg/food/grapes.svg new file mode 100644 index 0000000..df49dd0 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/grapes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/green-salad.svg b/core/resources/src/main/assets/svg/food/green-salad.svg new file mode 100644 index 0000000..c5639eb --- /dev/null +++ b/core/resources/src/main/assets/svg/food/green-salad.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/hamburger.svg b/core/resources/src/main/assets/svg/food/hamburger.svg new file mode 100644 index 0000000..0f12c8e --- /dev/null +++ b/core/resources/src/main/assets/svg/food/hamburger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/honey-pot.svg b/core/resources/src/main/assets/svg/food/honey-pot.svg new file mode 100644 index 0000000..235db20 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/honey-pot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/hotdog.svg b/core/resources/src/main/assets/svg/food/hotdog.svg new file mode 100644 index 0000000..4712d08 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/hotdog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/ice.svg b/core/resources/src/main/assets/svg/food/ice.svg new file mode 100644 index 0000000..24ec29d --- /dev/null +++ b/core/resources/src/main/assets/svg/food/ice.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/kiwi.svg b/core/resources/src/main/assets/svg/food/kiwi.svg new file mode 100644 index 0000000..1b50864 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/kiwi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/leafy-green.svg b/core/resources/src/main/assets/svg/food/leafy-green.svg new file mode 100644 index 0000000..3eaff0b --- /dev/null +++ b/core/resources/src/main/assets/svg/food/leafy-green.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/lemon.svg b/core/resources/src/main/assets/svg/food/lemon.svg new file mode 100644 index 0000000..0853927 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/lemon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/lime.svg b/core/resources/src/main/assets/svg/food/lime.svg new file mode 100644 index 0000000..0294276 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/lime.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/lobster.svg b/core/resources/src/main/assets/svg/food/lobster.svg new file mode 100644 index 0000000..4666656 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/lobster.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/mango.svg b/core/resources/src/main/assets/svg/food/mango.svg new file mode 100644 index 0000000..972a72c --- /dev/null +++ b/core/resources/src/main/assets/svg/food/mango.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/meat-on-bone.svg b/core/resources/src/main/assets/svg/food/meat-on-bone.svg new file mode 100644 index 0000000..f0c1feb --- /dev/null +++ b/core/resources/src/main/assets/svg/food/meat-on-bone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/onion.svg b/core/resources/src/main/assets/svg/food/onion.svg new file mode 100644 index 0000000..8fe1ac6 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/onion.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/pancakes.svg b/core/resources/src/main/assets/svg/food/pancakes.svg new file mode 100644 index 0000000..1d7e27f --- /dev/null +++ b/core/resources/src/main/assets/svg/food/pancakes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/pea-pod.svg b/core/resources/src/main/assets/svg/food/pea-pod.svg new file mode 100644 index 0000000..edfcc54 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/pea-pod.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/peach.svg b/core/resources/src/main/assets/svg/food/peach.svg new file mode 100644 index 0000000..8d965ea --- /dev/null +++ b/core/resources/src/main/assets/svg/food/peach.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/peanuts.svg b/core/resources/src/main/assets/svg/food/peanuts.svg new file mode 100644 index 0000000..40ed30f --- /dev/null +++ b/core/resources/src/main/assets/svg/food/peanuts.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/pepper.svg b/core/resources/src/main/assets/svg/food/pepper.svg new file mode 100644 index 0000000..08ef4de --- /dev/null +++ b/core/resources/src/main/assets/svg/food/pepper.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/pie.svg b/core/resources/src/main/assets/svg/food/pie.svg new file mode 100644 index 0000000..854013a --- /dev/null +++ b/core/resources/src/main/assets/svg/food/pie.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/pineapple.svg b/core/resources/src/main/assets/svg/food/pineapple.svg new file mode 100644 index 0000000..54b9f68 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/pineapple.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/pizza.svg b/core/resources/src/main/assets/svg/food/pizza.svg new file mode 100644 index 0000000..4d68162 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/pizza.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/popcorn.svg b/core/resources/src/main/assets/svg/food/popcorn.svg new file mode 100644 index 0000000..f5b18da --- /dev/null +++ b/core/resources/src/main/assets/svg/food/popcorn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/pot-of-food.svg b/core/resources/src/main/assets/svg/food/pot-of-food.svg new file mode 100644 index 0000000..0dcdef1 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/pot-of-food.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/potato.svg b/core/resources/src/main/assets/svg/food/potato.svg new file mode 100644 index 0000000..00f3809 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/potato.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/poultry-leg.svg b/core/resources/src/main/assets/svg/food/poultry-leg.svg new file mode 100644 index 0000000..6a760a3 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/poultry-leg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/rice-cracker.svg b/core/resources/src/main/assets/svg/food/rice-cracker.svg new file mode 100644 index 0000000..1aba646 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/rice-cracker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/rice.svg b/core/resources/src/main/assets/svg/food/rice.svg new file mode 100644 index 0000000..5deedaf --- /dev/null +++ b/core/resources/src/main/assets/svg/food/rice.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/roasted-sweet-potato.svg b/core/resources/src/main/assets/svg/food/roasted-sweet-potato.svg new file mode 100644 index 0000000..70f35f9 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/roasted-sweet-potato.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/salt.svg b/core/resources/src/main/assets/svg/food/salt.svg new file mode 100644 index 0000000..60e0e54 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/salt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/sandwich.svg b/core/resources/src/main/assets/svg/food/sandwich.svg new file mode 100644 index 0000000..f08fed7 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/sandwich.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/shaved-ice.svg b/core/resources/src/main/assets/svg/food/shaved-ice.svg new file mode 100644 index 0000000..96ecd9d --- /dev/null +++ b/core/resources/src/main/assets/svg/food/shaved-ice.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/shrimp.svg b/core/resources/src/main/assets/svg/food/shrimp.svg new file mode 100644 index 0000000..348cb06 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/shrimp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/squid.svg b/core/resources/src/main/assets/svg/food/squid.svg new file mode 100644 index 0000000..58b4435 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/squid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/strawberry.svg b/core/resources/src/main/assets/svg/food/strawberry.svg new file mode 100644 index 0000000..35572c4 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/strawberry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/sushi.svg b/core/resources/src/main/assets/svg/food/sushi.svg new file mode 100644 index 0000000..006d9c0 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/sushi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/taco.svg b/core/resources/src/main/assets/svg/food/taco.svg new file mode 100644 index 0000000..3855440 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/taco.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/takeout-box.svg b/core/resources/src/main/assets/svg/food/takeout-box.svg new file mode 100644 index 0000000..d0e0d81 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/takeout-box.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/tamale.svg b/core/resources/src/main/assets/svg/food/tamale.svg new file mode 100644 index 0000000..ff37e1b --- /dev/null +++ b/core/resources/src/main/assets/svg/food/tamale.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/tangerine.svg b/core/resources/src/main/assets/svg/food/tangerine.svg new file mode 100644 index 0000000..273e91c --- /dev/null +++ b/core/resources/src/main/assets/svg/food/tangerine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/tomato.svg b/core/resources/src/main/assets/svg/food/tomato.svg new file mode 100644 index 0000000..6a695f2 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/tomato.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/tropical-drink.svg b/core/resources/src/main/assets/svg/food/tropical-drink.svg new file mode 100644 index 0000000..f43261d --- /dev/null +++ b/core/resources/src/main/assets/svg/food/tropical-drink.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/watermelon.svg b/core/resources/src/main/assets/svg/food/watermelon.svg new file mode 100644 index 0000000..ba71f17 --- /dev/null +++ b/core/resources/src/main/assets/svg/food/watermelon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/food/wine.svg b/core/resources/src/main/assets/svg/food/wine.svg new file mode 100644 index 0000000..c4099cb --- /dev/null +++ b/core/resources/src/main/assets/svg/food/wine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/baby-chick.svg b/core/resources/src/main/assets/svg/nature/baby-chick.svg new file mode 100644 index 0000000..6be6607 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/baby-chick.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/bear.svg b/core/resources/src/main/assets/svg/nature/bear.svg new file mode 100644 index 0000000..41e56a7 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/bear.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/beaver.svg b/core/resources/src/main/assets/svg/nature/beaver.svg new file mode 100644 index 0000000..7926de9 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/beaver.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/black-cat.svg b/core/resources/src/main/assets/svg/nature/black-cat.svg new file mode 100644 index 0000000..d78a3ce --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/black-cat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/blossom.svg b/core/resources/src/main/assets/svg/nature/blossom.svg new file mode 100644 index 0000000..3c5d462 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/blossom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/blowfish.svg b/core/resources/src/main/assets/svg/nature/blowfish.svg new file mode 100644 index 0000000..c00bbc9 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/blowfish.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/bone.svg b/core/resources/src/main/assets/svg/nature/bone.svg new file mode 100644 index 0000000..6b33169 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/bone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/bouquet.svg b/core/resources/src/main/assets/svg/nature/bouquet.svg new file mode 100644 index 0000000..09736fa --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/bouquet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/cactus.svg b/core/resources/src/main/assets/svg/nature/cactus.svg new file mode 100644 index 0000000..4fc2d09 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/cactus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/cherry-blossom.svg b/core/resources/src/main/assets/svg/nature/cherry-blossom.svg new file mode 100644 index 0000000..4b42be6 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/cherry-blossom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/cloud.svg b/core/resources/src/main/assets/svg/nature/cloud.svg new file mode 100644 index 0000000..65d6660 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/cloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/clover.svg b/core/resources/src/main/assets/svg/nature/clover.svg new file mode 100644 index 0000000..777a8e6 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/clover.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/comet.svg b/core/resources/src/main/assets/svg/nature/comet.svg new file mode 100644 index 0000000..2d5426c --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/comet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/crocodile.svg b/core/resources/src/main/assets/svg/nature/crocodile.svg new file mode 100644 index 0000000..5a99525 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/crocodile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/dolphin.svg b/core/resources/src/main/assets/svg/nature/dolphin.svg new file mode 100644 index 0000000..a95a34d --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/dolphin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/duck.svg b/core/resources/src/main/assets/svg/nature/duck.svg new file mode 100644 index 0000000..4a28a08 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/duck.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/evergreen-tree.svg b/core/resources/src/main/assets/svg/nature/evergreen-tree.svg new file mode 100644 index 0000000..ebfbf1d --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/evergreen-tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/face-rabbit.svg b/core/resources/src/main/assets/svg/nature/face-rabbit.svg new file mode 100644 index 0000000..d05b2df --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/face-rabbit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/face-sun-with.svg b/core/resources/src/main/assets/svg/nature/face-sun-with.svg new file mode 100644 index 0000000..bd44ca7 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/face-sun-with.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/fish.svg b/core/resources/src/main/assets/svg/nature/fish.svg new file mode 100644 index 0000000..4655b7e --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/fish.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/fox.svg b/core/resources/src/main/assets/svg/nature/fox.svg new file mode 100644 index 0000000..f206a7b --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/fox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/frog.svg b/core/resources/src/main/assets/svg/nature/frog.svg new file mode 100644 index 0000000..e6b593f --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/frog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/goose.svg b/core/resources/src/main/assets/svg/nature/goose.svg new file mode 100644 index 0000000..85eda4d --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/goose.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/herb.svg b/core/resources/src/main/assets/svg/nature/herb.svg new file mode 100644 index 0000000..da4326c --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/herb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/hyacinth.svg b/core/resources/src/main/assets/svg/nature/hyacinth.svg new file mode 100644 index 0000000..91047b4 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/hyacinth.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/jellyfish.svg b/core/resources/src/main/assets/svg/nature/jellyfish.svg new file mode 100644 index 0000000..d764a55 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/jellyfish.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/lady-beetle.svg b/core/resources/src/main/assets/svg/nature/lady-beetle.svg new file mode 100644 index 0000000..83ddcb4 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/lady-beetle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/leaf-fluttering-in-wind.svg b/core/resources/src/main/assets/svg/nature/leaf-fluttering-in-wind.svg new file mode 100644 index 0000000..271254e --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/leaf-fluttering-in-wind.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/leafless-tree.svg b/core/resources/src/main/assets/svg/nature/leafless-tree.svg new file mode 100644 index 0000000..1983926 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/leafless-tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/lotus.svg b/core/resources/src/main/assets/svg/nature/lotus.svg new file mode 100644 index 0000000..8b88421 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/lotus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/mammoth.svg b/core/resources/src/main/assets/svg/nature/mammoth.svg new file mode 100644 index 0000000..5b7f242 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/mammoth.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/maple-leaf.svg b/core/resources/src/main/assets/svg/nature/maple-leaf.svg new file mode 100644 index 0000000..6168afd --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/maple-leaf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/microbe.svg b/core/resources/src/main/assets/svg/nature/microbe.svg new file mode 100644 index 0000000..8b1c4f7 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/microbe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/milky-way.svg b/core/resources/src/main/assets/svg/nature/milky-way.svg new file mode 100644 index 0000000..4a15c78 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/milky-way.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/mount-fuji.svg b/core/resources/src/main/assets/svg/nature/mount-fuji.svg new file mode 100644 index 0000000..7e12815 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/mount-fuji.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/mountain.svg b/core/resources/src/main/assets/svg/nature/mountain.svg new file mode 100644 index 0000000..41547f4 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/mountain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/mushroom.svg b/core/resources/src/main/assets/svg/nature/mushroom.svg new file mode 100644 index 0000000..38ccc05 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/mushroom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/orangutan.svg b/core/resources/src/main/assets/svg/nature/orangutan.svg new file mode 100644 index 0000000..ece944c --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/orangutan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/palm-tree.svg b/core/resources/src/main/assets/svg/nature/palm-tree.svg new file mode 100644 index 0000000..10c0716 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/palm-tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/park.svg b/core/resources/src/main/assets/svg/nature/park.svg new file mode 100644 index 0000000..c346253 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/park.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/parrot.svg b/core/resources/src/main/assets/svg/nature/parrot.svg new file mode 100644 index 0000000..6d6e348 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/parrot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/paw-prints.svg b/core/resources/src/main/assets/svg/nature/paw-prints.svg new file mode 100644 index 0000000..9d6adbc --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/paw-prints.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/phoenix.svg b/core/resources/src/main/assets/svg/nature/phoenix.svg new file mode 100644 index 0000000..d7a07b5 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/phoenix.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/pig-face.svg b/core/resources/src/main/assets/svg/nature/pig-face.svg new file mode 100644 index 0000000..4a650e8 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/pig-face.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/pig-nose.svg b/core/resources/src/main/assets/svg/nature/pig-nose.svg new file mode 100644 index 0000000..40a68e1 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/pig-nose.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/polar-bear.svg b/core/resources/src/main/assets/svg/nature/polar-bear.svg new file mode 100644 index 0000000..14d85c6 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/polar-bear.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/potted-plant.svg b/core/resources/src/main/assets/svg/nature/potted-plant.svg new file mode 100644 index 0000000..78e0d1a --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/potted-plant.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/rainbow.svg b/core/resources/src/main/assets/svg/nature/rainbow.svg new file mode 100644 index 0000000..6b957d7 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/rainbow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/ram.svg b/core/resources/src/main/assets/svg/nature/ram.svg new file mode 100644 index 0000000..cc69d6e --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/ram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/rock.svg b/core/resources/src/main/assets/svg/nature/rock.svg new file mode 100644 index 0000000..0fed58e --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/rock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/rose.svg b/core/resources/src/main/assets/svg/nature/rose.svg new file mode 100644 index 0000000..f3970d2 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/rose.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/rosette.svg b/core/resources/src/main/assets/svg/nature/rosette.svg new file mode 100644 index 0000000..4beb675 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/rosette.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/sauropod.svg b/core/resources/src/main/assets/svg/nature/sauropod.svg new file mode 100644 index 0000000..2eb9a0b --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/sauropod.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/shark.svg b/core/resources/src/main/assets/svg/nature/shark.svg new file mode 100644 index 0000000..6a7c8a8 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/shark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/snake.svg b/core/resources/src/main/assets/svg/nature/snake.svg new file mode 100644 index 0000000..c5d3fc7 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/snake.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/snow-capped-mountain.svg b/core/resources/src/main/assets/svg/nature/snow-capped-mountain.svg new file mode 100644 index 0000000..d9ee80f --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/snow-capped-mountain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/snowflake.svg b/core/resources/src/main/assets/svg/nature/snowflake.svg new file mode 100644 index 0000000..8f7207b --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/snowflake.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/snowman.svg b/core/resources/src/main/assets/svg/nature/snowman.svg new file mode 100644 index 0000000..4b48d4b --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/snowman.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/star.svg b/core/resources/src/main/assets/svg/nature/star.svg new file mode 100644 index 0000000..af1188a --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/sun-behind-cloud.svg b/core/resources/src/main/assets/svg/nature/sun-behind-cloud.svg new file mode 100644 index 0000000..e1a3440 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/sun-behind-cloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/sun-behind-large-cloud.svg b/core/resources/src/main/assets/svg/nature/sun-behind-large-cloud.svg new file mode 100644 index 0000000..a31c605 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/sun-behind-large-cloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/sun-behind-rain-cloud.svg b/core/resources/src/main/assets/svg/nature/sun-behind-rain-cloud.svg new file mode 100644 index 0000000..518dd34 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/sun-behind-rain-cloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/sun-behind-small-cloud.svg b/core/resources/src/main/assets/svg/nature/sun-behind-small-cloud.svg new file mode 100644 index 0000000..0481ca4 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/sun-behind-small-cloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/sun.svg b/core/resources/src/main/assets/svg/nature/sun.svg new file mode 100644 index 0000000..c71dd6a --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/sun.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/sunflower.svg b/core/resources/src/main/assets/svg/nature/sunflower.svg new file mode 100644 index 0000000..0c367a6 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/sunflower.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/sunrise-over-mountains.svg b/core/resources/src/main/assets/svg/nature/sunrise-over-mountains.svg new file mode 100644 index 0000000..e6a0e97 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/sunrise-over-mountains.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/sunrise.svg b/core/resources/src/main/assets/svg/nature/sunrise.svg new file mode 100644 index 0000000..75c479d --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/sunrise.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/sunset.svg b/core/resources/src/main/assets/svg/nature/sunset.svg new file mode 100644 index 0000000..7d7965d --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/sunset.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/swan.svg b/core/resources/src/main/assets/svg/nature/swan.svg new file mode 100644 index 0000000..b6aaf31 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/swan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/tent.svg b/core/resources/src/main/assets/svg/nature/tent.svg new file mode 100644 index 0000000..e50df11 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/tent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/tornado.svg b/core/resources/src/main/assets/svg/nature/tornado.svg new file mode 100644 index 0000000..04682aa --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/tornado.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/tree.svg b/core/resources/src/main/assets/svg/nature/tree.svg new file mode 100644 index 0000000..ec9f1c3 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/turtle.svg b/core/resources/src/main/assets/svg/nature/turtle.svg new file mode 100644 index 0000000..1124a18 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/turtle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/umbrella-on-ground.svg b/core/resources/src/main/assets/svg/nature/umbrella-on-ground.svg new file mode 100644 index 0000000..33e93db --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/umbrella-on-ground.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/unicorn.svg b/core/resources/src/main/assets/svg/nature/unicorn.svg new file mode 100644 index 0000000..e4320e8 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/unicorn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/volcano.svg b/core/resources/src/main/assets/svg/nature/volcano.svg new file mode 100644 index 0000000..335a5d3 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/volcano.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/wave.svg b/core/resources/src/main/assets/svg/nature/wave.svg new file mode 100644 index 0000000..8f3c285 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/wave.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/wilted-flower.svg b/core/resources/src/main/assets/svg/nature/wilted-flower.svg new file mode 100644 index 0000000..b1cf5e5 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/wilted-flower.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/wolf.svg b/core/resources/src/main/assets/svg/nature/wolf.svg new file mode 100644 index 0000000..64e3656 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/wolf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/wood.svg b/core/resources/src/main/assets/svg/nature/wood.svg new file mode 100644 index 0000000..544eeaf --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/wood.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/nature/worm.svg b/core/resources/src/main/assets/svg/nature/worm.svg new file mode 100644 index 0000000..1ffabf0 --- /dev/null +++ b/core/resources/src/main/assets/svg/nature/worm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/amulet.svg b/core/resources/src/main/assets/svg/objects/amulet.svg new file mode 100644 index 0000000..7410613 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/amulet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/axe.svg b/core/resources/src/main/assets/svg/objects/axe.svg new file mode 100644 index 0000000..ee12a39 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/axe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/bandage.svg b/core/resources/src/main/assets/svg/objects/bandage.svg new file mode 100644 index 0000000..f0212be --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/bandage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/bathtub.svg b/core/resources/src/main/assets/svg/objects/bathtub.svg new file mode 100644 index 0000000..698e9f7 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/bathtub.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/battery.svg b/core/resources/src/main/assets/svg/objects/battery.svg new file mode 100644 index 0000000..9777b1f --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/battery.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/bomb.svg b/core/resources/src/main/assets/svg/objects/bomb.svg new file mode 100644 index 0000000..b08a27b --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/bomb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/brain.svg b/core/resources/src/main/assets/svg/objects/brain.svg new file mode 100644 index 0000000..dcec33f --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/brain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/buoy.svg b/core/resources/src/main/assets/svg/objects/buoy.svg new file mode 100644 index 0000000..fe23bf9 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/buoy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/calendar.svg b/core/resources/src/main/assets/svg/objects/calendar.svg new file mode 100644 index 0000000..754566f --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/calendar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/candle.svg b/core/resources/src/main/assets/svg/objects/candle.svg new file mode 100644 index 0000000..63be7d5 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/candle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/card.svg b/core/resources/src/main/assets/svg/objects/card.svg new file mode 100644 index 0000000..09a01f3 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/card.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/cigarette.svg b/core/resources/src/main/assets/svg/objects/cigarette.svg new file mode 100644 index 0000000..a7c97b2 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/cigarette.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/coffin.svg b/core/resources/src/main/assets/svg/objects/coffin.svg new file mode 100644 index 0000000..7261730 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/coffin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/computermouse.svg b/core/resources/src/main/assets/svg/objects/computermouse.svg new file mode 100644 index 0000000..78736fd --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/computermouse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/couch.svg b/core/resources/src/main/assets/svg/objects/couch.svg new file mode 100644 index 0000000..7e2d1cf --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/couch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/crown.svg b/core/resources/src/main/assets/svg/objects/crown.svg new file mode 100644 index 0000000..802f886 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/crown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/crystal_ball.svg b/core/resources/src/main/assets/svg/objects/crystal_ball.svg new file mode 100644 index 0000000..9fe7f3d --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/crystal_ball.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/diamond.svg b/core/resources/src/main/assets/svg/objects/diamond.svg new file mode 100644 index 0000000..2501879 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/diamond.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/die.svg b/core/resources/src/main/assets/svg/objects/die.svg new file mode 100644 index 0000000..16917d3 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/die.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/disk.svg b/core/resources/src/main/assets/svg/objects/disk.svg new file mode 100644 index 0000000..d6803ef --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/disk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/key.svg b/core/resources/src/main/assets/svg/objects/key.svg new file mode 100644 index 0000000..7bd355c --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/key.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/light_bulb.svg b/core/resources/src/main/assets/svg/objects/light_bulb.svg new file mode 100644 index 0000000..d4a84ad --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/light_bulb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/lipstick.svg b/core/resources/src/main/assets/svg/objects/lipstick.svg new file mode 100644 index 0000000..98152d9 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/lipstick.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/locked.svg b/core/resources/src/main/assets/svg/objects/locked.svg new file mode 100644 index 0000000..57f2d91 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/locked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/money.svg b/core/resources/src/main/assets/svg/objects/money.svg new file mode 100644 index 0000000..fd193f3 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/money.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/money_bag.svg b/core/resources/src/main/assets/svg/objects/money_bag.svg new file mode 100644 index 0000000..77159e7 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/money_bag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/oil-drum.svg b/core/resources/src/main/assets/svg/objects/oil-drum.svg new file mode 100644 index 0000000..c0b5142 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/oil-drum.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/package.svg b/core/resources/src/main/assets/svg/objects/package.svg new file mode 100644 index 0000000..394fea8 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/package.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/paperclip.svg b/core/resources/src/main/assets/svg/objects/paperclip.svg new file mode 100644 index 0000000..bcc9abf --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/paperclip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/phone.svg b/core/resources/src/main/assets/svg/objects/phone.svg new file mode 100644 index 0000000..5e7a58a --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/pill.svg b/core/resources/src/main/assets/svg/objects/pill.svg new file mode 100644 index 0000000..5646678 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/pill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/pole.svg b/core/resources/src/main/assets/svg/objects/pole.svg new file mode 100644 index 0000000..faf2046 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/pole.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/pushpin.svg b/core/resources/src/main/assets/svg/objects/pushpin.svg new file mode 100644 index 0000000..a763f42 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/pushpin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/ring.svg b/core/resources/src/main/assets/svg/objects/ring.svg new file mode 100644 index 0000000..1d3bef3 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/ring.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/ruler.svg b/core/resources/src/main/assets/svg/objects/ruler.svg new file mode 100644 index 0000000..af692c5 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/ruler.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/shoe.svg b/core/resources/src/main/assets/svg/objects/shoe.svg new file mode 100644 index 0000000..cc04de6 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/shoe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/siren.svg b/core/resources/src/main/assets/svg/objects/siren.svg new file mode 100644 index 0000000..f9b4e4a --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/siren.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/sponge.svg b/core/resources/src/main/assets/svg/objects/sponge.svg new file mode 100644 index 0000000..db15e4f --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/sponge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/sunglasses.svg b/core/resources/src/main/assets/svg/objects/sunglasses.svg new file mode 100644 index 0000000..40364e5 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/sunglasses.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/tube.svg b/core/resources/src/main/assets/svg/objects/tube.svg new file mode 100644 index 0000000..2b98cca --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/tube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/vest.svg b/core/resources/src/main/assets/svg/objects/vest.svg new file mode 100644 index 0000000..230baaa --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/vest.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/zzz_astudio.svg b/core/resources/src/main/assets/svg/objects/zzz_astudio.svg new file mode 100644 index 0000000..b4d493b --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/zzz_astudio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/zzz_kotlin.svg b/core/resources/src/main/assets/svg/objects/zzz_kotlin.svg new file mode 100644 index 0000000..e0d43b9 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/zzz_kotlin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/zzz_md.svg b/core/resources/src/main/assets/svg/objects/zzz_md.svg new file mode 100644 index 0000000..f2c154c --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/zzz_md.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/zzzz_image_toolbox.svg b/core/resources/src/main/assets/svg/objects/zzzz_image_toolbox.svg new file mode 100644 index 0000000..f4079e8 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/zzzz_image_toolbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/objects/zzzz_image_toolbox_old.svg b/core/resources/src/main/assets/svg/objects/zzzz_image_toolbox_old.svg new file mode 100644 index 0000000..66510d3 --- /dev/null +++ b/core/resources/src/main/assets/svg/objects/zzzz_image_toolbox_old.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/acancer.svg b/core/resources/src/main/assets/svg/symbols/acancer.svg new file mode 100644 index 0000000..4522e2a --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/acancer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/acapricorn.svg b/core/resources/src/main/assets/svg/symbols/acapricorn.svg new file mode 100644 index 0000000..9f7beaa --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/acapricorn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/agemini.svg b/core/resources/src/main/assets/svg/symbols/agemini.svg new file mode 100644 index 0000000..7bef18a --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/agemini.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/aleo.svg b/core/resources/src/main/assets/svg/symbols/aleo.svg new file mode 100644 index 0000000..5c769f4 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/aleo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/alibra.svg b/core/resources/src/main/assets/svg/symbols/alibra.svg new file mode 100644 index 0000000..337167d --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/alibra.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/aophiuchus.svg b/core/resources/src/main/assets/svg/symbols/aophiuchus.svg new file mode 100644 index 0000000..ce21518 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/aophiuchus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/apisces.svg b/core/resources/src/main/assets/svg/symbols/apisces.svg new file mode 100644 index 0000000..fd60677 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/apisces.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/aquarius.svg b/core/resources/src/main/assets/svg/symbols/aquarius.svg new file mode 100644 index 0000000..fba5286 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/aquarius.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/aries.svg b/core/resources/src/main/assets/svg/symbols/aries.svg new file mode 100644 index 0000000..f7d9a8a --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/aries.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/asagittarius.svg b/core/resources/src/main/assets/svg/symbols/asagittarius.svg new file mode 100644 index 0000000..0e09e5f --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/asagittarius.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/ascorpio.svg b/core/resources/src/main/assets/svg/symbols/ascorpio.svg new file mode 100644 index 0000000..4efea24 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/ascorpio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/ataurus.svg b/core/resources/src/main/assets/svg/symbols/ataurus.svg new file mode 100644 index 0000000..865dcf8 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/ataurus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/avirgo.svg b/core/resources/src/main/assets/svg/symbols/avirgo.svg new file mode 100644 index 0000000..ae10648 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/avirgo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/biohazard.svg b/core/resources/src/main/assets/svg/symbols/biohazard.svg new file mode 100644 index 0000000..23684be --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/biohazard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/children-crossing.svg b/core/resources/src/main/assets/svg/symbols/children-crossing.svg new file mode 100644 index 0000000..3128a46 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/children-crossing.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/cradioactive.svg b/core/resources/src/main/assets/svg/symbols/cradioactive.svg new file mode 100644 index 0000000..199b1e2 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/cradioactive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/cwarning.svg b/core/resources/src/main/assets/svg/symbols/cwarning.svg new file mode 100644 index 0000000..ab54416 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/cwarning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/heavy-dollar-sign.svg b/core/resources/src/main/assets/svg/symbols/heavy-dollar-sign.svg new file mode 100644 index 0000000..0a63f87 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/heavy-dollar-sign.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/male-sign-fe.svg b/core/resources/src/main/assets/svg/symbols/male-sign-fe.svg new file mode 100644 index 0000000..9775759 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/male-sign-fe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/male_sign.svg b/core/resources/src/main/assets/svg/symbols/male_sign.svg new file mode 100644 index 0000000..dbbb142 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/male_sign.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/natom-symbol.svg b/core/resources/src/main/assets/svg/symbols/natom-symbol.svg new file mode 100644 index 0000000..87c6a4b --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/natom-symbol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/no-entry.svg b/core/resources/src/main/assets/svg/symbols/no-entry.svg new file mode 100644 index 0000000..c61cbd8 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/no-entry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/prohibited.svg b/core/resources/src/main/assets/svg/symbols/prohibited.svg new file mode 100644 index 0000000..53bf8b6 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/prohibited.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/recycling.svg b/core/resources/src/main/assets/svg/symbols/recycling.svg new file mode 100644 index 0000000..7e33463 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/recycling.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/speech-balloon.svg b/core/resources/src/main/assets/svg/symbols/speech-balloon.svg new file mode 100644 index 0000000..708a287 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/speech-balloon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/symbols/tick.svg b/core/resources/src/main/assets/svg/symbols/tick.svg new file mode 100644 index 0000000..74584c7 --- /dev/null +++ b/core/resources/src/main/assets/svg/symbols/tick.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/ambulance.svg b/core/resources/src/main/assets/svg/transportation/ambulance.svg new file mode 100644 index 0000000..9846ce1 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/ambulance.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/articulated-lorry.svg b/core/resources/src/main/assets/svg/transportation/articulated-lorry.svg new file mode 100644 index 0000000..d910702 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/articulated-lorry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/auto-rickshaw.svg b/core/resources/src/main/assets/svg/transportation/auto-rickshaw.svg new file mode 100644 index 0000000..d249ba2 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/auto-rickshaw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/automobile.svg b/core/resources/src/main/assets/svg/transportation/automobile.svg new file mode 100644 index 0000000..719028e --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/automobile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/bus.svg b/core/resources/src/main/assets/svg/transportation/bus.svg new file mode 100644 index 0000000..6c72f6a --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/bus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/construction.svg b/core/resources/src/main/assets/svg/transportation/construction.svg new file mode 100644 index 0000000..205d908 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/construction.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/delivery-truck.svg b/core/resources/src/main/assets/svg/transportation/delivery-truck.svg new file mode 100644 index 0000000..97bf912 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/delivery-truck.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/fire-engine.svg b/core/resources/src/main/assets/svg/transportation/fire-engine.svg new file mode 100644 index 0000000..ff92562 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/fire-engine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/globe.svg b/core/resources/src/main/assets/svg/transportation/globe.svg new file mode 100644 index 0000000..e4c6280 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/helicopter.svg b/core/resources/src/main/assets/svg/transportation/helicopter.svg new file mode 100644 index 0000000..edf1a89 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/helicopter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/luggage.svg b/core/resources/src/main/assets/svg/transportation/luggage.svg new file mode 100644 index 0000000..759147a --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/luggage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/map.svg b/core/resources/src/main/assets/svg/transportation/map.svg new file mode 100644 index 0000000..bf1f465 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/map.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/minibus.svg b/core/resources/src/main/assets/svg/transportation/minibus.svg new file mode 100644 index 0000000..9558648 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/minibus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/motor-scooter.svg b/core/resources/src/main/assets/svg/transportation/motor-scooter.svg new file mode 100644 index 0000000..3dbe8d4 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/motor-scooter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/motorcycle.svg b/core/resources/src/main/assets/svg/transportation/motorcycle.svg new file mode 100644 index 0000000..ebf3dc8 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/motorcycle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/passenger-ship.svg b/core/resources/src/main/assets/svg/transportation/passenger-ship.svg new file mode 100644 index 0000000..323781e --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/passenger-ship.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/pickup-truck.svg b/core/resources/src/main/assets/svg/transportation/pickup-truck.svg new file mode 100644 index 0000000..b2a58e4 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/pickup-truck.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/police-car.svg b/core/resources/src/main/assets/svg/transportation/police-car.svg new file mode 100644 index 0000000..a130b4f --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/police-car.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/racing-car.svg b/core/resources/src/main/assets/svg/transportation/racing-car.svg new file mode 100644 index 0000000..a841398 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/racing-car.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/ringed-planet.svg b/core/resources/src/main/assets/svg/transportation/ringed-planet.svg new file mode 100644 index 0000000..fd45374 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/ringed-planet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/rocket.svg b/core/resources/src/main/assets/svg/transportation/rocket.svg new file mode 100644 index 0000000..18a86e2 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/rocket.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/satellite.svg b/core/resources/src/main/assets/svg/transportation/satellite.svg new file mode 100644 index 0000000..85aca0e --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/satellite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/shinto-shrine.svg b/core/resources/src/main/assets/svg/transportation/shinto-shrine.svg new file mode 100644 index 0000000..4139336 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/shinto-shrine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/small-airplane.svg b/core/resources/src/main/assets/svg/transportation/small-airplane.svg new file mode 100644 index 0000000..56c04f6 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/small-airplane.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/speedboat.svg b/core/resources/src/main/assets/svg/transportation/speedboat.svg new file mode 100644 index 0000000..4fe116a --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/speedboat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/sport-utility-vehicle.svg b/core/resources/src/main/assets/svg/transportation/sport-utility-vehicle.svg new file mode 100644 index 0000000..4ac9802 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/sport-utility-vehicle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/statue-of-liberty.svg b/core/resources/src/main/assets/svg/transportation/statue-of-liberty.svg new file mode 100644 index 0000000..cd560b3 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/statue-of-liberty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/taxi.svg b/core/resources/src/main/assets/svg/transportation/taxi.svg new file mode 100644 index 0000000..2f1aa9e --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/taxi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/tokyo-tower.svg b/core/resources/src/main/assets/svg/transportation/tokyo-tower.svg new file mode 100644 index 0000000..08e56d2 --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/tokyo-tower.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/tractor.svg b/core/resources/src/main/assets/svg/transportation/tractor.svg new file mode 100644 index 0000000..95e8e0d --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/tractor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/tram-car.svg b/core/resources/src/main/assets/svg/transportation/tram-car.svg new file mode 100644 index 0000000..a57d80d --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/tram-car.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/trolleybus.svg b/core/resources/src/main/assets/svg/transportation/trolleybus.svg new file mode 100644 index 0000000..a921e1e --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/trolleybus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/assets/svg/transportation/ufo.svg b/core/resources/src/main/assets/svg/transportation/ufo.svg new file mode 100644 index 0000000..c8de61a --- /dev/null +++ b/core/resources/src/main/assets/svg/transportation/ufo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/resources/src/main/ic_launcher-playstore.png b/core/resources/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..d8e5d57 Binary files /dev/null and b/core/resources/src/main/ic_launcher-playstore.png differ diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/Icons.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/Icons.kt new file mode 100644 index 0000000..56761a5 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/Icons.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +data object Icons { + data object Filled + data object Outlined + data object Rounded + data object TwoTone +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/emoji/Emoji.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/emoji/Emoji.kt new file mode 100644 index 0000000..4d510e4 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/emoji/Emoji.kt @@ -0,0 +1,145 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.net.Uri +import androidx.annotation.StringRes +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.core.net.toUri +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.EmojiEmotions +import com.t8rin.imagetoolbox.core.resources.icons.EmojiEvents +import com.t8rin.imagetoolbox.core.resources.icons.EmojiFoodBeverage +import com.t8rin.imagetoolbox.core.resources.icons.EmojiNature +import com.t8rin.imagetoolbox.core.resources.icons.EmojiObjects +import com.t8rin.imagetoolbox.core.resources.icons.EmojiSymbols +import com.t8rin.imagetoolbox.core.resources.icons.EmojiTransportation +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList + +object Emoji { + + @Volatile + private var _allIconsCategorized: ImmutableList? = null + + @Volatile + private var _allIcons: ImmutableList? = null + + @Volatile + private var _animatedIcons: Map? = null + + val allIcons: ImmutableList get() = _allIcons ?: persistentListOf() + val allIconsCategorized: ImmutableList + get() = _allIconsCategorized ?: persistentListOf() + + fun animatedIconFor(icon: Uri): Uri? = _animatedIcons?.get(icon) + + fun Context.initEmoji() { + synchronized(Emoji) { + if (!_allIcons.isNullOrEmpty() && !_allIconsCategorized.isNullOrEmpty()) return + + val animatedIcons = mutableMapOf() + val categories = listOf( + EmojiCategory( + title = R.string.emotions, + icon = Icons.Outlined.EmojiEmotions, + category = "emotions" + ), + EmojiCategory( + title = R.string.food_and_drink, + icon = Icons.Outlined.EmojiFoodBeverage, + category = "food" + ), + EmojiCategory( + title = R.string.nature_and_animals, + icon = Icons.Outlined.EmojiNature, + category = "nature" + ), + EmojiCategory( + title = R.string.objects, + icon = Icons.Outlined.EmojiObjects, + category = "objects" + ), + EmojiCategory( + title = R.string.activities, + icon = Icons.Outlined.EmojiEvents, + category = "events" + ), + EmojiCategory( + title = R.string.travels_and_places, + icon = Icons.Outlined.EmojiTransportation, + category = "transportation" + ), + EmojiCategory( + title = R.string.symbols, + icon = Icons.Rounded.EmojiSymbols, + category = "symbols" + ) + ).map { category -> + emojiData( + title = category.title, + icon = category.icon, + category = category.category, + animatedIcons = animatedIcons + ) + }.toPersistentList() + + _allIconsCategorized = categories + _allIcons = categories + .flatMap(EmojiData::emojis) + .toPersistentList() + _animatedIcons = animatedIcons + } + } + + private fun Context.emojiData( + @StringRes title: Int, + icon: ImageVector, + category: String, + animatedIcons: MutableMap + ) = EmojiData( + title = title, + icon = icon, + emojis = assets.list("lottie/$category")?.toSet().orEmpty().let { animatedFiles -> + assets.list("svg/$category")?.toList().orEmpty() + .sortedWith(String.CASE_INSENSITIVE_ORDER) + .map { filename -> + val iconUri = "file:///android_asset/svg/$category/$filename".toUri() + val animatedFilename = filename.replaceAfterLast(".", "lottie") + + if (animatedFilename in animatedFiles) { + animatedIcons[iconUri] = + "file:///android_asset/lottie/$category/$animatedFilename".toUri() + } + + iconUri + } + .toPersistentList() + } + ) + + private data class EmojiCategory( + @StringRes val title: Int, + val icon: ImageVector, + val category: String + ) + +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/emoji/EmojiData.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/emoji/EmojiData.kt new file mode 100644 index 0000000..f454134 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/emoji/EmojiData.kt @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.annotation.StringRes +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.vector.ImageVector +import kotlinx.collections.immutable.ImmutableList + +@Stable +@Immutable +data class EmojiData( + @StringRes val title: Int, + val icon: ImageVector, + val emojis: ImmutableList +) \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AccessTime.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AccessTime.kt new file mode 100644 index 0000000..a94e6d0 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AccessTime.kt @@ -0,0 +1,91 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.AccessTime: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.AccessTime", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(13f, 11.6f) + verticalLineTo(8f) + quadTo(13f, 7.575f, 12.712f, 7.287f) + quadTo(12.425f, 7f, 12f, 7f) + quadTo(11.575f, 7f, 11.288f, 7.287f) + quadTo(11f, 7.575f, 11f, 8f) + verticalLineTo(11.975f) + quadTo(11f, 12.175f, 11.075f, 12.363f) + quadTo(11.15f, 12.55f, 11.3f, 12.7f) + lineTo(14.6f, 16f) + quadTo(14.875f, 16.275f, 15.3f, 16.275f) + quadTo(15.725f, 16.275f, 16f, 16f) + quadTo(16.275f, 15.725f, 16.275f, 15.3f) + quadTo(16.275f, 14.875f, 16f, 14.6f) + close() + moveTo(12f, 22f) + quadTo(9.925f, 22f, 8.1f, 21.212f) + quadTo(6.275f, 20.425f, 4.925f, 19.075f) + quadTo(3.575f, 17.725f, 2.787f, 15.9f) + quadTo(2f, 14.075f, 2f, 12f) + quadTo(2f, 9.925f, 2.787f, 8.1f) + quadTo(3.575f, 6.275f, 4.925f, 4.925f) + quadTo(6.275f, 3.575f, 8.1f, 2.787f) + quadTo(9.925f, 2f, 12f, 2f) + quadTo(14.075f, 2f, 15.9f, 2.787f) + quadTo(17.725f, 3.575f, 19.075f, 4.925f) + quadTo(20.425f, 6.275f, 21.212f, 8.1f) + quadTo(22f, 9.925f, 22f, 12f) + quadTo(22f, 14.075f, 21.212f, 15.9f) + quadTo(20.425f, 17.725f, 19.075f, 19.075f) + quadTo(17.725f, 20.425f, 15.9f, 21.212f) + quadTo(14.075f, 22f, 12f, 22f) + close() + moveTo(12f, 12f) + quadTo(12f, 12f, 12f, 12f) + quadTo(12f, 12f, 12f, 12f) + quadTo(12f, 12f, 12f, 12f) + quadTo(12f, 12f, 12f, 12f) + quadTo(12f, 12f, 12f, 12f) + quadTo(12f, 12f, 12f, 12f) + quadTo(12f, 12f, 12f, 12f) + quadTo(12f, 12f, 12f, 12f) + close() + moveTo(12f, 20f) + quadTo(15.325f, 20f, 17.663f, 17.663f) + quadTo(20f, 15.325f, 20f, 12f) + quadTo(20f, 8.675f, 17.663f, 6.338f) + quadTo(15.325f, 4f, 12f, 4f) + quadTo(8.675f, 4f, 6.338f, 6.338f) + quadTo(4f, 8.675f, 4f, 12f) + quadTo(4f, 15.325f, 6.338f, 17.663f) + quadTo(8.675f, 20f, 12f, 20f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Add.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Add.kt new file mode 100644 index 0000000..b6bd40a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Add.kt @@ -0,0 +1,64 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Add: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Add", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(440f, 520f) + lineTo(240f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(200f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(240f, 440f) + horizontalLineToRelative(200f) + verticalLineToRelative(-200f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 200f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 240f) + verticalLineToRelative(200f) + horizontalLineToRelative(200f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(760f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(720f, 520f) + lineTo(520f, 520f) + verticalLineToRelative(200f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 760f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 720f) + verticalLineToRelative(-200f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AddCircle.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AddCircle.kt new file mode 100644 index 0000000..ce5eaa7 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AddCircle.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.AddCircle: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.AddCircle", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(440f, 520f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 680f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 640f) + verticalLineToRelative(-120f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(680f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(640f, 440f) + lineTo(520f, 440f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 320f) + verticalLineToRelative(120f) + lineTo(320f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(280f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(320f, 520f) + horizontalLineToRelative(120f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AddPhotoAlt.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AddPhotoAlt.kt new file mode 100644 index 0000000..e848f1f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AddPhotoAlt.kt @@ -0,0 +1,287 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.AddPhotoAlt", + 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(-7f) + 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(7f) + 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(17f, 7f) + horizontalLineToRelative(-1f) + 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(1f) + verticalLineToRelative(-1f) + 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(1f) + horizontalLineToRelative(1f) + 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(-1f) + verticalLineToRelative(1f) + 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(-1f) + 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.Rounded.AddPhotoAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.AddPhotoAlt", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(17.288f, 8.712f) + curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) + verticalLineToRelative(-1f) + horizontalLineToRelative(-1f) + 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(1f) + verticalLineToRelative(-1f) + 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(1f) + horizontalLineToRelative(1f) + 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(-1f) + verticalLineToRelative(1f) + 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(20.563f, 10.8f) + curveToRelative(-0.292f, -0.167f, -0.604f, -0.192f, -0.938f, -0.075f) + curveToRelative(-0.267f, 0.083f, -0.533f, 0.15f, -0.8f, 0.2f) + reflectiveCurveToRelative(-0.542f, 0.075f, -0.825f, 0.075f) + curveToRelative(-1.383f, 0f, -2.563f, -0.487f, -3.537f, -1.463f) + curveToRelative(-0.975f, -0.975f, -1.463f, -2.154f, -1.463f, -3.537f) + curveToRelative(0f, -0.283f, 0.025f, -0.558f, 0.075f, -0.825f) + reflectiveCurveToRelative(0.117f, -0.533f, 0.2f, -0.8f) + curveToRelative(0.133f, -0.333f, 0.112f, -0.646f, -0.063f, -0.938f) + curveToRelative(-0.175f, -0.292f, -0.429f, -0.438f, -0.763f, -0.438f) + horizontalLineToRelative(-7.45f) + 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(14f) + curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) + reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) + verticalLineToRelative(-7.45f) + curveToRelative(0f, -0.333f, -0.146f, -0.583f, -0.438f, -0.75f) + close() + moveTo(17.45f, 16.725f) + curveToRelative(-0.1f, 0.183f, -0.25f, 0.275f, -0.45f, 0.275f) + horizontalLineTo(7f) + curveToRelative(-0.2f, 0f, -0.35f, -0.092f, -0.45f, -0.275f) + curveToRelative(-0.1f, -0.183f, -0.083f, -0.358f, 0.05f, -0.525f) + lineToRelative(2f, -2.675f) + curveToRelative(0.1f, -0.133f, 0.233f, -0.2f, 0.4f, -0.2f) + reflectiveCurveToRelative(0.3f, 0.067f, 0.4f, 0.2f) + lineToRelative(1.85f, 2.475f) + lineToRelative(2.6f, -3.475f) + curveToRelative(0.1f, -0.133f, 0.233f, -0.2f, 0.4f, -0.2f) + reflectiveCurveToRelative(0.3f, 0.067f, 0.4f, 0.2f) + lineToRelative(2.75f, 3.675f) + curveToRelative(0.133f, 0.167f, 0.15f, 0.342f, 0.05f, 0.525f) + 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), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(19.604f, 10.739f) + curveToRelative(-0.267f, 0.083f, -0.533f, 0.15f, -0.8f, 0.2f) + reflectiveCurveToRelative(-0.542f, 0.075f, -0.825f, 0.075f) + curveToRelative(-1.383f, 0f, -2.563f, -0.487f, -3.537f, -1.463f) + curveToRelative(-0.975f, -0.975f, -1.463f, -2.154f, -1.463f, -3.537f) + curveToRelative(0f, -0.283f, 0.025f, -0.558f, 0.075f, -0.825f) + reflectiveCurveToRelative(0.117f, -0.533f, 0.2f, -0.8f) + curveToRelative(0.027f, -0.067f, 0.043f, -0.133f, 0.057f, -0.198f) + horizontalLineTo(5f) + verticalLineToRelative(14.809f) + horizontalLineToRelative(14.82f) + verticalLineToRelative(-8.291f) + curveToRelative(-0.072f, 0.012f, -0.142f, 0.005f, -0.216f, 0.031f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(16f, 7f) + horizontalLineToRelative(1f) + 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) + horizontalLineToRelative(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) + reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.713f) + curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.713f, -0.287f) + horizontalLineToRelative(-1f) + 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) + horizontalLineToRelative(-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) + reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.713f) + curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.713f, 0.287f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(9.4f, 13.525f) + 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) + curveToRelative(0.1f, 0.183f, 0.25f, 0.275f, 0.45f, 0.275f) + horizontalLineToRelative(10f) + curveToRelative(0.2f, 0f, 0.35f, -0.092f, 0.45f, -0.275f) + curveToRelative(0.1f, -0.183f, 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(20.992f, 11.66f) + curveToRelative(0f, -0.552f, -0.448f, -1f, -1f, -1f) + curveToRelative(-0.552f, 0f, -1f, 0.448f, -1f, 1f) + verticalLineToRelative(0.908f) + curveToRelative(0f, 0.014f, 0.007f, 0.025f, 0.008f, 0.039f) + verticalLineToRelative(6.394f) + horizontalLineTo(5f) + verticalLineTo(5f) + horizontalLineToRelative(7.343f) + curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) + reflectiveCurveToRelative(-0.448f, -1f, -1f, -1f) + horizontalLineToRelative(-7.343f) + 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(14f) + curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) + reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) + verticalLineToRelative(-7f) + curveToRelative(0f, -0.016f, -0.007f, -0.028f, -0.008f, -0.043f) + verticalLineToRelative(-0.298f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AddSticky.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AddSticky.kt new file mode 100644 index 0000000..ffb5142 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AddSticky.kt @@ -0,0 +1,99 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Album.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Album.kt new file mode 100644 index 0000000..476a950 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Album.kt @@ -0,0 +1,222 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Album: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Album", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 660f) + quadToRelative(75f, 0f, 127.5f, -52.5f) + reflectiveQuadTo(660f, 480f) + quadToRelative(0f, -75f, -52.5f, -127.5f) + reflectiveQuadTo(480f, 300f) + quadToRelative(-75f, 0f, -127.5f, 52.5f) + reflectiveQuadTo(300f, 480f) + quadToRelative(0f, 75f, 52.5f, 127.5f) + reflectiveQuadTo(480f, 660f) + close() + moveTo(480f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 520f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} + +val Icons.Rounded.Album: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Album", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 660f) + quadToRelative(75f, 0f, 127.5f, -52.5f) + reflectiveQuadTo(660f, 480f) + quadToRelative(0f, -75f, -52.5f, -127.5f) + reflectiveQuadTo(480f, 300f) + quadToRelative(-75f, 0f, -127.5f, 52.5f) + reflectiveQuadTo(300f, 480f) + quadToRelative(0f, 75f, 52.5f, 127.5f) + reflectiveQuadTo(480f, 660f) + close() + moveTo(480f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 520f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + } + }.build() +} + +val Icons.TwoTone.Album: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.Album", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(12f, 16.5f) + curveToRelative(1.25f, 0f, 2.313f, -0.438f, 3.188f, -1.313f) + reflectiveCurveToRelative(1.313f, -1.938f, 1.313f, -3.188f) + reflectiveCurveToRelative(-0.438f, -2.313f, -1.313f, -3.188f) + reflectiveCurveToRelative(-1.938f, -1.313f, -3.188f, -1.313f) + reflectiveCurveToRelative(-2.313f, 0.438f, -3.188f, 1.313f) + reflectiveCurveToRelative(-1.313f, 1.938f, -1.313f, 3.188f) + reflectiveCurveToRelative(0.438f, 2.313f, 1.313f, 3.188f) + reflectiveCurveToRelative(1.938f, 1.313f, 3.188f, 1.313f) + close() + moveTo(11.288f, 12.712f) + 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) + 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(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() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(17.675f, 6.325f) + 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() + moveTo(12f, 16.5f) + curveToRelative(-2.485f, 0f, -4.5f, -2.015f, -4.5f, -4.5f) + reflectiveCurveToRelative(2.015f, -4.5f, 4.5f, -4.5f) + reflectiveCurveToRelative(4.5f, 2.015f, 4.5f, 4.5f) + reflectiveCurveToRelative(-2.015f, 4.5f, -4.5f, 4.5f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AlignVerticalCenter.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AlignVerticalCenter.kt new file mode 100644 index 0000000..6436bdc --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AlignVerticalCenter.kt @@ -0,0 +1,78 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.AlignVerticalCenter: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.AlignVerticalCenter", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(340f, 840f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(280f, 780f) + verticalLineToRelative(-260f) + lineTo(120f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 440f) + horizontalLineToRelative(160f) + verticalLineToRelative(-260f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(340f, 120f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(400f, 180f) + verticalLineToRelative(260f) + horizontalLineToRelative(160f) + verticalLineToRelative(-140f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(620f, 240f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(680f, 300f) + verticalLineToRelative(140f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 520f) + lineTo(680f, 520f) + verticalLineToRelative(140f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(620f, 720f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(560f, 660f) + verticalLineToRelative(-140f) + lineTo(400f, 520f) + verticalLineToRelative(260f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(340f, 840f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AlternateEmail.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AlternateEmail.kt new file mode 100644 index 0000000..c5b3e67 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AlternateEmail.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.AlternateEmail: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.AlternateEmail", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + verticalLineToRelative(58f) + quadToRelative(0f, 59f, -40.5f, 100.5f) + reflectiveQuadTo(740f, 680f) + quadToRelative(-35f, 0f, -66f, -15f) + reflectiveQuadToRelative(-52f, -43f) + quadToRelative(-29f, 29f, -65.5f, 43.5f) + reflectiveQuadTo(480f, 680f) + quadToRelative(-83f, 0f, -141.5f, -58.5f) + reflectiveQuadTo(280f, 480f) + quadToRelative(0f, -83f, 58.5f, -141.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(83f, 0f, 141.5f, 58.5f) + reflectiveQuadTo(680f, 480f) + verticalLineToRelative(58f) + quadToRelative(0f, 26f, 17f, 44f) + reflectiveQuadToRelative(43f, 18f) + quadToRelative(26f, 0f, 43f, -18f) + reflectiveQuadToRelative(17f, -44f) + verticalLineToRelative(-58f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 840f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 880f) + lineTo(480f, 880f) + close() + moveTo(480f, 600f) + quadToRelative(50f, 0f, 85f, -35f) + reflectiveQuadToRelative(35f, -85f) + quadToRelative(0f, -50f, -35f, -85f) + reflectiveQuadToRelative(-85f, -35f) + quadToRelative(-50f, 0f, -85f, 35f) + reflectiveQuadToRelative(-35f, 85f) + quadToRelative(0f, 50f, 35f, 85f) + reflectiveQuadToRelative(85f, 35f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Analogous.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Analogous.kt new file mode 100644 index 0000000..d7e05a4 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Analogous.kt @@ -0,0 +1,70 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AnalogousComplementary.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AnalogousComplementary.kt new file mode 100644 index 0000000..672ee63 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AnalogousComplementary.kt @@ -0,0 +1,81 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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.Black), 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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Analytics.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Analytics.kt new file mode 100644 index 0000000..19fdc8a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Analytics.kt @@ -0,0 +1,88 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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.Black), 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.Black), 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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Android.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Android.kt new file mode 100644 index 0000000..7b0e0a1 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Android.kt @@ -0,0 +1,79 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Android: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Android", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(40f, 720f) + quadToRelative(9f, -107f, 65.5f, -197f) + reflectiveQuadTo(256f, 380f) + lineToRelative(-74f, -128f) + quadToRelative(-6f, -9f, -3f, -19f) + reflectiveQuadToRelative(13f, -15f) + quadToRelative(8f, -5f, 18f, -2f) + reflectiveQuadToRelative(16f, 12f) + lineToRelative(74f, 128f) + quadToRelative(86f, -36f, 180f, -36f) + reflectiveQuadToRelative(180f, 36f) + lineToRelative(74f, -128f) + quadToRelative(6f, -9f, 16f, -12f) + reflectiveQuadToRelative(18f, 2f) + quadToRelative(10f, 5f, 13f, 15f) + reflectiveQuadToRelative(-3f, 19f) + lineToRelative(-74f, 128f) + quadToRelative(94f, 53f, 150.5f, 143f) + reflectiveQuadTo(920f, 720f) + lineTo(40f, 720f) + close() + moveTo(280f, 610f) + quadToRelative(21f, 0f, 35.5f, -14.5f) + reflectiveQuadTo(330f, 560f) + quadToRelative(0f, -21f, -14.5f, -35.5f) + reflectiveQuadTo(280f, 510f) + quadToRelative(-21f, 0f, -35.5f, 14.5f) + reflectiveQuadTo(230f, 560f) + quadToRelative(0f, 21f, 14.5f, 35.5f) + reflectiveQuadTo(280f, 610f) + close() + moveTo(680f, 610f) + quadToRelative(21f, 0f, 35.5f, -14.5f) + reflectiveQuadTo(730f, 560f) + quadToRelative(0f, -21f, -14.5f, -35.5f) + reflectiveQuadTo(680f, 510f) + quadToRelative(-21f, 0f, -35.5f, 14.5f) + reflectiveQuadTo(630f, 560f) + quadToRelative(0f, 21f, 14.5f, 35.5f) + reflectiveQuadTo(680f, 610f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Animation.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Animation.kt new file mode 100644 index 0000000..3c9c499 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Animation.kt @@ -0,0 +1,71 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Apng.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Apng.kt new file mode 100644 index 0000000..8f2df89 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Apng.kt @@ -0,0 +1,122 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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.Black), 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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ApngBox.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ApngBox.kt new file mode 100644 index 0000000..afacce6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ApngBox.kt @@ -0,0 +1,262 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), + 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.Black), + 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.Black), + 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.Black), + 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.Black), + 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.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(5f, 5f) + horizontalLineToRelative(14f) + verticalLineToRelative(14f) + horizontalLineToRelative(-14f) + close() + } + 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) + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Apps.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Apps.kt new file mode 100644 index 0000000..4c471dc --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Apps.kt @@ -0,0 +1,111 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Archive.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Archive.kt new file mode 100644 index 0000000..399c1ad --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Archive.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AreaChart.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AreaChart.kt new file mode 100644 index 0000000..88441fb --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AreaChart.kt @@ -0,0 +1,40 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowBack.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowBack.kt new file mode 100644 index 0000000..e781b63 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowBack.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ArrowBack: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ArrowBack", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(313f, 520f) + lineToRelative(196f, 196f) + quadToRelative(12f, 12f, 11.5f, 28f) + reflectiveQuadTo(508f, 772f) + quadToRelative(-12f, 11f, -28f, 11.5f) + reflectiveQuadTo(452f, 772f) + lineTo(188f, 508f) + quadToRelative(-6f, -6f, -8.5f, -13f) + reflectiveQuadToRelative(-2.5f, -15f) + quadToRelative(0f, -8f, 2.5f, -15f) + reflectiveQuadToRelative(8.5f, -13f) + lineToRelative(264f, -264f) + quadToRelative(11f, -11f, 27.5f, -11f) + reflectiveQuadToRelative(28.5f, 11f) + quadToRelative(12f, 12f, 12f, 28.5f) + reflectiveQuadTo(508f, 245f) + lineTo(313f, 440f) + horizontalLineToRelative(447f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 520f) + lineTo(313f, 520f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowBackIos.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowBackIos.kt new file mode 100644 index 0000000..198f5ac --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowBackIos.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ArrowBackIos: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ArrowBackIos", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(142f, 480f) + lineToRelative(294f, 294f) + quadToRelative(15f, 15f, 14.5f, 35f) + reflectiveQuadTo(435f, 844f) + quadToRelative(-15f, 15f, -35f, 15f) + reflectiveQuadToRelative(-35f, -15f) + lineTo(57f, 537f) + quadToRelative(-12f, -12f, -18f, -27f) + reflectiveQuadToRelative(-6f, -30f) + quadToRelative(0f, -15f, 6f, -30f) + reflectiveQuadToRelative(18f, -27f) + lineToRelative(308f, -308f) + quadToRelative(15f, -15f, 35.5f, -14.5f) + reflectiveQuadTo(436f, 116f) + quadToRelative(15f, 15f, 15f, 35f) + reflectiveQuadToRelative(-15f, 35f) + lineTo(142f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowCircleRight.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowCircleRight.kt new file mode 100644 index 0000000..f399959 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowCircleRight.kt @@ -0,0 +1,78 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ArrowCircleRight: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ArrowCircleRight", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(488f, 520f) + lineTo(452f, 556f) + quadToRelative(-11f, 11f, -11f, 28f) + reflectiveQuadToRelative(11f, 28f) + quadToRelative(11f, 11f, 28f, 11f) + reflectiveQuadToRelative(28f, -11f) + lineToRelative(104f, -104f) + quadToRelative(12f, -12f, 12f, -28f) + reflectiveQuadToRelative(-12f, -28f) + lineTo(508f, 348f) + quadToRelative(-11f, -11f, -28f, -11f) + reflectiveQuadToRelative(-28f, 11f) + quadToRelative(-11f, 11f, -11f, 28f) + reflectiveQuadToRelative(11f, 28f) + lineToRelative(36f, 36f) + lineTo(360f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(320f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(360f, 520f) + horizontalLineToRelative(128f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowDropDown.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowDropDown.kt new file mode 100644 index 0000000..ff2ea46 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowDropDown.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ArrowDropDown: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ArrowDropDown", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(459f, 579f) + lineTo(314f, 434f) + quadToRelative(-3f, -3f, -4.5f, -6.5f) + reflectiveQuadTo(308f, 420f) + quadToRelative(0f, -8f, 5.5f, -14f) + reflectiveQuadToRelative(14.5f, -6f) + horizontalLineToRelative(304f) + quadToRelative(9f, 0f, 14.5f, 6f) + reflectiveQuadToRelative(5.5f, 14f) + quadToRelative(0f, 2f, -6f, 14f) + lineTo(501f, 579f) + quadToRelative(-5f, 5f, -10f, 7f) + reflectiveQuadToRelative(-11f, 2f) + quadToRelative(-6f, 0f, -11f, -2f) + reflectiveQuadToRelative(-10f, -7f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowDropUp.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowDropUp.kt new file mode 100644 index 0000000..b68ce95 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowDropUp.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ArrowDropUp: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ArrowDropUp", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(328f, 560f) + quadToRelative(-9f, 0f, -14.5f, -6f) + reflectiveQuadToRelative(-5.5f, -14f) + quadToRelative(0f, -2f, 6f, -14f) + lineToRelative(145f, -145f) + quadToRelative(5f, -5f, 10f, -7f) + reflectiveQuadToRelative(11f, -2f) + quadToRelative(6f, 0f, 11f, 2f) + reflectiveQuadToRelative(10f, 7f) + lineToRelative(145f, 145f) + quadToRelative(3f, 3f, 4.5f, 6.5f) + reflectiveQuadToRelative(1.5f, 7.5f) + quadToRelative(0f, 8f, -5.5f, 14f) + reflectiveQuadToRelative(-14.5f, 6f) + lineTo(328f, 560f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowForwardIos.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowForwardIos.kt new file mode 100644 index 0000000..2d034a8 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowForwardIos.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ArrowForwardIos: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ArrowForwardIos", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(579f, 480f) + lineTo(285f, 186f) + quadToRelative(-15f, -15f, -14.5f, -35.5f) + reflectiveQuadTo(286f, 115f) + quadToRelative(15f, -15f, 35.5f, -15f) + reflectiveQuadToRelative(35.5f, 15f) + lineToRelative(307f, 308f) + quadToRelative(12f, 12f, 18f, 27f) + reflectiveQuadToRelative(6f, 30f) + quadToRelative(0f, 15f, -6f, 30f) + reflectiveQuadToRelative(-18f, 27f) + lineTo(356f, 845f) + quadToRelative(-15f, 15f, -35f, 14.5f) + reflectiveQuadTo(286f, 844f) + quadToRelative(-15f, -15f, -15f, -35.5f) + reflectiveQuadToRelative(15f, -35.5f) + lineToRelative(293f, -293f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowLeft.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowLeft.kt new file mode 100644 index 0000000..cebe255 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowLeft.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ArrowLeft: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ArrowLeft", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(526f, 646f) + lineTo(381f, 501f) + quadToRelative(-5f, -5f, -7f, -10f) + reflectiveQuadToRelative(-2f, -11f) + quadToRelative(0f, -6f, 2f, -11f) + reflectiveQuadToRelative(7f, -10f) + lineToRelative(145f, -145f) + quadToRelative(3f, -3f, 6.5f, -4.5f) + reflectiveQuadToRelative(7.5f, -1.5f) + quadToRelative(8f, 0f, 14f, 5.5f) + reflectiveQuadToRelative(6f, 14.5f) + verticalLineToRelative(304f) + quadToRelative(0f, 9f, -6f, 14.5f) + reflectiveQuadToRelative(-14f, 5.5f) + quadToRelative(-2f, 0f, -14f, -6f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowRight.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowRight.kt new file mode 100644 index 0000000..6636a88 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArrowRight.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ArrowRight: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ArrowRight", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(420f, 652f) + quadToRelative(-8f, 0f, -14f, -5.5f) + reflectiveQuadToRelative(-6f, -14.5f) + verticalLineToRelative(-304f) + quadToRelative(0f, -9f, 6f, -14.5f) + reflectiveQuadToRelative(14f, -5.5f) + quadToRelative(2f, 0f, 14f, 6f) + lineToRelative(145f, 145f) + quadToRelative(5f, 5f, 7f, 10f) + reflectiveQuadToRelative(2f, 11f) + quadToRelative(0f, 6f, -2f, 11f) + reflectiveQuadToRelative(-7f, 10f) + lineTo(434f, 646f) + quadToRelative(-3f, 3f, -6.5f, 4.5f) + reflectiveQuadTo(420f, 652f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArtTrack.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArtTrack.kt new file mode 100644 index 0000000..6abce6c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArtTrack.kt @@ -0,0 +1,204 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Ascii.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Ascii.kt new file mode 100644 index 0000000..86f7cbe --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Ascii.kt @@ -0,0 +1,110 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AspectRatio.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AspectRatio.kt new file mode 100644 index 0000000..6b2f7b6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AspectRatio.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.AspectRatio: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.AspectRatio", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(680f, 600f) + horizontalLineToRelative(-80f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(560f, 640f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(600f, 680f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(760f, 640f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(720f, 480f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(680f, 520f) + verticalLineToRelative(80f) + close() + moveTo(280f, 360f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(400f, 320f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(360f, 280f) + lineTo(240f, 280f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(200f, 320f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(240f, 480f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(280f, 440f) + verticalLineToRelative(-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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AutoAwesome.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AutoAwesome.kt new file mode 100644 index 0000000..fbd8aea --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AutoAwesome.kt @@ -0,0 +1,116 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.AutoAwesome: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.AutoAwesome", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(19f, 8.3f) + quadToRelative(-0.125f, 0f, -0.262f, -0.075f) + quadTo(18.6f, 8.15f, 18.55f, 8f) + lineToRelative(-0.8f, -1.75f) + lineToRelative(-1.75f, -0.8f) + quadToRelative(-0.15f, -0.05f, -0.225f, -0.188f) + quadTo(15.7f, 5.125f, 15.7f, 5f) + reflectiveQuadToRelative(0.075f, -0.263f) + quadTo(15.85f, 4.6f, 16f, 4.55f) + lineToRelative(1.75f, -0.8f) + lineToRelative(0.8f, -1.75f) + quadToRelative(0.05f, -0.15f, 0.188f, -0.225f) + quadToRelative(0.137f, -0.075f, 0.262f, -0.075f) + reflectiveQuadToRelative(0.263f, 0.075f) + quadToRelative(0.137f, 0.075f, 0.187f, 0.225f) + lineToRelative(0.8f, 1.75f) + lineToRelative(1.75f, 0.8f) + quadToRelative(0.15f, 0.05f, 0.225f, 0.187f) + quadToRelative(0.075f, 0.138f, 0.075f, 0.263f) + reflectiveQuadToRelative(-0.075f, 0.262f) + quadTo(22.15f, 5.4f, 22f, 5.45f) + lineToRelative(-1.75f, 0.8f) + lineToRelative(-0.8f, 1.75f) + quadToRelative(-0.05f, 0.15f, -0.187f, 0.225f) + quadToRelative(-0.138f, 0.075f, -0.263f, 0.075f) + close() + moveTo(19f, 22.3f) + quadToRelative(-0.125f, 0f, -0.262f, -0.075f) + quadToRelative(-0.138f, -0.075f, -0.188f, -0.225f) + lineToRelative(-0.8f, -1.75f) + lineToRelative(-1.75f, -0.8f) + quadToRelative(-0.15f, -0.05f, -0.225f, -0.188f) + quadToRelative(-0.075f, -0.137f, -0.075f, -0.262f) + reflectiveQuadToRelative(0.075f, -0.262f) + quadToRelative(0.075f, -0.138f, 0.225f, -0.188f) + lineToRelative(1.75f, -0.8f) + lineToRelative(0.8f, -1.75f) + quadToRelative(0.05f, -0.15f, 0.188f, -0.225f) + quadToRelative(0.137f, -0.075f, 0.262f, -0.075f) + reflectiveQuadToRelative(0.263f, 0.075f) + quadToRelative(0.137f, 0.075f, 0.187f, 0.225f) + lineToRelative(0.8f, 1.75f) + lineToRelative(1.75f, 0.8f) + quadToRelative(0.15f, 0.05f, 0.225f, 0.188f) + quadToRelative(0.075f, 0.137f, 0.075f, 0.262f) + reflectiveQuadToRelative(-0.075f, 0.262f) + quadToRelative(-0.075f, 0.138f, -0.225f, 0.188f) + lineToRelative(-1.75f, 0.8f) + lineToRelative(-0.8f, 1.75f) + quadToRelative(-0.05f, 0.15f, -0.187f, 0.225f) + quadToRelative(-0.138f, 0.075f, -0.263f, 0.075f) + close() + moveTo(9f, 18.575f) + quadToRelative(-0.275f, 0f, -0.525f, -0.15f) + reflectiveQuadTo(8.1f, 18f) + lineToRelative(-1.6f, -3.5f) + lineTo(3f, 12.9f) + quadToRelative(-0.275f, -0.125f, -0.425f, -0.375f) + quadToRelative(-0.15f, -0.25f, -0.15f, -0.525f) + reflectiveQuadToRelative(0.15f, -0.525f) + quadToRelative(0.15f, -0.25f, 0.425f, -0.375f) + lineToRelative(3.5f, -1.6f) + lineTo(8.1f, 6f) + quadToRelative(0.125f, -0.275f, 0.375f, -0.425f) + quadToRelative(0.25f, -0.15f, 0.525f, -0.15f) + reflectiveQuadToRelative(0.525f, 0.15f) + quadToRelative(0.25f, 0.15f, 0.375f, 0.425f) + lineToRelative(1.6f, 3.5f) + lineToRelative(3.5f, 1.6f) + quadToRelative(0.275f, 0.125f, 0.425f, 0.375f) + quadToRelative(0.15f, 0.25f, 0.15f, 0.525f) + reflectiveQuadToRelative(-0.15f, 0.525f) + quadToRelative(-0.15f, 0.25f, -0.425f, 0.375f) + lineToRelative(-3.5f, 1.6f) + lineTo(9.9f, 18f) + quadToRelative(-0.125f, 0.275f, -0.375f, 0.425f) + quadToRelative(-0.25f, 0.15f, -0.525f, 0.15f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AutoDelete.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AutoDelete.kt new file mode 100644 index 0000000..bab126a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AutoDelete.kt @@ -0,0 +1,129 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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) + verticalLineToRelative(520f) + verticalLineToRelative(-520f) + close() + moveTo(280f, 840f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(200f, 760f) + verticalLineToRelative(-520f) + horizontalLineToRelative(-1f) + quadToRelative(-17f, 0f, -28f, -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(140f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(720f, 420f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(680f, 380f) + verticalLineToRelative(-140f) + lineTo(280f, 240f) + verticalLineToRelative(520f) + horizontalLineToRelative(107f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(427f, 800f) + quadToRelative(0f, 16f, -11.5f, 28f) + reflectiveQuadTo(387f, 840f) + lineTo(280f, 840f) + close() + moveTo(371.5f, 331.5f) + quadTo(360f, 343f, 360f, 360f) + verticalLineToRelative(280f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(400f, 680f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(440f, 640f) + verticalLineToRelative(-280f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(400f, 320f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + close() + moveTo(531.5f, 331.5f) + quadTo(520f, 343f, 520f, 360f) + verticalLineToRelative(60f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(560f, 460f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(600f, 420f) + verticalLineToRelative(-60f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(560f, 320f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + close() + moveTo(538.5f, 821.5f) + quadTo(480f, 763f, 480f, 680f) + reflectiveQuadToRelative(58.5f, -141.5f) + quadTo(597f, 480f, 680f, 480f) + quadToRelative(41f, 0f, 77.5f, 16f) + reflectiveQuadToRelative(63.5f, 43f) + quadToRelative(27f, 27f, 43f, 63.5f) + reflectiveQuadToRelative(16f, 77.5f) + quadToRelative(0f, 83f, -58.5f, 141.5f) + reflectiveQuadTo(680f, 880f) + quadToRelative(-83f, 0f, -141.5f, -58.5f) + close() + moveTo(700f, 672f) + verticalLineToRelative(-92f) + quadToRelative(0f, -8f, -6f, -14f) + reflectiveQuadToRelative(-14f, -6f) + quadToRelative(-8f, 0f, -14f, 6f) + reflectiveQuadToRelative(-6f, 14f) + verticalLineToRelative(91f) + quadToRelative(0f, 8f, 3f, 15.5f) + reflectiveQuadToRelative(9f, 13.5f) + lineToRelative(60f, 60f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(6f, -6f, 6f, -14f) + reflectiveQuadToRelative(-6f, -14f) + lineToRelative(-60f, -60f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AutoFixHigh.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AutoFixHigh.kt new file mode 100644 index 0000000..4471748 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AutoFixHigh.kt @@ -0,0 +1,296 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.AutoFixHigh: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.AutoFixHigh", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(17.143f, 9.586f) + lineToRelative(-2.727f, -2.728f) + curveToRelative(-0.193f, -0.183f, -0.434f, -0.28f, -0.684f, -0.28f) + reflectiveCurveToRelative(-0.492f, 0.096f, -0.684f, 0.28f) + lineTo(2.282f, 17.624f) + curveToRelative(-0.376f, 0.376f, -0.376f, 0.983f, 0f, 1.359f) + lineToRelative(2.727f, 2.728f) + curveToRelative(0.193f, 0.193f, 0.434f, 0.289f, 0.684f, 0.289f) + reflectiveCurveToRelative(0.492f, -0.096f, 0.684f, -0.28f) + lineToRelative(10.765f, -10.766f) + curveToRelative(0.376f, -0.376f, 0.376f, -0.993f, 0f, -1.369f) + close() + moveTo(13.731f, 8.911f) + lineToRelative(1.359f, 1.359f) + lineToRelative(-1.128f, 1.128f) + lineToRelative(-1.359f, -1.359f) + lineToRelative(1.128f, -1.128f) + close() + moveTo(5.694f, 19.677f) + lineToRelative(-1.359f, -1.359f) + lineToRelative(6.91f, -6.92f) + lineToRelative(1.359f, 1.359f) + lineToRelative(-6.91f, 6.92f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(19.87f, 6.963f) + lineToRelative(0.566f, -1.226f) + lineToRelative(1.225f, -0.567f) + curveToRelative(0.451f, -0.208f, 0.451f, -0.844f, 0f, -1.052f) + lineToRelative(-1.225f, -0.567f) + lineToRelative(-0.566f, -1.214f) + curveToRelative(-0.208f, -0.451f, -0.844f, -0.451f, -1.052f, 0f) + lineToRelative(-0.566f, 1.226f) + lineToRelative(-1.214f, 0.567f) + curveToRelative(-0.451f, 0.208f, -0.451f, 0.844f, 0f, 1.052f) + lineToRelative(1.225f, 0.567f) + lineToRelative(0.566f, 1.214f) + curveToRelative(0.197f, 0.451f, 0.844f, 0.451f, 1.04f, 0f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(8.74f, 6.753f) + lineToRelative(0.515f, -1.114f) + lineToRelative(1.114f, -0.515f) + curveToRelative(0.41f, -0.189f, 0.41f, -0.767f, 0f, -0.956f) + lineToRelative(-1.114f, -0.515f) + lineToRelative(-0.515f, -1.104f) + curveToRelative(-0.189f, -0.41f, -0.767f, -0.41f, -0.956f, 0f) + lineToRelative(-0.515f, 1.114f) + lineToRelative(-1.104f, 0.515f) + curveToRelative(-0.41f, 0.189f, -0.41f, 0.767f, 0f, 0.956f) + lineToRelative(1.114f, 0.515f) + lineToRelative(0.515f, 1.104f) + curveToRelative(0.179f, 0.41f, 0.767f, 0.41f, 0.946f, 0f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(19.823f, 17.837f) + lineToRelative(0.515f, -1.114f) + lineToRelative(1.114f, -0.515f) + curveToRelative(0.41f, -0.189f, 0.41f, -0.767f, 0f, -0.956f) + lineToRelative(-1.114f, -0.515f) + lineToRelative(-0.515f, -1.104f) + curveToRelative(-0.189f, -0.41f, -0.767f, -0.41f, -0.956f, 0f) + lineToRelative(-0.515f, 1.114f) + lineToRelative(-1.104f, 0.515f) + curveToRelative(-0.41f, 0.189f, -0.41f, 0.767f, 0f, 0.956f) + lineToRelative(1.114f, 0.515f) + lineToRelative(0.515f, 1.104f) + curveToRelative(0.179f, 0.41f, 0.767f, 0.41f, 0.946f, 0f) + close() + } + }.build() +} + +val Icons.Rounded.AutoFixHigh: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.AutoFixHigh", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(19.87f, 6.965f) + lineToRelative(0.566f, -1.226f) + lineToRelative(1.225f, -0.567f) + curveToRelative(0.451f, -0.208f, 0.451f, -0.844f, 0f, -1.053f) + lineToRelative(-1.225f, -0.567f) + lineToRelative(-0.566f, -1.214f) + curveToRelative(-0.208f, -0.451f, -0.844f, -0.451f, -1.052f, 0f) + lineToRelative(-0.566f, 1.226f) + lineToRelative(-1.214f, 0.567f) + curveToRelative(-0.451f, 0.208f, -0.451f, 0.844f, 0f, 1.053f) + lineToRelative(1.225f, 0.567f) + lineToRelative(0.566f, 1.214f) + curveToRelative(0.197f, 0.451f, 0.844f, 0.451f, 1.04f, 0f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(8.74f, 6.755f) + lineToRelative(0.515f, -1.115f) + lineToRelative(1.114f, -0.515f) + curveToRelative(0.41f, -0.189f, 0.41f, -0.768f, 0f, -0.957f) + lineToRelative(-1.114f, -0.505f) + lineToRelative(-0.515f, -1.115f) + curveToRelative(-0.179f, -0.41f, -0.767f, -0.41f, -0.946f, 0f) + lineToRelative(-0.515f, 1.115f) + lineToRelative(-1.114f, 0.515f) + curveToRelative(-0.41f, 0.189f, -0.41f, 0.768f, 0f, 0.957f) + lineToRelative(1.114f, 0.515f) + lineToRelative(0.515f, 1.104f) + curveToRelative(0.179f, 0.41f, 0.767f, 0.41f, 0.946f, 0f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(18.877f, 13.642f) + lineToRelative(-0.515f, 1.112f) + lineToRelative(-1.114f, 0.514f) + curveToRelative(-0.41f, 0.189f, -0.41f, 0.766f, 0f, 0.954f) + lineToRelative(1.114f, 0.514f) + lineToRelative(0.515f, 1.112f) + curveToRelative(0.189f, 0.409f, 0.767f, 0.409f, 0.956f, 0f) + lineToRelative(0.515f, -1.112f) + lineToRelative(1.104f, -0.524f) + curveToRelative(0.41f, -0.189f, 0.41f, -0.766f, 0f, -0.954f) + lineToRelative(-1.114f, -0.514f) + lineToRelative(-0.515f, -1.112f) + curveToRelative(-0.179f, -0.399f, -0.767f, -0.399f, -0.946f, 0.01f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(17.143f, 9.588f) + lineToRelative(-2.727f, -2.729f) + curveToRelative(-0.376f, -0.376f, -0.983f, -0.376f, -1.359f, 0f) + lineTo(2.282f, 17.63f) + curveToRelative(-0.376f, 0.376f, -0.376f, 0.983f, 0f, 1.36f) + lineToRelative(2.727f, 2.729f) + curveToRelative(0.376f, 0.376f, 0.983f, 0.376f, 1.359f, 0f) + lineToRelative(10.765f, -10.77f) + curveToRelative(0.385f, -0.366f, 0.385f, -0.983f, 0.01f, -1.36f) + close() + moveTo(13.77f, 11.603f) + lineToRelative(-1.359f, -1.36f) + lineToRelative(1.33f, -1.331f) + lineToRelative(1.359f, 1.36f) + lineToRelative(-1.33f, 1.331f) + close() + } + }.build() +} + +val Icons.TwoTone.AutoFixHigh: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.AutoFixHigh", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color(0xFF1D1D1B))) { + moveTo(17.167f, 9.715f) + lineToRelative(0.001f, 0.001f) + lineToRelative(-2.727f, -2.728f) + curveToRelative(-0.193f, -0.183f, -0.434f, -0.28f, -0.684f, -0.28f) + reflectiveCurveToRelative(-0.492f, 0.096f, -0.684f, 0.28f) + lineTo(2.307f, 17.754f) + curveToRelative(-0.376f, 0.376f, -0.376f, 0.983f, 0f, 1.359f) + lineToRelative(2.727f, 2.728f) + curveToRelative(0.193f, 0.193f, 0.434f, 0.289f, 0.684f, 0.289f) + reflectiveCurveToRelative(0.492f, -0.096f, 0.684f, -0.28f) + lineToRelative(10.765f, -10.766f) + curveToRelative(0.376f, -0.376f, 0.376f, -0.993f, 0f, -1.369f) + close() + moveTo(5.719f, 19.807f) + lineToRelative(-1.359f, -1.359f) + lineToRelative(6.91f, -6.92f) + lineToRelative(1.359f, 1.359f) + lineToRelative(-6.91f, 6.92f) + close() + moveTo(13.987f, 11.528f) + lineToRelative(-1.359f, -1.359f) + lineToRelative(1.128f, -1.128f) + lineToRelative(1.359f, 1.359f) + lineToRelative(-1.128f, 1.128f) + close() + } + path(fill = SolidColor(Color(0xFF1D1D1B))) { + moveTo(19.895f, 7.093f) + lineToRelative(0.566f, -1.226f) + lineToRelative(1.225f, -0.567f) + curveToRelative(0.451f, -0.208f, 0.451f, -0.844f, 0f, -1.052f) + lineToRelative(-1.225f, -0.567f) + lineToRelative(-0.566f, -1.214f) + curveToRelative(-0.208f, -0.451f, -0.844f, -0.451f, -1.052f, 0f) + lineToRelative(-0.566f, 1.226f) + lineToRelative(-1.214f, 0.567f) + curveToRelative(-0.451f, 0.208f, -0.451f, 0.844f, 0f, 1.052f) + lineToRelative(1.225f, 0.567f) + lineToRelative(0.566f, 1.214f) + curveToRelative(0.197f, 0.451f, 0.844f, 0.451f, 1.04f, 0f) + lineToRelative(0.001f, -0f) + close() + } + path(fill = SolidColor(Color(0xFF1D1D1B))) { + moveTo(8.765f, 6.883f) + lineToRelative(0.515f, -1.114f) + lineToRelative(1.114f, -0.515f) + curveToRelative(0.41f, -0.189f, 0.41f, -0.767f, 0f, -0.956f) + lineToRelative(-1.114f, -0.515f) + lineToRelative(-0.515f, -1.104f) + curveToRelative(-0.189f, -0.41f, -0.767f, -0.41f, -0.956f, 0f) + lineToRelative(-0.515f, 1.114f) + lineToRelative(-1.104f, 0.515f) + curveToRelative(-0.41f, 0.189f, -0.41f, 0.767f, 0f, 0.956f) + lineToRelative(1.114f, 0.515f) + lineToRelative(0.515f, 1.104f) + curveToRelative(0.179f, 0.41f, 0.767f, 0.41f, 0.946f, 0f) + close() + } + path(fill = SolidColor(Color(0xFF1D1D1B))) { + moveTo(19.848f, 17.967f) + lineToRelative(0.515f, -1.114f) + lineToRelative(1.114f, -0.515f) + curveToRelative(0.41f, -0.189f, 0.41f, -0.767f, 0f, -0.956f) + lineToRelative(-1.114f, -0.515f) + lineToRelative(-0.515f, -1.104f) + curveToRelative(-0.189f, -0.41f, -0.767f, -0.41f, -0.956f, 0f) + lineToRelative(-0.515f, 1.114f) + lineToRelative(-1.104f, 0.515f) + curveToRelative(-0.41f, 0.189f, -0.41f, 0.767f, 0f, 0.956f) + lineToRelative(1.114f, 0.515f) + lineToRelative(0.515f, 1.104f) + curveToRelative(0.179f, 0.41f, 0.767f, 0.41f, 0.946f, 0f) + close() + } + path( + fill = SolidColor(Color(0xFF1D1D1C)), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(12.627f, 10.164f) + lineToRelative(1.128f, -1.128f) + lineToRelative(1.359f, 1.359f) + lineToRelative(-1.128f, 1.128f) + close() + } + path( + fill = SolidColor(Color(0xFF1D1D1C)), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(4.357f, 18.441f) + lineToRelative(6.915f, -6.915f) + lineToRelative(1.359f, 1.359f) + lineToRelative(-6.915f, 6.915f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AutoMode.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AutoMode.kt new file mode 100644 index 0000000..03eea18 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AutoMode.kt @@ -0,0 +1,139 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.AutoMode: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.AutoMode", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(12f, 23f) + quadToRelative(-2.8f, 0f, -5.15f, -1.275f) + quadTo(4.5f, 20.45f, 3f, 18.3f) + lineTo(3f, 20f) + quadToRelative(0f, 0.425f, -0.287f, 0.712f) + quadTo(2.425f, 21f, 2f, 21f) + reflectiveQuadToRelative(-0.712f, -0.288f) + quadTo(1f, 20.425f, 1f, 20f) + verticalLineToRelative(-4f) + quadToRelative(0f, -0.425f, 0.288f, -0.713f) + quadTo(1.575f, 15f, 2f, 15f) + horizontalLineToRelative(4f) + quadToRelative(0.425f, 0f, 0.713f, 0.287f) + quadTo(7f, 15.575f, 7f, 16f) + reflectiveQuadToRelative(-0.287f, 0.712f) + quadTo(6.425f, 17f, 6f, 17f) + lineTo(4.55f, 17f) + quadToRelative(1.2f, 1.8f, 3.15f, 2.9f) + quadTo(9.65f, 21f, 12f, 21f) + quadToRelative(2.75f, 0f, 4.938f, -1.462f) + quadToRelative(2.187f, -1.463f, 3.262f, -3.813f) + quadToRelative(0.15f, -0.325f, 0.45f, -0.475f) + quadToRelative(0.3f, -0.15f, 0.65f, -0.075f) + quadToRelative(0.45f, 0.125f, 0.675f, 0.55f) + quadToRelative(0.225f, 0.425f, 0.025f, 0.85f) + quadToRelative(-1.3f, 2.875f, -3.975f, 4.65f) + quadTo(15.35f, 23f, 12f, 23f) + close() + moveTo(1.975f, 10.975f) + quadToRelative(-0.375f, -0.1f, -0.612f, -0.425f) + quadToRelative(-0.238f, -0.325f, -0.138f, -0.75f) + quadToRelative(0.225f, -1.05f, 0.638f, -2.013f) + quadToRelative(0.412f, -0.962f, 0.987f, -1.837f) + quadToRelative(0.25f, -0.35f, 0.663f, -0.438f) + quadToRelative(0.412f, -0.087f, 0.762f, 0.188f) + quadToRelative(0.3f, 0.225f, 0.375f, 0.612f) + quadToRelative(0.075f, 0.388f, -0.125f, 0.713f) + quadToRelative(-0.475f, 0.725f, -0.825f, 1.537f) + quadToRelative(-0.35f, 0.813f, -0.525f, 1.688f) + quadToRelative(-0.075f, 0.425f, -0.438f, 0.625f) + quadToRelative(-0.362f, 0.2f, -0.762f, 0.1f) + close() + moveTo(5.75f, 4.35f) + quadToRelative(-0.3f, -0.3f, -0.25f, -0.775f) + quadToRelative(0.05f, -0.475f, 0.45f, -0.75f) + quadToRelative(0.875f, -0.575f, 1.837f, -0.988f) + quadToRelative(0.963f, -0.412f, 2.013f, -0.612f) + quadToRelative(0.425f, -0.075f, 0.762f, 0.15f) + quadToRelative(0.338f, 0.225f, 0.413f, 0.6f) + quadToRelative(0.075f, 0.425f, -0.125f, 0.775f) + quadToRelative(-0.2f, 0.35f, -0.625f, 0.425f) + quadToRelative(-0.875f, 0.2f, -1.675f, 0.513f) + quadToRelative(-0.8f, 0.312f, -1.525f, 0.812f) + quadToRelative(-0.3f, 0.2f, -0.65f, 0.162f) + quadToRelative(-0.35f, -0.037f, -0.625f, -0.312f) + close() + moveTo(10.45f, 13.575f) + lineTo(8f, 12.45f) + quadToRelative(-0.3f, -0.125f, -0.3f, -0.45f) + reflectiveQuadToRelative(0.3f, -0.45f) + lineToRelative(2.45f, -1.1f) + lineTo(11.55f, 8f) + quadToRelative(0.125f, -0.3f, 0.45f, -0.3f) + reflectiveQuadToRelative(0.45f, 0.3f) + lineToRelative(1.1f, 2.45f) + lineToRelative(2.45f, 1.1f) + quadToRelative(0.3f, 0.125f, 0.3f, 0.45f) + reflectiveQuadToRelative(-0.3f, 0.45f) + lineToRelative(-2.45f, 1.1f) + lineToRelative(-1.1f, 2.45f) + quadToRelative(-0.125f, 0.3f, -0.45f, 0.3f) + reflectiveQuadToRelative(-0.45f, -0.3f) + close() + moveTo(18.275f, 4.3f) + quadToRelative(-0.25f, 0.3f, -0.612f, 0.362f) + quadToRelative(-0.363f, 0.063f, -0.688f, -0.137f) + quadToRelative(-0.725f, -0.475f, -1.537f, -0.825f) + quadToRelative(-0.813f, -0.35f, -1.688f, -0.525f) + quadToRelative(-0.425f, -0.075f, -0.637f, -0.463f) + quadToRelative(-0.213f, -0.387f, -0.088f, -0.787f) + quadToRelative(0.1f, -0.375f, 0.438f, -0.575f) + quadToRelative(0.337f, -0.2f, 0.737f, -0.125f) + quadToRelative(1.05f, 0.2f, 2.012f, 0.612f) + quadToRelative(0.963f, 0.413f, 1.838f, 0.988f) + quadToRelative(0.375f, 0.25f, 0.438f, 0.687f) + quadToRelative(0.062f, 0.438f, -0.213f, 0.788f) + close() + moveTo(22.025f, 10.975f) + quadToRelative(-0.4f, 0.1f, -0.762f, -0.1f) + quadToRelative(-0.363f, -0.2f, -0.438f, -0.625f) + quadToRelative(-0.175f, -0.875f, -0.513f, -1.688f) + quadToRelative(-0.337f, -0.812f, -0.837f, -1.537f) + quadToRelative(-0.225f, -0.35f, -0.138f, -0.738f) + quadToRelative(0.088f, -0.387f, 0.413f, -0.612f) + quadToRelative(0.35f, -0.25f, 0.75f, -0.163f) + quadToRelative(0.4f, 0.088f, 0.65f, 0.438f) + quadToRelative(0.575f, 0.875f, 0.988f, 1.837f) + quadToRelative(0.412f, 0.963f, 0.637f, 2.013f) + quadToRelative(0.1f, 0.425f, -0.137f, 0.75f) + quadToRelative(-0.238f, 0.325f, -0.613f, 0.425f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AvTimer.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AvTimer.kt new file mode 100644 index 0000000..c323449 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AvTimer.kt @@ -0,0 +1,108 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.AvTimer: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.AvTimer", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(340.5f, 811.5f) + quadTo(275f, 783f, 226f, 734f) + reflectiveQuadToRelative(-77.5f, -114.5f) + quadTo(120f, 554f, 120f, 480f) + quadToRelative(0f, -78f, 30f, -146f) + reflectiveQuadToRelative(84f, -117f) + quadToRelative(12f, -11f, 28.5f, -10.5f) + reflectiveQuadTo(290f, 218f) + lineToRelative(218f, 218f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(264f, 304f) + quadToRelative(-30f, 36f, -47f, 80.5f) + reflectiveQuadTo(200f, 480f) + quadToRelative(0f, 116f, 82f, 198f) + reflectiveQuadToRelative(198f, 82f) + quadToRelative(116f, 0f, 198f, -82f) + reflectiveQuadToRelative(82f, -198f) + quadToRelative(0f, -107f, -68.5f, -184.5f) + reflectiveQuadTo(520f, 204f) + verticalLineToRelative(36f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 240f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 120f) + quadToRelative(74f, 0f, 139.5f, 28.5f) + reflectiveQuadTo(734f, 226f) + quadToRelative(49f, 49f, 77.5f, 114.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 74f, -28.5f, 139.5f) + reflectiveQuadTo(734f, 734f) + quadToRelative(-49f, 49f, -114.5f, 77.5f) + reflectiveQuadTo(480f, 840f) + quadToRelative(-74f, 0f, -139.5f, -28.5f) + close() + moveTo(251.5f, 508.5f) + quadTo(240f, 497f, 240f, 480f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(263f, 440f, 280f, 440f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(320f, 463f, 320f, 480f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(297f, 520f, 280f, 520f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + moveTo(451.5f, 708.5f) + quadTo(440f, 697f, 440f, 680f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(463f, 640f, 480f, 640f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(520f, 663f, 520f, 680f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(497f, 720f, 480f, 720f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + moveTo(651.5f, 508.5f) + quadTo(640f, 497f, 640f, 480f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(663f, 440f, 680f, 440f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(720f, 463f, 720f, 480f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(697f, 520f, 680f, 520f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BackgroundColor.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BackgroundColor.kt new file mode 100644 index 0000000..230dca9 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BackgroundColor.kt @@ -0,0 +1,233 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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.287f, 4.712f) + 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) + reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(7.287f, 4.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) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(11.288f, 4.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(15.288f, 4.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) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(19.288f, 4.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) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(7.287f, 20.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) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(3.287f, 8.712f) + 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) + reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(3.287f, 12.712f) + 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) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(3.287f, 16.712f) + 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) + reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(3.287f, 20.712f) + 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) + 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(19.288f, 8.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) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(15.94f, 21f) + curveToRelative(-0.683f, 0f, -1.329f, -0.131f, -1.938f, -0.394f) + reflectiveCurveToRelative(-1.14f, -0.621f, -1.594f, -1.075f) + reflectiveCurveToRelative(-0.813f, -0.985f, -1.075f, -1.594f) + reflectiveCurveToRelative(-0.394f, -1.254f, -0.394f, -1.938f) + curveToRelative(0f, -0.692f, 0.135f, -1.342f, 0.406f, -1.95f) + curveToRelative(0.271f, -0.608f, 0.637f, -1.138f, 1.1f, -1.587f) + reflectiveCurveToRelative(1.002f, -0.806f, 1.619f, -1.069f) + reflectiveCurveToRelative(1.275f, -0.394f, 1.975f, -0.394f) + curveToRelative(0.667f, 0f, 1.296f, 0.115f, 1.888f, 0.344f) + reflectiveCurveToRelative(1.11f, 0.546f, 1.556f, 0.95f) + reflectiveCurveToRelative(0.8f, 0.883f, 1.063f, 1.438f) + reflectiveCurveToRelative(0.394f, 1.152f, 0.394f, 1.794f) + curveToRelative(0f, 0.958f, -0.292f, 1.694f, -0.875f, 2.206f) + reflectiveCurveToRelative(-1.292f, 0.769f, -2.125f, 0.769f) + horizontalLineToRelative(-0.925f) + curveToRelative(-0.075f, 0f, -0.127f, 0.021f, -0.156f, 0.063f) + reflectiveCurveToRelative(-0.044f, 0.087f, -0.044f, 0.138f) + curveToRelative(0f, 0.1f, 0.063f, 0.244f, 0.188f, 0.431f) + reflectiveCurveToRelative(0.188f, 0.402f, 0.188f, 0.644f) + curveToRelative(0f, 0.417f, -0.115f, 0.725f, -0.344f, 0.925f) + reflectiveCurveToRelative(-0.531f, 0.3f, -0.906f, 0.3f) + close() + moveTo(13.727f, 16.288f) + curveToRelative(0.142f, -0.142f, 0.213f, -0.321f, 0.213f, -0.538f) + curveToRelative(0f, -0.217f, -0.071f, -0.396f, -0.213f, -0.538f) + reflectiveCurveToRelative(-0.321f, -0.213f, -0.538f, -0.213f) + curveToRelative(-0.217f, 0f, -0.396f, 0.071f, -0.538f, 0.213f) + reflectiveCurveToRelative(-0.213f, 0.321f, -0.213f, 0.538f) + curveToRelative(0f, 0.217f, 0.071f, 0.396f, 0.213f, 0.538f) + reflectiveCurveToRelative(0.321f, 0.213f, 0.538f, 0.213f) + curveToRelative(0.217f, 0f, 0.396f, -0.071f, 0.538f, -0.213f) + close() + moveTo(15.227f, 14.287f) + curveToRelative(0.142f, -0.142f, 0.213f, -0.321f, 0.213f, -0.538f) + reflectiveCurveToRelative(-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) + reflectiveCurveToRelative(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) + close() + moveTo(17.727f, 14.287f) + curveToRelative(0.142f, -0.142f, 0.213f, -0.321f, 0.213f, -0.538f) + reflectiveCurveToRelative(-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) + reflectiveCurveToRelative(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) + close() + moveTo(19.227f, 16.288f) + curveToRelative(0.142f, -0.142f, 0.213f, -0.321f, 0.213f, -0.538f) + 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) + 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) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Badge.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Badge.kt new file mode 100644 index 0000000..48df50c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Badge.kt @@ -0,0 +1,127 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Badge: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Badge", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(240f, 720f) + horizontalLineToRelative(240f) + verticalLineToRelative(-18f) + quadToRelative(0f, -17f, -9.5f, -31.5f) + reflectiveQuadTo(444f, 648f) + quadToRelative(-20f, -9f, -40.5f, -13.5f) + reflectiveQuadTo(360f, 630f) + quadToRelative(-23f, 0f, -43.5f, 4.5f) + reflectiveQuadTo(276f, 648f) + quadToRelative(-17f, 8f, -26.5f, 22.5f) + reflectiveQuadTo(240f, 702f) + verticalLineToRelative(18f) + close() + moveTo(590f, 660f) + horizontalLineToRelative(100f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(720f, 630f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(690f, 600f) + lineTo(590f, 600f) + quadToRelative(-13f, 0f, -21.5f, 8.5f) + reflectiveQuadTo(560f, 630f) + quadToRelative(0f, 13f, 8.5f, 21.5f) + reflectiveQuadTo(590f, 660f) + close() + moveTo(360f, 600f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(420f, 540f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(360f, 480f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(300f, 540f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(360f, 600f) + close() + moveTo(590f, 540f) + horizontalLineToRelative(100f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(720f, 510f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(690f, 480f) + lineTo(590f, 480f) + quadToRelative(-13f, 0f, -21.5f, 8.5f) + reflectiveQuadTo(560f, 510f) + quadToRelative(0f, 13f, 8.5f, 21.5f) + reflectiveQuadTo(590f, 540f) + close() + moveTo(160f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(80f, 800f) + verticalLineToRelative(-440f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(160f, 280f) + horizontalLineToRelative(200f) + verticalLineToRelative(-120f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(440f, 80f) + horizontalLineToRelative(80f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(600f, 160f) + verticalLineToRelative(120f) + horizontalLineToRelative(200f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 360f) + verticalLineToRelative(440f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 880f) + lineTo(160f, 880f) + close() + moveTo(160f, 800f) + horizontalLineToRelative(640f) + verticalLineToRelative(-440f) + lineTo(600f, 360f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(520f, 440f) + horizontalLineToRelative(-80f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(360f, 360f) + lineTo(160f, 360f) + verticalLineToRelative(440f) + close() + moveTo(440f, 360f) + horizontalLineToRelative(80f) + verticalLineToRelative(-200f) + horizontalLineToRelative(-80f) + verticalLineToRelative(200f) + close() + moveTo(480f, 580f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BarChart.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BarChart.kt new file mode 100644 index 0000000..fda696e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BarChart.kt @@ -0,0 +1,80 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.BarChart: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.BarChart", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(680f, 800f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(640f, 760f) + verticalLineToRelative(-200f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(680f, 520f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 560f) + verticalLineToRelative(200f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 800f) + horizontalLineToRelative(-80f) + close() + moveTo(440f, 800f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(400f, 760f) + verticalLineToRelative(-560f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(440f, 160f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(560f, 200f) + verticalLineToRelative(560f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(520f, 800f) + horizontalLineToRelative(-80f) + close() + moveTo(200f, 800f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 760f) + verticalLineToRelative(-360f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 360f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(320f, 400f) + verticalLineToRelative(360f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(280f, 800f) + horizontalLineToRelative(-80f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BarcodeScanner.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BarcodeScanner.kt new file mode 100644 index 0000000..1c85c6f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BarcodeScanner.kt @@ -0,0 +1,180 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Base64.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Base64.kt new file mode 100644 index 0000000..1c6124f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Base64.kt @@ -0,0 +1,475 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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.Black)) { + moveTo(8.662f, 12.477f) + horizontalLineToRelative(1.429f) + verticalLineToRelative(1.429f) + horizontalLineToRelative(-1.429f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(20.889f, 13.111f) + horizontalLineToRelative(-1.111f) + verticalLineToRelative(-2.222f) + horizontalLineToRelative(1.111f) + curveToRelative(0.315f, 0f, 0.579f, -0.107f, 0.792f, -0.319f) + reflectiveCurveToRelative(0.319f, -0.477f, 0.319f, -0.792f) + reflectiveCurveToRelative(-0.107f, -0.579f, -0.319f, -0.792f) + curveToRelative(-0.213f, -0.213f, -0.477f, -0.319f, -0.792f, -0.319f) + horizontalLineToRelative(-1.111f) + verticalLineToRelative(-2.222f) + curveToRelative(0f, -0.611f, -0.218f, -1.134f, -0.653f, -1.569f) + reflectiveCurveToRelative(-0.958f, -0.653f, -1.569f, -0.653f) + horizontalLineToRelative(-2.222f) + verticalLineToRelative(-1.111f) + curveToRelative(0f, -0.315f, -0.106f, -0.579f, -0.319f, -0.792f) + curveToRelative(-0.213f, -0.213f, -0.477f, -0.319f, -0.792f, -0.319f) + reflectiveCurveToRelative(-0.579f, 0.107f, -0.792f, 0.319f) + reflectiveCurveToRelative(-0.319f, 0.477f, -0.319f, 0.792f) + verticalLineToRelative(1.111f) + horizontalLineToRelative(-2.222f) + verticalLineToRelative(-1.111f) + curveToRelative(0f, -0.315f, -0.107f, -0.579f, -0.319f, -0.792f) + reflectiveCurveToRelative(-0.477f, -0.319f, -0.792f, -0.319f) + reflectiveCurveToRelative(-0.579f, 0.107f, -0.792f, 0.319f) + curveToRelative(-0.213f, 0.213f, -0.319f, 0.477f, -0.319f, 0.792f) + verticalLineToRelative(1.111f) + horizontalLineToRelative(-2.222f) + curveToRelative(-0.611f, 0f, -1.134f, 0.218f, -1.569f, 0.653f) + reflectiveCurveToRelative(-0.653f, 0.958f, -0.653f, 1.569f) + verticalLineToRelative(2.222f) + horizontalLineToRelative(-1.111f) + curveToRelative(-0.315f, 0f, -0.579f, 0.106f, -0.792f, 0.319f) + curveToRelative(-0.213f, 0.213f, -0.319f, 0.477f, -0.319f, 0.792f) + reflectiveCurveToRelative(0.107f, 0.579f, 0.319f, 0.792f) + reflectiveCurveToRelative(0.477f, 0.319f, 0.792f, 0.319f) + horizontalLineToRelative(1.111f) + verticalLineToRelative(2.222f) + horizontalLineToRelative(-1.111f) + curveToRelative(-0.315f, 0f, -0.579f, 0.107f, -0.792f, 0.319f) + reflectiveCurveToRelative(-0.319f, 0.477f, -0.319f, 0.792f) + reflectiveCurveToRelative(0.107f, 0.579f, 0.319f, 0.792f) + curveToRelative(0.213f, 0.213f, 0.477f, 0.319f, 0.792f, 0.319f) + horizontalLineToRelative(1.111f) + verticalLineToRelative(2.222f) + curveToRelative(0f, 0.611f, 0.218f, 1.134f, 0.653f, 1.569f) + reflectiveCurveToRelative(0.958f, 0.653f, 1.569f, 0.653f) + horizontalLineToRelative(2.222f) + verticalLineToRelative(1.111f) + curveToRelative(0f, 0.315f, 0.106f, 0.579f, 0.319f, 0.792f) + curveToRelative(0.213f, 0.213f, 0.477f, 0.319f, 0.792f, 0.319f) + reflectiveCurveToRelative(0.579f, -0.107f, 0.792f, -0.319f) + reflectiveCurveToRelative(0.319f, -0.477f, 0.319f, -0.792f) + verticalLineToRelative(-1.111f) + horizontalLineToRelative(2.222f) + verticalLineToRelative(1.111f) + curveToRelative(0f, 0.315f, 0.107f, 0.579f, 0.319f, 0.792f) + reflectiveCurveToRelative(0.477f, 0.319f, 0.792f, 0.319f) + reflectiveCurveToRelative(0.579f, -0.107f, 0.792f, -0.319f) + curveToRelative(0.213f, -0.213f, 0.319f, -0.477f, 0.319f, -0.792f) + verticalLineToRelative(-1.111f) + horizontalLineToRelative(2.222f) + curveToRelative(0.611f, 0f, 1.134f, -0.218f, 1.569f, -0.653f) + reflectiveCurveToRelative(0.653f, -0.958f, 0.653f, -1.569f) + verticalLineToRelative(-2.222f) + horizontalLineToRelative(1.111f) + curveToRelative(0.315f, 0f, 0.579f, -0.106f, 0.792f, -0.319f) + curveToRelative(0.213f, -0.213f, 0.319f, -0.477f, 0.319f, -0.792f) + reflectiveCurveToRelative(-0.107f, -0.579f, -0.319f, -0.792f) + reflectiveCurveToRelative(-0.477f, -0.319f, -0.792f, -0.319f) + close() + moveTo(10.568f, 11.524f) + curveToRelative(0.27f, 0f, 0.496f, 0.091f, 0.679f, 0.274f) + curveToRelative(0.183f, 0.183f, 0.274f, 0.409f, 0.274f, 0.679f) + verticalLineToRelative(1.429f) + curveToRelative(0f, 0.27f, -0.091f, 0.496f, -0.274f, 0.679f) + curveToRelative(-0.183f, 0.183f, -0.409f, 0.274f, -0.679f, 0.274f) + horizontalLineToRelative(-2.382f) + curveToRelative(-0.27f, 0f, -0.496f, -0.091f, -0.679f, -0.274f) + curveToRelative(-0.183f, -0.183f, -0.274f, -0.409f, -0.274f, -0.679f) + verticalLineToRelative(-3.811f) + curveToRelative(0f, -0.27f, 0.091f, -0.496f, 0.274f, -0.679f) + curveToRelative(0.183f, -0.183f, 0.409f, -0.274f, 0.679f, -0.274f) + horizontalLineToRelative(2.62f) + curveToRelative(0.206f, 0f, 0.377f, 0.068f, 0.512f, 0.203f) + curveToRelative(0.135f, 0.135f, 0.202f, 0.306f, 0.202f, 0.512f) + reflectiveCurveToRelative(-0.068f, 0.377f, -0.202f, 0.512f) + reflectiveCurveToRelative(-0.306f, 0.203f, -0.512f, 0.203f) + horizontalLineToRelative(-2.144f) + verticalLineToRelative(0.953f) + horizontalLineToRelative(1.906f) + close() + moveTo(16.765f, 11.682f) + verticalLineToRelative(2.461f) + curveToRelative(0f, 0.207f, -0.068f, 0.377f, -0.203f, 0.512f) + curveToRelative(-0.135f, 0.135f, -0.306f, 0.203f, -0.512f, 0.203f) + curveToRelative(-0.206f, 0f, -0.377f, -0.068f, -0.512f, -0.203f) + curveToRelative(-0.135f, -0.135f, -0.203f, -0.306f, -0.203f, -0.512f) + verticalLineToRelative(-0.715f) + horizontalLineToRelative(-2.145f) + curveToRelative(-0.206f, 0f, -0.377f, -0.068f, -0.512f, -0.203f) + curveToRelative(-0.135f, -0.135f, -0.203f, -0.306f, -0.203f, -0.512f) + verticalLineToRelative(-2.859f) + curveToRelative(0f, -0.207f, 0.068f, -0.377f, 0.203f, -0.512f) + curveToRelative(0.135f, -0.135f, 0.306f, -0.203f, 0.512f, -0.203f) + curveToRelative(0.207f, 0f, 0.377f, 0.068f, 0.512f, 0.203f) + curveToRelative(0.135f, 0.135f, 0.203f, 0.306f, 0.203f, 0.512f) + verticalLineToRelative(2.145f) + horizontalLineToRelative(1.43f) + verticalLineToRelative(-2.145f) + curveToRelative(0f, -0.207f, 0.068f, -0.377f, 0.203f, -0.512f) + curveToRelative(0.135f, -0.135f, 0.306f, -0.203f, 0.512f, -0.203f) + curveToRelative(0.207f, 0f, 0.377f, 0.068f, 0.512f, 0.203f) + curveToRelative(0.135f, 0.135f, 0.203f, 0.306f, 0.203f, 0.512f) + verticalLineToRelative(1.828f) + 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.Black)) { + moveTo(10.568f, 11.524f) + horizontalLineToRelative(-1.906f) + verticalLineToRelative(-0.953f) + horizontalLineToRelative(2.144f) + curveToRelative(0.206f, 0f, 0.377f, -0.068f, 0.512f, -0.202f) + reflectiveCurveToRelative(0.202f, -0.306f, 0.202f, -0.512f) + reflectiveCurveToRelative(-0.068f, -0.377f, -0.202f, -0.512f) + reflectiveCurveToRelative(-0.306f, -0.202f, -0.512f, -0.202f) + horizontalLineToRelative(-2.62f) + curveToRelative(-0.27f, 0f, -0.496f, 0.091f, -0.679f, 0.274f) + curveToRelative(-0.183f, 0.183f, -0.274f, 0.409f, -0.274f, 0.679f) + verticalLineToRelative(3.811f) + curveToRelative(0f, 0.27f, 0.091f, 0.496f, 0.274f, 0.679f) + curveToRelative(0.183f, 0.183f, 0.409f, 0.274f, 0.679f, 0.274f) + horizontalLineToRelative(2.382f) + curveToRelative(0.27f, 0f, 0.496f, -0.091f, 0.679f, -0.274f) + curveToRelative(0.183f, -0.183f, 0.274f, -0.409f, 0.274f, -0.679f) + verticalLineToRelative(-1.429f) + curveToRelative(0f, -0.27f, -0.091f, -0.496f, -0.274f, -0.679f) + curveToRelative(-0.183f, -0.183f, -0.409f, -0.274f, -0.679f, -0.274f) + close() + moveTo(10.091f, 13.906f) + horizontalLineToRelative(-1.429f) + verticalLineToRelative(-1.429f) + horizontalLineToRelative(1.429f) + verticalLineToRelative(1.429f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(20.889f, 13.111f) + horizontalLineToRelative(-1.111f) + verticalLineToRelative(-2.222f) + horizontalLineToRelative(1.111f) + curveToRelative(0.315f, 0f, 0.579f, -0.107f, 0.792f, -0.319f) + reflectiveCurveToRelative(0.319f, -0.477f, 0.319f, -0.792f) + reflectiveCurveToRelative(-0.107f, -0.579f, -0.319f, -0.792f) + curveToRelative(-0.213f, -0.213f, -0.477f, -0.319f, -0.792f, -0.319f) + horizontalLineToRelative(-1.111f) + verticalLineToRelative(-2.222f) + curveToRelative(0f, -0.611f, -0.218f, -1.134f, -0.653f, -1.569f) + reflectiveCurveToRelative(-0.958f, -0.653f, -1.569f, -0.653f) + horizontalLineToRelative(-2.222f) + verticalLineToRelative(-1.111f) + curveToRelative(0f, -0.315f, -0.106f, -0.579f, -0.319f, -0.792f) + curveToRelative(-0.213f, -0.213f, -0.477f, -0.319f, -0.792f, -0.319f) + reflectiveCurveToRelative(-0.579f, 0.107f, -0.792f, 0.319f) + reflectiveCurveToRelative(-0.319f, 0.477f, -0.319f, 0.792f) + verticalLineToRelative(1.111f) + horizontalLineToRelative(-2.222f) + verticalLineToRelative(-1.111f) + curveToRelative(0f, -0.315f, -0.107f, -0.579f, -0.319f, -0.792f) + reflectiveCurveToRelative(-0.477f, -0.319f, -0.792f, -0.319f) + reflectiveCurveToRelative(-0.579f, 0.107f, -0.792f, 0.319f) + curveToRelative(-0.213f, 0.213f, -0.319f, 0.477f, -0.319f, 0.792f) + verticalLineToRelative(1.111f) + horizontalLineToRelative(-2.222f) + curveToRelative(-0.611f, 0f, -1.134f, 0.218f, -1.569f, 0.653f) + reflectiveCurveToRelative(-0.653f, 0.958f, -0.653f, 1.569f) + verticalLineToRelative(2.222f) + horizontalLineToRelative(-1.111f) + curveToRelative(-0.315f, 0f, -0.579f, 0.106f, -0.792f, 0.319f) + curveToRelative(-0.213f, 0.213f, -0.319f, 0.477f, -0.319f, 0.792f) + reflectiveCurveToRelative(0.107f, 0.579f, 0.319f, 0.792f) + reflectiveCurveToRelative(0.477f, 0.319f, 0.792f, 0.319f) + horizontalLineToRelative(1.111f) + verticalLineToRelative(2.222f) + horizontalLineToRelative(-1.111f) + curveToRelative(-0.315f, 0f, -0.579f, 0.107f, -0.792f, 0.319f) + reflectiveCurveToRelative(-0.319f, 0.477f, -0.319f, 0.792f) + reflectiveCurveToRelative(0.107f, 0.579f, 0.319f, 0.792f) + curveToRelative(0.213f, 0.213f, 0.477f, 0.319f, 0.792f, 0.319f) + horizontalLineToRelative(1.111f) + verticalLineToRelative(2.222f) + curveToRelative(0f, 0.611f, 0.218f, 1.134f, 0.653f, 1.569f) + reflectiveCurveToRelative(0.958f, 0.653f, 1.569f, 0.653f) + horizontalLineToRelative(2.222f) + verticalLineToRelative(1.111f) + curveToRelative(0f, 0.315f, 0.106f, 0.579f, 0.319f, 0.792f) + curveToRelative(0.213f, 0.213f, 0.477f, 0.319f, 0.792f, 0.319f) + reflectiveCurveToRelative(0.579f, -0.107f, 0.792f, -0.319f) + reflectiveCurveToRelative(0.319f, -0.477f, 0.319f, -0.792f) + verticalLineToRelative(-1.111f) + horizontalLineToRelative(2.222f) + verticalLineToRelative(1.111f) + curveToRelative(0f, 0.315f, 0.107f, 0.579f, 0.319f, 0.792f) + reflectiveCurveToRelative(0.477f, 0.319f, 0.792f, 0.319f) + reflectiveCurveToRelative(0.579f, -0.107f, 0.792f, -0.319f) + curveToRelative(0.213f, -0.213f, 0.319f, -0.477f, 0.319f, -0.792f) + verticalLineToRelative(-1.111f) + horizontalLineToRelative(2.222f) + curveToRelative(0.611f, 0f, 1.134f, -0.218f, 1.569f, -0.653f) + reflectiveCurveToRelative(0.653f, -0.958f, 0.653f, -1.569f) + verticalLineToRelative(-2.222f) + horizontalLineToRelative(1.111f) + curveToRelative(0.315f, 0f, 0.579f, -0.106f, 0.792f, -0.319f) + curveToRelative(0.213f, -0.213f, 0.319f, -0.477f, 0.319f, -0.792f) + reflectiveCurveToRelative(-0.107f, -0.579f, -0.319f, -0.792f) + reflectiveCurveToRelative(-0.477f, -0.319f, -0.792f, -0.319f) + close() + moveTo(17.852f, 17.234f) + verticalLineToRelative(0.001f) + curveToRelative(0f, 0.341f, -0.276f, 0.618f, -0.618f, 0.618f) + horizontalLineTo(6.766f) + 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.Black)) { + moveTo(16.765f, 11.999f) + verticalLineToRelative(-2.145f) + curveToRelative(0f, -0.207f, -0.068f, -0.377f, -0.203f, -0.512f) + curveToRelative(-0.135f, -0.135f, -0.306f, -0.203f, -0.512f, -0.203f) + curveToRelative(-0.206f, 0f, -0.377f, 0.068f, -0.512f, 0.203f) + curveToRelative(-0.135f, 0.135f, -0.203f, 0.306f, -0.203f, 0.512f) + verticalLineToRelative(2.145f) + horizontalLineToRelative(-1.43f) + verticalLineToRelative(-2.145f) + curveToRelative(0f, -0.207f, -0.068f, -0.377f, -0.203f, -0.512f) + curveToRelative(-0.135f, -0.135f, -0.306f, -0.203f, -0.512f, -0.203f) + curveToRelative(-0.206f, 0f, -0.377f, 0.068f, -0.512f, 0.203f) + curveToRelative(-0.135f, 0.135f, -0.203f, 0.306f, -0.203f, 0.512f) + verticalLineToRelative(2.859f) + curveToRelative(0f, 0.207f, 0.068f, 0.377f, 0.203f, 0.512f) + curveToRelative(0.135f, 0.135f, 0.306f, 0.203f, 0.512f, 0.203f) + horizontalLineToRelative(2.145f) + verticalLineToRelative(0.715f) + curveToRelative(0f, 0.207f, 0.068f, 0.377f, 0.203f, 0.512f) + curveToRelative(0.135f, 0.135f, 0.306f, 0.203f, 0.512f, 0.203f) + curveToRelative(0.207f, 0f, 0.377f, -0.068f, 0.512f, -0.203f) + curveToRelative(0.135f, -0.135f, 0.203f, -0.306f, 0.203f, -0.512f) + verticalLineToRelative(-2.145f) + 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.Black)) { + moveTo(10.568f, 11.524f) + horizontalLineToRelative(-1.906f) + verticalLineToRelative(-0.953f) + horizontalLineToRelative(2.144f) + curveToRelative(0.206f, 0f, 0.377f, -0.068f, 0.512f, -0.202f) + reflectiveCurveToRelative(0.202f, -0.306f, 0.202f, -0.512f) + reflectiveCurveToRelative(-0.068f, -0.377f, -0.202f, -0.512f) + reflectiveCurveToRelative(-0.306f, -0.202f, -0.512f, -0.202f) + horizontalLineToRelative(-2.62f) + curveToRelative(-0.27f, 0f, -0.496f, 0.091f, -0.679f, 0.274f) + curveToRelative(-0.183f, 0.183f, -0.274f, 0.409f, -0.274f, 0.679f) + verticalLineToRelative(3.811f) + curveToRelative(0f, 0.27f, 0.091f, 0.496f, 0.274f, 0.679f) + curveToRelative(0.183f, 0.183f, 0.409f, 0.274f, 0.679f, 0.274f) + horizontalLineToRelative(2.382f) + curveToRelative(0.27f, 0f, 0.496f, -0.091f, 0.679f, -0.274f) + curveToRelative(0.183f, -0.183f, 0.274f, -0.409f, 0.274f, -0.679f) + verticalLineToRelative(-1.429f) + curveToRelative(0f, -0.27f, -0.091f, -0.496f, -0.274f, -0.679f) + curveToRelative(-0.183f, -0.183f, -0.409f, -0.274f, -0.679f, -0.274f) + close() + moveTo(10.091f, 13.906f) + horizontalLineToRelative(-1.429f) + verticalLineToRelative(-1.429f) + horizontalLineToRelative(1.429f) + verticalLineToRelative(1.429f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(20.889f, 13.111f) + horizontalLineToRelative(-1.111f) + verticalLineToRelative(-2.222f) + horizontalLineToRelative(1.111f) + curveToRelative(0.315f, 0f, 0.579f, -0.107f, 0.792f, -0.319f) + reflectiveCurveToRelative(0.319f, -0.477f, 0.319f, -0.792f) + reflectiveCurveToRelative(-0.107f, -0.579f, -0.319f, -0.792f) + curveToRelative(-0.213f, -0.213f, -0.477f, -0.319f, -0.792f, -0.319f) + horizontalLineToRelative(-1.111f) + verticalLineToRelative(-2.222f) + curveToRelative(0f, -0.611f, -0.218f, -1.134f, -0.653f, -1.569f) + reflectiveCurveToRelative(-0.958f, -0.653f, -1.569f, -0.653f) + horizontalLineToRelative(-2.222f) + verticalLineToRelative(-1.111f) + curveToRelative(0f, -0.315f, -0.106f, -0.579f, -0.319f, -0.792f) + curveToRelative(-0.213f, -0.213f, -0.477f, -0.319f, -0.792f, -0.319f) + reflectiveCurveToRelative(-0.579f, 0.107f, -0.792f, 0.319f) + reflectiveCurveToRelative(-0.319f, 0.477f, -0.319f, 0.792f) + verticalLineToRelative(1.111f) + horizontalLineToRelative(-2.222f) + verticalLineToRelative(-1.111f) + curveToRelative(0f, -0.315f, -0.107f, -0.579f, -0.319f, -0.792f) + reflectiveCurveToRelative(-0.477f, -0.319f, -0.792f, -0.319f) + reflectiveCurveToRelative(-0.579f, 0.107f, -0.792f, 0.319f) + curveToRelative(-0.213f, 0.213f, -0.319f, 0.477f, -0.319f, 0.792f) + verticalLineToRelative(1.111f) + horizontalLineToRelative(-2.222f) + curveToRelative(-0.611f, 0f, -1.134f, 0.218f, -1.569f, 0.653f) + reflectiveCurveToRelative(-0.653f, 0.958f, -0.653f, 1.569f) + verticalLineToRelative(2.222f) + horizontalLineToRelative(-1.111f) + curveToRelative(-0.315f, 0f, -0.579f, 0.106f, -0.792f, 0.319f) + curveToRelative(-0.213f, 0.213f, -0.319f, 0.477f, -0.319f, 0.792f) + reflectiveCurveToRelative(0.107f, 0.579f, 0.319f, 0.792f) + reflectiveCurveToRelative(0.477f, 0.319f, 0.792f, 0.319f) + horizontalLineToRelative(1.111f) + verticalLineToRelative(2.222f) + horizontalLineToRelative(-1.111f) + curveToRelative(-0.315f, 0f, -0.579f, 0.107f, -0.792f, 0.319f) + reflectiveCurveToRelative(-0.319f, 0.477f, -0.319f, 0.792f) + reflectiveCurveToRelative(0.107f, 0.579f, 0.319f, 0.792f) + curveToRelative(0.213f, 0.213f, 0.477f, 0.319f, 0.792f, 0.319f) + horizontalLineToRelative(1.111f) + verticalLineToRelative(2.222f) + curveToRelative(0f, 0.611f, 0.218f, 1.134f, 0.653f, 1.569f) + reflectiveCurveToRelative(0.958f, 0.653f, 1.569f, 0.653f) + horizontalLineToRelative(2.222f) + verticalLineToRelative(1.111f) + curveToRelative(0f, 0.315f, 0.106f, 0.579f, 0.319f, 0.792f) + curveToRelative(0.213f, 0.213f, 0.477f, 0.319f, 0.792f, 0.319f) + reflectiveCurveToRelative(0.579f, -0.107f, 0.792f, -0.319f) + reflectiveCurveToRelative(0.319f, -0.477f, 0.319f, -0.792f) + verticalLineToRelative(-1.111f) + horizontalLineToRelative(2.222f) + verticalLineToRelative(1.111f) + curveToRelative(0f, 0.315f, 0.107f, 0.579f, 0.319f, 0.792f) + reflectiveCurveToRelative(0.477f, 0.319f, 0.792f, 0.319f) + reflectiveCurveToRelative(0.579f, -0.107f, 0.792f, -0.319f) + curveToRelative(0.213f, -0.213f, 0.319f, -0.477f, 0.319f, -0.792f) + verticalLineToRelative(-1.111f) + horizontalLineToRelative(2.222f) + curveToRelative(0.611f, 0f, 1.134f, -0.218f, 1.569f, -0.653f) + reflectiveCurveToRelative(0.653f, -0.958f, 0.653f, -1.569f) + verticalLineToRelative(-2.222f) + horizontalLineToRelative(1.111f) + curveToRelative(0.315f, 0f, 0.579f, -0.106f, 0.792f, -0.319f) + curveToRelative(0.213f, -0.213f, 0.319f, -0.477f, 0.319f, -0.792f) + reflectiveCurveToRelative(-0.107f, -0.579f, -0.319f, -0.792f) + reflectiveCurveToRelative(-0.477f, -0.319f, -0.792f, -0.319f) + close() + moveTo(17.852f, 17.234f) + verticalLineToRelative(0.001f) + curveToRelative(0f, 0.341f, -0.276f, 0.618f, -0.618f, 0.618f) + horizontalLineTo(6.766f) + 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.Black)) { + moveTo(16.765f, 11.999f) + verticalLineToRelative(-2.145f) + curveToRelative(0f, -0.207f, -0.068f, -0.377f, -0.203f, -0.512f) + curveToRelative(-0.135f, -0.135f, -0.306f, -0.203f, -0.512f, -0.203f) + curveToRelative(-0.206f, 0f, -0.377f, 0.068f, -0.512f, 0.203f) + curveToRelative(-0.135f, 0.135f, -0.203f, 0.306f, -0.203f, 0.512f) + verticalLineToRelative(2.145f) + horizontalLineToRelative(-1.43f) + verticalLineToRelative(-2.145f) + curveToRelative(0f, -0.207f, -0.068f, -0.377f, -0.203f, -0.512f) + curveToRelative(-0.135f, -0.135f, -0.306f, -0.203f, -0.512f, -0.203f) + curveToRelative(-0.206f, 0f, -0.377f, 0.068f, -0.512f, 0.203f) + curveToRelative(-0.135f, 0.135f, -0.203f, 0.306f, -0.203f, 0.512f) + verticalLineToRelative(2.859f) + curveToRelative(0f, 0.207f, 0.068f, 0.377f, 0.203f, 0.512f) + curveToRelative(0.135f, 0.135f, 0.306f, 0.203f, 0.512f, 0.203f) + horizontalLineToRelative(2.145f) + verticalLineToRelative(0.715f) + curveToRelative(0f, 0.207f, 0.068f, 0.377f, 0.203f, 0.512f) + curveToRelative(0.135f, 0.135f, 0.306f, 0.203f, 0.512f, 0.203f) + curveToRelative(0.207f, 0f, 0.377f, -0.068f, 0.512f, -0.203f) + curveToRelative(0.135f, -0.135f, 0.203f, -0.306f, 0.203f, -0.512f) + verticalLineToRelative(-2.145f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(17.852f, 17.234f) + verticalLineToRelative(0.001f) + curveToRelative(0f, 0.341f, -0.276f, 0.618f, -0.618f, 0.618f) + horizontalLineTo(6.766f) + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BatchPrediction.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BatchPrediction.kt new file mode 100644 index 0000000..7ed99e9 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BatchPrediction.kt @@ -0,0 +1,115 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(200f, 800f) + verticalLineToRelative(-400f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(280f, 320f) + horizontalLineToRelative(400f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(760f, 400f) + verticalLineToRelative(400f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(680f, 880f) + lineTo(280f, 880f) + close() + moveTo(280f, 800f) + horizontalLineToRelative(400f) + verticalLineToRelative(-400f) + lineTo(280f, 400f) + verticalLineToRelative(400f) + close() + moveTo(460f, 760f) + horizontalLineToRelative(40f) + quadToRelative(8f, 0f, 14f, -6f) + reflectiveQuadToRelative(6f, -14f) + quadToRelative(0f, -8f, -6f, -14f) + reflectiveQuadToRelative(-14f, -6f) + horizontalLineToRelative(-40f) + quadToRelative(-8f, 0f, -14f, 6f) + reflectiveQuadToRelative(-6f, 14f) + quadToRelative(0f, 8f, 6f, 14f) + reflectiveQuadToRelative(14f, 6f) + close() + moveTo(453f, 680f) + horizontalLineToRelative(54f) + quadToRelative(5f, 0f, 9f, -4f) + reflectiveQuadToRelative(5f, -9f) + quadToRelative(3f, -14f, 12f, -26.5f) + reflectiveQuadToRelative(17f, -25.5f) + quadToRelative(12f, -17f, 21f, -36.5f) + reflectiveQuadToRelative(9f, -40.5f) + quadToRelative(0f, -41f, -29f, -69.5f) + reflectiveQuadTo(480f, 440f) + quadToRelative(-42f, 0f, -71f, 28.5f) + reflectiveQuadTo(380f, 538f) + quadToRelative(0f, 21f, 9.5f, 40f) + reflectiveQuadToRelative(20.5f, 36f) + quadToRelative(8f, 13f, 16.5f, 26f) + reflectiveQuadToRelative(11.5f, 27f) + quadToRelative(2f, 5f, 6f, 9f) + reflectiveQuadToRelative(9f, 4f) + close() + moveTo(480f, 600f) + close() + moveTo(270f, 260f) + quadToRelative(-13f, 0f, -21.5f, -8.5f) + reflectiveQuadTo(240f, 230f) + quadToRelative(0f, -13f, 8.5f, -21.5f) + reflectiveQuadTo(270f, 200f) + horizontalLineToRelative(420f) + quadToRelative(13f, 0f, 21.5f, 8.5f) + reflectiveQuadTo(720f, 230f) + quadToRelative(0f, 13f, -8.5f, 21.5f) + reflectiveQuadTo(690f, 260f) + lineTo(270f, 260f) + close() + moveTo(310f, 140f) + quadToRelative(-13f, 0f, -21.5f, -8.5f) + reflectiveQuadTo(280f, 110f) + quadToRelative(0f, -13f, 8.5f, -21.5f) + reflectiveQuadTo(310f, 80f) + horizontalLineToRelative(340f) + quadToRelative(13f, 0f, 21.5f, 8.5f) + reflectiveQuadTo(680f, 110f) + quadToRelative(0f, 13f, -8.5f, 21.5f) + reflectiveQuadTo(650f, 140f) + lineTo(310f, 140f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Beta.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Beta.kt new file mode 100644 index 0000000..0dba444 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Beta.kt @@ -0,0 +1,72 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Bitcoin.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Bitcoin.kt new file mode 100644 index 0000000..bad08bc --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Bitcoin.kt @@ -0,0 +1,89 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Blender.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Blender.kt new file mode 100644 index 0000000..bc9ee1f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Blender.kt @@ -0,0 +1,90 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Blender: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Blender", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(320f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(240f, 800f) + verticalLineToRelative(-40f) + quadToRelative(0f, -47f, 20.5f, -87f) + reflectiveQuadToRelative(53.5f, -67f) + lineToRelative(-25f, -166f) + horizontalLineToRelative(-89f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 360f) + verticalLineToRelative(-160f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 120f) + horizontalLineToRelative(200f) + quadToRelative(11f, -11f, 17.5f, -25.5f) + reflectiveQuadTo(440f, 80f) + horizontalLineToRelative(80f) + quadToRelative(16f, 0f, 22.5f, 14.5f) + reflectiveQuadTo(560f, 120f) + horizontalLineToRelative(112f) + quadToRelative(18f, 0f, 30.5f, 14f) + reflectiveQuadToRelative(9.5f, 32f) + lineToRelative(-66f, 440f) + quadToRelative(33f, 27f, 53.5f, 67f) + reflectiveQuadToRelative(20.5f, 87f) + verticalLineToRelative(40f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(640f, 880f) + lineTo(320f, 880f) + close() + moveTo(277f, 360f) + lineTo(253f, 200f) + horizontalLineToRelative(-53f) + verticalLineToRelative(160f) + horizontalLineToRelative(77f) + close() + moveTo(480f, 760f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 720f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 680f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 720f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 760f) + close() + moveTo(388f, 560f) + horizontalLineToRelative(184f) + lineToRelative(54f, -360f) + lineTo(334f, 200f) + lineToRelative(54f, 360f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Block.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Block.kt new file mode 100644 index 0000000..99eb27b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Block.kt @@ -0,0 +1,139 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} + +val Icons.Rounded.Block: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Block", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(54f, 0f, 104f, -17.5f) + reflectiveQuadToRelative(92f, -50.5f) + lineTo(228f, 284f) + quadToRelative(-33f, 42f, -50.5f, 92f) + reflectiveQuadTo(160f, 480f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + close() + moveTo(732f, 676f) + quadToRelative(33f, -42f, 50.5f, -92f) + reflectiveQuadTo(800f, 480f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-54f, 0f, -104f, 17.5f) + reflectiveQuadTo(284f, 228f) + lineToRelative(448f, 448f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BlurCircular.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BlurCircular.kt new file mode 100644 index 0000000..a400311 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BlurCircular.kt @@ -0,0 +1,339 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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() +} + +val Icons.Outlined.BlurCircular: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.BlurCircular", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(428.5f, 428.5f) + quadTo(440f, 417f, 440f, 400f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(417f, 360f, 400f, 360f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(360f, 383f, 360f, 400f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(383f, 440f, 400f, 440f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(428.5f, 588.5f) + quadTo(440f, 577f, 440f, 560f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(417f, 520f, 400f, 520f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(360f, 543f, 360f, 560f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(383f, 600f, 400f, 600f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(294f, 414f) + quadToRelative(6f, -6f, 6f, -14f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(-6f, -6f, -14f, -6f) + reflectiveQuadToRelative(-14f, 6f) + quadToRelative(-6f, 6f, -6f, 14f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + close() + moveTo(400f, 700f) + quadToRelative(8f, 0f, 14f, -6f) + reflectiveQuadToRelative(6f, -14f) + quadToRelative(0f, -8f, -6f, -14f) + reflectiveQuadToRelative(-14f, -6f) + quadToRelative(-8f, 0f, -14f, 6f) + reflectiveQuadToRelative(-6f, 14f) + quadToRelative(0f, 8f, 6f, 14f) + reflectiveQuadToRelative(14f, 6f) + close() + moveTo(294f, 574f) + quadToRelative(6f, -6f, 6f, -14f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(-6f, -6f, -14f, -6f) + reflectiveQuadToRelative(-14f, 6f) + quadToRelative(-6f, 6f, -6f, 14f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + close() + moveTo(414f, 294f) + quadToRelative(6f, -6f, 6f, -14f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(-6f, -6f, -14f, -6f) + reflectiveQuadToRelative(-14f, 6f) + quadToRelative(-6f, 6f, -6f, 14f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + close() + moveTo(588.5f, 428.5f) + quadTo(600f, 417f, 600f, 400f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(577f, 360f, 560f, 360f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(520f, 383f, 520f, 400f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(543f, 440f, 560f, 440f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(574f, 294f) + quadToRelative(6f, -6f, 6f, -14f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(-6f, -6f, -14f, -6f) + reflectiveQuadToRelative(-14f, 6f) + quadToRelative(-6f, 6f, -6f, 14f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + close() + moveTo(694f, 574f) + quadToRelative(6f, -6f, 6f, -14f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(-6f, -6f, -14f, -6f) + reflectiveQuadToRelative(-14f, 6f) + quadToRelative(-6f, 6f, -6f, 14f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + close() + moveTo(694f, 414f) + quadToRelative(6f, -6f, 6f, -14f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(-6f, -6f, -14f, -6f) + reflectiveQuadToRelative(-14f, 6f) + quadToRelative(-6f, 6f, -6f, 14f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + close() + moveTo(324f, 848.5f) + quadTo(251f, 817f, 197f, 763f) + reflectiveQuadToRelative(-85.5f, -127f) + quadTo(80f, 563f, 80f, 480f) + reflectiveQuadToRelative(31.5f, -156f) + quadTo(143f, 251f, 197f, 197f) + reflectiveQuadToRelative(127f, -85.5f) + quadTo(397f, 80f, 480f, 80f) + reflectiveQuadToRelative(156f, 31.5f) + quadTo(709f, 143f, 763f, 197f) + reflectiveQuadToRelative(85.5f, 127f) + quadTo(880f, 397f, 880f, 480f) + reflectiveQuadToRelative(-31.5f, 156f) + quadTo(817f, 709f, 763f, 763f) + reflectiveQuadToRelative(-127f, 85.5f) + quadTo(563f, 880f, 480f, 880f) + reflectiveQuadToRelative(-156f, -31.5f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + close() + moveTo(574f, 694f) + quadToRelative(6f, -6f, 6f, -14f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(-6f, -6f, -14f, -6f) + reflectiveQuadToRelative(-14f, 6f) + quadToRelative(-6f, 6f, -6f, 14f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + close() + moveTo(588.5f, 588.5f) + quadTo(600f, 577f, 600f, 560f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(577f, 520f, 560f, 520f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(520f, 543f, 520f, 560f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(543f, 600f, 560f, 600f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BlurLinear.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BlurLinear.kt new file mode 100644 index 0000000..c3bb6df --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BlurLinear.kt @@ -0,0 +1,186 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.BlurLinear: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.BlurLinear", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(243f, 683f) + quadToRelative(17f, -17f, 17f, -43f) + reflectiveQuadToRelative(-17f, -43f) + quadToRelative(-17f, -17f, -43f, -17f) + reflectiveQuadToRelative(-43f, 17f) + quadToRelative(-17f, 17f, -17f, 43f) + reflectiveQuadToRelative(17f, 43f) + quadToRelative(17f, 17f, 43f, 17f) + reflectiveQuadToRelative(43f, -17f) + close() + moveTo(388.5f, 508.5f) + quadTo(400f, 497f, 400f, 480f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(377f, 440f, 360f, 440f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(320f, 463f, 320f, 480f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(343f, 520f, 360f, 520f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(388.5f, 348.5f) + quadTo(400f, 337f, 400f, 320f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(377f, 280f, 360f, 280f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(320f, 303f, 320f, 320f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(343f, 360f, 360f, 360f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(800f, 840f) + lineTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 760f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + close() + moveTo(243f, 363f) + quadToRelative(17f, -17f, 17f, -43f) + reflectiveQuadToRelative(-17f, -43f) + quadToRelative(-17f, -17f, -43f, -17f) + reflectiveQuadToRelative(-43f, 17f) + quadToRelative(-17f, 17f, -17f, 43f) + reflectiveQuadToRelative(17f, 43f) + quadToRelative(17f, 17f, 43f, 17f) + reflectiveQuadToRelative(43f, -17f) + close() + moveTo(243f, 523f) + quadToRelative(17f, -17f, 17f, -43f) + reflectiveQuadToRelative(-17f, -43f) + quadToRelative(-17f, -17f, -43f, -17f) + reflectiveQuadToRelative(-43f, 17f) + quadToRelative(-17f, 17f, -17f, 43f) + reflectiveQuadToRelative(17f, 43f) + quadToRelative(17f, 17f, 43f, 17f) + reflectiveQuadToRelative(43f, -17f) + close() + moveTo(388.5f, 668.5f) + quadTo(400f, 657f, 400f, 640f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(377f, 600f, 360f, 600f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(320f, 623f, 320f, 640f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(343f, 680f, 360f, 680f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(694.5f, 654.5f) + quadTo(700f, 649f, 700f, 640f) + reflectiveQuadToRelative(-5.5f, -14.5f) + quadTo(689f, 620f, 680f, 620f) + reflectiveQuadToRelative(-14.5f, 5.5f) + quadTo(660f, 631f, 660f, 640f) + reflectiveQuadToRelative(5.5f, 14.5f) + quadTo(671f, 660f, 680f, 660f) + reflectiveQuadToRelative(14.5f, -5.5f) + close() + moveTo(800f, 200f) + lineTo(160f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 200f) + close() + moveTo(694.5f, 334.5f) + quadTo(700f, 329f, 700f, 320f) + reflectiveQuadToRelative(-5.5f, -14.5f) + quadTo(689f, 300f, 680f, 300f) + reflectiveQuadToRelative(-14.5f, 5.5f) + quadTo(660f, 311f, 660f, 320f) + reflectiveQuadToRelative(5.5f, 14.5f) + quadTo(671f, 340f, 680f, 340f) + reflectiveQuadToRelative(14.5f, -5.5f) + close() + moveTo(694.5f, 494.5f) + quadTo(700f, 489f, 700f, 480f) + reflectiveQuadToRelative(-5.5f, -14.5f) + quadTo(689f, 460f, 680f, 460f) + reflectiveQuadToRelative(-14.5f, 5.5f) + quadTo(660f, 471f, 660f, 480f) + reflectiveQuadToRelative(5.5f, 14.5f) + quadTo(671f, 500f, 680f, 500f) + reflectiveQuadToRelative(14.5f, -5.5f) + close() + moveTo(548.5f, 348.5f) + quadTo(560f, 337f, 560f, 320f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(537f, 280f, 520f, 280f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(480f, 303f, 480f, 320f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(503f, 360f, 520f, 360f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(548.5f, 508.5f) + quadTo(560f, 497f, 560f, 480f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(537f, 440f, 520f, 440f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(480f, 463f, 480f, 480f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(503f, 520f, 520f, 520f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(548.5f, 668.5f) + quadTo(560f, 657f, 560f, 640f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(537f, 600f, 520f, 600f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(480f, 623f, 480f, 640f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(503f, 680f, 520f, 680f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(120f, 760f) + verticalLineToRelative(-560f) + verticalLineToRelative(560f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BoldLine.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BoldLine.kt new file mode 100644 index 0000000..4e4f71e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BoldLine.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Bolt.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Bolt.kt new file mode 100644 index 0000000..8cc8c42 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Bolt.kt @@ -0,0 +1,155 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} + +val Icons.Rounded.Bolt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Bolt", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(360f, 600f) + lineTo(236f, 600f) + quadToRelative(-24f, 0f, -35.5f, -21.5f) + reflectiveQuadTo(203f, 537f) + lineToRelative(299f, -430f) + quadToRelative(10f, -14f, 26f, -19.5f) + reflectiveQuadToRelative(33f, 0.5f) + quadToRelative(17f, 6f, 25f, 21f) + reflectiveQuadToRelative(6f, 32f) + lineToRelative(-32f, 259f) + horizontalLineToRelative(155f) + quadToRelative(26f, 0f, 36.5f, 23f) + reflectiveQuadToRelative(-6.5f, 43f) + lineTo(416f, 860f) + quadToRelative(-11f, 13f, -27f, 17f) + reflectiveQuadToRelative(-31f, -3f) + quadToRelative(-15f, -7f, -23.5f, -21.5f) + reflectiveQuadTo(328f, 821f) + lineToRelative(32f, -221f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Book2.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Book2.kt new file mode 100644 index 0000000..796225a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Book2.kt @@ -0,0 +1,92 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +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.Book2: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Book2", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(240f, 613f) + quadToRelative(14f, -7f, 29f, -10f) + reflectiveQuadToRelative(31f, -3f) + horizontalLineToRelative(20f) + verticalLineToRelative(-440f) + horizontalLineToRelative(-20f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(240f, 220f) + verticalLineToRelative(393f) + close() + moveTo(400f, 600f) + horizontalLineToRelative(320f) + verticalLineToRelative(-440f) + lineTo(400f, 160f) + verticalLineToRelative(440f) + close() + moveTo(240f, 613f) + verticalLineToRelative(-453f) + verticalLineToRelative(453f) + close() + moveTo(300f, 880f) + quadToRelative(-58f, 0f, -99f, -41f) + reflectiveQuadToRelative(-41f, -99f) + verticalLineToRelative(-520f) + quadToRelative(0f, -58f, 41f, -99f) + reflectiveQuadToRelative(99f, -41f) + horizontalLineToRelative(420f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(800f, 160f) + verticalLineToRelative(501f) + quadToRelative(0f, 8f, -6.5f, 14.5f) + reflectiveQuadTo(770f, 690f) + quadToRelative(-14f, 7f, -22f, 20f) + reflectiveQuadToRelative(-8f, 30f) + quadToRelative(0f, 17f, 8f, 30.5f) + reflectiveQuadToRelative(22f, 19.5f) + quadToRelative(14f, 6f, 22f, 16.5f) + reflectiveQuadToRelative(8f, 22.5f) + verticalLineToRelative(10f) + quadToRelative(0f, 17f, -11.5f, 29f) + reflectiveQuadTo(760f, 880f) + lineTo(300f, 880f) + close() + moveTo(300f, 800f) + horizontalLineToRelative(373f) + quadToRelative(-6f, -14f, -9.5f, -28.5f) + reflectiveQuadTo(660f, 740f) + quadToRelative(0f, -16f, 3f, -31f) + reflectiveQuadToRelative(10f, -29f) + lineTo(300f, 680f) + quadToRelative(-26f, 0f, -43f, 17.5f) + reflectiveQuadTo(240f, 740f) + quadToRelative(0f, 26f, 17f, 43f) + reflectiveQuadToRelative(43f, 17f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Bookmark.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Bookmark.kt new file mode 100644 index 0000000..6bb93b2 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Bookmark.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Bookmark: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Bookmark", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(480f, 720f) + lineToRelative(-168f, 72f) + quadToRelative(-40f, 17f, -76f, -6.5f) + reflectiveQuadTo(200f, 719f) + verticalLineToRelative(-519f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(280f, 120f) + horizontalLineToRelative(400f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(760f, 200f) + verticalLineToRelative(519f) + quadToRelative(0f, 43f, -36f, 66.5f) + reflectiveQuadToRelative(-76f, 6.5f) + lineToRelative(-168f, -72f) + close() + moveTo(480f, 632f) + lineTo(680f, 718f) + verticalLineToRelative(-518f) + lineTo(280f, 200f) + verticalLineToRelative(518f) + lineToRelative(200f, -86f) + close() + moveTo(480f, 200f) + lineTo(280f, 200f) + horizontalLineToRelative(400f) + horizontalLineToRelative(-200f) + close() + } + }.build() +} + +val Icons.Rounded.Bookmark: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Bookmark", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(480f, 720f) + lineToRelative(-168f, 72f) + quadToRelative(-40f, 17f, -76f, -6.5f) + reflectiveQuadTo(200f, 719f) + verticalLineToRelative(-519f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(280f, 120f) + horizontalLineToRelative(400f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(760f, 200f) + verticalLineToRelative(519f) + quadToRelative(0f, 43f, -36f, 66.5f) + reflectiveQuadToRelative(-76f, 6.5f) + lineToRelative(-168f, -72f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BookmarkOff.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BookmarkOff.kt new file mode 100644 index 0000000..648653b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BookmarkOff.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BookmarkRemove.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BookmarkRemove.kt new file mode 100644 index 0000000..12830a7 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BookmarkRemove.kt @@ -0,0 +1,73 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.BookmarkRemove: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.BookmarkRemove", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(640f, 280f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 240f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 200f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 240f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 280f) + lineTo(640f, 280f) + close() + moveTo(480f, 720f) + lineToRelative(-168f, 72f) + quadToRelative(-40f, 17f, -76f, -6.5f) + reflectiveQuadTo(200f, 719f) + verticalLineToRelative(-519f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(280f, 120f) + horizontalLineToRelative(225f) + quadToRelative(18f, 0f, 27f, 16f) + reflectiveQuadToRelative(1f, 33f) + quadToRelative(-7f, 17f, -10f, 34f) + reflectiveQuadToRelative(-3f, 37f) + quadToRelative(0f, 72f, 45.5f, 127f) + reflectiveQuadTo(680f, 436f) + quadToRelative(12f, 2f, 21.5f, 2.5f) + reflectiveQuadToRelative(18.5f, 0.5f) + quadToRelative(17f, 0f, 28.5f, 10.5f) + reflectiveQuadTo(760f, 476f) + verticalLineToRelative(243f) + quadToRelative(0f, 43f, -36f, 66.5f) + reflectiveQuadToRelative(-76f, 6.5f) + lineToRelative(-168f, -72f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Boosty.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Boosty.kt new file mode 100644 index 0000000..1808ef8 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Boosty.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderBottom.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderBottom.kt new file mode 100644 index 0000000..e134e46 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderBottom.kt @@ -0,0 +1,210 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.BorderBottom: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.BorderBottom", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 360f) + close() + moveTo(320f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 520f) + close() + moveTo(480f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 520f) + close() + moveTo(640f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 520f) + close() + moveTo(480f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 680f) + close() + moveTo(160f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 200f) + close() + moveTo(320f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 200f) + close() + moveTo(480f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 200f) + close() + moveTo(640f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 200f) + close() + moveTo(800f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 200f) + close() + moveTo(160f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 360f) + close() + moveTo(800f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 360f) + close() + moveTo(160f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 520f) + close() + moveTo(800f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 520f) + close() + moveTo(160f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 680f) + close() + moveTo(800f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 680f) + close() + moveTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 760f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + lineTo(160f, 840f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderColor.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderColor.kt new file mode 100644 index 0000000..ef0162b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderColor.kt @@ -0,0 +1,91 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderHorizontal.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderHorizontal.kt new file mode 100644 index 0000000..f9b3b23 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderHorizontal.kt @@ -0,0 +1,210 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.BorderHorizontal: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.BorderHorizontal", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 360f) + close() + moveTo(480f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 680f) + close() + moveTo(160f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 200f) + close() + moveTo(320f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 200f) + close() + moveTo(480f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 200f) + close() + moveTo(640f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 200f) + close() + moveTo(800f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 200f) + close() + moveTo(160f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 360f) + close() + moveTo(800f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 360f) + close() + moveTo(160f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 680f) + close() + moveTo(800f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 680f) + close() + moveTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 840f) + close() + moveTo(320f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 840f) + close() + moveTo(480f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 840f) + close() + moveTo(640f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 840f) + close() + moveTo(800f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + close() + moveTo(160f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 440f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 520f) + lineTo(160f, 520f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderLeft.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderLeft.kt new file mode 100644 index 0000000..81477b1 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderLeft.kt @@ -0,0 +1,210 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.BorderLeft: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.BorderLeft", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 360f) + close() + moveTo(320f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 520f) + close() + moveTo(480f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 520f) + close() + moveTo(640f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 520f) + close() + moveTo(480f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 680f) + close() + moveTo(320f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 200f) + close() + moveTo(480f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 200f) + close() + moveTo(640f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 200f) + close() + moveTo(800f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 200f) + close() + moveTo(800f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 360f) + close() + moveTo(800f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 520f) + close() + moveTo(800f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 680f) + close() + moveTo(320f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 840f) + close() + moveTo(480f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 840f) + close() + moveTo(640f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 840f) + close() + moveTo(800f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + close() + moveTo(120f, 800f) + verticalLineToRelative(-640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 160f) + verticalLineToRelative(640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderRight.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderRight.kt new file mode 100644 index 0000000..a54a4aa --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderRight.kt @@ -0,0 +1,210 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.BorderRight: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.BorderRight", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 360f) + close() + moveTo(320f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 520f) + close() + moveTo(480f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 520f) + close() + moveTo(640f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 520f) + close() + moveTo(480f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 680f) + close() + moveTo(160f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 200f) + close() + moveTo(320f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 200f) + close() + moveTo(480f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 200f) + close() + moveTo(640f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 200f) + close() + moveTo(160f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 360f) + close() + moveTo(160f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 520f) + close() + moveTo(160f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 680f) + close() + moveTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 840f) + close() + moveTo(320f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 840f) + close() + moveTo(480f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 840f) + close() + moveTo(640f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 840f) + close() + moveTo(760f, 800f) + verticalLineToRelative(-640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + verticalLineToRelative(640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 800f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderStyle.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderStyle.kt new file mode 100644 index 0000000..ab358fe --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderStyle.kt @@ -0,0 +1,124 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.BorderStyle: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.BorderStyle", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(800f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 360f) + close() + moveTo(800f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 520f) + close() + moveTo(800f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 680f) + close() + moveTo(320f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 840f) + close() + moveTo(480f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 840f) + close() + moveTo(640f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 840f) + close() + moveTo(800f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + close() + moveTo(120f, 800f) + verticalLineToRelative(-600f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 120f) + horizontalLineToRelative(600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 200f) + lineTo(200f, 200f) + verticalLineToRelative(600f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderTop.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderTop.kt new file mode 100644 index 0000000..ef47d31 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderTop.kt @@ -0,0 +1,210 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.BorderTop: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.BorderTop", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 360f) + close() + moveTo(320f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 520f) + close() + moveTo(480f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 520f) + close() + moveTo(640f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 520f) + close() + moveTo(480f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 680f) + close() + moveTo(160f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 360f) + close() + moveTo(800f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 360f) + close() + moveTo(160f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 520f) + close() + moveTo(800f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 520f) + close() + moveTo(160f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 680f) + close() + moveTo(800f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 680f) + close() + moveTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 840f) + close() + moveTo(320f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 840f) + close() + moveTo(480f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 840f) + close() + moveTo(640f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 840f) + close() + moveTo(800f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + close() + moveTo(160f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 200f) + lineTo(160f, 200f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderVertical.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderVertical.kt new file mode 100644 index 0000000..0eb4aaf --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderVertical.kt @@ -0,0 +1,210 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.BorderVertical: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.BorderVertical", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(320f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 520f) + close() + moveTo(640f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 520f) + close() + moveTo(160f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 200f) + close() + moveTo(320f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 200f) + close() + moveTo(640f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 200f) + close() + moveTo(800f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 200f) + close() + moveTo(160f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 360f) + close() + moveTo(800f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 360f) + close() + moveTo(160f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 520f) + close() + moveTo(800f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 520f) + close() + moveTo(160f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 680f) + close() + moveTo(800f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 680f) + close() + moveTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 840f) + close() + moveTo(320f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 840f) + close() + moveTo(640f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 840f) + close() + moveTo(800f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + close() + moveTo(440f, 800f) + verticalLineToRelative(-640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 160f) + verticalLineToRelative(640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 800f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Brick.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Brick.kt new file mode 100644 index 0000000..736ee58 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Brick.kt @@ -0,0 +1,192 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.Brick: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Brick", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(80f, 740f) + verticalLineToRelative(-360f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(140f, 320f) + horizontalLineToRelative(60f) + verticalLineToRelative(-100f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(260f, 160f) + horizontalLineToRelative(120f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(440f, 220f) + verticalLineToRelative(100f) + horizontalLineToRelative(80f) + verticalLineToRelative(-100f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(580f, 160f) + horizontalLineToRelative(120f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(760f, 220f) + verticalLineToRelative(100f) + horizontalLineToRelative(60f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(880f, 380f) + verticalLineToRelative(360f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(820f, 800f) + lineTo(140f, 800f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(80f, 740f) + close() + moveTo(160f, 720f) + horizontalLineToRelative(640f) + verticalLineToRelative(-320f) + lineTo(160f, 400f) + verticalLineToRelative(320f) + close() + moveTo(280f, 320f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + close() + moveTo(600f, 320f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + close() + moveTo(160f, 720f) + horizontalLineToRelative(640f) + horizontalLineToRelative(-640f) + close() + moveTo(280f, 320f) + horizontalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(600f, 320f) + horizontalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + } + }.build() +} + +val Icons.TwoTone.Brick: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.Brick", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(21.563f, 8.438f) + curveToRelative(-0.292f, -0.292f, -0.646f, -0.438f, -1.063f, -0.438f) + horizontalLineToRelative(-1.5f) + verticalLineToRelative(-2.5f) + curveToRelative(0f, -0.417f, -0.146f, -0.771f, -0.438f, -1.063f) + reflectiveCurveToRelative(-0.646f, -0.438f, -1.063f, -0.438f) + horizontalLineToRelative(-3f) + curveToRelative(-0.417f, 0f, -0.771f, 0.146f, -1.063f, 0.438f) + reflectiveCurveToRelative(-0.438f, 0.646f, -0.438f, 1.063f) + verticalLineToRelative(2.5f) + horizontalLineToRelative(-2f) + verticalLineToRelative(-2.5f) + curveToRelative(0f, -0.417f, -0.146f, -0.771f, -0.438f, -1.063f) + reflectiveCurveToRelative(-0.646f, -0.438f, -1.063f, -0.438f) + horizontalLineToRelative(-3f) + curveToRelative(-0.417f, 0f, -0.771f, 0.146f, -1.063f, 0.438f) + reflectiveCurveToRelative(-0.438f, 0.646f, -0.438f, 1.063f) + verticalLineToRelative(2.5f) + horizontalLineToRelative(-1.5f) + curveToRelative(-0.417f, 0f, -0.771f, 0.146f, -1.063f, 0.438f) + reflectiveCurveToRelative(-0.438f, 0.646f, -0.438f, 1.063f) + verticalLineToRelative(9f) + curveToRelative(0f, 0.417f, 0.146f, 0.771f, 0.438f, 1.063f) + reflectiveCurveToRelative(0.646f, 0.438f, 1.063f, 0.438f) + horizontalLineToRelative(17f) + curveToRelative(0.417f, 0f, 0.771f, -0.146f, 1.063f, -0.438f) + reflectiveCurveToRelative(0.438f, -0.646f, 0.438f, -1.063f) + verticalLineToRelative(-9f) + curveToRelative(0f, -0.417f, -0.146f, -0.771f, -0.438f, -1.063f) + close() + moveTo(15f, 6f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + verticalLineToRelative(-2f) + close() + moveTo(7f, 6f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + verticalLineToRelative(-2f) + close() + moveTo(20f, 18f) + horizontalLineTo(4f) + verticalLineToRelative(-8f) + horizontalLineToRelative(16f) + verticalLineToRelative(8f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(4f, 10f) + horizontalLineToRelative(16f) + verticalLineToRelative(8f) + horizontalLineToRelative(-16f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(7f, 6f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(15f, 6f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Brightness4.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Brightness4.kt new file mode 100644 index 0000000..940eb44 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Brightness4.kt @@ -0,0 +1,118 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Brightness4: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Brightness4", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(492f, 680f) + quadToRelative(83f, 0f, 141.5f, -58.5f) + reflectiveQuadTo(692f, 480f) + quadToRelative(0f, -83f, -58.5f, -141.5f) + reflectiveQuadTo(492f, 280f) + horizontalLineToRelative(-16.5f) + quadToRelative(-8.5f, 0f, -16.5f, 2f) + quadToRelative(-15f, 3f, -19f, 17.5f) + reflectiveQuadToRelative(7f, 22.5f) + quadToRelative(38f, 28f, 58f, 69.5f) + reflectiveQuadToRelative(20f, 88.5f) + quadToRelative(0f, 48f, -19f, 90.5f) + reflectiveQuadTo(447f, 638f) + quadToRelative(-12f, 8f, -8.5f, 22.5f) + reflectiveQuadTo(459f, 677f) + quadToRelative(8f, 2f, 16.5f, 2.5f) + reflectiveQuadToRelative(16.5f, 0.5f) + close() + moveTo(346f, 800f) + lineTo(240f, 800f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 720f) + verticalLineToRelative(-106f) + lineToRelative(-77f, -78f) + quadToRelative(-11f, -12f, -17f, -26.5f) + reflectiveQuadTo(60f, 480f) + quadToRelative(0f, -15f, 6f, -29.5f) + reflectiveQuadTo(83f, 424f) + lineToRelative(77f, -78f) + verticalLineToRelative(-106f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(240f, 160f) + horizontalLineToRelative(106f) + lineToRelative(78f, -77f) + quadToRelative(12f, -11f, 26.5f, -17f) + reflectiveQuadToRelative(29.5f, -6f) + quadToRelative(15f, 0f, 29.5f, 6f) + reflectiveQuadToRelative(26.5f, 17f) + lineToRelative(78f, 77f) + horizontalLineToRelative(106f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(800f, 240f) + verticalLineToRelative(106f) + lineToRelative(77f, 78f) + quadToRelative(11f, 12f, 17f, 26.5f) + reflectiveQuadToRelative(6f, 29.5f) + quadToRelative(0f, 15f, -6f, 29.5f) + reflectiveQuadTo(877f, 536f) + lineToRelative(-77f, 78f) + verticalLineToRelative(106f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 800f) + lineTo(614f, 800f) + lineToRelative(-78f, 77f) + quadToRelative(-12f, 11f, -26.5f, 17f) + reflectiveQuadTo(480f, 900f) + quadToRelative(-15f, 0f, -29.5f, -6f) + reflectiveQuadTo(424f, 877f) + lineToRelative(-78f, -77f) + close() + moveTo(380f, 720f) + lineTo(480f, 820f) + lineTo(580f, 720f) + horizontalLineToRelative(140f) + verticalLineToRelative(-140f) + lineToRelative(100f, -100f) + lineToRelative(-100f, -100f) + verticalLineToRelative(-140f) + lineTo(580f, 240f) + lineTo(480f, 140f) + lineTo(380f, 240f) + lineTo(240f, 240f) + verticalLineToRelative(140f) + lineTo(140f, 480f) + lineToRelative(100f, 100f) + verticalLineToRelative(140f) + horizontalLineToRelative(140f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BrightnessHigh.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BrightnessHigh.kt new file mode 100644 index 0000000..82196e9 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BrightnessHigh.kt @@ -0,0 +1,112 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.BrightnessHigh: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.BrightnessHigh", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(346f, 800f) + lineTo(240f, 800f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 720f) + verticalLineToRelative(-106f) + lineToRelative(-77f, -78f) + quadToRelative(-11f, -12f, -17f, -26.5f) + reflectiveQuadTo(60f, 480f) + quadToRelative(0f, -15f, 6f, -29.5f) + reflectiveQuadTo(83f, 424f) + lineToRelative(77f, -78f) + verticalLineToRelative(-106f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(240f, 160f) + horizontalLineToRelative(106f) + lineToRelative(78f, -77f) + quadToRelative(12f, -11f, 26.5f, -17f) + reflectiveQuadToRelative(29.5f, -6f) + quadToRelative(15f, 0f, 29.5f, 6f) + reflectiveQuadToRelative(26.5f, 17f) + lineToRelative(78f, 77f) + horizontalLineToRelative(106f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(800f, 240f) + verticalLineToRelative(106f) + lineToRelative(77f, 78f) + quadToRelative(11f, 12f, 17f, 26.5f) + reflectiveQuadToRelative(6f, 29.5f) + quadToRelative(0f, 15f, -6f, 29.5f) + reflectiveQuadTo(877f, 536f) + lineToRelative(-77f, 78f) + verticalLineToRelative(106f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 800f) + lineTo(614f, 800f) + lineToRelative(-78f, 77f) + quadToRelative(-12f, 11f, -26.5f, 17f) + reflectiveQuadTo(480f, 900f) + quadToRelative(-15f, 0f, -29.5f, -6f) + reflectiveQuadTo(424f, 877f) + lineToRelative(-78f, -77f) + close() + moveTo(480f, 680f) + quadToRelative(83f, 0f, 141.5f, -58.5f) + reflectiveQuadTo(680f, 480f) + quadToRelative(0f, -83f, -58.5f, -141.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(-83f, 0f, -141.5f, 58.5f) + reflectiveQuadTo(280f, 480f) + quadToRelative(0f, 83f, 58.5f, 141.5f) + reflectiveQuadTo(480f, 680f) + close() + moveTo(480f, 480f) + close() + moveTo(380f, 720f) + lineToRelative(100f, 100f) + lineToRelative(100f, -100f) + horizontalLineToRelative(140f) + verticalLineToRelative(-140f) + lineToRelative(100f, -100f) + lineToRelative(-100f, -100f) + verticalLineToRelative(-140f) + lineTo(580f, 240f) + lineTo(480f, 140f) + lineTo(380f, 240f) + lineTo(240f, 240f) + verticalLineToRelative(140f) + lineTo(140f, 480f) + lineToRelative(100f, 100f) + verticalLineToRelative(140f) + horizontalLineToRelative(140f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BrokenImageAlt.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BrokenImageAlt.kt new file mode 100644 index 0000000..1773d8f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BrokenImageAlt.kt @@ -0,0 +1,85 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BrushColor.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BrushColor.kt new file mode 100644 index 0000000..87a3f40 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BrushColor.kt @@ -0,0 +1,120 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BubbleDelete.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BubbleDelete.kt new file mode 100644 index 0000000..71be0c3 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BubbleDelete.kt @@ -0,0 +1,166 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BugReport.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BugReport.kt new file mode 100644 index 0000000..7534298 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BugReport.kt @@ -0,0 +1,388 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.BugReport: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.BugReport", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 840f) + quadToRelative(-65f, 0f, -120.5f, -32f) + reflectiveQuadTo(272f, 720f) + horizontalLineToRelative(-72f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 680f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 640f) + horizontalLineToRelative(44f) + quadToRelative(-3f, -20f, -3.5f, -40f) + reflectiveQuadToRelative(-0.5f, -40f) + horizontalLineToRelative(-40f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 520f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 480f) + horizontalLineToRelative(40f) + quadToRelative(0f, -20f, 0.5f, -40f) + reflectiveQuadToRelative(3.5f, -40f) + horizontalLineToRelative(-44f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 360f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 320f) + horizontalLineToRelative(72f) + quadToRelative(14f, -23f, 31.5f, -43f) + reflectiveQuadToRelative(40.5f, -35f) + lineToRelative(-37f, -38f) + quadToRelative(-11f, -11f, -11f, -27.5f) + reflectiveQuadToRelative(12f, -28.5f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(58f, 58f) + quadToRelative(28f, -9f, 57f, -9f) + reflectiveQuadToRelative(57f, 9f) + lineToRelative(60f, -59f) + quadToRelative(11f, -11f, 27.5f, -11f) + reflectiveQuadToRelative(28.5f, 12f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineToRelative(-38f, 38f) + quadToRelative(23f, 15f, 41.5f, 34.5f) + reflectiveQuadTo(688f, 320f) + horizontalLineToRelative(72f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 360f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 400f) + horizontalLineToRelative(-44f) + quadToRelative(3f, 20f, 3.5f, 40f) + reflectiveQuadToRelative(0.5f, 40f) + horizontalLineToRelative(40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 520f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 560f) + horizontalLineToRelative(-40f) + quadToRelative(0f, 20f, -0.5f, 40f) + reflectiveQuadToRelative(-3.5f, 40f) + horizontalLineToRelative(44f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 680f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 720f) + horizontalLineToRelative(-72f) + quadToRelative(-32f, 56f, -87.5f, 88f) + reflectiveQuadTo(480f, 840f) + close() + moveTo(480f, 760f) + quadToRelative(66f, 0f, 113f, -47f) + reflectiveQuadToRelative(47f, -113f) + verticalLineToRelative(-160f) + quadToRelative(0f, -66f, -47f, -113f) + reflectiveQuadToRelative(-113f, -47f) + quadToRelative(-66f, 0f, -113f, 47f) + reflectiveQuadToRelative(-47f, 113f) + verticalLineToRelative(160f) + quadToRelative(0f, 66f, 47f, 113f) + reflectiveQuadToRelative(113f, 47f) + close() + moveTo(440f, 640f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(560f, 600f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(520f, 560f) + horizontalLineToRelative(-80f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(400f, 600f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(440f, 640f) + close() + moveTo(440f, 480f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(560f, 440f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(520f, 400f) + horizontalLineToRelative(-80f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(400f, 440f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(440f, 480f) + close() + moveTo(480f, 520f) + close() + } + }.build() +} + +val Icons.Rounded.BugReport: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.BugReport", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 840f) + quadToRelative(-65f, 0f, -120.5f, -32f) + reflectiveQuadTo(272f, 720f) + horizontalLineToRelative(-72f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 680f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 640f) + horizontalLineToRelative(44f) + quadToRelative(-3f, -20f, -3.5f, -40f) + reflectiveQuadToRelative(-0.5f, -40f) + horizontalLineToRelative(-40f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 520f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 480f) + horizontalLineToRelative(40f) + quadToRelative(0f, -20f, 0.5f, -40f) + reflectiveQuadToRelative(3.5f, -40f) + horizontalLineToRelative(-44f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 360f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 320f) + horizontalLineToRelative(72f) + quadToRelative(14f, -23f, 31.5f, -43f) + reflectiveQuadToRelative(40.5f, -35f) + lineToRelative(-37f, -38f) + quadToRelative(-11f, -11f, -11f, -27.5f) + reflectiveQuadToRelative(12f, -28.5f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(58f, 58f) + quadToRelative(28f, -9f, 57f, -9f) + reflectiveQuadToRelative(57f, 9f) + lineToRelative(60f, -59f) + quadToRelative(11f, -11f, 27.5f, -11f) + reflectiveQuadToRelative(28.5f, 12f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineToRelative(-38f, 38f) + quadToRelative(23f, 15f, 41.5f, 34.5f) + reflectiveQuadTo(688f, 320f) + horizontalLineToRelative(72f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 360f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 400f) + horizontalLineToRelative(-44f) + quadToRelative(3f, 20f, 3.5f, 40f) + reflectiveQuadToRelative(0.5f, 40f) + horizontalLineToRelative(40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 520f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 560f) + horizontalLineToRelative(-40f) + quadToRelative(0f, 20f, -0.5f, 40f) + reflectiveQuadToRelative(-3.5f, 40f) + horizontalLineToRelative(44f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 680f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 720f) + horizontalLineToRelative(-72f) + quadToRelative(-32f, 56f, -87.5f, 88f) + reflectiveQuadTo(480f, 840f) + close() + moveTo(440f, 640f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(560f, 600f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(520f, 560f) + horizontalLineToRelative(-80f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(400f, 600f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(440f, 640f) + close() + moveTo(440f, 480f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(560f, 440f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(520f, 400f) + horizontalLineToRelative(-80f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(400f, 440f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(440f, 480f) + close() + } + }.build() +} + +val Icons.TwoTone.BugReport: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.BugReport", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(18f, 14f) + horizontalLineToRelative(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) + reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.713f) + curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.713f, -0.287f) + horizontalLineToRelative(-1f) + curveToRelative(0f, -0.333f, -0.004f, -0.667f, -0.013f, -1f) + curveToRelative(-0.008f, -0.333f, -0.037f, -0.667f, -0.087f, -1f) + horizontalLineToRelative(1.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) + reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.713f) + curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.713f, -0.287f) + horizontalLineToRelative(-1.8f) + curveToRelative(-0.233f, -0.4f, -0.504f, -0.763f, -0.813f, -1.088f) + curveToRelative(-0.308f, -0.325f, -0.654f, -0.612f, -1.038f, -0.862f) + lineToRelative(0.95f, -0.95f) + curveToRelative(0.183f, -0.183f, 0.275f, -0.417f, 0.275f, -0.7f) + curveToRelative(0f, -0.283f, -0.092f, -0.517f, -0.275f, -0.7f) + curveToRelative(-0.2f, -0.2f, -0.438f, -0.3f, -0.712f, -0.3f) + curveToRelative(-0.275f, 0f, -0.504f, 0.092f, -0.688f, 0.275f) + lineToRelative(-1.5f, 1.475f) + curveToRelative(-0.467f, -0.15f, -0.942f, -0.225f, -1.425f, -0.225f) + reflectiveCurveToRelative(-0.958f, 0.075f, -1.425f, 0.225f) + lineToRelative(-1.45f, -1.45f) + curveToRelative(-0.183f, -0.183f, -0.417f, -0.275f, -0.7f, -0.275f) + curveToRelative(-0.283f, 0f, -0.517f, 0.092f, -0.7f, 0.275f) + curveToRelative(-0.2f, 0.2f, -0.3f, 0.438f, -0.3f, 0.712f) + curveToRelative(0f, 0.275f, 0.092f, 0.504f, 0.275f, 0.688f) + lineToRelative(0.925f, 0.95f) + curveToRelative(-0.383f, 0.25f, -0.721f, 0.542f, -1.012f, 0.875f) + curveToRelative(-0.292f, 0.333f, -0.554f, 0.692f, -0.788f, 1.075f) + horizontalLineToRelative(-1.8f) + 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(1.1f) + curveToRelative(-0.05f, 0.333f, -0.079f, 0.667f, -0.087f, 1f) + curveToRelative(-0.008f, 0.333f, -0.013f, 0.667f, -0.013f, 1f) + horizontalLineToRelative(-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) + reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.713f) + curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.713f, 0.287f) + horizontalLineToRelative(1f) + curveToRelative(0f, 0.333f, 0.004f, 0.667f, 0.013f, 1f) + curveToRelative(0.008f, 0.333f, 0.037f, 0.667f, 0.087f, 1f) + horizontalLineToRelative(-1.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) + reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.713f) + curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.713f, 0.287f) + horizontalLineToRelative(1.8f) + curveToRelative(0.533f, 0.933f, 1.263f, 1.667f, 2.188f, 2.2f) + reflectiveCurveToRelative(1.929f, 0.8f, 3.013f, 0.8f) + reflectiveCurveToRelative(2.088f, -0.267f, 3.013f, -0.8f) + reflectiveCurveToRelative(1.654f, -1.267f, 2.188f, -2.2f) + horizontalLineToRelative(1.8f) + 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(-1.1f) + curveToRelative(0.05f, -0.333f, 0.079f, -0.667f, 0.087f, -1f) + curveToRelative(0.008f, -0.333f, 0.013f, -0.667f, 0.013f, -1f) + close() + moveTo(16f, 15f) + 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) + verticalLineToRelative(-4f) + 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) + verticalLineToRelative(4f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(11f, 16f) + 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.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(11f, 12f) + 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) + 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) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(12f, 19f) + 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) + verticalLineToRelative(-4f) + 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) + verticalLineToRelative(4f) + 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) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Build.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Build.kt new file mode 100644 index 0000000..d38e777 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Build.kt @@ -0,0 +1,232 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} + +val Icons.Rounded.Build: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Build", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(360f, 600f) + quadToRelative(-100f, 0f, -170f, -70f) + reflectiveQuadToRelative(-70f, -170f) + quadToRelative(0f, -20f, 3f, -40f) + reflectiveQuadToRelative(11f, -38f) + quadToRelative(5f, -10f, 12.5f, -15f) + reflectiveQuadToRelative(16.5f, -7f) + quadToRelative(9f, -2f, 18.5f, 0.5f) + reflectiveQuadTo(199f, 271f) + lineToRelative(105f, 105f) + lineToRelative(72f, -72f) + lineToRelative(-105f, -105f) + quadToRelative(-8f, -8f, -10.5f, -17.5f) + reflectiveQuadTo(260f, 163f) + quadToRelative(2f, -9f, 7f, -16.5f) + reflectiveQuadToRelative(15f, -12.5f) + quadToRelative(18f, -8f, 38f, -11f) + reflectiveQuadToRelative(40f, -3f) + quadToRelative(100f, 0f, 170f, 70f) + reflectiveQuadToRelative(70f, 170f) + quadToRelative(0f, 23f, -4f, 43.5f) + reflectiveQuadTo(584f, 444f) + lineToRelative(202f, 200f) + quadToRelative(29f, 29f, 29f, 71f) + reflectiveQuadToRelative(-29f, 71f) + quadToRelative(-29f, 29f, -71f, 29f) + reflectiveQuadToRelative(-71f, -30f) + lineTo(444f, 584f) + quadToRelative(-20f, 8f, -40.5f, 12f) + reflectiveQuadToRelative(-43.5f, 4f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Business.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Business.kt new file mode 100644 index 0000000..5cd9d59 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Business.kt @@ -0,0 +1,119 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Business: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Business", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(2f, 19f) + verticalLineTo(5f) + quadTo(2f, 4.175f, 2.588f, 3.588f) + quadTo(3.175f, 3f, 4f, 3f) + horizontalLineTo(10f) + quadTo(10.825f, 3f, 11.413f, 3.588f) + quadTo(12f, 4.175f, 12f, 5f) + verticalLineTo(7f) + horizontalLineTo(20f) + quadTo(20.825f, 7f, 21.413f, 7.588f) + quadTo(22f, 8.175f, 22f, 9f) + verticalLineTo(19f) + quadTo(22f, 19.825f, 21.413f, 20.413f) + quadTo(20.825f, 21f, 20f, 21f) + horizontalLineTo(4f) + quadTo(3.175f, 21f, 2.588f, 20.413f) + quadTo(2f, 19.825f, 2f, 19f) + close() + moveTo(4f, 19f) + horizontalLineTo(6f) + verticalLineTo(17f) + horizontalLineTo(4f) + close() + moveTo(4f, 15f) + horizontalLineTo(6f) + verticalLineTo(13f) + horizontalLineTo(4f) + close() + moveTo(4f, 11f) + horizontalLineTo(6f) + verticalLineTo(9f) + horizontalLineTo(4f) + close() + moveTo(4f, 7f) + horizontalLineTo(6f) + verticalLineTo(5f) + horizontalLineTo(4f) + close() + moveTo(8f, 19f) + horizontalLineTo(10f) + verticalLineTo(17f) + horizontalLineTo(8f) + close() + moveTo(8f, 15f) + horizontalLineTo(10f) + verticalLineTo(13f) + horizontalLineTo(8f) + close() + moveTo(8f, 11f) + horizontalLineTo(10f) + verticalLineTo(9f) + horizontalLineTo(8f) + close() + moveTo(8f, 7f) + horizontalLineTo(10f) + verticalLineTo(5f) + horizontalLineTo(8f) + close() + moveTo(12f, 19f) + horizontalLineTo(20f) + verticalLineTo(9f) + horizontalLineTo(12f) + verticalLineTo(11f) + horizontalLineTo(14f) + verticalLineTo(13f) + horizontalLineTo(12f) + verticalLineTo(15f) + horizontalLineTo(14f) + verticalLineTo(17f) + horizontalLineTo(12f) + close() + moveTo(16f, 13f) + verticalLineTo(11f) + horizontalLineTo(18f) + verticalLineTo(13f) + close() + moveTo(16f, 17f) + verticalLineTo(15f) + horizontalLineTo(18f) + verticalLineTo(17f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Calculate.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Calculate.kt new file mode 100644 index 0000000..d8803a5 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Calculate.kt @@ -0,0 +1,265 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Calculate: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Calculate", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(320f, 640f) + verticalLineToRelative(50f) + quadToRelative(0f, 13f, 8.5f, 21.5f) + reflectiveQuadTo(350f, 720f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(380f, 690f) + verticalLineToRelative(-50f) + horizontalLineToRelative(50f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(460f, 610f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(430f, 580f) + horizontalLineToRelative(-50f) + verticalLineToRelative(-50f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(350f, 500f) + quadToRelative(-13f, 0f, -21.5f, 8.5f) + reflectiveQuadTo(320f, 530f) + verticalLineToRelative(50f) + horizontalLineToRelative(-50f) + quadToRelative(-13f, 0f, -21.5f, 8.5f) + reflectiveQuadTo(240f, 610f) + quadToRelative(0f, 13f, 8.5f, 21.5f) + reflectiveQuadTo(270f, 640f) + horizontalLineToRelative(50f) + close() + moveTo(550f, 690f) + horizontalLineToRelative(140f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(720f, 660f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(690f, 630f) + lineTo(550f, 630f) + quadToRelative(-13f, 0f, -21.5f, 8.5f) + reflectiveQuadTo(520f, 660f) + quadToRelative(0f, 13f, 8.5f, 21.5f) + reflectiveQuadTo(550f, 690f) + close() + moveTo(550f, 590f) + horizontalLineToRelative(140f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(720f, 560f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(690f, 530f) + lineTo(550f, 530f) + quadToRelative(-13f, 0f, -21.5f, 8.5f) + reflectiveQuadTo(520f, 560f) + quadToRelative(0f, 13f, 8.5f, 21.5f) + reflectiveQuadTo(550f, 590f) + close() + moveTo(620f, 382f) + lineTo(655f, 417f) + quadToRelative(9f, 9f, 21f, 9f) + reflectiveQuadToRelative(21f, -9f) + quadToRelative(8f, -8f, 8.5f, -20.5f) + reflectiveQuadTo(698f, 375f) + lineToRelative(-36f, -37f) + lineToRelative(35f, -35f) + quadToRelative(9f, -9f, 9f, -21f) + reflectiveQuadToRelative(-9f, -21f) + quadToRelative(-9f, -9f, -21f, -9f) + reflectiveQuadToRelative(-21f, 9f) + lineToRelative(-35f, 35f) + lineToRelative(-35f, -35f) + quadToRelative(-9f, -9f, -21f, -9f) + reflectiveQuadToRelative(-21f, 9f) + quadToRelative(-9f, 9f, -9f, 21f) + reflectiveQuadToRelative(9f, 21f) + lineToRelative(35f, 35f) + lineToRelative(-36f, 37f) + quadToRelative(-8f, 9f, -8f, 21f) + reflectiveQuadToRelative(9f, 21f) + quadToRelative(9f, 9f, 21f, 9f) + reflectiveQuadToRelative(21f, -9f) + lineToRelative(35f, -35f) + close() + moveTo(280f, 368f) + horizontalLineToRelative(140f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(450f, 338f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(420f, 308f) + lineTo(280f, 308f) + quadToRelative(-13f, 0f, -21.5f, 8.5f) + reflectiveQuadTo(250f, 338f) + quadToRelative(0f, 13f, 8.5f, 21.5f) + reflectiveQuadTo(280f, 368f) + 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(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, 200f) + verticalLineToRelative(560f) + verticalLineToRelative(-560f) + close() + } + }.build() +} + +val Icons.Rounded.Calculate: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Calculate", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(320f, 640f) + verticalLineToRelative(50f) + quadToRelative(0f, 13f, 8.5f, 21.5f) + reflectiveQuadTo(350f, 720f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(380f, 690f) + verticalLineToRelative(-50f) + horizontalLineToRelative(50f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(460f, 610f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(430f, 580f) + horizontalLineToRelative(-50f) + verticalLineToRelative(-50f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(350f, 500f) + quadToRelative(-13f, 0f, -21.5f, 8.5f) + reflectiveQuadTo(320f, 530f) + verticalLineToRelative(50f) + horizontalLineToRelative(-50f) + quadToRelative(-13f, 0f, -21.5f, 8.5f) + reflectiveQuadTo(240f, 610f) + quadToRelative(0f, 13f, 8.5f, 21.5f) + reflectiveQuadTo(270f, 640f) + horizontalLineToRelative(50f) + close() + moveTo(550f, 690f) + horizontalLineToRelative(140f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(720f, 660f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(690f, 630f) + lineTo(550f, 630f) + quadToRelative(-13f, 0f, -21.5f, 8.5f) + reflectiveQuadTo(520f, 660f) + quadToRelative(0f, 13f, 8.5f, 21.5f) + reflectiveQuadTo(550f, 690f) + close() + moveTo(550f, 590f) + horizontalLineToRelative(140f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(720f, 560f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(690f, 530f) + lineTo(550f, 530f) + quadToRelative(-13f, 0f, -21.5f, 8.5f) + reflectiveQuadTo(520f, 560f) + quadToRelative(0f, 13f, 8.5f, 21.5f) + reflectiveQuadTo(550f, 590f) + close() + moveTo(620f, 382f) + lineTo(655f, 417f) + quadToRelative(9f, 9f, 21f, 9f) + reflectiveQuadToRelative(21f, -9f) + quadToRelative(8f, -8f, 8.5f, -20.5f) + reflectiveQuadTo(698f, 375f) + lineToRelative(-36f, -37f) + lineToRelative(35f, -35f) + quadToRelative(9f, -9f, 9f, -21f) + reflectiveQuadToRelative(-9f, -21f) + quadToRelative(-9f, -9f, -21f, -9f) + reflectiveQuadToRelative(-21f, 9f) + lineToRelative(-35f, 35f) + lineToRelative(-35f, -35f) + quadToRelative(-9f, -9f, -21f, -9f) + reflectiveQuadToRelative(-21f, 9f) + quadToRelative(-9f, 9f, -9f, 21f) + reflectiveQuadToRelative(9f, 21f) + lineToRelative(35f, 35f) + lineToRelative(-36f, 37f) + quadToRelative(-8f, 9f, -8f, 21f) + reflectiveQuadToRelative(9f, 21f) + quadToRelative(9f, 9f, 21f, 9f) + reflectiveQuadToRelative(21f, -9f) + lineToRelative(35f, -35f) + close() + moveTo(280f, 368f) + horizontalLineToRelative(140f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(450f, 338f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(420f, 308f) + lineTo(280f, 308f) + quadToRelative(-13f, 0f, -21.5f, 8.5f) + reflectiveQuadTo(250f, 338f) + quadToRelative(0f, 13f, 8.5f, 21.5f) + reflectiveQuadTo(280f, 368f) + 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(560f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 840f) + lineTo(200f, 840f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CalendarMonth.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CalendarMonth.kt new file mode 100644 index 0000000..600867b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CalendarMonth.kt @@ -0,0 +1,132 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.CalendarMonth: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.CalendarMonth", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(200f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 800f) + verticalLineToRelative(-560f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 160f) + horizontalLineToRelative(40f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(280f, 80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(320f, 120f) + verticalLineToRelative(40f) + horizontalLineToRelative(320f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(680f, 80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(720f, 120f) + verticalLineToRelative(40f) + horizontalLineToRelative(40f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(840f, 240f) + verticalLineToRelative(560f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 880f) + lineTo(200f, 880f) + close() + moveTo(200f, 800f) + horizontalLineToRelative(560f) + verticalLineToRelative(-400f) + lineTo(200f, 400f) + verticalLineToRelative(400f) + close() + moveTo(480f, 560f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 520f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 480f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 520f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 560f) + close() + moveTo(320f, 560f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 520f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 480f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 520f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 560f) + close() + moveTo(640f, 560f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 520f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 480f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 520f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 560f) + close() + moveTo(480f, 720f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 680f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 680f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 720f) + close() + moveTo(320f, 720f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 680f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 680f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 720f) + close() + moveTo(640f, 720f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 680f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 680f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 720f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Call.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Call.kt new file mode 100644 index 0000000..e9844a4 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Call.kt @@ -0,0 +1,84 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Call: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Call", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(798f, 840f) + quadToRelative(-125f, 0f, -247f, -54.5f) + reflectiveQuadTo(329f, 631f) + quadTo(229f, 531f, 174.5f, 409f) + reflectiveQuadTo(120f, 162f) + quadToRelative(0f, -18f, 12f, -30f) + reflectiveQuadToRelative(30f, -12f) + horizontalLineToRelative(162f) + quadToRelative(14f, 0f, 25f, 9.5f) + reflectiveQuadToRelative(13f, 22.5f) + lineToRelative(26f, 140f) + quadToRelative(2f, 16f, -1f, 27f) + reflectiveQuadToRelative(-11f, 19f) + lineToRelative(-97f, 98f) + quadToRelative(20f, 37f, 47.5f, 71.5f) + reflectiveQuadTo(387f, 574f) + quadToRelative(31f, 31f, 65f, 57.5f) + reflectiveQuadToRelative(72f, 48.5f) + lineToRelative(94f, -94f) + quadToRelative(9f, -9f, 23.5f, -13.5f) + reflectiveQuadTo(670f, 570f) + lineToRelative(138f, 28f) + quadToRelative(14f, 4f, 23f, 14.5f) + reflectiveQuadToRelative(9f, 23.5f) + verticalLineToRelative(162f) + quadToRelative(0f, 18f, -12f, 30f) + reflectiveQuadToRelative(-30f, 12f) + close() + moveTo(241f, 360f) + lineToRelative(66f, -66f) + lineToRelative(-17f, -94f) + horizontalLineToRelative(-89f) + quadToRelative(5f, 41f, 14f, 81f) + reflectiveQuadToRelative(26f, 79f) + close() + moveTo(599f, 718f) + quadToRelative(39f, 17f, 79.5f, 27f) + reflectiveQuadToRelative(81.5f, 13f) + verticalLineToRelative(-88f) + lineToRelative(-94f, -19f) + lineToRelative(-67f, 67f) + close() + moveTo(241f, 360f) + close() + moveTo(599f, 718f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CameraAlt.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CameraAlt.kt new file mode 100644 index 0000000..412bdde --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CameraAlt.kt @@ -0,0 +1,196 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.CameraAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.CameraAlt", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(12f, 17.5f) + quadTo(13.875f, 17.5f, 15.188f, 16.188f) + quadTo(16.5f, 14.875f, 16.5f, 13f) + quadTo(16.5f, 11.125f, 15.188f, 9.813f) + quadTo(13.875f, 8.5f, 12f, 8.5f) + quadTo(10.125f, 8.5f, 8.813f, 9.813f) + quadTo(7.5f, 11.125f, 7.5f, 13f) + quadTo(7.5f, 14.875f, 8.813f, 16.188f) + quadTo(10.125f, 17.5f, 12f, 17.5f) + close() + moveTo(12f, 15.5f) + quadTo(10.95f, 15.5f, 10.225f, 14.775f) + quadTo(9.5f, 14.05f, 9.5f, 13f) + quadTo(9.5f, 11.95f, 10.225f, 11.225f) + quadTo(10.95f, 10.5f, 12f, 10.5f) + quadTo(13.05f, 10.5f, 13.775f, 11.225f) + quadTo(14.5f, 11.95f, 14.5f, 13f) + quadTo(14.5f, 14.05f, 13.775f, 14.775f) + quadTo(13.05f, 15.5f, 12f, 15.5f) + close() + moveTo(4f, 21f) + quadTo(3.175f, 21f, 2.588f, 20.413f) + quadTo(2f, 19.825f, 2f, 19f) + verticalLineTo(7f) + quadTo(2f, 6.175f, 2.588f, 5.588f) + quadTo(3.175f, 5f, 4f, 5f) + horizontalLineTo(7.15f) + lineTo(8.4f, 3.65f) + quadTo(8.675f, 3.35f, 9.063f, 3.175f) + quadTo(9.45f, 3f, 9.875f, 3f) + horizontalLineTo(14.125f) + quadTo(14.55f, 3f, 14.938f, 3.175f) + quadTo(15.325f, 3.35f, 15.6f, 3.65f) + lineTo(16.85f, 5f) + horizontalLineTo(20f) + quadTo(20.825f, 5f, 21.413f, 5.588f) + quadTo(22f, 6.175f, 22f, 7f) + verticalLineTo(19f) + quadTo(22f, 19.825f, 21.413f, 20.413f) + quadTo(20.825f, 21f, 20f, 21f) + close() + moveTo(4f, 19f) + horizontalLineTo(20f) + quadTo(20f, 19f, 20f, 19f) + quadTo(20f, 19f, 20f, 19f) + verticalLineTo(7f) + quadTo(20f, 7f, 20f, 7f) + quadTo(20f, 7f, 20f, 7f) + horizontalLineTo(15.95f) + lineTo(14.125f, 5f) + horizontalLineTo(9.875f) + lineTo(8.05f, 7f) + horizontalLineTo(4f) + quadTo(4f, 7f, 4f, 7f) + quadTo(4f, 7f, 4f, 7f) + verticalLineTo(19f) + quadTo(4f, 19f, 4f, 19f) + quadTo(4f, 19f, 4f, 19f) + close() + moveTo(12f, 13f) + quadTo(12f, 13f, 12f, 13f) + quadTo(12f, 13f, 12f, 13f) + quadTo(12f, 13f, 12f, 13f) + quadTo(12f, 13f, 12f, 13f) + quadTo(12f, 13f, 12f, 13f) + quadTo(12f, 13f, 12f, 13f) + quadTo(12f, 13f, 12f, 13f) + quadTo(12f, 13f, 12f, 13f) + close() + } + }.build() +} + +val Icons.Rounded.CameraAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.CameraAlt", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(12f, 17.5f) + quadTo(13.875f, 17.5f, 15.188f, 16.188f) + quadTo(16.5f, 14.875f, 16.5f, 13f) + quadTo(16.5f, 11.125f, 15.188f, 9.813f) + quadTo(13.875f, 8.5f, 12f, 8.5f) + quadTo(10.125f, 8.5f, 8.813f, 9.813f) + quadTo(7.5f, 11.125f, 7.5f, 13f) + quadTo(7.5f, 14.875f, 8.813f, 16.188f) + quadTo(10.125f, 17.5f, 12f, 17.5f) + close() + moveTo(12f, 15.5f) + quadTo(10.95f, 15.5f, 10.225f, 14.775f) + quadTo(9.5f, 14.05f, 9.5f, 13f) + quadTo(9.5f, 11.95f, 10.225f, 11.225f) + quadTo(10.95f, 10.5f, 12f, 10.5f) + quadTo(13.05f, 10.5f, 13.775f, 11.225f) + quadTo(14.5f, 11.95f, 14.5f, 13f) + quadTo(14.5f, 14.05f, 13.775f, 14.775f) + quadTo(13.05f, 15.5f, 12f, 15.5f) + close() + moveTo(4f, 21f) + quadTo(3.175f, 21f, 2.588f, 20.413f) + quadTo(2f, 19.825f, 2f, 19f) + verticalLineTo(7f) + quadTo(2f, 6.175f, 2.588f, 5.588f) + quadTo(3.175f, 5f, 4f, 5f) + horizontalLineTo(7.15f) + lineTo(8.4f, 3.65f) + quadTo(8.675f, 3.35f, 9.063f, 3.175f) + quadTo(9.45f, 3f, 9.875f, 3f) + horizontalLineTo(14.125f) + quadTo(14.55f, 3f, 14.938f, 3.175f) + quadTo(15.325f, 3.35f, 15.6f, 3.65f) + lineTo(16.85f, 5f) + horizontalLineTo(20f) + quadTo(20.825f, 5f, 21.413f, 5.588f) + quadTo(22f, 6.175f, 22f, 7f) + verticalLineTo(19f) + quadTo(22f, 19.825f, 21.413f, 20.413f) + quadTo(20.825f, 21f, 20f, 21f) + close() + moveTo(4f, 19f) + horizontalLineTo(20f) + quadTo(20f, 19f, 20f, 19f) + quadTo(20f, 19f, 20f, 19f) + verticalLineTo(7f) + quadTo(20f, 7f, 20f, 7f) + quadTo(20f, 7f, 20f, 7f) + horizontalLineTo(15.95f) + lineTo(14.125f, 5f) + horizontalLineTo(9.875f) + lineTo(8.05f, 7f) + horizontalLineTo(4f) + quadTo(4f, 7f, 4f, 7f) + quadTo(4f, 7f, 4f, 7f) + verticalLineTo(19f) + quadTo(4f, 19f, 4f, 19f) + quadTo(4f, 19f, 4f, 19f) + close() + moveTo(4f, 19f) + quadTo(4f, 19f, 4f, 19f) + quadTo(4f, 19f, 4f, 19f) + verticalLineTo(7f) + quadTo(4f, 7f, 4f, 7f) + quadTo(4f, 7f, 4f, 7f) + horizontalLineTo(8.05f) + lineTo(9.875f, 5f) + horizontalLineTo(14.125f) + lineTo(15.95f, 7f) + horizontalLineTo(20f) + quadTo(20f, 7f, 20f, 7f) + quadTo(20f, 7f, 20f, 7f) + verticalLineTo(19f) + quadTo(20f, 19f, 20f, 19f) + quadTo(20f, 19f, 20f, 19f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Cancel.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Cancel.kt new file mode 100644 index 0000000..214f254 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Cancel.kt @@ -0,0 +1,151 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Cancel: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Cancel", + 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, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} + +val Icons.Rounded.Cancel: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Cancel", + 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, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CancelSmall.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CancelSmall.kt new file mode 100644 index 0000000..a1d4153 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CancelSmall.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Casino.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Casino.kt new file mode 100644 index 0000000..823ae19 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Casino.kt @@ -0,0 +1,189 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Casino: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Casino", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(300f, 720f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(360f, 660f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(300f, 600f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(240f, 660f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(300f, 720f) + close() + moveTo(300f, 360f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(360f, 300f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(300f, 240f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(240f, 300f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(300f, 360f) + close() + moveTo(480f, 540f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(540f, 480f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(480f, 420f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(420f, 480f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(480f, 540f) + close() + moveTo(660f, 720f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(720f, 660f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(660f, 600f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(600f, 660f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(660f, 720f) + close() + moveTo(660f, 360f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(720f, 300f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(660f, 240f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(600f, 300f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(660f, 360f) + 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(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, 200f) + verticalLineToRelative(560f) + verticalLineToRelative(-560f) + close() + } + }.build() +} + +val Icons.Rounded.Casino: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Casino", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(300f, 720f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(360f, 660f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(300f, 600f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(240f, 660f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(300f, 720f) + close() + moveTo(300f, 360f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(360f, 300f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(300f, 240f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(240f, 300f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(300f, 360f) + close() + moveTo(480f, 540f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(540f, 480f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(480f, 420f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(420f, 480f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(480f, 540f) + close() + moveTo(660f, 720f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(720f, 660f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(660f, 600f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(600f, 660f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(660f, 720f) + close() + moveTo(660f, 360f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(720f, 300f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(660f, 240f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(600f, 300f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(660f, 360f) + 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(560f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 840f) + lineTo(200f, 840f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Celebration.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Celebration.kt new file mode 100644 index 0000000..146ce95 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Celebration.kt @@ -0,0 +1,236 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Celebration: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Celebration", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(212f, 748f) + lineToRelative(282f, -100f) + lineToRelative(-182f, -182f) + lineToRelative(-100f, 282f) + close() + moveTo(921f, 281f) + quadToRelative(-9f, 9f, -21f, 9f) + reflectiveQuadToRelative(-21f, -9f) + lineToRelative(-3f, -3f) + quadToRelative(-14f, -14f, -35f, -14f) + reflectiveQuadToRelative(-35f, 14f) + lineTo(603f, 481f) + quadToRelative(-9f, 9f, -21f, 9f) + reflectiveQuadToRelative(-21f, -9f) + quadToRelative(-9f, -9f, -9f, -21f) + reflectiveQuadToRelative(9f, -21f) + lineToRelative(203f, -203f) + quadToRelative(32f, -32f, 77f, -32f) + reflectiveQuadToRelative(77f, 32f) + lineToRelative(3f, 3f) + quadToRelative(9f, 9f, 9f, 21f) + reflectiveQuadToRelative(-9f, 21f) + close() + moveTo(399f, 161f) + quadToRelative(9f, -9f, 21f, -9f) + reflectiveQuadToRelative(21f, 9f) + lineToRelative(5f, 5f) + quadToRelative(32f, 32f, 32f, 76f) + reflectiveQuadToRelative(-32f, 76f) + lineToRelative(-3f, 3f) + quadToRelative(-9f, 9f, -21f, 9f) + reflectiveQuadToRelative(-21f, -9f) + quadToRelative(-9f, -9f, -9f, -21f) + reflectiveQuadToRelative(9f, -21f) + lineToRelative(3f, -3f) + quadToRelative(14f, -14f, 14f, -34f) + reflectiveQuadToRelative(-14f, -34f) + lineToRelative(-5f, -5f) + quadToRelative(-9f, -9f, -9f, -21f) + reflectiveQuadToRelative(9f, -21f) + close() + moveTo(561f, 81f) + quadToRelative(9f, -9f, 21f, -9f) + reflectiveQuadToRelative(21f, 9f) + lineToRelative(43f, 43f) + quadToRelative(32f, 32f, 32f, 77f) + reflectiveQuadToRelative(-32f, 77f) + lineTo(523f, 401f) + quadToRelative(-9f, 9f, -21f, 9f) + reflectiveQuadToRelative(-21f, -9f) + quadToRelative(-9f, -9f, -9f, -21f) + reflectiveQuadToRelative(9f, -21f) + lineToRelative(123f, -123f) + quadToRelative(14f, -14f, 14f, -35f) + reflectiveQuadToRelative(-14f, -35f) + lineToRelative(-43f, -43f) + quadToRelative(-9f, -9f, -9f, -21f) + reflectiveQuadToRelative(9f, -21f) + close() + moveTo(881f, 561f) + quadToRelative(-9f, 9f, -21f, 9f) + reflectiveQuadToRelative(-21f, -9f) + lineToRelative(-43f, -43f) + quadToRelative(-14f, -14f, -35f, -14f) + reflectiveQuadToRelative(-35f, 14f) + lineToRelative(-43f, 43f) + quadToRelative(-9f, 9f, -21f, 9f) + reflectiveQuadToRelative(-21f, -9f) + quadToRelative(-9f, -9f, -9f, -21f) + reflectiveQuadToRelative(9f, -21f) + lineToRelative(43f, -43f) + quadToRelative(32f, -32f, 77f, -32f) + reflectiveQuadToRelative(77f, 32f) + lineToRelative(43f, 43f) + quadToRelative(9f, 9f, 9f, 21f) + reflectiveQuadToRelative(-9f, 21f) + close() + moveTo(212f, 748f) + close() + moveTo(108f, 800f) + lineTo(259f, 380f) + quadToRelative(5f, -13f, 15.5f, -20f) + reflectiveQuadToRelative(22.5f, -7f) + quadToRelative(8f, 0f, 15f, 3f) + reflectiveQuadToRelative(13f, 9f) + lineToRelative(270f, 270f) + quadToRelative(6f, 6f, 9f, 13f) + reflectiveQuadToRelative(3f, 15f) + quadToRelative(0f, 12f, -7f, 22.5f) + reflectiveQuadTo(580f, 701f) + lineTo(160f, 852f) + quadToRelative(-12f, 5f, -23f, 1.5f) + reflectiveQuadTo(118f, 842f) + quadToRelative(-8f, -8f, -11.5f, -19f) + reflectiveQuadToRelative(1.5f, -23f) + close() + } + }.build() +} + +val Icons.Rounded.Celebration: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Celebration", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(108f, 800f) + lineToRelative(151f, -420f) + quadToRelative(5f, -13f, 15.5f, -20f) + reflectiveQuadToRelative(22.5f, -7f) + quadToRelative(8f, 0f, 15f, 3f) + reflectiveQuadToRelative(13f, 9f) + lineToRelative(270f, 270f) + quadToRelative(6f, 6f, 9f, 13f) + reflectiveQuadToRelative(3f, 15f) + quadToRelative(0f, 12f, -7f, 22.5f) + reflectiveQuadTo(580f, 701f) + lineTo(160f, 852f) + quadToRelative(-12f, 5f, -23f, 1.5f) + reflectiveQuadTo(118f, 842f) + quadToRelative(-8f, -8f, -11.5f, -19f) + reflectiveQuadToRelative(1.5f, -23f) + close() + moveTo(921f, 281f) + quadToRelative(-9f, 9f, -21f, 9f) + reflectiveQuadToRelative(-21f, -9f) + lineToRelative(-3f, -3f) + quadToRelative(-14f, -14f, -35f, -14f) + reflectiveQuadToRelative(-35f, 14f) + lineTo(603f, 481f) + quadToRelative(-9f, 9f, -21f, 9f) + reflectiveQuadToRelative(-21f, -9f) + quadToRelative(-9f, -9f, -9f, -21f) + reflectiveQuadToRelative(9f, -21f) + lineToRelative(203f, -203f) + quadToRelative(32f, -32f, 77f, -32f) + reflectiveQuadToRelative(77f, 32f) + lineToRelative(3f, 3f) + quadToRelative(9f, 9f, 9f, 21f) + reflectiveQuadToRelative(-9f, 21f) + close() + moveTo(399f, 161f) + quadToRelative(9f, -9f, 21f, -9f) + reflectiveQuadToRelative(21f, 9f) + lineToRelative(5f, 5f) + quadToRelative(32f, 32f, 32f, 76f) + reflectiveQuadToRelative(-32f, 76f) + lineToRelative(-3f, 3f) + quadToRelative(-9f, 9f, -21f, 9f) + reflectiveQuadToRelative(-21f, -9f) + quadToRelative(-9f, -9f, -9f, -21f) + reflectiveQuadToRelative(9f, -21f) + lineToRelative(3f, -3f) + quadToRelative(14f, -14f, 14f, -34f) + reflectiveQuadToRelative(-14f, -34f) + lineToRelative(-5f, -5f) + quadToRelative(-9f, -9f, -9f, -21f) + reflectiveQuadToRelative(9f, -21f) + close() + moveTo(561f, 81f) + quadToRelative(9f, -9f, 21f, -9f) + reflectiveQuadToRelative(21f, 9f) + lineToRelative(43f, 43f) + quadToRelative(32f, 32f, 32f, 77f) + reflectiveQuadToRelative(-32f, 77f) + lineTo(523f, 401f) + quadToRelative(-9f, 9f, -21f, 9f) + reflectiveQuadToRelative(-21f, -9f) + quadToRelative(-9f, -9f, -9f, -21f) + reflectiveQuadToRelative(9f, -21f) + lineToRelative(123f, -123f) + quadToRelative(14f, -14f, 14f, -35f) + reflectiveQuadToRelative(-14f, -35f) + lineToRelative(-43f, -43f) + quadToRelative(-9f, -9f, -9f, -21f) + reflectiveQuadToRelative(9f, -21f) + close() + moveTo(881f, 561f) + quadToRelative(-9f, 9f, -21f, 9f) + reflectiveQuadToRelative(-21f, -9f) + lineToRelative(-43f, -43f) + quadToRelative(-14f, -14f, -35f, -14f) + reflectiveQuadToRelative(-35f, 14f) + lineToRelative(-43f, 43f) + quadToRelative(-9f, 9f, -21f, 9f) + reflectiveQuadToRelative(-21f, -9f) + quadToRelative(-9f, -9f, -9f, -21f) + reflectiveQuadToRelative(9f, -21f) + lineToRelative(43f, -43f) + quadToRelative(32f, -32f, 77f, -32f) + reflectiveQuadToRelative(77f, 32f) + lineToRelative(43f, 43f) + quadToRelative(9f, 9f, 9f, 21f) + reflectiveQuadToRelative(-9f, 21f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CenterFocusStrong.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CenterFocusStrong.kt new file mode 100644 index 0000000..190dc1c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CenterFocusStrong.kt @@ -0,0 +1,112 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.CenterFocusStrong: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.CenterFocusStrong", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 680f) + quadToRelative(-83f, 0f, -141.5f, -58.5f) + reflectiveQuadTo(280f, 480f) + quadToRelative(0f, -83f, 58.5f, -141.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(83f, 0f, 141.5f, 58.5f) + reflectiveQuadTo(680f, 480f) + quadToRelative(0f, 83f, -58.5f, 141.5f) + reflectiveQuadTo(480f, 680f) + close() + moveTo(200f, 840f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 760f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 640f) + verticalLineToRelative(120f) + 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(760f, 840f) + lineTo(640f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 760f) + horizontalLineToRelative(120f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 640f) + verticalLineToRelative(120f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 840f) + close() + moveTo(120f, 320f) + verticalLineToRelative(-120f) + 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(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 320f) + close() + moveTo(760f, 320f) + verticalLineToRelative(-120f) + lineTo(640f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 120f) + horizontalLineToRelative(120f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(840f, 200f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 320f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ChangeCircle.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ChangeCircle.kt new file mode 100644 index 0000000..3e41671 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ChangeCircle.kt @@ -0,0 +1,120 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ChangeCircle: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ChangeCircle", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(483f, 619f) + quadToRelative(-29f, 1f, -55.5f, -9.5f) + reflectiveQuadTo(381f, 579f) + quadToRelative(-20f, -20f, -30.5f, -45f) + reflectiveQuadTo(340f, 481f) + quadToRelative(0f, -10f, 1f, -19.5f) + reflectiveQuadToRelative(4f, -18.5f) + quadToRelative(4f, -12f, -0.5f, -24f) + reflectiveQuadTo(329f, 402f) + quadToRelative(-12f, -5f, -23.5f, 0f) + reflectiveQuadTo(290f, 419f) + quadToRelative(-5f, 15f, -7.5f, 30f) + reflectiveQuadToRelative(-2.5f, 31f) + quadToRelative(0f, 40f, 15.5f, 76.5f) + reflectiveQuadTo(339f, 621f) + quadToRelative(27f, 28f, 63.5f, 43f) + reflectiveQuadToRelative(75.5f, 16f) + lineToRelative(-17f, 17f) + quadToRelative(-9f, 9f, -9f, 21f) + reflectiveQuadToRelative(9f, 21f) + quadToRelative(9f, 9f, 21f, 9f) + reflectiveQuadToRelative(21f, -9f) + lineToRelative(64f, -64f) + quadToRelative(12f, -12f, 12f, -28f) + reflectiveQuadToRelative(-12f, -28f) + lineToRelative(-64f, -64f) + quadToRelative(-9f, -9f, -21f, -9f) + reflectiveQuadToRelative(-21f, 9f) + quadToRelative(-9f, 9f, -9f, 21f) + reflectiveQuadToRelative(9f, 21f) + lineToRelative(22f, 22f) + close() + moveTo(476f, 340f) + quadToRelative(29f, 0f, 56f, 10.5f) + reflectiveQuadToRelative(47f, 30.5f) + quadToRelative(20f, 20f, 30.5f, 45f) + reflectiveQuadToRelative(10.5f, 53f) + quadToRelative(0f, 10f, -1f, 19.5f) + reflectiveQuadToRelative(-4f, 18.5f) + quadToRelative(-4f, 12f, 0.5f, 24.5f) + reflectiveQuadTo(631f, 559f) + quadToRelative(12f, 5f, 23.5f, 0f) + reflectiveQuadToRelative(15.5f, -17f) + quadToRelative(5f, -15f, 7.5f, -30.5f) + reflectiveQuadTo(680f, 480f) + quadToRelative(0f, -40f, -14.5f, -76.5f) + reflectiveQuadTo(622f, 338f) + quadToRelative(-28f, -28f, -64.5f, -42.5f) + reflectiveQuadTo(482f, 281f) + lineToRelative(18f, -18f) + quadToRelative(8f, -9f, 8f, -21f) + reflectiveQuadToRelative(-9f, -21f) + quadToRelative(-9f, -9f, -21f, -9f) + reflectiveQuadToRelative(-21f, 9f) + lineToRelative(-64f, 64f) + quadToRelative(-12f, 12f, -12f, 28f) + reflectiveQuadToRelative(12f, 28f) + lineToRelative(64f, 64f) + quadToRelative(9f, 9f, 21f, 9f) + reflectiveQuadToRelative(21f, -9f) + quadToRelative(9f, -9f, 9f, -21f) + reflectiveQuadToRelative(-9f, -21f) + lineToRelative(-23f, -23f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ChangeHistory.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ChangeHistory.kt new file mode 100644 index 0000000..ceaeb41 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ChangeHistory.kt @@ -0,0 +1,56 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ChangeHistory: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.ChangeHistory", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(152f, 800f) + quadToRelative(-23f, 0f, -35f, -20.5f) + reflectiveQuadToRelative(1f, -40.5f) + lineToRelative(328f, -525f) + quadToRelative(12f, -19f, 34f, -19f) + reflectiveQuadToRelative(34f, 19f) + lineToRelative(328f, 525f) + quadToRelative(13f, 20f, 1f, 40.5f) + reflectiveQuadTo(808f, 800f) + lineTo(152f, 800f) + close() + moveTo(224f, 720f) + horizontalLineToRelative(512f) + lineTo(480f, 310f) + lineTo(224f, 720f) + close() + moveTo(480f, 515f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Check.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Check.kt new file mode 100644 index 0000000..f4d8fa0 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Check.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Check: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Check", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(382f, 606f) + lineToRelative(339f, -339f) + quadToRelative(12f, -12f, 28f, -12f) + reflectiveQuadToRelative(28f, 12f) + quadToRelative(12f, 12f, 12f, 28.5f) + reflectiveQuadTo(777f, 324f) + lineTo(410f, 692f) + quadToRelative(-12f, 12f, -28f, 12f) + reflectiveQuadToRelative(-28f, -12f) + lineTo(182f, 520f) + quadToRelative(-12f, -12f, -11.5f, -28.5f) + reflectiveQuadTo(183f, 463f) + quadToRelative(12f, -12f, 28.5f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + lineToRelative(142f, 143f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CheckBoxOutlineBlank.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CheckBoxOutlineBlank.kt new file mode 100644 index 0000000..f773610 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CheckBoxOutlineBlank.kt @@ -0,0 +1,58 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.CheckBoxOutlineBlank: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.CheckBoxOutlineBlank", + 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() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CheckCircle.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CheckCircle.kt new file mode 100644 index 0000000..d84f1c9 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CheckCircle.kt @@ -0,0 +1,131 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.CheckCircle: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.CheckCircle", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(424f, 552f) + lineToRelative(-86f, -86f) + quadToRelative(-11f, -11f, -28f, -11f) + reflectiveQuadToRelative(-28f, 11f) + quadToRelative(-11f, 11f, -11f, 28f) + reflectiveQuadToRelative(11f, 28f) + lineToRelative(114f, 114f) + quadToRelative(12f, 12f, 28f, 12f) + reflectiveQuadToRelative(28f, -12f) + lineToRelative(226f, -226f) + quadToRelative(11f, -11f, 11f, -28f) + reflectiveQuadToRelative(-11f, -28f) + quadToRelative(-11f, -11f, -28f, -11f) + reflectiveQuadToRelative(-28f, 11f) + lineTo(424f, 552f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} + +val Icons.Rounded.CheckCircle: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.CheckCircle", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(424f, 552f) + lineToRelative(-86f, -86f) + quadToRelative(-11f, -11f, -28f, -11f) + reflectiveQuadToRelative(-28f, 11f) + quadToRelative(-11f, 11f, -11f, 28f) + reflectiveQuadToRelative(11f, 28f) + lineToRelative(114f, 114f) + quadToRelative(12f, 12f, 28f, 12f) + reflectiveQuadToRelative(28f, -12f) + lineToRelative(226f, -226f) + quadToRelative(11f, -11f, 11f, -28f) + reflectiveQuadToRelative(-11f, -28f) + quadToRelative(-11f, -11f, -28f, -11f) + reflectiveQuadToRelative(-28f, 11f) + lineTo(424f, 552f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Circle.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Circle.kt new file mode 100644 index 0000000..9684b70 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Circle.kt @@ -0,0 +1,99 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Circle: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Circle", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} + +val Icons.Rounded.Circle: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Circle", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ClipboardFile.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ClipboardFile.kt new file mode 100644 index 0000000..f5cff8b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ClipboardFile.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Close.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Close.kt new file mode 100644 index 0000000..cb17f62 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Close.kt @@ -0,0 +1,64 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Close: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Close", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 536f) + lineTo(284f, 732f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + lineToRelative(196f, -196f) + lineToRelative(-196f, -196f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(196f, 196f) + lineToRelative(196f, -196f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineTo(536f, 480f) + lineToRelative(196f, 196f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(480f, 536f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Cloud.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Cloud.kt new file mode 100644 index 0000000..1672a53 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Cloud.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Cloud: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Cloud", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(260f, 800f) + quadToRelative(-91f, 0f, -155.5f, -63f) + reflectiveQuadTo(40f, 583f) + quadToRelative(0f, -78f, 47f, -139f) + reflectiveQuadToRelative(123f, -78f) + quadToRelative(25f, -92f, 100f, -149f) + reflectiveQuadToRelative(170f, -57f) + quadToRelative(117f, 0f, 198.5f, 81.5f) + reflectiveQuadTo(760f, 440f) + quadToRelative(69f, 8f, 114.5f, 59.5f) + reflectiveQuadTo(920f, 620f) + quadToRelative(0f, 75f, -52.5f, 127.5f) + reflectiveQuadTo(740f, 800f) + lineTo(260f, 800f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Code.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Code.kt new file mode 100644 index 0000000..331223b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Code.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Code: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Code", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(193f, 481f) + lineToRelative(155f, 155f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(108f, 508f) + quadToRelative(-6f, -6f, -8.5f, -13f) + reflectiveQuadTo(97f, 480f) + quadToRelative(0f, -8f, 2.5f, -15f) + reflectiveQuadToRelative(8.5f, -13f) + lineToRelative(184f, -184f) + quadToRelative(12f, -12f, 28.5f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + quadToRelative(12f, 12f, 12f, 28.5f) + reflectiveQuadTo(349f, 325f) + lineTo(193f, 481f) + close() + moveTo(767f, 479f) + lineTo(612f, 324f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(184f, 184f) + quadToRelative(6f, 6f, 8.5f, 13f) + reflectiveQuadToRelative(2.5f, 15f) + quadToRelative(0f, 8f, -2.5f, 15f) + reflectiveQuadToRelative(-8.5f, 13f) + lineTo(668f, 692f) + quadToRelative(-12f, 12f, -28f, 11.5f) + reflectiveQuadTo(612f, 691f) + quadToRelative(-12f, -12f, -12f, -28.5f) + reflectiveQuadToRelative(12f, -28.5f) + lineToRelative(155f, -155f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Collage.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Collage.kt new file mode 100644 index 0000000..b0d4a8a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Collage.kt @@ -0,0 +1,303 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + moveTo(15f, 13f) + lineToRelative(0f, 0f) + lineToRelative(0f, 0f) + lineToRelative(0f, 0f) + lineToRelative(0f, 0f) + lineToRelative(0f, 0f) + close() + } + path( + fill = SolidColor(Color.Black), + 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.Black), + 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.Black), + 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.Black), + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Compare.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Compare.kt new file mode 100644 index 0000000..32ab152 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Compare.kt @@ -0,0 +1,156 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CompareArrows.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CompareArrows.kt new file mode 100644 index 0000000..b42ab63 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CompareArrows.kt @@ -0,0 +1,86 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Complementary.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Complementary.kt new file mode 100644 index 0000000..0592472 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Complementary.kt @@ -0,0 +1,59 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Contacts.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Contacts.kt new file mode 100644 index 0000000..9b0d953 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Contacts.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Contacts: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Contacts", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 520f) + quadToRelative(50f, 0f, 85f, -35f) + reflectiveQuadToRelative(35f, -85f) + quadToRelative(0f, -50f, -35f, -85f) + reflectiveQuadToRelative(-85f, -35f) + quadToRelative(-50f, 0f, -85f, 35f) + reflectiveQuadToRelative(-35f, 85f) + quadToRelative(0f, 50f, 35f, 85f) + reflectiveQuadToRelative(85f, 35f) + 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(230f, 720f) + horizontalLineToRelative(500f) + quadToRelative(-45f, -56f, -109f, -88f) + reflectiveQuadToRelative(-141f, -32f) + quadToRelative(-77f, 0f, -141f, 32f) + reflectiveQuadToRelative(-109f, 88f) + close() + moveTo(200f, 920f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 880f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 840f) + horizontalLineToRelative(560f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 880f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 920f) + lineTo(200f, 920f) + close() + moveTo(200f, 120f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 40f) + horizontalLineToRelative(560f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 120f) + lineTo(200f, 120f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContentCopy.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContentCopy.kt new file mode 100644 index 0000000..c7d1e9e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContentCopy.kt @@ -0,0 +1,78 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ContentCopy: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ContentCopy", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(360f, 720f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(280f, 640f) + verticalLineToRelative(-480f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(360f, 80f) + horizontalLineToRelative(360f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(800f, 160f) + verticalLineToRelative(480f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 720f) + lineTo(360f, 720f) + close() + moveTo(360f, 640f) + horizontalLineToRelative(360f) + verticalLineToRelative(-480f) + lineTo(360f, 160f) + verticalLineToRelative(480f) + close() + moveTo(200f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 800f) + verticalLineToRelative(-520f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 280f) + verticalLineToRelative(520f) + horizontalLineToRelative(400f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(640f, 840f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(600f, 880f) + lineTo(200f, 880f) + close() + moveTo(360f, 640f) + verticalLineToRelative(-480f) + verticalLineToRelative(480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContentPaste.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContentPaste.kt new file mode 100644 index 0000000..fa84034 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContentPaste.kt @@ -0,0 +1,81 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ContentPaste: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ContentPaste", + 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(167f) + quadToRelative(11f, -35f, 43f, -57.5f) + reflectiveQuadToRelative(70f, -22.5f) + quadToRelative(40f, 0f, 71.5f, 22.5f) + reflectiveQuadTo(594f, 120f) + horizontalLineToRelative(166f) + 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) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 320f) + lineTo(320f, 320f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 280f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(560f) + close() + moveTo(480f, 200f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 160f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 120f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 160f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 200f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContentPasteGo.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContentPasteGo.kt new file mode 100644 index 0000000..a6ee7e9 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContentPasteGo.kt @@ -0,0 +1,107 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ContentPasteGo: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ContentPasteGo", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(727f, 720f) + lineTo(520f, 720f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(480f, 680f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(520f, 640f) + horizontalLineToRelative(207f) + lineToRelative(-36f, -36f) + quadToRelative(-11f, -11f, -11f, -27.5f) + reflectiveQuadToRelative(12f, -28.5f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(104f, 104f) + quadToRelative(12f, 12f, 12f, 28f) + reflectiveQuadToRelative(-12f, 28f) + lineTo(748f, 812f) + quadToRelative(-12f, 12f, -28f, 11.5f) + reflectiveQuadTo(692f, 811f) + quadToRelative(-11f, -12f, -11.5f, -28f) + reflectiveQuadToRelative(11.5f, -28f) + lineToRelative(35f, -35f) + 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(167f) + quadToRelative(11f, -35f, 43f, -57.5f) + reflectiveQuadToRelative(70f, -22.5f) + quadToRelative(40f, 0f, 71.5f, 22.5f) + reflectiveQuadTo(594f, 120f) + horizontalLineToRelative(166f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(840f, 200f) + verticalLineToRelative(200f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 440f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 400f) + verticalLineToRelative(-200f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 320f) + lineTo(320f, 320f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 280f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(560f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(400f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(360f, 840f) + lineTo(200f, 840f) + close() + moveTo(480f, 200f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 160f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 120f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 160f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 200f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContentPasteOff.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContentPasteOff.kt new file mode 100644 index 0000000..6c931c7 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContentPasteOff.kt @@ -0,0 +1,98 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ContentPasteOff: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ContentPasteOff", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(800f, 680f) + quadToRelative(-15f, 0f, -27.5f, -10.5f) + reflectiveQuadTo(760f, 639f) + verticalLineToRelative(-439f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 320f) + lineTo(467f, 320f) + quadToRelative(-16f, 0f, -30.5f, -6f) + reflectiveQuadTo(411f, 297f) + lineTo(302f, 188f) + quadToRelative(-6f, -6f, -9f, -13f) + reflectiveQuadToRelative(-3f, -15f) + quadToRelative(0f, -16f, 11.5f, -28f) + reflectiveQuadToRelative(29.5f, -12f) + horizontalLineToRelative(36f) + quadToRelative(11f, -35f, 43f, -57.5f) + reflectiveQuadToRelative(70f, -22.5f) + quadToRelative(40f, 0f, 71.5f, 22.5f) + reflectiveQuadTo(594f, 120f) + horizontalLineToRelative(166f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(840f, 200f) + verticalLineToRelative(440f) + quadToRelative(0f, 20f, -12.5f, 30f) + reflectiveQuadTo(800f, 680f) + close() + moveTo(480f, 200f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 160f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 120f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 160f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 200f) + close() + moveTo(646f, 760f) + lineTo(200f, 314f) + verticalLineToRelative(446f) + horizontalLineToRelative(446f) + close() + moveTo(200f, 840f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 760f) + verticalLineToRelative(-526f) + lineToRelative(-37f, -37f) + quadToRelative(-12f, -12f, -12f, -28.5f) + reflectiveQuadTo(83f, 140f) + quadToRelative(12f, -12f, 28.5f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + lineToRelative(680f, 680f) + quadToRelative(12f, 12f, 12f, 28f) + reflectiveQuadToRelative(-12f, 28f) + quadToRelative(-12f, 12f, -28.5f, 12f) + reflectiveQuadTo(763f, 876f) + lineToRelative(-37f, -36f) + lineTo(200f, 840f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContractEdit.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContractEdit.kt new file mode 100644 index 0000000..e2460d6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContractEdit.kt @@ -0,0 +1,141 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContractImage.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContractImage.kt new file mode 100644 index 0000000..9220801 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContractImage.kt @@ -0,0 +1,210 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Contrast.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Contrast.kt new file mode 100644 index 0000000..aee9f62 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Contrast.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Contrast: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Contrast", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(520f, 797f) + quadToRelative(119f, -15f, 199.5f, -104.5f) + reflectiveQuadTo(800f, 480f) + quadToRelative(0f, -123f, -80.5f, -212.5f) + reflectiveQuadTo(520f, 163f) + verticalLineToRelative(634f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Cool.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Cool.kt new file mode 100644 index 0000000..e4ec0e1 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Cool.kt @@ -0,0 +1,149 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CopyAll.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CopyAll.kt new file mode 100644 index 0000000..846c9b6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CopyAll.kt @@ -0,0 +1,140 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.CopyAll: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.CopyAll", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(360f, 720f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(280f, 640f) + verticalLineToRelative(-480f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(360f, 80f) + horizontalLineToRelative(360f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(800f, 160f) + verticalLineToRelative(480f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 720f) + lineTo(360f, 720f) + close() + moveTo(360f, 640f) + horizontalLineToRelative(360f) + verticalLineToRelative(-480f) + lineTo(360f, 160f) + verticalLineToRelative(480f) + close() + moveTo(540f, 400f) + close() + moveTo(131.5f, 308.5f) + quadTo(120f, 297f, 120f, 280f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(143f, 240f, 160f, 240f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(200f, 263f, 200f, 280f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(177f, 320f, 160f, 320f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + moveTo(131.5f, 448.5f) + quadTo(120f, 437f, 120f, 420f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(143f, 380f, 160f, 380f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(200f, 403f, 200f, 420f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(177f, 460f, 160f, 460f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + moveTo(131.5f, 588.5f) + quadTo(120f, 577f, 120f, 560f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(143f, 520f, 160f, 520f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(200f, 543f, 200f, 560f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(177f, 600f, 160f, 600f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + moveTo(131.5f, 728.5f) + quadTo(120f, 717f, 120f, 700f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(143f, 660f, 160f, 660f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(200f, 683f, 200f, 700f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(177f, 740f, 160f, 740f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + moveTo(131.5f, 868.5f) + quadTo(120f, 857f, 120f, 840f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(143f, 800f, 160f, 800f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(200f, 823f, 200f, 840f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(177f, 880f, 160f, 880f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + moveTo(271.5f, 868.5f) + quadTo(260f, 857f, 260f, 840f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(283f, 800f, 300f, 800f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(340f, 823f, 340f, 840f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(317f, 880f, 300f, 880f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + moveTo(411.5f, 868.5f) + quadTo(400f, 857f, 400f, 840f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(423f, 800f, 440f, 800f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(480f, 823f, 480f, 840f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(457f, 880f, 440f, 880f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + moveTo(551.5f, 868.5f) + quadTo(540f, 857f, 540f, 840f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(563f, 800f, 580f, 800f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(620f, 823f, 620f, 840f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(597f, 880f, 580f, 880f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Counter.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Counter.kt new file mode 100644 index 0000000..9c0cc96 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Counter.kt @@ -0,0 +1,206 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Crashlytics.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Crashlytics.kt new file mode 100644 index 0000000..c722479 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Crashlytics.kt @@ -0,0 +1,91 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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.Black), 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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CreateNewFolder.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CreateNewFolder.kt new file mode 100644 index 0000000..2e31d9b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CreateNewFolder.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.CreateNewFolder: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.CreateNewFolder", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + 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(207f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(57f, 57f) + horizontalLineToRelative(320f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 320f) + verticalLineToRelative(400f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 800f) + lineTo(160f, 800f) + close() + moveTo(160f, 720f) + horizontalLineToRelative(640f) + verticalLineToRelative(-400f) + lineTo(447f, 320f) + lineToRelative(-80f, -80f) + lineTo(160f, 240f) + verticalLineToRelative(480f) + close() + moveTo(160f, 720f) + verticalLineToRelative(-480f) + verticalLineToRelative(480f) + close() + moveTo(560f, 560f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(600f, 640f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(640f, 600f) + verticalLineToRelative(-40f) + horizontalLineToRelative(40f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(720f, 520f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(680f, 480f) + horizontalLineToRelative(-40f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(600f, 400f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(560f, 440f) + verticalLineToRelative(40f) + horizontalLineToRelative(-40f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(480f, 520f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(520f, 560f) + horizontalLineToRelative(40f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CropFree.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CropFree.kt new file mode 100644 index 0000000..54e5171 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CropFree.kt @@ -0,0 +1,102 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.CropFree: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.CropFree", + 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(-120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 640f) + verticalLineToRelative(120f) + 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(760f, 840f) + lineTo(640f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 760f) + horizontalLineToRelative(120f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 640f) + verticalLineToRelative(120f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 840f) + close() + moveTo(120f, 320f) + verticalLineToRelative(-120f) + 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(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 320f) + close() + moveTo(760f, 320f) + verticalLineToRelative(-120f) + lineTo(640f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 120f) + horizontalLineToRelative(120f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(840f, 200f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 320f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CropSmall.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CropSmall.kt new file mode 100644 index 0000000..2bae570 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CropSmall.kt @@ -0,0 +1,182 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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) { + Builder( + name = "TwoTone.CropSmall", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + 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.Black)) { + 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.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(7.571f, 7.571f) + horizontalLineToRelative(8.857f) + verticalLineToRelative(8.852f) + horizontalLineToRelative(-8.857f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Crown.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Crown.kt new file mode 100644 index 0000000..fa237cf --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Crown.kt @@ -0,0 +1,91 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Rounded.Crown: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Crown", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(240f, 800f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(200f, 760f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(240f, 720f) + horizontalLineToRelative(480f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(760f, 760f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(720f, 800f) + lineTo(240f, 800f) + close() + moveTo(268f, 660f) + quadToRelative(-29f, 0f, -51.5f, -19f) + reflectiveQuadTo(189f, 593f) + lineToRelative(-40f, -254f) + quadToRelative(-2f, 0f, -4.5f, 0.5f) + reflectiveQuadToRelative(-4.5f, 0.5f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(80f, 280f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(140f, 220f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(200f, 280f) + quadToRelative(0f, 7f, -1.5f, 13f) + reflectiveQuadToRelative(-3.5f, 11f) + lineToRelative(125f, 56f) + lineToRelative(125f, -171f) + quadToRelative(-11f, -8f, -18f, -21f) + reflectiveQuadToRelative(-7f, -28f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(540f, 140f) + quadToRelative(0f, 15f, -7f, 28f) + reflectiveQuadToRelative(-18f, 21f) + lineToRelative(125f, 171f) + lineToRelative(125f, -56f) + quadToRelative(-2f, -5f, -3.5f, -11f) + reflectiveQuadToRelative(-1.5f, -13f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(820f, 220f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(880f, 280f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(820f, 340f) + quadToRelative(-2f, 0f, -4.5f, -0.5f) + reflectiveQuadToRelative(-4.5f, -0.5f) + lineToRelative(-40f, 254f) + quadToRelative(-5f, 29f, -27.5f, 48f) + reflectiveQuadTo(692f, 660f) + lineTo(268f, 660f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Cube.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Cube.kt new file mode 100644 index 0000000..b37a9b4 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Cube.kt @@ -0,0 +1,128 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Curve.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Curve.kt new file mode 100644 index 0000000..f844716 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Curve.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DarkMode.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DarkMode.kt new file mode 100644 index 0000000..bf98c04 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DarkMode.kt @@ -0,0 +1,111 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.DarkMode: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.DarkMode", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 840f) + quadToRelative(-151f, 0f, -255.5f, -104.5f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, -138f, 90f, -239.5f) + reflectiveQuadTo(440f, 122f) + quadToRelative(13f, -2f, 23f, 3.5f) + reflectiveQuadToRelative(16f, 14.5f) + quadToRelative(6f, 9f, 6.5f, 21f) + reflectiveQuadToRelative(-7.5f, 23f) + quadToRelative(-17f, 26f, -25.5f, 55f) + reflectiveQuadToRelative(-8.5f, 61f) + quadToRelative(0f, 90f, 63f, 153f) + reflectiveQuadToRelative(153f, 63f) + quadToRelative(31f, 0f, 61.5f, -9f) + reflectiveQuadToRelative(54.5f, -25f) + quadToRelative(11f, -7f, 22.5f, -6.5f) + reflectiveQuadTo(819f, 481f) + quadToRelative(10f, 5f, 15.5f, 15f) + reflectiveQuadToRelative(3.5f, 24f) + quadToRelative(-14f, 138f, -117.5f, 229f) + reflectiveQuadTo(480f, 840f) + close() + moveTo(480f, 760f) + quadToRelative(88f, 0f, 158f, -48.5f) + reflectiveQuadTo(740f, 585f) + quadToRelative(-20f, 5f, -40f, 8f) + reflectiveQuadToRelative(-40f, 3f) + quadToRelative(-123f, 0f, -209.5f, -86.5f) + reflectiveQuadTo(364f, 300f) + quadToRelative(0f, -20f, 3f, -40f) + reflectiveQuadToRelative(8f, -40f) + quadToRelative(-78f, 32f, -126.5f, 102f) + reflectiveQuadTo(200f, 480f) + quadToRelative(0f, 116f, 82f, 198f) + reflectiveQuadToRelative(198f, 82f) + close() + moveTo(470f, 490f) + close() + } + }.build() +} + +val Icons.Rounded.DarkMode: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.DarkMode", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 840f) + quadToRelative(-151f, 0f, -255.5f, -104.5f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, -138f, 90f, -239.5f) + reflectiveQuadTo(440f, 122f) + quadToRelative(13f, -2f, 23f, 3.5f) + reflectiveQuadToRelative(16f, 14.5f) + quadToRelative(6f, 9f, 6.5f, 21f) + reflectiveQuadToRelative(-7.5f, 23f) + quadToRelative(-17f, 26f, -25.5f, 55f) + reflectiveQuadToRelative(-8.5f, 61f) + quadToRelative(0f, 90f, 63f, 153f) + reflectiveQuadToRelative(153f, 63f) + quadToRelative(31f, 0f, 61.5f, -9f) + reflectiveQuadToRelative(54.5f, -25f) + quadToRelative(11f, -7f, 22.5f, -6.5f) + reflectiveQuadTo(819f, 481f) + quadToRelative(10f, 5f, 15.5f, 15f) + reflectiveQuadToRelative(3.5f, 24f) + quadToRelative(-14f, 138f, -117.5f, 229f) + reflectiveQuadTo(480f, 840f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DashboardCustomize.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DashboardCustomize.kt new file mode 100644 index 0000000..992b07a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DashboardCustomize.kt @@ -0,0 +1,136 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.DashboardCustomize: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.DashboardCustomize", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 440f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 400f) + verticalLineToRelative(-240f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(440f, 160f) + verticalLineToRelative(240f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(400f, 440f) + lineTo(160f, 440f) + close() + moveTo(200f, 200f) + verticalLineToRelative(160f) + verticalLineToRelative(-160f) + close() + moveTo(560f, 440f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(520f, 400f) + verticalLineToRelative(-240f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(560f, 120f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + verticalLineToRelative(240f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 440f) + lineTo(560f, 440f) + close() + moveTo(600f, 200f) + verticalLineToRelative(160f) + verticalLineToRelative(-160f) + close() + moveTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + verticalLineToRelative(-240f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 520f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(440f, 560f) + verticalLineToRelative(240f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(400f, 840f) + lineTo(160f, 840f) + close() + moveTo(200f, 600f) + verticalLineToRelative(160f) + verticalLineToRelative(-160f) + close() + moveTo(680f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(640f, 800f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-81f) + quadToRelative(-17f, 0f, -28f, -11.5f) + reflectiveQuadTo(520f, 680f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(560f, 640f) + horizontalLineToRelative(80f) + verticalLineToRelative(-81f) + quadToRelative(0f, -17f, 11.5f, -28f) + reflectiveQuadToRelative(28.5f, -11f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(720f, 560f) + verticalLineToRelative(80f) + horizontalLineToRelative(81f) + quadToRelative(17f, 0f, 28f, 11.5f) + reflectiveQuadToRelative(11f, 28.5f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 720f) + horizontalLineToRelative(-80f) + verticalLineToRelative(81f) + quadToRelative(0f, 17f, -11.5f, 28f) + reflectiveQuadTo(680f, 840f) + close() + moveTo(600f, 200f) + verticalLineToRelative(160f) + horizontalLineToRelative(160f) + verticalLineToRelative(-160f) + lineTo(600f, 200f) + close() + moveTo(200f, 200f) + verticalLineToRelative(160f) + horizontalLineToRelative(160f) + verticalLineToRelative(-160f) + lineTo(200f, 200f) + close() + moveTo(200f, 600f) + verticalLineToRelative(160f) + horizontalLineToRelative(160f) + verticalLineToRelative(-160f) + lineTo(200f, 600f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DashedLine.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DashedLine.kt new file mode 100644 index 0000000..862869f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DashedLine.kt @@ -0,0 +1,61 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DataSaverOff.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DataSaverOff.kt new file mode 100644 index 0000000..741f4a8 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DataSaverOff.kt @@ -0,0 +1,80 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.DataSaverOff: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.DataSaverOff", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(12f, 22f) + quadTo(9.925f, 22f, 8.1f, 21.212f) + quadTo(6.275f, 20.425f, 4.925f, 19.075f) + quadTo(3.575f, 17.725f, 2.787f, 15.9f) + quadTo(2f, 14.075f, 2f, 12f) + quadTo(2f, 8.75f, 3.875f, 6.15f) + quadTo(5.75f, 3.55f, 8.85f, 2.525f) + quadTo(9.575f, 2.275f, 10.188f, 2.7f) + quadTo(10.8f, 3.125f, 10.8f, 3.85f) + quadTo(10.8f, 4.35f, 10.512f, 4.762f) + quadTo(10.225f, 5.175f, 9.775f, 5.325f) + quadTo(7.625f, 6f, 6.313f, 7.838f) + quadTo(5f, 9.675f, 5f, 12f) + quadTo(5f, 14.925f, 7.037f, 16.962f) + quadTo(9.075f, 19f, 12f, 19f) + quadTo(13.3f, 19f, 14.512f, 18.55f) + quadTo(15.725f, 18.1f, 16.675f, 17.25f) + quadTo(17.05f, 16.9f, 17.587f, 16.9f) + quadTo(18.125f, 16.9f, 18.5f, 17.25f) + quadTo(19.075f, 17.775f, 19.1f, 18.438f) + quadTo(19.125f, 19.1f, 18.55f, 19.6f) + quadTo(17.2f, 20.775f, 15.538f, 21.388f) + quadTo(13.875f, 22f, 12f, 22f) + close() + moveTo(19f, 12f) + quadTo(19f, 9.7f, 17.675f, 7.863f) + quadTo(16.35f, 6.025f, 14.2f, 5.325f) + quadTo(13.75f, 5.175f, 13.462f, 4.762f) + quadTo(13.175f, 4.35f, 13.175f, 3.85f) + quadTo(13.175f, 3.125f, 13.788f, 2.7f) + quadTo(14.4f, 2.275f, 15.125f, 2.525f) + quadTo(18.25f, 3.575f, 20.125f, 6.175f) + quadTo(22f, 8.775f, 22f, 12f) + quadTo(22f, 12.45f, 21.962f, 12.913f) + quadTo(21.925f, 13.375f, 21.825f, 13.925f) + quadTo(21.7f, 14.65f, 21.087f, 14.962f) + quadTo(20.475f, 15.275f, 19.75f, 15f) + quadTo(19.275f, 14.825f, 19.013f, 14.363f) + quadTo(18.75f, 13.9f, 18.85f, 13.4f) + quadTo(18.925f, 12.975f, 18.962f, 12.65f) + quadTo(19f, 12.325f, 19f, 12f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Database.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Database.kt new file mode 100644 index 0000000..22dbd78 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Database.kt @@ -0,0 +1,71 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DateRange.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DateRange.kt new file mode 100644 index 0000000..0b07917 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DateRange.kt @@ -0,0 +1,189 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.DateRange: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.DateRange", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(320f, 560f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 520f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 480f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 520f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 560f) + close() + moveTo(480f, 560f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 520f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 480f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 520f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 560f) + close() + moveTo(640f, 560f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 520f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 480f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 520f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 560f) + close() + moveTo(200f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 800f) + verticalLineToRelative(-560f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 160f) + horizontalLineToRelative(40f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(280f, 80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(320f, 120f) + verticalLineToRelative(40f) + horizontalLineToRelative(320f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(680f, 80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(720f, 120f) + verticalLineToRelative(40f) + horizontalLineToRelative(40f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(840f, 240f) + verticalLineToRelative(560f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 880f) + lineTo(200f, 880f) + close() + moveTo(200f, 800f) + horizontalLineToRelative(560f) + verticalLineToRelative(-400f) + lineTo(200f, 400f) + verticalLineToRelative(400f) + close() + moveTo(200f, 320f) + horizontalLineToRelative(560f) + verticalLineToRelative(-80f) + lineTo(200f, 240f) + verticalLineToRelative(80f) + close() + moveTo(200f, 320f) + verticalLineToRelative(-80f) + verticalLineToRelative(80f) + close() + } + }.build() +} + +val Icons.Rounded.DateRange: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.DateRange", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(291.5f, 548.5f) + quadTo(280f, 537f, 280f, 520f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(303f, 480f, 320f, 480f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(360f, 503f, 360f, 520f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(337f, 560f, 320f, 560f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + moveTo(451.5f, 548.5f) + quadTo(440f, 537f, 440f, 520f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(463f, 480f, 480f, 480f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(520f, 503f, 520f, 520f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(497f, 560f, 480f, 560f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + moveTo(611.5f, 548.5f) + quadTo(600f, 537f, 600f, 520f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(623f, 480f, 640f, 480f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(680f, 503f, 680f, 520f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(657f, 560f, 640f, 560f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + moveTo(200f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 800f) + verticalLineToRelative(-560f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 160f) + horizontalLineToRelative(40f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(280f, 80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(320f, 120f) + verticalLineToRelative(40f) + horizontalLineToRelative(320f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(680f, 80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(720f, 120f) + verticalLineToRelative(40f) + horizontalLineToRelative(40f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(840f, 240f) + verticalLineToRelative(560f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 880f) + lineTo(200f, 880f) + close() + moveTo(200f, 800f) + horizontalLineToRelative(560f) + verticalLineToRelative(-400f) + lineTo(200f, 400f) + verticalLineToRelative(400f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Delete.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Delete.kt new file mode 100644 index 0000000..c720b1d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Delete.kt @@ -0,0 +1,157 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DeleteSweep.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DeleteSweep.kt new file mode 100644 index 0000000..db5d170 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DeleteSweep.kt @@ -0,0 +1,198 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DensitySmall.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DensitySmall.kt new file mode 100644 index 0000000..06d5158 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DensitySmall.kt @@ -0,0 +1,86 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.DensitySmall: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.DensitySmall", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 880f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 840f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 800f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 840f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 880f) + lineTo(160f, 880f) + close() + moveTo(160f, 640f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 600f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 560f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 600f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 640f) + lineTo(160f, 640f) + close() + moveTo(160f, 400f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 360f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 320f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 360f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 400f) + lineTo(160f, 400f) + close() + moveTo(160f, 160f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 80f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 160f) + lineTo(160f, 160f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Description.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Description.kt new file mode 100644 index 0000000..7dd56b6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Description.kt @@ -0,0 +1,156 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Description: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Description", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(360f, 720f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(640f, 680f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(600f, 640f) + lineTo(360f, 640f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(320f, 680f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(360f, 720f) + close() + moveTo(360f, 560f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(640f, 520f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(600f, 480f) + lineTo(360f, 480f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(320f, 520f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(360f, 560f) + close() + moveTo(240f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 800f) + verticalLineToRelative(-640f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(240f, 80f) + horizontalLineToRelative(287f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(194f, 194f) + quadToRelative(11f, 11f, 17f, 25.5f) + reflectiveQuadToRelative(6f, 30.5f) + verticalLineToRelative(447f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 880f) + lineTo(240f, 880f) + close() + moveTo(520f, 320f) + verticalLineToRelative(-160f) + lineTo(240f, 160f) + verticalLineToRelative(640f) + horizontalLineToRelative(480f) + verticalLineToRelative(-440f) + lineTo(560f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(520f, 320f) + close() + moveTo(240f, 160f) + verticalLineToRelative(200f) + verticalLineToRelative(-200f) + verticalLineToRelative(640f) + verticalLineToRelative(-640f) + close() + } + }.build() +} + +val Icons.Rounded.Description: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Description", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(360f, 720f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(640f, 680f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(600f, 640f) + lineTo(360f, 640f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(320f, 680f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(360f, 720f) + close() + moveTo(360f, 560f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(640f, 520f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(600f, 480f) + lineTo(360f, 480f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(320f, 520f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(360f, 560f) + close() + moveTo(240f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 800f) + verticalLineToRelative(-640f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(240f, 80f) + horizontalLineToRelative(287f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(194f, 194f) + quadToRelative(11f, 11f, 17f, 25.5f) + reflectiveQuadToRelative(6f, 30.5f) + verticalLineToRelative(447f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 880f) + lineTo(240f, 880f) + close() + moveTo(520f, 320f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(560f, 360f) + horizontalLineToRelative(160f) + lineTo(520f, 160f) + verticalLineToRelative(160f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Deselect.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Deselect.kt new file mode 100644 index 0000000..705a941 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Deselect.kt @@ -0,0 +1,210 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DesignServices.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DesignServices.kt new file mode 100644 index 0000000..16f5ea8 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DesignServices.kt @@ -0,0 +1,241 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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)) { + moveToRelative(352f, 438f) + lineToRelative(86f, -87f) + lineToRelative(-56f, -57f) + lineToRelative(-16f, 16f) + quadToRelative(-11f, 11f, -27.5f, 11.5f) + reflectiveQuadTo(310f, 310f) + quadToRelative(-12f, -12f, -12f, -28.5f) + reflectiveQuadToRelative(12f, -28.5f) + lineToRelative(15f, -15f) + lineToRelative(-45f, -45f) + lineToRelative(-87f, 87f) + lineToRelative(159f, 158f) + close() + moveTo(680f, 767f) + lineTo(767f, 680f) + lineTo(722f, 635f) + lineTo(706f, 650f) + quadToRelative(-12f, 12f, -28f, 12f) + reflectiveQuadToRelative(-28f, -12f) + quadToRelative(-12f, -12f, -12f, -28f) + reflectiveQuadToRelative(12f, -28f) + lineToRelative(15f, -16f) + lineToRelative(-57f, -56f) + lineToRelative(-86f, 86f) + lineToRelative(158f, 159f) + close() + moveTo(649f, 257f) + lineTo(705f, 313f) + lineTo(761f, 257f) + lineTo(704f, 200f) + lineTo(649f, 257f) + close() + moveTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + verticalLineToRelative(-113f) + quadToRelative(0f, -8f, 3f, -15.5f) + reflectiveQuadToRelative(9f, -13.5f) + lineToRelative(163f, -163f) + lineToRelative(-173f, -173f) + quadToRelative(-17f, -17f, -17f, -42f) + reflectiveQuadToRelative(17f, -42f) + lineToRelative(116f, -116f) + quadToRelative(17f, -17f, 42f, -16.5f) + reflectiveQuadToRelative(42f, 17.5f) + lineToRelative(174f, 173f) + lineToRelative(151f, -152f) + quadToRelative(12f, -12f, 27f, -18f) + reflectiveQuadToRelative(31f, -6f) + quadToRelative(16f, 0f, 31f, 6f) + reflectiveQuadToRelative(27f, 18f) + lineToRelative(53f, 54f) + quadToRelative(12f, 12f, 18f, 27f) + reflectiveQuadToRelative(6f, 31f) + quadToRelative(0f, 16f, -6f, 30.5f) + reflectiveQuadTo(816f, 313f) + lineTo(665f, 465f) + lineToRelative(173f, 173f) + quadToRelative(17f, 17f, 17f, 42f) + reflectiveQuadToRelative(-17f, 42f) + lineTo(722f, 838f) + quadToRelative(-17f, 17f, -42f, 17f) + reflectiveQuadToRelative(-42f, -17f) + lineTo(465f, 665f) + lineTo(302f, 828f) + quadToRelative(-6f, 6f, -13.5f, 9f) + reflectiveQuadToRelative(-15.5f, 3f) + lineTo(160f, 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(20.95f, 15.95f) + lineToRelative(-4.325f, -4.325f) + 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) + lineToRelative(-4.35f, -4.325f) + curveToRelative(-0.283f, -0.283f, -0.633f, -0.429f, -1.05f, -0.438f) + curveToRelative(-0.417f, -0.008f, -0.767f, 0.129f, -1.05f, 0.412f) + lineToRelative(-2.9f, 2.9f) + curveToRelative(-0.283f, 0.283f, -0.425f, 0.633f, -0.425f, 1.05f) + reflectiveCurveToRelative(0.142f, 0.767f, 0.425f, 1.05f) + lineToRelative(4.325f, 4.325f) + lineToRelative(-4.075f, 4.075f) + curveToRelative(-0.1f, 0.1f, -0.175f, 0.212f, -0.225f, 0.337f) + reflectiveCurveToRelative(-0.075f, 0.254f, -0.075f, 0.388f) + verticalLineToRelative(2.825f) + 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(2.825f) + curveToRelative(0.133f, 0f, 0.263f, -0.025f, 0.388f, -0.075f) + reflectiveCurveToRelative(0.237f, -0.125f, 0.337f, -0.225f) + lineToRelative(4.075f, -4.075f) + lineToRelative(4.325f, 4.325f) + curveToRelative(0.283f, 0.283f, 0.633f, 0.425f, 1.05f, 0.425f) + reflectiveCurveToRelative(0.767f, -0.142f, 1.05f, -0.425f) + lineToRelative(2.9f, -2.9f) + curveToRelative(0.283f, -0.283f, 0.425f, -0.633f, 0.425f, -1.05f) + reflectiveCurveToRelative(-0.142f, -0.767f, -0.425f, -1.05f) + close() + moveTo(4.825f, 7f) + lineToRelative(2.175f, -2.175f) + lineToRelative(1.125f, 1.125f) + lineToRelative(-0.375f, 0.375f) + curveToRelative(-0.2f, 0.2f, -0.3f, 0.438f, -0.3f, 0.712f) + curveToRelative(0f, 0.275f, 0.1f, 0.513f, 0.3f, 0.713f) + reflectiveCurveToRelative(0.438f, 0.296f, 0.713f, 0.287f) + curveToRelative(0.275f, -0.008f, 0.504f, -0.104f, 0.688f, -0.287f) + lineToRelative(0.4f, -0.4f) + 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(17f, 19.175f) + lineToRelative(-3.95f, -3.975f) + lineToRelative(2.15f, -2.15f) + lineToRelative(1.425f, 1.4f) + lineToRelative(-0.375f, 0.4f) + curveToRelative(-0.2f, 0.2f, -0.3f, 0.433f, -0.3f, 0.7f) + reflectiveCurveToRelative(0.1f, 0.5f, 0.3f, 0.7f) + reflectiveCurveToRelative(0.433f, 0.3f, 0.7f, 0.3f) + reflectiveCurveToRelative(0.5f, -0.1f, 0.7f, -0.3f) + lineToRelative(0.4f, -0.375f) + lineToRelative(1.125f, 1.125f) + lineToRelative(-2.175f, 2.175f) + 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(-0.4f, 0.4f) + curveToRelative(-0.183f, 0.183f, -0.412f, 0.279f, -0.688f, 0.287f) + reflectiveCurveToRelative(-0.512f, -0.087f, -0.712f, -0.287f) + reflectiveCurveToRelative(-0.3f, -0.438f, -0.3f, -0.712f) + reflectiveCurveToRelative(0.1f, -0.512f, 0.3f, -0.712f) + lineToRelative(0.375f, -0.375f) + 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(-0.4f, 0.375f) + curveToRelative(-0.2f, 0.2f, -0.433f, 0.3f, -0.7f, 0.3f) + reflectiveCurveToRelative(-0.5f, -0.1f, -0.7f, -0.3f) + reflectiveCurveToRelative(-0.3f, -0.433f, -0.3f, -0.7f) + reflectiveCurveToRelative(0.1f, -0.5f, 0.3f, -0.7f) + lineToRelative(0.375f, -0.4f) + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Difference.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Difference.kt new file mode 100644 index 0000000..fc4cb5b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Difference.kt @@ -0,0 +1,120 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Difference: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Difference", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(500f, 360f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(540f, 440f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(580f, 400f) + verticalLineToRelative(-40f) + horizontalLineToRelative(40f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(660f, 320f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(620f, 280f) + horizontalLineToRelative(-40f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(540f, 200f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(500f, 240f) + verticalLineToRelative(40f) + horizontalLineToRelative(-40f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(420f, 320f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(460f, 360f) + horizontalLineToRelative(40f) + close() + moveTo(460f, 600f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(660f, 560f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(620f, 520f) + lineTo(460f, 520f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(420f, 560f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(460f, 600f) + close() + moveTo(320f, 760f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(240f, 680f) + verticalLineToRelative(-560f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(320f, 40f) + horizontalLineToRelative(247f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(194f, 194f) + quadToRelative(11f, 11f, 17f, 25.5f) + reflectiveQuadToRelative(6f, 30.5f) + verticalLineToRelative(367f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 760f) + lineTo(320f, 760f) + close() + moveTo(320f, 680f) + horizontalLineToRelative(440f) + verticalLineToRelative(-360f) + lineTo(560f, 120f) + lineTo(320f, 120f) + verticalLineToRelative(560f) + close() + moveTo(160f, 920f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(80f, 840f) + verticalLineToRelative(-520f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(160f, 320f) + verticalLineToRelative(520f) + horizontalLineToRelative(400f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(600f, 880f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(560f, 920f) + lineTo(160f, 920f) + close() + moveTo(320f, 680f) + verticalLineToRelative(-560f) + verticalLineToRelative(560f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DirectionsWalk.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DirectionsWalk.kt new file mode 100644 index 0000000..f979e36 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DirectionsWalk.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.DirectionsWalk: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.DirectionsWalk", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(436f, 596f) + lineTo(371f, 888f) + quadToRelative(-3f, 14f, -14.5f, 23f) + reflectiveQuadTo(330f, 920f) + quadToRelative(-20f, 0f, -32f, -15f) + reflectiveQuadToRelative(-8f, -34f) + lineToRelative(102f, -515f) + lineToRelative(-72f, 28f) + verticalLineToRelative(96f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(280f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(240f, 480f) + verticalLineToRelative(-122f) + quadToRelative(0f, -12f, 6.5f, -21.5f) + reflectiveQuadTo(264f, 322f) + lineToRelative(178f, -76f) + quadToRelative(14f, -6f, 29.5f, -7f) + reflectiveQuadToRelative(29.5f, 4f) + quadToRelative(14f, 5f, 26.5f, 14f) + reflectiveQuadToRelative(20.5f, 23f) + lineToRelative(40f, 64f) + quadToRelative(13f, 20f, 30.5f, 38f) + reflectiveQuadToRelative(39.5f, 31f) + quadToRelative(14f, 8f, 31f, 14.5f) + reflectiveQuadToRelative(34f, 9.5f) + quadToRelative(16f, 3f, 26.5f, 14.5f) + reflectiveQuadTo(760f, 480f) + quadToRelative(0f, 17f, -12f, 28f) + reflectiveQuadToRelative(-29f, 9f) + quadToRelative(-56f, -8f, -100.5f, -35f) + reflectiveQuadTo(541f, 417f) + lineToRelative(-25f, 123f) + lineToRelative(72f, 68f) + quadToRelative(6f, 6f, 9f, 13.5f) + reflectiveQuadToRelative(3f, 15.5f) + verticalLineToRelative(243f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(560f, 920f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(520f, 880f) + verticalLineToRelative(-220f) + lineToRelative(-84f, -64f) + close() + moveTo(540f, 220f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(460f, 140f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(540f, 60f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(620f, 140f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(540f, 220f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DisabledVisible.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DisabledVisible.kt new file mode 100644 index 0000000..716576b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DisabledVisible.kt @@ -0,0 +1,108 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.DisabledVisible: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.DisabledVisible", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(680f, 800f) + quadToRelative(59f, 0f, 109.5f, -27f) + reflectiveQuadToRelative(80.5f, -73f) + quadToRelative(-30f, -46f, -80.5f, -73f) + reflectiveQuadTo(680f, 600f) + quadToRelative(-59f, 0f, -109.5f, 27f) + reflectiveQuadTo(490f, 700f) + quadToRelative(30f, 46f, 80.5f, 73f) + reflectiveQuadTo(680f, 800f) + close() + moveTo(680f, 880f) + quadToRelative(-78f, 0f, -143f, -34f) + reflectiveQuadToRelative(-106f, -91f) + quadToRelative(-8f, -12f, -12.5f, -25.5f) + reflectiveQuadTo(414f, 702f) + quadToRelative(0f, -14f, 4f, -29f) + reflectiveQuadToRelative(13f, -28f) + quadToRelative(40f, -57f, 105.5f, -91f) + reflectiveQuadTo(680f, 520f) + quadToRelative(78f, 0f, 143f, 34f) + reflectiveQuadToRelative(106f, 91f) + quadToRelative(8f, 12f, 12.5f, 25.5f) + reflectiveQuadTo(946f, 698f) + quadToRelative(0f, 14f, -4f, 29f) + reflectiveQuadToRelative(-13f, 28f) + quadToRelative(-40f, 57f, -105.5f, 91f) + reflectiveQuadTo(680f, 880f) + close() + moveTo(680f, 760f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(620f, 700f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(680f, 640f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(740f, 700f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(680f, 760f) + close() + moveTo(319f, 847f) + quadToRelative(-110f, -48f, -174.5f, -147f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(155f, 0f, 267.5f, 101f) + reflectiveQuadTo(877f, 435f) + quadToRelative(2f, 20f, -9f, 30.5f) + reflectiveQuadTo(842f, 478f) + quadToRelative(-15f, 2f, -28.5f, -6.5f) + reflectiveQuadTo(798f, 443f) + quadToRelative(-14f, -121f, -105f, -202f) + reflectiveQuadToRelative(-213f, -81f) + quadToRelative(-56f, 0f, -105.5f, 18f) + reflectiveQuadTo(284f, 228f) + lineToRelative(209f, 209f) + quadToRelative(14f, 14f, 12.5f, 29.5f) + reflectiveQuadTo(493f, 493f) + quadToRelative(-11f, 11f, -26.5f, 12.5f) + reflectiveQuadTo(437f, 493f) + lineTo(228f, 284f) + quadToRelative(-32f, 41f, -50f, 90.5f) + reflectiveQuadTo(160f, 480f) + quadToRelative(0f, 99f, 53f, 177.5f) + reflectiveQuadTo(351f, 773f) + quadToRelative(20f, 8f, 24f, 23.5f) + reflectiveQuadToRelative(-2f, 29.5f) + quadToRelative(-6f, 14f, -20.5f, 21.5f) + reflectiveQuadTo(319f, 847f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DocumentScanner.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DocumentScanner.kt new file mode 100644 index 0000000..89fe6d6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DocumentScanner.kt @@ -0,0 +1,437 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} + +val Icons.Rounded.DocumentScanner: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.DocumentScanner", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(80f, 200f) + verticalLineToRelative(-80f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(160f, 40f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(280f, 80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 120f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(120f, 240f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(80f, 200f) + close() + moveTo(800f, 200f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(680f, 80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(720f, 40f) + horizontalLineToRelative(80f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 120f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 240f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(800f, 200f) + close() + moveTo(80f, 840f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 720f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(160f, 760f) + verticalLineToRelative(80f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(280f, 880f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 920f) + horizontalLineToRelative(-80f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(80f, 840f) + close() + moveTo(800f, 920f) + horizontalLineToRelative(-80f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(680f, 880f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(720f, 840f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(840f, 720f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 760f) + verticalLineToRelative(80f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 920f) + close() + moveTo(280f, 800f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(200f, 720f) + verticalLineToRelative(-480f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(280f, 160f) + horizontalLineToRelative(400f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(760f, 240f) + verticalLineToRelative(480f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(680f, 800f) + lineTo(280f, 800f) + close() + moveTo(400f, 400f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(600f, 360f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(560f, 320f) + lineTo(400f, 320f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(360f, 360f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(400f, 400f) + close() + moveTo(400f, 520f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(600f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(560f, 440f) + lineTo(400f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(360f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(400f, 520f) + close() + moveTo(400f, 640f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(600f, 600f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(560f, 560f) + lineTo(400f, 560f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(360f, 600f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(400f, 640f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Done.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Done.kt new file mode 100644 index 0000000..01c5410 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Done.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Done: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Done", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(381f, 720f) + lineToRelative(424f, -424f) + lineToRelative(-57f, -56f) + lineToRelative(-368f, 367f) + lineToRelative(-169f, -170f) + lineToRelative(-57f, 57f) + lineToRelative(227f, 226f) + close() + moveTo(324f, 776f) + lineTo(98f, 550f) + quadToRelative(-12f, -12f, -17.5f, -26.5f) + reflectiveQuadTo(75f, 494f) + quadToRelative(0f, -15f, 5.5f, -30f) + reflectiveQuadTo(98f, 437f) + lineToRelative(56f, -56f) + quadToRelative(12f, -12f, 26.5f, -18f) + reflectiveQuadToRelative(30.5f, -6f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(26.5f, 18f) + lineToRelative(113f, 113f) + lineToRelative(310f, -311f) + quadToRelative(11f, -12f, 26f, -17.5f) + reflectiveQuadToRelative(30f, -5.5f) + quadToRelative(15f, 0f, 30f, 5.5f) + reflectiveQuadToRelative(27f, 16.5f) + lineToRelative(57f, 56f) + quadToRelative(12f, 12f, 18f, 26.5f) + reflectiveQuadToRelative(6f, 30.5f) + quadToRelative(0f, 16f, -5.5f, 30.5f) + reflectiveQuadTo(862f, 352f) + lineTo(438f, 776f) + quadToRelative(-12f, 12f, -27f, 18f) + reflectiveQuadToRelative(-30f, 6f) + quadToRelative(-15f, 0f, -30f, -6f) + reflectiveQuadToRelative(-27f, -18f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DoorBack.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DoorBack.kt new file mode 100644 index 0000000..98f862f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DoorBack.kt @@ -0,0 +1,78 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.DoorBack: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.DoorBack", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 760f) + horizontalLineToRelative(40f) + verticalLineToRelative(-560f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(280f, 120f) + horizontalLineToRelative(400f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(760f, 200f) + verticalLineToRelative(560f) + horizontalLineToRelative(40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + lineTo(160f, 840f) + close() + moveTo(280f, 760f) + horizontalLineToRelative(400f) + verticalLineToRelative(-560f) + lineTo(280f, 200f) + verticalLineToRelative(560f) + close() + moveTo(400f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(440f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(400f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(360f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(400f, 520f) + close() + moveTo(280f, 200f) + verticalLineToRelative(560f) + verticalLineToRelative(-560f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DotDashedLine.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DotDashedLine.kt new file mode 100644 index 0000000..376631a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DotDashedLine.kt @@ -0,0 +1,72 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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.Black)) { + 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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Dots.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Dots.kt new file mode 100644 index 0000000..e4826e8 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Dots.kt @@ -0,0 +1,84 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Download.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Download.kt new file mode 100644 index 0000000..2e2eb86 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Download.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Download: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Download", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(465f, 620.5f) + quadToRelative(-7f, -2.5f, -13f, -8.5f) + lineTo(308f, 468f) + quadToRelative(-12f, -12f, -11.5f, -28f) + reflectiveQuadToRelative(11.5f, -28f) + quadToRelative(12f, -12f, 28.5f, -12.5f) + reflectiveQuadTo(365f, 411f) + lineToRelative(75f, 75f) + verticalLineToRelative(-286f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 200f) + verticalLineToRelative(286f) + lineToRelative(75f, -75f) + quadToRelative(12f, -12f, 28.5f, -11.5f) + reflectiveQuadTo(652f, 412f) + quadToRelative(11f, 12f, 11.5f, 28f) + reflectiveQuadTo(652f, 468f) + lineTo(508f, 612f) + quadToRelative(-6f, 6f, -13f, 8.5f) + reflectiveQuadToRelative(-15f, 2.5f) + quadToRelative(-8f, 0f, -15f, -2.5f) + close() + moveTo(240f, 800f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 720f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(240f, 640f) + verticalLineToRelative(80f) + horizontalLineToRelative(480f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(760f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 640f) + verticalLineToRelative(80f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 800f) + lineTo(240f, 800f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DownloadDone.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DownloadDone.kt new file mode 100644 index 0000000..2c66c35 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DownloadDone.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.DownloadDone: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.DownloadDone", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(382f, 526f) + lineToRelative(338f, -338f) + quadToRelative(12f, -12f, 28.5f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + quadToRelative(12f, 12f, 12f, 28.5f) + reflectiveQuadTo(777f, 245f) + lineTo(410f, 612f) + quadToRelative(-12f, 12f, -28f, 12f) + reflectiveQuadToRelative(-28f, -12f) + lineTo(183f, 441f) + quadToRelative(-12f, -12f, -11.5f, -28.5f) + reflectiveQuadTo(184f, 384f) + quadToRelative(12f, -12f, 28.5f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + lineToRelative(141f, 142f) + close() + moveTo(240f, 800f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(200f, 760f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(240f, 720f) + horizontalLineToRelative(480f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(760f, 760f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(720f, 800f) + lineTo(240f, 800f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DownloadFile.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DownloadFile.kt new file mode 100644 index 0000000..872db69 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DownloadFile.kt @@ -0,0 +1,101 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.DownloadFile: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.DownloadFile", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(13f, 15.6f) + 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) + curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.712f) + verticalLineToRelative(3.175f) + lineToRelative(-0.9f, -0.9f) + curveToRelative(-0.1f, -0.1f, -0.213f, -0.175f, -0.338f, -0.225f) + reflectiveCurveToRelative(-0.25f, -0.071f, -0.375f, -0.063f) + reflectiveCurveToRelative(-0.246f, 0.038f, -0.363f, 0.087f) + reflectiveCurveToRelative(-0.225f, 0.125f, -0.325f, 0.225f) + curveToRelative(-0.183f, 0.2f, -0.279f, 0.433f, -0.287f, 0.7f) + reflectiveCurveToRelative(0.087f, 0.5f, 0.287f, 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.279f, -0.7f, -0.287f) + reflectiveCurveToRelative(-0.5f, 0.087f, -0.7f, 0.287f) + lineToRelative(-0.875f, 0.875f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(19.85f, 8.063f) + curveToRelative(-0.1f, -0.242f, -0.242f, -0.454f, -0.425f, -0.638f) + lineToRelative(-4.85f, -4.85f) + 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(-7.175f) + 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(12f) + curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) + reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) + verticalLineToRelative(-11.175f) + curveToRelative(0f, -0.267f, -0.05f, -0.521f, -0.15f, -0.763f) + close() + moveTo(18f, 20f) + horizontalLineTo(6f) + verticalLineTo(4f) + horizontalLineToRelative(7f) + 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(4f) + verticalLineToRelative(11f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(6f, 4f) + lineToRelative(0f, 5f) + lineToRelative(0f, -5f) + lineToRelative(0f, 16f) + lineToRelative(0f, -16f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DownloadForOffline.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DownloadForOffline.kt new file mode 100644 index 0000000..0c3f625 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DownloadForOffline.kt @@ -0,0 +1,90 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.DownloadForOffline: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.DownloadForOffline", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(320f, 680f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(680f, 640f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(640f, 600f) + lineTo(320f, 600f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(280f, 640f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(320f, 680f) + close() + moveTo(440f, 406f) + lineTo(404f, 371f) + quadToRelative(-11f, -11f, -27.5f, -11f) + reflectiveQuadTo(348f, 372f) + 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, 11.5f, -27.5f) + reflectiveQuadTo(612f, 372f) + quadToRelative(-11f, -11f, -27.5f, -11.5f) + reflectiveQuadTo(556f, 371f) + lineToRelative(-36f, 35f) + verticalLineToRelative(-126f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 240f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 280f) + verticalLineToRelative(126f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DownloadOff.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DownloadOff.kt new file mode 100644 index 0000000..71c203c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DownloadOff.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Rounded.DownloadOff: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.DownloadOff", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(763f, 876f) + lineToRelative(-77f, -76f) + lineTo(240f, 800f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 720f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(240f, 640f) + verticalLineToRelative(80f) + horizontalLineToRelative(366f) + lineTo(503f, 617f) + quadToRelative(-6f, 3f, -11.5f, 4.5f) + reflectiveQuadTo(480f, 623f) + quadToRelative(-10f, 0f, -16.5f, -3f) + reflectiveQuadToRelative(-11.5f, -8f) + lineTo(308f, 468f) + quadToRelative(-11f, -11f, -11.5f, -25.5f) + reflectiveQuadTo(304f, 418f) + lineTo(83f, 197f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(12f, -29f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(29f, 11f) + lineToRelative(679f, 680f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-12f, 12f, -29f, 12f) + reflectiveQuadToRelative(-28f, -12f) + close() + moveTo(652f, 412f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineToRelative(-7f, 7f) + quadToRelative(-12f, 12f, -28f, 11.5f) + reflectiveQuadTo(589f, 474f) + quadToRelative(-12f, -12f, -12f, -28.5f) + reflectiveQuadToRelative(12f, -28.5f) + lineToRelative(6f, -6f) + quadToRelative(11f, -11f, 28.5f, -10.5f) + reflectiveQuadTo(652f, 412f) + close() + moveTo(480f, 160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 200f) + verticalLineToRelative(110f) + quadToRelative(0f, 20f, -12.5f, 29.5f) + reflectiveQuadTo(480f, 349f) + quadToRelative(-15f, 0f, -27.5f, -10f) + reflectiveQuadTo(440f, 309f) + verticalLineToRelative(-109f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 160f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DragHandle.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DragHandle.kt new file mode 100644 index 0000000..e77a816 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DragHandle.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.DragHandle: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.DragHandle", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(200f, 600f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 560f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 520f) + horizontalLineToRelative(560f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 560f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 600f) + lineTo(200f, 600f) + close() + moveTo(200f, 440f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 400f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 360f) + horizontalLineToRelative(560f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 400f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 440f) + lineTo(200f, 440f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Draw.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Draw.kt new file mode 100644 index 0000000..5897cce --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Draw.kt @@ -0,0 +1,333 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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.Black)) { + 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.Black), + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EditAlt.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EditAlt.kt new file mode 100644 index 0000000..466be2b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EditAlt.kt @@ -0,0 +1,117 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black) + ) { + 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.Black)) { + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Email.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Email.kt new file mode 100644 index 0000000..d9afd81 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Email.kt @@ -0,0 +1,153 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Email: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Email", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(4f, 20f) + quadTo(3.175f, 20f, 2.588f, 19.413f) + quadTo(2f, 18.825f, 2f, 18f) + verticalLineTo(6f) + quadTo(2f, 5.175f, 2.588f, 4.588f) + quadTo(3.175f, 4f, 4f, 4f) + horizontalLineTo(20f) + quadTo(20.825f, 4f, 21.413f, 4.588f) + quadTo(22f, 5.175f, 22f, 6f) + verticalLineTo(18f) + quadTo(22f, 18.825f, 21.413f, 19.413f) + quadTo(20.825f, 20f, 20f, 20f) + close() + moveTo(20f, 8f) + lineTo(12.525f, 12.675f) + quadTo(12.4f, 12.75f, 12.262f, 12.788f) + quadTo(12.125f, 12.825f, 12f, 12.825f) + quadTo(11.875f, 12.825f, 11.738f, 12.788f) + quadTo(11.6f, 12.75f, 11.475f, 12.675f) + lineTo(4f, 8f) + verticalLineTo(18f) + quadTo(4f, 18f, 4f, 18f) + quadTo(4f, 18f, 4f, 18f) + horizontalLineTo(20f) + quadTo(20f, 18f, 20f, 18f) + quadTo(20f, 18f, 20f, 18f) + close() + moveTo(12f, 11f) + lineTo(20f, 6f) + horizontalLineTo(4f) + close() + moveTo(4f, 8f) + verticalLineTo(8.25f) + quadTo(4f, 8.125f, 4f, 7.938f) + quadTo(4f, 7.75f, 4f, 7.525f) + quadTo(4f, 7.025f, 4f, 6.775f) + quadTo(4f, 6.525f, 4f, 6.8f) + verticalLineTo(6f) + verticalLineTo(6.8f) + quadTo(4f, 6.525f, 4f, 6.787f) + quadTo(4f, 7.05f, 4f, 7.525f) + quadTo(4f, 7.775f, 4f, 7.963f) + quadTo(4f, 8.15f, 4f, 8.25f) + verticalLineTo(8f) + verticalLineTo(18f) + quadTo(4f, 18f, 4f, 18f) + quadTo(4f, 18f, 4f, 18f) + quadTo(4f, 18f, 4f, 18f) + quadTo(4f, 18f, 4f, 18f) + close() + } + }.build() +} + +val Icons.Rounded.Email: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Email", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(4f, 20f) + quadTo(3.175f, 20f, 2.588f, 19.413f) + quadTo(2f, 18.825f, 2f, 18f) + verticalLineTo(6f) + quadTo(2f, 5.175f, 2.588f, 4.588f) + quadTo(3.175f, 4f, 4f, 4f) + horizontalLineTo(20f) + quadTo(20.825f, 4f, 21.413f, 4.588f) + quadTo(22f, 5.175f, 22f, 6f) + verticalLineTo(18f) + quadTo(22f, 18.825f, 21.413f, 19.413f) + quadTo(20.825f, 20f, 20f, 20f) + close() + moveTo(20f, 8f) + lineTo(12.525f, 12.675f) + quadTo(12.4f, 12.75f, 12.262f, 12.788f) + quadTo(12.125f, 12.825f, 12f, 12.825f) + quadTo(11.875f, 12.825f, 11.738f, 12.788f) + quadTo(11.6f, 12.75f, 11.475f, 12.675f) + lineTo(4f, 8f) + verticalLineTo(18f) + quadTo(4f, 18f, 4f, 18f) + quadTo(4f, 18f, 4f, 18f) + horizontalLineTo(20f) + quadTo(20f, 18f, 20f, 18f) + quadTo(20f, 18f, 20f, 18f) + close() + moveTo(12f, 11f) + lineTo(20f, 6f) + horizontalLineTo(4f) + close() + moveTo(20f, 8f) + lineTo(19.6f, 8.25f) + quadTo(19.8f, 8.125f, 19.9f, 7.938f) + quadTo(20f, 7.75f, 20f, 7.525f) + quadTo(20f, 7.025f, 19.575f, 6.775f) + quadTo(19.15f, 6.525f, 18.7f, 6.8f) + lineTo(20f, 6f) + horizontalLineTo(4f) + lineTo(5.3f, 6.8f) + quadTo(4.85f, 6.525f, 4.425f, 6.787f) + quadTo(4f, 7.05f, 4f, 7.525f) + quadTo(4f, 7.775f, 4.1f, 7.963f) + quadTo(4.2f, 8.15f, 4.4f, 8.25f) + lineTo(4f, 8f) + verticalLineTo(18f) + quadTo(4f, 18f, 4f, 18f) + quadTo(4f, 18f, 4f, 18f) + horizontalLineTo(20f) + quadTo(20f, 18f, 20f, 18f) + quadTo(20f, 18f, 20f, 18f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Emergency.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Emergency.kt new file mode 100644 index 0000000..c79ca17 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Emergency.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Emergency: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Emergency", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(410f, 770f) + verticalLineToRelative(-168f) + lineToRelative(-146f, 84f) + quadToRelative(-25f, 14f, -53f, 7f) + reflectiveQuadToRelative(-42f, -33f) + quadToRelative(-14f, -25f, -7f, -53f) + reflectiveQuadToRelative(32f, -42f) + lineToRelative(146f, -85f) + lineToRelative(-146f, -84f) + quadToRelative(-25f, -14f, -32f, -42.5f) + reflectiveQuadToRelative(7f, -53.5f) + quadToRelative(14f, -25f, 42f, -32f) + reflectiveQuadToRelative(53f, 7f) + lineToRelative(146f, 84f) + verticalLineToRelative(-169f) + quadToRelative(0f, -29f, 20.5f, -49.5f) + reflectiveQuadTo(480f, 120f) + quadToRelative(29f, 0f, 49.5f, 20.5f) + reflectiveQuadTo(550f, 190f) + verticalLineToRelative(169f) + lineToRelative(146f, -84f) + quadToRelative(25f, -14f, 53f, -7f) + reflectiveQuadToRelative(42f, 32f) + quadToRelative(14f, 25f, 6.5f, 53.5f) + reflectiveQuadTo(765f, 396f) + lineToRelative(-145f, 84f) + lineToRelative(146f, 85f) + quadToRelative(25f, 14f, 32f, 42f) + reflectiveQuadToRelative(-7f, 54f) + quadToRelative(-14f, 25f, -42f, 32f) + reflectiveQuadToRelative(-53f, -7f) + lineToRelative(-146f, -84f) + verticalLineToRelative(168f) + quadToRelative(0f, 29f, -20.5f, 49.5f) + reflectiveQuadTo(480f, 840f) + quadToRelative(-29f, 0f, -49.5f, -20.5f) + reflectiveQuadTo(410f, 770f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiEmotions.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiEmotions.kt new file mode 100644 index 0000000..dd17caf --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiEmotions.kt @@ -0,0 +1,107 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.EmojiEmotions: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.EmojiEmotions", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(15.5f, 11f) + quadTo(16.125f, 11f, 16.563f, 10.563f) + quadTo(17f, 10.125f, 17f, 9.5f) + quadTo(17f, 8.875f, 16.563f, 8.438f) + quadTo(16.125f, 8f, 15.5f, 8f) + quadTo(14.875f, 8f, 14.438f, 8.438f) + quadTo(14f, 8.875f, 14f, 9.5f) + quadTo(14f, 10.125f, 14.438f, 10.563f) + quadTo(14.875f, 11f, 15.5f, 11f) + close() + moveTo(8.5f, 11f) + quadTo(9.125f, 11f, 9.563f, 10.563f) + quadTo(10f, 10.125f, 10f, 9.5f) + quadTo(10f, 8.875f, 9.563f, 8.438f) + quadTo(9.125f, 8f, 8.5f, 8f) + quadTo(7.875f, 8f, 7.438f, 8.438f) + quadTo(7f, 8.875f, 7f, 9.5f) + quadTo(7f, 10.125f, 7.438f, 10.563f) + quadTo(7.875f, 11f, 8.5f, 11f) + close() + moveTo(12f, 22f) + quadTo(9.925f, 22f, 8.1f, 21.212f) + quadTo(6.275f, 20.425f, 4.925f, 19.075f) + quadTo(3.575f, 17.725f, 2.787f, 15.9f) + quadTo(2f, 14.075f, 2f, 12f) + quadTo(2f, 9.925f, 2.787f, 8.1f) + quadTo(3.575f, 6.275f, 4.925f, 4.925f) + quadTo(6.275f, 3.575f, 8.1f, 2.787f) + quadTo(9.925f, 2f, 12f, 2f) + quadTo(14.075f, 2f, 15.9f, 2.787f) + quadTo(17.725f, 3.575f, 19.075f, 4.925f) + quadTo(20.425f, 6.275f, 21.212f, 8.1f) + quadTo(22f, 9.925f, 22f, 12f) + quadTo(22f, 14.075f, 21.212f, 15.9f) + quadTo(20.425f, 17.725f, 19.075f, 19.075f) + quadTo(17.725f, 20.425f, 15.9f, 21.212f) + quadTo(14.075f, 22f, 12f, 22f) + close() + moveTo(12f, 12f) + quadTo(12f, 12f, 12f, 12f) + quadTo(12f, 12f, 12f, 12f) + quadTo(12f, 12f, 12f, 12f) + quadTo(12f, 12f, 12f, 12f) + quadTo(12f, 12f, 12f, 12f) + quadTo(12f, 12f, 12f, 12f) + quadTo(12f, 12f, 12f, 12f) + quadTo(12f, 12f, 12f, 12f) + close() + moveTo(12f, 20f) + quadTo(15.35f, 20f, 17.675f, 17.675f) + quadTo(20f, 15.35f, 20f, 12f) + quadTo(20f, 8.65f, 17.675f, 6.325f) + quadTo(15.35f, 4f, 12f, 4f) + quadTo(8.65f, 4f, 6.325f, 6.325f) + quadTo(4f, 8.65f, 4f, 12f) + quadTo(4f, 15.35f, 6.325f, 17.675f) + quadTo(8.65f, 20f, 12f, 20f) + close() + moveTo(12f, 17.5f) + quadTo(13.45f, 17.5f, 14.675f, 16.8f) + quadTo(15.9f, 16.1f, 16.65f, 14.9f) + quadTo(16.8f, 14.6f, 16.625f, 14.3f) + quadTo(16.45f, 14f, 16.1f, 14f) + horizontalLineTo(7.9f) + quadTo(7.55f, 14f, 7.375f, 14.3f) + quadTo(7.2f, 14.6f, 7.35f, 14.9f) + quadTo(8.1f, 16.1f, 9.337f, 16.8f) + quadTo(10.575f, 17.5f, 12f, 17.5f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiEvents.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiEvents.kt new file mode 100644 index 0000000..ade837f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiEvents.kt @@ -0,0 +1,99 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.EmojiEvents: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.EmojiEvents", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(440f, 760f) + verticalLineToRelative(-124f) + quadToRelative(-49f, -11f, -87.5f, -41.5f) + reflectiveQuadTo(296f, 518f) + quadToRelative(-75f, -9f, -125.5f, -65.5f) + reflectiveQuadTo(120f, 320f) + verticalLineToRelative(-40f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 200f) + horizontalLineToRelative(80f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(360f, 120f) + horizontalLineToRelative(240f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(680f, 200f) + horizontalLineToRelative(80f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(840f, 280f) + verticalLineToRelative(40f) + quadToRelative(0f, 76f, -50.5f, 132.5f) + reflectiveQuadTo(664f, 518f) + quadToRelative(-18f, 46f, -56.5f, 76.5f) + reflectiveQuadTo(520f, 636f) + verticalLineToRelative(124f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 840f) + lineTo(320f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 760f) + horizontalLineToRelative(120f) + close() + moveTo(280f, 432f) + verticalLineToRelative(-152f) + horizontalLineToRelative(-80f) + verticalLineToRelative(40f) + quadToRelative(0f, 38f, 22f, 68.5f) + reflectiveQuadToRelative(58f, 43.5f) + close() + moveTo(480f, 560f) + quadToRelative(50f, 0f, 85f, -35f) + reflectiveQuadToRelative(35f, -85f) + verticalLineToRelative(-240f) + lineTo(360f, 200f) + verticalLineToRelative(240f) + quadToRelative(0f, 50f, 35f, 85f) + reflectiveQuadToRelative(85f, 35f) + close() + moveTo(680f, 432f) + quadToRelative(36f, -13f, 58f, -43.5f) + reflectiveQuadToRelative(22f, -68.5f) + verticalLineToRelative(-40f) + horizontalLineToRelative(-80f) + verticalLineToRelative(152f) + close() + moveTo(480f, 380f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiFoodBeverage.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiFoodBeverage.kt new file mode 100644 index 0000000..b860996 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiFoodBeverage.kt @@ -0,0 +1,107 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.EmojiFoodBeverage: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.EmojiFoodBeverage", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(200f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 760f) + horizontalLineToRelative(560f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 840f) + lineTo(200f, 840f) + close() + moveTo(320f, 680f) + quadToRelative(-66f, 0f, -113f, -47f) + reflectiveQuadToRelative(-47f, -113f) + verticalLineToRelative(-311f) + quadToRelative(0f, -37f, 26f, -63f) + reflectiveQuadToRelative(63f, -26f) + horizontalLineToRelative(551f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 200f) + verticalLineToRelative(120f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 400f) + horizontalLineToRelative(-80f) + verticalLineToRelative(120f) + quadToRelative(0f, 66f, -47f, 113f) + reflectiveQuadToRelative(-113f, 47f) + lineTo(320f, 680f) + close() + moveTo(320f, 200f) + horizontalLineToRelative(320f) + horizontalLineToRelative(-400f) + horizontalLineToRelative(80f) + close() + moveTo(720f, 320f) + horizontalLineToRelative(80f) + verticalLineToRelative(-120f) + horizontalLineToRelative(-80f) + verticalLineToRelative(120f) + close() + moveTo(560f, 600f) + quadToRelative(33f, 0f, 56.5f, -23.5f) + reflectiveQuadTo(640f, 520f) + verticalLineToRelative(-320f) + lineTo(400f, 200f) + verticalLineToRelative(16f) + lineToRelative(72f, 58f) + quadToRelative(2f, 2f, 8f, 16f) + verticalLineToRelative(170f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + lineTo(300f, 480f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + verticalLineToRelative(-170f) + quadToRelative(0f, -2f, 8f, -16f) + lineToRelative(72f, -58f) + verticalLineToRelative(-16f) + lineTo(240f, 200f) + verticalLineToRelative(320f) + quadToRelative(0f, 33f, 23.5f, 56.5f) + reflectiveQuadTo(320f, 600f) + horizontalLineToRelative(240f) + close() + moveTo(360f, 200f) + horizontalLineToRelative(40f) + horizontalLineToRelative(-40f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiMultiple.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiMultiple.kt new file mode 100644 index 0000000..0995470 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiMultiple.kt @@ -0,0 +1,91 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiNature.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiNature.kt new file mode 100644 index 0000000..3bb2a27 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiNature.kt @@ -0,0 +1,150 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.EmojiNature: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.EmojiNature", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(720f, 360f) + lineToRelative(-32f, 28f) + quadToRelative(-14f, 13f, -33f, 13f) + reflectiveQuadToRelative(-33f, -11f) + quadToRelative(-14f, -11f, -19f, -28f) + reflectiveQuadToRelative(1f, -36f) + lineToRelative(16f, -50f) + lineToRelative(-34f, -20f) + quadToRelative(-16f, -9f, -22.5f, -26f) + reflectiveQuadToRelative(-1.5f, -34f) + quadToRelative(5f, -17f, 20f, -26.5f) + reflectiveQuadToRelative(34f, -9.5f) + horizontalLineToRelative(40f) + lineToRelative(12f, -38f) + quadToRelative(6f, -19f, 20.5f, -30.5f) + reflectiveQuadTo(720f, 80f) + quadToRelative(17f, 0f, 31.5f, 11.5f) + reflectiveQuadTo(772f, 122f) + lineToRelative(12f, 38f) + horizontalLineToRelative(40f) + quadToRelative(19f, 0f, 33.5f, 9.5f) + reflectiveQuadTo(878f, 196f) + quadToRelative(7f, 18f, 0f, 35f) + reflectiveQuadToRelative(-22f, 25f) + lineToRelative(-36f, 20f) + lineToRelative(16f, 50f) + quadToRelative(6f, 19f, 1f, 36.5f) + reflectiveQuadTo(818f, 390f) + quadToRelative(-15f, 11f, -33.5f, 11f) + reflectiveQuadTo(752f, 388f) + lineToRelative(-32f, -28f) + close() + moveTo(720f, 280f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(760f, 240f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(720f, 200f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(680f, 240f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(720f, 280f) + close() + moveTo(552f, 716f) + quadToRelative(23f, 60f, -15f, 112f) + reflectiveQuadTo(430f, 880f) + quadToRelative(-33f, 0f, -62.5f, -17f) + reflectiveQuadTo(324f, 818f) + quadToRelative(-83f, 12f, -137.5f, -42.5f) + reflectiveQuadTo(142f, 636f) + quadToRelative(-30f, -17f, -46f, -46.5f) + reflectiveQuadTo(80f, 522f) + quadToRelative(0f, -61f, 55.5f, -98.5f) + reflectiveQuadTo(244f, 408f) + lineToRelative(62f, 26f) + quadToRelative(20f, -31f, 53f, -50.5f) + reflectiveQuadToRelative(71f, -21.5f) + verticalLineToRelative(-52f) + quadToRelative(0f, -13f, 8.5f, -21.5f) + reflectiveQuadTo(460f, 280f) + quadToRelative(13f, 0f, 21.5f, 8.5f) + reflectiveQuadTo(490f, 310f) + verticalLineToRelative(60f) + quadToRelative(37f, 11f, 61f, 34.5f) + reflectiveQuadToRelative(41f, 65.5f) + horizontalLineToRelative(58f) + quadToRelative(13f, 0f, 21.5f, 8.5f) + reflectiveQuadTo(680f, 500f) + quadToRelative(0f, 13f, -8.5f, 21.5f) + reflectiveQuadTo(650f, 530f) + horizontalLineToRelative(-52f) + quadToRelative(-2f, 38f, -20.5f, 71f) + reflectiveQuadTo(528f, 654f) + lineToRelative(24f, 62f) + close() + moveTo(304f, 740f) + quadToRelative(0f, -27f, 4.5f, -52.5f) + reflectiveQuadTo(322f, 638f) + quadToRelative(-23f, 11f, -49.5f, 15.5f) + reflectiveQuadTo(220f, 656f) + quadToRelative(0f, 39f, 22.5f, 61.5f) + reflectiveQuadTo(304f, 740f) + close() + moveTo(230f, 576f) + quadToRelative(32f, 0f, 56.5f, -8f) + reflectiveQuadToRelative(63.5f, -32f) + lineToRelative(-120f, -50f) + quadToRelative(-29f, -12f, -49.5f, 0.5f) + reflectiveQuadTo(160f, 526f) + quadToRelative(0f, 26f, 17f, 38f) + reflectiveQuadToRelative(53f, 12f) + close() + moveTo(430f, 800f) + quadToRelative(25f, 0f, 40.5f, -17.5f) + reflectiveQuadTo(478f, 746f) + lineToRelative(-54f, -136f) + quadToRelative(-19f, 32f, -29.5f, 64f) + reflectiveQuadTo(384f, 732f) + quadToRelative(0f, 33f, 11.5f, 50.5f) + reflectiveQuadTo(430f, 800f) + close() + moveTo(496f, 578f) + quadToRelative(10f, -10f, 16f, -26.5f) + reflectiveQuadToRelative(6f, -34.5f) + quadToRelative(0f, -32f, -21f, -54f) + reflectiveQuadToRelative(-52f, -22f) + quadToRelative(-18f, 0f, -34f, 6f) + reflectiveQuadToRelative(-27f, 17f) + lineToRelative(78f, 36f) + lineToRelative(34f, 78f) + close() + moveTo(322f, 638f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiObjects.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiObjects.kt new file mode 100644 index 0000000..10ee6dd --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiObjects.kt @@ -0,0 +1,100 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.EmojiObjects: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.EmojiObjects", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-26f, 0f, -47f, -12.5f) + reflectiveQuadTo(400f, 834f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(320f, 754f) + verticalLineToRelative(-142f) + quadToRelative(-59f, -39f, -94.5f, -103f) + reflectiveQuadTo(190f, 370f) + quadToRelative(0f, -121f, 84.5f, -205.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(121f, 0f, 205.5f, 84.5f) + reflectiveQuadTo(770f, 370f) + quadToRelative(0f, 77f, -35.5f, 140f) + reflectiveQuadTo(640f, 612f) + verticalLineToRelative(142f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(560f, 834f) + quadToRelative(-12f, 21f, -33f, 33.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(400f, 754f) + horizontalLineToRelative(160f) + verticalLineToRelative(-36f) + lineTo(400f, 718f) + verticalLineToRelative(36f) + close() + moveTo(400f, 678f) + horizontalLineToRelative(160f) + verticalLineToRelative(-38f) + lineTo(400f, 640f) + verticalLineToRelative(38f) + close() + moveTo(392f, 560f) + horizontalLineToRelative(58f) + verticalLineToRelative(-108f) + lineToRelative(-67f, -67f) + quadToRelative(-9f, -9f, -9f, -21f) + reflectiveQuadToRelative(9f, -21f) + quadToRelative(9f, -9f, 21f, -9f) + reflectiveQuadToRelative(21f, 9f) + lineToRelative(55f, 55f) + lineToRelative(55f, -55f) + quadToRelative(9f, -9f, 21f, -9f) + reflectiveQuadToRelative(21f, 9f) + quadToRelative(9f, 9f, 9f, 21f) + reflectiveQuadToRelative(-9f, 21f) + lineToRelative(-67f, 67f) + verticalLineToRelative(108f) + horizontalLineToRelative(58f) + quadToRelative(54f, -26f, 88f, -76.5f) + reflectiveQuadTo(690f, 370f) + quadToRelative(0f, -88f, -61f, -149f) + reflectiveQuadToRelative(-149f, -61f) + quadToRelative(-88f, 0f, -149f, 61f) + reflectiveQuadToRelative(-61f, 149f) + quadToRelative(0f, 63f, 34f, 113.5f) + reflectiveQuadToRelative(88f, 76.5f) + close() + moveTo(480f, 398f) + close() + moveTo(480f, 360f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiSticky.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiSticky.kt new file mode 100644 index 0000000..fcdd3a4 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiSticky.kt @@ -0,0 +1,100 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiSymbols.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiSymbols.kt new file mode 100644 index 0000000..09dc512 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiSymbols.kt @@ -0,0 +1,177 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.EmojiSymbols: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.EmojiSymbols", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(400f, 160f) + lineTo(160f, 160f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 80f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(440f, 120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(400f, 160f) + close() + moveTo(240f, 280f) + horizontalLineToRelative(-80f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 240f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 200f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(440f, 240f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(400f, 280f) + horizontalLineToRelative(-80f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(280f, 440f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(240f, 400f) + verticalLineToRelative(-120f) + close() + moveTo(576f, 836f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + lineToRelative(256f, -256f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineTo(576f, 836f) + close() + moveTo(580f, 640f) + quadToRelative(-26f, 0f, -43f, -17f) + reflectiveQuadToRelative(-17f, -43f) + quadToRelative(0f, -26f, 17f, -43f) + reflectiveQuadToRelative(43f, -17f) + quadToRelative(26f, 0f, 43f, 17f) + reflectiveQuadToRelative(17f, 43f) + quadToRelative(0f, 26f, -17f, 43f) + reflectiveQuadToRelative(-43f, 17f) + close() + moveTo(780f, 840f) + quadToRelative(-26f, 0f, -43f, -17f) + reflectiveQuadToRelative(-17f, -43f) + quadToRelative(0f, -26f, 17f, -43f) + reflectiveQuadToRelative(43f, -17f) + quadToRelative(26f, 0f, 43f, 17f) + reflectiveQuadToRelative(17f, 43f) + quadToRelative(0f, 26f, -17f, 43f) + reflectiveQuadToRelative(-43f, 17f) + close() + moveTo(620f, 440f) + quadToRelative(-41f, 0f, -70.5f, -29.5f) + reflectiveQuadTo(520f, 340f) + quadToRelative(0f, -41f, 29.5f, -71.5f) + reflectiveQuadTo(620f, 238f) + quadToRelative(12f, 0f, 21.5f, 1.5f) + reflectiveQuadTo(660f, 244f) + verticalLineToRelative(-124f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(700f, 80f) + horizontalLineToRelative(100f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 160f) + horizontalLineToRelative(-80f) + verticalLineToRelative(180f) + quadToRelative(0f, 41f, -29.5f, 70.5f) + reflectiveQuadTo(620f, 440f) + close() + moveTo(220f, 880f) + quadToRelative(-41f, 0f, -70.5f, -30.5f) + reflectiveQuadTo(120f, 778f) + quadToRelative(0f, -18f, 7.5f, -36.5f) + reflectiveQuadTo(150f, 708f) + lineToRelative(42f, -42f) + lineToRelative(-14f, -14f) + quadToRelative(-15f, -15f, -22.5f, -32.5f) + reflectiveQuadTo(148f, 582f) + quadToRelative(0f, -41f, 29.5f, -70.5f) + reflectiveQuadTo(248f, 482f) + quadToRelative(41f, 0f, 70.5f, 29.5f) + reflectiveQuadTo(348f, 582f) + quadToRelative(0f, 20f, -6.5f, 37.5f) + reflectiveQuadTo(320f, 652f) + lineToRelative(-14f, 14f) + lineToRelative(28f, 28f) + lineToRelative(27f, -27f) + quadToRelative(12f, -12f, 28.5f, -11.5f) + reflectiveQuadTo(418f, 668f) + quadToRelative(11f, 12f, 11.5f, 28f) + reflectiveQuadTo(418f, 724f) + lineToRelative(-28f, 28f) + lineToRelative(28f, 28f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineToRelative(-28f, -28f) + lineToRelative(-42f, 42f) + quadToRelative(-15f, 15f, -33.5f, 22.5f) + reflectiveQuadTo(220f, 880f) + close() + moveTo(248f, 610f) + lineTo(262f, 596f) + quadToRelative(3f, -3f, 4.5f, -6f) + reflectiveQuadToRelative(1.5f, -8f) + quadToRelative(0f, -9f, -6f, -14.5f) + reflectiveQuadToRelative(-14f, -5.5f) + quadToRelative(-8f, 0f, -14f, 5.5f) + reflectiveQuadToRelative(-6f, 14.5f) + quadToRelative(0f, 3f, 1.5f, 7f) + reflectiveQuadToRelative(4.5f, 7f) + lineToRelative(14f, 14f) + close() + moveTo(218f, 800f) + quadToRelative(3f, 0f, 8f, -1.5f) + reflectiveQuadToRelative(8f, -4.5f) + lineToRelative(44f, -42f) + lineToRelative(-28f, -28f) + lineToRelative(-44f, 42f) + quadToRelative(-3f, 3f, -4.5f, 7f) + reflectiveQuadToRelative(-1.5f, 9f) + quadToRelative(0f, 8f, 5f, 13f) + reflectiveQuadToRelative(13f, 5f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiTransportation.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiTransportation.kt new file mode 100644 index 0000000..40c54b0 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiTransportation.kt @@ -0,0 +1,144 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.EmojiTransportation: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.EmojiTransportation", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(440f, 880f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(400f, 840f) + verticalLineToRelative(-200f) + quadToRelative(0f, -7f, 1f, -14f) + reflectiveQuadToRelative(3f, -13f) + lineToRelative(42f, -119f) + quadToRelative(8f, -24f, 29f, -39f) + reflectiveQuadToRelative(47f, -15f) + horizontalLineToRelative(236f) + quadToRelative(26f, 0f, 47f, 15f) + reflectiveQuadToRelative(29f, 39f) + lineToRelative(42f, 119f) + quadToRelative(2f, 6f, 3f, 13f) + reflectiveQuadToRelative(1f, 14f) + verticalLineToRelative(200f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 880f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(800f, 840f) + verticalLineToRelative(-20f) + lineTo(480f, 820f) + verticalLineToRelative(20f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(440f, 880f) + close() + moveTo(480f, 580f) + horizontalLineToRelative(320f) + lineToRelative(-28f, -80f) + lineTo(508f, 500f) + lineToRelative(-28f, 80f) + close() + moveTo(460f, 640f) + verticalLineToRelative(120f) + verticalLineToRelative(-120f) + close() + moveTo(520f, 740f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(560f, 700f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(520f, 660f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(480f, 700f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(520f, 740f) + close() + moveTo(760f, 740f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(800f, 700f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(760f, 660f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(720f, 700f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(760f, 740f) + close() + moveTo(240f, 560f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(440f, 320f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(240f, 720f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(240f, 880f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(80f, 880f) + verticalLineToRelative(-480f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(160f, 320f) + horizontalLineToRelative(120f) + verticalLineToRelative(-160f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(360f, 80f) + horizontalLineToRelative(240f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(680f, 160f) + verticalLineToRelative(200f) + horizontalLineToRelative(-80f) + verticalLineToRelative(-200f) + lineTo(360f, 160f) + verticalLineToRelative(240f) + lineTo(160f, 400f) + verticalLineToRelative(480f) + lineTo(80f, 880f) + close() + moveTo(460f, 760f) + horizontalLineToRelative(360f) + verticalLineToRelative(-120f) + lineTo(460f, 640f) + verticalLineToRelative(120f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Encrypted.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Encrypted.kt new file mode 100644 index 0000000..17fb34c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Encrypted.kt @@ -0,0 +1,165 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EnergySavingsLeaf.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EnergySavingsLeaf.kt new file mode 100644 index 0000000..9ce3d65 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EnergySavingsLeaf.kt @@ -0,0 +1,92 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.EnergySavingsLeaf: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.EnergySavingsLeaf", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 720f) + quadToRelative(100f, 0f, 169f, -70f) + reflectiveQuadToRelative(71f, -170f) + verticalLineToRelative(-240f) + lineTo(480f, 240f) + quadToRelative(-100f, 2f, -170f, 71f) + reflectiveQuadToRelative(-70f, 169f) + quadToRelative(0f, 100f, 70f, 170f) + reflectiveQuadToRelative(170f, 70f) + close() + moveTo(433f, 653f) + lineTo(617f, 489f) + quadToRelative(9f, -8f, 5f, -19f) + reflectiveQuadToRelative(-16f, -13f) + lineToRelative(-144f, -14f) + lineToRelative(86f, -119f) + quadToRelative(3f, -5f, 3.5f, -9.5f) + reflectiveQuadTo(548f, 306f) + quadToRelative(-4f, -5f, -10f, -4.5f) + reflectiveQuadToRelative(-11f, 4.5f) + lineTo(344f, 470f) + quadToRelative(-9f, 8f, -5f, 19f) + reflectiveQuadToRelative(16f, 13f) + lineToRelative(144f, 14f) + lineToRelative(-87f, 119f) + quadToRelative(-3f, 5f, -3f, 9.5f) + reflectiveQuadToRelative(4f, 8.5f) + quadToRelative(4f, 4f, 9.5f, 4f) + reflectiveQuadToRelative(10.5f, -4f) + close() + moveTo(480f, 800f) + quadToRelative(-56f, 0f, -105.5f, -17.5f) + reflectiveQuadTo(284f, 733f) + lineToRelative(-55f, 55f) + quadToRelative(-6f, 6f, -13.5f, 9f) + reflectiveQuadToRelative(-15.5f, 3f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 760f) + quadToRelative(0f, -8f, 3f, -15.5f) + reflectiveQuadToRelative(9f, -13.5f) + lineToRelative(55f, -55f) + quadToRelative(-32f, -41f, -49.5f, -90.5f) + reflectiveQuadTo(160f, 480f) + quadToRelative(0f, -134f, 93f, -227f) + reflectiveQuadToRelative(227f, -93f) + horizontalLineToRelative(240f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(800f, 240f) + verticalLineToRelative(240f) + quadToRelative(0f, 134f, -93f, 227f) + reflectiveQuadToRelative(-227f, 93f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Engineering.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Engineering.kt new file mode 100644 index 0000000..4102ce5 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Engineering.kt @@ -0,0 +1,247 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Engineering: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Engineering", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(42f, 800f) + verticalLineToRelative(-72f) + quadToRelative(0f, -33f, 17f, -62f) + reflectiveQuadToRelative(47f, -44f) + quadToRelative(51f, -26f, 115f, -44f) + reflectiveQuadToRelative(141f, -18f) + quadToRelative(77f, 0f, 141f, 18f) + reflectiveQuadToRelative(115f, 44f) + quadToRelative(30f, 15f, 47f, 44f) + reflectiveQuadToRelative(17f, 62f) + verticalLineToRelative(72f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(642f, 840f) + lineTo(82f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(42f, 800f) + close() + moveTo(122f, 760f) + horizontalLineToRelative(480f) + verticalLineToRelative(-32f) + quadToRelative(0f, -11f, -5.5f, -20f) + reflectiveQuadTo(582f, 694f) + quadToRelative(-36f, -18f, -92.5f, -36f) + reflectiveQuadTo(362f, 640f) + quadToRelative(-71f, 0f, -127.5f, 18f) + reflectiveQuadTo(142f, 694f) + quadToRelative(-9f, 5f, -14.5f, 14f) + reflectiveQuadToRelative(-5.5f, 20f) + verticalLineToRelative(32f) + close() + moveTo(362f, 520f) + quadToRelative(-66f, 0f, -113f, -47f) + reflectiveQuadToRelative(-47f, -113f) + horizontalLineToRelative(-10f) + quadToRelative(-9f, 0f, -14.5f, -5.5f) + reflectiveQuadTo(172f, 340f) + quadToRelative(0f, -9f, 5.5f, -14.5f) + reflectiveQuadTo(192f, 320f) + horizontalLineToRelative(10f) + quadToRelative(0f, -45f, 22f, -81f) + reflectiveQuadToRelative(58f, -57f) + verticalLineToRelative(38f) + quadToRelative(0f, 9f, 5.5f, 14.5f) + reflectiveQuadTo(302f, 240f) + quadToRelative(9f, 0f, 14.5f, -5.5f) + reflectiveQuadTo(322f, 220f) + verticalLineToRelative(-54f) + quadToRelative(9f, -3f, 19f, -4.5f) + reflectiveQuadToRelative(21f, -1.5f) + quadToRelative(11f, 0f, 21f, 1.5f) + reflectiveQuadToRelative(19f, 4.5f) + verticalLineToRelative(54f) + quadToRelative(0f, 9f, 5.5f, 14.5f) + reflectiveQuadTo(422f, 240f) + quadToRelative(9f, 0f, 14.5f, -5.5f) + reflectiveQuadTo(442f, 220f) + verticalLineToRelative(-38f) + quadToRelative(36f, 21f, 58f, 57f) + reflectiveQuadToRelative(22f, 81f) + horizontalLineToRelative(10f) + quadToRelative(9f, 0f, 14.5f, 5.5f) + reflectiveQuadTo(552f, 340f) + quadToRelative(0f, 9f, -5.5f, 14.5f) + reflectiveQuadTo(532f, 360f) + horizontalLineToRelative(-10f) + quadToRelative(0f, 66f, -47f, 113f) + reflectiveQuadToRelative(-113f, 47f) + close() + moveTo(362f, 440f) + quadToRelative(33f, 0f, 56.5f, -23.5f) + reflectiveQuadTo(442f, 360f) + lineTo(282f, 360f) + quadToRelative(0f, 33f, 23.5f, 56.5f) + reflectiveQuadTo(362f, 440f) + close() + moveTo(659f, 584f) + lineTo(656f, 570f) + quadToRelative(-6f, -2f, -11.5f, -4.5f) + reflectiveQuadTo(634f, 558f) + lineToRelative(-12f, 4f) + quadToRelative(-7f, 2f, -13.5f, 0f) + reflectiveQuadToRelative(-10.5f, -9f) + lineToRelative(-4f, -7f) + quadToRelative(-4f, -6f, -2.5f, -13f) + reflectiveQuadToRelative(6.5f, -12f) + lineToRelative(10f, -9f) + verticalLineToRelative(-24f) + lineToRelative(-10f, -9f) + quadToRelative(-5f, -5f, -6.5f, -12f) + reflectiveQuadToRelative(2.5f, -13f) + lineToRelative(4f, -7f) + quadToRelative(4f, -7f, 10.5f, -9f) + reflectiveQuadToRelative(13.5f, 0f) + lineToRelative(12f, 4f) + quadToRelative(4f, -4f, 10f, -7f) + reflectiveQuadToRelative(12f, -5f) + lineToRelative(3f, -14f) + quadToRelative(2f, -7f, 7f, -11.5f) + reflectiveQuadToRelative(12f, -4.5f) + horizontalLineToRelative(8f) + quadToRelative(7f, 0f, 12f, 4.5f) + reflectiveQuadToRelative(7f, 11.5f) + lineToRelative(3f, 14f) + quadToRelative(6f, 2f, 12f, 5f) + reflectiveQuadToRelative(10f, 7f) + lineToRelative(12f, -4f) + quadToRelative(7f, -2f, 13.5f, 0f) + reflectiveQuadToRelative(10.5f, 9f) + lineToRelative(4f, 7f) + quadToRelative(4f, 6f, 2.5f, 13f) + reflectiveQuadToRelative(-6.5f, 12f) + lineToRelative(-10f, 9f) + verticalLineToRelative(24f) + lineToRelative(10f, 9f) + quadToRelative(5f, 5f, 6.5f, 12f) + reflectiveQuadToRelative(-2.5f, 13f) + lineToRelative(-4f, 7f) + quadToRelative(-4f, 7f, -10.5f, 9f) + reflectiveQuadToRelative(-13.5f, 0f) + lineToRelative(-12f, -4f) + quadToRelative(-5f, 5f, -10.5f, 7.5f) + reflectiveQuadTo(708f, 570f) + lineToRelative(-3f, 14f) + quadToRelative(-2f, 7f, -7f, 11.5f) + reflectiveQuadToRelative(-12f, 4.5f) + horizontalLineToRelative(-8f) + quadToRelative(-7f, 0f, -12f, -4.5f) + reflectiveQuadToRelative(-7f, -11.5f) + close() + moveTo(682f, 530f) + quadToRelative(12f, 0f, 21f, -9f) + reflectiveQuadToRelative(9f, -21f) + quadToRelative(0f, -12f, -9f, -21f) + reflectiveQuadToRelative(-21f, -9f) + quadToRelative(-12f, 0f, -21f, 9f) + reflectiveQuadToRelative(-9f, 21f) + quadToRelative(0f, 12f, 9f, 21f) + reflectiveQuadToRelative(21f, 9f) + close() + moveTo(750f, 378f) + lineTo(746f, 358f) + quadToRelative(-9f, -3f, -16.5f, -7.5f) + reflectiveQuadTo(716f, 340f) + lineToRelative(-21f, 7f) + quadToRelative(-9f, 3f, -18f, -0.5f) + reflectiveQuadTo(663f, 335f) + lineToRelative(-6f, -10f) + quadToRelative(-5f, -8f, -3.5f, -17.5f) + reflectiveQuadTo(663f, 291f) + lineToRelative(17f, -15f) + quadToRelative(-2f, -5f, -2f, -8f) + verticalLineToRelative(-16f) + quadToRelative(0f, -3f, 2f, -8f) + lineToRelative(-17f, -15f) + quadToRelative(-8f, -7f, -9.5f, -16.5f) + reflectiveQuadTo(657f, 195f) + lineToRelative(6f, -10f) + quadToRelative(5f, -8f, 14f, -11.5f) + reflectiveQuadToRelative(18f, -0.5f) + lineToRelative(21f, 7f) + quadToRelative(6f, -6f, 13.5f, -10.5f) + reflectiveQuadTo(746f, 162f) + lineToRelative(4f, -20f) + quadToRelative(2f, -10f, 9.5f, -16f) + reflectiveQuadToRelative(17.5f, -6f) + horizontalLineToRelative(10f) + quadToRelative(10f, 0f, 17.5f, 6f) + reflectiveQuadToRelative(9.5f, 16f) + lineToRelative(4f, 20f) + quadToRelative(9f, 3f, 16.5f, 7.5f) + reflectiveQuadTo(848f, 180f) + lineToRelative(21f, -7f) + quadToRelative(9f, -3f, 18f, 0.5f) + reflectiveQuadToRelative(14f, 11.5f) + lineToRelative(6f, 10f) + quadToRelative(5f, 8f, 3.5f, 17.5f) + reflectiveQuadTo(901f, 229f) + lineToRelative(-17f, 15f) + quadToRelative(2f, 5f, 2f, 8f) + verticalLineToRelative(16f) + quadToRelative(0f, 3f, -2f, 8f) + lineToRelative(17f, 15f) + quadToRelative(8f, 7f, 9.5f, 16.5f) + reflectiveQuadTo(907f, 325f) + lineToRelative(-6f, 10f) + quadToRelative(-5f, 8f, -14f, 11.5f) + reflectiveQuadToRelative(-18f, 0.5f) + lineToRelative(-21f, -7f) + quadToRelative(-6f, 6f, -13.5f, 10.5f) + reflectiveQuadTo(818f, 358f) + lineToRelative(-4f, 20f) + quadToRelative(-2f, 10f, -9.5f, 16f) + reflectiveQuadToRelative(-17.5f, 6f) + horizontalLineToRelative(-10f) + quadToRelative(-10f, 0f, -17.5f, -6f) + reflectiveQuadToRelative(-9.5f, -16f) + close() + moveTo(782f, 310f) + quadToRelative(21f, 0f, 35.5f, -14.5f) + reflectiveQuadTo(832f, 260f) + quadToRelative(0f, -21f, -14.5f, -35.5f) + reflectiveQuadTo(782f, 210f) + quadToRelative(-21f, 0f, -35.5f, 14.5f) + reflectiveQuadTo(732f, 260f) + quadToRelative(0f, 21f, 14.5f, 35.5f) + reflectiveQuadTo(782f, 310f) + close() + moveTo(362f, 760f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Eraser.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Eraser.kt new file mode 100644 index 0000000..54d3e7b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Eraser.kt @@ -0,0 +1,102 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), + 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.Black)) { + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Error.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Error.kt new file mode 100644 index 0000000..167215b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Error.kt @@ -0,0 +1,143 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Error: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Error", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(508.5f, 668.5f) + quadTo(520f, 657f, 520f, 640f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(497f, 600f, 480f, 600f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(440f, 623f, 440f, 640f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(463f, 680f, 480f, 680f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(508.5f, 508.5f) + quadTo(520f, 497f, 520f, 480f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 320f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} + +val Icons.Rounded.Error: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Error", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(508.5f, 668.5f) + quadTo(520f, 657f, 520f, 640f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(497f, 600f, 480f, 600f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(440f, 623f, 440f, 640f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(463f, 680f, 480f, 680f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(508.5f, 508.5f) + quadTo(520f, 497f, 520f, 480f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 320f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EvShadow.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EvShadow.kt new file mode 100644 index 0000000..4457941 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EvShadow.kt @@ -0,0 +1,229 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Rounded.EvShadow: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.EvShadow", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(389f, 613f) + lineToRelative(337f, -337f) + quadToRelative(-11f, -12f, -22f, -23.5f) + reflectiveQuadTo(680f, 231f) + lineTo(367f, 545f) + quadToRelative(4f, 18f, 9f, 34.5f) + reflectiveQuadToRelative(13f, 33.5f) + close() + moveTo(774f, 607f) + quadToRelative(18f, -41f, 23f, -86f) + lineTo(547f, 771f) + quadToRelative(8f, 3f, 16.5f, 7f) + reflectiveQuadToRelative(16.5f, 6f) + quadToRelative(44f, -14f, 81f, -40f) + reflectiveQuadToRelative(66f, -61f) + quadToRelative(29f, -35f, 47f, -76f) + close() + moveTo(160f, 480f) + quadToRelative(0f, 122f, 79f, 211.5f) + reflectiveQuadTo(436f, 797f) + quadToRelative(-72f, -55f, -114f, -137.5f) + reflectiveQuadTo(280f, 480f) + quadToRelative(0f, -97f, 42f, -179.5f) + reflectiveQuadTo(436f, 163f) + quadToRelative(-118f, 16f, -197f, 105.5f) + reflectiveQuadTo(160f, 480f) + close() + moveTo(477f, 727f) + lineTo(792f, 413f) + quadToRelative(-4f, -18f, -9f, -35f) + reflectiveQuadToRelative(-13f, -33f) + lineTo(432f, 682f) + quadToRelative(11f, 13f, 21f, 23.5f) + reflectiveQuadToRelative(24f, 21.5f) + close() + moveTo(324f, 848.5f) + quadTo(251f, 817f, 197f, 763f) + reflectiveQuadToRelative(-85.5f, -127f) + quadTo(80f, 563f, 80f, 480f) + reflectiveQuadToRelative(31.5f, -156f) + quadTo(143f, 251f, 197f, 197f) + reflectiveQuadToRelative(127f, -85.5f) + quadTo(397f, 80f, 480f, 80f) + reflectiveQuadToRelative(156f, 31.5f) + quadTo(709f, 143f, 763f, 197f) + reflectiveQuadToRelative(85.5f, 127f) + quadTo(880f, 397f, 880f, 480f) + reflectiveQuadToRelative(-31.5f, 156f) + quadTo(817f, 709f, 763f, 763f) + reflectiveQuadToRelative(-127f, 85.5f) + quadTo(563f, 880f, 480f, 880f) + reflectiveQuadToRelative(-156f, -31.5f) + close() + moveTo(363f, 435f) + lineToRelative(247f, -247f) + quadToRelative(-8f, -3f, -14.5f, -6.5f) + reflectiveQuadTo(581f, 176f) + quadToRelative(-86f, 28f, -145.5f, 97.5f) + reflectiveQuadTo(363f, 435f) + close() + moveTo(580f, 480f) + close() + } + }.build() +} + +val Icons.TwoTone.EvShadow: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.EvShadow", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + 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.037f, 5.775f) + curveToRelative(0.217f, 0.167f, 0.417f, 0.346f, 0.6f, 0.537f) + reflectiveCurveToRelative(0.367f, 0.388f, 0.55f, 0.588f) + lineToRelative(-8.425f, 8.425f) + curveToRelative(-0.133f, -0.283f, -0.242f, -0.563f, -0.325f, -0.838f) + curveToRelative(-0.083f, -0.275f, -0.158f, -0.563f, -0.225f, -0.862f) + lineToRelative(7.825f, -7.85f) + close() + moveTo(14.563f, 4.4f) + curveToRelative(0.133f, 0.033f, 0.254f, 0.079f, 0.362f, 0.137f) + curveToRelative(0.108f, 0.058f, 0.229f, 0.113f, 0.362f, 0.163f) + lineToRelative(-6.175f, 6.175f) + curveToRelative(0.217f, -1.533f, 0.821f, -2.879f, 1.813f, -4.037f) + curveToRelative(0.992f, -1.158f, 2.204f, -1.971f, 3.638f, -2.438f) + close() + moveTo(6.013f, 17.287f) + curveToRelative(-1.317f, -1.492f, -1.975f, -3.254f, -1.975f, -5.287f) + reflectiveCurveToRelative(0.658f, -3.796f, 1.975f, -5.287f) + curveToRelative(1.317f, -1.492f, 2.958f, -2.371f, 4.925f, -2.638f) + curveToRelative(-1.2f, 0.917f, -2.15f, 2.063f, -2.85f, 3.438f) + curveToRelative(-0.7f, 1.375f, -1.05f, 2.871f, -1.05f, 4.487f) + reflectiveCurveToRelative(0.35f, 3.112f, 1.05f, 4.487f) + curveToRelative(0.7f, 1.375f, 1.65f, 2.521f, 2.85f, 3.438f) + curveToRelative(-1.967f, -0.267f, -3.608f, -1.146f, -4.925f, -2.638f) + close() + moveTo(11.963f, 18.175f) + curveToRelative(-0.233f, -0.183f, -0.433f, -0.362f, -0.6f, -0.537f) + curveToRelative(-0.167f, -0.175f, -0.342f, -0.371f, -0.525f, -0.588f) + lineToRelative(8.45f, -8.425f) + curveToRelative(0.133f, 0.267f, 0.242f, 0.542f, 0.325f, 0.825f) + curveToRelative(0.083f, 0.283f, 0.158f, 0.575f, 0.225f, 0.875f) + lineToRelative(-7.875f, 7.85f) + close() + moveTo(19.388f, 15.175f) + curveToRelative(-0.3f, 0.683f, -0.692f, 1.317f, -1.175f, 1.9f) + curveToRelative(-0.483f, 0.583f, -1.033f, 1.092f, -1.65f, 1.525f) + curveToRelative(-0.617f, 0.433f, -1.292f, 0.767f, -2.025f, 1f) + curveToRelative(-0.133f, -0.033f, -0.271f, -0.083f, -0.412f, -0.15f) + reflectiveCurveToRelative(-0.279f, -0.125f, -0.412f, -0.175f) + lineToRelative(6.25f, -6.25f) + curveToRelative(-0.083f, 0.75f, -0.275f, 1.467f, -0.575f, 2.15f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(9.762f, 15.325f) + lineToRelative(8.425f, -8.425f) + curveToRelative(-0.183f, -0.2f, -0.367f, -0.396f, -0.55f, -0.587f) + reflectiveCurveToRelative(-0.383f, -0.371f, -0.6f, -0.538f) + lineToRelative(-7.825f, 7.85f) + curveToRelative(0.067f, 0.3f, 0.142f, 0.587f, 0.225f, 0.863f) + reflectiveCurveToRelative(0.192f, 0.554f, 0.325f, 0.837f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(19.388f, 15.175f) + curveToRelative(0.3f, -0.683f, 0.492f, -1.4f, 0.575f, -2.15f) + lineToRelative(-6.25f, 6.25f) + curveToRelative(0.133f, 0.05f, 0.271f, 0.108f, 0.412f, 0.175f) + reflectiveCurveToRelative(0.279f, 0.117f, 0.412f, 0.15f) + curveToRelative(0.733f, -0.233f, 1.408f, -0.567f, 2.025f, -1f) + reflectiveCurveToRelative(1.167f, -0.942f, 1.65f, -1.525f) + curveToRelative(0.483f, -0.583f, 0.875f, -1.217f, 1.175f, -1.9f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(11.962f, 18.175f) + lineToRelative(7.875f, -7.85f) + curveToRelative(-0.067f, -0.3f, -0.142f, -0.592f, -0.225f, -0.875f) + reflectiveCurveToRelative(-0.192f, -0.558f, -0.325f, -0.825f) + lineToRelative(-8.45f, 8.425f) + curveToRelative(0.183f, 0.217f, 0.358f, 0.412f, 0.525f, 0.587f) + reflectiveCurveToRelative(0.367f, 0.354f, 0.6f, 0.538f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(9.113f, 10.875f) + lineToRelative(6.175f, -6.175f) + curveToRelative(-0.133f, -0.05f, -0.254f, -0.104f, -0.363f, -0.162f) + reflectiveCurveToRelative(-0.229f, -0.104f, -0.363f, -0.138f) + curveToRelative(-1.433f, 0.467f, -2.646f, 1.279f, -3.638f, 2.438f) + reflectiveCurveToRelative(-1.596f, 2.504f, -1.813f, 4.037f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Event.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Event.kt new file mode 100644 index 0000000..2a3ecc4 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Event.kt @@ -0,0 +1,92 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Event: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Event", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(580f, 720f) + quadToRelative(-42f, 0f, -71f, -29f) + reflectiveQuadToRelative(-29f, -71f) + quadToRelative(0f, -42f, 29f, -71f) + reflectiveQuadToRelative(71f, -29f) + quadToRelative(42f, 0f, 71f, 29f) + reflectiveQuadToRelative(29f, 71f) + quadToRelative(0f, 42f, -29f, 71f) + reflectiveQuadToRelative(-71f, 29f) + close() + moveTo(200f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 800f) + verticalLineToRelative(-560f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 160f) + horizontalLineToRelative(40f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(280f, 80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(320f, 120f) + verticalLineToRelative(40f) + horizontalLineToRelative(320f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(680f, 80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(720f, 120f) + verticalLineToRelative(40f) + horizontalLineToRelative(40f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(840f, 240f) + verticalLineToRelative(560f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 880f) + lineTo(200f, 880f) + close() + moveTo(200f, 800f) + horizontalLineToRelative(560f) + verticalLineToRelative(-400f) + lineTo(200f, 400f) + verticalLineToRelative(400f) + close() + moveTo(200f, 320f) + horizontalLineToRelative(560f) + verticalLineToRelative(-80f) + lineTo(200f, 240f) + verticalLineToRelative(80f) + close() + moveTo(200f, 320f) + verticalLineToRelative(-80f) + verticalLineToRelative(80f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Exercise.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Exercise.kt new file mode 100644 index 0000000..c467cfc --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Exercise.kt @@ -0,0 +1,121 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Exif.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Exif.kt new file mode 100644 index 0000000..3499b66 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Exif.kt @@ -0,0 +1,347 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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.Black), 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.Black), 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.Black), 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.Black), 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.Black), 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.Black)) { + 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.Black), + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ExifEdit.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ExifEdit.kt new file mode 100644 index 0000000..9280785 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ExifEdit.kt @@ -0,0 +1,312 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black), + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black), + 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.Black)) { + 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.Black)) { + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Extension.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Extension.kt new file mode 100644 index 0000000..7914170 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Extension.kt @@ -0,0 +1,151 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Extension: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Extension", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(352f, 840f) + lineTo(200f, 840f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 760f) + verticalLineToRelative(-152f) + quadToRelative(48f, 0f, 84f, -30.5f) + reflectiveQuadToRelative(36f, -77.5f) + quadToRelative(0f, -47f, -36f, -77.5f) + reflectiveQuadTo(120f, 392f) + verticalLineToRelative(-152f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 160f) + horizontalLineToRelative(160f) + quadToRelative(0f, -42f, 29f, -71f) + reflectiveQuadToRelative(71f, -29f) + quadToRelative(42f, 0f, 71f, 29f) + reflectiveQuadToRelative(29f, 71f) + horizontalLineToRelative(160f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(800f, 240f) + verticalLineToRelative(160f) + quadToRelative(42f, 0f, 71f, 29f) + reflectiveQuadToRelative(29f, 71f) + quadToRelative(0f, 42f, -29f, 71f) + reflectiveQuadToRelative(-71f, 29f) + verticalLineToRelative(160f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 840f) + lineTo(568f, 840f) + quadToRelative(0f, -50f, -31.5f, -85f) + reflectiveQuadTo(460f, 720f) + quadToRelative(-45f, 0f, -76.5f, 35f) + reflectiveQuadTo(352f, 840f) + close() + } + }.build() +} + +val Icons.Outlined.Extension: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Extension", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(352f, 840f) + lineTo(200f, 840f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 760f) + verticalLineToRelative(-152f) + quadToRelative(48f, 0f, 84f, -30.5f) + reflectiveQuadToRelative(36f, -77.5f) + quadToRelative(0f, -47f, -36f, -77.5f) + reflectiveQuadTo(120f, 392f) + verticalLineToRelative(-152f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 160f) + horizontalLineToRelative(160f) + quadToRelative(0f, -42f, 29f, -71f) + reflectiveQuadToRelative(71f, -29f) + quadToRelative(42f, 0f, 71f, 29f) + reflectiveQuadToRelative(29f, 71f) + horizontalLineToRelative(160f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(800f, 240f) + verticalLineToRelative(160f) + quadToRelative(42f, 0f, 71f, 29f) + reflectiveQuadToRelative(29f, 71f) + quadToRelative(0f, 42f, -29f, 71f) + reflectiveQuadToRelative(-71f, 29f) + verticalLineToRelative(160f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 840f) + lineTo(568f, 840f) + quadToRelative(0f, -50f, -31.5f, -85f) + reflectiveQuadTo(460f, 720f) + quadToRelative(-45f, 0f, -76.5f, 35f) + reflectiveQuadTo(352f, 840f) + close() + moveTo(200f, 760f) + horizontalLineToRelative(85f) + quadToRelative(24f, -66f, 77f, -93f) + reflectiveQuadToRelative(98f, -27f) + quadToRelative(45f, 0f, 98f, 27f) + reflectiveQuadToRelative(77f, 93f) + horizontalLineToRelative(85f) + verticalLineToRelative(-240f) + horizontalLineToRelative(80f) + quadToRelative(8f, 0f, 14f, -6f) + reflectiveQuadToRelative(6f, -14f) + quadToRelative(0f, -8f, -6f, -14f) + reflectiveQuadToRelative(-14f, -6f) + horizontalLineToRelative(-80f) + verticalLineToRelative(-240f) + lineTo(480f, 240f) + verticalLineToRelative(-80f) + quadToRelative(0f, -8f, -6f, -14f) + reflectiveQuadToRelative(-14f, -6f) + quadToRelative(-8f, 0f, -14f, 6f) + reflectiveQuadToRelative(-6f, 14f) + verticalLineToRelative(80f) + lineTo(200f, 240f) + verticalLineToRelative(88f) + quadToRelative(54f, 20f, 87f, 67f) + reflectiveQuadToRelative(33f, 105f) + quadToRelative(0f, 57f, -33f, 104f) + reflectiveQuadToRelative(-87f, 68f) + verticalLineToRelative(88f) + close() + moveTo(460f, 500f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ExtensionOff.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ExtensionOff.kt new file mode 100644 index 0000000..7b83a63 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ExtensionOff.kt @@ -0,0 +1,122 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ExtensionOff: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.ExtensionOff", + 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(-152f) + quadToRelative(48f, 0f, 84f, -30.5f) + reflectiveQuadToRelative(36f, -77.5f) + quadToRelative(0f, -47f, -36f, -77.5f) + reflectiveQuadTo(120f, 392f) + verticalLineToRelative(-152f) + quadToRelative(0f, -17f, 6f, -31.5f) + reflectiveQuadToRelative(17f, -25.5f) + lineToRelative(57f, 57f) + verticalLineToRelative(88f) + quadToRelative(54f, 20f, 87f, 67f) + reflectiveQuadToRelative(33f, 105f) + quadToRelative(0f, 57f, -33f, 104f) + reflectiveQuadToRelative(-87f, 68f) + verticalLineToRelative(88f) + horizontalLineToRelative(88f) + quadToRelative(21f, -54f, 68f, -87f) + reflectiveQuadToRelative(104f, -33f) + quadToRelative(57f, 0f, 104f, 33f) + reflectiveQuadToRelative(68f, 87f) + horizontalLineToRelative(88f) + lineToRelative(57f, 57f) + quadToRelative(-11f, 11f, -25.5f, 17f) + reflectiveQuadToRelative(-31.5f, 6f) + lineTo(568f, 840f) + quadToRelative(0f, -48f, -30.5f, -84f) + reflectiveQuadTo(460f, 720f) + quadToRelative(-47f, 0f, -77.5f, 36f) + reflectiveQuadTo(352f, 840f) + lineTo(200f, 840f) + close() + moveTo(800f, 686f) + lineTo(720f, 606f) + verticalLineToRelative(-86f) + horizontalLineToRelative(80f) + quadToRelative(8f, 0f, 14f, -6f) + reflectiveQuadToRelative(6f, -14f) + quadToRelative(0f, -8f, -6f, -14f) + reflectiveQuadToRelative(-14f, -6f) + horizontalLineToRelative(-80f) + verticalLineToRelative(-240f) + lineTo(480f, 240f) + verticalLineToRelative(-80f) + quadToRelative(0f, -8f, -6f, -14f) + reflectiveQuadToRelative(-14f, -6f) + quadToRelative(-8f, 0f, -14f, 6f) + reflectiveQuadToRelative(-6f, 14f) + verticalLineToRelative(80f) + horizontalLineToRelative(-86f) + lineToRelative(-80f, -80f) + horizontalLineToRelative(86f) + quadToRelative(0f, -42f, 29f, -71f) + reflectiveQuadToRelative(71f, -29f) + quadToRelative(42f, 0f, 71f, 29f) + reflectiveQuadToRelative(29f, 71f) + horizontalLineToRelative(160f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(800f, 240f) + verticalLineToRelative(160f) + quadToRelative(42f, 0f, 71f, 29f) + reflectiveQuadToRelative(29f, 71f) + quadToRelative(0f, 42f, -29f, 71f) + reflectiveQuadToRelative(-71f, 29f) + verticalLineToRelative(86f) + close() + moveTo(763f, 876f) + lineTo(83f, 197f) + quadToRelative(-12f, -12f, -11.5f, -28.5f) + reflectiveQuadTo(84f, 140f) + quadToRelative(12f, -12f, 28.5f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + lineToRelative(679f, 680f) + quadToRelative(12f, 12f, 12f, 28f) + reflectiveQuadToRelative(-12f, 28f) + quadToRelative(-12f, 12f, -28.5f, 12f) + reflectiveQuadTo(763f, 876f) + close() + moveTo(537f, 423f) + close() + moveTo(460f, 500f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Eyedropper.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Eyedropper.kt new file mode 100644 index 0000000..c48dbf1 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Eyedropper.kt @@ -0,0 +1,157 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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.Black)) { + moveTo(20.319f, 3.627f) + curveToRelative(-1.17f, -1.17f, -3.07f, -1.17f, -4.24f, 0f) + lineToRelative(-1.82f, 1.82f) + lineToRelative(-0.301f, -0.301f) + curveToRelative(-0.586f, -0.586f, -1.536f, -0.586f, -2.121f, 0f) + curveToRelative(-0.586f, 0.586f, -0.586f, 1.536f, 0f, 2.121f) + lineToRelative(0.355f, 0.355f) + lineToRelative(-0.71f, 0.71f) + lineToRelative(-6.85f, 6.85f) + curveToRelative(-0.095f, 0.095f, -0.17f, 0.209f, -0.221f, 0.334f) + lineToRelative(-1.534f, 3.795f) + curveToRelative(-0.151f, 0.374f, -0.064f, 0.801f, 0.221f, 1.086f) + lineToRelative(0.56f, 0.56f) + curveToRelative(0.285f, 0.285f, 0.713f, 0.372f, 1.086f, 0.221f) + lineToRelative(3.795f, -1.534f) + curveToRelative(0.125f, -0.05f, 0.238f, -0.126f, 0.333f, -0.221f) + lineToRelative(6.85f, -6.85f) + curveToRelative(0.01f, -0.01f, 0.013f, -0.023f, 0.022f, -0.033f) + lineToRelative(0.682f, -0.682f) + lineToRelative(0.36f, 0.36f) + curveToRelative(0.586f, 0.586f, 1.536f, 0.586f, 2.121f, 0f) + curveToRelative(0.586f, -0.586f, 0.586f, -1.536f, 0f, -2.121f) + lineToRelative(-0.408f, -0.408f) + lineToRelative(1.82f, -1.82f) + curveToRelative(1.17f, -1.17f, 1.17f, -3.07f, 0f, -4.24f) + close() + moveTo(13.361f, 12.095f) + lineToRelative(-5.627f, 5.635f) + curveToRelative(-0.09f, 0.09f, -0.197f, 0.163f, -0.314f, 0.213f) + lineToRelative(-2.292f, 0.984f) + lineToRelative(0.984f, -2.292f) + curveToRelative(0.05f, -0.117f, 0.123f, -0.224f, 0.213f, -0.314f) + lineToRelative(5.731f, -5.723f) + lineToRelative(0f, 0f) + lineToRelative(0.619f, -0.619f) + lineToRelative(0.227f, -0.227f) + curveToRelative(0f, -0f, 0f, -0.001f, 0.001f, -0.001f) + lineToRelative(0.709f, -0.709f) + lineToRelative(1.396f, 1.396f) + lineToRelative(-1.651f, 1.651f) + lineToRelative(0.006f, 0.006f) + 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.Black)) { + moveTo(20.319f, 3.627f) + curveToRelative(-1.17f, -1.17f, -3.07f, -1.17f, -4.24f, 0f) + lineToRelative(-1.82f, 1.82f) + lineToRelative(-0.301f, -0.301f) + curveToRelative(-0.586f, -0.586f, -1.536f, -0.586f, -2.121f, 0f) + curveToRelative(-0.586f, 0.586f, -0.586f, 1.536f, 0f, 2.121f) + lineToRelative(0.355f, 0.355f) + lineToRelative(-0.71f, 0.71f) + lineToRelative(-6.85f, 6.85f) + curveToRelative(-0.095f, 0.095f, -0.17f, 0.209f, -0.221f, 0.334f) + lineToRelative(-1.534f, 3.795f) + curveToRelative(-0.151f, 0.374f, -0.064f, 0.801f, 0.221f, 1.086f) + lineToRelative(0.56f, 0.56f) + curveToRelative(0.285f, 0.285f, 0.713f, 0.372f, 1.086f, 0.221f) + lineToRelative(3.795f, -1.534f) + curveToRelative(0.125f, -0.05f, 0.238f, -0.126f, 0.333f, -0.221f) + lineToRelative(6.85f, -6.85f) + curveToRelative(0.01f, -0.01f, 0.013f, -0.023f, 0.022f, -0.033f) + lineToRelative(0.682f, -0.682f) + lineToRelative(0.36f, 0.36f) + curveToRelative(0.586f, 0.586f, 1.536f, 0.586f, 2.121f, 0f) + curveToRelative(0.586f, -0.586f, 0.586f, -1.536f, 0f, -2.121f) + lineToRelative(-0.408f, -0.408f) + lineToRelative(1.82f, -1.82f) + curveToRelative(1.17f, -1.17f, 1.17f, -3.07f, 0f, -4.24f) + close() + moveTo(13.361f, 12.095f) + lineToRelative(-5.627f, 5.635f) + curveToRelative(-0.09f, 0.09f, -0.197f, 0.163f, -0.314f, 0.213f) + lineToRelative(-2.292f, 0.984f) + lineToRelative(0.984f, -2.292f) + curveToRelative(0.05f, -0.117f, 0.123f, -0.224f, 0.213f, -0.314f) + lineToRelative(5.731f, -5.723f) + lineToRelative(0f, 0f) + lineToRelative(0.619f, -0.619f) + lineToRelative(0.227f, -0.227f) + curveToRelative(0f, -0f, 0f, -0.001f, 0.001f, -0.001f) + lineToRelative(0.709f, -0.709f) + lineToRelative(1.396f, 1.396f) + lineToRelative(-1.651f, 1.651f) + lineToRelative(0.006f, 0.006f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(13.361f, 12.095f) + lineToRelative(-5.627f, 5.635f) + curveToRelative(-0.09f, 0.09f, -0.197f, 0.163f, -0.314f, 0.213f) + lineToRelative(-2.292f, 0.984f) + lineToRelative(0.984f, -2.292f) + curveToRelative(0.05f, -0.117f, 0.123f, -0.224f, 0.213f, -0.314f) + lineToRelative(5.731f, -5.723f) + lineToRelative(0f, 0f) + lineToRelative(0.619f, -0.619f) + lineToRelative(0.227f, -0.227f) + curveToRelative(0f, -0f, 0f, -0.001f, 0.001f, -0.001f) + lineToRelative(0.709f, -0.709f) + lineToRelative(1.396f, 1.396f) + lineToRelative(-1.651f, 1.651f) + lineToRelative(0.006f, 0.006f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FabCorner.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FabCorner.kt new file mode 100644 index 0000000..c08ace8 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FabCorner.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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(10.997f, 9f) + lineTo(15.003f, 9f) + arcTo(1.997f, 1.997f, 0f, isMoreThanHalf = false, isPositiveArc = true, 17f, 10.997f) + lineTo(17f, 15.003f) + arcTo(1.997f, 1.997f, 0f, isMoreThanHalf = false, isPositiveArc = true, 15.003f, 17f) + lineTo(10.997f, 17f) + arcTo(1.997f, 1.997f, 0f, isMoreThanHalf = false, isPositiveArc = true, 9f, 15.003f) + lineTo(9f, 10.997f) + arcTo(1.997f, 1.997f, 0f, isMoreThanHalf = false, isPositiveArc = true, 10.997f, 9f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(20f, 3f) + curveToRelative(-0.552f, 0f, -1f, 0.448f, -1f, 1f) + verticalLineToRelative(11.998f) + curveToRelative(0f, 1.658f, -1.344f, 3.002f, -3.002f, 3.002f) + horizontalLineTo(4f) + curveToRelative(-0.552f, 0f, -1f, 0.448f, -1f, 1f) + reflectiveCurveToRelative(0.448f, 1f, 1f, 1f) + horizontalLineToRelative(12.497f) + curveToRelative(2.487f, 0f, 4.503f, -2.016f, 4.503f, -4.503f) + verticalLineTo(4f) + curveToRelative(0f, -0.552f, -0.448f, -1f, -1f, -1f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Face5.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Face5.kt new file mode 100644 index 0000000..2127690 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Face5.kt @@ -0,0 +1,386 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Face5: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Face5", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 240f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(560f, 240f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(400f, 240f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(680f, 280f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(360f, 280f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(280f, 280f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(440f, 280f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(520f, 280f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(600f, 280f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(480f, 320f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(560f, 320f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(640f, 320f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(400f, 320f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(320f, 320f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(360f, 360f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(280f, 360f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(440f, 360f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(520f, 360f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(600f, 360f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(680f, 360f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(200f, 360f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(240f, 320f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(320f, 240f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(360f, 200f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(440f, 200f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(520f, 200f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(600f, 200f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(640f, 240f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(720f, 320f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(760f, 360f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + close() + moveTo(360f, 570f) + quadToRelative(-21f, 0f, -35.5f, -14.5f) + reflectiveQuadTo(310f, 520f) + quadToRelative(0f, -21f, 14.5f, -35.5f) + reflectiveQuadTo(360f, 470f) + quadToRelative(21f, 0f, 35.5f, 14.5f) + reflectiveQuadTo(410f, 520f) + quadToRelative(0f, 21f, -14.5f, 35.5f) + reflectiveQuadTo(360f, 570f) + close() + moveTo(600f, 570f) + quadToRelative(-21f, 0f, -35.5f, -14.5f) + reflectiveQuadTo(550f, 520f) + quadToRelative(0f, -21f, 14.5f, -35.5f) + reflectiveQuadTo(600f, 470f) + quadToRelative(21f, 0f, 35.5f, 14.5f) + reflectiveQuadTo(650f, 520f) + quadToRelative(0f, 21f, -14.5f, 35.5f) + reflectiveQuadTo(600f, 570f) + close() + moveTo(480f, 880f) + quadToRelative(-82f, 0f, -155f, -31.5f) + reflectiveQuadToRelative(-127.5f, -86f) + quadTo(143f, 708f, 111.5f, 635f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadToRelative(86f, -127f) + quadTo(252f, 143f, 325f, 111.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 82f, -31.5f, 155f) + reflectiveQuadTo(763f, 762.5f) + quadToRelative(-54f, 54.5f, -127f, 86f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227f, -93.5f) + reflectiveQuadTo(800f, 480f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-133f, 0f, -226.5f, 93f) + reflectiveQuadTo(160f, 480f) + quadToRelative(0f, 133f, 93.5f, 226.5f) + reflectiveQuadTo(480f, 800f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Face6.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Face6.kt new file mode 100644 index 0000000..642e014 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Face6.kt @@ -0,0 +1,108 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Face6: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Face6", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-82f, 0f, -155f, -31.5f) + reflectiveQuadToRelative(-127.5f, -86f) + quadTo(143f, 708f, 111.5f, 635f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadToRelative(86f, -127f) + quadTo(252f, 143f, 325f, 111.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 82f, -31.5f, 155f) + reflectiveQuadTo(763f, 762.5f) + quadToRelative(-54f, 54.5f, -127f, 86f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 160f) + quadToRelative(-111f, 0f, -196f, 66.5f) + reflectiveQuadTo(171f, 396f) + quadToRelative(20f, -5f, 41.5f, -19.5f) + reflectiveQuadTo(262f, 306f) + quadToRelative(15f, -31f, 44f, -48.5f) + reflectiveQuadToRelative(64f, -17.5f) + horizontalLineToRelative(220f) + quadToRelative(35f, 0f, 64f, 17.5f) + reflectiveQuadToRelative(44f, 48.5f) + quadToRelative(28f, 57f, 50.5f, 71f) + reflectiveQuadToRelative(40.5f, 19f) + quadToRelative(-28f, -103f, -112.5f, -169.5f) + reflectiveQuadTo(480f, 160f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227.5f, -94f) + reflectiveQuadTo(800f, 478f) + quadToRelative(-71f, -7f, -109f, -44.5f) + reflectiveQuadTo(626f, 342f) + quadToRelative(-5f, -11f, -14.5f, -16.5f) + reflectiveQuadTo(591f, 320f) + lineTo(370f, 320f) + quadToRelative(-12f, 0f, -21.5f, 5.5f) + reflectiveQuadTo(334f, 342f) + quadToRelative(-27f, 55f, -66f, 92.5f) + reflectiveQuadTo(160f, 479f) + quadToRelative(0f, 134f, 93.5f, 227.5f) + reflectiveQuadTo(480f, 800f) + close() + moveTo(360f, 570f) + quadToRelative(-21f, 0f, -35.5f, -14.5f) + reflectiveQuadTo(310f, 520f) + quadToRelative(0f, -21f, 14.5f, -35.5f) + reflectiveQuadTo(360f, 470f) + quadToRelative(21f, 0f, 35.5f, 14.5f) + reflectiveQuadTo(410f, 520f) + quadToRelative(0f, 21f, -14.5f, 35.5f) + reflectiveQuadTo(360f, 570f) + close() + moveTo(600f, 570f) + quadToRelative(-21f, 0f, -35.5f, -14.5f) + reflectiveQuadTo(550f, 520f) + quadToRelative(0f, -21f, 14.5f, -35.5f) + reflectiveQuadTo(600f, 470f) + quadToRelative(21f, 0f, 35.5f, 14.5f) + reflectiveQuadTo(650f, 520f) + quadToRelative(0f, 21f, -14.5f, 35.5f) + reflectiveQuadTo(600f, 570f) + close() + moveTo(480f, 240f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Favorite.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Favorite.kt new file mode 100644 index 0000000..be7355f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Favorite.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Favorite: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Favorite", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(451.5f, 808f) + quadToRelative(-14.5f, -5f, -25.5f, -16f) + lineToRelative(-69f, -63f) + quadToRelative(-106f, -97f, -191.5f, -192.5f) + reflectiveQuadTo(80f, 326f) + quadToRelative(0f, -94f, 63f, -157f) + reflectiveQuadToRelative(157f, -63f) + quadToRelative(53f, 0f, 100f, 22.5f) + reflectiveQuadToRelative(80f, 61.5f) + quadToRelative(33f, -39f, 80f, -61.5f) + reflectiveQuadTo(660f, 106f) + quadToRelative(94f, 0f, 157f, 63f) + reflectiveQuadToRelative(63f, 157f) + quadToRelative(0f, 115f, -85f, 211f) + reflectiveQuadTo(602f, 730f) + lineToRelative(-68f, 62f) + quadToRelative(-11f, 11f, -25.5f, 16f) + reflectiveQuadToRelative(-28.5f, 5f) + quadToRelative(-14f, 0f, -28.5f, -5f) + close() + moveTo(442f, 270f) + quadToRelative(-29f, -41f, -62f, -62.5f) + reflectiveQuadTo(300f, 186f) + quadToRelative(-60f, 0f, -100f, 40f) + reflectiveQuadToRelative(-40f, 100f) + quadToRelative(0f, 52f, 37f, 110.5f) + reflectiveQuadTo(285.5f, 550f) + quadToRelative(51.5f, 55f, 106f, 103f) + reflectiveQuadToRelative(88.5f, 79f) + quadToRelative(34f, -31f, 88.5f, -79f) + reflectiveQuadToRelative(106f, -103f) + quadTo(726f, 495f, 763f, 436.5f) + reflectiveQuadTo(800f, 326f) + quadToRelative(0f, -60f, -40f, -100f) + reflectiveQuadToRelative(-100f, -40f) + quadToRelative(-47f, 0f, -80f, 21.5f) + reflectiveQuadTo(518f, 270f) + quadToRelative(-7f, 10f, -17f, 15f) + reflectiveQuadToRelative(-21f, 5f) + quadToRelative(-11f, 0f, -21f, -5f) + reflectiveQuadToRelative(-17f, -15f) + close() + moveTo(480f, 459f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/File.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/File.kt new file mode 100644 index 0000000..51e3c46 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/File.kt @@ -0,0 +1,167 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.File: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.File", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(19.85f, 8.063f) + curveToRelative(-0.1f, -0.242f, -0.242f, -0.454f, -0.425f, -0.638f) + lineToRelative(-4.85f, -4.85f) + 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(-7.175f) + 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(12f) + curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) + reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) + verticalLineToRelative(-11.175f) + curveToRelative(0f, -0.267f, -0.05f, -0.521f, -0.15f, -0.763f) + close() + moveTo(14f, 9f) + 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) + lineToRelative(5f, 5f) + horizontalLineToRelative(-4f) + close() + } + }.build() +} + +val Icons.Outlined.File: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.File", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(19.85f, 8.063f) + curveToRelative(-0.1f, -0.242f, -0.242f, -0.454f, -0.425f, -0.638f) + lineToRelative(-4.85f, -4.85f) + 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(-7.175f) + 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(12f) + curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) + reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) + verticalLineToRelative(-11.175f) + curveToRelative(0f, -0.267f, -0.05f, -0.521f, -0.15f, -0.763f) + close() + moveTo(18f, 20f) + horizontalLineTo(6f) + verticalLineTo(4f) + horizontalLineToRelative(7f) + 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(4f) + verticalLineToRelative(11f) + close() + } + }.build() +} + + +val Icons.TwoTone.File: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.File", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(19.85f, 8.063f) + curveToRelative(-0.1f, -0.242f, -0.242f, -0.454f, -0.425f, -0.638f) + lineToRelative(-4.85f, -4.85f) + 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(-7.175f) + 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(12f) + curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) + reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) + verticalLineToRelative(-11.175f) + curveToRelative(0f, -0.267f, -0.05f, -0.521f, -0.15f, -0.763f) + close() + moveTo(18f, 20f) + horizontalLineTo(6f) + verticalLineTo(4f) + horizontalLineToRelative(7f) + 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(4f) + verticalLineToRelative(11f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(6f, 4f) + lineToRelative(0f, 5f) + lineToRelative(0f, -5f) + lineToRelative(0f, 16f) + lineToRelative(0f, -16f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(18.01f, 20f) + horizontalLineTo(6.01f) + verticalLineTo(4f) + horizontalLineToRelative(7f) + 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(4f) + verticalLineToRelative(11f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileCopy.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileCopy.kt new file mode 100644 index 0000000..065c6a4 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileCopy.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FileCopy: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FileCopy", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(760f, 760f) + lineTo(320f, 760f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(240f, 680f) + verticalLineToRelative(-560f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(320f, 40f) + horizontalLineToRelative(247f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(194f, 194f) + quadToRelative(11f, 11f, 17f, 25.5f) + reflectiveQuadToRelative(6f, 30.5f) + verticalLineToRelative(367f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 760f) + close() + moveTo(760f, 320f) + lineTo(620f, 320f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(560f, 260f) + verticalLineToRelative(-140f) + lineTo(320f, 120f) + verticalLineToRelative(560f) + horizontalLineToRelative(440f) + verticalLineToRelative(-360f) + close() + moveTo(160f, 920f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(80f, 840f) + verticalLineToRelative(-520f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(160f, 320f) + verticalLineToRelative(520f) + horizontalLineToRelative(400f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(600f, 880f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(560f, 920f) + lineTo(160f, 920f) + close() + moveTo(320f, 120f) + verticalLineToRelative(200f) + verticalLineToRelative(-200f) + verticalLineToRelative(560f) + verticalLineToRelative(-560f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileExport.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileExport.kt new file mode 100644 index 0000000..5be8816 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileExport.kt @@ -0,0 +1,144 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Rounded.FileExport: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FileExport", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(19.85f, 8.063f) + curveToRelative(-0.1f, -0.242f, -0.242f, -0.454f, -0.425f, -0.638f) + lineToRelative(-4.85f, -4.85f) + 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(-7.175f) + 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(12f) + curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) + reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) + verticalLineToRelative(-11.175f) + curveToRelative(0f, -0.267f, -0.05f, -0.521f, -0.15f, -0.763f) + close() + moveTo(14.975f, 16.669f) + 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) + reflectiveCurveToRelative(-0.521f, -0.096f, -0.713f, -0.287f) + curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.713f) + verticalLineToRelative(-1.225f) + lineToRelative(-2.25f, 2.25f) + curveToRelative(-0.2f, 0.2f, -0.433f, 0.296f, -0.7f, 0.287f) + reflectiveCurveToRelative(-0.5f, -0.112f, -0.7f, -0.313f) + curveToRelative(-0.183f, -0.2f, -0.279f, -0.433f, -0.287f, -0.7f) + curveToRelative(-0.008f, -0.267f, 0.087f, -0.5f, 0.287f, -0.7f) + lineToRelative(2.25f, -2.25f) + horizontalLineToRelative(-1.25f) + curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) + curveToRelative(-0.192f, -0.192f, -0.288f, -0.429f, -0.288f, -0.713f) + curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.288f, -0.712f) + curveToRelative(0.192f, -0.192f, 0.429f, -0.288f, 0.712f, -0.288f) + horizontalLineToRelative(3.65f) + 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) + verticalLineToRelative(3.65f) + close() + moveTo(14f, 9f) + 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) + lineToRelative(5f, 5f) + horizontalLineToRelative(-4f) + close() + } + }.build() +} + +val Icons.Outlined.FileExport: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FileExport", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(12.969f, 15.443f) + lineToRelative(-2.25f, 2.25f) + curveToRelative(-0.2f, 0.2f, -0.433f, 0.296f, -0.7f, 0.287f) + reflectiveCurveToRelative(-0.5f, -0.112f, -0.7f, -0.313f) + curveToRelative(-0.183f, -0.2f, -0.279f, -0.433f, -0.287f, -0.7f) + curveToRelative(-0.008f, -0.267f, 0.087f, -0.5f, 0.287f, -0.7f) + lineToRelative(2.25f, -2.25f) + horizontalLineToRelative(-1.25f) + 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(3.65f) + curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) + reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) + verticalLineToRelative(3.65f) + 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(-1.225f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(14f, 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(12f) + curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) + reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) + verticalLineToRelative(-12f) + lineToRelative(-6f, -6f) + close() + moveTo(18f, 20f) + horizontalLineTo(6f) + verticalLineTo(4f) + horizontalLineToRelative(7f) + 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(4f) + verticalLineToRelative(11f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileImage.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileImage.kt new file mode 100644 index 0000000..97e2ecd --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileImage.kt @@ -0,0 +1,164 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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(19.85f, 8.063f) + curveToRelative(-0.1f, -0.242f, -0.242f, -0.454f, -0.425f, -0.638f) + lineToRelative(-4.85f, -4.85f) + 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(-7.175f) + 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(12f) + curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) + reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) + verticalLineToRelative(-11.175f) + curveToRelative(0f, -0.267f, -0.05f, -0.521f, -0.15f, -0.763f) + close() + moveTo(7.86f, 9.245f) + curveToRelative(0.334f, -0.334f, 0.74f, -0.501f, 1.217f, -0.501f) + reflectiveCurveToRelative(0.883f, 0.167f, 1.216f, 0.501f) + curveToRelative(0.334f, 0.334f, 0.501f, 0.739f, 0.501f, 1.216f) + curveToRelative(0f, 0.477f, -0.167f, 0.883f, -0.501f, 1.217f) + curveToRelative(-0.334f, 0.334f, -0.739f, 0.501f, -1.216f, 0.501f) + reflectiveCurveToRelative(-0.883f, -0.167f, -1.217f, -0.501f) + curveToRelative(-0.334f, -0.334f, -0.501f, -0.739f, -0.501f, -1.217f) + curveToRelative(0f, -0.477f, 0.167f, -0.883f, 0.501f, -1.216f) + close() + moveTo(17.054f, 18.757f) + curveToRelative(-0.091f, 0.167f, -0.228f, 0.251f, -0.41f, 0.251f) + horizontalLineTo(7.528f) + curveToRelative(-0.182f, 0f, -0.319f, -0.084f, -0.41f, -0.251f) + curveToRelative(-0.091f, -0.167f, -0.076f, -0.327f, 0.046f, -0.479f) + lineToRelative(1.823f, -2.439f) + curveToRelative(0.091f, -0.122f, 0.213f, -0.182f, 0.365f, -0.182f) + reflectiveCurveToRelative(0.273f, 0.061f, 0.365f, 0.182f) + lineToRelative(1.687f, 2.256f) + lineToRelative(2.37f, -3.168f) + curveToRelative(0.091f, -0.122f, 0.213f, -0.182f, 0.365f, -0.182f) + curveToRelative(0.152f, 0f, 0.273f, 0.061f, 0.365f, 0.182f) + lineToRelative(2.507f, 3.35f) + curveToRelative(0.122f, 0.152f, 0.137f, 0.311f, 0.046f, 0.479f) + close() + moveTo(14f, 9f) + 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) + lineToRelative(5f, 5f) + horizontalLineToRelative(-4f) + 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(19.85f, 8.063f) + curveToRelative(-0.1f, -0.242f, -0.242f, -0.454f, -0.425f, -0.638f) + lineToRelative(-4.85f, -4.85f) + 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(-7.175f) + 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(12f) + curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) + reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) + verticalLineToRelative(-11.175f) + curveToRelative(0f, -0.267f, -0.05f, -0.521f, -0.15f, -0.763f) + close() + moveTo(18f, 20f) + horizontalLineTo(6f) + verticalLineTo(4f) + horizontalLineToRelative(7f) + 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(4f) + verticalLineToRelative(11f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(6f, 4f) + lineToRelative(0f, 5f) + lineToRelative(0f, -5f) + lineToRelative(0f, 16f) + lineToRelative(0f, -16f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(7.528f, 18.828f) + horizontalLineToRelative(9.116f) + curveToRelative(0.182f, 0f, 0.319f, -0.084f, 0.41f, -0.251f) + reflectiveCurveToRelative(0.076f, -0.327f, -0.046f, -0.479f) + lineToRelative(-2.507f, -3.35f) + curveToRelative(-0.091f, -0.122f, -0.213f, -0.182f, -0.365f, -0.182f) + reflectiveCurveToRelative(-0.273f, 0.061f, -0.365f, 0.182f) + lineToRelative(-2.37f, 3.168f) + lineToRelative(-1.687f, -2.256f) + curveToRelative(-0.091f, -0.122f, -0.213f, -0.182f, -0.365f, -0.182f) + reflectiveCurveToRelative(-0.273f, 0.061f, -0.365f, 0.182f) + lineToRelative(-1.823f, 2.439f) + curveToRelative(-0.122f, 0.152f, -0.137f, 0.311f, -0.046f, 0.479f) + reflectiveCurveToRelative(0.228f, 0.251f, 0.41f, 0.251f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(10.293f, 11.499f) + curveToRelative(0.334f, -0.334f, 0.501f, -0.739f, 0.501f, -1.217f) + reflectiveCurveToRelative(-0.167f, -0.883f, -0.501f, -1.217f) + reflectiveCurveToRelative(-0.739f, -0.501f, -1.217f, -0.501f) + reflectiveCurveToRelative(-0.883f, 0.167f, -1.217f, 0.501f) + reflectiveCurveToRelative(-0.501f, 0.739f, -0.501f, 1.217f) + reflectiveCurveToRelative(0.167f, 0.883f, 0.501f, 1.217f) + reflectiveCurveToRelative(0.739f, 0.501f, 1.217f, 0.501f) + reflectiveCurveToRelative(0.883f, -0.167f, 1.217f, -0.501f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileImport.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileImport.kt new file mode 100644 index 0000000..8196fdb --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileImport.kt @@ -0,0 +1,144 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Rounded.FileImport: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FileImport", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(19.85f, 8.063f) + curveToRelative(-0.1f, -0.242f, -0.242f, -0.454f, -0.425f, -0.638f) + lineToRelative(-4.85f, -4.85f) + 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(-7.175f) + 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(12f) + curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) + reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) + verticalLineToRelative(-11.175f) + curveToRelative(0f, -0.267f, -0.05f, -0.521f, -0.15f, -0.763f) + close() + moveTo(14.987f, 16.969f) + curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.288f, 0.712f) + curveToRelative(-0.192f, 0.192f, -0.429f, 0.288f, -0.712f, 0.288f) + horizontalLineToRelative(-3.65f) + 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.713f) + curveToRelative(0.192f, -0.192f, 0.429f, -0.287f, 0.713f, -0.287f) + horizontalLineToRelative(1.225f) + lineToRelative(-2.25f, -2.25f) + 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.279f, 0.7f, -0.287f) + curveToRelative(0.267f, -0.008f, 0.5f, 0.087f, 0.7f, 0.287f) + lineToRelative(2.25f, 2.25f) + verticalLineToRelative(-1.25f) + 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) + 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) + verticalLineToRelative(3.65f) + close() + moveTo(14f, 9f) + 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) + lineToRelative(5f, 5f) + horizontalLineToRelative(-4f) + close() + } + }.build() +} + +val Icons.Outlined.FileImport: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FileImport", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(11.562f, 15.969f) + lineToRelative(-2.25f, -2.25f) + curveToRelative(-0.2f, -0.2f, -0.296f, -0.433f, -0.287f, -0.7f) + reflectiveCurveToRelative(0.113f, -0.5f, 0.313f, -0.7f) + curveToRelative(0.2f, -0.183f, 0.433f, -0.279f, 0.7f, -0.287f) + curveToRelative(0.267f, -0.008f, 0.5f, 0.088f, 0.7f, 0.288f) + lineToRelative(2.25f, 2.25f) + lineToRelative(0f, -1.25f) + curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.288f, -0.712f) + curveToRelative(0.192f, -0.192f, 0.429f, -0.287f, 0.713f, -0.287f) + reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.288f) + reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.713f) + lineToRelative(-0f, 3.65f) + curveToRelative(-0f, 0.283f, -0.096f, 0.521f, -0.288f, 0.712f) + reflectiveCurveToRelative(-0.429f, 0.287f, -0.713f, 0.287f) + lineToRelative(-3.65f, -0f) + curveToRelative(-0.283f, -0f, -0.521f, -0.096f, -0.712f, -0.288f) + reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.713f) + reflectiveCurveToRelative(0.096f, -0.521f, 0.288f, -0.712f) + reflectiveCurveToRelative(0.429f, -0.287f, 0.713f, -0.287f) + lineToRelative(1.225f, 0f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(14f, 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(12f) + curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) + reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) + verticalLineToRelative(-12f) + lineToRelative(-6f, -6f) + close() + moveTo(18f, 20f) + horizontalLineTo(6f) + verticalLineTo(4f) + horizontalLineToRelative(7f) + 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(4f) + verticalLineToRelative(11f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileOpen.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileOpen.kt new file mode 100644 index 0000000..8f639a6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileOpen.kt @@ -0,0 +1,259 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FileOpen: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FileOpen", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(240f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 800f) + verticalLineToRelative(-640f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(240f, 80f) + horizontalLineToRelative(287f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(194f, 194f) + quadToRelative(11f, 11f, 17f, 25.5f) + reflectiveQuadToRelative(6f, 30.5f) + verticalLineToRelative(167f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 560f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(720f, 520f) + verticalLineToRelative(-160f) + lineTo(560f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(520f, 320f) + verticalLineToRelative(-160f) + lineTo(240f, 160f) + verticalLineToRelative(640f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(600f, 840f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(560f, 880f) + lineTo(240f, 880f) + close() + moveTo(760f, 777f) + verticalLineToRelative(49f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(720f, 866f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(680f, 826f) + verticalLineToRelative(-146f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(720f, 640f) + horizontalLineToRelative(146f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(906f, 680f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(866f, 720f) + horizontalLineToRelative(-50f) + lineToRelative(90f, 90f) + quadToRelative(11f, 11f, 11f, 27.5f) + reflectiveQuadTo(906f, 866f) + quadToRelative(-12f, 12f, -28.5f, 12f) + reflectiveQuadTo(849f, 866f) + lineToRelative(-89f, -89f) + close() + moveTo(240f, 800f) + verticalLineToRelative(-640f) + verticalLineToRelative(640f) + close() + } + }.build() +} + +val Icons.Rounded.FileOpen: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FileOpen", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(720f, 640f) + horizontalLineToRelative(146f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(906f, 680f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(866f, 720f) + horizontalLineToRelative(-50f) + lineToRelative(90f, 90f) + quadToRelative(11f, 11f, 11f, 27.5f) + reflectiveQuadTo(906f, 866f) + quadToRelative(-12f, 12f, -28.5f, 12f) + reflectiveQuadTo(849f, 866f) + lineToRelative(-89f, -89f) + verticalLineToRelative(49f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(720f, 866f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(680f, 826f) + verticalLineToRelative(-146f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(720f, 640f) + close() + moveTo(520f, 160f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(560f, 360f) + horizontalLineToRelative(160f) + lineTo(520f, 160f) + close() + moveTo(240f, 80f) + horizontalLineToRelative(287f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(194f, 194f) + quadToRelative(11f, 11f, 17f, 25.5f) + reflectiveQuadToRelative(6f, 30.5f) + verticalLineToRelative(167f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 560f) + lineTo(640f, 560f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(600f, 600f) + verticalLineToRelative(240f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(560f, 880f) + lineTo(240f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 800f) + verticalLineToRelative(-640f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(240f, 80f) + close() + } + }.build() +} + +val Icons.TwoTone.FileOpen: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.FileOpen", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(6f, 22f) + 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(7.175f) + curveToRelative(0.267f, 0f, 0.521f, 0.05f, 0.762f, 0.15f) + reflectiveCurveToRelative(0.454f, 0.242f, 0.637f, 0.425f) + lineToRelative(4.85f, 4.85f) + curveToRelative(0.183f, 0.183f, 0.325f, 0.396f, 0.425f, 0.637f) + reflectiveCurveToRelative(0.15f, 0.496f, 0.15f, 0.762f) + verticalLineToRelative(4.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(-4f) + horizontalLineToRelative(-4f) + curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) + reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) + verticalLineTo(4f) + horizontalLineToRelative(-7f) + verticalLineToRelative(16f) + horizontalLineToRelative(8f) + 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) + horizontalLineTo(6f) + close() + moveTo(19f, 19.425f) + verticalLineToRelative(1.225f) + 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.65f) + curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) + reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) + horizontalLineToRelative(3.65f) + 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(-1.25f) + lineToRelative(2.25f, 2.25f) + curveToRelative(0.183f, 0.183f, 0.275f, 0.412f, 0.275f, 0.688f) + reflectiveCurveToRelative(-0.092f, 0.512f, -0.275f, 0.712f) + curveToRelative(-0.2f, 0.2f, -0.438f, 0.3f, -0.712f, 0.3f) + reflectiveCurveToRelative(-0.512f, -0.1f, -0.712f, -0.3f) + lineToRelative(-2.225f, -2.225f) + close() + moveTo(6f, 20f) + verticalLineTo(4f) + verticalLineToRelative(16f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(6f, 2f) + horizontalLineToRelative(7.175f) + curveToRelative(0.267f, 0f, 0.521f, 0.05f, 0.762f, 0.15f) + reflectiveCurveToRelative(0.454f, 0.242f, 0.637f, 0.425f) + lineToRelative(4.85f, 4.85f) + curveToRelative(0.183f, 0.183f, 0.325f, 0.396f, 0.425f, 0.637f) + reflectiveCurveToRelative(0.15f, 0.496f, 0.15f, 0.762f) + verticalLineToRelative(4.175f) + curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) + reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) + horizontalLineToRelative(-3f) + 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) + 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) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FilePresent.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FilePresent.kt new file mode 100644 index 0000000..1a8ed34 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FilePresent.kt @@ -0,0 +1,164 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FilePresent: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FilePresent", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 760f) + quadToRelative(67f, 0f, 113.5f, -47f) + reflectiveQuadTo(640f, 600f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(600f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(560f, 480f) + verticalLineToRelative(120f) + quadToRelative(0f, 33f, -23f, 56.5f) + reflectiveQuadTo(480f, 680f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(400f, 600f) + verticalLineToRelative(-220f) + quadToRelative(0f, -9f, 6f, -14.5f) + reflectiveQuadToRelative(14f, -5.5f) + quadToRelative(9f, 0f, 14.5f, 5.5f) + reflectiveQuadTo(440f, 380f) + verticalLineToRelative(180f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 600f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 560f) + verticalLineToRelative(-180f) + quadToRelative(0f, -42f, -29f, -71f) + reflectiveQuadToRelative(-71f, -29f) + quadToRelative(-42f, 0f, -71f, 29f) + reflectiveQuadToRelative(-29f, 71f) + verticalLineToRelative(220f) + quadToRelative(0f, 66f, 47f, 113f) + reflectiveQuadToRelative(113f, 47f) + close() + moveTo(240f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 800f) + verticalLineToRelative(-640f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(240f, 80f) + horizontalLineToRelative(360f) + lineToRelative(200f, 200f) + verticalLineToRelative(520f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 880f) + lineTo(240f, 880f) + close() + moveTo(240f, 800f) + horizontalLineToRelative(480f) + verticalLineToRelative(-480f) + lineTo(600f, 320f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(560f, 280f) + verticalLineToRelative(-120f) + lineTo(240f, 160f) + verticalLineToRelative(640f) + close() + moveTo(240f, 160f) + verticalLineToRelative(160f) + verticalLineToRelative(-160f) + verticalLineToRelative(640f) + verticalLineToRelative(-640f) + close() + } + }.build() +} + +val Icons.Rounded.FilePresent: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FilePresent", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 760f) + quadToRelative(67f, 0f, 113.5f, -47f) + reflectiveQuadTo(640f, 600f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(600f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(560f, 480f) + verticalLineToRelative(120f) + quadToRelative(0f, 33f, -23f, 56.5f) + reflectiveQuadTo(480f, 680f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(400f, 600f) + verticalLineToRelative(-220f) + quadToRelative(0f, -9f, 6f, -14.5f) + reflectiveQuadToRelative(14f, -5.5f) + quadToRelative(9f, 0f, 14.5f, 5.5f) + reflectiveQuadTo(440f, 380f) + verticalLineToRelative(180f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 600f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 560f) + verticalLineToRelative(-180f) + quadToRelative(0f, -42f, -29f, -71f) + reflectiveQuadToRelative(-71f, -29f) + quadToRelative(-42f, 0f, -71f, 29f) + reflectiveQuadToRelative(-29f, 71f) + verticalLineToRelative(220f) + quadToRelative(0f, 66f, 47f, 113f) + reflectiveQuadToRelative(113f, 47f) + close() + moveTo(240f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 800f) + verticalLineToRelative(-640f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(240f, 80f) + horizontalLineToRelative(360f) + lineToRelative(200f, 200f) + verticalLineToRelative(520f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 880f) + lineTo(240f, 880f) + close() + moveTo(560f, 160f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(600f, 320f) + horizontalLineToRelative(120f) + lineTo(560f, 160f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileRename.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileRename.kt new file mode 100644 index 0000000..a6c10ab --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileRename.kt @@ -0,0 +1,160 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.FileRename: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FileRename", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(10.692f, 20.308f) + lineToRelative(3.19f, -3.19f) + curveToRelative(0.076f, -0.076f, 0.179f, -0.119f, 0.287f, -0.119f) + horizontalLineToRelative(5.832f) + curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) + curveToRelative(0.391f, 0.392f, 0.587f, 0.863f, 0.587f, 1.413f) + reflectiveCurveToRelative(-0.196f, 1.021f, -0.587f, 1.413f) + curveToRelative(-0.392f, 0.391f, -0.863f, 0.587f, -1.413f, 0.587f) + horizontalLineToRelative(-9.022f) + curveToRelative(-0.361f, 0f, -0.542f, -0.436f, -0.287f, -0.692f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(4f, 19f) + horizontalLineToRelative(1.425f) + lineToRelative(9.775f, -9.775f) + lineToRelative(-1.425f, -1.425f) + lineToRelative(-9.775f, 9.775f) + verticalLineToRelative(1.425f) + close() + moveTo(3f, 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) + verticalLineToRelative(-2.425f) + curveToRelative(0f, -0.267f, 0.05f, -0.521f, 0.15f, -0.762f) + reflectiveCurveToRelative(0.242f, -0.454f, 0.425f, -0.637f) + lineTo(15.2f, 3.575f) + curveToRelative(0.2f, -0.183f, 0.421f, -0.325f, 0.663f, -0.425f) + reflectiveCurveToRelative(0.496f, -0.15f, 0.762f, -0.15f) + reflectiveCurveToRelative(0.525f, 0.05f, 0.775f, 0.15f) + reflectiveCurveToRelative(0.467f, 0.25f, 0.65f, 0.45f) + lineToRelative(1.375f, 1.4f) + curveToRelative(0.2f, 0.183f, 0.346f, 0.4f, 0.438f, 0.65f) + reflectiveCurveToRelative(0.138f, 0.5f, 0.138f, 0.75f) + curveToRelative(0f, 0.267f, -0.046f, 0.521f, -0.138f, 0.762f) + reflectiveCurveToRelative(-0.237f, 0.463f, -0.438f, 0.663f) + lineToRelative(-12.6f, 12.6f) + curveToRelative(-0.183f, 0.183f, -0.396f, 0.325f, -0.637f, 0.425f) + reflectiveCurveToRelative(-0.496f, 0.15f, -0.762f, 0.15f) + horizontalLineToRelative(-2.425f) + close() + moveTo(18f, 6.4f) + lineToRelative(-1.4f, -1.4f) + lineToRelative(1.4f, 1.4f) + close() + moveTo(14.475f, 8.525f) + lineToRelative(-0.7f, -0.725f) + lineToRelative(1.425f, 1.425f) + lineToRelative(-0.725f, -0.7f) + close() + } + }.build() +} + +val Icons.TwoTone.FileRename: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.FileRename", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(10.692f, 20.308f) + lineToRelative(3.19f, -3.19f) + curveToRelative(0.076f, -0.076f, 0.179f, -0.119f, 0.287f, -0.119f) + horizontalLineToRelative(5.832f) + curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) + curveToRelative(0.391f, 0.392f, 0.587f, 0.863f, 0.587f, 1.413f) + reflectiveCurveToRelative(-0.196f, 1.021f, -0.587f, 1.413f) + curveToRelative(-0.392f, 0.391f, -0.863f, 0.587f, -1.413f, 0.587f) + horizontalLineToRelative(-9.022f) + curveToRelative(-0.361f, 0f, -0.542f, -0.436f, -0.287f, -0.692f) + close() + } + path(fill = SolidColor(Color.Black), fillAlpha = 0.3f) { + moveTo(4f, 19f) + horizontalLineToRelative(1.425f) + lineToRelative(9.775f, -9.775f) + lineToRelative(-1.425f, -1.425f) + lineToRelative(-9.775f, 9.775f) + verticalLineToRelative(1.425f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(4f, 19f) + horizontalLineToRelative(1.425f) + lineToRelative(9.775f, -9.775f) + lineToRelative(-1.425f, -1.425f) + lineToRelative(-9.775f, 9.775f) + verticalLineToRelative(1.425f) + close() + moveTo(3f, 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) + verticalLineToRelative(-2.425f) + curveToRelative(0f, -0.267f, 0.05f, -0.521f, 0.15f, -0.762f) + reflectiveCurveToRelative(0.242f, -0.454f, 0.425f, -0.637f) + lineTo(15.2f, 3.575f) + curveToRelative(0.2f, -0.183f, 0.421f, -0.325f, 0.663f, -0.425f) + reflectiveCurveToRelative(0.496f, -0.15f, 0.762f, -0.15f) + reflectiveCurveToRelative(0.525f, 0.05f, 0.775f, 0.15f) + reflectiveCurveToRelative(0.467f, 0.25f, 0.65f, 0.45f) + lineToRelative(1.375f, 1.4f) + curveToRelative(0.2f, 0.183f, 0.346f, 0.4f, 0.438f, 0.65f) + reflectiveCurveToRelative(0.138f, 0.5f, 0.138f, 0.75f) + curveToRelative(0f, 0.267f, -0.046f, 0.521f, -0.138f, 0.762f) + reflectiveCurveToRelative(-0.237f, 0.463f, -0.438f, 0.663f) + lineToRelative(-12.6f, 12.6f) + curveToRelative(-0.183f, 0.183f, -0.396f, 0.325f, -0.637f, 0.425f) + reflectiveCurveToRelative(-0.496f, 0.15f, -0.762f, 0.15f) + horizontalLineToRelative(-2.425f) + close() + moveTo(18f, 6.4f) + lineToRelative(-1.4f, -1.4f) + lineToRelative(1.4f, 1.4f) + close() + moveTo(14.475f, 8.525f) + lineToRelative(-0.7f, -0.725f) + lineToRelative(1.425f, 1.425f) + lineToRelative(-0.725f, -0.7f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileReplace.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileReplace.kt new file mode 100644 index 0000000..c45e098 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileReplace.kt @@ -0,0 +1,101 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.FileReplace: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FileReplace", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(20.85f, 11.063f) + curveToRelative(-0.1f, -0.242f, -0.242f, -0.454f, -0.425f, -0.638f) + lineToRelative(-4.85f, -4.85f) + 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(-6.175f) + curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) + reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) + verticalLineToRelative(5f) + curveToRelative(0f, 0.552f, 0.448f, 1f, 1f, 1f) + reflectiveCurveToRelative(1f, -0.448f, 1f, -1f) + verticalLineToRelative(-5f) + horizontalLineToRelative(6f) + 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(4f) + verticalLineToRelative(9f) + horizontalLineTo(7.999f) + verticalLineToRelative(-0.999f) + curveToRelative(0f, -0.552f, -0.448f, -1f, -1f, -1f) + reflectiveCurveToRelative(-1f, 0.448f, -1f, 1f) + verticalLineToRelative(1f) + horizontalLineToRelative(0.001f) + curveToRelative(0f, 0.549f, 0.196f, 1.02f, 0.587f, 1.411f) + 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(-9.175f) + curveToRelative(0f, -0.267f, -0.05f, -0.521f, -0.15f, -0.763f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(14.634f, 15.284f) + lineToRelative(-2.581f, -2.581f) + curveToRelative(-0.2f, -0.2f, -0.436f, -0.301f, -0.707f, -0.301f) + reflectiveCurveToRelative(-0.507f, 0.1f, -0.707f, 0.301f) + curveToRelative(-0.2f, 0.2f, -0.3f, 0.436f, -0.3f, 0.707f) + reflectiveCurveToRelative(0.1f, 0.507f, 0.3f, 0.707f) + lineToRelative(0.884f, 0.884f) + horizontalLineToRelative(-2.851f) + verticalLineToRelative(-0.001f) + horizontalLineTo(3.999f) + verticalLineTo(2.99f) + horizontalLineToRelative(7.979f) + curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) + curveToRelative(0f, -0.552f, -0.448f, -1f, -1f, -1f) + horizontalLineTo(3.999f) + verticalLineToRelative(0.01f) + curveToRelative(-1.104f, 0f, -1.999f, 0.895f, -1.999f, 1.999f) + verticalLineToRelative(12.001f) + curveToRelative(0f, 1.104f, 0.895f, 1.999f, 1.999f, 1.999f) + horizontalLineToRelative(7.506f) + lineToRelative(-0.866f, 0.866f) + curveToRelative(-0.2f, 0.2f, -0.3f, 0.436f, -0.3f, 0.707f) + reflectiveCurveToRelative(0.1f, 0.507f, 0.3f, 0.707f) + curveToRelative(0.2f, 0.2f, 0.436f, 0.301f, 0.707f, 0.301f) + reflectiveCurveToRelative(0.507f, -0.1f, 0.707f, -0.301f) + lineToRelative(2.581f, -2.581f) + curveToRelative(0.2f, -0.2f, 0.301f, -0.436f, 0.301f, -0.707f) + reflectiveCurveToRelative(-0.1f, -0.507f, -0.301f, -0.707f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FilterAlt.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FilterAlt.kt new file mode 100644 index 0000000..1c37575 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FilterAlt.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FilterAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FilterAlt", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(440f, 800f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(400f, 760f) + verticalLineToRelative(-240f) + lineTo(168f, 224f) + quadToRelative(-15f, -20f, -4.5f, -42f) + reflectiveQuadToRelative(36.5f, -22f) + horizontalLineToRelative(560f) + quadToRelative(26f, 0f, 36.5f, 22f) + reflectiveQuadToRelative(-4.5f, 42f) + lineTo(560f, 520f) + verticalLineToRelative(240f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(520f, 800f) + horizontalLineToRelative(-80f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FilterBAndW.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FilterBAndW.kt new file mode 100644 index 0000000..be8f1de --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FilterBAndW.kt @@ -0,0 +1,112 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FilterBAndW: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FilterBAndW", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(760f, 840f) + lineTo(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) + close() + moveTo(200f, 760f) + horizontalLineToRelative(280f) + verticalLineToRelative(-320f) + lineToRelative(280f, 320f) + verticalLineToRelative(-560f) + lineTo(480f, 200f) + verticalLineToRelative(240f) + lineTo(200f, 760f) + close() + } + }.build() +} + +val Icons.TwoTone.FilterBAndW: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.FilterBAndW", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(19f, 21f) + 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) + 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(14f) + 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) + close() + moveTo(5f, 19f) + horizontalLineToRelative(7f) + verticalLineToRelative(-8f) + lineToRelative(7f, 8f) + verticalLineTo(5f) + horizontalLineToRelative(-7f) + verticalLineToRelative(6f) + lineToRelative(-7f, 8f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(5f, 19f) + lineToRelative(7f, 0f) + lineToRelative(0f, -8f) + lineToRelative(7f, 8f) + lineToRelative(0f, -14f) + lineToRelative(-7f, 0f) + lineToRelative(0f, 6f) + lineToRelative(-7f, 8f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FilterFrames.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FilterFrames.kt new file mode 100644 index 0000000..7f11d71 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FilterFrames.kt @@ -0,0 +1,85 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FilterFrames: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FilterFrames", + 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(-560f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(160f, 160f) + horizontalLineToRelative(160f) + lineToRelative(132f, -132f) + quadToRelative(12f, -12f, 28f, -12f) + reflectiveQuadToRelative(28f, 12f) + lineToRelative(132f, 132f) + horizontalLineToRelative(160f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 240f) + verticalLineToRelative(560f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 880f) + lineTo(160f, 880f) + close() + moveTo(160f, 800f) + horizontalLineToRelative(640f) + verticalLineToRelative(-560f) + lineTo(160f, 240f) + verticalLineToRelative(560f) + close() + moveTo(240f, 680f) + verticalLineToRelative(-320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(280f, 320f) + horizontalLineToRelative(400f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(720f, 360f) + verticalLineToRelative(320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(680f, 720f) + lineTo(280f, 720f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(240f, 680f) + close() + moveTo(320f, 640f) + horizontalLineToRelative(320f) + verticalLineToRelative(-240f) + lineTo(320f, 400f) + verticalLineToRelative(240f) + close() + moveTo(480f, 520f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FilterHdr.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FilterHdr.kt new file mode 100644 index 0000000..5de6bec --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FilterHdr.kt @@ -0,0 +1,64 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FilterHdr: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FilterHdr", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(88f, 656f) + lineToRelative(160f, -213f) + quadToRelative(6f, -8f, 14.5f, -12f) + reflectiveQuadToRelative(17.5f, -4f) + quadToRelative(9f, 0f, 17.5f, 4f) + reflectiveQuadToRelative(14.5f, 12f) + lineToRelative(136f, 181f) + quadToRelative(6f, 8f, 14f, 12f) + reflectiveQuadToRelative(18f, 4f) + quadToRelative(25f, 0f, 36f, -22.5f) + reflectiveQuadToRelative(-4f, -42.5f) + lineToRelative(-84f, -111f) + quadToRelative(-8f, -11f, -8f, -24f) + reflectiveQuadToRelative(8f, -24f) + lineToRelative(100f, -133f) + quadToRelative(6f, -8f, 14.5f, -12f) + reflectiveQuadToRelative(17.5f, -4f) + quadToRelative(9f, 0f, 17.5f, 4f) + reflectiveQuadToRelative(14.5f, 12f) + lineToRelative(280f, 373f) + quadToRelative(15f, 20f, 4f, 42f) + reflectiveQuadToRelative(-36f, 22f) + lineTo(120f, 720f) + quadToRelative(-25f, 0f, -36f, -22f) + reflectiveQuadToRelative(4f, -42f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FinanceMode.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FinanceMode.kt new file mode 100644 index 0000000..b587dfe --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FinanceMode.kt @@ -0,0 +1,104 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.FinanceMode: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FinanceMode", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(320f, 470f) + verticalLineToRelative(-170f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(380f, 240f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(440f, 300f) + verticalLineToRelative(170f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(380f, 530f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(320f, 470f) + close() + moveTo(520f, 461f) + verticalLineToRelative(-321f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(580f, 80f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(640f, 140f) + verticalLineToRelative(321f) + quadToRelative(0f, 30f, -18.5f, 45f) + reflectiveQuadTo(580f, 521f) + quadToRelative(-23f, 0f, -41.5f, -15f) + reflectiveQuadTo(520f, 461f) + close() + moveTo(120f, 599f) + verticalLineToRelative(-139f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(180f, 400f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(240f, 460f) + verticalLineToRelative(139f) + quadToRelative(0f, 30f, -18.5f, 45f) + reflectiveQuadTo(180f, 659f) + quadToRelative(-23f, 0f, -41.5f, -15f) + reflectiveQuadTo(120f, 599f) + close() + moveTo(216f, 842f) + quadToRelative(-26f, 0f, -36.5f, -24.5f) + reflectiveQuadTo(188f, 774f) + lineToRelative(164f, -164f) + quadToRelative(11f, -11f, 26.5f, -12f) + reflectiveQuadToRelative(27.5f, 10f) + lineToRelative(114f, 98f) + lineToRelative(224f, -224f) + horizontalLineToRelative(-24f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(680f, 442f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(720f, 402f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 442f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 602f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(800f, 562f) + verticalLineToRelative(-24f) + lineTo(550f, 788f) + quadToRelative(-11f, 11f, -26.5f, 12f) + reflectiveQuadTo(496f, 790f) + lineToRelative(-114f, -98f) + lineToRelative(-138f, 138f) + quadToRelative(-5f, 5f, -12.5f, 8.5f) + reflectiveQuadTo(216f, 842f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FindInPage.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FindInPage.kt new file mode 100644 index 0000000..cae4c55 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FindInPage.kt @@ -0,0 +1,170 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Firebase.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Firebase.kt new file mode 100644 index 0000000..df266d9 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Firebase.kt @@ -0,0 +1,129 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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.Black), + stroke = SolidColor(Color.Black), + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FitScreen.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FitScreen.kt new file mode 100644 index 0000000..228a3cc --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FitScreen.kt @@ -0,0 +1,126 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FitScreen: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FitScreen", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(800f, 320f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(680f, 200f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(720f, 160f) + horizontalLineToRelative(80f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 240f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(800f, 320f) + close() + moveTo(80f, 320f) + verticalLineToRelative(-80f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(160f, 160f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(280f, 200f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 240f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(120f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(80f, 320f) + close() + moveTo(800f, 800f) + horizontalLineToRelative(-80f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(680f, 760f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(720f, 720f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(840f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 640f) + verticalLineToRelative(80f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 800f) + close() + moveTo(160f, 800f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(80f, 720f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(160f, 640f) + verticalLineToRelative(80f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(280f, 760f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 800f) + horizontalLineToRelative(-80f) + close() + moveTo(240f, 560f) + verticalLineToRelative(-160f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(320f, 320f) + horizontalLineToRelative(320f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(720f, 400f) + verticalLineToRelative(160f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(640f, 640f) + lineTo(320f, 640f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(240f, 560f) + close() + moveTo(320f, 560f) + horizontalLineToRelative(320f) + verticalLineToRelative(-160f) + lineTo(320f, 400f) + verticalLineToRelative(160f) + close() + moveTo(320f, 560f) + verticalLineToRelative(-160f) + verticalLineToRelative(160f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Flag.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Flag.kt new file mode 100644 index 0000000..4ed0cd5 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Flag.kt @@ -0,0 +1,79 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Flag: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Flag", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(280f, 560f) + verticalLineToRelative(240f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(200f, 800f) + verticalLineToRelative(-600f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(240f, 160f) + horizontalLineToRelative(287f) + quadToRelative(14f, 0f, 25f, 9f) + reflectiveQuadToRelative(14f, 23f) + lineToRelative(10f, 48f) + horizontalLineToRelative(184f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 280f) + verticalLineToRelative(320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 640f) + lineTo(553f, 640f) + quadToRelative(-14f, 0f, -25f, -9f) + reflectiveQuadToRelative(-14f, -23f) + lineToRelative(-10f, -48f) + lineTo(280f, 560f) + close() + moveTo(586f, 560f) + horizontalLineToRelative(134f) + verticalLineToRelative(-240f) + lineTo(543f, 320f) + quadToRelative(-14f, 0f, -25f, -9f) + reflectiveQuadToRelative(-14f, -23f) + lineToRelative(-10f, -48f) + lineTo(280f, 240f) + verticalLineToRelative(240f) + horizontalLineToRelative(257f) + quadToRelative(14f, 0f, 25f, 9f) + reflectiveQuadToRelative(14f, 23f) + lineToRelative(10f, 48f) + close() + moveTo(500f, 400f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Flip.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Flip.kt new file mode 100644 index 0000000..cfee261 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Flip.kt @@ -0,0 +1,140 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FlipVertical.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FlipVertical.kt new file mode 100644 index 0000000..651e3ad --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FlipVertical.kt @@ -0,0 +1,140 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FloodFill.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FloodFill.kt new file mode 100644 index 0000000..d08b43b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FloodFill.kt @@ -0,0 +1,80 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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) + quadToRelative(-10f, -10f, -15f, -22f) + reflectiveQuadToRelative(-5f, -25f) + quadToRelative(0f, -13f, 5f, -25f) + reflectiveQuadToRelative(15f, -22f) + lineToRelative(230f, -229f) + lineToRelative(-75f, -75f) + quadToRelative(-13f, -13f, -13.5f, -31f) + reflectiveQuadToRelative(12.5f, -32f) + quadToRelative(13f, -14f, 32f, -14f) + reflectiveQuadToRelative(33f, 14f) + lineToRelative(367f, 367f) + quadToRelative(10f, 10f, 14.5f, 22f) + reflectiveQuadToRelative(4.5f, 25f) + quadToRelative(0f, 13f, -4.5f, 25f) + reflectiveQuadTo(686f, 574f) + lineTo(440f, 820f) + quadToRelative(-10f, 10f, -22f, 15f) + reflectiveQuadToRelative(-25f, 5f) + quadToRelative(-13f, 0f, -25f, -5f) + reflectiveQuadToRelative(-22f, -15f) + close() + moveTo(393f, 314f) + lineTo(179f, 528f) + horizontalLineToRelative(428f) + lineTo(393f, 314f) + close() + moveTo(792f, 840f) + quadToRelative(-36f, 0f, -61f, -25.5f) + reflectiveQuadTo(706f, 752f) + quadToRelative(0f, -27f, 13.5f, -51f) + reflectiveQuadToRelative(30.5f, -47f) + lineToRelative(19f, -24f) + quadToRelative(9f, -11f, 23.5f, -11.5f) + reflectiveQuadTo(816f, 629f) + lineToRelative(20f, 25f) + quadToRelative(16f, 23f, 30f, 47f) + reflectiveQuadToRelative(14f, 51f) + quadToRelative(0f, 37f, -26f, 62.5f) + reflectiveQuadTo(792f, 840f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Folder.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Folder.kt new file mode 100644 index 0000000..272685b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Folder.kt @@ -0,0 +1,56 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Folder: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Folder", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + 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(207f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(57f, 57f) + horizontalLineToRelative(320f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 320f) + verticalLineToRelative(400f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 800f) + lineTo(160f, 800f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderImage.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderImage.kt new file mode 100644 index 0000000..b490616 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderImage.kt @@ -0,0 +1,71 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderMatch.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderMatch.kt new file mode 100644 index 0000000..d67713c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderMatch.kt @@ -0,0 +1,219 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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() +} + +val Icons.Outlined.FolderMatch: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.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() + moveTo(520f, 800f) + horizontalLineToRelative(280f) + verticalLineToRelative(-140f) + lineTo(617f, 660f) + lineToRelative(-40f, -60f) + horizontalLineToRelative(-57f) + verticalLineToRelative(200f) + close() + moveTo(160f, 360f) + horizontalLineToRelative(280f) + verticalLineToRelative(-140f) + lineTo(257f, 220f) + lineToRelative(-40f, -60f) + horizontalLineToRelative(-57f) + verticalLineToRelative(200f) + close() + moveTo(520f, 800f) + verticalLineToRelative(-200f) + verticalLineToRelative(200f) + close() + moveTo(160f, 360f) + verticalLineToRelative(-200f) + verticalLineToRelative(200f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderOff.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderOff.kt new file mode 100644 index 0000000..a2adad9 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderOff.kt @@ -0,0 +1,85 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FolderOff: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FolderOff", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(880f, 320f) + verticalLineToRelative(351f) + quadToRelative(0f, 20f, -12.5f, 30f) + reflectiveQuadTo(840f, 711f) + quadToRelative(-15f, 0f, -27.5f, -10.5f) + reflectiveQuadTo(800f, 670f) + verticalLineToRelative(-350f) + lineTo(467f, 320f) + quadToRelative(-16f, 0f, -30.5f, -6f) + reflectiveQuadTo(411f, 297f) + lineToRelative(-68f, -68f) + quadToRelative(-14f, -14f, -12.5f, -30f) + reflectiveQuadToRelative(12.5f, -27f) + quadToRelative(11f, -11f, 27f, -12.5f) + reflectiveQuadToRelative(30f, 12.5f) + lineToRelative(68f, 68f) + horizontalLineToRelative(332f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 320f) + 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) + lineToRelative(80f, 80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(480f) + horizontalLineToRelative(447f) + lineTo(56f, 168f) + quadToRelative(-11f, -11f, -11.5f, -27.5f) + reflectiveQuadTo(56f, 112f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(736f, 736f) + quadToRelative(12f, 12f, 11.5f, 28f) + reflectiveQuadTo(847f, 904f) + quadToRelative(-12f, 11f, -28f, 11.5f) + reflectiveQuadTo(791f, 904f) + lineTo(687f, 800f) + lineTo(160f, 800f) + close() + moveTo(368f, 480f) + close() + moveTo(577f, 463f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderOpen.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderOpen.kt new file mode 100644 index 0000000..6927080 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderOpen.kt @@ -0,0 +1,125 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FolderOpen: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FolderOpen", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + 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(207f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(57f, 57f) + horizontalLineToRelative(360f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 280f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 320f) + lineTo(447f, 320f) + lineToRelative(-80f, -80f) + lineTo(160f, 240f) + verticalLineToRelative(480f) + lineToRelative(79f, -263f) + quadToRelative(8f, -26f, 29.5f, -41.5f) + reflectiveQuadTo(316f, 400f) + horizontalLineToRelative(516f) + quadToRelative(41f, 0f, 64.5f, 32.5f) + reflectiveQuadTo(909f, 503f) + lineToRelative(-72f, 240f) + quadToRelative(-8f, 26f, -29.5f, 41.5f) + reflectiveQuadTo(760f, 800f) + lineTo(160f, 800f) + close() + moveTo(244f, 720f) + horizontalLineToRelative(516f) + lineToRelative(72f, -240f) + lineTo(316f, 480f) + lineToRelative(-72f, 240f) + close() + moveTo(160f, 458f) + verticalLineToRelative(-218f) + verticalLineToRelative(218f) + close() + moveTo(244f, 720f) + lineTo(316f, 480f) + lineTo(244f, 720f) + close() + } + }.build() +} + +val Icons.Rounded.FolderOpen: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FolderOpen", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + 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(207f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(57f, 57f) + horizontalLineToRelative(360f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 280f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 320f) + lineTo(314f, 320f) + quadToRelative(-62f, 0f, -108f, 39f) + reflectiveQuadToRelative(-46f, 99f) + verticalLineToRelative(262f) + lineToRelative(79f, -263f) + quadToRelative(8f, -26f, 29.5f, -41.5f) + reflectiveQuadTo(316f, 400f) + horizontalLineToRelative(516f) + quadToRelative(41f, 0f, 64.5f, 32.5f) + reflectiveQuadTo(909f, 503f) + lineToRelative(-72f, 240f) + quadToRelative(-8f, 26f, -29.5f, 41.5f) + reflectiveQuadTo(760f, 800f) + lineTo(160f, 800f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderShared.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderShared.kt new file mode 100644 index 0000000..ad6e3f6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderShared.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FolderShared: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FolderShared", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + 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(207f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(57f, 57f) + horizontalLineToRelative(320f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 320f) + verticalLineToRelative(400f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 800f) + lineTo(160f, 800f) + close() + moveTo(160f, 720f) + horizontalLineToRelative(640f) + verticalLineToRelative(-400f) + lineTo(447f, 320f) + lineToRelative(-80f, -80f) + lineTo(160f, 240f) + verticalLineToRelative(480f) + close() + moveTo(160f, 720f) + verticalLineToRelative(-480f) + verticalLineToRelative(480f) + close() + moveTo(440f, 680f) + horizontalLineToRelative(320f) + verticalLineToRelative(-22f) + quadToRelative(0f, -45f, -44f, -71.5f) + reflectiveQuadTo(600f, 560f) + quadToRelative(-72f, 0f, -116f, 26.5f) + reflectiveQuadTo(440f, 658f) + verticalLineToRelative(22f) + close() + moveTo(600f, 520f) + quadToRelative(33f, 0f, 56.5f, -23.5f) + reflectiveQuadTo(680f, 440f) + quadToRelative(0f, -33f, -23.5f, -56.5f) + reflectiveQuadTo(600f, 360f) + quadToRelative(-33f, 0f, -56.5f, 23.5f) + reflectiveQuadTo(520f, 440f) + quadToRelative(0f, 33f, 23.5f, 56.5f) + reflectiveQuadTo(600f, 520f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderSpecial.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderSpecial.kt new file mode 100644 index 0000000..fa4ff75 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderSpecial.kt @@ -0,0 +1,90 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FolderSpecial: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FolderSpecial", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + 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(207f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(57f, 57f) + horizontalLineToRelative(320f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 320f) + verticalLineToRelative(400f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 800f) + lineTo(160f, 800f) + close() + moveTo(160f, 720f) + horizontalLineToRelative(640f) + verticalLineToRelative(-400f) + lineTo(447f, 320f) + lineToRelative(-80f, -80f) + lineTo(160f, 240f) + verticalLineToRelative(480f) + close() + moveTo(160f, 720f) + verticalLineToRelative(-480f) + verticalLineToRelative(480f) + close() + moveTo(596f, 598f) + lineTo(664f, 649f) + quadToRelative(6f, 5f, 11.5f, 1f) + reflectiveQuadToRelative(3.5f, -11f) + lineToRelative(-25f, -85f) + lineToRelative(70f, -56f) + quadToRelative(5f, -5f, 3f, -11.5f) + reflectiveQuadToRelative(-9f, -6.5f) + horizontalLineToRelative(-86f) + lineToRelative(-26f, -82f) + quadToRelative(-2f, -7f, -10f, -7f) + reflectiveQuadToRelative(-10f, 7f) + lineToRelative(-26f, 82f) + horizontalLineToRelative(-86f) + quadToRelative(-7f, 0f, -9f, 6.5f) + reflectiveQuadToRelative(3f, 11.5f) + lineToRelative(70f, 56f) + lineToRelative(-25f, 85f) + quadToRelative(-2f, 7f, 3.5f, 11f) + reflectiveQuadToRelative(11.5f, -1f) + lineToRelative(68f, -51f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderZip.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderZip.kt new file mode 100644 index 0000000..69bc4f7 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderZip.kt @@ -0,0 +1,185 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FolderZip: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FolderZip", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(640f, 480f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(640f, 560f) + horizontalLineToRelative(-80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + close() + moveTo(640f, 640f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(447f, 320f) + lineToRelative(-80f, -80f) + lineTo(160f, 240f) + verticalLineToRelative(480f) + horizontalLineToRelative(400f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(160f) + verticalLineToRelative(-400f) + lineTo(640f, 320f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(-80f) + lineTo(447f, 320f) + 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(207f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(57f, 57f) + horizontalLineToRelative(320f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 320f) + verticalLineToRelative(400f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 800f) + lineTo(160f, 800f) + close() + moveTo(160f, 720f) + verticalLineToRelative(-480f) + verticalLineToRelative(480f) + close() + } + }.build() +} + +val Icons.TwoTone.FolderZip: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.FolderZip", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(16f, 12f) + verticalLineToRelative(-2f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + moveTo(16f, 14f) + horizontalLineToRelative(-2f) + verticalLineToRelative(-2f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + close() + moveTo(16f, 16f) + verticalLineToRelative(-2f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + moveTo(11.175f, 8f) + lineToRelative(-2f, -2f) + horizontalLineToRelative(-5.175f) + verticalLineToRelative(12f) + horizontalLineToRelative(10f) + verticalLineToRelative(-2f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(4f) + verticalLineTo(8f) + horizontalLineToRelative(-4f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + verticalLineToRelative(-2f) + horizontalLineToRelative(-2.825f) + close() + 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(5.175f) + curveToRelative(0.267f, 0f, 0.521f, 0.05f, 0.762f, 0.15f) + reflectiveCurveToRelative(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) + reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) + verticalLineToRelative(10f) + 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) + verticalLineTo(6f) + verticalLineToRelative(12f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(11.175f, 8f) + lineToRelative(-2f, -2f) + lineToRelative(-5.175f, 0f) + lineToRelative(0f, 12f) + lineToRelative(10f, 0f) + lineToRelative(0f, -2f) + lineToRelative(2f, 0f) + lineToRelative(0f, 2f) + lineToRelative(4f, 0f) + lineToRelative(0f, -10f) + lineToRelative(-4f, 0f) + lineToRelative(0f, 2f) + lineToRelative(-2f, 0f) + lineToRelative(0f, -2f) + lineToRelative(-2.825f, 0f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FontDownload.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FontDownload.kt new file mode 100644 index 0000000..1c9a1bb --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FontDownload.kt @@ -0,0 +1,78 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FontDownload: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FontDownload", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(384f, 598f) + horizontalLineToRelative(192f) + lineToRelative(35f, 97f) + quadToRelative(4f, 11f, 14f, 18f) + reflectiveQuadToRelative(22f, 7f) + quadToRelative(20f, 0f, 32.5f, -16.5f) + reflectiveQuadTo(684f, 667f) + lineTo(532f, 265f) + quadToRelative(-5f, -11f, -15f, -18f) + reflectiveQuadToRelative(-22f, -7f) + horizontalLineToRelative(-30f) + quadToRelative(-12f, 0f, -22f, 7f) + reflectiveQuadToRelative(-15f, 18f) + lineTo(276f, 667f) + quadToRelative(-8f, 19f, 4f, 36f) + reflectiveQuadToRelative(32f, 17f) + quadToRelative(13f, 0f, 22.5f, -7f) + reflectiveQuadToRelative(14.5f, -19f) + lineToRelative(35f, -96f) + close() + moveTo(408f, 528f) + lineTo(478f, 330f) + horizontalLineToRelative(4f) + lineToRelative(70f, 198f) + lineTo(408f, 528f) + close() + moveTo(160f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(80f, 800f) + verticalLineToRelative(-640f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(160f, 80f) + horizontalLineToRelative(640f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 160f) + verticalLineToRelative(640f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 880f) + lineTo(160f, 880f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FontFamily.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FontFamily.kt new file mode 100644 index 0000000..ee136d9 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FontFamily.kt @@ -0,0 +1,181 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatAlignCenter.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatAlignCenter.kt new file mode 100644 index 0000000..6597015 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatAlignCenter.kt @@ -0,0 +1,98 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FormatAlignCenter: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FormatAlignCenter", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 760f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + lineTo(160f, 840f) + close() + moveTo(320f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 600f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 680f) + lineTo(320f, 680f) + close() + moveTo(160f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 440f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 520f) + lineTo(160f, 520f) + close() + moveTo(320f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 280f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 360f) + lineTo(320f, 360f) + close() + moveTo(160f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 200f) + lineTo(160f, 200f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatAlignLeft.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatAlignLeft.kt new file mode 100644 index 0000000..0ea56bc --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatAlignLeft.kt @@ -0,0 +1,99 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FormatAlignLeft: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FormatAlignLeft", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 760f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + lineTo(160f, 840f) + close() + moveTo(160f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 600f) + horizontalLineToRelative(400f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(600f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(560f, 680f) + lineTo(160f, 680f) + close() + moveTo(160f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 440f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 520f) + lineTo(160f, 520f) + close() + moveTo(160f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 280f) + horizontalLineToRelative(400f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(600f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(560f, 360f) + lineTo(160f, 360f) + close() + moveTo(160f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 200f) + lineTo(160f, 200f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatAlignRight.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatAlignRight.kt new file mode 100644 index 0000000..57f20b6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatAlignRight.kt @@ -0,0 +1,99 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FormatAlignRight: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FormatAlignRight", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 200f) + lineTo(160f, 200f) + close() + moveTo(400f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(360f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(400f, 280f) + horizontalLineToRelative(400f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 360f) + lineTo(400f, 360f) + close() + moveTo(160f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 440f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 520f) + lineTo(160f, 520f) + close() + moveTo(400f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(360f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(400f, 600f) + horizontalLineToRelative(400f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 680f) + lineTo(400f, 680f) + close() + moveTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 760f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + lineTo(160f, 840f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatBold.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatBold.kt new file mode 100644 index 0000000..47eebaf --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatBold.kt @@ -0,0 +1,73 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FormatBold: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FormatBold", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(352f, 760f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(272f, 680f) + verticalLineToRelative(-400f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(352f, 200f) + horizontalLineToRelative(141f) + quadToRelative(65f, 0f, 120f, 40f) + reflectiveQuadToRelative(55f, 111f) + quadToRelative(0f, 51f, -23f, 78.5f) + reflectiveQuadTo(602f, 469f) + quadToRelative(25f, 11f, 55.5f, 41f) + reflectiveQuadToRelative(30.5f, 90f) + quadToRelative(0f, 89f, -65f, 124.5f) + reflectiveQuadTo(501f, 760f) + lineTo(352f, 760f) + close() + moveTo(393f, 648f) + horizontalLineToRelative(104f) + quadToRelative(48f, 0f, 58.5f, -24.5f) + reflectiveQuadTo(566f, 588f) + quadToRelative(0f, -11f, -10.5f, -35.5f) + reflectiveQuadTo(494f, 528f) + lineTo(393f, 528f) + verticalLineToRelative(120f) + close() + moveTo(393f, 420f) + horizontalLineToRelative(93f) + quadToRelative(33f, 0f, 48f, -17f) + reflectiveQuadToRelative(15f, -38f) + quadToRelative(0f, -24f, -17f, -39f) + reflectiveQuadToRelative(-44f, -15f) + horizontalLineToRelative(-95f) + verticalLineToRelative(109f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatColorFill.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatColorFill.kt new file mode 100644 index 0000000..ebc5dd6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatColorFill.kt @@ -0,0 +1,86 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FormatColorFill: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FormatColorFill", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(332f, 28f) + lineToRelative(315f, 315f) + quadToRelative(23f, 23f, 23f, 57f) + reflectiveQuadToRelative(-23f, 57f) + lineTo(457f, 647f) + quadToRelative(-23f, 23f, -57f, 23f) + reflectiveQuadToRelative(-57f, -23f) + lineTo(153f, 457f) + quadToRelative(-23f, -23f, -23f, -57f) + reflectiveQuadToRelative(23f, -57f) + lineToRelative(190f, -191f) + lineToRelative(-68f, -68f) + quadToRelative(-12f, -12f, -11.5f, -28f) + reflectiveQuadToRelative(12.5f, -28f) + quadToRelative(12f, -11f, 28f, -11.5f) + reflectiveQuadToRelative(28f, 11.5f) + close() + moveTo(400f, 209f) + lineTo(209f, 400f) + horizontalLineToRelative(382f) + lineTo(400f, 209f) + close() + moveTo(703.5f, 656.5f) + quadTo(680f, 633f, 680f, 600f) + quadToRelative(0f, -21f, 12.5f, -45f) + reflectiveQuadToRelative(27.5f, -45f) + quadToRelative(9f, -12f, 19f, -25f) + reflectiveQuadToRelative(21f, -25f) + quadToRelative(11f, 12f, 21f, 25f) + reflectiveQuadToRelative(19f, 25f) + quadToRelative(15f, 21f, 27.5f, 45f) + reflectiveQuadToRelative(12.5f, 45f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 680f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + close() + moveTo(160f, 960f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(80f, 880f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(160f, 800f) + horizontalLineToRelative(640f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 880f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 960f) + lineTo(160f, 960f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatItalic.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatItalic.kt new file mode 100644 index 0000000..98131b3 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatItalic.kt @@ -0,0 +1,64 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FormatItalic: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FormatItalic", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(250f, 760f) + quadToRelative(-21f, 0f, -35.5f, -14.5f) + reflectiveQuadTo(200f, 710f) + quadToRelative(0f, -21f, 14.5f, -35.5f) + reflectiveQuadTo(250f, 660f) + horizontalLineToRelative(110f) + lineToRelative(120f, -360f) + lineTo(370f, 300f) + quadToRelative(-21f, 0f, -35.5f, -14.5f) + reflectiveQuadTo(320f, 250f) + quadToRelative(0f, -21f, 14.5f, -35.5f) + reflectiveQuadTo(370f, 200f) + horizontalLineToRelative(300f) + quadToRelative(21f, 0f, 35.5f, 14.5f) + reflectiveQuadTo(720f, 250f) + quadToRelative(0f, 21f, -14.5f, 35.5f) + reflectiveQuadTo(670f, 300f) + horizontalLineToRelative(-90f) + lineTo(460f, 660f) + horizontalLineToRelative(90f) + quadToRelative(21f, 0f, 35.5f, 14.5f) + reflectiveQuadTo(600f, 710f) + quadToRelative(0f, 21f, -14.5f, 35.5f) + reflectiveQuadTo(550f, 760f) + lineTo(250f, 760f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatLineSpacing.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatLineSpacing.kt new file mode 100644 index 0000000..062110b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatLineSpacing.kt @@ -0,0 +1,110 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FormatLineSpacing: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FormatLineSpacing", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(200f, 314f) + lineToRelative(-36f, 35f) + quadToRelative(-11f, 11f, -27.5f, 11f) + reflectiveQuadTo(108f, 348f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + lineToRelative(104f, -104f) + quadToRelative(6f, -6f, 13f, -8.5f) + reflectiveQuadToRelative(15f, -2.5f) + quadToRelative(8f, 0f, 15f, 2.5f) + reflectiveQuadToRelative(13f, 8.5f) + lineToRelative(104f, 104f) + quadToRelative(11f, 11f, 11.5f, 27.5f) + reflectiveQuadTo(372f, 348f) + quadToRelative(-11f, 11f, -27.5f, 11.5f) + reflectiveQuadTo(316f, 349f) + lineToRelative(-36f, -35f) + verticalLineToRelative(332f) + lineToRelative(36f, -35f) + quadToRelative(11f, -11f, 27.5f, -11f) + reflectiveQuadToRelative(28.5f, 12f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineTo(268f, 772f) + quadToRelative(-6f, 6f, -13f, 8.5f) + reflectiveQuadToRelative(-15f, 2.5f) + quadToRelative(-8f, 0f, -15f, -2.5f) + reflectiveQuadToRelative(-13f, -8.5f) + lineTo(108f, 668f) + quadToRelative(-11f, -11f, -11.5f, -27.5f) + reflectiveQuadTo(108f, 612f) + quadToRelative(11f, -11f, 27.5f, -11.5f) + reflectiveQuadTo(164f, 611f) + lineToRelative(36f, 35f) + verticalLineToRelative(-332f) + close() + moveTo(520f, 760f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(480f, 720f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(520f, 680f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 720f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 760f) + lineTo(520f, 760f) + close() + moveTo(520f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(480f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(520f, 440f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 520f) + lineTo(520f, 520f) + close() + moveTo(520f, 280f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(480f, 240f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(520f, 200f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 240f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 280f) + lineTo(520f, 280f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatPaintVariant.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatPaintVariant.kt new file mode 100644 index 0000000..c99dd45 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatPaintVariant.kt @@ -0,0 +1,200 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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.Black)) { + 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(440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 160f) + verticalLineToRelative(400f) + 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(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 320f) + verticalLineToRelative(-120f) + horizontalLineToRelative(-40f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(520f, 280f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(480f, 240f) + verticalLineToRelative(-40f) + 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(0xFF1D1D1B))) { + moveTo(19.746f, 2.86f) + curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.713f, -0.287f) + horizontalLineToRelative(-11f) + 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) + 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.413f, -0.588f) + reflectiveCurveToRelative(0.587f, -0.862f, 0.587f, -1.412f) + verticalLineToRelative(-4f) + horizontalLineToRelative(3f) + curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.413f, -0.588f) + reflectiveCurveToRelative(0.587f, -0.862f, 0.587f, -1.412f) + verticalLineTo(3.573f) + curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.713f) + close() + moveTo(18.034f, 13.573f) + horizontalLineTo(6.034f) + verticalLineToRelative(-2f) + horizontalLineToRelative(12f) + verticalLineToRelative(2f) + close() + moveTo(18.034f, 9.573f) + horizontalLineTo(6.034f) + verticalLineToRelative(-3f) + curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.588f, -1.413f) + curveToRelative(0.392f, -0.392f, 0.862f, -0.587f, 1.412f, -0.587f) + horizontalLineToRelative(4f) + verticalLineToRelative(1f) + curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.288f, 0.712f) + curveToRelative(0.192f, 0.192f, 0.429f, 0.288f, 0.712f, 0.288f) + reflectiveCurveToRelative(0.521f, -0.096f, 0.713f, -0.288f) + curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.712f) + verticalLineToRelative(-1f) + horizontalLineToRelative(1f) + verticalLineToRelative(3f) + curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.288f, 0.712f) + curveToRelative(0.192f, 0.192f, 0.429f, 0.288f, 0.712f, 0.288f) + reflectiveCurveToRelative(0.521f, -0.096f, 0.713f, -0.288f) + curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.712f) + verticalLineToRelative(-3f) + horizontalLineToRelative(1f) + verticalLineToRelative(5f) + close() + } + path(fill = SolidColor(Color(0xFF1D1D1B))) { + moveTo(6.034f, 13.573f) + verticalLineToRelative(-2f) + verticalLineToRelative(2f) + close() + } + path( + fill = SolidColor(Color(0xFF1D1D1B)), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(6.034f, 9.573f) + horizontalLineToRelative(12f) + verticalLineToRelative(-5f) + horizontalLineToRelative(-1f) + 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) + horizontalLineToRelative(-1f) + verticalLineToRelative(1f) + 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(-1f) + 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(0xFF1D1D1B)), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(6.034f, 11.573f) + horizontalLineToRelative(12f) + verticalLineToRelative(2f) + horizontalLineToRelative(-12f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatParagraph.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatParagraph.kt new file mode 100644 index 0000000..07e33c2 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatParagraph.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FormatParagraph: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FormatParagraph", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(371.5f, 788.5f) + quadTo(360f, 777f, 360f, 760f) + verticalLineToRelative(-200f) + quadToRelative(-83f, 0f, -141.5f, -58.5f) + reflectiveQuadTo(160f, 360f) + quadToRelative(0f, -83f, 58.5f, -141.5f) + reflectiveQuadTo(360f, 160f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(720f, 200f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(680f, 240f) + horizontalLineToRelative(-40f) + verticalLineToRelative(520f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(600f, 800f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(560f, 760f) + verticalLineToRelative(-520f) + lineTo(440f, 240f) + verticalLineToRelative(520f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(400f, 800f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatShapes.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatShapes.kt new file mode 100644 index 0000000..45200ab --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatShapes.kt @@ -0,0 +1,164 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FormatShapes: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FormatShapes", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(327f, 599f) + lineToRelative(114f, -300f) + quadToRelative(3f, -9f, 11f, -14f) + reflectiveQuadToRelative(17f, -5f) + horizontalLineToRelative(22f) + quadToRelative(9f, 0f, 17f, 5f) + reflectiveQuadToRelative(11f, 14f) + lineToRelative(114f, 303f) + quadToRelative(5f, 14f, -3f, 26f) + reflectiveQuadToRelative(-23f, 12f) + quadToRelative(-9f, 0f, -17f, -5.5f) + reflectiveQuadTo(579f, 620f) + lineToRelative(-25f, -72f) + lineTo(408f, 548f) + lineToRelative(-25f, 73f) + quadToRelative(-3f, 9f, -11f, 14f) + reflectiveQuadToRelative(-17f, 5f) + quadToRelative(-16f, 0f, -24.5f, -13f) + reflectiveQuadToRelative(-3.5f, -28f) + close() + moveTo(426f, 496f) + horizontalLineToRelative(108f) + lineToRelative(-52f, -150f) + horizontalLineToRelative(-4f) + lineToRelative(-52f, 150f) + close() + moveTo(40f, 880f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(80f, 680f) + horizontalLineToRelative(40f) + verticalLineToRelative(-400f) + lineTo(80f, 280f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(40f, 240f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(80f, 40f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(280f, 80f) + verticalLineToRelative(40f) + horizontalLineToRelative(400f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(720f, 40f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(920f, 80f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(880f, 280f) + horizontalLineToRelative(-40f) + verticalLineToRelative(400f) + horizontalLineToRelative(40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(920f, 720f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(880f, 920f) + lineTo(720f, 920f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(680f, 880f) + verticalLineToRelative(-40f) + lineTo(280f, 840f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 920f) + lineTo(80f, 920f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(40f, 880f) + close() + moveTo(280f, 760f) + horizontalLineToRelative(400f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(720f, 680f) + horizontalLineToRelative(40f) + verticalLineToRelative(-400f) + horizontalLineToRelative(-40f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(680f, 240f) + verticalLineToRelative(-40f) + lineTo(280f, 200f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 280f) + horizontalLineToRelative(-40f) + verticalLineToRelative(400f) + horizontalLineToRelative(40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(280f, 720f) + verticalLineToRelative(40f) + close() + moveTo(120f, 200f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + close() + moveTo(760f, 200f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + close() + moveTo(760f, 840f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + close() + moveTo(120f, 840f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + close() + moveTo(200f, 200f) + close() + moveTo(760f, 200f) + close() + moveTo(760f, 760f) + close() + moveTo(200f, 760f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatStrikethrough.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatStrikethrough.kt new file mode 100644 index 0000000..2a09ccf --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatStrikethrough.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FormatStrikethrough: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FormatStrikethrough", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(120f, 560f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(80f, 520f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 480f) + horizontalLineToRelative(720f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 520f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 560f) + lineTo(120f, 560f) + close() + moveTo(420f, 400f) + verticalLineToRelative(-120f) + lineTo(260f, 280f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(200f, 220f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(260f, 160f) + horizontalLineToRelative(440f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(760f, 220f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(700f, 280f) + lineTo(540f, 280f) + verticalLineToRelative(120f) + lineTo(420f, 400f) + close() + moveTo(420f, 640f) + horizontalLineToRelative(120f) + verticalLineToRelative(100f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(480f, 800f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(420f, 740f) + verticalLineToRelative(-100f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatUnderlined.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatUnderlined.kt new file mode 100644 index 0000000..9cc47ef --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatUnderlined.kt @@ -0,0 +1,72 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FormatUnderlined: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FormatUnderlined", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(240f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(200f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(240f, 760f) + horizontalLineToRelative(480f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(760f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(720f, 840f) + lineTo(240f, 840f) + close() + moveTo(480f, 680f) + quadToRelative(-101f, 0f, -157f, -63f) + reflectiveQuadToRelative(-56f, -167f) + verticalLineToRelative(-279f) + quadToRelative(0f, -21f, 15.5f, -36f) + reflectiveQuadToRelative(36.5f, -15f) + quadToRelative(21f, 0f, 36f, 15f) + reflectiveQuadToRelative(15f, 36f) + verticalLineToRelative(285f) + quadToRelative(0f, 56f, 28f, 91f) + reflectiveQuadToRelative(82f, 35f) + quadToRelative(54f, 0f, 82f, -35f) + reflectiveQuadToRelative(28f, -91f) + verticalLineToRelative(-285f) + quadToRelative(0f, -21f, 15.5f, -36f) + reflectiveQuadToRelative(36.5f, -15f) + quadToRelative(21f, 0f, 36f, 15f) + reflectiveQuadToRelative(15f, 36f) + verticalLineToRelative(279f) + quadToRelative(0f, 104f, -56f, 167f) + reflectiveQuadToRelative(-157f, 63f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Forum.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Forum.kt new file mode 100644 index 0000000..4e0c889 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Forum.kt @@ -0,0 +1,114 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FreeArrow.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FreeArrow.kt new file mode 100644 index 0000000..251692a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FreeArrow.kt @@ -0,0 +1,77 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FreeDoubleArrow.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FreeDoubleArrow.kt new file mode 100644 index 0000000..5b20ad8 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FreeDoubleArrow.kt @@ -0,0 +1,91 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FreeDraw.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FreeDraw.kt new file mode 100644 index 0000000..e5cb057 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FreeDraw.kt @@ -0,0 +1,97 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FrontHand.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FrontHand.kt new file mode 100644 index 0000000..6324ba6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FrontHand.kt @@ -0,0 +1,179 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FrontHand: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FrontHand", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(259f, 821f) + quadToRelative(-99f, -99f, -99f, -241f) + verticalLineToRelative(-380f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(240f, 200f) + verticalLineToRelative(240f) + horizontalLineToRelative(80f) + verticalLineToRelative(-320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(360f, 80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(400f, 120f) + verticalLineToRelative(320f) + horizontalLineToRelative(80f) + verticalLineToRelative(-360f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(520f, 40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(560f, 80f) + verticalLineToRelative(360f) + horizontalLineToRelative(80f) + verticalLineToRelative(-280f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(680f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(720f, 160f) + verticalLineToRelative(230f) + quadToRelative(-29f, 21f, -44.5f, 53f) + reflectiveQuadTo(660f, 510f) + verticalLineToRelative(50f) + horizontalLineToRelative(-50f) + quadToRelative(-63f, 0f, -106.5f, 43.5f) + reflectiveQuadTo(460f, 710f) + verticalLineToRelative(50f) + quadToRelative(0f, 13f, 8.5f, 21.5f) + reflectiveQuadTo(490f, 790f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(520f, 760f) + verticalLineToRelative(-50f) + quadToRelative(0f, -38f, 26f, -64f) + reflectiveQuadToRelative(64f, -26f) + horizontalLineToRelative(70f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(720f, 580f) + verticalLineToRelative(-70f) + quadToRelative(0f, -38f, 26f, -64f) + reflectiveQuadToRelative(64f, -26f) + quadToRelative(13f, 0f, 21.5f, 8.5f) + reflectiveQuadTo(840f, 450f) + verticalLineToRelative(130f) + quadToRelative(0f, 142f, -99f, 241f) + reflectiveQuadTo(500f, 920f) + quadToRelative(-142f, 0f, -241f, -99f) + close() + } + }.build() +} + +val Icons.Outlined.FrontHand: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.FrontHand", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(548.5f, 51.5f) + quadTo(560f, 63f, 560f, 80f) + verticalLineToRelative(360f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(520f, 480f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(480f, 440f) + verticalLineToRelative(-360f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(520f, 40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + close() + moveTo(388.5f, 91.5f) + quadTo(400f, 103f, 400f, 120f) + verticalLineToRelative(320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(360f, 480f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(320f, 440f) + verticalLineToRelative(-320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(360f, 80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + close() + moveTo(500f, 920f) + quadToRelative(-142f, 0f, -241f, -99f) + reflectiveQuadToRelative(-99f, -241f) + verticalLineToRelative(-380f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(240f, 200f) + verticalLineToRelative(380f) + quadToRelative(0f, 109f, 75.5f, 184.5f) + reflectiveQuadTo(500f, 840f) + quadToRelative(109f, 0f, 184.5f, -75.5f) + reflectiveQuadTo(760f, 580f) + verticalLineToRelative(-140f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(720f, 480f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(680f, 640f) + horizontalLineToRelative(-80f) + quadToRelative(-33f, 0f, -56.5f, 23.5f) + reflectiveQuadTo(520f, 720f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 800f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 760f) + verticalLineToRelative(-40f) + quadToRelative(0f, -66f, 47f, -113f) + reflectiveQuadToRelative(113f, -47f) + horizontalLineToRelative(40f) + verticalLineToRelative(-400f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(680f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(720f, 160f) + verticalLineToRelative(207f) + quadToRelative(10f, -3f, 19.5f, -5f) + reflectiveQuadToRelative(20.5f, -2f) + horizontalLineToRelative(40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 400f) + verticalLineToRelative(180f) + quadToRelative(0f, 142f, -99f, 241f) + reflectiveQuadTo(500f, 920f) + close() + moveTo(540f, 600f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Fullscreen.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Fullscreen.kt new file mode 100644 index 0000000..78370d2 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Fullscreen.kt @@ -0,0 +1,102 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Fullscreen: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Fullscreen", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(200f, 760f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(320f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(280f, 840f) + lineTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 680f) + verticalLineToRelative(80f) + close() + moveTo(760f, 760f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 680f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + lineTo(680f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(640f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(680f, 760f) + horizontalLineToRelative(80f) + close() + moveTo(200f, 200f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 320f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 280f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(320f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(280f, 200f) + horizontalLineToRelative(-80f) + close() + moveTo(760f, 200f) + horizontalLineToRelative(-80f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(640f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(680f, 120f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 320f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 280f) + verticalLineToRelative(-80f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FullscreenExit.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FullscreenExit.kt new file mode 100644 index 0000000..8b5acf5 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FullscreenExit.kt @@ -0,0 +1,102 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FullscreenExit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FullscreenExit", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(240f, 720f) + horizontalLineToRelative(-80f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 680f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 640f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(320f, 680f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(280f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(240f, 800f) + verticalLineToRelative(-80f) + close() + moveTo(720f, 720f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(680f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(640f, 800f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(680f, 640f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 680f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 720f) + horizontalLineToRelative(-80f) + close() + moveTo(240f, 240f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(280f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(320f, 160f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(280f, 320f) + lineTo(160f, 320f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 280f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 240f) + horizontalLineToRelative(80f) + close() + moveTo(720f, 240f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 280f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 320f) + lineTo(680f, 320f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(640f, 280f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(680f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(720f, 160f) + verticalLineToRelative(80f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Functions.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Functions.kt new file mode 100644 index 0000000..1abdfa0 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Functions.kt @@ -0,0 +1,68 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Functions: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Functions", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(500f, 480f) + lineTo(253f, 252f) + quadToRelative(-6f, -6f, -9.5f, -13.5f) + reflectiveQuadTo(240f, 223f) + verticalLineToRelative(-23f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(280f, 160f) + horizontalLineToRelative(380f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(720f, 220f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(660f, 280f) + lineTo(431f, 280f) + lineToRelative(184f, 171f) + quadToRelative(13f, 12f, 13f, 29f) + reflectiveQuadToRelative(-13f, 29f) + lineTo(431f, 680f) + horizontalLineToRelative(229f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(720f, 740f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(660f, 800f) + lineTo(269f, 800f) + quadToRelative(-12f, 0f, -20.5f, -8.5f) + reflectiveQuadTo(240f, 771f) + verticalLineToRelative(-38f) + quadToRelative(0f, -6f, 2f, -11.5f) + reflectiveQuadToRelative(7f, -10.5f) + lineToRelative(251f, -231f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Gamepad.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Gamepad.kt new file mode 100644 index 0000000..0f01743 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Gamepad.kt @@ -0,0 +1,150 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Gamepad: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Gamepad", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 306f) + close() + moveTo(654f, 480f) + close() + moveTo(306f, 480f) + close() + moveTo(480f, 654f) + close() + moveTo(452f, 392f) + lineTo(372f, 312f) + quadToRelative(-6f, -6f, -9f, -13.5f) + reflectiveQuadToRelative(-3f, -15.5f) + verticalLineToRelative(-163f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(400f, 80f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(600f, 120f) + verticalLineToRelative(163f) + quadToRelative(0f, 8f, -3f, 15.5f) + reflectiveQuadToRelative(-9f, 13.5f) + lineToRelative(-80f, 80f) + quadToRelative(-6f, 6f, -13f, 8.5f) + reflectiveQuadToRelative(-15f, 2.5f) + quadToRelative(-8f, 0f, -15f, -2.5f) + reflectiveQuadToRelative(-13f, -8.5f) + close() + moveTo(568f, 508f) + quadToRelative(-6f, -6f, -8.5f, -13f) + reflectiveQuadToRelative(-2.5f, -15f) + quadToRelative(0f, -8f, 2.5f, -15f) + reflectiveQuadToRelative(8.5f, -13f) + lineToRelative(80f, -80f) + quadToRelative(6f, -6f, 13.5f, -9f) + reflectiveQuadToRelative(15.5f, -3f) + horizontalLineToRelative(163f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 400f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 600f) + lineTo(677f, 600f) + quadToRelative(-8f, 0f, -15.5f, -3f) + reflectiveQuadToRelative(-13.5f, -9f) + lineToRelative(-80f, -80f) + close() + moveTo(80f, 560f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 360f) + horizontalLineToRelative(163f) + quadToRelative(8f, 0f, 15.5f, 3f) + reflectiveQuadToRelative(13.5f, 9f) + lineToRelative(80f, 80f) + quadToRelative(6f, 6f, 8.5f, 13f) + reflectiveQuadToRelative(2.5f, 15f) + quadToRelative(0f, 8f, -2.5f, 15f) + reflectiveQuadToRelative(-8.5f, 13f) + lineToRelative(-80f, 80f) + quadToRelative(-6f, 6f, -13.5f, 9f) + reflectiveQuadToRelative(-15.5f, 3f) + lineTo(120f, 600f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(80f, 560f) + close() + moveTo(360f, 840f) + verticalLineToRelative(-163f) + quadToRelative(0f, -8f, 3f, -15.5f) + reflectiveQuadToRelative(9f, -13.5f) + lineToRelative(80f, -80f) + quadToRelative(6f, -6f, 13f, -8.5f) + reflectiveQuadToRelative(15f, -2.5f) + quadToRelative(8f, 0f, 15f, 2.5f) + reflectiveQuadToRelative(13f, 8.5f) + lineToRelative(80f, 80f) + quadToRelative(6f, 6f, 9f, 13.5f) + reflectiveQuadToRelative(3f, 15.5f) + verticalLineToRelative(163f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(560f, 880f) + lineTo(400f, 880f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(360f, 840f) + close() + moveTo(480f, 306f) + lineTo(520f, 266f) + verticalLineToRelative(-106f) + horizontalLineToRelative(-80f) + verticalLineToRelative(106f) + lineToRelative(40f, 40f) + close() + moveTo(160f, 520f) + horizontalLineToRelative(106f) + lineToRelative(40f, -40f) + lineToRelative(-40f, -40f) + lineTo(160f, 440f) + verticalLineToRelative(80f) + close() + moveTo(440f, 800f) + horizontalLineToRelative(80f) + verticalLineToRelative(-106f) + lineToRelative(-40f, -40f) + lineToRelative(-40f, 40f) + verticalLineToRelative(106f) + close() + moveTo(694f, 520f) + horizontalLineToRelative(106f) + verticalLineToRelative(-80f) + lineTo(694f, 440f) + lineToRelative(-40f, 40f) + lineToRelative(40f, 40f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Gif.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Gif.kt new file mode 100644 index 0000000..b682f62 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Gif.kt @@ -0,0 +1,97 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Gif: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Gif", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(468.5f, 591.5f) + quadTo(460f, 583f, 460f, 570f) + verticalLineToRelative(-180f) + quadToRelative(0f, -13f, 8.5f, -21.5f) + reflectiveQuadTo(490f, 360f) + quadToRelative(13f, 0f, 21.5f, 8.5f) + reflectiveQuadTo(520f, 390f) + verticalLineToRelative(180f) + quadToRelative(0f, 13f, -8.5f, 21.5f) + reflectiveQuadTo(490f, 600f) + quadToRelative(-13f, 0f, -21.5f, -8.5f) + close() + moveTo(240f, 600f) + quadToRelative(-18f, 0f, -29f, -12.5f) + reflectiveQuadTo(200f, 560f) + verticalLineToRelative(-160f) + quadToRelative(0f, -15f, 11f, -27.5f) + reflectiveQuadToRelative(29f, -12.5f) + horizontalLineToRelative(130f) + quadToRelative(13f, 0f, 21.5f, 8.5f) + reflectiveQuadTo(400f, 390f) + quadToRelative(0f, 13f, -8.5f, 21.5f) + reflectiveQuadTo(370f, 420f) + lineTo(260f, 420f) + verticalLineToRelative(120f) + horizontalLineToRelative(80f) + verticalLineToRelative(-30f) + quadToRelative(0f, -13f, 8.5f, -21.5f) + reflectiveQuadTo(370f, 480f) + quadToRelative(13f, 0f, 21.5f, 8.5f) + reflectiveQuadTo(400f, 510f) + verticalLineToRelative(50f) + quadToRelative(0f, 15f, -11f, 27.5f) + reflectiveQuadTo(360f, 600f) + lineTo(240f, 600f) + close() + moveTo(588.5f, 591.5f) + quadTo(580f, 583f, 580f, 570f) + verticalLineToRelative(-180f) + quadToRelative(0f, -13f, 8.5f, -21.5f) + reflectiveQuadTo(610f, 360f) + horizontalLineToRelative(120f) + quadToRelative(13f, 0f, 21.5f, 8.5f) + reflectiveQuadTo(760f, 390f) + quadToRelative(0f, 13f, -8.5f, 21.5f) + reflectiveQuadTo(730f, 420f) + horizontalLineToRelative(-90f) + verticalLineToRelative(40f) + horizontalLineToRelative(50f) + quadToRelative(13f, 0f, 21.5f, 8.5f) + reflectiveQuadTo(720f, 490f) + quadToRelative(0f, 13f, -8.5f, 21.5f) + reflectiveQuadTo(690f, 520f) + horizontalLineToRelative(-50f) + verticalLineToRelative(50f) + quadToRelative(0f, 13f, -8.5f, 21.5f) + reflectiveQuadTo(610f, 600f) + quadToRelative(-13f, 0f, -21.5f, -8.5f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/GifBox.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/GifBox.kt new file mode 100644 index 0000000..f1dbcdc --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/GifBox.kt @@ -0,0 +1,314 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.GifBox: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.GifBox", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(12.288f, 14.787f) + curveToRelative(0.142f, -0.142f, 0.213f, -0.321f, 0.213f, -0.538f) + verticalLineToRelative(-4.5f) + 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.5f) + 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) + close() + moveTo(7f, 15f) + horizontalLineToRelative(2f) + curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) + reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) + verticalLineToRelative(-1.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(0.75f) + horizontalLineToRelative(-1f) + verticalLineToRelative(-3f) + horizontalLineToRelative(1.75f) + curveToRelative(0.217f, 0f, 0.396f, -0.071f, 0.538f, -0.213f) + reflectiveCurveToRelative(0.213f, -0.321f, 0.213f, -0.538f) + reflectiveCurveToRelative(-0.071f, -0.396f, -0.213f, -0.538f) + reflectiveCurveToRelative(-0.321f, -0.213f, -0.538f, -0.213f) + horizontalLineToRelative(-2.25f) + 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) + curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.712f, 0.287f) + close() + moveTo(15.288f, 14.787f) + curveToRelative(0.142f, -0.142f, 0.213f, -0.321f, 0.213f, -0.538f) + verticalLineToRelative(-1.25f) + horizontalLineToRelative(1.25f) + curveToRelative(0.217f, 0f, 0.396f, -0.071f, 0.538f, -0.213f) + reflectiveCurveToRelative(0.213f, -0.321f, 0.213f, -0.538f) + reflectiveCurveToRelative(-0.071f, -0.396f, -0.213f, -0.538f) + reflectiveCurveToRelative(-0.321f, -0.213f, -0.538f, -0.213f) + horizontalLineToRelative(-1.25f) + verticalLineToRelative(-1f) + horizontalLineToRelative(2.25f) + curveToRelative(0.217f, 0f, 0.396f, -0.071f, 0.538f, -0.213f) + reflectiveCurveToRelative(0.213f, -0.321f, 0.213f, -0.538f) + reflectiveCurveToRelative(-0.071f, -0.396f, -0.213f, -0.538f) + reflectiveCurveToRelative(-0.321f, -0.213f, -0.538f, -0.213f) + horizontalLineToRelative(-3f) + curveToRelative(-0.217f, 0f, -0.396f, 0.071f, -0.538f, 0.213f) + reflectiveCurveToRelative(-0.213f, 0.321f, -0.213f, 0.538f) + verticalLineToRelative(4.5f) + 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) + 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(14f) + 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) + horizontalLineTo(5f) + close() + moveTo(5f, 19f) + horizontalLineToRelative(14f) + verticalLineTo(5f) + horizontalLineTo(5f) + verticalLineToRelative(14f) + close() + moveTo(5f, 19f) + verticalLineTo(5f) + verticalLineToRelative(14f) + close() + } + }.build() +} + +val Icons.TwoTone.GifBox: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.GifBox", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(12.288f, 14.787f) + curveToRelative(0.142f, -0.142f, 0.213f, -0.321f, 0.213f, -0.538f) + verticalLineToRelative(-4.5f) + 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.5f) + 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) + close() + moveTo(7f, 15f) + horizontalLineToRelative(2f) + curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) + reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) + verticalLineToRelative(-1.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(0.75f) + horizontalLineToRelative(-1f) + verticalLineToRelative(-3f) + horizontalLineToRelative(1.75f) + curveToRelative(0.217f, 0f, 0.396f, -0.071f, 0.538f, -0.213f) + reflectiveCurveToRelative(0.213f, -0.321f, 0.213f, -0.538f) + reflectiveCurveToRelative(-0.071f, -0.396f, -0.213f, -0.538f) + reflectiveCurveToRelative(-0.321f, -0.213f, -0.538f, -0.213f) + horizontalLineToRelative(-2.25f) + 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) + curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.712f, 0.287f) + close() + moveTo(15.288f, 14.787f) + curveToRelative(0.142f, -0.142f, 0.213f, -0.321f, 0.213f, -0.538f) + verticalLineToRelative(-1.25f) + horizontalLineToRelative(1.25f) + curveToRelative(0.217f, 0f, 0.396f, -0.071f, 0.538f, -0.213f) + reflectiveCurveToRelative(0.213f, -0.321f, 0.213f, -0.538f) + reflectiveCurveToRelative(-0.071f, -0.396f, -0.213f, -0.538f) + reflectiveCurveToRelative(-0.321f, -0.213f, -0.538f, -0.213f) + horizontalLineToRelative(-1.25f) + verticalLineToRelative(-1f) + horizontalLineToRelative(2.25f) + curveToRelative(0.217f, 0f, 0.396f, -0.071f, 0.538f, -0.213f) + reflectiveCurveToRelative(0.213f, -0.321f, 0.213f, -0.538f) + reflectiveCurveToRelative(-0.071f, -0.396f, -0.213f, -0.538f) + reflectiveCurveToRelative(-0.321f, -0.213f, -0.538f, -0.213f) + horizontalLineToRelative(-3f) + curveToRelative(-0.217f, 0f, -0.396f, 0.071f, -0.538f, 0.213f) + reflectiveCurveToRelative(-0.213f, 0.321f, -0.213f, 0.538f) + verticalLineToRelative(4.5f) + 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) + 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(14f) + 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) + horizontalLineTo(5f) + close() + moveTo(5f, 19f) + horizontalLineToRelative(14f) + verticalLineTo(5f) + horizontalLineTo(5f) + verticalLineToRelative(14f) + close() + moveTo(5f, 19f) + verticalLineTo(5f) + verticalLineToRelative(14f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(5f, 5f) + horizontalLineToRelative(14f) + verticalLineToRelative(14f) + horizontalLineToRelative(-14f) + close() + } + }.build() +} + +val Icons.Rounded.GifBox: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.GifBox", + 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(491.5f, 591.5f) + quadTo(500f, 583f, 500f, 570f) + verticalLineToRelative(-180f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(470f, 360f) + quadToRelative(-13f, 0f, -21.5f, 8.5f) + reflectiveQuadTo(440f, 390f) + verticalLineToRelative(180f) + quadToRelative(0f, 13f, 8.5f, 21.5f) + reflectiveQuadTo(470f, 600f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + close() + moveTo(280f, 600f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(400f, 560f) + verticalLineToRelative(-50f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(370f, 480f) + quadToRelative(-13f, 0f, -21.5f, 8.5f) + reflectiveQuadTo(340f, 510f) + verticalLineToRelative(30f) + horizontalLineToRelative(-40f) + verticalLineToRelative(-120f) + horizontalLineToRelative(70f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(400f, 390f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(370f, 360f) + horizontalLineToRelative(-90f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(240f, 400f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(280f, 600f) + close() + moveTo(620f, 570f) + verticalLineToRelative(-50f) + horizontalLineToRelative(50f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(700f, 490f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(670f, 460f) + horizontalLineToRelative(-50f) + verticalLineToRelative(-40f) + horizontalLineToRelative(90f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(740f, 390f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(710f, 360f) + lineTo(590f, 360f) + quadToRelative(-13f, 0f, -21.5f, 8.5f) + reflectiveQuadTo(560f, 390f) + verticalLineToRelative(180f) + quadToRelative(0f, 13f, 8.5f, 21.5f) + reflectiveQuadTo(590f, 600f) + quadToRelative(13f, 0f, 21.5f, -8.5f) + reflectiveQuadTo(620f, 570f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Github.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Github.kt new file mode 100644 index 0000000..82ed19e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Github.kt @@ -0,0 +1,85 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Glyphs.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Glyphs.kt new file mode 100644 index 0000000..f08f45d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Glyphs.kt @@ -0,0 +1,148 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/GooglePlay.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/GooglePlay.kt new file mode 100644 index 0000000..e1efda5 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/GooglePlay.kt @@ -0,0 +1,68 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Gradient.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Gradient.kt new file mode 100644 index 0000000..4cf4d5e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Gradient.kt @@ -0,0 +1,497 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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() +} + +val Icons.Outlined.Gradient: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Gradient", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(440f, 520f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(360f, 600f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(520f, 600f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(600f, 520f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(280f, 520f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + 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(560f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 840f) + lineTo(200f, 840f) + close() + moveTo(280f, 760f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + close() + moveTo(440f, 760f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + close() + moveTo(760f, 760f) + verticalLineToRelative(-80f) + verticalLineToRelative(80f) + close() + moveTo(200f, 680f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(-320f) + lineTo(200f, 200f) + verticalLineToRelative(320f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + close() + moveTo(200f, 760f) + verticalLineToRelative(-560f) + verticalLineToRelative(560f) + close() + moveTo(760f, 520f) + verticalLineToRelative(80f) + verticalLineToRelative(-80f) + close() + moveTo(600f, 680f) + verticalLineToRelative(80f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + close() + } + }.build() +} + +val Icons.TwoTone.Gradient: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.Gradient", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(11f, 13f) + verticalLineToRelative(-2f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + moveTo(9f, 15f) + verticalLineToRelative(-2f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + moveTo(13f, 15f) + verticalLineToRelative(-2f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + moveTo(15f, 13f) + verticalLineToRelative(-2f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + moveTo(7f, 13f) + verticalLineToRelative(-2f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + 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(14f) + 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) + horizontalLineTo(5f) + close() + moveTo(7f, 19f) + horizontalLineToRelative(2f) + verticalLineToRelative(-2f) + horizontalLineToRelative(-2f) + verticalLineToRelative(2f) + close() + moveTo(11f, 19f) + horizontalLineToRelative(2f) + verticalLineToRelative(-2f) + horizontalLineToRelative(-2f) + verticalLineToRelative(2f) + close() + moveTo(19f, 19f) + verticalLineToRelative(-2f) + verticalLineToRelative(2f) + close() + moveTo(5f, 17f) + horizontalLineToRelative(2f) + verticalLineToRelative(-2f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(2f) + verticalLineToRelative(-2f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(2f) + verticalLineToRelative(-2f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(2f) + verticalLineToRelative(-2f) + horizontalLineToRelative(-2f) + verticalLineToRelative(-2f) + horizontalLineToRelative(2f) + verticalLineTo(5f) + horizontalLineTo(5f) + verticalLineToRelative(8f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + verticalLineToRelative(2f) + close() + moveTo(5f, 19f) + verticalLineTo(5f) + verticalLineToRelative(14f) + close() + moveTo(19f, 13f) + verticalLineToRelative(2f) + verticalLineToRelative(-2f) + close() + moveTo(15f, 17f) + verticalLineToRelative(2f) + horizontalLineToRelative(2f) + verticalLineToRelative(-2f) + horizontalLineToRelative(-2f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(7f, 17f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(11f, 17f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(15f, 17f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(9f, 11f) + lineToRelative(0f, 2f) + lineToRelative(2f, 0f) + lineToRelative(0f, -2f) + lineToRelative(2f, 0f) + lineToRelative(0f, 2f) + lineToRelative(2f, 0f) + lineToRelative(0f, -2f) + lineToRelative(2f, 0f) + lineToRelative(0f, 2f) + lineToRelative(2f, 0f) + lineToRelative(0f, -8f) + lineToRelative(-14f, 0f) + lineToRelative(0f, 8f) + lineToRelative(2f, 0f) + lineToRelative(0f, -2f) + lineToRelative(2f, 0f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(5f, 15f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(9f, 15f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(13f, 15f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(17f, 15f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(11f, 13f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(15f, 13f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(7f, 13f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/GraphicEq.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/GraphicEq.kt new file mode 100644 index 0000000..2907ac2 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/GraphicEq.kt @@ -0,0 +1,68 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.GraphicEq: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.GraphicEq", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(280f, 720f) + verticalLineToRelative(-480f) + horizontalLineToRelative(80f) + verticalLineToRelative(480f) + horizontalLineToRelative(-80f) + close() + moveTo(440f, 880f) + verticalLineToRelative(-800f) + horizontalLineToRelative(80f) + verticalLineToRelative(800f) + horizontalLineToRelative(-80f) + close() + moveTo(120f, 560f) + verticalLineToRelative(-160f) + horizontalLineToRelative(80f) + verticalLineToRelative(160f) + horizontalLineToRelative(-80f) + close() + moveTo(600f, 720f) + verticalLineToRelative(-480f) + horizontalLineToRelative(80f) + verticalLineToRelative(480f) + horizontalLineToRelative(-80f) + close() + moveTo(760f, 560f) + verticalLineToRelative(-160f) + horizontalLineToRelative(80f) + verticalLineToRelative(160f) + horizontalLineToRelative(-80f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/GridOn.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/GridOn.kt new file mode 100644 index 0000000..841b183 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/GridOn.kt @@ -0,0 +1,107 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + + +val Icons.Outlined.GridOn: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.GridOn", + 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(133f) + verticalLineToRelative(-133f) + lineTo(200f, 627f) + verticalLineToRelative(133f) + close() + moveTo(413f, 760f) + horizontalLineToRelative(134f) + verticalLineToRelative(-133f) + lineTo(413f, 627f) + verticalLineToRelative(133f) + close() + moveTo(627f, 760f) + horizontalLineToRelative(133f) + verticalLineToRelative(-133f) + lineTo(627f, 627f) + verticalLineToRelative(133f) + close() + moveTo(200f, 547f) + horizontalLineToRelative(133f) + verticalLineToRelative(-134f) + lineTo(200f, 413f) + verticalLineToRelative(134f) + close() + moveTo(413f, 547f) + horizontalLineToRelative(134f) + verticalLineToRelative(-134f) + lineTo(413f, 413f) + verticalLineToRelative(134f) + close() + moveTo(627f, 547f) + horizontalLineToRelative(133f) + verticalLineToRelative(-134f) + lineTo(627f, 413f) + verticalLineToRelative(134f) + close() + moveTo(200f, 333f) + horizontalLineToRelative(133f) + verticalLineToRelative(-133f) + lineTo(200f, 200f) + verticalLineToRelative(133f) + close() + moveTo(413f, 333f) + horizontalLineToRelative(134f) + verticalLineToRelative(-133f) + lineTo(413f, 200f) + verticalLineToRelative(133f) + close() + moveTo(627f, 333f) + horizontalLineToRelative(133f) + verticalLineToRelative(-133f) + lineTo(627f, 200f) + verticalLineToRelative(133f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HandshakeAlt.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HandshakeAlt.kt new file mode 100644 index 0000000..24de37b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HandshakeAlt.kt @@ -0,0 +1,96 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HardDrive.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HardDrive.kt new file mode 100644 index 0000000..992839c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HardDrive.kt @@ -0,0 +1,121 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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() +} + +val Icons.Outlined.HardDrive: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.HardDrive", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 680f) + horizontalLineToRelative(640f) + verticalLineToRelative(-240f) + lineTo(160f, 440f) + verticalLineToRelative(240f) + close() + moveTo(722.5f, 602.5f) + quadTo(740f, 585f, 740f, 560f) + reflectiveQuadToRelative(-17.5f, -42.5f) + quadTo(705f, 500f, 680f, 500f) + reflectiveQuadToRelative(-42.5f, 17.5f) + quadTo(620f, 535f, 620f, 560f) + reflectiveQuadToRelative(17.5f, 42.5f) + quadTo(655f, 620f, 680f, 620f) + reflectiveQuadToRelative(42.5f, -17.5f) + close() + moveTo(880f, 360f) + lineTo(767f, 360f) + lineToRelative(-80f, -80f) + lineTo(273f, 280f) + lineToRelative(-80f, 80f) + lineTo(80f, 360f) + lineToRelative(137f, -137f) + quadToRelative(11f, -11f, 25.5f, -17f) + reflectiveQuadToRelative(30.5f, -6f) + horizontalLineToRelative(414f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(137f, 137f) + close() + moveTo(160f, 760f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(80f, 680f) + verticalLineToRelative(-320f) + horizontalLineToRelative(800f) + verticalLineToRelative(320f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 760f) + lineTo(160f, 760f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HashTag.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HashTag.kt new file mode 100644 index 0000000..45175ea --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HashTag.kt @@ -0,0 +1,98 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.HashTag: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.HashTag", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(360f, 640f) + lineToRelative(-33f, 131f) + quadToRelative(-3f, 13f, -13f, 21f) + reflectiveQuadToRelative(-24f, 8f) + quadToRelative(-19f, 0f, -31f, -15f) + reflectiveQuadToRelative(-7f, -33f) + lineToRelative(28f, -112f) + lineTo(171f, 640f) + quadToRelative(-20f, 0f, -32f, -15.5f) + reflectiveQuadToRelative(-7f, -34.5f) + quadToRelative(3f, -14f, 14f, -22f) + reflectiveQuadToRelative(25f, -8f) + horizontalLineToRelative(129f) + lineToRelative(40f, -160f) + lineTo(231f, 400f) + quadToRelative(-20f, 0f, -32f, -15.5f) + reflectiveQuadToRelative(-7f, -34.5f) + quadToRelative(3f, -14f, 14f, -22f) + reflectiveQuadToRelative(25f, -8f) + horizontalLineToRelative(129f) + lineToRelative(33f, -131f) + quadToRelative(3f, -13f, 13f, -21f) + reflectiveQuadToRelative(24f, -8f) + quadToRelative(19f, 0f, 31f, 15f) + reflectiveQuadToRelative(7f, 33f) + lineToRelative(-28f, 112f) + horizontalLineToRelative(160f) + lineToRelative(33f, -131f) + quadToRelative(3f, -13f, 13f, -21f) + reflectiveQuadToRelative(24f, -8f) + quadToRelative(19f, 0f, 31f, 15f) + reflectiveQuadToRelative(7f, 33f) + lineToRelative(-28f, 112f) + horizontalLineToRelative(109f) + quadToRelative(20f, 0f, 32f, 15.5f) + reflectiveQuadToRelative(7f, 34.5f) + quadToRelative(-3f, 14f, -14f, 22f) + reflectiveQuadToRelative(-25f, 8f) + lineTo(660f, 400f) + lineToRelative(-40f, 160f) + horizontalLineToRelative(109f) + quadToRelative(20f, 0f, 32f, 15.5f) + reflectiveQuadToRelative(7f, 34.5f) + quadToRelative(-3f, 14f, -14f, 22f) + reflectiveQuadToRelative(-25f, 8f) + lineTo(600f, 640f) + lineToRelative(-33f, 131f) + quadToRelative(-3f, 13f, -13f, 21f) + reflectiveQuadToRelative(-24f, 8f) + quadToRelative(-19f, 0f, -31f, -15f) + reflectiveQuadToRelative(-7f, -33f) + lineToRelative(28f, -112f) + lineTo(360f, 640f) + close() + moveTo(380f, 560f) + horizontalLineToRelative(160f) + lineToRelative(40f, -160f) + lineTo(420f, 400f) + lineToRelative(-40f, 160f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Healing.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Healing.kt new file mode 100644 index 0000000..d1680bd --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Healing.kt @@ -0,0 +1,211 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Healing: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Healing", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 706f) + lineTo(330f, 856f) + quadToRelative(-23f, 23f, -56f, 23f) + reflectiveQuadToRelative(-56f, -23f) + lineTo(104f, 742f) + quadToRelative(-23f, -23f, -23f, -56f) + reflectiveQuadToRelative(23f, -56f) + lineToRelative(150f, -150f) + lineToRelative(-150f, -150f) + quadToRelative(-23f, -23f, -23f, -56f) + reflectiveQuadToRelative(23f, -56f) + lineToRelative(114f, -114f) + quadToRelative(23f, -23f, 56f, -23f) + reflectiveQuadToRelative(56f, 23f) + lineToRelative(150f, 150f) + lineToRelative(150f, -150f) + quadToRelative(23f, -23f, 56f, -23f) + reflectiveQuadToRelative(56f, 23f) + lineToRelative(114f, 114f) + quadToRelative(23f, 23f, 23f, 56f) + reflectiveQuadToRelative(-23f, 56f) + lineTo(706f, 480f) + lineToRelative(150f, 150f) + quadToRelative(23f, 23f, 23f, 56f) + reflectiveQuadToRelative(-23f, 56f) + lineTo(742f, 856f) + quadToRelative(-23f, 23f, -56f, 23f) + reflectiveQuadToRelative(-56f, -23f) + lineTo(480f, 706f) + close() + moveTo(480f, 440f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 400f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 360f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 400f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 440f) + close() + moveTo(310f, 424f) + lineTo(424f, 310f) + lineTo(274f, 160f) + lineTo(160f, 274f) + lineTo(310f, 424f) + close() + moveTo(400f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(440f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(400f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(360f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(400f, 520f) + close() + moveTo(480f, 600f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 560f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 520f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 560f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 600f) + close() + moveTo(560f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(600f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(560f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(520f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(560f, 520f) + close() + moveTo(536f, 650f) + lineTo(686f, 800f) + lineTo(800f, 686f) + lineTo(650f, 536f) + lineTo(536f, 650f) + close() + moveTo(339f, 339f) + close() + moveTo(621f, 621f) + close() + } + }.build() +} + +val Icons.Rounded.Healing: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Healing", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(218f, 856f) + lineTo(104f, 742f) + quadToRelative(-23f, -23f, -23f, -56f) + reflectiveQuadToRelative(23f, -56f) + lineToRelative(526f, -526f) + quadToRelative(23f, -23f, 56f, -23f) + reflectiveQuadToRelative(56f, 23f) + lineToRelative(114f, 114f) + quadToRelative(23f, 23f, 23f, 56f) + reflectiveQuadToRelative(-23f, 56f) + lineTo(330f, 856f) + quadToRelative(-23f, 23f, -56f, 23f) + reflectiveQuadToRelative(-56f, -23f) + close() + moveTo(508f, 734f) + lineTo(734f, 508f) + lineTo(856f, 630f) + quadToRelative(23f, 23f, 23f, 56f) + reflectiveQuadToRelative(-23f, 56f) + lineTo(742f, 856f) + quadToRelative(-23f, 23f, -56f, 23f) + reflectiveQuadToRelative(-56f, -23f) + lineTo(508f, 734f) + close() + moveTo(480f, 600f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 560f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 520f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 560f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 600f) + close() + moveTo(400f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(440f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(400f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(360f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(400f, 520f) + close() + moveTo(560f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(600f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(560f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(520f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(560f, 520f) + close() + moveTo(225f, 451f) + lineTo(104f, 330f) + quadToRelative(-23f, -23f, -23f, -56f) + reflectiveQuadToRelative(23f, -56f) + lineToRelative(114f, -114f) + quadToRelative(23f, -23f, 56f, -23f) + reflectiveQuadToRelative(56f, 23f) + lineToRelative(122f, 122f) + lineToRelative(-227f, 225f) + close() + moveTo(480f, 440f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 400f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 360f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 400f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 440f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Help.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Help.kt new file mode 100644 index 0000000..6385e93 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Help.kt @@ -0,0 +1,192 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Rounded.Help: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.HelpOutline", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(11.95f, 18f) + quadTo(12.475f, 18f, 12.837f, 17.638f) + quadTo(13.2f, 17.275f, 13.2f, 16.75f) + quadTo(13.2f, 16.225f, 12.837f, 15.863f) + quadTo(12.475f, 15.5f, 11.95f, 15.5f) + quadTo(11.425f, 15.5f, 11.063f, 15.863f) + quadTo(10.7f, 16.225f, 10.7f, 16.75f) + quadTo(10.7f, 17.275f, 11.063f, 17.638f) + quadTo(11.425f, 18f, 11.95f, 18f) + close() + moveTo(12f, 22f) + quadTo(9.925f, 22f, 8.1f, 21.212f) + quadTo(6.275f, 20.425f, 4.925f, 19.075f) + quadTo(3.575f, 17.725f, 2.787f, 15.9f) + quadTo(2f, 14.075f, 2f, 12f) + quadTo(2f, 9.925f, 2.787f, 8.1f) + quadTo(3.575f, 6.275f, 4.925f, 4.925f) + quadTo(6.275f, 3.575f, 8.1f, 2.787f) + quadTo(9.925f, 2f, 12f, 2f) + quadTo(14.075f, 2f, 15.9f, 2.787f) + quadTo(17.725f, 3.575f, 19.075f, 4.925f) + quadTo(20.425f, 6.275f, 21.212f, 8.1f) + quadTo(22f, 9.925f, 22f, 12f) + quadTo(22f, 14.075f, 21.212f, 15.9f) + quadTo(20.425f, 17.725f, 19.075f, 19.075f) + quadTo(17.725f, 20.425f, 15.9f, 21.212f) + quadTo(14.075f, 22f, 12f, 22f) + close() + moveTo(12f, 20f) + quadTo(15.35f, 20f, 17.675f, 17.675f) + quadTo(20f, 15.35f, 20f, 12f) + quadTo(20f, 8.65f, 17.675f, 6.325f) + quadTo(15.35f, 4f, 12f, 4f) + quadTo(8.65f, 4f, 6.325f, 6.325f) + quadTo(4f, 8.65f, 4f, 12f) + quadTo(4f, 15.35f, 6.325f, 17.675f) + quadTo(8.65f, 20f, 12f, 20f) + close() + moveTo(12f, 20f) + quadTo(8.65f, 20f, 6.325f, 17.675f) + quadTo(4f, 15.35f, 4f, 12f) + quadTo(4f, 8.65f, 6.325f, 6.325f) + quadTo(8.65f, 4f, 12f, 4f) + quadTo(15.35f, 4f, 17.675f, 6.325f) + quadTo(20f, 8.65f, 20f, 12f) + quadTo(20f, 15.35f, 17.675f, 17.675f) + quadTo(15.35f, 20f, 12f, 20f) + close() + moveTo(12.1f, 7.7f) + quadTo(12.725f, 7.7f, 13.188f, 8.1f) + quadTo(13.65f, 8.5f, 13.65f, 9.1f) + quadTo(13.65f, 9.65f, 13.313f, 10.075f) + quadTo(12.975f, 10.5f, 12.55f, 10.875f) + quadTo(11.975f, 11.375f, 11.538f, 11.975f) + quadTo(11.1f, 12.575f, 11.1f, 13.325f) + quadTo(11.1f, 13.675f, 11.363f, 13.913f) + quadTo(11.625f, 14.15f, 11.975f, 14.15f) + quadTo(12.35f, 14.15f, 12.613f, 13.9f) + quadTo(12.875f, 13.65f, 12.95f, 13.275f) + quadTo(13.05f, 12.75f, 13.4f, 12.337f) + quadTo(13.75f, 11.925f, 14.15f, 11.55f) + quadTo(14.725f, 11f, 15.137f, 10.35f) + quadTo(15.55f, 9.7f, 15.55f, 8.9f) + quadTo(15.55f, 7.625f, 14.512f, 6.813f) + quadTo(13.475f, 6f, 12.1f, 6f) + quadTo(11.15f, 6f, 10.288f, 6.4f) + quadTo(9.425f, 6.8f, 8.975f, 7.625f) + quadTo(8.8f, 7.925f, 8.863f, 8.262f) + quadTo(8.925f, 8.6f, 9.2f, 8.775f) + quadTo(9.55f, 8.975f, 9.925f, 8.9f) + quadTo(10.3f, 8.825f, 10.55f, 8.475f) + quadTo(10.825f, 8.1f, 11.238f, 7.9f) + quadTo(11.65f, 7.7f, 12.1f, 7.7f) + close() + } + }.build() +} + +val Icons.Outlined.Help: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Help", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(513.5f, 705.5f) + quadTo(528f, 691f, 528f, 670f) + reflectiveQuadToRelative(-14.5f, -35.5f) + quadTo(499f, 620f, 478f, 620f) + reflectiveQuadToRelative(-35.5f, 14.5f) + quadTo(428f, 649f, 428f, 670f) + reflectiveQuadToRelative(14.5f, 35.5f) + quadTo(457f, 720f, 478f, 720f) + reflectiveQuadToRelative(35.5f, -14.5f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + close() + moveTo(480f, 480f) + close() + moveTo(484f, 308f) + quadToRelative(25f, 0f, 43.5f, 16f) + reflectiveQuadToRelative(18.5f, 40f) + quadToRelative(0f, 22f, -13.5f, 39f) + reflectiveQuadTo(502f, 435f) + quadToRelative(-23f, 20f, -40.5f, 44f) + reflectiveQuadTo(444f, 533f) + quadToRelative(0f, 14f, 10.5f, 23.5f) + reflectiveQuadTo(479f, 566f) + quadToRelative(15f, 0f, 25.5f, -10f) + reflectiveQuadToRelative(13.5f, -25f) + quadToRelative(4f, -21f, 18f, -37.5f) + reflectiveQuadToRelative(30f, -31.5f) + quadToRelative(23f, -22f, 39.5f, -48f) + reflectiveQuadToRelative(16.5f, -58f) + quadToRelative(0f, -51f, -41.5f, -83.5f) + reflectiveQuadTo(484f, 240f) + quadToRelative(-38f, 0f, -72.5f, 16f) + reflectiveQuadTo(359f, 305f) + quadToRelative(-7f, 12f, -4.5f, 25.5f) + reflectiveQuadTo(368f, 351f) + quadToRelative(14f, 8f, 29f, 5f) + reflectiveQuadToRelative(25f, -17f) + quadToRelative(11f, -15f, 27.5f, -23f) + reflectiveQuadToRelative(34.5f, -8f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Highlight.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Highlight.kt new file mode 100644 index 0000000..168e119 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Highlight.kt @@ -0,0 +1,96 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Highlight: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Highlight", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(198f, 293f) + lineToRelative(-30f, -29f) + quadToRelative(-12f, -11f, -11.5f, -27.5f) + reflectiveQuadTo(168f, 208f) + quadToRelative(12f, -12f, 28.5f, -12.5f) + reflectiveQuadTo(225f, 207f) + lineToRelative(29f, 29f) + quadToRelative(11f, 11f, 11.5f, 27.5f) + reflectiveQuadTo(254f, 292f) + quadToRelative(-11f, 11f, -27.5f, 11.5f) + reflectiveQuadTo(198f, 293f) + close() + moveTo(440f, 160f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 120f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 160f) + close() + moveTo(708f, 235f) + lineTo(736f, 207f) + quadToRelative(11f, -11f, 27.5f, -11f) + reflectiveQuadToRelative(28.5f, 12f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineToRelative(-28f, 28f) + quadToRelative(-11f, 11f, -27.5f, 11.5f) + reflectiveQuadTo(708f, 293f) + quadToRelative(-12f, -12f, -12f, -29f) + reflectiveQuadToRelative(12f, -29f) + close() + moveTo(360f, 800f) + verticalLineToRelative(-120f) + lineToRelative(-97f, -97f) + quadToRelative(-11f, -11f, -17f, -25.5f) + reflectiveQuadToRelative(-6f, -30.5f) + verticalLineToRelative(-87f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(320f, 360f) + horizontalLineToRelative(320f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(720f, 440f) + verticalLineToRelative(87f) + quadToRelative(0f, 16f, -6f, 30.5f) + reflectiveQuadTo(697f, 583f) + lineToRelative(-97f, 97f) + verticalLineToRelative(120f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(520f, 880f) + horizontalLineToRelative(-80f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(360f, 800f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Highlighter.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Highlighter.kt new file mode 100644 index 0000000..08f6a1a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Highlighter.kt @@ -0,0 +1,84 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/History.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/History.kt new file mode 100644 index 0000000..10a3607 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/History.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.History: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.History", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 840f) + quadToRelative(-126f, 0f, -223f, -76.5f) + reflectiveQuadTo(131f, 568f) + quadToRelative(-4f, -15f, 6f, -27.5f) + reflectiveQuadToRelative(27f, -14.5f) + quadToRelative(16f, -2f, 29f, 6f) + reflectiveQuadToRelative(18f, 24f) + quadToRelative(24f, 90f, 99f, 147f) + reflectiveQuadToRelative(170f, 57f) + quadToRelative(117f, 0f, 198.5f, -81.5f) + reflectiveQuadTo(760f, 480f) + quadToRelative(0f, -117f, -81.5f, -198.5f) + reflectiveQuadTo(480f, 200f) + quadToRelative(-69f, 0f, -129f, 32f) + reflectiveQuadToRelative(-101f, 88f) + horizontalLineToRelative(70f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 360f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 400f) + lineTo(160f, 400f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 360f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 200f) + verticalLineToRelative(54f) + quadToRelative(51f, -64f, 124.5f, -99f) + reflectiveQuadTo(480f, 120f) + quadToRelative(75f, 0f, 140.5f, 28.5f) + reflectiveQuadToRelative(114f, 77f) + quadToRelative(48.5f, 48.5f, 77f, 114f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 75f, -28.5f, 140.5f) + reflectiveQuadToRelative(-77f, 114f) + quadToRelative(-48.5f, 48.5f, -114f, 77f) + reflectiveQuadTo(480f, 840f) + close() + moveTo(520f, 464f) + lineTo(620f, 564f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(452f, 508f) + quadToRelative(-6f, -6f, -9f, -13.5f) + reflectiveQuadToRelative(-3f, -15.5f) + verticalLineToRelative(-159f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 320f) + verticalLineToRelative(144f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HistoryToggleOff.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HistoryToggleOff.kt new file mode 100644 index 0000000..2a50257 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HistoryToggleOff.kt @@ -0,0 +1,173 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +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.HistoryToggleOff: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.HistoryToggleOff", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(196.5f, 254f) + quadTo(185f, 242f, 185f, 225f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(208f, 185f, 225f, 185f) + reflectiveQuadToRelative(29f, 11.5f) + quadToRelative(12f, 11.5f, 12f, 28.5f) + reflectiveQuadToRelative(-12f, 29f) + quadToRelative(-12f, 12f, -29f, 12f) + reflectiveQuadToRelative(-28.5f, -12f) + close() + moveTo(358f, 161f) + quadToRelative(-12f, -12f, -12f, -29f) + reflectiveQuadToRelative(12f, -28.5f) + quadToRelative(12f, -11.5f, 29f, -11.5f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(427f, 115f, 427f, 132f) + reflectiveQuadToRelative(-11.5f, 29f) + quadTo(404f, 173f, 387f, 173f) + reflectiveQuadToRelative(-29f, -12f) + close() + moveTo(544.5f, 161f) + quadTo(533f, 149f, 533f, 132f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(556f, 92f, 573f, 92f) + reflectiveQuadToRelative(29f, 11.5f) + quadToRelative(12f, 11.5f, 12f, 28.5f) + reflectiveQuadToRelative(-12f, 29f) + quadToRelative(-12f, 12f, -29f, 12f) + reflectiveQuadToRelative(-28.5f, -12f) + close() + moveTo(706f, 254f) + quadToRelative(-12f, -12f, -12f, -29f) + reflectiveQuadToRelative(12f, -29f) + quadToRelative(12f, -12f, 29f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + quadToRelative(11.5f, 12f, 11.5f, 29f) + reflectiveQuadToRelative(-11.5f, 29f) + quadTo(752f, 266f, 735f, 266f) + reflectiveQuadToRelative(-29f, -12f) + close() + moveTo(799f, 415.5f) + quadTo(787f, 404f, 787f, 387f) + reflectiveQuadToRelative(12f, -28.5f) + quadToRelative(12f, -11.5f, 29f, -11.5f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(868f, 370f, 868f, 387f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(845f, 427f, 828f, 427f) + reflectiveQuadToRelative(-29f, -11.5f) + close() + moveTo(799f, 602f) + quadToRelative(-12f, -12f, -12f, -29f) + reflectiveQuadToRelative(12f, -28.5f) + quadToRelative(12f, -11.5f, 29f, -11.5f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(868f, 556f, 868f, 573f) + reflectiveQuadToRelative(-11.5f, 29f) + quadTo(845f, 614f, 828f, 614f) + reflectiveQuadToRelative(-29f, -12f) + close() + moveTo(706f, 763.5f) + quadTo(694f, 752f, 694f, 735f) + reflectiveQuadToRelative(12f, -29f) + quadToRelative(12f, -12f, 29f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + quadToRelative(11.5f, 12f, 11.5f, 29f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(752f, 775f, 735f, 775f) + reflectiveQuadToRelative(-29f, -11.5f) + close() + moveTo(544.5f, 856.5f) + quadTo(533f, 845f, 533f, 828f) + reflectiveQuadToRelative(11.5f, -29f) + quadToRelative(11.5f, -12f, 28.5f, -12f) + reflectiveQuadToRelative(29f, 12f) + quadToRelative(12f, 12f, 12f, 29f) + reflectiveQuadToRelative(-12f, 28.5f) + quadTo(590f, 868f, 573f, 868f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + moveTo(358f, 856.5f) + quadTo(346f, 845f, 346f, 828f) + reflectiveQuadToRelative(12f, -29f) + quadToRelative(12f, -12f, 29f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + quadToRelative(11.5f, 12f, 11.5f, 29f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(404f, 868f, 387f, 868f) + reflectiveQuadToRelative(-29f, -11.5f) + close() + moveTo(196.5f, 762.5f) + quadTo(185f, 751f, 185f, 734f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(208f, 694f, 225f, 694f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(265f, 717f, 265f, 734f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(242f, 774f, 225f, 774f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + moveTo(132f, 613f) + quadToRelative(-17f, 0f, -28.5f, -12f) + reflectiveQuadTo(92f, 572f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(132f, 532f) + quadToRelative(17f, 0f, 29f, 11.5f) + reflectiveQuadToRelative(12f, 28.5f) + quadToRelative(0f, 17f, -12f, 29f) + reflectiveQuadToRelative(-29f, 12f) + close() + moveTo(132f, 427f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(92f, 387f) + quadToRelative(0f, -17f, 11.5f, -29f) + reflectiveQuadToRelative(28.5f, -12f) + quadToRelative(17f, 0f, 29f, 12f) + reflectiveQuadToRelative(12f, 29f) + quadToRelative(0f, 17f, -12f, 28.5f) + reflectiveQuadTo(132f, 427f) + close() + moveTo(520f, 464f) + lineTo(640f, 584f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(452f, 508f) + quadToRelative(-6f, -6f, -9f, -13.5f) + reflectiveQuadToRelative(-3f, -15.5f) + verticalLineToRelative(-159f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 320f) + verticalLineToRelative(144f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Home.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Home.kt new file mode 100644 index 0000000..9f97856 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Home.kt @@ -0,0 +1,80 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Home: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Home", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(240f, 760f) + horizontalLineToRelative(120f) + verticalLineToRelative(-200f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(400f, 520f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(600f, 560f) + verticalLineToRelative(200f) + horizontalLineToRelative(120f) + verticalLineToRelative(-360f) + lineTo(480f, 220f) + lineTo(240f, 400f) + verticalLineToRelative(360f) + close() + moveTo(160f, 760f) + verticalLineToRelative(-360f) + quadToRelative(0f, -19f, 8.5f, -36f) + reflectiveQuadToRelative(23.5f, -28f) + lineToRelative(240f, -180f) + quadToRelative(21f, -16f, 48f, -16f) + reflectiveQuadToRelative(48f, 16f) + lineToRelative(240f, 180f) + quadToRelative(15f, 11f, 23.5f, 28f) + reflectiveQuadToRelative(8.5f, 36f) + verticalLineToRelative(360f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 840f) + lineTo(560f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(520f, 800f) + verticalLineToRelative(-200f) + horizontalLineToRelative(-80f) + verticalLineToRelative(200f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(400f, 840f) + lineTo(240f, 840f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 760f) + close() + moveTo(480f, 490f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HorizontalSplit.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HorizontalSplit.kt new file mode 100644 index 0000000..dcfb949 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HorizontalSplit.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.HorizontalSplit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.HorizontalSplit", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 760f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 720f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 520f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 560f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 760f) + lineTo(160f, 760f) + close() + moveTo(160f, 440f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 400f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 360f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 400f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 440f) + lineTo(160f, 440f) + close() + moveTo(160f, 280f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 240f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 200f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 240f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 280f) + lineTo(160f, 280f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HourglassEmpty.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HourglassEmpty.kt new file mode 100644 index 0000000..a8882d2 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HourglassEmpty.kt @@ -0,0 +1,92 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.HourglassEmpty: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.HourglassEmpty", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(320f, 800f) + horizontalLineToRelative(320f) + verticalLineToRelative(-120f) + quadToRelative(0f, -66f, -47f, -113f) + reflectiveQuadToRelative(-113f, -47f) + quadToRelative(-66f, 0f, -113f, 47f) + reflectiveQuadToRelative(-47f, 113f) + verticalLineToRelative(120f) + close() + moveTo(480f, 440f) + quadToRelative(66f, 0f, 113f, -47f) + reflectiveQuadToRelative(47f, -113f) + verticalLineToRelative(-120f) + lineTo(320f, 160f) + verticalLineToRelative(120f) + quadToRelative(0f, 66f, 47f, 113f) + reflectiveQuadToRelative(113f, 47f) + close() + moveTo(200f, 880f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 840f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 800f) + horizontalLineToRelative(40f) + verticalLineToRelative(-120f) + quadToRelative(0f, -61f, 28.5f, -114.5f) + reflectiveQuadTo(348f, 480f) + quadToRelative(-51f, -32f, -79.5f, -85.5f) + reflectiveQuadTo(240f, 280f) + verticalLineToRelative(-120f) + horizontalLineToRelative(-40f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 80f) + horizontalLineToRelative(560f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 160f) + horizontalLineToRelative(-40f) + verticalLineToRelative(120f) + quadToRelative(0f, 61f, -28.5f, 114.5f) + reflectiveQuadTo(612f, 480f) + quadToRelative(51f, 32f, 79.5f, 85.5f) + reflectiveQuadTo(720f, 680f) + verticalLineToRelative(120f) + horizontalLineToRelative(40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 840f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 880f) + lineTo(200f, 880f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HyperOS.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HyperOS.kt new file mode 100644 index 0000000..c7a4191 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HyperOS.kt @@ -0,0 +1,444 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/IOS.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/IOS.kt new file mode 100644 index 0000000..f64ec56 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/IOS.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Image.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Image.kt new file mode 100644 index 0000000..cca0300 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Image.kt @@ -0,0 +1,182 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Image: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Image", + 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() + } + }.build() +} + +val Icons.Rounded.Image: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Image", + 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(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() + } + }.build() +} + +val Icons.TwoTone.Image: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.Image", + 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(14f) + 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) + horizontalLineTo(5f) + close() + moveTo(5f, 19f) + horizontalLineToRelative(14f) + verticalLineTo(5f) + horizontalLineTo(5f) + verticalLineToRelative(14f) + close() + moveTo(5f, 19f) + verticalLineTo(5f) + verticalLineToRelative(14f) + close() + 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(5f, 5f) + horizontalLineToRelative(14f) + verticalLineToRelative(14f) + horizontalLineToRelative(-14f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageCombine.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageCombine.kt new file mode 100644 index 0000000..c7258b0 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageCombine.kt @@ -0,0 +1,291 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), + 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.Black), + 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.Black)) { + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageConvert.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageConvert.kt new file mode 100644 index 0000000..f88e0f8 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageConvert.kt @@ -0,0 +1,285 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + moveTo(9.018f, 12.944f) + horizontalLineToRelative(7.393f) + curveToRelative(0.148f, 0f, 0.259f, -0.068f, 0.333f, -0.203f) + reflectiveCurveToRelative(0.062f, -0.265f, -0.037f, -0.388f) + lineToRelative(-2.033f, -2.717f) + curveToRelative(-0.074f, -0.099f, -0.173f, -0.148f, -0.296f, -0.148f) + reflectiveCurveToRelative(-0.222f, 0.049f, -0.296f, 0.148f) + lineToRelative(-1.922f, 2.569f) + lineToRelative(-1.368f, -1.83f) + curveToRelative(-0.074f, -0.099f, -0.173f, -0.148f, -0.296f, -0.148f) + reflectiveCurveToRelative(-0.222f, 0.049f, -0.296f, 0.148f) + lineToRelative(-1.479f, 1.978f) + curveToRelative(-0.099f, 0.123f, -0.111f, 0.253f, -0.037f, 0.388f) + reflectiveCurveToRelative(0.185f, 0.203f, 0.333f, 0.203f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(5.239f, 11.268f) + curveToRelative(0f, 1.31f, 0.33f, 2.527f, 0.989f, 3.652f) + reflectiveCurveToRelative(1.556f, 2.026f, 2.689f, 2.704f) + curveToRelative(0.217f, 0.139f, 0.369f, 0.327f, 0.454f, 0.566f) + reflectiveCurveToRelative(0.066f, 0.466f, -0.058f, 0.682f) + curveToRelative(-0.124f, 0.231f, -0.314f, 0.381f, -0.57f, 0.451f) + reflectiveCurveToRelative(-0.501f, 0.042f, -0.733f, -0.081f) + curveToRelative(-1.443f, -0.832f, -2.576f, -1.957f, -3.399f, -3.374f) + curveToRelative(-0.823f, -1.418f, -1.234f, -2.951f, -1.234f, -4.599f) + curveToRelative(0f, -0.401f, 0.027f, -0.794f, 0.081f, -1.179f) + reflectiveCurveToRelative(0.128f, -0.77f, 0.221f, -1.156f) + lineToRelative(-0.303f, 0.185f) + curveToRelative(-0.217f, 0.139f, -0.45f, 0.173f, -0.698f, 0.104f) + reflectiveCurveToRelative(-0.435f, -0.212f, -0.559f, -0.428f) + reflectiveCurveToRelative(-0.151f, -0.451f, -0.081f, -0.705f) + reflectiveCurveToRelative(0.213f, -0.443f, 0.431f, -0.566f) + lineToRelative(2.817f, -1.618f) + curveToRelative(0.217f, -0.123f, 0.454f, -0.15f, 0.71f, -0.081f) + reflectiveCurveToRelative(0.446f, 0.212f, 0.57f, 0.428f) + lineToRelative(1.63f, 2.773f) + curveToRelative(0.124f, 0.216f, 0.151f, 0.451f, 0.081f, 0.705f) + reflectiveCurveToRelative(-0.213f, 0.443f, -0.431f, 0.566f) + reflectiveCurveToRelative(-0.454f, 0.15f, -0.71f, 0.081f) + reflectiveCurveToRelative(-0.446f, -0.212f, -0.57f, -0.428f) + lineToRelative(-0.792f, -1.364f) + curveToRelative(-0.171f, 0.431f, -0.303f, 0.871f, -0.396f, 1.317f) + reflectiveCurveToRelative(-0.14f, 0.901f, -0.14f, 1.364f) + close() + moveTo(12.688f, 3.849f) + curveToRelative(-0.636f, 0f, -1.265f, 0.081f, -1.886f, 0.243f) + reflectiveCurveToRelative(-1.211f, 0.397f, -1.769f, 0.705f) + curveToRelative(-0.233f, 0.123f, -0.477f, 0.166f, -0.733f, 0.127f) + reflectiveCurveToRelative(-0.446f, -0.166f, -0.57f, -0.381f) + curveToRelative(-0.14f, -0.247f, -0.171f, -0.497f, -0.093f, -0.751f) + reflectiveCurveToRelative(0.241f, -0.451f, 0.489f, -0.589f) + curveToRelative(0.698f, -0.401f, 1.432f, -0.701f, 2.2f, -0.901f) + reflectiveCurveToRelative(1.556f, -0.3f, 2.363f, -0.3f) + curveToRelative(1.226f, 0f, 2.402f, 0.227f, 3.527f, 0.682f) + reflectiveCurveToRelative(2.13f, 1.113f, 3.015f, 1.976f) + verticalLineToRelative(-0.347f) + curveToRelative(0f, -0.262f, 0.089f, -0.482f, 0.268f, -0.659f) + reflectiveCurveToRelative(0.4f, -0.266f, 0.663f, -0.266f) + reflectiveCurveToRelative(0.485f, 0.089f, 0.663f, 0.266f) + reflectiveCurveToRelative(0.268f, 0.397f, 0.268f, 0.659f) + verticalLineToRelative(3.236f) + curveToRelative(0f, 0.262f, -0.089f, 0.482f, -0.268f, 0.659f) + reflectiveCurveToRelative(-0.4f, 0.266f, -0.663f, 0.266f) + horizontalLineToRelative(-3.259f) + curveToRelative(-0.264f, 0f, -0.485f, -0.089f, -0.663f, -0.266f) + reflectiveCurveToRelative(-0.268f, -0.397f, -0.268f, -0.659f) + reflectiveCurveToRelative(0.089f, -0.482f, 0.268f, -0.659f) + reflectiveCurveToRelative(0.4f, -0.266f, 0.663f, -0.266f) + horizontalLineToRelative(1.606f) + curveToRelative(-0.714f, -0.878f, -1.575f, -1.56f, -2.584f, -2.045f) + reflectiveCurveToRelative(-2.087f, -0.728f, -3.236f, -0.728f) + close() + moveTo(18.322f, 16.122f) + curveToRelative(0.59f, -0.678f, 1.04f, -1.425f, 1.35f, -2.242f) + reflectiveCurveToRelative(0.466f, -1.672f, 0.466f, -2.565f) + curveToRelative(0f, -0.262f, 0.089f, -0.493f, 0.268f, -0.693f) + curveToRelative(0.178f, -0.2f, 0.4f, -0.3f, 0.663f, -0.3f) + reflectiveCurveToRelative(0.485f, 0.1f, 0.663f, 0.3f) + curveToRelative(0.178f, 0.2f, 0.268f, 0.431f, 0.268f, 0.693f) + curveToRelative(0f, 1.002f, -0.159f, 1.968f, -0.477f, 2.901f) + reflectiveCurveToRelative(-0.78f, 1.799f, -1.385f, 2.6f) + curveToRelative(-0.605f, 0.801f, -1.323f, 1.487f, -2.153f, 2.057f) + curveToRelative(-0.83f, 0.57f, -1.734f, 0.994f, -2.712f, 1.271f) + lineToRelative(0.233f, 0.139f) + curveToRelative(0.217f, 0.123f, 0.357f, 0.312f, 0.419f, 0.566f) + reflectiveCurveToRelative(0.031f, 0.489f, -0.093f, 0.705f) + curveToRelative(-0.124f, 0.216f, -0.31f, 0.354f, -0.559f, 0.416f) + curveToRelative(-0.248f, 0.062f, -0.481f, 0.031f, -0.698f, -0.092f) + lineToRelative(-2.84f, -1.618f) + curveToRelative(-0.217f, -0.123f, -0.361f, -0.312f, -0.431f, -0.566f) + reflectiveCurveToRelative(-0.043f, -0.489f, 0.081f, -0.705f) + lineToRelative(1.63f, -2.797f) + curveToRelative(0.124f, -0.216f, 0.31f, -0.354f, 0.559f, -0.416f) + curveToRelative(0.248f, -0.062f, 0.481f, -0.031f, 0.698f, 0.092f) + reflectiveCurveToRelative(0.361f, 0.312f, 0.431f, 0.566f) + reflectiveCurveToRelative(0.043f, 0.489f, -0.081f, 0.705f) + lineToRelative(-0.861f, 1.456f) + curveToRelative(0.885f, -0.123f, 1.719f, -0.397f, 2.503f, -0.82f) + reflectiveCurveToRelative(1.47f, -0.975f, 2.06f, -1.653f) + 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.Black)) { + moveTo(9.018f, 12.944f) + horizontalLineToRelative(7.393f) + curveToRelative(0.148f, 0f, 0.259f, -0.068f, 0.333f, -0.203f) + reflectiveCurveToRelative(0.062f, -0.265f, -0.037f, -0.388f) + lineToRelative(-2.033f, -2.717f) + curveToRelative(-0.074f, -0.099f, -0.173f, -0.148f, -0.296f, -0.148f) + reflectiveCurveToRelative(-0.222f, 0.049f, -0.296f, 0.148f) + lineToRelative(-1.922f, 2.569f) + lineToRelative(-1.368f, -1.83f) + curveToRelative(-0.074f, -0.099f, -0.173f, -0.148f, -0.296f, -0.148f) + reflectiveCurveToRelative(-0.222f, 0.049f, -0.296f, 0.148f) + lineToRelative(-1.479f, 1.978f) + curveToRelative(-0.099f, 0.123f, -0.111f, 0.253f, -0.037f, 0.388f) + reflectiveCurveToRelative(0.185f, 0.203f, 0.333f, 0.203f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(5.239f, 11.268f) + curveToRelative(0f, 1.31f, 0.33f, 2.527f, 0.989f, 3.652f) + reflectiveCurveToRelative(1.556f, 2.026f, 2.689f, 2.704f) + curveToRelative(0.217f, 0.139f, 0.369f, 0.327f, 0.454f, 0.566f) + reflectiveCurveToRelative(0.066f, 0.466f, -0.058f, 0.682f) + curveToRelative(-0.124f, 0.231f, -0.314f, 0.381f, -0.57f, 0.451f) + reflectiveCurveToRelative(-0.501f, 0.042f, -0.733f, -0.081f) + curveToRelative(-1.443f, -0.832f, -2.576f, -1.957f, -3.399f, -3.374f) + curveToRelative(-0.823f, -1.418f, -1.234f, -2.951f, -1.234f, -4.599f) + curveToRelative(0f, -0.401f, 0.027f, -0.794f, 0.081f, -1.179f) + reflectiveCurveToRelative(0.128f, -0.77f, 0.221f, -1.156f) + lineToRelative(-0.303f, 0.185f) + curveToRelative(-0.217f, 0.139f, -0.45f, 0.173f, -0.698f, 0.104f) + reflectiveCurveToRelative(-0.435f, -0.212f, -0.559f, -0.428f) + reflectiveCurveToRelative(-0.151f, -0.451f, -0.081f, -0.705f) + reflectiveCurveToRelative(0.213f, -0.443f, 0.431f, -0.566f) + lineToRelative(2.817f, -1.618f) + curveToRelative(0.217f, -0.123f, 0.454f, -0.15f, 0.71f, -0.081f) + reflectiveCurveToRelative(0.446f, 0.212f, 0.57f, 0.428f) + lineToRelative(1.63f, 2.773f) + curveToRelative(0.124f, 0.216f, 0.151f, 0.451f, 0.081f, 0.705f) + reflectiveCurveToRelative(-0.213f, 0.443f, -0.431f, 0.566f) + reflectiveCurveToRelative(-0.454f, 0.15f, -0.71f, 0.081f) + reflectiveCurveToRelative(-0.446f, -0.212f, -0.57f, -0.428f) + lineToRelative(-0.792f, -1.364f) + curveToRelative(-0.171f, 0.431f, -0.303f, 0.871f, -0.396f, 1.317f) + reflectiveCurveToRelative(-0.14f, 0.901f, -0.14f, 1.364f) + close() + moveTo(12.688f, 3.849f) + curveToRelative(-0.636f, 0f, -1.265f, 0.081f, -1.886f, 0.243f) + reflectiveCurveToRelative(-1.211f, 0.397f, -1.769f, 0.705f) + curveToRelative(-0.233f, 0.123f, -0.477f, 0.166f, -0.733f, 0.127f) + reflectiveCurveToRelative(-0.446f, -0.166f, -0.57f, -0.381f) + curveToRelative(-0.14f, -0.247f, -0.171f, -0.497f, -0.093f, -0.751f) + reflectiveCurveToRelative(0.241f, -0.451f, 0.489f, -0.589f) + curveToRelative(0.698f, -0.401f, 1.432f, -0.701f, 2.2f, -0.901f) + reflectiveCurveToRelative(1.556f, -0.3f, 2.363f, -0.3f) + curveToRelative(1.226f, 0f, 2.402f, 0.227f, 3.527f, 0.682f) + reflectiveCurveToRelative(2.13f, 1.113f, 3.015f, 1.976f) + verticalLineToRelative(-0.347f) + curveToRelative(0f, -0.262f, 0.089f, -0.482f, 0.268f, -0.659f) + reflectiveCurveToRelative(0.4f, -0.266f, 0.663f, -0.266f) + reflectiveCurveToRelative(0.485f, 0.089f, 0.663f, 0.266f) + reflectiveCurveToRelative(0.268f, 0.397f, 0.268f, 0.659f) + verticalLineToRelative(3.236f) + curveToRelative(0f, 0.262f, -0.089f, 0.482f, -0.268f, 0.659f) + reflectiveCurveToRelative(-0.4f, 0.266f, -0.663f, 0.266f) + horizontalLineToRelative(-3.259f) + curveToRelative(-0.264f, 0f, -0.485f, -0.089f, -0.663f, -0.266f) + reflectiveCurveToRelative(-0.268f, -0.397f, -0.268f, -0.659f) + reflectiveCurveToRelative(0.089f, -0.482f, 0.268f, -0.659f) + reflectiveCurveToRelative(0.4f, -0.266f, 0.663f, -0.266f) + horizontalLineToRelative(1.606f) + curveToRelative(-0.714f, -0.878f, -1.575f, -1.56f, -2.584f, -2.045f) + reflectiveCurveToRelative(-2.087f, -0.728f, -3.236f, -0.728f) + close() + moveTo(18.322f, 16.122f) + curveToRelative(0.59f, -0.678f, 1.04f, -1.425f, 1.35f, -2.242f) + reflectiveCurveToRelative(0.466f, -1.672f, 0.466f, -2.565f) + curveToRelative(0f, -0.262f, 0.089f, -0.493f, 0.268f, -0.693f) + curveToRelative(0.178f, -0.2f, 0.4f, -0.3f, 0.663f, -0.3f) + reflectiveCurveToRelative(0.485f, 0.1f, 0.663f, 0.3f) + curveToRelative(0.178f, 0.2f, 0.268f, 0.431f, 0.268f, 0.693f) + curveToRelative(0f, 1.002f, -0.159f, 1.968f, -0.477f, 2.901f) + reflectiveCurveToRelative(-0.78f, 1.799f, -1.385f, 2.6f) + curveToRelative(-0.605f, 0.801f, -1.323f, 1.487f, -2.153f, 2.057f) + curveToRelative(-0.83f, 0.57f, -1.734f, 0.994f, -2.712f, 1.271f) + lineToRelative(0.233f, 0.139f) + curveToRelative(0.217f, 0.123f, 0.357f, 0.312f, 0.419f, 0.566f) + reflectiveCurveToRelative(0.031f, 0.489f, -0.093f, 0.705f) + curveToRelative(-0.124f, 0.216f, -0.31f, 0.354f, -0.559f, 0.416f) + curveToRelative(-0.248f, 0.062f, -0.481f, 0.031f, -0.698f, -0.092f) + lineToRelative(-2.84f, -1.618f) + curveToRelative(-0.217f, -0.123f, -0.361f, -0.312f, -0.431f, -0.566f) + reflectiveCurveToRelative(-0.043f, -0.489f, 0.081f, -0.705f) + lineToRelative(1.63f, -2.797f) + curveToRelative(0.124f, -0.216f, 0.31f, -0.354f, 0.559f, -0.416f) + curveToRelative(0.248f, -0.062f, 0.481f, -0.031f, 0.698f, 0.092f) + reflectiveCurveToRelative(0.361f, 0.312f, 0.431f, 0.566f) + reflectiveCurveToRelative(0.043f, 0.489f, -0.081f, 0.705f) + lineToRelative(-0.861f, 1.456f) + curveToRelative(0.885f, -0.123f, 1.719f, -0.397f, 2.503f, -0.82f) + reflectiveCurveToRelative(1.47f, -0.975f, 2.06f, -1.653f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(12.296f, 3.716f) + lineTo(13.134f, 3.716f) + arcTo( + 7.081f, + 7.081f, + 0f, + isMoreThanHalf = false, + isPositiveArc = true, + 20.215f, + 10.797f + ) + lineTo(20.215f, 11.635f) + arcTo( + 7.081f, + 7.081f, + 0f, + isMoreThanHalf = false, + isPositiveArc = true, + 13.134f, + 18.716f + ) + lineTo(12.296f, 18.716f) + arcTo(7.081f, 7.081f, 0f, isMoreThanHalf = false, isPositiveArc = true, 5.215f, 11.635f) + lineTo(5.215f, 10.797f) + arcTo(7.081f, 7.081f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12.296f, 3.716f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageDownload.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageDownload.kt new file mode 100644 index 0000000..c11d063 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageDownload.kt @@ -0,0 +1,202 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageEdit.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageEdit.kt new file mode 100644 index 0000000..9dc15d1 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageEdit.kt @@ -0,0 +1,230 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.ImageEdit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.ImageEdit", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(16.221f, 12.74f) + lineToRelative(-1.153f, -1.541f) + curveToRelative(-0.092f, -0.123f, -0.215f, -0.184f, -0.369f, -0.184f) + curveToRelative(-0.154f, 0f, -0.277f, 0.061f, -0.369f, 0.184f) + lineToRelative(-2.398f, 3.205f) + lineToRelative(-1.706f, -2.283f) + curveToRelative(-0.092f, -0.123f, -0.215f, -0.184f, -0.369f, -0.184f) + reflectiveCurveToRelative(-0.277f, 0.061f, -0.369f, 0.184f) + lineToRelative(-1.844f, 2.467f) + curveToRelative(-0.123f, 0.154f, -0.138f, 0.315f, -0.046f, 0.484f) + reflectiveCurveToRelative(0.231f, 0.254f, 0.415f, 0.254f) + horizontalLineToRelative(5.623f) + lineToRelative(2.586f, -2.586f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(22.454f, 13.867f) + curveToRelative(-0.075f, -0.183f, -0.179f, -0.349f, -0.312f, -0.5f) + lineToRelative(-0.924f, -0.924f) + curveToRelative(-0.15f, -0.15f, -0.316f, -0.262f, -0.5f, -0.337f) + curveToRelative(-0.183f, -0.075f, -0.374f, -0.112f, -0.574f, -0.112f) + curveToRelative(-0.183f, 0f, -0.366f, 0.033f, -0.55f, 0.1f) + curveToRelative(-0.183f, 0.066f, -0.349f, 0.175f, -0.5f, 0.325f) + lineToRelative(-5.219f, 5.194f) + curveToRelative(-0.1f, 0.1f, -0.175f, 0.213f, -0.225f, 0.337f) + reflectiveCurveToRelative(-0.075f, 0.254f, -0.075f, 0.387f) + verticalLineToRelative(1.649f) + curveToRelative(0f, 0.283f, 0.096f, 0.52f, 0.287f, 0.712f) + reflectiveCurveToRelative(0.428f, 0.287f, 0.712f, 0.287f) + horizontalLineToRelative(1.649f) + curveToRelative(0.134f, 0f, 0.262f, -0.025f, 0.387f, -0.075f) + reflectiveCurveToRelative(0.237f, -0.125f, 0.337f, -0.225f) + lineToRelative(5.195f, -5.195f) + curveToRelative(0.15f, -0.15f, 0.258f, -0.32f, 0.325f, -0.512f) + curveToRelative(0.066f, -0.191f, 0.1f, -0.379f, 0.1f, -0.562f) + reflectiveCurveToRelative(-0.037f, -0.366f, -0.112f, -0.55f) + lineToRelative(-0.001f, 0.001f) + close() + moveTo(16.023f, 19.486f) + horizontalLineToRelative(-0.949f) + verticalLineToRelative(-0.949f) + lineToRelative(3.047f, -3.022f) + lineToRelative(0.475f, 0.45f) + lineToRelative(0.45f, 0.475f) + lineToRelative(-3.022f, 3.047f) + lineToRelative(0.001f, 0.001f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(19.912f, 4.088f) + curveToRelative(-0.391f, -0.391f, -0.861f, -0.586f, -1.41f, -0.587f) + verticalLineToRelative(-0f) + horizontalLineTo(5.501f) + curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.413f, 0.588f) + curveToRelative(-0.392f, 0.392f, -0.588f, 0.862f, -0.588f, 1.412f) + verticalLineToRelative(13.017f) + horizontalLineToRelative(0.002f) + curveToRelative(0.004f, 0.542f, 0.198f, 1.008f, 0.586f, 1.396f) + curveToRelative(0.392f, 0.392f, 0.863f, 0.588f, 1.413f, 0.588f) + horizontalLineToRelative(5.285f) + curveToRelative(0.553f, 0f, 1.001f, -0.448f, 1.001f, -1f) + reflectiveCurveToRelative(-0.448f, -1f, -1.001f, -1f) + horizontalLineToRelative(-5.285f) + verticalLineTo(5.5f) + horizontalLineToRelative(12.998f) + verticalLineToRelative(3.772f) + curveToRelative(0f, 0.552f, 0.448f, 1f, 1.001f, 1f) + reflectiveCurveToRelative(1.001f, -0.448f, 1.001f, -1f) + verticalLineToRelative(-3.772f) + curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) + close() + } + }.build() +} + +val Icons.TwoTone.ImageEdit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.ImageEdit", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(19.5f, 10.272f) + curveToRelative(-0.553f, 0f, -1.001f, -0.448f, -1.001f, -1f) + verticalLineToRelative(-3.772f) + horizontalLineTo(5.501f) + verticalLineToRelative(13f) + horizontalLineToRelative(5.285f) + curveToRelative(0.548f, 0f, 0.99f, 0.441f, 0.998f, 0.986f) + horizontalLineToRelative(1.792f) + verticalLineToRelative(-1.149f) + curveToRelative(0f, -0.134f, 0.025f, -0.263f, 0.075f, -0.387f) + curveToRelative(0.05f, -0.125f, 0.125f, -0.237f, 0.225f, -0.337f) + lineToRelative(5.219f, -5.194f) + curveToRelative(0.123f, -0.123f, 0.261f, -0.211f, 0.406f, -0.278f) + verticalLineToRelative(-1.869f) + curveToRelative(-0f, 0f, -0.001f, 0f, -0.001f, 0f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(16.221f, 12.74f) + lineToRelative(-1.153f, -1.541f) + curveToRelative(-0.092f, -0.123f, -0.215f, -0.184f, -0.369f, -0.184f) + curveToRelative(-0.154f, 0f, -0.277f, 0.061f, -0.369f, 0.184f) + lineToRelative(-2.398f, 3.205f) + lineToRelative(-1.706f, -2.283f) + curveToRelative(-0.092f, -0.123f, -0.215f, -0.184f, -0.369f, -0.184f) + reflectiveCurveToRelative(-0.277f, 0.061f, -0.369f, 0.184f) + lineToRelative(-1.844f, 2.467f) + curveToRelative(-0.123f, 0.154f, -0.138f, 0.315f, -0.046f, 0.484f) + reflectiveCurveToRelative(0.231f, 0.254f, 0.415f, 0.254f) + horizontalLineToRelative(5.623f) + lineToRelative(2.586f, -2.586f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(22.454f, 13.867f) + curveToRelative(-0.075f, -0.183f, -0.179f, -0.349f, -0.312f, -0.5f) + lineToRelative(-0.924f, -0.924f) + curveToRelative(-0.15f, -0.15f, -0.316f, -0.262f, -0.5f, -0.337f) + curveToRelative(-0.183f, -0.075f, -0.374f, -0.112f, -0.574f, -0.112f) + curveToRelative(-0.183f, 0f, -0.366f, 0.033f, -0.55f, 0.1f) + curveToRelative(-0.183f, 0.066f, -0.349f, 0.175f, -0.5f, 0.325f) + lineToRelative(-5.219f, 5.194f) + curveToRelative(-0.1f, 0.1f, -0.175f, 0.213f, -0.225f, 0.337f) + reflectiveCurveToRelative(-0.075f, 0.254f, -0.075f, 0.387f) + verticalLineToRelative(1.649f) + curveToRelative(0f, 0.283f, 0.096f, 0.52f, 0.287f, 0.712f) + reflectiveCurveToRelative(0.428f, 0.287f, 0.712f, 0.287f) + horizontalLineToRelative(1.649f) + curveToRelative(0.134f, 0f, 0.262f, -0.025f, 0.387f, -0.075f) + reflectiveCurveToRelative(0.237f, -0.125f, 0.337f, -0.225f) + lineToRelative(5.195f, -5.195f) + curveToRelative(0.15f, -0.15f, 0.258f, -0.32f, 0.325f, -0.512f) + curveToRelative(0.066f, -0.191f, 0.1f, -0.379f, 0.1f, -0.562f) + reflectiveCurveToRelative(-0.037f, -0.366f, -0.112f, -0.55f) + lineToRelative(-0.001f, 0.001f) + close() + moveTo(16.023f, 19.486f) + horizontalLineToRelative(-0.949f) + verticalLineToRelative(-0.949f) + lineToRelative(3.047f, -3.022f) + lineToRelative(0.475f, 0.45f) + lineToRelative(0.45f, 0.475f) + lineToRelative(-3.022f, 3.047f) + lineToRelative(0.001f, 0.001f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(16.023f, 19.486f) + horizontalLineToRelative(-0.949f) + verticalLineToRelative(-0.949f) + lineToRelative(3.047f, -3.022f) + lineToRelative(0.475f, 0.45f) + lineToRelative(0.45f, 0.475f) + lineToRelative(-3.022f, 3.047f) + lineToRelative(0.001f, 0.001f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(19.912f, 4.088f) + curveToRelative(-0.391f, -0.391f, -0.861f, -0.586f, -1.41f, -0.587f) + verticalLineToRelative(-0f) + horizontalLineTo(5.501f) + curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.413f, 0.588f) + curveToRelative(-0.392f, 0.392f, -0.588f, 0.862f, -0.588f, 1.412f) + verticalLineToRelative(13.017f) + horizontalLineToRelative(0.002f) + curveToRelative(0.004f, 0.542f, 0.198f, 1.008f, 0.586f, 1.396f) + curveToRelative(0.392f, 0.392f, 0.863f, 0.588f, 1.413f, 0.588f) + horizontalLineToRelative(5.285f) + curveToRelative(0.553f, 0f, 1.001f, -0.448f, 1.001f, -1f) + reflectiveCurveToRelative(-0.448f, -1f, -1.001f, -1f) + horizontalLineToRelative(-5.285f) + verticalLineTo(5.5f) + horizontalLineToRelative(12.998f) + verticalLineToRelative(3.772f) + curveToRelative(0f, 0.552f, 0.448f, 1f, 1.001f, 1f) + reflectiveCurveToRelative(1.001f, -0.448f, 1.001f, -1f) + verticalLineToRelative(-3.772f) + curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageEmbedded.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageEmbedded.kt new file mode 100644 index 0000000..b4b2e3f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageEmbedded.kt @@ -0,0 +1,119 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageLimit.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageLimit.kt new file mode 100644 index 0000000..e9a2824 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageLimit.kt @@ -0,0 +1,166 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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.Black)) { + 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.Black)) { + 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.Black), + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageOverlay.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageOverlay.kt new file mode 100644 index 0000000..5fb0d3a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageOverlay.kt @@ -0,0 +1,184 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.ImageOverlay: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.ImageOverlay", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(4f, 16f) + 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(10f) + curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) + reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) + verticalLineToRelative(1f) + 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(-1f) + horizontalLineTo(4f) + verticalLineToRelative(10f) + horizontalLineToRelative(1f) + 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(-1f) + close() + moveTo(10f, 22f) + curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) + reflectiveCurveToRelative(-0.587f, -0.863f, -0.587f, -1.413f) + 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) + 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) + close() + moveTo(10f, 20f) + horizontalLineToRelative(10f) + verticalLineToRelative(-10f) + horizontalLineToRelative(-10f) + verticalLineToRelative(10f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(14.5f, 17f) + 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() + } + }.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.Black)) { + moveTo(4f, 16f) + 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(10f) + curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) + reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) + verticalLineToRelative(1f) + 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(-1f) + horizontalLineTo(4f) + verticalLineToRelative(10f) + horizontalLineToRelative(1f) + 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(-1f) + close() + moveTo(10f, 22f) + curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) + reflectiveCurveToRelative(-0.587f, -0.863f, -0.587f, -1.413f) + 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) + 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) + close() + moveTo(10f, 20f) + horizontalLineToRelative(10f) + verticalLineToRelative(-10f) + horizontalLineToRelative(-10f) + verticalLineToRelative(10f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(14.5f, 17f) + 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), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(10f, 10f) + horizontalLineToRelative(10f) + verticalLineToRelative(10f) + horizontalLineToRelative(-10f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageReset.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageReset.kt new file mode 100644 index 0000000..a75fb7c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageReset.kt @@ -0,0 +1,101 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ImageReset: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ImageReset", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(320f, 360f) + lineTo(160f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 320f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 160f) + verticalLineToRelative(94f) + quadToRelative(50f, -62f, 122.5f, -98f) + reflectiveQuadTo(480f, 120f) + quadToRelative(99f, 0f, 179.5f, 48f) + reflectiveQuadTo(788f, 294f) + quadToRelative(8f, 14f, 4.5f, 30f) + reflectiveQuadTo(775f, 348f) + quadToRelative(-14f, 8f, -30.5f, 4.5f) + reflectiveQuadTo(720f, 335f) + quadToRelative(-37f, -61f, -100f, -98f) + reflectiveQuadToRelative(-140f, -37f) + quadToRelative(-57f, 0f, -107.5f, 21f) + reflectiveQuadTo(284f, 280f) + horizontalLineToRelative(36f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 360f) + close() + moveTo(280f, 720f) + horizontalLineToRelative(400f) + quadToRelative(12f, 0f, 18f, -11f) + reflectiveQuadToRelative(-2f, -21f) + lineTo(586f, 541f) + quadToRelative(-6f, -8f, -16f, -8f) + reflectiveQuadToRelative(-16f, 8f) + lineTo(450f, 680f) + lineToRelative(-74f, -99f) + quadToRelative(-6f, -8f, -16f, -8f) + reflectiveQuadToRelative(-16f, 8f) + lineToRelative(-80f, 107f) + quadToRelative(-8f, 10f, -2f, 21f) + reflectiveQuadToRelative(18f, 11f) + close() + moveTo(200f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 800f) + verticalLineToRelative(-280f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 480f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 520f) + verticalLineToRelative(280f) + horizontalLineToRelative(560f) + verticalLineToRelative(-280f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 480f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 520f) + verticalLineToRelative(280f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 880f) + lineTo(200f, 880f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageResize.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageResize.kt new file mode 100644 index 0000000..71fb246 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageResize.kt @@ -0,0 +1,365 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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(3.287f, 4.712f) + 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) + reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) + close() + moveTo(7.287f, 4.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) + 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) + close() + moveTo(11.288f, 4.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(3.287f, 8.712f) + 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) + reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) + close() + moveTo(3.287f, 12.712f) + 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) + 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) + close() + moveTo(19.288f, 12.712f) + 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) + 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) + close() + moveTo(19.288f, 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) + 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) + close() + moveTo(11.288f, 20.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(15.288f, 20.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) + 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) + close() + moveTo(19.288f, 20.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) + 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) + close() + moveTo(19f, 8f) + 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) + 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.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(3f, 19f) + 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) + 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) + 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.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(3.287f, 4.712f) + 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) + reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) + close() + moveTo(7.287f, 4.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) + 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) + close() + moveTo(11.288f, 4.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(3.287f, 8.712f) + 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) + reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) + close() + moveTo(3.287f, 12.712f) + 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) + 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) + close() + moveTo(19.288f, 12.712f) + 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) + 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) + close() + moveTo(19.288f, 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) + 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) + close() + moveTo(11.288f, 20.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(15.288f, 20.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) + 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) + close() + moveTo(19.288f, 20.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) + 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) + close() + moveTo(19f, 8f) + 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) + 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.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(3f, 19f) + 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) + 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) + 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(4f, 3f) + horizontalLineToRelative(15f) + curveToRelative(1.104f, 0f, 2f, 0.896f, 2f, 2f) + verticalLineToRelative(15f) + curveToRelative(0f, 0.552f, -0.448f, 1f, -1f, 1f) + horizontalLineTo(5f) + curveToRelative(-1.104f, 0f, -2f, -0.896f, -2f, -2f) + verticalLineTo(4f) + curveToRelative(0f, -0.552f, 0.448f, -1f, 1f, -1f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSaw.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSaw.kt new file mode 100644 index 0000000..674e460 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSaw.kt @@ -0,0 +1,148 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSearch.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSearch.kt new file mode 100644 index 0000000..77f1322 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSearch.kt @@ -0,0 +1,291 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Rounded.ImageSearch: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ImageSearch", + 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(164f) + quadToRelative(20f, 0f, 31f, 17f) + reflectiveQuadToRelative(3f, 37f) + quadToRelative(-7f, 21f, -10.5f, 42.5f) + reflectiveQuadTo(384f, 260f) + quadToRelative(0f, 109f, 75.5f, 184.5f) + reflectiveQuadTo(642f, 520f) + quadToRelative(21f, 0f, 40.5f, -3f) + reflectiveQuadToRelative(38.5f, -9f) + lineToRelative(96f, 96f) + quadToRelative(11f, 11f, 17f, 25.5f) + reflectiveQuadToRelative(6f, 30.5f) + verticalLineToRelative(100f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 840f) + lineTo(200f, 840f) + close() + moveTo(834f, 508f) + lineTo(738f, 412f) + quadToRelative(-21f, 14f, -45f, 21f) + reflectiveQuadToRelative(-51f, 7f) + quadToRelative(-74f, 0f, -126f, -52.5f) + reflectiveQuadTo(464f, 260f) + quadToRelative(0f, -75f, 52.5f, -127.5f) + reflectiveQuadTo(644f, 80f) + quadToRelative(75f, 0f, 127.5f, 52.5f) + reflectiveQuadTo(824f, 260f) + quadToRelative(0f, 27f, -8f, 52f) + reflectiveQuadToRelative(-20f, 46f) + lineToRelative(94f, 94f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + close() + moveTo(644f, 360f) + quadToRelative(42f, 0f, 71f, -29f) + reflectiveQuadToRelative(29f, -71f) + quadToRelative(0f, -42f, -29f, -71f) + reflectiveQuadToRelative(-71f, -29f) + quadToRelative(-42f, 0f, -71f, 29f) + reflectiveQuadToRelative(-29f, 71f) + quadToRelative(0f, 42f, 29f, 71f) + reflectiveQuadToRelative(71f, 29f) + close() + moveTo(450f, 640f) + lineToRelative(-66f, -88f) + quadToRelative(-9f, -12f, -24f, -12f) + reflectiveQuadToRelative(-24f, 12f) + lineToRelative(-72f, 96f) + quadToRelative(-8f, 10f, -2f, 21f) + reflectiveQuadToRelative(18f, 11f) + horizontalLineToRelative(400f) + quadToRelative(12f, 0f, 18f, -11f) + reflectiveQuadToRelative(-2f, -21f) + lineToRelative(-99f, -132f) + quadToRelative(-11f, -2f, -22.5f, -5f) + reflectiveQuadToRelative(-22.5f, -7f) + lineTo(450f, 640f) + close() + } + }.build() +} + +val Icons.Outlined.ImageSearch: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.ImageSearch", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(450f, 640f) + lineToRelative(-66f, -88f) + quadToRelative(-9f, -12f, -24f, -12f) + reflectiveQuadToRelative(-24f, 12f) + lineToRelative(-72f, 96f) + quadToRelative(-8f, 10f, -2f, 21f) + reflectiveQuadToRelative(18f, 11f) + horizontalLineToRelative(400f) + quadToRelative(12f, 0f, 18f, -11f) + reflectiveQuadToRelative(-2f, -21f) + lineToRelative(-99f, -132f) + quadToRelative(-11f, -2f, -22.5f, -5f) + reflectiveQuadToRelative(-22.5f, -7f) + lineTo(450f, 640f) + 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(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(400f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(360f, 200f) + lineTo(200f, 200f) + verticalLineToRelative(560f) + horizontalLineToRelative(560f) + verticalLineToRelative(-133f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 587f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 627f) + verticalLineToRelative(133f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 840f) + lineTo(200f, 840f) + close() + moveTo(480f, 480f) + close() + moveTo(642f, 440f) + quadToRelative(-74f, 0f, -126f, -52.5f) + reflectiveQuadTo(464f, 260f) + quadToRelative(0f, -75f, 52.5f, -127.5f) + reflectiveQuadTo(644f, 80f) + quadToRelative(75f, 0f, 127.5f, 52.5f) + reflectiveQuadTo(824f, 260f) + quadToRelative(0f, 27f, -8f, 52f) + reflectiveQuadToRelative(-20f, 46f) + lineToRelative(94f, 94f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineToRelative(-96f, -96f) + quadToRelative(-21f, 14f, -45f, 21f) + reflectiveQuadToRelative(-51f, 7f) + close() + moveTo(644f, 360f) + quadToRelative(42f, 0f, 71f, -29f) + reflectiveQuadToRelative(29f, -71f) + quadToRelative(0f, -42f, -29f, -71f) + reflectiveQuadToRelative(-71f, -29f) + quadToRelative(-42f, 0f, -71f, 29f) + reflectiveQuadToRelative(-29f, 71f) + quadToRelative(0f, 42f, 29f, 71f) + reflectiveQuadToRelative(71f, 29f) + close() + } + }.build() +} + +val Icons.TwoTone.ImageSearch: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.ImageSearch", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(8.237f, 5.573f) + horizontalLineToRelative(-3.966f) + verticalLineToRelative(14f) + horizontalLineToRelative(13.966f) + verticalLineToRelative(-3.325f) + 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) + curveToRelative(0.012f, 0f, 0.022f, 0.006f, 0.034f, 0.006f) + verticalLineToRelative(-2.797f) + lineToRelative(-1.584f, -1.584f) + curveToRelative(-0.35f, 0.233f, -0.725f, 0.408f, -1.125f, 0.525f) + curveToRelative(-0.4f, 0.117f, -0.825f, 0.175f, -1.275f, 0.175f) + curveToRelative(-1.233f, 0f, -2.283f, -0.438f, -3.15f, -1.313f) + curveToRelative(-0.867f, -0.875f, -1.3f, -1.938f, -1.3f, -3.188f) + curveToRelative(0f, -0.939f, 0.247f, -1.772f, 0.742f, -2.5f) + horizontalLineToRelative(-2.342f) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(10.521f, 16.573f) + lineToRelative(-1.65f, -2.2f) + curveToRelative(-0.15f, -0.2f, -0.35f, -0.3f, -0.6f, -0.3f) + reflectiveCurveToRelative(-0.45f, 0.1f, -0.6f, 0.3f) + lineToRelative(-1.8f, 2.4f) + 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.475f, -3.3f) + curveToRelative(-0.183f, -0.033f, -0.371f, -0.075f, -0.563f, -0.125f) + reflectiveCurveToRelative(-0.379f, -0.108f, -0.563f, -0.175f) + lineToRelative(-2.55f, 3.4f) + close() + moveTo(4.271f, 21.573f) + 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(5.573f) + 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(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) + horizontalLineToRelative(-4f) + verticalLineToRelative(14f) + horizontalLineToRelative(14f) + verticalLineToRelative(-3.325f) + 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.325f) + curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) + reflectiveCurveToRelative(-0.863f, 0.587f, -1.413f, 0.587f) + horizontalLineTo(4.271f) + close() + moveTo(15.321f, 11.573f) + curveToRelative(-1.233f, 0f, -2.283f, -0.438f, -3.15f, -1.313f) + curveToRelative(-0.867f, -0.875f, -1.3f, -1.938f, -1.3f, -3.188f) + reflectiveCurveToRelative(0.438f, -2.313f, 1.313f, -3.188f) + reflectiveCurveToRelative(1.938f, -1.313f, 3.188f, -1.313f) + reflectiveCurveToRelative(2.313f, 0.438f, 3.188f, 1.313f) + reflectiveCurveToRelative(1.313f, 1.938f, 1.313f, 3.188f) + curveToRelative(0f, 0.45f, -0.067f, 0.883f, -0.2f, 1.3f) + reflectiveCurveToRelative(-0.3f, 0.8f, -0.5f, 1.15f) + lineToRelative(2.35f, 2.35f) + curveToRelative(0.183f, 0.183f, 0.275f, 0.417f, 0.275f, 0.7f) + reflectiveCurveToRelative(-0.092f, 0.517f, -0.275f, 0.7f) + curveToRelative(-0.183f, 0.183f, -0.417f, 0.275f, -0.7f, 0.275f) + reflectiveCurveToRelative(-0.517f, -0.092f, -0.7f, -0.275f) + lineToRelative(-2.4f, -2.4f) + curveToRelative(-0.35f, 0.233f, -0.725f, 0.408f, -1.125f, 0.525f) + reflectiveCurveToRelative(-0.825f, 0.175f, -1.275f, 0.175f) + close() + moveTo(15.371f, 9.573f) + curveToRelative(0.7f, 0f, 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) + reflectiveCurveToRelative(-1.075f, -0.725f, -1.775f, -0.725f) + 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) + reflectiveCurveToRelative(1.075f, 0.725f, 1.775f, 0.725f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSticky.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSticky.kt new file mode 100644 index 0000000..b2a8501 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSticky.kt @@ -0,0 +1,88 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSync.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSync.kt new file mode 100644 index 0000000..7dcd489 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSync.kt @@ -0,0 +1,137 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageText.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageText.kt new file mode 100644 index 0000000..0878463 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageText.kt @@ -0,0 +1,201 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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.Black), 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.Black), 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.Black), 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.Black), 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.Black)) { + 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.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(3f, 7f) + horizontalLineToRelative(10f) + verticalLineToRelative(10f) + horizontalLineToRelative(-10f) + close() + } + path(fill = SolidColor(Color.Black)) { + 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.Black)) { + moveTo(3f, 17f) + verticalLineTo(7f) + verticalLineToRelative(10f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(17f, 5.175f) + horizontalLineToRelative(6f) + verticalLineToRelative(3f) + horizontalLineToRelative(-6f) + verticalLineToRelative(-3f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(17f, 15.824f) + horizontalLineToRelative(6f) + verticalLineToRelative(3f) + horizontalLineToRelative(-6f) + verticalLineToRelative(-3f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(16.84f, 10.5f) + horizontalLineToRelative(6f) + verticalLineToRelative(3f) + horizontalLineToRelative(-6f) + verticalLineToRelative(-3f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageToText.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageToText.kt new file mode 100644 index 0000000..eadde80 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageToText.kt @@ -0,0 +1,107 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageToolboxBroken.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageToolboxBroken.kt new file mode 100644 index 0000000..05d8c72 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageToolboxBroken.kt @@ -0,0 +1,201 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageTooltip.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageTooltip.kt new file mode 100644 index 0000000..97dbf1a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageTooltip.kt @@ -0,0 +1,123 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageWeight.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageWeight.kt new file mode 100644 index 0000000..8e85aad --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageWeight.kt @@ -0,0 +1,179 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.ImageWeight: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.ImageWeight", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(6f, 19f) + horizontalLineToRelative(12f) + lineToRelative(-1.425f, -10f) + horizontalLineTo(7.425f) + lineToRelative(-1.425f, 10f) + close() + moveTo(12f, 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) + 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(14.825f, 7f) + 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.463f, 1.587f) + reflectiveCurveToRelative(-0.896f, 0.688f, -1.513f, 0.688f) + horizontalLineTo(6f) + curveToRelative(-0.617f, 0f, -1.121f, -0.229f, -1.513f, -0.688f) + reflectiveCurveToRelative(-0.546f, -0.988f, -0.463f, -1.587f) + lineToRelative(1.425f, -10f) + curveToRelative(0.083f, -0.483f, 0.308f, -0.892f, 0.675f, -1.225f) + curveToRelative(0.367f, -0.333f, 0.8f, -0.5f, 1.3f, -0.5f) + horizontalLineToRelative(1.75f) + curveToRelative(-0.05f, -0.167f, -0.092f, -0.329f, -0.125f, -0.488f) + reflectiveCurveToRelative(-0.05f, -0.329f, -0.05f, -0.512f) + curveToRelative(0f, -0.833f, 0.292f, -1.542f, 0.875f, -2.125f) + reflectiveCurveToRelative(1.292f, -0.875f, 2.125f, -0.875f) + curveToRelative(0.833f, 0f, 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.512f) + reflectiveCurveToRelative(-0.075f, 0.321f, -0.125f, 0.488f) + close() + moveTo(6f, 19f) + horizontalLineToRelative(12f) + horizontalLineTo(6f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(11.511f, 15.587f) + 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() + } + }.build() +} + +val Icons.TwoTone.ImageWeight: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.ImageWeight", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + 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) + reflectiveCurveToRelative(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) + reflectiveCurveToRelative(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) + reflectiveCurveToRelative(0.896f, 0.688f, 1.513f, 0.688f) + horizontalLineToRelative(12f) + curveToRelative(0.617f, 0f, 1.121f, -0.229f, 1.513f, -0.688f) + reflectiveCurveToRelative(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) + reflectiveCurveToRelative(-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) + reflectiveCurveToRelative(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.Black)) { + moveTo(11.511f, 15.587f) + 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), + 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() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImagesMode.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImagesMode.kt new file mode 100644 index 0000000..47bec55 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImagesMode.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImagesearchRoller.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImagesearchRoller.kt new file mode 100644 index 0000000..d7acbff --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImagesearchRoller.kt @@ -0,0 +1,155 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Info.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Info.kt new file mode 100644 index 0000000..b12cbad --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Info.kt @@ -0,0 +1,143 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Info: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Info", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(508.5f, 668.5f) + quadTo(520f, 657f, 520f, 640f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 480f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 680f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + close() + moveTo(508.5f, 348.5f) + quadTo(520f, 337f, 520f, 320f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(497f, 280f, 480f, 280f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(440f, 303f, 440f, 320f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(463f, 360f, 480f, 360f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + } + }.build() +} + +val Icons.Outlined.Info: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Info", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(508.5f, 668.5f) + quadTo(520f, 657f, 520f, 640f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 480f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 680f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + close() + moveTo(508.5f, 348.5f) + quadTo(520f, 337f, 520f, 320f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(497f, 280f, 480f, 280f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(440f, 303f, 440f, 320f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(463f, 360f, 480f, 360f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Interests.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Interests.kt new file mode 100644 index 0000000..17ad417 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Interests.kt @@ -0,0 +1,97 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Rounded.Interests: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Interests", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(113f, 381f) + lineToRelative(132f, -238f) + quadToRelative(6f, -11f, 15f, -16f) + reflectiveQuadToRelative(20f, -5f) + quadToRelative(11f, 0f, 20f, 5f) + reflectiveQuadToRelative(15f, 16f) + lineToRelative(132f, 238f) + quadToRelative(5f, 10f, 4.5f, 20f) + reflectiveQuadToRelative(-5.5f, 19f) + quadToRelative(-5f, 9f, -14f, 14.5f) + reflectiveQuadToRelative(-20f, 5.5f) + lineTo(148f, 440f) + quadToRelative(-11f, 0f, -20f, -5.5f) + reflectiveQuadTo(114f, 420f) + quadToRelative(-5f, -9f, -5.5f, -19f) + reflectiveQuadToRelative(4.5f, -20f) + close() + moveTo(167f, 793f) + quadToRelative(-47f, -47f, -47f, -113f) + reflectiveQuadToRelative(47f, -113f) + quadToRelative(47f, -47f, 113f, -47f) + reflectiveQuadToRelative(113f, 47f) + quadToRelative(47f, 47f, 47f, 113f) + reflectiveQuadToRelative(-47f, 113f) + quadToRelative(-47f, 47f, -113f, 47f) + reflectiveQuadToRelative(-113f, -47f) + close() + moveTo(520f, 800f) + verticalLineToRelative(-240f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(560f, 520f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 560f) + verticalLineToRelative(240f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + lineTo(560f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(520f, 800f) + close() + moveTo(654f, 418f) + lineTo(601f, 373f) + quadToRelative(-69f, -58f, -95f, -91.5f) + reflectiveQuadTo(480f, 207f) + quadToRelative(0f, -45f, 31.5f, -76f) + reflectiveQuadToRelative(78.5f, -31f) + quadToRelative(27f, 0f, 50.5f, 12.5f) + reflectiveQuadTo(680f, 147f) + quadToRelative(16f, -22f, 39.5f, -34.5f) + reflectiveQuadTo(770f, 100f) + quadToRelative(47f, 0f, 78.5f, 31f) + reflectiveQuadToRelative(31.5f, 76f) + quadToRelative(0f, 41f, -26f, 74.5f) + reflectiveQuadTo(759f, 373f) + lineToRelative(-53f, 45f) + quadToRelative(-11f, 10f, -26f, 10f) + reflectiveQuadToRelative(-26f, -10f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Interface.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Interface.kt new file mode 100644 index 0000000..413c930 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Interface.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/InvertColors.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/InvertColors.kt new file mode 100644 index 0000000..ad5813c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/InvertColors.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.InvertColors: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.InvertColors", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 840f) + quadToRelative(-133f, 0f, -226.5f, -92f) + reflectiveQuadTo(160f, 525f) + quadToRelative(0f, -66f, 25f, -122.5f) + reflectiveQuadTo(254f, 302f) + lineToRelative(184f, -181f) + quadToRelative(9f, -8f, 20f, -12.5f) + reflectiveQuadToRelative(22f, -4.5f) + quadToRelative(11f, 0f, 22f, 4.5f) + reflectiveQuadToRelative(20f, 12.5f) + lineToRelative(184f, 181f) + quadToRelative(44f, 44f, 69f, 100.5f) + reflectiveQuadTo(800f, 525f) + quadToRelative(0f, 131f, -93.5f, 223f) + reflectiveQuadTo(480f, 840f) + close() + moveTo(480f, 760f) + verticalLineToRelative(-568f) + lineTo(310f, 360f) + quadToRelative(-35f, 33f, -52.5f, 75f) + reflectiveQuadTo(240f, 525f) + quadToRelative(0f, 97f, 70f, 166f) + reflectiveQuadToRelative(170f, 69f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/IosShare.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/IosShare.kt new file mode 100644 index 0000000..96ac7b7 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/IosShare.kt @@ -0,0 +1,88 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.IosShare: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.IosShare", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(240f, 920f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 840f) + verticalLineToRelative(-440f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(240f, 320f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 360f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 400f) + horizontalLineToRelative(-80f) + verticalLineToRelative(440f) + horizontalLineToRelative(480f) + verticalLineToRelative(-440f) + horizontalLineToRelative(-80f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 360f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 320f) + horizontalLineToRelative(80f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(800f, 400f) + verticalLineToRelative(440f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 920f) + lineTo(240f, 920f) + close() + moveTo(440f, 193f) + lineTo(404f, 229f) + quadToRelative(-12f, 12f, -28f, 11.5f) + reflectiveQuadTo(348f, 228f) + quadToRelative(-11f, -12f, -11.5f, -28f) + reflectiveQuadToRelative(11.5f, -28f) + lineToRelative(104f, -104f) + quadToRelative(12f, -12f, 28f, -12f) + reflectiveQuadToRelative(28f, 12f) + lineToRelative(104f, 104f) + quadToRelative(11f, 11f, 11f, 27.5f) + reflectiveQuadTo(612f, 228f) + quadToRelative(-12f, 12f, -28.5f, 12f) + reflectiveQuadTo(555f, 228f) + lineToRelative(-35f, -35f) + verticalLineToRelative(407f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 640f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 600f) + verticalLineToRelative(-407f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Jpg.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Jpg.kt new file mode 100644 index 0000000..d68cb01 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Jpg.kt @@ -0,0 +1,96 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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.Black), 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Jxl.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Jxl.kt new file mode 100644 index 0000000..4139dc6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Jxl.kt @@ -0,0 +1,119 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/KeyVariant.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/KeyVariant.kt new file mode 100644 index 0000000..a478033 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/KeyVariant.kt @@ -0,0 +1,204 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/KeyVertical.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/KeyVertical.kt new file mode 100644 index 0000000..1d9e855 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/KeyVertical.kt @@ -0,0 +1,79 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Keyboard.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Keyboard.kt new file mode 100644 index 0000000..9349fec --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Keyboard.kt @@ -0,0 +1,174 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Keyboard: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Keyboard", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 760f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(80f, 680f) + verticalLineToRelative(-400f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(160f, 200f) + horizontalLineToRelative(640f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 280f) + verticalLineToRelative(400f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 760f) + lineTo(160f, 760f) + close() + moveTo(160f, 680f) + horizontalLineToRelative(640f) + verticalLineToRelative(-400f) + lineTo(160f, 280f) + verticalLineToRelative(400f) + close() + moveTo(360f, 640f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(640f, 600f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(600f, 560f) + lineTo(360f, 560f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(320f, 600f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(360f, 640f) + close() + moveTo(160f, 680f) + verticalLineToRelative(-400f) + verticalLineToRelative(400f) + close() + moveTo(240f, 400f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(280f, 360f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(240f, 320f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(200f, 360f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(240f, 400f) + close() + moveTo(360f, 400f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(400f, 360f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(360f, 320f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(320f, 360f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(360f, 400f) + close() + moveTo(480f, 400f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 360f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 320f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 360f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 400f) + 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(720f, 400f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(760f, 360f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(720f, 320f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(680f, 360f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(720f, 400f) + close() + moveTo(240f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(280f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(240f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(200f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(240f, 520f) + close() + moveTo(360f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(400f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(360f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(320f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(360f, 520f) + close() + moveTo(480f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 520f) + close() + moveTo(600f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(640f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(600f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(560f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(600f, 520f) + close() + moveTo(720f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(760f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(720f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(680f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(720f, 520f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/KeyboardArrowDown.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/KeyboardArrowDown.kt new file mode 100644 index 0000000..f8124c5 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/KeyboardArrowDown.kt @@ -0,0 +1,56 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.KeyboardArrowDown: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.KeyboardArrowDown", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(465f, 596.5f) + quadToRelative(-7f, -2.5f, -13f, -8.5f) + lineTo(268f, 404f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(156f, 156f) + lineToRelative(156f, -156f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineTo(508f, 588f) + quadToRelative(-6f, 6f, -13f, 8.5f) + reflectiveQuadToRelative(-15f, 2.5f) + quadToRelative(-8f, 0f, -15f, -2.5f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Label.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Label.kt new file mode 100644 index 0000000..d33cb74 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Label.kt @@ -0,0 +1,56 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Label: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Label", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(807f, 526f) + lineTo(666f, 726f) + quadToRelative(-11f, 16f, -28.5f, 25f) + reflectiveQuadToRelative(-37.5f, 9f) + lineTo(200f, 760f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 680f) + verticalLineToRelative(-400f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 200f) + horizontalLineToRelative(400f) + quadToRelative(20f, 0f, 37.5f, 9f) + reflectiveQuadToRelative(28.5f, 25f) + lineToRelative(141f, 200f) + quadToRelative(15f, 21f, 15f, 46f) + reflectiveQuadToRelative(-15f, 46f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LabelPercent.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LabelPercent.kt new file mode 100644 index 0000000..c155843 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LabelPercent.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Landscape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Landscape.kt new file mode 100644 index 0000000..4785edc --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Landscape.kt @@ -0,0 +1,170 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Language.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Language.kt new file mode 100644 index 0000000..5ccff52 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Language.kt @@ -0,0 +1,134 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Language: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Language", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-82f, 0f, -155f, -31.5f) + reflectiveQuadToRelative(-127.5f, -86f) + quadTo(143f, 708f, 111.5f, 635f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -155.5f) + reflectiveQuadToRelative(86f, -127f) + quadTo(252f, 143f, 325f, 111.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 155.5f, 31.5f) + reflectiveQuadToRelative(127f, 86f) + quadToRelative(54.5f, 54.5f, 86f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 82f, -31.5f, 155f) + reflectiveQuadToRelative(-86f, 127.5f) + quadToRelative(-54.5f, 54.5f, -127f, 86f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 798f) + quadToRelative(26f, -36f, 45f, -75f) + reflectiveQuadToRelative(31f, -83f) + lineTo(404f, 640f) + quadToRelative(12f, 44f, 31f, 83f) + reflectiveQuadToRelative(45f, 75f) + close() + moveTo(376f, 782f) + quadToRelative(-18f, -33f, -31.5f, -68.5f) + reflectiveQuadTo(322f, 640f) + lineTo(204f, 640f) + quadToRelative(29f, 50f, 72.5f, 87f) + reflectiveQuadToRelative(99.5f, 55f) + close() + moveTo(584f, 782f) + quadToRelative(56f, -18f, 99.5f, -55f) + reflectiveQuadToRelative(72.5f, -87f) + lineTo(638f, 640f) + quadToRelative(-9f, 38f, -22.5f, 73.5f) + reflectiveQuadTo(584f, 782f) + close() + moveTo(170f, 560f) + horizontalLineToRelative(136f) + quadToRelative(-3f, -20f, -4.5f, -39.5f) + reflectiveQuadTo(300f, 480f) + quadToRelative(0f, -21f, 1.5f, -40.5f) + reflectiveQuadTo(306f, 400f) + lineTo(170f, 400f) + quadToRelative(-5f, 20f, -7.5f, 39.5f) + reflectiveQuadTo(160f, 480f) + quadToRelative(0f, 21f, 2.5f, 40.5f) + reflectiveQuadTo(170f, 560f) + close() + moveTo(386f, 560f) + horizontalLineToRelative(188f) + quadToRelative(3f, -20f, 4.5f, -39.5f) + reflectiveQuadTo(580f, 480f) + quadToRelative(0f, -21f, -1.5f, -40.5f) + reflectiveQuadTo(574f, 400f) + lineTo(386f, 400f) + quadToRelative(-3f, 20f, -4.5f, 39.5f) + reflectiveQuadTo(380f, 480f) + quadToRelative(0f, 21f, 1.5f, 40.5f) + reflectiveQuadTo(386f, 560f) + close() + moveTo(654f, 560f) + horizontalLineToRelative(136f) + quadToRelative(5f, -20f, 7.5f, -39.5f) + reflectiveQuadTo(800f, 480f) + quadToRelative(0f, -21f, -2.5f, -40.5f) + reflectiveQuadTo(790f, 400f) + lineTo(654f, 400f) + quadToRelative(3f, 20f, 4.5f, 39.5f) + reflectiveQuadTo(660f, 480f) + quadToRelative(0f, 21f, -1.5f, 40.5f) + reflectiveQuadTo(654f, 560f) + close() + moveTo(638f, 320f) + horizontalLineToRelative(118f) + quadToRelative(-29f, -50f, -72.5f, -87f) + reflectiveQuadTo(584f, 178f) + quadToRelative(18f, 33f, 31.5f, 68.5f) + reflectiveQuadTo(638f, 320f) + close() + moveTo(404f, 320f) + horizontalLineToRelative(152f) + quadToRelative(-12f, -44f, -31f, -83f) + reflectiveQuadToRelative(-45f, -75f) + quadToRelative(-26f, 36f, -45f, 75f) + reflectiveQuadToRelative(-31f, 83f) + close() + moveTo(204f, 320f) + horizontalLineToRelative(118f) + quadToRelative(9f, -38f, 22.5f, -73.5f) + reflectiveQuadTo(376f, 178f) + quadToRelative(-56f, 18f, -99.5f, 55f) + reflectiveQuadTo(204f, 320f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Lasso.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Lasso.kt new file mode 100644 index 0000000..2eda2dc --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Lasso.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LastPage.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LastPage.kt new file mode 100644 index 0000000..2396bd0 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LastPage.kt @@ -0,0 +1,68 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.LastPage: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.LastPage", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(408f, 480f) + lineTo(252f, 324f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(184f, 184f) + quadToRelative(6f, 6f, 8.5f, 13f) + reflectiveQuadToRelative(2.5f, 15f) + quadToRelative(0f, 8f, -2.5f, 15f) + reflectiveQuadToRelative(-8.5f, 13f) + lineTo(308f, 692f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + lineToRelative(156f, -156f) + close() + moveTo(708.5f, 251.5f) + quadTo(720f, 263f, 720f, 280f) + verticalLineToRelative(400f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(680f, 720f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(640f, 680f) + verticalLineToRelative(-400f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(680f, 240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Latitude.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Latitude.kt new file mode 100644 index 0000000..3dd6421 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Latitude.kt @@ -0,0 +1,42 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Layers.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Layers.kt new file mode 100644 index 0000000..ab48efc --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Layers.kt @@ -0,0 +1,119 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LayersSearchOutline.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LayersSearchOutline.kt new file mode 100644 index 0000000..0de1600 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LayersSearchOutline.kt @@ -0,0 +1,70 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LetterO.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LetterO.kt new file mode 100644 index 0000000..b6c6dd9 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LetterO.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LetterS.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LetterS.kt new file mode 100644 index 0000000..00c6d01 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LetterS.kt @@ -0,0 +1,61 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/License.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/License.kt new file mode 100644 index 0000000..27585e8 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/License.kt @@ -0,0 +1,88 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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(395f, 485f) + 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(480f, 840f) + lineTo(293f, 902f) + quadToRelative(-20f, 7f, -36.5f, -5f) + reflectiveQuadTo(240f, 865f) + verticalLineToRelative(-254f) + quadToRelative(-38f, -42f, -59f, -96f) + reflectiveQuadToRelative(-21f, -115f) + quadToRelative(0f, -134f, 93f, -227f) + reflectiveQuadToRelative(227f, -93f) + quadToRelative(134f, 0f, 227f, 93f) + reflectiveQuadToRelative(93f, 227f) + quadToRelative(0f, 61f, -21f, 115f) + reflectiveQuadToRelative(-59f, 96f) + verticalLineToRelative(254f) + quadToRelative(0f, 20f, -16.5f, 32f) + reflectiveQuadTo(667f, 902f) + lineToRelative(-187f, -62f) + close() + moveTo(650f, 570f) + quadToRelative(70f, -70f, 70f, -170f) + reflectiveQuadToRelative(-70f, -170f) + quadToRelative(-70f, -70f, -170f, -70f) + reflectiveQuadToRelative(-170f, 70f) + quadToRelative(-70f, 70f, -70f, 170f) + reflectiveQuadToRelative(70f, 170f) + quadToRelative(70f, 70f, 170f, 70f) + reflectiveQuadToRelative(170f, -70f) + close() + moveTo(320f, 801f) + lineToRelative(160f, -41f) + lineToRelative(160f, 41f) + verticalLineToRelative(-124f) + quadToRelative(-35f, 20f, -75.5f, 31.5f) + reflectiveQuadTo(480f, 720f) + quadToRelative(-44f, 0f, -84.5f, -11.5f) + reflectiveQuadTo(320f, 677f) + verticalLineToRelative(124f) + close() + moveTo(480f, 739f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LightMode.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LightMode.kt new file mode 100644 index 0000000..690dc23 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LightMode.kt @@ -0,0 +1,156 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.LightMode: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.LightMode", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 600f) + quadToRelative(50f, 0f, 85f, -35f) + reflectiveQuadToRelative(35f, -85f) + quadToRelative(0f, -50f, -35f, -85f) + reflectiveQuadToRelative(-85f, -35f) + quadToRelative(-50f, 0f, -85f, 35f) + reflectiveQuadToRelative(-35f, 85f) + quadToRelative(0f, 50f, 35f, 85f) + reflectiveQuadToRelative(85f, 35f) + close() + moveTo(480f, 680f) + quadToRelative(-83f, 0f, -141.5f, -58.5f) + reflectiveQuadTo(280f, 480f) + quadToRelative(0f, -83f, 58.5f, -141.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(83f, 0f, 141.5f, 58.5f) + reflectiveQuadTo(680f, 480f) + quadToRelative(0f, 83f, -58.5f, 141.5f) + reflectiveQuadTo(480f, 680f) + close() + moveTo(80f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(40f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(80f, 440f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 520f) + lineTo(80f, 520f) + close() + moveTo(800f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 440f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(920f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(880f, 520f) + horizontalLineToRelative(-80f) + close() + moveTo(480f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 160f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 80f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 200f) + close() + moveTo(480f, 920f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 880f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 800f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 920f) + close() + moveTo(226f, 282f) + lineToRelative(-43f, -42f) + quadToRelative(-12f, -11f, -11.5f, -28f) + reflectiveQuadToRelative(11.5f, -29f) + quadToRelative(12f, -12f, 29f, -12f) + reflectiveQuadToRelative(28f, 12f) + lineToRelative(42f, 43f) + quadToRelative(11f, 12f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 12f, -27.5f, 11.5f) + reflectiveQuadTo(226f, 282f) + close() + moveTo(720f, 777f) + lineTo(678f, 734f) + quadToRelative(-11f, -12f, -11f, -28.5f) + reflectiveQuadToRelative(11f, -27.5f) + quadToRelative(11f, -12f, 27.5f, -11.5f) + reflectiveQuadTo(734f, 678f) + lineToRelative(43f, 42f) + quadToRelative(12f, 11f, 11.5f, 28f) + reflectiveQuadTo(777f, 777f) + quadToRelative(-12f, 12f, -29f, 12f) + reflectiveQuadToRelative(-28f, -12f) + close() + moveTo(678f, 282f) + quadToRelative(-12f, -11f, -11.5f, -27.5f) + reflectiveQuadTo(678f, 226f) + lineToRelative(42f, -43f) + quadToRelative(11f, -12f, 28f, -11.5f) + reflectiveQuadToRelative(29f, 11.5f) + quadToRelative(12f, 12f, 12f, 29f) + reflectiveQuadToRelative(-12f, 28f) + lineToRelative(-43f, 42f) + quadToRelative(-12f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + close() + moveTo(183f, 777f) + quadToRelative(-12f, -12f, -12f, -29f) + reflectiveQuadToRelative(12f, -28f) + lineToRelative(43f, -42f) + quadToRelative(12f, -11f, 28.5f, -11f) + reflectiveQuadToRelative(27.5f, 11f) + quadToRelative(12f, 11f, 11.5f, 27.5f) + reflectiveQuadTo(282f, 734f) + lineToRelative(-42f, 43f) + quadToRelative(-11f, 12f, -28f, 11.5f) + reflectiveQuadTo(183f, 777f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Lightbulb.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Lightbulb.kt new file mode 100644 index 0000000..f234718 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Lightbulb.kt @@ -0,0 +1,196 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Rounded.Lightbulb: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Lightbulb", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(400f, 800f) + horizontalLineToRelative(160f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(360f, 760f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(320f, 720f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(360f, 680f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(640f, 720f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(600f, 760f) + lineTo(360f, 760f) + close() + moveTo(330f, 640f) + quadToRelative(-69f, -41f, -109.5f, -110f) + reflectiveQuadTo(180f, 380f) + quadToRelative(0f, -125f, 87.5f, -212.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(125f, 0f, 212.5f, 87.5f) + reflectiveQuadTo(780f, 380f) + quadToRelative(0f, 81f, -40.5f, 150f) + reflectiveQuadTo(630f, 640f) + lineTo(330f, 640f) + close() + } + }.build() +} + +val Icons.TwoTone.Lightbulb: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.Lightbulb", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(423.5f, 856.5f) + quadTo(400f, 833f, 400f, 800f) + horizontalLineToRelative(160f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(480f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + close() + moveTo(360f, 760f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(320f, 720f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(360f, 680f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(640f, 720f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(600f, 760f) + lineTo(360f, 760f) + close() + moveTo(330f, 640f) + quadToRelative(-69f, -41f, -109.5f, -110f) + reflectiveQuadTo(180f, 380f) + quadToRelative(0f, -125f, 87.5f, -212.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(125f, 0f, 212.5f, 87.5f) + reflectiveQuadTo(780f, 380f) + quadToRelative(0f, 81f, -40.5f, 150f) + reflectiveQuadTo(630f, 640f) + lineTo(330f, 640f) + close() + moveTo(354f, 560f) + horizontalLineToRelative(252f) + quadToRelative(45f, -32f, 69.5f, -79f) + reflectiveQuadTo(700f, 380f) + quadToRelative(0f, -92f, -64f, -156f) + reflectiveQuadToRelative(-156f, -64f) + quadToRelative(-92f, 0f, -156f, 64f) + reflectiveQuadToRelative(-64f, 156f) + quadToRelative(0f, 54f, 24.5f, 101f) + reflectiveQuadToRelative(69.5f, 79f) + close() + moveTo(480f, 560f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f + ) { + moveTo(330f, 640f) + quadToRelative(-69f, -41f, -109.5f, -110f) + reflectiveQuadTo(180f, 380f) + quadToRelative(0f, -125f, 87.5f, -212.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(125f, 0f, 212.5f, 87.5f) + reflectiveQuadTo(780f, 380f) + quadToRelative(0f, 81f, -40.5f, 150f) + reflectiveQuadTo(630f, 640f) + lineTo(330f, 640f) + close() + } + }.build() +} + +val Icons.Outlined.Lightbulb: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Lightbulb", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(423.5f, 856.5f) + quadTo(400f, 833f, 400f, 800f) + horizontalLineToRelative(160f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(480f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + close() + moveTo(360f, 760f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(320f, 720f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(360f, 680f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(640f, 720f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(600f, 760f) + lineTo(360f, 760f) + close() + moveTo(330f, 640f) + quadToRelative(-69f, -41f, -109.5f, -110f) + reflectiveQuadTo(180f, 380f) + quadToRelative(0f, -125f, 87.5f, -212.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(125f, 0f, 212.5f, 87.5f) + reflectiveQuadTo(780f, 380f) + quadToRelative(0f, 81f, -40.5f, 150f) + reflectiveQuadTo(630f, 640f) + lineTo(330f, 640f) + close() + moveTo(354f, 560f) + horizontalLineToRelative(252f) + quadToRelative(45f, -32f, 69.5f, -79f) + reflectiveQuadTo(700f, 380f) + quadToRelative(0f, -92f, -64f, -156f) + reflectiveQuadToRelative(-156f, -64f) + quadToRelative(-92f, 0f, -156f, 64f) + reflectiveQuadToRelative(-64f, 156f) + quadToRelative(0f, 54f, 24.5f, 101f) + reflectiveQuadToRelative(69.5f, 79f) + close() + moveTo(480f, 560f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Line.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Line.kt new file mode 100644 index 0000000..b89a69f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Line.kt @@ -0,0 +1,56 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LineArrow.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LineArrow.kt new file mode 100644 index 0000000..0bacb36 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LineArrow.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LineDoubleArrow.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LineDoubleArrow.kt new file mode 100644 index 0000000..cfa4f5a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LineDoubleArrow.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LineWeight.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LineWeight.kt new file mode 100644 index 0000000..e0ddc5b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LineWeight.kt @@ -0,0 +1,90 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.LineWeight: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.LineWeight", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(140f, 800f) + quadToRelative(-8f, 0f, -14f, -6f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(0f, -8f, 6f, -14f) + reflectiveQuadToRelative(14f, -6f) + horizontalLineToRelative(680f) + quadToRelative(8f, 0f, 14f, 6f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(0f, 8f, -6f, 14f) + reflectiveQuadToRelative(-14f, 6f) + lineTo(140f, 800f) + close() + moveTo(160f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 600f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 680f) + lineTo(160f, 680f) + close() + moveTo(160f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 480f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 400f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 440f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 520f) + lineTo(160f, 520f) + close() + moveTo(160f, 320f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 280f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 160f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 200f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 320f) + lineTo(160f, 320f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LinearScale.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LinearScale.kt new file mode 100644 index 0000000..5ab26a9 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LinearScale.kt @@ -0,0 +1,68 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.LinearScale: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.LinearScale", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(680f, 680f) + quadToRelative(-72f, 0f, -127f, -45.5f) + reflectiveQuadTo(484f, 520f) + lineTo(272f, 520f) + quadToRelative(-12f, 27f, -37f, 43.5f) + reflectiveQuadTo(180f, 580f) + quadToRelative(-42f, 0f, -71f, -29f) + reflectiveQuadToRelative(-29f, -71f) + quadToRelative(0f, -42f, 29f, -71f) + reflectiveQuadToRelative(71f, -29f) + quadToRelative(30f, 0f, 55f, 16.5f) + reflectiveQuadToRelative(37f, 43.5f) + horizontalLineToRelative(212f) + quadToRelative(14f, -69f, 69f, -114.5f) + reflectiveQuadTo(680f, 280f) + quadToRelative(83f, 0f, 141.5f, 58.5f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -58.5f, 141.5f) + reflectiveQuadTo(680f, 680f) + close() + moveTo(680f, 600f) + quadToRelative(50f, 0f, 85f, -35f) + reflectiveQuadToRelative(35f, -85f) + quadToRelative(0f, -50f, -35f, -85f) + reflectiveQuadToRelative(-85f, -35f) + quadToRelative(-50f, 0f, -85f, 35f) + reflectiveQuadToRelative(-35f, 85f) + quadToRelative(0f, 50f, 35f, 85f) + reflectiveQuadToRelative(85f, 35f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Link.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Link.kt new file mode 100644 index 0000000..14d2785 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Link.kt @@ -0,0 +1,102 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Link: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Link", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(318f, 840f) + quadToRelative(-82f, 0f, -140f, -58f) + reflectiveQuadToRelative(-58f, -140f) + quadToRelative(0f, -40f, 15f, -76f) + reflectiveQuadToRelative(43f, -64f) + lineToRelative(105f, -105f) + quadToRelative(12f, -12f, 28.5f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + quadToRelative(12f, 12f, 12f, 28f) + reflectiveQuadToRelative(-12f, 28f) + lineTo(234f, 559f) + quadToRelative(-17f, 17f, -25.5f, 38.5f) + reflectiveQuadTo(200f, 642f) + quadToRelative(0f, 49f, 34.5f, 83.5f) + reflectiveQuadTo(318f, 760f) + quadToRelative(23f, 0f, 45f, -8.5f) + reflectiveQuadToRelative(39f, -25.5f) + lineToRelative(105f, -106f) + quadToRelative(12f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 12f) + quadToRelative(12f, 12f, 12f, 28f) + reflectiveQuadToRelative(-12f, 28f) + lineTo(458f, 782f) + quadToRelative(-28f, 28f, -64f, 43f) + reflectiveQuadToRelative(-76f, 15f) + close() + moveTo(368f, 592f) + quadToRelative(-12f, -12f, -12f, -28.5f) + reflectiveQuadToRelative(12f, -28.5f) + lineToRelative(167f, -167f) + quadToRelative(12f, -12f, 28.5f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + quadToRelative(12f, 12f, 12f, 28.5f) + reflectiveQuadTo(592f, 425f) + lineTo(425f, 592f) + quadToRelative(-12f, 12f, -28.5f, 12f) + reflectiveQuadTo(368f, 592f) + close() + moveTo(620f, 563f) + quadToRelative(-12f, -12f, -12f, -28f) + reflectiveQuadToRelative(12f, -28f) + lineToRelative(106f, -105f) + quadToRelative(17f, -17f, 25f, -38f) + reflectiveQuadToRelative(8f, -44f) + quadToRelative(0f, -50f, -34f, -85f) + reflectiveQuadToRelative(-84f, -35f) + quadToRelative(-23f, 0f, -44.5f, 8.5f) + reflectiveQuadTo(558f, 234f) + lineTo(453f, 340f) + quadToRelative(-12f, 12f, -28f, 12f) + reflectiveQuadToRelative(-28f, -12f) + quadToRelative(-12f, -12f, -12f, -28.5f) + reflectiveQuadToRelative(12f, -28.5f) + lineToRelative(105f, -105f) + quadToRelative(28f, -28f, 64f, -43f) + reflectiveQuadToRelative(76f, -15f) + quadToRelative(82f, 0f, 139.5f, 58f) + reflectiveQuadTo(839f, 319f) + quadToRelative(0f, 39f, -14.5f, 75f) + reflectiveQuadTo(782f, 458f) + lineTo(677f, 563f) + quadToRelative(-12f, 12f, -28.5f, 12f) + reflectiveQuadTo(620f, 563f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LocalFireDepartment.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LocalFireDepartment.kt new file mode 100644 index 0000000..5f2d6b2 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LocalFireDepartment.kt @@ -0,0 +1,147 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.LocalFireDepartment: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.LocalFireDepartment", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(253f, 787f) + quadToRelative(-93f, -93f, -93f, -227f) + quadToRelative(0f, -113f, 67f, -217f) + reflectiveQuadToRelative(184f, -182f) + quadToRelative(22f, -15f, 45.5f, -1.5f) + reflectiveQuadTo(480f, 200f) + verticalLineToRelative(52f) + quadToRelative(0f, 34f, 23.5f, 57f) + reflectiveQuadToRelative(57.5f, 23f) + quadToRelative(17f, 0f, 32.5f, -7.5f) + reflectiveQuadTo(621f, 303f) + quadToRelative(8f, -10f, 20.5f, -12.5f) + reflectiveQuadTo(665f, 296f) + quadToRelative(63f, 45f, 99f, 115f) + reflectiveQuadToRelative(36f, 149f) + quadToRelative(0f, 134f, -93f, 227f) + reflectiveQuadTo(480f, 880f) + quadToRelative(-134f, 0f, -227f, -93f) + close() + moveTo(240f, 560f) + quadToRelative(0f, 52f, 21f, 98.5f) + reflectiveQuadToRelative(60f, 81.5f) + quadToRelative(-1f, -5f, -1f, -9f) + verticalLineToRelative(-9f) + quadToRelative(0f, -32f, 12f, -60f) + reflectiveQuadToRelative(35f, -51f) + lineToRelative(113f, -111f) + lineToRelative(113f, 111f) + quadToRelative(23f, 23f, 35f, 51f) + reflectiveQuadToRelative(12f, 60f) + verticalLineToRelative(9f) + quadToRelative(0f, 4f, -1f, 9f) + quadToRelative(39f, -35f, 60f, -81.5f) + reflectiveQuadToRelative(21f, -98.5f) + quadToRelative(0f, -50f, -18.5f, -94.5f) + reflectiveQuadTo(648f, 386f) + quadToRelative(-20f, 13f, -42f, 19.5f) + reflectiveQuadToRelative(-45f, 6.5f) + quadToRelative(-62f, 0f, -107.5f, -41f) + reflectiveQuadTo(401f, 270f) + quadToRelative(-78f, 66f, -119.5f, 140.5f) + reflectiveQuadTo(240f, 560f) + close() + moveTo(480f, 612f) + lineTo(423f, 668f) + quadToRelative(-11f, 11f, -17f, 25f) + reflectiveQuadToRelative(-6f, 29f) + quadToRelative(0f, 32f, 23.5f, 55f) + reflectiveQuadToRelative(56.5f, 23f) + quadToRelative(33f, 0f, 56.5f, -23f) + reflectiveQuadToRelative(23.5f, -55f) + quadToRelative(0f, -16f, -6f, -29.5f) + reflectiveQuadTo(537f, 668f) + lineToRelative(-57f, -56f) + close() + } + }.build() +} + +val Icons.Rounded.LocalFireDepartment: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.LocalFireDepartment", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 560f) + quadToRelative(0f, -113f, 67f, -217f) + reflectiveQuadToRelative(184f, -182f) + quadToRelative(22f, -15f, 45.5f, -1.5f) + reflectiveQuadTo(480f, 200f) + verticalLineToRelative(52f) + quadToRelative(0f, 34f, 23.5f, 57f) + reflectiveQuadToRelative(57.5f, 23f) + quadToRelative(17f, 0f, 32.5f, -7.5f) + reflectiveQuadTo(621f, 303f) + quadToRelative(8f, -10f, 20.5f, -12.5f) + reflectiveQuadTo(665f, 296f) + quadToRelative(63f, 45f, 99f, 115f) + reflectiveQuadToRelative(36f, 149f) + quadToRelative(0f, 88f, -43f, 160.5f) + reflectiveQuadTo(644f, 835f) + quadToRelative(17f, -24f, 26.5f, -52.5f) + reflectiveQuadTo(680f, 722f) + quadToRelative(0f, -40f, -15f, -75.5f) + reflectiveQuadTo(622f, 583f) + lineTo(480f, 444f) + lineTo(339f, 583f) + quadToRelative(-29f, 29f, -44f, 64f) + reflectiveQuadToRelative(-15f, 75f) + quadToRelative(0f, 32f, 9.5f, 60.5f) + reflectiveQuadTo(316f, 835f) + quadToRelative(-70f, -42f, -113f, -114.5f) + reflectiveQuadTo(160f, 560f) + close() + moveTo(480f, 556f) + lineTo(565f, 639f) + quadToRelative(17f, 17f, 26f, 38f) + reflectiveQuadToRelative(9f, 45f) + quadToRelative(0f, 49f, -35f, 83.5f) + reflectiveQuadTo(480f, 840f) + quadToRelative(-50f, 0f, -85f, -34.5f) + reflectiveQuadTo(360f, 722f) + quadToRelative(0f, -23f, 9f, -44.5f) + reflectiveQuadToRelative(26f, -38.5f) + lineToRelative(85f, -83f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LocationOn.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LocationOn.kt new file mode 100644 index 0000000..5ef72cd --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LocationOn.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.LocationOn: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.LocationOn", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 853f) + quadToRelative(-14f, 0f, -28f, -5f) + reflectiveQuadToRelative(-25f, -15f) + quadToRelative(-65f, -60f, -115f, -117f) + reflectiveQuadToRelative(-83.5f, -110.5f) + quadToRelative(-33.5f, -53.5f, -51f, -103f) + reflectiveQuadTo(160f, 408f) + quadToRelative(0f, -150f, 96.5f, -239f) + reflectiveQuadTo(480f, 80f) + quadToRelative(127f, 0f, 223.5f, 89f) + reflectiveQuadTo(800f, 408f) + quadToRelative(0f, 45f, -17.5f, 94.5f) + reflectiveQuadToRelative(-51f, 103f) + quadTo(698f, 659f, 648f, 716f) + reflectiveQuadTo(533f, 833f) + quadToRelative(-11f, 10f, -25f, 15f) + reflectiveQuadToRelative(-28f, 5f) + close() + moveTo(480f, 480f) + quadToRelative(33f, 0f, 56.5f, -23.5f) + reflectiveQuadTo(560f, 400f) + quadToRelative(0f, -33f, -23.5f, -56.5f) + reflectiveQuadTo(480f, 320f) + quadToRelative(-33f, 0f, -56.5f, 23.5f) + reflectiveQuadTo(400f, 400f) + quadToRelative(0f, 33f, 23.5f, 56.5f) + reflectiveQuadTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LocationSearching.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LocationSearching.kt new file mode 100644 index 0000000..8fc19b3 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LocationSearching.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.LocationSearching: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.LocationSearching", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(440f, 880f) + verticalLineToRelative(-40f) + quadToRelative(-125f, -14f, -214.5f, -103.5f) + reflectiveQuadTo(122f, 522f) + lineTo(82f, 522f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(42f, 482f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(82f, 442f) + horizontalLineToRelative(40f) + quadToRelative(14f, -125f, 103.5f, -214.5f) + reflectiveQuadTo(440f, 124f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 44f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 84f) + verticalLineToRelative(40f) + quadToRelative(125f, 14f, 214.5f, 103.5f) + reflectiveQuadTo(838f, 442f) + horizontalLineToRelative(40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(918f, 482f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(878f, 522f) + horizontalLineToRelative(-40f) + quadToRelative(-14f, 125f, -103.5f, 214.5f) + reflectiveQuadTo(520f, 840f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 920f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 880f) + close() + moveTo(480f, 762f) + quadToRelative(116f, 0f, 198f, -82f) + reflectiveQuadToRelative(82f, -198f) + quadToRelative(0f, -116f, -82f, -198f) + reflectiveQuadToRelative(-198f, -82f) + quadToRelative(-116f, 0f, -198f, 82f) + reflectiveQuadToRelative(-82f, 198f) + quadToRelative(0f, 116f, 82f, 198f) + reflectiveQuadToRelative(198f, 82f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Lock.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Lock.kt new file mode 100644 index 0000000..f769481 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Lock.kt @@ -0,0 +1,78 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Lock: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Lock", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(240f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 800f) + verticalLineToRelative(-400f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(240f, 320f) + horizontalLineToRelative(40f) + verticalLineToRelative(-80f) + quadToRelative(0f, -83f, 58.5f, -141.5f) + reflectiveQuadTo(480f, 40f) + quadToRelative(83f, 0f, 141.5f, 58.5f) + reflectiveQuadTo(680f, 240f) + verticalLineToRelative(80f) + horizontalLineToRelative(40f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(800f, 400f) + verticalLineToRelative(400f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 880f) + lineTo(240f, 880f) + close() + moveTo(480f, 680f) + quadToRelative(33f, 0f, 56.5f, -23.5f) + reflectiveQuadTo(560f, 600f) + quadToRelative(0f, -33f, -23.5f, -56.5f) + reflectiveQuadTo(480f, 520f) + quadToRelative(-33f, 0f, -56.5f, 23.5f) + reflectiveQuadTo(400f, 600f) + quadToRelative(0f, 33f, 23.5f, 56.5f) + reflectiveQuadTo(480f, 680f) + close() + moveTo(360f, 320f) + horizontalLineToRelative(240f) + verticalLineToRelative(-80f) + quadToRelative(0f, -50f, -35f, -85f) + reflectiveQuadToRelative(-85f, -35f) + quadToRelative(-50f, 0f, -85f, 35f) + reflectiveQuadToRelative(-35f, 85f) + verticalLineToRelative(80f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LockOpen.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LockOpen.kt new file mode 100644 index 0000000..d6b73e6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LockOpen.kt @@ -0,0 +1,77 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.LockOpen: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.LockOpen", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(240f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 800f) + verticalLineToRelative(-400f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(240f, 320f) + horizontalLineToRelative(360f) + verticalLineToRelative(-80f) + quadToRelative(0f, -50f, -35f, -85f) + reflectiveQuadToRelative(-85f, -35f) + quadToRelative(-42f, 0f, -73.5f, 25.5f) + reflectiveQuadTo(364f, 209f) + quadToRelative(-4f, 14f, -16.5f, 22.5f) + reflectiveQuadTo(320f, 240f) + quadToRelative(-17f, 0f, -28.5f, -11f) + reflectiveQuadToRelative(-8.5f, -26f) + quadToRelative(11f, -68f, 66.5f, -115.5f) + reflectiveQuadTo(480f, 40f) + quadToRelative(83f, 0f, 141.5f, 58.5f) + reflectiveQuadTo(680f, 240f) + verticalLineToRelative(80f) + horizontalLineToRelative(40f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(800f, 400f) + verticalLineToRelative(400f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 880f) + lineTo(240f, 880f) + close() + moveTo(480f, 680f) + quadToRelative(33f, 0f, 56.5f, -23.5f) + reflectiveQuadTo(560f, 600f) + quadToRelative(0f, -33f, -23.5f, -56.5f) + reflectiveQuadTo(480f, 520f) + quadToRelative(-33f, 0f, -56.5f, 23.5f) + reflectiveQuadTo(400f, 600f) + quadToRelative(0f, 33f, 23.5f, 56.5f) + reflectiveQuadTo(480f, 680f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Longitude.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Longitude.kt new file mode 100644 index 0000000..5b32863 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Longitude.kt @@ -0,0 +1,38 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Loyalty.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Loyalty.kt new file mode 100644 index 0000000..15607d8 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Loyalty.kt @@ -0,0 +1,158 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.Loyalty: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Loyalty", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(856f, 570f) + lineTo(570f, 856f) + quadToRelative(-12f, 12f, -27f, 18f) + reflectiveQuadToRelative(-30f, 6f) + quadToRelative(-15f, 0f, -30f, -6f) + reflectiveQuadToRelative(-27f, -18f) + lineTo(103f, 503f) + quadToRelative(-11f, -11f, -17f, -25.5f) + reflectiveQuadTo(80f, 447f) + verticalLineToRelative(-287f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(160f, 80f) + horizontalLineToRelative(287f) + quadToRelative(16f, 0f, 31f, 6.5f) + reflectiveQuadToRelative(26f, 17.5f) + lineToRelative(352f, 353f) + quadToRelative(12f, 12f, 17.5f, 27f) + reflectiveQuadToRelative(5.5f, 30f) + quadToRelative(0f, 15f, -5.5f, 29.5f) + reflectiveQuadTo(856f, 570f) + close() + moveTo(513f, 800f) + lineToRelative(286f, -286f) + lineToRelative(-353f, -354f) + lineTo(160f, 160f) + verticalLineToRelative(286f) + lineToRelative(353f, 354f) + close() + moveTo(260f, 320f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(320f, 260f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(260f, 200f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(200f, 260f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(260f, 320f) + close() + moveTo(480f, 480f) + close() + moveTo(548f, 672f) + lineTo(660f, 560f) + quadToRelative(11f, -11f, 17.5f, -26f) + reflectiveQuadToRelative(6.5f, -32f) + quadToRelative(0f, -34f, -24f, -58f) + reflectiveQuadToRelative(-58f, -24f) + quadToRelative(-19f, 0f, -37.5f, 11f) + reflectiveQuadTo(520f, 468f) + quadToRelative(-30f, -28f, -47f, -38f) + reflectiveQuadToRelative(-35f, -10f) + quadToRelative(-34f, 0f, -58f, 24f) + reflectiveQuadToRelative(-24f, 58f) + quadToRelative(0f, 17f, 6.5f, 32f) + reflectiveQuadToRelative(17.5f, 26f) + lineToRelative(112f, 112f) + quadToRelative(12f, 12f, 28f, 12f) + reflectiveQuadToRelative(28f, -12f) + close() + } + }.build() +} + +val Icons.Rounded.Loyalty: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Loyalty", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(856f, 570f) + lineTo(570f, 856f) + quadToRelative(-12f, 12f, -27f, 18f) + reflectiveQuadToRelative(-30f, 6f) + quadToRelative(-15f, 0f, -30f, -6f) + reflectiveQuadToRelative(-27f, -18f) + lineTo(103f, 503f) + quadToRelative(-11f, -11f, -17f, -25.5f) + reflectiveQuadTo(80f, 447f) + verticalLineToRelative(-287f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(160f, 80f) + horizontalLineToRelative(287f) + quadToRelative(16f, 0f, 31f, 6.5f) + reflectiveQuadToRelative(26f, 17.5f) + lineToRelative(352f, 353f) + quadToRelative(12f, 12f, 17.5f, 27f) + reflectiveQuadToRelative(5.5f, 30f) + quadToRelative(0f, 15f, -5.5f, 29.5f) + reflectiveQuadTo(856f, 570f) + close() + moveTo(260f, 320f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(320f, 260f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(260f, 200f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(200f, 260f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(260f, 320f) + close() + moveTo(548f, 672f) + lineTo(660f, 560f) + quadToRelative(11f, -11f, 17.5f, -26f) + reflectiveQuadToRelative(6.5f, -32f) + quadToRelative(0f, -34f, -24f, -58f) + reflectiveQuadToRelative(-58f, -24f) + quadToRelative(-19f, 0f, -37.5f, 11f) + reflectiveQuadTo(520f, 468f) + quadToRelative(-30f, -28f, -47f, -38f) + reflectiveQuadToRelative(-35f, -10f) + quadToRelative(-34f, 0f, -58f, 24f) + reflectiveQuadToRelative(-24f, 58f) + quadToRelative(0f, 17f, 6.5f, 32f) + reflectiveQuadToRelative(17.5f, 26f) + lineToRelative(112f, 112f) + quadToRelative(12f, 12f, 28f, 12f) + reflectiveQuadToRelative(28f, -12f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Manga.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Manga.kt new file mode 100644 index 0000000..988881f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Manga.kt @@ -0,0 +1,144 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MaterialDesign.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MaterialDesign.kt new file mode 100644 index 0000000..f783e50 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MaterialDesign.kt @@ -0,0 +1,78 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Memory.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Memory.kt new file mode 100644 index 0000000..f78a351 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Memory.kt @@ -0,0 +1,136 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Memory: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Memory", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(360f, 560f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(400f, 360f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(600f, 400f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(560f, 600f) + lineTo(400f, 600f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(360f, 560f) + close() + moveTo(440f, 520f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + close() + moveTo(360f, 800f) + verticalLineToRelative(-40f) + horizontalLineToRelative(-80f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(200f, 680f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-40f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 560f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 520f) + horizontalLineToRelative(40f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-40f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 400f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 360f) + horizontalLineToRelative(40f) + verticalLineToRelative(-80f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(280f, 200f) + horizontalLineToRelative(80f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(400f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(440f, 160f) + verticalLineToRelative(40f) + horizontalLineToRelative(80f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(560f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(600f, 160f) + verticalLineToRelative(40f) + horizontalLineToRelative(80f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(760f, 280f) + verticalLineToRelative(80f) + horizontalLineToRelative(40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 400f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 440f) + horizontalLineToRelative(-40f) + verticalLineToRelative(80f) + horizontalLineToRelative(40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 560f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 600f) + horizontalLineToRelative(-40f) + verticalLineToRelative(80f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(680f, 760f) + horizontalLineToRelative(-80f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(560f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(520f, 800f) + verticalLineToRelative(-40f) + horizontalLineToRelative(-80f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(400f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(360f, 800f) + close() + moveTo(680f, 680f) + verticalLineToRelative(-400f) + lineTo(280f, 280f) + verticalLineToRelative(400f) + horizontalLineToRelative(400f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MenuOpen.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MenuOpen.kt new file mode 100644 index 0000000..0bec0e9 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MenuOpen.kt @@ -0,0 +1,91 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.MenuOpen: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.MenuOpen", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 720f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 680f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 640f) + horizontalLineToRelative(440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(640f, 680f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(600f, 720f) + lineTo(160f, 720f) + close() + moveTo(756f, 652f) + lineTo(612f, 508f) + quadToRelative(-12f, -12f, -12f, -28f) + reflectiveQuadToRelative(12f, -28f) + lineToRelative(144f, -144f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineTo(696f, 480f) + lineToRelative(116f, 116f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + close() + moveTo(160f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 440f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 520f) + lineTo(160f, 520f) + close() + moveTo(160f, 320f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 280f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 240f) + horizontalLineToRelative(440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(640f, 280f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(600f, 320f) + lineTo(160f, 320f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MeshDownload.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MeshDownload.kt new file mode 100644 index 0000000..d7e84b4 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MeshDownload.kt @@ -0,0 +1,166 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MeshGradient.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MeshGradient.kt new file mode 100644 index 0000000..faa92ac --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MeshGradient.kt @@ -0,0 +1,115 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MiniEdit.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MiniEdit.kt new file mode 100644 index 0000000..43eae60 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MiniEdit.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.MiniEdit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.MiniEdit", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(18.825f, 7.917f) + curveToRelative(-0.117f, -0.285f, -0.278f, -0.544f, -0.486f, -0.778f) + lineToRelative(-1.439f, -1.439f) + curveToRelative(-0.233f, -0.233f, -0.492f, -0.408f, -0.778f, -0.525f) + curveToRelative(-0.285f, -0.117f, -0.583f, -0.175f, -0.894f, -0.175f) + curveToRelative(-0.285f, 0f, -0.57f, 0.052f, -0.856f, 0.156f) + curveToRelative(-0.285f, 0.103f, -0.544f, 0.272f, -0.778f, 0.506f) + lineTo(5.467f, 13.75f) + curveToRelative(-0.156f, 0.156f, -0.272f, 0.331f, -0.35f, 0.525f) + reflectiveCurveToRelative(-0.117f, 0.395f, -0.117f, 0.603f) + verticalLineToRelative(2.567f) + curveToRelative(0f, 0.441f, 0.149f, 0.81f, 0.447f, 1.108f) + curveToRelative(0.298f, 0.298f, 0.667f, 0.447f, 1.108f, 0.447f) + horizontalLineToRelative(2.567f) + curveToRelative(0.208f, 0f, 0.408f, -0.039f, 0.603f, -0.117f) + reflectiveCurveToRelative(0.369f, -0.194f, 0.525f, -0.35f) + lineToRelative(8.089f, -8.089f) + curveToRelative(0.233f, -0.233f, 0.402f, -0.499f, 0.506f, -0.797f) + curveToRelative(0.103f, -0.298f, 0.156f, -0.59f, 0.156f, -0.875f) + reflectiveCurveToRelative(-0.058f, -0.57f, -0.175f, -0.856f) + close() + moveTo(8.811f, 16.667f) + horizontalLineToRelative(-1.478f) + verticalLineToRelative(-1.478f) + lineToRelative(4.744f, -4.706f) + lineToRelative(0.739f, 0.7f) + lineToRelative(0.7f, 0.739f) + lineToRelative(-4.706f, 4.744f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MiniEditLarge.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MiniEditLarge.kt new file mode 100644 index 0000000..29db602 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MiniEditLarge.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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.Black)) { + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Mobile.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Mobile.kt new file mode 100644 index 0000000..fd43843 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Mobile.kt @@ -0,0 +1,121 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileArrowDown.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileArrowDown.kt new file mode 100644 index 0000000..68401b4 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileArrowDown.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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) + 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(495f, 620.5f) + quadToRelative(7f, -2.5f, 13f, -8.5f) + lineToRelative(104f, -104f) + quadToRelative(11f, -11f, 11.5f, -27.5f) + reflectiveQuadTo(612f, 452f) + quadToRelative(-11f, -11f, -27.5f, -11.5f) + reflectiveQuadTo(556f, 451f) + lineToRelative(-36f, 35f) + verticalLineToRelative(-126f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 320f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 360f) + verticalLineToRelative(126f) + lineToRelative(-36f, -35f) + quadToRelative(-11f, -11f, -27.5f, -11f) + reflectiveQuadTo(348f, 452f) + quadToRelative(-11f, 11f, -11f, 28f) + reflectiveQuadToRelative(11f, 28f) + lineToRelative(104f, 104f) + quadToRelative(6f, 6f, 13f, 8.5f) + reflectiveQuadToRelative(15f, 2.5f) + quadToRelative(8f, 0f, 15f, -2.5f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileArrowUpRight.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileArrowUpRight.kt new file mode 100644 index 0000000..89a9030 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileArrowUpRight.kt @@ -0,0 +1,145 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.MobileArrowUpRight: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.MobileArrowUpRight", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + 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(280f, 840f) + verticalLineToRelative(-720f) + verticalLineToRelative(720f) + close() + moveTo(520f, 496f) + verticalLineToRelative(64f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(560f, 600f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(600f, 560f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(560f, 360f) + lineTo(400f, 360f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(360f, 400f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(400f, 440f) + horizontalLineToRelative(64f) + lineToRelative(-96f, 95f) + quadToRelative(-12f, 12f, -12f, 28.5f) + reflectiveQuadToRelative(12f, 28.5f) + quadToRelative(12f, 12f, 28.5f, 12f) + reflectiveQuadToRelative(28.5f, -12f) + lineToRelative(95f, -96f) + 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 + ).apply { + path(fill = SolidColor(Color.Black)) { + 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(520f, 496f) + verticalLineToRelative(64f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(560f, 600f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(600f, 560f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(560f, 360f) + lineTo(400f, 360f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(360f, 400f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(400f, 440f) + horizontalLineToRelative(64f) + lineToRelative(-96f, 95f) + quadToRelative(-12f, 12f, -12f, 28.5f) + reflectiveQuadToRelative(12f, 28.5f) + quadToRelative(12f, 12f, 28.5f, 12f) + reflectiveQuadToRelative(28.5f, -12f) + lineToRelative(95f, -96f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileCast.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileCast.kt new file mode 100644 index 0000000..ad3a03e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileCast.kt @@ -0,0 +1,117 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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(211.5f, 908.5f) + quadTo(200f, 897f, 200f, 880f) + reflectiveQuadToRelative(11.5f, -28.5f) + quadTo(223f, 840f, 240f, 840f) + reflectiveQuadToRelative(28.5f, 11.5f) + quadTo(280f, 863f, 280f, 880f) + reflectiveQuadToRelative(-11.5f, 28.5f) + quadTo(257f, 920f, 240f, 920f) + reflectiveQuadToRelative(-28.5f, -11.5f) + close() + moveTo(313f, 807f) + quadToRelative(-15f, -15f, -33.5f, -26f) + reflectiveQuadTo(240f, 765f) + quadToRelative(-17f, -5f, -28.5f, -16.5f) + reflectiveQuadTo(200f, 720f) + quadToRelative(0f, -17f, 12f, -28.5f) + reflectiveQuadToRelative(28f, -8.5f) + quadToRelative(37f, 6f, 70.5f, 23.5f) + reflectiveQuadTo(370f, 750f) + quadToRelative(26f, 26f, 43.5f, 59.5f) + reflectiveQuadTo(437f, 880f) + quadToRelative(3f, 16f, -8.5f, 28f) + reflectiveQuadTo(400f, 920f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(355f, 880f) + quadToRelative(-5f, -21f, -16f, -39.5f) + reflectiveQuadTo(313f, 807f) + close() + moveTo(340.5f, 632.5f) + quadTo(293f, 609f, 240f, 602f) + quadToRelative(-17f, -2f, -28.5f, -13.5f) + reflectiveQuadTo(200f, 560f) + quadToRelative(0f, -17f, 12f, -28.5f) + reflectiveQuadToRelative(29f, -9.5f) + quadToRelative(69f, 7f, 131f, 36.5f) + reflectiveQuadTo(483f, 637f) + quadToRelative(49f, 49f, 77.5f, 111f) + reflectiveQuadTo(597f, 879f) + quadToRelative(2f, 17f, -9f, 29f) + reflectiveQuadToRelative(-28f, 12f) + quadToRelative(-17f, 0f, -29f, -11.5f) + reflectiveQuadTo(517f, 880f) + quadToRelative(-7f, -53f, -30f, -100.5f) + reflectiveQuadTo(426f, 694f) + quadToRelative(-38f, -38f, -85.5f, -61.5f) + close() + moveTo(508.5f, 228.5f) + quadTo(520f, 217f, 520f, 200f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(497f, 160f, 480f, 160f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(440f, 183f, 440f, 200f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(463f, 240f, 480f, 240f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(760f, 436f) + verticalLineToRelative(443f) + quadToRelative(0f, 18f, -11.5f, 31.5f) + reflectiveQuadTo(720f, 924f) + quadToRelative(-17f, 0f, -28f, -11f) + reflectiveQuadToRelative(-13f, -28f) + quadToRelative(-13f, -180f, -139f, -305.5f) + reflectiveQuadTo(234f, 441f) + quadToRelative(-14f, -1f, -24f, -10.5f) + reflectiveQuadTo(200f, 407f) + verticalLineToRelative(-287f) + 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) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileLandscape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileLandscape.kt new file mode 100644 index 0000000..b72ab29 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileLandscape.kt @@ -0,0 +1,79 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileLayout.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileLayout.kt new file mode 100644 index 0000000..2ccf8df --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileLayout.kt @@ -0,0 +1,111 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(40f, 760f) + verticalLineToRelative(-80f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(120f, 600f) + horizontalLineToRelative(240f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(440f, 680f) + verticalLineToRelative(80f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(360f, 840f) + lineTo(120f, 840f) + close() + moveTo(600f, 840f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(520f, 760f) + verticalLineToRelative(-560f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(600f, 120f) + horizontalLineToRelative(240f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(920f, 200f) + verticalLineToRelative(560f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(840f, 840f) + lineTo(600f, 840f) + close() + moveTo(720f, 720f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(760f, 680f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(720f, 640f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(680f, 680f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(720f, 720f) + close() + moveTo(120f, 520f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(40f, 440f) + verticalLineToRelative(-240f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(120f, 120f) + horizontalLineToRelative(240f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(440f, 200f) + verticalLineToRelative(240f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(360f, 520f) + lineTo(120f, 520f) + close() + moveTo(280f, 320f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(320f, 280f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(280f, 240f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(240f, 280f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(280f, 320f) + close() + moveTo(150f, 440f) + horizontalLineToRelative(100f) + quadToRelative(12f, 0f, 18f, -11f) + reflectiveQuadToRelative(-2f, -21f) + lineToRelative(-50f, -67f) + quadToRelative(-6f, -8f, -16f, -8f) + reflectiveQuadToRelative(-16f, 8f) + lineToRelative(-50f, 67f) + quadToRelative(-8f, 10f, -2f, 21f) + reflectiveQuadToRelative(18f, 11f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileRotateLock.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileRotateLock.kt new file mode 100644 index 0000000..37f9b81 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileRotateLock.kt @@ -0,0 +1,132 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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) + quadToRelative(-14f, 0f, -24f, -10f) + reflectiveQuadToRelative(-10f, -24f) + verticalLineToRelative(-132f) + quadToRelative(0f, -14f, 10f, -24f) + reflectiveQuadToRelative(24f, -10f) + horizontalLineToRelative(6f) + verticalLineToRelative(-40f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(800f, 40f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 120f) + verticalLineToRelative(40f) + horizontalLineToRelative(6f) + quadToRelative(14f, 0f, 24f, 10f) + reflectiveQuadToRelative(10f, 24f) + verticalLineToRelative(132f) + quadToRelative(0f, 14f, -10f, 24f) + reflectiveQuadToRelative(-24f, 10f) + lineTo(714f, 360f) + close() + moveTo(760f, 160f) + horizontalLineToRelative(80f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(800f, 80f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(760f, 120f) + verticalLineToRelative(40f) + close() + moveTo(401f, 873f) + lineToRelative(-77f, -77f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineTo(550f, 910f) + quadToRelative(12f, 12f, 6.5f, 28.5f) + reflectiveQuadTo(534f, 957f) + quadToRelative(-14f, 2f, -27f, 2.5f) + reflectiveQuadTo(480f, 960f) + quadToRelative(-99f, 0f, -186.5f, -37.5f) + reflectiveQuadToRelative(-153f, -103f) + quadTo(75f, 754f, 37.5f, 666.5f) + reflectiveQuadTo(0f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(40f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, 71f, 24f, 136f) + reflectiveQuadToRelative(66.5f, 117f) + quadTo(213f, 785f, 272f, 821.5f) + reflectiveQuadTo(401f, 873f) + close() + moveTo(550f, 720f) + lineTo(720f, 550f) + lineTo(410f, 240f) + lineTo(240f, 410f) + lineTo(550f, 720f) + close() + moveTo(480f, 480f) + close() + moveTo(373f, 404f) + quadToRelative(13f, 0f, 21.5f, -9f) + reflectiveQuadToRelative(8.5f, -21f) + quadToRelative(0f, -13f, -8.5f, -21.5f) + reflectiveQuadTo(373f, 344f) + quadToRelative(-12f, 0f, -21f, 8.5f) + reflectiveQuadToRelative(-9f, 21.5f) + quadToRelative(0f, 12f, 9f, 21f) + reflectiveQuadToRelative(21f, 9f) + close() + moveTo(496f, 778f) + lineTo(183f, 464f) + quadToRelative(-11f, -11f, -16.5f, -25.5f) + reflectiveQuadTo(161f, 410f) + quadToRelative(0f, -15f, 5.5f, -29f) + reflectiveQuadToRelative(16.5f, -25f) + lineToRelative(173f, -173f) + quadToRelative(11f, -11f, 25.5f, -17f) + reflectiveQuadToRelative(28.5f, -6f) + quadToRelative(15f, 0f, 29f, 6f) + reflectiveQuadToRelative(25f, 17f) + lineToRelative(313f, 313f) + quadToRelative(11f, 11f, 17f, 25f) + reflectiveQuadToRelative(6f, 29f) + quadToRelative(0f, 14f, -6f, 28.5f) + reflectiveQuadTo(777f, 604f) + lineTo(604f, 778f) + quadToRelative(-11f, 11f, -25f, 16.5f) + reflectiveQuadToRelative(-29f, 5.5f) + quadToRelative(-14f, 0f, -28.5f, -5.5f) + reflectiveQuadTo(496f, 778f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileShare.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileShare.kt new file mode 100644 index 0000000..02720d5 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileShare.kt @@ -0,0 +1,157 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.MobileShare: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.MobileShare", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(486f, 520f) + lineToRelative(-15f, 16f) + quadToRelative(-11f, 12f, -11f, 28f) + reflectiveQuadToRelative(12f, 28f) + quadToRelative(11f, 11f, 28f, 11f) + reflectiveQuadToRelative(28f, -11f) + lineToRelative(84f, -84f) + quadToRelative(6f, -6f, 8.5f, -13f) + reflectiveQuadToRelative(2.5f, -15f) + quadToRelative(0f, -8f, -2.5f, -15f) + reflectiveQuadToRelative(-8.5f, -13f) + lineToRelative(-84f, -84f) + quadToRelative(-11f, -11f, -28f, -11f) + reflectiveQuadToRelative(-29f, 12f) + quadToRelative(-11f, 11f, -11f, 27f) + reflectiveQuadToRelative(11f, 28f) + lineToRelative(15f, 16f) + horizontalLineToRelative(-86f) + quadToRelative(-33f, 0f, -56.5f, 23.5f) + reflectiveQuadTo(320f, 520f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(360f, 640f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(400f, 600f) + verticalLineToRelative(-80f) + horizontalLineToRelative(86f) + 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() + moveTo(280f, 840f) + horizontalLineToRelative(400f) + verticalLineToRelative(-720f) + lineTo(280f, 120f) + verticalLineToRelative(720f) + close() + moveTo(280f, 840f) + verticalLineToRelative(-720f) + verticalLineToRelative(720f) + 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)) { + moveToRelative(486f, 520f) + lineToRelative(-15f, 16f) + quadToRelative(-11f, 12f, -11f, 28f) + reflectiveQuadToRelative(12f, 28f) + quadToRelative(11f, 11f, 28f, 11f) + reflectiveQuadToRelative(28f, -11f) + lineToRelative(84f, -84f) + quadToRelative(6f, -6f, 8.5f, -13f) + reflectiveQuadToRelative(2.5f, -15f) + quadToRelative(0f, -8f, -2.5f, -15f) + reflectiveQuadToRelative(-8.5f, -13f) + lineToRelative(-84f, -84f) + quadToRelative(-11f, -11f, -28f, -11f) + reflectiveQuadToRelative(-29f, 12f) + quadToRelative(-11f, 11f, -11f, 27f) + reflectiveQuadToRelative(11f, 28f) + lineToRelative(15f, 16f) + horizontalLineToRelative(-86f) + quadToRelative(-33f, 0f, -56.5f, 23.5f) + reflectiveQuadTo(320f, 520f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(360f, 640f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(400f, 600f) + verticalLineToRelative(-80f) + horizontalLineToRelative(86f) + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileVibrate.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileVibrate.kt new file mode 100644 index 0000000..53ca9fe --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileVibrate.kt @@ -0,0 +1,110 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(240f, 760f) + verticalLineToRelative(-560f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(320f, 120f) + horizontalLineToRelative(320f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(720f, 200f) + verticalLineToRelative(560f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(640f, 840f) + lineTo(320f, 840f) + close() + moveTo(508.5f, 308.5f) + quadTo(520f, 297f, 520f, 280f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(497f, 240f, 480f, 240f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(440f, 263f, 440f, 280f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(463f, 320f, 480f, 320f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(0f, 560f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(40f, 360f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(80f, 400f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(40f, 600f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(0f, 560f) + close() + moveTo(120f, 640f) + verticalLineToRelative(-320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 320f) + verticalLineToRelative(320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 640f) + close() + moveTo(880f, 560f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(920f, 360f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(960f, 400f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(920f, 600f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(880f, 560f) + close() + moveTo(760f, 640f) + verticalLineToRelative(-320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 320f) + verticalLineToRelative(320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 640f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ModelTraining.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ModelTraining.kt new file mode 100644 index 0000000..f5739e0 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ModelTraining.kt @@ -0,0 +1,112 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Rounded.ModelTraining: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ModelTraining", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(440f, 740f) + quadToRelative(0f, -23f, -15.5f, -45.5f) + reflectiveQuadToRelative(-34.5f, -47f) + quadToRelative(-19f, -24.5f, -34.5f, -51f) + reflectiveQuadTo(340f, 540f) + quadToRelative(0f, -58f, 41f, -99f) + reflectiveQuadToRelative(99f, -41f) + quadToRelative(58f, 0f, 99f, 41f) + reflectiveQuadToRelative(41f, 99f) + quadToRelative(0f, 30f, -15.5f, 56.5f) + reflectiveQuadToRelative(-34.5f, 51f) + quadToRelative(-19f, 24.5f, -34.5f, 47f) + reflectiveQuadTo(520f, 740f) + horizontalLineToRelative(-80f) + close() + moveTo(451.5f, 828.5f) + quadTo(440f, 817f, 440f, 800f) + verticalLineToRelative(-20f) + horizontalLineToRelative(80f) + verticalLineToRelative(20f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + close() + moveTo(720f, 720f) + quadToRelative(-9f, -9f, -10.5f, -23f) + reflectiveQuadToRelative(6.5f, -25f) + quadToRelative(21f, -33f, 32.5f, -71.5f) + reflectiveQuadTo(760f, 520f) + quadToRelative(0f, -55f, -19.5f, -103.5f) + reflectiveQuadTo(686f, 330f) + quadToRelative(-11f, -13f, -11.5f, -28.5f) + reflectiveQuadTo(686f, 274f) + quadToRelative(12f, -12f, 29f, -12f) + reflectiveQuadToRelative(28f, 12f) + quadToRelative(45f, 48f, 71f, 111f) + reflectiveQuadToRelative(26f, 135f) + quadToRelative(0f, 54f, -15f, 103.5f) + reflectiveQuadTo(783f, 715f) + quadToRelative(-11f, 17f, -30f, 18f) + reflectiveQuadToRelative(-33f, -13f) + close() + moveTo(177f, 715f) + quadToRelative(-27f, -42f, -42f, -91.5f) + reflectiveQuadTo(120f, 520f) + quadToRelative(0f, -150f, 105f, -255f) + reflectiveQuadToRelative(255f, -105f) + horizontalLineToRelative(8f) + lineToRelative(-36f, -36f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(104f, 104f) + quadToRelative(6f, 6f, 8.5f, 13f) + reflectiveQuadToRelative(2.5f, 15f) + quadToRelative(0f, 8f, -2.5f, 15f) + reflectiveQuadToRelative(-8.5f, 13f) + lineTo(508f, 332f) + quadToRelative(-12f, 12f, -28f, 11.5f) + reflectiveQuadTo(452f, 331f) + quadToRelative(-12f, -12f, -12f, -28.5f) + reflectiveQuadToRelative(12f, -28.5f) + lineToRelative(34f, -34f) + horizontalLineToRelative(-6f) + quadToRelative(-116f, 0f, -198f, 82f) + reflectiveQuadToRelative(-82f, 198f) + quadToRelative(0f, 42f, 11.5f, 80.5f) + reflectiveQuadTo(244f, 672f) + quadToRelative(8f, 11f, 6.5f, 25f) + reflectiveQuadTo(240f, 720f) + quadToRelative(-14f, 14f, -33f, 13f) + reflectiveQuadToRelative(-30f, -18f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Mop.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Mop.kt new file mode 100644 index 0000000..7db6805 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Mop.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MoreVert.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MoreVert.kt new file mode 100644 index 0000000..c49ff4f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MoreVert.kt @@ -0,0 +1,68 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.MoreVert: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.MoreVert", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 800f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(400f, 720f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(480f, 640f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(560f, 720f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(480f, 800f) + close() + moveTo(480f, 560f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(400f, 480f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(480f, 400f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(560f, 480f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(480f, 560f) + close() + moveTo(480f, 320f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(400f, 240f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(480f, 160f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(560f, 240f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(480f, 320f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MotionMode.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MotionMode.kt new file mode 100644 index 0000000..9efdcdf --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MotionMode.kt @@ -0,0 +1,73 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.MotionMode: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.MotionMode", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(200f, 194f) + verticalLineToRelative(572f) + quadToRelative(-17f, -17f, -32f, -36f) + reflectiveQuadToRelative(-28f, -39f) + verticalLineToRelative(-422f) + quadToRelative(13f, -20f, 28f, -39f) + reflectiveQuadToRelative(32f, -36f) + close() + moveTo(360f, 98f) + verticalLineToRelative(764f) + quadToRelative(-21f, -7f, -41f, -15.5f) + reflectiveQuadTo(280f, 827f) + verticalLineToRelative(-694f) + quadToRelative(19f, -11f, 39f, -19.5f) + reflectiveQuadToRelative(41f, -15.5f) + close() + moveTo(640f, 847f) + verticalLineToRelative(-734f) + quadToRelative(106f, 47f, 173f, 145f) + reflectiveQuadToRelative(67f, 222f) + quadToRelative(0f, 124f, -67f, 222f) + reflectiveQuadTo(640f, 847f) + close() + moveTo(480f, 880f) + quadToRelative(-10f, 0f, -20f, -0.5f) + reflectiveQuadTo(440f, 878f) + verticalLineToRelative(-796f) + quadToRelative(10f, -1f, 20f, -1.5f) + reflectiveQuadToRelative(20f, -0.5f) + quadToRelative(20f, 0f, 40f, 2f) + reflectiveQuadToRelative(40f, 6f) + verticalLineToRelative(784f) + quadToRelative(-20f, 4f, -40f, 6f) + reflectiveQuadToRelative(-40f, 2f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MotionPhotosAuto.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MotionPhotosAuto.kt new file mode 100644 index 0000000..74dbc2e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MotionPhotosAuto.kt @@ -0,0 +1,120 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.MotionPhotosAuto: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.MotionPhotosAuto", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(416f, 538f) + horizontalLineToRelative(128f) + lineToRelative(24f, 65f) + quadToRelative(3f, 8f, 9.5f, 12.5f) + reflectiveQuadTo(592f, 620f) + quadToRelative(14f, 0f, 21.5f, -11f) + reflectiveQuadToRelative(2.5f, -24f) + lineTo(514f, 317f) + quadToRelative(-3f, -8f, -9.5f, -12.5f) + reflectiveQuadTo(490f, 300f) + horizontalLineToRelative(-20f) + quadToRelative(-8f, 0f, -14.5f, 4.5f) + reflectiveQuadTo(446f, 317f) + lineTo(345f, 586f) + quadToRelative(-5f, 12f, 2f, 23f) + reflectiveQuadToRelative(21f, 11f) + quadToRelative(8f, 0f, 14.5f, -4.5f) + reflectiveQuadTo(392f, 603f) + lineToRelative(24f, -65f) + close() + moveTo(432f, 492f) + lineTo(478f, 360f) + horizontalLineToRelative(4f) + lineToRelative(46f, 132f) + horizontalLineToRelative(-96f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -32f, 5f, -64f) + reflectiveQuadToRelative(15f, -63f) + quadToRelative(5f, -16f, 20.5f, -21.5f) + reflectiveQuadTo(150f, 334f) + quadToRelative(15f, 8f, 21.5f, 23.5f) + reflectiveQuadTo(173f, 390f) + quadToRelative(-6f, 22f, -9.5f, 44.5f) + reflectiveQuadTo(160f, 480f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-24f, 0f, -47.5f, 3.5f) + reflectiveQuadTo(386f, 174f) + quadToRelative(-17f, 5f, -32f, -1f) + reflectiveQuadToRelative(-22f, -21f) + quadToRelative(-7f, -15f, -0.5f, -30.5f) + reflectiveQuadTo(354f, 101f) + quadToRelative(30f, -11f, 62f, -16f) + reflectiveQuadToRelative(64f, -5f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(220f, 280f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(160f, 220f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(220f, 160f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(280f, 220f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(220f, 280f) + close() + moveTo(240f, 480f) + quadToRelative(0f, -100f, 70f, -170f) + reflectiveQuadToRelative(170f, -70f) + quadToRelative(100f, 0f, 170f, 70f) + reflectiveQuadToRelative(70f, 170f) + quadToRelative(0f, 100f, -70f, 170f) + reflectiveQuadToRelative(-170f, 70f) + quadToRelative(-100f, 0f, -170f, -70f) + reflectiveQuadToRelative(-70f, -170f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MotionPlay.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MotionPlay.kt new file mode 100644 index 0000000..f9a1a9e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MotionPlay.kt @@ -0,0 +1,97 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.MotionPlay: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.MotionPlay", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(431f, 619f) + lineToRelative(184f, -122f) + quadToRelative(9f, -6f, 9f, -17f) + reflectiveQuadToRelative(-9f, -17f) + lineTo(431f, 341f) + quadToRelative(-10f, -7f, -20.5f, -1.5f) + reflectiveQuadTo(400f, 357f) + verticalLineToRelative(246f) + quadToRelative(0f, 12f, 10.5f, 17.5f) + reflectiveQuadTo(431f, 619f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -32f, 5f, -64f) + reflectiveQuadToRelative(15f, -63f) + quadToRelative(5f, -16f, 20.5f, -21.5f) + reflectiveQuadTo(150f, 334f) + quadToRelative(15f, 8f, 21.5f, 23.5f) + reflectiveQuadTo(173f, 390f) + quadToRelative(-6f, 22f, -9.5f, 44.5f) + reflectiveQuadTo(160f, 480f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-24f, 0f, -47.5f, 3.5f) + reflectiveQuadTo(386f, 174f) + quadToRelative(-17f, 5f, -32f, -1f) + reflectiveQuadToRelative(-22f, -21f) + quadToRelative(-7f, -15f, -0.5f, -30.5f) + reflectiveQuadTo(354f, 101f) + quadToRelative(30f, -11f, 62f, -16f) + reflectiveQuadToRelative(64f, -5f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(177.5f, 262.5f) + quadTo(160f, 245f, 160f, 220f) + reflectiveQuadToRelative(17.5f, -42.5f) + quadTo(195f, 160f, 220f, 160f) + reflectiveQuadToRelative(42.5f, 17.5f) + quadTo(280f, 195f, 280f, 220f) + reflectiveQuadToRelative(-17.5f, 42.5f) + quadTo(245f, 280f, 220f, 280f) + reflectiveQuadToRelative(-42.5f, -17.5f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MultipleImageEdit.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MultipleImageEdit.kt new file mode 100644 index 0000000..cf560aa --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MultipleImageEdit.kt @@ -0,0 +1,293 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.MultipleImageEdit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.MultipleImageEdit", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(6.75f, 6.5f) + curveToRelative(-0.217f, 0f, -0.396f, -0.071f, -0.538f, -0.213f) + reflectiveCurveToRelative(-0.213f, -0.321f, -0.213f, -0.538f) + reflectiveCurveToRelative(0.071f, -0.396f, 0.213f, -0.538f) + reflectiveCurveToRelative(0.321f, -0.213f, 0.538f, -0.213f) + horizontalLineToRelative(10.5f) + curveToRelative(0.217f, 0f, 0.396f, 0.071f, 0.538f, 0.213f) + reflectiveCurveToRelative(0.213f, 0.321f, 0.213f, 0.538f) + reflectiveCurveToRelative(-0.071f, 0.396f, -0.213f, 0.538f) + reflectiveCurveToRelative(-0.321f, 0.213f, -0.538f, 0.213f) + horizontalLineTo(6.75f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(7.75f, 3.5f) + curveToRelative(-0.217f, 0f, -0.396f, -0.071f, -0.538f, -0.213f) + reflectiveCurveToRelative(-0.213f, -0.321f, -0.213f, -0.538f) + reflectiveCurveToRelative(0.071f, -0.396f, 0.213f, -0.538f) + reflectiveCurveToRelative(0.321f, -0.213f, 0.538f, -0.213f) + horizontalLineToRelative(8.5f) + curveToRelative(0.217f, 0f, 0.396f, 0.071f, 0.538f, 0.213f) + reflectiveCurveToRelative(0.213f, 0.321f, 0.213f, 0.538f) + reflectiveCurveToRelative(-0.071f, 0.396f, -0.213f, 0.538f) + reflectiveCurveToRelative(-0.321f, 0.213f, -0.538f, 0.213f) + horizontalLineTo(7.75f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(20.614f, 16.46f) + curveToRelative(-0.062f, -0.151f, -0.147f, -0.288f, -0.257f, -0.411f) + lineToRelative(-0.761f, -0.761f) + curveToRelative(-0.123f, -0.123f, -0.26f, -0.216f, -0.411f, -0.277f) + curveToRelative(-0.151f, -0.062f, -0.308f, -0.092f, -0.473f, -0.092f) + curveToRelative(-0.151f, 0f, -0.301f, 0.027f, -0.452f, 0.082f) + curveToRelative(-0.151f, 0.054f, -0.288f, 0.144f, -0.411f, 0.267f) + lineToRelative(-4.295f, 4.275f) + curveToRelative(-0.082f, 0.082f, -0.144f, 0.175f, -0.185f, 0.277f) + reflectiveCurveToRelative(-0.062f, 0.209f, -0.062f, 0.319f) + verticalLineToRelative(1.357f) + curveToRelative(0f, 0.233f, 0.079f, 0.428f, 0.236f, 0.586f) + reflectiveCurveToRelative(0.353f, 0.236f, 0.586f, 0.236f) + horizontalLineToRelative(1.357f) + curveToRelative(0.11f, 0f, 0.216f, -0.021f, 0.319f, -0.062f) + reflectiveCurveToRelative(0.195f, -0.103f, 0.277f, -0.185f) + lineToRelative(4.275f, -4.275f) + curveToRelative(0.123f, -0.123f, 0.212f, -0.264f, 0.267f, -0.421f) + curveToRelative(0.054f, -0.158f, 0.082f, -0.312f, 0.082f, -0.462f) + reflectiveCurveToRelative(-0.031f, -0.301f, -0.092f, -0.452f) + lineToRelative(-0.001f, 0.001f) + close() + moveTo(15.321f, 21.084f) + horizontalLineToRelative(-0.781f) + verticalLineToRelative(-0.781f) + lineToRelative(2.507f, -2.487f) + lineToRelative(0.391f, 0.37f) + lineToRelative(0.37f, 0.391f) + lineToRelative(-2.487f, 2.507f) + lineToRelative(0.001f, 0.001f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(14.943f, 15.698f) + lineToRelative(-0.951f, -1.292f) + 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) + lineToRelative(-1f, -1.325f) + curveToRelative(-0.1f, -0.133f, -0.233f, -0.196f, -0.4f, -0.188f) + curveToRelative(-0.167f, 0.008f, -0.3f, 0.079f, -0.4f, 0.212f) + lineToRelative(-1.125f, 1.5f) + curveToRelative(-0.133f, 0.167f, -0.146f, 0.342f, -0.037f, 0.525f) + curveToRelative(0.108f, 0.183f, 0.263f, 0.275f, 0.463f, 0.275f) + horizontalLineToRelative(4.068f) + lineToRelative(1.683f, -1.683f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(19f, 11.63f) + verticalLineToRelative(-1.63f) + curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) + 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) + curveToRelative(-0.391f, 0.392f, -0.588f, 0.862f, -0.588f, 1.412f) + verticalLineToRelative(10f) + curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) + curveToRelative(0.392f, 0.391f, 0.862f, 0.588f, 1.412f, 0.588f) + horizontalLineToRelative(3.8f) + curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) + reflectiveCurveToRelative(-0.448f, -1f, -1f, -1f) + horizontalLineToRelative(-3.8f) + verticalLineToRelative(-10f) + horizontalLineToRelative(10f) + verticalLineToRelative(1.642f) + horizontalLineToRelative(0f) + verticalLineToRelative(0.988f) + curveToRelative(0f, 0.552f, 0.448f, 1f, 1f, 1f) + curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) + verticalLineToRelative(-1f) + horizontalLineToRelative(-0f) + close() + } + }.build() +} + +val Icons.TwoTone.MultipleImageEdit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.MultipleImageEdit", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(6.75f, 6.5f) + curveToRelative(-0.217f, 0f, -0.396f, -0.071f, -0.538f, -0.213f) + reflectiveCurveToRelative(-0.213f, -0.321f, -0.213f, -0.538f) + reflectiveCurveToRelative(0.071f, -0.396f, 0.213f, -0.538f) + reflectiveCurveToRelative(0.321f, -0.213f, 0.538f, -0.213f) + horizontalLineToRelative(10.5f) + curveToRelative(0.217f, 0f, 0.396f, 0.071f, 0.538f, 0.213f) + reflectiveCurveToRelative(0.213f, 0.321f, 0.213f, 0.538f) + reflectiveCurveToRelative(-0.071f, 0.396f, -0.213f, 0.538f) + reflectiveCurveToRelative(-0.321f, 0.213f, -0.538f, 0.213f) + horizontalLineTo(6.75f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(7.75f, 3.5f) + curveToRelative(-0.217f, 0f, -0.396f, -0.071f, -0.538f, -0.213f) + reflectiveCurveToRelative(-0.213f, -0.321f, -0.213f, -0.538f) + reflectiveCurveToRelative(0.071f, -0.396f, 0.213f, -0.538f) + reflectiveCurveToRelative(0.321f, -0.213f, 0.538f, -0.213f) + horizontalLineToRelative(8.5f) + curveToRelative(0.217f, 0f, 0.396f, 0.071f, 0.538f, 0.213f) + reflectiveCurveToRelative(0.213f, 0.321f, 0.213f, 0.538f) + reflectiveCurveToRelative(-0.071f, 0.396f, -0.213f, 0.538f) + reflectiveCurveToRelative(-0.321f, 0.213f, -0.538f, 0.213f) + horizontalLineTo(7.75f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(20.614f, 16.46f) + curveToRelative(-0.062f, -0.151f, -0.147f, -0.288f, -0.257f, -0.411f) + lineToRelative(-0.761f, -0.761f) + curveToRelative(-0.123f, -0.123f, -0.26f, -0.216f, -0.411f, -0.277f) + curveToRelative(-0.151f, -0.062f, -0.308f, -0.092f, -0.473f, -0.092f) + curveToRelative(-0.151f, 0f, -0.301f, 0.027f, -0.452f, 0.082f) + curveToRelative(-0.151f, 0.054f, -0.288f, 0.144f, -0.411f, 0.267f) + lineToRelative(-4.295f, 4.275f) + curveToRelative(-0.082f, 0.082f, -0.144f, 0.175f, -0.185f, 0.277f) + reflectiveCurveToRelative(-0.062f, 0.209f, -0.062f, 0.319f) + verticalLineToRelative(1.357f) + curveToRelative(0f, 0.233f, 0.079f, 0.428f, 0.236f, 0.586f) + reflectiveCurveToRelative(0.353f, 0.236f, 0.586f, 0.236f) + horizontalLineToRelative(1.357f) + curveToRelative(0.11f, 0f, 0.216f, -0.021f, 0.319f, -0.062f) + reflectiveCurveToRelative(0.195f, -0.103f, 0.277f, -0.185f) + lineToRelative(4.275f, -4.275f) + curveToRelative(0.123f, -0.123f, 0.212f, -0.264f, 0.267f, -0.421f) + curveToRelative(0.054f, -0.158f, 0.082f, -0.312f, 0.082f, -0.462f) + reflectiveCurveToRelative(-0.031f, -0.301f, -0.092f, -0.452f) + lineToRelative(-0.001f, 0.001f) + close() + moveTo(15.321f, 21.084f) + horizontalLineToRelative(-0.781f) + verticalLineToRelative(-0.781f) + lineToRelative(2.507f, -2.487f) + lineToRelative(0.391f, 0.37f) + lineToRelative(0.37f, 0.391f) + lineToRelative(-2.487f, 2.507f) + lineToRelative(0.001f, 0.001f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(15.321f, 21.084f) + horizontalLineToRelative(-0.781f) + verticalLineToRelative(-0.781f) + lineToRelative(2.507f, -2.487f) + lineToRelative(0.391f, 0.37f) + lineToRelative(0.37f, 0.391f) + lineToRelative(-2.487f, 2.507f) + lineToRelative(0.001f, 0.001f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(14.943f, 15.698f) + lineToRelative(-0.951f, -1.292f) + 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) + lineToRelative(-1f, -1.325f) + curveToRelative(-0.1f, -0.133f, -0.233f, -0.196f, -0.4f, -0.188f) + curveToRelative(-0.167f, 0.008f, -0.3f, 0.079f, -0.4f, 0.212f) + lineToRelative(-1.125f, 1.5f) + curveToRelative(-0.133f, 0.167f, -0.146f, 0.342f, -0.037f, 0.525f) + curveToRelative(0.108f, 0.183f, 0.263f, 0.275f, 0.463f, 0.275f) + horizontalLineToRelative(4.068f) + lineToRelative(1.683f, -1.683f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(19f, 11.63f) + verticalLineToRelative(-1.63f) + curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) + 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) + curveToRelative(-0.391f, 0.392f, -0.588f, 0.862f, -0.588f, 1.412f) + verticalLineToRelative(10f) + curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) + curveToRelative(0.392f, 0.391f, 0.862f, 0.588f, 1.412f, 0.588f) + horizontalLineToRelative(3.8f) + curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) + reflectiveCurveToRelative(-0.448f, -1f, -1f, -1f) + horizontalLineToRelative(-3.8f) + verticalLineToRelative(-10f) + horizontalLineToRelative(10f) + verticalLineToRelative(1.642f) + horizontalLineToRelative(0f) + verticalLineToRelative(0.988f) + curveToRelative(0f, 0.552f, 0.448f, 1f, 1f, 1f) + curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) + verticalLineToRelative(-1f) + horizontalLineToRelative(-0f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(18f, 13.63f) + curveToRelative(-0.552f, 0f, -1f, -0.448f, -1f, -1f) + verticalLineToRelative(-0.988f) + horizontalLineToRelative(-0f) + verticalLineToRelative(-1.642f) + horizontalLineTo(7f) + verticalLineToRelative(10f) + horizontalLineToRelative(3.8f) + curveToRelative(0.552f, 0f, 1f, 0.448f, 1f, 1f) + curveToRelative(0f, 0.03f, -0.014f, 0.055f, -0.017f, 0.084f) + horizontalLineToRelative(1.524f) + verticalLineToRelative(-0.946f) + curveToRelative(0f, -0.11f, 0.021f, -0.216f, 0.062f, -0.319f) + curveToRelative(0.041f, -0.103f, 0.103f, -0.195f, 0.185f, -0.277f) + lineToRelative(4.295f, -4.275f) + curveToRelative(0.071f, -0.071f, 0.156f, -0.105f, 0.235f, -0.153f) + verticalLineToRelative(-1.502f) + curveToRelative(-0.029f, 0.003f, -0.054f, 0.017f, -0.084f, 0.017f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MultipleStop.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MultipleStop.kt new file mode 100644 index 0000000..09a7bca --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MultipleStop.kt @@ -0,0 +1,126 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.MultipleStop: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.MultipleStop", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(273f, 680f) + lineToRelative(36f, 36f) + quadToRelative(11f, 11f, 11f, 27.5f) + reflectiveQuadTo(308f, 772f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(148f, 668f) + quadToRelative(-6f, -6f, -8.5f, -13f) + reflectiveQuadToRelative(-2.5f, -15f) + quadToRelative(0f, -8f, 2.5f, -15f) + reflectiveQuadToRelative(8.5f, -13f) + lineToRelative(104f, -104f) + quadToRelative(11f, -11f, 27.5f, -11f) + reflectiveQuadToRelative(28.5f, 11f) + quadToRelative(12f, 12f, 12f, 28.5f) + reflectiveQuadTo(308f, 565f) + lineToRelative(-35f, 35f) + horizontalLineToRelative(127f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(440f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(400f, 680f) + lineTo(273f, 680f) + close() + moveTo(560f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(520f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(560f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(600f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(560f, 680f) + close() + moveTo(720f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(680f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(720f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(760f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(720f, 680f) + close() + moveTo(687f, 360f) + lineTo(560f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(520f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(560f, 280f) + horizontalLineToRelative(127f) + lineToRelative(-36f, -36f) + quadToRelative(-11f, -11f, -11f, -27.5f) + reflectiveQuadToRelative(12f, -28.5f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(104f, 104f) + quadToRelative(6f, 6f, 8.5f, 13f) + reflectiveQuadToRelative(2.5f, 15f) + quadToRelative(0f, 8f, -2.5f, 15f) + reflectiveQuadToRelative(-8.5f, 13f) + lineTo(708f, 452f) + quadToRelative(-11f, 11f, -27.5f, 11f) + reflectiveQuadTo(652f, 452f) + quadToRelative(-12f, -12f, -12f, -28.5f) + reflectiveQuadToRelative(12f, -28.5f) + lineToRelative(35f, -35f) + close() + moveTo(240f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(200f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(240f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(280f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 360f) + close() + moveTo(400f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(360f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(400f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(440f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(400f, 360f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MusicAdd.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MusicAdd.kt new file mode 100644 index 0000000..268d7b5 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MusicAdd.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NeonBrush.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NeonBrush.kt new file mode 100644 index 0000000..5137b26 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NeonBrush.kt @@ -0,0 +1,95 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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.Black), + stroke = SolidColor(Color.Black), + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Neurology.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Neurology.kt new file mode 100644 index 0000000..1395cbf --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Neurology.kt @@ -0,0 +1,362 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NextPlan.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NextPlan.kt new file mode 100644 index 0000000..fef64db --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NextPlan.kt @@ -0,0 +1,96 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.NextPlan: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.NextPlan", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(584f, 480f) + horizontalLineToRelative(-24f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(520f, 520f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(560f, 560f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(720f, 520f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(680f, 360f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(640f, 400f) + verticalLineToRelative(22f) + quadToRelative(-32f, -38f, -76.5f, -60f) + reflectiveQuadTo(466f, 340f) + quadToRelative(-83f, 0f, -143.5f, 49.5f) + reflectiveQuadTo(245f, 514f) + quadToRelative(-4f, 18f, 7f, 32f) + reflectiveQuadToRelative(28f, 14f) + quadToRelative(17f, 0f, 29.5f, -12.5f) + reflectiveQuadTo(327f, 518f) + quadToRelative(14f, -43f, 52f, -70.5f) + reflectiveQuadToRelative(87f, -27.5f) + quadToRelative(36f, 0f, 67f, 16.5f) + reflectiveQuadToRelative(51f, 43.5f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NoiseAlt.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NoiseAlt.kt new file mode 100644 index 0000000..77f462d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NoiseAlt.kt @@ -0,0 +1,151 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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(19f, 19f) + moveToRelative(-1f, 0f) + arcToRelative(1f, 1f, 0f, isMoreThanHalf = true, isPositiveArc = true, 2f, 0f) + arcToRelative(1f, 1f, 0f, isMoreThanHalf = true, isPositiveArc = true, -2f, 0f) + } + path(fill = SolidColor(Color.Black)) { + moveTo(15f, 16f) + curveToRelative(-0.552f, 0f, -1f, 0.448f, -1f, 1f) + verticalLineToRelative(2f) + curveToRelative(0f, 0.552f, 0.448f, 1f, 1f, 1f) + reflectiveCurveToRelative(1f, -0.448f, 1f, -1f) + verticalLineToRelative(-2f) + curveToRelative(0f, -0.552f, -0.448f, -1f, -1f, -1f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(19f, 10f) + horizontalLineToRelative(-1f) + verticalLineToRelative(-2f) + horizontalLineToRelative(1f) + curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) + curveToRelative(0f, -0.552f, -0.448f, -1f, -1f, -1f) + horizontalLineToRelative(-1f) + verticalLineToRelative(-1f) + curveToRelative(0f, -0.552f, -0.448f, -1f, -1f, -1f) + horizontalLineToRelative(-4f) + curveToRelative(-0.552f, 0f, -1f, 0.448f, -1f, 1f) + verticalLineToRelative(1f) + horizontalLineToRelative(-2f) + verticalLineToRelative(-1f) + curveToRelative(0f, -0.552f, -0.448f, -1f, -1f, -1f) + reflectiveCurveToRelative(-1f, 0.448f, -1f, 1f) + verticalLineToRelative(1f) + horizontalLineToRelative(-1f) + curveToRelative(-0.552f, 0f, -1f, 0.448f, -1f, 1f) + curveToRelative(0f, 0.552f, 0.448f, 1f, 1f, 1f) + horizontalLineToRelative(3f) + verticalLineToRelative(2f) + horizontalLineToRelative(-4f) + verticalLineToRelative(-1f) + curveToRelative(0f, -0.552f, -0.448f, -1f, -1f, -1f) + reflectiveCurveToRelative(-1f, 0.448f, -1f, 1f) + verticalLineToRelative(6f) + curveToRelative(0f, 0.552f, 0.448f, 1f, 1f, 1f) + horizontalLineToRelative(1f) + verticalLineToRelative(1f) + curveToRelative(0f, 0.552f, 0.448f, 1f, 1f, 1f) + reflectiveCurveToRelative(1f, -0.448f, 1f, -1f) + verticalLineToRelative(-1f) + horizontalLineToRelative(1f) + curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) + verticalLineToRelative(-1f) + horizontalLineToRelative(2f) + verticalLineToRelative(1f) + curveToRelative(0f, 0.552f, 0.448f, 1f, 1f, 1f) + reflectiveCurveToRelative(1f, -0.448f, 1f, -1f) + verticalLineToRelative(-1f) + horizontalLineToRelative(1f) + curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) + verticalLineToRelative(-1f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-1f) + curveToRelative(-0.552f, 0f, -1f, 0.448f, -1f, 1f) + curveToRelative(0f, 0.552f, 0.448f, 1f, 1f, 1f) + horizontalLineToRelative(2f) + curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) + verticalLineToRelative(-4f) + curveToRelative(0f, -0.552f, -0.448f, -1f, -1f, -1f) + close() + moveTo(6f, 14f) + verticalLineToRelative(-2f) + horizontalLineToRelative(2f) + verticalLineToRelative(2f) + horizontalLineToRelative(-2f) + close() + moveTo(15f, 10f) + curveToRelative(-0.552f, 0f, -1f, 0.448f, -1f, 1f) + verticalLineToRelative(1f) + horizontalLineToRelative(-2f) + verticalLineToRelative(-2f) + horizontalLineToRelative(1f) + curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) + verticalLineToRelative(-3f) + horizontalLineToRelative(2f) + verticalLineToRelative(4f) + horizontalLineToRelative(-1f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(5f, 5f) + moveToRelative(-1f, 0f) + arcToRelative(1f, 1f, 0f, isMoreThanHalf = true, isPositiveArc = true, 2f, 0f) + arcToRelative(1f, 1f, 0f, isMoreThanHalf = true, isPositiveArc = true, -2f, 0f) + } + path(fill = SolidColor(Color.Black)) { + moveTo(11f, 16f) + curveToRelative(-0.552f, 0f, -1f, 0.448f, -1f, 1f) + verticalLineToRelative(1f) + horizontalLineToRelative(-1f) + curveToRelative(-0.552f, 0f, -1f, 0.448f, -1f, 1f) + curveToRelative(0f, 0.552f, 0.448f, 1f, 1f, 1f) + horizontalLineToRelative(2f) + curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) + verticalLineToRelative(-2f) + curveToRelative(0f, -0.552f, -0.448f, -1f, -1f, -1f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(5f, 19f) + moveToRelative(-1f, 0f) + arcToRelative(1f, 1f, 0f, isMoreThanHalf = true, isPositiveArc = true, 2f, 0f) + arcToRelative(1f, 1f, 0f, isMoreThanHalf = true, isPositiveArc = true, -2f, 0f) + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NoteAdd.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NoteAdd.kt new file mode 100644 index 0000000..a6a6206 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NoteAdd.kt @@ -0,0 +1,89 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.NoteAdd: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.NoteAdd", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(240f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 800f) + verticalLineToRelative(-640f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(240f, 80f) + horizontalLineToRelative(287f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(194f, 194f) + quadToRelative(11f, 11f, 17f, 25.5f) + reflectiveQuadToRelative(6f, 30.5f) + verticalLineToRelative(447f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 880f) + lineTo(240f, 880f) + close() + moveTo(520f, 320f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(560f, 360f) + horizontalLineToRelative(160f) + lineTo(520f, 160f) + verticalLineToRelative(160f) + close() + moveTo(440f, 600f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 720f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 680f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(640f, 560f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(600f, 520f) + horizontalLineToRelative(-80f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 400f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 440f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(320f, 560f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(360f, 600f) + horizontalLineToRelative(80f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NoteSticky.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NoteSticky.kt new file mode 100644 index 0000000..78f7b87 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NoteSticky.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.NoteSticky: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.NoteSticky", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(200f, 760f) + horizontalLineToRelative(360f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(600f, 560f) + horizontalLineToRelative(160f) + verticalLineToRelative(-360f) + lineTo(200f, 200f) + verticalLineToRelative(560f) + 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(367f) + quadToRelative(0f, 16f, -6f, 30.5f) + reflectiveQuadTo(817f, 623f) + lineTo(623f, 817f) + quadToRelative(-11f, 11f, -25.5f, 17f) + reflectiveQuadToRelative(-30.5f, 6f) + lineTo(200f, 840f) + close() + moveTo(440f, 560f) + lineTo(320f, 560f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 520f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 480f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(480f, 520f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(440f, 560f) + close() + moveTo(640f, 400f) + lineTo(320f, 400f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 360f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 320f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 360f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 400f) + close() + moveTo(200f, 760f) + verticalLineToRelative(-560f) + verticalLineToRelative(560f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Numeric.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Numeric.kt new file mode 100644 index 0000000..e5d736a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Numeric.kt @@ -0,0 +1,109 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Opacity.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Opacity.kt new file mode 100644 index 0000000..9ef56bb --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Opacity.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Opacity: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Opacity", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 840f) + quadToRelative(-133f, 0f, -226.5f, -92f) + reflectiveQuadTo(160f, 524f) + quadToRelative(0f, -65f, 25f, -121.5f) + reflectiveQuadTo(254f, 302f) + lineToRelative(184f, -181f) + quadToRelative(9f, -8f, 20f, -12.5f) + reflectiveQuadToRelative(22f, -4.5f) + quadToRelative(11f, 0f, 22f, 4.5f) + reflectiveQuadToRelative(20f, 12.5f) + lineToRelative(184f, 181f) + quadToRelative(44f, 44f, 69f, 100.5f) + reflectiveQuadTo(800f, 524f) + quadToRelative(0f, 132f, -93.5f, 224f) + reflectiveQuadTo(480f, 840f) + close() + moveTo(242f, 560f) + horizontalLineToRelative(474f) + quadToRelative(12f, -72f, -13.5f, -123f) + reflectiveQuadTo(650f, 360f) + lineTo(480f, 192f) + lineTo(310f, 360f) + quadToRelative(-27f, 26f, -53f, 77f) + reflectiveQuadToRelative(-15f, 123f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/OpenInNew.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/OpenInNew.kt new file mode 100644 index 0000000..fe00200 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/OpenInNew.kt @@ -0,0 +1,85 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.OpenInNew: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.OpenInNew", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).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(240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(480f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(440f, 200f) + lineTo(200f, 200f) + verticalLineToRelative(560f) + horizontalLineToRelative(560f) + verticalLineToRelative(-240f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 480f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 520f) + verticalLineToRelative(240f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 840f) + lineTo(200f, 840f) + close() + moveTo(760f, 256f) + lineTo(416f, 600f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + lineToRelative(344f, -344f) + lineTo(600f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(560f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(600f, 120f) + horizontalLineToRelative(200f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + verticalLineToRelative(200f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 400f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 360f) + verticalLineToRelative(-104f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Padding.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Padding.kt new file mode 100644 index 0000000..4ffd4fa --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Padding.kt @@ -0,0 +1,92 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Padding: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Padding", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(320f, 360f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(360f, 320f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(320f, 280f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(280f, 320f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(320f, 360f) + close() + moveTo(480f, 360f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 320f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 320f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 360f) + close() + moveTo(640f, 360f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(680f, 320f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(640f, 280f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(600f, 320f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(640f, 360f) + 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(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, 200f) + verticalLineToRelative(560f) + verticalLineToRelative(-560f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Pages.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Pages.kt new file mode 100644 index 0000000..9332d06 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Pages.kt @@ -0,0 +1,104 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Pages: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Pages", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(458.5f, 779f) + quadToRelative(-10.5f, -3f, -19.5f, -8f) + quadToRelative(-41f, -25f, -86f, -38f) + reflectiveQuadToRelative(-93f, -13f) + quadToRelative(-42f, 0f, -82.5f, 11f) + reflectiveQuadTo(100f, 762f) + quadToRelative(-21f, 11f, -40.5f, -1f) + reflectiveQuadTo(40f, 726f) + verticalLineToRelative(-482f) + quadToRelative(0f, -11f, 5.5f, -21f) + reflectiveQuadTo(62f, 208f) + quadToRelative(46f, -24f, 96f, -36f) + reflectiveQuadToRelative(102f, -12f) + quadToRelative(58f, 0f, 113.5f, 15f) + reflectiveQuadTo(480f, 220f) + verticalLineToRelative(484f) + quadToRelative(51f, -32f, 107f, -48f) + reflectiveQuadToRelative(113f, -16f) + quadToRelative(36f, 0f, 70.5f, 6f) + reflectiveQuadToRelative(69.5f, 18f) + verticalLineToRelative(-480f) + quadToRelative(15f, 5f, 29.5f, 10.5f) + reflectiveQuadTo(898f, 208f) + quadToRelative(11f, 5f, 16.5f, 15f) + reflectiveQuadToRelative(5.5f, 21f) + verticalLineToRelative(482f) + quadToRelative(0f, 23f, -19.5f, 35f) + reflectiveQuadToRelative(-40.5f, 1f) + quadToRelative(-37f, -20f, -77.5f, -31f) + reflectiveQuadTo(700f, 720f) + quadToRelative(-48f, 0f, -93f, 13f) + reflectiveQuadToRelative(-86f, 38f) + quadToRelative(-9f, 5f, -19.5f, 8f) + reflectiveQuadToRelative(-21.5f, 3f) + quadToRelative(-11f, 0f, -21.5f, -3f) + close() + moveTo(593f, 570f) + quadToRelative(-10f, 9f, -21.5f, 3.5f) + reflectiveQuadTo(560f, 555f) + verticalLineToRelative(-327f) + quadToRelative(0f, -4f, 1.5f, -7.5f) + reflectiveQuadToRelative(4.5f, -6.5f) + lineToRelative(160f, -160f) + quadToRelative(10f, -10f, 22f, -5f) + reflectiveQuadToRelative(12f, 19f) + verticalLineToRelative(343f) + quadToRelative(0f, 5f, -2f, 8.5f) + reflectiveQuadToRelative(-5f, 6.5f) + lineTo(593f, 570f) + close() + moveTo(400f, 665f) + verticalLineToRelative(-396f) + quadToRelative(-33f, -14f, -68.5f, -21.5f) + reflectiveQuadTo(260f, 240f) + quadToRelative(-37f, 0f, -72f, 7f) + reflectiveQuadToRelative(-68f, 21f) + verticalLineToRelative(397f) + quadToRelative(35f, -13f, 69.5f, -19f) + reflectiveQuadToRelative(70.5f, -6f) + quadToRelative(36f, 0f, 70.5f, 6f) + reflectiveQuadToRelative(69.5f, 19f) + close() + moveTo(400f, 665f) + verticalLineToRelative(-396f) + verticalLineToRelative(396f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Palette.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Palette.kt new file mode 100644 index 0000000..96ab795 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Palette.kt @@ -0,0 +1,320 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Palette: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Palette", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-82f, 0f, -155f, -31.5f) + reflectiveQuadToRelative(-127.5f, -86f) + quadTo(143f, 708f, 111.5f, 635f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 32.5f, -156f) + reflectiveQuadToRelative(88f, -127f) + quadTo(256f, 143f, 330f, 111.5f) + reflectiveQuadTo(488f, 80f) + quadToRelative(80f, 0f, 151f, 27.5f) + reflectiveQuadToRelative(124.5f, 76f) + quadToRelative(53.5f, 48.5f, 85f, 115f) + reflectiveQuadTo(880f, 442f) + quadToRelative(0f, 115f, -70f, 176.5f) + reflectiveQuadTo(640f, 680f) + horizontalLineToRelative(-74f) + quadToRelative(-9f, 0f, -12.5f, 5f) + reflectiveQuadToRelative(-3.5f, 11f) + quadToRelative(0f, 12f, 15f, 34.5f) + reflectiveQuadToRelative(15f, 51.5f) + quadToRelative(0f, 50f, -27.5f, 74f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(303f, 503f) + quadToRelative(17f, -17f, 17f, -43f) + reflectiveQuadToRelative(-17f, -43f) + quadToRelative(-17f, -17f, -43f, -17f) + reflectiveQuadToRelative(-43f, 17f) + quadToRelative(-17f, 17f, -17f, 43f) + reflectiveQuadToRelative(17f, 43f) + quadToRelative(17f, 17f, 43f, 17f) + reflectiveQuadToRelative(43f, -17f) + close() + moveTo(423f, 343f) + quadToRelative(17f, -17f, 17f, -43f) + reflectiveQuadToRelative(-17f, -43f) + quadToRelative(-17f, -17f, -43f, -17f) + reflectiveQuadToRelative(-43f, 17f) + quadToRelative(-17f, 17f, -17f, 43f) + reflectiveQuadToRelative(17f, 43f) + quadToRelative(17f, 17f, 43f, 17f) + reflectiveQuadToRelative(43f, -17f) + close() + moveTo(623f, 343f) + quadToRelative(17f, -17f, 17f, -43f) + reflectiveQuadToRelative(-17f, -43f) + quadToRelative(-17f, -17f, -43f, -17f) + reflectiveQuadToRelative(-43f, 17f) + quadToRelative(-17f, 17f, -17f, 43f) + reflectiveQuadToRelative(17f, 43f) + quadToRelative(17f, 17f, 43f, 17f) + reflectiveQuadToRelative(43f, -17f) + close() + moveTo(743f, 503f) + quadToRelative(17f, -17f, 17f, -43f) + reflectiveQuadToRelative(-17f, -43f) + quadToRelative(-17f, -17f, -43f, -17f) + reflectiveQuadToRelative(-43f, 17f) + quadToRelative(-17f, 17f, -17f, 43f) + reflectiveQuadToRelative(17f, 43f) + quadToRelative(17f, 17f, 43f, 17f) + reflectiveQuadToRelative(43f, -17f) + close() + } + }.build() +} + +val Icons.Outlined.Palette: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Palette", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-82f, 0f, -155f, -31.5f) + reflectiveQuadToRelative(-127.5f, -86f) + quadTo(143f, 708f, 111.5f, 635f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 32.5f, -156f) + reflectiveQuadToRelative(88f, -127f) + quadTo(256f, 143f, 330f, 111.5f) + reflectiveQuadTo(488f, 80f) + quadToRelative(80f, 0f, 151f, 27.5f) + reflectiveQuadToRelative(124.5f, 76f) + quadToRelative(53.5f, 48.5f, 85f, 115f) + reflectiveQuadTo(880f, 442f) + quadToRelative(0f, 115f, -70f, 176.5f) + reflectiveQuadTo(640f, 680f) + horizontalLineToRelative(-74f) + quadToRelative(-9f, 0f, -12.5f, 5f) + reflectiveQuadToRelative(-3.5f, 11f) + quadToRelative(0f, 12f, 15f, 34.5f) + reflectiveQuadToRelative(15f, 51.5f) + quadToRelative(0f, 50f, -27.5f, 74f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 480f) + close() + moveTo(303f, 503f) + quadToRelative(17f, -17f, 17f, -43f) + reflectiveQuadToRelative(-17f, -43f) + quadToRelative(-17f, -17f, -43f, -17f) + reflectiveQuadToRelative(-43f, 17f) + quadToRelative(-17f, 17f, -17f, 43f) + reflectiveQuadToRelative(17f, 43f) + quadToRelative(17f, 17f, 43f, 17f) + reflectiveQuadToRelative(43f, -17f) + close() + moveTo(423f, 343f) + quadToRelative(17f, -17f, 17f, -43f) + reflectiveQuadToRelative(-17f, -43f) + quadToRelative(-17f, -17f, -43f, -17f) + reflectiveQuadToRelative(-43f, 17f) + quadToRelative(-17f, 17f, -17f, 43f) + reflectiveQuadToRelative(17f, 43f) + quadToRelative(17f, 17f, 43f, 17f) + reflectiveQuadToRelative(43f, -17f) + close() + moveTo(623f, 343f) + quadToRelative(17f, -17f, 17f, -43f) + reflectiveQuadToRelative(-17f, -43f) + quadToRelative(-17f, -17f, -43f, -17f) + reflectiveQuadToRelative(-43f, 17f) + quadToRelative(-17f, 17f, -17f, 43f) + reflectiveQuadToRelative(17f, 43f) + quadToRelative(17f, 17f, 43f, 17f) + reflectiveQuadToRelative(43f, -17f) + close() + moveTo(743f, 503f) + quadToRelative(17f, -17f, 17f, -43f) + reflectiveQuadToRelative(-17f, -43f) + quadToRelative(-17f, -17f, -43f, -17f) + reflectiveQuadToRelative(-43f, 17f) + quadToRelative(-17f, 17f, -17f, 43f) + reflectiveQuadToRelative(17f, 43f) + quadToRelative(17f, 17f, 43f, 17f) + reflectiveQuadToRelative(43f, -17f) + close() + moveTo(480f, 800f) + quadToRelative(9f, 0f, 14.5f, -5f) + reflectiveQuadToRelative(5.5f, -13f) + quadToRelative(0f, -14f, -15f, -33f) + reflectiveQuadToRelative(-15f, -57f) + quadToRelative(0f, -42f, 29f, -67f) + reflectiveQuadToRelative(71f, -25f) + horizontalLineToRelative(70f) + quadToRelative(66f, 0f, 113f, -38.5f) + reflectiveQuadTo(800f, 442f) + quadToRelative(0f, -121f, -92.5f, -201.5f) + reflectiveQuadTo(488f, 160f) + quadToRelative(-136f, 0f, -232f, 93f) + reflectiveQuadToRelative(-96f, 227f) + quadToRelative(0f, 133f, 93.5f, 226.5f) + reflectiveQuadTo(480f, 800f) + close() + } + }.build() +} + +val Icons.TwoTone.Palette: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.Palette", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(7.575f, 12.575f) + curveToRelative(0.283f, -0.283f, 0.425f, -0.642f, 0.425f, -1.075f) + curveToRelative(0f, -0.433f, -0.142f, -0.792f, -0.425f, -1.075f) + reflectiveCurveToRelative(-0.642f, -0.425f, -1.075f, -0.425f) + curveToRelative(-0.433f, 0f, -0.792f, 0.142f, -1.075f, 0.425f) + reflectiveCurveToRelative(-0.425f, 0.642f, -0.425f, 1.075f) + curveToRelative(0f, 0.433f, 0.142f, 0.792f, 0.425f, 1.075f) + reflectiveCurveToRelative(0.642f, 0.425f, 1.075f, 0.425f) + curveToRelative(0.433f, 0f, 0.792f, -0.142f, 1.075f, -0.425f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(10.575f, 8.575f) + curveToRelative(0.283f, -0.283f, 0.425f, -0.642f, 0.425f, -1.075f) + reflectiveCurveToRelative(-0.142f, -0.792f, -0.425f, -1.075f) + reflectiveCurveToRelative(-0.642f, -0.425f, -1.075f, -0.425f) + reflectiveCurveToRelative(-0.792f, 0.142f, -1.075f, 0.425f) + reflectiveCurveToRelative(-0.425f, 0.642f, -0.425f, 1.075f) + reflectiveCurveToRelative(0.142f, 0.792f, 0.425f, 1.075f) + reflectiveCurveToRelative(0.642f, 0.425f, 1.075f, 0.425f) + reflectiveCurveToRelative(0.792f, -0.142f, 1.075f, -0.425f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(15.575f, 8.575f) + curveToRelative(0.283f, -0.283f, 0.425f, -0.642f, 0.425f, -1.075f) + reflectiveCurveToRelative(-0.142f, -0.792f, -0.425f, -1.075f) + reflectiveCurveToRelative(-0.642f, -0.425f, -1.075f, -0.425f) + reflectiveCurveToRelative(-0.792f, 0.142f, -1.075f, 0.425f) + reflectiveCurveToRelative(-0.425f, 0.642f, -0.425f, 1.075f) + reflectiveCurveToRelative(0.142f, 0.792f, 0.425f, 1.075f) + reflectiveCurveToRelative(0.642f, 0.425f, 1.075f, 0.425f) + reflectiveCurveToRelative(0.792f, -0.142f, 1.075f, -0.425f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(18.575f, 12.575f) + curveToRelative(0.283f, -0.283f, 0.425f, -0.642f, 0.425f, -1.075f) + curveToRelative(0f, -0.433f, -0.142f, -0.792f, -0.425f, -1.075f) + reflectiveCurveToRelative(-0.642f, -0.425f, -1.075f, -0.425f) + reflectiveCurveToRelative(-0.792f, 0.142f, -1.075f, 0.425f) + reflectiveCurveToRelative(-0.425f, 0.642f, -0.425f, 1.075f) + curveToRelative(0f, 0.433f, 0.142f, 0.792f, 0.425f, 1.075f) + reflectiveCurveToRelative(0.642f, 0.425f, 1.075f, 0.425f) + reflectiveCurveToRelative(0.792f, -0.142f, 1.075f, -0.425f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(21.213f, 7.463f) + curveToRelative(-0.525f, -1.108f, -1.233f, -2.067f, -2.125f, -2.875f) + curveToRelative(-0.892f, -0.808f, -1.929f, -1.442f, -3.113f, -1.9f) + curveToRelative(-1.183f, -0.458f, -2.442f, -0.688f, -3.775f, -0.688f) + curveToRelative(-1.4f, 0f, -2.717f, 0.263f, -3.95f, 0.787f) + curveToRelative(-1.233f, 0.525f, -2.313f, 1.238f, -3.237f, 2.138f) + curveToRelative(-0.925f, 0.9f, -1.658f, 1.958f, -2.2f, 3.175f) + curveToRelative(-0.542f, 1.217f, -0.813f, 2.517f, -0.813f, 3.9f) + curveToRelative(0f, 1.367f, 0.263f, 2.658f, 0.787f, 3.875f) + curveToRelative(0.525f, 1.217f, 1.242f, 2.279f, 2.15f, 3.188f) + reflectiveCurveToRelative(1.971f, 1.625f, 3.188f, 2.15f) + curveToRelative(1.217f, 0.525f, 2.508f, 0.787f, 3.875f, 0.787f) + curveToRelative(0.75f, 0f, 1.354f, -0.2f, 1.813f, -0.6f) + curveToRelative(0.458f, -0.4f, 0.688f, -1.017f, 0.688f, -1.85f) + curveToRelative(0f, -0.483f, -0.125f, -0.912f, -0.375f, -1.287f) + reflectiveCurveToRelative(-0.375f, -0.663f, -0.375f, -0.862f) + curveToRelative(0f, -0.1f, 0.029f, -0.192f, 0.088f, -0.275f) + curveToRelative(0.058f, -0.083f, 0.162f, -0.125f, 0.313f, -0.125f) + horizontalLineToRelative(1.85f) + curveToRelative(1.667f, 0f, 3.083f, -0.513f, 4.25f, -1.537f) + curveToRelative(1.167f, -1.025f, 1.75f, -2.496f, 1.75f, -4.413f) + curveToRelative(0f, -1.283f, -0.263f, -2.479f, -0.787f, -3.587f) + close() + moveTo(18.825f, 14.037f) + curveToRelative(-0.783f, 0.642f, -1.725f, 0.963f, -2.825f, 0.963f) + horizontalLineToRelative(-1.75f) + curveToRelative(-0.7f, 0f, -1.292f, 0.208f, -1.775f, 0.625f) + reflectiveCurveToRelative(-0.725f, 0.975f, -0.725f, 1.675f) + curveToRelative(0f, 0.633f, 0.125f, 1.108f, 0.375f, 1.425f) + curveToRelative(0.25f, 0.317f, 0.375f, 0.592f, 0.375f, 0.825f) + curveToRelative(0f, 0.133f, -0.046f, 0.242f, -0.138f, 0.325f) + reflectiveCurveToRelative(-0.212f, 0.125f, -0.362f, 0.125f) + curveToRelative(-2.217f, 0f, -4.104f, -0.779f, -5.662f, -2.338f) + curveToRelative(-1.558f, -1.558f, -2.338f, -3.446f, -2.338f, -5.662f) + curveToRelative(0f, -2.233f, 0.8f, -4.125f, 2.4f, -5.675f) + curveToRelative(1.6f, -1.55f, 3.533f, -2.325f, 5.8f, -2.325f) + curveToRelative(2.117f, 0f, 3.946f, 0.671f, 5.487f, 2.013f) + reflectiveCurveToRelative(2.313f, 3.021f, 2.313f, 5.037f) + curveToRelative(0f, 1.35f, -0.392f, 2.346f, -1.175f, 2.987f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(12f, 20f) + curveToRelative(0.15f, 0f, 0.271f, -0.042f, 0.363f, -0.125f) + reflectiveCurveToRelative(0.138f, -0.192f, 0.138f, -0.325f) + curveToRelative(0f, -0.233f, -0.125f, -0.508f, -0.375f, -0.825f) + reflectiveCurveToRelative(-0.375f, -0.792f, -0.375f, -1.425f) + curveToRelative(0f, -0.7f, 0.242f, -1.258f, 0.725f, -1.675f) + curveToRelative(0.483f, -0.417f, 1.075f, -0.625f, 1.775f, -0.625f) + horizontalLineToRelative(1.75f) + curveToRelative(1.1f, 0f, 2.042f, -0.321f, 2.825f, -0.962f) + reflectiveCurveToRelative(1.175f, -1.638f, 1.175f, -2.987f) + curveToRelative(0f, -2.017f, -0.771f, -3.696f, -2.313f, -5.037f) + reflectiveCurveToRelative(-3.371f, -2.013f, -5.488f, -2.013f) + curveToRelative(-2.267f, 0f, -4.2f, 0.775f, -5.8f, 2.325f) + reflectiveCurveToRelative(-2.4f, 3.442f, -2.4f, 5.675f) + curveToRelative(0f, 2.217f, 0.779f, 4.104f, 2.338f, 5.662f) + curveToRelative(1.558f, 1.558f, 3.446f, 2.338f, 5.662f, 2.338f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PaletteBox.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PaletteBox.kt new file mode 100644 index 0000000..4abeeb6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PaletteBox.kt @@ -0,0 +1,147 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PaletteSwatch.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PaletteSwatch.kt new file mode 100644 index 0000000..69cf94d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PaletteSwatch.kt @@ -0,0 +1,211 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), + 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.Black), + 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.Black)) { + 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.Black), + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Panorama.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Panorama.kt new file mode 100644 index 0000000..c450914 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Panorama.kt @@ -0,0 +1,140 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Password.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Password.kt new file mode 100644 index 0000000..9dea111 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Password.kt @@ -0,0 +1,164 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Password: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Password", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(120f, 680f) + horizontalLineToRelative(720f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 720f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 760f) + lineTo(120f, 760f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(80f, 720f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 680f) + close() + moveTo(160f, 458f) + lineTo(141f, 492f) + quadToRelative(-6f, 11f, -18f, 14f) + reflectiveQuadToRelative(-23f, -3f) + quadToRelative(-11f, -6f, -14f, -18f) + reflectiveQuadToRelative(3f, -23f) + lineToRelative(19f, -34f) + lineTo(70f, 428f) + quadToRelative(-13f, 0f, -21.5f, -8.5f) + reflectiveQuadTo(40f, 398f) + quadToRelative(0f, -13f, 8.5f, -21.5f) + reflectiveQuadTo(70f, 368f) + horizontalLineToRelative(38f) + lineToRelative(-19f, -32f) + quadToRelative(-6f, -11f, -3f, -23f) + reflectiveQuadToRelative(14f, -18f) + quadToRelative(11f, -6f, 23f, -3f) + reflectiveQuadToRelative(18f, 14f) + lineToRelative(19f, 32f) + lineToRelative(19f, -32f) + quadToRelative(6f, -11f, 18f, -14f) + reflectiveQuadToRelative(23f, 3f) + quadToRelative(11f, 6f, 14f, 18f) + reflectiveQuadToRelative(-3f, 23f) + lineToRelative(-19f, 32f) + horizontalLineToRelative(38f) + quadToRelative(13f, 0f, 21.5f, 8.5f) + reflectiveQuadTo(280f, 398f) + quadToRelative(0f, 13f, -8.5f, 21.5f) + reflectiveQuadTo(250f, 428f) + horizontalLineToRelative(-38f) + lineToRelative(19f, 34f) + quadToRelative(6f, 11f, 3f, 23f) + reflectiveQuadToRelative(-14f, 18f) + quadToRelative(-11f, 6f, -23f, 3f) + reflectiveQuadToRelative(-18f, -14f) + lineToRelative(-19f, -34f) + close() + moveTo(480f, 458f) + lineTo(461f, 492f) + quadToRelative(-6f, 11f, -18f, 14f) + reflectiveQuadToRelative(-23f, -3f) + quadToRelative(-11f, -6f, -14f, -18f) + reflectiveQuadToRelative(3f, -23f) + lineToRelative(19f, -34f) + horizontalLineToRelative(-38f) + quadToRelative(-13f, 0f, -21.5f, -8.5f) + reflectiveQuadTo(360f, 398f) + quadToRelative(0f, -13f, 8.5f, -21.5f) + reflectiveQuadTo(390f, 368f) + horizontalLineToRelative(38f) + lineToRelative(-19f, -32f) + quadToRelative(-6f, -11f, -3f, -23f) + reflectiveQuadToRelative(14f, -18f) + quadToRelative(11f, -6f, 23f, -3f) + reflectiveQuadToRelative(18f, 14f) + lineToRelative(19f, 32f) + lineToRelative(19f, -32f) + quadToRelative(6f, -11f, 18f, -14f) + reflectiveQuadToRelative(23f, 3f) + quadToRelative(11f, 6f, 14f, 18f) + reflectiveQuadToRelative(-3f, 23f) + lineToRelative(-19f, 32f) + horizontalLineToRelative(38f) + quadToRelative(13f, 0f, 21.5f, 8.5f) + reflectiveQuadTo(600f, 398f) + quadToRelative(0f, 13f, -8.5f, 21.5f) + reflectiveQuadTo(570f, 428f) + horizontalLineToRelative(-38f) + lineToRelative(19f, 34f) + quadToRelative(6f, 11f, 3f, 23f) + reflectiveQuadToRelative(-14f, 18f) + quadToRelative(-11f, 6f, -23f, 3f) + reflectiveQuadToRelative(-18f, -14f) + lineToRelative(-19f, -34f) + close() + moveTo(800f, 458f) + lineTo(781f, 492f) + quadToRelative(-6f, 11f, -18f, 14f) + reflectiveQuadToRelative(-23f, -3f) + quadToRelative(-11f, -6f, -14f, -18f) + reflectiveQuadToRelative(3f, -23f) + lineToRelative(19f, -34f) + horizontalLineToRelative(-38f) + quadToRelative(-13f, 0f, -21.5f, -8.5f) + reflectiveQuadTo(680f, 398f) + quadToRelative(0f, -13f, 8.5f, -21.5f) + reflectiveQuadTo(710f, 368f) + horizontalLineToRelative(38f) + lineToRelative(-19f, -32f) + quadToRelative(-6f, -11f, -3f, -23f) + reflectiveQuadToRelative(14f, -18f) + quadToRelative(11f, -6f, 23f, -3f) + reflectiveQuadToRelative(18f, 14f) + lineToRelative(19f, 32f) + lineToRelative(19f, -32f) + quadToRelative(6f, -11f, 18f, -14f) + reflectiveQuadToRelative(23f, 3f) + quadToRelative(11f, 6f, 14f, 18f) + reflectiveQuadToRelative(-3f, 23f) + lineToRelative(-19f, 32f) + horizontalLineToRelative(38f) + quadToRelative(13f, 0f, 21.5f, 8.5f) + reflectiveQuadTo(920f, 398f) + quadToRelative(0f, 13f, -8.5f, 21.5f) + reflectiveQuadTo(890f, 428f) + horizontalLineToRelative(-38f) + lineToRelative(19f, 34f) + quadToRelative(6f, 11f, 3f, 23f) + reflectiveQuadToRelative(-14f, 18f) + quadToRelative(-11f, 6f, -23f, 3f) + reflectiveQuadToRelative(-18f, -14f) + lineToRelative(-19f, -34f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Pdf.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Pdf.kt new file mode 100644 index 0000000..6da5617 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Pdf.kt @@ -0,0 +1,293 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Pen.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Pen.kt new file mode 100644 index 0000000..858ca67 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Pen.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Percent.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Percent.kt new file mode 100644 index 0000000..e43affb --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Percent.kt @@ -0,0 +1,90 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Percent: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Percent", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(300f, 440f) + quadToRelative(-58f, 0f, -99f, -41f) + reflectiveQuadToRelative(-41f, -99f) + quadToRelative(0f, -58f, 41f, -99f) + reflectiveQuadToRelative(99f, -41f) + quadToRelative(58f, 0f, 99f, 41f) + reflectiveQuadToRelative(41f, 99f) + quadToRelative(0f, 58f, -41f, 99f) + reflectiveQuadToRelative(-99f, 41f) + close() + moveTo(300f, 360f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(360f, 300f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(300f, 240f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(240f, 300f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(300f, 360f) + close() + moveTo(660f, 800f) + quadToRelative(-58f, 0f, -99f, -41f) + reflectiveQuadToRelative(-41f, -99f) + quadToRelative(0f, -58f, 41f, -99f) + reflectiveQuadToRelative(99f, -41f) + quadToRelative(58f, 0f, 99f, 41f) + reflectiveQuadToRelative(41f, 99f) + quadToRelative(0f, 58f, -41f, 99f) + reflectiveQuadToRelative(-99f, 41f) + close() + moveTo(660f, 720f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(720f, 660f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(660f, 600f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(600f, 660f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(660f, 720f) + close() + moveTo(188f, 772f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + lineToRelative(528f, -528f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineTo(244f, 772f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Person.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Person.kt new file mode 100644 index 0000000..143fd86 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Person.kt @@ -0,0 +1,132 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Person: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Person", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(367f, 433f) + quadToRelative(-47f, -47f, -47f, -113f) + reflectiveQuadToRelative(47f, -113f) + quadToRelative(47f, -47f, 113f, -47f) + reflectiveQuadToRelative(113f, 47f) + quadToRelative(47f, 47f, 47f, 113f) + reflectiveQuadToRelative(-47f, 113f) + quadToRelative(-47f, 47f, -113f, 47f) + reflectiveQuadToRelative(-113f, -47f) + close() + moveTo(160f, 720f) + verticalLineToRelative(-32f) + quadToRelative(0f, -34f, 17.5f, -62.5f) + reflectiveQuadTo(224f, 582f) + quadToRelative(62f, -31f, 126f, -46.5f) + reflectiveQuadTo(480f, 520f) + quadToRelative(66f, 0f, 130f, 15.5f) + reflectiveQuadTo(736f, 582f) + quadToRelative(29f, 15f, 46.5f, 43.5f) + reflectiveQuadTo(800f, 688f) + verticalLineToRelative(32f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 800f) + lineTo(240f, 800f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 720f) + close() + moveTo(240f, 720f) + horizontalLineToRelative(480f) + verticalLineToRelative(-32f) + quadToRelative(0f, -11f, -5.5f, -20f) + reflectiveQuadTo(700f, 654f) + quadToRelative(-54f, -27f, -109f, -40.5f) + reflectiveQuadTo(480f, 600f) + quadToRelative(-56f, 0f, -111f, 13.5f) + reflectiveQuadTo(260f, 654f) + quadToRelative(-9f, 5f, -14.5f, 14f) + reflectiveQuadToRelative(-5.5f, 20f) + verticalLineToRelative(32f) + close() + moveTo(536.5f, 376.5f) + quadTo(560f, 353f, 560f, 320f) + reflectiveQuadToRelative(-23.5f, -56.5f) + quadTo(513f, 240f, 480f, 240f) + reflectiveQuadToRelative(-56.5f, 23.5f) + quadTo(400f, 287f, 400f, 320f) + reflectiveQuadToRelative(23.5f, 56.5f) + quadTo(447f, 400f, 480f, 400f) + reflectiveQuadToRelative(56.5f, -23.5f) + close() + moveTo(480f, 320f) + close() + moveTo(480f, 720f) + close() + } + }.build() +} + +val Icons.Rounded.Person: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Person", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(367f, 433f) + quadToRelative(-47f, -47f, -47f, -113f) + reflectiveQuadToRelative(47f, -113f) + quadToRelative(47f, -47f, 113f, -47f) + reflectiveQuadToRelative(113f, 47f) + quadToRelative(47f, 47f, 47f, 113f) + reflectiveQuadToRelative(-47f, 113f) + quadToRelative(-47f, 47f, -113f, 47f) + reflectiveQuadToRelative(-113f, -47f) + close() + moveTo(160f, 720f) + verticalLineToRelative(-32f) + quadToRelative(0f, -34f, 17.5f, -62.5f) + reflectiveQuadTo(224f, 582f) + quadToRelative(62f, -31f, 126f, -46.5f) + reflectiveQuadTo(480f, 520f) + quadToRelative(66f, 0f, 130f, 15.5f) + reflectiveQuadTo(736f, 582f) + quadToRelative(29f, 15f, 46.5f, 43.5f) + reflectiveQuadTo(800f, 688f) + verticalLineToRelative(32f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 800f) + lineTo(240f, 800f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 720f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Perspective.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Perspective.kt new file mode 100644 index 0000000..0149e0e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Perspective.kt @@ -0,0 +1,91 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), + 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.Black), + stroke = SolidColor(Color.Black), + 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.Black), + stroke = SolidColor(Color.Black), + 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.Black), + stroke = SolidColor(Color.Black), + 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.Black), + stroke = SolidColor(Color.Black), + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Phone.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Phone.kt new file mode 100644 index 0000000..0fdf61c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Phone.kt @@ -0,0 +1,202 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Phone: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Phone", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(19.95f, 21f) + quadTo(16.825f, 21f, 13.775f, 19.638f) + quadTo(10.725f, 18.275f, 8.225f, 15.775f) + quadTo(5.725f, 13.275f, 4.363f, 10.225f) + quadTo(3f, 7.175f, 3f, 4.05f) + quadTo(3f, 3.6f, 3.3f, 3.3f) + quadTo(3.6f, 3f, 4.05f, 3f) + horizontalLineTo(8.1f) + quadTo(8.45f, 3f, 8.725f, 3.237f) + quadTo(9f, 3.475f, 9.05f, 3.8f) + lineTo(9.7f, 7.3f) + quadTo(9.75f, 7.7f, 9.675f, 7.975f) + quadTo(9.6f, 8.25f, 9.4f, 8.45f) + lineTo(6.975f, 10.9f) + quadTo(7.475f, 11.825f, 8.163f, 12.688f) + quadTo(8.85f, 13.55f, 9.675f, 14.35f) + quadTo(10.45f, 15.125f, 11.3f, 15.788f) + quadTo(12.15f, 16.45f, 13.1f, 17f) + lineTo(15.45f, 14.65f) + quadTo(15.675f, 14.425f, 16.038f, 14.313f) + quadTo(16.4f, 14.2f, 16.75f, 14.25f) + lineTo(20.2f, 14.95f) + quadTo(20.55f, 15.05f, 20.775f, 15.313f) + quadTo(21f, 15.575f, 21f, 15.9f) + verticalLineTo(19.95f) + quadTo(21f, 20.4f, 20.7f, 20.7f) + quadTo(20.4f, 21f, 19.95f, 21f) + close() + moveTo(6.025f, 9f) + lineTo(7.675f, 7.35f) + quadTo(7.675f, 7.35f, 7.675f, 7.35f) + quadTo(7.675f, 7.35f, 7.675f, 7.35f) + lineTo(7.25f, 5f) + quadTo(7.25f, 5f, 7.25f, 5f) + quadTo(7.25f, 5f, 7.25f, 5f) + horizontalLineTo(5.025f) + quadTo(5.025f, 5f, 5.025f, 5f) + quadTo(5.025f, 5f, 5.025f, 5f) + quadTo(5.15f, 6.025f, 5.375f, 7.025f) + quadTo(5.6f, 8.025f, 6.025f, 9f) + close() + moveTo(14.975f, 17.95f) + quadTo(15.95f, 18.375f, 16.962f, 18.625f) + quadTo(17.975f, 18.875f, 19f, 18.95f) + quadTo(19f, 18.95f, 19f, 18.95f) + quadTo(19f, 18.95f, 19f, 18.95f) + verticalLineTo(16.75f) + quadTo(19f, 16.75f, 19f, 16.75f) + quadTo(19f, 16.75f, 19f, 16.75f) + lineTo(16.65f, 16.275f) + quadTo(16.65f, 16.275f, 16.65f, 16.275f) + quadTo(16.65f, 16.275f, 16.65f, 16.275f) + close() + moveTo(6.025f, 9f) + quadTo(6.025f, 9f, 6.025f, 9f) + quadTo(6.025f, 9f, 6.025f, 9f) + quadTo(6.025f, 9f, 6.025f, 9f) + quadTo(6.025f, 9f, 6.025f, 9f) + quadTo(6.025f, 9f, 6.025f, 9f) + quadTo(6.025f, 9f, 6.025f, 9f) + quadTo(6.025f, 9f, 6.025f, 9f) + quadTo(6.025f, 9f, 6.025f, 9f) + close() + moveTo(14.975f, 17.95f) + quadTo(14.975f, 17.95f, 14.975f, 17.95f) + quadTo(14.975f, 17.95f, 14.975f, 17.95f) + quadTo(14.975f, 17.95f, 14.975f, 17.95f) + quadTo(14.975f, 17.95f, 14.975f, 17.95f) + quadTo(14.975f, 17.95f, 14.975f, 17.95f) + quadTo(14.975f, 17.95f, 14.975f, 17.95f) + quadTo(14.975f, 17.95f, 14.975f, 17.95f) + quadTo(14.975f, 17.95f, 14.975f, 17.95f) + close() + } + }.build() +} + +val Icons.Rounded.Phone: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Phone", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(19.95f, 21f) + quadTo(16.825f, 21f, 13.775f, 19.638f) + quadTo(10.725f, 18.275f, 8.225f, 15.775f) + quadTo(5.725f, 13.275f, 4.363f, 10.225f) + quadTo(3f, 7.175f, 3f, 4.05f) + quadTo(3f, 3.6f, 3.3f, 3.3f) + quadTo(3.6f, 3f, 4.05f, 3f) + horizontalLineTo(8.1f) + quadTo(8.45f, 3f, 8.725f, 3.237f) + quadTo(9f, 3.475f, 9.05f, 3.8f) + lineTo(9.7f, 7.3f) + quadTo(9.75f, 7.7f, 9.675f, 7.975f) + quadTo(9.6f, 8.25f, 9.4f, 8.45f) + lineTo(6.975f, 10.9f) + quadTo(7.475f, 11.825f, 8.163f, 12.688f) + quadTo(8.85f, 13.55f, 9.675f, 14.35f) + quadTo(10.45f, 15.125f, 11.3f, 15.788f) + quadTo(12.15f, 16.45f, 13.1f, 17f) + lineTo(15.45f, 14.65f) + quadTo(15.675f, 14.425f, 16.038f, 14.313f) + quadTo(16.4f, 14.2f, 16.75f, 14.25f) + lineTo(20.2f, 14.95f) + quadTo(20.55f, 15.05f, 20.775f, 15.313f) + quadTo(21f, 15.575f, 21f, 15.9f) + verticalLineTo(19.95f) + quadTo(21f, 20.4f, 20.7f, 20.7f) + quadTo(20.4f, 21f, 19.95f, 21f) + close() + moveTo(6.05f, 9f) + lineTo(7.7f, 7.35f) + quadTo(7.7f, 7.35f, 7.7f, 7.35f) + quadTo(7.7f, 7.35f, 7.7f, 7.35f) + lineTo(7.25f, 5f) + quadTo(7.25f, 5f, 7.25f, 5f) + quadTo(7.25f, 5f, 7.25f, 5f) + horizontalLineTo(5.05f) + quadTo(5.05f, 5f, 5.05f, 5f) + quadTo(5.05f, 5f, 5.05f, 5f) + quadTo(5.175f, 6.025f, 5.4f, 7.025f) + quadTo(5.625f, 8.025f, 6.05f, 9f) + close() + moveTo(15f, 17.905f) + quadTo(15.975f, 18.325f, 16.979f, 18.597f) + quadTo(17.983f, 18.868f, 19f, 18.95f) + quadTo(19f, 18.95f, 19f, 18.95f) + quadTo(19f, 18.95f, 19f, 18.95f) + verticalLineTo(16.75f) + quadTo(19f, 16.75f, 19f, 16.75f) + quadTo(19f, 16.75f, 19f, 16.75f) + lineTo(16.65f, 16.25f) + quadTo(16.65f, 16.25f, 16.65f, 16.25f) + quadTo(16.65f, 16.25f, 16.65f, 16.25f) + close() + moveTo(6.05f, 9f) + quadTo(5.625f, 8.025f, 5.4f, 7.025f) + quadTo(5.175f, 6.025f, 5.05f, 5f) + quadTo(5.05f, 5f, 5.05f, 5f) + quadTo(5.05f, 5f, 5.05f, 5f) + horizontalLineTo(7.25f) + quadTo(7.25f, 5f, 7.25f, 5f) + quadTo(7.25f, 5f, 7.25f, 5f) + lineTo(7.7f, 7.35f) + quadTo(7.7f, 7.35f, 7.7f, 7.35f) + quadTo(7.7f, 7.35f, 7.7f, 7.35f) + close() + moveTo(15f, 17.9f) + lineTo(16.65f, 16.25f) + quadTo(16.65f, 16.25f, 16.65f, 16.25f) + quadTo(16.65f, 16.25f, 16.65f, 16.25f) + lineTo(19f, 16.75f) + quadTo(19f, 16.75f, 19f, 16.75f) + quadTo(19f, 16.75f, 19f, 16.75f) + verticalLineTo(18.95f) + quadTo(19f, 18.95f, 19f, 18.95f) + quadTo(19f, 18.95f, 19f, 18.95f) + quadTo(17.975f, 18.875f, 16.975f, 18.6f) + quadTo(15.975f, 18.325f, 15f, 17.9f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoCameraBack.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoCameraBack.kt new file mode 100644 index 0000000..e319154 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoCameraBack.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.PhotoCameraBack: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.PhotoCameraBack", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 840f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(80f, 760f) + verticalLineToRelative(-480f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(160f, 200f) + horizontalLineToRelative(126f) + lineToRelative(50f, -54f) + quadToRelative(11f, -12f, 26.5f, -19f) + reflectiveQuadToRelative(32.5f, -7f) + horizontalLineToRelative(170f) + quadToRelative(17f, 0f, 32.5f, 7f) + reflectiveQuadToRelative(26.5f, 19f) + lineToRelative(50f, 54f) + horizontalLineToRelative(126f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 280f) + verticalLineToRelative(480f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 840f) + lineTo(160f, 840f) + close() + moveTo(160f, 760f) + horizontalLineToRelative(640f) + verticalLineToRelative(-480f) + lineTo(638f, 280f) + lineToRelative(-73f, -80f) + lineTo(395f, 200f) + lineToRelative(-73f, 80f) + lineTo(160f, 280f) + verticalLineToRelative(480f) + close() + moveTo(480f, 520f) + 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() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoLibrary.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoLibrary.kt new file mode 100644 index 0000000..bae2b46 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoLibrary.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.PhotoLibrary: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.PhotoLibrary", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(530f, 500f) + lineToRelative(-46f, -60f) + quadToRelative(-6f, -8f, -16f, -8f) + reflectiveQuadToRelative(-16f, 8f) + lineToRelative(-67f, 88f) + quadToRelative(-8f, 10f, -2.5f, 21f) + reflectiveQuadToRelative(18.5f, 11f) + horizontalLineToRelative(318f) + quadToRelative(13f, 0f, 18.5f, -11f) + reflectiveQuadToRelative(-2.5f, -21f) + lineToRelative(-97f, -127f) + quadToRelative(-6f, -8f, -16f, -8f) + reflectiveQuadToRelative(-16f, 8f) + lineToRelative(-76f, 99f) + close() + moveTo(320f, 720f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(240f, 640f) + verticalLineToRelative(-480f) + 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) + lineTo(320f, 720f) + close() + moveTo(320f, 640f) + horizontalLineToRelative(480f) + verticalLineToRelative(-480f) + lineTo(320f, 160f) + verticalLineToRelative(480f) + close() + moveTo(160f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(80f, 800f) + verticalLineToRelative(-520f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(160f, 280f) + verticalLineToRelative(520f) + horizontalLineToRelative(520f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(720f, 840f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(680f, 880f) + lineTo(160f, 880f) + close() + moveTo(320f, 160f) + verticalLineToRelative(480f) + verticalLineToRelative(-480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoPickerMobile.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoPickerMobile.kt new file mode 100644 index 0000000..98cc96a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoPickerMobile.kt @@ -0,0 +1,82 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoPrints.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoPrints.kt new file mode 100644 index 0000000..2bb0bde --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoPrints.kt @@ -0,0 +1,150 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoSizeSelectLarge.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoSizeSelectLarge.kt new file mode 100644 index 0000000..4cd5dec --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoSizeSelectLarge.kt @@ -0,0 +1,159 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.PhotoSizeSelectLarge: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.PhotoSizeSelectLarge", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(234f, 720f) + horizontalLineToRelative(320f) + quadToRelative(13f, 0f, 18.5f, -11f) + reflectiveQuadToRelative(-2.5f, -21f) + lineToRelative(-96f, -127f) + quadToRelative(-6f, -8f, -16f, -8f) + reflectiveQuadToRelative(-16f, 8f) + lineToRelative(-72f, 97f) + quadToRelative(-6f, 8f, -16f, 8f) + reflectiveQuadToRelative(-16f, -8f) + lineToRelative(-32f, -43f) + quadToRelative(-6f, -8f, -16f, -8f) + reflectiveQuadToRelative(-16f, 8f) + lineToRelative(-56f, 73f) + quadToRelative(-8f, 10f, -2.5f, 21f) + reflectiveQuadToRelative(18.5f, 11f) + close() + moveTo(200f, 840f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 760f) + verticalLineToRelative(-400f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 280f) + horizontalLineToRelative(400f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(680f, 360f) + verticalLineToRelative(400f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(600f, 840f) + lineTo(200f, 840f) + close() + 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(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(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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoSizeSelectSmall.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoSizeSelectSmall.kt new file mode 100644 index 0000000..f506bff --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoSizeSelectSmall.kt @@ -0,0 +1,179 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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(194f, 760f) + horizontalLineToRelative(240f) + quadToRelative(12f, 0f, 18f, -11f) + reflectiveQuadToRelative(-2f, -21f) + lineToRelative(-64f, -87f) + quadToRelative(-6f, -8f, -16f, -8f) + reflectiveQuadToRelative(-16f, 8f) + lineToRelative(-44f, 58f) + quadToRelative(-6f, 8f, -16f, 8f) + reflectiveQuadToRelative(-16f, -8f) + lineToRelative(-24f, -32f) + quadToRelative(-6f, -8f, -16f, -7.5f) + reflectiveQuadToRelative(-16f, 8.5f) + lineToRelative(-45f, 60f) + quadToRelative(-8f, 10f, -1.5f, 21f) + reflectiveQuadToRelative(18.5f, 11f) + close() + moveTo(200f, 840f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 760f) + verticalLineToRelative(-240f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 440f) + horizontalLineToRelative(240f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(520f, 520f) + verticalLineToRelative(240f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(440f, 840f) + lineTo(200f, 840f) + close() + moveTo(160f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 200f) + close() + moveTo(320f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 200f) + close() + moveTo(480f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 200f) + close() + moveTo(640f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 200f) + close() + moveTo(800f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 200f) + close() + moveTo(640f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 840f) + close() + moveTo(160f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 360f) + close() + moveTo(800f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 360f) + close() + moveTo(800f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 520f) + close() + moveTo(800f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 680f) + close() + moveTo(800f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PictureInPictureCenter.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PictureInPictureCenter.kt new file mode 100644 index 0000000..97a3e0f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PictureInPictureCenter.kt @@ -0,0 +1,85 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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(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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PinEnd.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PinEnd.kt new file mode 100644 index 0000000..51b9265 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PinEnd.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.PinEnd: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.PinEnd", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + 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(200f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(800f, 440f) + verticalLineToRelative(-200f) + lineTo(160f, 240f) + verticalLineToRelative(480f) + horizontalLineToRelative(360f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(560f, 760f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(520f, 800f) + lineTo(160f, 800f) + close() + moveTo(496f, 400f) + lineTo(586f, 490f) + quadToRelative(11f, 11f, 11f, 27.5f) + reflectiveQuadTo(586f, 546f) + quadToRelative(-12f, 12f, -28.5f, 12f) + reflectiveQuadTo(529f, 546f) + lineToRelative(-89f, -89f) + verticalLineToRelative(49f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(400f, 546f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(360f, 506f) + verticalLineToRelative(-146f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(400f, 320f) + horizontalLineToRelative(146f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(586f, 360f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(546f, 400f) + horizontalLineToRelative(-50f) + close() + moveTo(760f, 800f) + quadToRelative(-50f, 0f, -85f, -35f) + reflectiveQuadToRelative(-35f, -85f) + quadToRelative(0f, -50f, 35f, -85f) + reflectiveQuadToRelative(85f, -35f) + quadToRelative(50f, 0f, 85f, 35f) + reflectiveQuadToRelative(35f, 85f) + quadToRelative(0f, 50f, -35f, 85f) + reflectiveQuadToRelative(-85f, 35f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Pix.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Pix.kt new file mode 100644 index 0000000..5b0ff72 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Pix.kt @@ -0,0 +1,92 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Rounded.Pix: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Api", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(480f, 560f) + lineToRelative(-80f, -80f) + lineToRelative(80f, -80f) + lineToRelative(80f, 80f) + lineToRelative(-80f, 80f) + close() + moveTo(395f, 325f) + lineTo(295f, 225f) + lineToRelative(128f, -128f) + quadToRelative(12f, -12f, 27f, -18f) + reflectiveQuadToRelative(30f, -6f) + quadToRelative(15f, 0f, 30f, 6f) + reflectiveQuadToRelative(27f, 18f) + lineToRelative(128f, 128f) + lineToRelative(-100f, 100f) + lineToRelative(-85f, -85f) + lineToRelative(-85f, 85f) + close() + moveTo(225f, 665f) + lineTo(97f, 537f) + quadToRelative(-12f, -12f, -18f, -27f) + reflectiveQuadToRelative(-6f, -30f) + quadToRelative(0f, -15f, 6f, -30f) + reflectiveQuadToRelative(18f, -27f) + lineToRelative(128f, -128f) + lineToRelative(100f, 100f) + lineToRelative(-85f, 85f) + lineToRelative(85f, 85f) + lineToRelative(-100f, 100f) + close() + moveTo(735f, 665f) + lineTo(635f, 565f) + lineToRelative(85f, -85f) + lineToRelative(-85f, -85f) + lineToRelative(100f, -100f) + lineToRelative(128f, 128f) + quadToRelative(12f, 12f, 18f, 27f) + reflectiveQuadToRelative(6f, 30f) + quadToRelative(0f, 15f, -6f, 30f) + reflectiveQuadToRelative(-18f, 27f) + lineTo(735f, 665f) + close() + moveTo(423f, 863f) + lineTo(295f, 735f) + lineToRelative(100f, -100f) + lineToRelative(85f, 85f) + lineToRelative(85f, -85f) + lineToRelative(100f, 100f) + lineTo(537f, 863f) + quadToRelative(-12f, 12f, -27f, 18f) + reflectiveQuadToRelative(-30f, 6f) + quadToRelative(-15f, 0f, -30f, -6f) + reflectiveQuadToRelative(-27f, -18f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Place.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Place.kt new file mode 100644 index 0000000..e70a588 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Place.kt @@ -0,0 +1,86 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Place: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Place", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(12f, 19.35f) + quadTo(15.05f, 16.55f, 16.525f, 14.262f) + quadTo(18f, 11.975f, 18f, 10.2f) + quadTo(18f, 7.475f, 16.263f, 5.738f) + quadTo(14.525f, 4f, 12f, 4f) + quadTo(9.475f, 4f, 7.738f, 5.738f) + quadTo(6f, 7.475f, 6f, 10.2f) + quadTo(6f, 11.975f, 7.475f, 14.262f) + quadTo(8.95f, 16.55f, 12f, 19.35f) + close() + moveTo(10.675f, 20.825f) + quadTo(9.05f, 19.325f, 7.8f, 17.9f) + quadTo(6.55f, 16.475f, 5.713f, 15.137f) + quadTo(4.875f, 13.8f, 4.438f, 12.563f) + quadTo(4f, 11.325f, 4f, 10.2f) + quadTo(4f, 6.45f, 6.412f, 4.225f) + quadTo(8.825f, 2f, 12f, 2f) + quadTo(15.175f, 2f, 17.587f, 4.225f) + quadTo(20f, 6.45f, 20f, 10.2f) + quadTo(20f, 11.325f, 19.563f, 12.563f) + quadTo(19.125f, 13.8f, 18.288f, 15.137f) + quadTo(17.45f, 16.475f, 16.2f, 17.9f) + quadTo(14.95f, 19.325f, 13.325f, 20.825f) + quadTo(13.05f, 21.075f, 12.7f, 21.2f) + quadTo(12.35f, 21.325f, 12f, 21.325f) + quadTo(11.65f, 21.325f, 11.3f, 21.2f) + quadTo(10.95f, 21.075f, 10.675f, 20.825f) + close() + moveTo(12f, 10f) + quadTo(12f, 10f, 12f, 10f) + quadTo(12f, 10f, 12f, 10f) + quadTo(12f, 10f, 12f, 10f) + quadTo(12f, 10f, 12f, 10f) + quadTo(12f, 10f, 12f, 10f) + quadTo(12f, 10f, 12f, 10f) + quadTo(12f, 10f, 12f, 10f) + quadTo(12f, 10f, 12f, 10f) + close() + moveTo(12f, 12f) + quadTo(12.825f, 12f, 13.413f, 11.413f) + quadTo(14f, 10.825f, 14f, 10f) + quadTo(14f, 9.175f, 13.413f, 8.587f) + quadTo(12.825f, 8f, 12f, 8f) + quadTo(11.175f, 8f, 10.587f, 8.587f) + quadTo(10f, 9.175f, 10f, 10f) + quadTo(10f, 10.825f, 10.587f, 11.413f) + quadTo(11.175f, 12f, 12f, 12f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PlayCircle.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PlayCircle.kt new file mode 100644 index 0000000..f69695f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PlayCircle.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.PlayCircle: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.PlayCircle", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(426f, 630f) + lineToRelative(195f, -125f) + quadToRelative(14f, -9f, 14f, -25f) + reflectiveQuadToRelative(-14f, -25f) + lineTo(426f, 330f) + quadToRelative(-15f, -10f, -30.5f, -1.5f) + reflectiveQuadTo(380f, 355f) + verticalLineToRelative(250f) + quadToRelative(0f, 18f, 15.5f, 26.5f) + reflectiveQuadTo(426f, 630f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Png.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Png.kt new file mode 100644 index 0000000..9d8ab3b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Png.kt @@ -0,0 +1,115 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Polygon.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Polygon.kt new file mode 100644 index 0000000..4d1ad59 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Polygon.kt @@ -0,0 +1,107 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), + 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.Black), + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Prefix.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Prefix.kt new file mode 100644 index 0000000..d8dc997 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Prefix.kt @@ -0,0 +1,61 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Preview.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Preview.kt new file mode 100644 index 0000000..47a6a8a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Preview.kt @@ -0,0 +1,175 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} + +val Icons.TwoTone.Preview: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.Preview", + 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(14f) + 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) + horizontalLineTo(5f) + close() + moveTo(5f, 19f) + horizontalLineToRelative(14f) + verticalLineTo(7f) + horizontalLineTo(5f) + verticalLineToRelative(12f) + close() + moveTo(8.338f, 15.887f) + curveToRelative(-1.075f, -0.742f, -1.854f, -1.704f, -2.338f, -2.888f) + curveToRelative(0.483f, -1.183f, 1.263f, -2.146f, 2.338f, -2.888f) + reflectiveCurveToRelative(2.296f, -1.112f, 3.663f, -1.112f) + curveToRelative(1.367f, 0f, 2.588f, 0.371f, 3.663f, 1.112f) + reflectiveCurveToRelative(1.854f, 1.704f, 2.338f, 2.888f) + curveToRelative(-0.483f, 1.183f, -1.263f, 2.146f, -2.338f, 2.888f) + reflectiveCurveToRelative(-2.296f, 1.112f, -3.663f, 1.112f) + curveToRelative(-1.367f, 0f, -2.588f, -0.371f, -3.663f, -1.112f) + close() + moveTo(14.55f, 14.837f) + curveToRelative(0.767f, -0.442f, 1.367f, -1.054f, 1.8f, -1.837f) + curveToRelative(-0.433f, -0.783f, -1.033f, -1.396f, -1.8f, -1.837f) + curveToRelative(-0.767f, -0.442f, -1.617f, -0.663f, -2.55f, -0.663f) + curveToRelative(-0.933f, 0f, -1.783f, 0.221f, -2.55f, 0.663f) + curveToRelative(-0.767f, 0.442f, -1.367f, 1.054f, -1.8f, 1.837f) + curveToRelative(0.433f, 0.783f, 1.033f, 1.396f, 1.8f, 1.837f) + reflectiveCurveToRelative(1.617f, 0.663f, 2.55f, 0.663f) + curveToRelative(0.933f, 0f, 1.783f, -0.221f, 2.55f, -0.663f) + close() + moveTo(13.063f, 14.063f) + curveToRelative(0.292f, -0.292f, 0.438f, -0.646f, 0.438f, -1.063f) + reflectiveCurveToRelative(-0.146f, -0.771f, -0.438f, -1.063f) + curveToRelative(-0.292f, -0.292f, -0.646f, -0.438f, -1.063f, -0.438f) + reflectiveCurveToRelative(-0.771f, 0.146f, -1.063f, 0.438f) + reflectiveCurveToRelative(-0.438f, 0.646f, -0.438f, 1.063f) + reflectiveCurveToRelative(0.146f, 0.771f, 0.438f, 1.063f) + reflectiveCurveToRelative(0.646f, 0.438f, 1.063f, 0.438f) + reflectiveCurveToRelative(0.771f, -0.146f, 1.063f, -0.438f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(5f, 7f) + verticalLineToRelative(12f) + horizontalLineToRelative(14f) + verticalLineTo(7f) + horizontalLineTo(5f) + close() + moveTo(15.662f, 15.888f) + curveToRelative(-1.075f, 0.742f, -2.296f, 1.112f, -3.662f, 1.112f) + reflectiveCurveToRelative(-2.588f, -0.371f, -3.662f, -1.112f) + curveToRelative(-1.075f, -0.742f, -1.854f, -1.704f, -2.338f, -2.888f) + curveToRelative(0.483f, -1.183f, 1.263f, -2.146f, 2.338f, -2.888f) + curveToRelative(1.075f, -0.742f, 2.296f, -1.112f, 3.662f, -1.112f) + reflectiveCurveToRelative(2.588f, 0.371f, 3.662f, 1.112f) + curveToRelative(1.075f, 0.742f, 1.854f, 1.704f, 2.338f, 2.888f) + curveToRelative(-0.483f, 1.183f, -1.263f, 2.146f, -2.338f, 2.888f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Print.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Print.kt new file mode 100644 index 0000000..134e35c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Print.kt @@ -0,0 +1,217 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Psychology.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Psychology.kt new file mode 100644 index 0000000..a1baed2 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Psychology.kt @@ -0,0 +1,110 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Public.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Public.kt new file mode 100644 index 0000000..984d799 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Public.kt @@ -0,0 +1,91 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Public: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Public", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(440f, 798f) + verticalLineToRelative(-78f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(360f, 640f) + verticalLineToRelative(-40f) + lineTo(168f, 408f) + quadToRelative(-3f, 18f, -5.5f, 36f) + reflectiveQuadToRelative(-2.5f, 36f) + quadToRelative(0f, 121f, 79.5f, 212f) + reflectiveQuadTo(440f, 798f) + close() + moveTo(716f, 696f) + quadToRelative(20f, -22f, 36f, -47.5f) + reflectiveQuadToRelative(26.5f, -53f) + quadToRelative(10.5f, -27.5f, 16f, -56.5f) + reflectiveQuadToRelative(5.5f, -59f) + quadToRelative(0f, -98f, -54.5f, -179f) + reflectiveQuadTo(600f, 184f) + verticalLineToRelative(16f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(520f, 280f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(400f, 400f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(600f, 520f) + verticalLineToRelative(120f) + horizontalLineToRelative(40f) + quadToRelative(26f, 0f, 47f, 15.5f) + reflectiveQuadToRelative(29f, 40.5f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PushPin.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PushPin.kt new file mode 100644 index 0000000..60cc61e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PushPin.kt @@ -0,0 +1,127 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.PushPin: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.PushPin", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(640f, 200f) + verticalLineToRelative(280f) + lineToRelative(68f, 68f) + quadToRelative(6f, 6f, 9f, 13.5f) + reflectiveQuadToRelative(3f, 15.5f) + verticalLineToRelative(23f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(680f, 640f) + lineTo(520f, 640f) + verticalLineToRelative(234f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 914f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 874f) + verticalLineToRelative(-234f) + lineTo(280f, 640f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(240f, 600f) + verticalLineToRelative(-23f) + quadToRelative(0f, -8f, 3f, -15.5f) + reflectiveQuadToRelative(9f, -13.5f) + lineToRelative(68f, -68f) + verticalLineToRelative(-280f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 120f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 200f) + close() + moveTo(354f, 560f) + horizontalLineToRelative(252f) + lineToRelative(-46f, -46f) + verticalLineToRelative(-314f) + lineTo(400f, 200f) + verticalLineToRelative(314f) + lineToRelative(-46f, 46f) + close() + moveTo(480f, 560f) + close() + } + }.build() +} + +val Icons.Rounded.PushPin: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.PushPin", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(640f, 200f) + verticalLineToRelative(280f) + lineToRelative(68f, 68f) + quadToRelative(6f, 6f, 9f, 13.5f) + reflectiveQuadToRelative(3f, 15.5f) + verticalLineToRelative(23f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(680f, 640f) + lineTo(520f, 640f) + verticalLineToRelative(234f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 914f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 874f) + verticalLineToRelative(-234f) + lineTo(280f, 640f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(240f, 600f) + verticalLineToRelative(-23f) + quadToRelative(0f, -8f, 3f, -15.5f) + reflectiveQuadToRelative(9f, -13.5f) + lineToRelative(68f, -68f) + verticalLineToRelative(-280f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 120f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 200f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QrCode.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QrCode.kt new file mode 100644 index 0000000..b7c7e9e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QrCode.kt @@ -0,0 +1,300 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QrCode2.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QrCode2.kt new file mode 100644 index 0000000..3c9ea80 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QrCode2.kt @@ -0,0 +1,192 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.QrCode2: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.QrCode2", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(520f, 840f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(440f, 760f) + verticalLineToRelative(-200f) + horizontalLineToRelative(80f) + verticalLineToRelative(200f) + horizontalLineToRelative(-80f) + close() + moveTo(760f, 640f) + verticalLineToRelative(-160f) + horizontalLineToRelative(80f) + verticalLineToRelative(160f) + horizontalLineToRelative(-80f) + close() + moveTo(680f, 480f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(200f, 560f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(120f, 480f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(480f, 200f) + verticalLineToRelative(-80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(180f, 300f) + horizontalLineToRelative(120f) + verticalLineToRelative(-120f) + lineTo(180f, 180f) + verticalLineToRelative(120f) + close() + moveTo(120f, 320f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 160f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 360f) + lineTo(160f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 320f) + close() + moveTo(180f, 780f) + horizontalLineToRelative(120f) + verticalLineToRelative(-120f) + lineTo(180f, 660f) + verticalLineToRelative(120f) + close() + moveTo(120f, 800f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 600f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 640f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 840f) + lineTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + close() + moveTo(660f, 300f) + horizontalLineToRelative(120f) + verticalLineToRelative(-120f) + lineTo(660f, 180f) + verticalLineToRelative(120f) + close() + moveTo(600f, 320f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 120f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 360f) + lineTo(640f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 320f) + close() + moveTo(680f, 840f) + verticalLineToRelative(-120f) + horizontalLineToRelative(-80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(160f) + verticalLineToRelative(120f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + lineTo(680f, 840f) + close() + moveTo(520f, 560f) + verticalLineToRelative(-80f) + horizontalLineToRelative(160f) + verticalLineToRelative(80f) + lineTo(520f, 560f) + close() + moveTo(360f, 560f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(-80f) + horizontalLineToRelative(240f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + horizontalLineToRelative(-80f) + close() + moveTo(400f, 360f) + verticalLineToRelative(-160f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + horizontalLineToRelative(80f) + verticalLineToRelative(80f) + lineTo(400f, 360f) + close() + moveTo(210f, 270f) + verticalLineToRelative(-60f) + horizontalLineToRelative(60f) + verticalLineToRelative(60f) + horizontalLineToRelative(-60f) + close() + moveTo(210f, 750f) + verticalLineToRelative(-60f) + horizontalLineToRelative(60f) + verticalLineToRelative(60f) + horizontalLineToRelative(-60f) + close() + moveTo(690f, 270f) + verticalLineToRelative(-60f) + horizontalLineToRelative(60f) + verticalLineToRelative(60f) + horizontalLineToRelative(-60f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QrCodeScanner.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QrCodeScanner.kt new file mode 100644 index 0000000..6cdae1f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QrCodeScanner.kt @@ -0,0 +1,210 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.QrCodeScanner: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.QrCodeScanner", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(120f, 280f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(80f, 240f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 80f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(280f, 120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 160f) + horizontalLineToRelative(-80f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(120f, 280f) + close() + moveTo(120f, 880f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(80f, 840f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 680f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(160f, 720f) + verticalLineToRelative(80f) + horizontalLineToRelative(80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(280f, 840f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 880f) + lineTo(120f, 880f) + close() + moveTo(720f, 880f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(680f, 840f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(720f, 800f) + horizontalLineToRelative(80f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(840f, 680f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 720f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 880f) + lineTo(720f, 880f) + close() + moveTo(840f, 280f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(800f, 240f) + verticalLineToRelative(-80f) + horizontalLineToRelative(-80f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(680f, 120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(720f, 80f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 120f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 280f) + close() + moveTo(700f, 760f) + verticalLineToRelative(-60f) + horizontalLineToRelative(60f) + verticalLineToRelative(60f) + horizontalLineToRelative(-60f) + close() + moveTo(700f, 640f) + verticalLineToRelative(-60f) + horizontalLineToRelative(60f) + verticalLineToRelative(60f) + horizontalLineToRelative(-60f) + close() + moveTo(640f, 700f) + verticalLineToRelative(-60f) + horizontalLineToRelative(60f) + verticalLineToRelative(60f) + horizontalLineToRelative(-60f) + close() + moveTo(580f, 760f) + verticalLineToRelative(-60f) + horizontalLineToRelative(60f) + verticalLineToRelative(60f) + horizontalLineToRelative(-60f) + close() + moveTo(520f, 700f) + verticalLineToRelative(-60f) + horizontalLineToRelative(60f) + verticalLineToRelative(60f) + horizontalLineToRelative(-60f) + close() + moveTo(640f, 580f) + verticalLineToRelative(-60f) + horizontalLineToRelative(60f) + verticalLineToRelative(60f) + horizontalLineToRelative(-60f) + close() + moveTo(580f, 640f) + verticalLineToRelative(-60f) + horizontalLineToRelative(60f) + verticalLineToRelative(60f) + horizontalLineToRelative(-60f) + close() + moveTo(520f, 580f) + verticalLineToRelative(-60f) + horizontalLineToRelative(60f) + verticalLineToRelative(60f) + horizontalLineToRelative(-60f) + close() + moveTo(560f, 440f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(520f, 400f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(560f, 200f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(760f, 240f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(720f, 440f) + lineTo(560f, 440f) + close() + moveTo(240f, 760f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(200f, 720f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(240f, 520f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(440f, 560f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(400f, 760f) + lineTo(240f, 760f) + close() + moveTo(240f, 440f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(200f, 400f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(240f, 200f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(440f, 240f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(400f, 440f) + lineTo(240f, 440f) + close() + moveTo(260f, 700f) + horizontalLineToRelative(120f) + verticalLineToRelative(-120f) + lineTo(260f, 580f) + verticalLineToRelative(120f) + close() + moveTo(260f, 380f) + horizontalLineToRelative(120f) + verticalLineToRelative(-120f) + lineTo(260f, 260f) + verticalLineToRelative(120f) + close() + moveTo(580f, 380f) + horizontalLineToRelative(120f) + verticalLineToRelative(-120f) + lineTo(580f, 260f) + verticalLineToRelative(120f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QualityHigh.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QualityHigh.kt new file mode 100644 index 0000000..b94908b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QualityHigh.kt @@ -0,0 +1,195 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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() +} + +val Icons.Rounded.QualityHigh: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.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() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QualityLow.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QualityLow.kt new file mode 100644 index 0000000..d07f9ce --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QualityLow.kt @@ -0,0 +1,106 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QualityMedium.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QualityMedium.kt new file mode 100644 index 0000000..f98cac8 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QualityMedium.kt @@ -0,0 +1,122 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Rabbit.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Rabbit.kt new file mode 100644 index 0000000..0b68cad --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Rabbit.kt @@ -0,0 +1,86 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RadioButtonChecked.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RadioButtonChecked.kt new file mode 100644 index 0000000..1e4088c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RadioButtonChecked.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.RadioButtonChecked: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.RadioButtonChecked", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(621.5f, 621.5f) + quadTo(680f, 563f, 680f, 480f) + reflectiveQuadToRelative(-58.5f, -141.5f) + quadTo(563f, 280f, 480f, 280f) + reflectiveQuadToRelative(-141.5f, 58.5f) + quadTo(280f, 397f, 280f, 480f) + reflectiveQuadToRelative(58.5f, 141.5f) + quadTo(397f, 680f, 480f, 680f) + reflectiveQuadToRelative(141.5f, -58.5f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RadioButtonUnchecked.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RadioButtonUnchecked.kt new file mode 100644 index 0000000..e347b66 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RadioButtonUnchecked.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.RadioButtonUnchecked: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.RadioButtonUnchecked", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RampLeft.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RampLeft.kt new file mode 100644 index 0000000..53f7c98 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RampLeft.kt @@ -0,0 +1,71 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.RampLeft: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.RampLeft", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(440f, 800f) + verticalLineToRelative(-527f) + lineToRelative(-36f, 36f) + quadToRelative(-11f, 11f, -27.5f, 11f) + reflectiveQuadTo(348f, 308f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + lineToRelative(104f, -104f) + quadToRelative(6f, -6f, 13f, -8.5f) + reflectiveQuadToRelative(15f, -2.5f) + quadToRelative(8f, 0f, 15f, 2.5f) + reflectiveQuadToRelative(13f, 8.5f) + lineToRelative(104f, 104f) + quadToRelative(11f, 11f, 11.5f, 27.5f) + reflectiveQuadTo(612f, 308f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineToRelative(-36f, -35f) + verticalLineToRelative(87f) + quadToRelative(0f, 109f, 68.5f, 186.5f) + reflectiveQuadTo(720f, 666f) + quadToRelative(15f, 10f, 18.5f, 26.5f) + reflectiveQuadTo(731f, 720f) + quadToRelative(-14f, 14f, -32.5f, 15f) + reflectiveQuadTo(664f, 725f) + quadToRelative(-45f, -31f, -80.5f, -65f) + reflectiveQuadTo(520f, 588f) + verticalLineToRelative(212f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 800f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Receipt.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Receipt.kt new file mode 100644 index 0000000..0b67205 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Receipt.kt @@ -0,0 +1,164 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Receipt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Receipt", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(120f, 856f) + verticalLineToRelative(-752f) + quadToRelative(0f, -7f, 6f, -9.5f) + reflectiveQuadToRelative(11f, 2.5f) + lineToRelative(29f, 29f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + lineToRelative(32f, -32f) + quadToRelative(6f, -6f, 14f, -6f) + reflectiveQuadToRelative(14f, 6f) + lineToRelative(32f, 32f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + lineToRelative(32f, -32f) + quadToRelative(6f, -6f, 14f, -6f) + reflectiveQuadToRelative(14f, 6f) + lineToRelative(32f, 32f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + lineToRelative(32f, -32f) + quadToRelative(6f, -6f, 14f, -6f) + reflectiveQuadToRelative(14f, 6f) + lineToRelative(32f, 32f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + lineToRelative(32f, -32f) + quadToRelative(6f, -6f, 14f, -6f) + reflectiveQuadToRelative(14f, 6f) + lineToRelative(32f, 32f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + lineToRelative(32f, -32f) + quadToRelative(6f, -6f, 14f, -6f) + reflectiveQuadToRelative(14f, 6f) + lineToRelative(32f, 32f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + lineToRelative(29f, -29f) + quadToRelative(5f, -5f, 11f, -2.5f) + reflectiveQuadToRelative(6f, 9.5f) + verticalLineToRelative(752f) + quadToRelative(0f, 7f, -6f, 9.5f) + reflectiveQuadTo(823f, 863f) + lineToRelative(-29f, -29f) + quadToRelative(-6f, -6f, -14f, -6f) + reflectiveQuadToRelative(-14f, 6f) + lineToRelative(-32f, 32f) + quadToRelative(-6f, 6f, -14f, 6f) + reflectiveQuadToRelative(-14f, -6f) + lineToRelative(-32f, -32f) + quadToRelative(-6f, -6f, -14f, -6f) + reflectiveQuadToRelative(-14f, 6f) + lineToRelative(-32f, 32f) + quadToRelative(-6f, 6f, -14f, 6f) + reflectiveQuadToRelative(-14f, -6f) + lineToRelative(-32f, -32f) + quadToRelative(-6f, -6f, -14f, -6f) + reflectiveQuadToRelative(-14f, 6f) + lineToRelative(-32f, 32f) + quadToRelative(-6f, 6f, -14f, 6f) + reflectiveQuadToRelative(-14f, -6f) + lineToRelative(-32f, -32f) + quadToRelative(-6f, -6f, -14f, -6f) + reflectiveQuadToRelative(-14f, 6f) + lineToRelative(-32f, 32f) + quadToRelative(-6f, 6f, -14f, 6f) + reflectiveQuadToRelative(-14f, -6f) + lineToRelative(-32f, -32f) + quadToRelative(-6f, -6f, -14f, -6f) + reflectiveQuadToRelative(-14f, 6f) + lineToRelative(-32f, 32f) + quadToRelative(-6f, 6f, -14f, 6f) + reflectiveQuadToRelative(-14f, -6f) + lineToRelative(-32f, -32f) + quadToRelative(-6f, -6f, -14f, -6f) + reflectiveQuadToRelative(-14f, 6f) + lineToRelative(-29f, 29f) + quadToRelative(-5f, 5f, -11f, 2.5f) + reflectiveQuadToRelative(-6f, -9.5f) + close() + moveTo(280f, 680f) + horizontalLineToRelative(400f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(720f, 640f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(680f, 600f) + lineTo(280f, 600f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(240f, 640f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(280f, 680f) + close() + moveTo(280f, 520f) + horizontalLineToRelative(400f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(720f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(680f, 440f) + lineTo(280f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(240f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(280f, 520f) + close() + moveTo(280f, 360f) + horizontalLineToRelative(400f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(720f, 320f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(680f, 280f) + lineTo(280f, 280f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(240f, 320f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(280f, 360f) + close() + moveTo(200f, 764f) + horizontalLineToRelative(560f) + verticalLineToRelative(-568f) + lineTo(200f, 196f) + verticalLineToRelative(568f) + close() + moveTo(200f, 196f) + verticalLineToRelative(568f) + verticalLineToRelative(-568f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RecordVoiceOver.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RecordVoiceOver.kt new file mode 100644 index 0000000..956d12a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RecordVoiceOver.kt @@ -0,0 +1,128 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.RecordVoiceOver: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.RecordVoiceOver", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(920f, 360f) + quadToRelative(0f, 69f, -24.5f, 131.5f) + reflectiveQuadTo(829f, 605f) + quadToRelative(-12f, 14f, -30f, 15f) + reflectiveQuadToRelative(-32f, -13f) + quadToRelative(-13f, -13f, -12f, -31f) + reflectiveQuadToRelative(12f, -33f) + quadToRelative(30f, -38f, 46.5f, -85f) + reflectiveQuadToRelative(16.5f, -98f) + quadToRelative(0f, -51f, -16.5f, -97f) + reflectiveQuadTo(767f, 179f) + quadToRelative(-12f, -15f, -12.5f, -33f) + reflectiveQuadToRelative(12.5f, -32f) + quadToRelative(13f, -14f, 31.5f, -13.5f) + reflectiveQuadTo(829f, 115f) + quadToRelative(42f, 51f, 66.5f, 113.5f) + reflectiveQuadTo(920f, 360f) + close() + moveTo(738f, 360f) + quadToRelative(0f, 32f, -10f, 61.5f) + reflectiveQuadTo(700f, 476f) + quadToRelative(-11f, 15f, -29.5f, 15.5f) + reflectiveQuadTo(638f, 478f) + quadToRelative(-13f, -13f, -13.5f, -31.5f) + reflectiveQuadTo(633f, 411f) + quadToRelative(6f, -11f, 9.5f, -24f) + reflectiveQuadToRelative(3.5f, -27f) + quadToRelative(0f, -14f, -3.5f, -27f) + reflectiveQuadToRelative(-9.5f, -25f) + quadToRelative(-9f, -17f, -8.5f, -35f) + reflectiveQuadToRelative(13.5f, -31f) + quadToRelative(14f, -14f, 32.5f, -13.5f) + reflectiveQuadTo(700f, 244f) + quadToRelative(18f, 25f, 28f, 54.5f) + reflectiveQuadToRelative(10f, 61.5f) + close() + moveTo(360f, 520f) + quadToRelative(-66f, 0f, -113f, -47f) + reflectiveQuadToRelative(-47f, -113f) + quadToRelative(0f, -66f, 47f, -113f) + reflectiveQuadToRelative(113f, -47f) + quadToRelative(66f, 0f, 113f, 47f) + reflectiveQuadToRelative(47f, 113f) + quadToRelative(0f, 66f, -47f, 113f) + reflectiveQuadToRelative(-113f, 47f) + close() + moveTo(40f, 760f) + verticalLineToRelative(-32f) + quadToRelative(0f, -33f, 17f, -62f) + reflectiveQuadToRelative(47f, -44f) + quadToRelative(51f, -26f, 115f, -44f) + reflectiveQuadToRelative(141f, -18f) + quadToRelative(77f, 0f, 141f, 18f) + reflectiveQuadToRelative(115f, 44f) + quadToRelative(30f, 15f, 47f, 44f) + reflectiveQuadToRelative(17f, 62f) + verticalLineToRelative(32f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(600f, 840f) + lineTo(120f, 840f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(40f, 760f) + close() + moveTo(120f, 760f) + horizontalLineToRelative(480f) + verticalLineToRelative(-32f) + quadToRelative(0f, -11f, -5.5f, -20f) + reflectiveQuadTo(580f, 694f) + quadToRelative(-36f, -18f, -92.5f, -36f) + reflectiveQuadTo(360f, 640f) + quadToRelative(-71f, 0f, -127.5f, 18f) + reflectiveQuadTo(140f, 694f) + quadToRelative(-9f, 5f, -14.5f, 14f) + reflectiveQuadToRelative(-5.5f, 20f) + verticalLineToRelative(32f) + close() + moveTo(360f, 440f) + quadToRelative(33f, 0f, 56.5f, -23.5f) + reflectiveQuadTo(440f, 360f) + quadToRelative(0f, -33f, -23.5f, -56.5f) + reflectiveQuadTo(360f, 280f) + quadToRelative(-33f, 0f, -56.5f, 23.5f) + reflectiveQuadTo(280f, 360f) + quadToRelative(0f, 33f, 23.5f, 56.5f) + reflectiveQuadTo(360f, 440f) + close() + moveTo(360f, 360f) + close() + moveTo(360f, 760f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Rectangle.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Rectangle.kt new file mode 100644 index 0000000..e7bce32 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Rectangle.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Rectangle: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Rectangle", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Redo.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Redo.kt new file mode 100644 index 0000000..e45b082 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Redo.kt @@ -0,0 +1,73 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Redo: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Redo", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(648f, 400f) + lineTo(396f, 400f) + quadToRelative(-63f, 0f, -109.5f, 40f) + reflectiveQuadTo(240f, 540f) + quadToRelative(0f, 60f, 46.5f, 100f) + reflectiveQuadTo(396f, 680f) + horizontalLineToRelative(244f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 720f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 760f) + lineTo(396f, 760f) + quadToRelative(-97f, 0f, -166.5f, -63f) + reflectiveQuadTo(160f, 540f) + quadToRelative(0f, -94f, 69.5f, -157f) + reflectiveQuadTo(396f, 320f) + horizontalLineToRelative(252f) + lineToRelative(-76f, -76f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(144f, 144f) + quadToRelative(6f, 6f, 8.5f, 13f) + reflectiveQuadToRelative(2.5f, 15f) + quadToRelative(0f, 8f, -2.5f, 15f) + reflectiveQuadToRelative(-8.5f, 13f) + lineTo(628f, 532f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + lineToRelative(76f, -76f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Refresh.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Refresh.kt new file mode 100644 index 0000000..76c32d7 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Refresh.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Refresh: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Refresh", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 800f) + quadToRelative(-134f, 0f, -227f, -93f) + reflectiveQuadToRelative(-93f, -227f) + quadToRelative(0f, -134f, 93f, -227f) + reflectiveQuadToRelative(227f, -93f) + quadToRelative(69f, 0f, 132f, 28.5f) + reflectiveQuadTo(720f, 270f) + verticalLineToRelative(-70f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(760f, 160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 200f) + verticalLineToRelative(200f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 440f) + lineTo(560f, 440f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(520f, 400f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(560f, 360f) + horizontalLineToRelative(128f) + quadToRelative(-32f, -56f, -87.5f, -88f) + reflectiveQuadTo(480f, 240f) + quadToRelative(-100f, 0f, -170f, 70f) + reflectiveQuadToRelative(-70f, 170f) + quadToRelative(0f, 100f, 70f, 170f) + reflectiveQuadToRelative(170f, 70f) + quadToRelative(68f, 0f, 124.5f, -34.5f) + reflectiveQuadTo(692f, 593f) + quadToRelative(8f, -14f, 22.5f, -19.5f) + reflectiveQuadToRelative(29.5f, -0.5f) + quadToRelative(16f, 5f, 23f, 21f) + reflectiveQuadToRelative(-1f, 30f) + quadToRelative(-41f, 80f, -117f, 128f) + reflectiveQuadToRelative(-169f, 48f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ReleaseAlert.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ReleaseAlert.kt new file mode 100644 index 0000000..d39b262 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ReleaseAlert.kt @@ -0,0 +1,203 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ReleaseAlert: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ReleaseAlert", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(326f, 870f) + lineToRelative(-58f, -98f) + lineToRelative(-110f, -24f) + quadToRelative(-15f, -3f, -24f, -15.5f) + reflectiveQuadToRelative(-7f, -27.5f) + lineToRelative(11f, -113f) + lineToRelative(-75f, -86f) + quadToRelative(-10f, -11f, -10f, -26f) + reflectiveQuadToRelative(10f, -26f) + lineToRelative(75f, -86f) + lineToRelative(-11f, -113f) + quadToRelative(-2f, -15f, 7f, -27.5f) + reflectiveQuadToRelative(24f, -15.5f) + lineToRelative(110f, -24f) + lineToRelative(58f, -98f) + quadToRelative(8f, -13f, 22f, -17.5f) + reflectiveQuadToRelative(28f, 1.5f) + lineToRelative(104f, 44f) + lineToRelative(104f, -44f) + quadToRelative(14f, -6f, 28f, -1.5f) + reflectiveQuadToRelative(22f, 17.5f) + lineToRelative(58f, 98f) + lineToRelative(110f, 24f) + quadToRelative(15f, 3f, 24f, 15.5f) + reflectiveQuadToRelative(7f, 27.5f) + lineToRelative(-11f, 113f) + lineToRelative(75f, 86f) + quadToRelative(10f, 11f, 10f, 26f) + reflectiveQuadToRelative(-10f, 26f) + lineToRelative(-75f, 86f) + lineToRelative(11f, 113f) + quadToRelative(2f, 15f, -7f, 27.5f) + reflectiveQuadTo(802f, 748f) + lineToRelative(-110f, 24f) + lineToRelative(-58f, 98f) + quadToRelative(-8f, 13f, -22f, 17.5f) + reflectiveQuadTo(584f, 886f) + lineToRelative(-104f, -44f) + lineToRelative(-104f, 44f) + quadToRelative(-14f, 6f, -28f, 1.5f) + reflectiveQuadTo(326f, 870f) + close() + moveTo(508.5f, 668.5f) + quadTo(520f, 657f, 520f, 640f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(497f, 600f, 480f, 600f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(440f, 623f, 440f, 640f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(463f, 680f, 480f, 680f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(508.5f, 508.5f) + quadTo(520f, 497f, 520f, 480f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 320f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + close() + } + }.build() +} + +val Icons.Outlined.ReleaseAlert: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.ReleaseAlert", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(326f, 870f) + lineToRelative(-58f, -98f) + lineToRelative(-110f, -24f) + quadToRelative(-15f, -3f, -24f, -15.5f) + reflectiveQuadToRelative(-7f, -27.5f) + lineToRelative(11f, -113f) + lineToRelative(-75f, -86f) + quadToRelative(-10f, -11f, -10f, -26f) + reflectiveQuadToRelative(10f, -26f) + lineToRelative(75f, -86f) + lineToRelative(-11f, -113f) + quadToRelative(-2f, -15f, 7f, -27.5f) + reflectiveQuadToRelative(24f, -15.5f) + lineToRelative(110f, -24f) + lineToRelative(58f, -98f) + quadToRelative(8f, -13f, 22f, -17.5f) + reflectiveQuadToRelative(28f, 1.5f) + lineToRelative(104f, 44f) + lineToRelative(104f, -44f) + quadToRelative(14f, -6f, 28f, -1.5f) + reflectiveQuadToRelative(22f, 17.5f) + lineToRelative(58f, 98f) + lineToRelative(110f, 24f) + quadToRelative(15f, 3f, 24f, 15.5f) + reflectiveQuadToRelative(7f, 27.5f) + lineToRelative(-11f, 113f) + lineToRelative(75f, 86f) + quadToRelative(10f, 11f, 10f, 26f) + reflectiveQuadToRelative(-10f, 26f) + lineToRelative(-75f, 86f) + lineToRelative(11f, 113f) + quadToRelative(2f, 15f, -7f, 27.5f) + reflectiveQuadTo(802f, 748f) + lineToRelative(-110f, 24f) + lineToRelative(-58f, 98f) + quadToRelative(-8f, 13f, -22f, 17.5f) + reflectiveQuadTo(584f, 886f) + lineToRelative(-104f, -44f) + lineToRelative(-104f, 44f) + quadToRelative(-14f, 6f, -28f, 1.5f) + reflectiveQuadTo(326f, 870f) + close() + moveTo(378f, 798f) + lineTo(480f, 754f) + lineTo(584f, 798f) + lineTo(640f, 702f) + lineTo(750f, 676f) + lineTo(740f, 564f) + lineTo(814f, 480f) + lineTo(740f, 394f) + lineTo(750f, 282f) + lineTo(640f, 258f) + lineTo(582f, 162f) + lineTo(480f, 206f) + lineTo(376f, 162f) + lineTo(320f, 258f) + lineTo(210f, 282f) + lineTo(220f, 394f) + lineTo(146f, 480f) + lineTo(220f, 564f) + lineTo(210f, 678f) + lineTo(320f, 702f) + lineTo(378f, 798f) + close() + moveTo(480f, 480f) + close() + moveTo(508.5f, 668.5f) + quadTo(520f, 657f, 520f, 640f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(497f, 600f, 480f, 600f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(440f, 623f, 440f, 640f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(463f, 680f, 480f, 680f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(508.5f, 508.5f) + quadTo(520f, 497f, 520f, 480f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 320f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RemoveCircle.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RemoveCircle.kt new file mode 100644 index 0000000..072a44e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RemoveCircle.kt @@ -0,0 +1,80 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.RemoveCircle: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.RemoveCircle", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(320f, 520f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(680f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(640f, 440f) + lineTo(320f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(280f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(320f, 520f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Reorder.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Reorder.kt new file mode 100644 index 0000000..0c45c0f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Reorder.kt @@ -0,0 +1,86 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Reorder: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Reorder", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 760f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 720f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 680f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 720f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 760f) + lineTo(160f, 760f) + close() + moveTo(160f, 600f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 560f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 520f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 560f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 600f) + lineTo(160f, 600f) + close() + moveTo(160f, 440f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 400f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 360f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 400f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 440f) + lineTo(160f, 440f) + close() + moveTo(160f, 280f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 240f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 200f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 240f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 280f) + lineTo(160f, 280f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Repeat.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Repeat.kt new file mode 100644 index 0000000..f410806 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Repeat.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Repeat: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Repeat", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(274f, 760f) + lineToRelative(34f, 34f) + quadToRelative(12f, 12f, 11.5f, 28f) + reflectiveQuadTo(308f, 850f) + quadToRelative(-12f, 12f, -28.5f, 12.5f) + reflectiveQuadTo(251f, 851f) + lineTo(148f, 748f) + quadToRelative(-6f, -6f, -8.5f, -13f) + reflectiveQuadToRelative(-2.5f, -15f) + quadToRelative(0f, -8f, 2.5f, -15f) + reflectiveQuadToRelative(8.5f, -13f) + lineToRelative(103f, -103f) + quadToRelative(12f, -12f, 28.5f, -11.5f) + reflectiveQuadTo(308f, 590f) + quadToRelative(11f, 12f, 11.5f, 28f) + reflectiveQuadTo(308f, 646f) + lineToRelative(-34f, 34f) + horizontalLineToRelative(406f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(720f, 520f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(760f, 560f) + verticalLineToRelative(120f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(680f, 760f) + lineTo(274f, 760f) + close() + moveTo(686f, 280f) + lineTo(280f, 280f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 440f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(200f, 400f) + verticalLineToRelative(-120f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(280f, 200f) + horizontalLineToRelative(406f) + lineToRelative(-34f, -34f) + quadToRelative(-12f, -12f, -11.5f, -28f) + reflectiveQuadToRelative(11.5f, -28f) + quadToRelative(12f, -12f, 28.5f, -12.5f) + reflectiveQuadTo(709f, 109f) + lineToRelative(103f, 103f) + quadToRelative(6f, 6f, 8.5f, 13f) + reflectiveQuadToRelative(2.5f, 15f) + quadToRelative(0f, 8f, -2.5f, 15f) + reflectiveQuadToRelative(-8.5f, 13f) + lineTo(709f, 371f) + quadToRelative(-12f, 12f, -28.5f, 11.5f) + reflectiveQuadTo(652f, 370f) + quadToRelative(-11f, -12f, -11.5f, -28f) + reflectiveQuadToRelative(11.5f, -28f) + lineToRelative(34f, -34f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RepeatOne.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RepeatOne.kt new file mode 100644 index 0000000..de1eb0b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RepeatOne.kt @@ -0,0 +1,110 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.RepeatOne: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.RepeatOne", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(460f, 420f) + horizontalLineToRelative(-30f) + quadToRelative(-13f, 0f, -21.5f, -8.5f) + reflectiveQuadTo(400f, 390f) + quadToRelative(0f, -13f, 8.5f, -21.5f) + reflectiveQuadTo(430f, 360f) + horizontalLineToRelative(50f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 400f) + verticalLineToRelative(170f) + quadToRelative(0f, 13f, -8.5f, 21.5f) + reflectiveQuadTo(490f, 600f) + quadToRelative(-13f, 0f, -21.5f, -8.5f) + reflectiveQuadTo(460f, 570f) + verticalLineToRelative(-150f) + close() + moveTo(274f, 760f) + lineToRelative(34f, 34f) + quadToRelative(12f, 12f, 11.5f, 28f) + reflectiveQuadTo(308f, 850f) + quadToRelative(-12f, 12f, -28.5f, 12.5f) + reflectiveQuadTo(251f, 851f) + lineTo(148f, 748f) + quadToRelative(-6f, -6f, -8.5f, -13f) + reflectiveQuadToRelative(-2.5f, -15f) + quadToRelative(0f, -8f, 2.5f, -15f) + reflectiveQuadToRelative(8.5f, -13f) + lineToRelative(103f, -103f) + quadToRelative(12f, -12f, 28.5f, -11.5f) + reflectiveQuadTo(308f, 590f) + quadToRelative(11f, 12f, 11.5f, 28f) + reflectiveQuadTo(308f, 646f) + lineToRelative(-34f, 34f) + horizontalLineToRelative(406f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(720f, 520f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(760f, 560f) + verticalLineToRelative(120f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(680f, 760f) + lineTo(274f, 760f) + close() + moveTo(686f, 280f) + lineTo(280f, 280f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 440f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(200f, 400f) + verticalLineToRelative(-120f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(280f, 200f) + horizontalLineToRelative(406f) + lineToRelative(-34f, -34f) + quadToRelative(-12f, -12f, -11.5f, -28f) + reflectiveQuadToRelative(11.5f, -28f) + quadToRelative(12f, -12f, 28.5f, -12.5f) + reflectiveQuadTo(709f, 109f) + lineToRelative(103f, 103f) + quadToRelative(6f, 6f, 8.5f, 13f) + reflectiveQuadToRelative(2.5f, 15f) + quadToRelative(0f, 8f, -2.5f, 15f) + reflectiveQuadToRelative(-8.5f, 13f) + lineTo(709f, 371f) + quadToRelative(-12f, 12f, -28.5f, 11.5f) + reflectiveQuadTo(652f, 370f) + quadToRelative(-11f, -12f, -11.5f, -28f) + reflectiveQuadToRelative(11.5f, -28f) + lineToRelative(34f, -34f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ResponsiveLayout.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ResponsiveLayout.kt new file mode 100644 index 0000000..72ad6e7 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ResponsiveLayout.kt @@ -0,0 +1,69 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RestartAlt.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RestartAlt.kt new file mode 100644 index 0000000..15d4f59 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RestartAlt.kt @@ -0,0 +1,88 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.RestartAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.RestartAlt", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(393f, 828f) + quadToRelative(-103f, -29f, -168f, -113.5f) + reflectiveQuadTo(160f, 520f) + quadToRelative(0f, -57f, 19f, -108.5f) + reflectiveQuadToRelative(54f, -94.5f) + quadToRelative(11f, -12f, 27f, -12.5f) + reflectiveQuadToRelative(29f, 12.5f) + quadToRelative(11f, 11f, 11.5f, 27f) + reflectiveQuadTo(290f, 374f) + quadToRelative(-24f, 31f, -37f, 68f) + reflectiveQuadToRelative(-13f, 78f) + quadToRelative(0f, 81f, 47.5f, 144.5f) + reflectiveQuadTo(410f, 751f) + quadToRelative(13f, 4f, 21.5f, 15f) + reflectiveQuadToRelative(8.5f, 24f) + quadToRelative(0f, 20f, -14f, 31.5f) + reflectiveQuadToRelative(-33f, 6.5f) + close() + moveTo(567f, 828f) + quadToRelative(-19f, 5f, -33f, -7f) + reflectiveQuadToRelative(-14f, -32f) + quadToRelative(0f, -12f, 8.5f, -23f) + reflectiveQuadToRelative(21.5f, -15f) + quadToRelative(75f, -24f, 122.5f, -87f) + reflectiveQuadTo(720f, 520f) + quadToRelative(0f, -100f, -70f, -170f) + reflectiveQuadToRelative(-170f, -70f) + horizontalLineToRelative(-3f) + lineToRelative(16f, 16f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineToRelative(-84f, -84f) + quadToRelative(-6f, -6f, -8.5f, -13f) + reflectiveQuadToRelative(-2.5f, -15f) + quadToRelative(0f, -8f, 2.5f, -15f) + reflectiveQuadToRelative(8.5f, -13f) + lineToRelative(84f, -84f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineToRelative(-16f, 16f) + horizontalLineToRelative(3f) + quadToRelative(134f, 0f, 227f, 93f) + reflectiveQuadToRelative(93f, 227f) + quadToRelative(0f, 109f, -65f, 194f) + reflectiveQuadTo(567f, 828f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Restore.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Restore.kt new file mode 100644 index 0000000..da0dea0 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Restore.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Restore: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Restore", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(12f, 21f) + quadTo(8.85f, 21f, 6.425f, 19.087f) + quadTo(4f, 17.175f, 3.275f, 14.2f) + quadTo(3.175f, 13.825f, 3.425f, 13.512f) + quadTo(3.675f, 13.2f, 4.1f, 13.15f) + quadTo(4.5f, 13.1f, 4.825f, 13.3f) + quadTo(5.15f, 13.5f, 5.275f, 13.9f) + quadTo(5.875f, 16.15f, 7.75f, 17.575f) + quadTo(9.625f, 19f, 12f, 19f) + quadTo(14.925f, 19f, 16.962f, 16.962f) + quadTo(19f, 14.925f, 19f, 12f) + quadTo(19f, 9.075f, 16.962f, 7.037f) + quadTo(14.925f, 5f, 12f, 5f) + quadTo(10.275f, 5f, 8.775f, 5.8f) + quadTo(7.275f, 6.6f, 6.25f, 8f) + horizontalLineTo(8f) + quadTo(8.425f, 8f, 8.712f, 8.288f) + quadTo(9f, 8.575f, 9f, 9f) + quadTo(9f, 9.425f, 8.712f, 9.712f) + quadTo(8.425f, 10f, 8f, 10f) + horizontalLineTo(4f) + quadTo(3.575f, 10f, 3.287f, 9.712f) + quadTo(3f, 9.425f, 3f, 9f) + verticalLineTo(5f) + quadTo(3f, 4.575f, 3.287f, 4.287f) + quadTo(3.575f, 4f, 4f, 4f) + quadTo(4.425f, 4f, 4.713f, 4.287f) + quadTo(5f, 4.575f, 5f, 5f) + verticalLineTo(6.35f) + quadTo(6.275f, 4.75f, 8.113f, 3.875f) + quadTo(9.95f, 3f, 12f, 3f) + quadTo(13.875f, 3f, 15.512f, 3.713f) + quadTo(17.15f, 4.425f, 18.362f, 5.637f) + quadTo(19.575f, 6.85f, 20.288f, 8.488f) + quadTo(21f, 10.125f, 21f, 12f) + quadTo(21f, 13.875f, 20.288f, 15.512f) + quadTo(19.575f, 17.15f, 18.362f, 18.362f) + quadTo(17.15f, 19.575f, 15.512f, 20.288f) + quadTo(13.875f, 21f, 12f, 21f) + close() + moveTo(13f, 11.6f) + lineTo(15.5f, 14.1f) + quadTo(15.775f, 14.375f, 15.775f, 14.8f) + quadTo(15.775f, 15.225f, 15.5f, 15.5f) + quadTo(15.225f, 15.775f, 14.8f, 15.775f) + quadTo(14.375f, 15.775f, 14.1f, 15.5f) + lineTo(11.3f, 12.7f) + quadTo(11.15f, 12.55f, 11.075f, 12.363f) + quadTo(11f, 12.175f, 11f, 11.975f) + verticalLineTo(8f) + quadTo(11f, 7.575f, 11.288f, 7.287f) + quadTo(11.575f, 7f, 12f, 7f) + quadTo(12.425f, 7f, 12.712f, 7.287f) + quadTo(13f, 7.575f, 13f, 8f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Rotate90Ccw.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Rotate90Ccw.kt new file mode 100644 index 0000000..46d44a2 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Rotate90Ccw.kt @@ -0,0 +1,104 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.Rotate90Ccw: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Rotate90Ccw", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(520f, 880f) + quadToRelative(-37f, 0f, -72.5f, -7.5f) + reflectiveQuadTo(378f, 851f) + quadToRelative(-15f, -6f, -20f, -21.5f) + reflectiveQuadToRelative(1f, -30.5f) + quadToRelative(6f, -15f, 21f, -21.5f) + reflectiveQuadToRelative(30f, 0.5f) + quadToRelative(26f, 11f, 53.5f, 16.5f) + reflectiveQuadTo(520f, 800f) + quadToRelative(117f, 0f, 198.5f, -81.5f) + reflectiveQuadTo(800f, 520f) + quadToRelative(0f, -117f, -82f, -198.5f) + reflectiveQuadTo(519f, 240f) + horizontalLineToRelative(-7f) + lineToRelative(36f, 36f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(388f, 228f) + quadToRelative(-5f, -5f, -8f, -12.5f) + reflectiveQuadToRelative(-3f, -15.5f) + quadToRelative(0f, -8f, 3f, -15.5f) + reflectiveQuadToRelative(8f, -12.5f) + lineToRelative(104f, -104f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineToRelative(-36f, 36f) + horizontalLineToRelative(7f) + quadToRelative(150f, 0f, 255.5f, 105f) + reflectiveQuadTo(880f, 520f) + quadToRelative(0f, 150f, -105f, 255f) + reflectiveQuadTo(520f, 880f) + close() + moveTo(265f, 740.5f) + quadToRelative(-7f, -2.5f, -13f, -8.5f) + lineTo(68f, 548f) + quadToRelative(-6f, -6f, -8.5f, -13f) + reflectiveQuadTo(57f, 520f) + quadToRelative(0f, -8f, 2.5f, -15f) + reflectiveQuadToRelative(8.5f, -13f) + lineToRelative(184f, -184f) + quadToRelative(6f, -6f, 13f, -8.5f) + reflectiveQuadToRelative(15f, -2.5f) + quadToRelative(8f, 0f, 15f, 2.5f) + reflectiveQuadToRelative(13f, 8.5f) + lineToRelative(184f, 184f) + quadToRelative(6f, 6f, 8.5f, 13f) + reflectiveQuadToRelative(2.5f, 15f) + quadToRelative(0f, 8f, -2.5f, 15f) + reflectiveQuadToRelative(-8.5f, 13f) + lineTo(308f, 732f) + quadToRelative(-6f, 6f, -13f, 8.5f) + reflectiveQuadToRelative(-15f, 2.5f) + quadToRelative(-8f, 0f, -15f, -2.5f) + close() + moveTo(280f, 646f) + lineTo(406f, 520f) + lineTo(280f, 394f) + lineTo(154f, 520f) + lineTo(280f, 646f) + close() + moveTo(280f, 520f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Rotate90Cw.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Rotate90Cw.kt new file mode 100644 index 0000000..ee8b142 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Rotate90Cw.kt @@ -0,0 +1,192 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RotateLeft.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RotateLeft.kt new file mode 100644 index 0000000..248d9c1 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RotateLeft.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Rounded.RotateLeft: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.RotateLeft", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = false + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(170f, 482f) + quadToRelative(-21f, 0f, -33.5f, -15f) + reflectiveQuadToRelative(-7.5f, -35f) + quadToRelative(6f, -25f, 16f, -48f) + reflectiveQuadToRelative(23f, -45f) + quadToRelative(10f, -17f, 29.5f, -19f) + reflectiveQuadToRelative(34.5f, 12f) + quadToRelative(9f, 9f, 11f, 22.5f) + reflectiveQuadToRelative(-5f, 24.5f) + quadToRelative(-10f, 17f, -17.5f, 35.5f) + reflectiveQuadTo(208f, 452f) + quadToRelative(-3f, 13f, -14.5f, 21.5f) + reflectiveQuadTo(170f, 482f) + close() + moveTo(438f, 830f) + quadToRelative(0f, 22f, -15f, 34f) + reflectiveQuadToRelative(-35f, 7f) + quadToRelative(-24f, -7f, -47f, -16.5f) + reflectiveQuadTo(295f, 832f) + quadToRelative(-17f, -10f, -19f, -29.5f) + reflectiveQuadToRelative(12f, -34.5f) + quadToRelative(9f, -9f, 22.5f, -11f) + reflectiveQuadToRelative(24.5f, 5f) + quadToRelative(17f, 10f, 35.5f, 17.5f) + reflectiveQuadTo(408f, 792f) + quadToRelative(13f, 3f, 21.5f, 14.5f) + reflectiveQuadTo(438f, 830f) + close() + moveTo(232f, 712f) + quadToRelative(-15f, 14f, -34.5f, 12f) + reflectiveQuadTo(168f, 705f) + quadToRelative(-13f, -23f, -22.5f, -46f) + reflectiveQuadTo(129f, 612f) + quadToRelative(-5f, -20f, 7f, -35f) + reflectiveQuadToRelative(34f, -15f) + quadToRelative(13f, 0f, 24f, 8.5f) + reflectiveQuadToRelative(14f, 21.5f) + quadToRelative(5f, 19f, 12.5f, 37.5f) + reflectiveQuadTo(238f, 665f) + quadToRelative(7f, 11f, 5f, 25f) + reflectiveQuadToRelative(-11f, 22f) + close() + moveTo(567f, 870f) + quadToRelative(-20f, 5f, -34.5f, -7f) + reflectiveQuadTo(518f, 830f) + quadToRelative(0f, -13f, 8.5f, -24f) + reflectiveQuadToRelative(21.5f, -14f) + quadToRelative(92f, -24f, 151f, -98.5f) + reflectiveQuadTo(758f, 522f) + quadToRelative(0f, -117f, -81.5f, -198.5f) + reflectiveQuadTo(478f, 242f) + horizontalLineToRelative(-8f) + lineToRelative(36f, 36f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(346f, 230f) + quadToRelative(-6f, -6f, -8.5f, -13f) + reflectiveQuadToRelative(-2.5f, -15f) + quadToRelative(0f, -8f, 2.5f, -15f) + reflectiveQuadToRelative(8.5f, -13f) + lineToRelative(103f, -104f) + quadToRelative(12f, -11f, 29f, -11f) + reflectiveQuadToRelative(28f, 11f) + quadToRelative(12f, 12f, 12f, 29f) + reflectiveQuadToRelative(-11f, 28f) + lineToRelative(-35f, 35f) + horizontalLineToRelative(6f) + quadToRelative(150f, 0f, 255f, 105f) + reflectiveQuadToRelative(105f, 255f) + quadToRelative(0f, 124f, -76f, 220f) + reflectiveQuadTo(567f, 870f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RotateRight.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RotateRight.kt new file mode 100644 index 0000000..a619c93 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RotateRight.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Rounded.RotateRight: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.RotateRight", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = false + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(790f, 482f) + quadToRelative(-12f, 0f, -23.5f, -8.5f) + reflectiveQuadTo(752f, 452f) + quadToRelative(-5f, -19f, -12.5f, -37.5f) + reflectiveQuadTo(722f, 379f) + quadToRelative(-7f, -11f, -5f, -24.5f) + reflectiveQuadToRelative(11f, -22.5f) + quadToRelative(15f, -14f, 34.5f, -12f) + reflectiveQuadToRelative(29.5f, 19f) + quadToRelative(13f, 22f, 23f, 45f) + reflectiveQuadToRelative(16f, 48f) + quadToRelative(5f, 20f, -7.5f, 35f) + reflectiveQuadTo(790f, 482f) + close() + moveTo(522f, 830f) + quadToRelative(0f, -12f, 8.5f, -23.5f) + reflectiveQuadTo(552f, 792f) + quadToRelative(19f, -5f, 37.5f, -12.5f) + reflectiveQuadTo(625f, 762f) + quadToRelative(11f, -7f, 24.5f, -5f) + reflectiveQuadToRelative(22.5f, 11f) + quadToRelative(14f, 15f, 12f, 34.5f) + reflectiveQuadTo(665f, 832f) + quadToRelative(-23f, 13f, -46f, 22.5f) + reflectiveQuadTo(572f, 871f) + quadToRelative(-20f, 5f, -35f, -7f) + reflectiveQuadToRelative(-15f, -34f) + close() + moveTo(728f, 712f) + quadToRelative(-9f, -8f, -11f, -22f) + reflectiveQuadToRelative(5f, -25f) + quadToRelative(10f, -17f, 17.5f, -35.5f) + reflectiveQuadTo(752f, 592f) + quadToRelative(3f, -13f, 14f, -21.5f) + reflectiveQuadToRelative(24f, -8.5f) + quadToRelative(22f, 0f, 34f, 15f) + reflectiveQuadToRelative(7f, 35f) + quadToRelative(-7f, 24f, -16.5f, 47f) + reflectiveQuadTo(792f, 705f) + quadToRelative(-10f, 17f, -29.5f, 19f) + reflectiveQuadTo(728f, 712f) + close() + moveTo(393f, 870f) + quadToRelative(-119f, -32f, -195f, -128f) + reflectiveQuadToRelative(-76f, -220f) + quadToRelative(0f, -150f, 105f, -255f) + reflectiveQuadToRelative(255f, -105f) + horizontalLineToRelative(6f) + lineToRelative(-35f, -35f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(12f, -29f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(29f, 11f) + lineToRelative(103f, 104f) + quadToRelative(6f, 6f, 8.5f, 13f) + reflectiveQuadToRelative(2.5f, 15f) + quadToRelative(0f, 8f, -2.5f, 15f) + reflectiveQuadToRelative(-8.5f, 13f) + lineTo(510f, 334f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + lineToRelative(36f, -36f) + horizontalLineToRelative(-8f) + quadToRelative(-117f, 0f, -198.5f, 81.5f) + reflectiveQuadTo(202f, 522f) + quadToRelative(0f, 97f, 59f, 171.5f) + reflectiveQuadTo(412f, 792f) + quadToRelative(13f, 3f, 21.5f, 14f) + reflectiveQuadToRelative(8.5f, 24f) + quadToRelative(0f, 21f, -14.5f, 33f) + reflectiveQuadTo(393f, 870f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RoundedCorner.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RoundedCorner.kt new file mode 100644 index 0000000..8c4e738 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RoundedCorner.kt @@ -0,0 +1,166 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.RoundedCorner: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.RoundedCorner", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 200f) + close() + moveTo(320f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 200f) + close() + moveTo(160f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 360f) + close() + moveTo(160f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 520f) + close() + moveTo(160f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 680f) + close() + moveTo(800f, 680f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 640f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 640f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 680f) + close() + moveTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(160f, 840f) + close() + moveTo(320f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 840f) + close() + moveTo(480f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 840f) + close() + moveTo(640f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 840f) + close() + moveTo(800f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 800f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 760f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + close() + moveTo(760f, 480f) + verticalLineToRelative(-160f) + quadToRelative(0f, -50f, -35f, -85f) + reflectiveQuadToRelative(-85f, -35f) + lineTo(480f, 200f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 120f) + horizontalLineToRelative(160f) + quadToRelative(83f, 0f, 141.5f, 58.5f) + reflectiveQuadTo(840f, 320f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Routine.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Routine.kt new file mode 100644 index 0000000..b073aeb --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Routine.kt @@ -0,0 +1,170 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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(481f, 760f) + quadToRelative(-56f, 0f, -107f, -21f) + reflectiveQuadToRelative(-91f, -61f) + quadToRelative(-40f, -40f, -61f, -91f) + reflectiveQuadToRelative(-21f, -107f) + quadToRelative(0f, -51f, 17f, -97.5f) + reflectiveQuadToRelative(50f, -84.5f) + quadToRelative(13f, -14f, 32f, -9.5f) + reflectiveQuadToRelative(27f, 24.5f) + quadToRelative(21f, 55f, 52.5f, 104f) + reflectiveQuadToRelative(73.5f, 91f) + quadToRelative(42f, 42f, 91f, 73.5f) + reflectiveQuadTo(648f, 634f) + quadToRelative(20f, 8f, 24.5f, 27f) + reflectiveQuadToRelative(-9.5f, 32f) + quadToRelative(-38f, 33f, -84.5f, 50f) + reflectiveQuadTo(481f, 760f) + close() + moveTo(704f, 568f) + quadToRelative(-16f, -5f, -23f, -20.5f) + reflectiveQuadToRelative(-4f, -32.5f) + quadToRelative(9f, -48f, -6f, -94.5f) + reflectiveQuadTo(621f, 339f) + quadToRelative(-35f, -35f, -80.5f, -49.5f) + reflectiveQuadTo(448f, 283f) + quadToRelative(-17f, 3f, -32f, -4f) + reflectiveQuadToRelative(-21f, -23f) + quadToRelative(-6f, -16f, 1.5f, -31f) + reflectiveQuadToRelative(23.5f, -19f) + quadToRelative(69f, -15f, 138f, 4.5f) + reflectiveQuadTo(679f, 282f) + quadToRelative(51f, 51f, 71f, 120f) + reflectiveQuadToRelative(5f, 138f) + quadToRelative(-4f, 17f, -19f, 25f) + reflectiveQuadToRelative(-32f, 3f) + close() + moveTo(480f, 120f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 80f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 0f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 40f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 120f) + close() + moveTo(480f, 960f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 920f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 840f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 880f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 960f) + close() + moveTo(735f, 226f) + quadToRelative(-12f, -12f, -12f, -28.5f) + reflectiveQuadToRelative(12f, -28.5f) + lineToRelative(28f, -28f) + quadToRelative(11f, -11f, 27.5f, -11f) + reflectiveQuadToRelative(28.5f, 11f) + quadToRelative(12f, 12f, 12f, 28.5f) + reflectiveQuadTo(819f, 198f) + lineToRelative(-28f, 28f) + quadToRelative(-12f, 12f, -28f, 12f) + reflectiveQuadToRelative(-28f, -12f) + close() + moveTo(141f, 819f) + quadToRelative(-12f, -12f, -12f, -28.5f) + reflectiveQuadToRelative(12f, -28.5f) + lineToRelative(28f, -28f) + quadToRelative(12f, -12f, 28f, -12f) + reflectiveQuadToRelative(28f, 12f) + quadToRelative(12f, 12f, 12f, 28.5f) + reflectiveQuadTo(225f, 791f) + lineToRelative(-28f, 28f) + quadToRelative(-11f, 11f, -27.5f, 11f) + reflectiveQuadTo(141f, 819f) + close() + moveTo(880f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(880f, 440f) + horizontalLineToRelative(40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(960f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(920f, 520f) + horizontalLineToRelative(-40f) + close() + moveTo(40f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(0f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(40f, 440f) + horizontalLineToRelative(40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(80f, 520f) + lineTo(40f, 520f) + close() + moveTo(819f, 819f) + quadToRelative(-12f, 12f, -28.5f, 12f) + reflectiveQuadTo(762f, 819f) + lineToRelative(-28f, -28f) + quadToRelative(-12f, -12f, -12f, -28f) + reflectiveQuadToRelative(12f, -28f) + quadToRelative(12f, -12f, 28.5f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + lineToRelative(28f, 28f) + quadToRelative(11f, 11f, 11f, 27.5f) + reflectiveQuadTo(819f, 819f) + close() + moveTo(226f, 225f) + quadToRelative(-12f, 12f, -28.5f, 12f) + reflectiveQuadTo(169f, 225f) + lineToRelative(-28f, -28f) + quadToRelative(-11f, -11f, -11f, -27.5f) + reflectiveQuadToRelative(11f, -28.5f) + quadToRelative(12f, -12f, 28.5f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + lineToRelative(28f, 28f) + quadToRelative(12f, 12f, 12f, 28f) + reflectiveQuadToRelative(-12f, 28f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RssFeed.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RssFeed.kt new file mode 100644 index 0000000..7f281a2 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RssFeed.kt @@ -0,0 +1,84 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.RssFeed: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.RssFeed", + 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) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 680f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(280f, 760f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(200f, 840f) + close() + moveTo(740f, 840f) + quadToRelative(-26f, 0f, -43.5f, -19f) + reflectiveQuadTo(676f, 776f) + quadToRelative(-11f, -97f, -52.5f, -181.5f) + reflectiveQuadTo(516f, 444f) + quadToRelative(-66f, -66f, -150.5f, -107.5f) + reflectiveQuadTo(184f, 284f) + quadToRelative(-26f, -3f, -45f, -20.5f) + reflectiveQuadTo(120f, 220f) + quadToRelative(0f, -26f, 18f, -42.5f) + reflectiveQuadToRelative(43f, -14.5f) + quadToRelative(123f, 11f, 230.5f, 62.5f) + reflectiveQuadTo(601f, 359f) + quadToRelative(82f, 82f, 133.5f, 189.5f) + reflectiveQuadTo(797f, 779f) + quadToRelative(2f, 25f, -14.5f, 43f) + reflectiveQuadTo(740f, 840f) + close() + moveTo(500f, 840f) + quadToRelative(-25f, 0f, -43f, -17.5f) + reflectiveQuadTo(434f, 780f) + quadToRelative(-9f, -49f, -31.5f, -90.5f) + reflectiveQuadTo(346f, 614f) + quadToRelative(-34f, -34f, -75.5f, -56.5f) + reflectiveQuadTo(180f, 526f) + quadToRelative(-25f, -5f, -42.5f, -23f) + reflectiveQuadTo(120f, 460f) + quadToRelative(0f, -26f, 18f, -43f) + reflectiveQuadToRelative(43f, -13f) + quadToRelative(73f, 10f, 136.5f, 42.5f) + reflectiveQuadTo(431f, 529f) + quadToRelative(50f, 50f, 82.5f, 113.5f) + reflectiveQuadTo(556f, 779f) + quadToRelative(4f, 25f, -13f, 43f) + reflectiveQuadToRelative(-43f, 18f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SamsungLetter.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SamsungLetter.kt new file mode 100644 index 0000000..8c1097b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SamsungLetter.kt @@ -0,0 +1,61 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Save.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Save.kt new file mode 100644 index 0000000..7e9dd7c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Save.kt @@ -0,0 +1,145 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Save: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Save", + 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(447f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(114f, 114f) + quadToRelative(11f, 11f, 17f, 25.5f) + reflectiveQuadToRelative(6f, 30.5f) + verticalLineToRelative(447f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 840f) + lineTo(200f, 840f) + close() + moveTo(760f, 314f) + lineTo(646f, 200f) + lineTo(200f, 200f) + verticalLineToRelative(560f) + horizontalLineToRelative(560f) + verticalLineToRelative(-446f) + close() + moveTo(565f, 685f) + 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(280f, 400f) + horizontalLineToRelative(280f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(600f, 360f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(560f, 240f) + lineTo(280f, 240f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(240f, 280f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(280f, 400f) + close() + moveTo(200f, 314f) + verticalLineToRelative(446f) + verticalLineToRelative(-560f) + verticalLineToRelative(114f) + close() + } + }.build() +} + +val Icons.Rounded.Save: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Save", + 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(447f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(114f, 114f) + quadToRelative(11f, 11f, 17f, 25.5f) + reflectiveQuadToRelative(6f, 30.5f) + verticalLineToRelative(447f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 840f) + lineTo(200f, 840f) + close() + moveTo(565f, 685f) + 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(280f, 400f) + horizontalLineToRelative(280f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(600f, 360f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(560f, 240f) + lineTo(280f, 240f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(240f, 280f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(280f, 400f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SaveAs.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SaveAs.kt new file mode 100644 index 0000000..b50c934 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SaveAs.kt @@ -0,0 +1,133 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.SaveAs: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.SaveAs", + 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(447f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(114f, 114f) + quadToRelative(11f, 11f, 17f, 25.5f) + reflectiveQuadToRelative(6f, 30.5f) + verticalLineToRelative(127f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 480f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(760f, 440f) + verticalLineToRelative(-127f) + lineTo(647f, 200f) + lineTo(200f, 200f) + verticalLineToRelative(560f) + horizontalLineToRelative(200f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(440f, 800f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(400f, 840f) + lineTo(200f, 840f) + close() + moveTo(200f, 200f) + verticalLineToRelative(560f) + verticalLineToRelative(-560f) + close() + moveTo(520f, 880f) + 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(863f, 700f) + lineTo(655f, 908f) + quadToRelative(-6f, 6f, -13.5f, 9f) + reflectiveQuadTo(626f, 920f) + horizontalLineToRelative(-66f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(520f, 880f) + close() + moveTo(820f, 657f) + lineTo(783f, 620f) + lineTo(820f, 657f) + close() + moveTo(580f, 860f) + horizontalLineToRelative(38f) + lineToRelative(121f, -122f) + lineToRelative(-18f, -19f) + lineToRelative(-19f, -18f) + lineToRelative(-122f, 121f) + verticalLineToRelative(38f) + close() + moveTo(721f, 719f) + lineTo(702f, 701f) + lineTo(739f, 738f) + lineTo(721f, 719f) + close() + moveTo(280f, 400f) + horizontalLineToRelative(280f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(600f, 360f) + verticalLineToRelative(-80f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(560f, 240f) + lineTo(280f, 240f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(240f, 280f) + verticalLineToRelative(80f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(280f, 400f) + close() + moveTo(480f, 720f) + horizontalLineToRelative(4f) + lineToRelative(116f, -115f) + verticalLineToRelative(-5f) + quadToRelative(0f, -50f, -35f, -85f) + reflectiveQuadToRelative(-85f, -35f) + quadToRelative(-50f, 0f, -85f, 35f) + reflectiveQuadToRelative(-35f, 85f) + quadToRelative(0f, 50f, 35f, 85f) + reflectiveQuadToRelative(85f, 35f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SaveConfirm.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SaveConfirm.kt new file mode 100644 index 0000000..1378f92 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SaveConfirm.kt @@ -0,0 +1,132 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.SaveConfirm", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(5f, 19f) + lineToRelative(0f, -14f) + lineToRelative(0f, 5.075f) + lineToRelative(0f, -0.075f) + lineToRelative(0f, 9f) + close() + } + 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(11.175f) + curveToRelative(0.267f, 0f, 0.521f, 0.05f, 0.762f, 0.15f) + reflectiveCurveToRelative(0.454f, 0.242f, 0.637f, 0.425f) + lineToRelative(2.85f, 2.85f) + curveToRelative(0.183f, 0.183f, 0.325f, 0.396f, 0.425f, 0.637f) + reflectiveCurveToRelative(0.15f, 0.496f, 0.15f, 0.762f) + verticalLineToRelative(1.55f) + 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(-1.525f) + lineToRelative(-2.85f, -2.85f) + horizontalLineTo(5f) + verticalLineToRelative(14f) + horizontalLineToRelative(5.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) + curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) + horizontalLineToRelative(-5.8f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(7f, 10f) + horizontalLineToRelative(7f) + curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) + reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) + verticalLineToRelative(-2f) + curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.712f) + reflectiveCurveToRelative(-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(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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(11.05f, 17.85f) + curveToRelative(-0.017f, -0.15f, -0.029f, -0.296f, -0.038f, -0.438f) + reflectiveCurveToRelative(-0.013f, -0.287f, -0.013f, -0.438f) + curveToRelative(0f, -0.9f, 0.167f, -1.767f, 0.5f, -2.6f) + reflectiveCurveToRelative(0.817f, -1.575f, 1.45f, -2.225f) + curveToRelative(-0.15f, -0.05f, -0.304f, -0.087f, -0.463f, -0.112f) + reflectiveCurveToRelative(-0.321f, -0.038f, -0.488f, -0.038f) + curveToRelative(-0.833f, 0f, -1.542f, 0.292f, -2.125f, 0.875f) + reflectiveCurveToRelative(-0.875f, 1.292f, -0.875f, 2.125f) + curveToRelative(0f, 0.65f, 0.188f, 1.237f, 0.563f, 1.763f) + reflectiveCurveToRelative(0.871f, 0.887f, 1.487f, 1.087f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(21.537f, 13.463f) + curveToRelative(-0.975f, -0.975f, -2.154f, -1.463f, -3.537f, -1.463f) + curveToRelative(-1.383f, 0f, -2.563f, 0.487f, -3.537f, 1.463f) + curveToRelative(-0.975f, 0.975f, -1.463f, 2.154f, -1.463f, 3.537f) + reflectiveCurveToRelative(0.487f, 2.563f, 1.463f, 3.537f) + curveToRelative(0.975f, 0.975f, 2.154f, 1.463f, 3.537f, 1.463f) + curveToRelative(1.383f, 0f, 2.563f, -0.487f, 3.537f, -1.463f) + curveToRelative(0.975f, -0.975f, 1.463f, -2.154f, 1.463f, -3.537f) + reflectiveCurveToRelative(-0.487f, -2.563f, -1.463f, -3.537f) + close() + moveTo(20.225f, 16.325f) + lineToRelative(-2.25f, 2.225f) + curveToRelative(-0.2f, 0.2f, -0.433f, 0.3f, -0.7f, 0.3f) + reflectiveCurveToRelative(-0.5f, -0.1f, -0.7f, -0.3f) + lineToRelative(-0.8f, -0.8f) + curveToRelative(-0.15f, -0.15f, -0.225f, -0.325f, -0.225f, -0.525f) + curveToRelative(0f, -0.2f, 0.075f, -0.375f, 0.225f, -0.525f) + curveToRelative(0.15f, -0.15f, 0.325f, -0.229f, 0.525f, -0.237f) + curveToRelative(0.2f, -0.008f, 0.375f, 0.063f, 0.525f, 0.212f) + lineToRelative(0.45f, 0.45f) + lineToRelative(1.9f, -1.875f) + curveToRelative(0.15f, -0.133f, 0.325f, -0.204f, 0.525f, -0.213f) + curveToRelative(0.2f, -0.008f, 0.375f, 0.063f, 0.525f, 0.213f) + curveToRelative(0.15f, 0.15f, 0.225f, 0.329f, 0.225f, 0.537f) + curveToRelative(0f, 0.208f, -0.075f, 0.388f, -0.225f, 0.538f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ScaleUnbalanced.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ScaleUnbalanced.kt new file mode 100644 index 0000000..1d1a15a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ScaleUnbalanced.kt @@ -0,0 +1,80 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Scanner.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Scanner.kt new file mode 100644 index 0000000..63a8bd5 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Scanner.kt @@ -0,0 +1,227 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} + +val Icons.Rounded.Scanner: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Scanner", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(704f, 480f) + lineTo(178f, 290f) + quadToRelative(-16f, -6f, -23f, -21f) + reflectiveQuadToRelative(-1f, -31f) + quadToRelative(6f, -16f, 21f, -23f) + reflectiveQuadToRelative(31f, -1f) + lineToRelative(586f, 214f) + quadToRelative(20f, 8f, 34f, 28f) + reflectiveQuadToRelative(14f, 44f) + verticalLineToRelative(220f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 800f) + lineTo(200f, 800f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 720f) + verticalLineToRelative(-160f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 480f) + horizontalLineToRelative(504f) + close() + moveTo(440f, 680f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(720f, 640f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(680f, 600f) + lineTo(440f, 600f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(400f, 640f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(440f, 680f) + close() + moveTo(280f, 680f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(320f, 640f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(280f, 600f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(240f, 640f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(280f, 680f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Schedule.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Schedule.kt new file mode 100644 index 0000000..3727029 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Schedule.kt @@ -0,0 +1,131 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.Schedule: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Schedule", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(520f, 464f) + verticalLineToRelative(-144f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 320f) + verticalLineToRelative(159f) + quadToRelative(0f, 8f, 3f, 15.5f) + reflectiveQuadToRelative(9f, 13.5f) + lineToRelative(132f, 132f) + quadToRelative(11f, 11f, 28f, 11f) + reflectiveQuadToRelative(28f, -11f) + quadToRelative(11f, -11f, 11f, -28f) + reflectiveQuadToRelative(-11f, -28f) + lineTo(520f, 464f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 480f) + close() + moveTo(480f, 800f) + quadToRelative(133f, 0f, 226.5f, -93.5f) + reflectiveQuadTo(800f, 480f) + quadToRelative(0f, -133f, -93.5f, -226.5f) + reflectiveQuadTo(480f, 160f) + quadToRelative(-133f, 0f, -226.5f, 93.5f) + reflectiveQuadTo(160f, 480f) + quadToRelative(0f, 133f, 93.5f, 226.5f) + reflectiveQuadTo(480f, 800f) + close() + } + }.build() +} + +val Icons.Rounded.Schedule: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Schedule", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(520f, 464f) + verticalLineToRelative(-144f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 320f) + verticalLineToRelative(159f) + quadToRelative(0f, 8f, 3f, 15.5f) + reflectiveQuadToRelative(9f, 13.5f) + lineToRelative(132f, 132f) + quadToRelative(11f, 11f, 28f, 11f) + reflectiveQuadToRelative(28f, -11f) + quadToRelative(11f, -11f, 11f, -28f) + reflectiveQuadToRelative(-11f, -28f) + lineTo(520f, 464f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Scissors.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Scissors.kt new file mode 100644 index 0000000..91646cd --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Scissors.kt @@ -0,0 +1,239 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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.Black), + 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.Black), + 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.Black), + 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.Black)) { + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ScissorsSmall.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ScissorsSmall.kt new file mode 100644 index 0000000..e34324d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ScissorsSmall.kt @@ -0,0 +1,244 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ScreenRotationAlt.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ScreenRotationAlt.kt new file mode 100644 index 0000000..9cf9fd2 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ScreenRotationAlt.kt @@ -0,0 +1,98 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ScreenRotationAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ScreenRotationAlt", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(487f, 856f) + lineTo(219f, 589f) + quadToRelative(-6f, -6f, -9f, -13f) + reflectiveQuadToRelative(-3f, -15f) + quadToRelative(0f, -16f, 11f, -28.5f) + reflectiveQuadToRelative(29f, -12.5f) + quadToRelative(8f, 0f, 15.5f, 3f) + reflectiveQuadToRelative(13.5f, 9f) + lineToRelative(268f, 268f) + lineToRelative(200f, -200f) + horizontalLineToRelative(-64f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(640f, 560f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(680f, 520f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 560f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 760f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(800f, 720f) + verticalLineToRelative(-64f) + lineTo(600f, 856f) + quadToRelative(-11f, 11f, -25.5f, 17f) + reflectiveQuadTo(544f, 879f) + quadToRelative(-15f, 0f, -30f, -6f) + reflectiveQuadToRelative(-27f, -17f) + close() + moveTo(120f, 440f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(80f, 400f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 200f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(160f, 240f) + verticalLineToRelative(64f) + lineToRelative(200f, -200f) + quadToRelative(12f, -12f, 27f, -17.5f) + reflectiveQuadToRelative(30f, -5.5f) + quadToRelative(16f, 0f, 30.5f, 5.5f) + reflectiveQuadTo(473f, 104f) + lineToRelative(268f, 267f) + quadToRelative(6f, 6f, 9f, 13f) + reflectiveQuadToRelative(3f, 15f) + quadToRelative(0f, 16f, -11f, 28.5f) + reflectiveQuadTo(713f, 440f) + quadToRelative(-8f, 0f, -15.5f, -3f) + reflectiveQuadToRelative(-13.5f, -9f) + lineTo(416f, 160f) + lineTo(216f, 360f) + horizontalLineToRelative(64f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(320f, 400f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(280f, 440f) + lineTo(120f, 440f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Search.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Search.kt new file mode 100644 index 0000000..cffd096 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Search.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Search: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Search", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(380f, 640f) + quadToRelative(-109f, 0f, -184.5f, -75.5f) + reflectiveQuadTo(120f, 380f) + quadToRelative(0f, -109f, 75.5f, -184.5f) + reflectiveQuadTo(380f, 120f) + quadToRelative(109f, 0f, 184.5f, 75.5f) + reflectiveQuadTo(640f, 380f) + quadToRelative(0f, 44f, -14f, 83f) + reflectiveQuadToRelative(-38f, 69f) + lineToRelative(224f, 224f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(532f, 588f) + quadToRelative(-30f, 24f, -69f, 38f) + reflectiveQuadToRelative(-83f, 14f) + close() + moveTo(380f, 560f) + quadToRelative(75f, 0f, 127.5f, -52.5f) + reflectiveQuadTo(560f, 380f) + quadToRelative(0f, -75f, -52.5f, -127.5f) + reflectiveQuadTo(380f, 200f) + quadToRelative(-75f, 0f, -127.5f, 52.5f) + reflectiveQuadTo(200f, 380f) + quadToRelative(0f, 75f, 52.5f, 127.5f) + reflectiveQuadTo(380f, 560f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SearchOff.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SearchOff.kt new file mode 100644 index 0000000..7e0d035 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SearchOff.kt @@ -0,0 +1,102 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.SearchOff: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.SearchOff", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(280f, 708f) + lineToRelative(56f, 57f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + quadToRelative(6f, -6f, 6f, -14.5f) + reflectiveQuadToRelative(-6f, -14.5f) + lineToRelative(-56f, -56f) + lineToRelative(57f, -57f) + quadToRelative(6f, -6f, 6f, -14f) + reflectiveQuadToRelative(-6f, -14f) + quadToRelative(-6f, -6f, -14f, -6f) + reflectiveQuadToRelative(-14f, 6f) + lineToRelative(-57f, 57f) + lineToRelative(-57f, -57f) + quadToRelative(-6f, -6f, -14f, -6f) + reflectiveQuadToRelative(-14f, 6f) + quadToRelative(-6f, 6f, -6f, 14f) + reflectiveQuadToRelative(6f, 14f) + lineToRelative(57f, 57f) + lineToRelative(-57f, 57f) + quadToRelative(-6f, 6f, -6f, 14f) + reflectiveQuadToRelative(6f, 14f) + quadToRelative(6f, 6f, 14f, 6f) + reflectiveQuadToRelative(14f, -6f) + lineToRelative(57f, -57f) + close() + moveTo(138.5f, 821.5f) + quadTo(80f, 763f, 80f, 680f) + reflectiveQuadToRelative(58.5f, -141.5f) + quadTo(197f, 480f, 280f, 480f) + reflectiveQuadToRelative(141.5f, 58.5f) + quadTo(480f, 597f, 480f, 680f) + reflectiveQuadToRelative(-58.5f, 141.5f) + quadTo(363f, 880f, 280f, 880f) + reflectiveQuadToRelative(-141.5f, -58.5f) + close() + moveTo(568f, 584f) + quadToRelative(-12f, -13f, -25.5f, -26.5f) + reflectiveQuadTo(516f, 532f) + quadToRelative(38f, -24f, 61f, -64f) + reflectiveQuadToRelative(23f, -88f) + quadToRelative(0f, -75f, -52.5f, -127.5f) + reflectiveQuadTo(420f, 200f) + quadToRelative(-75f, 0f, -127.5f, 52.5f) + reflectiveQuadTo(240f, 380f) + quadToRelative(0f, 6f, 0.5f, 11.5f) + reflectiveQuadTo(242f, 403f) + quadToRelative(-18f, 2f, -39.5f, 8f) + reflectiveQuadTo(164f, 425f) + quadToRelative(-2f, -11f, -3f, -22f) + reflectiveQuadToRelative(-1f, -23f) + quadToRelative(0f, -109f, 75.5f, -184.5f) + reflectiveQuadTo(420f, 120f) + quadToRelative(109f, 0f, 184.5f, 75.5f) + reflectiveQuadTo(680f, 380f) + quadToRelative(0f, 43f, -13.5f, 81.5f) + reflectiveQuadTo(629f, 532f) + lineToRelative(223f, 224f) + quadToRelative(11f, 11f, 11.5f, 27.5f) + reflectiveQuadTo(852f, 812f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(568f, 584f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Security.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Security.kt new file mode 100644 index 0000000..e189f82 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Security.kt @@ -0,0 +1,69 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Security: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Security", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 796f) + quadToRelative(97f, -30f, 162f, -118.5f) + reflectiveQuadTo(718f, 480f) + lineTo(480f, 480f) + verticalLineToRelative(-315f) + lineToRelative(-240f, 90f) + verticalLineToRelative(207f) + quadToRelative(0f, 7f, 2f, 18f) + horizontalLineToRelative(238f) + verticalLineToRelative(316f) + close() + moveTo(480f, 876f) + quadToRelative(-7f, 0f, -13f, -1f) + reflectiveQuadToRelative(-12f, -3f) + quadToRelative(-135f, -45f, -215f, -166.5f) + reflectiveQuadTo(160f, 444f) + verticalLineToRelative(-189f) + quadToRelative(0f, -25f, 14.5f, -45f) + reflectiveQuadToRelative(37.5f, -29f) + lineToRelative(240f, -90f) + quadToRelative(14f, -5f, 28f, -5f) + reflectiveQuadToRelative(28f, 5f) + lineToRelative(240f, 90f) + quadToRelative(23f, 9f, 37.5f, 29f) + reflectiveQuadToRelative(14.5f, 45f) + verticalLineToRelative(189f) + quadToRelative(0f, 140f, -80f, 261.5f) + reflectiveQuadTo(505f, 872f) + quadToRelative(-6f, 2f, -12f, 3f) + reflectiveQuadToRelative(-13f, 1f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Segment.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Segment.kt new file mode 100644 index 0000000..e30ca5f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Segment.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Segment: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Segment", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(400f, 720f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(360f, 680f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(400f, 640f) + horizontalLineToRelative(400f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 680f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 720f) + lineTo(400f, 720f) + close() + moveTo(400f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(360f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(400f, 440f) + horizontalLineToRelative(400f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 520f) + lineTo(400f, 520f) + close() + moveTo(160f, 320f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 280f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 240f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 280f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 320f) + lineTo(160f, 320f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SelectAll.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SelectAll.kt new file mode 100644 index 0000000..22490d6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SelectAll.kt @@ -0,0 +1,218 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SelectInverse.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SelectInverse.kt new file mode 100644 index 0000000..37562f5 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SelectInverse.kt @@ -0,0 +1,100 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ServiceToolbox.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ServiceToolbox.kt new file mode 100644 index 0000000..0ce7cac --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ServiceToolbox.kt @@ -0,0 +1,240 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Settings.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Settings.kt new file mode 100644 index 0000000..3d1668a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Settings.kt @@ -0,0 +1,225 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Settings: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Settings", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(433f, 880f) + quadToRelative(-27f, 0f, -46.5f, -18f) + reflectiveQuadTo(363f, 818f) + lineToRelative(-9f, -66f) + quadToRelative(-13f, -5f, -24.5f, -12f) + reflectiveQuadTo(307f, 725f) + lineToRelative(-62f, 26f) + quadToRelative(-25f, 11f, -50f, 2f) + reflectiveQuadToRelative(-39f, -32f) + lineToRelative(-47f, -82f) + quadToRelative(-14f, -23f, -8f, -49f) + reflectiveQuadToRelative(27f, -43f) + lineToRelative(53f, -40f) + quadToRelative(-1f, -7f, -1f, -13.5f) + verticalLineToRelative(-27f) + quadToRelative(0f, -6.5f, 1f, -13.5f) + lineToRelative(-53f, -40f) + quadToRelative(-21f, -17f, -27f, -43f) + reflectiveQuadToRelative(8f, -49f) + lineToRelative(47f, -82f) + quadToRelative(14f, -23f, 39f, -32f) + reflectiveQuadToRelative(50f, 2f) + lineToRelative(62f, 26f) + quadToRelative(11f, -8f, 23f, -15f) + reflectiveQuadToRelative(24f, -12f) + lineToRelative(9f, -66f) + quadToRelative(4f, -26f, 23.5f, -44f) + reflectiveQuadToRelative(46.5f, -18f) + horizontalLineToRelative(94f) + quadToRelative(27f, 0f, 46.5f, 18f) + reflectiveQuadToRelative(23.5f, 44f) + lineToRelative(9f, 66f) + quadToRelative(13f, 5f, 24.5f, 12f) + reflectiveQuadToRelative(22.5f, 15f) + lineToRelative(62f, -26f) + quadToRelative(25f, -11f, 50f, -2f) + reflectiveQuadToRelative(39f, 32f) + lineToRelative(47f, 82f) + quadToRelative(14f, 23f, 8f, 49f) + reflectiveQuadToRelative(-27f, 43f) + lineToRelative(-53f, 40f) + quadToRelative(1f, 7f, 1f, 13.5f) + verticalLineToRelative(27f) + quadToRelative(0f, 6.5f, -2f, 13.5f) + lineToRelative(53f, 40f) + quadToRelative(21f, 17f, 27f, 43f) + reflectiveQuadToRelative(-8f, 49f) + lineToRelative(-48f, 82f) + quadToRelative(-14f, 23f, -39f, 32f) + reflectiveQuadToRelative(-50f, -2f) + lineToRelative(-60f, -26f) + quadToRelative(-11f, 8f, -23f, 15f) + reflectiveQuadToRelative(-24f, 12f) + lineToRelative(-9f, 66f) + quadToRelative(-4f, 26f, -23.5f, 44f) + reflectiveQuadTo(527f, 880f) + horizontalLineToRelative(-94f) + close() + moveTo(440f, 800f) + horizontalLineToRelative(79f) + lineToRelative(14f, -106f) + quadToRelative(31f, -8f, 57.5f, -23.5f) + reflectiveQuadTo(639f, 633f) + lineToRelative(99f, 41f) + lineToRelative(39f, -68f) + lineToRelative(-86f, -65f) + quadToRelative(5f, -14f, 7f, -29.5f) + reflectiveQuadToRelative(2f, -31.5f) + quadToRelative(0f, -16f, -2f, -31.5f) + reflectiveQuadToRelative(-7f, -29.5f) + lineToRelative(86f, -65f) + lineToRelative(-39f, -68f) + lineToRelative(-99f, 42f) + quadToRelative(-22f, -23f, -48.5f, -38.5f) + reflectiveQuadTo(533f, 266f) + lineToRelative(-13f, -106f) + horizontalLineToRelative(-79f) + lineToRelative(-14f, 106f) + quadToRelative(-31f, 8f, -57.5f, 23.5f) + reflectiveQuadTo(321f, 327f) + lineToRelative(-99f, -41f) + lineToRelative(-39f, 68f) + lineToRelative(86f, 64f) + quadToRelative(-5f, 15f, -7f, 30f) + reflectiveQuadToRelative(-2f, 32f) + quadToRelative(0f, 16f, 2f, 31f) + reflectiveQuadToRelative(7f, 30f) + lineToRelative(-86f, 65f) + lineToRelative(39f, 68f) + lineToRelative(99f, -42f) + quadToRelative(22f, 23f, 48.5f, 38.5f) + reflectiveQuadTo(427f, 694f) + lineToRelative(13f, 106f) + close() + moveTo(482f, 620f) + quadToRelative(58f, 0f, 99f, -41f) + reflectiveQuadToRelative(41f, -99f) + quadToRelative(0f, -58f, -41f, -99f) + reflectiveQuadToRelative(-99f, -41f) + quadToRelative(-59f, 0f, -99.5f, 41f) + reflectiveQuadTo(342f, 480f) + quadToRelative(0f, 58f, 40.5f, 99f) + reflectiveQuadToRelative(99.5f, 41f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} + +val Icons.Rounded.Settings: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Settings", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(433f, 880f) + quadToRelative(-27f, 0f, -46.5f, -18f) + reflectiveQuadTo(363f, 818f) + lineToRelative(-9f, -66f) + quadToRelative(-13f, -5f, -24.5f, -12f) + reflectiveQuadTo(307f, 725f) + lineToRelative(-62f, 26f) + quadToRelative(-25f, 11f, -50f, 2f) + reflectiveQuadToRelative(-39f, -32f) + lineToRelative(-47f, -82f) + quadToRelative(-14f, -23f, -8f, -49f) + reflectiveQuadToRelative(27f, -43f) + lineToRelative(53f, -40f) + quadToRelative(-1f, -7f, -1f, -13.5f) + verticalLineToRelative(-27f) + quadToRelative(0f, -6.5f, 1f, -13.5f) + lineToRelative(-53f, -40f) + quadToRelative(-21f, -17f, -27f, -43f) + reflectiveQuadToRelative(8f, -49f) + lineToRelative(47f, -82f) + quadToRelative(14f, -23f, 39f, -32f) + reflectiveQuadToRelative(50f, 2f) + lineToRelative(62f, 26f) + quadToRelative(11f, -8f, 23f, -15f) + reflectiveQuadToRelative(24f, -12f) + lineToRelative(9f, -66f) + quadToRelative(4f, -26f, 23.5f, -44f) + reflectiveQuadToRelative(46.5f, -18f) + horizontalLineToRelative(94f) + quadToRelative(27f, 0f, 46.5f, 18f) + reflectiveQuadToRelative(23.5f, 44f) + lineToRelative(9f, 66f) + quadToRelative(13f, 5f, 24.5f, 12f) + reflectiveQuadToRelative(22.5f, 15f) + lineToRelative(62f, -26f) + quadToRelative(25f, -11f, 50f, -2f) + reflectiveQuadToRelative(39f, 32f) + lineToRelative(47f, 82f) + quadToRelative(14f, 23f, 8f, 49f) + reflectiveQuadToRelative(-27f, 43f) + lineToRelative(-53f, 40f) + quadToRelative(1f, 7f, 1f, 13.5f) + verticalLineToRelative(27f) + quadToRelative(0f, 6.5f, -2f, 13.5f) + lineToRelative(53f, 40f) + quadToRelative(21f, 17f, 27f, 43f) + reflectiveQuadToRelative(-8f, 49f) + lineToRelative(-48f, 82f) + quadToRelative(-14f, 23f, -39f, 32f) + reflectiveQuadToRelative(-50f, -2f) + lineToRelative(-60f, -26f) + quadToRelative(-11f, 8f, -23f, 15f) + reflectiveQuadToRelative(-24f, 12f) + lineToRelative(-9f, 66f) + quadToRelative(-4f, 26f, -23.5f, 44f) + reflectiveQuadTo(527f, 880f) + horizontalLineToRelative(-94f) + close() + moveTo(482f, 620f) + quadToRelative(58f, 0f, 99f, -41f) + reflectiveQuadToRelative(41f, -99f) + quadToRelative(0f, -58f, -41f, -99f) + reflectiveQuadToRelative(-99f, -41f) + quadToRelative(-59f, 0f, -99.5f, 41f) + reflectiveQuadTo(342f, 480f) + quadToRelative(0f, 58f, 40.5f, 99f) + reflectiveQuadToRelative(99.5f, 41f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SettingsBackupRestore.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SettingsBackupRestore.kt new file mode 100644 index 0000000..ce0cb3f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SettingsBackupRestore.kt @@ -0,0 +1,88 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.SettingsBackupRestore: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.SettingsBackupRestore", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 840f) + quadToRelative(-126f, 0f, -223f, -76.5f) + reflectiveQuadTo(131f, 568f) + quadToRelative(-4f, -15f, 6f, -27.5f) + reflectiveQuadToRelative(27f, -14.5f) + quadToRelative(16f, -2f, 29f, 6f) + reflectiveQuadToRelative(18f, 24f) + quadToRelative(24f, 90f, 99f, 147f) + reflectiveQuadToRelative(170f, 57f) + quadToRelative(117f, 0f, 198.5f, -81.5f) + reflectiveQuadTo(760f, 480f) + quadToRelative(0f, -117f, -81.5f, -198.5f) + reflectiveQuadTo(480f, 200f) + quadToRelative(-69f, 0f, -129f, 32f) + reflectiveQuadToRelative(-101f, 88f) + horizontalLineToRelative(70f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 360f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 400f) + lineTo(160f, 400f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 360f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(200f, 200f) + verticalLineToRelative(54f) + quadToRelative(51f, -64f, 124.5f, -99f) + reflectiveQuadTo(480f, 120f) + quadToRelative(75f, 0f, 140.5f, 28.5f) + reflectiveQuadToRelative(114f, 77f) + quadToRelative(48.5f, 48.5f, 77f, 114f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 75f, -28.5f, 140.5f) + reflectiveQuadToRelative(-77f, 114f) + quadToRelative(-48.5f, 48.5f, -114f, 77f) + reflectiveQuadTo(480f, 840f) + close() + moveTo(480f, 560f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(400f, 480f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(480f, 400f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(560f, 480f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(480f, 560f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SettingsEthernet.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SettingsEthernet.kt new file mode 100644 index 0000000..e7ee5bd --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SettingsEthernet.kt @@ -0,0 +1,104 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.SettingsEthernet: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.SettingsEthernet", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(806f, 480f) + lineTo(652f, 324f) + quadToRelative(-11f, -11f, -11.5f, -27.5f) + reflectiveQuadTo(652f, 268f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(184f, 184f) + quadToRelative(6f, 6f, 8.5f, 13f) + reflectiveQuadToRelative(2.5f, 15f) + quadToRelative(0f, 8f, -2.5f, 15f) + reflectiveQuadToRelative(-8.5f, 13f) + lineTo(708f, 692f) + quadToRelative(-11f, 11f, -27.5f, 11.5f) + reflectiveQuadTo(652f, 692f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + lineToRelative(154f, -156f) + close() + moveTo(154f, 480f) + lineTo(308f, 636f) + quadToRelative(11f, 11f, 11.5f, 27.5f) + reflectiveQuadTo(308f, 692f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(68f, 508f) + quadToRelative(-6f, -6f, -8.5f, -13f) + reflectiveQuadTo(57f, 480f) + quadToRelative(0f, -8f, 2.5f, -15f) + reflectiveQuadToRelative(8.5f, -13f) + lineToRelative(184f, -184f) + quadToRelative(11f, -11f, 27.5f, -11.5f) + reflectiveQuadTo(308f, 268f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineTo(154f, 480f) + close() + moveTo(320f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 520f) + close() + moveTo(480f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 520f) + close() + moveTo(640f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 440f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 520f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SettingsSuggest.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SettingsSuggest.kt new file mode 100644 index 0000000..80669a7 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SettingsSuggest.kt @@ -0,0 +1,192 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.SettingsSuggest: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.SettingsSuggest", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(18.525f, 8.3f) + quadToRelative(-0.125f, 0f, -0.262f, -0.075f) + quadToRelative(-0.138f, -0.075f, -0.188f, -0.225f) + lineToRelative(-0.65f, -1.4f) + lineToRelative(-1.4f, -0.65f) + quadToRelative(-0.15f, -0.05f, -0.225f, -0.188f) + quadToRelative(-0.075f, -0.137f, -0.075f, -0.262f) + reflectiveQuadToRelative(0.075f, -0.263f) + quadToRelative(0.075f, -0.137f, 0.225f, -0.187f) + lineToRelative(1.4f, -0.65f) + lineToRelative(0.65f, -1.4f) + quadToRelative(0.05f, -0.15f, 0.188f, -0.225f) + quadToRelative(0.137f, -0.075f, 0.262f, -0.075f) + reflectiveQuadToRelative(0.263f, 0.075f) + quadToRelative(0.137f, 0.075f, 0.187f, 0.225f) + lineToRelative(0.65f, 1.4f) + lineToRelative(1.4f, 0.65f) + quadToRelative(0.15f, 0.05f, 0.225f, 0.187f) + quadToRelative(0.075f, 0.138f, 0.075f, 0.263f) + reflectiveQuadToRelative(-0.075f, 0.262f) + quadToRelative(-0.075f, 0.138f, -0.225f, 0.188f) + lineToRelative(-1.4f, 0.65f) + lineToRelative(-0.65f, 1.4f) + quadToRelative(-0.05f, 0.15f, -0.187f, 0.225f) + quadToRelative(-0.138f, 0.075f, -0.263f, 0.075f) + close() + moveTo(20.525f, 15.325f) + quadToRelative(-0.125f, 0f, -0.25f, -0.075f) + reflectiveQuadToRelative(-0.2f, -0.2f) + lineToRelative(-0.35f, -0.75f) + lineToRelative(-0.75f, -0.35f) + quadToRelative(-0.075f, -0.05f, -0.275f, -0.45f) + quadToRelative(0f, -0.125f, 0.075f, -0.25f) + reflectiveQuadToRelative(0.2f, -0.2f) + lineToRelative(0.75f, -0.35f) + lineToRelative(0.35f, -0.75f) + quadToRelative(0.05f, -0.075f, 0.45f, -0.275f) + quadToRelative(0.125f, 0f, 0.25f, 0.075f) + reflectiveQuadToRelative(0.2f, 0.2f) + lineToRelative(0.35f, 0.75f) + lineToRelative(0.75f, 0.35f) + quadToRelative(0.075f, 0.05f, 0.275f, 0.45f) + quadToRelative(0f, 0.125f, -0.075f, 0.25f) + reflectiveQuadToRelative(-0.2f, 0.2f) + lineToRelative(-0.75f, 0.35f) + lineToRelative(-0.35f, 0.75f) + quadToRelative(-0.05f, 0.075f, -0.45f, 0.275f) + close() + moveTo(8.4f, 22f) + quadToRelative(-0.375f, 0f, -0.65f, -0.25f) + reflectiveQuadToRelative(-0.325f, -0.625f) + lineToRelative(-0.2f, -1.475f) + quadToRelative(-0.2f, -0.075f, -0.387f, -0.2f) + quadToRelative(-0.188f, -0.125f, -0.313f, -0.25f) + lineToRelative(-1.375f, 0.6f) + quadToRelative(-0.35f, 0.175f, -0.712f, 0.05f) + quadToRelative(-0.363f, -0.125f, -0.563f, -0.475f) + lineToRelative(-1.6f, -2.8f) + quadToRelative(-0.2f, -0.325f, -0.112f, -0.7f) + quadToRelative(0.087f, -0.375f, 0.387f, -0.6f) + lineToRelative(1.175f, -0.875f) + verticalLineToRelative(-0.8f) + lineToRelative(-1.175f, -0.875f) + quadToRelative(-0.3f, -0.225f, -0.387f, -0.6f) + quadToRelative(-0.088f, -0.375f, 0.112f, -0.7f) + lineToRelative(1.6f, -2.8f) + quadToRelative(0.2f, -0.35f, 0.563f, -0.475f) + quadToRelative(0.362f, -0.125f, 0.712f, 0.05f) + lineToRelative(1.375f, 0.6f) + quadToRelative(0.125f, -0.125f, 0.313f, -0.25f) + quadToRelative(0.187f, -0.125f, 0.387f, -0.2f) + lineToRelative(0.2f, -1.475f) + quadToRelative(0.05f, -0.375f, 0.325f, -0.625f) + reflectiveQuadTo(8.4f, 6f) + horizontalLineToRelative(3.25f) + quadToRelative(0.375f, 0f, 0.65f, 0.25f) + reflectiveQuadToRelative(0.325f, 0.625f) + lineToRelative(0.2f, 1.475f) + quadToRelative(0.2f, 0.075f, 0.388f, 0.2f) + quadToRelative(0.187f, 0.125f, 0.312f, 0.25f) + lineToRelative(1.375f, -0.6f) + quadToRelative(0.35f, -0.175f, 0.713f, -0.05f) + quadToRelative(0.362f, 0.125f, 0.562f, 0.475f) + lineToRelative(1.6f, 2.8f) + quadToRelative(0.2f, 0.325f, 0.113f, 0.7f) + quadToRelative(-0.088f, 0.375f, -0.388f, 0.6f) + lineToRelative(-1.175f, 0.875f) + verticalLineToRelative(0.8f) + lineToRelative(1.175f, 0.875f) + quadToRelative(0.3f, 0.225f, 0.388f, 0.6f) + quadToRelative(0.087f, 0.375f, -0.113f, 0.7f) + lineToRelative(-1.6f, 2.8f) + quadToRelative(-0.2f, 0.35f, -0.562f, 0.475f) + quadToRelative(-0.363f, 0.125f, -0.713f, -0.05f) + lineToRelative(-1.375f, -0.6f) + quadToRelative(-0.125f, 0.125f, -0.312f, 0.25f) + quadToRelative(-0.188f, 0.125f, -0.388f, 0.2f) + lineToRelative(-0.2f, 1.475f) + quadToRelative(-0.05f, 0.375f, -0.325f, 0.625f) + reflectiveQuadToRelative(-0.65f, 0.25f) + close() + moveTo(10.025f, 17f) + quadToRelative(1.25f, 0f, 2.125f, -0.875f) + reflectiveQuadTo(13.025f, 14f) + quadToRelative(0f, -1.25f, -0.875f, -2.125f) + reflectiveQuadTo(10.025f, 11f) + quadToRelative(-1.25f, 0f, -2.125f, 0.875f) + reflectiveQuadTo(7.025f, 14f) + quadToRelative(0f, 1.25f, 0.875f, 2.125f) + reflectiveQuadToRelative(2.125f, 0.875f) + close() + moveTo(10.025f, 15f) + quadToRelative(-0.425f, 0f, -0.713f, -0.288f) + quadToRelative(-0.287f, -0.287f, -0.287f, -0.712f) + reflectiveQuadToRelative(0.287f, -0.713f) + quadTo(9.6f, 13f, 10.025f, 13f) + reflectiveQuadToRelative(0.713f, 0.287f) + quadToRelative(0.287f, 0.288f, 0.287f, 0.713f) + reflectiveQuadToRelative(-0.287f, 0.712f) + quadToRelative(-0.288f, 0.288f, -0.713f, 0.288f) + close() + moveTo(9.275f, 20f) + horizontalLineToRelative(1.5f) + lineToRelative(0.2f, -1.8f) + quadToRelative(0.725f, -0.2f, 1.238f, -0.512f) + quadToRelative(0.512f, -0.313f, 1.012f, -0.838f) + lineToRelative(1.65f, 0.75f) + lineToRelative(0.7f, -1.25f) + lineToRelative(-1.45f, -1.1f) + quadToRelative(0.2f, -0.575f, 0.2f, -1.25f) + reflectiveQuadToRelative(-0.2f, -1.25f) + lineToRelative(1.45f, -1.1f) + lineToRelative(-0.7f, -1.25f) + lineToRelative(-1.65f, 0.75f) + quadToRelative(-0.5f, -0.525f, -1.012f, -0.838f) + quadTo(11.7f, 10f, 10.975f, 9.8f) + lineToRelative(-0.2f, -1.8f) + horizontalLineToRelative(-1.5f) + lineToRelative(-0.2f, 1.8f) + quadToRelative(-0.725f, 0.2f, -1.237f, 0.512f) + quadToRelative(-0.513f, 0.313f, -1.013f, 0.838f) + lineToRelative(-1.65f, -0.75f) + lineToRelative(-0.7f, 1.25f) + lineToRelative(1.45f, 1.1f) + quadToRelative(-0.2f, 0.575f, -0.212f, 1.25f) + quadToRelative(-0.013f, 0.675f, 0.212f, 1.25f) + lineToRelative(-1.45f, 1.1f) + lineToRelative(0.7f, 1.25f) + lineToRelative(1.65f, -0.75f) + quadToRelative(0.5f, 0.525f, 1.013f, 0.838f) + quadToRelative(0.512f, 0.312f, 1.237f, 0.512f) + close() + moveTo(10.025f, 14f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SettingsTimelapse.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SettingsTimelapse.kt new file mode 100644 index 0000000..282ad1d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SettingsTimelapse.kt @@ -0,0 +1,160 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.SettingsTimelapse", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 480f) + close() + moveTo(480f, 480f) + close() + moveTo(440f, 880f) + horizontalLineToRelative(-35f) + quadToRelative(-15f, 0f, -26f, -10f) + reflectiveQuadToRelative(-13f, -25f) + lineToRelative(-12f, -91f) + quadToRelative(-13f, -5f, -24.5f, -12f) + reflectiveQuadTo(307f, 727f) + lineToRelative(-62f, 26f) + quadToRelative(-25f, 11f, -50f, 2f) + reflectiveQuadToRelative(-39f, -32f) + lineToRelative(-47f, -82f) + quadToRelative(-14f, -23f, -8f, -49f) + reflectiveQuadToRelative(27f, -43f) + lineToRelative(53f, -40f) + quadToRelative(-1f, -7f, -1f, -13.5f) + verticalLineToRelative(-27f) + quadToRelative(0f, -6.5f, 1f, -13.5f) + lineToRelative(-53f, -40f) + quadToRelative(-21f, -17f, -27f, -43f) + reflectiveQuadToRelative(8f, -49f) + lineToRelative(47f, -82f) + quadToRelative(14f, -23f, 39f, -32f) + reflectiveQuadToRelative(50f, 2f) + lineToRelative(62f, 26f) + quadToRelative(11f, -8f, 23f, -15f) + reflectiveQuadToRelative(24f, -12f) + lineToRelative(8f, -66f) + quadToRelative(3f, -27f, 23f, -44.5f) + reflectiveQuadToRelative(47f, -17.5f) + horizontalLineToRelative(96f) + quadToRelative(27f, 0f, 47f, 17.5f) + reflectiveQuadToRelative(23f, 44.5f) + lineToRelative(8f, 66f) + quadToRelative(13f, 5f, 24.5f, 12f) + reflectiveQuadToRelative(22.5f, 15f) + lineToRelative(60f, -26f) + quadToRelative(25f, -11f, 50.5f, -2f) + reflectiveQuadToRelative(39.5f, 32f) + lineToRelative(47f, 82f) + quadToRelative(14f, 23f, 8.5f, 49f) + reflectiveQuadTo(832f, 415f) + lineToRelative(-53f, 38f) + quadToRelative(1f, 7f, 1f, 13f) + verticalLineToRelative(13f) + quadToRelative(0f, 17f, -12.5f, 29f) + reflectiveQuadTo(737f, 520f) + quadToRelative(-16f, 0f, -26.5f, -12f) + reflectiveQuadTo(700f, 479f) + quadToRelative(0f, -15f, -2f, -30f) + reflectiveQuadToRelative(-7f, -30f) + lineToRelative(86f, -65f) + lineToRelative(-39f, -68f) + lineToRelative(-99f, 42f) + quadToRelative(-22f, -23f, -48.5f, -38.5f) + reflectiveQuadTo(533f, 266f) + lineToRelative(-13f, -106f) + horizontalLineToRelative(-79f) + lineToRelative(-14f, 106f) + quadToRelative(-31f, 8f, -57.5f, 23.5f) + reflectiveQuadTo(321f, 327f) + lineToRelative(-99f, -41f) + lineToRelative(-39f, 68f) + lineToRelative(86f, 64f) + quadToRelative(-5f, 15f, -7f, 30f) + reflectiveQuadToRelative(-2f, 32f) + quadToRelative(0f, 16f, 2f, 31f) + reflectiveQuadToRelative(7f, 30f) + lineToRelative(-86f, 65f) + lineToRelative(39f, 68f) + lineToRelative(99f, -42f) + quadToRelative(24f, 25f, 54f, 42f) + reflectiveQuadToRelative(65f, 22f) + verticalLineToRelative(184f) + close() + moveTo(750f, 822f) + quadToRelative(-10f, 6f, -20f, 0.5f) + reflectiveQuadTo(720f, 805f) + verticalLineToRelative(-170f) + quadToRelative(0f, -12f, 10f, -17.5f) + reflectiveQuadToRelative(20f, 0.5f) + lineToRelative(142f, 85f) + quadToRelative(10f, 6f, 10f, 17f) + reflectiveQuadToRelative(-10f, 17f) + lineToRelative(-142f, 85f) + close() + moveTo(550f, 822f) + quadToRelative(-10f, 6f, -20f, 0.5f) + reflectiveQuadTo(520f, 805f) + verticalLineToRelative(-170f) + quadToRelative(0f, -12f, 10f, -17.5f) + reflectiveQuadToRelative(20f, 0.5f) + lineToRelative(142f, 85f) + quadToRelative(10f, 6f, 10f, 17f) + reflectiveQuadToRelative(-10f, 17f) + lineToRelative(-142f, 85f) + close() + moveTo(482f, 340f) + quadToRelative(56f, 0f, 96f, 38f) + reflectiveQuadToRelative(44f, 94f) + quadToRelative(2f, 20f, -9.5f, 35.5f) + reflectiveQuadTo(581f, 524f) + quadToRelative(-17f, 1f, -29.5f, -10.5f) + reflectiveQuadTo(541f, 485f) + quadToRelative(4f, -26f, -14f, -45.5f) + reflectiveQuadTo(482f, 420f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(422f, 480f) + quadToRelative(0f, 14f, 5.5f, 25.5f) + reflectiveQuadTo(443f, 525f) + quadToRelative(11f, 10f, 10f, 25.5f) + reflectiveQuadTo(440f, 577f) + quadToRelative(-14f, 12f, -32.5f, 10.5f) + reflectiveQuadTo(376f, 572f) + quadToRelative(-18f, -18f, -26f, -42f) + reflectiveQuadToRelative(-8f, -50f) + quadToRelative(0f, -59f, 41f, -99.5f) + reflectiveQuadToRelative(99f, -40.5f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Shadow.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Shadow.kt new file mode 100644 index 0000000..de5cb40 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Shadow.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Share.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Share.kt new file mode 100644 index 0000000..60eba0a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Share.kt @@ -0,0 +1,161 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Share: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Share", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(680f, 880f) + quadToRelative(-50f, 0f, -85f, -35f) + reflectiveQuadToRelative(-35f, -85f) + quadToRelative(0f, -6f, 3f, -28f) + lineTo(282f, 568f) + quadToRelative(-16f, 15f, -37f, 23.5f) + reflectiveQuadToRelative(-45f, 8.5f) + quadToRelative(-50f, 0f, -85f, -35f) + reflectiveQuadToRelative(-35f, -85f) + quadToRelative(0f, -50f, 35f, -85f) + reflectiveQuadToRelative(85f, -35f) + quadToRelative(24f, 0f, 45f, 8.5f) + reflectiveQuadToRelative(37f, 23.5f) + lineToRelative(281f, -164f) + quadToRelative(-2f, -7f, -2.5f, -13.5f) + reflectiveQuadTo(560f, 200f) + quadToRelative(0f, -50f, 35f, -85f) + reflectiveQuadToRelative(85f, -35f) + quadToRelative(50f, 0f, 85f, 35f) + reflectiveQuadToRelative(35f, 85f) + quadToRelative(0f, 50f, -35f, 85f) + reflectiveQuadToRelative(-85f, 35f) + quadToRelative(-24f, 0f, -45f, -8.5f) + reflectiveQuadTo(598f, 288f) + lineTo(317f, 452f) + quadToRelative(2f, 7f, 2.5f, 13.5f) + reflectiveQuadToRelative(0.5f, 14.5f) + quadToRelative(0f, 8f, -0.5f, 14.5f) + reflectiveQuadTo(317f, 508f) + lineToRelative(281f, 164f) + quadToRelative(16f, -15f, 37f, -23.5f) + reflectiveQuadToRelative(45f, -8.5f) + quadToRelative(50f, 0f, 85f, 35f) + reflectiveQuadToRelative(35f, 85f) + quadToRelative(0f, 50f, -35f, 85f) + reflectiveQuadToRelative(-85f, 35f) + close() + moveTo(680f, 800f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(720f, 760f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(680f, 720f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(640f, 760f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(680f, 800f) + close() + moveTo(200f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(240f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(200f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(160f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(200f, 520f) + close() + moveTo(708.5f, 228.5f) + quadTo(720f, 217f, 720f, 200f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(697f, 160f, 680f, 160f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(640f, 183f, 640f, 200f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(663f, 240f, 680f, 240f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(680f, 760f) + close() + moveTo(200f, 480f) + close() + moveTo(680f, 200f) + close() + } + }.build() +} + +val Icons.Rounded.Share: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Share", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(680f, 880f) + quadToRelative(-50f, 0f, -85f, -35f) + reflectiveQuadToRelative(-35f, -85f) + quadToRelative(0f, -6f, 3f, -28f) + lineTo(282f, 568f) + quadToRelative(-16f, 15f, -37f, 23.5f) + reflectiveQuadToRelative(-45f, 8.5f) + quadToRelative(-50f, 0f, -85f, -35f) + reflectiveQuadToRelative(-35f, -85f) + quadToRelative(0f, -50f, 35f, -85f) + reflectiveQuadToRelative(85f, -35f) + quadToRelative(24f, 0f, 45f, 8.5f) + reflectiveQuadToRelative(37f, 23.5f) + lineToRelative(281f, -164f) + quadToRelative(-2f, -7f, -2.5f, -13.5f) + reflectiveQuadTo(560f, 200f) + quadToRelative(0f, -50f, 35f, -85f) + reflectiveQuadToRelative(85f, -35f) + quadToRelative(50f, 0f, 85f, 35f) + reflectiveQuadToRelative(35f, 85f) + quadToRelative(0f, 50f, -35f, 85f) + reflectiveQuadToRelative(-85f, 35f) + quadToRelative(-24f, 0f, -45f, -8.5f) + reflectiveQuadTo(598f, 288f) + lineTo(317f, 452f) + quadToRelative(2f, 7f, 2.5f, 13.5f) + reflectiveQuadToRelative(0.5f, 14.5f) + quadToRelative(0f, 8f, -0.5f, 14.5f) + reflectiveQuadTo(317f, 508f) + lineToRelative(281f, 164f) + quadToRelative(16f, -15f, 37f, -23.5f) + reflectiveQuadToRelative(45f, -8.5f) + quadToRelative(50f, 0f, 85f, 35f) + reflectiveQuadToRelative(35f, 85f) + quadToRelative(0f, 50f, -35f, 85f) + reflectiveQuadToRelative(-85f, 35f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShareOff.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShareOff.kt new file mode 100644 index 0000000..a59b82d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShareOff.kt @@ -0,0 +1,217 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Shield.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Shield.kt new file mode 100644 index 0000000..ff961e5 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Shield.kt @@ -0,0 +1,70 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Shield: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Shield", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 876f) + quadToRelative(-7f, 0f, -13f, -1f) + reflectiveQuadToRelative(-12f, -3f) + quadToRelative(-135f, -45f, -215f, -166.5f) + reflectiveQuadTo(160f, 444f) + verticalLineToRelative(-189f) + quadToRelative(0f, -25f, 14.5f, -45f) + reflectiveQuadToRelative(37.5f, -29f) + lineToRelative(240f, -90f) + quadToRelative(14f, -5f, 28f, -5f) + reflectiveQuadToRelative(28f, 5f) + lineToRelative(240f, 90f) + quadToRelative(23f, 9f, 37.5f, 29f) + reflectiveQuadToRelative(14.5f, 45f) + verticalLineToRelative(189f) + quadToRelative(0f, 140f, -80f, 261.5f) + reflectiveQuadTo(505f, 872f) + quadToRelative(-6f, 2f, -12f, 3f) + reflectiveQuadToRelative(-13f, 1f) + close() + moveTo(480f, 796f) + quadToRelative(104f, -33f, 172f, -132f) + reflectiveQuadToRelative(68f, -220f) + verticalLineToRelative(-189f) + lineToRelative(-240f, -90f) + lineToRelative(-240f, 90f) + verticalLineToRelative(189f) + quadToRelative(0f, 121f, 68f, 220f) + reflectiveQuadToRelative(172f, 132f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShieldLock.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShieldLock.kt new file mode 100644 index 0000000..564e1df --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShieldLock.kt @@ -0,0 +1,187 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShineDiamond.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShineDiamond.kt new file mode 100644 index 0000000..cb4d8a1 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShineDiamond.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShortText.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShortText.kt new file mode 100644 index 0000000..6dc5a75 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShortText.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ShortText: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ShortText", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(200f, 600f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 560f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 520f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(560f, 560f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(520f, 600f) + lineTo(200f, 600f) + close() + moveTo(200f, 440f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 400f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 360f) + horizontalLineToRelative(560f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 400f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 440f) + lineTo(200f, 440f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Shuffle.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Shuffle.kt new file mode 100644 index 0000000..5562843 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Shuffle.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Shuffle: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Shuffle", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(600f, 800f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(560f, 760f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(600f, 720f) + horizontalLineToRelative(64f) + lineToRelative(-99f, -99f) + quadToRelative(-12f, -12f, -11.5f, -28.5f) + reflectiveQuadTo(566f, 564f) + quadToRelative(12f, -12f, 28.5f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + lineToRelative(97f, 98f) + verticalLineToRelative(-62f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(760f, 560f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 600f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 800f) + lineTo(600f, 800f) + close() + moveTo(172f, 788f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + lineToRelative(492f, -492f) + horizontalLineToRelative(-64f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(560f, 200f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(600f, 160f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 200f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 400f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(720f, 360f) + verticalLineToRelative(-64f) + lineTo(228f, 788f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + close() + moveTo(171f, 228f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + quadToRelative(11f, -11f, 27.5f, -11f) + reflectiveQuadToRelative(28.5f, 11f) + lineToRelative(168f, 167f) + quadToRelative(11f, 11f, 11.5f, 27.5f) + reflectiveQuadTo(395f, 395f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(171f, 228f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Signature.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Signature.kt new file mode 100644 index 0000000..bc44c7d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Signature.kt @@ -0,0 +1,166 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SkewMore.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SkewMore.kt new file mode 100644 index 0000000..d5f20ca --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SkewMore.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SkipNext.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SkipNext.kt new file mode 100644 index 0000000..6585a6e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SkipNext.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.SkipNext: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.SkipNext", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(660f, 680f) + verticalLineToRelative(-400f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(700f, 240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(740f, 280f) + verticalLineToRelative(400f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(700f, 720f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(660f, 680f) + close() + moveTo(220f, 645f) + verticalLineToRelative(-330f) + quadToRelative(0f, -18f, 12f, -29f) + reflectiveQuadToRelative(28f, -11f) + quadToRelative(5f, 0f, 11f, 1f) + reflectiveQuadToRelative(11f, 5f) + lineToRelative(248f, 166f) + quadToRelative(9f, 6f, 13.5f, 14.5f) + reflectiveQuadTo(548f, 480f) + quadToRelative(0f, 10f, -4.5f, 18.5f) + reflectiveQuadTo(530f, 513f) + lineTo(282f, 679f) + quadToRelative(-5f, 4f, -11f, 5f) + reflectiveQuadToRelative(-11f, 1f) + quadToRelative(-16f, 0f, -28f, -11f) + reflectiveQuadToRelative(-12f, -29f) + close() + moveTo(300f, 480f) + close() + moveTo(300f, 570f) + lineTo(436f, 480f) + lineTo(300f, 390f) + verticalLineToRelative(180f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Slider.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Slider.kt new file mode 100644 index 0000000..2f7d27d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Slider.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Slideshow.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Slideshow.kt new file mode 100644 index 0000000..2df163c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Slideshow.kt @@ -0,0 +1,73 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Slideshow: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Slideshow", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(634f, 497f) + quadToRelative(9f, -6f, 9f, -17f) + reflectiveQuadToRelative(-9f, -17f) + lineTo(411f, 320f) + quadToRelative(-10f, -7f, -20.5f, -1f) + reflectiveQuadTo(380f, 337f) + verticalLineToRelative(286f) + quadToRelative(0f, 12f, 10.5f, 18f) + reflectiveQuadToRelative(20.5f, -1f) + lineToRelative(223f, -143f) + 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(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, 200f) + verticalLineToRelative(560f) + verticalLineToRelative(-560f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Sms.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Sms.kt new file mode 100644 index 0000000..b742362 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Sms.kt @@ -0,0 +1,83 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Sms: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Sms", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(320f, 440f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(360f, 400f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(320f, 360f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(280f, 400f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(320f, 440f) + close() + moveTo(480f, 440f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 400f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 360f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 400f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 440f) + close() + moveTo(640f, 440f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(680f, 400f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(640f, 360f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(600f, 400f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(640f, 440f) + close() + moveTo(240f, 720f) + lineToRelative(-92f, 92f) + quadToRelative(-19f, 19f, -43.5f, 8.5f) + reflectiveQuadTo(80f, 783f) + verticalLineToRelative(-623f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(160f, 80f) + horizontalLineToRelative(640f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 160f) + verticalLineToRelative(480f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 720f) + lineTo(240f, 720f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Snail.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Snail.kt new file mode 100644 index 0000000..c8197d9 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Snail.kt @@ -0,0 +1,79 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Snowflake.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Snowflake.kt new file mode 100644 index 0000000..54fa6ef --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Snowflake.kt @@ -0,0 +1,84 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Speed.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Speed.kt new file mode 100644 index 0000000..017f629 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Speed.kt @@ -0,0 +1,130 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} + +val Icons.Outlined.Speed: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Speed", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(418f, 620f) + quadToRelative(24f, 24f, 62f, 23.5f) + reflectiveQuadToRelative(56f, -27.5f) + lineToRelative(169f, -253f) + quadToRelative(9f, -14f, -2.5f, -25.5f) + reflectiveQuadTo(677f, 335f) + lineTo(424f, 504f) + quadToRelative(-27f, 18f, -28.5f, 55f) + reflectiveQuadToRelative(22.5f, 61f) + close() + moveTo(480f, 160f) + quadToRelative(36f, 0f, 71f, 6f) + reflectiveQuadToRelative(68f, 19f) + quadToRelative(16f, 6f, 34f, 22.5f) + reflectiveQuadToRelative(10f, 31.5f) + quadToRelative(-8f, 15f, -36f, 20f) + reflectiveQuadToRelative(-45f, -1f) + quadToRelative(-25f, -9f, -50.5f, -13.5f) + reflectiveQuadTo(480f, 240f) + quadToRelative(-133f, 0f, -226.5f, 93.5f) + reflectiveQuadTo(160f, 560f) + quadToRelative(0f, 42f, 11.5f, 83f) + reflectiveQuadToRelative(32.5f, 77f) + horizontalLineToRelative(552f) + quadToRelative(23f, -38f, 33.5f, -79f) + reflectiveQuadToRelative(10.5f, -85f) + quadToRelative(0f, -26f, -4.5f, -51f) + reflectiveQuadTo(782f, 456f) + quadToRelative(-6f, -17f, -2f, -33f) + reflectiveQuadToRelative(18f, -27f) + quadToRelative(13f, -10f, 28.5f, -6f) + reflectiveQuadToRelative(21.5f, 18f) + quadToRelative(15f, 35f, 23f, 71.5f) + reflectiveQuadToRelative(9f, 74.5f) + quadToRelative(1f, 57f, -13f, 109f) + reflectiveQuadToRelative(-41f, 99f) + quadToRelative(-11f, 18f, -30f, 28f) + reflectiveQuadToRelative(-40f, 10f) + lineTo(204f, 800f) + quadToRelative(-21f, 0f, -40f, -10f) + reflectiveQuadToRelative(-30f, -28f) + quadToRelative(-26f, -45f, -40f, -95.5f) + reflectiveQuadTo(80f, 560f) + quadToRelative(0f, -83f, 31.5f, -155.5f) + reflectiveQuadToRelative(86f, -127f) + quadTo(252f, 223f, 325f, 191.5f) + reflectiveQuadTo(480f, 160f) + close() + moveTo(487f, 473f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SplitAlt.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SplitAlt.kt new file mode 100644 index 0000000..08f7ede --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SplitAlt.kt @@ -0,0 +1,213 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SplitComplementary.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SplitComplementary.kt new file mode 100644 index 0000000..090e276 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SplitComplementary.kt @@ -0,0 +1,70 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Spray.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Spray.kt new file mode 100644 index 0000000..3fab283 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Spray.kt @@ -0,0 +1,136 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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(18.854f, 7.25f) + 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(12.854f, 6.25f) + 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(15.854f, 5.25f) + 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(18.854f, 4.25f) + 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(15.854f, 8.25f) + 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(18.854f, 10.25f) + 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) + reflectiveCurveToRelative(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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(11.146f, 7.75f) + verticalLineToRelative(-0.5f) + curveToRelative(0f, -0.276f, -0.224f, -0.5f, -0.5f, -0.5f) + horizontalLineToRelative(-0.5f) + verticalLineToRelative(-2f) + curveToRelative(0f, -0.552f, -0.448f, -1f, -1f, -1f) + horizontalLineToRelative(-1f) + curveToRelative(-0.552f, 0f, -1f, 0.448f, -1f, 1f) + verticalLineToRelative(2f) + horizontalLineToRelative(-0.5f) + curveToRelative(-0.276f, 0f, -0.5f, 0.224f, -0.5f, 0.5f) + verticalLineToRelative(0.5f) + curveToRelative(-1.105f, 0f, -2f, 0.895f, -2f, 2f) + verticalLineToRelative(10f) + curveToRelative(0f, 1.105f, 0.895f, 2f, 2f, 2f) + horizontalLineToRelative(5f) + curveToRelative(1.105f, 0f, 2f, -0.895f, 2f, -2f) + verticalLineToRelative(-10f) + curveToRelative(0f, -1.105f, -0.895f, -2f, -2f, -2f) + close() + moveTo(11.146f, 19.75f) + horizontalLineToRelative(-5f) + verticalLineToRelative(-10f) + horizontalLineToRelative(5f) + verticalLineToRelative(10f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Square.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Square.kt new file mode 100644 index 0000000..fcb916e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Square.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SquareFoot.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SquareFoot.kt new file mode 100644 index 0000000..0609a7a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SquareFoot.kt @@ -0,0 +1,55 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SquareHarmony.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SquareHarmony.kt new file mode 100644 index 0000000..b1a7eb4 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SquareHarmony.kt @@ -0,0 +1,81 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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.Black), 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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StackSticky.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StackSticky.kt new file mode 100644 index 0000000..4b9c6c1 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StackSticky.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StackStickyOff.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StackStickyOff.kt new file mode 100644 index 0000000..d174d83 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StackStickyOff.kt @@ -0,0 +1,98 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Stacks.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Stacks.kt new file mode 100644 index 0000000..d255b1d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Stacks.kt @@ -0,0 +1,304 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StampedLine.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StampedLine.kt new file mode 100644 index 0000000..8dc010b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StampedLine.kt @@ -0,0 +1,179 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Star.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Star.kt new file mode 100644 index 0000000..f0d1daf --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Star.kt @@ -0,0 +1,129 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Star: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Star", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 691f) + lineTo(314f, 791f) + quadToRelative(-11f, 7f, -23f, 6f) + reflectiveQuadToRelative(-21f, -8f) + quadToRelative(-9f, -7f, -14f, -17.5f) + reflectiveQuadToRelative(-2f, -23.5f) + lineToRelative(44f, -189f) + lineToRelative(-147f, -127f) + quadToRelative(-10f, -9f, -12.5f, -20.5f) + reflectiveQuadTo(140f, 389f) + quadToRelative(4f, -11f, 12f, -18f) + reflectiveQuadToRelative(22f, -9f) + lineToRelative(194f, -17f) + lineToRelative(75f, -178f) + quadToRelative(5f, -12f, 15.5f, -18f) + reflectiveQuadToRelative(21.5f, -6f) + quadToRelative(11f, 0f, 21.5f, 6f) + reflectiveQuadToRelative(15.5f, 18f) + lineToRelative(75f, 178f) + lineToRelative(194f, 17f) + quadToRelative(14f, 2f, 22f, 9f) + reflectiveQuadToRelative(12f, 18f) + quadToRelative(4f, 11f, 1.5f, 22.5f) + reflectiveQuadTo(809f, 432f) + lineTo(662f, 559f) + lineToRelative(44f, 189f) + quadToRelative(3f, 13f, -2f, 23.5f) + reflectiveQuadTo(690f, 789f) + quadToRelative(-9f, 7f, -21f, 8f) + reflectiveQuadToRelative(-23f, -6f) + lineTo(480f, 691f) + close() + } + }.build() +} + +val Icons.Outlined.Star: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Star", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(354f, 673f) + lineToRelative(126f, -76f) + lineToRelative(126f, 77f) + lineToRelative(-33f, -144f) + lineToRelative(111f, -96f) + lineToRelative(-146f, -13f) + lineToRelative(-58f, -136f) + lineToRelative(-58f, 135f) + lineToRelative(-146f, 13f) + lineToRelative(111f, 97f) + lineToRelative(-33f, 143f) + close() + moveTo(480f, 691f) + lineTo(314f, 791f) + quadToRelative(-11f, 7f, -23f, 6f) + reflectiveQuadToRelative(-21f, -8f) + quadToRelative(-9f, -7f, -14f, -17.5f) + reflectiveQuadToRelative(-2f, -23.5f) + lineToRelative(44f, -189f) + lineToRelative(-147f, -127f) + quadToRelative(-10f, -9f, -12.5f, -20.5f) + reflectiveQuadTo(140f, 389f) + quadToRelative(4f, -11f, 12f, -18f) + reflectiveQuadToRelative(22f, -9f) + lineToRelative(194f, -17f) + lineToRelative(75f, -178f) + quadToRelative(5f, -12f, 15.5f, -18f) + reflectiveQuadToRelative(21.5f, -6f) + quadToRelative(11f, 0f, 21.5f, 6f) + reflectiveQuadToRelative(15.5f, 18f) + lineToRelative(75f, 178f) + lineToRelative(194f, 17f) + quadToRelative(14f, 2f, 22f, 9f) + reflectiveQuadToRelative(12f, 18f) + quadToRelative(4f, 11f, 1.5f, 22.5f) + reflectiveQuadTo(809f, 432f) + lineTo(662f, 559f) + lineToRelative(44f, 189f) + quadToRelative(3f, 13f, -2f, 23.5f) + reflectiveQuadTo(690f, 789f) + quadToRelative(-9f, 7f, -21f, 8f) + reflectiveQuadToRelative(-23f, -6f) + lineTo(480f, 691f) + close() + moveTo(480f, 490f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StarSticky.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StarSticky.kt new file mode 100644 index 0000000..255f80b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StarSticky.kt @@ -0,0 +1,95 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Start.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Start.kt new file mode 100644 index 0000000..ac8f924 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Start.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Start: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Start", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(120f, 720f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(80f, 680f) + verticalLineToRelative(-400f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(160f, 280f) + verticalLineToRelative(400f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(120f, 720f) + close() + moveTo(727f, 520f) + lineTo(280f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(240f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(280f, 440f) + horizontalLineToRelative(447f) + lineTo(612f, 324f) + quadToRelative(-11f, -11f, -11.5f, -27.5f) + reflectiveQuadTo(612f, 268f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(184f, 184f) + quadToRelative(6f, 6f, 8.5f, 13f) + reflectiveQuadToRelative(2.5f, 15f) + quadToRelative(0f, 8f, -2.5f, 15f) + reflectiveQuadToRelative(-8.5f, 13f) + lineTo(668f, 692f) + quadToRelative(-11f, 11f, -27.5f, 11f) + reflectiveQuadTo(612f, 692f) + quadToRelative(-12f, -12f, -12f, -28.5f) + reflectiveQuadToRelative(12f, -28.5f) + lineToRelative(115f, -115f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Storage.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Storage.kt new file mode 100644 index 0000000..9f3f162 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Storage.kt @@ -0,0 +1,104 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Storage: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Storage", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(200f, 800f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 720f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 640f) + horizontalLineToRelative(560f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(840f, 720f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 800f) + lineTo(200f, 800f) + close() + moveTo(200f, 320f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 240f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 160f) + horizontalLineToRelative(560f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(840f, 240f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 320f) + lineTo(200f, 320f) + close() + moveTo(200f, 560f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 400f) + horizontalLineToRelative(560f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 560f) + lineTo(200f, 560f) + close() + moveTo(240f, 280f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(280f, 240f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(240f, 200f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(200f, 240f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(240f, 280f) + close() + moveTo(240f, 520f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(280f, 480f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(240f, 440f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(200f, 480f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(240f, 520f) + close() + moveTo(240f, 760f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(280f, 720f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(240f, 680f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(200f, 720f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(240f, 760f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Stream.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Stream.kt new file mode 100644 index 0000000..423a9fe --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Stream.kt @@ -0,0 +1,126 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Stream: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Stream", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 560f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(160f, 400f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(240f, 480f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(160f, 560f) + close() + moveTo(198f, 704f) + lineTo(316f, 586f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineTo(254f, 760f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + close() + moveTo(318f, 372f) + lineTo(200f, 254f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(118f, 118f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + close() + moveTo(480f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(400f, 800f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(480f, 720f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(560f, 800f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 240f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(400f, 160f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(560f, 160f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(480f, 240f) + close() + moveTo(586f, 316f) + lineTo(706f, 198f) + quadToRelative(11f, -11f, 27.5f, -11.5f) + reflectiveQuadTo(762f, 198f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineTo(643f, 373f) + quadToRelative(-12f, 12f, -28.5f, 12f) + reflectiveQuadTo(586f, 373f) + quadToRelative(-11f, -12f, -11.5f, -28.5f) + reflectiveQuadTo(586f, 316f) + close() + moveTo(706f, 760f) + lineTo(588f, 642f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(118f, 118f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + close() + moveTo(800f, 560f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(720f, 480f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(800f, 400f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 560f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Stylus.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Stylus.kt new file mode 100644 index 0000000..600cae9 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Stylus.kt @@ -0,0 +1,182 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Suffix.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Suffix.kt new file mode 100644 index 0000000..ed917b3 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Suffix.kt @@ -0,0 +1,78 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SupervisedUserCircle.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SupervisedUserCircle.kt new file mode 100644 index 0000000..5837a7c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SupervisedUserCircle.kt @@ -0,0 +1,114 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.SupervisedUserCircle: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.SupervisedUserCircle", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(412f, 792f) + quadToRelative(45f, -91f, 120f, -121.5f) + reflectiveQuadTo(660f, 640f) + quadToRelative(23f, 0f, 45f, 4f) + reflectiveQuadToRelative(43f, 10f) + quadToRelative(24f, -38f, 38f, -82f) + reflectiveQuadToRelative(14f, -92f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 45f, 11.5f, 86f) + reflectiveQuadToRelative(34.5f, 76f) + quadToRelative(41f, -20f, 85f, -31f) + reflectiveQuadToRelative(89f, -11f) + quadToRelative(32f, 0f, 61.5f, 5.5f) + reflectiveQuadTo(500f, 620f) + quadToRelative(-23f, 12f, -43.5f, 28f) + reflectiveQuadTo(418f, 682f) + quadToRelative(-12f, -2f, -20.5f, -2f) + lineTo(380f, 680f) + quadToRelative(-32f, 0f, -63.5f, 7f) + reflectiveQuadTo(256f, 708f) + quadToRelative(32f, 32f, 71.5f, 53.5f) + reflectiveQuadTo(412f, 792f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(380f, 540f) + quadToRelative(-58f, 0f, -99f, -41f) + reflectiveQuadToRelative(-41f, -99f) + quadToRelative(0f, -58f, 41f, -99f) + reflectiveQuadToRelative(99f, -41f) + quadToRelative(58f, 0f, 99f, 41f) + reflectiveQuadToRelative(41f, 99f) + quadToRelative(0f, 58f, -41f, 99f) + reflectiveQuadToRelative(-99f, 41f) + close() + moveTo(380f, 460f) + quadToRelative(25f, 0f, 42.5f, -17.5f) + reflectiveQuadTo(440f, 400f) + quadToRelative(0f, -25f, -17.5f, -42.5f) + reflectiveQuadTo(380f, 340f) + quadToRelative(-25f, 0f, -42.5f, 17.5f) + reflectiveQuadTo(320f, 400f) + quadToRelative(0f, 25f, 17.5f, 42.5f) + reflectiveQuadTo(380f, 460f) + close() + moveTo(660f, 580f) + quadToRelative(-42f, 0f, -71f, -29f) + reflectiveQuadToRelative(-29f, -71f) + quadToRelative(0f, -42f, 29f, -71f) + reflectiveQuadToRelative(71f, -29f) + quadToRelative(42f, 0f, 71f, 29f) + reflectiveQuadToRelative(29f, 71f) + quadToRelative(0f, 42f, -29f, 71f) + reflectiveQuadToRelative(-71f, 29f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Svg.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Svg.kt new file mode 100644 index 0000000..eceac67 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Svg.kt @@ -0,0 +1,97 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SwapHoriz.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SwapHoriz.kt new file mode 100644 index 0000000..e618873 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SwapHoriz.kt @@ -0,0 +1,86 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.SwapHoriz: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.SwapHoriz", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(233f, 640f) + lineToRelative(75f, 75f) + quadToRelative(11f, 11f, 11f, 27.5f) + reflectiveQuadTo(308f, 771f) + quadToRelative(-12f, 12f, -28.5f, 12f) + reflectiveQuadTo(251f, 771f) + lineTo(108f, 628f) + quadToRelative(-6f, -6f, -8.5f, -13f) + reflectiveQuadTo(97f, 600f) + 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(308f, 485f) + lineToRelative(-75f, 75f) + horizontalLineToRelative(247f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 600f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 640f) + lineTo(233f, 640f) + close() + moveTo(727f, 400f) + lineTo(480f, 400f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 360f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 320f) + 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(708f, 532f) + quadToRelative(-12f, 12f, -28f, 11.5f) + reflectiveQuadTo(652f, 531f) + quadToRelative(-11f, -12f, -11.5f, -28f) + reflectiveQuadToRelative(11.5f, -28f) + lineToRelative(75f, -75f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SwapVerticalCircle.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SwapVerticalCircle.kt new file mode 100644 index 0000000..7c3de14 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SwapVerticalCircle.kt @@ -0,0 +1,215 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Swatch.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Swatch.kt new file mode 100644 index 0000000..969d8d7 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Swatch.kt @@ -0,0 +1,72 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SwipeDown.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SwipeDown.kt new file mode 100644 index 0000000..6965ab7 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SwipeDown.kt @@ -0,0 +1,127 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.SwipeDown: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.SwipeDown", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(152f, 488f) + quadToRelative(-6f, -27f, -9f, -54f) + reflectiveQuadToRelative(-3f, -54f) + quadToRelative(0f, -74f, 22f, -144f) + reflectiveQuadToRelative(64f, -130f) + quadToRelative(8f, -11f, 20f, -12.5f) + reflectiveQuadToRelative(21f, 7.5f) + quadToRelative(9f, 9f, 10f, 22.5f) + reflectiveQuadToRelative(-7f, 24.5f) + quadToRelative(-35f, 52f, -52.5f, 110.5f) + reflectiveQuadTo(200f, 380f) + quadToRelative(0f, 26f, 3f, 51.5f) + reflectiveQuadToRelative(10f, 50.5f) + lineToRelative(44f, -43f) + quadToRelative(9f, -8f, 21f, -8.5f) + reflectiveQuadToRelative(21f, 8.5f) + quadToRelative(9f, 9f, 9f, 21f) + reflectiveQuadToRelative(-9f, 21f) + lineToRelative(-91f, 91f) + quadToRelative(-12f, 12f, -28f, 12f) + reflectiveQuadToRelative(-28f, -12f) + lineToRelative(-91f, -91f) + quadToRelative(-9f, -9f, -9f, -21f) + reflectiveQuadToRelative(9f, -21f) + quadToRelative(9f, -9f, 21f, -9f) + reflectiveQuadToRelative(21f, 9f) + lineToRelative(49f, 49f) + close() + moveTo(658f, 833f) + quadToRelative(-23f, 8f, -46.5f, 7.5f) + reflectiveQuadTo(566f, 829f) + lineTo(340f, 724f) + quadToRelative(-15f, -7f, -21f, -22.5f) + reflectiveQuadToRelative(1f, -30.5f) + lineToRelative(2f, -4f) + quadToRelative(10f, -20f, 28f, -32.5f) + reflectiveQuadToRelative(40f, -14.5f) + lineToRelative(68f, -5f) + lineToRelative(-112f, -307f) + quadToRelative(-6f, -16f, 1f, -30.5f) + reflectiveQuadToRelative(23f, -20.5f) + quadToRelative(16f, -6f, 30.5f, 1f) + reflectiveQuadToRelative(20.5f, 23f) + lineToRelative(130f, 357f) + quadToRelative(7f, 19f, -4f, 35.5f) + reflectiveQuadTo(516f, 692f) + lineToRelative(-47f, 3f) + lineToRelative(131f, 61f) + quadToRelative(7f, 3f, 15f, 3.5f) + reflectiveQuadToRelative(15f, -1.5f) + lineToRelative(157f, -57f) + quadToRelative(31f, -11f, 45f, -41.5f) + reflectiveQuadToRelative(3f, -61.5f) + lineToRelative(-55f, -150f) + quadToRelative(-6f, -16f, 1f, -30.5f) + reflectiveQuadToRelative(23f, -20.5f) + quadToRelative(16f, -6f, 30.5f, 1f) + reflectiveQuadToRelative(20.5f, 23f) + lineToRelative(55f, 150f) + quadToRelative(23f, 63f, -4.5f, 122.5f) + reflectiveQuadTo(815f, 776f) + lineToRelative(-157f, 57f) + close() + moveTo(568.5f, 367f) + quadToRelative(14.5f, 7f, 20.5f, 23f) + lineToRelative(41f, 112f) + quadToRelative(6f, 16f, -1f, 31f) + reflectiveQuadToRelative(-23f, 21f) + quadToRelative(-16f, 6f, -31f, -1f) + reflectiveQuadToRelative(-21f, -23f) + lineToRelative(-40f, -113f) + quadToRelative(-6f, -16f, 1f, -30.5f) + reflectiveQuadToRelative(23f, -20.5f) + quadToRelative(16f, -6f, 30.5f, 1f) + close() + moveTo(694.5f, 364f) + quadToRelative(14.5f, 7f, 20.5f, 23f) + lineToRelative(27f, 75f) + quadToRelative(6f, 16f, -0.5f, 30.5f) + reflectiveQuadTo(719f, 513f) + quadToRelative(-16f, 6f, -31f, -1f) + reflectiveQuadToRelative(-21f, -23f) + lineToRelative(-27f, -75f) + quadToRelative(-6f, -16f, 1f, -30.5f) + reflectiveQuadToRelative(23f, -20.5f) + quadToRelative(16f, -6f, 30.5f, 1f) + close() + moveTo(679f, 605f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SwipeVertical.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SwipeVertical.kt new file mode 100644 index 0000000..26254cc --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SwipeVertical.kt @@ -0,0 +1,137 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.SwipeVertical: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.SwipeVertical", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(161f, 820f) + quadToRelative(-59f, -72f, -90f, -159f) + reflectiveQuadTo(40f, 480f) + quadToRelative(0f, -94f, 31f, -181f) + reflectiveQuadToRelative(90f, -159f) + horizontalLineToRelative(-51f) + quadToRelative(-13f, 0f, -21.5f, -8.5f) + reflectiveQuadTo(80f, 110f) + quadToRelative(0f, -13f, 8.5f, -21.5f) + reflectiveQuadTo(110f, 80f) + horizontalLineToRelative(130f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(280f, 120f) + verticalLineToRelative(130f) + quadToRelative(0f, 13f, -8.5f, 21.5f) + reflectiveQuadTo(250f, 280f) + quadToRelative(-13f, 0f, -21.5f, -8.5f) + reflectiveQuadTo(220f, 250f) + verticalLineToRelative(-86f) + quadToRelative(-58f, 66f, -89f, 147f) + reflectiveQuadToRelative(-31f, 169f) + quadToRelative(0f, 88f, 31f, 169f) + reflectiveQuadToRelative(89f, 147f) + verticalLineToRelative(-86f) + quadToRelative(0f, -13f, 8.5f, -21.5f) + reflectiveQuadTo(250f, 680f) + quadToRelative(13f, 0f, 21.5f, 8.5f) + reflectiveQuadTo(280f, 710f) + verticalLineToRelative(130f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 880f) + lineTo(110f, 880f) + quadToRelative(-13f, 0f, -21.5f, -8.5f) + reflectiveQuadTo(80f, 850f) + quadToRelative(0f, -13f, 8.5f, -21.5f) + reflectiveQuadTo(110f, 820f) + horizontalLineToRelative(51f) + close() + moveTo(658f, 833f) + quadToRelative(-23f, 8f, -46.5f, 7.5f) + reflectiveQuadTo(566f, 829f) + lineTo(340f, 724f) + quadToRelative(-15f, -7f, -21f, -22.5f) + reflectiveQuadToRelative(1f, -30.5f) + lineToRelative(2f, -4f) + quadToRelative(10f, -20f, 28f, -32.5f) + reflectiveQuadToRelative(40f, -14.5f) + lineToRelative(68f, -5f) + lineToRelative(-112f, -307f) + quadToRelative(-6f, -16f, 1f, -30.5f) + reflectiveQuadToRelative(23f, -20.5f) + quadToRelative(16f, -6f, 30.5f, 1f) + reflectiveQuadToRelative(20.5f, 23f) + lineToRelative(130f, 357f) + quadToRelative(7f, 19f, -4f, 35.5f) + reflectiveQuadTo(516f, 692f) + lineToRelative(-47f, 3f) + lineToRelative(131f, 61f) + quadToRelative(7f, 3f, 15f, 3.5f) + reflectiveQuadToRelative(15f, -1.5f) + lineToRelative(157f, -57f) + quadToRelative(31f, -11f, 45f, -41.5f) + reflectiveQuadToRelative(3f, -61.5f) + lineToRelative(-55f, -150f) + quadToRelative(-6f, -16f, 1f, -30.5f) + reflectiveQuadToRelative(23f, -20.5f) + quadToRelative(16f, -6f, 30.5f, 1f) + reflectiveQuadToRelative(20.5f, 23f) + lineToRelative(55f, 150f) + quadToRelative(23f, 63f, -4.5f, 122.5f) + reflectiveQuadTo(815f, 776f) + lineToRelative(-157f, 57f) + close() + moveTo(514f, 417f) + quadToRelative(-6f, -16f, 1f, -30.5f) + reflectiveQuadToRelative(23f, -20.5f) + quadToRelative(16f, -6f, 30.5f, 1f) + reflectiveQuadToRelative(20.5f, 23f) + lineToRelative(41f, 112f) + quadToRelative(6f, 16f, -1f, 31f) + reflectiveQuadToRelative(-23f, 21f) + quadToRelative(-16f, 6f, -31f, -1f) + reflectiveQuadToRelative(-21f, -23f) + lineToRelative(-40f, -113f) + close() + moveTo(640f, 414f) + quadToRelative(-6f, -16f, 1f, -30.5f) + reflectiveQuadToRelative(23f, -20.5f) + quadToRelative(16f, -6f, 30.5f, 1f) + reflectiveQuadToRelative(20.5f, 23f) + lineToRelative(27f, 75f) + quadToRelative(6f, 16f, -0.5f, 30.5f) + reflectiveQuadTo(719f, 513f) + quadToRelative(-16f, 6f, -31f, -1f) + reflectiveQuadToRelative(-21f, -23f) + lineToRelative(-27f, -75f) + close() + moveTo(689f, 605f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Symbol.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Symbol.kt new file mode 100644 index 0000000..b4fa168 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Symbol.kt @@ -0,0 +1,106 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SyncArrowDown.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SyncArrowDown.kt new file mode 100644 index 0000000..75740c3 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SyncArrowDown.kt @@ -0,0 +1,116 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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(120f, 780f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(80f, 740f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 700f) + horizontalLineToRelative(17f) + quadToRelative(-47f, -42f, -72f, -99.5f) + reflectiveQuadTo(40f, 480f) + quadToRelative(0f, -91f, 49.5f, -165.5f) + reflectiveQuadTo(220f, 205f) + quadToRelative(15f, -7f, 29.5f, 1f) + reflectiveQuadToRelative(19.5f, 24f) + quadToRelative(5f, 16f, -3.5f, 30f) + reflectiveQuadTo(242f, 282f) + quadToRelative(-56f, 27f, -89f, 80f) + reflectiveQuadToRelative(-33f, 117f) + quadToRelative(0f, 50f, 21f, 93.5f) + reflectiveQuadToRelative(59f, 75.5f) + verticalLineToRelative(-28f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(240f, 580f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(280f, 620f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 780f) + lineTo(120f, 780f) + close() + moveTo(452f, 758f) + quadToRelative(-14f, 5f, -25f, -4f) + reflectiveQuadToRelative(-16f, -24f) + quadToRelative(-5f, -15f, 0f, -29f) + reflectiveQuadToRelative(19f, -20f) + quadToRelative(59f, -26f, 94.5f, -80f) + reflectiveQuadTo(560f, 481f) + quadToRelative(0f, -50f, -21f, -93.5f) + reflectiveQuadTo(480f, 312f) + verticalLineToRelative(28f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(440f, 380f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(400f, 340f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(440f, 180f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(600f, 220f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(560f, 260f) + horizontalLineToRelative(-17f) + quadToRelative(47f, 42f, 72f, 99.5f) + reflectiveQuadTo(640f, 480f) + quadToRelative(0f, 94f, -51.5f, 169f) + reflectiveQuadTo(452f, 758f) + close() + moveTo(752f, 772f) + lineTo(660f, 680f) + quadToRelative(-11f, -12f, -11f, -28.5f) + reflectiveQuadToRelative(12f, -27.5f) + quadToRelative(12f, -11f, 28.5f, -11.5f) + reflectiveQuadTo(717f, 624f) + lineToRelative(23f, 23f) + verticalLineToRelative(-447f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(780f, 160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(820f, 200f) + verticalLineToRelative(448f) + lineToRelative(24f, -24f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineToRelative(-92f, 92f) + quadToRelative(-12f, 12f, -28f, 12f) + reflectiveQuadToRelative(-28f, -12f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TableChart.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TableChart.kt new file mode 100644 index 0000000..5832ddb --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TableChart.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.TableChart: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.TableChart", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(760f, 840f) + lineTo(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) + close() + moveTo(200f, 320f) + horizontalLineToRelative(560f) + verticalLineToRelative(-120f) + lineTo(200f, 200f) + verticalLineToRelative(120f) + close() + moveTo(300f, 400f) + lineTo(200f, 400f) + verticalLineToRelative(360f) + horizontalLineToRelative(100f) + verticalLineToRelative(-360f) + close() + moveTo(660f, 400f) + verticalLineToRelative(360f) + horizontalLineToRelative(100f) + verticalLineToRelative(-360f) + lineTo(660f, 400f) + close() + moveTo(580f, 400f) + lineTo(380f, 400f) + verticalLineToRelative(360f) + horizontalLineToRelative(200f) + verticalLineToRelative(-360f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TableEye.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TableEye.kt new file mode 100644 index 0000000..49828aa --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TableEye.kt @@ -0,0 +1,99 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TableRows.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TableRows.kt new file mode 100644 index 0000000..1875433 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TableRows.kt @@ -0,0 +1,125 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.TableRows: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.TableRows", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(760f, 760f) + verticalLineToRelative(-120f) + lineTo(200f, 640f) + verticalLineToRelative(120f) + horizontalLineToRelative(560f) + close() + moveTo(760f, 560f) + verticalLineToRelative(-160f) + lineTo(200f, 400f) + verticalLineToRelative(160f) + horizontalLineToRelative(560f) + close() + moveTo(760f, 320f) + verticalLineToRelative(-120f) + lineTo(200f, 200f) + verticalLineToRelative(120f) + horizontalLineToRelative(560f) + 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(560f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 840f) + lineTo(200f, 840f) + close() + } + }.build() +} + +val Icons.Rounded.TableRows: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.TableRows", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 800f) + verticalLineToRelative(-106f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 654f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 694f) + verticalLineToRelative(106f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + lineTo(160f, 840f) + close() + moveTo(160f, 574f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 534f) + verticalLineToRelative(-109f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 385f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 425f) + verticalLineToRelative(109f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 574f) + lineTo(160f, 574f) + close() + moveTo(160f, 305f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 265f) + verticalLineToRelative(-105f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + horizontalLineToRelative(640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 160f) + verticalLineToRelative(105f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 305f) + lineTo(160f, 305f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TagText.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TagText.kt new file mode 100644 index 0000000..2e05ba8 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TagText.kt @@ -0,0 +1,166 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Telegram.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Telegram.kt new file mode 100644 index 0000000..117a209 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Telegram.kt @@ -0,0 +1,58 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Telegram: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Telegram", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(18.687f, 5.371f) + curveToRelative(0.123f, -0.002f, 0.394f, 0.028f, 0.571f, 0.172f) + curveToRelative(0.117f, 0.102f, 0.192f, 0.244f, 0.21f, 0.399f) + curveToRelative(0.02f, 0.114f, 0.044f, 0.376f, 0.025f, 0.579f) + curveToRelative(-0.221f, 2.33f, -1.181f, 7.983f, -1.67f, 10.592f) + curveToRelative(-0.206f, 1.105f, -0.613f, 1.475f, -1.007f, 1.51f) + curveToRelative(-0.855f, 0.08f, -1.504f, -0.565f, -2.333f, -1.107f) + curveToRelative(-1.296f, -0.851f, -2.029f, -1.38f, -3.288f, -2.21f) + curveToRelative(-1.455f, -0.958f, -0.512f, -1.486f, 0.317f, -2.345f) + curveToRelative(0.217f, -0.226f, 3.986f, -3.655f, 4.06f, -3.966f) + curveToRelative(0.009f, -0.039f, 0.017f, -0.184f, -0.069f, -0.26f) + reflectiveCurveToRelative(-0.214f, -0.05f, -0.306f, -0.029f) + curveToRelative(-0.13f, 0.029f, -2.201f, 1.4f, -6.214f, 4.107f) + curveToRelative(-0.589f, 0.405f, -1.121f, 0.602f, -1.599f, 0.589f) + curveToRelative(-0.525f, -0.01f, -1.537f, -0.296f, -2.29f, -0.54f) + curveToRelative(-0.923f, -0.301f, -1.656f, -0.459f, -1.592f, -0.969f) + curveToRelative(0.033f, -0.265f, 0.399f, -0.537f, 1.096f, -0.814f) + curveToRelative(4.295f, -1.871f, 7.158f, -3.105f, 8.592f, -3.7f) + curveToRelative(4.091f, -1.702f, 4.942f, -1.998f, 5.495f, -2.007f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TelevisionAmbientLight.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TelevisionAmbientLight.kt new file mode 100644 index 0000000..eab5910 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TelevisionAmbientLight.kt @@ -0,0 +1,107 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Tetradic.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Tetradic.kt new file mode 100644 index 0000000..1dc29e5 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Tetradic.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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.Black), 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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextFields.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextFields.kt new file mode 100644 index 0000000..5d5b055 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextFields.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.TextFields: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.TextFields", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(340f, 800f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(280f, 740f) + verticalLineToRelative(-460f) + lineTo(140f, 280f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(80f, 220f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(140f, 160f) + horizontalLineToRelative(400f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(600f, 220f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(540f, 280f) + lineTo(400f, 280f) + verticalLineToRelative(460f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(340f, 800f) + close() + moveTo(700f, 800f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(640f, 740f) + verticalLineToRelative(-260f) + horizontalLineToRelative(-60f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(520f, 420f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(580f, 360f) + horizontalLineToRelative(240f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(880f, 420f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(820f, 480f) + horizontalLineToRelative(-60f) + verticalLineToRelative(260f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(700f, 800f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextFormat.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextFormat.kt new file mode 100644 index 0000000..0460456 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextFormat.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.TextFormat: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.TextFormat", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(240f, 760f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(200f, 720f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(240f, 680f) + horizontalLineToRelative(480f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(760f, 720f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(720f, 760f) + lineTo(240f, 760f) + close() + moveTo(294f, 552f) + lineTo(431f, 184f) + quadToRelative(4f, -11f, 13.5f, -17.5f) + reflectiveQuadTo(466f, 160f) + horizontalLineToRelative(28f) + quadToRelative(12f, 0f, 21.5f, 6.5f) + reflectiveQuadTo(529f, 184f) + lineToRelative(137f, 369f) + quadToRelative(6f, 17f, -4f, 32f) + reflectiveQuadToRelative(-28f, 15f) + quadToRelative(-11f, 0f, -20.5f, -6.5f) + reflectiveQuadTo(600f, 576f) + lineToRelative(-30f, -88f) + lineTo(392f, 488f) + lineToRelative(-32f, 89f) + quadToRelative(-4f, 11f, -13f, 17f) + reflectiveQuadToRelative(-20f, 6f) + quadToRelative(-19f, 0f, -29.5f, -15.5f) + reflectiveQuadTo(294f, 552f) + close() + moveTo(414f, 424f) + horizontalLineToRelative(132f) + lineToRelative(-64f, -182f) + horizontalLineToRelative(-4f) + lineToRelative(-64f, 182f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextRotationAngleup.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextRotationAngleup.kt new file mode 100644 index 0000000..7444c3a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextRotationAngleup.kt @@ -0,0 +1,86 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.TextRotationAngleup: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.TextRotationAngleup", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(761f, 496f) + lineTo(417f, 840f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + lineToRelative(344f, -344f) + horizontalLineToRelative(-24f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(641f, 400f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(681f, 360f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(841f, 400f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(801f, 560f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(761f, 520f) + verticalLineToRelative(-24f) + close() + moveTo(333f, 484f) + lineTo(372f, 568f) + quadToRelative(5f, 10f, 3.5f, 20.5f) + reflectiveQuadTo(366f, 607f) + quadToRelative(-14f, 14f, -32f, 10.5f) + reflectiveQuadTo(308f, 597f) + lineTo(146f, 239f) + quadToRelative(-5f, -11f, -3f, -22f) + reflectiveQuadToRelative(10f, -19f) + lineToRelative(20f, -20f) + quadToRelative(8f, -8f, 19f, -10f) + reflectiveQuadToRelative(22f, 3f) + lineToRelative(359f, 164f) + quadToRelative(17f, 8f, 20f, 26f) + reflectiveQuadToRelative(-10f, 31f) + quadToRelative(-8f, 8f, -19f, 10f) + reflectiveQuadToRelative(-22f, -3f) + lineToRelative(-83f, -41f) + lineToRelative(-126f, 126f) + close() + moveTo(303f, 422f) + lineTo(397f, 330f) + lineTo(223f, 246f) + lineTo(221f, 248f) + lineTo(303f, 422f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextSearch.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextSearch.kt new file mode 100644 index 0000000..f3fe4a0 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextSearch.kt @@ -0,0 +1,201 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.TextSearch: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.TextSearch", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(120f, 760f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(80f, 720f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 680f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(480f, 720f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(440f, 760f) + lineTo(120f, 760f) + close() + moveTo(120f, 560f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(80f, 520f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 480f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(280f, 520f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 560f) + lineTo(120f, 560f) + close() + moveTo(120f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(80f, 320f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 280f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(280f, 320f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(240f, 360f) + lineTo(120f, 360f) + close() + moveTo(560f, 640f) + quadToRelative(-83f, 0f, -141.5f, -58.5f) + reflectiveQuadTo(360f, 440f) + quadToRelative(0f, -83f, 58.5f, -141.5f) + reflectiveQuadTo(560f, 240f) + quadToRelative(83f, 0f, 141.5f, 58.5f) + reflectiveQuadTo(760f, 440f) + quadToRelative(0f, 29f, -8.5f, 57.5f) + reflectiveQuadTo(726f, 550f) + lineToRelative(126f, 126f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(670f, 606f) + quadToRelative(-24f, 17f, -52.5f, 25.5f) + reflectiveQuadTo(560f, 640f) + close() + moveTo(560f, 560f) + quadToRelative(50f, 0f, 85f, -35f) + reflectiveQuadToRelative(35f, -85f) + quadToRelative(0f, -50f, -35f, -85f) + reflectiveQuadToRelative(-85f, -35f) + quadToRelative(-50f, 0f, -85f, 35f) + reflectiveQuadToRelative(-35f, 85f) + quadToRelative(0f, 50f, 35f, 85f) + reflectiveQuadToRelative(85f, 35f) + close() + } + }.build() +} + +val Icons.TwoTone.TextSearch: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.TextSearch", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color(0xFF1D1D1B))) { + moveTo(3.246f, 18.573f) + 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(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) + horizontalLineTo(3.246f) + close() + } + path(fill = SolidColor(Color(0xFF1D1D1B))) { + moveTo(3.246f, 13.573f) + 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(3f) + 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(-3f) + close() + } + path(fill = SolidColor(Color(0xFF1D1D1B))) { + moveTo(3.246f, 8.573f) + 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) + 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() + } + path(fill = SolidColor(Color(0xFF1D1D1B))) { + moveTo(21.546f, 16.473f) + lineToRelative(-3.15f, -3.15f) + curveToRelative(0.283f, -0.4f, 0.496f, -0.838f, 0.637f, -1.313f) + curveToRelative(0.142f, -0.475f, 0.213f, -0.954f, 0.213f, -1.438f) + curveToRelative(0f, -1.383f, -0.487f, -2.563f, -1.463f, -3.538f) + curveToRelative(-0.975f, -0.975f, -2.154f, -1.462f, -3.537f, -1.462f) + curveToRelative(-1.383f, 0f, -2.563f, 0.487f, -3.537f, 1.462f) + curveToRelative(-0.975f, 0.975f, -1.463f, 2.154f, -1.463f, 3.538f) + curveToRelative(0f, 1.383f, 0.487f, 2.563f, 1.463f, 3.537f) + curveToRelative(0.975f, 0.975f, 2.154f, 1.463f, 3.537f, 1.463f) + curveToRelative(0.483f, 0f, 0.963f, -0.071f, 1.438f, -0.213f) + reflectiveCurveToRelative(0.912f, -0.354f, 1.313f, -0.638f) + lineToRelative(3.15f, 3.15f) + 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) + close() + moveTo(16.371f, 12.698f) + curveToRelative(-0.583f, 0.583f, -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) + reflectiveCurveToRelative(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) + reflectiveCurveToRelative(-0.292f, 1.542f, -0.875f, 2.125f) + close() + } + path( + fill = SolidColor(Color(0xFF1D1D1B)), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(14.246f, 13.573f) + curveToRelative(0.833f, 0f, 1.542f, -0.292f, 2.125f, -0.875f) + curveToRelative(0.583f, -0.583f, 0.875f, -1.292f, 0.875f, -2.125f) + reflectiveCurveToRelative(-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) + reflectiveCurveToRelative(0.292f, 1.542f, 0.875f, 2.125f) + curveToRelative(0.583f, 0.583f, 1.292f, 0.875f, 2.125f, 0.875f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextSticky.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextSticky.kt new file mode 100644 index 0000000..b3be15b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextSticky.kt @@ -0,0 +1,90 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Texture.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Texture.kt new file mode 100644 index 0000000..c5c29da --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Texture.kt @@ -0,0 +1,96 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Texture: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Texture", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(439f, 828f) + quadToRelative(-11f, -11f, -12.5f, -26.5f) + reflectiveQuadTo(439f, 772f) + lineToRelative(333f, -333f) + quadToRelative(14f, -14f, 29.5f, -12.5f) + reflectiveQuadTo(828f, 439f) + quadToRelative(13f, 13f, 12f, 29f) + reflectiveQuadToRelative(-13f, 28f) + lineTo(495f, 828f) + quadToRelative(-12f, 12f, -28f, 12f) + reflectiveQuadToRelative(-28f, -12f) + close() + moveTo(728f, 840f) + quadToRelative(-14f, 0f, -19f, -12f) + reflectiveQuadToRelative(5f, -22f) + lineToRelative(92f, -92f) + quadToRelative(10f, -10f, 22f, -5f) + reflectiveQuadToRelative(12f, 19f) + verticalLineToRelative(72f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 840f) + horizontalLineToRelative(-72f) + close() + moveTo(131f, 829f) + quadToRelative(-11f, -11f, -12f, -27f) + reflectiveQuadToRelative(13f, -30f) + lineToRelative(641f, -641f) + quadToRelative(15f, -15f, 31f, -13f) + reflectiveQuadToRelative(27f, 13f) + quadToRelative(11f, 11f, 12f, 27f) + reflectiveQuadToRelative(-14f, 30f) + lineTo(187f, 829f) + quadToRelative(-14f, 14f, -29.5f, 12.5f) + reflectiveQuadTo(131f, 829f) + close() + moveTo(131f, 521f) + quadToRelative(-11f, -11f, -12f, -27f) + reflectiveQuadToRelative(13f, -30f) + lineToRelative(332f, -332f) + quadToRelative(14f, -14f, 29.5f, -12.5f) + reflectiveQuadTo(520f, 132f) + quadToRelative(11f, 11f, 12.5f, 26.5f) + reflectiveQuadTo(520f, 188f) + lineTo(187f, 521f) + quadToRelative(-14f, 14f, -29.5f, 12.5f) + reflectiveQuadTo(131f, 521f) + close() + moveTo(120f, 232f) + verticalLineToRelative(-72f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 120f) + horizontalLineToRelative(72f) + quadToRelative(14f, 0f, 19f, 12f) + reflectiveQuadToRelative(-5f, 22f) + lineToRelative(-92f, 92f) + quadToRelative(-10f, 10f, -22f, 5f) + reflectiveQuadToRelative(-12f, -19f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Timelapse.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Timelapse.kt new file mode 100644 index 0000000..647af5c --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Timelapse.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Timelapse: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Timelapse", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 720f) + quadToRelative(100f, 0f, 170f, -70f) + reflectiveQuadToRelative(70f, -170f) + quadToRelative(0f, -87f, -55.5f, -153f) + reflectiveQuadTo(524f, 244f) + quadToRelative(-18f, -2f, -31f, 10f) + reflectiveQuadToRelative(-13f, 30f) + verticalLineToRelative(196f) + lineTo(342f, 618f) + quadToRelative(-13f, 13f, -12f, 31f) + reflectiveQuadToRelative(15f, 29f) + quadToRelative(29f, 23f, 64f, 32.5f) + reflectiveQuadToRelative(71f, 9.5f) + close() + moveTo(480f, 880f) + quadToRelative(-83f, 0f, -156f, -31.5f) + reflectiveQuadTo(197f, 763f) + quadToRelative(-54f, -54f, -85.5f, -127f) + reflectiveQuadTo(80f, 480f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 197f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 80f) + quadToRelative(83f, 0f, 156f, 31.5f) + reflectiveQuadTo(763f, 197f) + quadToRelative(54f, 54f, 85.5f, 127f) + reflectiveQuadTo(880f, 480f) + quadToRelative(0f, 83f, -31.5f, 156f) + reflectiveQuadTo(763f, 763f) + quadToRelative(-54f, 54f, -127f, 85.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(134f, 0f, 227f, -93f) + reflectiveQuadToRelative(93f, -227f) + quadToRelative(0f, -134f, -93f, -227f) + reflectiveQuadToRelative(-227f, -93f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 134f, 93f, 227f) + reflectiveQuadToRelative(227f, 93f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Timer.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Timer.kt new file mode 100644 index 0000000..164fa94 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Timer.kt @@ -0,0 +1,98 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Timer: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Timer", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(400f, 120f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(360f, 80f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(400f, 40f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(600f, 80f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(560f, 120f) + lineTo(400f, 120f) + close() + moveTo(480f, 560f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 520f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 320f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 360f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 560f) + close() + moveTo(480f, 880f) + quadToRelative(-74f, 0f, -139.5f, -28.5f) + reflectiveQuadTo(226f, 774f) + quadToRelative(-49f, -49f, -77.5f, -114.5f) + reflectiveQuadTo(120f, 520f) + quadToRelative(0f, -74f, 28.5f, -139.5f) + reflectiveQuadTo(226f, 266f) + quadToRelative(49f, -49f, 114.5f, -77.5f) + reflectiveQuadTo(480f, 160f) + quadToRelative(62f, 0f, 119f, 20f) + reflectiveQuadToRelative(107f, 58f) + lineToRelative(28f, -28f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineToRelative(-28f, 28f) + quadToRelative(38f, 50f, 58f, 107f) + reflectiveQuadToRelative(20f, 119f) + quadToRelative(0f, 74f, -28.5f, 139.5f) + reflectiveQuadTo(734f, 774f) + quadToRelative(-49f, 49f, -114.5f, 77.5f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(116f, 0f, 198f, -82f) + reflectiveQuadToRelative(82f, -198f) + quadToRelative(0f, -116f, -82f, -198f) + reflectiveQuadToRelative(-198f, -82f) + quadToRelative(-116f, 0f, -198f, 82f) + reflectiveQuadToRelative(-82f, 198f) + quadToRelative(0f, 116f, 82f, 198f) + reflectiveQuadToRelative(198f, 82f) + close() + moveTo(480f, 520f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Title.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Title.kt new file mode 100644 index 0000000..ea6b250 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Title.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Title: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Title", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(420f, 280f) + lineTo(260f, 280f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(200f, 220f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(260f, 160f) + horizontalLineToRelative(440f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(760f, 220f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(700f, 280f) + lineTo(540f, 280f) + verticalLineToRelative(460f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(480f, 800f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(420f, 740f) + verticalLineToRelative(-460f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ToggleOff.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ToggleOff.kt new file mode 100644 index 0000000..0a1aac7 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ToggleOff.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ToggleOff: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.ToggleOff", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(280f, 720f) + quadToRelative(-100f, 0f, -170f, -70f) + reflectiveQuadTo(40f, 480f) + quadToRelative(0f, -100f, 70f, -170f) + reflectiveQuadToRelative(170f, -70f) + horizontalLineToRelative(400f) + quadToRelative(100f, 0f, 170f, 70f) + reflectiveQuadToRelative(70f, 170f) + quadToRelative(0f, 100f, -70f, 170f) + reflectiveQuadToRelative(-170f, 70f) + lineTo(280f, 720f) + close() + moveTo(280f, 640f) + horizontalLineToRelative(400f) + quadToRelative(66f, 0f, 113f, -47f) + reflectiveQuadToRelative(47f, -113f) + quadToRelative(0f, -66f, -47f, -113f) + reflectiveQuadToRelative(-113f, -47f) + lineTo(280f, 320f) + quadToRelative(-66f, 0f, -113f, 47f) + reflectiveQuadToRelative(-47f, 113f) + quadToRelative(0f, 66f, 47f, 113f) + reflectiveQuadToRelative(113f, 47f) + close() + moveTo(280f, 600f) + quadToRelative(50f, 0f, 85f, -35f) + reflectiveQuadToRelative(35f, -85f) + quadToRelative(0f, -50f, -35f, -85f) + reflectiveQuadToRelative(-85f, -35f) + quadToRelative(-50f, 0f, -85f, 35f) + reflectiveQuadToRelative(-35f, 85f) + quadToRelative(0f, 50f, 35f, 85f) + reflectiveQuadToRelative(85f, 35f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ToggleOn.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ToggleOn.kt new file mode 100644 index 0000000..077bd3d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ToggleOn.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ToggleOn: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.ToggleOn", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(280f, 720f) + quadToRelative(-100f, 0f, -170f, -70f) + reflectiveQuadTo(40f, 480f) + quadToRelative(0f, -100f, 70f, -170f) + reflectiveQuadToRelative(170f, -70f) + horizontalLineToRelative(400f) + quadToRelative(100f, 0f, 170f, 70f) + reflectiveQuadToRelative(70f, 170f) + quadToRelative(0f, 100f, -70f, 170f) + reflectiveQuadToRelative(-170f, 70f) + lineTo(280f, 720f) + close() + moveTo(280f, 640f) + horizontalLineToRelative(400f) + quadToRelative(66f, 0f, 113f, -47f) + reflectiveQuadToRelative(47f, -113f) + quadToRelative(0f, -66f, -47f, -113f) + reflectiveQuadToRelative(-113f, -47f) + lineTo(280f, 320f) + quadToRelative(-66f, 0f, -113f, 47f) + reflectiveQuadToRelative(-47f, 113f) + quadToRelative(0f, 66f, 47f, 113f) + reflectiveQuadToRelative(113f, 47f) + close() + moveTo(680f, 600f) + quadToRelative(50f, 0f, 85f, -35f) + reflectiveQuadToRelative(35f, -85f) + quadToRelative(0f, -50f, -35f, -85f) + reflectiveQuadToRelative(-85f, -35f) + quadToRelative(-50f, 0f, -85f, 35f) + reflectiveQuadToRelative(-35f, 85f) + quadToRelative(0f, 50f, 35f, 85f) + reflectiveQuadToRelative(85f, 35f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Ton.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Ton.kt new file mode 100644 index 0000000..25c7157 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Ton.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Tonality.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Tonality.kt new file mode 100644 index 0000000..e7e6fce --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Tonality.kt @@ -0,0 +1,163 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TopLeft.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TopLeft.kt new file mode 100644 index 0000000..b0ba09d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TopLeft.kt @@ -0,0 +1,195 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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(11.288f, 12.712f) + 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) + 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(19.288f, 8.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) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(19.288f, 12.712f) + 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) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(3.287f, 16.712f) + 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) + reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(19.288f, 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) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(3.287f, 20.712f) + 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) + 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(7.287f, 20.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) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(11.288f, 20.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(15.288f, 20.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) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(19.288f, 20.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) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(15.097f, 4.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) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(19.097f, 4.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) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(12f, 3f) + horizontalLineTo(4f) + curveToRelative(-0.552f, 0f, -1f, 0.448f, -1f, 1f) + verticalLineToRelative(8f) + 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(-7f) + horizontalLineToRelative(7f) + curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) + reflectiveCurveToRelative(-0.448f, -1f, -1f, -1f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Topic.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Topic.kt new file mode 100644 index 0000000..913d2f1 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Topic.kt @@ -0,0 +1,92 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Topic: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Topic", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + 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(207f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(57f, 57f) + horizontalLineToRelative(320f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 320f) + verticalLineToRelative(400f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 800f) + lineTo(160f, 800f) + close() + moveTo(160f, 720f) + horizontalLineToRelative(640f) + verticalLineToRelative(-400f) + lineTo(447f, 320f) + lineToRelative(-80f, -80f) + lineTo(160f, 240f) + verticalLineToRelative(480f) + close() + moveTo(160f, 720f) + verticalLineToRelative(-480f) + verticalLineToRelative(480f) + close() + moveTo(280f, 640f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(560f, 600f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(520f, 560f) + lineTo(280f, 560f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(240f, 600f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(280f, 640f) + close() + moveTo(280f, 480f) + horizontalLineToRelative(400f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(720f, 440f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(680f, 400f) + lineTo(280f, 400f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(240f, 440f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(280f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Tortoise.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Tortoise.kt new file mode 100644 index 0000000..5ee7df4 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Tortoise.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TouchApp.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TouchApp.kt new file mode 100644 index 0000000..d50ee3f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TouchApp.kt @@ -0,0 +1,174 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.TouchApp: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.TouchApp", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(419f, 880f) + quadToRelative(-28f, 0f, -52.5f, -12f) + reflectiveQuadTo(325f, 834f) + lineTo(124f, 579f) + quadToRelative(-8f, -9f, -7f, -21.5f) + reflectiveQuadToRelative(9f, -20.5f) + quadToRelative(20f, -21f, 48f, -25f) + reflectiveQuadToRelative(52f, 11f) + lineToRelative(74f, 45f) + verticalLineToRelative(-328f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(340f, 200f) + quadToRelative(17f, 0f, 29f, 11.5f) + reflectiveQuadToRelative(12f, 28.5f) + verticalLineToRelative(200f) + horizontalLineToRelative(299f) + quadToRelative(50f, 0f, 85f, 35f) + reflectiveQuadToRelative(35f, 85f) + verticalLineToRelative(160f) + quadToRelative(0f, 66f, -47f, 113f) + reflectiveQuadTo(640f, 880f) + lineTo(419f, 880f) + close() + moveTo(479f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(439f, 320f) + quadToRelative(0f, -2f, 5f, -20f) + quadToRelative(8f, -14f, 12f, -28.5f) + reflectiveQuadToRelative(4f, -31.5f) + quadToRelative(0f, -50f, -35f, -85f) + reflectiveQuadToRelative(-85f, -35f) + quadToRelative(-50f, 0f, -85f, 35f) + reflectiveQuadToRelative(-35f, 85f) + quadToRelative(0f, 17f, 4f, 31.5f) + reflectiveQuadToRelative(12f, 28.5f) + quadToRelative(3f, 5f, 4f, 10f) + reflectiveQuadToRelative(1f, 10f) + quadToRelative(0f, 17f, -11f, 28.5f) + reflectiveQuadTo(202f, 360f) + quadToRelative(-11f, 0f, -20.5f, -6f) + reflectiveQuadTo(167f, 339f) + quadToRelative(-13f, -22f, -20f, -47f) + reflectiveQuadToRelative(-7f, -52f) + quadToRelative(0f, -83f, 58.5f, -141.5f) + reflectiveQuadTo(340f, 40f) + quadToRelative(83f, 0f, 141.5f, 58.5f) + reflectiveQuadTo(540f, 240f) + quadToRelative(0f, 27f, -7f, 52f) + reflectiveQuadToRelative(-20f, 47f) + quadToRelative(-5f, 9f, -14f, 15f) + reflectiveQuadToRelative(-20f, 6f) + close() + } + }.build() +} + +val Icons.Outlined.TouchApp: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.TouchApp", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(419f, 880f) + quadToRelative(-28f, 0f, -52.5f, -12f) + reflectiveQuadTo(325f, 834f) + lineTo(124f, 579f) + quadToRelative(-8f, -9f, -7f, -21.5f) + reflectiveQuadToRelative(9f, -20.5f) + quadToRelative(20f, -21f, 48f, -25f) + reflectiveQuadToRelative(52f, 11f) + lineToRelative(74f, 45f) + verticalLineToRelative(-328f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(340f, 200f) + quadToRelative(17f, 0f, 29f, 11.5f) + reflectiveQuadToRelative(12f, 28.5f) + verticalLineToRelative(400f) + quadToRelative(0f, 23f, -20.5f, 34.5f) + reflectiveQuadTo(320f, 674f) + lineToRelative(-36f, -22f) + lineToRelative(104f, 133f) + quadToRelative(6f, 7f, 14f, 11f) + reflectiveQuadToRelative(17f, 4f) + horizontalLineToRelative(221f) + quadToRelative(33f, 0f, 56.5f, -23.5f) + reflectiveQuadTo(720f, 720f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(680f, 520f) + lineTo(501f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(461f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(501f, 440f) + horizontalLineToRelative(179f) + quadToRelative(50f, 0f, 85f, 35f) + reflectiveQuadToRelative(35f, 85f) + verticalLineToRelative(160f) + quadToRelative(0f, 66f, -47f, 113f) + reflectiveQuadTo(640f, 880f) + lineTo(419f, 880f) + close() + moveTo(502f, 620f) + close() + moveTo(479f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(439f, 320f) + quadToRelative(0f, -2f, 5f, -20f) + quadToRelative(8f, -14f, 12f, -28.5f) + reflectiveQuadToRelative(4f, -31.5f) + quadToRelative(0f, -50f, -35f, -85f) + reflectiveQuadToRelative(-85f, -35f) + quadToRelative(-50f, 0f, -85f, 35f) + reflectiveQuadToRelative(-35f, 85f) + quadToRelative(0f, 17f, 4f, 31.5f) + reflectiveQuadToRelative(12f, 28.5f) + quadToRelative(3f, 5f, 4f, 10f) + reflectiveQuadToRelative(1f, 10f) + quadToRelative(0f, 17f, -11f, 28.5f) + reflectiveQuadTo(202f, 360f) + quadToRelative(-11f, 0f, -20.5f, -6f) + reflectiveQuadTo(167f, 339f) + quadToRelative(-13f, -22f, -20f, -47f) + reflectiveQuadToRelative(-7f, -52f) + quadToRelative(0f, -83f, 58.5f, -141.5f) + reflectiveQuadTo(340f, 40f) + quadToRelative(83f, 0f, 141.5f, 58.5f) + reflectiveQuadTo(540f, 240f) + quadToRelative(0f, 27f, -7f, 52f) + reflectiveQuadToRelative(-20f, 47f) + quadToRelative(-5f, 9f, -14f, 15f) + reflectiveQuadToRelative(-20f, 6f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Translate.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Translate.kt new file mode 100644 index 0000000..4776314 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Translate.kt @@ -0,0 +1,101 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Translate: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Translate", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(603f, 758f) + lineToRelative(-34f, 97f) + quadToRelative(-4f, 11f, -14f, 18f) + reflectiveQuadToRelative(-22f, 7f) + quadToRelative(-20f, 0f, -32.5f, -16.5f) + reflectiveQuadTo(496f, 827f) + lineToRelative(152f, -402f) + quadToRelative(5f, -11f, 15f, -18f) + reflectiveQuadToRelative(22f, -7f) + horizontalLineToRelative(30f) + quadToRelative(12f, 0f, 22f, 7f) + reflectiveQuadToRelative(15f, 18f) + lineToRelative(152f, 403f) + quadToRelative(8f, 19f, -4f, 35.5f) + reflectiveQuadTo(868f, 880f) + quadToRelative(-13f, 0f, -22.5f, -7f) + reflectiveQuadTo(831f, 854f) + lineToRelative(-34f, -96f) + lineTo(603f, 758f) + close() + moveTo(362f, 559f) + lineTo(188f, 732f) + quadToRelative(-11f, 11f, -27.5f, 11.5f) + reflectiveQuadTo(132f, 732f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + lineToRelative(174f, -174f) + quadToRelative(-35f, -35f, -63.5f, -80f) + reflectiveQuadTo(190f, 320f) + horizontalLineToRelative(84f) + quadToRelative(20f, 39f, 40f, 68f) + reflectiveQuadToRelative(48f, 58f) + quadToRelative(33f, -33f, 68.5f, -92.5f) + reflectiveQuadTo(484f, 240f) + lineTo(80f, 240f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(40f, 200f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(80f, 160f) + horizontalLineToRelative(240f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(360f, 80f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(400f, 120f) + verticalLineToRelative(40f) + horizontalLineToRelative(240f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 200f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 240f) + horizontalLineToRelative(-76f) + quadToRelative(-21f, 72f, -63f, 148f) + reflectiveQuadToRelative(-83f, 116f) + lineToRelative(96f, 98f) + lineToRelative(-30f, 82f) + lineToRelative(-122f, -125f) + close() + moveTo(628f, 688f) + horizontalLineToRelative(144f) + lineToRelative(-72f, -204f) + lineToRelative(-72f, 204f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Triadic.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Triadic.kt new file mode 100644 index 0000000..3f24f1e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Triadic.kt @@ -0,0 +1,70 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Triangle.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Triangle.kt new file mode 100644 index 0000000..8224ed5 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Triangle.kt @@ -0,0 +1,119 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), + 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.Black), + 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.Black), + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Tune.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Tune.kt new file mode 100644 index 0000000..4b62481 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Tune.kt @@ -0,0 +1,131 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Tune: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Tune", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(451.5f, 828.5f) + quadTo(440f, 817f, 440f, 800f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 600f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 640f) + verticalLineToRelative(40f) + horizontalLineToRelative(280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 720f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 760f) + lineTo(520f, 760f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 840f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + close() + moveTo(160f, 760f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 720f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 680f) + horizontalLineToRelative(160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 720f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 760f) + lineTo(160f, 760f) + close() + moveTo(291.5f, 588.5f) + quadTo(280f, 577f, 280f, 560f) + verticalLineToRelative(-40f) + lineTo(160f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 440f) + horizontalLineToRelative(120f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 360f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 400f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 600f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + close() + moveTo(480f, 520f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 480f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 440f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 480f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 520f) + lineTo(480f, 520f) + close() + moveTo(611.5f, 348.5f) + quadTo(600f, 337f, 600f, 320f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 160f) + verticalLineToRelative(40f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 240f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 280f) + lineTo(680f, 280f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(640f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + close() + moveTo(160f, 280f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(120f, 240f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(160f, 200f) + horizontalLineToRelative(320f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 240f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 280f) + lineTo(160f, 280f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/USDT.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/USDT.kt new file mode 100644 index 0000000..05d1365 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/USDT.kt @@ -0,0 +1,114 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Unarchive.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Unarchive.kt new file mode 100644 index 0000000..4e36fda --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Unarchive.kt @@ -0,0 +1,170 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Undo.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Undo.kt new file mode 100644 index 0000000..9b47938 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Undo.kt @@ -0,0 +1,73 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Undo: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Undo", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(320f, 760f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(280f, 720f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(320f, 680f) + horizontalLineToRelative(244f) + quadToRelative(63f, 0f, 109.5f, -40f) + reflectiveQuadTo(720f, 540f) + quadToRelative(0f, -60f, -46.5f, -100f) + reflectiveQuadTo(564f, 400f) + lineTo(312f, 400f) + lineToRelative(76f, 76f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(188f, 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(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineToRelative(-76f, 76f) + horizontalLineToRelative(252f) + quadToRelative(97f, 0f, 166.5f, 63f) + reflectiveQuadTo(800f, 540f) + quadToRelative(0f, 94f, -69.5f, 157f) + reflectiveQuadTo(564f, 760f) + lineTo(320f, 760f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Upcoming.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Upcoming.kt new file mode 100644 index 0000000..909041e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Upcoming.kt @@ -0,0 +1,112 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Upcoming: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Upcoming", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 840f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(80f, 760f) + verticalLineToRelative(-200f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(160f, 480f) + horizontalLineToRelative(166f) + quadToRelative(15f, 0f, 26f, 10f) + reflectiveQuadToRelative(13f, 24f) + quadToRelative(5f, 34f, 40f, 60f) + reflectiveQuadToRelative(75f, 26f) + quadToRelative(40f, 0f, 75f, -26f) + reflectiveQuadToRelative(40f, -60f) + quadToRelative(2f, -14f, 13f, -24f) + reflectiveQuadToRelative(26f, -10f) + horizontalLineToRelative(166f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 560f) + verticalLineToRelative(200f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(800f, 840f) + lineTo(160f, 840f) + close() + moveTo(160f, 760f) + horizontalLineToRelative(640f) + verticalLineToRelative(-200f) + lineTo(664f, 560f) + quadToRelative(-25f, 55f, -74.5f, 87.5f) + reflectiveQuadTo(480f, 680f) + quadToRelative(-60f, 0f, -109.5f, -32.5f) + reflectiveQuadTo(296f, 560f) + lineTo(160f, 560f) + verticalLineToRelative(200f) + close() + moveTo(676f, 404f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + lineToRelative(86f, -86f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + lineToRelative(-86f, 86f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + close() + moveTo(284f, 404f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineToRelative(-86f, -86f) + quadToRelative(-11f, -11f, -11f, -28f) + reflectiveQuadToRelative(11f, -28f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(86f, 86f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + close() + moveTo(480f, 320f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(440f, 280f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 160f) + verticalLineToRelative(120f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(480f, 320f) + close() + moveTo(160f, 760f) + horizontalLineToRelative(640f) + horizontalLineToRelative(-640f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Update.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Update.kt new file mode 100644 index 0000000..94b1737 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Update.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Update: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Update", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 840f) + quadToRelative(-75f, 0f, -140.5f, -28.5f) + reflectiveQuadToRelative(-114f, -77f) + quadToRelative(-48.5f, -48.5f, -77f, -114f) + reflectiveQuadTo(120f, 480f) + quadToRelative(0f, -75f, 28.5f, -140.5f) + reflectiveQuadToRelative(77f, -114f) + quadToRelative(48.5f, -48.5f, 114f, -77f) + reflectiveQuadTo(480f, 120f) + quadToRelative(82f, 0f, 155.5f, 35f) + reflectiveQuadTo(760f, 254f) + verticalLineToRelative(-54f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(800f, 160f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(840f, 200f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(800f, 400f) + lineTo(640f, 400f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(600f, 360f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 320f) + horizontalLineToRelative(70f) + quadToRelative(-41f, -56f, -101f, -88f) + reflectiveQuadToRelative(-129f, -32f) + quadToRelative(-117f, 0f, -198.5f, 81.5f) + reflectiveQuadTo(200f, 480f) + quadToRelative(0f, 117f, 81.5f, 198.5f) + reflectiveQuadTo(480f, 760f) + quadToRelative(95f, 0f, 170f, -57f) + reflectiveQuadToRelative(99f, -147f) + quadToRelative(5f, -16f, 18f, -24f) + reflectiveQuadToRelative(29f, -6f) + quadToRelative(17f, 2f, 27f, 14.5f) + reflectiveQuadToRelative(6f, 27.5f) + quadToRelative(-29f, 119f, -126f, 195.5f) + reflectiveQuadTo(480f, 840f) + close() + moveTo(520f, 464f) + lineTo(620f, 564f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(452f, 508f) + quadToRelative(-6f, -6f, -9f, -13.5f) + reflectiveQuadToRelative(-3f, -15.5f) + verticalLineToRelative(-159f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(520f, 320f) + verticalLineToRelative(144f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/UploadFile.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/UploadFile.kt new file mode 100644 index 0000000..461ee16 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/UploadFile.kt @@ -0,0 +1,97 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.UploadFile: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.UploadFile", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(440f, 593f) + verticalLineToRelative(127f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 760f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 720f) + verticalLineToRelative(-127f) + lineToRelative(36f, 36f) + quadToRelative(6f, 6f, 13.5f, 9f) + reflectiveQuadToRelative(15f, 2.5f) + quadToRelative(7.5f, -0.5f, 14.5f, -3.5f) + reflectiveQuadToRelative(13f, -9f) + quadToRelative(11f, -12f, 11.5f, -28f) + reflectiveQuadTo(612f, 572f) + lineTo(508f, 468f) + quadToRelative(-6f, -6f, -13f, -8.5f) + reflectiveQuadToRelative(-15f, -2.5f) + quadToRelative(-8f, 0f, -15f, 2.5f) + reflectiveQuadToRelative(-13f, 8.5f) + lineTo(348f, 572f) + quadToRelative(-12f, 12f, -11.5f, 28f) + reflectiveQuadToRelative(12.5f, 28f) + quadToRelative(12f, 11f, 28f, 11.5f) + reflectiveQuadToRelative(28f, -11.5f) + lineToRelative(35f, -35f) + close() + moveTo(240f, 880f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(160f, 800f) + verticalLineToRelative(-640f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(240f, 80f) + horizontalLineToRelative(287f) + quadToRelative(16f, 0f, 30.5f, 6f) + reflectiveQuadToRelative(25.5f, 17f) + lineToRelative(194f, 194f) + quadToRelative(11f, 11f, 17f, 25.5f) + reflectiveQuadToRelative(6f, 30.5f) + verticalLineToRelative(447f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(720f, 880f) + lineTo(240f, 880f) + close() + moveTo(520f, 320f) + verticalLineToRelative(-160f) + lineTo(240f, 160f) + verticalLineToRelative(640f) + horizontalLineToRelative(480f) + verticalLineToRelative(-440f) + lineTo(560f, 360f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(520f, 320f) + close() + moveTo(240f, 160f) + verticalLineToRelative(200f) + verticalLineToRelative(-200f) + verticalLineToRelative(640f) + verticalLineToRelative(-640f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/VectorPolyline.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/VectorPolyline.kt new file mode 100644 index 0000000..ba565c6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/VectorPolyline.kt @@ -0,0 +1,192 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Verified.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Verified.kt new file mode 100644 index 0000000..3ec7c0a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Verified.kt @@ -0,0 +1,120 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Verified: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Verified", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(438f, 508f) + lineToRelative(-58f, -57f) + quadToRelative(-11f, -11f, -27.5f, -11f) + reflectiveQuadTo(324f, 452f) + quadToRelative(-11f, 11f, -11f, 28f) + reflectiveQuadToRelative(11f, 28f) + lineToRelative(86f, 86f) + quadToRelative(12f, 12f, 28f, 12f) + reflectiveQuadToRelative(28f, -12f) + lineToRelative(170f, -170f) + quadToRelative(12f, -12f, 11.5f, -28f) + reflectiveQuadTo(636f, 368f) + quadToRelative(-12f, -12f, -28.5f, -12.5f) + reflectiveQuadTo(579f, 367f) + lineTo(438f, 508f) + close() + moveTo(326f, 870f) + lineToRelative(-58f, -98f) + lineToRelative(-110f, -24f) + quadToRelative(-15f, -3f, -24f, -15.5f) + reflectiveQuadToRelative(-7f, -27.5f) + lineToRelative(11f, -113f) + lineToRelative(-75f, -86f) + quadToRelative(-10f, -11f, -10f, -26f) + reflectiveQuadToRelative(10f, -26f) + lineToRelative(75f, -86f) + lineToRelative(-11f, -113f) + quadToRelative(-2f, -15f, 7f, -27.5f) + reflectiveQuadToRelative(24f, -15.5f) + lineToRelative(110f, -24f) + lineToRelative(58f, -98f) + quadToRelative(8f, -13f, 22f, -17.5f) + reflectiveQuadToRelative(28f, 1.5f) + lineToRelative(104f, 44f) + lineToRelative(104f, -44f) + quadToRelative(14f, -6f, 28f, -1.5f) + reflectiveQuadToRelative(22f, 17.5f) + lineToRelative(58f, 98f) + lineToRelative(110f, 24f) + quadToRelative(15f, 3f, 24f, 15.5f) + reflectiveQuadToRelative(7f, 27.5f) + lineToRelative(-11f, 113f) + lineToRelative(75f, 86f) + quadToRelative(10f, 11f, 10f, 26f) + reflectiveQuadToRelative(-10f, 26f) + lineToRelative(-75f, 86f) + lineToRelative(11f, 113f) + quadToRelative(2f, 15f, -7f, 27.5f) + reflectiveQuadTo(802f, 748f) + lineToRelative(-110f, 24f) + lineToRelative(-58f, 98f) + quadToRelative(-8f, 13f, -22f, 17.5f) + reflectiveQuadTo(584f, 886f) + lineToRelative(-104f, -44f) + lineToRelative(-104f, 44f) + quadToRelative(-14f, 6f, -28f, 1.5f) + reflectiveQuadTo(326f, 870f) + close() + moveTo(378f, 798f) + lineTo(480f, 754f) + lineTo(584f, 798f) + lineTo(640f, 702f) + lineTo(750f, 676f) + lineTo(740f, 564f) + lineTo(814f, 480f) + lineTo(740f, 394f) + lineTo(750f, 282f) + lineTo(640f, 258f) + lineTo(582f, 162f) + lineTo(480f, 206f) + lineTo(376f, 162f) + lineTo(320f, 258f) + lineTo(210f, 282f) + lineTo(220f, 394f) + lineTo(146f, 480f) + lineTo(220f, 564f) + lineTo(210f, 678f) + lineTo(320f, 702f) + lineTo(378f, 798f) + close() + moveTo(480f, 480f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ViewColumn.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ViewColumn.kt new file mode 100644 index 0000000..f3950b1 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ViewColumn.kt @@ -0,0 +1,125 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ViewColumn: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.ViewColumn", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(121f, 680f) + verticalLineToRelative(-400f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(201f, 200f) + horizontalLineToRelative(559f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(840f, 280f) + verticalLineToRelative(400f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 760f) + lineTo(201f, 760f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(121f, 680f) + close() + moveTo(200f, 680f) + horizontalLineToRelative(133f) + verticalLineToRelative(-400f) + lineTo(200f, 280f) + verticalLineToRelative(400f) + close() + moveTo(413f, 680f) + horizontalLineToRelative(133f) + verticalLineToRelative(-400f) + lineTo(413f, 280f) + verticalLineToRelative(400f) + close() + moveTo(626f, 680f) + horizontalLineToRelative(133f) + verticalLineToRelative(-400f) + lineTo(626f, 280f) + verticalLineToRelative(400f) + close() + } + }.build() +} + +val Icons.Rounded.ViewColumn: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.ViewColumn", + 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(-400f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 200f) + horizontalLineToRelative(53f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(333f, 280f) + verticalLineToRelative(400f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(253f, 760f) + horizontalLineToRelative(-53f) + close() + moveTo(453f, 760f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(373f, 680f) + verticalLineToRelative(-400f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(453f, 200f) + horizontalLineToRelative(53f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(586f, 280f) + verticalLineToRelative(400f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(506f, 760f) + horizontalLineToRelative(-53f) + close() + moveTo(706f, 760f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(626f, 680f) + verticalLineToRelative(-400f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(706f, 200f) + horizontalLineToRelative(53f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(839f, 280f) + verticalLineToRelative(400f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(759f, 760f) + horizontalLineToRelative(-53f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ViewComfy.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ViewComfy.kt new file mode 100644 index 0000000..acbc157 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ViewComfy.kt @@ -0,0 +1,70 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ViewComfy: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.ViewComfy", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(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) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(80f, 720f) + close() + moveTo(800f, 440f) + verticalLineToRelative(-200f) + lineTo(160f, 240f) + verticalLineToRelative(200f) + horizontalLineToRelative(640f) + close() + moveTo(400f, 720f) + horizontalLineToRelative(400f) + verticalLineToRelative(-200f) + lineTo(400f, 520f) + verticalLineToRelative(200f) + close() + moveTo(160f, 720f) + horizontalLineToRelative(160f) + verticalLineToRelative(-200f) + lineTo(160f, 520f) + verticalLineToRelative(200f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ViewWeek.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ViewWeek.kt new file mode 100644 index 0000000..6a11a00 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ViewWeek.kt @@ -0,0 +1,70 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ViewWeek: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.ViewWeek", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(160f, 720f) + horizontalLineToRelative(160f) + verticalLineToRelative(-480f) + lineTo(160f, 240f) + verticalLineToRelative(480f) + close() + moveTo(400f, 720f) + horizontalLineToRelative(160f) + verticalLineToRelative(-480f) + lineTo(400f, 240f) + verticalLineToRelative(480f) + close() + moveTo(640f, 720f) + horizontalLineToRelative(160f) + verticalLineToRelative(-480f) + lineTo(640f, 240f) + verticalLineToRelative(480f) + 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() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Visibility.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Visibility.kt new file mode 100644 index 0000000..51dc432 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Visibility.kt @@ -0,0 +1,139 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Visibility: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Visibility", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(607.5f, 587.5f) + quadTo(660f, 535f, 660f, 460f) + reflectiveQuadToRelative(-52.5f, -127.5f) + quadTo(555f, 280f, 480f, 280f) + reflectiveQuadToRelative(-127.5f, 52.5f) + quadTo(300f, 385f, 300f, 460f) + reflectiveQuadToRelative(52.5f, 127.5f) + quadTo(405f, 640f, 480f, 640f) + reflectiveQuadToRelative(127.5f, -52.5f) + close() + moveTo(403.5f, 536.5f) + quadTo(372f, 505f, 372f, 460f) + reflectiveQuadToRelative(31.5f, -76.5f) + quadTo(435f, 352f, 480f, 352f) + reflectiveQuadToRelative(76.5f, 31.5f) + quadTo(588f, 415f, 588f, 460f) + reflectiveQuadToRelative(-31.5f, 76.5f) + quadTo(525f, 568f, 480f, 568f) + reflectiveQuadToRelative(-76.5f, -31.5f) + close() + moveTo(235.5f, 688f) + quadTo(125f, 616f, 61f, 498f) + quadToRelative(-5f, -9f, -7.5f, -18.5f) + reflectiveQuadTo(51f, 460f) + quadToRelative(0f, -10f, 2.5f, -19.5f) + reflectiveQuadTo(61f, 422f) + quadToRelative(64f, -118f, 174.5f, -190f) + reflectiveQuadTo(480f, 160f) + quadToRelative(134f, 0f, 244.5f, 72f) + reflectiveQuadTo(899f, 422f) + quadToRelative(5f, 9f, 7.5f, 18.5f) + reflectiveQuadTo(909f, 460f) + quadToRelative(0f, 10f, -2.5f, 19.5f) + reflectiveQuadTo(899f, 498f) + quadToRelative(-64f, 118f, -174.5f, 190f) + reflectiveQuadTo(480f, 760f) + quadToRelative(-134f, 0f, -244.5f, -72f) + close() + } + }.build() +} + +val Icons.Outlined.Visibility: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.Visibility", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(607.5f, 587.5f) + quadTo(660f, 535f, 660f, 460f) + reflectiveQuadToRelative(-52.5f, -127.5f) + quadTo(555f, 280f, 480f, 280f) + reflectiveQuadToRelative(-127.5f, 52.5f) + quadTo(300f, 385f, 300f, 460f) + reflectiveQuadToRelative(52.5f, 127.5f) + quadTo(405f, 640f, 480f, 640f) + reflectiveQuadToRelative(127.5f, -52.5f) + close() + moveTo(403.5f, 536.5f) + quadTo(372f, 505f, 372f, 460f) + reflectiveQuadToRelative(31.5f, -76.5f) + quadTo(435f, 352f, 480f, 352f) + reflectiveQuadToRelative(76.5f, 31.5f) + quadTo(588f, 415f, 588f, 460f) + reflectiveQuadToRelative(-31.5f, 76.5f) + quadTo(525f, 568f, 480f, 568f) + reflectiveQuadToRelative(-76.5f, -31.5f) + close() + moveTo(235.5f, 688f) + quadTo(125f, 616f, 61f, 498f) + quadToRelative(-5f, -9f, -7.5f, -18.5f) + reflectiveQuadTo(51f, 460f) + quadToRelative(0f, -10f, 2.5f, -19.5f) + reflectiveQuadTo(61f, 422f) + quadToRelative(64f, -118f, 174.5f, -190f) + reflectiveQuadTo(480f, 160f) + quadToRelative(134f, 0f, 244.5f, 72f) + reflectiveQuadTo(899f, 422f) + quadToRelative(5f, 9f, 7.5f, 18.5f) + reflectiveQuadTo(909f, 460f) + quadToRelative(0f, 10f, -2.5f, 19.5f) + reflectiveQuadTo(899f, 498f) + quadToRelative(-64f, 118f, -174.5f, 190f) + reflectiveQuadTo(480f, 760f) + quadToRelative(-134f, 0f, -244.5f, -72f) + close() + moveTo(480f, 460f) + close() + moveTo(687.5f, 620.5f) + quadTo(782f, 561f, 832f, 460f) + quadToRelative(-50f, -101f, -144.5f, -160.5f) + reflectiveQuadTo(480f, 240f) + quadToRelative(-113f, 0f, -207.5f, 59.5f) + reflectiveQuadTo(128f, 460f) + quadToRelative(50f, 101f, 144.5f, 160.5f) + reflectiveQuadTo(480f, 680f) + quadToRelative(113f, 0f, 207.5f, -59.5f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/VisibilityOff.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/VisibilityOff.kt new file mode 100644 index 0000000..5f96b6e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/VisibilityOff.kt @@ -0,0 +1,209 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.VisibilityOff: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.VisibilityOff", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(764f, 876f) + lineTo(624f, 738f) + quadToRelative(-35f, 11f, -71f, 16.5f) + reflectiveQuadToRelative(-73f, 5.5f) + quadToRelative(-134f, 0f, -245f, -72f) + reflectiveQuadTo(61f, 498f) + quadToRelative(-5f, -9f, -7.5f, -18.5f) + reflectiveQuadTo(51f, 460f) + quadToRelative(0f, -10f, 2.5f, -19.5f) + reflectiveQuadTo(61f, 422f) + quadToRelative(22f, -39f, 47f, -76f) + reflectiveQuadToRelative(58f, -66f) + lineToRelative(-83f, -84f) + quadToRelative(-11f, -11f, -11f, -27.5f) + reflectiveQuadTo(84f, 140f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(680f, 680f) + quadToRelative(11f, 11f, 11.5f, 27.5f) + reflectiveQuadTo(820f, 876f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + close() + moveTo(480f, 640f) + quadToRelative(11f, 0f, 21f, -1f) + reflectiveQuadToRelative(20f, -4f) + lineTo(305f, 419f) + quadToRelative(-3f, 10f, -4f, 20f) + reflectiveQuadToRelative(-1f, 21f) + quadToRelative(0f, 75f, 52.5f, 127.5f) + reflectiveQuadTo(480f, 640f) + close() + moveTo(480f, 160f) + quadToRelative(134f, 0f, 245.5f, 72.5f) + reflectiveQuadTo(900f, 423f) + quadToRelative(5f, 8f, 7.5f, 17.5f) + reflectiveQuadTo(910f, 460f) + quadToRelative(0f, 10f, -2f, 19.5f) + reflectiveQuadToRelative(-7f, 17.5f) + quadToRelative(-19f, 37f, -42.5f, 70f) + reflectiveQuadTo(806f, 629f) + quadToRelative(-14f, 14f, -33f, 13f) + reflectiveQuadToRelative(-33f, -15f) + lineToRelative(-80f, -80f) + quadToRelative(-7f, -7f, -9f, -16.5f) + reflectiveQuadToRelative(1f, -19.5f) + quadToRelative(4f, -13f, 6f, -25f) + reflectiveQuadToRelative(2f, -26f) + quadToRelative(0f, -75f, -52.5f, -127.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(-14f, 0f, -26f, 2f) + reflectiveQuadToRelative(-25f, 6f) + quadToRelative(-10f, 3f, -20f, 1f) + reflectiveQuadToRelative(-17f, -9f) + lineToRelative(-33f, -33f) + quadToRelative(-19f, -19f, -12.5f, -44f) + reflectiveQuadToRelative(31.5f, -32f) + quadToRelative(25f, -5f, 50.5f, -8f) + reflectiveQuadToRelative(51.5f, -3f) + close() + moveTo(559f, 386f) + quadToRelative(11f, 13f, 18.5f, 28.5f) + reflectiveQuadTo(587f, 447f) + quadToRelative(1f, 8f, -6f, 11f) + reflectiveQuadToRelative(-13f, -3f) + lineToRelative(-82f, -82f) + quadToRelative(-6f, -6f, -2.5f, -13f) + reflectiveQuadToRelative(11.5f, -7f) + quadToRelative(19f, 2f, 35f, 10.5f) + reflectiveQuadToRelative(29f, 22.5f) + close() + } + }.build() +} + +val Icons.Outlined.VisibilityOff: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.VisibilityOff", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(607f, 333f) + quadToRelative(29f, 29f, 42.5f, 66f) + reflectiveQuadToRelative(9.5f, 76f) + quadToRelative(0f, 15f, -11f, 25.5f) + reflectiveQuadTo(622f, 511f) + quadToRelative(-15f, 0f, -25.5f, -10.5f) + reflectiveQuadTo(586f, 475f) + quadToRelative(5f, -26f, -3f, -50f) + reflectiveQuadToRelative(-25f, -41f) + quadToRelative(-17f, -17f, -41f, -26f) + reflectiveQuadToRelative(-51f, -4f) + quadToRelative(-15f, 0f, -25.5f, -11f) + reflectiveQuadTo(430f, 317f) + quadToRelative(0f, -15f, 10.5f, -25.5f) + reflectiveQuadTo(466f, 281f) + quadToRelative(38f, -4f, 75f, 9.5f) + reflectiveQuadToRelative(66f, 42.5f) + close() + moveTo(480f, 240f) + quadToRelative(-19f, 0f, -37f, 1.5f) + reflectiveQuadToRelative(-36f, 5.5f) + quadToRelative(-17f, 3f, -30.5f, -5f) + reflectiveQuadTo(358f, 218f) + quadToRelative(-5f, -16f, 3.5f, -31f) + reflectiveQuadToRelative(24.5f, -18f) + quadToRelative(23f, -5f, 46.5f, -7f) + reflectiveQuadToRelative(47.5f, -2f) + quadToRelative(137f, 0f, 250.5f, 72f) + reflectiveQuadTo(904f, 426f) + quadToRelative(4f, 8f, 6f, 16.5f) + reflectiveQuadToRelative(2f, 17.5f) + quadToRelative(0f, 9f, -1.5f, 17.5f) + reflectiveQuadTo(905f, 494f) + quadToRelative(-18f, 40f, -44.5f, 75f) + reflectiveQuadTo(802f, 633f) + quadToRelative(-12f, 11f, -28f, 9f) + reflectiveQuadToRelative(-26f, -16f) + quadToRelative(-10f, -14f, -8.5f, -30.5f) + reflectiveQuadTo(753f, 568f) + quadToRelative(24f, -23f, 44f, -50f) + reflectiveQuadToRelative(35f, -58f) + quadToRelative(-50f, -101f, -144.5f, -160.5f) + reflectiveQuadTo(480f, 240f) + close() + moveTo(480f, 760f) + quadToRelative(-134f, 0f, -245f, -72.5f) + reflectiveQuadTo(60f, 497f) + quadToRelative(-5f, -8f, -7.5f, -17.5f) + reflectiveQuadTo(50f, 460f) + quadToRelative(0f, -10f, 2f, -19f) + reflectiveQuadToRelative(7f, -18f) + quadToRelative(20f, -40f, 46.5f, -76.5f) + reflectiveQuadTo(166f, 280f) + lineToRelative(-83f, -84f) + quadToRelative(-11f, -12f, -10.5f, -28.5f) + reflectiveQuadTo(84f, 140f) + quadToRelative(11f, -11f, 28f, -11f) + reflectiveQuadToRelative(28f, 11f) + lineToRelative(680f, 680f) + quadToRelative(11f, 11f, 11.5f, 27.5f) + reflectiveQuadTo(820f, 876f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(624f, 738f) + quadToRelative(-35f, 11f, -71f, 16.5f) + reflectiveQuadToRelative(-73f, 5.5f) + close() + moveTo(222f, 336f) + quadToRelative(-29f, 26f, -53f, 57f) + reflectiveQuadToRelative(-41f, 67f) + quadToRelative(50f, 101f, 144.5f, 160.5f) + reflectiveQuadTo(480f, 680f) + quadToRelative(20f, 0f, 39f, -2.5f) + reflectiveQuadToRelative(39f, -5.5f) + lineToRelative(-36f, -38f) + quadToRelative(-11f, 3f, -21f, 4.5f) + reflectiveQuadToRelative(-21f, 1.5f) + quadToRelative(-75f, 0f, -127.5f, -52.5f) + reflectiveQuadTo(300f, 460f) + quadToRelative(0f, -11f, 1.5f, -21f) + reflectiveQuadToRelative(4.5f, -21f) + lineToRelative(-84f, -82f) + close() + moveTo(541f, 429f) + close() + moveTo(390f, 504f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/VolunteerActivism.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/VolunteerActivism.kt new file mode 100644 index 0000000..247f962 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/VolunteerActivism.kt @@ -0,0 +1,208 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +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(535f, 873f) + quadToRelative(11f, 3f, 25.5f, 2.5f) + reflectiveQuadTo(585f, 871f) + lineToRelative(295f, -111f) + quadToRelative(0f, -34f, -24f, -57f) + reflectiveQuadToRelative(-56f, -23f) + lineTo(526f, 680f) + quadToRelative(-3f, 0f, -7f, -0.5f) + reflectiveQuadToRelative(-6f, -1.5f) + lineToRelative(-59f, -21f) + quadToRelative(-8f, -3f, -11f, -10f) + reflectiveQuadToRelative(-1f, -15f) + quadToRelative(2f, -7f, 10f, -11f) + reflectiveQuadToRelative(16f, -1f) + lineToRelative(45f, 17f) + quadToRelative(4f, 2f, 6.5f, 2.5f) + reflectiveQuadToRelative(7.5f, 0.5f) + horizontalLineToRelative(105f) + quadToRelative(19f, 0f, 33.5f, -13f) + reflectiveQuadToRelative(14.5f, -34f) + quadToRelative(0f, -14f, -8.5f, -27f) + reflectiveQuadTo(649f, 548f) + lineTo(372f, 445f) + quadToRelative(-7f, -2f, -14f, -3.5f) + reflectiveQuadToRelative(-14f, -1.5f) + horizontalLineToRelative(-64f) + verticalLineToRelative(361f) + lineToRelative(255f, 72f) + close() + moveTo(40f, 800f) + quadToRelative(0f, 33f, 23.5f, 56.5f) + reflectiveQuadTo(120f, 880f) + quadToRelative(33f, 0f, 56.5f, -23.5f) + reflectiveQuadTo(200f, 800f) + verticalLineToRelative(-280f) + quadToRelative(0f, -33f, -23.5f, -56.5f) + reflectiveQuadTo(120f, 440f) + quadToRelative(-33f, 0f, -56.5f, 23.5f) + reflectiveQuadTo(40f, 520f) + verticalLineToRelative(280f) + close() + moveTo(610.5f, 482.5f) + quadTo(596f, 477f, 584f, 466f) + lineTo(474f, 358f) + quadToRelative(-31f, -30f, -52.5f, -66.5f) + reflectiveQuadTo(400f, 212f) + quadToRelative(0f, -55f, 38.5f, -93.5f) + reflectiveQuadTo(532f, 80f) + quadToRelative(32f, 0f, 60f, 13.5f) + reflectiveQuadToRelative(48f, 36.5f) + quadToRelative(20f, -23f, 48f, -36.5f) + reflectiveQuadToRelative(60f, -13.5f) + quadToRelative(55f, 0f, 93.5f, 38.5f) + reflectiveQuadTo(880f, 212f) + quadToRelative(0f, 43f, -21f, 79.5f) + reflectiveQuadTo(807f, 358f) + lineTo(696f, 466f) + quadToRelative(-12f, 11f, -26.5f, 16.5f) + reflectiveQuadTo(640f, 488f) + quadToRelative(-15f, 0f, -29.5f, -5.5f) + 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)) { + moveToRelative(558f, 816f) + lineToRelative(238f, -74f) + quadToRelative(-5f, -9f, -14.5f, -15.5f) + reflectiveQuadTo(760f, 720f) + lineTo(558f, 720f) + quadToRelative(-27f, 0f, -43f, -2f) + reflectiveQuadToRelative(-33f, -8f) + lineToRelative(-57f, -19f) + quadToRelative(-16f, -5f, -23f, -20f) + reflectiveQuadToRelative(-2f, -31f) + quadToRelative(5f, -16f, 19.5f, -23.5f) + reflectiveQuadTo(450f, 614f) + lineToRelative(42f, 14f) + quadToRelative(17f, 5f, 38.5f, 8f) + reflectiveQuadToRelative(58.5f, 4f) + horizontalLineToRelative(11f) + quadToRelative(0f, -11f, -6.5f, -21f) + reflectiveQuadTo(578f, 606f) + lineToRelative(-234f, -86f) + horizontalLineToRelative(-64f) + verticalLineToRelative(220f) + lineToRelative(278f, 76f) + close() + moveTo(537f, 894f) + lineTo(280f, 822f) + quadToRelative(-8f, 26f, -31.5f, 42f) + reflectiveQuadTo(200f, 880f) + horizontalLineToRelative(-80f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(40f, 800f) + verticalLineToRelative(-280f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(120f, 440f) + horizontalLineToRelative(224f) + quadToRelative(7f, 0f, 14f, 1.5f) + reflectiveQuadToRelative(13f, 3.5f) + lineToRelative(235f, 87f) + quadToRelative(33f, 12f, 53.5f, 42f) + reflectiveQuadToRelative(20.5f, 66f) + horizontalLineToRelative(80f) + quadToRelative(50f, 0f, 85f, 33f) + reflectiveQuadToRelative(35f, 87f) + quadToRelative(0f, 22f, -11.5f, 34.5f) + reflectiveQuadTo(833f, 815f) + lineTo(583f, 893f) + quadToRelative(-11f, 4f, -23f, 4f) + reflectiveQuadToRelative(-23f, -3f) + close() + moveTo(120f, 800f) + horizontalLineToRelative(80f) + verticalLineToRelative(-280f) + horizontalLineToRelative(-80f) + verticalLineToRelative(280f) + close() + moveTo(610.5f, 482.5f) + quadTo(596f, 477f, 584f, 466f) + lineTo(474f, 358f) + quadToRelative(-31f, -30f, -52.5f, -66.5f) + reflectiveQuadTo(400f, 212f) + quadToRelative(0f, -55f, 38.5f, -93.5f) + reflectiveQuadTo(532f, 80f) + quadToRelative(32f, 0f, 60f, 13.5f) + reflectiveQuadToRelative(48f, 36.5f) + quadToRelative(20f, -23f, 48f, -36.5f) + reflectiveQuadToRelative(60f, -13.5f) + quadToRelative(55f, 0f, 93.5f, 38.5f) + reflectiveQuadTo(880f, 212f) + quadToRelative(0f, 43f, -21f, 79.5f) + reflectiveQuadTo(807f, 358f) + lineTo(696f, 466f) + quadToRelative(-12f, 11f, -26.5f, 16.5f) + reflectiveQuadTo(640f, 488f) + quadToRelative(-15f, 0f, -29.5f, -5.5f) + close() + moveTo(640f, 408f) + lineToRelative(109f, -107f) + quadToRelative(19f, -19f, 35f, -40.5f) + reflectiveQuadToRelative(16f, -48.5f) + quadToRelative(0f, -22f, -15f, -37f) + reflectiveQuadToRelative(-37f, -15f) + quadToRelative(-14f, 0f, -26.5f, 5.5f) + reflectiveQuadTo(700f, 182f) + lineToRelative(-29f, 35f) + quadToRelative(-12f, 14f, -31f, 14f) + reflectiveQuadToRelative(-31f, -14f) + lineToRelative(-29f, -35f) + quadToRelative(-9f, -11f, -21.5f, -16.5f) + reflectiveQuadTo(532f, 160f) + quadToRelative(-22f, 0f, -37f, 15f) + reflectiveQuadToRelative(-15f, 37f) + quadToRelative(0f, 27f, 16f, 48.5f) + reflectiveQuadToRelative(35f, 40.5f) + lineToRelative(109f, 107f) + close() + moveTo(640f, 254f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WallpaperAlt.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WallpaperAlt.kt new file mode 100644 index 0000000..a4c5021 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WallpaperAlt.kt @@ -0,0 +1,178 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.WallpaperAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.WallpaperAlt", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + 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(620f, 600f) + quadToRelative(6f, 0f, 9f, -5.5f) + reflectiveQuadToRelative(-1f, -10.5f) + lineToRelative(-85f, -113f) + quadToRelative(-3f, -4f, -8f, -4f) + reflectiveQuadToRelative(-8f, 4f) + lineToRelative(-67f, 89f) + lineToRelative(-47f, -63f) + quadToRelative(-3f, -4f, -8f, -4f) + reflectiveQuadToRelative(-8f, 4f) + lineToRelative(-65f, 87f) + quadToRelative(-4f, 5f, -1f, 10.5f) + reflectiveQuadToRelative(9f, 5.5f) + horizontalLineToRelative(280f) + close() + moveTo(628.5f, 388.5f) + quadTo(640f, 377f, 640f, 360f) + reflectiveQuadToRelative(-11.5f, -28.5f) + quadTo(617f, 320f, 600f, 320f) + reflectiveQuadToRelative(-28.5f, 11.5f) + quadTo(560f, 343f, 560f, 360f) + reflectiveQuadToRelative(11.5f, 28.5f) + quadTo(583f, 400f, 600f, 400f) + reflectiveQuadToRelative(28.5f, -11.5f) + close() + moveTo(280f, 840f) + verticalLineToRelative(-720f) + verticalLineToRelative(720f) + close() + } + }.build() +} + +val Icons.TwoTone.WallpaperAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.WallpaperAlt", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + 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(17f, 21f) + horizontalLineTo(7f) + verticalLineTo(3f) + horizontalLineToRelative(10f) + verticalLineToRelative(18f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(15.5f, 15f) + curveToRelative(0.1f, 0f, 0.175f, -0.046f, 0.225f, -0.138f) + reflectiveCurveToRelative(0.042f, -0.179f, -0.025f, -0.262f) + lineToRelative(-2.125f, -2.825f) + curveToRelative(-0.05f, -0.067f, -0.117f, -0.1f, -0.2f, -0.1f) + reflectiveCurveToRelative(-0.15f, 0.033f, -0.2f, 0.1f) + lineToRelative(-1.675f, 2.225f) + lineToRelative(-1.175f, -1.575f) + curveToRelative(-0.05f, -0.067f, -0.117f, -0.1f, -0.2f, -0.1f) + reflectiveCurveToRelative(-0.15f, 0.033f, -0.2f, 0.1f) + lineToRelative(-1.625f, 2.175f) + curveToRelative(-0.067f, 0.083f, -0.075f, 0.171f, -0.025f, 0.262f) + reflectiveCurveToRelative(0.125f, 0.138f, 0.225f, 0.138f) + horizontalLineToRelative(7f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(15.713f, 9.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) + 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) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(7f, 21f) + verticalLineTo(3f) + verticalLineToRelative(18f) + close() + } + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(7f, 3f) + horizontalLineToRelative(10f) + verticalLineToRelative(18f) + horizontalLineToRelative(-10f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WandShine.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WandShine.kt new file mode 100644 index 0000000..6d7c1a9 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WandShine.kt @@ -0,0 +1,160 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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.Black)) { + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WandStars.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WandStars.kt new file mode 100644 index 0000000..36ee54e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WandStars.kt @@ -0,0 +1,169 @@ +package com.t8rin.imagetoolbox.core.resources.icons + +import com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WarningAmber.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WarningAmber.kt new file mode 100644 index 0000000..b339471 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WarningAmber.kt @@ -0,0 +1,141 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.WarningAmber: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.WarningAmber", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(2.725f, 21f) + quadTo(2.45f, 21f, 2.225f, 20.862f) + quadTo(2f, 20.725f, 1.875f, 20.5f) + quadTo(1.75f, 20.275f, 1.737f, 20.013f) + quadTo(1.725f, 19.75f, 1.875f, 19.5f) + lineTo(11.125f, 3.5f) + quadTo(11.275f, 3.25f, 11.512f, 3.125f) + quadTo(11.75f, 3f, 12f, 3f) + quadTo(12.25f, 3f, 12.488f, 3.125f) + quadTo(12.725f, 3.25f, 12.875f, 3.5f) + lineTo(22.125f, 19.5f) + quadTo(22.275f, 19.75f, 22.263f, 20.013f) + quadTo(22.25f, 20.275f, 22.125f, 20.5f) + quadTo(22f, 20.725f, 21.775f, 20.862f) + quadTo(21.55f, 21f, 21.275f, 21f) + close() + moveTo(4.45f, 19f) + horizontalLineTo(19.55f) + lineTo(12f, 6f) + close() + moveTo(12f, 18f) + quadTo(12.425f, 18f, 12.712f, 17.712f) + quadTo(13f, 17.425f, 13f, 17f) + quadTo(13f, 16.575f, 12.712f, 16.288f) + quadTo(12.425f, 16f, 12f, 16f) + quadTo(11.575f, 16f, 11.288f, 16.288f) + quadTo(11f, 16.575f, 11f, 17f) + quadTo(11f, 17.425f, 11.288f, 17.712f) + quadTo(11.575f, 18f, 12f, 18f) + close() + moveTo(13f, 14f) + verticalLineTo(11f) + quadTo(13f, 10.575f, 12.712f, 10.288f) + quadTo(12.425f, 10f, 12f, 10f) + quadTo(11.575f, 10f, 11.288f, 10.288f) + quadTo(11f, 10.575f, 11f, 11f) + verticalLineTo(14f) + quadTo(11f, 14.425f, 11.288f, 14.712f) + quadTo(11.575f, 15f, 12f, 15f) + quadTo(12.425f, 15f, 12.712f, 14.712f) + quadTo(13f, 14.425f, 13f, 14f) + close() + moveTo(12f, 12.5f) + close() + } + }.build() +} + +val Icons.Rounded.WarningAmber: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.WarningAmber", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(2.725f, 21f) + quadTo(2.45f, 21f, 2.225f, 20.862f) + quadTo(2f, 20.725f, 1.875f, 20.5f) + quadTo(1.75f, 20.275f, 1.737f, 20.013f) + quadTo(1.725f, 19.75f, 1.875f, 19.5f) + lineTo(11.125f, 3.5f) + quadTo(11.275f, 3.25f, 11.512f, 3.125f) + quadTo(11.75f, 3f, 12f, 3f) + quadTo(12.25f, 3f, 12.488f, 3.125f) + quadTo(12.725f, 3.25f, 12.875f, 3.5f) + lineTo(22.125f, 19.5f) + quadTo(22.275f, 19.75f, 22.263f, 20.013f) + quadTo(22.25f, 20.275f, 22.125f, 20.5f) + quadTo(22f, 20.725f, 21.775f, 20.862f) + quadTo(21.55f, 21f, 21.275f, 21f) + close() + moveTo(4.45f, 19f) + horizontalLineTo(19.55f) + lineTo(12f, 6f) + close() + moveTo(12f, 18f) + quadTo(12.425f, 18f, 12.712f, 17.712f) + quadTo(13f, 17.425f, 13f, 17f) + quadTo(13f, 16.575f, 12.712f, 16.288f) + quadTo(12.425f, 16f, 12f, 16f) + quadTo(11.575f, 16f, 11.288f, 16.288f) + quadTo(11f, 16.575f, 11f, 17f) + quadTo(11f, 17.425f, 11.288f, 17.712f) + quadTo(11.575f, 18f, 12f, 18f) + close() + moveTo(13f, 14f) + verticalLineTo(11f) + quadTo(13f, 10.575f, 12.712f, 10.288f) + quadTo(12.425f, 10f, 12f, 10f) + quadTo(11.575f, 10f, 11.288f, 10.288f) + quadTo(11f, 10.575f, 11f, 11f) + verticalLineTo(14f) + quadTo(11f, 14.425f, 11.288f, 14.712f) + quadTo(11.575f, 15f, 12f, 15f) + quadTo(12.425f, 15f, 12.712f, 14.712f) + quadTo(13f, 14.425f, 13f, 14f) + close() + moveTo(4.45f, 19f) + lineTo(12f, 6f) + lineTo(19.55f, 19f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Water.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Water.kt new file mode 100644 index 0000000..b06f03f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Water.kt @@ -0,0 +1,140 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Water: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Water", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(213f, 680f) + quadToRelative(-29f, 0f, -42f, 12.5f) + reflectiveQuadTo(135f, 710f) + quadToRelative(-23f, 5f, -39f, -4.5f) + reflectiveQuadTo(80f, 677f) + quadToRelative(0f, -15f, 13f, -29f) + reflectiveQuadToRelative(41f, -28f) + quadToRelative(5f, -3f, 26.5f, -11.5f) + reflectiveQuadTo(217f, 600f) + quadToRelative(55f, 0f, 74f, 20f) + reflectiveQuadToRelative(56f, 20f) + quadToRelative(37f, 0f, 55.5f, -20f) + reflectiveQuadToRelative(76.5f, -20f) + quadToRelative(58f, 0f, 78.5f, 20f) + reflectiveQuadToRelative(57.5f, 20f) + quadToRelative(37f, 0f, 54f, -20f) + reflectiveQuadToRelative(74f, -20f) + quadToRelative(23f, 0f, 44.5f, 5.5f) + reflectiveQuadTo(831f, 622f) + quadToRelative(25f, 13f, 37f, 27f) + reflectiveQuadToRelative(12f, 28f) + quadToRelative(0f, 19f, -16f, 28.5f) + reflectiveQuadToRelative(-39f, 4.5f) + quadToRelative(-23f, -5f, -36.5f, -17.5f) + reflectiveQuadTo(747f, 680f) + quadToRelative(-37f, 0f, -55.5f, 20f) + reflectiveQuadTo(615f, 720f) + quadToRelative(-58f, 0f, -78.5f, -20f) + reflectiveQuadTo(479f, 680f) + quadToRelative(-37f, 0f, -55.5f, 20f) + reflectiveQuadTo(346f, 720f) + quadToRelative(-59f, 0f, -77f, -20f) + reflectiveQuadToRelative(-56f, -20f) + close() + moveTo(213f, 520f) + quadToRelative(-28f, 0f, -41.5f, 12.5f) + reflectiveQuadTo(135f, 550f) + quadToRelative(-23f, 5f, -39f, -4.5f) + reflectiveQuadTo(80f, 517f) + quadToRelative(0f, -15f, 13f, -29f) + reflectiveQuadToRelative(41f, -28f) + quadToRelative(5f, -3f, 26.5f, -11.5f) + reflectiveQuadTo(217f, 440f) + quadToRelative(55f, 0f, 74f, 20f) + reflectiveQuadToRelative(56f, 20f) + quadToRelative(37f, 0f, 55.5f, -20f) + reflectiveQuadToRelative(76.5f, -20f) + quadToRelative(58f, 0f, 78.5f, 20f) + reflectiveQuadToRelative(56.5f, 20f) + quadToRelative(36f, 0f, 54f, -20f) + reflectiveQuadToRelative(74f, -20f) + quadToRelative(35f, 0f, 56.5f, 8.5f) + reflectiveQuadTo(825f, 460f) + quadToRelative(29f, 15f, 42f, 28.5f) + reflectiveQuadToRelative(13f, 28.5f) + quadToRelative(0f, 19f, -16.5f, 28.5f) + reflectiveQuadTo(824f, 550f) + quadToRelative(-23f, -5f, -36f, -17.5f) + reflectiveQuadTo(747f, 520f) + quadToRelative(-37f, 0f, -55.5f, 20f) + reflectiveQuadTo(615f, 560f) + quadToRelative(-58f, 0f, -78.5f, -20f) + reflectiveQuadTo(479f, 520f) + quadToRelative(-37f, 0f, -54.5f, 20f) + reflectiveQuadTo(348f, 560f) + quadToRelative(-59f, 0f, -78.5f, -20f) + reflectiveQuadTo(213f, 520f) + close() + moveTo(213f, 360f) + quadToRelative(-28f, 0f, -41.5f, 12.5f) + reflectiveQuadTo(135f, 390f) + quadToRelative(-23f, 5f, -39f, -4.5f) + reflectiveQuadTo(80f, 357f) + quadToRelative(0f, -15f, 13f, -29f) + reflectiveQuadToRelative(41f, -28f) + quadToRelative(5f, -3f, 26.5f, -11.5f) + reflectiveQuadTo(217f, 280f) + quadToRelative(55f, 0f, 74f, 20f) + reflectiveQuadToRelative(56f, 20f) + quadToRelative(37f, 0f, 55.5f, -20f) + reflectiveQuadToRelative(76.5f, -20f) + quadToRelative(58f, 0f, 78.5f, 20f) + reflectiveQuadToRelative(56.5f, 20f) + quadToRelative(36f, 0f, 54f, -20f) + reflectiveQuadToRelative(74f, -20f) + quadToRelative(35f, 0f, 56.5f, 8.5f) + reflectiveQuadTo(825f, 300f) + quadToRelative(29f, 15f, 42f, 28.5f) + reflectiveQuadToRelative(13f, 28.5f) + quadToRelative(0f, 19f, -16.5f, 28.5f) + reflectiveQuadTo(824f, 390f) + quadToRelative(-23f, -5f, -36f, -17.5f) + reflectiveQuadTo(747f, 360f) + quadToRelative(-37f, 0f, -55.5f, 20f) + reflectiveQuadTo(615f, 400f) + quadToRelative(-58f, 0f, -78.5f, -20f) + reflectiveQuadTo(479f, 360f) + quadToRelative(-37f, 0f, -54.5f, 20f) + reflectiveQuadTo(348f, 400f) + quadToRelative(-59f, 0f, -78.5f, -20f) + reflectiveQuadTo(213f, 360f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WaterDrop.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WaterDrop.kt new file mode 100644 index 0000000..b221083 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WaterDrop.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.WaterDrop: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.WaterDrop", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-137f, 0f, -228.5f, -94f) + reflectiveQuadTo(160f, 552f) + quadToRelative(0f, -62f, 28f, -124f) + reflectiveQuadToRelative(70f, -119f) + quadToRelative(42f, -57f, 91f, -107f) + reflectiveQuadToRelative(91f, -87f) + quadToRelative(8f, -8f, 18.5f, -11.5f) + reflectiveQuadTo(480f, 100f) + quadToRelative(11f, 0f, 21.5f, 3.5f) + reflectiveQuadTo(520f, 115f) + quadToRelative(42f, 37f, 91f, 87f) + reflectiveQuadToRelative(91f, 107f) + quadToRelative(42f, 57f, 70f, 119f) + reflectiveQuadToRelative(28f, 124f) + quadToRelative(0f, 140f, -91.5f, 234f) + reflectiveQuadTo(480f, 880f) + close() + moveTo(480f, 800f) + quadToRelative(104f, 0f, 172f, -70.5f) + reflectiveQuadTo(720f, 552f) + quadToRelative(0f, -73f, -60.5f, -165f) + reflectiveQuadTo(480f, 186f) + quadTo(361f, 295f, 300.5f, 387f) + reflectiveQuadTo(240f, 552f) + quadToRelative(0f, 107f, 68f, 177.5f) + reflectiveQuadTo(480f, 800f) + close() + moveTo(480f, 480f) + close() + moveTo(491f, 760f) + quadToRelative(12f, -1f, 20.5f, -9.5f) + reflectiveQuadTo(520f, 730f) + quadToRelative(0f, -14f, -9f, -22.5f) + reflectiveQuadToRelative(-23f, -7.5f) + quadToRelative(-41f, 3f, -87f, -22.5f) + reflectiveQuadTo(343f, 585f) + quadToRelative(-2f, -11f, -10.5f, -18f) + reflectiveQuadToRelative(-19.5f, -7f) + quadToRelative(-14f, 0f, -23f, 10.5f) + reflectiveQuadToRelative(-6f, 24.5f) + quadToRelative(17f, 91f, 80f, 130f) + reflectiveQuadToRelative(127f, 35f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Watermark.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Watermark.kt new file mode 100644 index 0000000..6acce79 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Watermark.kt @@ -0,0 +1,154 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WatermarkAlt.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WatermarkAlt.kt new file mode 100644 index 0000000..8412195 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WatermarkAlt.kt @@ -0,0 +1,134 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 +import com.t8rin.imagetoolbox.core.resources.Icons + +val Icons.Outlined.WatermarkAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.WatermarkAlt", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(19.412f, 2.588f) + curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) + 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(12f) + curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) + reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) + verticalLineTo(4f) + curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) + close() + moveTo(18f, 20f) + horizontalLineTo(6f) + verticalLineTo(4f) + horizontalLineToRelative(12f) + verticalLineToRelative(16f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(17f, 12f) + verticalLineToRelative(-6f) + curveToRelative(0f, -0.283f, -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) + verticalLineToRelative(6f) + curveToRelative(0f, 0.283f, 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) + close() + } + }.build() +} + +val Icons.TwoTone.WatermarkAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "TwoTone.WatermarkAlt", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path( + fill = SolidColor(Color.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(6f, 4f) + horizontalLineToRelative(12f) + verticalLineToRelative(16f) + horizontalLineToRelative(-12f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(19.412f, 2.588f) + curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) + 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(12f) + curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) + reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) + verticalLineTo(4f) + curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) + close() + moveTo(18f, 20f) + horizontalLineTo(6f) + verticalLineTo(4f) + horizontalLineToRelative(12f) + verticalLineToRelative(16f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(17f, 12f) + verticalLineToRelative(-6f) + curveToRelative(0f, -0.283f, -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) + verticalLineToRelative(6f) + curveToRelative(0f, 0.283f, 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) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Waves.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Waves.kt new file mode 100644 index 0000000..c1c064d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Waves.kt @@ -0,0 +1,174 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Waves: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Waves", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(280f, 758f) + quadToRelative(-49f, 0f, -78f, 22f) + reflectiveQuadToRelative(-81f, 31f) + quadToRelative(-17f, 3f, -29f, -8f) + reflectiveQuadToRelative(-12f, -28f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 730f) + quadToRelative(36f, -11f, 68f, -30.5f) + reflectiveQuadToRelative(92f, -19.5f) + quadToRelative(38f, 0f, 62.5f, 8.5f) + reflectiveQuadToRelative(45.5f, 19f) + quadToRelative(21f, 10.5f, 42f, 19.5f) + reflectiveQuadToRelative(50f, 9f) + quadToRelative(29f, 0f, 50f, -9f) + reflectiveQuadToRelative(42f, -19.5f) + quadToRelative(21f, -10.5f, 46f, -19f) + reflectiveQuadToRelative(62f, -8.5f) + quadToRelative(60f, 0f, 92f, 19.5f) + reflectiveQuadToRelative(68f, 30.5f) + quadToRelative(17f, 5f, 28.5f, 16.5f) + reflectiveQuadTo(880f, 775f) + quadToRelative(0f, 17f, -12f, 28f) + reflectiveQuadToRelative(-29f, 8f) + quadToRelative(-52f, -9f, -81f, -31f) + reflectiveQuadToRelative(-78f, -22f) + quadToRelative(-28f, 0f, -48.5f, 8.5f) + reflectiveQuadToRelative(-41f, 19f) + quadTo(570f, 796f, 544.5f, 805f) + reflectiveQuadToRelative(-64.5f, 9f) + quadToRelative(-39f, 0f, -64.5f, -9f) + reflectiveQuadToRelative(-46f, -19.5f) + quadTo(349f, 775f, 329f, 766.5f) + reflectiveQuadToRelative(-49f, -8.5f) + close() + moveTo(280f, 580f) + quadToRelative(-49f, 0f, -78f, 22f) + reflectiveQuadToRelative(-81f, 31f) + quadToRelative(-17f, 3f, -29f, -8f) + reflectiveQuadToRelative(-12f, -28f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 552f) + quadToRelative(36f, -11f, 68f, -30.5f) + reflectiveQuadToRelative(92f, -19.5f) + quadToRelative(38f, 0f, 62.5f, 8.5f) + reflectiveQuadToRelative(45.5f, 19f) + quadToRelative(21f, 10.5f, 42f, 19.5f) + reflectiveQuadToRelative(50f, 9f) + quadToRelative(29f, 0f, 50f, -9f) + reflectiveQuadToRelative(42f, -19.5f) + quadToRelative(21f, -10.5f, 46f, -19f) + reflectiveQuadToRelative(62f, -8.5f) + quadToRelative(60f, 0f, 92f, 19.5f) + reflectiveQuadToRelative(68f, 30.5f) + quadToRelative(17f, 5f, 28.5f, 16.5f) + reflectiveQuadTo(880f, 597f) + quadToRelative(0f, 17f, -12f, 28f) + reflectiveQuadToRelative(-29f, 8f) + quadToRelative(-52f, -9f, -81f, -31f) + reflectiveQuadToRelative(-78f, -22f) + quadToRelative(-29f, 0f, -49.5f, 8.5f) + reflectiveQuadToRelative(-41f, 19f) + quadTo(569f, 618f, 544f, 627f) + reflectiveQuadToRelative(-64f, 9f) + quadToRelative(-39f, 0f, -64.5f, -9f) + reflectiveQuadToRelative(-46f, -19.5f) + quadTo(349f, 597f, 329f, 588.5f) + reflectiveQuadToRelative(-49f, -8.5f) + close() + moveTo(280f, 402f) + quadToRelative(-49f, 0f, -78f, 22f) + reflectiveQuadToRelative(-81f, 31f) + quadToRelative(-17f, 3f, -29f, -8f) + reflectiveQuadToRelative(-12f, -28f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 374f) + quadToRelative(36f, -11f, 68f, -30.5f) + reflectiveQuadToRelative(92f, -19.5f) + quadToRelative(38f, 0f, 62.5f, 8.5f) + reflectiveQuadToRelative(45.5f, 19f) + quadToRelative(21f, 10.5f, 42f, 19.5f) + reflectiveQuadToRelative(50f, 9f) + quadToRelative(29f, 0f, 50f, -9f) + reflectiveQuadToRelative(42f, -19.5f) + quadToRelative(21f, -10.5f, 46f, -19f) + reflectiveQuadToRelative(62f, -8.5f) + quadToRelative(60f, 0f, 92f, 19.5f) + reflectiveQuadToRelative(68f, 30.5f) + quadToRelative(17f, 5f, 28.5f, 16.5f) + reflectiveQuadTo(880f, 419f) + quadToRelative(0f, 17f, -12f, 28f) + reflectiveQuadToRelative(-29f, 8f) + quadToRelative(-52f, -9f, -81f, -31f) + reflectiveQuadToRelative(-78f, -22f) + quadToRelative(-28f, 0f, -48.5f, 8.5f) + reflectiveQuadToRelative(-41f, 19f) + quadTo(570f, 440f, 544.5f, 449f) + reflectiveQuadToRelative(-64.5f, 9f) + quadToRelative(-39f, 0f, -64.5f, -9f) + reflectiveQuadToRelative(-46f, -19.5f) + quadTo(349f, 419f, 329f, 410.5f) + reflectiveQuadToRelative(-49f, -8.5f) + close() + moveTo(280f, 224f) + quadToRelative(-49f, 0f, -78f, 22f) + reflectiveQuadToRelative(-81f, 31f) + quadToRelative(-17f, 3f, -29f, -8f) + reflectiveQuadToRelative(-12f, -28f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(120f, 196f) + quadToRelative(36f, -11f, 68f, -30.5f) + reflectiveQuadToRelative(92f, -19.5f) + quadToRelative(38f, 0f, 62.5f, 8.5f) + reflectiveQuadToRelative(45.5f, 19f) + quadToRelative(21f, 10.5f, 42f, 19.5f) + reflectiveQuadToRelative(50f, 9f) + quadToRelative(29f, 0f, 50f, -9f) + reflectiveQuadToRelative(42f, -19.5f) + quadToRelative(21f, -10.5f, 46f, -19f) + reflectiveQuadToRelative(62f, -8.5f) + quadToRelative(60f, 0f, 92f, 19.5f) + reflectiveQuadToRelative(68f, 30.5f) + quadToRelative(17f, 5f, 28.5f, 16.5f) + reflectiveQuadTo(880f, 241f) + quadToRelative(0f, 17f, -12f, 28f) + reflectiveQuadToRelative(-29f, 8f) + quadToRelative(-52f, -9f, -81f, -31f) + reflectiveQuadToRelative(-78f, -22f) + quadToRelative(-28f, 0f, -48.5f, 8.5f) + reflectiveQuadToRelative(-41f, 19f) + quadTo(570f, 262f, 544.5f, 271f) + reflectiveQuadToRelative(-64.5f, 9f) + quadToRelative(-39f, 0f, -64.5f, -9f) + reflectiveQuadToRelative(-46f, -19.5f) + quadTo(349f, 241f, 329f, 232.5f) + reflectiveQuadToRelative(-49f, -8.5f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Webhook.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Webhook.kt new file mode 100644 index 0000000..7740fbe --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Webhook.kt @@ -0,0 +1,132 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Webhook: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Webhook", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(280f, 840f) + quadToRelative(-83f, 0f, -141.5f, -58.5f) + reflectiveQuadTo(80f, 640f) + quadToRelative(0f, -56f, 27f, -101.5f) + reflectiveQuadToRelative(72f, -71.5f) + quadToRelative(21f, -12f, 41f, 0.5f) + reflectiveQuadToRelative(20f, 34.5f) + quadToRelative(0f, 11f, -4.5f, 20f) + reflectiveQuadTo(223f, 535f) + quadToRelative(-28f, 15f, -45.5f, 43f) + reflectiveQuadTo(160f, 640f) + quadToRelative(0f, 50f, 35f, 85f) + reflectiveQuadToRelative(85f, 35f) + quadToRelative(50f, 0f, 85f, -35f) + reflectiveQuadToRelative(35f, -85f) + quadToRelative(0f, -17f, 9.5f, -28.5f) + reflectiveQuadTo(436f, 600f) + horizontalLineToRelative(199f) + quadToRelative(8f, -9f, 19.5f, -14.5f) + reflectiveQuadTo(680f, 580f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(740f, 640f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(680f, 700f) + quadToRelative(-14f, 0f, -25.5f, -5.5f) + reflectiveQuadTo(635f, 680f) + lineTo(476f, 680f) + quadToRelative(-14f, 69f, -68.5f, 114.5f) + reflectiveQuadTo(280f, 840f) + close() + moveTo(280f, 700f) + quadToRelative(-25f, 0f, -42.5f, -17.5f) + reflectiveQuadTo(220f, 640f) + quadToRelative(0f, -22f, 14f, -38f) + reflectiveQuadToRelative(34f, -21f) + lineToRelative(94f, -156f) + quadToRelative(-29f, -27f, -45.5f, -64.5f) + reflectiveQuadTo(300f, 280f) + quadToRelative(0f, -83f, 58.5f, -141.5f) + reflectiveQuadTo(500f, 80f) + quadToRelative(70f, 0f, 123.5f, 42.5f) + reflectiveQuadTo(694f, 230f) + quadToRelative(5f, 19f, -7f, 34.5f) + reflectiveQuadTo(655f, 280f) + quadToRelative(-13f, 0f, -24.5f, -9.5f) + reflectiveQuadTo(615f, 247f) + quadToRelative(-11f, -38f, -42f, -62.5f) + reflectiveQuadTo(500f, 160f) + quadToRelative(-50f, 0f, -85f, 35f) + reflectiveQuadToRelative(-35f, 85f) + quadToRelative(0f, 33f, 16.5f, 60.5f) + reflectiveQuadTo(439f, 384f) + quadToRelative(14f, 8f, 17.5f, 20f) + reflectiveQuadToRelative(-3.5f, 24f) + lineTo(337f, 622f) + quadToRelative(2f, 5f, 2.5f, 9f) + reflectiveQuadToRelative(0.5f, 9f) + quadToRelative(0f, 25f, -17.5f, 42.5f) + reflectiveQuadTo(280f, 700f) + close() + moveTo(680f, 840f) + quadToRelative(-26f, 0f, -50.5f, -6.5f) + reflectiveQuadTo(584f, 816f) + quadToRelative(-27f, -15f, -21.5f, -45.5f) + reflectiveQuadTo(603f, 740f) + quadToRelative(5f, 0f, 11f, 2f) + reflectiveQuadToRelative(11f, 5f) + quadToRelative(13f, 7f, 26.5f, 10f) + reflectiveQuadToRelative(28.5f, 3f) + quadToRelative(50f, 0f, 85f, -35f) + reflectiveQuadToRelative(35f, -85f) + quadToRelative(0f, -50f, -35f, -85f) + reflectiveQuadToRelative(-85f, -35f) + quadToRelative(-10f, 0f, -19f, 1.5f) + reflectiveQuadToRelative(-18f, 4.5f) + quadToRelative(-16f, 5f, -30f, 0.5f) + reflectiveQuadTo(592f, 511f) + lineTo(489f, 339f) + quadToRelative(-21f, -4f, -35f, -20f) + reflectiveQuadToRelative(-14f, -39f) + quadToRelative(0f, -25f, 17.5f, -42.5f) + reflectiveQuadTo(500f, 220f) + quadToRelative(25f, 0f, 42.5f, 17.5f) + reflectiveQuadTo(560f, 280f) + verticalLineToRelative(8.5f) + quadToRelative(0f, 3.5f, -2f, 8.5f) + lineToRelative(87f, 146f) + quadToRelative(8f, -2f, 17f, -2.5f) + reflectiveQuadToRelative(18f, -0.5f) + quadToRelative(83f, 0f, 141.5f, 58.5f) + reflectiveQuadTo(880f, 640f) + quadToRelative(0f, 83f, -58.5f, 141.5f) + reflectiveQuadTo(680f, 840f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Webp.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Webp.kt new file mode 100644 index 0000000..f8c5929 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Webp.kt @@ -0,0 +1,110 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WebpBox.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WebpBox.kt new file mode 100644 index 0000000..2d4a802 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WebpBox.kt @@ -0,0 +1,241 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black), + fillAlpha = 0.3f, + strokeAlpha = 0.3f + ) { + moveTo(5f, 5f) + horizontalLineToRelative(14f) + verticalLineToRelative(14f) + horizontalLineToRelative(-14f) + close() + } + path(fill = SolidColor(Color.Black)) { + 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.Black)) { + 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.Black)) { + 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.Black)) { + 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.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) + moveTo(19f, 5f) + verticalLineToRelative(14f) + horizontalLineTo(5f) + verticalLineTo(5f) + horizontalLineToRelative(14f) + close() + } + }.build() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Wifi.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Wifi.kt new file mode 100644 index 0000000..79c503a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Wifi.kt @@ -0,0 +1,84 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Wifi: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.Wifi", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 840f) + quadToRelative(-42f, 0f, -71f, -29f) + reflectiveQuadToRelative(-29f, -71f) + quadToRelative(0f, -42f, 29f, -71f) + reflectiveQuadToRelative(71f, -29f) + quadToRelative(42f, 0f, 71f, 29f) + reflectiveQuadToRelative(29f, 71f) + quadToRelative(0f, 42f, -29f, 71f) + reflectiveQuadToRelative(-71f, 29f) + close() + moveTo(480f, 400f) + quadToRelative(75f, 0f, 142.5f, 24f) + reflectiveQuadTo(745f, 490f) + quadToRelative(20f, 15f, 20.5f, 39.5f) + reflectiveQuadTo(748f, 572f) + quadToRelative(-17f, 17f, -42f, 17.5f) + reflectiveQuadTo(661f, 576f) + quadToRelative(-38f, -26f, -84f, -41f) + reflectiveQuadToRelative(-97f, -15f) + quadToRelative(-51f, 0f, -97f, 15f) + reflectiveQuadToRelative(-84f, 41f) + quadToRelative(-20f, 14f, -45f, 13f) + reflectiveQuadToRelative(-42f, -18f) + quadToRelative(-17f, -18f, -17f, -42.5f) + reflectiveQuadToRelative(20f, -39.5f) + quadToRelative(55f, -42f, 122.5f, -65.5f) + reflectiveQuadTo(480f, 400f) + close() + moveTo(480f, 160f) + quadToRelative(125f, 0f, 235.5f, 41f) + reflectiveQuadTo(914f, 317f) + quadToRelative(20f, 17f, 21f, 42f) + reflectiveQuadToRelative(-17f, 43f) + quadToRelative(-17f, 17f, -42f, 17.5f) + reflectiveQuadTo(831f, 404f) + quadToRelative(-72f, -59f, -161.5f, -91.5f) + reflectiveQuadTo(480f, 280f) + quadToRelative(-100f, 0f, -189.5f, 32.5f) + reflectiveQuadTo(129f, 404f) + quadToRelative(-20f, 16f, -45f, 15.5f) + reflectiveQuadTo(42f, 402f) + quadToRelative(-18f, -18f, -17f, -43f) + reflectiveQuadToRelative(21f, -42f) + quadToRelative(88f, -75f, 198.5f, -116f) + reflectiveQuadTo(480f, 160f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WifiTetheringError.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WifiTetheringError.kt new file mode 100644 index 0000000..9555940 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WifiTetheringError.kt @@ -0,0 +1,120 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.WifiTetheringError: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.WifiTetheringError", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(233f, 781f) + quadToRelative(-12f, 12f, -29f, 12f) + reflectiveQuadToRelative(-28f, -13f) + quadToRelative(-46f, -55f, -71f, -121.5f) + reflectiveQuadTo(80f, 520f) + quadToRelative(0f, -83f, 31.5f, -156f) + reflectiveQuadTo(197f, 237f) + quadToRelative(54f, -54f, 127f, -85.5f) + reflectiveQuadTo(480f, 120f) + quadToRelative(91f, 0f, 173f, 39f) + reflectiveQuadToRelative(139f, 111f) + quadToRelative(11f, 13f, 9f, 29f) + reflectiveQuadToRelative(-15f, 27f) + quadToRelative(-13f, 11f, -29f, 9f) + reflectiveQuadToRelative(-27f, -15f) + quadToRelative(-46f, -57f, -111.5f, -88.5f) + reflectiveQuadTo(480f, 200f) + quadToRelative(-134f, 0f, -227f, 93f) + reflectiveQuadToRelative(-93f, 227f) + quadToRelative(0f, 56f, 18.5f, 108f) + reflectiveQuadToRelative(54.5f, 95f) + quadToRelative(11f, 13f, 11.5f, 29.5f) + reflectiveQuadTo(233f, 781f) + close() + moveTo(346f, 668f) + quadToRelative(-12f, 12f, -29f, 12.5f) + reflectiveQuadTo(290f, 667f) + quadToRelative(-23f, -31f, -36.5f, -68f) + reflectiveQuadTo(240f, 520f) + quadToRelative(0f, -100f, 70f, -170f) + reflectiveQuadToRelative(170f, -70f) + quadToRelative(100f, 0f, 170f, 70f) + reflectiveQuadToRelative(70f, 170f) + quadToRelative(0f, 42f, -13.5f, 79.5f) + reflectiveQuadTo(670f, 667f) + quadToRelative(-10f, 13f, -27f, 13.5f) + reflectiveQuadTo(614f, 669f) + quadToRelative(-11f, -11f, -11.5f, -28f) + reflectiveQuadToRelative(9.5f, -31f) + quadToRelative(13f, -20f, 20.5f, -42.5f) + reflectiveQuadTo(640f, 520f) + quadToRelative(0f, -66f, -47f, -113f) + reflectiveQuadToRelative(-113f, -47f) + quadToRelative(-66f, 0f, -113f, 47f) + reflectiveQuadToRelative(-47f, 113f) + quadToRelative(0f, 26f, 7.5f, 48f) + reflectiveQuadToRelative(20.5f, 42f) + quadToRelative(10f, 14f, 9.5f, 30.5f) + reflectiveQuadTo(346f, 668f) + close() + moveTo(480f, 600f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(400f, 520f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(480f, 440f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(560f, 520f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(480f, 600f) + close() + moveTo(840f, 800f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(800f, 760f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(840f, 720f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 760f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 800f) + close() + moveTo(800f, 600f) + verticalLineToRelative(-160f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(840f, 400f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(880f, 440f) + verticalLineToRelative(160f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(840f, 640f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(800f, 600f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Windows.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Windows.kt new file mode 100644 index 0000000..266b78b --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Windows.kt @@ -0,0 +1,89 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black), 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.Black), 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.Black), 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.Black), 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WrapText.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WrapText.kt new file mode 100644 index 0000000..749dcc7 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WrapText.kt @@ -0,0 +1,97 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.WrapText: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.WrapText", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + autoMirror = true + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(200f, 500f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 460f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 420f) + horizontalLineToRelative(490f) + quadToRelative(63f, 0f, 106.5f, 43.5f) + reflectiveQuadTo(840f, 570f) + quadToRelative(0f, 63f, -43.5f, 106.5f) + reflectiveQuadTo(690f, 720f) + horizontalLineToRelative(-96f) + lineToRelative(22f, 22f) + quadToRelative(12f, 12f, 11.5f, 28f) + reflectiveQuadTo(616f, 798f) + quadToRelative(-12f, 12f, -28.5f, 12.5f) + reflectiveQuadTo(559f, 799f) + lineToRelative(-91f, -91f) + quadToRelative(-6f, -6f, -8.5f, -13f) + reflectiveQuadToRelative(-2.5f, -15f) + quadToRelative(0f, -8f, 2.5f, -15f) + reflectiveQuadToRelative(8.5f, -13f) + lineToRelative(91f, -91f) + quadToRelative(12f, -12f, 28.5f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + quadToRelative(11f, 12f, 11.5f, 28.5f) + reflectiveQuadTo(616f, 618f) + lineToRelative(-22f, 22f) + horizontalLineToRelative(96f) + quadToRelative(29f, 0f, 49.5f, -20.5f) + reflectiveQuadTo(760f, 570f) + quadToRelative(0f, -29f, -20.5f, -49.5f) + reflectiveQuadTo(690f, 500f) + lineTo(200f, 500f) + close() + moveTo(200f, 720f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 680f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 640f) + horizontalLineToRelative(120f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(360f, 680f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(320f, 720f) + lineTo(200f, 720f) + close() + moveTo(200f, 280f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(160f, 240f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(200f, 200f) + horizontalLineToRelative(560f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(800f, 240f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(760f, 280f) + lineTo(200f, 280f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ZigzagLine.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ZigzagLine.kt new file mode 100644 index 0000000..8b1168d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ZigzagLine.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Black)) { + 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() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ZoomIn.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ZoomIn.kt new file mode 100644 index 0000000..32551fb --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ZoomIn.kt @@ -0,0 +1,92 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ZoomIn: ImageVector by lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Outlined.ZoomIn", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(340f, 420f) + horizontalLineToRelative(-40f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(260f, 380f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(300f, 340f) + horizontalLineToRelative(40f) + verticalLineToRelative(-40f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(380f, 260f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(420f, 300f) + verticalLineToRelative(40f) + horizontalLineToRelative(40f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(500f, 380f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(460f, 420f) + horizontalLineToRelative(-40f) + verticalLineToRelative(40f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(380f, 500f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(340f, 460f) + verticalLineToRelative(-40f) + close() + moveTo(380f, 640f) + quadToRelative(-109f, 0f, -184.5f, -75.5f) + reflectiveQuadTo(120f, 380f) + quadToRelative(0f, -109f, 75.5f, -184.5f) + reflectiveQuadTo(380f, 120f) + quadToRelative(109f, 0f, 184.5f, 75.5f) + reflectiveQuadTo(640f, 380f) + quadToRelative(0f, 44f, -14f, 83f) + reflectiveQuadToRelative(-38f, 69f) + lineToRelative(224f, 224f) + quadToRelative(11f, 11f, 11f, 28f) + reflectiveQuadToRelative(-11f, 28f) + quadToRelative(-11f, 11f, -28f, 11f) + reflectiveQuadToRelative(-28f, -11f) + lineTo(532f, 588f) + quadToRelative(-30f, 24f, -69f, 38f) + reflectiveQuadToRelative(-83f, 14f) + close() + moveTo(380f, 560f) + quadToRelative(75f, 0f, 127.5f, -52.5f) + reflectiveQuadTo(560f, 380f) + quadToRelative(0f, -75f, -52.5f, -127.5f) + reflectiveQuadTo(380f, 200f) + quadToRelative(-75f, 0f, -127.5f, 52.5f) + reflectiveQuadTo(200f, 380f) + quadToRelative(0f, 75f, 52.5f, 127.5f) + reflectiveQuadTo(380f, 560f) + close() + } + }.build() +} diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ArrowShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ArrowShape.kt new file mode 100644 index 0000000..a26979f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ArrowShape.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/BookmarkShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/BookmarkShape.kt new file mode 100644 index 0000000..5deec9f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/BookmarkShape.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/BurgerShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/BurgerShape.kt new file mode 100644 index 0000000..0e995d6 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/BurgerShape.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/CloverShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/CloverShape.kt new file mode 100644 index 0000000..c28c33a --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/CloverShape.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + ) + } +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/DropletShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/DropletShape.kt new file mode 100644 index 0000000..4e51589 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/DropletShape.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/EggShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/EggShape.kt new file mode 100644 index 0000000..0142fe3 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/EggShape.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ExplosionShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ExplosionShape.kt new file mode 100644 index 0000000..0c43c66 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ExplosionShape.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/HeartShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/HeartShape.kt new file mode 100644 index 0000000..1f82f33 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/HeartShape.kt @@ -0,0 +1,91 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/KotlinShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/KotlinShape.kt new file mode 100644 index 0000000..e9ae9ec --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/KotlinShape.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + ) + } +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/MapShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/MapShape.kt new file mode 100644 index 0000000..11a97de --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/MapShape.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/MaterialStarShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/MaterialStarShape.kt new file mode 100644 index 0000000..c9c35de --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/MaterialStarShape.kt @@ -0,0 +1,103 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + ) + } +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/MorphShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/MorphShape.kt new file mode 100644 index 0000000..ba15920 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/MorphShape.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/OctagonShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/OctagonShape.kt new file mode 100644 index 0000000..ea4056d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/OctagonShape.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + ) + } +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/OvalShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/OvalShape.kt new file mode 100644 index 0000000..a78109e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/OvalShape.kt @@ -0,0 +1,59 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + ) + } +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/PathShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/PathShape.kt new file mode 100644 index 0000000..ecfb55f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/PathShape.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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() +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/PentagonShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/PentagonShape.kt new file mode 100644 index 0000000..016ab0d --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/PentagonShape.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + ) + } +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/PillShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/PillShape.kt new file mode 100644 index 0000000..7638860 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/PillShape.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ShieldShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ShieldShape.kt new file mode 100644 index 0000000..a9844d2 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ShieldShape.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ShurikenShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ShurikenShape.kt new file mode 100644 index 0000000..b5a198f --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ShurikenShape.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/SimpleHeartShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/SimpleHeartShape.kt new file mode 100644 index 0000000..fcf4c5e --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/SimpleHeartShape.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + ) + } +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/SmallMaterialStarShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/SmallMaterialStarShape.kt new file mode 100644 index 0000000..0b18472 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/SmallMaterialStarShape.kt @@ -0,0 +1,105 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + ) + } +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/SquircleShape.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/SquircleShape.kt new file mode 100644 index 0000000..5cdf927 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/SquircleShape.kt @@ -0,0 +1,59 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + ) + } +} \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/utils/ColorUtils.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/utils/ColorUtils.kt new file mode 100644 index 0000000..5fd1302 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/utils/ColorUtils.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.utils + +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.graphics.isSpecified +import androidx.compose.ui.graphics.toArgb + +@Stable +fun Color.compositeOverSafe( + background: Color +): Color = toSafeSrgb().compositeOver(background.toSafeSrgb()) + +@Stable +fun Color.toSafeSrgb( + fallback: Color = Color.Transparent +): Color = if (isSpecified) { + runCatching { Color(toArgb()) }.getOrDefault(fallback) +} else { + fallback +} + +@Stable +fun Int.toSafeSrgb( + fallback: Color = Color.Transparent +): Color = runCatching { Color(this) }.getOrDefault(fallback) \ No newline at end of file diff --git a/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/utils/animation/ColorAnimation.kt b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/utils/animation/ColorAnimation.kt new file mode 100644 index 0000000..7310972 --- /dev/null +++ b/core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/utils/animation/ColorAnimation.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.utils.animation + +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.AnimationVector4D +import androidx.compose.animation.core.TwoWayConverter +import androidx.compose.animation.core.animateValueAsState +import androidx.compose.animation.core.spring +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.ui.graphics.Color +import com.t8rin.imagetoolbox.core.resources.utils.toSafeSrgb + +@Composable +fun animateColorAsState( + targetValue: Color, + animationSpec: AnimationSpec = spring(), + label: String = "ColorAnimation", + finishedListener: ((Color) -> Unit)? = null +): State = animateValueAsState( + targetValue = targetValue, + typeConverter = SafeColorVectorConverter, + animationSpec = animationSpec, + label = label, + finishedListener = finishedListener +) + +private val SafeColorVectorConverter = TwoWayConverter( + convertToVector = { color -> + color.toSafeSrgb().run { + AnimationVector4D( + v1 = red, + v2 = green, + v3 = blue, + v4 = alpha + ) + } + }, + convertFromVector = { + Color( + red = it.v1.toColorComponent(), + green = it.v2.toColorComponent(), + blue = it.v3.toColorComponent(), + alpha = it.v4.toColorComponent() + ) + } +) + +private fun Float.toColorComponent(): Float = if (isFinite()) coerceIn(0f, 1f) else 0f \ No newline at end of file diff --git a/core/resources/src/main/res/drawable-v31/ic_logo_animated.xml b/core/resources/src/main/res/drawable-v31/ic_logo_animated.xml new file mode 100644 index 0000000..afaca3f --- /dev/null +++ b/core/resources/src/main/res/drawable-v31/ic_logo_animated.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/resources/src/main/res/drawable/app_registration.xml b/core/resources/src/main/res/drawable/app_registration.xml new file mode 100644 index 0000000..6369fd3 --- /dev/null +++ b/core/resources/src/main/res/drawable/app_registration.xml @@ -0,0 +1,25 @@ + + + + diff --git a/core/resources/src/main/res/drawable/avatar.webp b/core/resources/src/main/res/drawable/avatar.webp new file mode 100644 index 0000000..6ed392f Binary files /dev/null and b/core/resources/src/main/res/drawable/avatar.webp differ diff --git a/core/resources/src/main/res/drawable/filter_preview_source.webp b/core/resources/src/main/res/drawable/filter_preview_source.webp new file mode 100644 index 0000000..c6e0e0c Binary files /dev/null and b/core/resources/src/main/res/drawable/filter_preview_source.webp differ diff --git a/core/resources/src/main/res/drawable/filter_preview_source_2.webp b/core/resources/src/main/res/drawable/filter_preview_source_2.webp new file mode 100644 index 0000000..d7864e2 Binary files /dev/null and b/core/resources/src/main/res/drawable/filter_preview_source_2.webp differ diff --git a/core/resources/src/main/res/drawable/filter_preview_source_3.webp b/core/resources/src/main/res/drawable/filter_preview_source_3.webp new file mode 100644 index 0000000..ff5d6e5 Binary files /dev/null and b/core/resources/src/main/res/drawable/filter_preview_source_3.webp differ diff --git a/core/resources/src/main/res/drawable/ic_24_barcode_scanner.xml b/core/resources/src/main/res/drawable/ic_24_barcode_scanner.xml new file mode 100644 index 0000000..f21254d --- /dev/null +++ b/core/resources/src/main/res/drawable/ic_24_barcode_scanner.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/core/resources/src/main/res/drawable/ic_launcher_foreground.xml b/core/resources/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..11a8aae --- /dev/null +++ b/core/resources/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + diff --git a/core/resources/src/main/res/drawable/ic_launcher_monochrome_24.xml b/core/resources/src/main/res/drawable/ic_launcher_monochrome_24.xml new file mode 100644 index 0000000..0a32259 --- /dev/null +++ b/core/resources/src/main/res/drawable/ic_launcher_monochrome_24.xml @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/core/resources/src/main/res/drawable/ic_logo_animated.xml b/core/resources/src/main/res/drawable/ic_logo_animated.xml new file mode 100644 index 0000000..f776bdc --- /dev/null +++ b/core/resources/src/main/res/drawable/ic_logo_animated.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + diff --git a/core/resources/src/main/res/drawable/ic_notification_icon.xml b/core/resources/src/main/res/drawable/ic_notification_icon.xml new file mode 100644 index 0000000..78d65e8 --- /dev/null +++ b/core/resources/src/main/res/drawable/ic_notification_icon.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + diff --git a/core/resources/src/main/res/drawable/image_to_text_outlined.xml b/core/resources/src/main/res/drawable/image_to_text_outlined.xml new file mode 100644 index 0000000..99c979e --- /dev/null +++ b/core/resources/src/main/res/drawable/image_to_text_outlined.xml @@ -0,0 +1,25 @@ + + + + diff --git a/core/resources/src/main/res/drawable/lookup.webp b/core/resources/src/main/res/drawable/lookup.webp new file mode 100644 index 0000000..4e4543a Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_amatorka.webp b/core/resources/src/main/res/drawable/lookup_amatorka.webp new file mode 100644 index 0000000..7a65ab5 Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_amatorka.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_bleach_bypass.webp b/core/resources/src/main/res/drawable/lookup_bleach_bypass.webp new file mode 100644 index 0000000..db5220a Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_bleach_bypass.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_candlelight.webp b/core/resources/src/main/res/drawable/lookup_candlelight.webp new file mode 100644 index 0000000..7112e47 Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_candlelight.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_celluloid.webp b/core/resources/src/main/res/drawable/lookup_celluloid.webp new file mode 100644 index 0000000..2d1d474 Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_celluloid.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_coffee.webp b/core/resources/src/main/res/drawable/lookup_coffee.webp new file mode 100644 index 0000000..647619e Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_coffee.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_drop_blues.webp b/core/resources/src/main/res/drawable/lookup_drop_blues.webp new file mode 100644 index 0000000..549fa9b Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_drop_blues.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_edgy_amber.webp b/core/resources/src/main/res/drawable/lookup_edgy_amber.webp new file mode 100644 index 0000000..0b6c791 Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_edgy_amber.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_fall_colors.webp b/core/resources/src/main/res/drawable/lookup_fall_colors.webp new file mode 100644 index 0000000..02dcd59 Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_fall_colors.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_filmstock_50.webp b/core/resources/src/main/res/drawable/lookup_filmstock_50.webp new file mode 100644 index 0000000..eeaedbd Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_filmstock_50.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_foggy_night.webp b/core/resources/src/main/res/drawable/lookup_foggy_night.webp new file mode 100644 index 0000000..a302066 Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_foggy_night.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_golden_forest.webp b/core/resources/src/main/res/drawable/lookup_golden_forest.webp new file mode 100644 index 0000000..26f53bc Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_golden_forest.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_greenish.webp b/core/resources/src/main/res/drawable/lookup_greenish.webp new file mode 100644 index 0000000..a2b886a Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_greenish.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_kodak.webp b/core/resources/src/main/res/drawable/lookup_kodak.webp new file mode 100644 index 0000000..551f1ff Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_kodak.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_miss_etikate.webp b/core/resources/src/main/res/drawable/lookup_miss_etikate.webp new file mode 100644 index 0000000..6a9d80a Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_miss_etikate.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_retro_yellow.webp b/core/resources/src/main/res/drawable/lookup_retro_yellow.webp new file mode 100644 index 0000000..d17ec12 Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_retro_yellow.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_soft_elegance_1.webp b/core/resources/src/main/res/drawable/lookup_soft_elegance_1.webp new file mode 100644 index 0000000..47e5a42 Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_soft_elegance_1.webp differ diff --git a/core/resources/src/main/res/drawable/lookup_soft_elegance_2.webp b/core/resources/src/main/res/drawable/lookup_soft_elegance_2.webp new file mode 100644 index 0000000..963d331 Binary files /dev/null and b/core/resources/src/main/res/drawable/lookup_soft_elegance_2.webp differ diff --git a/core/resources/src/main/res/drawable/mobile_screenshot.xml b/core/resources/src/main/res/drawable/mobile_screenshot.xml new file mode 100644 index 0000000..2e47f7b --- /dev/null +++ b/core/resources/src/main/res/drawable/mobile_screenshot.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/core/resources/src/main/res/drawable/multiple_image_edit.xml b/core/resources/src/main/res/drawable/multiple_image_edit.xml new file mode 100644 index 0000000..81d26f1 --- /dev/null +++ b/core/resources/src/main/res/drawable/multiple_image_edit.xml @@ -0,0 +1,37 @@ + + + + + + + + diff --git a/core/resources/src/main/res/drawable/outline_colorize_24.xml b/core/resources/src/main/res/drawable/outline_colorize_24.xml new file mode 100644 index 0000000..aca5a03 --- /dev/null +++ b/core/resources/src/main/res/drawable/outline_colorize_24.xml @@ -0,0 +1,25 @@ + + + + diff --git a/core/resources/src/main/res/drawable/outline_drag_handle_24.xml b/core/resources/src/main/res/drawable/outline_drag_handle_24.xml new file mode 100644 index 0000000..d6450dc --- /dev/null +++ b/core/resources/src/main/res/drawable/outline_drag_handle_24.xml @@ -0,0 +1,25 @@ + + + + diff --git a/core/resources/src/main/res/drawable/palette_swatch_outlined.xml b/core/resources/src/main/res/drawable/palette_swatch_outlined.xml new file mode 100644 index 0000000..641543d --- /dev/null +++ b/core/resources/src/main/res/drawable/palette_swatch_outlined.xml @@ -0,0 +1,29 @@ + + + + + diff --git a/core/resources/src/main/res/drawable/rounded_document_scanner_24.xml b/core/resources/src/main/res/drawable/rounded_document_scanner_24.xml new file mode 100644 index 0000000..11c7e3b --- /dev/null +++ b/core/resources/src/main/res/drawable/rounded_document_scanner_24.xml @@ -0,0 +1,29 @@ + + + + + + + diff --git a/core/resources/src/main/res/drawable/rounded_qr_code_scanner_24.xml b/core/resources/src/main/res/drawable/rounded_qr_code_scanner_24.xml new file mode 100644 index 0000000..c6cfc16 --- /dev/null +++ b/core/resources/src/main/res/drawable/rounded_qr_code_scanner_24.xml @@ -0,0 +1,29 @@ + + + + + + + diff --git a/core/resources/src/main/res/drawable/shape_find_in_file.xml b/core/resources/src/main/res/drawable/shape_find_in_file.xml new file mode 100644 index 0000000..86cf19a --- /dev/null +++ b/core/resources/src/main/res/drawable/shape_find_in_file.xml @@ -0,0 +1,30 @@ + + + + + + \ No newline at end of file diff --git a/core/resources/src/main/res/font/alegreya_sans_regular.ttf b/core/resources/src/main/res/font/alegreya_sans_regular.ttf new file mode 100644 index 0000000..491b883 Binary files /dev/null and b/core/resources/src/main/res/font/alegreya_sans_regular.ttf differ diff --git a/core/resources/src/main/res/font/axotrel_regular.ttf b/core/resources/src/main/res/font/axotrel_regular.ttf new file mode 100644 index 0000000..02342d0 Binary files /dev/null and b/core/resources/src/main/res/font/axotrel_regular.ttf differ diff --git a/core/resources/src/main/res/font/bad_script_regular.ttf b/core/resources/src/main/res/font/bad_script_regular.ttf new file mode 100644 index 0000000..35e3ad6 Binary files /dev/null and b/core/resources/src/main/res/font/bad_script_regular.ttf differ diff --git a/core/resources/src/main/res/font/cattedrale_regular.ttf b/core/resources/src/main/res/font/cattedrale_regular.ttf new file mode 100644 index 0000000..a9d4edf Binary files /dev/null and b/core/resources/src/main/res/font/cattedrale_regular.ttf differ diff --git a/core/resources/src/main/res/font/caveat_regular.ttf b/core/resources/src/main/res/font/caveat_regular.ttf new file mode 100644 index 0000000..9654095 Binary files /dev/null and b/core/resources/src/main/res/font/caveat_regular.ttf differ diff --git a/core/resources/src/main/res/font/caveat_variable.ttf b/core/resources/src/main/res/font/caveat_variable.ttf new file mode 100644 index 0000000..c083501 Binary files /dev/null and b/core/resources/src/main/res/font/caveat_variable.ttf differ diff --git a/core/resources/src/main/res/font/comfortaa_regular.ttf b/core/resources/src/main/res/font/comfortaa_regular.ttf new file mode 100644 index 0000000..6023bde Binary files /dev/null and b/core/resources/src/main/res/font/comfortaa_regular.ttf differ diff --git a/core/resources/src/main/res/font/comfortaa_varibale.ttf b/core/resources/src/main/res/font/comfortaa_varibale.ttf new file mode 100644 index 0000000..c1135a8 Binary files /dev/null and b/core/resources/src/main/res/font/comfortaa_varibale.ttf differ diff --git a/core/resources/src/main/res/font/dejavu_regular.ttf b/core/resources/src/main/res/font/dejavu_regular.ttf new file mode 100644 index 0000000..999bac7 Binary files /dev/null and b/core/resources/src/main/res/font/dejavu_regular.ttf differ diff --git a/core/resources/src/main/res/font/frm32_regular.ttf b/core/resources/src/main/res/font/frm32_regular.ttf new file mode 100644 index 0000000..67eac92 Binary files /dev/null and b/core/resources/src/main/res/font/frm32_regular.ttf differ diff --git a/core/resources/src/main/res/font/granite_fixed_regular.ttf b/core/resources/src/main/res/font/granite_fixed_regular.ttf new file mode 100644 index 0000000..c111ea2 Binary files /dev/null and b/core/resources/src/main/res/font/granite_fixed_regular.ttf differ diff --git a/core/resources/src/main/res/font/handjet_varibale.ttf b/core/resources/src/main/res/font/handjet_varibale.ttf new file mode 100644 index 0000000..5a14893 Binary files /dev/null and b/core/resources/src/main/res/font/handjet_varibale.ttf differ diff --git a/core/resources/src/main/res/font/jura_variable.ttf b/core/resources/src/main/res/font/jura_variable.ttf new file mode 100644 index 0000000..1f61786 Binary files /dev/null and b/core/resources/src/main/res/font/jura_variable.ttf differ diff --git a/core/resources/src/main/res/font/lcd_moving_regular.ttf b/core/resources/src/main/res/font/lcd_moving_regular.ttf new file mode 100644 index 0000000..028589f Binary files /dev/null and b/core/resources/src/main/res/font/lcd_moving_regular.ttf differ diff --git a/core/resources/src/main/res/font/lcd_octagon_regular.ttf b/core/resources/src/main/res/font/lcd_octagon_regular.ttf new file mode 100644 index 0000000..67d53ec Binary files /dev/null and b/core/resources/src/main/res/font/lcd_octagon_regular.ttf differ diff --git a/core/resources/src/main/res/font/minecraft_gnu_regular.ttf b/core/resources/src/main/res/font/minecraft_gnu_regular.ttf new file mode 100644 index 0000000..bb32b36 Binary files /dev/null and b/core/resources/src/main/res/font/minecraft_gnu_regular.ttf differ diff --git a/core/resources/src/main/res/font/montserrat_regular.ttf b/core/resources/src/main/res/font/montserrat_regular.ttf new file mode 100644 index 0000000..aa9033a Binary files /dev/null and b/core/resources/src/main/res/font/montserrat_regular.ttf differ diff --git a/core/resources/src/main/res/font/montserrat_variable.ttf b/core/resources/src/main/res/font/montserrat_variable.ttf new file mode 100644 index 0000000..656db66 Binary files /dev/null and b/core/resources/src/main/res/font/montserrat_variable.ttf differ diff --git a/core/resources/src/main/res/font/nokia_pixel_regular.ttf b/core/resources/src/main/res/font/nokia_pixel_regular.ttf new file mode 100644 index 0000000..998b07f Binary files /dev/null and b/core/resources/src/main/res/font/nokia_pixel_regular.ttf differ diff --git a/core/resources/src/main/res/font/nothing_font_regular.ttf b/core/resources/src/main/res/font/nothing_font_regular.ttf new file mode 100644 index 0000000..b14c402 Binary files /dev/null and b/core/resources/src/main/res/font/nothing_font_regular.ttf differ diff --git a/core/resources/src/main/res/font/nunito_variable.ttf b/core/resources/src/main/res/font/nunito_variable.ttf new file mode 100644 index 0000000..0a00f63 Binary files /dev/null and b/core/resources/src/main/res/font/nunito_variable.ttf differ diff --git a/core/resources/src/main/res/font/podkova_variable.ttf b/core/resources/src/main/res/font/podkova_variable.ttf new file mode 100644 index 0000000..258e047 Binary files /dev/null and b/core/resources/src/main/res/font/podkova_variable.ttf differ diff --git a/core/resources/src/main/res/font/ruslan_display_regular.ttf b/core/resources/src/main/res/font/ruslan_display_regular.ttf new file mode 100644 index 0000000..451a714 Binary files /dev/null and b/core/resources/src/main/res/font/ruslan_display_regular.ttf differ diff --git a/core/resources/src/main/res/font/tektur_variable.ttf b/core/resources/src/main/res/font/tektur_variable.ttf new file mode 100644 index 0000000..9dcdc0f Binary files /dev/null and b/core/resources/src/main/res/font/tektur_variable.ttf differ diff --git a/core/resources/src/main/res/font/tokeely_brookings_regular.ttf b/core/resources/src/main/res/font/tokeely_brookings_regular.ttf new file mode 100644 index 0000000..fd7035a Binary files /dev/null and b/core/resources/src/main/res/font/tokeely_brookings_regular.ttf differ diff --git a/core/resources/src/main/res/font/unisource_regular.ttf b/core/resources/src/main/res/font/unisource_regular.ttf new file mode 100644 index 0000000..2899b06 Binary files /dev/null and b/core/resources/src/main/res/font/unisource_regular.ttf differ diff --git a/core/resources/src/main/res/font/wopr_tweaked_regular.ttf b/core/resources/src/main/res/font/wopr_tweaked_regular.ttf new file mode 100644 index 0000000..781e6f4 Binary files /dev/null and b/core/resources/src/main/res/font/wopr_tweaked_regular.ttf differ diff --git a/core/resources/src/main/res/font/ysabeau_sc_variable.ttf b/core/resources/src/main/res/font/ysabeau_sc_variable.ttf new file mode 100644 index 0000000..ca7263d Binary files /dev/null and b/core/resources/src/main/res/font/ysabeau_sc_variable.ttf differ diff --git a/core/resources/src/main/res/font/ztivalia_regular.ttf b/core/resources/src/main/res/font/ztivalia_regular.ttf new file mode 100644 index 0000000..8bb150f Binary files /dev/null and b/core/resources/src/main/res/font/ztivalia_regular.ttf differ diff --git a/core/resources/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/core/resources/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..a5e7837 --- /dev/null +++ b/core/resources/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/core/resources/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/core/resources/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..a5e7837 --- /dev/null +++ b/core/resources/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/core/resources/src/main/res/mipmap-hdpi/ic_launcher.webp b/core/resources/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..986c3af Binary files /dev/null and b/core/resources/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/core/resources/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/core/resources/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..729a2f5 Binary files /dev/null and b/core/resources/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/core/resources/src/main/res/mipmap-mdpi/ic_launcher.webp b/core/resources/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..2d8a497 Binary files /dev/null and b/core/resources/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/core/resources/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/core/resources/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..3a67cbd Binary files /dev/null and b/core/resources/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/core/resources/src/main/res/mipmap-xhdpi/ic_launcher.webp b/core/resources/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..295e060 Binary files /dev/null and b/core/resources/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/core/resources/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/core/resources/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..05b5e71 Binary files /dev/null and b/core/resources/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/core/resources/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/core/resources/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..394bdda Binary files /dev/null and b/core/resources/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/core/resources/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/core/resources/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..55afbd3 Binary files /dev/null and b/core/resources/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/core/resources/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/core/resources/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..b02c57d Binary files /dev/null and b/core/resources/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/core/resources/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/core/resources/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..a6cca1c Binary files /dev/null and b/core/resources/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/core/resources/src/main/res/raw/keep.xml b/core/resources/src/main/res/raw/keep.xml new file mode 100644 index 0000000..8a40a98 --- /dev/null +++ b/core/resources/src/main/res/raw/keep.xml @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/core/resources/src/main/res/raw/roboto_bold.ttf b/core/resources/src/main/res/raw/roboto_bold.ttf new file mode 100644 index 0000000..4658f9a Binary files /dev/null and b/core/resources/src/main/res/raw/roboto_bold.ttf differ diff --git a/core/resources/src/main/res/values-ar/strings.xml b/core/resources/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000..bbba55f --- /dev/null +++ b/core/resources/src/main/res/values-ar/strings.xml @@ -0,0 +1,3744 @@ + + + + + اللغة + وضع 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 + مركز تمويه y + تمويه التقريب + عتبة النصوع + الرسم + لون الطلاء + لقد عطلت تطبيق الملفات، فعّله لاستخدام هذه الميزة + ارسم على صورة كما في دفتر الرسم، أو ارسم على الخلفية نفسها + طلاء ألفا + ارسم على صورة + اختيار صورة وارسم شيئًا عليها + ارسم على خلفية + اختيار لون الخلفية وارسم فوقها + لون الخلفية + الشفرة + تعمية وفك تعمية أي ملف (ليس فقط الصورة) بناءً على مختلف خوارزميات التعمية المتاحة + اختر الملف + تشفير + فك تشفير + اختر ملف للبدء + فك التشفير + التشفير + مفتاح + متابعة الملف + خزّن هذا الملف على جهازك أو استخدم إجراء المشاركة لوضعه في أي مكان تريده + سمات + يُرجى ملاحظة أن التوافق مع برامج أو خدمات تعمية الملفات الأخرى غير مضمون. قد يكون هناك اختلاف طفيف في معالجة المفتاح أو تكوين التشفير من أسباب عدم التوافق. + تطبيق + التوافق + تشفير الملفات على أساس كلمة المرور. يمكن تخزين الملفات التي تمت متابعتها في دليل 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 + أدوات JXL + اجري تحويل بدون فقدان الترميز من 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 والصور الأخرى التي لم يتم التعرف عليها كصور على أندرويد. بدون إذن، يتعذر على 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 + الارتباطات التشعبية + مرفقات الملفات + خطوط + النوافذ المنبثقة + الطوابع + الأشكال + ملاحظات نصية + ترميز النص + حقول النموذج + العلامات + مجهول + الشروح + فك التجميع + أضف ظلًا ضبابيًا خلف الطبقة مع لون وإزاحات قابلة للتكوين + تشويه المحتوى المدرك + لا توجد ذاكرة كافية لإكمال هذا الإجراء. يُرجى محاولة استخدام صورة أصغر أو إغلاق التطبيقات الأخرى أو إعادة تشغيل التطبيق. + العمال الموازيون + لم يتم حفظ هذه الصور لأن الملفات المحولة ستكون أكبر من الملفات الأصلية. تحقق من هذه القائمة قبل حذف الصور الأصلية. + الملفات التي تم تخطيها: %1$s + سجلات التطبيقات + عرض سجلات التطبيق لاكتشاف المشكلات + المفضلة في الأدوات المجمعة + إضافة المفضلة كعلامة تبويب عندما يتم تجميع الأدوات حسب النوع + إظهار المفضلة كآخر + ينقل علامة تبويب الأدوات المفضلة إلى النهاية + التباعد الأفقي + تباعد عمودي + طاقة متخلفة + استخدم خريطة طاقة بسيطة ذات حجم متدرج بدلاً من خوارزمية الطاقة الأمامية + إزالة المنطقة المقنعة + سوف تمر الطبقات عبر المنطقة المقنعة، مما يؤدي إلى إزالة المنطقة المحددة فقط بدلاً من حمايتها + في إتش إس إن تي إس سي + إعدادات NTSC المتقدمة + ضبط تناظري إضافي لمقاطع الفيديو VHS والبث الفني الأقوى + نزيف كروما + ارتداء الشريط + تتبع + مسحة لوما + رنين + ثلج + استخدم الحقل + نوع الفلتر + إدخال مرشح لمى + إدخال صفاء Lowpass + إزالة التشكيل الكروما + تحول المرحلة + إزاحة المرحلة + تبديل الرأس + ارتفاع تبديل الرأس + إزاحة تبديل الرأس + تحول تبديل الرأس + موقف خط الوسط + غضب منتصف الخط + تتبع ارتفاع الضوضاء + موجة التتبع + تتبع الثلوج + تتبع تباين الثلوج + تتبع الضوضاء + تتبع شدة الضوضاء + الضوضاء المركبة + تردد الضوضاء المركب + شدة الضوضاء المركبة + تفاصيل الضوضاء المركبة + تردد الرنين + قوة الرنين + ضجيج لوما + تردد الضوضاء لوما + شدة الضوضاء لوما + تفاصيل الضوضاء لوما + ضجيج كروما + تردد الضوضاء كروما + شدة الضوضاء كروما + تفاصيل الضوضاء كروما + شدة الثلوج + تباين الثلوج + ضجيج مرحلة الكروما + خطأ في مرحلة الكروما + تأخير الصفاء الأفقي + تأخير صفاء عمودي + سرعة شريط VHS + فقدان صفاء VHS + VHS شحذ شدة + VHS شحذ التردد + شدة موجة حافة VHS + سرعة موجة حافة VHS + تردد موجة حافة VHS + تفاصيل موجة حافة VHS + صفاء الإخراج lowpass + مقياس أفقي + مقياس عمودي + عامل القياس X + عامل القياس Y + يكتشف العلامات المائية الشائعة للصور ويرسمها باستخدام LaMa. يقوم بتنزيل نماذج الكشف والرسم تلقائيًا + عمى مزدوج + قم بتوسيع الصورة + مزيل الخلفية القائم على تجزئة الكائنات باستخدام تجزئة YOLO v11 + رموز تعبيرية متحركة + أظهر الرموز التعبيرية المتاحة كرسوم متحركة + عرض زاوية الخط + يظهر دوران الخط الحالي بالدرجات أثناء الرسم + حفظ إلى المجلد الأصلي + احفظ الملفات الجديدة بجوار الملف الأصلي بدلاً من المجلد المحدد + العلامة المائية PDF + لا توجد ذاكرة كافية + من المحتمل أن تكون الصورة كبيرة جدًا بحيث لا يمكن معالجتها على هذا الجهاز، أو أن الذاكرة المتوفرة بالنظام نفدت. حاول تقليل دقة الصورة أو إغلاق التطبيقات الأخرى أو اختيار ملف أصغر. + قائمة التصحيح + قائمة لاختبار وظائف التطبيق، وليس المقصود أن تظهر في إصدار الإنتاج + مزود + يتطلب PaddleOCR نماذج ONNX إضافية على جهازك. هل تريد تنزيل بيانات %1$s؟ + عالمي + كوري + اللاتينية + السلافية الشرقية + التايلاندية + اليونانية + إنجليزي + السيريلية + عربي + الديفاناغارية + التاميل + التيلجو + شادر + تظليل مسبقا + لم يتم تحديد تظليل + ستوديو شادر + أنشئ وحرِّر والتحقق من صحة واستيراد وتصدير تظليل الأجزاء المخصّصة + تظليل المحفوظة + افتح التظليلات المحفوظة أو قم بتكرارها أو تصديرها أو مشاركتها أو حذفها + تظليل جديد + ملف شادر + .itshader JSON + %1$d المعلمات + مصدر شادر + اكتب فقط نص الفراغ الرئيسي (). استخدم تنسيق الملمس لإحداثيات الأشعة فوق البنفسجية وInputImageTexture كعينة مصدر. + وظائف + يتم إدراج الوظائف المساعدة الاختيارية والثوابت والبنيات قبل الفراغ الرئيسي (). يتم إنشاء الزي الرسمي من المعلمات. + أضف الزي الرسمي هنا عندما يحتاج التظليل إلى قيم قابلة للتحرير. + إعادة تعيين التظليل + سيتم مسح مسودة التظليل الحالية + تظليل غير صالح + إصدار تظليل غير مدعوم %1$d. الإصدار المدعوم: %2$d. + يجب ألا يكون اسم التظليل فارغًا. + يجب ألا يكون مصدر التظليل فارغًا. + يحتوي مصدر التظليل على أحرف غير مدعومة: %1$s. استخدم مصدر ASCII GLSL فقط. + تم الإعلان عن المعلمة \\\"%1$s\\\" أكثر من مرة. + يجب ألا تكون أسماء المعلمات فارغة. + تستخدم المعلمة \\\"%1$s\\\" اسم GPUImage محجوزًا. + يجب أن تكون المعلمة \\\"%1$s\\\" معرف GLSL صالحًا. + المعلمة \\\"%1$s\\\" لها %2$s نوع القيمة \\\"%3$s\\\"، المتوقع \\\"%4$s\\\". + لا يمكن للمعلمة \\\"%1$s\\\" تعريف الحد الأدنى أو الحد الأقصى للقيم المنطقية. + تحتوي المعلمة \\\"%1$s\\\" على الحد الأدنى أكبر من الحد الأقصى. + المعلمة \\\"%1$s\\\" الافتراضية أقل من الحد الأدنى. + المعلمة \\\"%1$s\\\" الافتراضية أكبر من الحد الأقصى. + يجب أن يعلن Shader عن \\\"uniform Sampler2D %1$s;\\\". + يجب أن يعلن Shader \\\"Varying vec2 %1$s;\\\". + يجب أن يحدد التظليل \\\"void main()\\\". + يجب على Shader كتابة لون إلى gl_FragColor. + تظليل ShaderToy mainImage غير مدعوم. استخدم العقد الرئيسي () الفارغ الخاص بـ GPUImage. + الرسوم المتحركة ShaderToy الموحدة \\\"iTime\\\" غير مدعومة. + الرسوم المتحركة ShaderToy الموحدة \\\"iFrame\\\" غير مدعومة. + الزي الرسمي ShaderToy \\\"iResolution\\\" غير مدعوم. + مواد ShaderToy iChannel غير مدعومة. + القوام الخارجي غير مدعوم. + ملحقات النسيج الخارجية غير مدعومة. + معلمات تظليل Libretro غير مدعومة. + يتم دعم نسيج إدخال واحد فقط. قم بإزالة \\\"الزي الرسمي %1$s %2$s;\\\". + يجب تعريف المعلمة \\\"%1$s\\\" كـ \\\"موحدة %2$s %1$s;\\\". + تم تعريف المعلمة \\\"%1$s\\\" كـ \\\"موحدة %2$s %1$s;\\\"، متوقعة \\\"موحدة %3$s %1$s;\\\". + ملف التظليل فارغ. + يجب أن يكون ملف Shader عبارة عن كائن ‎.itshader JSON يحتوي على حقول الإصدار والاسم والتظليل. + ملف Shader غير صالح بتنسيق JSON أو لا يتطابق مع تنسيق .itshader. + الحقل \\\"%1$s\\\" مطلوب. + %1$s مطلوب. + %1$s \\\"%2$s\\\" غير مدعوم. الأنواع المدعومة: %3$s. + %1$s مطلوب لمعلمات \\\"%2$s\\\". + %1$s يجب أن يكون عددًا محدودًا. + %1$s يجب أن يكون عددًا صحيحًا. + %1$s يجب أن يكون صحيحًا أو خطأ. + يجب أن يكون %1$s عبارة عن سلسلة ألوان، أو مصفوفة RGB/RGBA، أو كائن لون. + يجب أن يستخدم %1$s التنسيق #RRGGBB أو #RRGGBBAA. + يجب أن يحتوي %1$s على قنوات ألوان سداسية عشرية صالحة. + %1$s يجب أن يحتوي على 3 أو 4 قنوات ألوان. + يجب أن يكون %1$s بين 0 و255. + يجب أن يكون %1$s عبارة عن مصفوفة مكونة من رقمين أو كائن يحتوي على x وy. + %1$s يجب أن يحتوي على رقمين بالضبط. + كرّر + امسح ملف EXIF دائمًا + أزِل بيانات EXIF الخاصة بالصورة عند الحفظ، حتى عندما تطلب الأداة الاحتفاظ بالبيانات التعريفية + مساعدة ونصائح + %1$d برامج تعليمية + أداة مفتوحة + تعرف على الأدوات الرئيسية وخيارات التصدير وتدفقات PDF وأدوات الألوان المساعدة وإصلاحات المشكلات الشائعة + ابدء + اختر الأدوات، واستورد الملفات، واحفظ النتائج، وأعد استخدام الإعدادات بشكل أسرع + تحرير الصور + تغيير حجم الصور واقتصاصها وتصفيتها ومسحها ورسمها ووضع علامة مائية عليها + الملفات والبيانات الوصفية + تحويل التنسيقات ومقارنة المخرجات وحماية الخصوصية والتحكم في أسماء الملفات + قوات الدفاع الشعبي والوثائق + أنشئ ملفات PDF ومسح المستندات ضوئيًا وصفحات التعرف الضوئي على الحروف وإعداد الملفات للمشاركة + النص، QR، والبيانات + التعرف على النص، ورموز المسح الضوئي، وترميز Base64، والتحقق من التجزئة، وملفات الحزمة + أدوات اللون + اختر الألوان، وأنشئ اللوحات، واستكشف مكتبات الألوان، وأنشئ التدرجات اللونية + أدوات إبداعية + ولّد رسومات SVG وASCII وأنسجة الضوضاء والتظليل والمرئيات التجريبية + استكشاف الأخطاء وإصلاحها + إصلاح مشكلات الملفات الثقيلة والشفافية والمشاركة والاستيراد وإعداد التقارير + اختر الأداة المناسبة + استخدم الفئات والبحث والمفضلات ومشاركة الإجراءات للبدء في المكان المناسب + ابدأ من أفضل نقطة دخول + يحتوي Image Toolbox على العديد من الأدوات المركزة، والعديد منها يتداخل للوهلة الأولى. ابدأ من المهمة التي ترغب في إنهائها، ثم اختر الأداة التي تتحكم في الجزء الأكثر أهمية من النتيجة: الحجم أو التنسيق أو البيانات الوصفية أو النص أو صفحات PDF أو الألوان أو التخطيط. + استخدم البحث عندما تعرف اسم الإجراء، مثل تغيير الحجم أو PDF أو EXIF أو OCR أو QR أو اللون. + افتح فئة عندما تعرف نوع المهمة فقط، ثم ثبّت الأدوات المتكرّرة كمفضلة. + عندما يقوم تطبيق آخر بمشاركة صورة في Image Toolbox، اختر الأداة من تدفق ورقة المشاركة. + استيراد النتائج وحفظها ومشاركتها + افهم التدفق المعتاد بدءًا من انتقاء المدخلات وحتى تصدير الملف الذي تم تحريره + اتبع تدفق التحرير المشترك + تتبع معظم الأدوات نفس الإيقاع: اختر الإدخال، واضبط الخيارات، وقم بالمعاينة، ثم احفظ أو شارك. التفاصيل المهمة هي أن الحفظ ينشئ ملف إخراج، بينما تكون المشاركة عادةً هي الأفضل للتسليم السريع إلى تطبيق آخر. + اختر ملفًا واحدًا لأدوات الصورة الواحدة أو عدة ملفات للأدوات المجمعة عندما يسمح المنتقي بذلك. + تحقق من عناصر التحكم في المعاينة والإخراج قبل الحفظ، وخاصة خيارات التنسيق والجودة واسم الملف. + استخدم المشاركة للتسليم السريع، أو احفظ عندما تحتاج إلى نسخة دائمة في المجلد المحدّد. + العمل مع العديد من الصور في وقت واحد + تعد الأدوات المجمعة هي الأفضل لمهام تغيير الحجم والتحويل والضغط والتسمية المتكررة + إعداد دفعة يمكن التنبؤ بها + تكون المعالجة المجمعة هي الأسرع عندما يجب أن يتبع كل مخرج نفس القواعد. كما أنه المكان الأسهل لارتكاب خطأ كبير، لذا اختر التسمية والجودة والبيانات التعريفية وسلوك المجلد قبل بدء عملية تصدير طويلة. + حدّد جميع الصور ذات الصلة معًا واحتفظ بالترتيب إذا كان الإخراج يعتمد على التسلسل. + عيّن قواعد تغيير الحجم والتنسيق والجودة وبيانات التعريف واسم الملف مرة واحدة قبل بدء التصدير. + شغّل مجموعة اختبار صغيرة أولاً عندما تكون الملفات المصدر كبيرة أو عندما يكون التنسيق المستهدف جديدًا بالنسبة لك. + تحرير واحد سريع + استخدم المحرر الشامل عندما تحتاج إلى عدة تغييرات بسيطة على صورة واحدة + أنهِ صورة واحدة دون التنقل بين الأدوات + يعد التحرير الفردي مفيدًا عندما تحتاج الصورة إلى سلسلة صغيرة من التغييرات بدلاً من عملية واحدة متخصصة. عادةً ما يكون أسرع في التنظيف السريع، ومعاينتها، وتصدير نسخة نهائية واحدة دون إنشاء سير عمل مجمع. + افتح تحريرًا فرديًا لصورة واحدة تحتاج إلى تعديلات أو علامات أو اقتصاص أو تدوير أو تغييرات شائعة. + طبّق التعديلات بالترتيب الذي يغير أقل عدد من وحدات البكسل أولاً، مثل الاقتصاص قبل المرشحات والمرشحات قبل الضغط النهائي. + استخدم أداة متخصّصة بدلاً من ذلك عندما تحتاج إلى معالجة مجمعة أو أهداف محدّدة لحجم الملف أو إخراج PDF أو التعرف الضوئي على الحروف أو تنظيف البيانات التعريفية. + استخدم الإعدادات المسبقة والإعدادات + احفظ الاختيارات المتكررة وقم بضبط التطبيق ليناسب سير العمل المفضل لديك + تجنب تكرار نفس الإعداد + تكون الإعدادات المسبقة والإعدادات مفيدة عندما تقوم بتصدير نفس النوع من الملفات بشكل متكرر. يؤدي الإعداد المسبق الجيد إلى تحويل قائمة التحقق اليدوية المتكررة إلى نقرة واحدة، بينما تحدد الإعدادات العامة الإعدادات الافتراضية التي تبدأ بها الأدوات الجديدة. + أنشئ إعدادات مسبقة لأحجام المخرجات أو التنسيقات أو قيم الجودة أو أنماط التسمية الشائعة. + راجع الإعدادات لمعرفة التنسيق الافتراضي والجودة وحفظ المجلد واسم الملف والمنتقي وسلوك الواجهة. + انسخ احتياطيًا من الإعدادات قبل إعادة تثبيت التطبيق أو الانتقال إلى جهاز آخر. + عيّن الإعدادات الافتراضية المفيدة + اختر التنسيق الافتراضي والجودة ووضع القياس ونوع تغيير الحجم ومساحة اللون مرة واحدة + اجعل الأدوات الجديدة تبدأ أقرب إلى هدفك + القيم الافتراضية ليست مجرد تفضيلات تجميلية. وهم يقررون التنسيق والجودة وسلوك تغيير الحجم ووضع القياس ومساحة اللون التي تظهر أولاً في العديد من الأدوات، مما يساعد على تجنب إعادة تحديد نفس الاختيارات في كل عملية تصدير. + عيّن تنسيق الصورة الافتراضي وجودتها لتتناسب مع وجهتك المعتادة، مثل JPEG للصور أو PNG/WebP لـ alpha. + اضبط نوع تغيير الحجم الافتراضي ووضع القياس إذا كنت تقوم في كثير من الأحيان بالتصدير لأبعاد صارمة أو منصات اجتماعية أو صفحات مستندات. + غيّر الإعدادات الافتراضية لمساحة اللون فقط عندما يحتاج سير عملك إلى معالجة ألوان متسقة عبر شاشات العرض أو الطباعة أو المحررات الخارجية. + المنتقى وقاذفة + استخدم إعدادات المنتقي عندما يفتح التطبيق الكثير من مربعات الحوار أو يبدأ في المكان الخطأ + اجعل فتح الملفات مطابقًا لعادتك + تتحكم إعدادات المنتقي والمشغل فيما إذا كان Image Toolbox يبدأ من الشاشة الرئيسية، أو من أداة، أو من تدفق تحديد الملف. تكون هذه الخيارات مفيدة بشكل خاص عندما تدخل عادةً من ورقة مشاركة Android أو عندما تريد أن يبدو التطبيق وكأنه مشغل أدوات. + استخدم إعدادات منتقي الصور إذا كان منتقي النظام يخفي الملفات، أو يعرض عددًا كبيرًا جدًا من العناصر الحديثة، أو يتعامل مع الملفات السحابية بشكل سيئ. + فعِّل تخطي الانتقاء فقط للأدوات التي تدخل إليها عادةً من المشاركة أو تعرف الملف المصدر بالفعل. + راجع وضع المشغل إذا كنت تريد أن يتصرف Image Toolbox مثل منتقي الأدوات أكثر من كونه شاشة رئيسية عادية. + مصدر الصورة + اختر وضع المنتقي الذي يعمل بشكل أفضل مع المعارض ومديري الملفات والملفات السحابية + اختر الملفات من المصدر الصحيح + تؤثر إعدادات مصدر الصورة على كيفية طلب التطبيق من Android للصور. يعتمد الخيار الأفضل على ما إذا كانت ملفاتك موجودة في معرض النظام، أو مجلد مدير الملفات، أو موفر السحابة، أو أي تطبيق آخر يشارك محتوى مؤقتًا. + استخدم المنتقي الافتراضي عندما تريد تجربة النظام الأكثر تكاملاً لصور المعرض الشائعة. + بدّل وضع المنتقي إذا لم تظهر الألبومات أو المجلدات أو الملفات السحابية أو التنسيقات غير القياسية في المكان الذي تتوقعه. + إذا اختفى ملف مشترك بعد مغادرة التطبيق المصدر، فأعد فتحه من خلال منتقي دائم أو احفظ نسخة محلية أولاً. + المفضلة ومشاركة الأدوات + حافظ على تركيز الشاشة الرئيسية وقم بإخفاء الأدوات التي لا معنى لها في تدفقات المشاركة + تقليل ضجيج قائمة الأدوات + تعد الإعدادات المفضلة والمخفية للمشاركة مفيدة عندما تستخدم عددًا قليلاً من الأدوات كل يوم. كما أنها تحافظ على عملية تدفق المشاركة عن طريق إخفاء الأدوات التي لا يمكنها استخدام نوع الملف الذي يرسله تطبيق آخر. + قم بتثبيت الأدوات المتكررة حتى يظل من السهل الوصول إليها حتى عندما يتم تجميع الأدوات حسب الفئة. + إخفاء الأدوات من تدفقات المشاركة عندما لا تكون ذات صلة بالملفات القادمة من تطبيق آخر. + استخدم إعدادات الترتيب المفضلة إذا كنت تفضل المفضلة في النهاية أو مجمعة مع الأدوات ذات الصلة. + إعدادات النسخ الاحتياطي + انقل الإعدادات المسبقة والتفضيلات وسلوك التطبيق إلى تثبيت آخر بأمان + احتفظ بإعداداتك المحمولة + يعد النسخ الاحتياطي والاستعادة مفيدًا للغاية بعد ضبط الإعدادات المسبقة والمجلدات وقواعد أسماء الملفات وترتيب الأدوات والتفضيلات المرئية. تتيح لك النسخة الاحتياطية تجربة الإعدادات أو إعادة تثبيت التطبيق دون إعادة بناء سير العمل من الذاكرة. + قم بإنشاء نسخة احتياطية بعد تغيير الإعدادات المسبقة أو سلوك اسم الملف أو التنسيقات الافتراضية أو ترتيب الأداة. + قم بتخزين النسخة الاحتياطية في مكان ما خارج مجلد التطبيق إذا كنت تخطط لمسح البيانات أو إعادة التثبيت أو نقل الأجهزة. + قم باستعادة الإعدادات قبل معالجة الدُفعات المهمة بحيث تتوافق الإعدادات الافتراضية وسلوك الحفظ مع الإعداد القديم. + تغيير حجم الصور وتحويلها + تغيير الحجم والتنسيق والجودة والبيانات الوصفية لصورة واحدة أو أكثر + إنشاء نسخ تم تغيير حجمها + يعد تغيير الحجم والتحويل الأداة الرئيسية للأبعاد المتوقعة وملفات الإخراج الأخف. استخدمه عندما يكون حجم الصورة وتنسيق الإخراج وسلوك البيانات التعريفية مهمًا في نفس الوقت. + اختر صورة واحدة أو أكثر، ثم اختر ما إذا كانت الأبعاد أو المقياس أو حجم الملف هو الأكثر أهمية. + حدّد تنسيق الإخراج والجودة؛ استخدم PNG أو WebP عندما يجب الحفاظ على الشفافية. + معاينة النتيجة، ثم احفظ النُسخ التي تم أُنشئت أو مشاركتها دون تغيير النسخ الأصلية. + تغيير الحجم حسب حجم الملف + استهدف حدًا للحجم عندما يرفض موقع ويب أو برنامج مراسلة أو نموذج الصور الثقيلة + تناسب حدود التحميل الصارمة + يعد تغيير الحجم حسب حجم الملف أفضل من تخمين قيم الجودة عندما تقبل الوجهة فقط الملفات التي تقل عن حد معين. قد تقوم الأداة بضبط الضغط والأبعاد للوصول إلى الهدف أثناء محاولة إبقاء النتيجة قابلة للاستخدام. + أدخل الحجم المستهدف أقل بقليل من حد التحميل الحقيقي لإفساح المجال للبيانات الوصفية وتغييرات الموفر. + تفضيل التنسيقات المفقودة للصور عندما يكون الهدف صغيرًا؛ يمكن أن يظل حجم PNG كبيرًا حتى بعد تغيير حجمه. + افحص الوجوه والنصوص والحواف بعد التصدير لأن أهداف الحجم الكبير يمكن أن تقدم عناصر مرئية. + الحد من تغيير الحجم + قم بتقليص الصور التي تتجاوز الحد الأقصى للعرض أو الارتفاع أو قيود الملف فقط + تجنب تغيير حجم الصور التي هي جيدة بالفعل + يعتبر Limit Resize مفيدًا للمجلدات المختلطة حيث تكون بعض الصور ضخمة والبعض الآخر معدة بالفعل. فبدلاً من فرض نفس تغيير الحجم على كل ملف، فإنه يحافظ على الصور المقبولة أقرب إلى جودتها الأصلية. + قم بتعيين الحد الأقصى للأبعاد أو الحدود بناءً على الوجهة بدلاً من تغيير حجم كل ملف بشكل أعمى. + قم بتمكين سلوك نمط التخطي إذا كان أكبر عندما تريد تجنب المخرجات التي تصبح أثقل من المصادر. + استخدم هذا للدفعات من الكاميرات أو لقطات الشاشة أو التنزيلات المختلفة حيث تختلف أحجام المصدر كثيرًا. + الاقتصاص والتدوير والتسوية + قم بتأطير المنطقة المهمة قبل تصدير الصورة أو استخدامها في أدوات أخرى + تنظيف التكوين + يؤدي الاقتصاص أولاً إلى جعل المرشحات اللاحقة والتعرف الضوئي على الحروف وصفحات PDF ونتائج المشاركة أكثر وضوحًا. + افتح \"اقتصاص\" واختر الصورة التي تريد تعديلها. + استخدم الاقتصاص الحر أو نسبة العرض إلى الارتفاع عندما يجب أن يتناسب الإخراج مع منشور أو مستند أو صورة رمزية. + قم بالتدوير أو الوجه قبل الحفظ حتى تتلقى الأدوات اللاحقة الصورة المصححة. + تطبيق المرشحات والتعديلات + أنشئ مجموعة مرشحات قابلة للتحرير وقم بمعاينة النتيجة قبل التصدير + ضبط مظهر الصورة + تعتبر المرشحات مفيدة للتصحيحات السريعة والإخراج المنمق وإعداد الصور للتعرف الضوئي على الحروف أو الطباعة. يمكن أن تكون التغييرات الصغيرة في التباين والحدة والسطوع أكثر أهمية من التأثيرات الثقيلة عندما تحتوي الصورة على نص أو تفاصيل دقيقة. + أضف المرشحات واحدًا تلو الآخر وراقب المعاينة بعد كل تغيير. + اضبط السطوع أو التباين أو الحدة أو التمويه أو اللون أو الأقنعة حسب المهمة. + صدِّر فقط عندما تتطابق المعاينة مع جهازك المستهدف أو المستند أو النظام الأساسي الاجتماعي. + المعاينة أولا + افحص الصور قبل اختيار أداة تحرير أو تحويل أو تنظيف أكثر ثقلاً + تحقق مما تلقيته بالفعل + تكون معاينة الصورة مفيدة عندما يأتي ملف من الدردشة أو التخزين السحابي أو تطبيق آخر ولا تكون متأكدًا مما يحتوي عليه. يساعد التحقق من الأبعاد والشفافية والاتجاه والجودة المرئية أولاً على اختيار أداة المتابعة الصحيحة. + افتح Image Preview عندما تحتاج إلى فحص عدة صور قبل أن تقرر ما يجب فعله بها. + ابحث عن مشاكل التدوير والمناطق الشفافة ومؤثرات الضغط والأبعاد الكبيرة بشكل غير متوقع. + انتقل إلى تغيير الحجم أو الاقتصاص أو المقارنة أو EXIF أو مزيل الخلفية فقط بعد أن تعرف ما يحتاج إلى إصلاح. + إزالة الخلفية أو استعادتها + امحُ الخلفيات تلقائيًا أو تحسين الحواف يدويًا باستخدام أدوات الاستعادة + احتفظ بالموضوع واحذف الباقي + تعمل إزالة الخلفية بشكل أفضل عند الجمع بين التحديد التلقائي والتنظيف اليدوي الدقيق. إن تنسيق التصدير النهائي مهم بقدر أهمية القناع، لأن JPEG سوف يقوم بتسوية المناطق الشفافة حتى لو كانت المعاينة تبدو صحيحة. + ابدأ بالإزالة التلقائية إذا تم فصل الموضوع بوضوح عن الخلفية. + قرّب وبدّل بين المحُ والاستعادة لإصلاح الشعر والزوايا والظلال والتفاصيل الصغيرة. + احفظ بتنسيق PNG أو WebP عندما تحتاج إلى الشفافية في الملف النهائي. + الرسم والتمييز والعلامة المائية + أضف الأسهم أو الملاحظات أو الإبرازات أو التوقيعات أو تراكبات العلامات المائية المتكررة + أضف معلومات مرئية + تساعد أدوات التوصيف في شرح الصورة، بينما تساعد العلامة المائية على وضع علامة تجارية على الملفات المصدرة أو حمايتها. + استخدم الرسم عندما تحتاج إلى ملاحظات يدوية أو أشكال أو تمييز أو تعليقات توضيحية سريعة. + استخدم العلامة المائية عندما تظهر نفس علامة النص أو الصورة بشكل متسق على المخرجات. + تحقق من العتامة والموضع والقياس قبل التصدير بحيث تكون العلامة مرئية ولكن لا تشتت الانتباه. + رسم الإعدادات الافتراضية + قم بتعيين عرض الخط واللون ووضع المسار وقفل الاتجاه والمكبر لترميز أسرع + اجعل الرسم يبدو متوقعًا + تكون إعدادات الرسم مفيدة عند إضافة تعليقات توضيحية إلى لقطات الشاشة أو وضع علامة على المستندات أو التوقيع على الصور بشكل متكرر. تعمل الإعدادات الافتراضية على تقليل وقت الإعداد، بينما يعمل المكبر وقفل الاتجاه على تسهيل عمليات التحرير الدقيقة على الشاشات الصغيرة. + قم بتعيين عرض الخط الافتراضي وارسم اللون على القيم التي تستخدمها كثيرًا للأسهم أو التمييزات أو التوقيعات. + استخدم وضع المسار الافتراضي عندما ترسم عادةً نفس النوع من الحدود أو الأشكال أو العلامات. + قم بتمكين قفل الاتجاه أو المكبر عندما يؤدي إدخال الإصبع إلى صعوبة وضع التفاصيل الصغيرة بدقة. + تخطيطات متعدّدة الصور + اختر أداة الصور المتعدّدة المناسبة قبل ترتيب لقطات الشاشة أو عمليات المسح أو شرائط المقارنة + استخدم أداة التخطيط الصحيحة + تبدو هذه الأدوات متشابهة، ولكن يتم ضبط كل واحدة منها لنوع مختلف من مخرجات الصور المتعدّدة. يؤدي اختيار الخيار الصحيح أولاً إلى تجنب محاربة عناصر التحكم في التخطيط التي صُمّمت للحصول على نتيجة مختلفة. + استخدم Collage Maker للتخطيطات المصممة بعدة صور في تركيبة واحدة. + استخدم دمج الصور أو تكديسها عندما تصبح الصور شريطًا رأسيًا أو أفقيًا طويلًا. + استخدم تقسيم الصور عندما تحتاج الصورة الكبيرة إلى عدة قطع أصغر. + تحويل صيغ الصور + أنشئ نسخ JPEG وPNG وWebP وAVIF وJXL ونسخ أخرى دون تحرير وحدات البكسل يدويًا + اختر التنسيق للوظيفة + تعد التنسيقات المختلفة أفضل للصور أو لقطات الشاشة أو الشفافية أو الضغط أو التوافق. يكون التحويل أكثر أمانًا عندما تفهم ما يدعمه التطبيق الوجهة وما إذا كان المصدر يحتوي على ألفا أو رسوم متحركة أو بيانات تعريف تريد الاحتفاظ بها. + استخدم JPEG للصور المتوافقة، وPNG للصور غير المفقودة وAlpha، وWebP للمشاركة المدمجة. + ضبط الجودة عندما يدعمها التنسيق؛ تعمل القيم المنخفضة على تقليل الحجم ولكن يمكنها إضافة عناصر فنية. + احتفظ بالنسخ الأصلية حتى تتأكد من فتح الملفات المحولة بشكل صحيح في التطبيق المستهدف. + تحقق من EXIF والخصوصية + عرض البيانات الوصفية أو تعديلها أو إزالتها قبل مشاركة الصور الحساسة + التحكم في بيانات الصورة المخفية + يمكن أن يحتوي ملف EXIF على بيانات الكاميرا والتواريخ والموقع والاتجاه وتفاصيل أخرى غير مرئية في الصورة. يجدر التحقق قبل المشاركة العامة أو تقارير الدعم أو قوائم السوق أو أي صورة قد تكشف عن مكان خاص. + افتح أدوات EXIF قبل مشاركة الصور التي قد تحتوي على معلومات الموقع أو الجهاز الخاص. + قم بإزالة الحقول الحساسة أو قم بتحرير العلامات غير الصحيحة مثل بيانات التاريخ أو الاتجاه أو المؤلف أو الكاميرا. + احفظ نسخة جديدة وشارك تلك النسخة بدلاً من النسخة الأصلية عندما تكون الخصوصية مهمة. + تنظيف EXIF التلقائي + قم بإزالة البيانات التعريفية بشكل افتراضي أثناء تحديد ما إذا كان يجب الاحتفاظ بالتواريخ أم لا + اجعل الخصوصية هي الوضع الافتراضي + تعد مجموعة إعدادات EXIF مفيدة عندما تريد أن يتم تنظيف الخصوصية تلقائيًا بدلاً من تذكرها لكل عملية تصدير. يعد الاحتفاظ بالتاريخ والوقت قرارًا منفصلاً لأن التواريخ يمكن أن تكون مفيدة للفرز ولكنها حساسة للمشاركة. + فعّل خيار EXIF الواضح دائمًا عندما تكون معظم عمليات التصدير مخصّصة للمشاركة العامة أو شبه العامة. + قم بتمكين الاحتفاظ بالتاريخ والوقت فقط عندما يكون الحفاظ على وقت الالتقاط أكثر أهمية من إزالة كل طابع زمني. + استخدم تحرير EXIF اليدوي للملفات لمرة واحدة حيث تحتاج إلى الاحتفاظ ببعض الحقول وإزالة الحقول الأخرى. + التحكم في أسماء الملفات + استخدم البادئات واللاحقات والطوابع الزمنية والإعدادات المسبقة والمجاميع الاختبارية والأرقام التسلسلية + تسهيل العثور على الصادرات + يساعد نمط اسم الملف الجيد في فرز الدُفعات ويمنع عمليات الكتابة الفوقية غير المقصودة. تكون إعدادات اسم الملف مفيدة بشكل خاص عندما تعود عمليات التصدير إلى المجلد المصدر، حيث يمكن أن تصبح الأسماء المتشابهة مربكة بسرعة. + قم بتعيين بادئة اسم الملف، أو اللاحقة، أو الطابع الزمني، أو الاسم الأصلي، أو معلومات الإعداد المسبق، أو خيارات التسلسل في الإعدادات. + استخدم الأرقام التسلسلية للدفعات التي يكون فيها الترتيب مهمًا، مثل الصفحات أو الإطارات أو الصور المجمعة. + قم بتمكين الكتابة الفوقية فقط عندما تكون متأكدًا من أن استبدال الملفات الموجودة آمن للمجلد الحالي. + الكتابة فوق الملفات + استبدل المخرجات بالأسماء المطابقة فقط عندما يكون سير عملك مدمرًا عن عمد + استخدم وضع الكتابة الفوقية بعناية + تعمل ميزة \"الكتابة فوق الملفات\" على تغيير كيفية معالجة تعارضات الأسماء. يعد ذلك مفيدًا لعمليات التصدير المتكررة إلى نفس المجلد، لكنه يمكنه أيضًا استبدال النتائج السابقة إذا كان نمط اسم الملف الخاص بك ينتج نفس الاسم مرة أخرى. + قم بتمكين الكتابة الفوقية فقط للمجلدات التي من المتوقع أن يتم فيها استبدال الملفات القديمة التي تم إنشاؤها وتكون قابلة للاسترداد. + استمر في تعطيل الكتابة الفوقية عند تصدير النسخ الأصلية أو ملفات العميل أو عمليات الفحص لمرة واحدة أو أي شيء بدون نسخة احتياطية. + قم بدمج الكتابة الفوقية مع نمط اسم الملف المتعمد بحيث يتم استبدال المخرجات المقصودة فقط. + أنماط أسماء الملفات + الجمع بين الأسماء الأصلية والبادئات واللاحقات والطوابع الزمنية والإعدادات المسبقة ووضع المقياس والمجاميع الاختبارية + بناء الأسماء التي تشرح الإخراج + يمكن لإعدادات نمط اسم الملف تحويل الملفات المصدرة إلى سجل مفيد لما حدث لها. وهذا مهم على دفعات لأن عرض المجلد قد يكون المكان الوحيد الذي يمكنك من خلاله التمييز بين الحجم الأصلي والإعداد المسبق والتنسيق وترتيب المعالجة. + استخدم اسم الملف الأصلي والبادئة واللاحقة والطابع الزمني عندما تحتاج إلى أسماء قابلة للقراءة للفرز اليدوي. + قم بإضافة الإعداد المسبق أو وضع القياس أو حجم الملف أو معلومات المجموع الاختباري عندما يجب أن توثق المخرجات كيفية إنتاجها. + تجنب الأسماء العشوائية لسير العمل حيث تحتاج الملفات إلى البقاء مقترنة بالنسخ الأصلية أو ترتيب الصفحات. + اختر مكان حفظ الملفات + تجنب فقدان الصادرات عن طريق تعيين المجلدات وحفظ المجلد الأصلي ومواقع الحفظ لمرة واحدة + اجعل النواتج قابلة للتنبؤ بها + يعتبر سلوك الحفظ أكثر أهمية عند معالجة العديد من الملفات أو مشاركة الملفات من موفري الخدمات السحابية. تحدد إعدادات المجلد ما إذا كانت المخرجات ستتجمع في مكان واحد معروف، أو تظهر بجانب النسخ الأصلية، أو تنتقل مؤقتًا إلى مكان آخر لمهمة محدّدة. + قم بتعيين مجلد حفظ افتراضي إذا كنت تريد دائمًا التصدير في مكان واحد. + استخدم الحفظ في المجلد الأصلي لسير عمل التنظيف حيث يجب أن يظل الإخراج بالقرب من الملف المصدر. + استخدم موقع الحفظ لمرة واحدة عندما تحتاج مهمة واحدة إلى وجهة مختلفة دون تغيير الإعداد الافتراضي. + حفظ المجلدات لمرة واحدة + أرسل عمليات التصدير التالية إلى مجلد مؤقت دون تغيير وجهتك العادية + احتفظ بالوظائف الخاصة منفصلة + يعد موقع الحفظ لمرة واحدة مفيدًا للمشاريع المؤقتة أو مجلدات العميل أو جلسات التنظيف أو الصادرات التي يجب ألا تختلط مع مجلد Image Toolbox العادي. إنه يوفر وجهة قصيرة العمر دون إعادة كتابة الإعداد الافتراضي الخاص بك. + اختر مجلدًا لمرة واحدة قبل بدء مهمة تنتمي إلى خارج موقع التصدير العادي. + استخدمه للمشاريع القصيرة أو المجلدات المشتركة أو الدفعات حيث يجب أن تظل المخرجات مجمعة معًا. + قم بالعودة إلى المجلد الافتراضي بعد المهمة حتى لا تصل عمليات التصدير اللاحقة بشكل غير متوقع إلى الموقع المؤقت. + توازن الجودة وحجم الملف + افهم متى يجب تغيير الأبعاد أو التنسيق أو الجودة بدلاً من التخمين + تقليص الملفات عمدا + يعتمد حجم الملف على أبعاد الصورة وتنسيقها وجودتها وشفافيتها وتعقيد المحتوى. إذا كان الملف لا يزال ثقيلًا للغاية، فعادةً ما يساعد تغيير الأبعاد أكثر من خفض الجودة بشكل متكرر. + قم بتغيير حجم الأبعاد أولاً عندما تكون الصورة أكبر مما يمكن أن تعرضه الوجهة. + جودة أقل فقط للتنسيقات المفقودة وفحص النص والحواف والتدرجات والوجوه بعد التصدير. + قم بتبديل التنسيق عندما لا تكون تغييرات الجودة كافية، على سبيل المثال WebP للمشاركة أو PNG للأصول الشفافة الواضحة. + العمل مع صيغ الرسوم المتحركة + استخدم أدوات GIF وAPNG وWebP وJXL عندما يحتوي الملف على إطارات بدلاً من صورة نقطية واحدة + لا تتعامل مع كل صورة على أنها ثابتة + تحتاج التنسيقات المتحركة إلى أدوات تفهم الإطارات والمدة والتوافق. + استخدم أدوات GIF أو APNG عندما تحتاج إلى عمليات على مستوى الإطار لتلك التنسيقات. + استخدم أدوات WebP عندما تحتاج إلى ضغط حديث أو إخراج WebP متحرك. + قم بمعاينة توقيت الرسوم المتحركة قبل المشاركة لأن بعض الأنظمة الأساسية تقوم بتحويل الصور المتحركة أو تسطيحها. + مقارنة ومعاينة الإخراج + افحص التغييرات وحجم الملف والاختلافات المرئية قبل الاحتفاظ بالتصدير + التحقق قبل المشاركة + تساعد أدوات المقارنة في اكتشاف المؤثرات المضغوطة أو الألوان الخاطئة أو التعديلات غير المرغوب فيها مبكرًا. + افتح المقارنة عندما تريد فحص نسختين جنبًا إلى جنب. + قرّب الحواف والنص والتدرجات والمناطق الشفافة التي يسهل تفويت المشكلات فيها. + إذا كان الإخراج ثقيلًا جدًا أو ضبابيًا، فارجع إلى الأداة السابقة واضبط الجودة أو التنسيق. + استخدم مركز أدوات PDF + دمج، تقسيم، تدوير، إزالة الصفحات، ضغط، حماية، التعرف الضوئي على الحروف، ووضع علامة مائية على ملفات PDF + اختر عملية PDF + يقوم مركز PDF بتجميع إجراءات المستند خلف نقطة إدخال واحدة حتى تتمكن من اختيار العملية المحددة. ابدأ من هناك عندما يكون الملف عبارة عن ملف PDF بالفعل، واستخدم «صور إلى PDF» أو «ماسح ضوئي للمستندات» عندما يكون مصدرك لا يزال عبارة عن مجموعة من الصور. + افتح أدوات PDF واختر العملية التي تتوافق مع هدفك. + حدّد ملف PDF أو الصور المصدر، ثم راجع خيارات ترتيب الصفحات أو التدوير أو الحماية أو التعرف الضوئي على الحروف. + احفظ ملف PDF الذي أُنشئ وافتحه مرة واحدة لتأكيد عدد الصفحات وترتيبها وسهولة قراءتها. + تحويل الصور إلى ملف PDF + اجمع بين عمليات المسح أو لقطات الشاشة أو الإيصالات أو الصور في مستند واحد قابل للمشاركة + إنشاء مستند نظيف + تصبح الصور صفحات PDF أفضل عندما يتم قصها وترتيبها وحجمها بشكل متسق. تعامل مع قائمة الصور كمجموعة صفحات: يؤثر كل اختيار للتدوير والهامش وحجم الصفحة على مدى سهولة قراءة المستند النهائي. + قم بقص الصور أو تدويرها أولاً عندما تكون حواف الصفحة أو الظلال أو الاتجاه غير صحيح. + حدّد الصور بترتيب الصفحة المقصود واضبط حجم الصفحة أو الهوامش إذا كانت الأداة توفرها. + قم بتصدير ملف PDF، ثم تأكد من أن جميع الصفحات قابلة للقراءة قبل إرسالها. + مسح المستندات ضوئيًا + التقط الصفحات الورقية وقم بإعدادها لملفات PDF أو OCR أو للمشاركة + التقاط صفحات قابلة للقراءة + يعمل المسح الضوئي للمستندات بشكل أفضل مع الصفحات المسطحة والإضاءة الجيدة والمنظور المصحح. يؤدي المسح النظيف قبل تصدير OCR أو PDF إلى توفير الوقت لأن التعرُّف على النص وضغطه يعتمدان على جودة الصفحة. + ضع المستند على سطح متباين به ما يكفي من الضوء والحد الأدنى من الظلال. + اضبط الزوايا المكتشفة أو قم بالقص يدويًا حتى تملأ الصفحة الإطار بشكل نظيف. + قم بالتصدير كصور أو PDF، ثم قم بتشغيل OCR إذا كنت بحاجة إلى نص قابل للتحديد. + حماية أو فتح ملفات PDF + استخدم كلمات المرور بعناية واحتفظ بنسخة مصدر قابلة للتحرير عندما يكون ذلك ممكنًا + التعامل مع الوصول إلى PDF بأمان + تعتبر أدوات الحماية مفيدة للمشاركة الخاضعة للتحكم، ولكن من السهل فقدان كلمات المرور وصعوبة استعادتها. قم بحماية النسخ للتوزيع، واحتفظ بالملفات المصدر غير المحمية بشكل منفصل، وتحقق من النتيجة قبل حذف أي شيء. + قم بحماية نسخة من ملف PDF، وليس مستندك المصدر الوحيد. + قم بتخزين كلمة المرور في مكان آمن قبل مشاركة الملف المحمي. + استخدم إلغاء القفل فقط للملفات المسموح لك بتحريرها، ثم تأكد من فتح النسخة غير المؤمنة بشكل صحيح. + تنظيم صفحات PDF + دمج الصفحات وتقسيمها وإعادة ترتيبها وتدويرها واقتصاصها وإزالتها وإضافة أرقام الصفحات بشكل متعمد + إصلاح هيكل الوثيقة قبل الزخرفة + أدوات صفحة PDF هي الأسهل في الاستخدام بنفس الترتيب الذي تستخدمه لإعداد مستند ورقي: قم بتجميع الصفحات، وإزالة الصفحات الخاطئة، وترتيبها، وتصحيح الاتجاه، ثم إضافة التفاصيل النهائية مثل أرقام الصفحات أو العلامات المائية. + استخدم الدمج أو تحويل الصور إلى PDF أولاً عندما لا يزال المستند مقسمًا عبر عدة ملفات. + قم بإعادة ترتيب الصفحات أو تدويرها أو قصها أو إزالتها قبل إضافة أرقام الصفحات أو التوقيعات أو العلامات المائية. + قم بمعاينة ملف PDF النهائي بعد التعديلات الهيكلية لأنه قد يكون من الصعب ملاحظة صفحة مفقودة أو تم تدويرها لاحقًا. + الشروح PDF + قم بإزالة التعليقات التوضيحية أو تسويتها عندما يعرض المشاهدون التعليقات والعلامات بشكل مختلف + السيطرة على ما يبقى مرئيا + يمكن أن تكون التعليقات التوضيحية عبارة عن تعليقات أو تمييزات أو رسومات أو علامات نموذج أو كائنات PDF إضافية أخرى. يعرضها بعض المشاهدين بشكل مختلف، لذا فإن إزالتها أو تسويتها يمكن أن تجعل المستندات المشتركة أكثر قابلية للتنبؤ بها. + قم بإزالة التعليقات التوضيحية عندما لا ينبغي تضمين التعليقات أو التمييزات أو علامات المراجعة في النسخة المشتركة. + التسوية عندما يجب أن تظل العلامات المرئية على الصفحة ولكنها لم تعد تتصرف مثل التعليقات التوضيحية القابلة للتحرير. + احتفظ بنسخة أصلية لأن إزالة التعليقات التوضيحية أو تسويتها قد يكون من الصعب التراجع عنها. + التعرف على النص + استخرج النص من الصور باستخدام محركات ولغات التعرف الضوئي على الحروف القابلة للتحديد + احصل على نتائج أفضل للتعرف الضوئي على الحروف + تعتمد دقة التعرف الضوئي على الحروف (OCR) على حدة الصورة وبيانات اللغة والتباين واتجاه الصفحة. عادةً ما تأتي أفضل النتائج من إعداد الصورة أولاً: إزالة الضوضاء، وتدويرها بشكل مستقيم، وزيادة إمكانية القراءة، ثم التعرف على النص. + قص الصورة إلى منطقة النص دوّرها في وضع مستقيم قبل التعرُّف عليها. + اختر اللغة الصحيحة وقم بتنزيل بيانات اللغة إذا طلب التطبيق ذلك. + انسخ النص الذي تم التعرف عليه أو شاركه، ثم تحقق يدويًا من الأسماء والأرقام وعلامات الترقيم. + اختر بيانات لغة التعرف الضوئي على الحروف + تحسين التعرُّف عن طريق مطابقة البرامج النصية واللغات ونماذج التعرف الضوئي على الحروف المُنزّلة + مطابقة التعرف الضوئي على الحروف (OCR) مع النص + يمكن أن تؤدي لغة التعرف الضوئي على الحروف الخاطئة إلى جعل الصور الجيدة تبدو وكأنها التعرُّف السيئ. تعتبر بيانات اللغة مهمة بالنسبة للنصوص البرمجية وعلامات الترقيم والأرقام والمستندات المختلطة، لذا اختر اللغة التي تظهر في الصورة بدلاً من لغة واجهة مستخدم الهاتف. + اختر اللغة أو النص الذي يظهر في الصورة، وليس لغة الهاتف فقط. + قم بتنزيل البيانات المفقودة قبل العمل دون اتصال أو معالجة دفعة. + بالنسبة للمستندات متعدّدة اللغات، شغّل اللغة الأكثر أهمية أولاً وتحقق يدويًا من الأسماء والأرقام. + ملفات PDF قابلة للبحث + قم بكتابة نص التعرف الضوئي على الحروف (OCR) مرة أخرى في الملفات حتى يمكن البحث في الصفحات الممسوحة ضوئيًا ونسخها + تحويل عمليات المسح إلى مستندات قابلة للبحث + يستطيع OCR القيام بأكثر من مجرد نسخ النص إلى الحافظة. عندما تكتب نصًا تم التعرف عليه في ملف أو ملف PDF قابل للبحث، تظل صورة الصفحة مرئية بينما يصبح البحث عن النص أو تحديده أو فهرسته أو أرشفته أسهل. + استخدم مخرجات PDF القابلة للبحث للمستندات الممسوحة ضوئيًا والتي يجب أن تظل تبدو مثل الصفحات الأصلية. + استخدم الكتابة إلى الملف عندما تحتاج فقط إلى النص المستخرج ولا تهتم بصورة الصفحة. + تحقق من الأرقام والأسماء والجداول المهمة يدويًا لأن نص التعرف الضوئي على الحروف يمكن أن يكون قابلاً للبحث ولكنه لا يزال غير كامل. + مسح رموز QR + اقرأ محتوى QR من إدخال الكاميرا أو الصور المحفوظة عند توفرها + قراءة الرموز بأمان + يمكن أن تحتوي رموز QR على روابط أو بيانات Wi-Fi أو جهات اتصال أو نص أو حمولات أخرى. + افتح Scan QR Code وحافظ على الرمز واضحًا ومسطحًا وداخل الإطار بالكامل. + قم بمراجعة المحتوى الذي تم فك تشفيره قبل فتح الروابط أو نسخ البيانات الحساسة. + إذا فشل المسح، قم بتحسين الإضاءة، أو قص الكود، أو تجربة صورة مصدر ذات دقة أعلى. + استخدم Base64 والمجاميع الاختبارية + تشفير الصور كنص، وفك تشفير النص مرة أخرى إلى الصور، والتحقق من سلامة الملف + التنقل بين الملفات والنص + يعد Base64 مفيدًا لحمولات الصور الصغيرة، بينما تساعد المجاميع الاختبارية في التأكد من عدم تغيير الملفات. تعد هذه الأدوات مفيدة للغاية في سير العمل الفني وتقارير الدعم وعمليات نقل الملفات والأماكن التي تحتاج فيها الملفات الثنائية إلى أن تصبح نصًا مؤقتًا. + استخدم أدوات Base64 لترميز صورة صغيرة عندما يحتاج سير العمل إلى نص بدلاً من ملف ثنائي. + قم بفك تشفير Base64 فقط من المصادر الموثوقة وتحقق من المعاينة قبل الحفظ. + استخدم أدوات المجموع الاختباري عندما تحتاج إلى مقارنة الملفات المصدرة أو تأكيد التحميل/التنزيل. + حزمة أو حماية البيانات + استخدم أدوات ZIP والتشفير لتجميع الملفات أو تحويل البيانات النصية + احتفظ بالملفات ذات الصلة معًا + تكون أدوات التغليف مفيدة عندما يجب أن تنتقل عدة مخرجات كملف واحد. كما أنها مفيدة للاحتفاظ بالصور وملفات PDF والسجلات والبيانات التعريفية التي تم إنشاؤها معًا عندما تحتاج إلى إرسال مجموعة نتائج كاملة. + استخدم ZIP عندما تحتاج إلى إرسال العديد من الصور أو المستندات المصدرة معًا. + استخدم أدوات التشفير فقط عندما تفهم الطريقة المحدّدة ويمكنك تخزين المفاتيح المطلوبة بأمان. + اختبر الأرشيف الذي تم إنشاؤه أو النص المحول قبل حذف الملفات المصدر. + المجموع الاختباري في الأسماء + استخدم أسماء الملفات المستندة إلى المجموع الاختباري عندما يكون التفرد والنزاهة أكثر أهمية من سهولة القراءة + تسمية الملفات حسب المحتوى + تكون أسماء ملفات المجموع الاختباري مفيدة عندما يكون للصادرات أسماء مرئية مكررة أو عندما تحتاج إلى إثبات تطابق ملفين. وهي أقل ملاءمة للتصفح اليدوي، لذا استخدمها للأرشيفات الفنية وسير عمل التحقق. + قم بتمكين المجموع الاختباري كاسم ملف عندما تكون هوية المحتوى أكثر أهمية من العنوان الذي يمكن للإنسان قراءته. + استخدمه للأرشيفات أو إلغاء البيانات المكررة أو عمليات التصدير المتكررة أو الملفات التي قد يتم تحميلها وتنزيلها مرة أخرى. + قم بإقران أسماء المجموع الاختباري بالمجلدات أو البيانات التعريفية عندما لا يزال الأشخاص بحاجة إلى فهم ما يمثله الملف. + اختيار الألوان من الصور + قم بتجربة وحدات البكسل ونسخ رموز الألوان وإعادة استخدام الألوان في اللوحات أو التصميمات + عينة اللون الدقيق + يعد انتقاء الألوان مفيدًا لمطابقة التصميم أو استخراج ألوان العلامة التجارية أو التحقق من قيم الصورة. التقريب أمرًا مهمًا لأن الحواف والظلال والضغط يمكن أن تجعل وحدات البكسل المجاورة مختلفة قليلاً عن اللون الذي تتوقعه. + افتح اختيار اللون من الصورة واختر صورة مصدر باللون الذي تحتاجه. + قرّب قبل أخذ عينات من التفاصيل الصغيرة حتى لا تؤثر وحدات البكسل القريبة على النتيجة. + انسخ اللون بالتنسيق الذي تتطلبه وجهتك، مثل HEX أو RGB أو HSV. + إنشاء لوحات واستخدام مكتبة الألوان + استخرج اللوحات، وتصفح الألوان المحفوظة، واحتفظ بأنظمة مرئية متسقة + قم ببناء لوحة قابلة لإعادة الاستخدام + تساعد اللوحات في الحفاظ على اتساق الرسومات والعلامات المائية والرسومات المصدرة. إنها مفيدة بشكل خاص عندما تقوم بتعليق لقطات الشاشة بشكل متكرر، أو إعداد أصول العلامة التجارية، أو عندما تحتاج إلى نفس الألوان عبر العديد من الأدوات. + أنشئ لوحة ألوان من صورة أو أضف الألوان يدويًا عندما تعرف القيم بالفعل. + قم بتسمية الألوان المهمة أو تنظيمها حتى تتمكن من العثور عليها مرة أخرى لاحقًا. + استخدم القيم المنسوخة في أدوات الرسم أو العلامة المائية أو التدرج أو SVG أو أدوات التصميم الخارجية. + إنشاء التدرجات + أنشئ صورًا متدرجة للخلفيات والعناصر النائبة والتجارب المرئية + التحكم في التحولات اللونية + تكون أدوات التدرج مفيدة عندما تحتاج إلى صورة تم إنشاؤها بدلاً من تحرير صورة موجودة. يمكن أن تصبح خلفيات نظيفة أو عناصر نائبة أو خلفيات أو أقنعة أو أصول بسيطة لأدوات التصميم الخارجية. + اختر نوع التدرج وقم بتعيين الألوان المهمة أولاً. + اضبط الاتجاه والتوقف والنعومة وحجم الإخراج لحالة الاستخدام المستهدفة. + قم بالتصدير بتنسيق يطابق الوجهة، عادةً PNG للحصول على رسومات نظيفة. + إمكانية الوصول إلى اللون + استخدم الألوان الديناميكية والتباين وخيارات الألوان العمياء عندما يصعب قراءة واجهة المستخدم + جعل الألوان أسهل في الاستخدام + تؤثر إعدادات الألوان على الراحة وسهولة القراءة ومدى سرعة مسح عناصر التحكم. إذا كان من الصعب التمييز بين شرائح الأدوات أو أشرطة التمرير أو الحالات المحددة، فقد تكون خيارات السمة وإمكانية الوصول أكثر فائدة من مجرد تغيير الوضع المظلم. + جرب الألوان الديناميكية عندما تريد أن يتبع التطبيق لوحة الخلفية الحالية. + قم بزيادة التباين أو تغيير المخطط عندما لا تبرز الأزرار والأسطح بدرجة كافية. + استخدم خيارات نظام عمى الألوان إذا كان من الصعب التمييز بين بعض الحالات أو اللهجات. + خلفية ألفا + يمكنك التحكم في لون المعاينة أو التعبئة المستخدم حول PNG وWebP والمخرجات المماثلة + فهم المعاينات الشفافة + يمكن أن يؤدي لون الخلفية لتنسيقات ألفا إلى تسهيل فحص الصور الشفافة، ولكن من المهم معرفة ما إذا كنت تقوم بتغيير سلوك المعاينة/التعبئة أو تسوية النتيجة النهائية. وهذا مهم بالنسبة للملصقات والشعارات والصور التي تمت إزالتها من الخلفية. + استخدم تنسيقًا يدعم ألفا أولاً، مثل PNG أو WebP، عندما يجب أن تظل وحدات البكسل الشفافة صالحة للتصدير. + اضبط إعدادات لون الخلفية عندما يصعب رؤية الحواف الشفافة على سطح التطبيق. + قم بتصدير اختبار صغير وأعد فتحه في تطبيق آخر للتأكد من حفظ الشفافية أو التعبئة المختارة. + اختيار نموذج الذكاء الاصطناعي + اختر نموذجًا حسب نوع الصورة والعيب بدلاً من اختيار النموذج الأكبر أولاً + مطابقة النموذج للضرر + يمكن لأدوات الذكاء الاصطناعي تنزيل أو استيراد نماذج ONNX/ORT المختلفة، ويتم تدريب كل نموذج على نوع معين من المشكلات. قد يؤدي نموذج كتل JPEG، أو تنظيف خط الرسوم المتحركة، أو تقليل الضوضاء، أو إزالة الخلفية، أو التلوين، أو رفع المستوى إلى نتائج أسوأ عند استخدامه على نوع صورة خاطئ. + اقرأ وصف النموذج قبل التنزيل؛ اختر النموذج الذي يسمي مشكلتك الفعلية، مثل الضغط أو الضوضاء أو الألوان النصفية أو التمويه أو إزالة الخلفية. + ابدأ بنموذج أصغر أو أكثر تحديدًا عند اختبار نوع صورة جديد، ثم قارنه بالنماذج الأثقل فقط إذا لم تكن النتيجة جيدة بما فيه الكفاية. + احتفظ فقط بالنماذج التي تستخدمها بالفعل، لأن النماذج التي تم تنزيلها واستيرادها يمكن أن تشغل مساحة تخزين ملحوظة. + فهم الأسر النموذجية + غالبًا ما تشير أسماء النماذج إلى هدفها: نماذج بنمط RealESRGAN راقية، ونماذج SCUnet وFBCNN تعمل على تنظيف الضوضاء أو الضغط، ونماذج الخلفية تنشئ أقنعة، وتضيف أدوات التلوين اللون إلى مدخلات التدرج الرمادي. يمكن أن تكون النماذج المخصصة المستوردة قوية، ولكن يجب أن تتطابق مع شكل الإدخال/الإخراج المتوقع وتستخدم تنسيق ONNX أو ORT المدعوم. + استخدم أدوات رفع المستوى عندما تحتاج إلى المزيد من وحدات البكسل، وأجهزة تقليل الضوضاء عندما تكون الصورة محببة، ومزيلات العناصر عندما تكون الكتل المضغوطة أو النطاقات هي المشكلة الرئيسية. + استخدم نماذج الخلفية فقط لفصل الموضوع؛ فهي ليست مثل إزالة الكائنات العامة أو تحسين الصورة. + قم باستيراد النماذج المخصصة فقط من المصادر التي تثق بها واختبرها على صورة صغيرة قبل استخدام الملفات المهمة. + معلمات الذكاء الاصطناعي + قم بضبط حجم القطعة والتداخل والعاملين المتوازيين لتحقيق التوازن بين الجودة والسرعة والذاكرة + توازن السرعة مع الاستقرار + يتحكم حجم القطعة في حجم قطع الصورة الكبيرة قبل معالجتها بواسطة النموذج. يمزج التداخل القطع المجاورة لإخفاء الحدود. يمكن للعمال المتوازيين تسريع عملية المعالجة، لكن كل عامل يحتاج إلى ذاكرة، لذا فإن القيم العالية يمكن أن تجعل الصور الكبيرة غير مستقرة على الهواتف ذات ذاكرة الوصول العشوائي المحدودة. + استخدم أجزاء أكبر للحصول على سياق أكثر سلاسة عندما يتعامل جهازك مع النموذج بشكل موثوق وعندما لا تكون الصورة ضخمة. + قم بزيادة التداخل عندما تصبح حدود القطعة مرئية، لكن تذكر أن المزيد من التداخل يعني المزيد من العمل المتكرر. + رفع العمال الموازيين فقط بعد اختبار مستقر؛ إذا تباطأت عملية المعالجة، أو ارتفعت حرارة الجهاز، أو تعطل، فقم بتقليل العمال أولاً. + تجنب القطع الأثرية + يتيح Chunking للتطبيق معالجة الصور الأكبر حجمًا مما يستطيع النموذج التعامل معه بشكل مريح مرة واحدة. تتمثل المفاضلة في أن كل مربع يحتوي على سياق محيط أقل، لذا قد يؤدي التداخل المنخفض أو القطع الصغيرة جدًا إلى إنشاء حدود مرئية أو تغييرات في النسيج أو تفاصيل غير متناسقة بين المناطق المتجاورة. + قم بزيادة التداخل إذا رأيت حدودًا مستطيلة، أو تغييرات متكررة في النسيج، أو انتقالات تفصيلية بين المناطق المعالجة. + قم بتقليل حجم القطعة إذا فشلت العملية قبل إنتاج المخرجات، خاصة في الصور الكبيرة أو النماذج الثقيلة. + احفظ اختبار اقتصاص صغير قبل معالجة الصورة الكاملة حتى تتمكن من مقارنة المعلمات بسرعة. + استقرار الذكاء الاصطناعي + تقليل حجم القطعة والعاملين ودقة المصدر عند تعطل معالجة الذكاء الاصطناعي أو تجميدها + التعافي من وظائف الذكاء الاصطناعي الثقيلة + تعد معالجة الذكاء الاصطناعي واحدة من أثقل مهام سير العمل في التطبيق. عادةً ما تعني الأعطال أو الجلسات الفاشلة أو التجميد أو إعادة تشغيل التطبيق المفاجئ أن النموذج أو دقة المصدر أو حجم القطعة أو عدد العاملين المتوازيين يتطلب الكثير من الجهد بالنسبة لحالة الجهاز الحالية. + إذا تعطل التطبيق أو فشل في فتح جلسة نموذجية، فقم بتقليل حجم العمال المتوازيين وحجم القطعة، ثم حاول استخدام نفس الصورة مرة أخرى. + قم بتغيير حجم الصور الكبيرة جدًا قبل معالجة الذكاء الاصطناعي عندما لا يتطلب الهدف الدقة الأصلية. + أغلق التطبيقات الثقيلة الأخرى، أو استخدم نموذجًا أصغر، أو قم بمعالجة عدد أقل من الملفات مرة واحدة عندما يستمر ضغط الذاكرة في العودة. + تشخيص فشل النموذج + لا يعني التشغيل الفاشل للذكاء الاصطناعي دائمًا أن الصورة سيئة. قد يكون ملف النموذج غير كامل، أو غير مدعوم، أو كبير جدًا بالنسبة للذاكرة، أو غير متطابق مع العملية. عندما يبلغ التطبيق عن جلسة فاشلة، تعامل معها كإشارة لتبسيط النموذج والمعلمات أولاً. + قم بحذف النموذج وإعادة تنزيله إذا فشل مباشرة بعد التنزيل أو إذا كان يتصرف كما لو كان الملف تالفًا. + جرّب نموذجًا مدمجًا قابلاً للتنزيل قبل إلقاء اللوم على النموذج المخصص المستورد، لأن النماذج المستوردة قد تحتوي على مدخلات غير متوافقة. + استخدم سجلات التطبيقات بعد إعادة إنتاج الفشل إذا كنت بحاجة إلى الإبلاغ عن عطل أو خطأ في الجلسة. + تجربة في شادر ستوديو + استخدم تأثيرات تظليل GPU لمعالجة الصور المتقدمة والمظهر المخصص + العمل بعناية مع التظليل + يعد Shader Studio قويًا، لكن تعليمات برمجية ومعلمات التظليل تحتاج إلى تغييرات صغيرة وقابلة للاختبار. تعامل معها كأداة تجريبية: احتفظ بخط أساس فعال، وقم بتغيير شيء واحد في كل مرة، واختبر بأحجام صور مختلفة قبل الوثوق في إعداد مسبق. + ابدأ باستخدام تظليل عامل معروف أو تأثير بسيط قبل إضافة المعلمات. + قم بتغيير معلمة واحدة أو مقطع تعليمات برمجية واحد في كل مرة وتحقق من المعاينة بحثًا عن الأخطاء. + قم بتصدير الإعدادات المسبقة فقط بعد اختبارها على عدد قليل من أحجام الصور المختلفة. + اصنع فن SVG أو ASCII + أنشئ أصولًا تشبه المتجهات أو صورًا نصية للمخرجات الإبداعية + اختر نوع الإخراج الإبداعي + تخدم أدوات SVG وASCII أهدافًا مختلفة: رسومات قابلة للتطوير أو عرض نمط النص. كلاهما عبارة عن تحويلات إبداعية، لذا فإن المعاينة بالحجم النهائي أكثر أهمية من الحكم على النتيجة من خلال صورة مصغرة صغيرة. + استخدم SVG Maker عندما يجب تغيير حجم النتيجة بشكل واضح أو إعادة استخدامها في سير عمل التصميم. + استخدم ASCII Art عندما تريد تمثيلاً نصيًا منمقًا لصورة ما. + معاينة بالحجم النهائي لأن التفاصيل الصغيرة يمكن أن تختفي في المخرجات التي أُنشئت. + توليد القوام الضوضاء + قم بإنشاء أنسجة إجرائية للخلفيات أو الأقنعة أو التراكبات أو التجارب + ضبط المخرجات الإجرائية + يقوم توليد الضوضاء بإنشاء صور جديدة من المعلمات، لذا فإن التغييرات الصغيرة يمكن أن تؤدي إلى نتائج مختلفة تمامًا. إنه مفيد للأنسجة أو الأقنعة أو التراكبات أو العناصر النائبة أو اختبار الضغط بأنماط مفصلة. + اختر نوع الضوضاء وحجم الإخراج قبل ضبط التفاصيل الدقيقة. + اضبط الحجم أو الكثافة أو الألوان أو البذرة حتى يناسب النمط الاستخدام المستهدف. + احفظ النتائج المفيدة واكتب المعلمات إذا كنت بحاجة إلى إعادة إنشاء النسيج لاحقًا. + مشاريع العلامات + استخدم ملفات المشروع عندما تحتاج التعليقات التوضيحية إلى أن تظل قابلة للتحرير بعد إغلاق التطبيق + اجعل تعديلات الطبقات قابلة لإعادة الاستخدام + تكون ملفات مشروع الترميز مفيدة عندما تحتاج لقطة الشاشة أو الرسم التخطيطي أو صورة التعليمات إلى تغييرات لاحقًا. تعتبر ملفات PNG/JPEG المصدرة صورًا نهائية، بينما تحتفظ ملفات المشروع بالطبقات القابلة للتحرير وحالة التحرير لإجراء التعديلات المستقبلية. + استخدم طبقات التمييز عندما تظل التعليقات التوضيحية أو وسائل الشرح أو عناصر التخطيط قابلة للتحرير. + احفظ ملف المشروع أو أعد فتحه قبل تصدير الصورة النهائية إذا كنت تتوقع المزيد من المراجعات. + قم بتصدير صورة عادية فقط عندما تكون مستعدًا لمشاركة نسخة نهائية مسطحة. + تشفير الملفات + حماية الملفات بكلمة مرور واختبار فك التشفير قبل حذف أي نسخة مصدر + التعامل مع المخرجات المشفرة بعناية + يمكن لأدوات التشفير حماية الملفات باستخدام التشفير المعتمد على كلمة المرور، ولكنها ليست بديلاً لتذكر المفتاح أو الاحتفاظ بالنسخ الاحتياطية. يعد التشفير مفيدًا للنقل والتخزين، بينما يكون ZIP أفضل عندما يكون الهدف الرئيسي هو تجميع العديد من المخرجات. + قم بتشفير النسخة أولاً واحتفظ بالنسخة الأصلية حتى تختبر أن فك التشفير يعمل مع كلمة المرور التي قمت بتخزينها. + استخدم مدير كلمات المرور أو مكانًا آمنًا آخر للمفتاح؛ لا يمكن للتطبيق تخمين كلمة المرور المفقودة لك. + قم بمشاركة الملفات المشفرة فقط مع الأشخاص الذين لديهم أيضًا كلمة المرور ويعرفون الأداة أو الطريقة التي يجب أن تقوم بفك تشفيرها. + ترتيب الأدوات + أدوات التجميع والترتيب والبحث والتثبيت بحيث تتوافق الشاشة الرئيسية مع سير عملك الحقيقي + جعل الشاشة الرئيسية أسرع + تستحق إعدادات ترتيب الأدوات الضبط بمجرد معرفة الأدوات التي تستخدمها أكثر من غيرها. يساعد التجميع على الاستكشاف، والترتيب المخصص يساعد على الذاكرة العضلية، والمفضلات تجعل الأدوات اليومية قابلة للوصول حتى عندما تصبح القائمة الكاملة كبيرة. + استخدم الوضع المجمع أثناء تعلم التطبيق أو عندما تفضل الأدوات المنظمة حسب نوع المهمة. + قم بالتبديل إلى الترتيب المخصص عندما تعرف بالفعل أدواتك المتكررة وتريد عددًا أقل من التمريرات. + استخدم إعدادات البحث والمفضلة معًا للحصول على الأدوات النادرة التي تتذكرها بالاسم ولكنك لا تريد أن تكون مرئية طوال الوقت. + التعامل مع الملفات الكبيرة + تجنب عمليات التصدير البطيئة وضغط الذاكرة والإنتاج الكبير بشكل غير متوقع + تقليل العمل قبل التصدير + يمكن أن تستغرق الصور الكبيرة جدًا والدفعات الطويلة والتنسيقات عالية الجودة مزيدًا من الذاكرة والوقت. عادةً ما يكون أسرع حل هو تقليل حجم العمل قبل العملية الأثقل، وليس انتظار انتهاء نفس المدخلات ذات الحجم الكبير. + قم بتغيير حجم الصور الضخمة قبل تطبيق المرشحات الثقيلة، أو التعرف الضوئي على الحروف، أو تحويل PDF. + استخدم دفعة أصغر أولاً لتأكيد الإعدادات، ثم قم بمعالجة الباقي بنفس الإعداد المسبق. + انخفاض الجودة أو تغيير التنسيق عندما يكون ملف الإخراج أكبر من المتوقع. + ذاكرة التخزين المؤقت والمعاينات + فهم المعاينات التي تم إنشاؤها وتنظيف ذاكرة التخزين المؤقت واستخدام التخزين بعد الجلسات المكثفة + حافظ على توازن التخزين والسرعة + يمكن أن يؤدي إنشاء المعاينة وذاكرة التخزين المؤقت إلى جعل التصفح المتكرر أسرع، ولكن جلسات التحرير المكثفة قد تترك بيانات مؤقتة خلفها. تساعد إعدادات ذاكرة التخزين المؤقت عندما يبدو التطبيق بطيئًا، أو تكون مساحة التخزين ضيقة، أو تبدو المعاينات قديمة بعد العديد من التجارب. + يعد إبقاء المعاينات ممكنة عند تصفح العديد من الصور أكثر أهمية من تقليل مساحة التخزين المؤقتة. + امسح ذاكرة التخزين المؤقت بعد دفعات كبيرة أو تجارب فاشلة أو جلسات طويلة تحتوي على العديد من الملفات عالية الدقة. + استخدم المسح التلقائي لذاكرة التخزين المؤقت إذا كنت تفضل تنظيف التخزين على الحفاظ على دفء المعاينات بين عمليات الإطلاق. + ضبط سلوك الشاشة + استخدم خيارات ملء الشاشة والوضع الآمن والسطوع وشريط النظام للعمل المركّز + جعل الواجهة تناسب الموقف + تساعدك إعدادات الشاشة عند التحرير في الوضع الأفقي، أو تقديم صور خاصة، أو الحاجة إلى سطوع ثابت. إنها ليست مطلوبة للاستخدام العادي، ولكنها تحل المواقف المحرجة مثل فحص QR أو المعاينات الخاصة أو تحرير المناظر الطبيعية الضيقة. + استخدم إعدادات ملء الشاشة أو شريط النظام عندما يكون تحرير المساحة أكثر أهمية من التنقل في Chrome. + استخدم الوضع الآمن عندما لا تظهر الصور الخاصة في لقطات الشاشة أو معاينات التطبيق. + استخدم فرض السطوع للأدوات التي يصعب فيها فحص الصور الداكنة أو رموز QR. + حافظ على الشفافية + منع الخلفيات الشفافة من التحول إلى اللون الأبيض أو الأسود أو المسطح + استخدم الإخراج المدرك لألفا + تبقى الشفافية فقط عندما تدعم الأداة المحددة وتنسيق الإخراج ألفا. إذا تحولت الخلفية المصدرة إلى اللون الأبيض أو الأسود، فعادةً ما تكون المشكلة هي تنسيق الإخراج، أو إعداد التعبئة/الخلفية، أو تطبيق الوجهة الذي أدى إلى تسوية الصورة. + اختر PNG أو WebP عند تصدير الصور أو الملصقات أو الشعارات أو التراكبات التي تمت إزالتها من الخلفية. + تجنب استخدام JPEG للحصول على إخراج شفاف لأنه لا يخزن قناة ألفا. + تحقق من الإعدادات المتعلقة بلون الخلفية لتنسيقات ألفا إذا أظهرت المعاينة خلفية مملوءة. + إصلاح مشكلات المشاركة والاستيراد + استخدم إعدادات المنتقي والتنسيقات المدعومة عندما لا تظهر الملفات أو تفتح بشكل صحيح + أدخل الملف إلى التطبيق + غالبًا ما تنتج مشكلات الاستيراد عن أذونات الموفر، أو التنسيقات غير المدعومة، أو سلوك المنتقي. قد تكون الملفات التي تتم مشاركتها من التطبيقات السحابية وبرامج المراسلة مؤقتة، لذا فإن اختيار مصدر ثابت يمكن أن يكون أكثر موثوقية لإجراء تعديلات أطول. + حاول فتح الملف من خلال منتقي النظام بدلاً من اختصار الملفات الحديثة. + إذا كان التنسيق غير مدعوم بواسطة أداة واحدة، فقم بتحويله أولاً أو افتح أداة أكثر تحديدًا. + قم بمراجعة إعدادات منتقي الصور والمشغل إذا كان سلوك تدفقات المشاركة أو الفتح المباشر للأداة يختلف عن المتوقع. + تأكيد الخروج + تجنب فقدان التعديلات غير المحفوظة عند حدوث الإيماءات أو الضغط الخلفي أو تبديل الأدوات عن طريق الخطأ + حماية العمل غير المكتمل + يعد تأكيد خروج الأداة مفيدًا عند استخدام التنقل بالإيماءات، أو تحرير الملفات الكبيرة، أو التبديل بين التطبيقات غالبًا. فهو يضيف وقفة صغيرة قبل مغادرة الأداة حتى لا يتم فقدان الإعدادات والمعاينات غير المكتملة عن طريق الصدفة. + قم بتمكين التأكيد في حالة قيام إيماءات الرجوع غير المقصودة بإغلاق الأداة قبل تصديرها. + أبقِه معطلاً إذا كنت تقوم في الغالب بإجراء تحويلات سريعة من خطوة واحدة وتفضل التنقل بشكل أسرع. + استخدمه مع الإعدادات المسبقة لسير عمل أطول حيث قد تكون إعادة بناء الإعدادات مزعجة. + استخدم السجلات عند الإبلاغ عن المشكلات + اجمع تفاصيل مفيدة قبل فتح مشكلة أو الاتصال بالمطور + الإبلاغ عن المشكلات مع السياق + من الأسهل تشخيص التقرير الواضح الذي يحتوي على الخطوات وإصدار التطبيق ونوع الإدخال والسجلات. تعد السجلات مفيدة للغاية بعد إعادة إنتاج المشكلة مباشرة لأنها تحافظ على تحديث السياق المحيط. + لاحظ اسم الأداة والإجراء الدقيق وما إذا كان يحدث مع ملف واحد أو كل ملف. + افتح سجلات التطبيقات بعد إعادة إنتاج المشكلة وشارك مخرجات السجل ذات الصلة عندما يكون ذلك ممكنًا. + قم بإرفاق لقطات شاشة أو ملفات نموذجية فقط عندما لا تحتوي على معلومات خاصة. + تم إيقاف معالجة الخلفية + لم يسمح Android ببدء إشعار معالجة الخلفية في الوقت المناسب. يعد هذا أحد قيود توقيت النظام التي لا يمكن إصلاحها بشكل موثوق. أعد تشغيل التطبيق وحاول مرة أخرى؛ قد يساعد إبقاء التطبيق مفتوحًا والسماح بالإشعارات أو العمل في الخلفية. + تخطي اختيار الملف + افتح الأدوات بشكل أسرع عندما يأتي الإدخال عادةً من المشاركة أو الحافظة أو شاشة سابقة + اجعل عمليات تشغيل الأداة المتكررة أسرع + يؤدي تخطي انتقاء الملفات إلى تغيير الخطوة الأولى للأدوات المدعومة. يكون ذلك مفيدًا عندما تقوم عادةً بإدخال أداة تحتوي على صورة محدّدة بالفعل أو من إجراءات مشاركة أندرويد، ولكن قد يكون الأمر مربكًا إذا كنت تتوقع أن تطلب كل أداة ملفًا على الفور. + فعِّل فقط عندما يوفر التدفق المعتاد الصورة المصدر بالفعل قبل فتح الأداة. + أبقِه معطلاً أثناء تعلم التطبيق، لأن الانتقاء الصريح يجعل فهم كل أداة أسهل. + إذا تم فتح أداة بدون إدخال واضح، فقم بتعطيل هذا الإعداد أو ابدأ من مشاركة/فتح باستخدام حتى يقوم Android بتمرير الملف أولاً. + افتح المحرر أولاً + تخطي المعاينة عندما يجب أن تنتقل الصورة المشتركة مباشرة إلى التحرير + اختر معاينة أو تحرير كمحطة أولى + فتح تحرير بدلاً من المعاينة مخصص للمستخدمين الذين يعرفون بالفعل أنهم يريدون تعديل الصورة. تكون المعاينة أكثر أمانًا عند فحص الملفات من الدردشة أو التخزين السحابي؛ يعد التحرير المباشر أسرع بالنسبة إلى لقطات الشاشة والاقتصاص السريع والتعليقات التوضيحية والتصحيحات لمرة واحدة. + قم بتمكين التحرير المباشر إذا كانت معظم الصور المشتركة تحتاج على الفور إلى إجراء تغييرات على الاقتصاص أو الترميز أو المرشحات أو التصدير. + اترك المعاينة أولاً عندما تقوم غالبًا بفحص البيانات التعريفية أو الأبعاد أو الشفافية أو جودة الصورة قبل اتخاذ القرار. + استخدم هذا مع المفضلة حتى تصل الصور المشتركة إلى الأدوات التي تستخدمها بالفعل. + إدخال النص مسبقا + أدخل القيم المحدّدة مسبقًا بدلاً من ضبط عناصر التحكم بشكل متكرر + استخدم قيمًا دقيقة عندما تكون أشرطة التمرير بطيئة + يساعد إدخال النص المحدّد مسبقًا عندما تحتاج إلى أرقام دقيقة للعمل المتكرر: أحجام الصور الاجتماعية، أو هوامش المستندات، أو أهداف الضغط، أو عروض الخطوط، أو القيم الأخرى التي يصعب الوصول إليها بالإيماءات. وهو مفيد أيضًا على الشاشات الصغيرة حيث تكون حركة شريط التمرير الدقيقة أكثر صعوبة. + قم بتمكين إدخال النص إذا كنت تعيد استخدام الأحجام أو النسب المئوية أو قيم الجودة أو معلمات الأداة بشكل متكرر. + احفظ القيمة كإعداد مسبق بعد كتابتها مرة واحدة، ثم أعد استخدامها بدلاً من إعادة بناء نفس الإعداد. + احتفظ بعناصر التحكم في الإيماءات للضبط البصري التقريبي والقيم المكتوبة لمتطلبات الإخراج الصارمة. + حفظ بالقرب من الأصل + ضع الملفات التي تم إنشاؤها بجانب الصور المصدر عندما يكون سياق المجلد مهمًا + الاحتفاظ بالمخرجات مع مصادرها + يعد Save To Original Folder مفيدًا لمجلدات الكاميرا ومجلدات المشروع وعمليات مسح المستندات ضوئيًا ومجموعات العميل حيث يجب أن تظل النسخة المحررة بجوار المصدر. يمكن أن يكون أقل ترتيبًا من مجلد الإخراج المخصص، لذا قم بدمجه مع لاحقات أو أنماط واضحة لأسماء الملفات. + قم بتمكينه عندما يكون كل مجلد مصدر ذا معنى بالفعل ويجب أن تظل المخرجات مجمعة هناك. + استخدم لاحقات أسماء الملفات أو البادئات أو الطوابع الزمنية أو الأنماط بحيث يسهل فصل النسخ المحررة عن النسخ الأصلية. + قم بتعطيله للدفعات المختلطة عندما تريد جمع كل الملفات التي تم إنشاؤها في مجلد تصدير واحد. + تخطي الإخراج الأكبر + تجنب حفظ التحويلات التي تصبح أثقل من المصدر بشكل غير متوقع + حماية الدفعات من مفاجآت الحجم السيئ + تؤدي بعض التحويلات إلى زيادة حجم الملفات، خاصة لقطات شاشة PNG، أو الصور المضغوطة بالفعل، أو WebP عالي الجودة، أو الصور التي تحتوي على بيانات تعريف محفوظة. السماح بالتخطي إذا كان أكبر يمنع تلك المخرجات من استبدال مصدر أصغر في سير العمل حيث يكون تقليل الحجم هو الهدف الرئيسي. + قم بتمكينه عندما يكون المقصود من التصدير ضغط الملفات أو تغيير حجمها أو إعدادها لحدود التحميل. + مراجعة الملفات التي تم تخطيها بعد الدفعة الواحدة؛ ربما تم تحسينها بالفعل أو قد تحتاج إلى تنسيق مختلف. + قم بتعطيله عندما تكون الجودة أو الشفافية أو توافق التنسيق أكثر أهمية من الحجم النهائي للملف. + الحافظة والروابط + التحكم في الإدخال التلقائي للحافظة ومعاينات الارتباط للحصول على أدوات تعتمد على النص بشكل أسرع + اجعل الإدخال التلقائي قابلاً للتنبؤ به + تعد إعدادات الحافظة والارتباط ملائمة لسير عمل QR وBase64 وURL وسير العمل الذي يحتوي على نصوص كثيفة، ولكن الإدخال التلقائي يجب أن يظل مقصودًا. إذا بدا أن التطبيق يملأ الحقول بنفسه، فتحقق من سلوك لصق الحافظة؛ إذا كانت بطاقات الارتباط مشوشة أو بطيئة، فاضبط سلوك معاينة الارتباط. + اسمح بلصق الحافظة تلقائيًا عندما تقوم بنسخ النص بشكل متكرر وافتح أداة المطابقة على الفور. + قم بتعطيل اللصق التلقائي إذا ظهر محتوى الحافظة الخاصة في أدوات لم تكن تتوقعها. + قم بإيقاف تشغيل معاينات الارتباط عندما يكون جلب المعاينة بطيئًا أو مشتتًا أو غير ضروري لسير عملك. + ضوابط مدمجة + استخدم محددات أكثر كثافة ووضع الإعدادات السريعة عندما تكون مساحة الشاشة ضيقة + تناسب المزيد من الضوابط على الشاشات الصغيرة + تساعد المحددات المدمجة ووضع الإعدادات السريعة على الهواتف الصغيرة وتحرير المناظر الطبيعية والأدوات التي تحتوي على العديد من المعلمات. والمقايضة هي أن عناصر التحكم يمكن أن تبدو أكثر كثافة، لذلك يكون هذا أفضل بعد أن تتعرف بالفعل على الخيارات الشائعة. + قم بتمكين المحددات المضغوطة عندما تبدو مربعات الحوار أو قوائم الخيارات طويلة جدًا بالنسبة للشاشة. + اضبط جانب الإعدادات السريعة إذا كان من السهل الوصول إلى عناصر التحكم في الأداة من جانب واحد من الشاشة. + ارجع إلى عناصر التحكم الأكثر اتساعًا إذا كنت لا تزال تتعلم التطبيق أو تفوتك النقر على الخيارات الكثيفة بشكل متكرر. + تحميل الصور من الويب + الصق عنوان URL لصفحة أو صورة، وقم بمعاينة الصور التي تم تحليلها، ثم احفظ النتائج المحدّدة أو شاركها + استخدم صور الويب عمدا + تقوم ميزة تحميل صور الويب بتوزيع مصادر الصور من عنوان URL المقدم، ومعاينة أول صورة متاحة، وتتيح لك حفظ أو مشاركة الصور المحدّدة التي حُلّلت. تستخدم بعض المواقع روابط مؤقتة أو محمية أو أُنشئت بواسطة برنامج نصي، لذا قد لا تعرض الصفحة التي تعمل في المتصفح أي صور قابلة للاستخدام. + الصق عنوان URL المباشر للصورة أو عنوان URL للصفحة وانتظر حتى تظهر قائمة الصور التي تم تحليلها. + استخدم عناصر التحكم في الإطار أو التحديد عندما تحتوي الصفحة على عدة صور وتحتاج إلى بعضها فقط. + إذا لم يتم تحميل أي شيء، فجرّب رابط الصورة المباشر أو قم بالتنزيل من خلال المتصفح أولاً؛ قد يقوم الموقع بحظر التحليل أو استخدام الروابط الخاصة. + اختر نوع تغيير الحجم + فهم الصريح والمرن والقص والملاءمة قبل فرض الصورة على أبعاد جديدة + التحكم في الشكل والقماش ونسبة العرض إلى الارتفاع + يحدّد نوع تغيير الحجم ما يحدث عندما لا يتطابق العرض والارتفاع المطلوبان مع نسبة العرض إلى الارتفاع الأصلية. إنه الفرق بين تمديد وحدات البكسل أو الحفاظ على النسب أو اقتصاص مساحة إضافية أو إضافة لوحة خلفية. + استخدم Explicit فقط عندما يكون التشويه مقبولاً أو عندما يكون للمصدر نفس نسبة العرض إلى الارتفاع مثل الهدف. + استخدم المرونة للحفاظ على التناسب عن طريق تغيير الحجم من الجانب المهم، خاصة للصور والدفعات المختلطة. + استخدم Crop للإطارات الصارمة بدون حدود، أو Fit عندما يجب أن تظل الصورة بأكملها مرئية داخل لوحة قماشية ثابتة. + اختر وضع مقياس الصورة + اختر مرشح إعادة التشكيل الذي يتحكم في الحدة والسلاسة والسرعة وعناصر الحواف + تغيير الحجم دون ليونة عرضية + يتحكم وضع مقياس الصورة في كيفية حساب وحدات البكسل الجديدة أثناء تغيير الحجم. تتميز الأوضاع البسيطة بأنها سريعة ويمكن التنبؤ بها، بينما يمكن للمرشحات المتقدمة الحفاظ على التفاصيل بشكل أفضل ولكنها قد تزيد من حدة الحواف أو إنشاء هالات أو تستغرق وقتًا أطول في الصور الكبيرة. + استخدم Basic أو Bilinear لتغيير الحجم اليومي بسرعة عندما تكون النتيجة أصغر حجمًا ونظيفة. + استخدم أوضاع نمط Lanczos أو Robidoux أو Spline أو EWA عندما تكون التفاصيل الدقيقة أو النص أو تقليل الحجم عالي الجودة أمرًا مهمًا. + استخدم الأقرب لرسومات البكسل والأيقونات والرسومات ذات الحواف الصلبة حيث قد تؤدي وحدات البكسل غير الواضحة إلى إفساد المظهر. + قياس مساحة اللون + قرر كيفية مزج الألوان أثناء إعادة تشكيل وحدات البكسل أثناء تغيير الحجم + حافظ على التدرجات والحواف طبيعية + تغير مساحة لون المقياس العمليات الحسابية المستخدمة أثناء تغيير الحجم. تكون معظم الصور جيدة مع الوضع الافتراضي، لكن المساحات الخطية أو الإدراكية يمكن أن تجعل التدرجات والحواف الداكنة والألوان المشبعة تبدو أكثر نظافة بعد القياس القوي. + اترك الإعداد الافتراضي عندما تكون السرعة والتوافق أكثر أهمية من اختلافات الألوان الصغيرة. + جرّب الأوضاع الخطية أو الإدراكية عندما تبدو الخطوط العريضة الداكنة أو لقطات شاشة واجهة المستخدم أو التدرجات أو نطاقات الألوان خاطئة بعد تغيير الحجم. + قارن قبل وبعد على الشاشة المستهدفة الحقيقية، لأن تغييرات مساحة اللون يمكن أن تكون دقيقة وتعتمد على المحتوى. + الحد من أوضاع تغيير الحجم + اعرف متى يجب تخطي الصور ذات الحد الزائد أو إعادة ترميزها أو تصغيرها لتناسبها + اختر ما يحدث عند الحد الأقصى + إن الحد من تغيير الحجم ليس مجرد أداة أصغر حجمًا للصور. يحدّد وضعه ما يفعله التطبيق عندما يكون المصدر بالفعل داخل حدودك أو عندما يخالف جانب واحد فقط القاعدة، وهو أمر مهم للمجلدات المختلطة والتحضير للتحميل. + استخدم \"تخطي\" عندما يجب ترك الصور الموجودة داخل الحدود دون تغيير وعدم تصديرها مرة أخرى. + استخدم إعادة الترميز عندما تكون الأبعاد مقبولة ولكنك لا تزال بحاجة إلى تنسيق جديد أو سلوك بيانات التعريف أو الجودة أو اسم الملف. + استخدم Zoom عندما يجب تصغير حجم الصور التي تكسر الحدود بشكل متناسب حتى تناسب المربع المسموح به. + أهداف نسبة العرض إلى الارتفاع + تجنب الوجوه الممتدة والحواف المقطوعة والحدود غير المتوقعة في أحجام المخرجات الصارمة + مطابقة الشكل قبل تغيير الحجم + العرض والارتفاع ليسا سوى نصف هدف تغيير الحجم. تُحدّد نسبة العرض إلى الارتفاع ما إذا كانت الصورة يمكن ملاءمتها بشكل طبيعي، أو تحتاج إلى اقتصاص، أو تحتاج إلى لوحة قماشية حولها. التحقق من الشكل أولاً يمنع نتائج تغيير الحجم الأكثر إثارة للدهشة. + قارن الشكل المصدر بالشكل المستهدف قبل اختيار Explicit أو Crop أو Fit. + قم بالقص أولاً عندما يفقد الهدف خلفية إضافية وتحتاج الوجهة إلى إطار صارم. + استخدم Fit أو Elastic عندما يجب أن يظل كل جزء من الصورة الأصلية مرئيًا. + تغيير الحجم الراقي أو العادي + اعرف متى تحتاج إضافة وحدات البكسل إلى الذكاء الاصطناعي ومتى يكون القياس البسيط كافيًا + لا تخلط بين الحجم والتفاصيل + تغيير الحجم العادي يغير الأبعاد عن طريق استيفاء وحدات البكسل؛ لا يمكنها اختراع تفاصيل حقيقية. يمكن للذكاء الاصطناعي الراقي إنشاء تفاصيل أكثر وضوحًا، ولكنه أبطأ وقد يهلوس الملمس، لذا يعتمد الاختيار الصحيح على ما إذا كنت بحاجة إلى التوافق أو السرعة أو الاستعادة البصرية. + استخدم تغيير الحجم الطبيعي للصور المصغرة وحدود التحميل ولقطات الشاشة وتغييرات الأبعاد البسيطة. + استخدم الذكاء الاصطناعي الراقي عندما تحتاج الصورة الصغيرة أو الناعمة إلى أن تبدو أفضل بحجم أكبر. + قارن الوجوه والنصوص والأنماط بعد ترقية الذكاء الاصطناعي لأن التفاصيل التي تم إنشاؤها قد تبدو مقنعة ولكنها غير صحيحة. + ترتيب التصفية مهم + طبّق التنظيف واللون والحدة والضغط النهائي بتسلسل مقصود + بناء كومة مرشح أنظف + المرشحات ليست قابلة للتبديل دائمًا. يمكن أن يؤدي التمويه قبل الوضوح، أو تعزيز التباين قبل التعرف الضوئي على الحروف، أو تغيير اللون قبل الضغط إلى إنتاج مخرجات مختلفة تمامًا من نفس عناصر التحكم. + قصّ ودوِّر أولاً حتى تعمل المرشحات اللاحقة فقط على وحدات البكسل التي ستبقى. + طبّق التعرض وتوازن اللون الأبيض والتباين قبل زيادة الوضوح أو تأثيرات النمط. + احتفظ بتغيير الحجم والضغط النهائي بالقرب من النهاية حتى يتم تصدير التفاصيل التي تمت معاينتها بشكل متوقع. + إصلاح حواف الخلفية + قم بتنظيف الهالات والظلال المتبقية والشعر والثقوب والخطوط العريضة الناعمة بعد الإزالة التلقائية + اجعل القواطع تبدو مقصودة + غالبًا ما تبدو الأقنعة التلقائية جيدة للوهلة الأولى ولكنها تكشف عن المشكلات على الخلفيات الساطعة أو الداكنة أو المنقوشة. تنظيف الحواف هو الفرق بين القطع السريع والملصق أو الشعار أو صورة المنتج القابلة لإعادة الاستخدام. + قم بمعاينة النتيجة مقابل خلفيات متباينة للكشف عن الهالات وتفاصيل الحواف المفقودة. + استخدم استعادة الشعر والزوايا والأشياء الشفافة قبل مسح بقايا الطعام المحيطة. + قم بتصدير اختبار صغير على لون الخلفية النهائي عندما يتم وضع القطع في التصميم. + علامات مائية قابلة للقراءة + اجعل العلامات مرئية على الصور الساطعة والداكنة والمزدحمة والمقتصة دون إتلاف الصورة + حماية الصورة دون إخفاءها + قد تختفي العلامة المائية التي تبدو جيدة على صورة ما على صورة أخرى. يجب اختيار الحجم والعتامة والتباين والموضع والتكرار للمجموعة بأكملها، وليس فقط للمعاينة الأولى. + اختبر العلامة على الصور الأكثر سطوعًا وأغمقًا في الدفعة قبل تصدير جميع الملفات. + استخدم درجة عتامة أقل للعلامات المتكررة الكبيرة وتباينًا أقوى لعلامات الزوايا الصغيرة. + احتفظ بالوجوه والنصوص وتفاصيل المنتج المهمة واضحة ما لم يكن المقصود من العلامة منع إعادة الاستخدام. + تقسيم صورة واحدة إلى البلاط + قص صورة واحدة حسب الصفوف والأعمدة والنسب المئوية المخصصة والتنسيق والجودة + تحضير الشبكات والقطع المطلوبة + يؤدي تقسيم الصور إلى إنشاء مخرجات متعدّدة من صورة مصدر واحدة. ويمكنه التقسيم حسب عدد الصفوف والأعمدة، وضبط النسب المئوية للصفوف أو الأعمدة، وحفظ القطع بتنسيق وجودة الإخراج المحدّدين، وهو أمر مفيد للشبكات وشرائح الصفحة والصور البانورامية والمشاركات الاجتماعية المرتبة. + اختر الصورة المصدر، ثم عيّن عدد الصفوف والأعمدة التي تحتاجها. + اضبط النسب المئوية للصفوف أو الأعمدة عندما لا تكون جميع القطع متساوية الحجم. + اختر تنسيق الإخراج وجودته قبل الحفظ بحيث تستخدم كل قطعة تم إنشاؤها نفس قواعد التصدير. + قطع شرائط الصورة + قم بإزالة المناطق الرأسية أو الأفقية ودمج الأجزاء المتبقية معًا مرة أخرى + قم بإزالة المنطقة دون ترك فجوة + يختلف قطع الصور عن الاقتصاص العادي. يمكنه إزالة شريط رأسي أو أفقي محدّد، ثم دمج أجزاء الصورة المتبقية معًا. يحافظ الوضع العكسي على المنطقة المحدّدة بدلاً من ذلك، واستخدام كلا الاتجاهين العكسيين يحافظ على المستطيل المحدّد. + استخدم القطع الرأسي للمناطق الجانبية أو الأعمدة أو الشرائط الوسطى؛ استخدم القطع الأفقي لللافتات أو الفجوات أو الصفوف. + فعّل العكس على المحور عندما تريد الاحتفاظ بالجزء المحدّد بدلاً من إزالته. + قم بمعاينة الحواف بعد القطع لأن المحتوى المدمج قد يبدو مفاجئًا عندما تتقاطع المنطقة التي تمت إزالتها مع تفاصيل مهمة. + اختر تنسيق الإخراج + اختر JPEG أو PNG أو WebP أو AVIF أو JXL أو PDF بناءً على المحتوى والوجهة + دع الوجهة تقرر التنسيق + يعتمد التنسيق الأفضل على ما تحتويه الصورة والمكان الذي سيتم استخدامه فيه. تفشل الصور ولقطات الشاشة والأصول الشفافة والمستندات والملفات المتحركة بطرق مختلفة عندما يتم فرضها بتنسيق خاطئ. + استخدم JPEG للتوافق الواسع للصور عندما لا تكون الشفافية ووحدات البكسل الدقيقة مطلوبة. + استخدم PNG للحصول على لقطات شاشة واضحة ولقطات واجهة المستخدم ورسومات شفافة وتعديلات بدون فقدان البيانات. + استخدم WebP أو AVIF أو JXL عندما يكون الإخراج الحديث المضغوط مهمًا ويدعمه تطبيق الاستقبال. + استخراج أغلفة الصوت + احفظ صورة الألبوم المضمنة أو إطارات الوسائط المتاحة من الملفات الصوتية كصور PNG + سحب العمل الفني من الملفات الصوتية + يستخدم Audio Covers بيانات تعريف وسائط Android لقراءة الأعمال الفنية المضمنة من الملفات الصوتية. عندما لا يتوفر العمل الفني المضمن، قد يعود المسترد إلى إطار الوسائط إذا تمكن Android من توفيره؛ وفي حالة عدم وجود أي منهما، تشير الأداة إلى عدم وجود صورة لاستخراجها. + افتح أغلفة الصوت وحدّد ملفًا صوتيًا واحدًا أو أكثر مع صورة الغلاف المتوقعة. + قم بمراجعة الغلاف المستخرج قبل الحفظ أو المشاركة، خاصة بالنسبة للملفات من تطبيقات البث أو التسجيل. + إذا لم يُعثر على صورة، فمن المحتمل أن الملف الصوتي لا يحتوي على غلاف مضمن أو أن أندرويد لا يمكنه الكشف عن إطار قابل للاستخدام. + التواريخ والبيانات الوصفية للموقع + قرر ما تريد الاحتفاظ به عندما يكون وقت الالتقاط مهمًا ولكن لا ينبغي أن يتسرب نظام تحديد المواقع العالمي (GPS) أو بيانات الجهاز + افصل البيانات الوصفية المفيدة عن البيانات الوصفية الخاصة + البيانات الوصفية ليست كلها بنفس القدر من الخطورة. يمكن أن تكون تواريخ الالتقاط مفيدة للأرشفة والفرز، في حين أن نظام تحديد المواقع العالمي (GPS) أو الحقول الشبيهة بالتسلسل للجهاز أو علامات البرامج أو حقول المالك يمكن أن تكشف أكثر مما هو مقصود. + احتفظ بعلامات التاريخ والوقت عندما يكون الترتيب الزمني مهمًا للألبومات أو عمليات المسح أو سجل المشروع. + قم بإزالة نظام تحديد المواقع العالمي (GPS) وعلامات تعريف الجهاز قبل المشاركة العامة أو إرسال الصور إلى الغرباء. + صدِّر عينة وفحص EXIF مرة أخرى عندما تكون متطلبات الخصوصية صارمة. + تجنب تضارب أسماء الملفات + قم بحماية مخرجات الدفعات عندما تشترك العديد من الملفات بأسماء مثل image.jpg أو Screenshot.png + اجعل كل اسم مخرجات فريدًا + تعد أسماء الملفات المكررة شائعة عندما تأتي الصور من برامج المراسلة أو لقطات الشاشة أو المجلدات السحابية أو الكاميرات المتعدّدة. يمنع النمط الجيد الاستبدال العرضي ويحافظ على إمكانية إرجاع المخرجات إلى مصادرها. + قم بتضمين الاسم الأصلي بالإضافة إلى لاحقة عندما يكون من الممكن التعرف على النسخة المحررة. + أضف التسلسل أو الطابع الزمني أو الإعداد المسبق أو الأبعاد عند معالجة دفعات مختلطة كبيرة. + تجنب الكتابة فوق سير العمل حتى تتأكد من عدم إمكانية تعارض نمط التسمية. + احفظ أو شارك + اختر بين ملف دائم أو تسليم مؤقت إلى تطبيق آخر + قم بالتصدير بالقصد الصحيح + قد يكون الحفظ والمشاركة متشابهين، لكنهما يحلان مشكلات مختلفة. يؤدي الحفظ إلى إنشاء ملف يمكنك العثور عليه لاحقًا؛ ترسل المشاركة النتيجة التي تم إنشاؤها عبر Android إلى تطبيق آخر وقد لا تترك نسخة محلية دائمة. + استخدم \"حفظ\" عندما يجب أن يبقى الإخراج في مجلد معروف، أو يتم نسخه احتياطيًا، أو إعادة استخدامه لاحقًا. + استخدم المشاركة للرسائل السريعة أو مرفقات البريد الإلكتروني أو النشر الاجتماعي أو إرسال النتيجة إلى محرر آخر. + احفظ أولاً عندما يكون التطبيق المتلقي غير موثوق به أو عندما يكون من الصعب إعادة إنشاء المشاركة الفاشلة. + حجم صفحة PDF والهوامش + اختر حجم الورق والسلوك الملائم ومساحة التنفس قبل إنشاء مستند + جعل صفحات الصور قابلة للطباعة والقراءة + يمكن أن تصبح الصور صفحات PDF غريبة عندما لا يتطابق شكلها مع حجم الورق المختار. تُحدّد الهوامش ووضع الملاءمة واتجاه الصفحة ما إذا كان المحتوى مقتصًا أو صغيرًا أو ممتدًا أو مريحًا للقراءة. + استخدم حجم ورق حقيقي عندما يكون من الممكن طباعة ملف PDF أو إرساله إلى نموذج. + استخدم الهوامش لعمليات المسح ولقطات الشاشة حتى لا يجلس النص على حافة الصفحة. + قم بمعاينة الصفحات الرأسية والأفقية معًا عندما يكون للصور المصدر اتجاهات مختلطة. + تنظيف عمليات المسح + قم بتحسين حواف الورق والظلال والتباين والتدوير وسهولة القراءة قبل PDF أو OCR + تحضير الصفحات قبل الأرشفة + يمكن التقاط المسح تقنيًا ولكن لا يزال من الصعب قراءته. غالبًا ما تكون خطوات التنظيف الصغيرة قبل تصدير PDF أكثر أهمية من إعدادات الضغط، خاصة بالنسبة للإيصالات والملاحظات المكتوبة بخط اليد والصفحات منخفضة الإضاءة. + قم بتصحيح المنظور واقتصاص حواف الجدول والأصابع والظلال وفوضى الخلفية. + قم بزيادة التباين بعناية حتى يصبح النص أكثر وضوحًا دون إتلاف الطوابع أو التوقيعات أو علامات القلم الرصاص. + قم بتشغيل تقنية التعرف الضوئي على الحروف (OCR) فقط بعد أن تصبح الصفحة في وضع مستقيم وقابلة للقراءة، ثم تحقق من الأرقام المهمة يدويًا. + ضغط ملفات PDF أو تدرجها الرمادي أو إصلاحها + استخدم عمليات PDF ذات الملف الواحد لنسخ المستندات الأخف أو القابلة للطباعة أو المعاد بناؤها + اختر عملية تنظيف PDF + تشتمل أدوات PDF على عمليات منفصلة للضغط وتحويل التدرج الرمادي والإصلاح. يعمل الضغط والتدرج الرمادي بشكل أساسي من خلال الصور المضمنة، بينما يؤدي الإصلاح إلى إعادة بناء بنية PDF؛ ولا ينبغي التعامل مع أي من هذه الأمور على أنها إصلاح مضمون لكل مستند. + استخدم ضغط PDF عندما يحتوي المستند على صور ويكون حجم الملف مهمًا للمشاركة. + استخدم تدرج الرمادي عندما تكون طباعة المستند أسهل أو عندما لا يحتاج إلى صور ملونة. + استخدم إصلاح PDF على نسخة عندما يفشل فتح مستند أو يكون هيكله الداخلي تالفًا، ثم تحقق من الملف المعاد إنشاؤه. + استخراج الصور من ملفات PDF + قم باستعادة صور PDF المضمنة وحزم النتائج المستخرجة في الأرشيف + حفظ الصور المصدر من المستندات + يقوم برنامج Extract Images بمسح ملف PDF بحثًا عن كائنات الصور المضمنة، ويتخطى التكرارات حيثما أمكن، ويخزن الصور المستردة في أرشيف ZIP تم إنشاؤه. فهو يستخرج فقط الصور المضمنة بالفعل في ملف PDF، وليس النص أو الرسومات المتجهة أو كل صفحة مرئية كلقطة شاشة. + استخدم استخراج الصور عندما تحتاج إلى تخزين الصور داخل ملف PDF، مثل الصور من الكتالوج أو الأصول الممسوحة ضوئيًا. + استخدم PDF لتحويل الصور أو استخراج الصفحة بدلاً من ذلك عندما تحتاج إلى صفحات كاملة، بما في ذلك النص والتخطيط. + إذا لم تبلغ الأداة عن أي صور مضمنة، فقد تكون الصفحة عبارة عن محتوى نصي/متجه أو قد لا يتم تخزين الصور ككائنات قابلة للاستخراج. + البيانات الوصفية، والتسطيح، ومخرجات الطباعة + قم بتحرير خصائص المستند أو تنقيط الصفحات أو إعداد تخطيطات طباعة مخصصة عن عمد + اختر إجراء المستند النهائي + تعمل بيانات تعريف PDF والتسوية وإعداد الطباعة على حل مشكلات المرحلة النهائية المختلفة. تتحكم البيانات التعريفية في خصائص المستند، ويعمل خيار Flatten PDF على تحويل الصفحات إلى مخرجات أقل قابلية للتحرير، ويعمل خيار Print PDF على إعداد تخطيط جديد مع حجم الصفحة، والاتجاه، والهامش، وعناصر التحكم لكل صفحة في الورقة. + استخدم بيانات التعريف عندما يكون المؤلف أو العنوان أو الكلمات الرئيسية أو المنتج أو تنظيف الخصوصية أمرًا مهمًا. + استخدم Flatten PDF عندما يصبح من الصعب تحرير محتوى الصفحة المرئي ككائنات PDF منفصلة. + استخدم طباعة PDF عندما تحتاج إلى حجم صفحة مخصّص أو اتجاه أو هوامش أو صفحات متعدّدة لكل ورقة. + تحضير الصور للتعرف الضوئي على الحروف + استخدم الاقتصاص والتدوير والتباين وتقليل التشويش وتغيير الحجم قبل أن تطلب التعرف الضوئي على الحروف لقراءة النص + إعطاء بكسلات أنظف للتعرف الضوئي على الحروف + غالبًا ما تأتي أخطاء التعرف الضوئي على الحروف من الصورة المدخلة وليس من محرك التعرف الضوئي على الحروف. الصفحات الملتوية والتباين المنخفض والضبابية والظلال والنص الصغير والخلفيات المختلطة كلها تقلل من جودة التعرُّف. + قص إلى منطقة النص ودوّر الصورة بحيث تكون الخطوط أفقية قبل التعرُّف عليها. + قم بزيادة التباين أو التحويل إلى أبيض وأسود أكثر وضوحًا فقط عندما يجعل قراءة الحروف أسهل. + قم بتغيير حجم النص الصغير لأعلى بشكل معتدل، ولكن تجنب التوضيح الشديد الذي يؤدي إلى إنشاء حواف زائفة. + تحقق من محتوى QR + قم بمراجعة الروابط وبيانات اعتماد Wi-Fi وجهات الاتصال والإجراءات قبل الوثوق بالرمز + التعامل مع النص الذي تم فك ترميزه على أنه إدخال غير موثوق به + يمكن لرمز الاستجابة السريعة إخفاء عنوان URL أو بطاقة جهة الاتصال أو كلمة مرور Wi-Fi أو حدث التقويم أو رقم الهاتف أو النص العادي. قد لا تتطابق العلامة المرئية بالقرب من الكود مع الحمولة الفعلية، لذا قم بمراجعة المحتوى الذي تم فك تشفيره قبل التصرف عليه. + افحص عنوان URL الذي تم فك تشفيره بالكامل قبل فتحه، وخاصة النطاقات المختصرة أو غير المألوفة. + كن حذرًا مع رموز Wi-Fi وجهات الاتصال والتقويم والهاتف والرسائل النصية القصيرة لأنها قد تؤدي إلى اتخاذ إجراءات حساسة. + انسخ المحتوى أولاً عندما تحتاج إلى التحقق منه في مكان آخر بدلاً من فتحه على الفور. + توليد رموز QR + أنشئ رموزًا من نص عادي وعناوين URL وشبكة Wi-Fi وجهات الاتصال والبريد الإلكتروني والهاتف والرسائل النصية القصيرة وبيانات الموقع + قم ببناء حمولة QR الصحيحة + يمكن لشاشة QR إنشاء رموز من المحتوى المُدخل بالإضافة إلى مسح الرموز الموجودة. تساعد أنواع QR المنظمة على تشفير البيانات بتنسيق تفهمه التطبيقات الأخرى، مثل تفاصيل تسجيل الدخول إلى Wi-Fi أو بطاقات الاتصال أو حقول البريد الإلكتروني أو أرقام الهواتف أو محتوى الرسائل القصيرة أو عناوين URL أو الإحداثيات الجغرافية. + اختر نصًا عاديًا للملاحظات البسيطة، أو استخدم نوعًا منظمًا عندما يجب أن يفتح الرمز كبيانات Wi-Fi أو جهة اتصال أو عنوان URL أو البريد الإلكتروني أو الهاتف أو الرسائل القصيرة أو الموقع. + تحقق من كل حقل قبل مشاركة الكود الذي تم إنشاؤه لأن المعاينة المرئية تعكس فقط الحمولة التي أدخلتها. + اختبر رمز الاستجابة السريعة المحفوظ باستخدام الماسح الضوئي عندما تتم طباعته أو نشره علنًا أو استخدامه من قبل أشخاص آخرين. + اختر وضع المجموع الاختباري + احسب التجزئة من الملفات أو النص، أو قارن ملفًا واحدًا، أو قم بفحص العديد من الملفات دفعة واحدة + التحقق من المحتوى وليس المظهر + تحتوي أدوات المجموع الاختباري على علامات تبويب منفصلة لتجزئة الملف، وتجزئة النص، ومقارنة ملف بتجزئة معروفة، ومقارنة الدُفعات. يُثبت المجموع الاختباري هوية البايت مقابل البايت للخوارزمية المحددة؛ لا يثبت أن الصورتين تبدوان متشابهتين فقط. + استخدم الحساب للملفات وتجزئة النص للبيانات النصية المكتوبة أو الملصقة. + استخدم المقارنة عندما يمنحك شخص ما المجموع الاختباري المتوقع لملف واحد. + استخدم المقارنة المجمعة عندما تحتاج العديد من الملفات إلى التجزئة أو التحقق في مسار واحد، ثم حافظ على اتساق الخوارزمية المحددة. + خصوصية الحافظة + استخدم ميزات الحافظة التلقائية دون الكشف عن البيانات الخاصة المنسوخة عن طريق الخطأ + احتفظ بالمحتوى المنسوخ بشكل مقصود + تُعد أتمتة الحافظة أمرًا مريحًا، لكن النص المنسوخ قد يحتوي على كلمات مرور أو روابط أو عناوين أو رموز مميزة أو ملاحظات خاصة. تعامل مع الإدخال المستند إلى الحافظة كميزة سرعة يجب أن تتوافق مع عاداتك وتوقعات الخصوصية. + قم بتعطيل استخدام الحافظة التلقائي في حالة ظهور نص خاص في الأدوات بشكل غير متوقع. + استخدم حفظ الحافظة فقط عندما تريد النتيجة عمدًا لتجنب تخزينها. + امسح الحافظة أو استبدلها بعد التعامل مع النص الحساس أو بيانات QR أو حمولات Base64. + تنسيقات الألوان + افهم قيم HEX وRGB وHSV وalpha وقيم الألوان المنسوخة قبل لصقها في مكان آخر + انسخ التنسيق الذي يتوقعه الهدف + يمكن كتابة نفس اللون المرئي بعدة طرق. قد تتوقع أداة التصميم، أو حقل CSS، أو قيمة ألوان Android، أو محرر الصور ترتيبًا مختلفًا، أو معالجة ألفا، أو نموذج ألوان. + استخدم HEX عند اللصق في حقول الويب أو التصميم أو السمات التي تتوقع رموز ألوان مضغوطة. + استخدم RGB أو HSV عندما تحتاج إلى ضبط القنوات أو السطوع أو التشبع أو تدرج الألوان يدويًا. + تحقق من ألفا بشكل منفصل عندما تكون الشفافية مهمة لأن بعض الوجهات تتجاهلها أو تستخدم ترتيبًا مختلفًا. + تصفح مكتبة الألوان + ابحث عن الألوان المسماة بالاسم أو HEX واحتفظ بالمفضلة بالقرب من الأعلى + ابحث عن الألوان المسماة القابلة لإعادة الاستخدام + تقوم مكتبة الألوان بتحميل مجموعة ألوان مسماة، وفرزها حسب الاسم، وتصفيتها حسب اسم اللون أو قيمة HEX، وتخزين الألوان المفضلة حتى يظل الوصول إلى الإدخالات المهمة أسهل. يكون ذلك مفيدًا عندما تحتاج إلى لون مسمى حقيقي بدلاً من أخذ عينات من الصورة. + ابحث حسب اسم اللون عندما تعرف العائلة، مثل الأحمر أو الأزرق أو الأردواز أو النعناع. + ابحث باستخدام HEX عندما يكون لديك بالفعل لون رقمي وتريد العثور على إدخالات مسماة مطابقة أو قريبة. + قم بتمييز الألوان المتكررة كمفضلة حتى تتقدم على المكتبة العامة أثناء التصفح. + استخدم مجموعات التدرج الشبكي + قم بتنزيل موارد التدرج الشبكي المتاحة وشارك الصور المحددة + استخدم أصول التدرج الشبكي البعيد + يمكن لـ Mesh Gradients تحميل مجموعة موارد عن بعد، وتنزيل صور التدرج المفقودة، وإظهار تقدم التنزيل، ومشاركة التدرجات المحددة. يعتمد التوفر على الوصول إلى الشبكة ومخزن الموارد البعيد، لذا فإن القائمة الفارغة تعني عادةً أن الموارد لم تكن متوفرة بعد. + افتح Mesh Gradients من أدوات التدرج عندما تريد صور متدرجة شبكية جاهزة. + انتظر تحميل المورد أو تقدم التنزيل قبل افتراض أن المجموعة فارغة. + حدد التدرجات اللونية التي تحتاجها وشاركها، أو ارجع إلى Gradient Maker عندما تريد إنشاء تدرج مخصص يدويًا. + حل الأصول التي تم إنشاؤها + اختر حجم المخرجات قبل إنشاء التدرجات اللونية أو الضوضاء أو الخلفيات أو الأعمال الفنية المشابهة لـ SVG أو الأنسجة + قم بالإنشاء بالحجم الذي تحتاجه + لا تحتوي الصور التي تم إنشاؤها على كاميرا أصلية يمكن الرجوع إليها. إذا كان الإخراج صغيرًا جدًا، فقد يؤدي القياس اللاحق إلى تنعيم الأنماط، أو تحويل التفاصيل، أو تغيير كيفية تجانب الأصول وضغطها. + قم بتعيين الأبعاد لحالة الاستخدام النهائي أولاً، مثل ورق الحائط أو الأيقونة أو الخلفية أو الطباعة أو التراكب. + استخدم أحجامًا أكبر للأنسجة التي يمكن قصها أو قرّبها أو إعادة استخدامها في عدة تخطيطات. + قم بتصدير الإصدارات التجريبية قبل المخرجات الضخمة لأن التفاصيل الإجرائية يمكن أن تغير طريقة عمل الضغط. + تصدير الخلفيات الحالية + يمكنك استرداد الخلفيات المتوفرة للشاشة الرئيسية والقفل والخلفيات المدمجة من الجهاز + حفظ أو مشاركة الخلفيات المثبتة + يقوم تطبيق Wallpapers Export بقراءة الخلفيات التي يعرضها Android من خلال واجهات برمجة تطبيقات خلفية النظام. اعتمادًا على الجهاز، قد يكون إصدار Android والأذونات ومتغير التصميم أو الخلفيات الرئيسية أو القفل أو الخلفيات المضمنة متاحة بشكل منفصل أو قد تكون مفقودة. + افتح تصدير الخلفيات لتحميل الخلفيات التي يسمح النظام للتطبيق بقراءتها. + حدد إدخالات الصفحة الرئيسية أو القفل أو خلفية الشاشة المضمنة المتوفرة التي تريد حفظها أو نسخها أو مشاركتها. + إذا كانت خلفية الشاشة مفقودة أو كانت الميزة غير متوفرة، فعادةً ما يكون ذلك عبارة عن قيود على النظام أو الإذن أو متغير البناء بدلاً من إعداد التحرير. + زوايا الرسوم المتحركة خنق + انسخ اللون كـ + HEX + الاسم + نموذج حصيرة بورتريه لقواطع سريعة للأشخاص مع شعر أكثر نعومة ومعالجة للحواف. + تحسين سريع للإضاءة المنخفضة باستخدام تقدير منحنى Zero-DCE++. + نموذج استعادة الصورة Restormer لتقليل خطوط المطر وتحف الطقس الرطب. + قم بتوثيق نموذج إزالة التشوه الذي يتنبأ بشبكة إحداثيات ويعيد رسم خريطة للصفحات المنحنية في مسح أكثر اتساعًا. + مقياس مدة الحركة + يتحكم في سرعة الرسوم المتحركة في التطبيق: القيمة 0 تعطل الحركة، والقيمة 1 تمثل الوضع العادي، أما القيم الأعلى فتؤدي إلى إبطاء الرسوم المتحركة + اعرض الأدوات الحديثة + اعرض الوصول السريع إلى الأدوات المستخدمة حديثًا على الشاشة الرئيسية + إحصائيات الاستخدام + استكشف عمليات فتح تطبيق وتشغيل الأدوات والأدوات الأكثر استخدامًا لديك + فتح التطبيق + فتح الأدوات + آخر أداة + %1$d يفتح • %2$s + لا توجد إحصائيات استخدام حتى الآن + افتح بعض الأدوات وستظهر هنا تلقائيًا + تصديات ناجحة + خط النشاط + تم حفظ البيانات + أعلى تنسيق + الأدوات المستخدمة + يتم تخزين إحصائيات الاستخدام الخاصة بك محليًا فقط على جهازك. يستخدمها ImageToolbox فقط لإظهار نشاطك الشخصي وأدواتك المفضلة ورؤى البيانات المحفوظة + محرر تحرير الصورة تنميق الصورة ضبط + تغيير الحجم تحويل المقياس الدقة الأبعاد العرض الارتفاع jpg jpeg png webp ضغط + ضغط الحجم والوزن بايت ميغابايت كيلو بايت تقليل الجودة وتحسينها + تقليم المحاصيل نسبة العرض إلى الارتفاع + تصحيح لون تأثير المرشح وضبط قناع Lut + رسم فرشاة الطلاء بالقلم الرصاص والتعليق التوضيحي + تشفير تعمية فك تشفير فك تعمية كلمة المرور السرية + مزيل الخلفية محو إزالة انقطاع ألفا شفافة + معاينة معرض العارض فحص مفتوح + غرزة دمج الجمع بين لقطة شاشة بانوراما طويلة + رابط تحميل الويب رابط الصورة على الإنترنت + عينة قطارة المنتقى ذات اللون السداسي rgb + لوحة الألوان حوامل استخراج المهيمنة + خصوصية البيانات الوصفية exif، إزالة موقع GPS النظيف + قارن الفرق قبل وبعد + الحد الأقصى لتغيير الحجم والحد الأدنى لأبعاد الميجابكسل + أدوات صفحات وثيقة pdf + يتعرف نص ocr على استخراج المسح + شبكة لون الخلفية التدرج + تراكب ختم نص شعار العلامة المائية + gif الرسوم المتحركة تحويل الإطارات + apng الرسوم المتحركة المتحركة png تحويل الإطارات + أرشيف مضغوط، حزمة ضغط، فك ضغط الملفات + jxl jpeg xl تحويل تنسيق الصورة jpg + تتبع ناقلات svg تحويل xml + تحويل التنسيق jpg jpeg png webp heic avif jxl bmp + وثيقة مستند مسح الماسح الضوئي مسح الورق منظور المحاصيل + مسح ماسح الباركود qr + التراص تراكب متوسط التصوير الفلكي المتوسط + تقسيم أجزاء شريحة شبكة البلاط + تحويل الألوان إلى مختبر Hex rgb وhsl وhsv cmyk + webp تحويل تنسيق الصور المتحركة + مولد الضوضاء الحبوب الملمس العشوائي + الجمع بين تخطيط الشبكة المجمعة + طبقات الترميز تعلق على تحرير التراكب + Base64 ترميز وفك تشفير البيانات uri + التحقق من تجزئة المجموع الاختباري md5 sha sha1 sha256 + خلفية شبكة التدرج + exif البيانات الوصفية تحرير كاميرا تاريخ GPS + قطع البلاط قطع الشبكة الانقسام + الغلاف الصوتي للألبوم الفني والبيانات الوصفية للموسيقى + خلفية شاشة خلفية التصدير + شخصيات فن النص ascii + ai ml العصبية تعزز الجزء الراقي + لوحة مواد مكتبة الألوان تسمى الألوان + شادر glsl جزء تأثير الاستوديو + دمج pdf دمج انضمام + pdf تقسيم صفحات منفصلة + pdf تدوير اتجاه الصفحات + pdf إعادة ترتيب، إعادة ترتيب، فرز الصفحات + ترقيم صفحات ارقام صفحات pdf + يتعرف نص pdf ocr على المستخرج القابل للبحث + pdf نص شعار ختم العلامة المائية + pdf التوقيع وقع الرسم + pdf حماية كلمة المرور قفل التشفير + فتح pdf فك تشفير كلمة المرور وإزالة الحماية + ضغط pdf تصغير الحجم تحسين + قوات الدفاع الشعبي تدرج الرمادي أسود أبيض أحادي اللون + إصلاح قوات الدفاع الشعبي إصلاح استعادة المكسورة + خصائص البيانات الوصفية pdf exif عنوان المؤلف + pdf إزالة حذف الصفحات + هوامش تقليم المحاصيل pdf + قوات الدفاع الشعبي تسطيح الشروح أشكال الطبقات + pdf استخراج الصور الصور + ضغط أرشيف pdf مضغوط + طابعة طباعة pdf + قراءة عارض معاينة قوات الدفاع الشعبي + تحويل الصور إلى pdf jpg jpeg png + pdf إلى صفحات الصور تصدير jpg png + قوات الدفاع الشعبي التعليقات التوضيحية إزالة نظيفة + البديل المحايد + خطأ + إسقاط الظل + ارتفاع الأسنان + نطاق الأسنان الأفقي + نطاق الأسنان العمودي + الحافة العلوية + الحافة اليمنى + الحافة السفلية + الحافة اليسرى + الحافة الممزقة + إعادة تعيين إحصائيات الاستخدام + يتم فتح الأداة وحفظ الإحصائيات والخط والبيانات المحفوظة وسيتم إعادة تعيين التنسيق العلوي. ستبقى عمليات فتح التطبيق دون تغيير. + صوتي + ملف + تصدير الملفات الشخصية + حفظ الملف الشخصي الحالي + حذف ملف تعريف التصدير + هل تريد حذف ملف تعريف التصدير \\"%1$s\\"؟ + رسم الحدود + إظهار حد رفيع حول لوحة الرسم ومسح القماش + مائج + عناصر واجهة المستخدم مدورة مع حافة متموجة ناعمة + مغرفة + الشق + زوايا مستديرة منحوتة إلى الداخل + زوايا متدرجة بقطع مزدوج + العنصر النائب + استخدم {filename} لإدراج اسم الملف الأصلي بدون ملحق + مضيئة + المبلغ الأساسي + مبلغ الحلبة + كمية راي + عرض الحلقة + تشويه المنظور + جافا الشكل والمظهر + قص + زاوية X + والزاوية + تغيير حجم الإخراج + قطرة الماء + الطول الموجي + مرحلة + تمريرة عالية + قناع اللون + الكروم + حل + نعومة + تعليق + طمس العدسة + مدخلات منخفضة + مدخلات عالية + انخفاض الناتج + ارتفاع الناتج + تأثيرات ضوئية + ارتفاع نتوء + نعومة عثرة + تموج + السعة X + السعة Y + الطول الموجي X + الطول الموجي Y + طمس التكيف + جيل الملمس + إنشاء مواد إجرائية متنوعة وحفظها كصور + نوع الملمس + تحيز + وقت + يشرق + تشتت + عينات + معامل الزاوية + معامل التدرج + نوع الشبكة + قوة المسافة + مقياس Y + ضبابي + نوع الأساس + عامل الاضطراب + التحجيم + التكرارات + خواتم + معدن مصقول + المواد الكاوية + الخلوية + رقعة الشطرنج + fBm + رخام + بلازما + لحاف + خشب + عشوائي + مربع + سداسية + مثمنة + الثلاثي + مخفف + الضوضاء VL + الضوضاء SC + مؤخرًا + لا توجد مرشحات مستخدمة مؤخرًا حتى الآن + مولد الملمس المعدن المصقول المواد الكاوية الخلوية الشطرنج الرخام البلازما لحاف الخشب الطوب التمويه خلية سحابة الكراك النسيج أوراق الشجر العسل الجليد سديم الحمم البركانية ورقة الصدأ الرمال الدخان الحجر التضاريس التضاريس تموج الماء + لبنة + تمويه + خلية + سحاب + كسر + قماش + أوراق الشجر + قرص العسل + الجليد + الحمم البركانية + سديم + ورق + الصدأ + رمل + دخان + حجر + التضاريس + التضاريس + تموج الماء + الخشب المتقدم + عرض الملاط + عدم انتظام + شطبة + خشونة + العتبة الأولى + العتبة الثانية + العتبة الثالثة + نعومة الحافة + عرض الحدود + التغطية + التفاصيل + عمق + المتفرعة + الخيوط الأفقية + الخيوط العمودية + زغب + الأوردة + إضاءة + عرض الكراك + الصقيع + تدفق + القشرة + كثافة السحب + النجوم + كثافة الألياف + قوة الألياف + البقع + تآكل + تأليب + رقائق + تردد الكثبان الرملية + زاوية الرياح + تموجات + الخصلات + مقياس الوريد + مستوى الماء + مستوى الجبل + تآكل + مستوى الثلوج + عدد الخطوط + سُمك الخط + تظليل + لون المسام + لون الملاط + لون الطوب الداكن + لون الطوب + تسليط الضوء على اللون + اللون الداكن + لون الغابة + لون الارض + لون الرمال + لون الخلفية + لون الخلية + لون الحافة + لون السماء + لون الظل + لون فاتح + لون السطح + لون الاختلاف + لون الكراك + لون الاعوجاج + لون اللحمة + لون الأوراق الداكن + لون الورقة + لون الحدود + لون العسل + لون عميق + لون الجليد + لون الصقيع + لون القشرة + غسل اللون + لون متوهج + لون الفضاء + اللون البنفسجي + اللون الأزرق + اللون الأساسي + لون الألياف + لون وصمة عار + لون معدني + لون الصدأ الداكن + لون الصدأ + اللون البرتقالي + لون الدخان + لون الوريد + لون الأراضي المنخفضة + لون الماء + لون الصخور + لون الثلج + لون منخفض + لون عالي + لون الخط + اللون الضحل + عشب + الأوساخ + جلد + أسمنت + أسفلت + طحلب + نار + أورورا + بقعة الزيت + ألوان مائية + تدفق مجردة + أوبال + دمشق ستيل + البرق + مخمل + رخامي الحبر + احباط الهولوغرافية + تلألؤ بيولوجي + الدوامة الكونية + مصباح الحمم البركانية + أفق الحدث + كسورية بلوم + نفق لوني + خسوف كورونا + جاذب غريب + تاج السوائل الممغنطة + المستعر الأعظم + قزحية + ريشة الطاووس + نوتيلوس شل + الكوكب الحلقي + كثافة الشفرة + طول الشفرة + رياح + بقع + كتل + رُطُوبَة + الحصى + التجاعيد + المسام + إجمالي + الشقوق + قطران + يرتدي + بقع + ألياف + تردد اللهب + دخان + شدة + شرائط + العصابات + التقزح اللوني + الظلام + تزهر + الصباغ + الحواف + ورق + انتشار + التماثل + حدة + اللعب بالألوان + حليبية + طبقات + للطي + بولندي + الفروع + اتجاه + شين + طيات + ريش + توازن الحبر + نطاق + التجاعيد + الحيود + الأسلحة + تطور + توهج الأساسية + النقط + إمالة القرص + حجم الأفق + عرض القرص + عدسة + بتلات + حليقة + تخريمية + الأوجه + انحناء + حجم القمر + حجم كورونا + أشعة + خاتم الماس + الفصوص + كثافة المدار + سماكة + المسامير + طول سبايك + حجم الجسم + معدني + دائرة نصف قطرها الصدمة + عرض القشرة + طرد + حجم التلميذ + حجم القزحية + اختلاف اللون + كاتش لايت + حجم العين + كثافة بارب + المنعطفات + تشامبرز + افتتاح + التلال + اللؤلؤة + حجم الكوكب + إمالة الدائري + عرض الحلقة + أَجواء + اللون الترابي + لون العشب الداكن + لون العشب + لون النصيحة + لون الأرض الداكن + اللون الجاف + لون حصاة + لون جلد + لون خرساني + لون القطران + لون الأسفلت + لون الحجر + لون الغبار + لون التربة + لون الطحلب الداكن + لون الطحلب + اللون الأحمر + اللون الأساسي + اللون الأخضر + اللون سماوي + اللون الأرجواني + لون ذهبي + لون الورق + لون الصباغ + اللون الثانوي + اللون الأول + اللون الثاني + اللون الوردي + اللون الصلب الداكن + اللون الصلب + لون فولاذي فاتح + لون أكسيد + لون هالة + لون الترباس + اللون المخملي + لون لمعان + لون الحبر الأزرق + لون الحبر الأحمر + لون الحبر الداكن + اللون الفضي + اللون الأصفر + لون الأنسجة + لون القرص + اللون الساخن + لون العدسة + اللون الخارجي + اللون الداخلي + لون كورونا + اللون البارد + لون دافئ + لون السحابة + لون اللهب + لون الريشة + لون القشرة + لون اللؤلؤ + لون الكوكب + لون الخاتم + حذف الملفات الأصلية بعد الحفظ + سيتم حذف الملفات المصدر التي تمت معالجتها بنجاح فقط + الملفات الأصلية المحذوفة: %1$d + فشل في حذف الملفات الأصلية: %1$d + الملفات الأصلية المحذوفة: %1$d، الفاشلة: %2$d + إيماءات الورقة + السماح بسحب أوراق الخيارات الموسعة + أخذ عينات فرعية من الكروما + عمق بت + ضياع + %1$d-بت + إعادة تسمية الدفعة + إعادة تسمية ملفات متعددة باستخدام أنماط اسم الملف + إعادة تسمية الملفات دفعة واحدة، اسم الملف، نمط التسلسل، تمديد التاريخ + اختر الملفات لإعادة تسميتها + نمط اسم الملف + إعادة تسمية + مصدر التاريخ + تم التقاط تاريخ EXIF + تاريخ تعديل الملف + تاريخ إنشاء الملف + التاريخ الحالي + التاريخ اليدوي + التاريخ اليدوي + الوقت اليدوي + التاريخ المحدد غير متاح لبعض الملفات. سيتم استخدام التاريخ الحالي لهم. + أدخل نمط اسم الملف + ينتج عن النمط اسم ملف غير صالح أو طويل جدًا + ينتج النمط أسماء ملفات مكررة + جميع أسماء الملفات تتطابق بالفعل مع النمط + يستخدم هذا النمط الرموز المميزة غير المتوفرة لإعادة تسمية الدُفعة + الاسم سيبقى كما هو + تمت إعادة تسمية الملفات بنجاح + لا يمكن إعادة تسمية بعض الملفات + لم يتم منح الإذن + يستخدم اسم الملف الأصلي بدون امتداد، مما يساعدك على الحفاظ على تعريف المصدر سليمًا. ويدعم أيضًا التقطيع باستخدام \\o{start:end} والترجمة الصوتية باستخدام \\o{t} والاستبدال بـ \\o{s/old/new/}. + عداد الزيادة التلقائية. يدعم التنسيق المخصص: \\c{padding} (على سبيل المثال \\c{3} -&gt; 001)، \\c{start:step} أو \\c{start:step:padding}. + اسم المجلد الأصلي حيث يوجد الملف الأصلي. + حجم الملف الأصلي. يدعم الوحدات: \\z{b}، \\z{kb}، \\z{mb} أو \\z فقط للتنسيق الذي يمكن قراءته بواسطة الإنسان. + ينشئ معرفًا فريدًا عالميًا عشوائيًا (UUID) لضمان تفرد اسم الملف. + لا يمكن التراجع عن إعادة تسمية الملفات. تأكد من صحة الأسماء الجديدة قبل المتابعة. + تأكيد إعادة التسمية + إعدادات القائمة الجانبية + مقياس القائمة + القائمة ألفا + توازن اللون الأبيض التلقائي + لقطة + التحقق من العلامات المائية المخفية + تخزين + حد ذاكرة التخزين المؤقت + امسح ذاكرة التخزين المؤقت تلقائيًا عندما يتجاوز هذا الحجم + الفاصل الزمني للتنظيف + كم مرة للتحقق مما إذا كان يجب مسح ذاكرة التخزين المؤقت + عند إطلاق التطبيق + يوم واحد + 1 أسبوع + شهر واحد + امسح ذاكرة التخزين المؤقت للتطبيق تلقائيًا وفقًا للحد والفاصل الزمني المحدد + منطقة المحاصيل المنقولة + يسمح بتحريك منطقة الاقتصاص بأكملها عن طريق السحب بداخلها + دمج GIF + قم بدمج مقاطع GIF متعددة في رسم متحرك واحد + ترتيب مقاطع GIF + يعكس + العب كما هو معكوس + يرتد + العب للأمام ثم للخلف + تطبيع حجم الإطار + قم بقياس الإطارات لتناسب أكبر مقطع؛ إيقاف للحفاظ على حجمها الأصلي + التأخير بين المقاطع (مللي ثانية) + مجلد + حدد كافة الصور من مجلد ومجلداته الفرعية + مكتشف مكررة + ابحث عن نسخ طبق الأصل وصور متشابهة بصريًا + الباحث عن النسخ المكررة، الصور المماثلة، النسخ الدقيقة، تخزين الصور، dHash SHA-256 + اختر الصور للعثور على التكرارات + حساسية التشابه + نسخ بالضبط + صور مماثلة + حدد كافة التكرارات بالضبط + حدد الكل باستثناء الموصى به + لم يتم العثور على التكرارات + حاول إضافة المزيد من الصور أو زيادة حساسية التشابه + تعذر قراءة %1$d صورة (صور) + %1$s • %2$s قابلة للاسترداد + %1$d المجموعات • %2$s قابلة للاسترداد + %1$d تم التحديد • %2$s + لا يمكن التراجع عن هذا. قد يطلب منك Android تأكيد الحذف في مربع حوار النظام. + لم يتم العثور عليه + التحرك للبدء + الانتقال إلى النهاية + \ No newline at end of file diff --git a/core/resources/src/main/res/values-be/strings.xml b/core/resources/src/main/res/values-be/strings.xml new file mode 100644 index 0000000..9534b57 --- /dev/null +++ b/core/resources/src/main/res/values-be/strings.xml @@ -0,0 +1,3741 @@ + + + + Выберыце малюнак для пачатку + Вышыня %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 выбраныя тыпы анатацый, такія як спасылкі, каментарыі, вылучэнні, фігуры або палі формы + Гіперспасылкі + Укладанні файлаў + Радкі + Усплывальныя вокны + Маркі + Формы + Тэкставыя нататкі + Разметка тэксту + Палі формы + Разметка + Невядомы + Анатацыі + Разгрупаваць + Дадайце цень размыцця за пласт з наладжвальнымі колерам і зрухамі + Скажэнне з улікам кантэнту + Не хапае памяці для выканання гэтага дзеяння. Калі ласка, паспрабуйце выкарыстоўваць меншы відарыс, закрыць іншыя праграмы або перазапусціць праграму. + Паралельныя рабочыя + Гэтыя выявы не былі захаваны, таму што пераўтвораныя файлы будуць большымі за арыгіналы. Праверце гэты спіс перад выдаленнем зыходных малюнкаў. + Прапушчаныя файлы: %1$s + Журналы праграм + Праглядзіце журналы праграмы, каб выявіць праблемы + Выбранае ў згрупаваных інструментах + Дадае абранае ў выглядзе ўкладкі, калі інструменты згрупаваны па тыпу + Паказаць абранае як апошняе + Перамяшчае ўкладку любімых інструментаў у канец + Гарызантальны інтэрвал + Вертыкальны інтэрвал + Зваротная энергія + Выкарыстоўвайце простую карту энергіі градыенту велічыні замест алгарытму прамой энергіі + Выдаліць замаскіраваную вобласць + Швы будуць праходзіць праз замаскіраваную вобласць, выдаляючы толькі выбраную вобласць, а не абараняючы яе + VHS NTSC + Пашыраныя налады NTSC + Дадатковая аналагавая настройка для мацнейшых VHS і артэфактаў трансляцыі + Крывацёк каляровасці + Нашэнне стужкі + Адсочванне + Мазок Luma + Звон + Снег + Выкарыстоўвайце поле + Тып фільтра + Уваходны фільтр яркасці + Уваходная каляровасць нізкіх частот + Дэмадуляцыя каляровасці + Фазавы зрух + Зрушэнне фаз + Пераключэнне галавы + Вышыня пераключэння галавы + Зрушэнне пераключэння галоўкі + Пераключэнне галавы + Становішча сярэдняй лініі + Дрыгаценне сярэдняй лініі + Адсочванне вышыні шуму + Адсочванне хвалі + Сачэнне за снегам + Адсочванне анізатрапіі снегу + Адсочванне шуму + Адсочванне інтэнсіўнасці шуму + Кампазітны шум + Кампазітная частата шуму + Кампазітная інтэнсіўнасць шуму + Кампазітныя дэталі шуму + Частата званка + Магутнасць званка + Шум яркасці + Частата шуму яркасці + Інтэнсіўнасць шуму Luma + Падрабязнасць яркасці шуму + Каляровы шум + Частата каляровага шуму + Інтэнсіўнасць каляровага шуму + Дэталі каляровага шуму + Інтэнсіўнасць снегападу + Анізатрапія снегу + Фазавы шум каляровасці + Памылка фазы каляровасці + Затрымка каляровасці па гарызанталі + Вертыкальная затрымка каляровасці + Хуткасць стужкі VHS + Страта каляровасці VHS + Інтэнсіўнасць рэзкасці VHS + VHS частата рэзкасці + Інтэнсіўнасць краёвай хвалі VHS + Хуткасць краёвай хвалі VHS + Кратавая частата хвалі VHS + Краёвая хваля хвалі VHS + Выхад каляровасці нізкіх частот + Гарызантальны маштаб + Маштаб вертыкальны + Маштабны каэфіцыент X + Маштабны каэфіцыент Y + Выяўляе агульныя вадзяныя знакі малюнкаў і зафарбоўвае іх з дапамогай LaMa. Аўтаматычна спампоўвае мадэлі выяўлення і замалёўкі + Дэйтэранапія + Разгарнуць малюнак + Праграма для выдалення фону на аснове сегментацыі аб\'ектаў з выкарыстаннем сегментацыі YOLO v11 + Паказаць вугал лініі + Паказвае бягучы паварот лініі ў градусах падчас малявання + Аніміраваныя Emojis + Паказаць даступныя эмодзі ў выглядзе анімацыі + Захаваць у зыходную тэчку + Захоўвайце новыя файлы побач з зыходным файлам замест выбранай папкі + Вадзяны знак PDF + Не хапае памяці + Відарыс, верагодна, занадта вялікі для апрацоўкі на гэтай прыладзе, або ў сістэме скончылася даступная памяць. Паспрабуйце паменшыць раздзяленне выявы, закрыць іншыя прыкладанні або выбраць файл меншага памеру. + Меню адладкі + Меню для тэсціравання функцый праграмы, гэта не прызначана для паказу ў вытворчасці + Правайдэр + PaddleOCR патрабуе дадатковых мадэляў ONNX на вашай прыладзе. Хочаце спампаваць даныя %1$s? + Універсальны + карэйская + лацінка + усходнеславян + Тайская + грэцкі + англійская + Кірыліца + арабская + Дэванагары + тамільская + Тэлугу + Шэйдэр + Прадусталяваныя шэйдары + Шэйдэр не выбраны + Шэйдэрная студыя + Стварайце, рэдагуйце, правярайце, імпартуйце і экспартуйце ўласныя шэйдары фрагментаў + Захаваныя шэйдары + Адкрыйце захаваныя шэйдары, дублюйце, экспартуйце, абагульвайце або выдаляйце + Новы шэйдар + Шейдерный файл + .itshader JSON + %1$d параметраў + Крыніца шэдэраў + Запішыце толькі цела void main(). Выкарыстоўвайце textureCoordinate для UV-каардынатаў і inputImageTexture у якасці зыходнага сэмплера. + Функцыі + Неабавязковыя дапаможныя функцыі, канстанты і структуры, устаўленыя перад void main(). Уніформа ствараецца з params. + Дадайце сюды форму, калі шэйдэру патрэбны значэнні, якія можна рэдагаваць. + Скінуць шэйдэр + Бягучы чарнавік шэйдара будзе ачышчаны + Няправільны шэйдар + Версія шэйдара %1$d не падтрымліваецца. Версія, якая падтрымліваецца: %2$d. + Поле для назвы шэйдара не павінна быць пустым. + Крыніца шэдэра не павінна быць пустой. + Крыніца шэйдара змяшчае сімвалы, якія не падтрымліваюцца: %1$s. Выкарыстоўвайце толькі крыніцу ASCII GLSL. + Параметр \\\"%1$s\\\" аб\'яўлены больш за адзін раз. + Імёны параметраў не павінны быць пустымі. + Параметр \\\"%1$s\\\" выкарыстоўвае зарэзерваванае імя GPUImage. + Параметр \\\"%1$s\\\" павінен быць сапраўдным ідэнтыфікатарам GLSL. + Параметр \\\"%1$s\\\" мае %2$s тып значэння \\\"%3$s\\\", чакаецца \\\"%4$s\\\". + Параметр \\\"%1$s\\\" не можа вызначыць мінімальнае або максімальнае для лагічных значэнняў. + Параметр \\\"%1$s\\\" мае мін. большы за макс. + Параметр \\\"%1$s\\\" па змаўчанні меншы за мін. + Параметр \\\"%1$s\\\" па змаўчанні большы за макс. + Шэйдэр павінен дэклараваць \\\"uniform sampler2D %1$s;\\\". + Шэйдар павінен дэклараваць \\\"variying vec2 %1$s;\\\". + Шэйдар павінен вызначаць \\\"void main()\\\". + Шэйдэр павінен запісаць колер у gl_FragColor. + Шэйдары ShaderToy mainImage не падтрымліваюцца. Выкарыстоўвайце несапраўдны кантракт main() GPUImage. + Аніміраваная форма ShaderToy \\\"iTime\\\" не падтрымліваецца. + Аніміраваны ўніформа ShaderToy \\\"iFrame\\\" не падтрымліваецца. + Універсальны \\\"iResolution\\\" ShaderToy не падтрымліваецца. + Тэкстуры ShaderToy iChannel не падтрымліваюцца. + Знешнія тэкстуры не падтрымліваюцца. + Знешнія пашырэнні тэкстур не падтрымліваюцца. + Параметры шэйдара Libretro не падтрымліваюцца. + Падтрымліваецца толькі адна ўваходная тэкстура. Выдаліць \\\"уніформа %1$s %2$s;\\\". + Параметр \\\"%1$s\\\" павінен быць аб\'яўлены як \\\"uniform %2$s %1$s;\\\". + Параметр \\\"%1$s\\\" заяўлены як \\\"аднастайны %2$s %1$s;\\\", чакаецца \\\"аднастайны %3$s %1$s;\\\". + Файл шэйдара пусты. + Файл шэйдара павінен быць аб\'ектам .itshader JSON з версіяй, назвай і палямі шэйдара. + Файл шэдэра не з\'яўляецца сапраўдным JSON або не адпавядае фармату .itshader. + Поле \\\"%1$s\\\" абавязковае. + Патрабуецца %1$s. + %1$s \\\"%2$s\\\" не падтрымліваецца. Падтрымліваюцца тыпы: %3$s. + %1$s патрабуецца для параметраў \\\"%2$s\\\". + %1$s павінна быць канечным лікам. + %1$s павінна быць цэлым лікам. + %1$s павінна быць праўдай або ілжывым. + %1$s павінен быць каляровым радком, масівам RGB/RGBA або каляровым аб\'ектам. + %1$s павінен выкарыстоўваць фармат #RRGGBB або #RRGGBBAA. + %1$s павінен утрымліваць сапраўдныя шаснаццатковыя каляровыя каналы. + %1$s павінен змяшчаць 3 ці 4 каляровыя каналы. + %1$s павінна быць ад 0 да 255. + %1$s павінен быць масівам з двух лікаў або аб\'ектам з x і y. + %1$s павінен змяшчаць роўна 2 лічбы. + Дублікат + Заўсёды ачышчайце EXIF + Выдаліць EXIF-дадзеныя выявы пры захаванні, нават калі інструмент запытвае захаванне метададзеных + Даведка і парады + %1$d падручнікаў + Адкрыты інструмент + Вывучыце асноўныя інструменты, параметры экспарту, патокі PDF, каляровыя ўтыліты і выпраўленні распаўсюджаных праблем + Пачатак працы + Выбірайце інструменты, імпартуйце файлы, захоўвайце вынікі і паўторна выкарыстоўвайце налады хутчэй + Рэдагаванне малюнкаў + Змяняйце памер, абразайце, фільтруйце, сцірайце фоны, малюйце і вадзяныя знакі + Файлы і метададзеныя + Пераўтварайце фарматы, параўноўвайце выхад, абараняйце канфідэнцыяльнасць і кантралюйце імёны файлаў + PDF і дакументы + Стварайце PDF-файлы, скануйце дакументы, старонкі OCR і рыхтуйце файлы для сумеснага выкарыстання + Тэкст, QR і даныя + Распазнаваць тэкст, сканаваць коды, кадзіраваць Base64, правяраць хэшы і пакаваць файлы + Інструменты колеру + Выбірайце колеры, стварайце палітры, вывучайце бібліятэкі колераў і стварайце градыенты + Інструменты творчасці + Стварайце малюнкі SVG, ASCII, шумавыя тэкстуры, шэйдары і эксперыментальныя візуальныя эфекты + Ліквідацыю непаладак + Выпраўце праблемы з вялікімі файламі, празрыстасцю, сумесным выкарыстаннем, імпартам і справаздачамі + Выберыце правільны інструмент + Выкарыстоўвайце катэгорыі, пошук, абранае і абагульванне, каб пачаць з патрэбнага месца + Пачніце з лепшай кропкі ўваходу + Image Toolbox мае шмат мэтанакіраваных інструментаў, і многія з іх на першы погляд перакрываюцца. Пачніце з задачы, якую хочаце скончыць, а затым выберыце інструмент, які кантралюе найбольш важную частку выніку: памер, фармат, метададзеныя, тэкст, старонкі PDF, колеры або макет. + Выкарыстоўвайце пошук, калі ведаеце назву дзеяння, напрыклад, змяненне памеру, PDF, EXIF, OCR, QR або колер. + Адкрыйце катэгорыю, калі вы ведаеце толькі тып задачы, а потым замацуеце часта сустракаемыя інструменты ў абраных. + Калі іншая праграма абагульвае выяву ў Image Toolbox, выберыце інструмент з табліцы абагульвання. + Імпартаваць, захоўваць і дзяліцца вынікамі + Зразумейце звычайны працэс ад выбару ўводу да экспарту адрэдагаванага файла + Выконвайце звычайны працэс рэдагавання + Большасць інструментаў прытрымліваюцца аднолькавага рытму: выбар уводу, налада параметраў, папярэдні прагляд, затым захаванне або абагульванне. Важнай дэталлю з\'яўляецца тое, што захаванне стварае выходны файл, у той час як абагульванне звычайна лепшае для хуткай перадачы ў іншую праграму. + Выберыце адзін файл для інструментаў з адной выявай або некалькі файлаў для пакетных інструментаў, калі выбар дазваляе. + Перад захаваннем праверце параметры папярэдняга прагляду і вываду, асабліва параметры фармату, якасці і імя файла. + Выкарыстоўвайце агульны доступ для хуткай перадачы або захавайце, калі вам патрэбна пастаянная копія ў выбранай тэчцы. + Працуйце з вялікай колькасцю малюнкаў адначасова + Пакетныя інструменты лепш за ўсё падыходзяць для шматразовага змянення памеру, пераўтварэння, сціску і наймення + Прыгатуйце прадказальную партыю + Пакетная апрацоўка самая хуткая, калі кожны вынік павінен адпавядаць аднолькавым правілам. Гэта таксама самае лёгкае месца, каб зрабіць вялікую памылку, таму выберыце найменне, якасць, метаданыя і паводзіны папкі перад пачаткам працяглага экспарту. + Выберыце ўсе звязаныя выявы разам і захавайце парадак, калі вынік залежыць ад паслядоўнасці. + Перад пачаткам экспарту ўсталюйце правілы змены памеру, фармату, якасці, метаданых і імя файла. + Спачатку запусціце невялікі тэставы пакет, калі зыходныя файлы вялікія або калі мэтавы фармат новы для вас. + Хуткае адзінкавае рэдагаванне + Выкарыстоўвайце рэдактар ​​\"усё ў адным\", калі вам трэба некалькі простых змяненняў на адным малюнку + Скончыце адзін відарыс без пераскокаў інструментаў + Адзінае рэдагаванне карысна, калі выява патрабуе невялікай ланцужкі змяненняў, а не адной спецыялізаванай аперацыі. Звычайна гэта хутчэй для хуткай ачысткі, папярэдняга прагляду і экспарту адной канчатковай копіі без стварэння пакетнага працоўнага працэсу. + Адкрыйце Single Edit для адной выявы, якая патрабуе агульных карэкціровак, разметкі, абрэзкі, павароту або экспарту змяненняў. + Ужывайце праўкі ў парадку, у якім спачатку змяняецца найменшая колькасць пікселяў, напрыклад, абрэзка перад фільтрамі і фільтры перад канчатковым сцісканнем. + Замест гэтага выкарыстоўвайце спецыялізаваны інструмент, калі вам патрэбна пакетная апрацоўка, дакладны мэтавы памер файла, вывад PDF, OCR або ачыстка метададзеных. + Выкарыстоўвайце перадусталёўкі і налады + Захавайце выбары, якія паўтараюцца, і наладзьце праграму для патрэбнага працоўнага працэсу + Пазбягайце паўтарэння той жа налады + Папярэднія ўстаноўкі і налады карысныя, калі вы часта экспартуеце файл аднаго і таго ж тыпу. Добрыя перадусталёўкі ператвараюць паўторны кантрольны спіс уручную ў адзін націск, у той час як глабальныя налады вызначаюць значэнні па змаўчанні, з якіх пачынаюцца новыя інструменты. + Стварыце перадусталёўкі для агульных выходных памераў, фарматаў, значэнняў якасці або шаблонаў найменняў. + Праглядзіце налады фармату па змаўчанні, якасці, папкі захавання, імя файла, выбару і паводзін інтэрфейсу. + Зрабіце рэзервовую копію налад перад пераўсталяваннем праграмы або пераходам на іншую прыладу. + Усталюйце карысныя налады па змаўчанні + Выберыце стандартны фармат, якасць, рэжым маштабу, тып змены памеру і каляровую прастору адзін раз + Зрабіце новыя інструменты бліжэй да вашай мэты + Значэнні па змаўчанні - гэта не толькі касметычныя перавагі. Яны вырашаюць, які фармат, якасць, паводзіны пры змене памеру, рэжым маштабу і колеравая прастора з\'яўляюцца першымі ў многіх інструментах, што дапамагае пазбегнуць паўторнага выбару тых жа варыянтаў пры кожным экспарце. + Усталюйце фармат і якасць выявы па змаўчанні ў адпаведнасці з вашым звычайным месцам прызначэння, напрыклад, JPEG для фатаграфій або PNG/WebP для альфа. + Наладзьце стандартны тып змены памеру і рэжым маштабу, калі вы часта экспартуеце для строгіх памераў, сацыяльных платформаў або старонак дакументаў. + Змяняйце налады каляровай прасторы па змаўчанні, толькі калі вашаму працоўнаму працэсу патрэбна аднастайная апрацоўка колеру на дысплэях, у друку або знешніх рэдактарах. + Выбар і праграма запуску + Выкарыстоўвайце налады выбару, калі праграма адкрывае занадта шмат дыялогавых вокнаў або запускаецца не ў тым месцы + Зрабіце так, каб адкрыццё файлаў адпавядала вашай звычцы + Налады выбару і запуску вызначаюць, ці будзе запускацца Image Toolbox з галоўнага экрана, інструмента або працэсу выбару файла. Гэтыя параметры асабліва карысныя, калі вы звычайна ўваходзіце з аркуша абагульвання Android або калі хочаце, каб праграма больш нагадвала праграму запуску інструментаў. + Выкарыстоўвайце налады выбару фота, калі сродак выбару сістэмы хавае файлы, паказвае занадта шмат нядаўніх элементаў або дрэнна апрацоўвае воблачныя файлы. + Уключыце прапуск выбару толькі для інструментаў, у якія вы звычайна ўваходзіце з агульнага доступу або ўжо ведаеце зыходны файл. + Праглядзіце рэжым запуску, калі хочаце, каб Image Toolbox паводзіў сябе больш як выбар інструментаў, чым як звычайны галоўны экран. + Крыніца выявы + Выберыце рэжым выбару, які лепш за ўсё працуе з галерэямі, файлавымі мэнэджарамі і воблачнымі файламі + Выбірайце файлы з патрэбнай крыніцы + Налады крыніцы выявы ўплываюць на тое, як праграма запытвае ў Android выявы. Лепшы выбар залежыць ад таго, знаходзяцца вашы файлы ў сістэмнай галерэі, у тэчцы дыспетчара файлаў, у воблачным пастаўшчыку або ў іншай праграме, якая абагульвае часовы кантэнт. + Скарыстайцеся выбарам па змаўчанні, калі вам патрэбны найбольш інтэграваны ў сістэму вопыт для звычайных малюнкаў галерэі. + Пераключыце рэжым выбару, калі альбомы, тэчкі, воблачныя файлы або нестандартныя фарматы з\'яўляюцца не там, дзе вы чакаеце. + Калі агульны файл знікае пасля выхаду з зыходнай праграмы, зноў адкрыйце яго з дапамогай пастаяннага выбару або спачатку захавайце лакальную копію. + Выбранае і інструменты абмену + Захоўвайце ўвагу на галоўным экране і хавайце інструменты, якія не маюць сэнсу ў патоках абмену + Паменшыць шум спісу інструментаў + Выбранае і налады схаваных для сумеснага выкарыстання карысныя, калі вы выкарыстоўваеце толькі некалькі інструментаў кожны дзень. Яны таксама забяспечваюць практычны паток абагульвання, хаваючы інструменты, якія не могуць выкарыстоўваць тып файла, які адпраўляе іншая праграма. + Замацоўвайце інструменты, якія часта сустракаюцца, каб да іх было лёгка дабрацца, нават калі інструменты згрупаваны па катэгорыях. + Схавайце інструменты ад патокаў абмену, калі яны не маюць значэння для файлаў, якія паступаюць з іншага прыкладання. + Выкарыстоўвайце налады абранага парадку, калі вы аддаеце перавагу абраным у канцы або згрупаваным з адпаведнымі інструментамі. + Рэзервовае капіраванне налад + Бяспечна перанясіце перадусталёўкі, налады і паводзіны праграмы ў іншую ўстаноўку + Захоўвайце вашу ўстаноўку партатыўнай + Рэзервовае капіраванне і аднаўленне найбольш карысна пасля таго, як вы наладзілі перадусталёўкі, тэчкі, правілы назваў файлаў, парадак інструментаў і візуальныя налады. Рэзервовае капіраванне дазваляе эксперыментаваць з наладамі або пераўсталёўваць праграму без аднаўлення працоўнага працэсу з памяці. + Стварыце рэзервовую копію пасля змены налад, паводзін імёнаў файлаў, фарматаў па змаўчанні або размяшчэння інструментаў. + Захоўвайце рэзервовую копію дзе-небудзь па-за папкай праграмы, калі вы плануеце ачысціць даныя, пераўсталяваць або перамясціць прылады. + Аднавіце налады перад апрацоўкай важных пакетаў, каб налады па змаўчанні і рэжым захавання адпавядалі вашым старым наладам. + Змена памеру і пераўтварэнне малюнкаў + Змяняйце памер, фармат, якасць і метаданыя для аднаго або некалькіх відарысаў + Стварыце копіі змененага памеру + Змена памеру і пераўтварэнне - гэта асноўны інструмент для прадказальных памераў і больш лёгкіх выходных файлаў. Выкарыстоўвайце яго, калі памер выявы, фармат вываду і паводзіны метададзеных маюць значэнне адначасова. + Выберыце адзін ці некалькі відарысаў, а затым выберыце, ці найбольш важныя памеры, маштаб ці памер файла. + Выберыце фармат вываду і якасць; выкарыстоўвайце PNG або WebP, калі трэба захаваць празрыстасць. + Праглядзіце вынік, затым захавайце або падзяліцеся створанымі копіямі, не змяняючы арыгіналы. + Змяніць памер у залежнасці ад памеру файла + Увядзіце абмежаванне памеру, калі вэб-сайт, мессенджер або форма адхіляюць цяжкія выявы + Адпавядаюць строгім абмежаванням загрузкі + Змяненне памеру ў залежнасці ад памеру файла лепш, чым угадванне значэнняў якасці, калі пункт прызначэння прымае толькі файлы ніжэй за вызначаны ліміт. Інструмент можа рэгуляваць сцісканне і памеры для дасягнення мэты, спрабуючы захаваць вынік прыдатным для выкарыстання. + Увядзіце мэтавы памер крыху ніжэй рэальнага ліміту загрузкі, каб пакінуць месца для змяненняў метададзеных і пастаўшчыка. + Аддавайце перавагу фарматам са стратамі для фатаграфій, калі мэта малая; PNG можа заставацца вялікім нават пасля змены памеру. + Правярайце твары, тэкст і краю пасля экспарту, таму што агрэсіўныя памеры могуць выклікаць бачныя артэфакты. + Абмежаваць змяненне памеру + Скарачаць толькі выявы, якія перавышаюць вашу максімальную шырыню, вышыню або абмежаванні для файла + Пазбягайце змены памеру малюнкаў, якія і без таго добрыя + Абмежаваць змяненне памеру карысна для змешаных папак, дзе некаторыя выявы велізарныя, а іншыя ўжо падрыхтаваныя. Замест таго, каб прымусова змяняць памер кожнага файла, ён захоўвае прымальныя выявы бліжэй да іх першапачатковай якасці. + Усталёўвайце максімальныя памеры або абмежаванні ў залежнасці ад месца прызначэння, а не змяняйце памер кожнага файла ўсляпую. + Уключыце паводзіны ў стылі \"прапусціць, калі больш\", калі вы хочаце пазбегнуць вынікаў, якія становяцца цяжэйшымі за крыніцы. + Выкарыстоўвайце гэта для пакетаў з розных камер, скрыншотаў або загрузак, дзе памеры зыходных файлаў моцна адрозніваюцца. + Абразанне, паварот і выпростванне + Перад экспартам або выкарыстаннем выявы ў іншых інструментах аформіце важную вобласць + Ачысціць склад + Абрэзка спачатку робіць пазнейшыя фільтры, OCR, старонкі PDF і вынікі абагульвання больш чыстымі. + Адкрыйце «Абрэзка» і выберыце выяву, якую хочаце наладзіць. + Выкарыстоўвайце бясплатную абрэзку або суадносіны бакоў, калі вынік павінен адпавядаць публікацыі, дакументу або аватару. + Павярніце або перавярніце перад захаваннем, каб пазнейшыя інструменты атрымалі выпраўлены малюнак. + Ужывайце фільтры і карэкціроўкі + Стварыце рэдагуемы стэк фільтраў і праглядзіце вынік перад экспартам + Наладзьце знешні выгляд выявы + Фільтры карысныя для хуткага выпраўлення, стылізаванага вываду і падрыхтоўкі малюнкаў для OCR або друку. Невялікія змены кантраснасці, рэзкасці і яркасці могуць мець большае значэнне, чым цяжкія эфекты, калі выява змяшчае тэкст або дробныя дэталі. + Дадавайце фільтры адзін за адным і сачыце за папярэднім праглядам пасля кожнай змены. + Адрэгулюйце яркасць, кантраснасць, рэзкасць, размытасць, колер або маскі ў залежнасці ад задачы. + Экспартуйце толькі тады, калі папярэдні прагляд адпавядае вашай мэтавай прыладзе, дакументу або сацыяльнай платформе. + Першы прагляд + Праверце выявы, перш чым выбраць больш цяжкі інструмент рэдагавання, пераўтварэння або ачысткі + Праверце, што вы на самой справе атрымалі + Папярэдні прагляд выявы карысны, калі файл паступае з чата, воблачнага сховішча або іншай праграмы, і вы не ўпэўнены, што ён утрымлівае. Спачатку праверка памераў, празрыстасці, арыентацыі і візуальнай якасці дапамагае выбраць правільны інструмент для далейшага назірання. + Адкрыйце Папярэдні прагляд малюнкаў, калі вам трэба праверыць некалькі малюнкаў, перш чым вырашыць, што з імі рабіць. + Шукайце праблемы з кручэннем, празрыстыя вобласці, артэфакты сціску і нечакана вялікія памеры. + Пераходзьце да змянення памеру, абрэзкі, параўнання, EXIF ​​або выдалення фону толькі пасля таго, як даведаецеся, што трэба выправіць. + Выдаліць або аднавіць фон + Сцірайце фоны аўтаматычна або ўдакладняйце краю ўручную з дапамогай інструментаў аднаўлення + Тэму захавайце, астатняе выдаліце + Выдаленне фону лепш за ўсё працуе, калі вы спалучаеце аўтаматычны выбар з дбайнай ручной ачысткай. Канчатковы фармат экспарту мае такое ж значэнне, як і маска, таму што JPEG згладзіць празрыстыя вобласці, нават калі папярэдні прагляд выглядае правільна. + Пачніце з аўтаматычнага выдалення, калі аб\'ект выразна аддзелены ад фону. + Павялічце маштаб і пераключайцеся паміж выдаленнем і аднаўленнем, каб выправіць валасы, куты, цені і дробныя дэталі. + Захавайце ў фармаце PNG або WebP, калі вам патрэбна празрыстасць канчатковага файла. + Маляванне, разметка і вадзяныя знакі + Дадайце стрэлкі, нататкі, блікі, подпісы або паўторныя накладкі вадзяных знакаў + Дадайце бачную інфармацыю + Інструменты разметкі дапамагаюць растлумачыць малюнак, а вадзяныя знакі дапамагаюць брэндаваць або абараняць экспартаваныя файлы. + Выкарыстоўвайце Draw, калі вам патрэбныя нататкі ад рукі, формы, вылучэнне або хуткія анатацыі. + Выкарыстоўвайце вадзяныя знакі, калі адзін і той жа знак тэксту або выявы павінен паслядоўна з\'яўляцца на выхадах. + Перад экспартам праверце непразрыстасць, становішча і маштаб, каб знак быў бачны, але не адцягваў увагу. + Маляваць па змаўчанні + Усталюйце шырыню лініі, колер, рэжым шляху, фіксацыю арыентацыі і лупу для больш хуткай разметкі + Зрабіце малюнак прадказальным + Налады малявання карысныя, калі вы часта анатаваеце здымкі экрана, пазначаеце дакументы або часта падпісваеце выявы. Значэнні па змаўчанні скарачаюць час наладкі, а лупа і фіксацыя арыентацыі палягчаюць дакладнае рэдагаванне на маленькіх экранах. + Усталюйце шырыню лініі па змаўчанні і намалюйце колер да значэнняў, якія вы часцей за ўсё выкарыстоўваеце для стрэлак, блікаў або подпісаў. + Выкарыстоўвайце стандартны рэжым контуру, калі вы звычайна малюеце аднолькавыя штрыхі, фігуры або разметку. + Уключыце лупу або блакіроўку арыентацыі, калі пры ўводзе пальцам дробныя дэталі цяжка дакладна размясціць. + Макеты з некалькімі малюнкамі + Выберыце правільны інструмент для некалькіх відарысаў, перш чым арганізоўваць скрыншоты, сканы або параўнальныя палоскі + Выкарыстоўвайце правільны інструмент макета + Гэтыя інструменты гучаць падобна, але кожны з іх настроены на розныя віды вываду некалькіх малюнкаў. Выбар правільнага спачатку пазбягае барацьбы з элементамі кіравання макетам, якія былі распрацаваны для іншага выніку. + Выкарыстоўвайце Collage Maker для распрацаваных макетаў з некалькіх малюнкаў у адной кампазіцыі. + Выкарыстоўвайце сшыванне або нагрувашчванне малюнкаў, калі выявы павінны стаць адной доўгай вертыкальнай або гарызантальнай паласой. + Выкарыстоўвайце раздзяленне выявы, калі адна вялікая выява павінна стаць некалькімі меншымі часткамі. + Пераўтварэнне фарматаў малюнкаў + Стварайце JPEG, PNG, WebP, AVIF, JXL і іншыя копіі без ручнога рэдагавання пікселяў + Выберыце фармат для працы + Розныя фарматы лепш падыходзяць для фатаграфій, скрыншотаў, празрыстасці, сціску або сумяшчальнасці. Пераўтварэнне найбольш бяспечнае, калі вы разумееце, што падтрымлівае праграма прызначэння і ці змяшчае крыніца альфа-версію, анімацыю або метаданыя, якія вы хочаце захаваць. + Выкарыстоўвайце JPEG для сумяшчальных фатаграфій, PNG для выяваў без страт і альфа-версію і WebP для кампактнага абмену. + Адрэгулюйце якасць, калі фармат гэта падтрымлівае; меншыя значэнні памяншаюць памер, але могуць дадаваць артэфакты. + Захоўвайце арыгіналы, пакуль не пераканаецеся, што пераўтвораныя файлы правільна адкрываюцца ў мэтавай праграме. + Праверце EXIF ​​і прыватнасць + Праглядайце, рэдагуйце або выдаляйце метаданыя, перш чым абагульваць канфідэнцыяльныя фатаграфіі + Кантроль схаваных даных выявы + EXIF можа ўтрымліваць дадзеныя камеры, даты, месцазнаходжанне, арыентацыю і іншыя дэталі, не бачныя на малюнку. Варта правяраць перад публічным абагульваннем, справаздачамі службы падтрымкі, спісамі на рынку або любымі фотаздымкамі, якія могуць выяўляць прыватнае месца. + Адкрыйце інструменты EXIF, перш чым абагульваць фатаграфіі, якія могуць утрымліваць інфармацыю пра месцазнаходжанне або прыватную інфармацыю аб прыладзе. + Выдаліце ​​канфідэнцыяльныя палі або адрэдагуйце няправільныя тэгі, такія як дата, арыентацыя, аўтар або даныя камеры. + Захавайце новую копію і абагульвайце яе замест арыгінала, калі прыватнасць мае значэнне. + Аўтаматычная ачыстка EXIF + Па змаўчанні выдаляйце метаданыя, вырашаючы, ці трэба захоўваць даты + Зрабіць прыватнасць стандартнай + Група налад EXIF ​​карысная, калі вы хочаце, каб ачыстка прыватнасці адбывалася аўтаматычна, а не запамінала яе для кожнага экспарту. Захоўваць дату і час - асобнае рашэнне, таму што даты могуць быць карыснымі для сартавання, але адчувальнымі для абмену. + Уключыце заўсёды чысты EXIF, калі большая частка вашага экспарту прызначана для публічнага або напаўпублічнага абмену. + Уключайце Keep Date Time толькі тады, калі захаванне часу здымкі больш важнае, чым выдаленне кожнай меткі часу. + Выкарыстоўвайце ручное рэдагаванне EXIF ​​для аднаразовых файлаў, у якіх вам трэба захаваць адны палі і выдаліць іншыя. + Імёны файлаў кіравання + Выкарыстоўвайце прэфіксы, суфіксы, пазнакі часу, прадусталяваныя налады, кантрольныя сумы і парадкавыя нумары + Зрабіце так, каб экспарт было лёгка знайсці + Добры шаблон імя файла дапамагае сартаваць пакеты і прадухіляе выпадковыя перазапісы. Налады імёнаў файлаў асабліва карысныя, калі экспарт вяртаецца ў зыходную тэчку, дзе падобныя імёны могуць хутка заблытацца. + Усталюйце прэфікс імя файла, суфікс, пазнаку часу, зыходнае імя, прадусталяваную інфармацыю або параметры паслядоўнасці ў наладах. + Выкарыстоўвайце парадкавыя нумары для партый, дзе парадак мае значэнне, такіх як старонкі, рамкі або калажы. + Уключайце перазапіс, толькі калі вы ўпэўнены, што замена існуючых файлаў бяспечная для бягучай папкі. + Перазапісаць файлы + Замяняйце выхады адпаведнымі імёнамі, толькі калі ваш працоўны працэс наўмысна разбуральны + Асцярожна выкарыстоўвайце рэжым перазапісу + Перазапісаць файлы змяняе спосаб апрацоўкі канфліктаў імёнаў. Гэта карысна для паўторнага экспарту ў тую ж тэчку, але яно таксама можа замяніць ранейшыя вынікі, калі ваш шаблон імя файла зноў стварае тое ж самае імя. + Уключыце перазапіс толькі для тэчак, у якіх чакаецца замена раней створаных файлаў, якія можна аднавіць. + Трымайце перазапіс адключаным пры экспарце арыгіналаў, кліенцкіх файлаў, аднаразовага сканавання або чаго-небудзь без рэзервовай копіі. + Аб\'яднайце перазапіс з наўмысным шаблонам назвы файла, каб замяніць толькі меркаваныя высновы. + Шаблоны імёнаў файлаў + Аб\'яднайце арыгінальныя назвы, прэфіксы, суфіксы, пазнакі часу, прадусталяваныя налады, рэжым маштабавання і кантрольныя сумы + Стварыце назвы, якія тлумачаць вынік + Налады шаблона імя файла могуць ператварыць экспартаваныя файлы ў карысную гісторыю таго, што з імі адбылося. Гэта мае значэнне ў партыях, таму што прагляд папкі можа быць адзіным месцам, дзе можна адрозніць арыгінальны памер, прадусталяванне, фармат і парадак апрацоўкі. + Выкарыстоўвайце арыгінальнае імя файла, прэфікс, суфікс і метку часу, калі вам патрэбныя чытэльныя імёны для ручнога сартавання. + Дадайце перадустаноўку, рэжым маштабавання, памер файла або інфармацыю аб кантрольнай суме, калі вынікі павінны дакументаваць, як яны былі створаны. + Пазбягайце выпадковых назваў для працоўных працэсаў, калі файлы павінны заставацца ў пары з арыгіналамі або парадкам старонак. + Выберыце месца захавання файлаў + Пазбегніце страты экспарту, усталяваўшы папкі, захаванне арыгінальных тэчак і месцы аднаразовага захавання + Трымайце вынікі прадказальнымі + Паводзіны захавання найбольш важныя, калі вы апрацоўваеце шмат файлаў або абагульваеце файлы з воблачных пастаўшчыкоў. Налады папкі вызначаюць, ці будуць вынікі збірацца ў адным вядомым месцы, з\'яўляцца побач з арыгіналамі або часова адпраўляцца ў іншае месца для выканання пэўнай задачы. + Усталюйце тэчку захавання па змаўчанні, калі вы заўсёды хочаце экспартаваць у адным месцы. + Выкарыстоўвайце save-to-original-folder для працоўных працэсаў ачысткі, дзе вывад павінен заставацца побач з зыходным файлам. + Выкарыстоўвайце аднаразовае месцазнаходжанне захавання, калі адна задача патрабуе іншага прызначэння, не змяняючы стандартнае месца. + Тэчкі аднаразовага захавання + Адпраўляйце наступныя экспарты ў часовую тэчку, не змяняючы звычайнае месца прызначэння + Трымайце спецыяльныя заданні асобна + Размяшчэнне аднаразовага захавання карысна для часовых праектаў, кліенцкіх тэчак, сеансаў ачысткі або экспарту, якія не павінны змешвацца з звычайнай папкай Image Toolbox. Гэта дае кароткачасовы пункт прызначэння без перапісвання налад па змаўчанні. + Выберыце аднаразовую папку, перш чым пачаць задачу, якая не належыць да вашага звычайнага месца экспарту. + Выкарыстоўвайце яго для кароткіх праектаў, агульных папак або пакетаў, дзе вынікі павінны заставацца згрупаванымі разам. + Вярніцеся ў тэчку па змаўчанні пасля задання, каб пазнейшы экспарт нечакана не апынуўся ў часовым месцы. + Збалансуйце якасць і памер файла + Зразумейце, калі трэба змяніць памеры, фармат або якасць, а не гадаць + Скарачаць файлы наўмысна + Памер файла залежыць ад памераў выявы, фармату, якасці, празрыстасці і складанасці кантэнту. Калі файл па-ранейшаму занадта цяжкі, змяненне памераў звычайна дапамагае больш, чым шматразовае зніжэнне якасці. + Спачатку змяніце памеры, калі відарыс большы, чым можа адлюстраваць месца прызначэння. + Ніжэйшая якасць толькі для фарматаў са стратамі і правярайце тэкст, краю, градыенты і грані пасля экспарту. + Пераключайце фармат, калі змены якасці недастаткова, напрыклад, WebP для абагульвання або PNG для выразных празрыстых актываў. + Праца з анімацыйнымі фарматамі + Выкарыстоўвайце інструменты GIF, APNG, WebP і JXL, калі файл мае кадры замест аднаго растравага малюнка + Не разглядайце кожную выяву як нерухомую + Аніміраваныя фарматы патрабуюць інструментаў, якія разумеюць кадры, працягласць і сумяшчальнасць. + Выкарыстоўвайце інструменты GIF або APNG, калі вам патрэбныя аперацыі на ўзроўні кадраў для гэтых фарматаў. + Выкарыстоўвайце інструменты WebP, калі вам патрэбна сучаснае сцісканне або аніміраваны вывад WebP. + Перад тым, як абагульваць, праглядзіце час анімацыі, таму што некаторыя платформы пераўтвараюць або згладжваюць аніміраваныя выявы. + Параўнанне і папярэдні прагляд выхаду + Праверце змены, памер файла і візуальныя адрозненні, перш чым працягваць экспарт + Праверце перад абагульваннем + Інструменты параўнання дапамагаюць своечасова выявіць артэфакты сціску, няправільныя колеры або непажаданыя праўкі. + Адкрыйце Параўнаць, калі вы хочаце праверыць дзве версіі побач. + Павялічце краю, тэкст, градыенты і празрыстыя вобласці, дзе праблемы лягчэй за ўсё прапусціць. + Калі вынік занадта цяжкі або размыты, вярніцеся да папярэдняга інструмента і адрэгулюйце якасць або фармат. + Выкарыстоўвайце цэнтр PDF інструментаў + Аб\'ядноўвайце, раздзяляйце, паварочвайце, выдаляйце старонкі, сціскайце, абараняйце файлы PDF, OCR і вадзяныя знакі + Выберыце аперацыю PDF + Канцэнтратар PDF дакументуе дзеянні за адной кропкай уваходу, каб вы маглі выбраць дакладную аперацыю. Пачніце там, калі файл ужо з\'яўляецца PDF, і выкарыстоўвайце Images to PDF або Document Scanner, калі ваша крыніца ўсё яшчэ з\'яўляецца наборам малюнкаў. + Адкрыйце PDF Tools і абярыце аперацыю, якая адпавядае вашай мэты. + Выберыце зыходны файл PDF або выявы, а потым праглядзіце парадак старонак, паварот, абарону або параметры OCR. + Захавайце згенераваны PDF і адкрыйце яго адзін раз, каб пацвердзіць колькасць старонак, парадак і чытальнасць. + Пераўтварыце выявы ў PDF + Аб\'яднайце сканы, скрыншоты, квітанцыі або фатаграфіі ў адзін дакумент, якім можна абагульваць + Стварыце чысты дакумент + Выявы становяцца лепшымі старонкамі PDF, калі іх абразаюць, упарадкоўваюць і змяняюць памер. Ставіцеся да спісу малюнкаў як да стоса старонак: кожны выбар павароту, поля і памеру старонкі ўплывае на тое, наколькі чытэльным выглядае канчатковы дакумент. + Спачатку абрэжце або павярніце выявы, калі краю старонкі, цені або арыентацыя няправільныя. + Выбірайце выявы ў патрэбным парадку старонак і адрэгулюйце памер старонкі або палі, калі гэта прапануе інструмент. + Экспартуйце PDF, а затым праверце, што ўсе старонкі даступныя для чытання перад адпраўкай. + Сканаваць дакументы + Захоплівайце папяровыя старонкі і падрыхтуйце іх для PDF, OCR або сумеснага выкарыстання + Захоп чытаемых старонак + Сканаванне дакументаў лепш за ўсё працуе з плоскімі старонкамі, добрым асвятленнем і выпраўленай перспектывай. Чыстае сканаванне перад OCR або экспартам у PDF эканоміць час, таму што распазнаванне тэксту і сцісканне залежаць ад якасці старонкі. + Размесціце дакумент на кантраснай паверхні з дастатковай колькасцю святла і мінімальнай колькасцю ценяў. + Адрэгулюйце выяўленыя куты або абрэжце ўручную, каб старонка чыста запаўняла рамку. + Экспарт у выглядзе малюнкаў або PDF, затым запусціце OCR, калі вам патрэбен тэкст, які можна выбраць. + Абараніце або разблакіруйце файлы PDF + Асцярожна выкарыстоўвайце паролі і па магчымасці захоўвайце зыходную копію, якую можна рэдагаваць + Атрымлівайце доступ да PDF бяспечна + Інструменты абароны карысныя для кантраляванага абмену, але паролі лёгка страціць і цяжка аднавіць. Абараняйце копіі для распаўсюджвання, захоўвайце неабароненыя зыходныя файлы асобна і правярайце вынік, перш чым што-небудзь выдаляць. + Абараніце копію PDF, а не ваш адзіны зыходны дакумент. + Захоўвайце пароль у бяспечным месцы, перш чым абагульваць абаронены файл. + Выкарыстоўвайце разблакіроўку толькі для файлаў, якія вам дазволена рэдагаваць, затым праверце, што разблакіраваная копія адкрываецца правільна. + Упарадкаваць старонкі PDF + Аб\'ядноўвайце, раздзяляйце, перастаўляйце, паварочвайце, абразайце, выдаляйце старонкі і наўмысна дадавайце нумары старонак + Выпраўце структуру дакумента перад упрыгожваннем + Інструменты старонак PDF прасцей за ўсё выкарыстоўваць у тым жа парадку, у якім вы б падрыхтавалі папяровы дакумент: збярыце старонкі, выдаліце ​​няправільныя, упарадкуйце іх, выпраўце арыентацыю, затым дадайце апошнія дэталі, такія як нумары старонак або вадзяныя знакі. + Спачатку выкарыстоўвайце аб\'яднанне або пераўтварэнне малюнкаў у PDF, калі дакумент усё яшчэ падзелены на некалькі файлаў. + Змяняйце парадак, паварочвайце, абразайце або выдаляйце старонкі перад даданнем нумароў старонак, подпісаў або вадзяных знакаў. + Папярэдне праглядзіце канчатковы PDF пасля структурных правак, таму што адну адсутную або павернутую старонку можа быць цяжка заўважыць пазней. + PDF анатацыі + Выдаляйце анатацыі або згладжвайце іх, калі гледачы па-рознаму паказваюць каментарыі і адзнакі + Кантралюйце тое, што застаецца бачным + Анатацыі могуць быць каментарыямі, вылучэннямі, малюнкамі, знакамі формы або іншымі дадатковымі аб\'ектамі PDF. Некаторыя праграмы прагляду адлюстроўваюць іх па-рознаму, таму іх выдаленне або звядзенне можа зрабіць агульныя дакументы больш прадказальнымі. + Выдаліце ​​анатацыі, калі каментарыі, вылучэнні або адзнакі рэцэнзіі не павінны быць уключаны ў агульную копію. + Згладзіць, калі бачныя пазнакі павінны заставацца на старонцы, але больш не паводзіць сябе як анатацыі, якія можна рэдагаваць. + Захоўвайце арыгінальную копію, таму што выдаленне або згладжванне анатацый можа быць цяжка адмяніць. + Распазнаць тэкст + Выцягвайце тэкст з малюнкаў з дапамогай механізмаў OCR і моў, якія можна выбраць + Атрымлівайце лепшыя вынікі OCR + Дакладнасць OCR залежыць ад выразнасці выявы, моўных дадзеных, кантраснасці і арыентацыі старонкі. Найлепшыя вынікі звычайна атрымліваюцца, калі спачатку падрыхтаваць відарыс: абрэзаць шумы, павярнуць у вертыкальнае становішча, палепшыць чытальнасць, а затым распазнаць тэкст. + Абрэжце малюнак да тэкставай вобласці і павярніце яго вертыкальна перад распазнаваннем. + Выберыце правільную мову і спампуйце моўныя даныя, калі іх запытае праграма. + Скапіруйце або падзяліцеся распазнаным тэкстам, затым уручную праверце імёны, лічбы і знакі прыпынку. + Выберыце моўныя дадзеныя OCR + Палепшыце распазнаванне шляхам супастаўлення сцэнарыяў, моў і спампаваных мадэляў OCR + Супастаўце OCR з тэкстам + Няправільная мова OCR можа прывесці да таго, што добрыя фатаграфіі будуць выглядаць як дрэнна распазнаныя. Моўныя дадзеныя важныя для сцэнарыяў, знакаў прыпынку, лічбаў і змешаных дакументаў, таму выбірайце мову, якая з\'яўляецца на малюнку, а не мову інтэрфейсу тэлефона. + Выберыце мову або пісьмо, якія з\'яўляюцца на малюнку, а не толькі мову тэлефона. + Спампуйце адсутныя даныя перад аўтаномнай працай або апрацоўкай пакета. + Для разнамоўных дакументаў спачатку запусціце самую важную мову і ўручную праверце імёны і нумары. + PDF-файлы з магчымасцю пошуку + Запішыце тэкст OCR назад у файлы, каб можна было шукаць і капіяваць адсканаваныя старонкі + Пераўтварыце сканы ў дакументы з магчымасцю пошуку + OCR можа зрабіць больш, чым капіяваць тэкст у буфер абмену. Калі вы запісваеце распазнаны тэкст у файл або PDF з магчымасцю пошуку, выява старонкі застаецца бачнай, а тэкст становіцца лягчэй шукаць, выбіраць, індэксаваць або архіваваць. + Выкарыстоўвайце вывадны файл PDF з магчымасцю пошуку для адсканаваных дакументаў, якія ўсё яшчэ павінны выглядаць як арыгінальныя старонкі. + Выкарыстоўвайце запіс у файл, калі вам патрэбны толькі выняты тэкст і не клапоціцеся пра выяву старонкі. + Правярайце важныя лічбы, імёны і табліцы ўручную, таму што тэкст OCR можа быць даступны для пошуку, але ўсё яшчэ недасканалы. + Сканіруйце QR-коды + Счытвайце змесціва QR з уводу камеры або захаваных відарысаў, калі яны даступныя + Бяспечна чытайце коды + QR-коды могуць утрымліваць спасылкі, даныя Wi-Fi, кантакты, тэкст або іншую карысную нагрузку. + Адкрыйце Scan QR Code і трымайце код выразным, плоскім і цалкам унутры рамкі. + Праглядзіце дэкадзіраваны кантэнт, перш чым адкрываць спасылкі або капіраваць канфідэнцыяльныя даныя. + Калі сканіраванне не атрымоўваецца, палепшыце асвятленне, абрэжце код або паспрабуйце зыходны відарыс з больш высокай разрознасцю. + Выкарыстоўвайце Base64 і кантрольныя сумы + Кадзіраваць выявы як тэкст, дэкадаваць тэкст назад у выявы і правяраць цэласнасць файла + Пераход паміж файламі і тэкстам + Base64 карысны для невялікіх карысных нагрузак малюнкаў, а кантрольныя сумы дапамагаюць пацвердзіць, што файлы не змяняліся. Гэтыя інструменты найбольш карысныя ў тэхнічных працоўных працэсах, справаздачах падтрымкі, перадачы файлаў і месцах, дзе двайковыя файлы павінны часова стаць тэкставымі. + Выкарыстоўвайце інструменты Base64 для кадзіравання невялікай выявы, калі працоўны працэс патрабуе тэксту замест двайковага файла. + Дэкадзіруйце Base64 толькі з надзейных крыніц і правярайце папярэдні прагляд перад захаваннем. + Выкарыстоўвайце інструменты кантрольнай сумы, калі вам трэба параўнаць экспартаваныя файлы або пацвердзіць загрузку/спампоўку. + Упакоўка або абарона дадзеных + Выкарыстоўвайце інструменты ZIP і шыфр для аб\'яднання файлаў або пераўтварэння тэкставых даных + Захоўвайце звязаныя файлы разам + Інструменты ўпакоўкі карысныя, калі некалькі вынікаў павінны перамяшчацца як адзін файл. Яны таксама добрыя для захоўвання згенераваных малюнкаў, файлаў PDF, журналаў і метададзеных разам, калі вам трэба адправіць поўны набор вынікаў. + Выкарыстоўвайце ZIP, калі вам трэба адправіць шмат экспартаваных малюнкаў або дакументаў разам. + Выкарыстоўвайце інструменты шыфравання, толькі калі вы разумееце абраны метад і можаце бяспечна захоўваць неабходныя ключы. + Перад выдаленнем зыходных файлаў праверце створаны архіў або трансфармаваны тэкст. + Кантрольныя сумы ў імёнах + Выкарыстоўвайце імёны файлаў на аснове кантрольнай сумы, калі унікальнасць і цэласнасць важныя больш, чым зручнасць чытання + Называйце файлы па змесце + Імёны файлаў кантрольнай сумы карысныя, калі экспарт можа мець дублікаты бачных імёнаў або калі вам трэба даказаць, што два файлы ідэнтычныя. Яны менш зручныя для ручнога прагляду, таму выкарыстоўвайце іх для тэхнічных архіваў і працоўных працэсаў праверкі. + Уключыць кантрольную суму ў якасці імя файла, калі ідэнтычнасць змесціва больш важная, чым загаловак, які чытаецца чалавекам. + Выкарыстоўвайце яго для архіваў, дэдуплікацыі, паўторнага экспарту або файлаў, якія можна загружаць і спампоўваць зноў. + Спалучайце імёны кантрольных сум з тэчкамі або метададзенымі, калі людзям усё яшчэ трэба зразумець, што ўяўляе сабой файл. + Выбірайце колеры з малюнкаў + Узоры пікселяў, капіраванне каляровых кодаў і паўторнае выкарыстанне колераў у палітрах або дызайнах + Узор дакладнага колеру + Выбар колеру карысны для супастаўлення дызайну, вылучэння брэндавых колераў або праверкі значэнняў выявы. Маштабаванне мае значэнне, таму што згладжванне, цені і сцісканне могуць зрабіць суседнія пікселі трохі адрознымі ад чаканага колеру. + Адкрыйце «Выбраць колер з выявы» і абярыце зыходны малюнак патрэбнага колеру. + Павялічце маштаб перад выбаркай дробных дэталяў, каб бліжэйшыя пікселі не паўплывалі на вынік. + Скапіруйце колер у фармаце, неабходным для вашага прызначэння, напрыклад, HEX, RGB або HSV. + Стварайце палітры і карыстайцеся бібліятэкай колераў + Выцягвайце палітры, праглядайце захаваныя колеры і падтрымлівайце паслядоўныя візуальныя сістэмы + Стварыце шматразовую палітру + Палітры дапамагаюць захаваць візуальную аднастайнасць экспартаванай графікі, вадзяных знакаў і малюнкаў. Яны асабліва карысныя, калі вы неаднаразова анатаваеце скрыншоты, рыхтуеце актывы брэнда або вам патрэбны аднолькавыя колеры ў некалькіх інструментах. + Стварыце палітру з выявы або дадайце колеры ўручную, калі вы ўжо ведаеце значэнні. + Назавіце або ўпарадкуйце важныя колеры, каб вы маглі знайсці іх пазней. + Выкарыстоўвайце скапіраваныя значэнні ў Draw, вадзяных знаках, градыентах, SVG або знешніх інструментах дызайну. + Стварайце градыенты + Стварайце градыентныя выявы для фону, запаўняльнікаў і візуальных эксперыментаў + Кіруйце каляровымі пераходамі + Градыентныя інструменты карысныя, калі вам патрэбна створаная выява замест рэдагавання існуючай. Яны могуць стаць чыстым фонам, запаўняльнікамі, шпалерамі, маскамі або простымі актывамі для знешніх інструментаў дызайну. + Выберыце тып градыенту і спачатку ўсталюйце важныя колеры. + Адрэгулюйце кірунак, прыпынкі, плыўнасць і памер вываду для мэтавага выпадку выкарыстання. + Экспарт у фармаце, які адпавядае прызначэнню, звычайна ў фармаце PNG для чыста створанай графікі. + Даступнасць колеру + Выкарыстоўвайце дынамічныя колеры, кантраснасць і параметры дальтоніка, калі карыстальніцкі інтэрфейс цяжка прачытаць + Зрабіць колеры прасцей у выкарыстанні + Налады колеру ўплываюць на камфорт, зручнасць чытання і хуткасць сканавання элементаў кіравання. Калі фішкі інструментаў, паўзункі або выбраныя стану цяжка адрозніць, тэмы і спецыяльныя магчымасці могуць быць больш карыснымі, чым простая змена цёмнага рэжыму. + Паспрабуйце дынамічныя колеры, калі вы хочаце, каб праграма прытрымлівалася бягучай палітры шпалер. + Павялічце кантраснасць або змяніце схему, калі кнопкі і паверхні недастаткова вылучаюцца. + Выкарыстоўвайце варыянты схемы дальтонік, калі некаторыя станы або акцэнты цяжка адрозніць. + Альфа фон + Кіруйце папярэднім праглядам або колерам залівання, які выкарыстоўваецца вакол празрыстых PNG, WebP і падобных вынікаў + Зразумець празрысты папярэдні прагляд + Колер фону для альфа-фарматаў можа зрабіць празрыстыя выявы лягчэйшымі для праверкі, але важна ведаць, ці змяняеце вы паводзіны папярэдняга прагляду/залівання або згладжваеце канчатковы вынік. Гэта важна для налепак, лагатыпаў і малюнкаў без фону. + Спачатку выкарыстоўвайце фармат з падтрымкай альфа-версіі, напрыклад PNG або WebP, калі празрыстыя пікселі павінны перажыць экспарт. + Адрэгулюйце параметры колеру фону, калі празрыстыя краю дрэнна бачныя на паверхні праграмы. + Экспартуйце невялікі тэст і паўторна адкрыйце яго ў іншай праграме, каб пацвердзіць, ці была захавана празрыстасць або выбраная заліўка. + Выбар мадэлі AI + Выбірайце мадэль па тыпу выявы і дэфекту замест таго, каб спачатку выбраць самую вялікую + Суаднясіце мадэль з пашкоджаннем + Інструменты штучнага інтэлекту могуць спампоўваць або імпартаваць розныя мадэлі ONNX/ORT, і кожная мадэль падрыхтавана для пэўнага тыпу праблемы. Мадэль для блокаў JPEG, ачысткі радкоў анімэ, выдалення шуму, выдалення фону, афарбоўвання або павелічэння маштабу можа прывесці да горшых вынікаў, калі выкарыстоўваецца на выяве няправільнага тыпу. + Прачытайце апісанне мадэлі перад загрузкай; выберыце мадэль, якая называе вашу актуальную праблему, напрыклад, сціск, шум, паўтоны, размытасць або выдаленне фону. + Пачніце з меншай або больш канкрэтнай мадэлі пры тэставанні новага тыпу відарыса, а потым параўноўвайце з больш цяжкімі мадэлямі, толькі калі вынік недастаткова добры. + Захоўвайце толькі тыя мадэлі, якімі вы сапраўды карыстаецеся, таму што спампаваныя і імпартаваныя мадэлі могуць займаць значнае месца ў сховішчы. + Зразумець мадэльныя сем\'і + Назвы мадэляў часта намякаюць на іх мэту: высокакласныя мадэлі ў стылі RealESRGAN, мадэлі SCUNet і FBCNN ачышчаюць шум або сціск, фонавыя мадэлі ствараюць маскі, а каларызатары дадаюць колер у адценні шэрага. Імпартаваныя ўласныя мадэлі могуць быць магутнымі, але яны павінны адпавядаць чаканай форме ўводу/вываду і выкарыстоўваць падтрымліваемы фармат ONNX або ORT. + Выкарыстоўвайце сродкі павышэння маштабу, калі вам патрэбна больш пікселяў, сродкі памяншэння шуму, калі выява зярністая, і сродкі выдалення артэфактаў, калі галоўнай праблемай з\'яўляюцца блокі сціску або паласы. + Выкарыстоўвайце фонавыя мадэлі толькі для падзелу прадметаў; яны не тое ж самае, што агульнае выдаленне аб\'екта або паляпшэнне выявы. + Імпартуйце карыстальніцкія мадэлі толькі з крыніц, якім вы давяраеце, і правярайце іх на невялікім малюнку перад выкарыстаннем важных файлаў. + Параметры штучнага інтэлекту + Наладзьце памер чанка, перакрыцце і паралельныя рабочыя, каб збалансаваць якасць, хуткасць і памяць + Збалансуйце хуткасць са стабільнасцю + Памер фрагмента кантралюе, наколькі вялікімі будуць фрагменты выявы перад іх апрацоўкай мадэллю. Перакрыцце змешвае суседнія кавалкі, каб схаваць межы. Паралельныя воркеры могуць паскорыць апрацоўку, але кожнаму воркеру патрэбна памяць, таму вялікія значэнні могуць зрабіць вялікія выявы нестабільнымі на тэлефонах з абмежаванай аператыўнай памяццю. + Выкарыстоўвайце больш буйныя кавалкі для больш гладкага кантэксту, калі ваша прылада надзейна апрацоўвае мадэль і выява не велізарная. + Павялічце перакрыцце, калі стануць бачнымі межы кавалкаў, але памятайце, што большае перакрыцце азначае больш паўторнай працы. + Падымайце паралельных работнікаў толькі пасля стабільнага тэсту; калі апрацоўка запавольваецца, награваецца прылада або выходзіць з ладу, спачатку скароціце працоўных. + Пазбягайце артэфактаў кавалкаў + Чанкінг дазваляе прылажэнню апрацоўваць выявы, памер якіх большы, чым мадэль можа зручна апрацоўваць адразу. Кампраміс заключаецца ў тым, што кожная плітка мае меншы навакольны кантэкст, таму нізкае перакрыцце або занадта малыя кавалкі могуць ствараць бачныя межы, змены тэкстуры або неадпаведныя дэталі паміж суседнімі абласцямі. + Павялічце перакрыцце, калі вы бачыце прамавугольныя межы, паўторныя змены тэкстуры або скачкі дэталяў паміж апрацаванымі абласцямі. + Паменшыце памер фрагмента, калі працэс не атрымоўваецца перад вытворчасцю вываду, асабліва на вялікіх малюнках або цяжкіх мадэлях. + Захавайце невялікі кроп-тэст перад апрацоўкай поўнага відарыса, каб вы маглі хутка параўнаць параметры. + Стабільнасць ІІ + Паменшыце памер чанка, працоўныя працэсары і дазвол крыніцы, калі апрацоўка штучнага інтэлекту выходзіць з ладу або завісае + Аднаўленне пасля цяжкіх работ AI + Апрацоўка AI - адзін з самых цяжкіх працоўных працэсаў у дадатку. Збоі, няўдалыя сеансы, завісанні або раптоўныя перазапускі праграмы звычайна азначаюць, што мадэль, раздзяленне крыніцы, памер фрагмента або колькасць паралельных рабочых занадта патрабавальныя для бягучага стану прылады. + Калі праграма выходзіць з ладу або не можа адкрыць сеанс мадэлі, паменшыце паралельныя рабочыя і памер кавалка, а затым паўтарыце спробу той жа выявы. + Змяняйце памер вельмі вялікіх малюнкаў перад апрацоўкай штучным інтэлектам, калі мэта не патрабуе зыходнага раздзялення. + Зачыніце іншыя цяжкія праграмы, выкарыстоўвайце меншую мадэль або апрацоўвайце менш файлаў адначасова, калі нагрузка на памяць працягвае вяртацца. + Дыягнаставаць няспраўнасці мадэлі + Няўдалы запуск AI не заўсёды азначае, што малюнак дрэнны. Файл мадэлі можа быць няпоўным, не падтрымліваецца, занадта вялікі для памяці або не адпавядае аперацыі. Калі праграма паведамляе аб няўдалым сеансе, разглядайце гэта як сігнал, каб спачатку спрасціць мадэль і параметры. + Выдаліце ​​і паўторна загрузіце мадэль, калі яна не працуе адразу пасля загрузкі або паводзіць сябе так, быццам файл пашкоджаны. + Паспрабуйце ўбудаваную мадэль для загрузкі, перш чым вінаваціць імпартаваную карыстальніцкую мадэль, таму што імпартаваныя мадэлі могуць мець несумяшчальныя ўваходныя дадзеныя. + Калі вам трэба паведаміць пра збой або памылку сеанса, выкарыстоўвайце журналы праграм пасля прайгравання збою. + Эксперыментуйце ў Shader Studio + Выкарыстоўвайце эфекты шэйдараў графічнага працэсара для пашыранай апрацоўкі малюнкаў і карыстальніцкага выгляду + Асцярожна працуйце з шэйдарамі + Shader Studio з\'яўляецца магутным, але код і параметры шэйдара патрабуюць невялікіх змяненняў, якія можна праверыць. Ставіцеся да гэтага як да эксперыментальнага інструмента: захоўвайце працоўны базавы ўзровень, змяняйце адно за адным і тэстуйце з рознымі памерамі выявы, перш чым давяраць прадусталяваным наладам. + Перш чым дадаваць параметры, пачніце з вядомага працоўнага шэйдара або простага эфекту. + Змяняйце па адным параметру або блоку кода і правярайце папярэдні прагляд на наяўнасць памылак. + Экспартуйце перадусталёўкі толькі пасля іх тэставання на некалькіх розных памерах малюнкаў. + Зрабіць мастацтва SVG або ASCII + Стварайце вектарныя актывы або тэкставыя выявы для творчага выхаду + Выберыце тып творчага вываду + Інструменты SVG і ASCII служаць розным мэтам: маштабуемая графіка або рэндэрынг тэксту. Абодва з\'яўляюцца творчымі пераўтварэннямі, таму папярэдні прагляд у канчатковым памеры больш важны, чым судзіць аб выніку па малюсенькай мініяцюры. + Выкарыстоўвайце SVG Maker, калі вынік павінен дакладна маштабавацца або паўторна выкарыстоўвацца ў працоўных працэсах праектавання. + Выкарыстоўвайце ASCII Art, калі вам патрэбна стылізаванае тэкставае прадстаўленне выявы. + Папярэдні прагляд у канчатковым памеры, таму што дробныя дэталі могуць знікнуць у згенераваным вывадзе. + Стварэнне шумавых тэкстур + Стварайце працэдурныя тэкстуры для фонаў, масак, накладанняў або эксперыментаў + Наладзьце працэдурны вывад + Генерацыя шуму стварае новыя выявы з параметраў, таму невялікія змены могуць даць вельмі розныя вынікі. Гэта карысна для тэкстур, масак, накладанняў, запаўняльнікаў або тэставання сціску з падрабязнымі ўзорамі. + Выберыце тып шуму і выхадны памер, перш чым наладжваць дробныя дэталі. + Адрэгулюйце маштаб, інтэнсіўнасць, колеры або зерне, пакуль узор не будзе адпавядаць мэты выкарыстання. + Захавайце карысныя вынікі і запішыце параметры, калі пазней спатрэбіцца ўзнавіць тэкстуру. + Праекты разметкі + Выкарыстоўвайце файлы праекта, калі анатацыі павінны заставацца даступнымі для рэдагавання пасля закрыцця праграмы + Захоўвайце шматслойныя праўкі шматразовымі + Файлы праекта разметкі карысныя, калі скрыншот, дыяграма або малюнак з інструкцыямі могуць спатрэбіцца змяніць пазней. Экспартаваныя файлы PNG/JPEG з\'яўляюцца канчатковымі выявамі, у той час як файлы праекта захоўваюць рэдагуемыя пласты і стан рэдагавання для будучых карэкціровак. + Выкарыстоўвайце слаі разметкі, калі анатацыі, выноскі або элементы макета павінны заставацца даступнымі для рэдагавання. + Захавайце або паўторна адкрыйце файл праекта перад экспартам канчатковага відарыса, калі вы чакаеце дадатковых версій. + Экспартуйце нармальную выяву толькі тады, калі вы будзеце гатовыя падзяліцца згладжанай канчатковай версіяй. + Шыфраваць файлы + Абараніце файлы паролем і праверце расшыфроўку перад выдаленнем любой зыходнай копіі + Уважліва звяртайцеся з зашыфраванымі вывадамі + Інструменты шыфравання могуць абараняць файлы з дапамогай шыфравання на аснове пароля, але яны не замяняюць запамінанне ключа або захаванне рэзервовых копій. Шыфраванне карысна для транспарціроўкі і захоўвання, у той час як ZIP лепш, калі галоўнай мэтай з\'яўляецца аб\'яднанне некалькіх выхадаў. + Спачатку зашыфруйце копію і захавайце арыгінал, пакуль вы не праверыце, што расшыфроўка працуе з паролем, які вы захавалі. + Выкарыстоўвайце менеджэр пароляў або іншае бяспечнае месца для ключа; праграма не можа адгадаць згублены пароль за вас. + Абагульвайце зашыфраваныя файлы толькі з людзьмі, якія таксама маюць пароль і ведаюць, які інструмент або метад павінен іх расшыфраваць. + Расстаўце інструменты + Інструменты групоўкі, упарадкавання, пошуку і замацавання, каб галоўны экран адпавядаў вашаму рэальнаму працоўнаму працэсу + Зрабіце галоўны экран больш хуткім + Налады размяшчэння інструментаў варта наладзіць, калі вы ведаеце, якімі інструментамі карыстаецеся часцей за ўсё. Групоўка дапамагае даследаваць, нестандартны парадак дапамагае мышачнай памяці, а абранае робіць штодзённыя інструменты даступнымі, нават калі поўны спіс становіцца вялікім. + Выкарыстоўвайце згрупаваны рэжым падчас вывучэння праграмы або калі вы аддаеце перавагу інструменты, арганізаваныя па тыпу задач. + Пераключыцеся на карыстальніцкі парадак, калі вы ўжо ведаеце інструменты, якія часта карыстаецеся, і хочаце менш пракручванняў. + Выкарыстоўвайце налады пошуку і абранае разам для рэдкіх інструментаў, якія вы памятаеце па назве, але не хочаце, каб яны былі бачныя ўвесь час. + Апрацоўваць вялікія файлы + Пазбягайце павольнага экспарту, нагрузкі на памяць і нечакана вялікага выхаду + Скароціце працу перад экспартам + Вельмі вялікія выявы, доўгія пакеты і высакаякасныя фарматы могуць заняць больш памяці і часу. Самае хуткае выпраўленне звычайна заключаецца ў памяншэнні аб\'ёму працы перад самай цяжкай аперацыяй, а не ў чаканні, пакуль скончыцца тая ж самая вялізная праца. + Змяняйце памер велізарных малюнкаў перад прымяненнем цяжкіх фільтраў, OCR або пераўтварэння PDF. + Спачатку выкарыстоўвайце меншую партыю для пацверджання налад, а затым апрацуйце астатнюю частку з той жа папярэдняй наладай. + Зніжэнне якасці або змяненне фармату, калі выхадны файл большы за чаканы. + Кэш і папярэдні прагляд + Разуменне згенераваных папярэдніх праглядаў, ачысткі кэша і выкарыстання сховішча пасля цяжкіх сеансаў + Захоўвайце баланс памяці і хуткасці + Стварэнне папярэдняга прагляду і кэш могуць паскорыць паўторны прагляд, але цяжкія сеансы рэдагавання могуць пакінуць часовыя даныя. Налады кэша дапамагаюць, калі праграма працуе павольна, мала сховішча або папярэдні прагляд выглядае састарэлым пасля шматлікіх эксперыментаў. + Трымаць папярэдні прагляд уключаным, калі праглядаць шмат малюнкаў больш важна, чым мінімізаваць часовае сховішча. + Ачышчайце кэш пасля вялікіх партый, няўдалых эксперыментаў або працяглых сеансаў з вялікай колькасцю файлаў высокай раздзяляльнасці. + Выкарыстоўвайце аўтаматычную ачыстку кэша, калі вы аддаеце перавагу ачыстцы сховішча, чым падтрыманню папярэдняга прагляду ў цёплым стане паміж запускамі. + Адрэгулюйце паводзіны экрана + Выкарыстоўвайце поўнаэкранны рэжым, бяспечны рэжым, яркасць і параметры сістэмнай панэлі для сканцэнтраванай працы + Зрабіце так, каб інтэрфейс адпавядаў сітуацыі + Налады экрана дапамагаюць, калі вы рэдагуеце альбом у альбомным рэжыме, паказваеце асабістыя выявы або вам патрэбна пастаянная яркасць. Яны не патрабуюцца для звычайнага выкарыстання, але яны вырашаюць нязручныя сітуацыі, такія як праверка QR, прыватны папярэдні прагляд або сціснутае альбомнае рэдагаванне. + Выкарыстоўвайце налады поўнаэкраннага рэжыму або сістэмнай панэлі, калі рэдагаванне прасторы важней, чым навігацыя chrome. + Выкарыстоўвайце бяспечны рэжым, калі прыватныя выявы не павінны з\'яўляцца на скрыншотах або папярэднім праглядзе прыкладанняў. + Выкарыстоўвайце кантроль яркасці для інструментаў, дзе цёмныя выявы або QR-коды цяжка праверыць. + Захоўвайце празрыстасць + Не дазваляйце празрыстым фонам станавіцца белымі, чорнымі або згладжанымі + Выкарыстоўваць альфа-вывад + Празрыстасць захоўваецца толькі тады, калі выбраны інструмент і фармат вываду падтрымліваюць альфа-версію. Калі экспартаваны фон становіцца белым або чорным, праблема звычайна заключаецца ў фармаце вываду, параметрах залівання/фону або праграме прызначэння, якая згладзіла малюнак. + Выберыце PNG або WebP пры экспарце малюнкаў, налепак, лагатыпаў або накладанняў без фону. + Пазбягайце JPEG для празрыстага вываду, таму што ён не захоўвае альфа-канал. + Праверце налады, звязаныя з колерам фону для альфа-фарматаў, калі папярэдні прагляд паказвае запоўнены фон. + Выпраўце праблемы з абагульваннем і імпартам + Выкарыстоўвайце налады выбару і фарматы, якія падтрымліваюцца, калі файлы не адлюстроўваюцца або не адкрываюцца належным чынам + Загрузіце файл у праграму + Праблемы з імпартам часта ўзнікаюць з-за дазволаў пастаўшчыка, непадтрымоўваных фарматаў або паводзін выбаршчыка. Файлы, якія абагульваюцца з воблачных праграм і мессенджераў, могуць быць часовымі, таму выбар пастаяннай крыніцы можа быць больш надзейным для працяглых правак. + Паспрабуйце адкрыць файл праз сістэмны выбар замест ярлыка апошніх файлаў. + Калі фармат не падтрымліваецца адным інструментам, спачатку пераўтварыце яго або адкрыйце больш спецыяльны інструмент. + Праглядзіце налады выбару выявы і запуску, калі патокі абагульвання або непасрэднае адкрыццё інструмента паводзяць сябе інакш, чым чакалася. + Пацверджанне выхаду + Пазбягайце страты незахаваных правак, калі жэсты, націсканне назад або пераключэнне інструментаў адбываюцца выпадкова + Абараніце незавершаную працу + Пацвярджэнне выхаду з інструмента карысна, калі вы выкарыстоўваеце навігацыю жэстамі, рэдагуеце вялікія файлы або часта пераключаецеся паміж праграмамі. Ён дадае невялікую паўзу перад выхадам з інструмента, каб незавершаныя налады і папярэдні прагляд не былі страчаны выпадкова. + Уключыце пацвярджэнне, калі выпадковыя жэсты назад калі-небудзь закрывалі інструмент перад экспартам. + Трымайце яго адключаным, калі вы ў асноўным робіце хуткія аднакрокавыя пераўтварэнні і аддаеце перавагу больш хуткай навігацыі. + Выкарыстоўвайце яго разам з папярэднімі наладамі для больш працяглых працоўных працэсаў, калі перабудова налад будзе раздражняць. + Выкарыстоўвайце журналы, паведамляючы аб праблемах + Збярыце карысную інфармацыю, перш чым адкрыць праблему або звязацца з распрацоўшчыкам + Паведаміць аб праблемах з кантэкстам + Выразную справаздачу з крокамі, версіяй праграмы, тыпам уводу і журналамі значна лягчэй дыягнаставаць. Журналы найбольш карысныя адразу пасля прайгравання праблемы, таму што яны захоўваюць навакольны кантэкст свежым. + Звярніце ўвагу на назву інструмента, дакладнае дзеянне і тое, адбываецца яно з адным файлам ці з кожным файлам. + Адкрыйце журналы праграмы пасля прайгравання праблемы і па магчымасці абагульвайце адпаведныя вынікі журнала. + Далучайце скрыншоты або ўзоры файлаў, толькі калі яны не ўтрымліваюць прыватнай інфармацыі. + Фонавая апрацоўка была спынена + Android не дазволіў своечасова запусціць апавяшчэнне аб фонавай апрацоўцы. Гэта абмежаванне сістэмнага часу, якое нельга надзейна выправіць. Перазапусціце праграму і паўтарыце спробу; можа дапамагчы трымаць праграму адкрытай і дазваляць апавяшчэнні або фонавую працу. + Прапусціць выбар файла + Адкрывайце інструменты хутчэй, калі ўвод звычайна паступае з абагульвання, буфера абмену або папярэдняга экрана + Зрабіце паўторныя запускі інструмента больш хуткімі + Прапусціць выбар файла змяняе першы крок падтрымоўваных інструментаў. Гэта карысна, калі вы звычайна ўваходзіце ў інструмент з ужо абранай выявай або з дзеянняў абагульвання Android, але гэта можа збянтэжыць, калі вы чакаеце, што кожны інструмент неадкладна запытвае файл. + Уключайце яго толькі тады, калі ваш звычайны паток ужо забяспечвае зыходны відарыс перад адкрыццём інструмента. + Трымайце яго адключаным падчас вывучэння праграмы, таму што выразны выбар робіць кожны працэс інструмента больш зразумелым. + Калі інструмент адкрываецца без відавочнага ўводу, адключыце гэты параметр або пачніце з \"Падзяліцца/Адкрыць з дапамогай\", каб Android перадаваў файл першым. + Спачатку адкрыйце рэдактар + Прапусціце папярэдні прагляд, калі абагуленая выява павінна ісці адразу ў рэдагаванне + Выберыце папярэдні прагляд або рэдагаванне ў якасці першага прыпынку + Open Edit Instead Of Preview прызначаны для карыстальнікаў, якія ўжо ведаюць, што хочуць змяніць выяву. Папярэдні прагляд бяспечней, калі вы правяраеце файлы з чата або воблачнага сховішча; прамое рэдагаванне хутчэй для скрыншотаў, хуткай абрэзкі, анатацый і аднаразовых выпраўленняў. + Уключыце прамое рэдагаванне, калі большасць агульных малюнкаў неадкладна патрабуюць абрэзкі, разметкі, фільтраў або экспарту змяненняў. + Спачатку пакіньце папярэдні прагляд, калі вы часта правяраеце метаданыя, памеры, празрыстасць або якасць выявы, перш чым прыняць рашэнне. + Выкарыстоўвайце гэта разам з абранымі, каб агульныя выявы былі блізкія да інструментаў, якімі вы насамрэч карыстаецеся. + Прадусталяваны ўвод тэксту + Увядзіце дакладныя прадусталяваныя значэнні замест таго, каб рэгулярна наладжваць элементы кіравання + Выкарыстоўвайце дакладныя значэнні, калі паўзункі працуюць павольна + Прадусталяваны ўвод тэксту дапамагае, калі вам патрэбныя дакладныя лічбы для паўторнай працы: памеры сацыяльных малюнкаў, палі дакументаў, мэты сціску, шырыня радкоў або іншыя значэнні, якія раздражняюць дасягнуць жэстамі. Гэта таксама карысна на маленькіх экранах, дзе цяжэй перамяшчаць паўзунок. + Уключыце ўвод тэксту, калі вы часта паўторна выкарыстоўваеце дакладныя памеры, працэнты, значэнні якасці або параметры інструмента. + Захавайце значэнне ў якасці перадусталёўкі, увёўшы яго адзін раз, а затым выкарыстоўвайце яго паўторна замест таго, каб аднаўляць тыя ж налады. + Захоўвайце элементы кіравання жэстамі для грубай візуальнай налады і ўведзеныя значэнні для строгіх патрабаванняў да вываду. + Захаваць каля арыгінала + Размяшчайце створаныя файлы побач з зыходнымі выявамі, калі важны кантэкст папкі + Захоўвайце вынікі з іх крыніцамі + Захаваць у зыходную тэчку зручна для тэчак камеры, тэчак праектаў, сканаваных дакументаў і кліенцкіх пакетаў, дзе адрэдагаваная копія павінна заставацца побач з крыніцай. Гэта можа быць менш акуратным, чым спецыяльная папка для вываду, таму аб\'яднайце яго з выразнымі суфіксамі імёнаў файлаў або шаблонамі. + Уключыце яго, калі кожная зыходная папка ўжо мае значэнне і вынікі павінны заставацца там згрупаванымі. + Выкарыстоўвайце суфіксы імёнаў файлаў, прэфіксы, пазнакі часу або шаблоны, каб адрэдагаваныя копіі было лёгка аддзяліць ад арыгіналаў. + Адключыце яго для змешаных пакетаў, калі вы хочаце, каб усе створаныя файлы былі сабраныя ў адну тэчку экспарту. + Прапусціць большы выхад + Пазбягайце захавання пераўтварэнняў, якія нечакана становяцца цяжэйшымі за крыніцу + Абараніце партыі ад сюрпрызаў дрэннага памеру + Некаторыя пераўтварэнні павялічваюць файлы, асабліва скрыншоты PNG, ужо сціснутыя фатаграфіі, высакаякасны WebP або выявы з захаванымі метаданымі. Allow Skip If Larger прадухіляе замену гэтых вынікаў меншай крыніцай у працоўных працэсах, дзе памяншэнне памеру з\'яўляецца галоўнай мэтай. + Уключыце яго, калі экспарт прызначаны для сціску, змены памеру або падрыхтоўкі файлаў да абмежаванняў на загрузку. + Прагляд прапушчаных файлаў пасля пакета; яны могуць быць ужо аптымізаваныя або можа спатрэбіцца іншы фармат. + Адключыце яго, калі якасць, празрыстасць або сумяшчальнасць фарматаў важныя больш, чым канчатковы памер файла. + Буфер абмену і спасылкі + Кіруйце аўтаматычным уводам у буфер абмену і папярэднім праглядам спасылак для больш хуткіх тэкставых інструментаў + Зрабіце аўтаматычны ўвод прадказальным + Налады буфера абмену і спасылкі зручныя для працоўных працэсаў QR, Base64, URL і тэксту, але аўтаматычны ўвод павінен заставацца наўмысным. Калі здаецца, што праграма сама запаўняе палі, праверце паводзіны ўстаўкі ў буфер абмену; калі карты са спасылкамі адчуваюць сябе шумна або павольна, адрэгулюйце паводзіны папярэдняга прагляду спасылак. + Дазволіць аўтаматычную ўстаўку ў буфер абмену, калі вы часта капіруеце тэкст, і неадкладна адкрываць інструмент супадзення. + Адключыце аўтаматычную ўстаўку, калі змесціва прыватнага буфера абмену з\'яўляецца ў інструментах, дзе вы гэтага не чакалі. + Выключыце папярэдні прагляд спасылак, калі выбарка папярэдняга прагляду павольная, адцягвае або непатрэбная для вашага працоўнага працэсу. + Кампактныя элементы кіравання + Выкарыстоўвайце больш шчыльныя селектары і хуткае размяшчэнне налад, калі месца на экране мала + Размясціце больш элементаў кіравання на маленькіх экранах + Кампактныя селектары і хуткае размяшчэнне налад дапамагаюць на маленькіх тэлефонах, альбомнае рэдагаванне і інструменты з вялікай колькасцю параметраў. Кампраміс заключаецца ў тым, што элементы кіравання могуць здавацца больш шчыльнымі, таму гэта лепш за ўсё пасля таго, як вы ўжо распазнаеце агульныя параметры. + Уключыце кампактныя селектары, калі дыялогавыя вокны або спісы опцый здаюцца занадта высокімі для экрана. + Адрэгулюйце бок хуткіх налад, калі да элементаў кіравання інструментам лягчэй дабрацца з аднаго боку дысплея. + Вярніцеся да больш прасторных элементаў кіравання, калі вы ўсё яшчэ вывучаеце праграму або часта прапускаеце шчыльныя параметры. + Загрузіць выявы з Інтэрнэту + Устаўце URL-адрас старонкі або відарыса, папярэдне праглядзіце прааналізаваныя выявы, а потым захавайце або падзяліцеся выбранымі вынікамі + Свядома выкарыстоўвайце вэб-малюнкі + Web Image Loading аналізуе крыніцы малюнкаў па прадастаўленым URL-адрасе, праглядае першы даступны малюнак і дазваляе вам захоўваць або абагульваць выбраныя разабраныя выявы. Некаторыя сайты выкарыстоўваюць часовыя, абароненыя або створаныя скрыптамі спасылкі, таму старонка, якая працуе ў браўзеры, усё роўна можа не вяртаць прыдатныя для выкарыстання выявы. + Устаўце прамы URL-адрас выявы або URL-адрас старонкі і пачакайце, пакуль з\'явіцца прааналізаваны спіс малюнкаў. + Выкарыстоўвайце рамку або элементы кіравання выбарам, калі старонка змяшчае некалькі малюнкаў і вам патрэбныя толькі некаторыя з іх. + Калі нічога не загружаецца, паспрабуйце прамую спасылку на выяву або спачатку загрузіце праз браўзер; сайт можа блакаваць разбор або выкарыстоўваць прыватныя спасылкі. + Выберыце тып змены памеру + Зразумейце яўныя, гнуткія, абрэзкі і падганянне, перш чым навязваць малюнку новыя вымярэнні + Кіруйце формай, палатном і суадносінамі бакоў + Тып змены памеру вырашае, што адбудзецца, калі запытаная шырыня і вышыня не адпавядаюць зыходным суадносінам бакоў. Гэта розніца паміж расцягваннем пікселяў, захаваннем прапорцый, абразаннем дадатковай вобласці або даданнем фонавага палатна. + Выкарыстоўвайце Explicit, толькі калі скажэнні прымальныя або крыніца ўжо мае тыя ж суадносіны бакоў, што і мэта. + Выкарыстоўвайце гнуткі, каб захаваць прапорцыі, змяняючы памер з важнага боку, асабліва для фатаграфій і змешаных партый. + Выкарыстоўвайце Crop для строгіх рамак без межаў або Fit, калі ўся выява павінна заставацца бачнай унутры фіксаванага палатна. + Выберыце рэжым маштабавання выявы + Выберыце фільтр паўторнай выбаркі, які кантралюе рэзкасць, плыўнасць, хуткасць і артэфакты краёў + Змяняйце памер без выпадковай мяккасці + Рэжым маштабу выявы кантралюе, як новыя пікселі разлічваюцца падчас змены памеру. Простыя рэжымы хуткія і прадказальныя, у той час як прасунутыя фільтры могуць лепш захоўваць дэталі, але могуць завастрыць краю, стварыць арэолы або заняць больш часу на вялікіх малюнках. + Выкарыстоўвайце Basic або Bilinear для хуткай штодзённай змены памеру, калі вынік павінен быць толькі меншым і чыстым. + Выкарыстоўвайце рэжымы Lanczos, Robidoux, Spline або EWA, калі важныя дробныя дэталі, тэкст або высакаякаснае памяншэнне маштабу. + Выкарыстоўвайце Nearest для піксельнага мастацтва, абразкоў і жорсткай графікі, дзе размытыя пікселі сапсуюць выгляд. + Маштаб каляровай прасторы + Вырашыце, як змешваюцца колеры пры паўторнай выбарцы пікселяў падчас змены памеру + Захоўвайце натуральныя градыенты і краю + Шкала каляровай прасторы змяняе матэматычныя вылічэнні, якія выкарыстоўваюцца пры змене памеру. Большасць малюнкаў падыходзяць па змаўчанні, але лінейныя або перцэпцыйныя прасторы могуць зрабіць градыенты, цёмныя краю і насычаныя колеры больш чыстымі пасля моцнага маштабавання. + Пакіньце значэнне па змаўчанні, калі хуткасць і сумяшчальнасць важныя больш, чым малюсенькія адрозненні ў колеры. + Паспрабуйце лінейны або перцэпцыйны рэжымы, калі цёмныя контуры, скрыншоты інтэрфейсу, градыенты або каляровыя паласы выглядаюць няправільна пасля змены памеру. + Параўнайце да і пасля на рэальным мэтавым экране, таму што змены каляровай прасторы могуць быць нязначнымі і залежаць ад зместу. + Абмежаваць рэжымы змены памеру + Ведайце, калі звышлімітныя выявы трэба прапусціць, перакадзіраваць або паменшыць, каб яны змясціліся + Выберыце, што адбываецца на мяжы + Limit Resize - гэта не толькі інструмент для памяншэння малюнкаў. Яго рэжым вызначае, што робіць праграма, калі крыніца ўжо знаходзіцца ў вашых межах або калі толькі адзін бок парушае правіла, што важна для змешаных папак і падрыхтоўкі да загрузкі. + Выкарыстоўвайце \"Прапусціць\", калі выявы ў межах абмежаванняў трэба пакінуць некранутымі і не экспартаваць зноў. + Выкарыстоўвайце Recode, калі памеры прымальныя, але вам усё яшчэ патрэбны новы фармат, паводзіны метададзеных, якасць або імя файла. + Выкарыстоўвайце Zoom, калі відарысы, якія парушаюць абмежаванні, павінны быць прапарцыянальна паменшаны, пакуль яны не адпавядаюць дазволенаму полі. + Мэты суадносін бакоў + Пазбягайце расцягнутых граняў, абрэзаных краёў і нечаканых межаў у строгіх памерах вываду + Супастаўце форму перад змяненнем памеру + Шырыня і вышыня складаюць толькі палову мэтавага памеру. Суадносіны бакоў вырашаюць, ці можа відарыс змясціцца натуральным чынам, яго трэба абрэзаць ці патрэбна вакол яго палатно. Першая праверка формы прадухіляе самыя дзіўныя вынікі змены памеру. + Параўнайце зыходную форму з мэтавай формай, перш чым выбраць «Яўна», «Абрэзаць» або «Падпасаваць». + Абрэжце спачатку, калі аб\'ект можа страціць лішні фон і месца прызначэння патрабуе строгай рамкі. + Выкарыстоўвайце Fit або Flexible, калі кожная частка зыходнага відарыса павінна заставацца бачнай. + Вышэйшы або звычайны памер + Ведайце, калі для дадання пікселяў патрэбен штучны інтэлект, а калі дастаткова простага маштабавання + Не блытайце памер з дэталямі + Нармальнае змяненне памеру змяняе памеры шляхам інтэрпаляцыі пікселяў; ён не можа вынайсці рэальныя дэталі. Высокакласны штучны інтэлект можа ствараць больш выразныя дэталі, але ён павольней і можа ствараць галюцынацыі тэкстуры, таму правільны выбар залежыць ад таго, патрэбна вам сумяшчальнасць, хуткасць або візуальнае аднаўленне. + Выкарыстоўвайце звычайны памер для мініяцюр, абмежаванняў на загрузку, скрыншотаў і простых змяненняў памераў. + Выкарыстоўвайце штучны інтэлект высокага маштабу, калі маленькая або мяккая выява павінна лепш выглядаць у большым памеры. + Параўноўвайце твары, тэкст і ўзоры пасля высокага маштабу AI, таму што згенераваныя дэталі могуць выглядаць пераканаўча, але некарэктна. + Парадак фільтраў мае значэнне + Ужывайце ачыстку, колер, рэзкасць і канчатковае сцісканне ў наўмыснай паслядоўнасці + Стварыце больш чысты фільтр + Фільтры не заўсёды ўзаемазаменныя. Размыццё перад павышэннем рэзкасці, узмацненне кантраснасці перад OCR або змяненне колеру перад сцісканнем могуць прывесці да вельмі розных вынікаў ад тых жа элементаў кіравання. + Спачатку абрэжце і павярніце, каб пазнейшыя фільтры дзейнічалі толькі на пікселях, якія застануцца. + Прымяніце экспазіцыю, баланс белага і кантраст перад рэзкасцю або стылізаванымі эфектамі. + Захоўвайце канчатковае змяненне памеру і сціску ў канцы, каб прагледжаныя дэталі прадказальна перажылі экспарт. + Выправіць краю фону + Ачысціце арэолы, рэшткі ценяў, валасы, дзіркі і мяккія контуры пасля аўтаматычнага выдалення + Зрабіце выразы наўмыснымі + Аўтаматычныя маскі часта выглядаюць добра з першага погляду, але выяўляюць праблемы на яркім, цёмным або ўзорыстым фоне. Ачыстка краю - гэта розніца паміж хуткім выразам і шматразовай налепкай, лагатыпам або выявай прадукту. + Праглядзіце вынік на кантрасным фоне, каб выявіць арэолы і адсутныя дэталі па краях. + Выкарыстоўвайце аднаўленне для валасоў, кутоў і празрыстых аб\'ектаў, перш чым сцерці навакольныя рэшткі. + Экспартуйце невялікі тэст канчатковага колеру фону, калі выраз будзе змешчаны ў дызайн. + Чытаемыя вадзяныя знакі + Зрабіце пазнакі бачнымі на яркіх, цёмных, занятых і абрэзаных выявах, не сапсаваўшы фатаграфію + Абараніце малюнак, не хаваючы яго + Вадзяны знак, які добра выглядае на адной выяве, можа знікнуць на іншай. Памер, непразрыстасць, кантраснасць, размяшчэнне і паўтарэнне трэба выбіраць для ўсёй серыі, а не толькі для першага прагляду. + Перад экспартам усіх файлаў праверце адзнаку на самых яркіх і самых цёмных малюнках у групе. + Выкарыстоўвайце меншую непразрыстасць для вялікіх паўтаральных знакаў і больш моцны кантраст для невялікіх кутніх знакаў. + Захоўвайце важныя твары, тэкст і дэталі прадукту выразнымі, калі знак не прызначаны для прадухілення паўторнага выкарыстання. + Разбіце адну выяву на пліткі + Разрэжце адзін малюнак па радках, слупках, карыстацкіх працэнтах, фармаце і якасці + Падрыхтуйце сеткі і замоўленыя часткі + Раздзяленне выявы стварае некалькі выхадаў з адной зыходнай выявы. Ён можа дзяліць па колькасці радкоў і слупкоў, наладжваць працэнты радкоў і слупкоў і захоўваць фрагменты з абраным фарматам вываду і якасцю, што карысна для сетак, зрэзаў старонак, панарам і ўпарадкаваных паведамленняў у сацыяльных сетках. + Выберыце зыходны малюнак, а затым усталюйце неабходную колькасць радкоў і слупкоў. + Адрэгулюйце працэнты ў радку або слупку, калі не ўсе часткі павінны быць аднолькавага памеру. + Перад захаваннем выберыце фармат і якасць вываду, каб кожны створаны фрагмент выкарыстоўваў аднолькавыя правілы экспарту. + Выражыце палоскі малюнка + Выдаліце ​​вертыкальныя або гарызантальныя вобласці і злучыце астатнія часткі разам + Выдаліць вобласць, не пакідаючы прабелаў + Рэзка выявы адрозніваецца ад звычайнай абрэзкі. Ён можа выдаліць выбраную вертыкальную або гарызантальную паласу, затым аб\'яднаць астатнія часткі выявы разам. Замест гэтага рэжым інверсіі захоўвае выбраную вобласць, а выкарыстанне абодвух зваротных напрамкаў захоўвае выбраны прамавугольнік. + Выкарыстоўвайце вертыкальную рэзку для бакавых абласцей, слупкоў або сярэдніх палос; выкарыстоўваць гарызантальную рэзку для банэраў, прабелаў або радкоў. + Уключыце інверсію на восі, калі хочаце захаваць выбраную частку, а не выдаляць яе. + Праглядзіце краю пасля выразання, таму што аб\'яднанае змесціва можа выглядаць рэзка, калі выдаленая вобласць перасякае важныя дэталі. + Выберыце фармат вываду + Выберыце JPEG, PNG, WebP, AVIF, JXL або PDF у залежнасці ад змесціва і прызначэння + Няхай пункт прызначэння вызначае фармат + Лепшы фармат залежыць ад таго, што змяшчае выява і дзе яна будзе выкарыстоўвацца. Фатаграфіі, скрыншоты, празрыстыя актывы, дакументы і аніміраваныя файлы па-рознаму выходзяць з ладу, калі іх фарматуюць у няправільным фармаце. + Выкарыстоўвайце JPEG для шырокай сумяшчальнасці фатаграфій, калі празрыстасць і дакладныя пікселі не патрабуюцца. + Выкарыстоўвайце PNG для выразных скрыншотаў, здымкаў карыстацкага інтэрфейсу, празрыстай графікі і рэдагавання без страт. + Выкарыстоўвайце WebP, AVIF або JXL, калі мае значэнне кампактны сучасны вывад і прымаючая праграма яго падтрымлівае. + Выняць аўдыё вокладкі + Захоўвайце ўбудаваныя вокладкі альбомаў або даступныя медыя-фреймы з аўдыяфайлаў як выявы PNG + Выцягнеце вокладкі з аўдыяфайлаў + Audio Covers выкарыстоўвае медыяметададзеныя Android для чытання ўбудаваных вокладак з аўдыяфайлаў. Калі ўбудаваная ілюстрацыя недаступная, рэтрывер можа вярнуцца да медыякадра, калі Android можа яго забяспечыць; калі ні таго, ні іншага не існуе, інструмент паведамляе, што выявы для здабывання няма. + Адкрыйце Audio Covers і абярыце адзін або некалькі аўдыяфайлаў з чаканай вокладкай. + Праглядзіце вынятую вокладку перад захаваннем або адкрыццём доступу, асабліва для файлаў з праграм струменевай перадачы або запісу. + Калі выява не знойдзена, аўдыяфайл, хутчэй за ўсё, не мае ўбудаванай вокладкі або Android не можа выставіць прыдатны кадр. + Даты і метаданыя месцазнаходжання + Вырашыце, што захаваць, калі час здымкі мае значэнне, але даныя GPS або прылады не павінны ўцечка + Аддзяліце карысныя метаданыя ад прыватных + Не ўсе метаданыя аднолькава рызыкоўныя. Даты здымкі могуць быць карысныя для архівавання і сартавання, у той час як GPS, палі, падобныя на серыйны нумар прылады, тэгі праграмнага забеспячэння або палі ўладальніка могуць раскрыць больш, чым меркавалася. + Захоўвайце пазнакі даты і часу, калі храналагічны парадак мае значэнне для альбомаў, сканаў або гісторыі праектаў. + Выдаліце ​​GPS і тэгі ідэнтыфікацыі прылад, перш чым публічна абагульваць або адпраўляць выявы незнаёмцам. + Экспартуйце ўзор і яшчэ раз праверце EXIF, калі патрабаванні прыватнасці будуць жорсткімі. + Пазбягайце сутыкненняў імёнаў файлаў + Абараніце пакетныя вывады, калі многія файлы маюць агульныя імёны, такія як image.jpg або screenshot.png + Зрабіце кожнае імя выхаду унікальным + Дублікаты файлаў часта сустракаюцца, калі выявы паступаюць з мессенджеров, скрыншотаў, воблачных папак або некалькіх камер. Добры ўзор прадухіляе выпадковую замену і забяспечвае прасочванне вынікаў да іх крыніц. + Уключыце арыгінальную назву і суфікс, калі адрэдагаваную копію трэба пазнаць. + Дадайце паслядоўнасць, метку часу, прадусталяваныя налады або памеры пры апрацоўцы вялікіх змешаных партый. + Пазбягайце перазапісу працоўных працэсаў, пакуль вы не пераканаецеся, што шаблон наймення не можа сутыкнуцца. + Захавайце або падзяліцеся + Выбірайце паміж пастаянным файлам і часовай перадачай у іншую праграму + Экспарт з правільным намерам + Захаваць і падзяліцца можа выглядаць падобна, але яны вырашаюць розныя праблемы. Пры захаванні ствараецца файл, які вы зможаце знайсці пазней; сумеснае выкарыстанне адпраўляе згенераваны вынік праз Android у іншую праграму і можа не пакінуць трывалай лакальнай копіі. + Выкарыстоўвайце \"Захаваць\", калі вынік павінен застацца ў вядомай папцы, стварыць рэзервовую копію або паўторна выкарыстоўваць пазней. + Выкарыстоўвайце Share для хуткіх паведамленняў, укладанняў электроннай пошты, публікацый у сацыяльных сетках або адпраўкі выніку іншаму рэдактару. + Спачатку захавайце, калі прымаючая праграма ненадзейная або калі няўдалае абагульванне будзе раздражняць паўторнае стварэнне. + Памер старонкі PDF і палі + Перад стварэннем дакумента выберыце памер паперы, паводзіны і месца для дыхання + Зрабіце старонкі з малюнкамі зручнымі для друку і чытання + Выявы могуць стаць нязручнымі старонкамі PDF, калі іх форма не адпавядае абранаму памеру паперы. Палі, рэжым падганяння і арыентацыя старонкі вызначаюць, ці змесціва будзе абрэзаным, малюсенькім, расцягнутым ці зручным для чытання. + Выкарыстоўвайце рэальны памер паперы, калі PDF можна раздрукаваць або адправіць у форму. + Выкарыстоўвайце палі для сканавання і скрыншотаў, каб тэкст не прылягаў да краю старонкі. + Папярэдні прагляд партрэтнай і альбомнай старонак разам, калі зыходныя выявы маюць змешаную арыентацыю. + Ачысціць сканы + Палепшыце краю паперы, цені, кантраснасць, паварот і чытальнасць перад PDF або OCR + Перад архіваваннем падрыхтуйце старонкі + Сканаванне можа быць тэхнічна зроблена, але ўсё яшчэ цяжка прачытаць. Невялікія этапы ачысткі перад экспартам PDF часта маюць большае значэнне, чым налады сціску, асабліва для квітанцый, рукапісных нататак і старонак са слабым асвятленнем. + Выпраўце перспектыву і абрэжце краю стала, пальцы, цені і беспарадак на фоне. + Асцярожна павялічвайце кантраснасць, каб тэкст стаў больш выразным, не знішчаючы пячаткі, подпісы або сляды алоўкам. + Запускайце распазнаванне толькі пасля таго, як старонка стане вертыкальнай і даступнай для чытання, а затым праверце важныя лічбы ўручную. + Сціскайце, адценні шэрага або аднаўляйце PDF-файлы + Выкарыстоўвайце аперацыі PDF з адным файлам для палегчаных копій дакументаў, якія можна раздрукаваць або перабудаваць + Выберыце аперацыю ачысткі PDF + PDF Tools уключае асобныя аперацыі для сціску, пераўтварэння адценняў шэрага і аднаўлення. Сцісканне і адценні шэрага працуюць у асноўным праз убудаваныя выявы, у той час як рамонт перабудоўвае структуру PDF; ні адзін з іх не павінен разглядацца як гарантаванае выпраўленне для кожнага дакумента. + Выкарыстоўвайце Compress PDF, калі дакумент змяшчае выявы і памер файла мае значэнне для абмену. + Выкарыстоўвайце адценні шэрага, калі дакумент павінен быць прасцей для друку або не патрабуе каляровых малюнкаў. + Выкарыстоўвайце Repair PDF на копіі, калі дакумент не адкрываецца або мае пашкоджаную ўнутраную структуру, затым праверце адноўлены файл. + Выманне малюнкаў з файлаў PDF + Аднаўляйце ўбудаваныя выявы PDF і спакуйце вынятыя вынікі ў архіў + Захаваць зыходныя выявы з дакументаў + Extract Images скануе PDF на наяўнасць убудаваных аб\'ектаў выявы, прапускае дублікаты, дзе гэта магчыма, і захоўвае адноўленыя выявы ў створаным ZIP-архіве. Ён здабывае толькі выявы, якія сапраўды ўбудаваны ў PDF, а не тэкст, вектарныя малюнкі або кожную бачную старонку ў выглядзе скрыншота. + Выкарыстоўвайце Extract Images, калі вам патрэбныя малюнкі, якія захоўваюцца ў PDF, напрыклад, фатаграфіі з каталога або адсканаваныя актывы. + Калі вам патрэбныя поўныя старонкі, уключаючы тэкст і макет, замест гэтага выкарыстоўвайце PDF у выявы або выманне старонкі. + Калі інструмент паведамляе аб адсутнасці ўбудаваных відарысаў, магчыма, старонка ўтрымлівае тэкставае/вектарнае змесціва або выявы не захоўваюцца як аб\'екты, якія можна выняць. + Метададзеныя, звядзенне і друк + Рэдагуйце ўласцівасці дакумента, растэрызуйце старонкі або наўмысна падрыхтуйце ўласныя макеты друку + Выберыце дзеянне канчатковага дакумента + Метададзеныя PDF, звядзенне і падрыхтоўка да друку вырашаюць розныя праблемы канчатковай стадыі. Метададзеныя кантралююць уласцівасці дакумента, Flatten PDF растэрызуе старонкі ў менш прыдатныя для рэдагавання вынікі, а Print PDF рыхтуе новы макет з элементамі кіравання памерам старонкі, арыентацыяй, палямі і колькасцю старонак на аркушы. + Выкарыстоўвайце метаданыя, калі важныя аўтар, назва, ключавыя словы, вытворца або ачыстка прыватнасці. + Выкарыстоўвайце Flatten PDF, калі бачнае змесціва старонкі стане цяжэй рэдагаваць як асобныя аб\'екты PDF. + Выкарыстоўвайце Print PDF, калі вам патрэбны нестандартны памер старонкі, арыентацыя, палі або некалькі старонак на аркушы. + Падрыхтуйце выявы для OCR + Выкарыстоўвайце абрэзку, паварот, кантраснасць, выдаленне шумоў і маштаб, перш чым запытваць OCR для чытання тэксту + Дайце больш чыстыя пікселі OCR + Памылкі OCR часта зыходзяць ад уваходнага малюнка, а не ад механізму OCR. Крывыя старонкі, нізкая кантраснасць, размытасць, цені, дробны тэкст і змешаны фон - усё гэта зніжае якасць распазнання. + Абрэжце тэкставую вобласць і павярніце выяву так, каб лініі былі гарызантальнымі перад распазнаваннем. + Павялічвайце кантраснасць або пераўтварайце ў больш чысты чорна-белы толькі тады, калі гэта палягчае чытанне літар. + Умерана змяняйце памер дробнага тэксту ўверх, але пазбягайце агрэсіўнага завастрэння, якое стварае фальшывыя краю. + Праверце QR-кантэнт + Праверце спасылкі, уліковыя даныя Wi-Fi, кантакты і дзеянні, перш чым давяраць коду + Разглядаць дэкадзіраваны тэкст як ненадзейны ўвод + QR-код можа схаваць URL, картку кантакту, пароль Wi-Fi, падзею календара, нумар тэлефона або звычайны тэкст. Бачная пазнака побач з кодам можа не адпавядаць рэальнай карыснай нагрузцы, таму праглядзіце дэкадзіраваны кантэнт, перш чым дзейнічаць з ім. + Праверце поўны дэкадзіраваны URL, перш чым адкрыць яго, асабліва скарочаныя або незнаёмыя дамены. + Будзьце асцярожныя з кодамі Wi-Fi, кантактаў, календара, тэлефона і SMS, таму што яны могуць выклікаць канфідэнцыйныя дзеянні. + Спачатку скапіруйце змесціва, калі вам трэба праверыць яго ў іншым месцы, а не адкрывайце адразу. + Стварэнне QR-кодаў + Стварайце коды з звычайнага тэксту, URL-адрасоў, Wi-Fi, кантактаў, электроннай пошты, тэлефона, SMS і дадзеных аб месцазнаходжанні + Стварыце правільную карысную нагрузку QR + Экран QR можа генераваць коды з уведзенага кантэнту, а таксама сканаваць існуючы. Структураваныя тыпы QR дапамагаюць кадзіраваць даныя ў фармаце, зразумелым іншым праграмам, напрыклад дадзеныя для ўваходу ў Wi-Fi, кантактныя карткі, палі электроннай пошты, нумары тэлефонаў, змесціва SMS, URL-адрасы або геаграфічныя каардынаты. + Выбірайце звычайны тэкст для простых нататак або выкарыстоўвайце структураваны тып, калі код павінен адкрывацца як Wi-Fi, кантакт, URL, электронная пошта, тэлефон, SMS або дадзеныя аб месцазнаходжанні. + Праверце кожнае поле, перш чым абагульваць згенераваны код, таму што бачны папярэдні прагляд адлюстроўвае толькі карысную нагрузку, якую вы ўвялі. + Праверце захаваны QR-код з дапамогай сканера, калі ён будзе раздрукаваны, публічна размешчаны або выкарыстаны іншымі людзьмі. + Выберыце рэжым кантрольнай сумы + Вылічвайце хэшы з файлаў або тэксту, параўноўвайце адзін файл або правярайце мноства файлаў + Правярайце змест, а не знешні выгляд + Інструменты кантрольнай сумы маюць асобныя ўкладкі для хэшавання файлаў, хэшавання тэксту, параўнання файла з вядомым хэшам і пакетнага параўнання. Кантрольная сума даказвае пабайтавую ідэнтычнасць абранага алгарытму; гэта не даказвае, што дзве выявы проста выглядаюць аднолькава. + Выкарыстоўвайце Calculate для файлаў і Text Hash для ўведзеных або ўстаўленых тэкставых даных. + Выкарыстоўвайце Compare, калі нехта дае вам чаканую кантрольную суму для аднаго файла. + Выкарыстоўвайце Batch Compare, калі шмат файлаў патрабуюць хэшаў або праверкі за адзін праход, а затым падтрымлівайце выбраны алгарытм паслядоўным. + Канфідэнцыяльнасць буфера абмену + Выкарыстоўвайце аўтаматычныя функцыі буфера абмену без выпадковага раскрыцця асабістых скапіраваных даных + Захоўвайце скапіраваны кантэнт наўмысна + Аўтаматызацыя буфера абмену зручная, але скапіраваны тэкст можа ўтрымліваць паролі, спасылкі, адрасы, токены або прыватныя нататкі. Разглядайце ўвод з буфера абмену як функцыю хуткасці, якая павінна адпавядаць вашым звычкам і чаканням прыватнасці. + Адключыць аўтаматычнае выкарыстанне буфера абмену, калі прыватны тэкст нечакана з\'яўляецца ў інструментах. + Выкарыстоўвайце захаванне толькі ў буфер абмену, толькі калі вы наўмысна хочаце, каб вынік не захоўваўся. + Ачысціце або заменіце буфер абмену пасля апрацоўкі канфідэнцыяльнага тэксту, даных QR або карысных нагрузак Base64. + Каляровыя фарматы + Азнаёмцеся са значэннямі колеру HEX, RGB, HSV, альфа і скапіраваных колераў перад устаўкай у іншае месца + Скапіруйце фармат, які чакае мэта + Адзін і той жа бачны колер можна запісаць некалькімі спосабамі. Інструмент дызайну, поле CSS, значэнне колеру Android або рэдактар ​​малюнкаў могуць чакаць іншага парадку, апрацоўкі альфа-версіі або каляровай мадэлі. + Выкарыстоўвайце HEX пры ўстаўцы ў палі Інтэрнэту, дызайну або тэмы, якія патрабуюць кампактных каляровых кодаў. + Выкарыстоўвайце RGB або HSV, калі вам трэба наладзіць каналы, яркасць, насычанасць або адценне ўручную. + Праверце альфа-версію асобна, калі празрыстасць мае значэнне, таму што некаторыя напрамкі яе ігнаруюць або выкарыстоўваюць іншы парадак. + Праглядзіце бібліятэку колераў + Шукайце названыя колеры па назве або HEX і захоўвайце любімыя ўверсе + Знайдзіце шматразовыя названыя колеры + Бібліятэка колераў загружае названую калекцыю колераў, сартуе яе па назве, фільтруе па назве колеру або шаснаццатковым значэнні і захоўвае любімыя колеры, каб важныя запісы заставаліся лягчэй даступнымі. Гэта карысна, калі вам патрэбен сапраўдны названы колер замест выбаркі з выявы. + Шукайце па назве колеру, калі вы ведаеце сямейства, напрыклад, чырвоны, сіні, шыферны або мяты. + Шукайце па HEX, калі ў вас ужо ёсць лікавы колер і вы хочаце знайсці адпаведныя або бліжэйшыя запісы з назвай. + Пазначайце часта сустракаемыя колеры як абраныя, каб яны апярэджвалі агульную бібліятэку падчас прагляду. + Выкарыстоўвайце сеткаватыя градыентныя калекцыі + Спампуйце даступныя рэсурсы градыенту сеткі і падзяліцеся выбранымі выявамі + Выкарыстанне аддаленых элементаў градыенту сеткі + Mesh Gradients можа загружаць аддаленую калекцыю рэсурсаў, спампоўваць адсутныя выявы градыентаў, паказваць ход загрузкі і абагульваць выбраныя градыенты. Даступнасць залежыць ад доступу да сеткі і аддаленага сховішча рэсурсаў, таму пусты спіс звычайна азначае, што рэсурсы яшчэ не былі даступныя. + Адкрыйце сеткаватыя градыенты з інструментаў градыенту, калі вам патрэбныя гатовыя сеткаватыя градыентныя выявы. + Дачакайцеся загрузкі рэсурсу або прагрэсу загрузкі, перш чым лічыць, што калекцыя пустая. + Выберыце і падзяліцеся неабходнымі градыентамі або вярніцеся да Gradient Maker, калі хочаце стварыць уласны градыент уручную. + Створаны дазвол актываў + Выберыце памер вываду, перш чым ствараць градыенты, шум, шпалеры, SVG-падобны малюнак або тэкстуры + Стварыце патрэбны вам памер + Створаныя выявы не маюць арыгінала камеры, на які можна было б вярнуцца. Калі вынік занадта малы, пазнейшае маштабаванне можа змякчыць узоры, зрушыць дэталі або змяніць тое, як аб\'ект аб\'ядноўваецца і сціскаецца. + Спачатку ўсталюйце памеры для канчатковага выпадку выкарыстання, такія як шпалеры, значок, фон, друк або накладанне. + Выкарыстоўвайце большыя памеры для тэкстур, якія можна абрэзаць, павялічыць або паўторна выкарыстоўваць у некалькіх макетах. + Экспартуйце тэставыя версіі перад велізарнымі вынікамі, таму што дэталі працэдуры могуць змяніць паводзіны сціску. + Экспарт бягучых шпалер + Атрымаць даступныя шпалеры Home, Lock і ўбудаваныя шпалеры з прылады + Захавайце або падзяліцеся ўсталяванымі шпалерамі + Шпалеры Экспарт счытвае шпалеры Android праз сістэмныя API шпалер. У залежнасці ад прылады, версіі Android, дазволаў і варыянту зборкі шпалеры Home, Lock або ўбудаваныя шпалеры могуць быць даступныя асобна або адсутнічаць. + Адкрыйце Экспарт шпалер, каб загрузіць шпалеры, якія сістэма дазваляе праграме чытаць. + Выберыце даступныя запісы Home, Lock або ўбудаваныя шпалеры, якія вы хочаце захаваць, скапіяваць або абагуліць. + Калі шпалеры адсутнічаюць або функцыя недаступная, гэта звычайна абмежаванне сістэмы, дазволу або варыянту зборкі, а не налада рэдагавання. + Куты Анімацыя Дросель + Капіраваць колер як + HEX + імя + Мадэль партрэтнага цыноўкі для хуткага стрыжкі асобы з больш мяккімі валасамі і апрацоўкай краёў. + Хуткае паляпшэнне пры слабым асвятленні з дапамогай ацэнкі крывой Zero-DCE++. + Мадэль аднаўлення выявы Restormer для памяншэння палос ад дажджу і артэфактаў вільготнага надвор\'я. + Мадэль дэфармацыі дакумента, якая прагназуе каардынатную сетку і пераназначае выгнутыя старонкі ў больш плоскае сканаванне. + Шкала працягласці руху + Кантралюе хуткасць анімацыі праграмы: 0 адключае рух, 1 - нармальна, больш высокія значэнні запавольваюць анімацыю + Паказаць нядаўнія інструменты + Адлюстраванне хуткага доступу да нядаўна выкарыстаных інструментаў на галоўным экране + Статыстыка выкарыстання + Даследуйце адкрыцці прыкладанняў, запускі інструментаў і найбольш часта выкарыстоўваюцца інструменты + Адкрываецца праграма + Інструмент адкрываецца + Апошні інструмент + Паспяховыя захаванні + Паласа актыўнасці + Дадзеныя захаваны + Топ фармат + Інструменты, якія выкарыстоўваюцца + %1$d адкрываецца • %2$s + Статыстыка выкарыстання пакуль адсутнічае + Адкрыйце некалькі інструментаў, і яны з\'явяцца тут аўтаматычна + Ваша статыстыка выкарыстання захоўваецца толькі лакальна на вашай прыладзе. ImageToolbox выкарыстоўвае іх толькі для таго, каб паказаць вашу асабістую дзейнасць, любімыя інструменты і захаваныя даныя + рэдактар, рэдагаванне фатаграфій, рэтуш малюнка, наладка + змяніць памер канвертаваць маштаб дазвол памеры шырыня вышыня jpg jpeg png webp сціснуць + сціснуць памер вага байт мб кб паменшыць якасць аптымізаваць + абрэзаць абрэзаць суадносіны бакоў + эфект фільтра карэкцыя колеру наладзіць маску lut + маляваць пэндзаль аловак анатаваць разметку + шыфр шыфраваць расшыфраваць сакрэтны пароль + выдаленне фону сцерці выдаліць выраз празрысты альфа + папярэдні прагляд прагляду галерэя агляд адкрыты + шывок зліццё камбінаванне панарама доўгі скрыншот + URL-адрас вэб-спасылкі для спампоўкі ў Інтэрнэце + выбар піпеткі ўзор шаснаццатковага колеру rgb + палітра колераў узоры экстракт дамінанта + exif metadata privacy выдаліць палоску ачысціць месцазнаходжанне GPS + параўнаць розніцу да пасля + ліміт змены памеру макс. мін. мегапікселяў памеры заціск + Інструменты старонак дакументаў pdf + OCR распазнаванне тэксту сканіраванне экстракта + сетка градыентнага колеру фону + вадзяны знак лагатып тэкст штампа накладанне + gif анімацыя аніміраваныя канвертаваць кадры + apng анімацыя аніміраваныя png канвертаваць кадры + архіў zip, сціснуць, распакаваць файлы + jxl jpeg xl канвертаваць фармат выявы jpg + svg вектарная трасіроўка канвертуе xml + фармат канвертаваць jpg jpeg png webp heic avif jxl bmp + сканер дакументаў сканіраванне паперы перспектыўнае кадраванне + сканер штрых-кода qr + накладанне сярэдняга сярэдняга астрафатаграфіі + падзеленыя пліткі сетка зрэз частак + пераўтварэнне колеру hex rgb hsl hsv cmyk лаб + webp канвертаваць фармат анімаваных малюнкаў + генератар шуму выпадковая зярністасць тэкстуры + калаж сеткі макета аб\'яднаць + слаі разметкі анатаваць накладанне рэдагаваць + base64 кадзіраваць дэкадаваць даныя URI + кантрольная сума хэш md5 sha sha1 sha256 праверыць + сеткаваты градыентны фон + рэдагаванне метаданых exif gps дата камеры + разрэзаць пліткі з раздзеленай сеткі + вокладка аўдыё альбом вокладка музыка метаданыя + Фон для экспарту шпалер + Тэкставыя сімвалы ASCII + ai ml нейронавага павышэння высокакласнага сегмента + палітра матэрыялаў бібліятэкі колераў з назвамі колераў + Студыя эфектаў фрагментаў шэйдэраў glsl + pdf аб\'яднаць аб\'яднаць аб\'яднаць + Pdf разбіць на асобныя старонкі + pdf з паваротам старонак + pdf змяніць парадак, сартаваць старонкі + Нумары старонак у pdf + pdf ocr тэкст распазнае экстракт з магчымасцю пошуку + Тэкст лагатыпа вадзянога знака pdf + Малюнак подпісы pdf + Шыфраваць блакаванне pdf з паролем + Расшыфраваць пароль для разблакоўкі pdf, зняць абарону + pdf сціснуць паменшыць памер аптымізаваць + pdf адценні шэрага чорна белы манахромны + Выпраўленне pdf, аднаўленне зламанага + Уласцівасці метададзеных pdf exif назва аўтара + Выдаленне выдалення старонак у pdf + Абрэзка палёў pdf + pdf згладзіць анатацыі ўтварае пласты + Выманне малюнкаў у pdf + Сціск архіва pdf zip + Прынтэр для друку pdf + Праграма прагляду pdf для чытання + выявы ў pdf канвертаваць jpg jpeg png дакумент + Экспарт старонак pdf ў выявы jpg png + pdf анатацыі каментарыі выдаліць чыстыя + Нейтральны варыянт + Памылка + Цень + Вышыня зуба + Дыяпазон гарызантальных зубоў + Вертыкальны дыяпазон зубоў + Верхні край + Правы край + Ніжні край + Левы край + Ірваны край + Скінуць статыстыку выкарыстання + Адкрыццё інструмента, статыстыка захавання, серыя, захаваныя даныя і верхні фармат будуць скінуты. Адкрыцці праграмы застануцца без змен. + Аўдыё + Файл + Экспарт профіляў + Захаваць бягучы профіль + Выдаліць профіль экспарту + Вы хочаце выдаліць профіль экспарту \\"%1$s\\"? + Малюнак мяжы + Пакажыце тонкую рамку вакол малюнка і палатна для сцірання + Хвалісты + Закругленыя элементы інтэрфейсу з мяккім хвалістым краем + Савок + Выемка + Скругленыя куты, выразаныя ўнутр + Ступеністыя куткі з падвойным зрэзам + Запаўняльнік + Выкарыстоўвайце {filename}, каб уставіць зыходнае імя файла без пашырэння + Успышка + Базавая велічыня + Сума кальца + Рэй сума + Шырыня кольца + Скажаць перспектыву + Выгляд і адчуванне Java + Зрух + Вугал X + і кут + Змяніць памер вываду + Кропля вады + Даўжыня хвалі + Фаза + Высокі перавал + Каляровая маска + Chrome + Растварыць + Мяккасць + Зваротная сувязь + Размыццё + Нізкі ўваход + Высокі ўваход + Нізкі выхад + Высокі выхад + Светлавыя эфекты + Вышыня гузы + Мяккасць удараў + Пульсацыя + Амплітуда Х + Амплітуда Y + Х даўжыня хвалі + Даўжыня хвалі Y + Адаптыўнае размыццё + Генерацыя тэкстур + Стварайце розныя працэдурныя тэкстуры і захоўвайце іх як выявы + Тып тэкстуры + прадузятасць + Час + Бляск + Дысперсія + Узоры + Вуглавы каэфіцыент + Каэфіцыент градыенту + Тып сеткі + Магутнасць на адлегласці + Маштаб Y + Невыразнасць + Тып асновы + Фактар ​​турбулентнасці + Маштабаванне + Ітэрацыі + Кольцы + Матавы метал + З\'едлівыя рэчывы + Сотавы + Шашкавая дошка + fBm + Мармуровы + плазма + Коўдра + Вуд + выпадковы + квадратны + Шасцігранныя + Васьмігранныя + Трохкутныя + Грабяныя + В. Л. Шум + SC Noise + Апошнія + Нядаўна выкарыстаных фільтраў пакуль няма + тэкстура генератар шліфаваны метал каўстык ячэістая шахматная дошка мармур плазма коўдра дрэва цэгла камуфляж ячэйка воблака расколіна тканіна лістота соты лёд лава туманнасць папера іржа пясок дым камень рэльеф мясцовасці рабізна + Цагла + Камуфляж + Сотавы + Воблака + Крэк + Тканіна + Лістота + Сотавы мёд + лёд + Лава + Туманнасць + папера + Іржа + Пясок + Дым + Камень + Рэльеф мясцовасці + Тапаграфія + Вадзяная рабізна + Пашыраны Вуд + Шырыня раствора + Нерэгулярнасць + Фаска + Шурпатасць + Першы парог + Другі парог + Трэці парог + Мяккасць краю + Шырыня мяжы + Пакрыццё + Дэталь + Глыбіня + Разгалінаванне + Гарызантальныя ніткі + Вертыкальныя ніткі + Фуз + Вены + Асвятленне + Шырыня расколіны + Мароз + Плынь + Скарынка + Шчыльнасць воблака + Зоркі + Шчыльнасць валакна + Трываласць валакна + Плямы + Карозія + Піттинг + Шматкі + Частата выдмы + Кут ветру + рабізна + Агонькі + Жылка луска + Вадзяны ўзровень + Горны ўзровень + Эрозія + Узровень снегу + Колькасць радкоў + Таўшчыня лініі + Зацяненне + Колер пор + Ступкавы колер + Цёмна-цагляны колер + Колер цэглы + Колер выдзялення + Цёмны колер + Лясны колер + Зямны колер + Пясочны колер + Колер фону + Клеткавы колер + Колер краю + Нябесны колер + Колер цені + Светлы колер + Колер паверхні + Варыяцыя колеру + Колер расколін + Колер дэфармацыі + Колер качка + Цёмны колер лісця + Колер лісця + Колер мяжы + Мядовы колер + Глыбокі колер + Колер лёду + Марозны колер + Колер кары + Мыйны колер + Колер святлення + Касмічны колер + Фіялетавы колер + Сіні колер + Базавы колер + Колер валакна + Колер плямы + Колер металу + Колер цёмнай іржы + Колер іржы + Аранжавы колер + Колер дыму + Колер жылак + Нізінны колер + Акварэль + Каляровы колер + Колер снегу + Нізкі колер + Высокі колер + Колер лініі + Дробны колер + Трава + Бруд + Скура + Бетонныя + Асфальт + Мох + Агонь + Аўрора + Нафтавая пляма + Акварэль + Абстрактная плынь + Апал + Дамаская сталь + Маланка + Аксаміт + Мармуровасць чарнілаў + Галаграфічная фольга + Біялюмінесцэнцыя + Касмічны вір + Лававая лямпа + Гарызонт падзей + Фрактал Блюм + Храматычны тунэль + Зацьменне Карона + Дзіўны аттрактар + Феравадкасная карона + Звышновая + касач + Пяро паўліна + Ракавіна Наўтылуса + Кольцавая планета + Шчыльнасць клінка + Даўжыня клінка + Вецер + Няроўнасць + Згусткі + Вільгаць + Галька + Маршчыны + Поры + Агрэгатны + Расколіны + дзёгаць + Насіць + Крапінкі + Валакна + Частата полымя + Дым + Інтэнсіўнасць + Стужкі + Гурты + Пералівы + Цемра + Квітнее + Пігмент + Краі + папера + Дыфузія + Сіметрыя + Рэзкасць + Каляровая гульня + Малочнасць + Пласты + Складаныя + польская + Галіны + Напрамак + Шын + Складкі + Апярэнне + Баланс чарнілаў + Спектр + Зморшчыны + Дыфракцыя + Зброя + Твіст + Ядро свячэнне + Кропкі + Нахіл дыска + Памер гарызонту + Шырыня дыска + Лінзаванне + Пялёсткі + Завітак + Скаль + Грані + Скрыўленне + Памер месяца + Памер кароны + Прамяні + Кольца з дыяментам + Долі + Шчыльнасць арбіты + Таўшчыня + Шыпы + Даўжыня шыпа + Памер цела + Металік + Ударны радыус + Шырыня ракавіны + Выкінулі + Памер зрэнкі + Памер вясёлкі + Колеравая варыяцыя + Пражэктар + Памер вачэй + Шчыльнасць бародкі + Павароты + Палаты + Адкрыццё + Грады + Перламутр + Памер планеты + Нахіл кольца + Шырыня кольца + Атмасфера + Колер бруду + Цёмна-травяны колер + Колер травы + Колер наканечніка + Цёмна-земляны колер + Сухі колер + Галечны колер + Колер скуры + Колер бетону + Колер дзёгцю + Колер асфальту + Каменны колер + Колер пылу + Колер глебы + Колер цёмнага моху + Колер моху + Чырвоны колер + Асноўны колер + Зялёны колер + Сіні колер + Пурпурны колер + Залаты колер + Колер паперы + Колер пігмента + Другасны колер + Першы колер + Другі колер + Ружовы колер + Цёмна-сталёвы колер + Колер сталі + Колер светлай сталі + Колер аксіду + Колер гало + Колер ніта + Аксамітны колер + Бляск колеру + Сіні колер чарнілаў + Чырвоны колер чарнілаў + Цёмны колер чарнілаў + Сярэбраны колер + Жоўты колер + Колер тканіны + Колер дыска + Гарачы колер + Колер лінзы + Знешні колер + Унутраны колер + Каронны колер + Халодны колер + Цёплы колер + Воблачны колер + Колер полымя + Колер пяра + Колер шкарлупіны + Жамчужны колер + Колер планеты + Колер кольца + Выдаліце ​​зыходныя файлы пасля захавання + Будуць выдалены толькі паспяхова апрацаваныя зыходныя файлы + Арыгінальныя файлы выдалены: %1$d + Не ўдалося выдаліць зыходныя файлы: %1$d + Арыгінальныя файлы выдалены: %1$d, не ўдалося: %2$d + Жэсты ліста + Дазволіць перацягванне разгорнутых аркушаў параметраў + Падвыбарка каляровасці + Разрадная глыбіня + Без страт + %1$d-біт + Пакетнае перайменаванне + Перайменаваць некалькі файлаў, выкарыстоўваючы шаблоны імёнаў файлаў + пакетнае перайменаванне файлаў імя файла шаблон паслядоўнасць пашырэнне даты + Выберыце файлы для перайменавання + Шаблон імя файла + Перайменаваць + Крыніца даты + Дата ўзяцця EXIF + Дата змены файла + Дата стварэння файла + Бягучая дата + Ручная дата + Ручная дата + Час уручную + Выбраная дата недаступная для некаторых файлаў. Для іх будзе выкарыстоўвацца бягучая дата. + Увядзіце шаблон імя файла + Шаблон стварае несапраўднае або занадта доўгае імя файла + Шаблон стварае дублікаты файлаў + Усе імёны файлаў ужо адпавядаюць шаблону + У гэтым шаблоне выкарыстоўваюцца токены, недаступныя для пакетнага перайменавання + Імя застанецца ранейшым + Файлы паспяхова перайменаваны + Не ўдалося перайменаваць некаторыя файлы + Дазволу не далі + Выкарыстоўвае зыходнае імя файла без пашырэння, што дапамагае захаваць ідэнтыфікацыю крыніцы. Таксама падтрымлівае нарэзку з \\o{start:end}, транслітарацыю з \\o{t} і замену на \\o{s/old/new/}. + Лічыльнік з аўтаматычным павелічэннем. Падтрымлівае індывідуальнае фарматаванне: \\c{padding} (напрыклад, \\c{3} -&gt; 001), \\c{start:step} або \\c{start:step:padding}. + Назва бацькоўскай папкі, у якой знаходзіўся зыходны файл. + Памер зыходнага файла. Падтрымлівае адзінкі: \\z{b}, \\z{kb}, \\z{mb} або проста \\z для фармату, які чытаецца чалавекам. + Стварае выпадковы універсальны ўнікальны ідэнтыфікатар (UUID), каб забяспечыць унікальнасць імя файла. + Перайменаванне файлаў не можа быць адменена. Перш чым працягнуць, пераканайцеся, што новыя імёны правільныя. + Пацвердзіце перайменаванне + Налады бакавога меню + Шкала меню + Альфа меню + Аўтаматычны баланс белага + Выразанне + Праверка схаваных вадзяных знакаў + Захоўванне + Ліміт кэша + Аўтаматычна ачысціць кэш, калі ён перавышае гэты памер + Інтэрвал ачысткі + Як часта правяраць, ці трэба ачышчаць кэш + Пры запуску праграмы + 1 дзень + 1 тыдзень + 1 месяц + Аўтаматычна ачысціць кэш праграмы ў адпаведнасці з абраным лімітам і інтэрвалам + Рухомая зона ўраджаю + Дазваляе перамяшчаць усю вобласць кадравання шляхам перацягвання ўнутр яе + Аб\'яднаць GIF + Аб\'яднайце некалькі GIF-кліпаў у адну анімацыю + Заказ кліпаў GIF + Рэверс + Гуляць у зваротным парадку + Бумеранг + Гуляць наперад, а затым назад + Нармалізаваць памер кадра + Маштаб кадраў, каб адпавядаць найбольшаму кліпу; выключыць, каб захаваць іх зыходны маштаб + Затрымка паміж кліпамі (мс) + Папка + Выберыце ўсе выявы з папкі і яе падтэчак + Пошук дублікатаў + Знайдзіце дакладныя копіі і візуальна падобныя выявы + пошук дублікатаў падобныя выявы дакладныя копіі фатаграфій ачыстка сховішча dHash SHA-256 + Выберыце выявы, каб знайсці дублікаты + Адчувальнасць да падабенства + Дакладныя копіі + Падобныя выявы + Выберыце ўсе дакладныя дублікаты + Выберыце ўсе, акрамя рэкамендаваных + Дублікаты не знойдзены + Паспрабуйце дадаць больш малюнкаў або павялічыць адчувальнасць да падабенства + Немагчыма прачытаць %1$d відарыс(аў) + %1$s • %2$s можна аднавіць + %1$d груп • %2$s можна вярнуць + %1$d выбрана • %2$s + Гэта нельга адмяніць. Android можа папрасіць вас пацвердзіць выдаленне ў сістэмным дыялогавым акне. + Не знойдзены + Рухайцеся, каб пачаць + Перайсці да канца + \ No newline at end of file diff --git a/core/resources/src/main/res/values-bn/strings.xml b/core/resources/src/main/res/values-bn/strings.xml new file mode 100644 index 0000000..7e5634c --- /dev/null +++ b/core/resources/src/main/res/values-bn/strings.xml @@ -0,0 +1,3739 @@ + + + + লোড হচ্ছে… + ছবিটি প্রিভিউ করার জন্য খুব বড়, তবে সেভ করার চেষ্টা করা হবে। + শুরু করার জন্য ছবি বেছে নিন + প্রস্থ %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 সংরক্ষণ করুন + লেয়ার আলফা + অনুভূমিক উল্টানো + উল্লম্ব উল্টানো + তালা + ছায়া যোগ করুন + ছায়া রঙ + পাঠ্য জ্যামিতি + তীক্ষ্ণ স্টাইলাইজেশনের জন্য টেক্সট প্রসারিত বা তির্যক করুন + স্কেল এক্স + স্কু এক্স + টীকাগুলি সরান৷ + পিডিএফ পৃষ্ঠাগুলি থেকে লিঙ্ক, মন্তব্য, হাইলাইট, আকার বা ফর্ম ক্ষেত্রগুলির মতো নির্বাচিত টীকা প্রকারগুলি সরান + হাইপারলিঙ্ক + ফাইল সংযুক্তি + লাইন + পপআপ + স্ট্যাম্প + আকৃতি + টেক্সট নোট + টেক্সট মার্কআপ + ফর্ম ক্ষেত্র + মার্কআপ + অজানা + টীকা + আনগ্রুপ করুন + কনফিগারযোগ্য রঙ এবং অফসেট সহ স্তরের পিছনে অস্পষ্ট ছায়া যোগ করুন + বিষয়বস্তু সচেতন বিকৃতি + এই ক্রিয়াটি সম্পূর্ণ করার জন্য যথেষ্ট মেমরি নেই৷ অনুগ্রহ করে একটি ছোট ছবি ব্যবহার করার চেষ্টা করুন, অন্যান্য অ্যাপ বন্ধ করে বা অ্যাপটি পুনরায় চালু করার চেষ্টা করুন। + সমান্তরাল শ্রমিক + এই ছবিগুলি সংরক্ষণ করা হয়নি কারণ রূপান্তরিত ফাইলগুলি আসলগুলির চেয়ে বড় হবে৷ মূল ছবি মুছে ফেলার আগে এই তালিকা চেক করুন. + ফাইলগুলি বাদ দেওয়া হয়েছে: %1$s + অ্যাপ লগ + সমস্যাগুলি চিহ্নিত করতে অ্যাপের লগগুলি দেখুন৷ + গ্রুপ করা টুলে ফেভারিট + যখন টুল টাইপ দ্বারা গোষ্ঠীবদ্ধ হয় তখন একটি ট্যাব হিসাবে পছন্দসই যোগ করে + শেষ হিসাবে প্রিয় দেখান + প্রিয় টুল ট্যাবটি শেষ পর্যন্ত সরান + অনুভূমিক ব্যবধান + উল্লম্ব ব্যবধান + পশ্চাৎপদ শক্তি + ফরোয়ার্ড এনার্জি অ্যালগরিদমের পরিবর্তে সাধারণ গ্রেডিয়েন্ট ম্যাগনিটিউড এনার্জি ম্যাপ ব্যবহার করুন + মুখোশযুক্ত এলাকা সরান + Seams মুখোশযুক্ত অঞ্চলের মধ্য দিয়ে যাবে, এটি রক্ষা করার পরিবর্তে শুধুমাত্র নির্বাচিত এলাকাটি সরিয়ে দেবে + ভিএইচএস এনটিএসসি + উন্নত NTSC সেটিংস + শক্তিশালী ভিএইচএস এবং সম্প্রচারিত শিল্পকর্মের জন্য অতিরিক্ত অ্যানালগ টিউনিং + ক্রোমা রক্তপাত + টেপ পরিধান + ট্র্যাকিং + লুমা স্মিয়ার + রিং হচ্ছে + তুষার + ক্ষেত্র ব্যবহার করুন + ফিল্টার প্রকার + ইনপুট লুমা ফিল্টার + ইনপুট ক্রোমা লোপাস + ক্রোমা ডিমোডুলেশন + ফেজ শিফট + ফেজ অফসেট + হেড সুইচিং + মাথা সুইচিং উচ্চতা + হেড সুইচিং অফসেট + হেড সুইচিং শিফট + মাঝামাঝি অবস্থান + মিড-লাইন জিটার + ট্র্যাকিং শব্দ উচ্চতা + ট্র্যাকিং তরঙ্গ + তুষার ট্র্যাকিং + তুষার অ্যানিসোট্রপি ট্র্যাকিং + ট্র্যাকিং গোলমাল + ট্র্যাকিং শব্দের তীব্রতা + যৌগিক শব্দ + যৌগিক শব্দ ফ্রিকোয়েন্সি + যৌগিক শব্দের তীব্রতা + যৌগিক গোলমাল বিস্তারিত + রিং ফ্রিকোয়েন্সি + রিং পাওয়ার + লুমার আওয়াজ + লুমা শব্দ ফ্রিকোয়েন্সি + লুমা শব্দের তীব্রতা + লুমা গোলমাল বিস্তারিত + ক্রোমা শব্দ + ক্রোমা শব্দ ফ্রিকোয়েন্সি + ক্রোমা শব্দের তীব্রতা + Chroma গোলমাল বিস্তারিত + তুষার তীব্রতা + স্নো অ্যানিসোট্রপি + ক্রোমা ফেজ শব্দ + ক্রোমা ফেজ ত্রুটি + Chroma বিলম্ব অনুভূমিক + Chroma বিলম্ব উল্লম্ব + ভিএইচএস টেপের গতি + ভিএইচএস ক্রোমা ক্ষতি + VHS তীক্ষ্ণতা তীব্রতা + ভিএইচএস ধারালো ফ্রিকোয়েন্সি + VHS প্রান্ত তরঙ্গ তীব্রতা + VHS প্রান্ত তরঙ্গ গতি + ভিএইচএস প্রান্ত তরঙ্গ ফ্রিকোয়েন্সি + VHS প্রান্ত তরঙ্গ বিস্তারিত + আউটপুট ক্রোমা লোপাস + স্কেল অনুভূমিক + স্কেল উল্লম্ব + স্কেল ফ্যাক্টর এক্স + স্কেল ফ্যাক্টর Y + সাধারণ ইমেজ ওয়াটারমার্ক সনাক্ত করে এবং LaMa দিয়ে রং করে। স্বয়ংক্রিয়ভাবে সনাক্তকরণ এবং ইনপেইন্টিং মডেল ডাউনলোড করে + Deuteranopia + ছবি প্রসারিত করুন + YOLO v11 সেগমেন্টেশন ব্যবহার করে অবজেক্ট সেগমেন্টেশন ভিত্তিক ব্যাকগ্রাউন্ড রিমুভার + রেখার কোণ দেখান + অঙ্কন করার সময় ডিগ্রীতে বর্তমান রেখার ঘূর্ণন দেখায় + অ্যানিমেটেড ইমোজিস + অ্যানিমেশন হিসাবে উপলব্ধ ইমোজি দেখান + অরিজিনাল ফোল্ডারে সেভ করুন + নির্বাচিত ফোল্ডারের পরিবর্তে আসল ফাইলের পাশে নতুন ফাইল সংরক্ষণ করুন + ওয়াটারমার্ক পিডিএফ + যথেষ্ট মেমরি নেই + এই ডিভাইসে প্রক্রিয়া করার জন্য চিত্রটি সম্ভবত খুব বড়, বা সিস্টেমের উপলব্ধ মেমরি ফুরিয়ে গেছে৷ ইমেজ রেজোলিউশন কমানোর চেষ্টা করুন, অন্যান্য অ্যাপ বন্ধ করুন বা একটি ছোট ফাইল বেছে নিন। + ডিবাগ মেনু + অ্যাপ ফাংশন পরীক্ষা করার জন্য মেনু, এটি প্রোডাকশন রিলিজে দেখানোর উদ্দেশ্যে নয় + প্রদানকারী + PaddleOCR-এর জন্য আপনার ডিভাইসে অতিরিক্ত ONNX মডেলের প্রয়োজন। আপনি কি %1$s ডেটা ডাউনলোড করতে চান? + সর্বজনীন + কোরিয়ান + ল্যাটিন + পূর্ব স্লাভিক + থাই + গ্রীক + ইংরেজি + সিরিলিক + আরবি + দেবনাগরী + তামিল + তেলেগু + শেডর + Shader প্রিসেট + কোনো শেডার নির্বাচন করা হয়নি + শেডার স্টুডিও + কাস্টম ফ্র্যাগমেন্ট শেডার তৈরি করুন, সম্পাদনা করুন, যাচাই করুন, আমদানি করুন এবং রপ্তানি করুন + সংরক্ষিত শেডার্স + সংরক্ষিত শেডার খুলুন, ডুপ্লিকেট, রপ্তানি, ভাগ করুন বা মুছুন + নতুন শেডার + শেডার ফাইল + .itshader JSON + %1$d প্যারামস + শেডের উৎস + শুধুমাত্র void main() এর বডি লিখুন। ইউভি স্থানাঙ্কের জন্য টেক্সচার কোঅর্ডিনেট ব্যবহার করুন এবং উৎস নমুনা হিসাবে ইনপুট ইমেজ টেক্সচার। + ফাংশন + ঐচ্ছিক সাহায্যকারী ফাংশন, ধ্রুবক, এবং স্ট্রাকস void main() এর আগে ঢোকানো হয়। ইউনিফর্ম params থেকে উত্পন্ন হয়. + শেডারের সম্পাদনাযোগ্য মান প্রয়োজন হলে এখানে ইউনিফর্ম যোগ করুন। + শেডার রিসেট করুন + বর্তমান শেডার খসড়া সাফ করা হবে + অবৈধ শেডার + অসমর্থিত শেডার সংস্করণ %1$d৷ সমর্থিত সংস্করণ: %2$d। + শেডারের নাম খালি হওয়া উচিত নয়। + শেডারের উৎস অবশ্যই খালি থাকবে না। + Shader উৎসে অসমর্থিত অক্ষর রয়েছে: %1$s। শুধুমাত্র ASCII GLSL উৎস ব্যবহার করুন। + প্যারামিটার \\\"%1$s\\\" একাধিকবার ঘোষণা করা হয়েছে। + পরামিতি নাম খালি হতে হবে না. + প্যারামিটার \\\"%1$s\\\" একটি সংরক্ষিত GPUI চিত্র নাম ব্যবহার করে। + প্যারামিটার \\\"%1$s\\\" অবশ্যই একটি বৈধ GLSL শনাক্তকারী হতে হবে। + প্যারামিটার \\\"%1$s\\\" এর %2$s মান প্রকার \\\"%3$s\\\" আছে, প্রত্যাশিত \\\"%4$s\\\"। + প্যারামিটার \\\"%1$s\\\" বুলের মানের জন্য সর্বনিম্ন বা সর্বোচ্চ নির্ধারণ করতে পারে না। + \\\"%1$s\\\" প্যারামিটারে সর্বোচ্চ থেকে মিনিমাম বেশি। + প্যারামিটার \\\"%1$s\\\" ডিফল্ট মিনিমাম থেকে কম। + প্যারামিটার \\\"%1$s\\\" ডিফল্ট সর্বোচ্চ থেকে বড়। + শেডারকে অবশ্যই \\\"ইউনিফর্ম স্যাম্পলার2D %1$s;\\\" ঘোষণা করতে হবে। + শেডারকে অবশ্যই \\\"পরিবর্তিত vec2 %1$s;\\\" ঘোষণা করতে হবে। + Shader অবশ্যই \\\"void main()\\\" সংজ্ঞায়িত করবে। + Shader অবশ্যই gl_FragColor এ একটি রঙ লিখতে হবে। + ShaderToy mainImage shaders সমর্থিত নয়। GPUImage এর void main() চুক্তি ব্যবহার করুন। + অ্যানিমেটেড ShaderToy ইউনিফর্ম \\\"iTime\\\" সমর্থিত নয়। + অ্যানিমেটেড ShaderToy ইউনিফর্ম \\\"iFrame\\\" সমর্থিত নয়। + ShaderToy ইউনিফর্ম \\\"iResolution\\\" সমর্থিত নয়। + ShaderToy iChannel টেক্সচার সমর্থিত নয়। + বাহ্যিক টেক্সচার সমর্থিত নয়। + বাহ্যিক টেক্সচার এক্সটেনশন সমর্থিত নয়। + Libretro shader পরামিতি সমর্থিত নয়। + শুধুমাত্র একটি ইনপুট টেক্সচার সমর্থিত। \\\"ইউনিফর্ম %1$s %2$s;\\\" সরান। + প্যারামিটার \\\"%1$s\\\" অবশ্যই \\\"ইনিফর্ম %2$s %1$s;\\\" হিসেবে ঘোষণা করতে হবে। + প্যারামিটার \\\"%1$s\\\" কে \\\"ইউনিফর্ম %2$s %1$s;\\\" হিসেবে ঘোষণা করা হয়েছে, প্রত্যাশিত \\\"ইউনিফর্ম %3$s %1$s;\\\"। + Shader ফাইলটি খালি। + Shader ফাইল সংস্করণ, নাম, এবং shader ক্ষেত্র সহ একটি .itshader JSON অবজেক্ট হতে হবে। + Shader ফাইলটি বৈধ JSON নয় বা .itshader ফর্ম্যাটের সাথে মেলে না। + ক্ষেত্র \\\"%1$s\\\" প্রয়োজন। + %1$s প্রয়োজন। + %1$s \\\"%2$s\\\" সমর্থিত নয়। সমর্থিত প্রকার: %3$s। + \\\"%2$s\\\" প্যারামিটারের জন্য %1$s প্রয়োজন। + %1$s একটি সসীম সংখ্যা হতে হবে। + %1$s একটি পূর্ণসংখ্যা হতে হবে। + %1$s সত্য বা মিথ্যা হতে হবে। + %1$s একটি রঙের স্ট্রিং, RGB/RGBA অ্যারে, বা রঙের বস্তু হতে হবে৷ + %1$s অবশ্যই #RRGGBB বা #RRGGBBAA ফর্ম্যাট ব্যবহার করতে হবে। + %1$s এ অবশ্যই বৈধ হেক্সাডেসিমেল রঙের চ্যানেল থাকতে হবে। + %1$s তে অবশ্যই 3 বা 4টি রঙের চ্যানেল থাকতে হবে৷ + %1$s 0 এবং 255 এর মধ্যে হতে হবে। + %1$s একটি দ্বি-সংখ্যার অ্যারে বা x এবং y সহ একটি বস্তু হতে হবে৷ + %1$s-এ অবশ্যই 2টি সংখ্যা থাকতে হবে। + ডুপ্লিকেট + সর্বদা EXIF ​​পরিষ্কার করুন + সেভ করার সময় ইমেজ EXIF ​​ডেটা মুছে ফেলুন, এমনকি যখন একটি টুল মেটাডেটা রাখার অনুরোধ করে + সাহায্য এবং টিপস + %1$d টিউটোরিয়াল + টুল খুলুন + প্রধান টুল, এক্সপোর্ট অপশন, পিডিএফ ফ্লো, কালার ইউটিলিটি এবং সাধারণ সমস্যার সমাধান জানুন + শুরু হচ্ছে + সরঞ্জামগুলি চয়ন করুন, ফাইলগুলি আমদানি করুন, ফলাফলগুলি সংরক্ষণ করুন এবং দ্রুত সেটিংস পুনরায় ব্যবহার করুন৷ + ইমেজ এডিটিং + রিসাইজ করুন, ক্রপ করুন, ফিল্টার করুন, ব্যাকগ্রাউন্ড মুছুন, আঁকুন এবং ওয়াটারমার্ক ইমেজ করুন + ফাইল এবং মেটাডেটা + ফরম্যাট রূপান্তর করুন, আউটপুট তুলনা করুন, গোপনীয়তা রক্ষা করুন এবং ফাইলের নাম নিয়ন্ত্রণ করুন + পিডিএফ এবং নথি + পিডিএফ তৈরি করুন, নথি স্ক্যান করুন, ওসিআর পৃষ্ঠাগুলি, এবং ভাগ করার জন্য ফাইল প্রস্তুত করুন + পাঠ্য, QR এবং ডেটা + পাঠ্য চিনুন, কোড স্ক্যান করুন, বেস64 এনকোড করুন, হ্যাশ চেক করুন এবং প্যাকেজ ফাইলগুলি দেখুন + রঙের সরঞ্জাম + রং বেছে নিন, প্যালেট তৈরি করুন, রঙের লাইব্রেরি অন্বেষণ করুন এবং গ্রেডিয়েন্ট তৈরি করুন + সৃজনশীল সরঞ্জাম + SVG, ASCII আর্ট, নয়েজ টেক্সচার, শেডার এবং পরীক্ষামূলক ভিজ্যুয়াল তৈরি করুন + সমস্যা সমাধান + ভারী ফাইল, স্বচ্ছতা, ভাগ করে নেওয়া, আমদানি এবং রিপোর্টিং সমস্যাগুলি ঠিক করুন + সঠিক টুল নির্বাচন করুন + সঠিক জায়গায় শুরু করতে বিভাগ, অনুসন্ধান, পছন্দ এবং শেয়ার ক্রিয়াগুলি ব্যবহার করুন৷ + সেরা এন্ট্রি পয়েন্ট থেকে শুরু করুন + ইমেজ টুলবক্সে অনেকগুলি ফোকাস করা টুল রয়েছে এবং সেগুলির অনেকগুলি প্রথম নজরে ওভারল্যাপ করে৷ আপনি যে কাজটি শেষ করতে চান তা থেকে শুরু করুন, তারপরে ফলাফলের সবচেয়ে গুরুত্বপূর্ণ অংশটি নিয়ন্ত্রণ করে এমন টুলটি নির্বাচন করুন: আকার, বিন্যাস, মেটাডেটা, পাঠ্য, PDF পৃষ্ঠা, রঙ বা লেআউট। + যখন আপনি অ্যাকশনের নাম জানেন, যেমন রিসাইজ, PDF, EXIF, OCR, QR, বা রঙের মতো সার্চ ব্যবহার করুন। + একটি বিভাগ খুলুন যখন আপনি শুধুমাত্র টাস্ক টাইপ জানেন, তারপর পছন্দসই হিসাবে ঘন ঘন টুল পিন করুন। + যখন অন্য অ্যাপ ইমেজ টুলবক্সে একটি ছবি শেয়ার করে, শেয়ার শীট ফ্লো থেকে টুলটি বেছে নিন। + আমদানি, সংরক্ষণ, এবং ভাগ ফলাফল + ইনপুট বাছাই থেকে সম্পাদিত ফাইল রপ্তানি পর্যন্ত স্বাভাবিক প্রবাহ বুঝুন + সাধারণ সম্পাদনা প্রবাহ অনুসরণ করুন + বেশিরভাগ সরঞ্জাম একই তাল অনুসরণ করে: ইনপুট চয়ন করুন, বিকল্পগুলি সামঞ্জস্য করুন, পূর্বরূপ দেখুন, তারপর সংরক্ষণ করুন বা ভাগ করুন৷ গুরুত্বপূর্ণ বিশদটি হ\'ল সংরক্ষণ করা একটি আউটপুট ফাইল তৈরি করে, যখন ভাগ করা সাধারণত অন্য অ্যাপে দ্রুত হ্যান্ডঅফের জন্য সর্বোত্তম। + একক-ইমেজ টুলের জন্য একটি ফাইল বা ব্যাচ টুলের জন্য একাধিক ফাইল বেছে নিন যখন পিকার এটির অনুমতি দেয়। + সংরক্ষণ করার আগে পূর্বরূপ এবং আউটপুট নিয়ন্ত্রণ পরীক্ষা করুন, বিশেষত বিন্যাস, গুণমান এবং ফাইলের নাম বিকল্পগুলি। + দ্রুত হ্যান্ডঅফের জন্য শেয়ার ব্যবহার করুন, অথবা নির্বাচিত ফোল্ডারে আপনার একটি অবিরাম অনুলিপি প্রয়োজন হলে সংরক্ষণ করুন। + একসাথে অনেক ইমেজ সঙ্গে কাজ + বারবার আকার পরিবর্তন, রূপান্তর, সংকোচন এবং নামকরণের জন্য ব্যাচ সরঞ্জামগুলি সেরা + একটি অনুমানযোগ্য ব্যাচ প্রস্তুত করুন + ব্যাচ প্রক্রিয়াকরণ দ্রুততম যখন প্রতিটি আউটপুট একই নিয়ম অনুসরণ করা উচিত। এটি একটি বড় ভুল করার সবচেয়ে সহজ জায়গা, তাই দীর্ঘ রপ্তানি শুরু করার আগে নামকরণ, গুণমান, মেটাডেটা এবং ফোল্ডার আচরণ বেছে নিন। + সমস্ত সম্পর্কিত চিত্র একসাথে নির্বাচন করুন এবং যদি আউটপুট অনুক্রমের উপর নির্ভর করে তবে অর্ডারটি রাখুন। + রপ্তানি শুরু করার আগে একবার আকার পরিবর্তন, বিন্যাস, গুণমান, মেটাডেটা এবং ফাইলের নাম নিয়ম সেট করুন। + সোর্স ফাইলগুলি বড় হলে বা লক্ষ্য বিন্যাস আপনার জন্য নতুন হলে প্রথমে একটি ছোট পরীক্ষা ব্যাচ চালান৷ + দ্রুত একক সম্পাদনা + যখন আপনার একটি ছবিতে বেশ কয়েকটি সাধারণ পরিবর্তনের প্রয়োজন হয় তখন অল-ইন-ওয়ান এডিটর ব্যবহার করুন + টুল হপিং ছাড়াই একটি ছবি শেষ করুন + একক সম্পাদনা উপযোগী হয় যখন চিত্রটির একটি বিশেষ ক্রিয়াকলাপের পরিবর্তে পরিবর্তনের একটি ছোট চেইন প্রয়োজন। একটি ব্যাচ ওয়ার্কফ্লো তৈরি না করে দ্রুত পরিষ্কার, পূর্বরূপ দেখা এবং একটি চূড়ান্ত অনুলিপি রপ্তানি করার জন্য এটি সাধারণত দ্রুত। + সাধারণ সমন্বয়, মার্কআপ, ক্রপ, ঘূর্ণন, বা রপ্তানি পরিবর্তন প্রয়োজন এমন একটি চিত্রের জন্য একক সম্পাদনা খুলুন। + এমন ক্রমে সম্পাদনাগুলি প্রয়োগ করুন যা প্রথমে সবচেয়ে কম পিক্সেল পরিবর্তন করে, যেমন ফিল্টারের আগে ক্রপ এবং চূড়ান্ত সংকোচনের আগে ফিল্টার। + আপনার ব্যাচ প্রসেসিং, সঠিক ফাইল-আকার লক্ষ্য, PDF আউটপুট, OCR, বা মেটাডেটা ক্লিনআপের প্রয়োজন হলে পরিবর্তে একটি বিশেষ সরঞ্জাম ব্যবহার করুন। + প্রিসেট এবং সেটিংস ব্যবহার করুন + বারবার পছন্দগুলি সংরক্ষণ করুন এবং আপনার পছন্দের কর্মপ্রবাহের জন্য অ্যাপটি টিউন করুন + একই সেটআপের পুনরাবৃত্তি এড়িয়ে চলুন + আপনি যখন প্রায়শই একই ধরণের ফাইল রপ্তানি করেন তখন প্রিসেট এবং সেটিংস দরকারী। একটি ভাল প্রিসেট একটি বারবার ম্যানুয়াল চেকলিস্টকে একটি ট্যাপে পরিণত করে, যখন বিশ্বব্যাপী সেটিংস ডিফল্টগুলিকে সংজ্ঞায়িত করে যা নতুন সরঞ্জামগুলি দিয়ে শুরু হয়। + সাধারণ আউটপুট আকার, বিন্যাস, গুণমান মান, বা নামকরণের ধরণগুলির জন্য প্রিসেট তৈরি করুন। + ডিফল্ট বিন্যাস, গুণমান, ফোল্ডার সংরক্ষণ, ফাইলের নাম, পিকার এবং ইন্টারফেস আচরণের জন্য সেটিংস পর্যালোচনা করুন। + অ্যাপটি পুনরায় ইনস্টল করার আগে বা অন্য ডিভাইসে যাওয়ার আগে সেটিংসের ব্যাক আপ নিন। + দরকারী ডিফল্ট সেট করুন + একবার ডিফল্ট বিন্যাস, গুণমান, স্কেল মোড, আকার পরিবর্তনের ধরন এবং রঙের স্থান চয়ন করুন + আপনার লক্ষ্যের কাছাকাছি নতুন টুল শুরু করুন + ডিফল্ট মান শুধুমাত্র অঙ্গরাগ পছন্দ নয়. তারা সিদ্ধান্ত নেয় কোন ফর্ম্যাট, গুণমান, আকার পরিবর্তনের আচরণ, স্কেল মোড এবং রঙের স্থান অনেক সরঞ্জামগুলিতে প্রথমে প্রদর্শিত হবে, যা প্রতিটি রপ্তানির ক্ষেত্রে একই পছন্দগুলি পুনরায় নির্বাচন করা এড়াতে সহায়তা করে। + আপনার স্বাভাবিক গন্তব্যের সাথে মেলে ডিফল্ট চিত্র বিন্যাস এবং গুণমান সেট করুন, যেমন ফটোর জন্য JPEG বা আলফার জন্য PNG/WebP। + আপনি যদি প্রায়ই কঠোর মাত্রা, সামাজিক প্ল্যাটফর্ম বা নথি পৃষ্ঠাগুলির জন্য রপ্তানি করেন তবে ডিফল্ট রিসাইজ টাইপ এবং স্কেল মোড টিউন করুন৷ + ডিসপ্লে, প্রিন্ট বা বাহ্যিক সম্পাদক জুড়ে আপনার কর্মপ্রবাহের সামঞ্জস্যপূর্ণ রঙ পরিচালনার প্রয়োজন হলেই ডিফল্ট রঙের স্থান পরিবর্তন করুন। + পিকার এবং লঞ্চার + অ্যাপটি যখন অনেক ডায়ালগ খোলে বা ভুল জায়গায় শুরু হয় তখন পিকার সেটিংস ব্যবহার করুন + ফাইল খোলাকে আপনার অভ্যাসের সাথে মিলিয়ে নিন + পিকার এবং লঞ্চার সেটিংস নিয়ন্ত্রণ করে যে ইমেজ টুলবক্স মূল স্ক্রীন, একটি টুল বা ফাইল নির্বাচন প্রবাহ থেকে শুরু হয় কিনা। এই বিকল্পগুলি বিশেষভাবে উপযোগী হয় যখন আপনি সাধারণত Android শেয়ার শীট থেকে প্রবেশ করেন বা যখন আপনি অ্যাপটিকে একটি টুল লঞ্চারের মতো অনুভব করতে চান৷ + ফটো পিকার সেটিংস ব্যবহার করুন যদি সিস্টেম পিকার ফাইল লুকিয়ে রাখে, অনেক সাম্প্রতিক আইটেম দেখায় বা ক্লাউড ফাইল খারাপভাবে পরিচালনা করে। + শুধুমাত্র সেই টুলগুলির জন্য স্কিপ পিকিং সক্ষম করুন যেখানে আপনি সাধারণত শেয়ার থেকে প্রবেশ করেন বা সোর্স ফাইলটি ইতিমধ্যেই জানেন৷ + লঞ্চার মোড পর্যালোচনা করুন যদি আপনি ইমেজ টুলবক্স একটি সাধারণ হোম স্ক্রীনের চেয়ে একটি টুল পিকারের মতো আচরণ করতে চান। + ইমেজ সোর্স + গ্যালারি, ফাইল ম্যানেজার এবং ক্লাউড ফাইলের সাথে সবচেয়ে ভালো কাজ করে এমন পিকার মোড বেছে নিন + সঠিক উৎস থেকে ফাইল বাছাই করুন + ইমেজ সোর্স সেটিংস প্রভাবিত করে কিভাবে অ্যাপটি অ্যান্ড্রয়েডকে ছবির জন্য জিজ্ঞাসা করে। আপনার ফাইলগুলি সিস্টেম গ্যালারিতে, একটি ফাইল ম্যানেজার ফোল্ডার, একটি ক্লাউড প্রদানকারী, বা অস্থায়ী বিষয়বস্তু ভাগ করে এমন অন্য অ্যাপে থাকে কিনা তার উপর সর্বোত্তম পছন্দ নির্ভর করে৷ + আপনি যখন সাধারণ গ্যালারি চিত্রগুলির জন্য সর্বাধিক সিস্টেম-ইন্টিগ্রেটেড অভিজ্ঞতা চান তখন ডিফল্ট পিকার ব্যবহার করুন৷ + আপনার প্রত্যাশা অনুযায়ী অ্যালবাম, ফোল্ডার, ক্লাউড ফাইল বা নন-স্ট্যান্ডার্ড ফরম্যাট উপস্থিত না হলে পিকার মোড পরিবর্তন করুন। + সোর্স অ্যাপ ছাড়ার পরে যদি শেয়ার করা ফাইলটি অদৃশ্য হয়ে যায়, তাহলে এটি একটি স্থায়ী পিকারের মাধ্যমে আবার খুলুন বা প্রথমে একটি স্থানীয় কপি সংরক্ষণ করুন। + পছন্দসই এবং শেয়ার টুল + মূল স্ক্রীনকে ফোকাস করে রাখুন এবং এমন টুল লুকান যা শেয়ার ফ্লোতে কোন মানে হয় না + টুল তালিকার শব্দ কমিয়ে দিন + আপনি যখন প্রতিদিন মাত্র কয়েকটি টুল ব্যবহার করেন তখন ফেভারিট এবং শেয়ার করার জন্য লুকানো সেটিংস দরকারী। অন্য অ্যাপ যে ধরনের ফাইল পাঠায় তা ব্যবহার করতে পারে না এমন টুল লুকিয়ে তারা শেয়ার প্রবাহকে ব্যবহারিক রাখে। + ঘন ঘন সরঞ্জামগুলিকে পিন করুন যাতে সরঞ্জামগুলি বিভাগ অনুসারে গোষ্ঠীবদ্ধ থাকা সত্ত্বেও তারা সহজেই পৌঁছাতে পারে৷ + অন্য অ্যাপ থেকে আসা ফাইলের জন্য অপ্রাসঙ্গিক হলে শেয়ার ফ্লো থেকে টুল লুকান। + পছন্দের অর্ডারিং সেটিংস ব্যবহার করুন যদি আপনি পছন্দের শেষে পছন্দ করেন বা সম্পর্কিত সরঞ্জামগুলির সাথে গোষ্ঠীবদ্ধ হন। + ব্যাক আপ সেটিংস + নিরাপদে অন্য ইনস্টলে প্রিসেট, পছন্দ এবং অ্যাপ আচরণ সরান + আপনার সেটআপ পোর্টেবল রাখুন + আপনি প্রিসেট, ফোল্ডার, ফাইলের নাম নিয়ম, টুল অর্ডার এবং ভিজ্যুয়াল পছন্দগুলি টিউন করার পরে ব্যাকআপ এবং পুনরুদ্ধার সবচেয়ে সহায়ক। একটি ব্যাকআপ আপনাকে সেটিংস নিয়ে পরীক্ষা করতে দেয় বা মেমরি থেকে আপনার ওয়ার্কফ্লো পুনর্নির্মাণ না করে অ্যাপটি পুনরায় ইনস্টল করতে দেয়। + প্রিসেট, ফাইলের নাম আচরণ, ডিফল্ট ফর্ম্যাট বা টুল বিন্যাস পরিবর্তন করার পরে একটি ব্যাকআপ তৈরি করুন। + আপনি যদি ডেটা সাফ করার, পুনরায় ইনস্টল করার বা ডিভাইসগুলি সরানোর পরিকল্পনা করেন তবে অ্যাপ ফোল্ডারের বাইরে কোথাও ব্যাকআপ সংরক্ষণ করুন৷ + গুরুত্বপূর্ণ ব্যাচগুলি প্রক্রিয়া করার আগে সেটিংস পুনরুদ্ধার করুন যাতে ডিফল্ট এবং সংরক্ষণ আচরণ আপনার পুরানো সেটআপের সাথে মেলে। + আকার পরিবর্তন করুন এবং ছবি রূপান্তর করুন + এক বা একাধিক ছবির আকার, বিন্যাস, গুণমান এবং মেটাডেটা পরিবর্তন করুন + রিসাইজ করা কপি তৈরি করুন + আকার পরিবর্তন এবং রূপান্তর পূর্বাভাসযোগ্য মাত্রা এবং হালকা আউটপুট ফাইলের জন্য প্রধান টুল। এটি ব্যবহার করুন যখন চিত্রের আকার, আউটপুট বিন্যাস এবং মেটাডেটা আচরণ একই সময়ে গুরুত্বপূর্ণ। + এক বা একাধিক ছবি বেছে নিন, তারপরে মাত্রা, স্কেল বা ফাইলের আকার সবচেয়ে গুরুত্বপূর্ণ কিনা তা বেছে নিন। + আউটপুট বিন্যাস এবং গুণমান নির্বাচন করুন; PNG বা WebP ব্যবহার করুন যখন স্বচ্ছতা সংরক্ষণ করতে হবে। + ফলাফলের পূর্বরূপ দেখুন, তারপর মূল পরিবর্তন না করে উত্পন্ন অনুলিপিগুলি সংরক্ষণ বা ভাগ করুন৷ + ফাইলের আকার অনুসারে আকার পরিবর্তন করুন + একটি ওয়েবসাইট, মেসেঞ্জার বা ফর্ম ভারী ছবি প্রত্যাখ্যান করলে একটি আকারের সীমা লক্ষ্য করুন + কঠোর আপলোড সীমা মাপসই + যখন গন্তব্য শুধুমাত্র একটি নির্দিষ্ট সীমার নিচের ফাইলগুলিকে গ্রহণ করে তখন মানের মান অনুমান করার চেয়ে ফাইলের আকার অনুসারে আকার পরিবর্তন করা ভাল। ফলাফলটি ব্যবহারযোগ্য রাখার চেষ্টা করার সময় টুলটি লক্ষ্যে পৌঁছানোর জন্য কম্প্রেশন এবং মাত্রা সামঞ্জস্য করতে পারে। + মেটাডেটা এবং প্রদানকারী পরিবর্তনের জন্য জায়গা ছেড়ে দিতে বাস্তব আপলোড সীমার সামান্য নিচে লক্ষ্য আকার লিখুন। + লক্ষ্য ছোট হলে ছবির জন্য ক্ষতিকর বিন্যাস পছন্দ করুন; আকার পরিবর্তন করার পরেও PNG বড় থাকতে পারে। + রপ্তানির পরে মুখ, টেক্সট এবং প্রান্তগুলি পরিদর্শন করুন কারণ আক্রমনাত্মক আকারের লক্ষ্যগুলি দৃশ্যমান শিল্পকর্মের পরিচয় দিতে পারে৷ + সীমিত আকার পরিবর্তন করুন + আপনার সর্বাধিক প্রস্থ, উচ্চতা বা ফাইলের সীমাবদ্ধতা অতিক্রম করে এমন চিত্রগুলিকে সঙ্কুচিত করুন৷ + ইতিমধ্যেই ঠিক আছে এমন চিত্রের আকার পরিবর্তন করা এড়িয়ে চলুন + লিমিট রিসাইজ মিশ্র ফোল্ডারগুলির জন্য উপযোগী যেখানে কিছু চিত্র বিশাল এবং অন্যগুলি ইতিমধ্যে প্রস্তুত। একই রিসাইজের মাধ্যমে প্রতিটি ফাইলকে বাধ্য করার পরিবর্তে, এটি গ্রহণযোগ্য ছবিগুলিকে তাদের আসল মানের কাছাকাছি রাখে। + অন্ধভাবে প্রতিটি ফাইলের আকার পরিবর্তন করার পরিবর্তে গন্তব্যের উপর ভিত্তি করে সর্বাধিক মাত্রা বা সীমা সেট করুন। + আপনি যখন উত্সের চেয়ে ভারী আউটপুটগুলি এড়াতে চান তখন স্কিপ-ইফ-বড় শৈলী আচরণ সক্ষম করুন৷ + বিভিন্ন ক্যামেরা, স্ক্রিনশট বা ডাউনলোডের ব্যাচের জন্য এটি ব্যবহার করুন যেখানে উৎসের আকার অনেক পরিবর্তিত হয়। + ক্রপ, ঘোরান, এবং সোজা + ইমেজ রপ্তানি করার আগে বা অন্য টুলে ব্যবহার করার আগে গুরুত্বপূর্ণ এলাকা ফ্রেম করুন + রচনাটি পরিষ্কার করুন + প্রথমে ক্রপ করা পরে ফিল্টার, ওসিআর, পিডিএফ পেজ এবং শেয়ারিং ফলাফল ক্লিনার করে। + ক্রপ খুলুন এবং আপনি যে চিত্রটি সামঞ্জস্য করতে চান তা চয়ন করুন। + আউটপুট একটি পোস্ট, নথি, বা অবতারের সাথে মানানসই হলে বিনামূল্যে ক্রপ বা একটি আকৃতির অনুপাত ব্যবহার করুন৷ + সংরক্ষণ করার আগে ঘোরান বা ফ্লিপ করুন যাতে পরবর্তী সরঞ্জামগুলি সংশোধন করা চিত্র পায়। + ফিল্টার এবং সমন্বয় প্রয়োগ করুন + একটি সম্পাদনাযোগ্য ফিল্টার স্ট্যাক তৈরি করুন এবং রপ্তানির আগে ফলাফলের পূর্বরূপ দেখুন + টিউন ইমেজ চেহারা + ফিল্টারগুলি দ্রুত সংশোধন, স্টাইলাইজড আউটপুট এবং OCR বা মুদ্রণের জন্য ছবি প্রস্তুত করার জন্য দরকারী। বৈসাদৃশ্য, তীক্ষ্ণতা এবং উজ্জ্বলতার ছোট পরিবর্তনগুলি ভারী প্রভাবের চেয়ে বেশি গুরুত্বপূর্ণ হতে পারে যখন ছবিতে পাঠ্য বা সূক্ষ্ম বিবরণ থাকে। + এক এক করে ফিল্টার যোগ করুন এবং প্রতিটি পরিবর্তনের পর প্রিভিউতে নজর রাখুন। + টাস্কের উপর নির্ভর করে উজ্জ্বলতা, বৈসাদৃশ্য, তীক্ষ্ণতা, অস্পষ্টতা, রঙ বা মুখোশ সামঞ্জস্য করুন। + পূর্বরূপ আপনার লক্ষ্য ডিভাইস, নথি, বা সামাজিক প্ল্যাটফর্মের সাথে মেলে শুধুমাত্র তখনই রপ্তানি করুন৷ + প্রথমে পূর্বরূপ দেখুন + একটি ভারী সম্পাদনা, রূপান্তর বা ক্লিনআপ টুল বেছে নেওয়ার আগে ছবিগুলি পরিদর্শন করুন + আপনি আসলে কি পেয়েছেন তা পরীক্ষা করুন + যখন কোনো ফাইল চ্যাট, ক্লাউড স্টোরেজ বা অন্য কোনো অ্যাপ থেকে আসে এবং আপনি নিশ্চিত নন যে এতে কী আছে তা হলে ইমেজ প্রিভিউ উপযোগী। মাত্রা, স্বচ্ছতা, অভিযোজন এবং চাক্ষুষ গুণমান পরীক্ষা করা প্রথমে সঠিক ফলো-আপ টুল বেছে নিতে সাহায্য করে। + ছবি প্রিভিউ খুলুন যখন আপনাকে সেগুলির সাথে কী করতে হবে তা সিদ্ধান্ত নেওয়ার আগে আপনাকে বেশ কয়েকটি ছবি পরিদর্শন করতে হবে। + ঘূর্ণন সমস্যা, স্বচ্ছ এলাকা, কম্প্রেশন আর্টিফ্যাক্ট এবং অপ্রত্যাশিতভাবে বড় মাত্রার জন্য দেখুন। + কি ঠিক করা দরকার তা জানার পরেই পুনরায় আকার, ক্রপ, তুলনা, EXIF ​​বা ব্যাকগ্রাউন্ড রিমুভারে যান। + একটি পটভূমি সরান বা পুনরুদ্ধার করুন + স্বয়ংক্রিয়ভাবে ব্যাকগ্রাউন্ড মুছে ফেলুন বা পুনরুদ্ধার সরঞ্জামগুলির সাথে ম্যানুয়ালি প্রান্তগুলি পরিমার্জন করুন৷ + সাবজেক্ট রাখুন, বাকিটা সরিয়ে দিন + আপনি সতর্ক ম্যানুয়াল ক্লিনআপের সাথে স্বয়ংক্রিয় নির্বাচনকে একত্রিত করলে পটভূমি অপসারণ সর্বোত্তম কাজ করে। চূড়ান্ত রপ্তানি বিন্যাস মুখোশের মতোই গুরুত্বপূর্ণ, কারণ পূর্বরূপ সঠিক দেখালেও JPEG স্বচ্ছ এলাকা সমতল করবে। + স্বয়ংক্রিয় অপসারণ দিয়ে শুরু করুন যদি বিষয়টি পটভূমি থেকে স্পষ্টভাবে আলাদা করা হয়। + জুম ইন করুন এবং চুল, কোণ, ছায়া এবং ছোট বিবরণ ঠিক করতে মুছে ফেলা এবং পুনরুদ্ধারের মধ্যে স্যুইচ করুন। + যখন আপনার চূড়ান্ত ফাইলে স্বচ্ছতার প্রয়োজন হয় তখন PNG বা WebP হিসাবে সংরক্ষণ করুন। + আঁকুন, চিহ্নিত করুন এবং জলছাপ করুন + তীর, নোট, হাইলাইট, স্বাক্ষর, বা বারবার ওয়াটারমার্ক ওভারলে যোগ করুন + দৃশ্যমান তথ্য যোগ করুন + মার্কআপ সরঞ্জামগুলি একটি চিত্র ব্যাখ্যা করতে সহায়তা করে, যখন ওয়াটারমার্কিং রপ্তানি করা ফাইলগুলিকে ব্র্যান্ড বা সুরক্ষিত করতে সহায়তা করে। + আপনার যখন ফ্রিহ্যান্ড নোট, আকার, হাইলাইটিং বা দ্রুত টীকা প্রয়োজন তখন ড্র ব্যবহার করুন। + ওয়াটারমার্কিং ব্যবহার করুন যখন একই টেক্সট বা ইমেজ চিহ্ন আউটপুটগুলিতে ধারাবাহিকভাবে প্রদর্শিত হবে। + রপ্তানি করার আগে অস্বচ্ছতা, অবস্থান এবং স্কেল পরীক্ষা করুন যাতে চিহ্নটি দৃশ্যমান হয় কিন্তু বিভ্রান্ত না হয়। + ডিফল্ট অঙ্কন + দ্রুত মার্কআপের জন্য লাইনের প্রস্থ, রঙ, পথ মোড, ওরিয়েন্টেশন লক এবং ম্যাগনিফায়ার সেট করুন + অঙ্কন অনুমানযোগ্য মনে করুন + আপনি যখন স্ক্রিনশট টীকা করেন, ডকুমেন্ট চিহ্নিত করেন বা প্রায়ই ছবি সাইন করেন তখন অঙ্কন সেটিংস সহায়ক। ডিফল্ট সেটআপের সময় কমিয়ে দেয়, যখন ম্যাগনিফায়ার এবং ওরিয়েন্টেশন লক ছোট স্ক্রিনে সুনির্দিষ্ট সম্পাদনা সহজ করে তোলে। + ডিফল্ট লাইন প্রস্থ সেট করুন এবং তীর, হাইলাইট বা স্বাক্ষরের জন্য আপনি সবচেয়ে বেশি ব্যবহার করেন এমন মানগুলিতে রঙ আঁকুন। + আপনি যখন সাধারণত একই ধরনের স্ট্রোক, আকার বা মার্কআপ আঁকেন তখন ডিফল্ট পাথ মোড ব্যবহার করুন। + যখন আঙুলের ইনপুট ছোট বিবরণ সঠিকভাবে স্থাপন করা কঠিন করে তখন ম্যাগনিফায়ার বা ওরিয়েন্টেশন লক সক্ষম করুন। + মাল্টি-ইমেজ লেআউট + স্ক্রিনশট, স্ক্যান বা তুলনা স্ট্রিপগুলি সাজানোর আগে সঠিক মাল্টি-ইমেজ টুলটি বেছে নিন + সঠিক লেআউট টুল ব্যবহার করুন + এই সরঞ্জামগুলি একই রকম শোনাচ্ছে, কিন্তু প্রতিটি একটি ভিন্ন ধরণের মাল্টি-ইমেজ আউটপুটের জন্য সুর করা হয়েছে৷ প্রথমে সঠিকটি বেছে নেওয়ার ফলে একটি ভিন্ন ফলাফলের জন্য ডিজাইন করা লেআউট নিয়ন্ত্রণের লড়াই এড়ানো যায়। + একটি রচনায় বেশ কয়েকটি চিত্র সহ ডিজাইন করা লেআউটের জন্য কোলাজ মেকার ব্যবহার করুন। + ইমেজ স্টিচিং বা স্ট্যাকিং ব্যবহার করুন যখন ছবিগুলি একটি লম্বা উল্লম্ব বা অনুভূমিক স্ট্রিপ হয়ে যাবে। + একটি বড় ইমেজ যখন অনেক ছোট টুকরো হতে হবে তখন ইমেজ স্প্লিটিং ব্যবহার করুন। + চিত্র বিন্যাস রূপান্তর + ম্যানুয়ালি পিক্সেল এডিট না করে JPEG, PNG, WebP, AVIF, JXL এবং অন্যান্য কপি তৈরি করুন + কাজের জন্য বিন্যাস চয়ন করুন + ফটো, স্ক্রিনশট, স্বচ্ছতা, কম্প্রেশন বা সামঞ্জস্যের জন্য বিভিন্ন ফরম্যাট ভালো। রূপান্তর সবচেয়ে নিরাপদ যখন আপনি বুঝতে পারেন যে গন্তব্য অ্যাপটি কী সমর্থন করে এবং উত্সটিতে আলফা, অ্যানিমেশন বা মেটাডেটা রয়েছে কিনা যা আপনি রাখতে চান৷ + সামঞ্জস্যপূর্ণ ফটোগুলির জন্য JPEG, ক্ষতিহীন চিত্রগুলির জন্য PNG এবং আলফা এবং কমপ্যাক্ট ভাগ করার জন্য WebP ব্যবহার করুন৷ + যখন বিন্যাস এটি সমর্থন করে গুণমান সামঞ্জস্য করুন; নিম্ন মান আকার হ্রাস কিন্তু শিল্পকর্ম যোগ করতে পারেন. + আসলগুলি রাখুন যতক্ষণ না আপনি লক্ষ্য অ্যাপে রূপান্তরিত ফাইলগুলি সঠিকভাবে খোলার যাচাই না করেন। + EXIF এবং গোপনীয়তা পরীক্ষা করুন + সংবেদনশীল ফটো শেয়ার করার আগে মেটাডেটা দেখুন, সম্পাদনা করুন বা সরান + লুকানো ছবি ডেটা নিয়ন্ত্রণ করুন + EXIF-এ ক্যামেরা ডেটা, তারিখ, অবস্থান, অভিযোজন, এবং অন্যান্য বিবরণ থাকতে পারে যা ছবিতে দৃশ্যমান নয়৷ পাবলিক শেয়ারিং, সাপোর্ট রিপোর্ট, মার্কেটপ্লেস লিস্টিং বা কোনো ব্যক্তিগত জায়গা প্রকাশ করতে পারে এমন কোনো ফটো আগে চেক করা উচিত। + অবস্থান বা ব্যক্তিগত ডিভাইসের তথ্য থাকতে পারে এমন ফটো শেয়ার করার আগে EXIF ​​টুল খুলুন। + সংবেদনশীল ক্ষেত্রগুলি সরান বা তারিখ, অভিযোজন, লেখক বা ক্যামেরা ডেটার মতো ভুল ট্যাগগুলি সম্পাদনা করুন৷ + একটি নতুন অনুলিপি সংরক্ষণ করুন এবং গোপনীয়তা গুরুত্বপূর্ণ হলে আসলটির পরিবর্তে সেই অনুলিপিটি ভাগ করুন। + অটো EXIF ​​ক্লিনআপ + তারিখগুলি সংরক্ষণ করা উচিত কিনা তা সিদ্ধান্ত নেওয়ার সময় ডিফল্টরূপে মেটাডেটা সরান৷ + গোপনীয়তা ডিফল্ট করুন + EXIF সেটিংস গ্রুপটি দরকারী যখন আপনি প্রতিটি রপ্তানির জন্য এটি মনে রাখার পরিবর্তে স্বয়ংক্রিয়ভাবে গোপনীয়তা পরিষ্কার করতে চান৷ তারিখ সময় রাখুন একটি পৃথক সিদ্ধান্ত কারণ তারিখগুলি সাজানোর জন্য দরকারী কিন্তু ভাগ করার জন্য সংবেদনশীল হতে পারে। + যখন আপনার বেশিরভাগ রপ্তানি সর্বজনীন বা আধা-পাবলিক ভাগাভাগির জন্য হয় তখন সর্বদা-ক্লিয়ার EXIF ​​সক্ষম করুন৷ + প্রতিটি টাইমস্ট্যাম্প অপসারণের চেয়ে ক্যাপচারের সময় সংরক্ষণ করা বেশি গুরুত্বপূর্ণ হলেই কেবলমাত্র তারিখের সময় রাখুন সক্ষম করুন৷ + এক-বন্ধ ফাইলের জন্য ম্যানুয়াল EXIF ​​সম্পাদনা ব্যবহার করুন যেখানে আপনাকে কিছু ক্ষেত্র রাখতে হবে এবং অন্যগুলি সরাতে হবে। + ফাইলের নাম নিয়ন্ত্রণ করুন + উপসর্গ, প্রত্যয়, টাইমস্ট্যাম্প, প্রিসেট, চেকসাম এবং ক্রম সংখ্যা ব্যবহার করুন + রপ্তানি খুঁজে পাওয়া সহজ করুন + একটি ভাল ফাইলের নাম প্যাটার্ন ব্যাচ বাছাই করতে সাহায্য করে এবং দুর্ঘটনাজনিত ওভাররাইট প্রতিরোধ করে। ফাইলের নাম সেটিংস বিশেষভাবে উপযোগী হয় যখন রপ্তানিগুলি উৎস ফোল্ডারে ফিরে যায়, যেখানে অনুরূপ নামগুলি দ্রুত বিভ্রান্তিকর হতে পারে। + ফাইলের নাম উপসর্গ, প্রত্যয়, টাইমস্ট্যাম্প, আসল নাম, প্রিসেট তথ্য, বা সেটিংসে ক্রম বিকল্পগুলি সেট করুন। + ব্যাচের জন্য ক্রম সংখ্যা ব্যবহার করুন যেখানে অর্ডার গুরুত্বপূর্ণ, যেমন পৃষ্ঠা, ফ্রেম বা কোলাজ। + ওভাররাইট সক্ষম করুন শুধুমাত্র যখন আপনি নিশ্চিত হন যে বিদ্যমান ফাইলগুলি প্রতিস্থাপন করা বর্তমান ফোল্ডারের জন্য নিরাপদ৷ + ফাইল ওভাররাইট করুন + আপনার ওয়ার্কফ্লো ইচ্ছাকৃতভাবে ধ্বংসাত্মক হলেই আউটপুটগুলিকে মিলিত নামের সাথে প্রতিস্থাপন করুন + ওভাররাইট মোড সাবধানে ব্যবহার করুন + নামকরণের বিরোধগুলি কীভাবে পরিচালনা করা হয় তা ফাইলগুলিকে ওভাররাইট করে। এটি একই ফোল্ডারে পুনরাবৃত্তি রপ্তানির জন্য দরকারী, তবে আপনার ফাইলের নাম প্যাটার্ন আবার একই নাম তৈরি করলে এটি আগের ফলাফলগুলি প্রতিস্থাপন করতে পারে। + শুধুমাত্র সেই ফোল্ডারগুলির জন্য ওভাররাইট সক্ষম করুন যেখানে পুরানো উত্পন্ন ফাইলগুলি প্রতিস্থাপন প্রত্যাশিত এবং পুনরুদ্ধারযোগ্য৷ + অরিজিনাল, ক্লায়েন্ট ফাইল, ওয়ান-টাইম স্ক্যান বা ব্যাকআপ ছাড়া যেকোনো কিছু রপ্তানি করার সময় ওভাররাইট অক্ষম রাখুন। + একটি ইচ্ছাকৃত ফাইলের নাম প্যাটার্নের সাথে ওভাররাইটকে একত্রিত করুন যাতে শুধুমাত্র উদ্দিষ্ট আউটপুটগুলি প্রতিস্থাপিত হয়। + ফাইলের নাম প্যাটার্ন + মূল নাম, উপসর্গ, প্রত্যয়, টাইমস্ট্যাম্প, প্রিসেট, স্কেল মোড এবং চেকসামগুলি একত্রিত করুন + আউটপুট ব্যাখ্যা করে এমন নাম তৈরি করুন + ফাইলের নাম প্যাটার্ন সেটিংস রপ্তানি করা ফাইলগুলিকে কী ঘটেছে তার একটি দরকারী ইতিহাসে পরিণত করতে পারে৷ এটি ব্যাচগুলিতে গুরুত্বপূর্ণ কারণ ফোল্ডার ভিউই একমাত্র স্থান হতে পারে যেখানে আপনি মূল আকার, প্রিসেট, বিন্যাস এবং প্রক্রিয়াকরণের ক্রম আলাদা করতে পারেন। + ম্যানুয়াল বাছাইয়ের জন্য আপনার যখন পঠনযোগ্য নাম প্রয়োজন তখন আসল ফাইলের নাম, উপসর্গ, প্রত্যয় এবং টাইমস্ট্যাম্প ব্যবহার করুন। + প্রিসেট, স্কেল মোড, ফাইলের আকার, বা চেকসাম তথ্য যোগ করুন যখন আউটপুটগুলি কীভাবে উত্পাদিত হয়েছিল তা নথিভুক্ত করতে হবে। + ওয়ার্কফ্লোগুলির জন্য এলোমেলো নামগুলি এড়িয়ে চলুন যেখানে ফাইলগুলি মূল বা পৃষ্ঠার ক্রমগুলির সাথে যুক্ত থাকতে হবে৷ + ফাইলগুলি কোথায় সংরক্ষিত হবে তা চয়ন করুন৷ + ফোল্ডার, অরিজিনাল-ফোল্ডার সেভিং এবং ওয়ান-টাইম সেভ লোকেশন সেট করে এক্সপোর্ট হারানো এড়িয়ে চলুন + আউটপুট অনুমানযোগ্য রাখুন + আপনি যখন অনেক ফাইল প্রসেস করেন বা ক্লাউড প্রোভাইডার থেকে ফাইল শেয়ার করেন তখন আচরণ সংরক্ষণ করা সবচেয়ে গুরুত্বপূর্ণ। ফোল্ডার সেটিংস নির্ধারণ করে যে আউটপুটগুলি একটি পরিচিত জায়গায় সংগ্রহ করা হয়, আসলগুলির পাশে উপস্থিত হয়, বা একটি নির্দিষ্ট কাজের জন্য অস্থায়ীভাবে অন্য কোথাও যায় কিনা। + আপনি যদি সর্বদা এক জায়গায় রপ্তানি করতে চান তবে একটি ডিফল্ট সংরক্ষণ ফোল্ডার সেট করুন। + ক্লিনআপ ওয়ার্কফ্লোগুলির জন্য সেভ-টু-অরিজিনাল-ফোল্ডার ব্যবহার করুন যেখানে আউটপুট সোর্স ফাইলের কাছাকাছি থাকা উচিত। + আপনার ডিফল্ট পরিবর্তন না করে একটি একক টাস্কের জন্য ভিন্ন গন্তব্যের প্রয়োজন হলে ওয়ান-টাইম সেভ লোকেশন ব্যবহার করুন। + এককালীন ফোল্ডার সংরক্ষণ করুন + আপনার স্বাভাবিক গন্তব্য পরিবর্তন না করেই পরবর্তী রপ্তানিগুলিকে একটি অস্থায়ী ফোল্ডারে পাঠান৷ + বিশেষ কাজ আলাদা রাখুন + ওয়ান-টাইম সেভ লোকেশন অস্থায়ী প্রকল্প, ক্লায়েন্ট ফোল্ডার, ক্লিনআপ সেশন বা রপ্তানির জন্য দরকারী যা আপনার নিয়মিত ইমেজ টুলবক্স ফোল্ডারের সাথে মিশ্রিত করা উচিত নয়। এটি আপনার ডিফল্ট সেটআপ পুনরায় লেখা ছাড়াই একটি স্বল্পকালীন গন্তব্য দেয়। + আপনার স্বাভাবিক রপ্তানি অবস্থানের বাইরের একটি টাস্ক শুরু করার আগে একটি ওয়ান-টাইম ফোল্ডার বেছে নিন। + সংক্ষিপ্ত প্রকল্প, ভাগ করা ফোল্ডার বা ব্যাচের জন্য এটি ব্যবহার করুন যেখানে আউটপুটগুলিকে একসাথে গোষ্ঠীবদ্ধ থাকতে হবে। + কাজের পরে ডিফল্ট ফোল্ডারে ফিরে যান যাতে পরে রপ্তানি অপ্রত্যাশিতভাবে অস্থায়ী অবস্থানে না আসে। + ভারসাম্য গুণমান এবং ফাইলের আকার + অনুমান করার পরিবর্তে কখন মাত্রা, বিন্যাস বা গুণমান পরিবর্তন করতে হবে তা বুঝুন + ইচ্ছাকৃতভাবে ফাইল সঙ্কুচিত করুন + ফাইলের আকার ছবির মাত্রা, বিন্যাস, গুণমান, স্বচ্ছতা এবং বিষয়বস্তুর জটিলতার উপর নির্ভর করে। যদি একটি ফাইল এখনও খুব ভারী হয়, মাত্রা পরিবর্তন সাধারণত বারবার গুণমান কমানোর চেয়ে বেশি সাহায্য করে। + চিত্রটি গন্তব্যের চেয়ে বড় হলে প্রথমে মাত্রা পরিবর্তন করুন। + শুধুমাত্র ক্ষতিকারক বিন্যাসের জন্য নিম্ন মানের এবং এক্সপোর্ট করার পরে পাঠ্য, প্রান্ত, গ্রেডিয়েন্ট এবং মুখগুলি পরীক্ষা করুন। + মানের পরিবর্তন পর্যাপ্ত না হলে ফর্ম্যাট পরিবর্তন করুন, উদাহরণস্বরূপ ভাগ করার জন্য WebP বা খাস্তা স্বচ্ছ সম্পদের জন্য PNG। + অ্যানিমেটেড ফরম্যাটের সাথে কাজ করুন + একটি ফাইলে একটি বিটম্যাপের পরিবর্তে ফ্রেম থাকলে GIF, APNG, WebP এবং JXL টুল ব্যবহার করুন + প্রতিটি চিত্রকে স্থির হিসাবে বিবেচনা করবেন না + অ্যানিমেটেড ফর্ম্যাটগুলির জন্য এমন সরঞ্জামগুলির প্রয়োজন যা ফ্রেম, সময়কাল এবং সামঞ্জস্য বোঝে। + সেই ফরম্যাটের জন্য ফ্রেম-লেভেল অপারেশনের প্রয়োজন হলে GIF বা APNG টুল ব্যবহার করুন। + আপনার যখন আধুনিক কম্প্রেশন বা অ্যানিমেটেড WebP আউটপুট প্রয়োজন তখন WebP টুল ব্যবহার করুন। + শেয়ার করার আগে অ্যানিমেশন টাইমিংয়ের পূর্বরূপ দেখুন কারণ কিছু প্ল্যাটফর্ম অ্যানিমেটেড ছবি রূপান্তর বা সমতল করে। + আউটপুট তুলনা করুন এবং পূর্বরূপ দেখুন + এক্সপোর্ট রাখার আগে পরিবর্তন, ফাইলের আকার এবং ভিজ্যুয়াল পার্থক্য পরিদর্শন করুন + শেয়ার করার আগে যাচাই করুন + তুলনা সরঞ্জামগুলি কম্প্রেশন আর্টিফ্যাক্ট, ভুল রং বা অবাঞ্ছিত সম্পাদনাগুলিকে প্রথম দিকে ধরতে সাহায্য করে। + আপনি যখন পাশাপাশি দুটি সংস্করণ পরিদর্শন করতে চান তখন তুলনা খুলুন। + প্রান্ত, পাঠ্য, গ্রেডিয়েন্ট এবং স্বচ্ছ অঞ্চলগুলিতে জুম করুন যেখানে সমস্যাগুলি মিস করা সবচেয়ে সহজ। + আউটপুট খুব ভারী বা ঝাপসা হলে, পূর্ববর্তী টুলে ফিরে যান এবং গুণমান বা বিন্যাস সামঞ্জস্য করুন। + পিডিএফ টুলস হাব ব্যবহার করুন + একত্রিত করুন, বিভক্ত করুন, ঘোরান, পৃষ্ঠাগুলি সরান, সংকুচিত করুন, সুরক্ষা করুন, OCR এবং ওয়াটারমার্ক PDF ফাইলগুলি + একটি পিডিএফ অপারেশন নির্বাচন করুন + পিডিএফ হাব একটি এন্ট্রি পয়েন্টের পিছনে নথির ক্রিয়াগুলিকে গোষ্ঠীভুক্ত করে যাতে আপনি সঠিক অপারেশনটি বেছে নিতে পারেন। যখন ফাইলটি ইতিমধ্যেই একটি PDF হয় তখন সেখানে শুরু করুন এবং আপনার উত্স এখনও চিত্রগুলির একটি সেট হলে PDF বা ডকুমেন্ট স্ক্যানারে চিত্রগুলি ব্যবহার করুন৷ + PDF টুল খুলুন এবং আপনার লক্ষ্যের সাথে মেলে এমন অপারেশন চয়ন করুন। + উৎস PDF বা ছবি নির্বাচন করুন, তারপর পৃষ্ঠার ক্রম, ঘূর্ণন, সুরক্ষা বা OCR বিকল্পগুলি পর্যালোচনা করুন। + জেনারেট করা পিডিএফ সংরক্ষণ করুন এবং পৃষ্ঠার সংখ্যা, অর্ডার এবং পঠনযোগ্যতা নিশ্চিত করতে একবার এটি খুলুন। + ছবিগুলিকে পিডিএফে পরিণত করুন + একটি শেয়ারযোগ্য নথিতে স্ক্যান, স্ক্রিনশট, রসিদ বা ফটো একত্রিত করুন + একটি পরিষ্কার নথি তৈরি করুন + ছবিগুলিকে ক্রপ করা, অর্ডার করা এবং ধারাবাহিকভাবে আকার দেওয়া হলে সেগুলি আরও ভাল PDF পৃষ্ঠা হয়ে যায়৷ চিত্র তালিকাটিকে একটি পৃষ্ঠা স্ট্যাকের মতো বিবেচনা করুন: প্রতিটি ঘূর্ণন, মার্জিন এবং পৃষ্ঠা-আকারের পছন্দ চূড়ান্ত নথিটি কতটা পাঠযোগ্য মনে হয় তা প্রভাবিত করে। + পৃষ্ঠার প্রান্ত, ছায়া বা ওরিয়েন্টেশন ভুল হলে প্রথমে ছবি কাটুন বা ঘোরান। + ইচ্ছাকৃত পৃষ্ঠার ক্রম অনুসারে ছবিগুলি নির্বাচন করুন এবং পৃষ্ঠার আকার বা মার্জিন সামঞ্জস্য করুন যদি টুলটি তাদের অফার করে। + পিডিএফ রপ্তানি করুন, তারপর এটি পাঠানোর আগে সমস্ত পৃষ্ঠাগুলি পাঠযোগ্য কিনা তা পরীক্ষা করুন৷ + নথি স্ক্যান করুন + কাগজের পৃষ্ঠাগুলি ক্যাপচার করুন এবং পিডিএফ, ওসিআর বা ভাগ করার জন্য প্রস্তুত করুন + পঠনযোগ্য পৃষ্ঠাগুলি ক্যাপচার করুন + নথি স্ক্যানিং সমতল পৃষ্ঠা, ভাল আলো, এবং সংশোধন দৃষ্টিকোণ সহ সবচেয়ে ভাল কাজ করে। OCR বা PDF রপ্তানির আগে একটি পরিষ্কার স্ক্যান সময় বাঁচায় কারণ পাঠ্যের স্বীকৃতি এবং সংকোচন উভয়ই পৃষ্ঠার গুণমানের উপর নির্ভর করে। + নথিটিকে পর্যাপ্ত আলো এবং ন্যূনতম ছায়া সহ একটি বিপরীত পৃষ্ঠে রাখুন। + সনাক্ত করা কোণগুলি সামঞ্জস্য করুন বা ম্যানুয়ালি ক্রপ করুন যাতে পৃষ্ঠাটি পরিষ্কারভাবে ফ্রেমটি পূরণ করে। + ছবি বা পিডিএফ হিসাবে রপ্তানি করুন, তারপরে আপনার যদি নির্বাচনযোগ্য পাঠ্যের প্রয়োজন হয় তবে OCR চালান। + পিডিএফ ফাইলগুলি সুরক্ষিত বা আনলক করুন + পাসওয়ার্ড সাবধানে ব্যবহার করুন এবং সম্ভব হলে একটি সম্পাদনাযোগ্য উৎস কপি রাখুন + পিডিএফ অ্যাক্সেস নিরাপদে হ্যান্ডেল + সুরক্ষা সরঞ্জামগুলি নিয়ন্ত্রিত ভাগ করে নেওয়ার জন্য দরকারী, তবে পাসওয়ার্ডগুলি হারানো সহজ এবং পুনরুদ্ধার করা কঠিন৷ বিতরণের জন্য কপিগুলি সুরক্ষিত করুন, অরক্ষিত উত্স ফাইলগুলি আলাদাভাবে রাখুন এবং কিছু মুছে ফেলার আগে ফলাফল যাচাই করুন। + PDF এর একটি কপি সুরক্ষিত করুন, আপনার একমাত্র উৎস নথি নয়। + সুরক্ষিত ফাইল শেয়ার করার আগে পাসওয়ার্ডটি নিরাপদ কোথাও সংরক্ষণ করুন। + আপনি যে ফাইলগুলিকে সম্পাদনা করার অনুমতি দিয়েছেন তার জন্যই আনলক ব্যবহার করুন, তারপর আনলক করা অনুলিপিটি সঠিকভাবে খোলে তা যাচাই করুন৷ + পিডিএফ পৃষ্ঠাগুলি সংগঠিত করুন + একত্রিত করুন, বিভক্ত করুন, পুনর্বিন্যাস করুন, ঘোরান, ক্রপ করুন, পৃষ্ঠাগুলি সরান এবং ইচ্ছাকৃতভাবে পৃষ্ঠা নম্বর যোগ করুন + সাজসজ্জার আগে নথির কাঠামো ঠিক করুন + পিডিএফ পৃষ্ঠা সরঞ্জামগুলি একই ক্রমে ব্যবহার করা সবচেয়ে সহজ যে আপনি একটি কাগজের নথি প্রস্তুত করবেন: পৃষ্ঠাগুলি সংগ্রহ করুন, ভুলগুলি সরান, সেগুলিকে ক্রমানুসারে রাখুন, সঠিক অভিযোজন করুন, তারপর পৃষ্ঠা নম্বর বা ওয়াটারমার্কের মতো চূড়ান্ত বিবরণ যুক্ত করুন৷ + ডকুমেন্টটি এখনও বেশ কয়েকটি ফাইলে বিভক্ত হলে প্রথমে মার্জ বা ইমেজ-টু-পিডিএফ ব্যবহার করুন। + পৃষ্ঠা নম্বর, স্বাক্ষর, বা ওয়াটারমার্ক যোগ করার আগে পৃষ্ঠাগুলিকে পুনর্বিন্যাস করুন, ঘোরান, ক্রপ করুন বা সরান৷ + কাঠামোগত সম্পাদনা করার পরে চূড়ান্ত PDF এর পূর্বরূপ দেখুন কারণ একটি অনুপস্থিত বা ঘোরানো পৃষ্ঠা পরে লক্ষ্য করা কঠিন হতে পারে। + পিডিএফ টীকা + দর্শকরা ভিন্নভাবে মন্তব্য এবং চিহ্ন দেখালে টীকাগুলি সরান বা সেগুলিকে সমতল করুন৷ + যা দৃশ্যমান থাকে তা নিয়ন্ত্রণ করুন + টীকাগুলি মন্তব্য, হাইলাইট, অঙ্কন, ফর্ম চিহ্ন বা অন্যান্য অতিরিক্ত PDF বস্তু হতে পারে। কিছু দর্শক সেগুলিকে ভিন্নভাবে প্রদর্শন করে, তাই সেগুলি সরানো বা সমতল করা শেয়ার করা নথিগুলিকে আরও অনুমানযোগ্য করে তুলতে পারে৷ + মন্তব্য, হাইলাইট, বা পর্যালোচনা চিহ্ন শেয়ার করা অনুলিপিতে অন্তর্ভুক্ত করা উচিত নয় তখন টীকাগুলি সরান৷ + যখন দৃশ্যমান চিহ্নগুলি পৃষ্ঠায় থাকা উচিত তখন সমতল করুন কিন্তু সম্পাদনাযোগ্য টীকাগুলির মতো আচরণ করবে না৷ + একটি আসল কপি রাখুন কারণ টীকাগুলি সরানো বা সমতল করা বিপরীত করা কঠিন হতে পারে। + পাঠ্য চিনুন + নির্বাচনযোগ্য OCR ইঞ্জিন এবং ভাষা সহ চিত্রগুলি থেকে পাঠ্য বের করুন + আরও ভাল ওসিআর ফলাফল পান + OCR নির্ভুলতা ছবির তীক্ষ্ণতা, ভাষা ডেটা, বৈসাদৃশ্য এবং পৃষ্ঠার অভিযোজনের উপর নির্ভর করে। সর্বোত্তম ফলাফল সাধারণত প্রথম ছবি প্রস্তুত করার মাধ্যমে আসে: শব্দ দূর করুন, সোজাভাবে ঘোরান, পঠনযোগ্যতা বাড়ান, তারপর পাঠ্য শনাক্ত করুন। + চিত্রটিকে পাঠ্য অঞ্চলে ক্রপ করুন এবং স্বীকৃতির আগে এটিকে সোজাভাবে ঘোরান৷ + সঠিক ভাষা বেছে নিন এবং অ্যাপটি অনুরোধ করলে ভাষার ডেটা ডাউনলোড করুন। + স্বীকৃত পাঠ্যটি অনুলিপি করুন বা ভাগ করুন, তারপর ম্যানুয়ালি নাম, সংখ্যা এবং বিরাম চিহ্ন পরীক্ষা করুন৷ + OCR ভাষা ডেটা বাছুন + স্ক্রিপ্ট, ভাষা এবং ডাউনলোড করা OCR মডেলের সাথে মিল রেখে স্বীকৃতি উন্নত করুন + পাঠ্যের সাথে OCR মিলান + ভুল ওসিআর ভাষা ভাল ফটোগুলিকে খারাপ স্বীকৃতির মতো দেখাতে পারে। স্ক্রিপ্ট, বিরাম চিহ্ন, সংখ্যা এবং মিশ্র নথিগুলির জন্য ভাষার ডেটা গুরুত্বপূর্ণ, তাই ফোন UI-এর ভাষার পরিবর্তে ছবিতে প্রদর্শিত ভাষা বেছে নিন। + শুধু ফোনের ভাষা নয়, ছবিতে যে ভাষা বা স্ক্রিপ্ট দেখা যাচ্ছে সেটি বেছে নিন। + অফলাইনে কাজ করার আগে বা একটি ব্যাচ প্রক্রিয়া করার আগে অনুপস্থিত ডেটা ডাউনলোড করুন। + মিশ্র-ভাষার নথিগুলির জন্য, প্রথমে সবচেয়ে গুরুত্বপূর্ণ ভাষা চালান এবং ম্যানুয়ালি নাম এবং নম্বর পরীক্ষা করুন। + অনুসন্ধানযোগ্য পিডিএফ + ফাইলগুলিতে OCR পাঠ্য লিখুন যাতে স্ক্যান করা পৃষ্ঠাগুলি অনুসন্ধান এবং অনুলিপি করা যায় + স্ক্যানগুলিকে অনুসন্ধানযোগ্য নথিতে পরিণত করুন৷ + OCR ক্লিপবোর্ডে পাঠ্য অনুলিপি করার চেয়ে আরও বেশি কিছু করতে পারে। আপনি যখন একটি ফাইল বা অনুসন্ধানযোগ্য PDF এ স্বীকৃত পাঠ্য লেখেন, তখন পৃষ্ঠার চিত্রটি দৃশ্যমান থাকে যখন পাঠ্য অনুসন্ধান, নির্বাচন, সূচী বা সংরক্ষণাগার সহজ হয়ে যায়। + স্ক্যান করা নথিগুলির জন্য অনুসন্ধানযোগ্য PDF আউটপুট ব্যবহার করুন যা এখনও মূল পৃষ্ঠাগুলির মতো দেখতে হবে৷ + যখন আপনার শুধুমাত্র এক্সট্রাক্ট করা পাঠ্যের প্রয়োজন হয় এবং পৃষ্ঠার চিত্রের বিষয়ে চিন্তা করবেন না তখন লেখা-টু-ফাইল ব্যবহার করুন। + গুরুত্বপূর্ণ সংখ্যা, নাম এবং টেবিলগুলি ম্যানুয়ালি পরীক্ষা করুন কারণ OCR পাঠ্য অনুসন্ধানযোগ্য কিন্তু এখনও অসম্পূর্ণ। + QR কোড স্ক্যান করুন + ক্যামেরা ইনপুট থেকে QR বিষয়বস্তু পড়ুন বা উপলব্ধ ছবি সংরক্ষণ করুন + নিরাপদে কোড পড়ুন + QR কোডগুলিতে লিঙ্ক, Wi-Fi ডেটা, পরিচিতি, পাঠ্য বা অন্যান্য পেলোড থাকতে পারে। + স্ক্যান QR কোড খুলুন এবং কোডটি তীক্ষ্ণ, সমতল এবং সম্পূর্ণ ফ্রেমের ভিতরে রাখুন। + লিঙ্ক খোলার আগে বা সংবেদনশীল ডেটা কপি করার আগে ডিকোড করা বিষয়বস্তু পর্যালোচনা করুন। + স্ক্যানিং ব্যর্থ হলে, আলোর উন্নতি করুন, কোডটি ক্রপ করুন, বা একটি উচ্চ-রেজোলিউশন সোর্স ইমেজ চেষ্টা করুন। + বেস 64 এবং চেকসাম ব্যবহার করুন + চিত্রগুলিকে পাঠ্য হিসাবে এনকোড করুন, পাঠ্যকে চিত্রগুলিতে ডিকোড করুন এবং ফাইলের অখণ্ডতা যাচাই করুন৷ + ফাইল এবং পাঠ্যের মধ্যে সরান + বেস64 ছোট ইমেজ পেলোডের জন্য উপযোগী, যখন চেকসামগুলি নিশ্চিত করতে সাহায্য করে যে ফাইলগুলি পরিবর্তন হয়নি। এই সরঞ্জামগুলি প্রযুক্তিগত কর্মপ্রবাহ, সমর্থন প্রতিবেদন, ফাইল স্থানান্তর এবং বাইনারি ফাইলগুলিকে সাময়িকভাবে পাঠ্য হয়ে ওঠার জন্য সবচেয়ে সহায়ক। + একটি ছোট ইমেজ এনকোড করতে Base64 টুল ব্যবহার করুন যখন একটি ওয়ার্কফ্লোতে একটি বাইনারি ফাইলের পরিবর্তে পাঠ্যের প্রয়োজন হয়। + শুধুমাত্র বিশ্বস্ত উৎস থেকে Base64 ডিকোড করুন এবং সংরক্ষণ করার আগে পূর্বরূপ যাচাই করুন। + রপ্তানি করা ফাইল তুলনা করতে বা আপলোড/ডাউনলোড নিশ্চিত করতে হলে চেকসাম টুল ব্যবহার করুন। + প্যাকেজ বা তথ্য রক্ষা + ফাইল বান্ডলিং বা টেক্সট ডেটা রূপান্তরের জন্য জিপ এবং সাইফার টুল ব্যবহার করুন + সম্পর্কিত ফাইল একসাথে রাখুন + প্যাকেজিং সরঞ্জামগুলি সহায়ক যখন একাধিক আউটপুট একটি ফাইল হিসাবে ভ্রমণ করা উচিত। এগুলি জেনারেট করা ছবি, পিডিএফ, লগ এবং মেটাডেটা একসাথে রাখার জন্যও ভাল যখন আপনাকে একটি সম্পূর্ণ ফলাফল সেট পাঠাতে হবে। + জিপ ব্যবহার করুন যখন আপনাকে অনেক এক্সপোর্ট করা ছবি বা নথি একসাথে পাঠাতে হবে। + আপনি যখন নির্বাচিত পদ্ধতি বুঝতে পারবেন এবং প্রয়োজনীয় কীগুলি নিরাপদে সংরক্ষণ করতে পারবেন শুধুমাত্র তখনই সাইফার টুল ব্যবহার করুন৷ + উৎস ফাইল মুছে ফেলার আগে তৈরি আর্কাইভ বা রূপান্তরিত পাঠ্য পরীক্ষা করুন। + নামে চেকসাম + চেকসাম-ভিত্তিক ফাইলের নাম ব্যবহার করুন যখন স্বতন্ত্রতা এবং অখণ্ডতা পাঠযোগ্যতার চেয়ে বেশি গুরুত্বপূর্ণ + বিষয়বস্তু অনুসারে ফাইলের নাম দিন + চেকসাম ফাইলের নামগুলি উপযোগী হয় যখন রপ্তানিতে ডুপ্লিকেট দৃশ্যমান নাম থাকতে পারে বা যখন আপনাকে প্রমাণ করতে হবে যে দুটি ফাইল অভিন্ন। এগুলি ম্যানুয়াল ব্রাউজিংয়ের জন্য কম বন্ধুত্বপূর্ণ, তাই প্রযুক্তিগত সংরক্ষণাগার এবং যাচাইকরণ কর্মপ্রবাহের জন্য এগুলি ব্যবহার করুন৷ + যখন বিষয়বস্তুর পরিচয় মানব-পাঠযোগ্য শিরোনামের চেয়ে বেশি গুরুত্বপূর্ণ তখন ফাইলের নাম হিসাবে চেকসাম সক্ষম করুন৷ + সংরক্ষণাগার, ডিডপ্লিকেশন, পুনরাবৃত্তিযোগ্য রপ্তানি বা ফাইল যা আবার আপলোড এবং ডাউনলোড করা যেতে পারে তার জন্য এটি ব্যবহার করুন। + ফোল্ডার বা মেটাডেটার সাথে চেকসাম নাম যুক্ত করুন যখন লোকেরা এখনও ফাইলটি কী উপস্থাপন করে তা বুঝতে হবে। + ছবি থেকে রং বাছাই করুন + নমুনা পিক্সেল, রঙের কোড অনুলিপি করুন এবং প্যালেট বা ডিজাইনে রং পুনরায় ব্যবহার করুন + সঠিক রঙের নমুনা + রঙ বাছাই একটি নকশার সাথে মিলে যাওয়া, ব্র্যান্ডের রঙ বের করা বা ছবির মান পরীক্ষা করার জন্য উপযোগী। জুম করা গুরুত্বপূর্ণ কারণ অ্যান্টিলিয়াসিং, শ্যাডো এবং কম্প্রেশন প্রতিবেশী পিক্সেলগুলিকে আপনার প্রত্যাশিত রঙ থেকে কিছুটা আলাদা করতে পারে। + ইমেজ থেকে পিক কালার খুলুন এবং আপনার প্রয়োজনীয় রঙের সাথে একটি উৎস ইমেজ বেছে নিন। + ছোট বিবরণ নমুনা করার আগে জুম ইন করুন যাতে কাছাকাছি পিক্সেল ফলাফল প্রভাবিত না করে। + আপনার গন্তব্যের জন্য প্রয়োজনীয় ফর্ম্যাটে রঙটি অনুলিপি করুন, যেমন HEX, RGB, বা HSV৷ + প্যালেট তৈরি করুন এবং রঙ লাইব্রেরি ব্যবহার করুন + প্যালেট বের করুন, সংরক্ষিত রং ব্রাউজ করুন এবং সামঞ্জস্যপূর্ণ ভিজ্যুয়াল সিস্টেম রাখুন + একটি পুনরায় ব্যবহারযোগ্য প্যালেট তৈরি করুন + প্যালেটগুলি রপ্তানি করা গ্রাফিক্স, ওয়াটারমার্ক এবং অঙ্কনগুলি দৃশ্যত সামঞ্জস্যপূর্ণ রাখতে সাহায্য করে। এগুলি বিশেষভাবে উপযোগী হয় যখন আপনি বারবার স্ক্রিনশট টীকা করেন, ব্র্যান্ডের সম্পদ প্রস্তুত করেন বা বিভিন্ন টুল জুড়ে একই রঙের প্রয়োজন হয়। + যখন আপনি ইতিমধ্যে মানগুলি জানেন তখন একটি চিত্র থেকে একটি প্যালেট তৈরি করুন বা ম্যানুয়ালি রং যোগ করুন। + গুরুত্বপূর্ণ রঙের নাম দিন বা সংগঠিত করুন যাতে আপনি সেগুলি পরে আবার খুঁজে পেতে পারেন। + ড্র, ওয়াটারমার্ক, গ্রেডিয়েন্ট, এসভিজি বা এক্সটার্নাল ডিজাইন টুলে কপি করা মান ব্যবহার করুন। + গ্রেডিয়েন্ট তৈরি করুন + ব্যাকগ্রাউন্ড, স্থানধারক এবং ভিজ্যুয়াল পরীক্ষার জন্য গ্রেডিয়েন্ট ইমেজ তৈরি করুন + রঙ পরিবর্তন নিয়ন্ত্রণ + গ্রেডিয়েন্ট টুলগুলি উপযোগী হয় যখন আপনি বিদ্যমান একটি সম্পাদনা করার পরিবর্তে একটি জেনারেট করা চিত্রের প্রয়োজন হয়। তারা পরিচ্ছন্ন ব্যাকগ্রাউন্ড, স্থানধারক, ওয়ালপেপার, মুখোশ বা বাহ্যিক ডিজাইনের সরঞ্জামগুলির জন্য সাধারণ সম্পদ হয়ে উঠতে পারে। + গ্রেডিয়েন্ট টাইপ নির্বাচন করুন এবং প্রথমে গুরুত্বপূর্ণ রং সেট করুন। + লক্ষ্য ব্যবহারের ক্ষেত্রে দিকনির্দেশ, স্টপ, মসৃণতা এবং আউটপুট আকার সামঞ্জস্য করুন। + গন্তব্যের সাথে মেলে এমন একটি বিন্যাসে রপ্তানি করুন, সাধারণত পরিষ্কার জেনারেটেড গ্রাফিক্সের জন্য PNG। + রঙ অ্যাক্সেসযোগ্যতা + যখন UI পড়া কঠিন হয় তখন গতিশীল রঙ, বৈসাদৃশ্য এবং রঙ-অন্ধ বিকল্পগুলি ব্যবহার করুন৷ + রং ব্যবহার করা সহজ করুন + রঙের সেটিংস আরাম, পঠনযোগ্যতা এবং আপনি কত দ্রুত নিয়ন্ত্রণ স্ক্যান করতে পারেন তা প্রভাবিত করে। টুল চিপ, স্লাইডার বা নির্বাচিত রাজ্যগুলিকে আলাদা করা কঠিন মনে হলে, থিম এবং অ্যাক্সেসিবিলিটি বিকল্পগুলি কেবল অন্ধকার মোড পরিবর্তন করার চেয়ে আরও কার্যকর হতে পারে। + আপনি যখন অ্যাপটিকে বর্তমান ওয়ালপেপার প্যালেট অনুসরণ করতে চান তখন গতিশীল রং ব্যবহার করে দেখুন। + কন্ট্রাস্ট বাড়ান বা স্কিম পরিবর্তন করুন যখন বোতাম এবং সারফেস যথেষ্ট আলাদা না হয়। + কিছু রাজ্য বা উচ্চারণ আলাদা করা কঠিন হলে রঙ-অন্ধ স্কিম বিকল্পগুলি ব্যবহার করুন। + আলফা ব্যাকগ্রাউন্ড + স্বচ্ছ PNG, WebP, এবং অনুরূপ আউটপুটগুলির চারপাশে ব্যবহৃত প্রিভিউ বা ফিল কালার নিয়ন্ত্রণ করুন + স্বচ্ছ পূর্বরূপ বুঝুন + আলফা ফরম্যাটের জন্য পটভূমির রঙ স্বচ্ছ চিত্রগুলিকে পরিদর্শন করা সহজ করে তুলতে পারে, তবে আপনি একটি পূর্বরূপ/পূর্ণ আচরণ পরিবর্তন করছেন বা চূড়ান্ত ফলাফল সমতল করছেন কিনা তা জানা গুরুত্বপূর্ণ। এটি স্টিকার, লোগো এবং ব্যাকগ্রাউন্ড-মুছে ফেলা ছবির জন্য গুরুত্বপূর্ণ। + প্রথমে একটি আলফা-সমর্থক বিন্যাস ব্যবহার করুন, যেমন PNG বা WebP, যখন স্বচ্ছ পিক্সেল রপ্তানি থেকে বেঁচে থাকতে হবে। + যখন স্বচ্ছ প্রান্তগুলি অ্যাপ পৃষ্ঠের বিপরীতে দেখা কঠিন হয় তখন পটভূমির রঙের সেটিংস সামঞ্জস্য করুন। + একটি ছোট পরীক্ষা রপ্তানি করুন এবং স্বচ্ছতা বা নির্বাচিত পূরণ সংরক্ষণ করা হয়েছে কিনা তা নিশ্চিত করতে অন্য অ্যাপে এটি পুনরায় খুলুন। + এআই মডেল পছন্দ + প্রথমে সবচেয়ে বড়টি বেছে না নিয়ে ছবির ধরন এবং ত্রুটি অনুসারে একটি মডেল বেছে নিন + ক্ষতির সাথে মডেলটি মিলান + AI টুলগুলি বিভিন্ন ONNX/ORT মডেল ডাউনলোড বা আমদানি করতে পারে এবং প্রতিটি মডেলকে একটি বিশেষ ধরনের সমস্যার জন্য প্রশিক্ষিত করা হয়। JPEG ব্লক, এনিমে লাইন ক্লিনআপ, ডিনোইসিং, ব্যাকগ্রাউন্ড রিমুভাল, কালারাইজেশন বা আপস্কেলিং এর জন্য একটি মডেল ভুল ইমেজ টাইপ ব্যবহার করলে খারাপ ফলাফল হতে পারে। + ডাউনলোড করার আগে মডেলের বিবরণ পড়ুন; যে মডেলটি আপনার প্রকৃত সমস্যার নাম দেয়, যেমন কম্প্রেশন, নয়েজ, হাফটোন, অস্পষ্টতা বা পটভূমি অপসারণ করে তা বেছে নিন। + একটি নতুন ইমেজ টাইপ পরীক্ষা করার সময় একটি ছোট বা আরও নির্দিষ্ট মডেল দিয়ে শুরু করুন, তারপর ফলাফল যথেষ্ট ভাল না হলেই ভারী মডেলগুলির সাথে তুলনা করুন৷ + শুধুমাত্র আপনি যে মডেলগুলি ব্যবহার করেন তা রাখুন, কারণ ডাউনলোড করা এবং আমদানি করা মডেলগুলি লক্ষণীয় স্টোরেজ স্পেস নিতে পারে৷ + মডেল পরিবার বুঝতে + মডেলের নামগুলি প্রায়শই তাদের লক্ষ্যের দিকে ইঙ্গিত করে: RealESRGAN-স্টাইল মডেলগুলি আপস্কেল, SCUNet এবং FBCNN মডেলগুলি শব্দ বা কম্প্রেশন পরিষ্কার করে, পটভূমি মডেলগুলি মুখোশ তৈরি করে এবং কালারাইজারগুলি গ্রেস্কেল ইনপুটে রঙ যোগ করে। আমদানি করা কাস্টম মডেল শক্তিশালী হতে পারে, কিন্তু তাদের প্রত্যাশিত ইনপুট/আউটপুট আকারের সাথে মেলে এবং সমর্থিত ONNX বা ORT ফর্ম্যাট ব্যবহার করা উচিত। + যখন আপনার আরও পিক্সেলের প্রয়োজন হয় তখন আপস্কেলার ব্যবহার করুন, চিত্রটি দানাদার হলে ডিনোইজার এবং আর্টিফ্যাক্ট রিমুভার ব্যবহার করুন যখন কম্প্রেশন ব্লক বা ব্যান্ডিং প্রধান সমস্যা। + শুধুমাত্র বিষয় পৃথকীকরণের জন্য ব্যাকগ্রাউন্ড মডেল ব্যবহার করুন; এগুলি সাধারণ বস্তু অপসারণ বা চিত্র বর্ধনের মতো নয়। + শুধুমাত্র আপনার বিশ্বস্ত উত্স থেকে কাস্টম মডেল আমদানি করুন এবং গুরুত্বপূর্ণ ফাইলগুলি ব্যবহার করার আগে একটি ছোট ছবিতে পরীক্ষা করুন৷ + AI পরামিতি + গুণমান, গতি এবং মেমরির ভারসাম্য রাখতে খণ্ডের আকার, ওভারল্যাপ এবং সমান্তরাল কর্মীদের সমন্বয় করুন + স্থিতিশীলতার সাথে গতির ভারসাম্য + মডেল দ্বারা প্রক্রিয়া করার আগে খণ্ডের আকার কতটা বড় ছবি টুকরা তা নিয়ন্ত্রণ করে। ওভারল্যাপ সীমানা লুকানোর জন্য প্রতিবেশী অংশগুলিকে মিশ্রিত করে। সমান্তরাল কর্মীরা প্রক্রিয়াকরণের গতি বাড়াতে পারে, কিন্তু প্রতিটি কর্মীর মেমরির প্রয়োজন হয়, তাই উচ্চ মান সীমিত RAM সহ ফোনে বড় ছবিগুলিকে অস্থির করে তুলতে পারে। + মসৃণ প্রেক্ষাপটের জন্য বৃহত্তর অংশগুলি ব্যবহার করুন যখন আপনার ডিভাইসটি মডেলটিকে নির্ভরযোগ্যভাবে পরিচালনা করে এবং চিত্রটি বিশাল না হয়৷ + খণ্ড সীমানা দৃশ্যমান হলে ওভারল্যাপ বাড়ান, কিন্তু মনে রাখবেন যে বেশি ওভারল্যাপ মানে আরও বারবার কাজ। + শুধুমাত্র একটি স্থিতিশীল পরীক্ষার পরে সমান্তরাল শ্রমিকদের বাড়ান; প্রক্রিয়াকরণ ধীর হয়ে গেলে, ডিভাইস গরম করলে বা ক্র্যাশ হলে প্রথমে কর্মীদের কমিয়ে দিন। + খণ্ড শিল্পকর্ম এড়িয়ে চলুন + Chunking অ্যাপটিকে মডেলের চেয়ে বড় ছবিগুলিকে একবারে আরামদায়কভাবে পরিচালনা করতে দেয়৷ ট্রেডঅফ হল যে প্রতিটি টাইলের কম পারিপার্শ্বিক প্রসঙ্গ রয়েছে, তাই কম ওভারল্যাপ বা খুব ছোট খণ্ডগুলি দৃশ্যমান সীমানা, টেক্সচার পরিবর্তন, বা প্রতিবেশী এলাকার মধ্যে অসঙ্গত বিবরণ তৈরি করতে পারে। + ওভারল্যাপ বাড়ান যদি আপনি আয়তক্ষেত্রাকার সীমানা, পুনরাবৃত্ত টেক্সচার পরিবর্তন বা প্রক্রিয়াকৃত অঞ্চলের মধ্যে বিস্তারিত লাফ দেখতে পান। + আউটপুট তৈরি করার আগে প্রক্রিয়াটি ব্যর্থ হলে খণ্ডের আকার হ্রাস করুন, বিশেষত বড় ছবি বা ভারী মডেলগুলিতে। + সম্পূর্ণ চিত্র প্রক্রিয়া করার আগে একটি ছোট ক্রপ পরীক্ষা সংরক্ষণ করুন যাতে আপনি দ্রুত পরামিতিগুলি তুলনা করতে পারেন। + এআই স্থিতিশীলতা + AI প্রসেসিং ক্র্যাশ বা হিমায়িত হলে নিম্ন অংশের আকার, কর্মী এবং উত্স রেজোলিউশন + ভারী AI কাজগুলি থেকে পুনরুদ্ধার করুন + এআই প্রসেসিং অ্যাপের সবচেয়ে ভারী ওয়ার্কফ্লোগুলির মধ্যে একটি। ক্র্যাশ, ব্যর্থ সেশন, ফ্রিজ, বা হঠাৎ অ্যাপ রিস্টার্ট মানে সাধারণত মডেল, সোর্স রেজোলিউশন, খণ্ডের আকার, বা সমান্তরাল কর্মীদের সংখ্যা বর্তমান ডিভাইসের অবস্থার জন্য খুব বেশি চাহিদা। + অ্যাপটি ক্র্যাশ হলে বা মডেল সেশন খুলতে ব্যর্থ হলে, সমান্তরাল কর্মী এবং খণ্ডের আকার কম হলে, একই চিত্র আবার চেষ্টা করুন। + AI প্রক্রিয়াকরণের আগে খুব বড় ছবিগুলির আকার পরিবর্তন করুন যখন লক্ষ্যটির মূল রেজোলিউশনের প্রয়োজন হয় না। + অন্যান্য ভারী অ্যাপ বন্ধ করুন, একটি ছোট মডেল ব্যবহার করুন, অথবা মেমরির চাপ ফিরে আসার সময় একবারে কম ফাইল প্রক্রিয়া করুন। + মডেল ব্যর্থতা নির্ণয় + একটি ব্যর্থ AI রানের অর্থ এই নয় যে চিত্রটি খারাপ। মডেল ফাইলটি অসম্পূর্ণ, অসমর্থিত, মেমরির জন্য খুব বড়, বা অপারেশনের সাথে অমিল হতে পারে। যখন অ্যাপটি একটি ব্যর্থ সেশনের প্রতিবেদন করে, প্রথমে মডেল এবং পরামিতিগুলিকে সরল করার জন্য এটিকে একটি সংকেত হিসাবে বিবেচনা করুন৷ + একটি মডেল মুছুন এবং পুনরায় ডাউনলোড করুন যদি এটি ডাউনলোডের সাথে সাথে ব্যর্থ হয় বা ফাইলটি দূষিত হওয়ার মতো আচরণ করে। + একটি আমদানি করা কাস্টম মডেলকে দোষারোপ করার আগে একটি অন্তর্নির্মিত ডাউনলোডযোগ্য মডেল ব্যবহার করে দেখুন, কারণ আমদানি করা মডেলগুলিতে বেমানান ইনপুট থাকতে পারে৷ + যদি আপনি একটি ক্র্যাশ বা সেশন ত্রুটি রিপোর্ট করতে চান তাহলে ব্যর্থতার পুনরুত্পাদন করার পরে অ্যাপ লগ ব্যবহার করুন৷ + Shader স্টুডিওতে পরীক্ষা + উন্নত ইমেজ প্রসেসিং এবং কাস্টম লুকের জন্য GPU শেডার ইফেক্ট ব্যবহার করুন + shaders সঙ্গে সাবধানে কাজ + Shader স্টুডিও শক্তিশালী, কিন্তু shader কোড এবং পরামিতি ছোট, পরীক্ষাযোগ্য পরিবর্তন প্রয়োজন। এটিকে একটি পরীক্ষামূলক সরঞ্জামের মতো আচরণ করুন: একটি কার্যকরী বেসলাইন রাখুন, একটি সময়ে একটি জিনিস পরিবর্তন করুন এবং একটি প্রিসেট বিশ্বাস করার আগে বিভিন্ন চিত্রের আকার দিয়ে পরীক্ষা করুন৷ + পরামিতি যোগ করার আগে একটি পরিচিত কাজ শেডার বা একটি সাধারণ প্রভাব থেকে শুরু করুন। + একবারে একটি প্যারামিটার বা কোড ব্লক পরিবর্তন করুন এবং ত্রুটিগুলির জন্য পূর্বরূপ পরীক্ষা করুন। + কয়েকটি ভিন্ন চিত্রের আকারে পরীক্ষা করার পরেই প্রিসেট রপ্তানি করুন। + SVG বা ASCII শিল্প তৈরি করুন + সৃজনশীল আউটপুটের জন্য ভেক্টর-সদৃশ সম্পদ বা পাঠ্য-ভিত্তিক চিত্র তৈরি করুন + সৃজনশীল আউটপুট প্রকার নির্বাচন করুন + SVG এবং ASCII টুলগুলি বিভিন্ন টার্গেট পরিবেশন করে: মাপযোগ্য গ্রাফিক্স বা টেক্সট-স্টাইল রেন্ডারিং। উভয়ই সৃজনশীল রূপান্তর, তাই একটি ছোট থাম্বনেল থেকে ফলাফল বিচার করার চেয়ে চূড়ান্ত আকারে পূর্বরূপ দেখা আরও গুরুত্বপূর্ণ৷ + SVG মেকার ব্যবহার করুন যখন ফলাফল পরিষ্কারভাবে স্কেল করা উচিত বা ডিজাইন ওয়ার্কফ্লোতে পুনরায় ব্যবহার করা উচিত। + আপনি যখন একটি চিত্রের একটি স্টাইলাইজড টেক্সট উপস্থাপনা চান তখন ASCII আর্ট ব্যবহার করুন। + চূড়ান্ত আকারে পূর্বরূপ দেখুন কারণ তৈরি আউটপুটে ছোট বিবরণ অদৃশ্য হয়ে যেতে পারে। + নয়েজ টেক্সচার তৈরি করুন + ব্যাকগ্রাউন্ড, মাস্ক, ওভারলে বা পরীক্ষার জন্য পদ্ধতিগত টেক্সচার তৈরি করুন + পদ্ধতিগত আউটপুট টিউন করুন + নয়েজ জেনারেশন পরামিতি থেকে নতুন ছবি তৈরি করে, তাই ছোট পরিবর্তনগুলি খুব ভিন্ন ফলাফল দিতে পারে। এটি টেক্সচার, মাস্ক, ওভারলে, স্থানধারক, বা বিস্তারিত নিদর্শন সহ কম্প্রেশন পরীক্ষা করার জন্য দরকারী। + সূক্ষ্ম বিবরণ টিউন করার আগে একটি শব্দের ধরন এবং আউটপুট আকার চয়ন করুন। + প্যাটার্ন টার্গেট ব্যবহার ফিট না হওয়া পর্যন্ত স্কেল, তীব্রতা, রং, বা বীজ সামঞ্জস্য করুন। + দরকারী ফলাফলগুলি সংরক্ষণ করুন এবং পরামিতিগুলি লিখুন যদি আপনি পরে টেক্সচারটি পুনরায় তৈরি করতে চান। + মার্কআপ প্রকল্প + অ্যাপটি বন্ধ করার পরে যখন টীকাগুলি সম্পাদনাযোগ্য থাকতে হবে তখন প্রকল্প ফাইলগুলি ব্যবহার করুন৷ + স্তরযুক্ত সম্পাদনাগুলি পুনরায় ব্যবহারযোগ্য রাখুন + মার্কআপ প্রজেক্ট ফাইলগুলি উপযোগী হয় যখন একটি স্ক্রিনশট, ডায়াগ্রাম বা নির্দেশনা ইমেজ পরে পরিবর্তনের প্রয়োজন হতে পারে। রপ্তানি করা PNG/JPEG ফাইলগুলি চূড়ান্ত চিত্র, যখন প্রকল্প ফাইলগুলি ভবিষ্যতের সামঞ্জস্যের জন্য সম্পাদনাযোগ্য স্তর এবং সম্পাদনা অবস্থা সংরক্ষণ করে। + যখন টীকা, কলআউট বা লেআউট উপাদানগুলি সম্পাদনাযোগ্য থাকা উচিত তখন মার্কআপ স্তরগুলি ব্যবহার করুন৷ + আপনি যদি আরও সংশোধন আশা করেন তবে একটি চূড়ান্ত চিত্র রপ্তানি করার আগে প্রকল্প ফাইলটি সংরক্ষণ করুন বা পুনরায় খুলুন৷ + যখন আপনি একটি চ্যাপ্টা চূড়ান্ত সংস্করণ ভাগ করতে প্রস্তুত তখনই একটি সাধারণ চিত্র রপ্তানি করুন৷ + ফাইল এনক্রিপ্ট করুন + কোনো সোর্স কপি মুছে ফেলার আগে পাসওয়ার্ড দিয়ে ফাইল সুরক্ষিত করুন এবং ডিক্রিপশন পরীক্ষা করুন + এনক্রিপ্ট করা আউটপুট সাবধানে পরিচালনা করুন + সাইফার টুলগুলি পাসওয়ার্ড-ভিত্তিক এনক্রিপশনের সাহায্যে ফাইলগুলিকে রক্ষা করতে পারে, কিন্তু তারা কী মনে রাখার বা ব্যাকআপ রাখার জন্য প্রতিস্থাপন নয়। এনক্রিপশন পরিবহন এবং সঞ্চয়স্থানের জন্য উপযোগী, যখন জিপ ভাল হয় যখন মূল লক্ষ্যটি বেশ কয়েকটি আউটপুট বান্ডিল করা হয়। + প্রথমে একটি কপি এনক্রিপ্ট করুন এবং আসলটি রাখুন যতক্ষণ না আপনি পরীক্ষা করছেন যে ডিক্রিপশন আপনার সংরক্ষিত পাসওয়ার্ডের সাথে কাজ করে। + একটি পাসওয়ার্ড ম্যানেজার বা চাবির জন্য অন্য নিরাপদ জায়গা ব্যবহার করুন; অ্যাপটি আপনার জন্য হারিয়ে যাওয়া পাসওয়ার্ড অনুমান করতে পারে না। + এনক্রিপ্ট করা ফাইলগুলি শুধুমাত্র সেই লোকেদের সাথে শেয়ার করুন যাদের কাছে পাসওয়ার্ড আছে এবং কোন টুল বা পদ্ধতি তাদের ডিক্রিপ্ট করা উচিত তা জানে৷ + সরঞ্জাম সাজান + গ্রুপ, অর্ডার, অনুসন্ধান, এবং পিন টুল যাতে প্রধান স্ক্রীন আপনার বাস্তব কর্মপ্রবাহের সাথে মেলে + প্রধান পর্দা দ্রুত করুন + আপনি কোন সরঞ্জামগুলি সবচেয়ে বেশি ব্যবহার করেন তা জানলে টুল বিন্যাস সেটিংস টিউন করার যোগ্য। গ্রুপিং অন্বেষণে সাহায্য করে, কাস্টম অর্ডার পেশী মেমরিতে সাহায্য করে, এবং পছন্দগুলি দৈনিক সরঞ্জামগুলিকে পৌঁছানোর যোগ্য রাখে এমনকি সম্পূর্ণ তালিকা বড় হয়ে গেলেও৷ + অ্যাপ শেখার সময় বা যখন আপনি টাস্ক টাইপ দ্বারা সংগঠিত টুল পছন্দ করেন তখন গ্রুপ মোড ব্যবহার করুন। + যখন আপনি ইতিমধ্যেই আপনার ঘন ঘন সরঞ্জামগুলি জানেন এবং কম স্ক্রোল চান তখন কাস্টম অর্ডারে স্যুইচ করুন৷ + বিরল সরঞ্জামগুলির জন্য অনুসন্ধান সেটিংস এবং পছন্দগুলি একসাথে ব্যবহার করুন যা আপনি নাম দ্বারা মনে রাখেন কিন্তু সব সময় দৃশ্যমান চান না৷ + বড় ফাইলগুলি পরিচালনা করুন + ধীরগতির রপ্তানি, মেমরির চাপ এবং অপ্রত্যাশিতভাবে বড় আউটপুট এড়িয়ে চলুন + রপ্তানির আগে কাজ কমিয়ে দিন + খুব বড় ছবি, লম্বা ব্যাচ এবং উচ্চ-মানের ফর্ম্যাটগুলি আরও মেমরি এবং সময় নিতে পারে। সবচেয়ে দ্রুত সমাধান হল সবচেয়ে ভারী অপারেশনের আগে কাজের পরিমাণ কমানো, একই বড় আকারের ইনপুট শেষ হওয়ার জন্য অপেক্ষা না করে। + ভারী ফিল্টার, ওসিআর, বা পিডিএফ রূপান্তর প্রয়োগ করার আগে বিশাল চিত্রগুলির আকার পরিবর্তন করুন। + সেটিংস নিশ্চিত করতে প্রথমে একটি ছোট ব্যাচ ব্যবহার করুন, তারপর একই প্রিসেট দিয়ে বাকিগুলি প্রক্রিয়া করুন। + আউটপুট ফাইল প্রত্যাশার চেয়ে বড় হলে নিম্ন মানের বা বিন্যাস পরিবর্তন করুন। + ক্যাশে এবং প্রিভিউ + জেনারেট করা প্রিভিউ, ক্যাশে ক্লিনআপ এবং ভারী সেশনের পরে স্টোরেজ ব্যবহার বুঝুন + স্টোরেজ এবং গতি ভারসাম্য বজায় রাখুন + প্রিভিউ জেনারেশন এবং ক্যাশে বারবার ব্রাউজিংকে দ্রুত করতে পারে, কিন্তু ভারী সম্পাদনা সেশনগুলি অস্থায়ী ডেটা পিছনে ফেলে যেতে পারে। ক্যাশে সেটিংস সাহায্য করে যখন অ্যাপটি ধীর মনে হয়, সঞ্চয়স্থান আঁটসাঁট হয় বা অনেক পরীক্ষা-নিরীক্ষার পরে প্রিভিউ বাসি দেখায়। + অস্থায়ী সঞ্চয়স্থান কমানোর চেয়ে অনেকগুলি ছবি ব্রাউজ করার সময় পূর্বরূপ সক্রিয় রাখুন৷ + বড় ব্যাচ, ব্যর্থ পরীক্ষা, বা অনেক উচ্চ-রেজোলিউশন ফাইল সহ দীর্ঘ সেশনের পরে ক্যাশে সাফ করুন। + আপনি লঞ্চের মধ্যে প্রিভিউ গরম রাখার চেয়ে স্টোরেজ ক্লিনআপ পছন্দ করলে স্বয়ংক্রিয় ক্যাশে ক্লিয়ারিং ব্যবহার করুন। + পর্দার আচরণ সামঞ্জস্য করুন + ফোকাসড কাজের জন্য ফুলস্ক্রিন, সুরক্ষিত মোড, উজ্জ্বলতা এবং সিস্টেম বার বিকল্পগুলি ব্যবহার করুন + ইন্টারফেসটিকে পরিস্থিতির সাথে মানানসই করুন + আপনি যখন ল্যান্ডস্কেপে সম্পাদনা করেন, ব্যক্তিগত ছবি উপস্থাপন করেন বা সামঞ্জস্যপূর্ণ উজ্জ্বলতার প্রয়োজন হয় তখন স্ক্রীন সেটিংস সাহায্য করে। এগুলি স্বাভাবিক ব্যবহারের জন্য প্রয়োজন হয় না, তবে তারা QR পরিদর্শন, ব্যক্তিগত পূর্বরূপ বা সঙ্কুচিত ল্যান্ডস্কেপ সম্পাদনার মতো বিশ্রী পরিস্থিতির সমাধান করে। + পূর্ণস্ক্রীন বা সিস্টেম বার সেটিংস ব্যবহার করুন যখন স্থান সম্পাদনা নেভিগেশন ক্রোমের চেয়ে বেশি গুরুত্বপূর্ণ। + যখন ব্যক্তিগত ছবি স্ক্রিনশট বা অ্যাপ প্রিভিউতে প্রদর্শিত হবে না তখন নিরাপদ মোড ব্যবহার করুন। + এমন সরঞ্জামগুলির জন্য উজ্জ্বলতা প্রয়োগ করুন যেখানে অন্ধকার ছবি বা QR কোডগুলি পরীক্ষা করা কঠিন। + স্বচ্ছতা বজায় রাখুন + স্বচ্ছ ব্যাকগ্রাউন্ডকে সাদা, কালো বা চ্যাপ্টা হতে বাধা দিন + আলফা-সচেতন আউটপুট ব্যবহার করুন + স্বচ্ছতা তখনই টিকে থাকে যখন নির্বাচিত টুল এবং আউটপুট ফরম্যাট আলফা সমর্থন করে। যদি একটি রপ্তানি করা ব্যাকগ্রাউন্ড সাদা বা কালো হয়ে যায়, তাহলে সমস্যাটি সাধারণত আউটপুট ফর্ম্যাট, একটি ফিল/ব্যাকগ্রাউন্ড সেটিং, বা একটি গন্তব্য অ্যাপ যা ছবিটিকে সমতল করে। + ব্যাকগ্রাউন্ড-মুছে ফেলা ছবি, স্টিকার, লোগো বা ওভারলে এক্সপোর্ট করার সময় PNG বা WebP বেছে নিন। + স্বচ্ছ আউটপুটের জন্য JPEG এড়িয়ে চলুন কারণ এটি একটি আলফা চ্যানেল সংরক্ষণ করে না। + আলফা ফরম্যাটের জন্য পটভূমির রঙ সম্পর্কিত সেটিংস চেক করুন যদি পূর্বরূপ একটি ভরাট ব্যাকগ্রাউন্ড দেখায়। + শেয়ারিং এবং ইম্পোর্ট সমস্যা ঠিক করুন + যখন ফাইলগুলি উপস্থিত হয় না বা সঠিকভাবে খোলা হয় না তখন পিকার সেটিংস এবং সমর্থিত ফর্ম্যাটগুলি ব্যবহার করুন৷ + অ্যাপটিতে ফাইলটি পান + আমদানি সমস্যা প্রায়ই প্রদানকারীর অনুমতি, অসমর্থিত বিন্যাস, বা চয়নকারী আচরণ দ্বারা সৃষ্ট হয়। ক্লাউড অ্যাপস এবং মেসেঞ্জার থেকে শেয়ার করা ফাইলগুলি অস্থায়ী হতে পারে, তাই একটি স্থায়ী উত্স বাছাই করা দীর্ঘ সম্পাদনার জন্য আরও নির্ভরযোগ্য হতে পারে। + সাম্প্রতিক ফাইল শর্টকাটের পরিবর্তে সিস্টেম পিকারের মাধ্যমে ফাইলটি খোলার চেষ্টা করুন। + যদি একটি ফর্ম্যাট একটি টুল দ্বারা সমর্থিত না হয়, প্রথমে এটি রূপান্তর করুন বা একটি আরো নির্দিষ্ট টুল খুলুন। + ছবি পিকার এবং লঞ্চার সেটিংস পর্যালোচনা করুন যদি শেয়ার প্রবাহ বা সরাসরি টুল ওপেনিং প্রত্যাশার চেয়ে ভিন্ন আচরণ করে। + প্রস্থান নিশ্চিতকরণ + অসংরক্ষিত সম্পাদনাগুলি হারানো এড়িয়ে চলুন যখন অঙ্গভঙ্গি, ব্যাক প্রেস বা টুল সুইচ দুর্ঘটনাক্রমে ঘটে + অসমাপ্ত কাজ রক্ষা করুন + আপনি যখন অঙ্গভঙ্গি নেভিগেশন ব্যবহার করেন, বড় ফাইল সম্পাদনা করেন বা প্রায়শই অ্যাপগুলির মধ্যে স্যুইচ করেন তখন টুল প্রস্থান নিশ্চিতকরণ সহায়ক। এটি একটি টুল ছাড়ার আগে একটি ছোট বিরতি যোগ করে যাতে অসমাপ্ত সেটিংস এবং প্রিভিউ দুর্ঘটনাক্রমে হারিয়ে না যায়। + আপনি রপ্তানি করার আগে যদি দুর্ঘটনাজনিত পিছনের অঙ্গভঙ্গিগুলি কখনও কোনও সরঞ্জাম বন্ধ করে থাকে তবে নিশ্চিতকরণ সক্ষম করুন৷ + আপনি যদি বেশিরভাগ দ্রুত এক-পদক্ষেপ রূপান্তর করেন এবং দ্রুত নেভিগেশন পছন্দ করেন তবে এটি অক্ষম রাখুন। + দীর্ঘ কর্মপ্রবাহের জন্য প্রিসেটের সাথে এটি ব্যবহার করুন যেখানে সেটিংস পুনর্নির্মাণ বিরক্তিকর হবে। + সমস্যা রিপোর্ট করার সময় লগ ব্যবহার করুন + একটি সমস্যা খোলার আগে বা বিকাশকারীর সাথে যোগাযোগ করার আগে দরকারী বিবরণ সংগ্রহ করুন + প্রসঙ্গ সহ সমস্যাগুলি রিপোর্ট করুন + ধাপ, অ্যাপ সংস্করণ, ইনপুট প্রকার এবং লগ সহ একটি স্পষ্ট প্রতিবেদন নির্ণয় করা অনেক সহজ। লগগুলি কোনও সমস্যা পুনরুত্পাদনের পরেই সবচেয়ে কার্যকর কারণ তারা আশেপাশের প্রসঙ্গকে তাজা রাখে৷ + টুলের নাম, সঠিক ক্রিয়া এবং এটি একটি ফাইল বা প্রতিটি ফাইলের সাথে ঘটে কিনা তা নোট করুন। + সমস্যাটি পুনরুত্পাদন করার পরে অ্যাপ লগ খুলুন এবং সম্ভব হলে প্রাসঙ্গিক লগ আউটপুট শেয়ার করুন। + স্ক্রিনশট বা নমুনা ফাইলগুলি শুধুমাত্র তখনই সংযুক্ত করুন যখন সেগুলিতে ব্যক্তিগত তথ্য থাকবে না৷ + পটভূমি প্রক্রিয়াকরণ বন্ধ করা হয়েছে + অ্যান্ড্রয়েড সময়মতো ব্যাকগ্রাউন্ড প্রসেসিং নোটিফিকেশন শুরু হতে দেয়নি। এটি একটি সিস্টেম টাইমিং সীমাবদ্ধতা যা নির্ভরযোগ্যভাবে স্থির করা যায় না। অ্যাপটি পুনরায় চালু করুন এবং আবার চেষ্টা করুন; অ্যাপটি খোলা রাখা এবং বিজ্ঞপ্তি বা ব্যাকগ্রাউন্ড কাজের অনুমতি দেওয়া সাহায্য করতে পারে। + ফাইল বাছাই এড়িয়ে যান + যখন ইনপুট সাধারণত শেয়ার, ক্লিপবোর্ড বা পূর্ববর্তী স্ক্রীন থেকে আসে তখন টুলগুলি দ্রুত খুলুন + পুনরাবৃত্ত টুল দ্রুত লঞ্চ করুন + Skip File Picking সমর্থিত টুলের প্রথম ধাপ পরিবর্তন করে। এটি কার্যকর হয় যখন আপনি সাধারণত একটি ইতিমধ্যে নির্বাচিত চিত্র সহ একটি টুল প্রবেশ করেন বা Android শেয়ার ক্রিয়াগুলি থেকে, কিন্তু আপনি যদি প্রতিটি টুল অবিলম্বে একটি ফাইলের জন্য জিজ্ঞাসা করার আশা করেন তবে এটি বিভ্রান্তিকর বোধ করতে পারে। + টুল খোলার আগে আপনার স্বাভাবিক প্রবাহ ইতিমধ্যেই উৎস চিত্র প্রদান করে তখনই এটি সক্ষম করুন। + অ্যাপটি শেখার সময় এটিকে অক্ষম রাখুন, কারণ স্পষ্ট বাছাই প্রতিটি টুলকে বোঝা সহজ করে তোলে। + যদি কোনও সরঞ্জাম কোনও স্পষ্ট ইনপুট ছাড়াই খোলে, এই সেটিংটি অক্ষম করুন বা শেয়ার/ওপেন উইথ থেকে শুরু করুন যাতে Android প্রথমে ফাইলটি পাস করে। + প্রথমে সম্পাদক খুলুন + যখন একটি ভাগ করা ছবি সরাসরি সম্পাদনায় যেতে হবে তখন পূর্বরূপ এড়িয়ে যান + প্রথম স্টপ হিসাবে পূর্বরূপ বা সম্পাদনা চয়ন করুন৷ + পূর্বরূপের পরিবর্তে সম্পাদনা খুলুন এমন ব্যবহারকারীদের জন্য যারা ইতিমধ্যে জানেন যে তারা চিত্রটি পরিবর্তন করতে চান৷ আপনি যখন চ্যাট বা ক্লাউড স্টোরেজ থেকে ফাইলগুলি পরিদর্শন করেন তখন পূর্বরূপ নিরাপদ হয়; সরাসরি সম্পাদনা স্ক্রিনশট, দ্রুত ক্রপ, টীকা এবং এককালীন সংশোধনের জন্য দ্রুততর। + অধিকাংশ শেয়ার করা ছবি অবিলম্বে ক্রপ, মার্কআপ, ফিল্টার, বা রপ্তানি পরিবর্তন প্রয়োজন হলে সরাসরি সম্পাদনা সক্ষম করুন। + সিদ্ধান্ত নেওয়ার আগে আপনি যখন প্রায়শই মেটাডেটা, মাত্রা, স্বচ্ছতা বা ছবির গুণমান পরীক্ষা করেন তখন প্রথমে পূর্বরূপ ছেড়ে যান। + এটি পছন্দের সাথে একসাথে ব্যবহার করুন যাতে ভাগ করা ছবিগুলি আপনি আসলে যে সরঞ্জামগুলি ব্যবহার করেন তার কাছাকাছি আসে৷ + প্রিসেট টেক্সট এন্ট্রি + বারবার নিয়ন্ত্রণ সামঞ্জস্য করার পরিবর্তে সঠিক প্রিসেট মান লিখুন + স্লাইডার ধীর হলে সুনির্দিষ্ট মান ব্যবহার করুন + প্রিসেট টেক্সট এন্ট্রি সাহায্য করে যখন আপনার বারবার কাজের জন্য সঠিক সংখ্যার প্রয়োজন হয়: সামাজিক চিত্রের আকার, নথির মার্জিন, কম্প্রেশন লক্ষ্য, লাইন প্রস্থ, বা অন্যান্য মান যা অঙ্গভঙ্গির সাথে পৌঁছাতে বিরক্তিকর। এটি ছোট স্ক্রিনেও কার্যকর যেখানে সূক্ষ্ম স্লাইডার চলাচল কঠিন। + টেক্সট এন্ট্রি সক্ষম করুন যদি আপনি প্রায়ই সঠিক মাপ, শতাংশ, গুণমান মান, বা টুল প্যারামিটার পুনরায় ব্যবহার করেন। + একবার টাইপ করার পরে মানটিকে একটি প্রিসেট হিসাবে সংরক্ষণ করুন, তারপর একই সেটআপ পুনর্নির্মাণের পরিবর্তে এটি পুনরায় ব্যবহার করুন। + রুক্ষ ভিজ্যুয়াল টিউনিং এবং কঠোর আউটপুট প্রয়োজনীয়তার জন্য টাইপ করা মানগুলির জন্য অঙ্গভঙ্গি নিয়ন্ত্রণ রাখুন। + মূল কাছাকাছি সংরক্ষণ করুন + ফোল্ডারের প্রসঙ্গ গুরুত্বপূর্ণ হলে উত্পন্ন ফাইলগুলিকে উত্স চিত্রগুলির পাশে রাখুন৷ + তাদের উত্সের সাথে আউটপুট রাখুন + সেভ টু অরিজিনাল ফোল্ডার ক্যামেরা ফোল্ডার, প্রোজেক্ট ফোল্ডার, ডকুমেন্ট স্ক্যান এবং ক্লায়েন্ট ব্যাচের জন্য সহজ যেখানে সম্পাদিত কপি উৎসের পাশে থাকা উচিত। এটি একটি ডেডিকেটেড আউটপুট ফোল্ডারের চেয়ে কম পরিপাটি হতে পারে, তাই এটিকে পরিষ্কার ফাইলের নাম প্রত্যয় বা নিদর্শনগুলির সাথে একত্রিত করুন৷ + প্রতিটি উৎস ফোল্ডার ইতিমধ্যেই অর্থপূর্ণ হলে এটি সক্ষম করুন এবং আউটপুটগুলি সেখানে গোষ্ঠীবদ্ধ থাকা উচিত। + ফাইলের নাম প্রত্যয়, উপসর্গ, টাইমস্ট্যাম্প বা প্যাটার্ন ব্যবহার করুন যাতে সম্পাদিত অনুলিপিগুলি মূল থেকে আলাদা করা সহজ হয়। + মিশ্র ব্যাচের জন্য এটি নিষ্ক্রিয় করুন যখন আপনি একটি রপ্তানি ফোল্ডারে সমস্ত তৈরি করা ফাইল সংগ্রহ করতে চান। + বড় আউটপুট এড়িয়ে যান + অপ্রত্যাশিতভাবে উৎসের চেয়ে ভারী হয়ে যাওয়া রূপান্তরগুলি সংরক্ষণ করা এড়িয়ে চলুন + খারাপ আকারের বিস্ময় থেকে ব্যাচগুলিকে রক্ষা করুন + কিছু রূপান্তর ফাইলগুলিকে বড় করে, বিশেষ করে PNG স্ক্রিনশট, ইতিমধ্যে সংকুচিত ফটো, উচ্চ-মানের WebP, বা মেটাডেটা সংরক্ষিত ছবিগুলি। এড়িয়ে যাওয়ার অনুমতি দিন যদি বড় সেই আউটপুটগুলিকে ওয়ার্কফ্লোতে একটি ছোট উত্স প্রতিস্থাপন করতে বাধা দেয় যেখানে আকার হ্রাস করাই প্রধান লক্ষ্য। + রপ্তানি যখন কম্প্রেস, পুনরায় আকার, বা আপলোড সীমার জন্য ফাইল প্রস্তুত করার জন্য বোঝানো হয় তখন এটি সক্ষম করুন৷ + একটি ব্যাচের পরে এড়িয়ে যাওয়া ফাইলগুলি পর্যালোচনা করুন; তারা ইতিমধ্যে অপ্টিমাইজ করা হতে পারে বা একটি ভিন্ন বিন্যাস প্রয়োজন হতে পারে. + যখন গুণমান, স্বচ্ছতা, বা বিন্যাস সামঞ্জস্য চূড়ান্ত ফাইল আকারের চেয়ে বেশি গুরুত্বপূর্ণ তখন এটি অক্ষম করুন। + ক্লিপবোর্ড এবং লিঙ্ক + দ্রুত পাঠ্য-ভিত্তিক সরঞ্জামগুলির জন্য স্বয়ংক্রিয় ক্লিপবোর্ড ইনপুট এবং লিঙ্ক প্রিভিউ নিয়ন্ত্রণ করুন + স্বয়ংক্রিয় ইনপুট অনুমানযোগ্য করুন + ক্লিপবোর্ড এবং লিঙ্ক সেটিংস QR, Base64, URL এবং টেক্সট-ভারী ওয়ার্কফ্লোগুলির জন্য সুবিধাজনক, কিন্তু স্বয়ংক্রিয় ইনপুট ইচ্ছাকৃত থাকা উচিত। যদি অ্যাপটি নিজেই ক্ষেত্রগুলি পূরণ করে বলে মনে হয়, ক্লিপবোর্ড পেস্ট আচরণ পরীক্ষা করুন; যদি লিঙ্ক কার্ডগুলি গোলমাল বা ধীর বোধ করে, লিঙ্কের পূর্বরূপ আচরণ সামঞ্জস্য করুন। + আপনি যখন প্রায়ই পাঠ্য অনুলিপি করেন এবং অবিলম্বে একটি ম্যাচিং টুল খুলুন তখন স্বয়ংক্রিয় ক্লিপবোর্ড পেস্টের অনুমতি দিন। + ব্যক্তিগত ক্লিপবোর্ড সামগ্রী এমন সরঞ্জামগুলিতে উপস্থিত হলে স্বয়ংক্রিয় পেস্ট অক্ষম করুন যেখানে আপনি এটি আশা করেননি৷ + আপনার কর্মপ্রবাহের জন্য প্রিভিউ আনয়ন ধীর, বিভ্রান্তিকর বা অপ্রয়োজনীয় হলে লিঙ্ক প্রিভিউ বন্ধ করুন। + কমপ্যাক্ট নিয়ন্ত্রণ + স্ক্রীন স্পেস টাইট হলে ঘন নির্বাচক এবং দ্রুত সেটিংস প্লেসমেন্ট ব্যবহার করুন + ছোট পর্দায় আরো নিয়ন্ত্রণ ফিট + কমপ্যাক্ট নির্বাচক এবং দ্রুত সেটিংস প্লেসমেন্ট ছোট ফোন, ল্যান্ডস্কেপ এডিটিং এবং অনেক প্যারামিটার সহ সরঞ্জামগুলিতে সহায়তা করে। ট্রেডঅফ হ\'ল নিয়ন্ত্রণগুলি আরও ঘন অনুভব করতে পারে, তাই আপনি ইতিমধ্যে সাধারণ বিকল্পগুলি চিনতে পারার পরে এটি সর্বোত্তম। + স্ক্রীনের জন্য ডায়ালগ বা বিকল্প তালিকাগুলি খুব লম্বা মনে হলে কমপ্যাক্ট নির্বাচককে সক্ষম করুন৷ + ডিসপ্লের এক পাশ থেকে টুল কন্ট্রোল পৌঁছানো সহজ হলে দ্রুত সেটিংস সাইড অ্যাডজাস্ট করুন। + আপনি যদি এখনও অ্যাপটি শিখছেন বা ঘন ঘন বিকল্পগুলি মিস-ট্যাপ করে থাকেন তবে রুমিয়ার কন্ট্রোলে ফিরে যান। + ওয়েব থেকে ছবি লোড করুন + একটি পৃষ্ঠা বা ছবির URL আটকান, পার্স করা ছবিগুলির পূর্বরূপ দেখুন, তারপর নির্বাচিত ফলাফলগুলি সংরক্ষণ বা ভাগ করুন৷ + ইচ্ছাকৃতভাবে ওয়েব ছবি ব্যবহার করুন + ওয়েব ইমেজ লোডিং প্রদত্ত ইউআরএল থেকে ইমেজ সোর্স পার্স করে, প্রথম উপলব্ধ ইমেজের পূর্বরূপ দেখায় এবং আপনাকে নির্বাচিত পার্স করা ছবিগুলো সেভ বা শেয়ার করতে দেয়। কিছু সাইট অস্থায়ী, সুরক্ষিত, বা স্ক্রিপ্ট-জেনারেটেড লিঙ্ক ব্যবহার করে, তাই ব্রাউজারে কাজ করে এমন একটি পৃষ্ঠা এখনও ব্যবহারযোগ্য ছবি ফেরত দিতে পারে না। + একটি সরাসরি চিত্র URL বা একটি পৃষ্ঠা URL আটকান এবং পার্স করা চিত্র তালিকা প্রদর্শিত হওয়ার জন্য অপেক্ষা করুন৷ + ফ্রেম বা নির্বাচন নিয়ন্ত্রণ ব্যবহার করুন যখন পৃষ্ঠাটিতে বেশ কয়েকটি ছবি থাকে এবং আপনার শুধুমাত্র কয়েকটির প্রয়োজন হয়। + যদি কিছুই লোড না হয়, একটি সরাসরি চিত্র লিঙ্ক চেষ্টা করুন বা প্রথমে ব্রাউজারের মাধ্যমে ডাউনলোড করুন; সাইট পার্সিং ব্লক বা ব্যক্তিগত লিঙ্ক ব্যবহার করতে পারে. + রিসাইজ টাইপ বেছে নিন + একটি ছবিকে নতুন মাত্রায় বাধ্য করার আগে স্পষ্ট, নমনীয়, ক্রপ এবং ফিট বুঝুন + আকৃতি, ক্যানভাস এবং আকৃতির অনুপাত নিয়ন্ত্রণ করুন + রিসাইজ টাইপ সিদ্ধান্ত নেয় যখন অনুরোধ করা প্রস্থ এবং উচ্চতা মূল আকৃতির অনুপাতের সাথে মেলে না তখন কী হবে। এটি পিক্সেল প্রসারিত করা, অনুপাত সংরক্ষণ করা, অতিরিক্ত এলাকা কাটা, বা একটি পটভূমি ক্যানভাস যোগ করার মধ্যে পার্থক্য। + শুধুমাত্র তখনই স্পষ্ট ব্যবহার করুন যখন বিকৃতি গ্রহণযোগ্য হয় বা উৎসের ইতিমধ্যেই লক্ষ্যের অনুপাতের অনুপাত থাকে। + গুরুত্বপূর্ণ দিক থেকে আকার পরিবর্তন করে অনুপাত বজায় রাখতে নমনীয় ব্যবহার করুন, বিশেষ করে ফটো এবং মিশ্র ব্যাচের জন্য। + সীমানা ছাড়াই কঠোর ফ্রেমের জন্য ক্রপ ব্যবহার করুন বা ফিট করুন যখন সম্পূর্ণ চিত্রটি একটি নির্দিষ্ট ক্যানভাসের মধ্যে দৃশ্যমান থাকবে। + ইমেজ স্কেল মোড বাছুন + রিস্যাম্পলিং ফিল্টার বেছে নিন যা তীক্ষ্ণতা, মসৃণতা, গতি এবং প্রান্তের শিল্পকর্ম নিয়ন্ত্রণ করে + আকস্মিক কোমলতা ছাড়াই আকার পরিবর্তন করুন + ইমেজ স্কেল মোড নিয়ন্ত্রণ করে কিভাবে নতুন পিক্সেল রিসাইজ করার সময় গণনা করা হয়। সহজ মোডগুলি দ্রুত এবং অনুমানযোগ্য, যখন উন্নত ফিল্টারগুলি বিশদগুলি আরও ভালভাবে সংরক্ষণ করতে পারে তবে প্রান্তগুলিকে তীক্ষ্ণ করতে পারে, হ্যালো তৈরি করতে পারে বা বড় ছবিতে আরও বেশি সময় নিতে পারে৷ + দ্রুত দৈনন্দিন আকার পরিবর্তনের জন্য মৌলিক বা বিলিনিয়ার ব্যবহার করুন যখন ফলাফল শুধুমাত্র ছোট এবং পরিষ্কার হতে হবে। + সূক্ষ্ম বিবরণ, টেক্সট, বা উচ্চ-মানের ডাউনস্কেলিং ব্যাপার থাকলে ল্যাঙ্কজোস, রবিডক্স, স্প্লাইন বা ইডাব্লুএ-স্টাইল মোড ব্যবহার করুন। + পিক্সেল আর্ট, আইকন এবং হার্ড-এজড গ্রাফিক্সের জন্য Nearest ব্যবহার করুন যেখানে ঝাপসা পিক্সেল লুক নষ্ট করবে। + স্কেল রঙের স্থান + আকার পরিবর্তনের সময় পিক্সেল পুনরায় নমুনা করার সময় রঙগুলি কীভাবে মিশ্রিত হয় তা নির্ধারণ করুন + গ্রেডিয়েন্ট এবং প্রান্ত প্রাকৃতিক রাখুন + স্কেল রঙের স্থান আকার পরিবর্তন করার সময় ব্যবহৃত গণিত পরিবর্তন করে। বেশিরভাগ ছবিই ডিফল্টের সাথে ঠিক থাকে, কিন্তু রৈখিক বা অনুধাবনযোগ্য স্থানগুলি গ্রেডিয়েন্ট, গাঢ় প্রান্তগুলি এবং স্যাচুরেটেড রঙগুলিকে শক্তিশালী স্কেলিং করার পরে পরিষ্কার দেখাতে পারে। + যখন গতি এবং সামঞ্জস্যতা ছোট রঙের পার্থক্যের চেয়ে বেশি গুরুত্বপূর্ণ তখন ডিফল্টটি ছেড়ে দিন। + যখন গাঢ় রূপরেখা, UI স্ক্রিনশট, গ্রেডিয়েন্ট, বা রঙের ব্যান্ডগুলি আকার পরিবর্তন করার পরে ভুল দেখায় তখন লিনিয়ার বা উপলব্ধিমূলক মোড ব্যবহার করে দেখুন। + আসল টার্গেট স্ক্রিনের আগে এবং পরে তুলনা করুন, কারণ রঙ-স্থান পরিবর্তন সূক্ষ্ম এবং বিষয়বস্তু নির্ভর হতে পারে। + রিসাইজ মোড সীমিত করুন + মাপসই করার জন্য কখন ওভার-লিমিট ছবিগুলি এড়িয়ে যাওয়া, পুনরায় কোড করা বা ছোট করা উচিত তা জানুন + সীমাতে কি হবে তা বেছে নিন + লিমিট রিসাইজ শুধুমাত্র একটি ছোট-ইমেজ টুল নয়। এটির মোড সিদ্ধান্ত নেয় অ্যাপটি কি করবে যখন একটি উৎস ইতিমধ্যেই আপনার সীমার মধ্যে থাকে বা যখন শুধুমাত্র একটি পক্ষ নিয়ম ভঙ্গ করে, যা মিশ্র ফোল্ডার এবং আপলোড প্রস্তুতির জন্য গুরুত্বপূর্ণ। + যখন সীমার ভিতরের ছবিগুলিকে স্পর্শ না করা উচিত এবং আবার রপ্তানি করা হবে না তখন Skip ব্যবহার করুন৷ + মাত্রা গ্রহণযোগ্য হলে Recode ব্যবহার করুন কিন্তু আপনার এখনও একটি নতুন বিন্যাস, মেটাডেটা আচরণ, গুণমান বা ফাইলের নাম প্রয়োজন। + জুম ব্যবহার করুন যখন সীমা ভঙ্গকারী ছবিগুলিকে আনুপাতিকভাবে ছোট করা উচিত যতক্ষণ না তারা অনুমোদিত বাক্সে ফিট হয়। + আকৃতির অনুপাত লক্ষ্য + প্রসারিত মুখ, কাটা প্রান্ত এবং কঠোর আউটপুট আকারে অপ্রত্যাশিত সীমানা এড়িয়ে চলুন + আকার পরিবর্তন করার আগে আকৃতির সাথে মিল করুন + প্রস্থ এবং উচ্চতা একটি রিসাইজ টার্গেটের মাত্র অর্ধেক। আকৃতির অনুপাত নির্ধারণ করে যে চিত্রটি স্বাভাবিকভাবে ফিট হতে পারে, ক্রপ করা প্রয়োজন, বা এর চারপাশে একটি ক্যানভাস প্রয়োজন। প্রথমে আকৃতি পরীক্ষা করা সবচেয়ে আশ্চর্যজনক পরিবর্তনের ফলাফলকে বাধা দেয়। + স্পষ্ট, ক্রপ বা ফিট বেছে নেওয়ার আগে লক্ষ্য আকৃতির সাথে উৎস আকৃতির তুলনা করুন। + প্রথমে ক্রপ করুন যখন বিষয় অতিরিক্ত পটভূমি হারাতে পারে এবং গন্তব্যের জন্য একটি কঠোর ফ্রেম প্রয়োজন। + ফিট বা নমনীয় ব্যবহার করুন যখন আসল ছবির প্রতিটি অংশ অবশ্যই দৃশ্যমান থাকবে। + আপস্কেল বা সাধারণ আকার পরিবর্তন করুন + পিক্সেল যোগ করার সময় AI প্রয়োজন এবং কখন সহজ স্কেলিং যথেষ্ট তা জানুন + বিস্তারিত সঙ্গে আকার বিভ্রান্ত করবেন না + পিক্সেল ইন্টারপোলেট করে সাধারণ আকার পরিবর্তন করে মাত্রা; এটা বাস্তব বিস্তারিত উদ্ভাবন করতে পারে না. এআই আপস্কেল তীক্ষ্ণ-দেখানো বিশদ তৈরি করতে পারে, তবে এটি ধীর এবং টেক্সচারকে হ্যালুসিনেট করতে পারে, তাই সঠিক পছন্দটি নির্ভর করে আপনার সামঞ্জস্য, গতি বা ভিজ্যুয়াল পুনরুদ্ধার প্রয়োজন কিনা তার উপর। + থাম্বনেইল, আপলোড সীমা, স্ক্রিনশট এবং সাধারণ মাত্রা পরিবর্তনের জন্য সাধারণ আকার পরিবর্তন করুন। + AI আপস্কেল ব্যবহার করুন যখন একটি ছোট বা সফট ইমেজ একটি বড় আকারে ভাল দেখতে প্রয়োজন। + AI আপস্কেলের পরে মুখ, টেক্সট এবং প্যাটার্নের তুলনা করুন কারণ জেনারেট করা বিশদটি বিশ্বাসযোগ্য কিন্তু ভুল হতে পারে। + ফিল্টার অর্ডার বিষয় + ইচ্ছাকৃত ক্রমানুসারে পরিষ্কার, রঙ, তীক্ষ্ণতা এবং চূড়ান্ত সংকোচন প্রয়োগ করুন + একটি ক্লিনার ফিল্টার স্ট্যাক তৈরি করুন + ফিল্টার সবসময় বিনিময়যোগ্য নয়। তীক্ষ্ণ করার আগে একটি অস্পষ্টতা, OCR এর আগে একটি বৈপরীত্য বুস্ট, বা কম্প্রেশনের আগে একটি রঙ পরিবর্তন একই নিয়ন্ত্রণগুলি থেকে খুব আলাদা আউটপুট তৈরি করতে পারে। + প্রথমে ক্রপ করুন এবং ঘোরান যাতে পরে ফিল্টারগুলি শুধুমাত্র পিক্সেলগুলিতে কাজ করে যা থাকবে৷ + তীক্ষ্ণ বা স্টাইলাইজড প্রভাবের আগে এক্সপোজার, সাদা ভারসাম্য এবং বৈসাদৃশ্য প্রয়োগ করুন। + চূড়ান্ত আকার পরিবর্তন এবং সংকোচনকে শেষের কাছাকাছি রাখুন যাতে পূর্বরূপ বিশদ বিবরণ অনুমানযোগ্যভাবে রপ্তানি টিকে থাকে। + পটভূমির প্রান্ত ঠিক করুন + স্বয়ংক্রিয়ভাবে অপসারণের পরে হ্যালোস, অবশিষ্ট ছায়া, চুল, গর্ত এবং নরম রূপরেখা পরিষ্কার করুন + কাটআউটগুলিকে ইচ্ছাকৃত দেখান + স্বয়ংক্রিয় মুখোশগুলি প্রায়শই এক নজরে ভাল দেখায় তবে উজ্জ্বল, অন্ধকার বা প্যাটার্নযুক্ত পটভূমিতে সমস্যাগুলি প্রকাশ করে। এজ ক্লিনআপ হল দ্রুত কাটআউট এবং পুনরায় ব্যবহারযোগ্য স্টিকার, লোগো বা পণ্যের ছবির মধ্যে পার্থক্য। + হ্যালোস এবং অনুপস্থিত প্রান্তের বিশদ প্রকাশ করতে বিপরীত পটভূমিতে ফলাফলের পূর্বরূপ দেখুন। + চারপাশের অবশিষ্টাংশ মুছে ফেলার আগে চুল, কোণ এবং স্বচ্ছ বস্তুর জন্য পুনরুদ্ধার ব্যবহার করুন। + যখন কাটআউটটি একটি ডিজাইনে স্থাপন করা হবে তখন চূড়ান্ত পটভূমির রঙে একটি ছোট পরীক্ষা রপ্তানি করুন। + পঠনযোগ্য জলছাপ + ফটো নষ্ট না করে উজ্জ্বল, অন্ধকার, ব্যস্ত এবং ক্রপ করা ছবিগুলিতে চিহ্নগুলি দৃশ্যমান করুন + এটি গোপন না করে ইমেজ রক্ষা করুন + একটি ওয়াটারমার্ক যা একটি ছবিতে ভাল দেখায় অন্যটিতে অদৃশ্য হয়ে যেতে পারে। আকার, অস্বচ্ছতা, বৈসাদৃশ্য, স্থান নির্ধারণ, এবং পুনরাবৃত্তি শুধুমাত্র প্রথম পূর্বরূপ নয়, পুরো ব্যাচের জন্য বেছে নেওয়া উচিত। + সমস্ত ফাইল রপ্তানি করার আগে ব্যাচের সবচেয়ে উজ্জ্বল এবং অন্ধকার ছবিগুলিতে চিহ্ন পরীক্ষা করুন। + বড় পুনরাবৃত্ত চিহ্নের জন্য নিম্ন অস্বচ্ছতা এবং ছোট কোণার চিহ্নের জন্য শক্তিশালী বৈসাদৃশ্য ব্যবহার করুন। + গুরুত্বপূর্ণ মুখ, পাঠ্য এবং পণ্যের বিশদ বিবরণ পরিষ্কার রাখুন যদি না চিহ্নটি পুনঃব্যবহার রোধ করার উদ্দেশ্যে হয়। + একটি ছবিকে টাইলসের মধ্যে বিভক্ত করুন + সারি, কলাম, কাস্টম শতাংশ, বিন্যাস এবং গুণমান দ্বারা একটি একক চিত্র কাটুন + গ্রিড এবং আদেশ টুকরা প্রস্তুত + ইমেজ স্প্লিটিং একটি সোর্স ইমেজ থেকে একাধিক আউটপুট তৈরি করে। এটি সারি এবং কলামের সংখ্যা দ্বারা বিভক্ত করতে পারে, সারি বা কলামের শতাংশ সামঞ্জস্য করতে পারে এবং নির্বাচিত আউটপুট বিন্যাস এবং গুণমানের সাথে টুকরোগুলি সংরক্ষণ করতে পারে, যা গ্রিড, পৃষ্ঠার স্লাইস, প্যানোরামা এবং অর্ডার করা সামাজিক পোস্টগুলির জন্য দরকারী৷ + সোর্স ইমেজ বেছে নিন, তারপর আপনার প্রয়োজনীয় সারি এবং কলামের সংখ্যা সেট করুন। + সারি বা কলাম শতাংশ সামঞ্জস্য করুন যখন টুকরা সব সমান আকারের হওয়া উচিত নয়। + সংরক্ষণ করার আগে আউটপুট বিন্যাস এবং গুণমান চয়ন করুন যাতে প্রতিটি তৈরি করা অংশ একই রপ্তানি নিয়ম ব্যবহার করে। + ইমেজ রেখাচিত্রমালা কাটা + উল্লম্ব বা অনুভূমিক অঞ্চলগুলি সরান এবং অবশিষ্ট অংশগুলিকে আবার একত্রিত করুন + একটি ফাঁক না রেখে একটি অঞ্চল সরান + ইমেজ কাটিং সাধারণ ফসল থেকে আলাদা। এটি একটি নির্বাচিত উল্লম্ব বা অনুভূমিক স্ট্রিপ মুছে ফেলতে পারে, তারপর বাকি চিত্র অংশগুলিকে একত্রিত করতে পারে৷ বিপরীত মোড পরিবর্তে নির্বাচিত অঞ্চল রাখে, এবং উভয় বিপরীত দিক ব্যবহার করা নির্বাচিত আয়তক্ষেত্রকে রাখে। + পাশের অঞ্চল, কলাম বা মাঝখানের স্ট্রিপের জন্য উল্লম্ব কাটিং ব্যবহার করুন; ব্যানার, ফাঁক বা সারিগুলির জন্য অনুভূমিক কাটা ব্যবহার করুন। + একটি অক্ষে বিপরীত সক্রিয় করুন যখন আপনি এটি অপসারণের পরিবর্তে নির্বাচিত অংশ রাখতে চান। + কাটার পরে প্রান্তগুলির পূর্বরূপ দেখুন কারণ সরানো এলাকাটি গুরুত্বপূর্ণ বিবরণ অতিক্রম করলে একত্রিত সামগ্রী হঠাৎ দেখা যেতে পারে। + আউটপুট বিন্যাস নির্বাচন করুন + বিষয়বস্তু এবং গন্তব্যের উপর ভিত্তি করে JPEG, PNG, WebP, AVIF, JXL বা PDF বেছে নিন + গন্তব্য বিন্যাস নির্ধারণ করা যাক + সর্বোত্তম বিন্যাস চিত্রটিতে কী রয়েছে এবং এটি কোথায় ব্যবহার করা হবে তার উপর নির্ভর করে। ফটো, স্ক্রিনশট, স্বচ্ছ সম্পদ, নথি, এবং অ্যানিমেটেড ফাইলগুলি ভুল বিন্যাসে বাধ্য করা হলে বিভিন্ন উপায়ে ব্যর্থ হয়৷ + যখন স্বচ্ছতা এবং সঠিক পিক্সেল প্রয়োজন হয় না তখন বিস্তৃত ফটো সামঞ্জস্যের জন্য JPEG ব্যবহার করুন। + তীক্ষ্ণ স্ক্রিনশট, UI ক্যাপচার, স্বচ্ছ গ্রাফিক্স এবং ক্ষতিহীন সম্পাদনার জন্য PNG ব্যবহার করুন। + WebP, AVIF, বা JXL ব্যবহার করুন যখন কম্প্যাক্ট আধুনিক আউটপুট গুরুত্বপূর্ণ এবং গ্রহণকারী অ্যাপ এটিকে সমর্থন করে। + অডিও কভার নিষ্কাশন + PNG ইমেজ হিসাবে অডিও ফাইল থেকে এমবেডেড অ্যালবাম আর্ট বা উপলব্ধ মিডিয়া ফ্রেম সংরক্ষণ করুন + অডিও ফাইল থেকে আর্টওয়ার্ক টানুন + অডিও কভারগুলি অডিও ফাইল থেকে এমবেড করা আর্টওয়ার্ক পড়তে Android মিডিয়া মেটাডেটা ব্যবহার করে। এমবেডেড আর্টওয়ার্ক অনুপলব্ধ হলে, অ্যান্ড্রয়েড একটি প্রদান করতে পারলে পুনরুদ্ধারকারী একটি মিডিয়া ফ্রেমে ফিরে যেতে পারে; যদি উভয়ই বিদ্যমান না থাকে, টুলটি রিপোর্ট করে যে নিষ্কাশন করার জন্য কোন চিত্র নেই। + অডিও কভার খুলুন এবং প্রত্যাশিত কভার আর্ট সহ এক বা একাধিক অডিও ফাইল নির্বাচন করুন। + সংরক্ষণ বা ভাগ করার আগে নিষ্কাশিত কভার পর্যালোচনা করুন, বিশেষত স্ট্রিমিং বা রেকর্ডিং অ্যাপ থেকে ফাইলগুলির জন্য। + যদি কোনো ছবি না পাওয়া যায়, তাহলে অডিও ফাইলে সম্ভবত কোনো এমবেডেড কভার নেই বা অ্যান্ড্রয়েড কোনো ব্যবহারযোগ্য ফ্রেম প্রকাশ করতে পারেনি। + তারিখ এবং অবস্থান মেটাডেটা + সময় ক্যাপচার করার সময় কি সংরক্ষণ করতে হবে তা নির্ধারণ করুন তবে জিপিএস বা ডিভাইস ডেটা ফাঁস হওয়া উচিত নয় + ব্যক্তিগত মেটাডেটা থেকে দরকারী মেটাডেটা আলাদা করুন + মেটাডেটা সব সমান ঝুঁকিপূর্ণ নয়। ক্যাপচারের তারিখগুলি সংরক্ষণাগার এবং বাছাই করার জন্য উপযোগী হতে পারে, যখন GPS, ডিভাইস সিরিয়ালের মতো ক্ষেত্র, সফ্টওয়্যার ট্যাগ বা মালিকের ক্ষেত্রগুলি উদ্দেশ্যের চেয়ে বেশি প্রকাশ করতে পারে। + অ্যালবাম, স্ক্যান বা প্রকল্পের ইতিহাসের জন্য কালানুক্রমিক ক্রম গুরুত্বপূর্ণ হলে তারিখ এবং সময় ট্যাগ রাখুন। + পাবলিক শেয়ারিং বা অপরিচিতদের কাছে ছবি পাঠানোর আগে GPS এবং ডিভাইস-শনাক্তকারী ট্যাগগুলি সরান। + গোপনীয়তার প্রয়োজনীয়তা কঠোর হলে একটি নমুনা রপ্তানি করুন এবং আবার EXIF ​​পরিদর্শন করুন৷ + ফাইলের নাম সংঘর্ষ এড়িয়ে চলুন + যখন অনেক ফাইল image.jpg বা screenshot.png এর মতো নাম শেয়ার করে তখন ব্যাচ আউটপুটগুলি সুরক্ষিত করুন + প্রতিটি আউটপুট নাম অনন্য করুন + বার্তাবাহক, স্ক্রিনশট, ক্লাউড ফোল্ডার বা একাধিক ক্যামেরা থেকে যখন ছবি আসে তখন ডুপ্লিকেট ফাইলের নাম সাধারণ। একটি ভাল প্যাটার্ন দুর্ঘটনাজনিত প্রতিস্থাপনকে বাধা দেয় এবং আউটপুটগুলিকে তাদের উত্সগুলিতে ফিরে পাওয়া যায়। + সম্পাদিত অনুলিপিটি স্বীকৃত হওয়া উচিত তখন মূল নাম এবং একটি প্রত্যয় অন্তর্ভুক্ত করুন। + বড় মিশ্র ব্যাচগুলি প্রক্রিয়া করার সময় ক্রম, টাইমস্ট্যাম্প, প্রিসেট বা মাত্রা যোগ করুন। + যতক্ষণ না আপনি যাচাই না করেন যে নামকরণ প্যাটার্ন সংঘর্ষ করতে পারে না ততক্ষণ ওভাররাইট ওয়ার্কফ্লোগুলি এড়িয়ে চলুন। + সংরক্ষণ বা ভাগ করুন + একটি স্থায়ী ফাইল এবং অন্য অ্যাপে একটি অস্থায়ী হ্যান্ডঅফের মধ্যে বেছে নিন + সঠিক উদ্দেশ্য সঙ্গে রপ্তানি + সেভ এবং শেয়ার অনুরূপ অনুভব করতে পারে, কিন্তু তারা বিভিন্ন সমস্যার সমাধান করে। সংরক্ষণ করা একটি ফাইল তৈরি করে যা আপনি পরে খুঁজে পেতে পারেন; শেয়ার করা Android এর মাধ্যমে একটি উৎপন্ন ফলাফল অন্য অ্যাপে পাঠায় এবং একটি টেকসই স্থানীয় অনুলিপি নাও থাকতে পারে। + যখন আউটপুট একটি পরিচিত ফোল্ডারে থাকতে হবে, ব্যাক আপ করতে হবে বা পরে পুনরায় ব্যবহার করতে হবে তখন সংরক্ষণ ব্যবহার করুন। + দ্রুত বার্তা, ইমেল সংযুক্তি, সামাজিক পোস্টিং বা অন্য সম্পাদককে ফলাফল পাঠানোর জন্য শেয়ার ব্যবহার করুন। + যখন প্রাপ্তি অ্যাপটি অবিশ্বস্ত হয় বা যখন একটি ব্যর্থ শেয়ার পুনরায় তৈরি করা বিরক্তিকর হবে তখন প্রথমে সংরক্ষণ করুন৷ + পিডিএফ পৃষ্ঠার আকার এবং মার্জিন + একটি নথি তৈরি করার আগে কাগজের আকার, উপযুক্ত আচরণ এবং শ্বাস নেওয়ার ঘর চয়ন করুন + ছবি পাতা মুদ্রণযোগ্য এবং পাঠযোগ্য করুন + চিত্রগুলি বিশ্রী PDF পৃষ্ঠায় পরিণত হতে পারে যখন তাদের আকৃতিটি নির্বাচিত কাগজের আকারের সাথে মেলে না। মার্জিন, ফিট মোড এবং পৃষ্ঠার অভিযোজন বিষয়বস্তু ক্রপ করা, ছোট, প্রসারিত বা পড়তে আরামদায়ক কিনা তা নির্ধারণ করে। + পিডিএফ প্রিন্ট বা ফর্ম জমা দেওয়ার সময় একটি বাস্তব কাগজ আকার ব্যবহার করুন. + স্ক্যান এবং স্ক্রিনশটগুলির জন্য মার্জিন ব্যবহার করুন যাতে পাঠ্যটি পৃষ্ঠার প্রান্তের বিপরীতে না বসে। + সোর্স ইমেজে মিশ্র অভিযোজন থাকলে প্রতিকৃতি এবং ল্যান্ডস্কেপ পৃষ্ঠাগুলি একসাথে প্রিভিউ করুন। + স্ক্যান পরিষ্কার করুন + PDF বা OCR এর আগে কাগজের প্রান্ত, ছায়া, বৈসাদৃশ্য, ঘূর্ণন এবং পাঠযোগ্যতা উন্নত করুন + সংরক্ষণাগার করার আগে পৃষ্ঠাগুলি প্রস্তুত করুন + একটি স্ক্যান প্রযুক্তিগতভাবে ক্যাপচার করা যেতে পারে কিন্তু এখনও পড়া কঠিন। পিডিএফ রপ্তানির আগে ছোট পরিষ্কারের পদক্ষেপগুলি প্রায়শই কম্প্রেশন সেটিংসের চেয়ে বেশি গুরুত্বপূর্ণ, বিশেষ করে রসিদ, হাতে লেখা নোট এবং কম আলোর পৃষ্ঠাগুলির জন্য। + সঠিক দৃষ্টিভঙ্গি এবং টেবিলের প্রান্ত, আঙুল, ছায়া এবং পটভূমির বিশৃঙ্খলা দূর করুন। + কন্ট্রাস্ট সাবধানে বাড়ান যাতে স্ট্যাম্প, স্বাক্ষর, বা পেন্সিল চিহ্ন ধ্বংস না করে পাঠ্য আরও পরিষ্কার হয়। + পৃষ্ঠাটি সোজা এবং পঠনযোগ্য হওয়ার পরেই OCR চালান, তারপর ম্যানুয়ালি গুরুত্বপূর্ণ নম্বরগুলি যাচাই করুন৷ + পিডিএফ কম্প্রেস, গ্রেস্কেল বা মেরামত করুন + হালকা, মুদ্রণযোগ্য, বা পুনঃনির্মিত নথি কপিগুলির জন্য এক-ফাইল পিডিএফ অপারেশন ব্যবহার করুন + পিডিএফ ক্লিনআপ অপারেশন বাছাই করুন + পিডিএফ টুল কম্প্রেশন, গ্রেস্কেল রূপান্তর এবং মেরামতের জন্য পৃথক অপারেশন অন্তর্ভুক্ত করে। কম্প্রেশন এবং গ্রেস্কেল মূলত এমবেডেড ইমেজের মাধ্যমে কাজ করে, যখন মেরামত পিডিএফ কাঠামো পুনর্নির্মাণ করে; প্রতিটি নথির জন্য এইগুলির কোনোটিকেই একটি গ্যারান্টিযুক্ত ফিক্স হিসাবে বিবেচনা করা উচিত নয়। + শেয়ার করার জন্য নথিতে ছবি এবং ফাইলের আকারের বিষয় থাকলে কম্প্রেস পিডিএফ ব্যবহার করুন। + যখন নথিটি মুদ্রণ করা সহজ হবে বা রঙিন চিত্রের প্রয়োজন হবে না তখন গ্রেস্কেল ব্যবহার করুন। + একটি নথি খুলতে ব্যর্থ হলে বা অভ্যন্তরীণ কাঠামো ক্ষতিগ্রস্ত হলে একটি অনুলিপিতে মেরামত PDF ব্যবহার করুন, তারপর পুনর্নির্মিত ফাইলটি যাচাই করুন৷ + পিডিএফ থেকে ছবি বের করুন + এমবেড করা পিডিএফ চিত্রগুলি পুনরুদ্ধার করুন এবং নিষ্কাশিত ফলাফলগুলি একটি সংরক্ষণাগারে প্যাক করুন৷ + নথি থেকে উৎস ছবি সংরক্ষণ করুন + এক্সট্রাক্ট ইমেজ এমবেডেড ইমেজ অবজেক্টের জন্য পিডিএফ স্ক্যান করে, যেখানে সম্ভব ডুপ্লিকেট এড়িয়ে যায় এবং জেনারেট করা জিপ আর্কাইভে পুনরুদ্ধার করা ছবি সঞ্চয় করে। এটি কেবলমাত্র পিডিএফ-এ এমবেড করা ছবিগুলিকে বের করে, পাঠ্য, ভেক্টর অঙ্কন বা স্ক্রিনশট হিসাবে প্রতিটি দৃশ্যমান পৃষ্ঠা নয়। + আপনার যখন পিডিএফ-এর মধ্যে সংরক্ষিত ছবি যেমন ক্যাটালগ বা স্ক্যান করা সম্পদের ছবি প্রয়োজন তখন Extract Images ব্যবহার করুন। + আপনার যখন টেক্সট এবং লেআউট সহ সম্পূর্ণ পৃষ্ঠার প্রয়োজন হয় তখন এর পরিবর্তে পিডিএফ টু ইমেজ বা পৃষ্ঠা নিষ্কাশন ব্যবহার করুন। + যদি টুলটি কোনো এমবেডেড ছবি না জানায়, তাহলে পৃষ্ঠাটি পাঠ্য/ভেক্টর বিষয়বস্তু হতে পারে বা ছবিগুলি নিষ্কাশনযোগ্য বস্তু হিসেবে সংরক্ষণ করা যাবে না। + মেটাডেটা, সমতল, এবং মুদ্রণ আউটপুট + নথির বৈশিষ্ট্য সম্পাদনা করুন, পৃষ্ঠাগুলি রাস্টারাইজ করুন বা ইচ্ছাকৃতভাবে কাস্টম প্রিন্ট লেআউট প্রস্তুত করুন + চূড়ান্ত-ডকুমেন্ট অ্যাকশন বেছে নিন + পিডিএফ মেটাডেটা, সমতলকরণ, এবং মুদ্রণ প্রস্তুতি বিভিন্ন চূড়ান্ত-পর্যায়ের সমস্যার সমাধান করে। মেটাডেটা নথির বৈশিষ্ট্য নিয়ন্ত্রণ করে, সমতল PDF পৃষ্ঠাগুলিকে কম সম্পাদনাযোগ্য আউটপুটে রাস্টারাইজ করে এবং প্রিন্ট PDF পৃষ্ঠার আকার, ওরিয়েন্টেশন, মার্জিন এবং পৃষ্ঠা-প্রতি-শীট নিয়ন্ত্রণ সহ একটি নতুন লেআউট প্রস্তুত করে। + মেটাডেটা ব্যবহার করুন যখন লেখক, শিরোনাম, কীওয়ার্ড, প্রযোজক, বা গোপনীয়তা ক্লিনআপ গুরুত্বপূর্ণ। + যখন দৃশ্যমান পৃষ্ঠার বিষয়বস্তু আলাদা PDF অবজেক্ট হিসাবে সম্পাদনা করা কঠিন হয়ে যায় তখন সমতল PDF ব্যবহার করুন। + আপনার কাস্টম পৃষ্ঠার আকার, ওরিয়েন্টেশন, মার্জিন বা শীট প্রতি একাধিক পৃষ্ঠার প্রয়োজন হলে প্রিন্ট PDF ব্যবহার করুন। + OCR এর জন্য ছবি প্রস্তুত করুন + পাঠ্য পড়তে OCR বলার আগে ক্রপ, ঘূর্ণন, বৈসাদৃশ্য, ডিনোয়েজ এবং স্কেল ব্যবহার করুন + ওসিআর ক্লিনার পিক্সেল দিন + OCR ভুল প্রায়ই OCR ইঞ্জিনের পরিবর্তে ইনপুট ইমেজ থেকে আসে। আঁকাবাঁকা পৃষ্ঠা, কম বৈসাদৃশ্য, অস্পষ্টতা, ছায়া, ক্ষুদ্র টেক্সট এবং মিশ্র পটভূমি সবই স্বীকৃতির গুণমানকে কমিয়ে দেয়। + পাঠ্য অঞ্চলে ক্রপ করুন এবং চিত্রটি ঘোরান যাতে স্বীকৃতির আগে লাইনগুলি অনুভূমিক হয়। + কন্ট্রাস্ট বাড়ান বা ক্লিনার ব্ল্যাক-এন্ড-হোয়াইট-এ রূপান্তর করুন শুধুমাত্র যখন এটি অক্ষর পড়া সহজ করে তোলে। + ছোট টেক্সটকে মাঝারিভাবে রিসাইজ করুন, কিন্তু আক্রমনাত্মক ধারালো করা এড়িয়ে চলুন যা জাল প্রান্ত তৈরি করে। + QR বিষয়বস্তু পরীক্ষা করুন + একটি কোড বিশ্বাস করার আগে লিঙ্ক, ওয়াই-ফাই শংসাপত্র, পরিচিতি এবং ক্রিয়াগুলি পর্যালোচনা করুন৷ + ডিকোড করা পাঠ্যকে অবিশ্বস্ত ইনপুট হিসাবে বিবেচনা করুন + একটি QR কোড একটি URL, যোগাযোগ কার্ড, Wi-Fi পাসওয়ার্ড, ক্যালেন্ডার ইভেন্ট, ফোন নম্বর, বা সাধারণ পাঠ্য লুকাতে পারে। কোডের কাছাকাছি দৃশ্যমান লেবেল প্রকৃত পেলোডের সাথে নাও মিলতে পারে, তাই এটিতে কাজ করার আগে ডিকোড করা সামগ্রী পর্যালোচনা করুন৷ + এটি খোলার আগে সম্পূর্ণ ডিকোড করা URL পরিদর্শন করুন, বিশেষত সংক্ষিপ্ত বা অপরিচিত ডোমেনগুলি৷ + ওয়াই-ফাই, পরিচিতি, ক্যালেন্ডার, ফোন এবং এসএমএস কোডগুলির সাথে সতর্ক থাকুন কারণ তারা সংবেদনশীল ক্রিয়াগুলিকে ট্রিগার করতে পারে৷ + কন্টেন্টটি তাৎক্ষণিকভাবে খোলার পরিবর্তে অন্য কোথাও যাচাই করার প্রয়োজন হলে প্রথমে কপি করুন। + QR কোড তৈরি করুন + প্লেইন টেক্সট, ইউআরএল, ওয়াই-ফাই, পরিচিতি, ইমেল, ফোন, এসএমএস এবং অবস্থান ডেটা থেকে কোড তৈরি করুন + সঠিক QR পেলোড তৈরি করুন + QR স্ক্রিন প্রবেশ করা বিষয়বস্তু থেকে কোড তৈরি করতে পারে পাশাপাশি বিদ্যমানগুলি স্ক্যান করতে পারে। স্ট্রাকচার্ড QR প্রকারগুলি অন্য অ্যাপগুলিকে বোঝার ফর্ম্যাটে ডেটা এনকোড করতে সাহায্য করে, যেমন Wi-Fi লগইন বিশদ, যোগাযোগ কার্ড, ইমেল ক্ষেত্র, ফোন নম্বর, SMS সামগ্রী, URL বা ভৌগলিক স্থানাঙ্ক। + সাধারণ নোটের জন্য প্লেইন টেক্সট বেছে নিন, অথবা যখন কোডটি Wi-Fi, পরিচিতি, URL, ইমেল, ফোন, এসএমএস, বা অবস্থান ডেটা হিসাবে খোলা হবে তখন একটি কাঠামোগত টাইপ ব্যবহার করুন। + জেনারেট করা কোড শেয়ার করার আগে প্রতিটি ফিল্ড চেক করুন কারণ দৃশ্যমান প্রিভিউ শুধুমাত্র আপনার লেখা পেলোডকে প্রতিফলিত করে। + সংরক্ষিত QR কোডটি একটি স্ক্যানার দিয়ে পরীক্ষা করুন যখন এটি মুদ্রিত হবে, সর্বজনীনভাবে পোস্ট করা হবে বা অন্য লোকেরা ব্যবহার করবে। + একটি চেকসাম মোড চয়ন করুন + ফাইল বা টেক্সট থেকে হ্যাশ গণনা করুন, একটি ফাইল তুলনা করুন, বা অনেক ফাইল ব্যাচ-চেক করুন + বিষয়বস্তু যাচাই করুন, চেহারা নয় + চেকসাম টুলস-এ ফাইল হ্যাশিং, টেক্সট হ্যাশিং, পরিচিত হ্যাশের সাথে একটি ফাইলের তুলনা এবং ব্যাচ তুলনা করার জন্য আলাদা ট্যাব রয়েছে। একটি চেকসাম নির্বাচিত অ্যালগরিদমের জন্য বাইট-ফর-বাইট পরিচয় প্রমাণ করে; এটি প্রমাণ করে না যে দুটি চিত্র কেবল একই রকম দেখায়। + টাইপ করা বা পেস্ট করা টেক্সট ডেটার জন্য ফাইলের জন্য ক্যালকুলেট এবং টেক্সট হ্যাশ ব্যবহার করুন। + যখন কেউ আপনাকে একটি ফাইলের জন্য একটি প্রত্যাশিত চেকসাম দেয় তখন তুলনা ব্যবহার করুন। + একটি পাসে অনেক ফাইলের হ্যাশ বা যাচাইকরণের প্রয়োজন হলে ব্যাচ তুলনা ব্যবহার করুন, তারপর নির্বাচিত অ্যালগরিদম সামঞ্জস্যপূর্ণ রাখুন। + ক্লিপবোর্ড গোপনীয়তা + দুর্ঘটনাক্রমে ব্যক্তিগত কপি করা ডেটা প্রকাশ না করে স্বয়ংক্রিয় ক্লিপবোর্ড বৈশিষ্ট্যগুলি ব্যবহার করুন৷ + কপি করা বিষয়বস্তু ইচ্ছাকৃত রাখুন + ক্লিপবোর্ড অটোমেশন সুবিধাজনক, তবে অনুলিপি করা পাঠ্যে পাসওয়ার্ড, লিঙ্ক, ঠিকানা, টোকেন বা ব্যক্তিগত নোট থাকতে পারে। ক্লিপবোর্ড-ভিত্তিক ইনপুটকে একটি গতি বৈশিষ্ট্য হিসাবে বিবেচনা করুন যা আপনার অভ্যাস এবং গোপনীয়তার প্রত্যাশার সাথে মেলে। + ব্যক্তিগত টেক্সট অপ্রত্যাশিতভাবে সরঞ্জামগুলিতে উপস্থিত হলে স্বয়ংক্রিয় ক্লিপবোর্ড ব্যবহার অক্ষম করুন৷ + ক্লিপবোর্ড-শুধুমাত্র সংরক্ষণ ব্যবহার করুন যখন আপনি ইচ্ছাকৃতভাবে ফলাফলটি সঞ্চয় এড়াতে চান। + সংবেদনশীল পাঠ্য, QR ডেটা, বা বেস64 পেলোডগুলি পরিচালনা করার পরে ক্লিপবোর্ডটি সাফ বা প্রতিস্থাপন করুন। + রঙ বিন্যাস + অন্য কোথাও পেস্ট করার আগে HEX, RGB, HSV, আলফা এবং কপি করা রঙের মান বুঝে নিন + লক্ষ্য প্রত্যাশিত বিন্যাস অনুলিপি + একই দৃশ্যমান রঙ বিভিন্ন উপায়ে লেখা যেতে পারে। একটি ডিজাইন টুল, CSS ফিল্ড, অ্যান্ড্রয়েড কালার ভ্যালু, বা ইমেজ এডিটর একটি ভিন্ন অর্ডার, আলফা হ্যান্ডলিং বা কালার মডেল আশা করতে পারে। + কম্প্যাক্ট কালার কোড আশা করে এমন ওয়েব, ডিজাইন বা থিম ফিল্ডে পেস্ট করার সময় HEX ব্যবহার করুন। + আপনার চ্যানেল, উজ্জ্বলতা, স্যাচুরেশন, বা রঙ ম্যানুয়ালি সামঞ্জস্য করার প্রয়োজন হলে RGB বা HSV ব্যবহার করুন। + স্বচ্ছতা গুরুত্বপূর্ণ হলে আলাদাভাবে আলফা পরীক্ষা করুন কারণ কিছু গন্তব্য এটিকে উপেক্ষা করে বা একটি ভিন্ন ক্রম ব্যবহার করে। + রঙ লাইব্রেরি ব্রাউজ করুন + নাম বা HEX দ্বারা নামযুক্ত রঙগুলি অনুসন্ধান করুন এবং পছন্দগুলি শীর্ষের কাছে রাখুন৷ + পুনঃব্যবহারযোগ্য নামযুক্ত রং খুঁজুন + কালার লাইব্রেরি একটি নামযুক্ত রঙের সংগ্রহ লোড করে, এটিকে নাম অনুসারে সাজায়, রঙের নাম বা HEX মান অনুসারে ফিল্টার করে এবং প্রিয় রঙগুলি সঞ্চয় করে যাতে গুরুত্বপূর্ণ এন্ট্রিগুলি পৌঁছানো সহজ থাকে৷ এটি উপযোগী হয় যখন আপনার একটি ছবি থেকে নমুনা নেওয়ার পরিবর্তে একটি প্রকৃত নামের রঙের প্রয়োজন হয়। + আপনি যখন পরিবারকে জানেন তখন একটি রঙের নাম দ্বারা অনুসন্ধান করুন, যেমন লাল, নীল, স্লেট বা পুদিনা। + আপনার কাছে ইতিমধ্যেই একটি সাংখ্যিক রঙ থাকলে HEX দ্বারা অনুসন্ধান করুন এবং ম্যাচিং বা কাছাকাছি নামযুক্ত এন্ট্রিগুলি খুঁজে পেতে চান৷ + ঘন ঘন রঙগুলিকে পছন্দসই হিসাবে চিহ্নিত করুন যাতে ব্রাউজ করার সময় তারা সাধারণ লাইব্রেরির থেকে এগিয়ে যায়৷ + জাল গ্রেডিয়েন্ট সংগ্রহ ব্যবহার করুন + উপলব্ধ জাল গ্রেডিয়েন্ট সম্পদ ডাউনলোড করুন এবং নির্বাচিত ছবি শেয়ার করুন + দূরবর্তী জাল গ্রেডিয়েন্ট সম্পদ ব্যবহার করুন + মেশ গ্রেডিয়েন্টগুলি একটি দূরবর্তী সংস্থান সংগ্রহ লোড করতে পারে, অনুপস্থিত গ্রেডিয়েন্ট চিত্রগুলি ডাউনলোড করতে পারে, ডাউনলোডের অগ্রগতি দেখাতে পারে এবং নির্বাচিত গ্রেডিয়েন্টগুলি ভাগ করতে পারে। প্রাপ্যতা নেটওয়ার্ক অ্যাক্সেস এবং রিমোট রিসোর্স স্টোরের উপর নির্ভর করে, তাই একটি খালি তালিকা সাধারণত বোঝায় যে সংস্থানগুলি এখনও উপলব্ধ ছিল না। + আপনি যখন রেডিমেড মেশ গ্রেডিয়েন্ট ইমেজ চান তখন গ্রেডিয়েন্ট টুল থেকে মেশ গ্রেডিয়েন্ট খুলুন। + সংগ্রহটি খালি বলে ধরে নেওয়ার আগে সম্পদ লোড বা ডাউনলোডের অগ্রগতির জন্য অপেক্ষা করুন। + আপনার প্রয়োজনীয় গ্রেডিয়েন্টগুলি নির্বাচন করুন এবং ভাগ করুন, অথবা আপনি যখন ম্যানুয়ালি একটি কাস্টম গ্রেডিয়েন্ট তৈরি করতে চান তখন গ্রেডিয়েন্ট মেকারে ফিরে যান। + উত্পন্ন সম্পদ রেজোলিউশন + গ্রেডিয়েন্ট, শব্দ, ওয়ালপেপার, SVG-এর মতো শিল্প বা টেক্সচার তৈরি করার আগে আউটপুট আকার বেছে নিন + আপনার প্রয়োজনীয় আকারে তৈরি করুন + জেনারেট করা ছবিগুলিতে ফিরে আসার জন্য একটি ক্যামেরা আসল নেই৷ আউটপুট খুব ছোট হলে, পরবর্তী স্কেলিং প্যাটার্ন নরম করতে পারে, বিশদ পরিবর্তন করতে পারে, বা সম্পদের টাইলস এবং সংকুচিত করার পদ্ধতি পরিবর্তন করতে পারে। + ওয়ালপেপার, আইকন, ব্যাকগ্রাউন্ড, প্রিন্ট বা ওভারলে এর মতো চূড়ান্ত ব্যবহারের ক্ষেত্রে প্রথমে মাত্রা সেট করুন। + টেক্সচারের জন্য বড় আকার ব্যবহার করুন যা ক্রপ করা, জুম করা বা বিভিন্ন লেআউটে পুনরায় ব্যবহার করা হতে পারে। + বিশাল আউটপুটের আগে পরীক্ষার সংস্করণগুলি রপ্তানি করুন কারণ পদ্ধতিগত বিবরণ কীভাবে কম্প্রেশন আচরণ করে তা পরিবর্তন করতে পারে। + বর্তমান ওয়ালপেপার রপ্তানি করুন + ডিভাইস থেকে উপলব্ধ হোম, লক এবং অন্তর্নির্মিত ওয়ালপেপার পুনরুদ্ধার করুন + ইনস্টল করা ওয়ালপেপার সংরক্ষণ বা ভাগ করুন + ওয়ালপেপার এক্সপোর্ট অ্যান্ড্রয়েড সিস্টেম ওয়ালপেপার API-এর মাধ্যমে যে ওয়ালপেপারগুলি প্রকাশ করে তা পড়ে৷ ডিভাইস, অ্যান্ড্রয়েড সংস্করণ, অনুমতি এবং বিল্ড ভেরিয়েন্টের উপর নির্ভর করে, হোম, লক বা অন্তর্নির্মিত ওয়ালপেপারগুলি আলাদাভাবে উপলব্ধ হতে পারে বা অনুপস্থিত হতে পারে। + সিস্টেম অ্যাপটিকে পড়ার অনুমতি দেয় এমন ওয়ালপেপার লোড করতে ওয়ালপেপার এক্সপোর্ট খুলুন। + উপলব্ধ হোম, লক, বা অন্তর্নির্মিত ওয়ালপেপার এন্ট্রিগুলি নির্বাচন করুন যা আপনি সংরক্ষণ করতে, অনুলিপি করতে বা ভাগ করতে চান৷ + যদি একটি ওয়ালপেপার অনুপস্থিত থাকে বা বৈশিষ্ট্যটি অনুপলব্ধ থাকে তবে এটি সাধারণত একটি সম্পাদনা সেটিংসের পরিবর্তে একটি সিস্টেম, অনুমতি বা বিল্ড-ভেরিয়েন্ট সীমাবদ্ধতা। + কর্নার অ্যানিমেশন থ্রটল + রঙ হিসাবে অনুলিপি করুন + HEX + নাম + নরম চুল এবং প্রান্ত হ্যান্ডলিং সহ দ্রুত ব্যক্তি কাটআউটের জন্য পোর্ট্রেট ম্যাটিং মডেল। + জিরো-ডিসিই++ বক্ররেখা অনুমান ব্যবহার করে দ্রুত কম-আলো বর্ধিতকরণ। + বৃষ্টির রেখা এবং ভেজা-আবহাওয়া আর্টিফ্যাক্টগুলি হ্রাস করার জন্য পুনরুদ্ধারকারী চিত্র পুনরুদ্ধার মডেল। + ডকুমেন্ট আনওয়ারিং মডেল যা একটি স্থানাঙ্ক গ্রিডের পূর্বাভাস দেয় এবং বাঁকা পৃষ্ঠাগুলিকে একটি ফ্ল্যাটার স্ক্যানে রিম্যাপ করে। + গতির সময়কাল স্কেল + অ্যাপ অ্যানিমেশন গতি নিয়ন্ত্রণ করে: 0 গতি নিষ্ক্রিয় করে, 1 স্বাভাবিক, উচ্চ মান অ্যানিমেশনকে ধীর করে দেয় + সাম্প্রতিক সরঞ্জামগুলি দেখান৷ + প্রধান স্ক্রিনে সম্প্রতি ব্যবহৃত সরঞ্জামগুলিতে দ্রুত অ্যাক্সেস প্রদর্শন করুন + ব্যবহার পরিসংখ্যান + এক্সপ্লোর অ্যাপ ওপেন, টুল লঞ্চ, এবং আপনার সবচেয়ে বেশি ব্যবহৃত টুল + অ্যাপ খোলে + টুল খোলে + শেষ টুল + সফল সংরক্ষণ + কার্যকলাপের ধারা + ডেটা সংরক্ষিত + শীর্ষ বিন্যাস + ব্যবহৃত সরঞ্জাম + %1$d খোলে • %2$s + এখনও কোন ব্যবহার পরিসংখ্যান + কয়েকটি টুল খুলুন এবং সেগুলি এখানে স্বয়ংক্রিয়ভাবে প্রদর্শিত হবে + আপনার ব্যবহারের পরিসংখ্যান শুধুমাত্র আপনার ডিভাইসে স্থানীয়ভাবে সংরক্ষণ করা হয়। ImageToolbox এগুলি শুধুমাত্র আপনার ব্যক্তিগত কার্যকলাপ, প্রিয় সরঞ্জাম এবং সংরক্ষিত ডেটা অন্তর্দৃষ্টি দেখাতে ব্যবহার করে + এডিটর এডিট ফটো পিকচার রিটাচ অ্যাডজাস্ট করুন + আকার পরিবর্তন করুন স্কেল রেজোলিউশন মাত্রা প্রস্থ উচ্চতা jpg jpeg png webp কম্প্রেস + কম্প্রেস আকার ওজন বাইট mb kb মান কমাতে অপ্টিমাইজ + ক্রপ ট্রিম কাট অ্যাসপেক্ট রেশিও + পেইন্ট ব্রাশ পেন্সিল টীকা মার্কআপ আঁকা + সাইফার এনক্রিপ্ট ডিক্রিপ্ট পাসওয়ার্ড গোপন + ব্যাকগ্রাউন্ড রিমুভার মুছে ফেলুন কাটআউট স্বচ্ছ আলফা + স্টিচ মার্জ একত্রিত প্যানোরামা দীর্ঘ স্ক্রিনশট + পিকার আইড্রপার নমুনা হেক্স আরজিবি রঙ + exif মেটাডেটা গোপনীয়তা স্ট্রিপ পরিষ্কার জিপিএস অবস্থান অপসারণ + সীমিত আকার পরিবর্তন করুন সর্বোচ্চ মিনিট মেগাপিক্সেল মাত্রা বাতা + পিডিএফ ডকুমেন্ট পেজ টুলস + ocr পাঠ্য নির্যাস স্ক্যান সনাক্ত + jxl jpeg xl রূপান্তর jpg ইমেজ ফরম্যাট + বিন্যাস রূপান্তর jpg jpeg png webp heic avif jxl bmp + বিভক্ত টাইলস গ্রিড টুকরা অংশ + ওয়েবপি কনভার্ট অ্যানিমেটেড ইমেজ ফরম্যাট + মার্কআপ স্তরগুলি ওভারলে সম্পাদনা টীকা + base64 এনকোড ডিকোড ডেটা ইউরি + মেশ গ্রেডিয়েন্ট ব্যাকগ্রাউন্ড + exif মেটাডেটা সম্পাদনা GPS তারিখ ক্যামেরা + ওয়ালপেপার এক্সপোর্ট ব্যাকগ্রাউন্ড + এআই এমএল নিউরাল উন্নত আপস্কেল সেগমেন্ট + shader glsl ফ্র্যাগমেন্ট ইফেক্ট স্টুডিও + পিডিএফ একত্রিত করুন যোগদান + পিডিএফ আলাদা পাতা বিভক্ত করুন + pdf পৃষ্ঠাগুলি সাজানোর পুনর্বিন্যাস করুন + পিডিএফ স্বাক্ষর সাইন ড্র + পিডিএফ পাসওয়ার্ড এনক্রিপ্ট লক রক্ষা করুন + পিডিএফ কম্প্রেস আকার অপ্টিমাইজ হ্রাস + পিডিএফ গ্রেস্কেল কালো সাদা একরঙা + পিডিএফ মেরামত ফিক্স ভাঙ্গা পুনরুদ্ধার + পিডিএফ মেটাডেটা বৈশিষ্ট্য exif লেখক শিরোনাম + pdf পৃষ্ঠাগুলি মুছে ফেলুন + পিডিএফ ক্রপ ট্রিম মার্জিন + পিডিএফ সমতল টীকা লেয়ার গঠন করে + পিডিএফ এক্সট্রাক্ট ইমেজ ছবি + পিডিএফ জিপ আর্কাইভ কম্প্রেস + পিডিএফ প্রিন্ট প্রিন্টার + পিডিএফ প্রিভিউ ভিউয়ার পড়া + ছবিগুলিকে পিডিএফে রূপান্তর করুন jpg jpeg png নথিতে + পিডিএফ টু ইমেজ পেজ এক্সপোর্ট jpg png + পিডিএফ টীকা মন্তব্য মুছে পরিষ্কার + নিরপেক্ষ বৈকল্পিক + ত্রুটি + ড্রপ শ্যাডো + দাঁতের উচ্চতা + অনুভূমিক দাঁত পরিসীমা + উল্লম্ব দাঁত পরিসীমা + শীর্ষ প্রান্ত + ডান প্রান্ত + নীচের প্রান্ত + বাম প্রান্ত + ছেঁড়া প্রান্ত + ফিল্টার প্রভাব রঙ সংশোধন লুট মাস্ক সামঞ্জস্য + প্রিভিউ দর্শক গ্যালারি খোলা পরিদর্শন + url ওয়েব ডাউনলোড ইন্টারনেট লিঙ্ক ইমেজ + প্যালেট রং swatches প্রভাবশালী নির্যাস + পরে আগে পার্থক্য পার্থক্য তুলনা + গ্রেডিয়েন্ট পটভূমি রঙ জাল + ওয়াটারমার্ক লোগো টেক্সট স্ট্যাম্প ওভারলে + জিআইএফ অ্যানিমেশন অ্যানিমেটেড কনভার্ট ফ্রেম + apng অ্যানিমেশন অ্যানিমেটেড png রূপান্তর ফ্রেম + জিপ আর্কাইভ কম্প্রেস প্যাক আনপ্যাক ফাইল + svg ভেক্টর ট্রেস রূপান্তর xml + নথি স্ক্যানার স্ক্যান কাগজ দৃষ্টিকোণ ফসল + কিউআর বারকোড কোড স্ক্যানার স্ক্যান + স্ট্যাকিং ওভারলে গড় মাঝারি অ্যাস্ট্রোফটোগ্রাফি + কালার কনভার্ট হেক্স আরজিবি এইচএসএল এইচএসভি cmyk ল্যাব + গোলমাল জেনারেটর এলোমেলো জমিন শস্য + কোলাজ গ্রিড লেআউট একত্রিত + চেকসাম হ্যাশ md5 sha sha1 sha256 যাচাই করুন + বিভক্ত গ্রিড টুকরা টাইল কাটা + অডিও কভার অ্যালবাম শিল্প সঙ্গীত মেটাডেটা + ascii টেক্সট আর্ট অক্ষর + রঙ লাইব্রেরি উপাদান প্যালেট নাম রং + pdf পৃষ্ঠাগুলির অভিযোজন ঘোরান + পিডিএফ পৃষ্ঠা সংখ্যা পৃষ্ঠা সংখ্যা + pdf ocr টেক্সট সনাক্ত করা অনুসন্ধানযোগ্য নির্যাস + পিডিএফ ওয়াটারমার্ক স্ট্যাম্প লোগো পাঠ্য + pdf আনলক পাসওয়ার্ড ডিক্রিপ্ট সুরক্ষা অপসারণ + ব্যবহারের পরিসংখ্যান রিসেট করুন + টুল খোলে, পরিসংখ্যান সংরক্ষণ করুন, স্ট্রিক, সংরক্ষিত ডেটা এবং শীর্ষ বিন্যাস পুনরায় সেট করা হবে। অ্যাপ ওপেন অপরিবর্তিত থাকবে। + অডিও + ফাইল + প্রোফাইল রপ্তানি করুন + বর্তমান প্রোফাইল সংরক্ষণ করুন + এক্সপোর্ট প্রোফাইল মুছুন + আপনি কি রপ্তানি প্রোফাইল \\"%1$s\\" মুছতে চান? + সীমানা অঙ্কন + অঙ্কন এবং ক্যানভাস মুছে ফেলার চারপাশে একটি পাতলা সীমানা দেখান + তরঙ্গায়িত + একটি নরম তরঙ্গায়িত প্রান্ত সহ গোলাকার UI উপাদান + স্কুপ + খাঁজ + গোলাকার কোণগুলি ভিতরের দিকে খোদাই করা + একটি ডবল কাটা সঙ্গে ধাপ কোণ + স্থানধারক + এক্সটেনশন ছাড়াই আসল ফাইলের নাম সন্নিবেশ করতে {filename} ব্যবহার করুন + ফ্লেয়ার + ভিত্তি পরিমাণ + রিং প্রস্থ + শিয়ার + এবং কোণ + জলের ফোঁটা + তরঙ্গদৈর্ঘ্য + পর্যায় + হাই পাস + রিং পরিমাণ + রে পরিমাণ + দৃষ্টিকোণ বিকৃত করুন + জাভা লুক অ্যান্ড ফিল + X কোণ + আউটপুটের আকার পরিবর্তন করুন + কালার মাস্ক + ক্রোম + দ্রবীভূত করা + কোমলতা + প্রতিক্রিয়া + লেন্স ব্লার + কম ইনপুট + উচ্চ ইনপুট + কম আউটপুট + উচ্চ আউটপুট + হালকা প্রভাব + বাম্প উচ্চতা + বাম্প স্নিগ্ধতা + লহর + এক্স প্রশস্ততা + Y প্রশস্ততা + X তরঙ্গদৈর্ঘ্য + Y তরঙ্গদৈর্ঘ্য + অভিযোজিত ঝাপসা + টেক্সচার জেনারেশন + বিভিন্ন পদ্ধতিগত টেক্সচার তৈরি করুন এবং ছবি হিসাবে সেভ করুন + টেক্সচার টাইপ + পক্ষপাত + সময় + চকচকে + বিচ্ছুরণ + নমুনা + কোণ সহগ + গ্রেডিয়েন্ট সহগ + গ্রিডের ধরন + দূরত্ব শক্তি + স্কেল Y + অস্পষ্টতা + ভিত্তি প্রকার + টার্বুলেন্স ফ্যাক্টর + স্কেলিং + পুনরাবৃত্তি + রিং + ব্রাশড মেটাল + কস্টিকস + সেলুলার + চেকারবোর্ড + fBm + মার্বেল + প্লাজমা + কোল্ট + কাঠ + এলোমেলো + বর্গক্ষেত্র + ষড়ভুজ + অষ্টভুজাকার + ত্রিভুজাকার + রিজড + ভিএল নয়েজ + এসসি গোলমাল + সাম্প্রতিক + কোনো সম্প্রতি ব্যবহৃত ফিল্টার এখনো + টেক্সচার জেনারেটর ব্রাশ করা ধাতু কস্টিক সেলুলার চেকারবোর্ড মার্বেল প্লাজমা কুইল্ট কাঠ ইট ছদ্মবেশ সেল মেঘ ফাটল ফ্যাব্রিক পাতা মৌচাক বরফ লাভা নীহারিকা কাগজ মরিচা বালি ধোঁয়া পাথর ভূখণ্ড ভূখণ্ড জল লহর + ইট + ছদ্মবেশ + সেল + মেঘ + ফাটল + ফ্যাব্রিক + ঝরা পাতা + মৌচাক + বরফ + লাভা + নীহারিকা + কাগজ + মরিচা + বালি + ধোঁয়া + পাথর + ভূখণ্ড + টপোগ্রাফি + জলের ঢেউ + উন্নত কাঠ + মর্টার প্রস্থ + অনিয়ম + বেভেল + রুক্ষতা + প্রথম প্রান্তিক + দ্বিতীয় প্রান্তিক + তৃতীয় প্রান্তিক + প্রান্ত কোমলতা + সীমানা প্রস্থ + কভারেজ + বিস্তারিত + গভীরতা + শাখাপ্রশাখা + অনুভূমিক থ্রেড + উল্লম্ব থ্রেড + ফাজ + শিরা + লাইটিং + ক্র্যাক প্রস্থ + তুষারপাত + প্রবাহ + ভূত্বক + মেঘের ঘনত্ব + তারা + ফাইবারের ঘনত্ব + ফাইবার শক্তি + দাগ + জারা + পিটিং + ফ্লেক্স + ডুন ফ্রিকোয়েন্সি + বায়ু কোণ + লহর + উইস্পস + শিরা স্কেল + পানির স্তর + পাহাড়ের স্তর + ক্ষয় + তুষার স্তর + লাইন গণনা + লাইন বেধ + শেডিং + ছিদ্র রঙ + মর্টার রঙ + গাঢ় ইটের রঙ + ইটের রঙ + হাইলাইট রঙ + গাঢ় রঙ + বনের রঙ + পৃথিবীর রঙ + বালির রঙ + পটভূমির রঙ + কোষের রঙ + প্রান্ত রঙ + আকাশী রঙ + ছায়া রঙ + হালকা রঙ + পৃষ্ঠের রঙ + বৈচিত্র্যের রঙ + ফাটল রঙ + ওয়ার্প রঙ + ওয়েফট রঙ + গাঢ় পাতার রঙ + পাতার রঙ + সীমানা রঙ + মধু রঙ + গভীর রঙ + বরফ রঙ + তুষার রঙ + ভূত্বকের রঙ + রঙ ধোয়া + উজ্জ্বল রঙ + স্থান রং + বেগুনি রঙ + নীল রং + বেস রঙ + ফাইবার রঙ + দাগের রঙ + ধাতব রঙ + গাঢ় মরিচা রঙ + মরিচা রঙ + কমলা রঙ + ধোঁয়ার রঙ + শিরার রঙ + নিম্নভূমির রঙ + জল রং + শিলা রঙ + তুষার রঙ + কম রঙ + উচ্চ রং + লাইনের রঙ + অগভীর রঙ + ঘাস + ময়লা + চামড়া + কংক্রিট + অ্যাসফল্ট + মস + আগুন + অরোরা + অয়েল স্লিক + জলরঙ + বিমূর্ত প্রবাহ + উপল + দামেস্ক ইস্পাত + বজ্রপাত + মখমল + কালি মার্বেল + হলোগ্রাফিক ফয়েল + বায়োলুমিনিসেন্স + মহাজাগতিক ঘূর্ণি + লাভা ল্যাম্প + ইভেন্ট হরাইজন + ফ্র্যাক্টাল ব্লুম + ক্রোম্যাটিক টানেল + গ্রহন করোনা + অদ্ভুত আকর্ষক + ফেরোফ্লুইড ক্রাউন + সুপারনোভা + আইরিস + ময়ূর পালক + নটিলাস শেল + রিংড প্ল্যানেট + ব্লেডের ঘনত্ব + ব্লেড দৈর্ঘ্য + বাতাস + প্যাচিনেস + গুচ্ছ + আর্দ্রতা + নুড়ি + বলিরেখা + ছিদ্র + সমষ্টি + ফাটল + টার + পরিধান + দাগ + তন্তু + শিখা ফ্রিকোয়েন্সি + ধোঁয়া + তীব্রতা + ফিতা + ব্যান্ড + ইরিডিসেন্স + অন্ধকার + প্রস্ফুটিত + রঙ্গক + প্রান্ত + কাগজ + ডিফিউশন + প্রতিসাম্য + তীক্ষ্ণতা + রঙের খেলা + মিল্কিনেস + স্তর + ভাঁজ + পোলিশ + শাখা + দিকনির্দেশনা + শিন + ভাঁজ + ফেদারিং + কালি ভারসাম্য + বর্ণালী + কুঁচকানো + বিবর্তন + অস্ত্র + টুইস্ট + কোর গ্লো + ব্লবস + ডিস্ক কাত + দিগন্তের আকার + ডিস্কের প্রস্থ + লেন্সিং + পাপড়ি + কার্ল + ফিলিগ্রি + দিক + বক্রতা + চাঁদের আকার + করোনার আকার + রশ্মি + হীরার আংটি + লবস + কক্ষপথের ঘনত্ব + পুরুত্ব + স্পাইকস + স্পাইক দৈর্ঘ্য + শরীরের আকার + ধাতব + শক ব্যাসার্ধ + শেল প্রস্থ + নিক্ষিপ্ত + ছাত্রের আকার + আইরিস আকার + রঙের বৈচিত্র + ক্যাচলাইট + চোখের আকার + বার্ব ঘনত্ব + বাঁক + চেম্বার + খোলা হচ্ছে + Ridges + মুক্তা + গ্রহের আকার + রিং কাত + রিং প্রস্থ + বায়ুমণ্ডল + ময়লা রঙ + গাঢ় ঘাসের রঙ + ঘাসের রঙ + টিপ রঙ + গাঢ় মাটির রঙ + শুকনো রঙ + নুড়ি রঙ + চামড়ার রঙ + কংক্রিট রঙ + টার রঙ + অ্যাসফল্ট রঙ + পাথরের রঙ + ধুলো রঙ + মাটির রঙ + গাঢ় শ্যাওলা রঙ + শ্যাওলা রঙ + লাল রং + মূল রঙ + সবুজ রঙ + সায়ান রঙ + ম্যাজেন্টা রঙ + সোনার রঙ + কাগজের রঙ + রঙ্গক রঙ + সেকেন্ডারি রঙ + প্রথম রঙ + দ্বিতীয় রঙ + গোলাপি রঙ + গাঢ় ইস্পাত রঙ + ইস্পাত রঙ + হালকা ইস্পাত রঙ + অক্সাইড রঙ + হ্যালো রঙ + বোল্ট রঙ + মখমল রঙ + ঝকঝকে রঙ + নীল কালি রঙ + লাল কালির রঙ + গাঢ় কালির রঙ + সিলভার রঙ + হলুদ রঙ + টিস্যুর রঙ + ডিস্কের রঙ + গরম রঙ + লেন্সের রঙ + বাইরের রঙ + ভিতরের রঙ + করোনার রঙ + ঠান্ডা রঙ + উষ্ণ রঙ + মেঘের রঙ + শিখার রঙ + পালকের রঙ + শেল রঙ + মুক্তার রঙ + গ্রহের রঙ + রিং রঙ + সংরক্ষণ করার পরে মূল ফাইল মুছুন + শুধুমাত্র সফলভাবে প্রক্রিয়াকৃত উৎস ফাইল মুছে ফেলা হবে + মূল ফাইল মুছে ফেলা হয়েছে: %1$d + আসল ফাইলগুলি মুছতে ব্যর্থ হয়েছে: %1$d + মূল ফাইল মুছে ফেলা হয়েছে: %1$d, ব্যর্থ হয়েছে: %2$d + শীট অঙ্গভঙ্গি + প্রসারিত বিকল্প শীট টেনে আনার অনুমতি দিন + ক্রোমা সাবস্যাম্পলিং + বিট গভীরতা + ক্ষতিহীন + %1$d-বিট + ব্যাচের নাম পরিবর্তন করুন + ফাইলের নাম প্যাটার্ন ব্যবহার করে একাধিক ফাইলের নাম পরিবর্তন করুন + ব্যাচ ফাইলের নাম পরিবর্তন করুন ফাইলের নাম প্যাটার্ন ক্রম তারিখ এক্সটেনশন + পুনঃনামকরণের জন্য ফাইলগুলি বেছে নিন + ফাইলের নাম প্যাটার্ন + নাম পরিবর্তন করুন + তারিখ উৎস + EXIF তারিখ নেওয়া হয়েছে + ফাইল পরিবর্তিত তারিখ + ফাইল তৈরির তারিখ + বর্তমান তারিখ + ম্যানুয়াল তারিখ + ম্যানুয়াল তারিখ + ম্যানুয়াল সময় + কিছু ফাইলের জন্য নির্বাচিত তারিখটি অনুপলব্ধ৷ তাদের জন্য বর্তমান তারিখ ব্যবহার করা হবে। + একটি ফাইলের নাম প্যাটার্ন লিখুন + প্যাটার্নটি একটি অবৈধ বা অত্যধিক দীর্ঘ ফাইলের নাম তৈরি করে + প্যাটার্নটি ডুপ্লিকেট ফাইলের নাম তৈরি করে + সমস্ত ফাইলের নাম ইতিমধ্যেই প্যাটার্নের সাথে মেলে৷ + এই প্যাটার্ন টোকেন ব্যবহার করে যা ব্যাচের নাম পরিবর্তনের জন্য উপলব্ধ নয় + নাম একই থাকবে + ফাইলগুলি সফলভাবে পুনঃনামকরণ করা হয়েছে৷ + কিছু ফাইলের নাম পরিবর্তন করা যায়নি + অনুমতি দেওয়া হয়নি + এক্সটেনশন ছাড়াই মূল ফাইলের নাম ব্যবহার করে, আপনাকে উৎস সনাক্তকরণ অক্ষত রাখতে সাহায্য করে। এছাড়াও \\o{start:end} দিয়ে স্লাইসিং, \\o{t} দিয়ে প্রতিস্থাপন এবং \\o{s/old/new/} দিয়ে প্রতিস্থাপন সমর্থন করে। + স্বয়ংক্রিয় বৃদ্ধি পাল্টা. কাস্টম ফর্ম্যাটিং সমর্থন করে: \\c{প্যাডিং} (যেমন \\c{3} -&gt; 001), \\c{start:step} বা \\c{start:step:padding}। + মূল ফাইলের নাম যেখানে মূল ফাইলটি ছিল। + আসল ফাইলের আকার। একক সমর্থন করে: \\z{b}, \\z{kb}, \\z{mb} অথবা মানুষের পঠনযোগ্য বিন্যাসের জন্য শুধুমাত্র \\z। + ফাইলের নামের স্বতন্ত্রতা নিশ্চিত করতে একটি র্যান্ডম ইউনিভার্সলি ইউনিক আইডেন্টিফায়ার (UUID) তৈরি করে। + ফাইল পুনঃনামকরণ পূর্বাবস্থায় ফেরানো যাবে না. চালিয়ে যাওয়ার আগে নিশ্চিত করুন যে নতুন নাম সঠিক। + পুনঃনাম নিশ্চিত করুন + সাইড মেনু সেটিংস + মেনু স্কেল + মেনু আলফা + অটো হোয়াইট ব্যালেন্স + ক্লিপিং + লুকানো ওয়াটারমার্কের জন্য পরীক্ষা করা হচ্ছে + স্টোরেজ + ক্যাশে সীমা + ক্যাশে স্বয়ংক্রিয়ভাবে সাফ করুন যখন এটি এই আকারের বাইরে বৃদ্ধি পায় + পরিচ্ছন্নতার ব্যবধান + কত ঘন ঘন ক্যাশে সাফ করা উচিত কিনা তা পরীক্ষা করতে হবে + অ্যাপ লঞ্চের সময় + 1 দিন + ১ সপ্তাহ + 1 মাস + নির্বাচিত সীমা এবং ব্যবধান অনুযায়ী স্বয়ংক্রিয়ভাবে অ্যাপ ক্যাশে সাফ করুন + চলমান ফসল এলাকা + এটি ভিতরে টেনে সমগ্র ফসল এলাকা সরানোর অনুমতি দেয় + GIF মার্জ করুন + একটি অ্যানিমেশনে একাধিক GIF ক্লিপ একত্রিত করুন + GIF ক্লিপ অর্ডার + বিপরীত + বিপরীত হিসাবে খেলুন + বুমেরাং + সামনে খেলুন এবং তারপর পিছনে + ফ্রেমের আকার স্বাভাবিক করুন + বৃহত্তম ক্লিপ মাপসই স্কেল ফ্রেম; তাদের আসল স্কেল সংরক্ষণের জন্য বন্ধ করুন + ক্লিপগুলির মধ্যে বিলম্ব (ms) + ফোল্ডার + একটি ফোল্ডার এবং এর সাবফোল্ডার থেকে সমস্ত ছবি নির্বাচন করুন + ডুপ্লিকেট ফাইন্ডার + সঠিক কপি এবং দৃশ্যত অনুরূপ ছবি খুঁজুন + ডুপ্লিকেট ফাইন্ডার অনুরূপ ছবি সঠিক কপি ফটো ক্লিনআপ স্টোরেজ dHash SHA-256 + সদৃশ খুঁজে পেতে ছবি বাছুন + সাদৃশ্য সংবেদনশীলতা + সঠিক কপি + অনুরূপ ছবি + সব সঠিক সদৃশ নির্বাচন করুন + প্রস্তাবিত ছাড়া সব নির্বাচন করুন + কোন সদৃশ পাওয়া যায়নি + আরও ছবি যোগ করার চেষ্টা করুন বা মিলের সংবেদনশীলতা বাড়ানোর চেষ্টা করুন + %1$d ছবি(গুলি) পড়া যায়নি + %1$s • %2$s পুনরুদ্ধারযোগ্য + %1$d গোষ্ঠী • %2$s পুনরুদ্ধারযোগ্য + %1$d নির্বাচিত • %2$s + এটি পূর্বাবস্থায় ফেরানো যাবে না। অ্যান্ড্রয়েড আপনাকে একটি সিস্টেম ডায়ালগে মুছে ফেলা নিশ্চিত করতে বলতে পারে। + পাওয়া যায়নি + শুরু করতে সরান + শেষ পর্যন্ত সরান + \ No newline at end of file diff --git a/core/resources/src/main/res/values-ca/strings.xml b/core/resources/src/main/res/values-ca/strings.xml new file mode 100644 index 0000000..48773fe --- /dev/null +++ b/core/resources/src/main/res/values-ca/strings.xml @@ -0,0 +1,3740 @@ + + + + + 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 + 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 + 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. + 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. + 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 + Distorsió conscient del contingut + No hi ha prou memòria per completar aquesta acció. Prova d\'utilitzar una imatge més petita, tanca altres aplicacions o reinicia l\'aplicació. + Treballadors paral·lels + Aquestes imatges no es van desar perquè els fitxers convertits serien més grans que els originals. Comproveu aquesta llista abans d\'esborrar les imatges originals. + Fitxers saltats: %1$s + Registres d\'aplicacions + Consulteu els registres de l\'aplicació per detectar els problemes + Preferits en eines agrupades + Afegeix els preferits com a pestanya quan les eines s\'agrupen per tipus + Mostra el favorit com a últim + Mou la pestanya d\'eines preferides fins al final + Espaiat horitzontal + Espaiat vertical + Energia endarrerida + Utilitzeu un mapa d\'energia de magnitud de gradient senzill en lloc d\'un algorisme d\'energia directa + Traieu la zona emmascarada + Les costures passaran per la regió emmascarada, eliminant només l\'àrea seleccionada en lloc de protegir-la + VHS NTSC + Configuració avançada de NTSC + Sintonització analògica addicional per a VHS i artefactes de transmissió més forts + Sagnat de croma + Desgast de la cinta + Seguiment + Frotis de llum + Sonant + Neu + Utilitza el camp + Tipus de filtre + Filtre de llum d\'entrada + Passa baix de croma d\'entrada + Demodulació cromàtica + Canvi de fase + Desplaçament de fase + Canvi de cap + Alçada de canvi del cap + Compensació del capçal + Canvi de capçal + Posició de la línia mitjana + Tremolar a la línia mitjana + Seguiment de l\'alçada del soroll + Onada de seguiment + Seguiment de la neu + Seguiment de l\'anisotropia de la neu + Seguiment del soroll + Seguiment de la intensitat del soroll + Soroll compost + Freqüència de soroll composta + Intensitat de soroll composta + Detall de soroll compost + Freqüència de trucada + Potència de trucada + Soroll Luma + Freqüència del soroll Luma + Intensitat del soroll Luma + Detall del soroll Luma + Soroll cromàtic + Freqüència del soroll cromàtic + Intensitat del soroll cromàtic + Detall del soroll cromàtic + Intensitat de la neu + Anisotropia de la neu + Soroll de fase cromàtica + Error de fase cromàtica + Retard de croma horitzontal + Retard de croma vertical + Velocitat de la cinta VHS + Pèrdua de croma VHS + VHS intensifica la intensitat + Freqüència d\'afinació VHS + Intensitat d\'ona de vora VHS + Velocitat d\'ona de vora VHS + Freqüència d\'ona de vora VHS + Detall de l\'ona de vora VHS + Passa baix cromàtica de sortida + Escala horitzontal + Escala vertical + Factor d\'escala X + Factor d\'escala Y + Detecta filigranes d\'imatge habituals i les pinta amb LaMa. Descàrregues de detecció i pintar models automàticament + Deuteranopia + Amplia la imatge + Eliminador de fons basat en la segmentació d\'objectes mitjançant la segmentació YOLO v11 + Mostra l\'angle de la línia + Mostra la rotació actual de la línia en graus mentre dibuixa + Emojis animats + Mostra els emojis disponibles com a animacions + Desa a la carpeta original + Deseu fitxers nous al costat del fitxer original en lloc de la carpeta seleccionada + Filigrana PDF + No hi ha prou memòria + És probable que la imatge sigui massa gran per processar-la en aquest dispositiu o que el sistema s\'hagi quedat sense memòria disponible. Proveu de reduir la resolució de la imatge, tanqueu altres aplicacions o trieu un fitxer més petit. + Menú depuració + Menú per provar les funcions de l\'aplicació, no està pensat que es mostri a la versió de producció + Proveïdor + PaddleOCR requereix models ONNX addicionals al vostre dispositiu. Voleu baixar dades %1$s? + Universal + coreà + llatí + eslau oriental + tailandès + grec + Anglès + ciríl·lic + àrab + Devanagari + Tamil + Telugu + Shader + Sombreador predefinit + No s\'ha seleccionat cap ombrejat + Shader Studio + Creeu, editeu, valideu, importeu i exporteu shaders de fragments personalitzats + Shaders guardats + Obre els ombrejats desats, duplica, exporta, comparteix o suprimeix + Nou shader + Fitxer shader + .itshader JSON + %1$d paràmetres + Font d\'ombra + Escriu només el cos de void main(). Utilitzeu textureCoordinate per a les coordenades UV i inputImageTexture com a mostrador d\'origen. + Funcions + Funcions d\'ajuda opcionals, constants i estructures inserides abans de void main(). Els uniformes es generen a partir de paràmetres. + Afegiu uniformes aquí quan el shader necessiti valors editables. + Restableix l\'ombrador + S\'esborrarà l\'esborrany actual de l\'ombra + Shader no vàlid + Versió d\'ombrejat no compatible %1$d. Versió compatible: %2$d. + El nom de l\'ombra no ha de quedar en blanc. + La font de l\'ombra no ha d\'estar en blanc. + La font d\'ombra conté caràcters no admesos: %1$s. Utilitzeu només la font ASCII GLSL. + El paràmetre \\\"%1$s\\\" es declara més d\'una vegada. + Els noms dels paràmetres no poden quedar en blanc. + El paràmetre \\\"%1$s\\\" utilitza un nom reservat de GPUImage. + El paràmetre \\\"%1$s\\\" ha de ser un identificador GLSL vàlid. + El paràmetre \\\"%1$s\\\" té el tipus de valor %2$s \\\"%3$s\\\", esperat \\\"%4$s\\\". + El paràmetre \\\"%1$s\\\" no pot definir el mínim ni el màxim per als valors bool. + El paràmetre \\\"%1$s\\\" té un mínim més gran que un màxim. + El paràmetre \\\"%1$s\\\" per defecte és inferior al min. + El paràmetre \\\"%1$s\\\" per defecte és superior al màxim. + Shader ha de declarar \\\"uniform sampler2D %1$s;\\\". + Shader ha de declarar \\\"vec2 variable %1$s;\\\". + Shader ha de definir \\\"void main()\\\". + Shader ha d\'escriure un color a gl_FragColor. + Els shaders mainImage de ShaderToy no són compatibles. Utilitzeu el contracte void main() de GPUImage. + L\'uniforme animat de ShaderToy \\\"iTime\\\" no és compatible. + L\'uniforme animat de ShaderToy \\\"iFrame\\\" no és compatible. + L\'uniforme \\\"iResolution\\\" de ShaderToy no és compatible. + Les textures iChannel de ShaderToy no són compatibles. + Les textures externes no s\'admeten. + Les extensions de textures externes no són compatibles. + Els paràmetres de l\'ombra de Libretro no són compatibles. + Només s\'admet una textura d\'entrada. Elimina \\\"uniforme %1$s %2$s;\\\". + El paràmetre \\\"%1$s\\\" s\'ha de declarar com a \\\"uniforme %2$s %1$s;\\\". + El paràmetre \\\"%1$s\\\" es declara com a \\\"uniform %2$s %1$s;\\\", esperat \\\"uniform %3$s %1$s;\\\". + El fitxer shader està buit. + El fitxer shader ha de ser un objecte JSON .itshader amb camps de versió, nom i shader. + El fitxer Shader no és un JSON vàlid o no coincideix amb el format .itshader. + El camp \\\"%1$s\\\" és obligatori. + %1$s és obligatori. + %1$s \\\"%2$s\\\" no és compatible. Tipus compatibles: %3$s. + %1$s és necessari per als paràmetres \\\"%2$s\\\". + %1$s ha de ser un nombre finit. + %1$s ha de ser un nombre enter. + %1$s ha de ser vertader o fals. + %1$s ha de ser una cadena de color, una matriu RGB/RGBA o un objecte de color. + %1$s ha d\'utilitzar el format #RRGGBB o #RRGGBBAA. + %1$s ha de contenir canals de color hexadecimals vàlids. + %1$s ha de contenir 3 o 4 canals de color. + %1$s ha d\'estar entre 0 i 255. + %1$s ha de ser una matriu de dos números o un objecte amb x i y. + %1$s ha de contenir exactament 2 números. + Duplicat + Netegeu sempre EXIF + Elimineu les dades EXIF ​​d\'imatge en desar, fins i tot quan una eina sol·liciti conservar les metadades + Ajuda i consells + %1$d tutorials + Eina oberta + Conegueu les eines principals, les opcions d\'exportació, els fluxos PDF, les utilitats de color i les solucions per a problemes habituals + Per començar + Trieu eines, importeu fitxers, deseu resultats i reutilitzeu la configuració més ràpidament + Edició d\'imatges + Canvia la mida, retalla, filtra, esborra fons, dibuixa i imatges de filigrana + Fitxers i metadades + Converteix formats, compara la sortida, protegeix la privadesa i controla els noms de fitxers + PDF i documents + Creeu PDF, escanegeu documents, pàgines OCR i prepareu fitxers per compartir-los + Text, QR i dades + Reconèixer text, escanejar codis, codificar Base64, comprovar els hash i paquets de fitxers + Eines de color + Trieu colors, creeu paletes, exploreu biblioteques de colors i creeu degradats + Eines creatives + Genereu SVG, art ASCII, textures de soroll, ombrejats i visuals experimentals + Resolució de problemes + Solucioneu problemes de fitxers pesats, transparència, compartició, importacions i informes + Trieu l\'eina adequada + Utilitzeu categories, cerques, preferits i compartiu accions per començar al lloc correcte + Comenceu des del millor punt d\'entrada + Image Toolbox té moltes eines enfocades, i moltes d\'elles es superposen a primera vista. Comenceu des de la tasca que voleu acabar i, a continuació, trieu l\'eina que controla la part més important del resultat: mida, format, metadades, text, pàgines PDF, colors o disseny. + Utilitzeu la cerca quan conegueu el nom de l\'acció, com ara canviar la mida, PDF, EXIF, OCR, QR o color. + Obriu una categoria quan només conegueu el tipus de tasca i, a continuació, fixeu les eines freqüents com a preferides. + Quan una altra aplicació comparteix una imatge a Image Toolbox, trieu l\'eina del flux del full de compartició. + Importeu, deseu i compartiu resultats + Entendre el flux habitual des de la selecció d\'entrada fins a l\'exportació del fitxer editat + Seguiu el flux d\'edició comú + La majoria de les eines segueixen el mateix ritme: triar l\'entrada, ajustar les opcions, previsualitzar i després desar o compartir. El detall important és que desar crea un fitxer de sortida, mentre que compartir sol ser el millor per a un lliurament ràpid a una altra aplicació. + Trieu un fitxer per a les eines d\'una sola imatge o diversos fitxers per a les eines per lots quan el selector ho permeti. + Comproveu la vista prèvia i els controls de sortida abans de desar, especialment les opcions de format, qualitat i nom de fitxer. + Utilitzeu compartir per a una transferència ràpida o deseu quan necessiteu una còpia persistent a la carpeta seleccionada. + Treballeu amb moltes imatges alhora + Les eines per lots són les millors per a tasques repetides de canvi de mida, conversió, compressió i nom + Prepareu un lot previsible + El processament per lots és més ràpid quan cada sortida ha de seguir les mateixes regles. També és el lloc més fàcil per cometre un gran error, així que trieu el nom, la qualitat, les metadades i el comportament de la carpeta abans d\'iniciar una exportació llarga. + Seleccioneu totes les imatges relacionades juntes i mantingueu l\'ordre si la sortida depèn de la seqüència. + Estableix les regles de canvi de mida, format, qualitat, metadades i nom de fitxer una vegada abans de començar l\'exportació. + Executeu primer un petit lot de prova quan els fitxers font siguin grans o quan el format de destinació sigui nou per a vosaltres. + Edició única ràpida + Utilitzeu l\'editor tot en un quan necessiteu diversos canvis senzills en una imatge + Acabeu una imatge sense saltar d\'eines + L\'edició única és útil quan la imatge necessita una petita cadena de canvis en lloc d\'una operació especialitzada. Normalment és més ràpid per a una neteja ràpida, visualització prèvia i exportació d\'una còpia final sense crear un flux de treball per lots. + Obriu l\'edició única per a una imatge que necessiti ajustos comuns, marques, retallades, rotacions o canvis d\'exportació. + Apliqueu primer les edicions en l\'ordre que canviïn menys píxels, com ara retallar abans dels filtres i els filtres abans de la compressió final. + Utilitzeu una eina especialitzada quan necessiteu processament per lots, objectius exactes de mida de fitxer, sortida PDF, OCR o neteja de metadades. + Utilitzeu els valors predefinits i la configuració + Deseu les opcions repetides i ajusteu l\'aplicació per al vostre flux de treball preferit + Eviteu repetir la mateixa configuració + Els valors predefinits i la configuració són útils quan exporteu sovint el mateix tipus de fitxer. Una bona configuració predeterminada converteix una llista de verificació manual repetida en un sol toc, mentre que la configuració global defineix els valors predeterminats amb què comencen les noves eines. + Creeu valors predefinits per a mides de sortida, formats, valors de qualitat o patrons de noms habituals. + Reviseu la configuració del format predeterminat, la qualitat, la carpeta desa, el nom del fitxer, el selector i el comportament de la interfície. + Fes una còpia de seguretat de la configuració abans de tornar a instal·lar l\'aplicació o de passar a un altre dispositiu. + Estableix valors predeterminats útils + Trieu el format predeterminat, la qualitat, el mode d\'escala, el tipus de canvi de mida i l\'espai de color una vegada + Feu que les noves eines comencin més a prop del vostre objectiu + Els valors per defecte no són només preferències estètiques. Ells decideixen quin format, qualitat, comportament de canvi de mida, mode d\'escala i espai de color apareixen primer en moltes eines, cosa que ajuda a evitar tornar a seleccionar les mateixes opcions a cada exportació. + Estableix el format i la qualitat d\'imatge per defecte perquè coincideixin amb la teva destinació habitual, com ara JPEG per a fotos o PNG/WebP per alfa. + Ajusteu el tipus de canvi de mida i el mode d\'escala predeterminats si sovint exporteu per a dimensions estrictes, plataformes socials o pàgines de documents. + Canvieu els valors predeterminats de l\'espai de color només quan el vostre flux de treball necessiti una gestió coherent del color en pantalles, impressió o editors externs. + Selector i llançador + Utilitzeu la configuració del selector quan l\'aplicació obre massa diàlegs o s\'iniciï al lloc equivocat + Feu que obrir fitxers coincideixi amb el vostre hàbit + La configuració del selector i del llançador controla si Image Toolbox s\'inicia des de la pantalla principal, una eina o un flux de selecció de fitxers. Aquestes opcions són especialment útils quan normalment entreu des del full de compartició d\'Android o quan voleu que l\'aplicació se senti més com un llançador d\'eines. + Utilitzeu la configuració del selector de fotos si el selector del sistema amaga fitxers, mostra massa elements recents o gestiona malament els fitxers del núvol. + Habiliteu la selecció de saltar només per a les eines on normalment introduïu des de la compartició o ja coneixeu el fitxer font. + Reviseu el mode de llançador si voleu que Image Toolbox es comporti més com un selector d\'eines que com una pantalla d\'inici normal. + Font de la imatge + Trieu el mode de selecció que funcioni millor amb galeries, gestors de fitxers i fitxers al núvol + Trieu fitxers de la font correcta + La configuració de la font d\'imatge afecta com l\'aplicació demana imatges a Android. La millor opció depèn de si els vostres fitxers viuen a la galeria del sistema, una carpeta del gestor de fitxers, un proveïdor de núvol o una altra aplicació que comparteixi contingut temporal. + Utilitzeu el selector predeterminat quan vulgueu l\'experiència més integrada al sistema per a les imatges de galeria habituals. + Canvia el mode de selecció si els àlbums, les carpetes, els fitxers al núvol o els formats no estàndard no apareixen on esperes. + Si un fitxer compartit desapareix després de sortir de l\'aplicació d\'origen, torneu-lo a obrir mitjançant un selector persistent o primer deseu una còpia local. + Preferits i eines per compartir + Manteniu la pantalla principal enfocada i amagueu les eines que no tenen sentit en els fluxos de compartició + Redueix el soroll de la llista d\'eines + Els paràmetres preferits i amagats per compartir són útils quan només utilitzeu poques eines cada dia. També mantenen pràctic el flux de compartició amagant eines que no poden utilitzar el tipus de fitxer que envia una altra aplicació. + Fixeu les eines freqüents perquè siguin fàcils d\'arribar fins i tot quan les eines s\'agrupin per categories. + Amaga les eines dels fluxos de compartició quan siguin irrellevants per als fitxers procedents d\'una altra aplicació. + Utilitzeu la configuració de comanda preferida si preferiu els preferits al final o agrupats amb eines relacionades. + Còpia de seguretat de la configuració + Mou els valors predefinits, les preferències i el comportament de l\'aplicació a una altra instal·lació de manera segura + Mantingueu la vostra configuració portàtil + La còpia de seguretat i la restauració és més útil després d\'haver ajustat els valors predefinits, les carpetes, les regles de noms de fitxers, l\'ordre de les eines i les preferències visuals. Una còpia de seguretat us permet experimentar amb la configuració o tornar a instal·lar l\'aplicació sense reconstruir el vostre flux de treball des de la memòria. + Creeu una còpia de seguretat després de canviar els valors predefinits, el comportament del nom de fitxer, els formats predeterminats o la disposició de les eines. + Emmagatzemeu la còpia de seguretat en algun lloc fora de la carpeta de l\'aplicació si teniu previst esborrar dades, reinstal·lar o moure dispositius. + Restaura la configuració abans de processar lots importants perquè els valors predeterminats i el comportament de desar coincideixin amb la teva configuració antiga. + Canviar la mida i convertir les imatges + Canvia la mida, el format, la qualitat i les metadades d\'una o moltes imatges + Creeu còpies redimensionades + Redimensionar i convertir és l\'eina principal per a dimensions predictibles i fitxers de sortida més lleugers. Utilitzeu-lo quan la mida de la imatge, el format de sortida i el comportament de les metadades siguin importants al mateix temps. + Trieu una o més imatges i, a continuació, trieu si les dimensions, l\'escala o la mida del fitxer són més importants. + Seleccioneu el format i la qualitat de sortida; utilitzeu PNG o WebP quan s\'ha de preservar la transparència. + Previsualitza el resultat i després desa o comparteix les còpies generades sense canviar els originals. + Canvia la mida per mida del fitxer + Orienta un límit de mida quan un lloc web, un missatger o un formulari rebutgen imatges pesades + S\'ajusta a límits estrictes de càrrega + Canviar la mida per mida del fitxer és millor que endevinar valors de qualitat quan la destinació només accepta fitxers per sota d\'un límit específic. L\'eina pot ajustar la compressió i les dimensions per arribar a l\'objectiu mentre intenta mantenir el resultat utilitzable. + Introduïu la mida objectiu lleugerament per sota del límit de càrrega real per deixar espai per als canvis de metadades i de proveïdor. + Preferiu els formats amb pèrdues per a les fotos quan l\'objectiu és petit; PNG pot mantenir-se gran fins i tot després de canviar la mida. + Inspeccioneu les cares, el text i les vores després de l\'exportació perquè els objectius de mida agressiu poden introduir artefactes visibles. + Limita el canvi de mida + Reduïu només les imatges que superin les vostres limitacions d\'amplada, alçada o fitxers màximes + Eviteu canviar la mida de les imatges que ja estan bé + Limit Resize és útil per a carpetes mixtes on algunes imatges són grans i d\'altres ja estan preparades. En lloc de forçar tots els fitxers a canviar la mateixa mida, manté les imatges acceptables més a prop de la seva qualitat original. + Establiu dimensions o límits màxims en funció de la destinació en lloc de canviar la mida de cada fitxer a cegues. + Activeu el comportament d\'estil de salt si és més gran quan vulgueu evitar sortides que es facin més pesades que les fonts. + Utilitzeu-ho per a lots de diferents càmeres, captures de pantalla o baixades on les mides de les fonts varien molt. + Retalla, gira i redreça + Emmarca l\'àrea important abans d\'exportar o utilitzar la imatge en altres eines + Netegeu la composició + El retall primer fa que els filtres posteriors, OCR, pàgines PDF i els resultats compartits siguin més nets. + Obriu Retalla i trieu la imatge que voleu ajustar. + Utilitzeu un retall gratuït o una relació d\'aspecte quan la sortida ha d\'ajustar-se a una publicació, un document o un avatar. + Gireu o gireu abans de desar perquè les eines posteriors rebin la imatge corregida. + Aplicar filtres i ajustaments + Creeu una pila de filtres editable i previsualitzeu el resultat abans de l\'exportació + Ajusta l\'aspecte de la imatge + Els filtres són útils per a correccions ràpides, resultats estilitzats i preparar imatges per a OCR o impressió. Els petits canvis en el contrast, la nitidesa i la brillantor poden importar més que els efectes pesats quan la imatge conté text o detalls fins. + Afegeix els filtres un per un i vigila la vista prèvia després de cada canvi. + Ajusteu la brillantor, el contrast, la nitidesa, el desenfocament, el color o les màscares en funció de la tasca. + Exporteu només quan la previsualització coincideixi amb el vostre dispositiu, document o plataforma social objectiu. + Vista prèvia primer + Inspeccioneu les imatges abans de triar una eina d\'edició, conversió o neteja més pesada + Comproveu el que realment heu rebut + La vista prèvia d\'imatges és útil quan un fitxer prové del xat, l\'emmagatzematge al núvol o una altra aplicació i no esteu segur del que conté. Comprovar les dimensions, la transparència, l\'orientació i la qualitat visual primer ajuda a triar l\'eina de seguiment correcta. + Obriu la vista prèvia de la imatge quan necessiteu inspeccionar diverses imatges abans de decidir què fer-hi. + Busqueu problemes de rotació, àrees transparents, artefactes de compressió i dimensions inesperadament grans. + Passeu a Canviar la mida, Retallar, Comparar, EXIF ​​o Eliminar fons només després de saber què cal arreglar. + Elimina o restaura un fons + Esborra els fons automàticament o perfecciona les vores manualment amb les eines de restauració + Mantingueu el tema, elimineu la resta + L\'eliminació de fons funciona millor quan combineu la selecció automàtica amb una neteja manual acurada. El format d\'exportació final importa tant com la màscara, perquè JPEG aplanarà les zones transparents encara que la vista prèvia sembli correcta. + Comenceu amb l\'eliminació automàtica si el subjecte està clarament separat del fons. + Apropa i canvia entre esborrar i restaurar per arreglar cabells, cantonades, ombres i petits detalls. + Deseu com a PNG o WebP quan necessiteu transparència al fitxer final. + Dibuixa, marca i marca d\'aigua + Afegiu fletxes, notes, ressalts, signatures o superposicions de filigrana repetides + Afegeix informació visible + Les eines de marcatge ajuden a explicar una imatge, mentre que la marca d\'aigua ajuda a marcar o protegir els fitxers exportats. + Utilitzeu Draw quan necessiteu notes a mà alçada, formes, ressaltats o anotacions ràpides. + Utilitzeu la marca d\'aigua quan la mateixa marca de text o imatge hagi d\'aparèixer de manera coherent a les sortides. + Comproveu l\'opacitat, la posició i l\'escala abans d\'exportar perquè la marca sigui visible però no distregui. + Dibuixa els valors predeterminats + Estableix l\'amplada de línia, el color, el mode de ruta, el bloqueig d\'orientació i la lupa per a un marcatge més ràpid + Feu que el dibuix se senti previsible + La configuració del dibuix és útil quan anoteu captures de pantalla, marqueu documents o firmeu imatges sovint. Els valors predeterminats redueixen el temps de configuració, mentre que la lupa i el bloqueig d\'orientació faciliten les edicions precises en pantalles petites. + Estableix l\'amplada de línia predeterminada i dibuixa el color amb els valors que més fas servir per a fletxes, ressalts o signatures. + Utilitzeu el mode de camí predeterminat quan normalment dibuixeu el mateix tipus de traços, formes o marques. + Activa la lupa o el bloqueig d\'orientació quan l\'entrada dels dits fa que els petits detalls siguin difícils de col·locar amb precisió. + Dissenys de diverses imatges + Trieu l\'eina de diverses imatges adequada abans d\'organitzar captures de pantalla, escanejos o tires de comparació + Utilitzeu l\'eina de disseny adequada + Aquestes eines sonen semblants, però cadascuna està ajustada per a un tipus diferent de sortida de diverses imatges. Escollir el correcte primer evita lluitar contra els controls de disseny dissenyats per a un resultat diferent. + Utilitzeu Collage Maker per a dissenys dissenyats amb diverses imatges en una composició. + Utilitzeu la unió o l\'apilament d\'imatges quan les imatges s\'hagin de convertir en una tira llarga vertical o horitzontal. + Utilitzeu la divisió d\'imatges quan una imatge gran ha de convertir-se en diverses peces més petites. + Converteix formats d\'imatge + Creeu còpies JPEG, PNG, WebP, AVIF, JXL i altres sense editar els píxels manualment + Trieu el format per a la feina + Els diferents formats són millors per a fotos, captures de pantalla, transparència, compressió o compatibilitat. La conversió és més segura quan enteneu què admet l\'aplicació de destinació i si la font conté alfa, animació o metadades que voleu conservar. + Utilitzeu JPEG per a fotos compatibles, PNG per a imatges alfa i sense pèrdues i WebP per compartir-les compactes. + Ajusteu la qualitat quan el format ho admeti; els valors més baixos redueixen la mida però poden afegir artefactes. + Conserveu els originals fins que verifiqueu que els fitxers convertits s\'obren correctament a l\'aplicació de destinació. + Comproveu EXIF ​​i privadesa + Consulta, edita o elimina metadades abans de compartir fotos sensibles + Controla les dades d\'imatge ocultes + EXIF pot contenir dades de la càmera, dates, ubicació, orientació i altres detalls que no es veuen a la imatge. Val la pena comprovar-ho abans de compartir en públic, informes d\'assistència, llistats del mercat o qualsevol foto que pugui revelar un lloc privat. + Obriu les eines EXIF ​​abans de compartir fotos que poden contenir informació sobre la ubicació o el dispositiu privat. + Elimineu camps sensibles o editeu etiquetes incorrectes, com ara dades de data, orientació, autor o càmera. + Deseu una còpia nova i compartiu-la en comptes de l\'original quan la privadesa sigui important. + Neteja automàtica d\'EXIF + Elimineu les metadades de manera predeterminada mentre decidiu si s\'han de conservar les dates + Feu que la privadesa sigui la predeterminada + El grup de configuració EXIF ​​és útil quan voleu que la neteja de privadesa es faci automàticament en lloc de recordar-ho per a cada exportació. Mantenir la data i l\'hora és una decisió independent perquè les dates poden ser útils per ordenar però sensibles per compartir. + Activeu EXIF ​​sempre clar quan la majoria de les vostres exportacions estiguin destinades a compartir-les en públic o semipúblic. + Habiliteu Conserva la data i l\'hora només quan conservar el temps de captura és més important que eliminar totes les marques de temps. + Utilitzeu l\'edició manual EXIF ​​per a fitxers únics on necessiteu conservar alguns camps i eliminar-ne d\'altres. + Controlar els noms dels fitxers + Utilitzeu prefixos, sufixos, marques de temps, valors predefinits, sumes de comprovació i números de seqüència + Feu que les exportacions siguin fàcils de trobar + Un bon patró de nom de fitxer ajuda a ordenar els lots i evita sobreescritures accidentals. La configuració del nom de fitxer és especialment útil quan les exportacions tornen a la carpeta d\'origen, on els noms similars poden confondre ràpidament. + Estableix el prefix del nom del fitxer, el sufix, la marca de temps, el nom original, la informació predefinida o les opcions de seqüència a Configuració. + Utilitzeu números de seqüència per a lots on l\'ordre importa, com ara pàgines, marcs o collages. + Habiliteu la sobreescritura només quan esteu segur que substituir els fitxers existents és segur per a la carpeta actual. + Sobreescriu els fitxers + Substituïu les sortides per noms coincidents només quan el vostre flux de treball sigui intencionadament destructiu + Utilitzeu el mode de sobreescritura amb cura + Sobreescriure fitxers canvia com es gestionen els conflictes de noms. És útil per a exportacions repetides a la mateixa carpeta, però també pot substituir els resultats anteriors si el vostre patró de nom de fitxer torna a produir el mateix nom. + Habiliteu la sobreescritura només per a les carpetes on s\'espera substituir els fitxers generats més antics i es poden recuperar. + Manteniu la sobreescritura desactivada quan exporteu originals, fitxers de client, escanejos puntuals o qualsevol cosa sense una còpia de seguretat. + Combineu la sobreescritura amb un patró de nom de fitxer deliberat perquè només es substitueixin les sortides previstes. + Patrons de nom de fitxer + Combina noms originals, prefixos, sufixos, marques de temps, valors predefinits, mode d\'escala i sumes de comprovació + Construeix noms que expliquen la sortida + La configuració del patró de nom de fitxer pot convertir els fitxers exportats en un historial útil del que els va passar. Això és important per lots perquè la vista de carpetes pot ser l\'únic lloc on podeu distingir la mida original, la configuració predeterminada, el format i l\'ordre de processament. + Utilitzeu el nom de fitxer original, el prefix, el sufix i la marca de temps quan necessiteu noms llegibles per a l\'ordenació manual. + Afegiu informació predeterminada, mode d\'escala, mida del fitxer o suma de comprovació quan les sortides hagin de documentar com s\'han produït. + Eviteu els noms aleatoris per als fluxos de treball en què els fitxers han de quedar emparellats amb els originals o l\'ordre de les pàgines. + Trieu on es desaran els fitxers + Eviteu perdre les exportacions configurant carpetes, desant les carpetes originals i desades ubicacions d\'una vegada + Mantenir les sortides previsibles + El comportament d\'estalvi és més important quan processeu molts fitxers o compartiu fitxers de proveïdors de núvol. La configuració de la carpeta decideix si les sortides es recullen en un lloc conegut, apareixen al costat dels originals o si van temporalment a un altre lloc per a una tasca específica. + Establiu una carpeta per desar per defecte si voleu exportar sempre en un sol lloc. + Utilitzeu desar a la carpeta original per a fluxos de treball de neteja on la sortida s\'ha de quedar a prop del fitxer font. + Utilitzeu la ubicació de desat d\'un sol cop quan una única tasca necessiti una destinació diferent sense canviar la vostra predeterminada. + Desar carpetes d\'una sola vegada + Envieu les properes exportacions a una carpeta temporal sense canviar la vostra destinació normal + Mantenir els treballs especials separats + La ubicació d\'emmagatzematge d\'una sola vegada és útil per a projectes temporals, carpetes de client, sessions de neteja o exportacions que no s\'han de barrejar amb la carpeta habitual de la Caixa d\'eines d\'imatge. Ofereix una destinació de curta durada sense reescriure la configuració predeterminada. + Trieu una carpeta única abans d\'iniciar una tasca que pertanyi fora de la vostra ubicació d\'exportació normal. + Utilitzeu-lo per a projectes curts, carpetes compartides o lots on les sortides han de quedar agrupades. + Torneu a la carpeta predeterminada després de la feina perquè les exportacions posteriors no arribin inesperadament a la ubicació temporal. + Equilibri la qualitat i la mida del fitxer + Entendre quan canviar les dimensions, el format o la qualitat en lloc d\'endevinar + Reduïu els fitxers intencionadament + La mida del fitxer depèn de les dimensions de la imatge, el format, la qualitat, la transparència i la complexitat del contingut. Si un fitxer encara és massa pesat, canviar les dimensions normalment ajuda més que a reduir la qualitat repetidament. + Canvieu la mida primer quan la imatge sigui més gran del que pot mostrar la destinació. + Baixa qualitat només per als formats amb pèrdua i comproveu el text, les vores, els degradats i les cares després de l\'exportació. + Canvia el format quan els canvis de qualitat no són suficients, per exemple WebP per compartir o PNG per a actius transparents nítids. + Treballar amb formats animats + Utilitzeu les eines GIF, APNG, WebP i JXL quan un fitxer tingui marcs en lloc d\'un mapa de bits + No tracteu totes les imatges com a immòbils + Els formats animats necessiten eines que entenguin els fotogrames, la durada i la compatibilitat. + Utilitzeu les eines GIF o APNG quan necessiteu operacions a nivell de fotograma per a aquests formats. + Utilitzeu les eines WebP quan necessiteu una compressió moderna o una sortida WebP animada. + Previsualitza el temps de l\'animació abans de compartir-la perquè algunes plataformes converteixen o aplanen les imatges animades. + Compara i previsualitza la sortida + Inspeccioneu els canvis, la mida del fitxer i les diferències visuals abans de mantenir una exportació + Comprova abans de compartir + Les eines de comparació ajuden a detectar els artefactes de compressió, els colors incorrectes o les edicions no desitjades abans d\'hora. + Obriu Compara quan vulgueu inspeccionar dues versions una al costat de l\'altra. + Amplieu les vores, el text, els degradats i les regions transparents on els problemes són més fàcils de perdre. + Si la sortida és massa pesada o borrosa, torneu a l\'eina anterior i ajusteu la qualitat o el format. + Utilitzeu el centre d\'eines PDF + Combina, divideix, gira, elimina pàgines, comprimeix, protegeix, OCR i fitxers PDF de filigrana + Trieu una operació PDF + El centre de PDF agrupa les accions del document darrere d\'un punt d\'entrada perquè pugueu triar l\'operació exacta. Comenceu allà quan el fitxer ja sigui un PDF i utilitzeu Imatges a PDF o Escàner de documents quan la vostra font encara sigui un conjunt d\'imatges. + Obriu Eines PDF i trieu l\'operació que coincideixi amb el vostre objectiu. + Seleccioneu el PDF o les imatges d\'origen i, a continuació, reviseu l\'ordre de la pàgina, la rotació, la protecció o les opcions d\'OCR. + Deseu el PDF generat i obriu-lo una vegada per confirmar el recompte de pàgines, l\'ordre i la llegibilitat. + Converteix les imatges en un PDF + Combina escanejos, captures de pantalla, rebuts o fotos en un sol document compartible + Construeix un document net + Les imatges es converteixen en pàgines PDF millors quan es retallen, s\'ordenen i es mida de manera coherent. Tracteu la llista d\'imatges com una pila de pàgines: cada opció de rotació, marge i mida de pàgina afecta la lectura del document final. + Retalla o gira les imatges primer quan les vores, les ombres o l\'orientació de la pàgina són incorrectes. + Seleccioneu les imatges en l\'ordre de pàgina previst i ajusteu la mida o els marges de la pàgina si l\'eina els ofereix. + Exporteu el PDF i comproveu que totes les pàgines es puguin llegir abans d\'enviar-lo. + Escaneja documents + Captureu pàgines en paper i prepareu-les per a PDF, OCR o per compartir-les + Captura pàgines llegibles + L\'escaneig de documents funciona millor amb pàgines planes, una bona il·luminació i una perspectiva corregida. Una exploració neta abans d\'exportar OCR o PDF estalvia temps perquè el reconeixement i la compressió del text depenen de la qualitat de la pàgina. + Col·loqueu el document en una superfície contrastada amb prou llum i ombres mínimes. + Ajusta les cantonades detectades o retalla manualment perquè la pàgina ompli el marc netament. + Exporteu com a imatges o PDF i, a continuació, executeu OCR si necessiteu text seleccionable. + Protegiu o desbloquegeu fitxers PDF + Utilitzeu les contrasenyes amb cura i manteniu una còpia d\'origen editable quan sigui possible + Gestioneu l\'accés a PDF amb seguretat + Les eines de protecció són útils per compartir controlat, però les contrasenyes són fàcils de perdre i difícils de recuperar. Protegiu les còpies per a la distribució, manteniu els fitxers font sense protecció per separat i verifiqueu el resultat abans de suprimir res. + Protegiu una còpia del PDF, no el vostre únic document font. + Guardeu la contrasenya en un lloc segur abans de compartir el fitxer protegit. + Utilitzeu el desbloqueig només per als fitxers que podeu editar i, a continuació, comproveu que la còpia desbloquejada s\'obre correctament. + Organitzar pàgines PDF + Combinar, dividir, reordenar, girar, retallar, eliminar pàgines i afegir números de pàgina deliberadament + Arregla l\'estructura del document abans de la decoració + Les eines de pàgines PDF són més fàcils d\'utilitzar en el mateix ordre en què prepararia un document en paper: recopilar pàgines, eliminar-ne les incorrectes, ordenar-les, orientar-les correctament i, a continuació, afegir detalls finals com ara números de pàgina o filigranes. + Utilitzeu primer la combinació o les imatges a PDF quan el document encara estigui dividit en diversos fitxers. + Reorganitza, gira, retalla o elimina pàgines abans d\'afegir números de pàgina, signatures o marques d\'aigua. + Previsualitza el PDF final després de les edicions estructurals perquè una pàgina que falta o gira pot ser difícil de notar més endavant. + Anotacions en PDF + Elimina les anotacions o aplana-les quan els espectadors mostrin comentaris i marques de manera diferent + Controla allò que queda visible + Les anotacions poden ser comentaris, ressaltats, dibuixos, marques de formulari o altres objectes PDF addicionals. Alguns visors els mostren de manera diferent, de manera que si els elimineu o aplaneu, es pot fer que els documents compartits siguin més previsibles. + Elimineu les anotacions quan no s\'hagin d\'incloure comentaris, ressaltats o marques de ressenya a la còpia compartida. + Aplana quan les marques visibles han de romandre a la pàgina però ja no es comporten com anotacions editables. + Conserveu una còpia original perquè eliminar o aplanar les anotacions pot ser difícil de revertir. + Reconeix el text + Extreu text d\'imatges amb motors i idiomes OCR seleccionables + Obteniu millors resultats OCR + La precisió de l\'OCR depèn de la nitidesa de la imatge, les dades de l\'idioma, el contrast i l\'orientació de la pàgina. Els millors resultats solen ser de preparar la imatge primer: retallar el soroll, girar en posició vertical, augmentar la llegibilitat i després reconèixer el text. + Retalla la imatge a l\'àrea de text i gira-la en posició vertical abans del reconeixement. + Trieu l\'idioma correcte i baixeu les dades d\'idioma si l\'aplicació ho sol·licita. + Copieu o compartiu el text reconegut i, a continuació, comproveu manualment els noms, els números i la puntuació. + Trieu les dades de l\'idioma OCR + Milloreu el reconeixement fent coincidir scripts, idiomes i models OCR baixats + Relaciona OCR amb el text + Un llenguatge OCR incorrecte pot fer que les bones fotos semblin un mal reconeixement. Les dades d\'idioma són importants per als scripts, la puntuació, els números i els documents mixts, així que trieu l\'idioma que apareix a la imatge en lloc de l\'idioma de la interfície d\'usuari del telèfon. + Trieu l\'idioma o l\'escriptura que apareix a la imatge, no només l\'idioma del telèfon. + Baixeu les dades que falten abans de treballar fora de línia o processar un lot. + Per als documents en diferents idiomes, executeu primer l\'idioma més important i comproveu manualment els noms i els números. + PDF cercables + Torneu a escriure el text OCR als fitxers perquè les pàgines escanejades es puguin cercar i copiar + Converteix els escanejos en documents cercables + L\'OCR pot fer més que copiar text al porta-retalls. Quan escriu text reconegut en un fitxer o PDF on es pot cercar, la imatge de la pàgina continua sent visible mentre el text es fa més fàcil de cercar, seleccionar, indexar o arxivar. + Utilitzeu la sortida PDF cercable per als documents escanejats que encara haurien de semblar a les pàgines originals. + Utilitzeu l\'escriptura a fitxer quan només necessiteu text extret i no us preocupeu per la imatge de la pàgina. + Comproveu manualment números, noms i taules importants perquè el text OCR es pot cercar però encara és imperfecte. + Escaneja codis QR + Llegeix el contingut QR de l\'entrada de la càmera o les imatges desades quan estiguin disponibles + Llegeix els codis amb seguretat + Els codis QR poden contenir enllaços, dades de Wi-Fi, contactes, text o altres càrregues útils. + Obriu Escaneja el codi QR i manteniu el codi nítid, pla i completament dins del marc. + Reviseu el contingut descodificat abans d\'obrir enllaços o copiar dades sensibles. + Si l\'escaneig falla, milloreu la il·luminació, retalleu el codi o proveu una imatge font de resolució més alta. + Utilitzeu Base64 i sumes de control + Codifiqueu les imatges com a text, torneu a descodificar el text a imatges i verifiqueu la integritat del fitxer + Moure\'s entre fitxers i text + Base64 és útil per a càrregues útils d\'imatges petites, mentre que les sumes de control ajuden a confirmar que els fitxers no han canviat. Aquestes eines són més útils en fluxos de treball tècnics, informes de suport, transferències de fitxers i llocs on els fitxers binaris s\'han de convertir temporalment en text. + Utilitzeu les eines Base64 per codificar una imatge petita quan un flux de treball necessita text en lloc d\'un fitxer binari. + Descodifiqueu Base64 només a partir de fonts de confiança i verifiqueu la vista prèvia abans de desar. + Utilitzeu les eines de suma de verificació quan necessiteu comparar fitxers exportats o confirmar una càrrega/descàrrega. + Empaquetar o protegir les dades + Utilitzeu eines ZIP i de xifratge per agrupar fitxers o transformar dades de text + Mantingueu els fitxers relacionats junts + Les eines d\'embalatge són útils quan diverses sortides han de viatjar com un sol fitxer. També són bons per mantenir les imatges, els PDF, els registres i les metadades generats junts quan necessiteu enviar un conjunt de resultats complet. + Utilitzeu ZIP quan necessiteu enviar moltes imatges o documents exportats junts. + Utilitzeu les eines de xifrat només quan entengueu el mètode seleccionat i pugueu emmagatzemar les claus necessàries de manera segura. + Proveu l\'arxiu creat o el text transformat abans de suprimir els fitxers font. + Sumes de comprovació en noms + Utilitzeu noms de fitxer basats en suma de comprovació quan la singularitat i la integritat importen més que la llegibilitat + Anomena els fitxers pel contingut + Els noms de fitxer de suma de verificació són útils quan les exportacions poden tenir noms visibles duplicats o quan necessiteu demostrar que dos fitxers són idèntics. Són menys amigables per a la navegació manual, així que utilitzeu-los per a arxius tècnics i fluxos de treball de verificació. + Activeu la suma de comprovació com a nom de fitxer quan la identitat del contingut sigui més important que un títol llegible per persones. + Utilitzeu-lo per a arxius, desduplicacions, exportacions repetibles o fitxers que es puguin carregar i baixar de nou. + Combina els noms de la suma de comprovació amb carpetes o metadades quan la gent encara necessita entendre què representa el fitxer. + Trieu colors de les imatges + Mostra píxels, copieu codis de color i reutilitzeu colors en paletes o dissenys + Mostra el color exacte + La selecció de colors és útil per fer coincidir un disseny, extreure colors de marca o comprovar els valors d\'imatge. El zoom és important perquè l\'antialiasing, les ombres i la compressió poden fer que els píxels veïns siguin lleugerament diferents del color que espereu. + Obriu Trieu color de la imatge i trieu una imatge d\'origen amb el color que necessiteu. + Apropeu-vos abans de mostrar petits detalls perquè els píxels propers no afectin el resultat. + Copieu el color en el format requerit per la vostra destinació, com ara HEX, RGB o HSV. + Creeu paletes i utilitzeu la biblioteca de colors + Extraieu paletes, navegueu pels colors desats i manteniu sistemes visuals coherents + Construeix una paleta reutilitzable + Les paletes ajuden a mantenir la coherència visual dels gràfics, les marques d\'aigua i els dibuixos exportats. Són especialment útils quan anoteu captures de pantalla repetidament, prepareu actius de marca o necessiteu els mateixos colors en diverses eines. + Genereu una paleta a partir d\'una imatge o afegiu colors manualment quan ja coneixeu els valors. + Anomena o organitza els colors importants perquè puguis tornar-los a trobar més tard. + Utilitzeu els valors copiats a les eines de dibuix, filigrana, degradat, SVG o de disseny extern. + Crea gradients + Creeu imatges degradades per a fons, marcadors de posició i experiments visuals + Controla les transicions de color + Les eines de degradat són útils quan necessiteu una imatge generada en lloc d\'editar-ne una existent. Poden convertir-se en fons nets, marcadors de posició, fons de pantalla, màscares o actius senzills per a eines de disseny externes. + Trieu el tipus de degradat i configureu primer els colors importants. + Ajusteu la direcció, les parades, la suavitat i la mida de sortida per al cas d\'ús objectiu. + Exporta en un format que coincideixi amb la destinació, normalment PNG per a gràfics nets generats. + Accessibilitat del color + Utilitzeu colors dinàmics, contrast i opcions daltònics quan la interfície d\'usuari sigui difícil de llegir + Facilita l\'ús dels colors + La configuració del color afecta la comoditat, la llegibilitat i la rapidesa amb què podeu escanejar els controls. Si els xips d\'eines, els controls lliscants o els estats seleccionats són difícils de distingir, les opcions de tema i accessibilitat poden ser més útils que simplement canviar el mode fosc. + Proveu colors dinàmics quan vulgueu que l\'aplicació segueixi la paleta de fons de pantalla actual. + Augmenta el contrast o canvia l\'esquema quan els botons i les superfícies no destaquen prou. + Utilitzeu les opcions d\'esquema de daltònics si alguns estats o accents són difícils de distingir. + Fons alfa + Controleu la vista prèvia o el color de farciment utilitzat al voltant de les sortides PNG transparents, WebP i similars + Entendre les visualitzacions prèvies transparents + El color de fons dels formats alfa pot facilitar la inspecció de les imatges transparents, però és important saber si esteu canviant un comportament de previsualització/emplenament o aplanant el resultat final. Això és important per als adhesius, els logotips i les imatges eliminades de fons. + Utilitzeu primer un format de suport alfa, com ara PNG o WebP, quan els píxels transparents han de sobreviure a l\'exportació. + Ajusteu la configuració del color de fons quan les vores transparents siguin difícils de veure a la superfície de l\'aplicació. + Exporteu una petita prova i torneu-la a obrir en una altra aplicació per confirmar si s\'ha desat la transparència o l\'emplenament escollit. + Elecció del model d\'IA + Trieu un model per tipus d\'imatge i defecte en lloc de triar primer el més gran + Relaciona el model amb el dany + AI Tools pot descarregar o importar diferents models ONNX/ORT, i cada model està entrenat per a un tipus de problema particular. Un model per a blocs JPEG, neteja de línia d\'anime, eliminació de soroll, eliminació de fons, colorització o ampliació d\'escala pot produir pitjors resultats quan s\'utilitza amb un tipus d\'imatge incorrecte. + Llegeix la descripció del model abans de descarregar; tria el model que anomena el teu problema real, com ara compressió, soroll, mitges tintes, desenfocament o eliminació de fons. + Comenceu amb un model més petit o més específic quan proveu un nou tipus d\'imatge i, a continuació, compareu amb models més pesats només si el resultat no és prou bo. + Conserveu només els models que utilitzeu realment, perquè els models descarregats i importats poden ocupar un espai d\'emmagatzematge notable. + Entendre les famílies model + Els noms dels models sovint insinuen el seu objectiu: els models d\'estil RealESRGAN d\'alt nivell, els models SCUNet i FBCNN netegen el soroll o la compressió, els models de fons creen màscares i els coloritzadors afegeixen color a l\'entrada en escala de grisos. Els models personalitzats importats poden ser potents, però haurien de coincidir amb la forma d\'entrada/sortida esperada i utilitzar el format ONNX o ORT compatible. + Utilitzeu augmentadors d\'escala quan necessiteu més píxels, eliminadors de soroll quan la imatge és granulosa i eliminadors d\'artefactes quan els blocs de compressió o les bandes són el problema principal. + Utilitzeu models de fons només per a la separació de temes; no són el mateix que l\'eliminació general d\'objectes o la millora de la imatge. + Importeu models personalitzats només de fonts de confiança i proveu-los en una imatge petita abans d\'utilitzar fitxers importants. + Paràmetres d\'IA + Ajusta la mida dels fragments, la superposició i els treballadors paral·lels per equilibrar la qualitat, la velocitat i la memòria + Equilibra velocitat amb estabilitat + La mida del fragment controla la mida de les peces de la imatge abans de ser processades pel model. La superposició combina els trossos veïns per amagar les vores. Els treballadors paral·lels poden accelerar el processament, però cada treballador necessita memòria, de manera que els valors alts poden fer que les imatges grans siguin inestables en telèfons amb memòria RAM limitada. + Utilitzeu fragments més grans per obtenir un context més suau quan el vostre dispositiu gestioni el model de manera fiable i la imatge no sigui gran. + Augmenteu la superposició quan les vores dels trossos siguin visibles, però recordeu que més solapament significa més treball repetit. + Aixecar treballadors paral·lels només després d\'una prova estable; si el processament s\'alenteix, s\'escalfa el dispositiu o s\'estavella, primer reduïu els treballadors. + Eviteu els artefactes en trossos + Chunking permet que l\'aplicació processi imatges més grans del que el model pot gestionar còmodament alhora. La compensació és que cada fitxa té menys context circumdant, de manera que una superposició baixa o trossos massa petits poden crear vores visibles, canvis de textura o detalls inconsistents entre les zones veïnes. + Augmenteu la superposició si veieu vores rectangulars, canvis repetits de textura o salts de detall entre regions processades. + Reduïu la mida del tros si el procés falla abans de produir la sortida, especialment en imatges grans o models pesats. + Deseu una petita prova de retall abans de processar la imatge completa perquè pugueu comparar els paràmetres ràpidament. + estabilitat de la IA + Reduïu la mida del fragment, els treballadors i la resolució de la font quan el processament d\'IA es bloqueja o es bloqueja + Recuperació de treballs pesats d\'IA + El processament d\'IA és un dels fluxos de treball més pesats de l\'aplicació. Els bloquejos, les sessions fallides, les congelacions o els reinicis sobtats d\'aplicacions solen significar que el model, la resolució d\'origen, la mida dels fragments o el nombre de treballadors paral·lels és massa exigent per a l\'estat actual del dispositiu. + Si l\'aplicació es bloqueja o no pot obrir una sessió de model, reduïu els treballadors paral·lels i la mida del fragment i torneu a provar la mateixa imatge. + Canvia la mida d\'imatges molt grans abans del processament d\'IA quan l\'objectiu no requereix la resolució original. + Tanqueu altres aplicacions pesades, utilitzeu un model més petit o processeu menys fitxers alhora quan la pressió de la memòria continuï tornant. + Diagnosticar les fallades del model + Una execució fallida de la IA no sempre vol dir que la imatge sigui dolenta. El fitxer del model pot ser incomplet, no admès, massa gran per a la memòria o no coincidir amb l\'operació. Quan l\'aplicació informa d\'una sessió fallida, tracteu-la com un senyal per simplificar primer el model i els paràmetres. + Suprimiu i torneu a baixar un model si falla immediatament després de la descàrrega o es comporta com si el fitxer estigués malmès. + Proveu un model descarregable integrat abans de culpar un model personalitzat importat, perquè els models importats poden tenir entrades incompatibles. + Utilitzeu els registres d\'aplicacions després de reproduir l\'error si necessiteu informar d\'una fallada o d\'un error de sessió. + Experimenta a Shader Studio + Utilitzeu efectes d\'ombrejat de GPU per a un processament d\'imatges avançat i aspectes personalitzats + Treballeu amb cura amb els shaders + Shader Studio és potent, però el codi i els paràmetres shader necessiten canvis petits i comprovables. Tracteu-lo com una eina experimental: manteniu una línia de base que funcioni, canvieu una cosa a la vegada i proveu amb diferents mides d\'imatge abans de confiar en un valor predefinit. + Comenceu des d\'un ombrejat de treball conegut o un efecte simple abans d\'afegir paràmetres. + Canvieu un paràmetre o un bloc de codi alhora i comproveu si hi ha errors a la vista prèvia. + Exporteu els valors predefinits només després de provar-los en unes quantes mides d\'imatge diferents. + Feu un art SVG o ASCII + Genereu recursos similars a vectors o imatges basades en text per obtenir resultats creatius + Trieu el tipus de sortida de la creativitat + Les eines SVG i ASCII serveixen a diferents objectius: gràfics escalables o representació d\'estil de text. Totes dues són conversions creatives, de manera que la previsualització a la mida final és més important que jutjar el resultat a partir d\'una miniatura petita. + Utilitzeu SVG Maker quan el resultat s\'hagi d\'escalar de manera neta o reutilitzar-se en els fluxos de treball de disseny. + Utilitzeu l\'art ASCII quan vulgueu una representació de text estilitzada d\'una imatge. + Vista prèvia a la mida final perquè els petits detalls poden desaparèixer a la sortida generada. + Genera textures de soroll + Creeu textures procedimentals per a fons, màscares, superposicions o experiments + Ajusta la sortida del procediment + La generació de soroll crea noves imatges a partir de paràmetres, de manera que petits canvis poden produir resultats molt diferents. És útil per a textures, màscares, superposicions, marcadors de posició o provar la compressió amb patrons detallats. + Trieu un tipus de soroll i una mida de sortida abans d\'ajustar els detalls. + Ajusteu l\'escala, la intensitat, els colors o la llavor fins que el patró s\'ajusti a l\'ús objectiu. + Deseu resultats útils i anoteu els paràmetres si necessiteu recrear la textura més tard. + Projectes de marcatge + Utilitzeu fitxers de projecte quan les anotacions s\'hagin de poder editar després de tancar l\'aplicació + Mantingueu les edicions en capes reutilitzables + Els fitxers de projecte de marcatge són útils quan una captura de pantalla, un diagrama o una imatge d\'instruccions poden necessitar canvis més endavant. Els fitxers PNG/JPEG exportats són imatges finals, mentre que els fitxers del projecte conserven les capes editables i l\'estat d\'edició per a ajustos futurs. + Utilitzeu les capes de marcatge quan les anotacions, els textos destacats o els elements de disseny han de romandre editables. + Deseu o torneu a obrir el fitxer del projecte abans d\'exportar una imatge final si espereu més revisions. + Exporteu una imatge normal només quan estigueu preparat per compartir una versió final aplanada. + Xifra fitxers + Protegiu els fitxers amb una contrasenya i proveu el desxifrat abans d\'esborrar qualsevol còpia d\'origen + Gestioneu les sortides xifrades amb cura + Les eines de xifratge poden protegir els fitxers amb un xifratge basat en contrasenyes, però no són un reemplaçament per recordar la clau o mantenir còpies de seguretat. El xifratge és útil per al transport i l\'emmagatzematge, mentre que ZIP és millor quan l\'objectiu principal és agrupar diverses sortides. + Encripteu una còpia primer i conserveu l\'original fins que hàgiu provat que el desxifrat funciona amb la contrasenya que heu emmagatzemat. + Utilitzeu un gestor de contrasenyes o un altre lloc segur per a la clau; l\'aplicació no pot endevinar una contrasenya perduda. + Comparteix fitxers xifrats només amb persones que també tinguin la contrasenya i sàpiguen quina eina o mètode els hauria de desxifrar. + Organitzar les eines + Agrupa, ordena, cerca i fixa les eines perquè la pantalla principal coincideixi amb el teu flux de treball real + Feu que la pantalla principal sigui més ràpida + Val la pena ajustar la configuració de la disposició de les eines un cop sàpigues quines eines fas servir més. L\'agrupació ajuda a l\'exploració, l\'ordre personalitzat ajuda a la memòria muscular i els preferits mantenen les eines diàries accessibles fins i tot quan la llista completa es fa gran. + Utilitzeu el mode agrupat mentre apreneu l\'aplicació o quan preferiu les eines organitzades per tipus de tasca. + Canvieu a l\'ordre personalitzat quan ja coneixeu les vostres eines freqüents i voleu menys desplaçaments. + Utilitzeu la configuració de cerca i els preferits junts per a eines rares que recordeu pel nom però que no voleu que es vegin tot el temps. + Manejar fitxers grans + Eviteu les exportacions lentes, la pressió de la memòria i la producció inesperadament gran + Redueix el treball abans d\'exportar + Les imatges molt grans, els lots llargs i els formats d\'alta qualitat poden necessitar més memòria i temps. La solució més ràpida sol ser reduir la quantitat de treball abans de l\'operació més pesada, sense esperar que finalitzi la mateixa entrada sobredimensionada. + Canvia la mida d\'imatges grans abans d\'aplicar filtres pesats, OCR o conversió PDF. + Utilitzeu primer un lot més petit per confirmar la configuració i, a continuació, processeu la resta amb el mateix valor predefinit. + Baixa qualitat o canvia de format quan el fitxer de sortida és més gran del que s\'esperava. + Memòria cau i visualitzacions prèvies + Comprèn les visualitzacions prèvies generades, la neteja de la memòria cau i l\'ús de l\'emmagatzematge després de sessions intenses + Manteniu l\'emmagatzematge i la velocitat equilibrats + La generació de previsualitzacions i la memòria cau poden fer que la navegació repetida sigui més ràpida, però les sessions d\'edició intenses poden deixar dades temporals enrere. La configuració de la memòria cau ajuda quan l\'aplicació se sent lenta, l\'emmagatzematge és reduït o les previsualitzacions semblen obsoletes després de molts experiments. + Mantenir les previsualitzacions activades quan navegueu per moltes imatges és més important que minimitzar l\'emmagatzematge temporal. + Esborra la memòria cau després de grans lots, experiments fallits o sessions llargues amb molts fitxers d\'alta resolució. + Utilitzeu l\'esborrat automàtic de la memòria cau si preferiu netejar l\'emmagatzematge en lloc de mantenir les previsualitzacions calentes entre llançaments. + Ajustar el comportament de la pantalla + Utilitzeu les opcions de pantalla completa, el mode segur, la brillantor i la barra del sistema per treballar centrat + Feu que la interfície s\'adapti a la situació + La configuració de la pantalla ajuda quan editeu en horitzontal, presenteu imatges privades o necessiteu una brillantor constant. No són necessaris per a un ús normal, però solucionen situacions incòmodes com ara la inspecció de QR, les visualitzacions prèvies privades o l\'edició del paisatge limitada. + Utilitzeu la configuració de la pantalla completa o de la barra del sistema quan l\'edició d\'espai sigui més important que la navegació Chrome. + Utilitzeu el mode segur quan les imatges privades no han d\'aparèixer a les captures de pantalla o a les previsualitzacions d\'aplicacions. + Utilitzeu l\'aplicació de la brillantor per a eines on les imatges fosques o els codis QR siguin difícils d\'inspeccionar. + Mantenir la transparència + Eviteu que els fons transparents es tornin blancs, negres o aplanats + Utilitzeu una sortida conscient de l\'alfa + La transparència només sobreviu quan l\'eina seleccionada i el format de sortida admeten alfa. Si un fons exportat es torna blanc o negre, el problema sol ser el format de sortida, una configuració d\'emplenament/fons o una aplicació de destinació que ha aplanat la imatge. + Trieu PNG o WebP quan exporteu imatges, adhesius, logotips o superposicions eliminats de fons. + Eviteu JPEG per a una sortida transparent perquè no emmagatzema un canal alfa. + Comproveu la configuració relacionada amb el color de fons dels formats alfa si la vista prèvia mostra un fons ple. + Solucioneu problemes d\'importació i compartició + Utilitzeu la configuració del selector i els formats admesos quan els fitxers no apareguin o s\'obren correctament + Introdueix el fitxer a l\'aplicació + Els problemes d\'importació solen ser causats pels permisos del proveïdor, els formats no compatibles o el comportament del selector. Els fitxers compartits des d\'aplicacions i missatgers al núvol poden ser temporals, de manera que escollir una font persistent pot ser més fiable per a edicions més llargues. + Proveu d\'obrir el fitxer mitjançant el selector del sistema en lloc d\'una drecera de fitxers recents. + Si un format no és compatible amb una eina, convertiu-lo primer o obriu una eina més específica. + Reviseu la configuració del selector d\'imatges i del llançador si els fluxos compartits o l\'obertura directa de l\'eina es comporten de manera diferent del que s\'esperava. + Confirmació de sortida + Eviteu perdre les edicions no desades quan els gestos, les pressions posteriors o els canvis d\'eina es produeixin accidentalment + Protegir els treballs inacabats + La confirmació de sortida de l\'eina és útil quan utilitzeu la navegació per gestos, editeu fitxers grans o sovint canvieu d\'aplicació. Afegeix una petita pausa abans de sortir d\'una eina, de manera que la configuració i les visualitzacions prèvies no es perdin per accident. + Activeu la confirmació si els gestos enrere accidentals han tancat una eina abans de l\'exportació. + Manteniu-lo desactivat si principalment feu conversions ràpides d\'un sol pas i preferiu una navegació més ràpida. + Utilitzeu-lo juntament amb els valors predefinits per a fluxos de treball més llargs on la reconstrucció de la configuració seria molesta. + Utilitzeu els registres quan informeu de problemes + Recolliu detalls útils abans d\'obrir un problema o contactar amb el desenvolupador + Informar de problemes amb context + Un informe clar amb passos, versió de l\'aplicació, tipus d\'entrada i registres és molt més fàcil de diagnosticar. Els registres són més útils just després de reproduir un problema perquè mantenen fresc el context circumdant. + Tingueu en compte el nom de l\'eina, l\'acció exacta i si passa amb un fitxer o amb cada fitxer. + Obriu els registres d\'aplicacions després de reproduir el problema i compartiu la sortida del registre corresponent quan sigui possible. + Adjunteu captures de pantalla o fitxers de mostra només quan no continguin informació privada. + S\'ha aturat el processament en segon pla + Android no va permetre que la notificació de processament en segon pla comencés a temps. Aquesta és una limitació de temps del sistema que no es pot solucionar de manera fiable. Reinicieu l\'aplicació i torneu-ho a provar; mantenir l\'aplicació oberta i permetre notificacions o treballs en segon pla pot ajudar. + Omet la selecció de fitxers + Obriu les eines més ràpidament quan l\'entrada normalment prové de compartir, porta-retalls o una pantalla anterior + Feu que l\'eina s\'iniciï més ràpidament + Omet la selecció de fitxers canvia el primer pas de les eines admeses. És útil quan normalment introduïu una eina amb una imatge ja seleccionada o des d\'accions de compartició d\'Android, però pot resultar confús si espereu que totes les eines demanin un fitxer immediatament. + Activeu-lo només quan el vostre flux habitual ja proporcioni la imatge d\'origen abans que s\'obri l\'eina. + Manteniu-lo desactivat mentre apreneu l\'aplicació, perquè la selecció explícita fa que cada flux d\'eines sigui més fàcil d\'entendre. + Si s\'obre una eina sense cap entrada òbvia, desactiveu aquesta configuració o comenceu des de Compartir/Obrir amb perquè Android passi primer el fitxer. + Obriu primer l\'editor + Omet la vista prèvia quan una imatge compartida s\'ha d\'editar directament + Trieu la previsualització o l\'edició com a primera parada + Obre Edita en comptes de vista prèvia és per als usuaris que ja saben que volen modificar la imatge. La vista prèvia és més segura quan inspeccioneu fitxers des del xat o de l\'emmagatzematge al núvol; L\'edició directa és més ràpida per a captures de pantalla, retalls ràpids, anotacions i correccions puntuals. + Activeu l\'edició directa si la majoria de les imatges compartides necessiten immediatament retallar, marcar, filtres o exportar canvis. + Deixeu la previsualització primer quan inspeccioneu sovint les metadades, les dimensions, la transparència o la qualitat de la imatge abans de decidir-vos. + Utilitzeu-ho juntament amb els preferits perquè les imatges compartides arribin a prop de les eines que utilitzeu realment. + Entrada de text predeterminada + Introduïu valors preestablerts exactes en lloc d\'ajustar els controls repetidament + Utilitzeu valors precisos quan els controls lliscants siguin lents + L\'entrada de text predeterminada us ajuda quan necessiteu números exactes per a treballs repetits: mides d\'imatge social, marges de documents, objectius de compressió, amplades de línia o altres valors molestos d\'aconseguir amb gestos. També és útil en pantalles petites on el moviment lliscant fi és més difícil. + Activeu l\'entrada de text si sovint reutilitzeu mides exactes, percentatges, valors de qualitat o paràmetres de l\'eina. + Deseu el valor com a valor predefinit després d\'escriure-lo una vegada i, a continuació, reutilitzau-lo en lloc de reconstruir la mateixa configuració. + Mantingueu els controls de gestos per a l\'ajustament visual aproximat i els valors escrits per a requisits de sortida estrictes. + Desa prop de l\'original + Col·loqueu els fitxers generats al costat de les imatges d\'origen quan el context de la carpeta sigui important + Conserveu les sortides amb les seves fonts + Desa a la carpeta original és útil per a carpetes de càmeres, carpetes de projectes, escanejos de documents i lots de clients on la còpia editada hauria de quedar al costat de la font. Pot ser menys ordenat que una carpeta de sortida dedicada, així que combineu-lo amb sufixos o patrons de nom de fitxer clars. + Activeu-lo quan cada carpeta d\'origen ja sigui significativa i les sortides haurien de romandre agrupades allí. + Utilitzeu sufixos de nom de fitxer, prefixos, marques de temps o patrons perquè les còpies editades siguin fàcils de separar dels originals. + Desactiveu-lo per a lots mixts quan vulgueu que tots els fitxers generats es recullin en una carpeta d\'exportació. + Omet una sortida més gran + Eviteu desar les conversions que de manera inesperada es fan més pesades que la font + Protegiu els lots de sorpreses de mala mida + Algunes conversions fan que els fitxers siguin més grans, especialment les captures de pantalla PNG, les fotos ja comprimides, el WebP d\'alta qualitat o les imatges amb metadades conservades. Allow Skip If Larger impedeix que aquestes sortides substitueixin una font més petita en fluxos de treball on reduir la mida és l\'objectiu principal. + Activeu-lo quan l\'exportació estigui destinada a comprimir, canviar la mida o preparar fitxers per als límits de càrrega. + Reviseu els fitxers saltats després d\'un lot; potser ja estan optimitzats o necessiten un format diferent. + Desactiveu-lo quan la qualitat, la transparència o la compatibilitat del format tinguin més importància que la mida final del fitxer. + Porta-retalls i enllaços + Controleu l\'entrada automàtica del porta-retalls i les previsualitzacions d\'enllaços per obtenir eines basades en text més ràpides + Feu que l\'entrada automàtica sigui previsible + La configuració del porta-retalls i dels enllaços és convenient per a fluxos de treball QR, Base64, URL i amb molta text, però l\'entrada automàtica hauria de ser intencionada. Si l\'aplicació sembla omplir els camps per si mateixa, comproveu el comportament d\'enganxar el porta-retalls; si les targetes d\'enllaç són sorolloses o lentes, ajusteu el comportament de la vista prèvia de l\'enllaç. + Permet l\'enganxament automàtic del porta-retalls quan copieu text sovint i obriu immediatament una eina de concordança. + Desactiveu l\'enganxament automàtic si el contingut privat del porta-retalls apareix a les eines on no ho esperàveu. + Desactiveu les previsualitzacions d\'enllaços quan l\'obtenció de la vista prèvia sigui lenta, distregui o no sigui necessària per al vostre flux de treball. + Controls compactes + Utilitzeu selectors més densos i col·locació ràpida de paràmetres quan l\'espai a la pantalla és reduït + Ajusteu més controls a pantalles petites + Els selectors compactes i la col·locació ràpida de la configuració ajuden a telèfons petits, edició horitzontal i eines amb molts paràmetres. La compensació és que els controls poden sentir-se més densos, de manera que això és millor després de reconèixer les opcions habituals. + Activeu els selectors compactes quan els diàlegs o les llistes d\'opcions semblin massa alts per a la pantalla. + Ajusteu la configuració ràpida si els controls de l\'eina són més fàcils d\'arribar des d\'un costat de la pantalla. + Torneu als controls més espaiosos si encara esteu aprenent l\'aplicació o sovint perdeu opcions denses. + Carregueu imatges del web + Enganxeu una pàgina o l\'URL d\'una imatge, previsualitzeu les imatges analitzades i, a continuació, deseu o compartiu els resultats seleccionats + Utilitzeu imatges web deliberadament + La càrrega d\'imatges web analitza les fonts d\'imatge a partir de l\'URL proporcionat, previsualitza la primera imatge disponible i us permet desar o compartir les imatges analitzades seleccionades. Alguns llocs utilitzen enllaços temporals, protegits o generats per scripts, de manera que és possible que una pàgina que funcioni en un navegador encara no torni cap imatge utilitzable. + Enganxeu l\'URL d\'una imatge directa o l\'URL d\'una pàgina i espereu que aparegui la llista d\'imatges analitzades. + Utilitzeu els controls de marc o de selecció quan la pàgina conté diverses imatges i només en necessiteu algunes. + Si no es carrega res, proveu primer un enllaç d\'imatge directe o descarregueu-lo a través del navegador; el lloc pot bloquejar l\'anàlisi o utilitzar enllaços privats. + Trieu el tipus de canvi de mida + Comprèn Explícit, Flexible, Retalla i Ajust abans de forçar una imatge a noves dimensions + Controla la forma, el llenç i la relació d\'aspecte + El tipus de canvi de mida decideix què passa quan l\'amplada i l\'alçada sol·licitades no coincideixen amb la relació d\'aspecte original. És la diferència entre estirar píxels, preservar les proporcions, retallar l\'àrea addicional o afegir un llenç de fons. + Utilitzeu Explícit només quan la distorsió sigui acceptable o la font ja tingui la mateixa relació d\'aspecte que l\'objectiu. + Utilitzeu Flexible per mantenir les proporcions canviant la mida des del costat important, especialment per a fotos i lots barrejats. + Utilitzeu Retalla per a marcs estrictes sense vores, o Ajusta quan tota la imatge ha de romandre visible dins d\'un llenç fix. + Trieu el mode d\'escala d\'imatge + Trieu el filtre de remuestreig que controla la nitidesa, la suavitat, la velocitat i els artefactes de vora + Canviar la mida sense suavitat accidental + El mode d\'escala d\'imatge controla com es calculen els nous píxels durant el canvi de mida. Els modes senzills són ràpids i previsibles, mentre que els filtres avançats poden preservar millor els detalls, però poden afinar els vores, crear halos o trigar més en imatges grans. + Utilitzeu Bàsic o Bilineal per a un canvi de mida ràpid cada dia quan el resultat només ha de ser més petit i net. + Utilitzeu els modes d\'estil Lanczos, Robidoux, Spline o EWA quan els detalls, el text o la reducció d\'alta qualitat siguin importants. + Utilitzeu Nearest per a l\'art de píxels, les icones i els gràfics de vora dur on els píxels borrosos arruïnaria l\'aspecte. + Escala l\'espai de color + Decidiu com es barregen els colors mentre es tornen a mostrejar els píxels durant el canvi de mida + Mantenir els degradats i les vores naturals + L\'escala de l\'espai de color canvia les matemàtiques utilitzades durant el canvi de mida. La majoria d\'imatges estan bé amb el valor predeterminat, però els espais lineals o perceptius poden fer que els degradats, les vores fosques i els colors saturats semblin més nets després d\'una escala forta. + Deixeu el valor predeterminat quan la velocitat i la compatibilitat importin més que les minúscules diferències de color. + Proveu els modes lineals o perceptius quan els contorns foscos, les captures de pantalla de la interfície d\'usuari, els degradats o les bandes de color tinguin un aspecte incorrecte després de canviar la mida. + Compareu abans i després a la pantalla de destinació real, perquè els canvis d\'espai de color poden ser subtils i depenen del contingut. + Limitar els modes de canvi de mida + Saber quan s\'han de saltar, recodificar o reduir les imatges per sobre del límit per adaptar-se + Trieu què passa al límit + Limit Resize no és només una eina per a imatges més petites. El seu mode decideix què fa l\'aplicació quan una font ja està dins dels vostres límits o quan només un costat incompleix la regla, cosa que és important per a carpetes mixtes i la preparació de la càrrega. + Utilitzeu Omet quan les imatges dins dels límits s\'hagin de deixar sense tocar i no tornar a exportar. + Utilitzeu Recode quan les dimensions siguin acceptables però encara necessiteu un format, un comportament de metadades, una qualitat o un nom de fitxer nous. + Utilitzeu Zoom quan les imatges que superen els límits s\'han de reduir proporcionalment fins que s\'ajustin al quadre permès. + Objectius de relació d\'aspecte + Eviteu cares estirades, vores retallades i vores inesperades en mides de sortida estrictes + Fes coincidir la forma abans de canviar la mida + L\'amplada i l\'alçada són només la meitat d\'un objectiu de canvi de mida. La relació d\'aspecte decideix si la imatge pot encaixar de manera natural, necessita retallar-la o necessita un llenç al seu voltant. Comprovar la forma primer evita els resultats de canvi de mida més sorprenents. + Compareu la forma d\'origen amb la forma de destinació abans de triar Explícit, Retalla o Ajust. + Retalla primer quan el subjecte pot perdre fons addicional i la destinació necessita un marc estricte. + Utilitzeu Ajust o Flexible quan totes les parts de la imatge original hagin de romandre visibles. + Canvi de mida normal o millorat + Saber quan afegir píxels necessita IA i quan n\'hi ha prou amb un escalat senzill + No confongueu la mida amb el detall + El canvi de mida normal canvia les dimensions mitjançant la interpolació de píxels; no pot inventar detalls reals. La IA de luxe pot crear detalls més nítids, però és més lent i pot al·lucinar la textura, de manera que l\'elecció correcta depèn de si necessiteu compatibilitat, velocitat o restauració visual. + Utilitzeu el canvi de mida normal per a les miniatures, els límits de càrrega, les captures de pantalla i els canvis de dimensions senzills. + Utilitzeu l\'extensió de l\'IA quan una imatge petita o suau hagi de veure\'s millor a una mida més gran. + Compareu cares, text i patrons després de l\'augment de la IA perquè els detalls generats poden semblar convincents però incorrectes. + L\'ordre del filtre és important + Apliqueu neteja, color, nitidesa i compressió final en una seqüència intencionada + Construeix una pila de filtres més neta + Els filtres no sempre són intercanviables. Un desenfocament abans de la nitidesa, un augment del contrast abans de l\'OCR o un canvi de color abans de la compressió poden produir resultats molt diferents dels mateixos controls. + Retalla i gira primer perquè els filtres posteriors funcionin només amb els píxels que quedaran. + Apliqueu l\'exposició, el balanç de blancs i el contrast abans d\'efectes nítids o estilitzats. + Mantingueu el redimensionament i la compressió finals a prop del final perquè els detalls previsualitzats sobrevisquin a l\'exportació de manera previsible. + Corregiu les vores del fons + Netegeu els halos, les ombres sobrants, els cabells, els forats i els contorns suaus després de l\'eliminació automàtica + Feu que els retalls semblin intencionats + Les màscares automàtiques sovint es veuen bé d\'un cop d\'ull, però revelen problemes en fons brillants, foscos o amb estampats. La neteja de vores és la diferència entre un retall ràpid i un adhesiu, un logotip o una imatge de producte reutilitzables. + Previsualitza el resultat amb fons contrastats per revelar halos i detalls de vora que falten. + Utilitzeu la restauració per als cabells, les cantonades i els objectes transparents abans d\'esborrar les restes del voltant. + Exporteu una petita prova sobre el color de fons final quan el retall es col·loqui en un disseny. + Filigranes llegibles + Feu que les marques siguin visibles en imatges brillants, fosques, ocupades i retallades sense arruïnar la foto + Protegiu la imatge sense amagar-la + Una marca d\'aigua que es veu bé en una imatge pot desaparèixer en una altra. La mida, l\'opacitat, el contrast, la ubicació i la repetició s\'han de triar per a tot el lot, no només per a la primera vista prèvia. + Proveu la marca a les imatges més brillants i fosques del lot abans d\'exportar tots els fitxers. + Utilitzeu una opacitat més baixa per a marques repetides grans i un contrast més fort per a marques de cantonades petites. + Mantingueu clars les cares importants, el text i els detalls del producte, tret que la marca tingui l\'objectiu d\'evitar la reutilització. + Dividiu una imatge en rajoles + Retalla una sola imatge per files, columnes, percentatges personalitzats, format i qualitat + Prepareu graelles i peces ordenades + La divisió d\'imatges crea múltiples sortides a partir d\'una imatge d\'origen. Es pot dividir per recomptes de files i columnes, ajustar percentatges de files o columnes i desar peces amb el format i la qualitat de sortida seleccionats, que és útil per a quadrícules, porcions de pàgina, panoràmiques i publicacions socials ordenades. + Trieu la imatge d\'origen i, a continuació, establiu el nombre de files i columnes que necessiteu. + Ajusteu els percentatges de fila o columna quan les peces no han de ser totes de la mateixa mida. + Trieu el format i la qualitat de sortida abans de desar-los, de manera que cada peça generada utilitzi les mateixes regles d\'exportació. + Retalla tires d\'imatge + Traieu les regions verticals o horitzontals i torneu a combinar les parts restants + Eliminar una regió sense deixar un buit + El tall d\'imatge és diferent del tall normal. Pot eliminar una franja vertical o horitzontal seleccionada i, a continuació, combinar les parts de la imatge restants. El mode invers manté la regió seleccionada, i l\'ús de les dues direccions inverses manté el rectangle seleccionat. + Utilitzeu el tall vertical per a regions laterals, columnes o tires centrals; utilitzeu el tall horitzontal per a banderoles, buits o files. + Activeu la inversa en un eix quan vulgueu mantenir la part seleccionada en lloc d\'eliminar-la. + Previsualitza les vores després de tallar perquè el contingut combinat pot semblar brusc quan l\'àrea eliminada creua detalls importants. + Trieu el format de sortida + Trieu JPEG, PNG, WebP, AVIF, JXL o PDF segons el contingut i la destinació + Deixa que la destinació decideixi el format + El millor format depèn del que conté la imatge i on s\'utilitzarà. Les fotos, les captures de pantalla, els actius transparents, els documents i els fitxers animats fallen de diferents maneres quan es força al format incorrecte. + Utilitzeu JPEG per a una àmplia compatibilitat fotogràfica quan no calgui transparència i píxels exactes. + Utilitzeu PNG per a captures de pantalla nítides, captures d\'interfície d\'usuari, gràfics transparents i edicions sense pèrdues. + Utilitzeu WebP, AVIF o JXL quan la sortida compacta moderna sigui important i l\'aplicació receptora ho admet. + Extreu cobertes d\'àudio + Desa la imatge de l\'àlbum incrustada o els marcs multimèdia disponibles dels fitxers d\'àudio com a imatges PNG + Traieu obres d\'art dels fitxers d\'àudio + Audio Covers utilitza metadades multimèdia d\'Android per llegir obres d\'art incrustades dels fitxers d\'àudio. Quan l\'obra gràfica incrustada no està disponible, el recuperador pot tornar a un marc multimèdia si Android pot proporcionar-ne un; si no n\'hi ha, l\'eina informa que no hi ha cap imatge per extreure. + Obriu Audio Covers i seleccioneu un o més fitxers d\'àudio amb la portada esperada. + Reviseu la coberta extreta abans de desar-la o compartir-la, especialment per a fitxers d\'aplicacions de reproducció o gravació. + Si no es troba cap imatge, és probable que el fitxer d\'àudio no tingui coberta incrustada o que Android no pugui exposar un marc utilitzable. + Metadades de dates i ubicació + Decidiu què voleu conservar quan el temps de captura sigui important, però les dades del GPS o del dispositiu no s\'han de filtrar + Separeu les metadades útils de les privades + Les metadades no són totes igualment arriscades. Les dates de captura poden ser útils per arxivar i ordenar, mentre que el GPS, els camps semblants a la sèrie del dispositiu, les etiquetes de programari o els camps del propietari poden revelar més del previst. + Conserveu les etiquetes de data i hora quan l\'ordre cronològic sigui important per a àlbums, escanejos o historial de projectes. + Elimineu el GPS i les etiquetes d\'identificació del dispositiu abans de compartir-les en públic o enviar imatges a desconeguts. + Exporteu una mostra i torneu a inspeccionar EXIF ​​quan els requisits de privadesa siguin estrictes. + Eviteu col·lisions de noms de fitxer + Protegiu les sortides per lots quan molts fitxers comparteixen noms com ara image.jpg o screenshot.png + Feu que cada nom de sortida sigui únic + Els noms de fitxer duplicats són habituals quan les imatges provenen de missatgers, captures de pantalla, carpetes al núvol o diverses càmeres. Un bon patró evita la substitució accidental i manté les sortides traçables fins a les seves fonts. + Incloeu el nom original més un sufix quan la còpia editada es pugui reconèixer. + Afegiu una seqüència, una marca de temps, una configuració predeterminada o unes dimensions quan processeu lots mixts grans. + Eviteu sobreescriure els fluxos de treball fins que no hàgiu verificat que el patró de denominació no pot xocar. + Guarda o comparteix + Trieu entre un fitxer persistent i un traspàs temporal a una altra aplicació + Exporta amb la intenció correcta + Desar i compartir poden semblar semblants, però resolen problemes diferents. Si deseu, es crea un fitxer que podreu trobar més tard; compartir envia un resultat generat a través d\'Android a una altra aplicació i pot no deixar una còpia local duradora. + Utilitzeu Desa quan la sortida ha de romandre en una carpeta coneguda, fer una còpia de seguretat o reutilitzar-se més tard. + Utilitzeu Compartir per a missatges ràpids, fitxers adjunts de correu electrònic, publicacions socials o enviar el resultat a un altre editor. + Deseu primer quan l\'aplicació receptora no sigui fiable o quan una compartició fallida seria molest de recrear. + Mida i marges de la pàgina PDF + Trieu la mida del paper, el comportament d\'ajust i l\'espai de respiració abans de crear un document + Feu que les pàgines d\'imatge siguin imprimibles i llegibles + Les imatges poden convertir-se en pàgines PDF incòmodes quan la seva forma no coincideix amb la mida de paper escollida. Els marges, el mode d\'ajust i l\'orientació de la pàgina decideixen si el contingut és retallat, petit, estirat o còmode de llegir. + Utilitzeu una mida de paper real quan el PDF es pugui imprimir o enviar a un formulari. + Utilitzeu marges per escanejar i captures de pantalla perquè el text no s\'assequi a la vora de la pàgina. + Previsualitza les pàgines vertical i horitzontal junts quan les imatges d\'origen tenen una orientació mixta. + Netegeu les exploracions + Milloreu les vores del paper, les ombres, el contrast, la rotació i la llegibilitat abans de PDF o OCR + Prepareu les pàgines abans d\'arxivar-les + Un escaneig es pot capturar tècnicament, però encara és difícil de llegir. Els petits passos de neteja abans de l\'exportació de PDF sovint importen més que la configuració de compressió, especialment per als rebuts, notes escrites a mà i pàgines amb poca llum. + Corregiu la perspectiva i retalleu les vores, els dits, les ombres i el desordre de fons de la taula. + Augmenta el contrast amb cura perquè el text sigui més clar sense destruir segells, signatures o marques de llapis. + Executeu OCR només després que la pàgina estigui en posició vertical i llegible i, a continuació, verifiqueu els números importants manualment. + Comprimeix, escala de grisos o repara PDF + Utilitzeu operacions PDF d\'un sol fitxer per a còpies de documents més lleugeres, imprimibles o reconstruïdes + Trieu l\'operació de neteja de PDF + PDF Tools inclou operacions separades per a la compressió, la conversió en escala de grisos i la reparació. La compressió i l\'escala de grisos funcionen principalment mitjançant imatges incrustades, mentre que la reparació reconstrueix l\'estructura del PDF; cap d\'aquests s\'ha de tractar com una solució garantida per a cada document. + Utilitzeu Comprimir PDF quan el document conté imatges i la mida del fitxer és important per compartir. + Utilitzeu Escala de grisos quan el document ha de ser més fàcil d\'imprimir o no necessita imatges en color. + Utilitzeu Repara PDF en una còpia quan un document no s\'obre o tingui l\'estructura interna danyada i, a continuació, verifiqueu el fitxer reconstruït. + Extreu imatges de PDF + Recupereu les imatges PDF incrustades i empaqueteu els resultats extrets en un arxiu + Desa les imatges d\'origen dels documents + Extract Images escaneja el PDF per trobar objectes d\'imatge incrustats, salta els duplicats sempre que sigui possible i emmagatzema les imatges recuperades en un arxiu ZIP generat. Només extreu imatges que estan realment incrustades al PDF, no text, dibuixos vectorials o totes les pàgines visibles com a captura de pantalla. + Utilitzeu Extreu imatges quan necessiteu imatges emmagatzemades dins d\'un PDF, com ara fotografies d\'un catàleg o actius escanejats. + Utilitzeu PDF a imatges o l\'extracció de pàgines quan necessiteu pàgines completes, incloent text i disseny. + Si l\'eina informa de cap imatge incrustada, la pàgina pot ser contingut de text/vector o les imatges no s\'emmagatzemen com a objectes extraïbles. + Metadades, aplanament i sortida d\'impressió + Editeu les propietats del document, rasteritzeu pàgines o prepareu dissenys d\'impressió personalitzats de manera intencionada + Trieu l\'acció del document final + Les metadades de PDF, l\'aplanament i la preparació d\'impressió resolen diferents problemes de la fase final. Les metadades controlen les propietats del document, Flatten PDF rasteritza les pàgines en una sortida menys editable i Print PDF prepara un nou disseny amb controls de mida de pàgina, orientació, marge i pàgines per full. + Utilitzeu les metadades quan l\'autor, el títol, les paraules clau, el productor o la neteja de privadesa siguin importants. + Utilitzeu Flatten PDF quan el contingut de la pàgina visible sigui més difícil d\'editar com a objectes PDF separats. + Utilitzeu Print PDF quan necessiteu una mida de pàgina personalitzada, una orientació, marges o diverses pàgines per full. + Prepareu imatges per OCR + Utilitzeu retall, rotació, contrast, reducció de soroll i escala abans de demanar a OCR que llegeixi el text + Doneu píxels més nets d\'OCR + Els errors d\'OCR sovint provenen de la imatge d\'entrada en lloc del motor OCR. Les pàgines tortes, el contrast baix, el desenfocament, les ombres, el text petit i els fons barrejats redueixen la qualitat del reconeixement. + Retalla l\'àrea de text i gira la imatge perquè les línies siguin horitzontals abans del reconeixement. + Augmenta el contrast o converteix en un blanc i negre més net només quan faci que les lletres siguin més fàcils de llegir. + Canvieu la mida del text petit cap amunt moderadament, però eviteu una nitidesa agressiva que creï vores falses. + Comproveu el contingut QR + Revisa els enllaços, les credencials de Wi-Fi, els contactes i les accions abans de confiar en un codi + Tracta el text descodificat com a entrada no fiable + Un codi QR pot amagar un URL, una targeta de contacte, una contrasenya de Wi-Fi, un esdeveniment del calendari, un número de telèfon o un text sense format. És possible que l\'etiqueta visible a prop del codi no coincideixi amb la càrrega útil real, així que reviseu el contingut descodificat abans d\'actuar-hi. + Inspeccioneu l\'URL descodificat complet abans d\'obrir-lo, especialment els dominis escurçats o desconeguts. + Aneu amb compte amb els codis de Wi-Fi, de contacte, de calendari, de telèfon i d\'SMS perquè poden desencadenar accions sensibles. + Copieu primer el contingut quan hàgiu de verificar-lo en un altre lloc en lloc d\'obrir-lo immediatament. + Genera codis QR + Creeu codis a partir de text sense format, URL, Wi-Fi, contactes, correu electrònic, telèfon, SMS i dades d\'ubicació + Creeu la càrrega útil QR adequada + La pantalla QR pot generar codis a partir del contingut introduït, així com escanejar els existents. Els tipus QR estructurats ajuden a codificar les dades en un format que altres aplicacions entenen, com ara les dades d\'inici de sessió de Wi-Fi, targetes de contacte, camps de correu electrònic, números de telèfon, contingut d\'SMS, URL o coordenades geogràfiques. + Trieu text sense format per a notes senzilles o utilitzeu un tipus estructurat quan el codi s\'hagi d\'obrir com a dades de Wi-Fi, contacte, URL, correu electrònic, telèfon, SMS o dades d\'ubicació. + Comproveu tots els camps abans de compartir el codi generat perquè la vista prèvia visible només reflecteix la càrrega útil que heu introduït. + Proveu el codi QR desat amb un escàner quan s\'imprimirà, es publicarà públicament o s\'utilitzarà per altres persones. + Trieu un mode de suma de comprovació + Calculeu hash a partir de fitxers o text, compareu un fitxer o comproveu molts fitxers per lots + Verifiqueu el contingut, no l\'aparença + Checksum Tools té pestanyes separades per hash de fitxers, hash de text, comparar un fitxer amb un hash conegut i comparar lots. Una suma de comprovació demostra la identitat byte per byte per a l\'algorisme seleccionat; no demostra que dues imatges simplement s\'assemblen. + Utilitzeu Calculate per als fitxers i Text Hash per a dades de text escrites o enganxades. + Utilitzeu Compara quan algú us ofereix una suma de comprovació esperada per a un fitxer. + Utilitzeu la comparació per lots quan molts fitxers necessiten hash o verificació en una sola passada i, a continuació, mantingueu l\'algorisme seleccionat coherent. + Privadesa del porta-retalls + Utilitzeu les funcions automàtiques del porta-retalls sense exposar accidentalment les dades copiades privades + Mantingueu el contingut copiat intencionat + L\'automatització del porta-retalls és convenient, però el text copiat pot contenir contrasenyes, enllaços, adreces, testimonis o notes privades. Tracteu l\'entrada basada en el porta-retalls com una funció de velocitat que hauria de coincidir amb els vostres hàbits i expectatives de privadesa. + Desactiveu l\'ús automàtic del porta-retalls si apareix text privat a les eines de manera inesperada. + Utilitzeu l\'emmagatzematge només del porta-retalls només quan vulgueu deliberadament que el resultat eviti l\'emmagatzematge. + Netegeu o substituïu el porta-retalls després de manipular text sensible, dades QR o càrregues útils de Base64. + Formats de color + Comprèn els valors de color HEX, RGB, HSV, alfa i copiats abans d\'enganxar-lo a un altre lloc + Copieu el format que l\'objectiu espera + El mateix color visible es pot escriure de diverses maneres. Una eina de disseny, un camp CSS, un valor de color d\'Android o un editor d\'imatges poden esperar un ordre, un maneig alfa o un model de color diferents. + Utilitzeu HEX quan enganxeu camps web, de disseny o temàtics que esperen codis de color compactes. + Utilitzeu RGB o HSV quan necessiteu ajustar manualment els canals, la brillantor, la saturació o la tonalitat. + Comproveu l\'alfa per separat quan la transparència sigui important perquè algunes destinacions l\'ignoren o utilitzen un ordre diferent. + Exploreu la biblioteca de colors + Cerqueu colors amb nom per nom o hexadecimal i manteniu els favorits a la part superior + Trobeu colors amb nom reutilitzables + Color Library carrega una col·lecció de colors amb nom, l\'ordena per nom, filtra per nom de color o valor HEX i emmagatzema els colors preferits perquè les entrades importants siguin més fàcils d\'arribar. És útil quan necessiteu un color amb nom real en lloc de fer un mostreig d\'una imatge. + Cerqueu per un nom de color quan coneixeu la família, com ara vermell, blau, pissarra o menta. + Cerqueu per HEX quan ja teniu un color numèric i voleu trobar entrades amb nom que coincideixin o properes. + Marqueu els colors freqüents com a favorits perquè es avancin de la biblioteca general mentre navegueu. + Utilitzeu col·leccions de degradats de malla + Baixeu els recursos de degradat de malla disponibles i compartiu les imatges seleccionades + Utilitzeu recursos de degradat de malla remots + Els degradats de malla poden carregar una col·lecció de recursos remots, descarregar imatges de degradat que falten, mostrar el progrés de la baixada i compartir els degradats seleccionats. La disponibilitat depèn de l\'accés a la xarxa i del magatzem de recursos remot, de manera que una llista buida normalment significa que els recursos encara no estaven disponibles. + Obriu Gradients de malla des de les eines de degradat quan vulgueu imatges de degradat de malla preparades. + Espereu la càrrega de recursos o el progrés de la descàrrega abans de suposar que la col·lecció està buida. + Seleccioneu i compartiu els degradats que necessiteu o torneu a Gradient Maker quan vulgueu crear un degradat personalitzat manualment. + Resolució d\'actius generats + Trieu la mida de sortida abans de generar degradats, soroll, fons de pantalla, art semblant a SVG o textures + Genereu a la mida que necessiteu + Les imatges generades no tenen un original de la càmera al qual recórrer. Si la sortida és massa petita, l\'escalat posterior pot suavitzar els patrons, canviar els detalls o canviar la forma en què es comprimeixen i es comprimeixen els elements. + Definiu primer les dimensions per al cas d\'ús final, com ara fons de pantalla, icona, fons, impressió o superposició. + Utilitzeu mides més grans per a textures que es poden retallar, ampliar o reutilitzar en diversos dissenys. + Exporteu versions de prova abans de grans sortides perquè els detalls del procediment poden canviar el comportament de la compressió. + Exporta fons de pantalla actuals + Recupera els fons de pantalla d\'inici, de bloqueig i integrats disponibles del dispositiu + Desa o comparteix fons de pantalla instal·lats + L\'exportació de fons de pantalla llegeix els fons de pantalla que exposa Android mitjançant les API de fons de pantalla del sistema. Segons el dispositiu, la versió d\'Android, els permisos i la variant de compilació, és possible que els fons de pantalla d\'inici, de bloqueig o integrats estiguin disponibles per separat o que faltin. + Obriu l\'exportació de fons de pantalla per carregar els fons de pantalla que el sistema permet llegir a l\'aplicació. + Seleccioneu les entrades d\'inici, de bloqueig o de fons de pantalla integrades disponibles que vulgueu desar, copiar o compartir. + Si falta un fons de pantalla o la funció no està disponible, normalment es tracta d\'una limitació de sistema, permís o variant de compilació en lloc d\'una configuració d\'edició. + Corners Animació Acelerador + Copia el color com a + HEX + nom + Model d\'estora retrat per a retalls ràpids de persones amb cabells més suaus i manipulació de vores. + Millora ràpida amb poca llum mitjançant l\'estimació de la corba Zero-DCE++. + Model de restauració d\'imatges Restormer per reduir les ratlles de pluja i els artefactes de temps humit. + Model de deformació de documents que prediu una graella de coordenades i reasigna les pàgines corbes en un escaneig més pla. + Escala de durada del moviment + Controla la velocitat de l\'animació de l\'aplicació: 0 desactiva el moviment, 1 és normal, els valors més alts ralenteixen les animacions + Mostra les eines recents + Mostra l\'accés ràpid a les eines utilitzades recentment a la pantalla principal + Estadístiques d\'ús + Explora les obres de l\'aplicació, els llançaments d\'eines i les teves eines més utilitzades + S\'obre l\'aplicació + S\'obre l\'eina + Última eina + Salvades amb èxit + Ratxa d\'activitat + Dades desades + Format superior + Eines utilitzades + %1$d obre • %2$s + Encara no hi ha estadístiques d\'ús + Obriu algunes eines i apareixeran aquí automàticament + Les vostres estadístiques d\'ús només s\'emmagatzemen localment al vostre dispositiu. ImageToolbox només els fa servir per mostrar la vostra activitat personal, les eines preferides i les dades desades + superposició de segell de text del logotip de la marca d\'aigua + gif animació animada convertir fotogrames + generador de soroll gra de textura aleatòria + combinació de disseny de quadrícula de collage + les capes de marcatge anoten l\'edició de superposició + metadades de música de la portada d\'àudio + fons d\'exportació de fons de pantalla + caràcters d\'art de text ascii + ai ml neuronal millora el segment de luxe + shader glsl fragment effect studio + pdf reordenar ordenar pàgines + paginació de números de pàgina en pdf + pdf ocr text reconeix l\'extracte cercable + pdf desbloquejar la contrasenya desxifrar eliminar la protecció + pdf comprimir reduir la mida optimitzar + pdf aplanar anotacions forma capes + Error + Ombra caiguda + Alçada de la dent + Gamma de dents horitzontals + Gamma de dents verticals + Bord superior + Bord dret + Bord inferior + Bord esquerre + Bord esquinçat + editor editar foto retoc d\'imatge ajustar + redimensionar convertir escala resolució dimensions amplada alçada jpg jpeg png webp comprimir + comprimir mida pes bytes mb kb reduir la qualitat optimitzar + retalla la relació d\'aspecte de retall + correcció de color de l\'efecte de filtre ajustar la màscara de lut + dibuixar pinzell llapis anotar marcatge + xifrar xifrar desxifrar la contrasenya secreta + eliminador de fons esborra eliminar retall alfa transparent + visualització prèvia de la galeria d\'inspecció oberta + Stitch merge combina una captura de pantalla llarga panoràmica + URL de descàrrega web imatge d\'enllaç a Internet + selector comptagotes mostra hexadecimal color rgb + paleta de colors mostres extracte dominant + privadesa de les metadades d\'exif eliminar la franja neta de la ubicació del GPS + compara la diferència de diferència abans després + límit canvia la mida màxima min megapíxels dimensions pinça + ocr text reconeix l\'extracte d\'exploració + malla de color de fons degradat + fotogrames de conversió png animats d\'animació apng + arxiu zip comprimir paquet descomprimir fitxers + jxl jpeg xl convertir format d\'imatge jpg + svg vector traça convertir xml + convertir format jpg jpeg png webp heic avif jxl bmp + escàner de documents escanejar paper retall en perspectiva + Escaneig de codi de barres qr + superposició d\'apilament astrofotografia mitjana mitjana + peces de rajoles dividides de quadrícula + conversió de color hexadecimal rgb hsl hsv cmyk lab + Webp convertir format d\'imatge animada + base64 codificar descodificar dades uri + checksum hash md5 sha sha1 sha256 verificar + fons degradat de malla + modifica metadades exif càmera de data GPS + tallar rajoles de peces de quadrícula dividides + paleta de materials de la biblioteca de colors anomenada colors + pdf dividir pàgines separades + pdf girar orientació de pàgines + text del logotip del segell de marca d\'aigua pdf + Sorteig de signatura en pdf + pdf protegeix contrasenya xifrat bloqueig + pdf en escala de grisos negre blanc monocrom + pdf reparació reparació recuperar trencat + propietats de metadades pdf títol de l\'autor exif + pdf eliminar esborrar pàgines + pdf retallar els marges + pdf extreu imatges imatges + comprimir arxiu pdf zip + impressora d\'impressió pdf + visor de vista prèvia de pdf llegit + imatges a pdf convertir document jpg jpeg png + pdf a pàgines d\'imatges exportar jpg png + pdf anotacions comentaris eliminar net + Variant neutral + Eines de pàgines de documents pdf + pdf combinar combinar unir + Restableix les estadístiques d\'ús + S\'obre l\'eina, desa les estadístiques, la ratxa, les dades desades i el format superior es restabliran. L\'obertura de l\'aplicació es mantindrà sense canvis. + Àudio + Fitxer + Exportar perfils + Desa el perfil actual + Suprimeix el perfil d\'exportació + Voleu suprimir el perfil d\'exportació \\"%1$s\\"? + Dibuix de vora + Mostra una vora fina al voltant del dibuix i esborra el llenç + Ondulat + Elements d\'interfície d\'usuari arrodonits amb una vora ondulada suau + Scoop + Osca + Cantons arrodonits tallats cap a dins + Cantonades esglaonades amb doble tall + Marcador de posició + Utilitzeu {filename} per inserir el nom de fitxer original sense extensió + Flare + Import base + Import de l\'anell + Quantitat de raigs + Ample de l\'anell + Distorsionar la perspectiva + Aspecte i sensació de Java + Cisalla + angle X + I angle + Canvia la mida de la sortida + Gota d\'aigua + Longitud d\'ona + Fase + Pas alt + Màscara de color + Chrome + Dissoldre + Suavitat + Feedback + Desenfocament de la lent + Entrada baixa + Entrada alta + Sortida baixa + Alt rendiment + Efectes de llum + Alçada de cop + Suavitat de cops + Onda + X amplitud + Amplitud Y + X longitud d\'ona + Longitud d\'ona Y + Desenfocament adaptatiu + Generació de textures + Genereu diverses textures procedimentals i deseu-les com a imatges + Tipus de textura + Biaix + Temps + Brilla + Dispersió + Mostres + Coeficient d\'angle + Coeficient de gradient + Tipus de quadrícula + Potència de distància + Escala Y + Borrosa + Tipus de base + Factor de turbulència + Escalat + Iteracions + Anells + Metall raspallat + Càustiques + Cel·lular + Tauler d\'escacs + fBm + Marbre + Plasma + Edredó + Fusta + aleatòria + Plaçada + Hexagonal + Octagonal + Triangular + Crestada + VL Soroll + SC Soroll + Recent + Encara no hi ha cap filtre utilitzat recentment + textura generador metall raspallat càustics cel·lular tauler d\'escacs marbre plasma edredó fusta maó camuflatge cel·lular núvol esquerdar teixit fullatge bresca gel lava nebulosa paper rovell sorra fum pedra terreny topografia aigua ondulació + Maó + Camuflatge + Cèl·lula + Núvol + Crack + Tela + Fullatge + bresca + Gel + Lava + Nebulosa + Paper + Rovell + Sorra + Fum + Pedra + Terreny + Topografia + Onda d\'aigua + Fusta avançada + Amplada del morter + Irregularitat + Bisell + Rugositat + Primer llindar + Segon llindar + Tercer llindar + Suavitat de vora + Amplada de la vora + Cobertura + Detall + profunditat + Ramificació + Fils horitzontals + Fils verticals + Fuzz + Venes + Il·luminació + Amplada de l\'esquerda + Gelada + Flux + Escorça + Densitat de núvols + Estrelles + Densitat de fibra + Força de la fibra + Taques + Corrosió + Pitting + Flocs + Freqüència de duna + Angle del vent + Ondes + Wips + Escala venosa + Nivell de l\'aigua + Nivell de muntanya + Erosió + Cota de neu + Recompte de línies + Gruix de la línia + Ombrejat + Color dels porus + Color morter + Color maó fosc + Color maó + Ressaltar el color + Color fosc + Color del bosc + Color de la terra + Color sorra + Color de fons + Color cel·lular + Color de la vora + Color del cel + Color de l\'ombra + Color clar + Color de la superfície + Variació de color + Color crack + Color d\'ordit + Color de trama + Color de fulla fosc + Color de fulla + Color de la vora + Color mel + Color profund + Color gel + Color gelada + Color de l\'escorça + Lava color + Color brillant + Color de l\'espai + Color violeta + Color blau + Color base + Color de fibra + Color de la taca + Color metall + Color rovell fosc + Color rovell + Color taronja + Color fum + Color de la vena + Color de la terra baixa + Color d\'aigua + Color roca + Color de la neu + Color baix + Color alt + Color de línia + Color poc profund + Brutícia + Aurora + Òpal + Supernova + Iris + Desgast + Fum + Intensitat + La foscor + polonès + Herba + Cuir + formigó + Asfalt + Molsa + Foc + Taca d\'oli + aquarel·la + Flux abstracte + Acer de Damasc + Llamp + Vellut + Marmorat de tinta + Làmina hologràfica + Bioluminescència + Vòrtex còsmic + Làmpada de lava + Horitzó d\'esdeveniments + Floració fractal + Túnel cromàtic + Eclipsi Corona + Atractor estrany + Corona de ferrofluids + Ploma de paó + Nautilus Shell + Planeta anellat + Densitat de fulla + Longitud de la fulla + Vent + Pegats + Grumolls + Humitat + Còdols + Arrugues + Porus + Agregat + Esquerdes + Tar + Taques + Fibres + Freqüència de la flama + Cintes + Bandes + Iridescència + Floreix + Pigment + Vores + Paper + Difusió + Simetria + Nitidez + Joc de colors + Lletitud + Capes + Plegable + Branques + Direcció + Sheen + Plecs + Emplomat + Balanç de tinta + Espectre + Arrugas + Difracció + Braços + Girar + resplendor del nucli + Taques + Inclinació del disc + Mida de l\'horitzó + Amplada del disc + Lentatge + Pètals + Rínxol + Filigrana + Facetes + Curvatura + Mida de la lluna + Mida corona + Raigs + Anell de diamants + Lòbuls + Densitat de l\'òrbita + Gruix + Pics + Longitud de punta + Mida corporal + Metàl·lic + Radi de xoc + Amplada de la closca + Tirat fora + Mida de la pupil·la + Mida de l\'iris + Variació de color + Catchlight + Mida dels ulls + Densitat de barb + Torns + Cambres + Obertura + Crestades + Perlescència + Mida del planeta + Inclinació de l\'anell + Ample de l\'anell + Atmosfera + Color brut + Color herba fosc + Color herba + Color de punta + Color terra fosc + Color sec + Color còdol + Color pell + Color concret + Color quitrà + Color asfalt + Color pedra + Color de pols + Color del sòl + Color molsa fosc + Color molsa + Color vermell + Color del nucli + Color verd + Color cian + Color magenta + Color daurat + Color de paper + Color del pigment + Color secundari + Primer color + Segon color + Color rosa + Color acer fosc + Color acer + Color acer clar + Color òxid + Color halo + Color del cargol + Color vellut + Color brillant + Color de tinta blava + Color de tinta vermella + Color de tinta fosc + Color plata + Color groc + Color del teixit + Color del disc + Color calent + Color de la lent + Color exterior + Color interior + Color corona + Color fred + Color càlid + Color del núvol + Color de la flama + Color ploma + Color de closca + Color perla + Color del planeta + Color de l\'anell + Suprimeix els fitxers originals després de desar-los + Només se suprimiran els fitxers font processats correctament + Fitxers originals suprimits: %1$d + No s\'han pogut suprimir els fitxers originals: %1$d + Fitxers originals suprimits: %1$d, error: %2$d + Gestos de fulla + Permet arrossegar els fulls d\'opcions ampliats + Submostreig de croma + Profunditat de bits + Sense pèrdues + %1$d-bit + Canviar el nom del lot + Canvieu el nom de diversos fitxers utilitzant patrons de nom de fitxer + canvi de nom per lots fitxers nom de fitxer patró seqüència extensió de data + Trieu fitxers per canviar el nom + Patró de nom de fitxer + Canvia el nom + Font de la data + Data EXIF ​​presa + Data de modificació del fitxer + Data de creació del fitxer + Data actual + Data manual + Data manual + Temps manual + La data seleccionada no està disponible per a alguns fitxers. S\'utilitzarà la data actual per a ells. + Introduïu un patró de nom de fitxer + El patró produeix un nom de fitxer no vàlid o massa llarg + El patró produeix noms de fitxer duplicats + Tots els noms de fitxer ja coincideixen amb el patró + Aquest patró utilitza fitxes que no estan disponibles per canviar el nom per lots + El nom es mantindrà igual + Els fitxers s\'han canviat de nom correctament + No s\'ha pogut canviar el nom d\'alguns fitxers + No es va concedir el permís + Utilitza el nom del fitxer original sense extensió, la qual cosa us ajuda a mantenir intacta la identificació de la font. També admet el tall amb \\o{start:end}, la transliteració amb \\o{t} i substituir per \\o{s/old/new/}. + Comptador d\'increment automàtic. Admet el format personalitzat: \\c{padding} (per exemple, \\c{3} -&gt; 001), \\c{start:step} o \\c{start:step:padding}. + El nom de la carpeta principal on es trobava el fitxer original. + La mida del fitxer original. Admet unitats: \\z{b}, \\z{kb}, \\z{mb} o només \\z per a un format llegible per persones. + Genera un identificador únic universal (UUID) aleatori per garantir la singularitat del nom del fitxer. + El canvi de nom dels fitxers no es pot desfer. Assegureu-vos que els noms nous siguin correctes abans de continuar. + Confirmeu el canvi de nom + Configuració del menú lateral + Escala del menú + Menú alfa + Balanç de blancs automàtic + Retall + Comprovació de les marques d\'aigua amagades + Emmagatzematge + Límit de memòria cau + Esborra la memòria cau automàticament quan creixi més enllà d\'aquesta mida + Interval de neteja + Amb quina freqüència comprovar si s\'ha d\'esborrar la memòria cau + Al llançament de l\'aplicació + 1 dia + 1 setmana + 1 mes + Esborra la memòria cau de l\'aplicació automàticament segons el límit i l\'interval seleccionats + Zona de cultiu mòbil + Permet moure tota l\'àrea de retall arrossegant-hi dins + Combina GIF + Combina diversos clips GIF en una sola animació + Ordre de clips GIF + Revés + Juga al revés + Bumerang + Juga cap endavant i després cap enrere + Normalitzar la mida del marc + Escala els marcs per adaptar-se al clip més gran; apagar per conservar la seva escala original + Retard entre clips (ms) + Carpeta + Seleccioneu totes les imatges d\'una carpeta i les seves subcarpetes + Cercador de duplicats + Trobeu còpies exactes i imatges visualment similars + cercador duplicat imatges similars còpies exactes fotos neteja emmagatzematge dHash SHA-256 + Trieu imatges per trobar duplicats + Sensibilitat de semblança + Còpies exactes + Imatges semblants + Seleccioneu tots els duplicats exactes + Seleccioneu-ho tot excepte els recomanats + No s\'han trobat duplicats + Intenteu afegir més imatges o augmentar la sensibilitat de semblança + No s\'han pogut llegir %1$d imatges + %1$s • %2$s recuperable + %1$d grups • %2$s recuperables + %1$d seleccionat • %2$s + Això no es pot desfer. Android pot demanar-vos que confirmeu la supressió en un diàleg del sistema. + No trobat + Mou per començar + Mou al final + \ No newline at end of file diff --git a/core/resources/src/main/res/values-cs/strings.xml b/core/resources/src/main/res/values-cs/strings.xml new file mode 100644 index 0000000..abe9b8d --- /dev/null +++ b/core/resources/src/main/res/values-cs/strings.xml @@ -0,0 +1,3741 @@ + + + + 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 + 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 + 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 barva + %1$s barev + %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. + Šíř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. + 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 + Content Aware Distortion + K dokončení této akce není dostatek paměti. Zkuste použít menší obrázek, zavřete ostatní aplikace nebo aplikaci restartujte. + Paralelní pracovníci + Tyto obrázky nebyly uloženy, protože převedené soubory by byly větší než originály. Před odstraněním originálních snímků zkontrolujte tento seznam. + Vynechané soubory: %1$s + Protokoly aplikací + Prohlédněte si protokoly aplikace, abyste zjistili problémy + Oblíbené ve seskupených nástrojích + Přidá oblíbené položky jako kartu, když jsou nástroje seskupeny podle typu + Zobrazit oblíbené jako poslední + Přesune záložku oblíbených nástrojů na konec + Horizontální rozteč + Vertikální rozestup + Zpětná energie + Místo dopředného energetického algoritmu použijte jednoduchou energetickou mapu gradientu + Odstraňte maskovanou oblast + Švy projdou maskovanou oblastí a odstraní pouze vybranou oblast místo její ochrany + VHS NTSC + Pokročilé nastavení NTSC + Extra analogové ladění pro silnější VHS a vysílací artefakty + Chroma krvácení + Opotřebení pásky + Sledování + Luma stěr + Zvonění + Sněžení + Použít pole + Typ filtru + Vstupní luma filtr + Vstupní chroma lowpass + Chroma demodulace + Fázový posun + Fázový posun + Přepínání hlavy + Výška přepínání hlavy + Offset spínání hlavy + Směna hlavy + Poloha uprostřed + Jitter ve střední linii + Sledování výšky hluku + Sledování vlny + Sledování sněhu + Sledování anizotropie sněhu + Hluk sledování + Sledování intenzity hluku + Složený hluk + Frekvence kompozitního šumu + Intenzita kompozitního hluku + Detail kompozitního šumu + Frekvence vyzvánění + Síla zvonění + Luma hluk + Luma frekvence šumu + Intenzita hluku Luma + Detail šumu Luma + Chroma šum + Frekvence chroma šumu + Intenzita chroma šumu + Detail chroma šumu + Intenzita sněhu + Anizotropie sněhu + Chroma fázový šum + Chyba fáze chroma + Chroma delay horizontální + Vertikální zpoždění barevnosti + Rychlost kazety VHS + Ztráta barevnosti VHS + VHS zostřuje intenzitu + VHS zostření frekvence + Intenzita okrajové vlny VHS + Rychlost okrajové vlny VHS + Frekvence okrajových vln VHS + Detail VHS okrajové vlny + Výstupní chroma lowpass + Měřítko horizontální + Vertikální měřítko + Měřítko X + Měřítko Y + Detekuje běžné obrazové vodoznaky a vybarvuje je pomocí LaMa. Automaticky stahuje modely detekce a malování + Deuteranopie + Rozbalte obrázek + Odstraňovač pozadí založený na segmentaci objektů pomocí YOLO v11 Segmentation + Zobrazit úhel čáry + Zobrazuje aktuální natočení čáry ve stupních při kreslení + Animované emotikony + Zobrazit dostupné emotikony jako animace + Uložit do původní složky + Nové soubory ukládejte vedle původního souboru namísto vybrané složky + Vodoznak PDF + Nedostatek paměti + Obraz je pravděpodobně příliš velký pro zpracování na tomto zařízení, nebo systému došla dostupná paměť. Zkuste snížit rozlišení obrázku, zavřete ostatní aplikace nebo vyberte menší soubor. + Nabídka ladění + Nabídka pro testování funkcí aplikace, toto není určeno k zobrazení v produkčním vydání + Poskytovatel + PaddleOCR vyžaduje na vašem zařízení další modely ONNX. Chcete stáhnout data %1$s? + Univerzální + korejština + latinský + východoslovanský + thajština + řecký + angličtina + cyrilice + arabština + dévanágarí + tamilština + telugština + Shader + Přednastavený shader + Není vybrán žádný shader + Shader Studio + Vytvářejte, upravujte, ověřujte, importujte a exportujte vlastní shadery fragmentů + Uložené shadery + Otevřete uložené shadery, duplikujte, exportujte, sdílejte nebo odstraňte + Nový shader + Shader soubor + .itshader JSON + %1$d parametry + Shader zdroj + Zapište pouze tělo void main(). Použijte textureCoordinate pro UV souřadnice a zadejteImageTexture jako zdrojový vzorkovač. + Funkce + Volitelné pomocné funkce, konstanty a struktury vložené před void main(). Uniformy jsou generovány z parametrů. + Zde přidejte uniformy, když shader potřebuje upravitelné hodnoty. + Resetovat shader + Aktuální koncept shaderu bude vymazán + Neplatný shader + Nepodporovaná verze shaderu %1$d. Podporovaná verze: %2$d. + Název shaderu nesmí být prázdný. + Zdroj stínování nesmí být prázdný. + Zdroj shaderu obsahuje nepodporované znaky: %1$s. Používejte pouze zdroj ASCII GLSL. + Parametr \\\"%1$s\\\" je deklarován více než jednou. + Názvy parametrů nesmí být prázdné. + Parametr \\\"%1$s\\\" používá vyhrazený název GPUImage. + Parametr \\\"%1$s\\\" musí být platný identifikátor GLSL. + Parametr \\\"%1$s\\\" má %2$s typ hodnoty \\\"%3$s\\\", očekáváno \\\"%4$s\\\". + Parametr \\\"%1$s\\\" nemůže definovat minimální nebo maximální hodnoty bool. + Parametr \\\"%1$s\\\" má min větší než max. + Výchozí parametr \\\"%1$s\\\" je nižší než min. + Výchozí parametr \\\"%1$s\\\" je větší než max. + Shader musí deklarovat \\\"uniform sampler2D %1$s;\\\". + Shader musí deklarovat \\\"proměnlivé vec2 %1$s;\\\". + Shader musí definovat \\\"void main()\\\". + Shader musí zapsat barvu do gl_FragColor. + ShaderToy mainImage shadery nejsou podporovány. Použijte void main() kontrakt GPUImage. + Animovaná uniforma ShaderToy \\\"iTime\\\" není podporována. + Animovaná uniforma ShaderToy \\\"iFrame\\\" není podporována. + Uniforma ShaderToy \\\"iResolution\\\" není podporována. + Textury ShaderToy iChannel nejsou podporovány. + Externí textury nejsou podporovány. + Externí rozšíření textur nejsou podporována. + Parametry shaderu Libretro nejsou podporovány. + Je podporována pouze jedna vstupní textura. Odebrat \\\"uniform %1$s %2$s;\\\". + Parametr \\\"%1$s\\\" musí být deklarován jako \\\"uniform %2$s %1$s;\\\". + Parametr \\\"%1$s\\\" je deklarován jako \\\"jednotný %2$s %1$s;\\\", očekáván \\\"jednotný %3$s %1$s;\\\". + Soubor shaderu je prázdný. + Shader soubor musí být objekt .itshader JSON s poli verze, název a shader. + Shader soubor není platný JSON nebo neodpovídá formátu .itshader. + Pole \\\"%1$s\\\" je povinné. + Je vyžadováno %1$s. + %1$s \\\"%2$s\\\" není podporováno. Podporované typy: %3$s. + %1$s je vyžadováno pro parametry \\\"%2$s\\\". + %1$s musí být konečné číslo. + %1$s musí být celé číslo. + %1$s musí být pravdivé nebo nepravdivé. + %1$s musí být barevný řetězec, pole RGB/RGBA nebo barevný objekt. + %1$s musí používat formát #RRGGBB nebo #RRGGBBAA. + %1$s musí obsahovat platné hexadecimální barevné kanály. + %1$s musí obsahovat 3 nebo 4 barevné kanály. + %1$s musí být mezi 0 a 255. + %1$s musí být pole dvou čísel nebo objekt s x a y. + %1$s musí obsahovat přesně 2 čísla. + Duplikát + Vždy vymažte EXIF + Odeberte data EXIF ​​obrázku při uložení, i když nástroj požaduje zachování metadat + Nápověda a tipy + %1$d výukových programů + Otevřete nástroj + Naučte se hlavní nástroje, možnosti exportu, toky PDF, nástroje pro barvy a opravy běžných problémů + Začínáme + Rychlejší výběr nástrojů, import souborů, ukládání výsledků a opětovné použití nastavení + Úprava obrázků + Změna velikosti, oříznutí, filtrování, mazání pozadí, kreslení a vodoznaků + Soubory a metadata + Převádějte formáty, porovnávejte výstupy, chraňte soukromí a kontrolujte názvy souborů + PDF a dokumenty + Vytvářejte soubory PDF, skenujte dokumenty, stránky OCR a připravujte soubory ke sdílení + Text, QR kód a data + Rozpoznávat text, skenovat kódy, kódovat Base64, kontrolovat hashe a balit soubory + Barevné nástroje + Vybírejte barvy, sestavujte palety, prozkoumávejte knihovny barev a vytvářejte přechody + Kreativní nástroje + Generujte SVG, ASCII art, textury šumu, shadery a experimentální vizuály + Odstraňování problémů + Opravte těžké soubory, průhlednost, sdílení, importy a problémy s hlášením + Vyberte si správný nástroj + Pomocí kategorií, vyhledávání, oblíbených položek a akcí sdílení můžete začít na správném místě + Začněte od nejlepšího vstupního bodu + Image Toolbox má mnoho zaměřených nástrojů a mnohé z nich se na první pohled překrývají. Začněte od úkolu, který chcete dokončit, a poté vyberte nástroj, který řídí nejdůležitější část výsledku: velikost, formát, metadata, text, stránky PDF, barvy nebo rozvržení. + Vyhledávání použijte, když znáte název akce, jako je změna velikosti, PDF, EXIF, OCR, QR nebo barva. + Otevřete kategorii, když znáte pouze typ úkolu, a poté připněte časté nástroje jako oblíbené. + Když jiná aplikace sdílí obrázek do Image Toolbox, vyberte nástroj z toku sdílení listu. + Importujte, ukládejte a sdílejte výsledky + Pochopte obvyklý postup od výběru vstupu po export upraveného souboru + Postupujte podle běžného postupu úprav + Většina nástrojů se řídí stejným rytmem: vyberte vstup, upravte možnosti, zobrazte náhled a poté uložte nebo sdílejte. Důležitým detailem je, že uložením se vytvoří výstupní soubor, zatímco sdílení je obvykle nejlepší pro rychlé předání jiné aplikaci. + Vyberte jeden soubor pro nástroje s jedním obrázkem nebo několik souborů pro dávkové nástroje, pokud to výběr umožňuje. + Před uložením zkontrolujte ovládací prvky náhledu a výstupu, zejména možnosti formátu, kvality a názvu souboru. + Použijte sdílení pro rychlé předání nebo uložte, když potřebujete trvalou kopii ve vybrané složce. + Pracujte s mnoha obrázky najednou + Dávkové nástroje jsou nejlepší pro úlohy opakované změny velikosti, převodu, komprese a pojmenování + Připravte předvídatelnou dávku + Dávkové zpracování je nejrychlejší, pokud by se každý výstup měl řídit stejnými pravidly. Je to také nejjednodušší místo, kde můžete udělat velkou chybu, takže před zahájením dlouhého exportu zvolte pojmenování, kvalitu, metadata a chování složky. + Vyberte všechny související obrázky společně a zachovejte pořadí, pokud výstup závisí na sekvenci. + Před zahájením exportu nastavte pravidla pro změnu velikosti, formátu, kvality, metadat a názvu souboru. + Když jsou zdrojové soubory velké nebo když je pro vás cílový formát nový, spusťte nejprve malou testovací dávku. + Rychlá jednotlivá úprava + Použijte editor all-in-one, když potřebujete několik jednoduchých změn na jednom obrázku + Dokončete jeden obrázek bez přeskakování nástroje + Single Edit je užitečná, když obrázek vyžaduje malý řetězec změn spíše než jednu specializovanou operaci. Obvykle je rychlejší pro rychlé vyčištění, zobrazení náhledu a export jedné konečné kopie bez vytváření dávkového pracovního postupu. + Otevřete Single Edit pro jeden obrázek, který vyžaduje běžné úpravy, označení, oříznutí, otočení nebo změny exportu. + Úpravy aplikujte v pořadí, ve kterém se změní nejméně pixelů jako první, například oříznutí před filtry a filtry před konečnou kompresí. + Pokud potřebujete dávkové zpracování, přesné cíle velikosti souboru, výstup PDF, OCR nebo vyčištění metadat, použijte místo toho specializovaný nástroj. + Použijte předvolby a nastavení + Uložte opakované volby a vylaďte aplikaci pro svůj preferovaný pracovní postup + Vyvarujte se opakování stejného nastavení + Předvolby a nastavení jsou užitečné, když často exportujete stejný druh souboru. Dobrá předvolba promění opakovaný ruční kontrolní seznam na jedno klepnutí, zatímco globální nastavení definují výchozí hodnoty, se kterými začínají nové nástroje. + Vytvořte předvolby pro běžné výstupní velikosti, formáty, hodnoty kvality nebo vzory pojmenování. + Zkontrolujte nastavení pro výchozí formát, kvalitu, složku pro ukládání, název souboru, výběr a chování rozhraní. + Před přeinstalací aplikace nebo přesunem na jiné zařízení zálohujte nastavení. + Nastavte užitečné výchozí hodnoty + Jednou vyberte výchozí formát, kvalitu, režim měřítka, typ změny velikosti a barevný prostor + Začněte s novými nástroji blíže vašemu cíli + Výchozí hodnoty nejsou jen kosmetické preference. Rozhodují o tom, jaký formát, kvalita, chování při změně velikosti, režim měřítka a barevný prostor se v mnoha nástrojích objeví jako první, což pomáhá vyhnout se opakovanému výběru stejných voleb při každém exportu. + Nastavte výchozí formát obrázku a kvalitu tak, aby odpovídaly vašemu obvyklému cíli, jako je JPEG pro fotografie nebo PNG/WebP pro alfa. + Vylaďte výchozí typ změny velikosti a režim měřítka, pokud často exportujete pro striktní rozměry, sociální platformy nebo stránky dokumentů. + Výchozí nastavení barevného prostoru změňte pouze tehdy, když váš pracovní postup vyžaduje konzistentní manipulaci s barvami na displejích, v tisku nebo v externích editorech. + Výběr a spouštěč + Nastavení výběru použijte, když aplikace otevře příliš mnoho dialogů nebo se spustí na nesprávném místě + Přizpůsobte otevírání souborů vašemu zvyku + Nastavení výběru a spouštěče řídí, zda se Image Toolbox spustí z hlavní obrazovky, nástroje nebo toku výběru souborů. Tyto možnosti jsou zvláště užitečné, když obvykle zadáváte ze sdíleného listu Android nebo když chcete, aby aplikace vypadala spíše jako spouštěč nástrojů. + Použijte nastavení pro výběr fotografií, pokud systémový výběr skrývá soubory, zobrazuje příliš mnoho posledních položek nebo špatně zpracovává cloudové soubory. + Povolte přeskakování pouze pro nástroje, do kterých obvykle zadáváte ze sdílení nebo již znáte zdrojový soubor. + Zkontrolujte režim spouštěče, pokud chcete, aby se Image Toolbox choval spíše jako nástroj pro výběr nástrojů než jako běžná domovská obrazovka. + Zdroj obrázku + Vyberte režim výběru, který nejlépe funguje s galeriemi, správci souborů a cloudovými soubory + Vyberte soubory ze správného zdroje + Nastavení zdroje obrázků ovlivňuje, jak aplikace žádá Android o obrázky. Nejlepší volba závisí na tom, zda jsou vaše soubory umístěny v systémové galerii, složce správce souborů, poskytovateli cloudu nebo jiné aplikaci, která sdílí dočasný obsah. + Výchozí nástroj pro výběr použijte, chcete-li pro běžné obrázky galerie maximálně integrované prostředí. + Přepněte režim výběru, pokud se alba, složky, cloudové soubory nebo nestandardní formáty nezobrazují tam, kde očekáváte. + Pokud sdílený soubor po opuštění zdrojové aplikace zmizí, znovu jej otevřete pomocí trvalého výběru nebo nejprve uložte místní kopii. + Oblíbené a nástroje pro sdílení + Udržujte hlavní obrazovku zaměřenou a skryjte nástroje, které v tocích sdílení nedávají smysl + Snižte šum seznamu nástrojů + Oblíbené a skryté nastavení pro sdílení jsou užitečné, když každý den používáte jen několik nástrojů. Také udržují tok sdílení praktický tím, že skrývají nástroje, které nemohou používat typ souboru odeslaného jinou aplikací. + Připněte časté nástroje, aby zůstaly snadno dosažitelné, i když jsou nástroje seskupeny podle kategorií. + Skryjte nástroje před sdílením, pokud nejsou relevantní pro soubory pocházející z jiné aplikace. + Pokud preferujete oblíbené položky na konci nebo seskupené se souvisejícími nástroji, použijte oblíbené nastavení řazení. + Zálohovat nastavení + Bezpečně přesuňte předvolby, preference a chování aplikace do jiné instalace + Udržujte své nastavení přenosné + Zálohování a obnovení je nejužitečnější poté, co vyladíte předvolby, složky, pravidla pro názvy souborů, pořadí nástrojů a vizuální předvolby. Záloha vám umožní experimentovat s nastavením nebo přeinstalovat aplikaci, aniž byste museli znovu sestavovat pracovní postup z paměti. + Vytvořte zálohu po změně přednastavení, chování názvu souboru, výchozích formátů nebo uspořádání nástrojů. + Pokud plánujete vymazat data, přeinstalovat nebo přesunout zařízení, uložte zálohu někde mimo složku aplikace. + Před zpracováním důležitých dávek obnovte nastavení, aby výchozí nastavení a chování při ukládání odpovídalo vašemu starému nastavení. + Změna velikosti a převod obrázků + Změňte velikost, formát, kvalitu a metadata pro jeden nebo více obrázků + Vytvářejte kopie se změněnou velikostí + Změnit velikost a převést je hlavním nástrojem pro předvídatelné rozměry a lehčí výstupní soubory. Použijte jej, když na velikosti obrázku, výstupním formátu a chování metadat záleží současně. + Vyberte jeden nebo více obrázků a poté zvolte, zda jsou nejdůležitější rozměry, měřítko nebo velikost souboru. + Vyberte výstupní formát a kvalitu; použijte PNG nebo WebP, když musí být zachována průhlednost. + Prohlédněte si výsledek a poté uložte nebo sdílejte vygenerované kopie beze změny originálů. + Změnit velikost podle velikosti souboru + Zaměřte se na limit velikosti, když web, messenger nebo formulář odmítají těžké obrázky + Dodržujte přísné limity nahrávání + Změna velikosti podle velikosti souboru je lepší než odhadování hodnot kvality, když cíl přijímá pouze soubory pod určitým limitem. Nástroj může upravit kompresi a rozměry tak, aby dosáhl cíle a zároveň se snažil zachovat použitelný výsledek. + Zadejte cílovou velikost mírně pod skutečný limit nahrávání, abyste ponechali prostor pro metadata a změny poskytovatele. + U fotografií preferujte ztrátové formáty, když je cíl malý; PNG může zůstat velký i po změně velikosti. + Po exportu zkontrolujte plochy, text a hrany, protože cíle s agresivní velikostí mohou způsobit viditelné artefakty. + Omezit změnu velikosti + Zmenšit pouze obrázky, které přesahují maximální šířku, výšku nebo omezení souboru + Vyhněte se změně velikosti obrázků, které jsou již v pořádku + Limit Resize je užitečný pro smíšené složky, kde jsou některé obrázky velké a jiné jsou již připravené. Namísto vynucování stejné velikosti každého souboru, udržuje přijatelné obrázky blíže jejich původní kvalitě. + Nastavit maximální rozměry nebo limity na základě cíle, spíše než slepě měnit velikost každého souboru. + Chcete-li se vyhnout výstupům, které jsou těžší než zdroje, povolte chování stylu přeskočení, pokud chcete větší. + Použijte to pro dávky z různých fotoaparátů, snímky obrazovky nebo stahování, kde se velikosti zdrojů velmi liší. + Ořízněte, otočte a narovnejte + Před exportem nebo použitím obrázku v jiných nástrojích zarámujte důležitou oblast + Vyčistěte kompozici + Oříznutí nejprve vyčistí pozdější filtry, OCR, stránky PDF a výsledky sdílení. + Otevřete Oříznout a vyberte obrázek, který chcete upravit. + Pokud se výstup musí vejít do příspěvku, dokumentu nebo avatara, použijte volné oříznutí nebo poměr stran. + Před uložením otočte nebo překlopte, aby pozdější nástroje obdržely opravený obrázek. + Použijte filtry a úpravy + Před exportem vytvořte upravitelný zásobník filtrů a zobrazte náhled výsledku + Vylaďte vzhled obrazu + Filtry jsou užitečné pro rychlé opravy, stylizovaný výstup a přípravu obrázků pro OCR nebo tisk. Malé změny kontrastu, ostrosti a jasu mohou mít větší význam než velké efekty, pokud obrázek obsahuje text nebo jemné detaily. + Přidávejte filtry jeden po druhém a po každé změně sledujte náhled. + Upravte jas, kontrast, ostrost, rozostření, barvu nebo masky v závislosti na úkolu. + Exportujte pouze v případě, že náhled odpovídá vašemu cílovému zařízení, dokumentu nebo sociální platformě. + Nejprve si prohlédněte náhled + Před výběrem náročnějšího nástroje pro úpravy, převod nebo čištění obrázky zkontrolujte + Zkontrolujte, co jste skutečně dostali + Náhled obrázku je užitečný, když soubor pochází z chatu, cloudového úložiště nebo jiné aplikace a nejste si jisti, co obsahuje. Kontrola rozměrů, průhlednosti, orientace a vizuální kvality nejprve pomůže vybrat správný následný nástroj. + Otevřete Náhled obrázku, když potřebujete zkontrolovat několik obrázků, než se rozhodnete, co s nimi udělat. + Hledejte problémy s rotací, průhledné oblasti, kompresní artefakty a neočekávaně velké rozměry. + Přejděte na Změnit velikost, Oříznout, Porovnat, EXIF ​​nebo Odstraňovač pozadí až poté, co víte, co je třeba opravit. + Odstraňte nebo obnovte pozadí + Vymažte pozadí automaticky nebo upravte okraje ručně pomocí nástrojů pro obnovení + Zachovejte předmět, odstraňte zbytek + Odstraňování pozadí funguje nejlépe, když kombinujete automatický výběr s pečlivým ručním čištěním. Na konečném formátu exportu záleží stejně jako na masce, protože JPEG srovná průhledné oblasti, i když náhled vypadá správně. + Pokud je objekt zřetelně oddělen od pozadí, začněte s automatickým odstraněním. + Přibližujte a přepínejte mezi mazáním a obnovením, abyste opravili vlasy, rohy, stíny a malé detaily. + Uložte jako PNG nebo WebP, když potřebujete průhlednost v konečném souboru. + Kreslení, označení a vodoznak + Přidejte šipky, poznámky, zvýraznění, podpisy nebo opakované překrytí vodoznaku + Přidejte viditelné informace + Nástroje pro označování pomáhají vysvětlit obrázek, zatímco vodoznak pomáhá označit nebo chránit exportované soubory. + Kreslit použijte, když potřebujete poznámky od ruky, tvary, zvýraznění nebo rychlé poznámky. + Vodoznak použijte, pokud by se na výstupech měla konzistentně zobrazovat stejná textová nebo obrazová značka. + Před exportem zkontrolujte neprůhlednost, polohu a měřítko, aby byla značka viditelná, ale nerušila. + Výchozí nastavení kreslení + Nastavte šířku čáry, barvu, režim cesty, zámek orientace a lupu pro rychlejší označování + Nechte kreslení působit předvídatelně + Nastavení kreslení je užitečné, když často anotujete snímky obrazovky, označujete dokumenty nebo podepisujete obrázky. Výchozí nastavení zkracuje dobu nastavení, zatímco lupa a zámek orientace usnadňují přesné úpravy na malých obrazovkách. + Nastavte výchozí šířku čáry a barvu kreslení na hodnoty, které nejčastěji používáte pro šipky, zvýraznění nebo podpisy. + Výchozí režim cesty použijte, když obvykle kreslíte stejný druh tahů, tvarů nebo označení. + Povolte lupu nebo zámek orientace, když vstup prstem ztěžuje přesné umístění malých detailů. + Rozložení s více obrázky + Před uspořádáním snímků obrazovky, skenů nebo srovnávacích pásků vyberte správný nástroj pro více obrázků + Použijte správný nástroj pro rozložení + Tyto nástroje zní podobně, ale každý z nich je vyladěn pro jiný druh výstupu více obrázků. Prvním výběrem správného se vyhnete soubojům s ovládacími prvky rozvržení, které byly navrženy pro jiný výsledek. + Použijte Collage Maker pro navržená rozvržení s několika obrázky v jedné kompozici. + Pokud by se z obrázků měl stát jeden dlouhý svislý nebo vodorovný pruh, použijte sešívání nebo skládání obrázků. + Rozdělení obrazu použijte, když se z jednoho velkého obrazu musí stát několik menších částí. + Převod obrazových formátů + Vytvářejte kopie JPEG, PNG, WebP, AVIF, JXL a další bez ruční úpravy pixelů + Vyberte formát úlohy + Různé formáty jsou lepší pro fotografie, snímky obrazovky, průhlednost, kompresi nebo kompatibilitu. Konverze je nejbezpečnější, když víte, co cílová aplikace podporuje a zda zdroj obsahuje alfa, animaci nebo metadata, která chcete zachovat. + Použijte JPEG pro kompatibilní fotografie, PNG pro bezztrátové obrázky a alfa a WebP pro kompaktní sdílení. + Upravte kvalitu, pokud to formát podporuje; nižší hodnoty zmenšují velikost, ale mohou přidávat artefakty. + Uchovávejte originály, dokud neověříte, že se převedené soubory správně otevírají v cílové aplikaci. + Zkontrolujte EXIF ​​a soukromí + Zobrazte, upravte nebo odstraňte metadata před sdílením citlivých fotografií + Ovládání skrytých obrazových dat + EXIF může obsahovat data fotoaparátu, data, umístění, orientaci a další podrobnosti, které nejsou na obrázku vidět. Před veřejným sdílením, zprávami podpory, výpisy z tržiště nebo jakoukoli fotografií, která může odhalit soukromé místo, se vyplatí zkontrolovat. + Před sdílením fotografií, které mohou obsahovat informace o poloze nebo soukromém zařízení, otevřete nástroje EXIF. + Odstraňte citlivá pole nebo upravte nesprávné značky, jako je datum, orientace, autor nebo data fotoaparátu. + Uložte si novou kopii a sdílejte tuto kopii místo originálu, když na soukromí záleží. + Automatické čištění EXIF + Při rozhodování, zda mají být data zachována, ve výchozím nastavení odeberte metadata + Nastavte soukromí jako výchozí + Skupina nastavení EXIF ​​je užitečná, když chcete, aby čištění soukromí probíhalo automaticky, místo aby si to pamatovalo pro každý export. Zachovat datum a čas je samostatné rozhodnutí, protože data mohou být užitečná pro řazení, ale citlivá pro sdílení. + Povolte vždy jasné EXIF, když je většina vašich exportů určena pro veřejné nebo poloveřejné sdílení. + Zachovat datum a čas povolte pouze tehdy, když je zachování času zachycení důležitější než odstranění každého časového razítka. + Použijte ruční editaci EXIF ​​pro jednorázové soubory, kde potřebujete některá pole ponechat a jiná odstranit. + Řídit názvy souborů + Používejte předpony, přípony, časová razítka, předvolby, kontrolní součty a pořadová čísla + Usnadněte vyhledávání exportů + Dobrý vzor názvu souboru pomáhá třídit dávky a zabraňuje náhodnému přepsání. Nastavení názvů souborů je užitečné zejména tehdy, když se exporty vrátí do zdrojové složky, kde se podobné názvy mohou rychle zmást. + Nastavte předponu, příponu, časové razítko, původní název, přednastavené informace nebo možnosti sekvence v Nastavení. + Použijte pořadová čísla pro dávky, kde záleží na pořadí, jako jsou stránky, rámečky nebo koláže. + Povolte přepisování pouze v případě, že jste si jisti, že nahrazení existujících souborů je pro aktuální složku bezpečné. + Přepsat soubory + Nahraďte výstupy odpovídajícími názvy pouze v případě, že je váš pracovní postup záměrně destruktivní + Režim přepisování používejte opatrně + Přepsat soubory mění způsob řešení konfliktů názvů. Je to užitečné pro opakované exporty do stejné složky, ale může také nahradit dřívější výsledky, pokud váš vzor názvu souboru znovu vytvoří stejný název. + Povolit přepsání pouze pro složky, kde se očekává a obnoví nahrazení starších vygenerovaných souborů. + Při exportu originálů, klientských souborů, jednorázových skenů nebo čehokoli bez zálohy mějte zakázáno přepisování. + Zkombinujte přepsání se záměrným vzorem názvu souboru, aby byly nahrazeny pouze zamýšlené výstupy. + Vzory názvů souborů + Kombinujte původní názvy, předpony, přípony, časová razítka, předvolby, režim měřítka a kontrolní součty + Sestavte názvy, které vysvětlují výstup + Nastavení vzoru názvu souboru může změnit exportované soubory na užitečnou historii toho, co se s nimi stalo. To je důležité v dávkách, protože zobrazení složky může být jediným místem, kde můžete rozlišit velikost originálu, přednastavení, formát a pořadí zpracování. + Pokud potřebujete čitelné názvy pro ruční řazení, použijte původní název souboru, předponu, příponu a časové razítko. + Přidejte informace o předvolbě, režimu měřítka, velikosti souboru nebo kontrolního součtu, kdy musí výstupy dokumentovat, jak byly vytvořeny. + Vyhněte se náhodným názvům pracovních postupů, kde soubory musí zůstat spárované s originály nebo pořadím stránek. + Vyberte, kam se mají soubory ukládat + Zabraňte ztrátě exportů nastavením složek, uložením původní složky a umístěním jednorázového uložení + Udržujte výstupy předvídatelné + Chování při ukládání je nejdůležitější, když zpracováváte mnoho souborů nebo sdílíte soubory od poskytovatelů cloudu. Nastavení složky rozhoduje o tom, zda se výstupy shromažďují na jednom známém místě, zobrazují se vedle originálů nebo dočasně jdou někam jinam pro konkrétní úkol. + Pokud chcete exporty vždy na jednom místě, nastavte výchozí složku pro ukládání. + Použijte uložení do původní složky pro pracovní postupy čištění, kde by měl výstup zůstat blízko zdrojového souboru. + Použijte místo pro jednorázové uložení, když jeden úkol vyžaduje jiný cíl, aniž byste změnili výchozí nastavení. + Složky pro jednorázové uložení + Odešlete další exporty do dočasné složky, aniž byste změnili svůj normální cíl + Udržujte speciální úlohy oddělené + Umístění jednorázového uložení je užitečné pro dočasné projekty, složky klientů, relace čištění nebo exporty, které by se neměly mísit s běžnou složkou Image Toolbox. Poskytuje krátkodobý cíl bez přepisování výchozího nastavení. + Před zahájením úlohy vyberte jednorázovou složku, která nepatří do vašeho normálního umístění exportu. + Použijte jej pro krátké projekty, sdílené složky nebo dávky, kde výstupy musí zůstat seskupeny. + Po dokončení úlohy se vraťte do výchozí složky, aby se pozdější exporty neočekávaně nedostaly do dočasného umístění. + Vyvažte kvalitu a velikost souboru + Pochopte, kdy změnit rozměry, formát nebo kvalitu namísto hádání + Soubory zmenšujte záměrně + Velikost souboru závisí na rozměrech obrázku, formátu, kvalitě, průhlednosti a složitosti obsahu. Pokud je soubor stále příliš těžký, změna rozměrů obvykle pomůže více než opakované snižování kvality. + Pokud je obrázek větší, než může cíl zobrazit, změňte nejprve rozměry. + Nižší kvalita pouze u ztrátových formátů a po exportu zkontrolujte text, okraje, přechody a plochy. + Přepněte formát, když změny kvality nestačí, například WebP pro sdílení nebo PNG pro ostré transparentní položky. + Práce s animovanými formáty + Pokud má soubor snímky namísto jedné bitmapy, použijte nástroje GIF, APNG, WebP a JXL + Nezacházejte s každým obrazem jako s nehybným obrazem + Animované formáty potřebují nástroje, které rozumí snímkům, trvání a kompatibilitě. + Použijte nástroje GIF nebo APNG, když pro tyto formáty potřebujete operace na úrovni rámce. + Použijte nástroje WebP, když potřebujete moderní kompresi nebo animovaný výstup WebP. + Před sdílením si prohlédněte časování animace, protože některé platformy převádějí nebo slučují animované obrázky. + Porovnejte a zobrazte náhled výstupu + Před uložením exportu zkontrolujte změny, velikost souboru a vizuální rozdíly + Před sdílením ověřte + Porovnávací nástroje pomáhají včas zachytit kompresní artefakty, nesprávné barvy nebo nežádoucí úpravy. + Otevřete Porovnat, pokud chcete zkontrolovat dvě verze vedle sebe. + Přibližte okraje, text, přechody a průhledné oblasti, kde lze problémy nejsnáze přehlédnout. + Pokud je výstup příliš těžký nebo rozmazaný, vraťte se k předchozímu nástroji a upravte kvalitu nebo formát. + Použijte centrum nástrojů PDF + Sloučit, rozdělovat, otáčet, odstraňovat stránky, komprimovat, chránit, OCR a vodoznakové soubory PDF + Vyberte operaci PDF + Centrum PDF seskupuje akce dokumentu za jeden vstupní bod, takže si můžete vybrat přesnou operaci. Začněte tam, když je soubor již PDF, a použijte Obrázky do PDF nebo Skener dokumentů, když je vaším zdrojem stále sada obrázků. + Otevřete Nástroje PDF a vyberte operaci, která odpovídá vašemu cíli. + Vyberte zdrojové PDF nebo obrázky a poté zkontrolujte pořadí stránek, otočení, ochranu nebo možnosti OCR. + Uložte vygenerovaný soubor PDF a jednou jej otevřete, abyste potvrdili počet stránek, pořadí a čitelnost. + Převeďte obrázky do PDF + Spojte skeny, snímky obrazovky, účtenky nebo fotografie do jednoho dokumentu, který lze sdílet + Vytvořte čistý dokument + Obrazy se stanou lepšími stránkami PDF, když jsou oříznuty, uspořádány a mají konzistentní velikost. Se seznamem obrázků zacházejte jako se stohem stránek: každé otočení, okraj a velikost stránky ovlivňují, jak čitelný bude výsledný dokument. + Pokud jsou okraje stránky, stíny nebo orientace nesprávné, nejprve ořízněte nebo otočte obrázky. + Vyberte obrázky v zamýšleném pořadí stránek a upravte velikost stránky nebo okraje, pokud je nástroj nabízí. + Exportujte PDF a před odesláním zkontrolujte, zda jsou všechny stránky čitelné. + Skenujte dokumenty + Zachyťte papírové stránky a připravte je pro PDF, OCR nebo sdílení + Zachyťte čitelné stránky + Skenování dokumentů funguje nejlépe s plochými stránkami, dobrým osvětlením a opravenou perspektivou. Čisté skenování před OCR nebo exportem PDF šetří čas, protože rozpoznávání textu i komprese závisí na kvalitě stránky. + Umístěte dokument na kontrastní povrch s dostatkem světla a minimálními stíny. + Upravte zjištěné rohy nebo ořízněte ručně, aby stránka čistě vyplnila rámeček. + Exportujte jako obrázky nebo PDF a poté spusťte OCR, pokud potřebujete volitelný text. + Ochrana nebo odemknutí souborů PDF + Hesla používejte opatrně a pokud je to možné, ponechejte si upravitelnou zdrojovou kopii + Zacházejte s přístupem k PDF bezpečně + Nástroje ochrany jsou užitečné pro kontrolované sdílení, ale hesla se snadno ztrácejí a obtížně se obnovují. Chraňte kopie pro distribuci, uchovávejte nechráněné zdrojové soubory odděleně a před odstraněním čehokoli ověřte výsledek. + Chraňte kopii PDF, nikoli váš jediný zdrojový dokument. + Před sdílením chráněného souboru si heslo uložte na bezpečném místě. + Odemknout použijte pouze pro soubory, které smíte upravovat, a poté ověřte, zda se odemčená kopie správně otevře. + Uspořádejte stránky PDF + Slučujte, rozdělujte, přeskupujte, otáčejte, ořezávejte, odeberte stránky a záměrně přidejte čísla stránek + Před dekorací opravte strukturu dokumentu + Nástroje pro stránky PDF se nejsnáze používají ve stejném pořadí, v jakém byste připravovali papírový dokument: shromážděte stránky, odstraňte nesprávné, seřaďte je, upravte orientaci a poté přidejte poslední podrobnosti, jako jsou čísla stránek nebo vodoznaky. + Pokud je dokument stále rozdělen do několika souborů, použijte nejprve sloučení nebo převod obrázků do PDF. + Před přidáním čísel stránek, podpisů nebo vodoznaků přeuspořádejte, otočte, ořízněte nebo odstraňte stránky. + Po strukturálních úpravách si zobrazte náhled konečného PDF, protože jedné chybějící nebo otočené stránky může být později těžké si všimnout. + PDF anotace + Odstraňte poznámky nebo je srovnejte, když diváci zobrazují komentáře a značky jinak + Ovládejte, co zůstává viditelné + Anotace mohou být komentáře, zvýraznění, kresby, značky formulářů nebo jiné další objekty PDF. Některé prohlížeče je zobrazují odlišně, takže jejich odstraněním nebo sloučením může být sdílené dokumenty předvídatelnější. + Odstraňte anotace, pokud by do sdílené kopie neměly být zahrnuty komentáře, zvýraznění nebo značky recenzí. + Sloučit, když by viditelné značky měly zůstat na stránce, ale už se nechovat jako upravitelné poznámky. + Ponechte si původní kopii, protože odstranění nebo sloučení anotací může být obtížné vrátit zpět. + Rozpoznat text + Extrahujte text z obrázků pomocí volitelných modulů OCR a jazyků + Získejte lepší výsledky OCR + Přesnost OCR závisí na ostrosti obrazu, jazykových datech, kontrastu a orientaci stránky. Nejlepších výsledků obvykle dosáhnete, když obrázek nejprve připravíte: ořízněte šum, otočte jej na výšku, zvyšte čitelnost a poté rozpoznávejte text. + Před rozpoznáním ořízněte obrázek do textové oblasti a otočte jej svisle. + Vyberte správný jazyk a stáhněte si jazyková data, pokud to aplikace vyžaduje. + Zkopírujte nebo sdílejte rozpoznaný text a poté ručně zkontrolujte jména, čísla a interpunkci. + Vyberte jazyková data OCR + Zlepšete rozpoznávání pomocí shody skriptů, jazyků a stažených modelů OCR + Přiřaďte OCR k textu + Nesprávný jazyk OCR může způsobit, že dobré fotografie budou vypadat jako špatné rozpoznání. Jazyková data jsou důležitá pro skripty, interpunkci, čísla a smíšené dokumenty, takže raději než jazyk uživatelského rozhraní telefonu vyberte jazyk, který se objeví na obrázku. + Vyberte jazyk nebo skript, který se objeví na obrázku, nejen jazyk telefonu. + Před prací offline nebo zpracováním dávky si stáhněte chybějící data. + U dokumentů se smíšenými jazyky spusťte nejprve nejdůležitější jazyk a ručně zkontrolujte jména a čísla. + Prohledávatelné soubory PDF + Zapište text OCR zpět do souborů, aby bylo možné naskenované stránky vyhledávat a kopírovat + Proměňte skeny na dokumenty s možností vyhledávání + OCR umí víc než jen kopírování textu do schránky. Když zapíšete rozpoznaný text do souboru nebo prohledávatelného PDF, obraz stránky zůstane viditelný, zatímco text se bude snáze vyhledávat, vybírat, indexovat nebo archivovat. + Použijte prohledávatelný výstup PDF pro naskenované dokumenty, které by měly stále vypadat jako původní stránky. + Zápis do souboru použijte, když potřebujete pouze extrahovaný text a nestaráte se o obrázek stránky. + Ručně zkontrolujte důležitá čísla, jména a tabulky, protože text OCR lze prohledávat, ale stále je nedokonalý. + Naskenujte QR kódy + Přečtěte si QR obsah ze vstupu fotoaparátu nebo uložených obrázků, pokud jsou k dispozici + Čtěte kódy bezpečně + QR kódy mohou obsahovat odkazy, data Wi-Fi, kontakty, text nebo jiné užitečné údaje. + Otevřete Scan QR Code a udržujte kód ostrý, plochý a celý uvnitř rámečku. + Před otevřením odkazů nebo zkopírováním citlivých dat zkontrolujte dekódovaný obsah. + Pokud se skenování nezdaří, vylepšete osvětlení, ořízněte kód nebo zkuste zdrojový obrázek s vyšším rozlišením. + Použijte Base64 a kontrolní součty + Kódujte obrázky jako text, dekódujte text zpět na obrázky a ověřte integritu souboru + Pohyb mezi soubory a textem + Base64 je užitečný pro malé množství obrázků, zatímco kontrolní součty pomáhají potvrdit, že se soubory nezměnily. Tyto nástroje jsou nejužitečnější v technických pracovních postupech, zprávách podpory, přenosech souborů a místech, kde se binární soubory musí dočasně stát textem. + Použijte nástroje Base64 ke kódování malého obrázku, když pracovní postup potřebuje text místo binárního souboru. + Dekódujte Base64 pouze z důvěryhodných zdrojů a před uložením ověřte náhled. + Nástroje kontrolního součtu použijte, když potřebujete porovnat exportované soubory nebo potvrdit nahrání/stažení. + Zabalte nebo chraňte data + Použijte ZIP a šifrovací nástroje pro sdružování souborů nebo transformaci textových dat + Uchovávejte související soubory pohromadě + Nástroje pro balení jsou užitečné, když by několik výstupů mělo cestovat jako jeden soubor. Jsou také dobré pro uchování vygenerovaných obrázků, souborů PDF, protokolů a metadat pohromadě, když potřebujete odeslat kompletní sadu výsledků. + ZIP použijte, když potřebujete odeslat mnoho exportovaných obrázků nebo dokumentů dohromady. + Šifrovací nástroje používejte pouze v případě, že rozumíte zvolené metodě a dokážete bezpečně uložit požadované klíče. + Před odstraněním zdrojových souborů otestujte vytvořený archiv nebo transformovaný text. + Kontrolní součty ve jménech + Použijte názvy souborů založené na kontrolním součtu, když na jedinečnosti a integritě záleží více než na čitelnosti + Pojmenujte soubory podle obsahu + Názvy souborů kontrolních součtů jsou užitečné, když exporty mohou mít duplicitní viditelné názvy nebo když potřebujete prokázat, že dva soubory jsou totožné. Jsou méně přátelské pro ruční procházení, takže je používejte pro technické archivy a ověřovací pracovní postupy. + Povolit kontrolní součet jako název souboru, když je identita obsahu důležitější než lidsky čitelný název. + Použijte jej pro archivy, deduplikaci, opakovatelné exporty nebo soubory, které lze nahrávat a znovu stahovat. + Spárujte názvy kontrolních součtů se složkami nebo metadaty, když lidé stále potřebují pochopit, co soubor představuje. + Vyberte barvy z obrázků + Vzorujte pixely, zkopírujte barevné kódy a znovu použijte barvy v paletách nebo návrzích + Vzorek přesné barvy + Výběr barev je užitečný pro sladění designu, extrahování barev značky nebo kontrolu hodnot obrázků. Přiblížení je důležité, protože vyhlazování, stíny a komprese mohou způsobit, že se sousední pixely mírně liší od barvy, kterou očekáváte. + Otevřete Vybrat barvu z obrázku a vyberte zdrojový obrázek s barvou, kterou potřebujete. + Před vzorkováním malých detailů přibližte, aby blízké pixely neovlivnily výsledek. + Zkopírujte barvu ve formátu požadovaném vaším cílem, jako je HEX, RGB nebo HSV. + Vytvářejte palety a používejte knihovnu barev + Extrahujte palety, procházejte uložené barvy a udržujte konzistentní vizuální systémy + Vytvořte znovu použitelnou paletu + Palety pomáhají udržovat exportovanou grafiku, vodoznaky a kresby vizuálně konzistentní. Jsou zvláště užitečné, když opakovaně anotujete snímky obrazovky, připravujete podklady značky nebo potřebujete stejné barvy v několika nástrojích. + Vygenerujte paletu z obrázku nebo přidejte barvy ručně, když již znáte hodnoty. + Pojmenujte nebo uspořádejte důležité barvy, abyste je později znovu našli. + Použijte zkopírované hodnoty v nástrojích Kreslení, vodoznak, přechod, SVG nebo externích návrhových nástrojích. + Vytvořte přechody + Vytvářejte obrázky s přechodem pro pozadí, zástupné symboly a vizuální experimenty + Ovládání barevných přechodů + Nástroje pro přechody jsou užitečné, když potřebujete vygenerovaný obrázek místo úpravy existujícího. Mohou se stát čistým pozadím, zástupnými symboly, tapetami, maskami nebo jednoduchými prostředky pro externí nástroje pro návrh. + Vyberte typ přechodu a nejprve nastavte důležité barvy. + Upravte směr, zastávky, plynulost a velikost výstupu pro cílový případ použití. + Exportujte ve formátu, který odpovídá cíli, obvykle PNG pro čistě generovanou grafiku. + Barevná dostupnost + Když je uživatelské rozhraní špatně čitelné, použijte dynamické barvy, kontrast a možnosti barvosleposti + Usnadněte použití barev + Nastavení barev ovlivňuje pohodlí, čitelnost a rychlost skenování ovládacích prvků. Pokud je obtížné rozlišit čipy nástrojů, posuvníky nebo vybrané stavy, možnosti motivu a usnadnění mohou být užitečnější než pouhá změna tmavého režimu. + Vyzkoušejte dynamické barvy, když chcete, aby aplikace sledovala aktuální paletu tapet. + Zvyšte kontrast nebo změňte schéma, když tlačítka a povrchy dostatečně nevyčnívají. + Pokud je obtížné rozlišit některé stavy nebo akcenty, použijte možnosti barevně slepého schématu. + Alfa pozadí + Ovládejte barvu náhledu nebo výplně používanou kolem průhledných výstupů PNG, WebP a podobných + Pochopte průhledné náhledy + Barva pozadí u alfa formátů může usnadnit kontrolu průhledných obrázků, ale je důležité vědět, zda měníte chování náhledu/výplně nebo slučujete konečný výsledek. To je důležité pro nálepky, loga a obrázky s odstraněným pozadím. + Nejprve použijte formát podporující alfa verzi, jako je PNG nebo WebP, když musí průhledné pixely přežít export. + Upravte nastavení barvy pozadí, když jsou průhledné okraje proti povrchu aplikace špatně vidět. + Exportujte malý test a znovu jej otevřete v jiné aplikaci, abyste potvrdili, zda byla uložena průhlednost nebo vybraná výplň. + Výběr modelu AI + Vyberte model podle typu obrázku a defektu místo toho, abyste nejprve vybírali ten největší + Přiřaďte model k poškození + Nástroje AI mohou stahovat nebo importovat různé modely ONNX/ORT a každý model je trénován pro konkrétní druh problému. Model pro bloky JPEG, čištění čar anime, odstranění šumu, odstranění pozadí, kolorizace nebo převzorkování může přinést horší výsledky, pokud se použije na nesprávný typ obrázku. + Před stažením si přečtěte popis modelu; vyberte model, který pojmenovává váš skutečný problém, jako je komprese, šum, polotóny, rozostření nebo odstranění pozadí. + Při testování nového typu obrázku začněte s menším nebo specifičtějším modelem a poté porovnávejte s těžšími modely pouze v případě, že výsledek není dostatečně dobrý. + Ponechte si pouze modely, které skutečně používáte, protože stažené a importované modely mohou zabírat znatelný úložný prostor. + Pochopte modelové rodiny + Názvy modelů často naznačují jejich cíl: modely ve stylu RealESRGAN upscale, modely SCUNet a FBCNN čistí šum nebo kompresi, modely na pozadí vytvářejí masky a kolorizéry přidávají barvu vstupu ve stupních šedi. Importované vlastní modely mohou být výkonné, ale měly by odpovídat očekávanému tvaru vstupu/výstupu a používat podporovaný formát ONNX nebo ORT. + Použijte upscalers, když potřebujete více pixelů, potlačení šumu, když je obraz zrnitý, a odstraňovače artefaktů, když jsou hlavním problémem kompresní bloky nebo pruhy. + Používejte modely na pozadí pouze pro oddělení objektů; nejsou totéž jako obecné odstranění objektu nebo vylepšení obrazu. + Importujte vlastní modely pouze ze zdrojů, kterým důvěřujete, a před použitím důležitých souborů je otestujte na malém obrázku. + parametry AI + Vylaďte velikost bloku, překrytí a paralelní pracovníky, abyste vyvážili kvalitu, rychlost a paměť + Vyvažte rychlost se stabilitou + Velikost bloku určuje, jak velké jsou části obrazu, než je model zpracuje. Překrytí prolne sousední kusy, aby se skryly hranice. Paralelní pracovníci mohou urychlit zpracování, ale každý pracovník potřebuje paměť, takže vysoké hodnoty mohou způsobit nestabilitu velkých obrázků na telefonech s omezenou RAM. + Použijte větší kusy pro hladší kontext, když vaše zařízení zvládá model spolehlivě a obraz není velký. + Zvětšete překrytí, když se okraje bloků stanou viditelnými, ale nezapomeňte, že větší překrytí znamená více opakované práce. + Paralelní dělníky zvedněte až po stabilním testu; pokud se zpracování zpomalí, zařízení se zahřívá nebo havaruje, nejprve snižte počet pracovníků. + Vyhněte se kusovým artefaktům + Chunking umožňuje aplikaci zpracovávat obrázky, které jsou větší, než dokáže model pohodlně zvládnout najednou. Kompromisem je, že každá dlaždice má méně okolního kontextu, takže nízké překrytí nebo příliš malé kousky mohou vytvářet viditelné okraje, změny textury nebo nekonzistentní detaily mezi sousedními oblastmi. + Zvětšete překrytí, pokud vidíte obdélníkové okraje, opakované změny textury nebo přeskakování detailů mezi zpracovanými oblastmi. + Snižte velikost bloku, pokud proces selže před vytvořením výstupu, zejména u velkých obrázků nebo těžkých modelů. + Před zpracováním celého obrázku si uložte malý test oříznutí, abyste mohli rychle porovnávat parametry. + Stabilita AI + Nižší velikost bloku, pracovníci a rozlišení zdroje, když zpracování AI selže nebo zamrzne + Zotavte se z těžkých úloh AI + Zpracování AI je jedním z nejtěžších pracovních postupů v aplikaci. Selhání, neúspěšné relace, zamrznutí nebo náhlé restartování aplikace obvykle znamenají, že model, rozlišení zdroje, velikost bloku nebo počet paralelních pracovníků jsou pro aktuální stav zařízení příliš náročné. + Pokud aplikace selže nebo se nepodaří otevřít relaci modelu, zmenšete paralelní pracovníky a velikost bloku, pak zkuste stejný obrázek znovu. + Změňte velikost velmi velkých obrázků před zpracováním AI, když cíl nevyžaduje původní rozlišení. + Zavřete jiné náročné aplikace, použijte menší model nebo zpracujte méně souborů najednou, když se tlak paměti neustále vrací. + Diagnostikujte selhání modelu + Neúspěšný běh AI nemusí vždy znamenat, že je obraz špatný. Soubor modelu může být neúplný, nepodporovaný, příliš velký pro paměť nebo neodpovídající operaci. Když aplikace hlásí neúspěšnou relaci, považujte to za signál, abyste nejprve zjednodušili model a parametry. + Odstraňte a znovu stáhněte model, pokud selže ihned po stažení nebo se chová, jako by byl soubor poškozen. + Než budete obviňovat importovaný vlastní model, vyzkoušejte vestavěný model ke stažení, protože importované modely mohou mít nekompatibilní vstupy. + Pokud potřebujete nahlásit selhání nebo chybu relace, použijte protokoly aplikací po zopakování selhání. + Experimentujte v Shader Studio + Používejte efekty shaderu GPU pro pokročilé zpracování obrazu a vlastní vzhled + Se shadery pracujte opatrně + Shader Studio je výkonné, ale kód a parametry shaderu vyžadují malé, testovatelné změny. Zacházejte s tím jako s experimentálním nástrojem: udržujte pracovní základní linii, měňte jednu věc po druhé a než důvěřujte předvolbě, testujte s různými velikostmi obrázků. + Před přidáním parametrů začněte od známého pracovního shaderu nebo jednoduchého efektu. + Měňte vždy jeden parametr nebo blok kódu a zkontrolujte, zda náhled neobsahuje chyby. + Exportujte předvolby až po jejich otestování na několika různých velikostech obrázků. + Vytvořte umění SVG nebo ASCII + Generujte vektorové podklady nebo textové obrázky pro kreativní výstup + Vyberte typ výstupu kreativy + Nástroje SVG a ASCII slouží různým cílům: škálovatelná grafika nebo vykreslování ve stylu textu. Oba jsou kreativní konverze, takže náhled na konečnou velikost je důležitější než posuzování výsledku z malé miniatury. + Použijte SVG Maker, když by měl výsledek čistě škálovat nebo být znovu použit v pracovních postupech návrhu. + ASCII Art použijte, pokud chcete stylizovanou textovou reprezentaci obrázku. + Náhled v konečné velikosti, protože malé detaily mohou zmizet v generovaném výstupu. + Generování šumových textur + Vytvářejte procedurální textury pro pozadí, masky, překryvy nebo experimenty + Vylaďte procedurální výstup + Generování šumu vytváří nové obrázky z parametrů, takže malé změny mohou přinést velmi odlišné výsledky. Je to užitečné pro textury, masky, překryvy, zástupné symboly nebo testování komprese s podrobnými vzory. + Před doladěním jemných detailů zvolte typ šumu a výstupní velikost. + Upravte měřítko, intenzitu, barvy nebo semena, dokud vzor nebude odpovídat cílovému použití. + Uložte si užitečné výsledky a zapište si parametry, pokud budete potřebovat texturu později znovu vytvořit. + Značkovací projekty + Použijte soubory projektu, když je třeba, aby poznámky po zavření aplikace zůstaly upravitelné + Udržujte vrstvené úpravy znovu použitelné + Soubory značkového projektu jsou užitečné, když snímek obrazovky, diagram nebo obrázek instrukce mohou později vyžadovat změny. Exportované soubory PNG/JPEG jsou konečné obrázky, zatímco soubory projektu zachovávají upravitelné vrstvy a stav úprav pro budoucí úpravy. + Vrstvy značek použijte, pokud by poznámky, popisky nebo prvky rozvržení měly zůstat upravitelné. + Před exportem konečného obrazu uložte nebo znovu otevřete soubor projektu, pokud očekáváte další revize. + Normální obrázek exportujte pouze v případě, že jste připraveni sdílet sloučenou konečnou verzi. + Šifrovat soubory + Před odstraněním jakékoli zdrojové kopie chraňte soubory heslem a otestujte dešifrování + Se šifrovanými výstupy zacházejte opatrně + Šifrovací nástroje mohou chránit soubory pomocí šifrování založeného na heslech, ale nenahrazují zapamatování klíče nebo zálohování. Šifrování je užitečné pro přenos a ukládání, zatímco ZIP je lepší, když je hlavním cílem sdružování několika výstupů. + Nejprve zašifrujte kopii a ponechte si originál, dokud neotestujete, že dešifrování funguje s heslem, které jste si uložili. + Použijte správce hesel nebo jiné bezpečné místo pro klíč; aplikace za vás nemůže uhodnout ztracené heslo. + Sdílejte zašifrované soubory pouze s lidmi, kteří mají také heslo a vědí, který nástroj nebo metoda je má dešifrovat. + Uspořádejte nástroje + Nástroje seskupování, objednávání, vyhledávání a připínání, aby hlavní obrazovka odpovídala vašemu skutečnému pracovnímu postupu + Zrychlete hlavní obrazovku + Nastavení uspořádání nástrojů se vyplatí vyladit, jakmile víte, které nástroje používáte nejčastěji. Seskupování pomáhá prozkoumávání, vlastní pořadí pomáhá svalové paměti a oblíbené položky udržují každodenní nástroje dostupné, i když se celý seznam zvětší. + Používejte seskupený režim při učení aplikace nebo když dáváte přednost nástrojům uspořádaným podle typu úkolu. + Přepněte na vlastní pořadí, když již znáte své časté nástroje a chcete méně posouvání. + Používejte společně nastavení vyhledávání a oblíbené položky pro vzácné nástroje, které si pamatujete podle názvu, ale nechcete, aby byly neustále viditelné. + Manipulujte s velkými soubory + Vyhněte se pomalému exportu, tlaku na paměť a neočekávaně velkému výstupu + Omezte práci před exportem + Velmi velké obrázky, dlouhé dávky a vysoce kvalitní formáty mohou vyžadovat více paměti a času. Nejrychlejší opravou je obvykle snížení množství práce před nejtěžší operací a nečekání na dokončení stejného předimenzovaného vstupu. + Změňte velikost velkých obrázků před použitím silných filtrů, OCR nebo převodu PDF. + Nejprve použijte menší dávku k potvrzení nastavení a poté zpracujte zbytek se stejnou předvolbou. + Nižší kvalita nebo změna formátu, když je výstupní soubor větší, než se očekávalo. + Cache a náhledy + Pochopte generované náhledy, čištění mezipaměti a využití úložiště po náročných relacích + Udržujte úložiště a rychlost v rovnováze + Generování náhledu a mezipaměť mohou zrychlit opakované procházení, ale náročné relace úprav mohou zanechat dočasná data. Nastavení mezipaměti pomáhá, když je aplikace pomalá, úložiště je málo nebo náhledy vypadají po mnoha experimentech zastaralé. + Ponechat náhledy zapnuté při procházení mnoha obrázků je důležitější než minimalizace dočasného úložiště. + Po velkých dávkách, neúspěšných experimentech nebo dlouhých relacích s mnoha soubory ve vysokém rozlišení vymažte mezipaměť. + Pokud dáváte přednost čištění úložiště před udržováním teplých náhledů mezi spuštěními, použijte automatické vymazání mezipaměti. + Upravte chování obrazovky + Pro soustředěnou práci použijte možnosti celé obrazovky, zabezpečeného režimu, jasu a systémové lišty + Přizpůsobte rozhraní situaci + Nastavení obrazovky vám pomůže, když upravujete na šířku, prezentujete soukromé obrázky nebo potřebujete konzistentní jas. Pro běžné použití nejsou vyžadovány, ale řeší nepříjemné situace jako QR kontrolu, soukromé náhledy nebo stísněné úpravy krajiny. + Když je úprava prostoru důležitější než navigační chrome, použijte nastavení celé obrazovky nebo systémového panelu. + Použijte zabezpečený režim, když se na snímcích obrazovky nebo náhledech aplikací nemají zobrazovat soukromé obrázky. + Použijte vynucení jasu pro nástroje, kde je obtížné zkontrolovat tmavé obrázky nebo QR kódy. + Udržujte transparentnost + Zabraňte tomu, aby se průhledné pozadí změnilo na bílé, černé nebo zploštělé + Použijte alfa-aware výstup + Průhlednost přetrvává pouze tehdy, když vybraný nástroj a výstupní formát podporují alfa. Pokud se exportované pozadí změní na bílé nebo černé, problémem je obvykle výstupní formát, nastavení výplně/pozadí nebo cílová aplikace, která obrázek sloučila. + Při exportu obrázků, nálepek, log nebo překryvných vrstev s odstraněným pozadím zvolte PNG nebo WebP. + Vyhněte se JPEG pro transparentní výstup, protože neukládá alfa kanál. + Zkontrolujte nastavení související s barvou pozadí pro formáty alfa, pokud náhled zobrazuje vyplněné pozadí. + Opravte problémy se sdílením a importem + Pokud se soubory nezobrazí nebo neotevírají správně, použijte nastavení výběru a podporované formáty + Získejte soubor do aplikace + Problémy s importem jsou často způsobeny oprávněními poskytovatele, nepodporovanými formáty nebo chováním výběru. Soubory sdílené z cloudových aplikací a messengerů mohou být dočasné, takže výběr trvalého zdroje může být spolehlivější pro delší úpravy. + Zkuste soubor otevřít pomocí nástroje pro výběr systému namísto zástupce posledních souborů. + Pokud formát není podporován jedním nástrojem, nejprve jej převeďte nebo otevřete specifičtější nástroj. + Zkontrolujte nastavení nástroje pro výběr obrázků a spouštěče, pokud se toky sdílení nebo přímé otevírání nástroje chovají jinak, než se očekávalo. + Potvrzení opuštění + Vyhněte se ztrátě neuložených úprav, když náhodně dojde ke gestům, zpětnému stisknutí nebo přepnutí nástroje + Chraňte nedokončenou práci + Potvrzení ukončení nástroje je užitečné, když používáte navigaci gesty, upravujete velké soubory nebo často přepínáte mezi aplikacemi. Přidává malou pauzu před opuštěním nástroje, aby se nedokončená nastavení a náhledy náhodně neztratily. + Povolte potvrzení, pokud náhodná gesta zpět někdy zavřela nástroj před exportem. + Pokud většinou provádíte rychlé převody v jednom kroku a dáváte přednost rychlejší navigaci, ponechte ji deaktivovanou. + Použijte jej společně s předvolbami pro delší pracovní postupy, kde by přestavba nastavení byla otravná. + Při hlášení problémů používejte protokoly + Před otevřením problému nebo kontaktováním vývojáře shromážděte užitečné podrobnosti + Nahlásit problémy s kontextem + Jasná sestava s kroky, verzí aplikace, typem vstupu a protokoly je mnohem snazší diagnostikovat. Protokoly jsou nejužitečnější hned po reprodukci problému, protože udržují okolní kontext čerstvý. + Poznamenejte si název nástroje, přesnou akci a to, zda se to stane s jedním souborem nebo s každým souborem. + Po zopakování problému otevřete protokoly aplikací a pokud je to možné, sdílejte příslušný výstup protokolu. + Snímky obrazovky nebo ukázkové soubory přikládejte pouze v případě, že neobsahují soukromé informace. + Zpracování na pozadí bylo zastaveno + Android nenechal včas spustit upozornění na zpracování na pozadí. Toto je omezení časování systému, které nelze spolehlivě opravit. Restartujte aplikaci a zkuste to znovu; může pomoci ponechání aplikace otevřené a povolení upozornění nebo práce na pozadí. + Přeskočit výběr souboru + Otevírejte nástroje rychleji, když vstup obvykle pochází ze sdílení, schránky nebo předchozí obrazovky + Urychlete opakované spouštění nástroje + Přeskočit výběr souboru změní první krok podporovaných nástrojů. Je to užitečné, když obvykle zadáváte nástroj s již vybraným obrázkem nebo z akcí sdílení Androidu, ale může to být matoucí, pokud očekáváte, že každý nástroj okamžitě požádá o soubor. + Povolte jej pouze v případě, že váš obvyklý postup poskytuje zdrojový obrázek před otevřením nástroje. + Při učení aplikace ji nechte deaktivovanou, protože explicitní výběr usnadňuje pochopení každého toku nástroje. + Pokud se nástroj otevře bez zjevného vstupu, deaktivujte toto nastavení nebo začněte ze Sdílet/Otevřít pomocí, aby Android předal soubor jako první. + Nejprve otevřete editor + Přeskočit náhled, když má sdílený obrázek přejít rovnou do úprav + Jako první zastávku vyberte náhled nebo úpravu + Otevřít úpravy místo náhledu je pro uživatele, kteří již vědí, že chtějí obrázek upravit. Náhled je bezpečnější, když kontrolujete soubory z chatu nebo cloudového úložiště; přímá úprava je rychlejší pro snímky obrazovky, rychlé oříznutí, anotace a jednorázové opravy. + Povolte přímé úpravy, pokud většina sdílených obrázků okamžitě potřebuje změny oříznout, označit, filtry nebo exportovat. + Když často kontrolujete metadata, rozměry, průhlednost nebo kvalitu obrazu, než se rozhodnete, ponechte nejprve náhled. + Použijte to společně s oblíbenými položkami, aby se sdílené obrázky přiblížily nástrojům, které skutečně používáte. + Přednastavené zadávání textu + Místo opakovaného nastavování ovládacích prvků zadejte přesné přednastavené hodnoty + Když jsou posuvníky pomalé, použijte přesné hodnoty + Přednastavené zadávání textu pomáhá, když potřebujete přesná čísla pro opakovanou práci: velikosti sociálních obrázků, okraje dokumentů, cíle komprese, šířky čar nebo jiné hodnoty, které je nepříjemné dosáhnout pomocí gest. Je také užitečné na malých obrazovkách, kde je pohyb jemného posuvníku těžší. + Povolte zadávání textu, pokud často opakovaně používáte přesné velikosti, procenta, hodnoty kvality nebo parametry nástroje. + Po prvním zadání hodnotu uložte jako předvolbu a poté ji znovu použijte namísto přestavby stejného nastavení. + Ponechejte si ovládání gesty pro hrubé vizuální ladění a zadané hodnoty pro přísné požadavky na výstup. + Uložit blízko originálu + Pokud na kontextu složky záleží, umístěte vygenerované soubory vedle zdrojových obrázků + Uchovávejte výstupy s jejich zdroji + Uložit do původní složky je užitečné pro složky fotoaparátu, složky projektů, skeny dokumentů a dávky klientů, kde by upravená kopie měla zůstat vedle zdroje. Může být méně přehledný než vyhrazená výstupní složka, takže ji zkombinujte s jasnými příponami souborů nebo vzory. + Povolte ji, když každá zdrojová složka již má smysl a výstupy by tam měly zůstat seskupené. + Používejte přípony souborů, předpony, časová razítka nebo vzory, takže upravené kopie lze snadno oddělit od originálů. + Pokud chcete všechny vygenerované soubory shromáždit v jedné exportní složce, zakažte jej pro smíšené dávky. + Přeskočte větší výstup + Neukládejte konverze, které se neočekávaně stanou těžšími než zdroj + Chraňte šarže před překvapeními špatné velikosti + Některé převody zvětšují soubory, zejména snímky obrazovky PNG, již zkomprimované fotografie, vysoce kvalitní WebP nebo obrázky se zachovanými metadaty. Allow Skip If Larger zabrání těmto výstupům nahradit menší zdroj v pracovních postupech, kde je hlavním cílem zmenšení velikosti. + Povolte ji, pokud má export komprimovat, změnit velikost nebo připravit soubory na limity nahrávání. + Kontrola přeskočených souborů po dávce; mohou být již optimalizovány nebo mohou potřebovat jiný formát. + Deaktivujte ji, pokud na kvalitě, průhlednosti nebo kompatibilitě formátu záleží více než na konečné velikosti souboru. + Schránka a odkazy + Ovládejte automatický vstup do schránky a náhledy odkazů pro rychlejší textové nástroje + Udělejte automatický vstup předvídatelným + Nastavení schránky a odkazu jsou vhodná pro pracovní postupy QR, Base64, URL a text, ale automatické zadávání by mělo zůstat záměrné. Pokud se zdá, že aplikace vyplňuje pole sama, zkontrolujte chování při vkládání do schránky; pokud jsou karty odkazů hlučné nebo pomalé, upravte chování náhledu odkazu. + Povolte automatické vkládání do schránky, když často kopírujete text, a okamžitě otevřete odpovídající nástroj. + Zakažte automatické vkládání, pokud se obsah soukromé schránky objeví v nástrojích tam, kde jste jej neočekávali. + Vypněte náhledy odkazů, když je načítání náhledu pomalé, rušivé nebo zbytečné pro váš pracovní postup. + Kompaktní ovládání + Když je na obrazovce málo místa, použijte hustší voliče a rychlé umístění nastavení + Umístěte více ovládacích prvků na malé obrazovky + Kompaktní voliče a rychlé umístění nastavení pomáhají na malých telefonech, úpravám na šířku a nástrojům s mnoha parametry. Kompromisem je, že ovládací prvky se mohou zdát hustší, takže je to nejlepší poté, co již rozpoznáte běžné možnosti. + Povolte kompaktní selektory, když se dialogy nebo seznamy voleb zdají být příliš vysoké pro obrazovku. + Upravte stranu rychlého nastavení, pokud jsou ovládací prvky nástroje snadněji dostupné z jedné strany displeje. + Vraťte se k prostornějším ovládacím prvkům, pokud se aplikaci stále učíte nebo často vynecháváte husté možnosti. + Načtěte obrázky z webu + Vložte adresu URL stránky nebo obrázku, zobrazte náhled analyzovaných obrázků a poté uložte nebo sdílejte vybrané výsledky + Webové obrázky používejte záměrně + Načítání webového obrázku analyzuje zdroje obrázků z poskytnuté adresy URL, zobrazí náhled prvního dostupného obrázku a umožní vám uložit nebo sdílet vybrané analyzované obrázky. Některé weby používají dočasné, chráněné odkazy nebo odkazy vygenerované skripty, takže stránka, která funguje v prohlížeči, stále nemusí vracet žádné použitelné obrázky. + Vložte přímou adresu URL obrázku nebo adresu URL stránky a počkejte, až se zobrazí seznam analyzovaných obrázků. + Ovládací prvky rámečku nebo výběru použijte, pokud stránka obsahuje několik obrázků a potřebujete pouze některé z nich. + Pokud se nic nenačte, zkuste nejprve přímý odkaz na obrázek nebo si jej stáhněte přes prohlížeč; web může blokovat analýzu nebo používat soukromé odkazy. + Vyberte typ změny velikosti + Než vnutíte obrázek do nových dimenzí, porozumějte explicitním, flexibilním, oříznutým a přizpůsobeným + Ovládejte tvar, plátno a poměr stran + Typ změny velikosti rozhoduje o tom, co se stane, když požadovaná šířka a výška neodpovídají původnímu poměru stran. Je to rozdíl mezi roztažením pixelů, zachováním proporcí, oříznutím extra oblasti nebo přidáním pozadí na plátno. + Explicitní použijte pouze v případě, že je zkreslení přijatelné nebo když zdroj již má stejný poměr stran jako cíl. + Použijte Flexibilní, chcete-li zachovat proporce změnou velikosti z důležité strany, zejména u fotografií a smíšených dávek. + Použijte Oříznout pro přesné rámečky bez okrajů nebo Přizpůsobit, když celý obraz musí zůstat viditelný na pevném plátně. + Vyberte režim měřítka obrázku + Vyberte filtr pro převzorkování, který řídí ostrost, hladkost, rychlost a okrajové artefakty + Změna velikosti bez náhodné měkkosti + Režim měřítka obrazu řídí způsob výpočtu nových pixelů během změny velikosti. Jednoduché režimy jsou rychlé a předvídatelné, zatímco pokročilé filtry dokážou lépe zachovat detaily, ale mohou zostřit okraje, vytvořit haló nebo trvat déle na velkých snímcích. + Použijte Basic nebo Bilinear pro rychlou každodenní změnu velikosti, když výsledek potřebuje být pouze menší a čistý. + Pokud záleží na jemných detailech, textu nebo vysoce kvalitním zmenšení, použijte režimy Lanczos, Robidoux, Spline nebo EWA. + Použijte Nearest pro pixel art, ikony a grafiku s pevnými okraji, kde by rozmazané pixely zničily vzhled. + Měřítko barevného prostoru + Rozhodněte, jak se barvy prolnou, zatímco se obrazové body během změny velikosti převzorkují + Udržujte přechody a okraje přirozené + Měřítko barevného prostoru mění matematické údaje použité při změně velikosti. Většina obrázků je s výchozím nastavením v pořádku, ale lineární nebo percepční prostory mohou způsobit, že přechody, tmavé okraje a syté barvy budou po silné změně měřítka čistší. + Ponechejte výchozí, když na rychlosti a kompatibilitě záleží více než na malých barevných rozdílech. + Vyzkoušejte lineární nebo percepční režimy, když tmavé obrysy, snímky obrazovky uživatelského rozhraní, přechody nebo barevné pruhy po změně velikosti vypadají špatně. + Porovnejte před a po na skutečné cílové obrazovce, protože změny barevného prostoru mohou být jemné a závislé na obsahu. + Omezit režimy změny velikosti + Zjistěte, kdy je třeba přeskočit, překódovat nebo zmenšit nadlimitní obrázky, aby se vešly + Vyberte, co se stane na limitu + Limit Resize není pouze nástroj pro menší obrázky. Jeho režim rozhoduje o tom, co aplikace udělá, když je zdroj již ve vašich limitech nebo když pravidlo poruší pouze jedna strana, což je důležité pro smíšené složky a přípravu nahrávání. + Přeskočit použijte, pokud by snímky v rámci limitů měly zůstat nedotčené a neměly by se znovu exportovat. + Použijte Recode, když jsou rozměry přijatelné, ale stále potřebujete nový formát, chování metadat, kvalitu nebo název souboru. + Použijte Zoom, když by se obrázky, které porušují limity, měly proporcionálně zmenšit, dokud se nevejdou do povoleného rámečku. + Cíle na poměr stran + Vyhněte se roztaženým plochám, oříznutým okrajům a neočekávaným okrajům v přísných výstupních velikostech + Před změnou velikosti přizpůsobte tvar + Šířka a výška jsou pouze polovinou cíle změny velikosti. Poměr stran rozhoduje o tom, zda se obrázek vejde přirozeně, potřebuje oříznutí nebo kolem něj potřebuje plátno. První kontrola tvaru zabrání nejpřekvapivějším výsledkům změny velikosti. + Porovnejte zdrojový tvar s cílovým tvarem, než zvolíte Explicitní, Oříznout nebo Přizpůsobit. + Nejprve ořízněte, když objekt může ztratit další pozadí a cíl potřebuje přísný rámeček. + Pokud musí zůstat viditelná každá část původního obrázku, použijte možnost Přizpůsobit nebo Přizpůsobit. + Upscale nebo normální změna velikosti + Vědět, kdy přidávání pixelů potřebuje AI a kdy stačí jednoduché škálování + Nepleťte si velikost s detailem + Normální změna velikosti mění rozměry interpolací pixelů; nemůže vymyslet skutečný detail. Upscale umělá inteligence dokáže vytvořit ostřeji vypadající detaily, ale je pomalejší a může způsobit halucinace textury, takže správná volba závisí na tom, zda potřebujete kompatibilitu, rychlost nebo vizuální obnovu. + Použijte normální změnu velikosti pro miniatury, limity nahrávání, snímky obrazovky a jednoduché změny rozměrů. + Použijte AI upscale, když malý nebo měkký obrázek potřebuje vypadat lépe ve větší velikosti. + Porovnejte obličeje, text a vzory po upscale AI, protože generované detaily mohou vypadat přesvědčivě, ale nesprávně. + Na pořadí filtrů záleží + Aplikujte čištění, barvu, ostrost a konečnou kompresi v záměrném pořadí + Sestavte čistší sadu filtrů + Filtry nejsou vždy zaměnitelné. Rozostření před doostřením, zvýšení kontrastu před OCR nebo změna barvy před kompresí mohou vytvořit velmi odlišný výstup ze stejných ovládacích prvků. + Nejprve ořízněte a otočte, aby pozdější filtry fungovaly pouze na pixelech, které zůstanou. + Před doostřením nebo stylizovanými efekty použijte expozici, vyvážení bílé a kontrast. + Konečnou změnu velikosti a kompresi udržujte blízko konce, aby podrobnosti v náhledu přežily export předvídatelně. + Opravte okraje pozadí + Po automatickém odstranění vyčistěte svatozáře, zbytky stínů, vlasy, dírky a jemné obrysy + Aby výřezy vypadaly záměrně + Automatické masky často vypadají dobře na první pohled, ale odhalují problémy na světlém, tmavém nebo vzorovaném pozadí. Začištění okrajů je rozdíl mezi rychlým výřezem a opakovaně použitelnou nálepkou, logem nebo obrázkem produktu. + Prohlédněte si výsledek na kontrastním pozadí, abyste odhalili svatozáře a chybějící detaily hran. + Před vymazáním okolních zbytků použijte obnovu na vlasy, rohy a průhledné předměty. + Exportujte malý test na konečnou barvu pozadí, když bude výřez umístěn do návrhu. + Čitelné vodoznaky + Zviditelněte značky na jasných, tmavých, zaneprázdněných a oříznutých obrázcích, aniž byste zničili fotografii + Chraňte snímek, aniž byste jej skryli + Vodoznak, který vypadá dobře na jednom obrázku, může na jiném zmizet. Velikost, krytí, kontrast, umístění a opakování by měly být zvoleny pro celou dávku, nejen pro první náhled. + Před exportem všech souborů otestujte značku na nejjasnějších a nejtmavších obrázcích v dávce. + Použijte nižší krytí pro velké opakované značky a silnější kontrast pro malé rohové značky. + Udržujte důležité obličeje, text a podrobnosti o produktu jasné, pokud značka nemá bránit opětovnému použití. + Rozdělte jeden obrázek na dlaždice + Vystřihněte jeden obrázek podle řádků, sloupců, vlastních procent, formátu a kvality + Připravte si mřížky a objednané kusy + Rozdělení obrazu vytváří více výstupů z jednoho zdrojového obrazu. Může se rozdělit podle počtu řádků a sloupců, upravit procenta řádků nebo sloupců a uložit kusy s vybraným výstupním formátem a kvalitou, což je užitečné pro mřížky, řezy stránek, panoramata a uspořádané sociální příspěvky. + Vyberte zdrojový obrázek a poté nastavte počet řádků a sloupců, které potřebujete. + Upravte procenta řádků nebo sloupců, pokud by všechny kusy neměly mít stejnou velikost. + Před uložením vyberte výstupní formát a kvalitu, aby každý vygenerovaný kus používal stejná pravidla exportu. + Vystřihněte proužky obrázků + Odstraňte svislé nebo vodorovné oblasti a sloučte zbývající části zpět k sobě + Odstraňte oblast bez ponechání mezery + Vyříznutí obrázku se liší od normálního oříznutí. Může odstranit vybraný svislý nebo vodorovný pruh a poté sloučit zbývající části obrazu dohromady. Inverzní režim místo toho zachová vybranou oblast a použití obou inverzních směrů zachová vybraný obdélník. + Použijte svislé řezání pro boční oblasti, sloupy nebo střední pásy; použijte horizontální řezání pro bannery, mezery nebo řady. + Povolte inverzní na ose, pokud chcete vybranou součást ponechat namísto jejího odstranění. + Náhled hran po oříznutí, protože sloučený obsah může vypadat náhle, když odstraněná oblast překročí důležité detaily. + Vyberte výstupní formát + Vyberte JPEG, PNG, WebP, AVIF, JXL nebo PDF podle obsahu a cíle + Nechte cíl rozhodnout o formátu + Nejlepší formát závisí na tom, co obrázek obsahuje a kde bude použit. Fotografie, snímky obrazovky, průhledné položky, dokumenty a animované soubory selžou různými způsoby, když jsou nuceny do nesprávného formátu. + Použijte JPEG pro širokou kompatibilitu fotografií, když není vyžadována průhlednost a přesné pixely. + Použijte PNG pro ostré snímky obrazovky, zachycení uživatelského rozhraní, průhlednou grafiku a bezztrátové úpravy. + Použijte WebP, AVIF nebo JXL, když záleží na kompaktním moderním výstupu a přijímající aplikace jej podporuje. + Extrahujte zvukové kryty + Uložte vložený obrázek alba nebo dostupné mediální snímky ze zvukových souborů jako obrázky PNG + Vytáhněte umělecká díla ze zvukových souborů + Audio Covers využívá metadata médií Android ke čtení vložených uměleckých děl ze zvukových souborů. Když vložená kresba není k dispozici, může se retriever vrátit zpět k mediálnímu rámu, pokud jej Android může poskytnout; pokud žádný neexistuje, nástroj oznámí, že není k dispozici žádný obrázek k extrahování. + Otevřete zvukové obaly a vyberte jeden nebo více zvukových souborů s očekávaným přebalem. + Před uložením nebo sdílením si extrahovaný obal prohlédněte, zejména u souborů z aplikací pro streamování nebo nahrávání. + Pokud není nalezen žádný obrázek, zvukový soubor pravděpodobně nemá vložený kryt nebo Android nemohl vystavit použitelný snímek. + Metadata data a místa + Rozhodněte se, co zachovat, když záleží na čase pořízení, ale data GPS nebo zařízení by neměla unikat + Oddělte užitečná metadata od soukromých metadat + Metadata nejsou všechna stejně riziková. Data zachycení mohou být užitečná pro archivy a třídění, zatímco GPS, sériová pole zařízení, softwarové značky nebo pole vlastníka mohou odhalit více, než bylo zamýšleno. + Uchovávejte značky data a času, když záleží na chronologickém pořadí alb, skenů nebo historie projektu. + Před veřejným sdílením nebo odesíláním snímků cizím lidem odstraňte GPS a štítky identifikující zařízení. + Exportujte vzorek a znovu zkontrolujte EXIF, když jsou požadavky na soukromí přísné. + Vyhněte se kolizím souborů + Chraňte dávkové výstupy, když mnoho souborů sdílí názvy jako image.jpg nebo screenshot.png + Udělejte každý název výstupu jedinečný + Duplicitní názvy souborů jsou běžné, když obrázky pocházejí z messengerů, snímků obrazovky, cloudových složek nebo více kamer. Dobrý vzor zabraňuje náhodné výměně a udržuje výstupy sledovatelné zpět k jejich zdrojům. + Pokud má být upravená kopie rozpoznatelná, uveďte původní název a příponu. + Při zpracování velkých smíšených dávek přidejte sekvenci, časové razítko, předvolbu nebo rozměry. + Vyhněte se přepisování pracovních postupů, dokud neověříte, že vzor pojmenování nemůže kolidovat. + Uložit nebo sdílet + Vyberte si mezi trvalým souborem a dočasným předáním do jiné aplikace + Exportujte se správným záměrem + Save and Share může vypadat podobně, ale řeší jiné problémy. Uložením se vytvoří soubor, který můžete později najít; sdílení odešle vygenerovaný výsledek přes Android do jiné aplikace a nemusí zanechat trvalou místní kopii. + Použijte Uložit, když výstup musí zůstat ve známé složce, musí být zálohován nebo znovu použit později. + Použijte Sdílet pro rychlé zprávy, přílohy e-mailů, příspěvky na sociální sítě nebo odeslání výsledku jinému editoru. + Nejprve uložte, když je přijímající aplikace nespolehlivá nebo když by bylo nepříjemné znovu vytvořit neúspěšné sdílení. + Velikost stránky a okraje PDF + Před vytvořením dokumentu zvolte velikost papíru, přizpůsobení a prostor pro dýchání + Udělejte stránky obrázků tisknutelné a čitelné + Obrázky se mohou stát nepohodlnými stránkami PDF, pokud jejich tvar neodpovídá zvolené velikosti papíru. Okraje, režim přizpůsobení a orientace stránky rozhodují o tom, zda bude obsah oříznutý, malý, roztažený nebo pohodlný ke čtení. + Když lze PDF vytisknout nebo odeslat do formuláře, použijte skutečnou velikost papíru. + Pro skenování a snímky obrazovky používejte okraje, aby text neseděl na okraji stránky. + Pokud mají zdrojové obrázky smíšenou orientaci, prohlédněte si stránky na výšku a na šířku společně. + Vyčistěte skeny + Vylepšete okraje papíru, stíny, kontrast, otočení a čitelnost před PDF nebo OCR + Před archivací připravte stránky + Skenování lze technicky zachytit, ale stále je těžké jej přečíst. Malé kroky čištění před exportem PDF jsou často důležitější než nastavení komprese, zejména pro účtenky, ručně psané poznámky a stránky se slabým osvětlením. + Opravte perspektivu a ořízněte okraje stolu, prsty, stíny a nepořádek na pozadí. + Opatrně zvyšujte kontrast, aby byl text jasnější, aniž byste zničili razítka, podpisy nebo značky tužkou. + OCR spusťte až poté, co je stránka svislá a čitelná, poté ověřte důležitá čísla ručně. + Komprimujte, ve stupních šedi nebo opravujte soubory PDF + Použijte operace PDF s jedním souborem pro světlejší, tisknutelné nebo předělané kopie dokumentů + Vyberte operaci vyčištění PDF + Nástroje PDF obsahují samostatné operace pro kompresi, převod ve stupních šedi a opravu. Komprese a stupně šedi fungují hlavně prostřednictvím vložených obrázků, zatímco oprava přestavuje strukturu PDF; žádný z nich by neměl být považován za zaručenou opravu pro každý dokument. + Komprimovat PDF použijte, pokud dokument obsahuje obrázky a velikost souboru je důležitá pro sdílení. + Stupně šedi použijte, když by se měl dokument snáze vytisknout nebo když nepotřebujete barevné obrázky. + Když se dokument neotevře nebo má poškozenou vnitřní strukturu, použijte Opravit PDF na kopii, poté ověřte přestavěný soubor. + Extrahujte obrázky z PDF + Obnovte vložené obrázky PDF a zabalte extrahované výsledky do archivu + Uložte zdrojové obrázky z dokumentů + Extrahovat obrazy skenuje PDF pro vložené obrazové objekty, vynechává duplikáty, kde je to možné, a ukládá obnovené obrazy do vygenerovaného archivu ZIP. Extrahuje pouze obrázky, které jsou skutečně vložené do PDF, nikoli text, vektorové kresby nebo každou viditelnou stránku jako snímek obrazovky. + Použijte Extrahovat obrázky, když potřebujete obrázky uložené v PDF, jako jsou fotografie z katalogu nebo naskenované položky. + Pokud potřebujete celé stránky, včetně textu a rozvržení, použijte místo toho PDF do obrázků nebo extrakci stránek. + Pokud nástroj nehlásí žádné vložené obrázky, stránka může obsahovat textový/vektorový obsah nebo obrázky nemusí být uloženy jako extrahovatelné objekty. + Metadata, zploštění a tiskový výstup + Záměrně upravujte vlastnosti dokumentu, rastrujte stránky nebo připravujte vlastní rozvržení tisku + Vyberte akci konečného dokumentu + Metadata PDF, zploštění a příprava tisku řeší různé problémy v konečné fázi. Metadata řídí vlastnosti dokumentu, Flatten PDF rastruje stránky na méně upravitelný výstup a Print PDF připraví nové rozvržení s velikostí stránky, orientací, okrajem a ovládacími prvky stránek na list. + Metadata použijte, když záleží na autorovi, názvu, klíčových slovech, producentovi nebo na vyčištění soukromí. + Použijte Sloučit PDF, když by mělo být obtížnější upravit viditelný obsah stránky jako samostatné objekty PDF. + Použijte Print PDF, když potřebujete vlastní velikost stránky, orientaci, okraje nebo více stránek na list. + Připravte snímky pro OCR + Než požádáte OCR o čtení textu, použijte oříznutí, otočení, kontrast, odstranění šumu a měřítko + Dejte OCR čistší pixely + Chyby OCR často pocházejí spíše ze vstupního obrazu než z nástroje OCR. Pokřivené stránky, nízký kontrast, rozostření, stíny, drobný text a smíšená pozadí snižují kvalitu rozpoznávání. + Ořízněte textovou oblast a otočte obrázek tak, aby čáry byly před rozpoznáním vodorovné. + Zvyšte kontrast nebo převeďte na čistší černobílou pouze tehdy, když to usnadní čtení písmen. + Mírně změňte velikost drobného textu směrem nahoru, ale vyhněte se agresivnímu doostřování, které vytváří falešné okraje. + Zkontrolujte obsah QR + Než důvěřujete kódu, zkontrolujte odkazy, přihlašovací údaje Wi-Fi, kontakty a akce + Zacházejte s dekódovaným textem jako s nedůvěryhodným vstupem + QR kód může skrýt adresu URL, kontaktní kartu, heslo Wi-Fi, událost kalendáře, telefonní číslo nebo prostý text. Viditelný štítek poblíž kódu se nemusí shodovat se skutečným nákladem, proto si dekódovaný obsah prohlédněte, než na něj zareagujete. + Před otevřením zkontrolujte celou dekódovanou adresu URL, zejména zkrácené nebo neznámé domény. + Buďte opatrní s kódy Wi-Fi, kontaktů, kalendáře, telefonu a SMS, protože mohou vyvolat citlivé akce. + Zkopírujte obsah nejprve, když jej potřebujete ověřit jinde, místo abyste jej okamžitě otevírali. + Generování QR kódů + Vytvářejte kódy z prostého textu, adres URL, Wi-Fi, kontaktů, e-mailu, telefonu, SMS a údajů o poloze + Sestavte si správné QR užitečné zatížení + QR obrazovka dokáže generovat kódy ze zadaného obsahu i skenovat stávající. Strukturované typy QR pomáhají kódovat data ve formátu, kterému ostatní aplikace rozumějí, jako jsou přihlašovací údaje Wi-Fi, kontaktní karty, pole pro e-maily, telefonní čísla, obsah SMS, adresy URL nebo zeměpisné souřadnice. + Vyberte si prostý text pro jednoduché poznámky, nebo použijte strukturovaný typ, kdy se má kód otevřít, jako Wi-Fi, kontakt, adresa URL, e-mail, telefon, SMS nebo údaje o poloze. + Před sdílením vygenerovaného kódu zkontrolujte všechna pole, protože viditelný náhled odráží pouze vámi zadané užitečné zatížení. + Otestujte uložený QR kód skenerem, až bude vytištěn, zveřejněn nebo použit jinými lidmi. + Vyberte režim kontrolního součtu + Vypočítejte hash ze souborů nebo textu, porovnejte jeden soubor nebo hromadně zkontrolujte mnoho souborů + Ověřujte obsah, ne vzhled + Nástroje kontrolního součtu mají samostatné karty pro hašování souborů, hašování textu, porovnání souboru se známým hašováním a porovnání dávek. Kontrolní součet prokazuje identitu bajtu za bajtem pro vybraný algoritmus; nedokazuje to, že dva obrázky pouze vypadají podobně. + Použijte Vypočítat pro soubory a Text Hash pro zadaná nebo vložená textová data. + Použijte Porovnat, když vám někdo dá očekávaný kontrolní součet pro jeden soubor. + Použijte dávkové porovnání, když mnoho souborů potřebuje hash nebo ověření v jednom průchodu, pak udržujte vybraný algoritmus konzistentní. + Soukromí schránky + Používejte funkce automatické schránky bez náhodného odhalení soukromých zkopírovaných dat + Ponechejte zkopírovaný obsah záměrně + Automatizace schránky je pohodlná, ale zkopírovaný text může obsahovat hesla, odkazy, adresy, tokeny nebo soukromé poznámky. Zadávejte vstup ze schránky jako funkci rychlosti, která by měla odpovídat vašim zvyklostem a očekáváním v oblasti soukromí. + Zakažte automatické použití schránky, pokud se v nástrojích neočekávaně objeví soukromý text. + Ukládání pouze do schránky používejte pouze tehdy, když záměrně chcete, aby se výsledek neukládal. + Po manipulaci s citlivým textem, daty QR nebo daty Base64 vymažte nebo vyměňte schránku. + Barevné formáty + Před vložením jinam se seznamte s hodnotami HEX, RGB, HSV, alfa a zkopírovanými barvami + Zkopírujte formát, který cíl očekává + Stejnou viditelnou barvu lze napsat několika způsoby. Nástroj pro návrh, pole CSS, hodnota barev pro Android nebo editor obrázků mohou očekávat jiné pořadí, zpracování alfa nebo barevný model. + HEX použijte při vkládání do polí webu, designu nebo tématu, která očekávají kompaktní barevné kódy. + Použijte RGB nebo HSV, když potřebujete ručně upravit kanály, jas, sytost nebo odstín. + Pokud na průhlednosti záleží, zaškrtněte alfa samostatně, protože některá místa ji ignorují nebo používají jiné pořadí. + Procházet knihovnu barev + Hledejte pojmenované barvy podle názvu nebo HEX a udržujte oblíbené v horní části + Najděte opakovaně použitelné pojmenované barvy + Knihovna barev načte pojmenovanou sbírku barev, seřadí ji podle názvu, filtruje podle názvu barvy nebo HEX hodnoty a ukládá oblíbené barvy, takže důležité položky zůstanou snáze dostupné. Je to užitečné, když potřebujete skutečnou pojmenovanou barvu namísto vzorkování z obrázku. + Hledejte podle názvu barvy, když znáte rodinu, jako je červená, modrá, břidlicová nebo mátová. + Hledejte podle HEX, pokud již máte číselnou barvu a chcete najít odpovídající nebo blízké pojmenované položky. + Označte časté barvy jako oblíbené, aby se při procházení posunuly před obecnou knihovnu. + Použijte mřížkové kolekce přechodů + Stáhněte si dostupné zdroje síťových přechodů a sdílejte vybrané obrázky + Použijte vzdálené prvky přechodu sítě + Síťové přechody mohou načíst sbírku vzdálených zdrojů, stáhnout chybějící obrázky přechodů, zobrazit průběh stahování a sdílet vybrané přechody. Dostupnost závisí na síťovém přístupu a vzdáleném úložišti zdrojů, takže prázdný seznam obvykle znamená, že zdroje ještě nebyly k dispozici. + Otevřete Síťové přechody z nástrojů pro přechody, když chcete hotové obrázky mřížkových přechodů. + Počkejte na načítání zdrojů nebo průběh stahování, než předpokládáte, že kolekce je prázdná. + Vyberte a sdílejte přechody, které potřebujete, nebo se vraťte do Gradient Maker, když chcete vytvořit vlastní přechod ručně. + Vygenerované rozlišení aktiv + Před generováním přechodů, šumu, tapet, umění podobného SVG nebo textur vyberte výstupní velikost + Vygenerujte ve velikosti, kterou potřebujete + Vygenerované snímky nemají originál z fotoaparátu, na který by se bylo možné vrátit. Pokud je výstup příliš malý, pozdější změna měřítka může změkčit vzory, posunout detaily nebo změnit způsob uspořádávání a komprese aktiv. + Nejprve nastavte rozměry pro konečný případ použití, jako je tapeta, ikona, pozadí, tisk nebo překrytí. + Větší velikosti použijte pro textury, které lze oříznout, přiblížit nebo znovu použít v několika rozvrženích. + Exportujte testovací verze před velkými výstupy, protože procedurální detaily mohou změnit chování komprese. + Export aktuálních tapet + Načtěte dostupné tapety Home, Lock a vestavěné tapety ze zařízení + Uložte nebo sdílejte nainstalované tapety + Tapety Export čte tapety, které Android vystavuje prostřednictvím systémových rozhraní API pro tapety. V závislosti na zařízení, verzi Androidu, oprávněních a variantě sestavení mohou být tapety Home, Lock nebo vestavěné tapety k dispozici samostatně nebo mohou chybět. + Otevřete Tapety Export pro načtení tapet, které systém umožňuje aplikaci číst. + Vyberte dostupné položky Plocha, Zámek nebo vestavěné tapety, které chcete uložit, zkopírovat nebo sdílet. + Pokud tapeta chybí nebo je funkce nedostupná, je to obvykle omezení systému, oprávnění nebo varianty sestavení, nikoli nastavení úprav. + Rohy animace plynu + Kopírovat barvu jako + HEX + jméno + Portrétní model rohože pro rychlé výřezy osob s měkčími vlasy a manipulací s okraji. + Rychlé vylepšení při slabém osvětlení pomocí odhadu křivky Zero-DCE++. + Restormer model obnovy obrazu pro snížení dešťových pruhů a artefaktů za vlhkého počasí. + Model nedeformování dokumentu, který předpovídá souřadnicovou mřížku a přemapuje zakřivené stránky na plošší sken. + Měřítko trvání pohybu + Ovládá rychlost animace aplikace: 0 deaktivuje pohyb, 1 je normální, vyšší hodnoty zpomalují animace + Zobrazit poslední nástroje + Zobrazení rychlého přístupu k naposledy použitým nástrojům na hlavní obrazovce + Statistiky použití + Prozkoumejte otevření aplikace, spuštění nástrojů a nejpoužívanější nástroje + Aplikace se otevře + Nástroj se otevře + Poslední nástroj + Úspěšné uložení + Série aktivit + Data uložena + Špičkový formát + Použité nástroje + %1$d se otevře • %2$s + Zatím žádné statistiky využití + Otevřete několik nástrojů a automaticky se zde zobrazí + Vaše statistiky využití jsou uloženy pouze lokálně ve vašem zařízení. ImageToolbox je používá pouze k zobrazení vaší osobní aktivity, oblíbených nástrojů a statistik uložených dat + Nástroje pro stránky dokumentů pdf + síť přechodů pozadí + pdf sloučit spojit spojit + pdf rozdělené samostatné stránky + pdf podpis podepsat kreslení + pdf odemknout heslo dešifrovat odstranit ochranu + pdf komprimovat snížit velikost optimalizovat + pdf černobílé černobílé odstíny šedé + pdf opravit opravit obnovit poškozený + pdf vlastnosti metadat exif název autora + pdf odstranit odstranit stránky + pdf okraje oříznutí + pdf sloučit anotace tvoří vrstvy + pdf extrahovat obrázky obrázky + pdf komprimovat archiv zip + tisková tiskárna pdf + prohlížeč náhledu pdf přečtený + obrázky do pdf převést dokument jpg jpeg png + pdf do obrázků stránky exportovat jpg png + pdf anotace komentáře odstranit čisté + Neutrální varianta + Chyba + Vržený stín + Výška zubů + Horizontální rozsah zubů + Vertikální rozsah zubů + Horní okraj + Pravý okraj + Dolní okraj + Levý okraj + Roztrhaný okraj + editor upravit fotografii obrázek retuš upravit + změnit velikost převést měřítko rozlišení rozměry šířka výška jpg jpeg png webp komprimovat + komprimovat velikost hmotnost bajtů mb kb snížit kvalitu optimalizovat + crop trim cut poměr stran + efekt filtru korekce barev upravit lut maska + kreslit štětec tužkou anotovat značení + šifra zašifrovat dešifrovat heslo tajný + odstraňovač pozadí vymazat odstranit výřez transparentní alfa + náhled prohlížeč galerie prohlédnout otevřít + steh sloučení kombinovat panorama dlouhý snímek obrazovky + url web ke stažení obrázek internetového odkazu + sběrač kapátko vzorek hex rgb barva + vzorník barev palety extrakt dominantní + exif metadata privacy remove strip clean gps location + porovnat rozdíl rozdílu před po + limit resize max min megapixels rozměry svorka + ocr text rozpoznat extrakt skenování + přechod barvy pozadí mřížky + překrytí textu razítka vodoznaku + GIF animace animované převést snímky + apng animace animovaný png převést snímky + zip archiv komprimovat rozbalit soubory + jxl jpeg xl převést obrazový formát jpg + svg vector trace convert xml + format convert jpg jpeg png webp heic avif jxl bmp + skener dokumentů skenování papíru perspektivy oříznutí + qr skenování čárových kódů + stacking overlay average medián astrofotografie + split dlaždice mřížka slice díly + color convert hex rgb hsl hsv cmyk lab + webp převést formát animovaného obrázku + generátor šumu náhodné zrno textury + koláž rozložení mřížky kombinovat + vrstvy značek anotovat překryvné úpravy + base64 kódování dekódování dat uri + kontrolní součet hash md5 sha sha1 sha256 ověřit + exif metadata upravit gps datum kamera + řezané dlaždice dělené mřížky + audio přebal alba hudební metadata + pozadí exportu tapety + ascii text art znaky + ai ml neuron enhance upscale segment + paleta materiálů knihovny barev s názvem barvy + studio efektu fragmentů shader glsl + pdf otočit orientaci stránek + pdf změnit uspořádání změnit pořadí stránek seřadit + pdf stránkování čísel stránek + pdf ocr text rozpoznat prohledávatelný extrakt + text loga vodoznaku ve formátu pdf + pdf chránit heslem šifrovat zámek + Resetovat statistiky využití + Nástroj se otevře, uloží se statistiky, pruh, uložená data a nejvyšší formát budou resetovány. Otevření aplikace zůstanou nezměněna. + Zvuk + Soubor + Export profilů + Uložit aktuální profil + Smazat exportní profil + Chcete smazat exportní profil \\"%1$s\\"? + Hranice kreslení + Ukažte tenký okraj kolem kreslicího a mazacího plátna + Zvlněný + Zaoblené prvky uživatelského rozhraní s měkkým zvlněným okrajem + Lopatka + Zářez + Zaoblené rohy vyřezávané dovnitř + Stupňovité rohy s dvojitým řezem + Zástupný symbol + Pomocí {filename} vložte původní název souboru bez přípony + Světlice + Základní částka + Množství prstenu + Množství paprsku + Šířka prstenu + Zkreslení perspektivy + Vzhled a dojem Java + Stříhat + X úhel + a úhel + Změnit velikost výstupu + Kapka vody + Vlnová délka + Fáze + Vysoký průsmyk + Barevná maska + Chrome + Rozpustit + Měkkost + Zpětná vazba + Rozostření objektivu + Nízký vstup + Vysoký vstup + Nízký výkon + Vysoký výkon + Světelné efekty + Výška nárazu + Měkkost nárazu + Vlnění + X amplituda + Y amplituda + X vlnová délka + Y vlnová délka + Adaptivní rozostření + Generování textur + Vytvářejte různé procedurální textury a ukládejte je jako obrázky + Typ textury + Zaujatost + Čas + Lesk + Disperze + Ukázky + Úhlový koeficient + Gradientový koeficient + Typ mřížky + Výkon na dálku + Měřítko Y + Fuzziness + Typ základu + Faktor turbulence + Měřítko + Iterace + Prsteny + Kartáčovaný kov + Žíraviny + Buněčný + Šachovnice + fBm + Mramor + Plazma + Deka + Dřevo + náhodný + Náměstí + Šestihranný + Osmiúhelníkový + Trojúhelníkový + Ridged + Hluk VL + SC Hluk + Nedávné + Zatím žádné nedávno použité filtry + generátor textur kartáčovaný kov žíraviny celulární šachovnice mramor plazma deka dřevo cihla maskování buňka oblak prasklina tkanina olistění voštinový led láva mlhovina papír rez písek kouř kámen terén topografie vlnění vody + Cihlový + Maskovat + Buňka + Mrak + Crack + Tkanina + Listy + Voštinový + Led + Láva + Mlhovina + Papír + Rez + Písek + Kouř + Kámen + Terén + Topografie + Vodní zvlnění + Pokročilé dřevo + Šířka malty + Nepravidelnost + Úkos + Drsnost + První práh + Druhý práh + Třetí práh + Měkkost okrajů + Šířka okraje + Krytí + Detail + Hloubka + Větvení + Vodorovné závity + Svislé závity + Chmýří + Žíly + Osvětlení + Šířka trhliny + Mráz + Tok + Kůra + Hustota oblačnosti + hvězdy + Hustota vláken + Pevnost vlákna + Skvrny + Koroze + Pitting + Vločky + Frekvence duny + Úhel větru + Vlnky + Pramínky + Žilní měřítko + Hladina vody + Horská úroveň + Eroze + Úroveň sněhu + Počet řádků + Tloušťka čáry + Stínování + Barva pórů + Barva malty + Tmavá cihlová barva + Barva cihlová + Barva zvýraznění + Tmavá barva + Lesní barva + Barva země + Písková barva + Barva pozadí + Barva buňky + Barva okraje + Barva nebe + Barva stínu + Světlá barva + Barva povrchu + Barva variace + Barva praskliny + Barva osnovy + Barva útku + Tmavá barva listů + Barva listů + Barva ohraničení + Medová barva + Sytá barva + Ledová barva + Mrazivá barva + Barva kůrky + Umyjte barvu + Barva září + Barva prostoru + Barva fialová + Modrá barva + Základní barva + Barva vlákna + Barva skvrny + Barva kovu + Barva tmavě rezavá + Barva rezavá + Oranžová barva + Barva kouře + Barva žíly + Nížinná barva + Vodová barva + Rocková barva + Barva sněhu + Nízká barva + Vysoká barva + Barva čáry + Mělká barva + Tráva + Špína + Kůže + Konkrétní + Asfalt + Mech + Oheň + Aurora + Olejová skvrna + Akvarel + Abstraktní tok + Opál + Damašková ocel + Blesk + Samet + Inkoustové mramorování + Holografická fólie + Bioluminiscence + Kosmický vír + Lávová lampa + Horizont událostí + Fraktální květ + Chromatický tunel + Eclipse Corona + Podivný atraktor + Ferrofluidní koruna + Supernova + Duhovka + Paví pero + Nautilus Shell + Prstencová planeta + Hustota čepele + Délka čepele + Vítr + Patchiness + Shluky + Vlhkost + Oblázky + Vrásky + Póry + Agregát + Trhliny + Dehet + Nosit + Skvrny + Vlákna + Frekvence plamene + Kouř + Intenzita + Stuhy + Kapely + Hra duhovými barvami + Tma + Kvete + Pigment + Hrany + Papír + Difúze + Symetrie + Ostrost + Barevná hra + Mléčnost + Vrstvy + Skládací + polština + Větve + Směr + Lesk + Záhyby + Opeření + Vyvážení inkoustu + Spektrum + Crinkles + Difrakce + Zbraně + Kroutit + Záře jádra + Blobs + Naklonění disku + Velikost horizontu + Šířka disku + Lensing + Okvětní lístky + Kučera + Filigrán + Fazety + Zakřivení + Velikost měsíce + Velikost korony + Paprsky + Diamantový prsten + Laloky + Hustota oběžné dráhy + Tloušťka + Hroty + Délka hrotu + Velikost těla + Kovový + Poloměr rázu + Šířka pláště + Vyhozený + Velikost zornice + Velikost duhovky + Barevná variace + Catchlight + Velikost očí + Hustota ostnů + Zatáčky + komory + Otevírací + Hřebeny + Pearlescence + Velikost planety + Naklonění prstence + Šířka prstenu + Atmosféra + Barva špíny + Tmavá barva trávy + Barva trávy + Barva hrotu + Tmavá barva země + Suchá barva + Oblázková barva + Barva kůže + Barva betonu + Barva dehtu + Asfaltová barva + Barva kamene + Barva prachu + Barva půdy + Tmavá mechová barva + Barva mechu + Červená barva + Barva jádra + Zelená barva + Azurová barva + Purpurová barva + Zlatá barva + Barva papíru + Pigmentová barva + Sekundární barva + První barva + Druhá barva + Barva růžová + Barva tmavá ocel + Barva oceli + Světlá ocelová barva + Oxidová barva + Halo barva + Barva šroubu + Sametová barva + Barva lesku + Modrá barva inkoustu + Červená barva inkoustu + Tmavá barva inkoustu + Stříbrná barva + Žlutá barva + Barva tkáně + Barva disku + Horká barva + Barva čočky + Vnější barva + Vnitřní barva + Barva korony + Studená barva + Teplá barva + Barva mraku + Barva plamene + Barva peří + Barva skořápky + Barva perleťová + Barva planety + Barva prstenu + Po uložení smažte původní soubory + Smazány budou pouze úspěšně zpracované zdrojové soubory + Původní soubory byly smazány: %1$d + Nepodařilo se odstranit původní soubory: %1$d + Původní soubory byly smazány: %1$d, neúspěšné: %2$d + Gesta s listem + Povolit přetahování rozbalených listů možností + Chroma subsampling + Bitová hloubka + Bezztrátový + %1$d-bit + Dávkové přejmenování + Přejmenujte více souborů pomocí vzorů názvů souborů + dávkové přejmenování souborů název souboru vzor sekvence přípona data + Vyberte soubory, které chcete přejmenovat + Vzor názvu souboru + Přejmenovat + Zdroj data + Datum pořízení EXIF + Datum změny souboru + Datum vytvoření souboru + Aktuální datum + Manuální datum + Manuální datum + Manuální čas + Vybrané datum není pro některé soubory dostupné. Použije se pro ně aktuální datum. + Zadejte vzor názvu souboru + Vzor vytváří neplatný nebo příliš dlouhý název souboru + Vzor vytváří duplicitní názvy souborů + Všechny názvy souborů již odpovídají vzoru + Tento vzor používá tokeny, které nejsou dostupné pro dávkové přejmenování + Jméno zůstane stejné + Soubory byly úspěšně přejmenovány + Některé soubory se nepodařilo přejmenovat + Povolení nebylo uděleno + Používá původní název souboru bez přípony, což vám pomáhá zachovat identifikaci zdroje nedotčenou. Podporuje také krájení pomocí \\o{start:end}, transliteraci pomocí \\o{t} a nahrazení \\o{s/old/new/}. + Automaticky se zvyšující počítadlo. Podporuje vlastní formátování: \\c{padding} (např. \\c{3} -&gt; 001), \\c{start:step} nebo \\c{start:step:padding}. + Název nadřazené složky, kde byl umístěn původní soubor. + Velikost původního souboru. Podporuje jednotky: \\z{b}, \\z{kb}, \\z{mb} nebo jen \\z pro formát čitelný člověkem. + Generuje náhodný Universally Unique Identifier (UUID), aby byla zajištěna jedinečnost názvu souboru. + Přejmenování souborů nelze vrátit zpět. Než budete pokračovat, ujistěte se, že jsou nová jména správná. + Potvrďte přejmenování + Nastavení boční nabídky + Menu měřítko + Menu alfa + Automatické vyvážení bílé + Výstřižek + Kontrola skrytých vodoznaků + Skladování + Limit mezipaměti + Jakmile mezipaměť překročí tuto velikost, automaticky ji vymažte + Interval čištění + Jak často kontrolovat, zda má být vymazána mezipaměť + Při spuštění aplikace + 1 den + 1 týden + 1 měsíc + Vymažte mezipaměť aplikace automaticky podle zvoleného limitu a intervalu + Pohyblivá plocha plodiny + Umožňuje přesunout celou oblast oříznutí tažením dovnitř + Sloučit GIF + Spojte více klipů GIF do jedné animace + Pořadí GIF klipů + Zvrátit + Hrát obráceně + Bumerang + Hrajte vpřed a poté vzad + Normalizujte velikost rámu + Přizpůsobte rámy tak, aby se vešly do největšího klipu; vypněte, aby se zachovalo jejich původní měřítko + Prodleva mezi klipy (ms) + Složka + Vyberte všechny obrázky ze složky a jejích podsložek + Duplicate Finder + Najděte přesné kopie a vizuálně podobné obrázky + duplicitní vyhledávač podobné obrázky přesné kopie fotografie čištění úložiště dHash SHA-256 + Vyberte obrázky a najděte duplikáty + Citlivost na podobnost + Přesné kopie + Podobné obrázky + Vyberte všechny přesné duplikáty + Vyberte vše kromě doporučených + Nebyly nalezeny žádné duplikáty + Zkuste přidat více obrázků nebo zvýšit citlivost na podobnost + Nelze přečíst %1$d obrázky + %1$s • %2$s lze získat zpět + %1$d skupin • %2$s lze získat zpět + %1$d vybráno • %2$s + Toto nelze vrátit zpět. Android vás může požádat o potvrzení smazání v dialogovém okně systému. + Nenalezeno + Začněte pohybem + Přesunout na konec + \ No newline at end of file diff --git a/core/resources/src/main/res/values-da/strings.xml b/core/resources/src/main/res/values-da/strings.xml new file mode 100644 index 0000000..5d048ea --- /dev/null +++ b/core/resources/src/main/res/values-da/strings.xml @@ -0,0 +1,3739 @@ + + + + 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 + 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 + 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. + 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. + 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 + Indholdsbevidst forvrængning + Ikke nok hukommelse til at fuldføre denne handling. Prøv at bruge et mindre billede, lukke andre apps eller genstarte appen. + Parallelarbejdere + Disse billeder blev ikke gemt, fordi de konverterede filer ville være større end originalerne. Tjek denne liste, før du sletter originalbilleder. + Filer sprunget over: %1$s + App logs + Se logfiler for appen for at se problemerne + Favoritter i grupperede værktøjer + Tilføjer favoritter som en fane, når værktøjer er grupperet efter type + Vis favorit som sidst + Flytter fanen favoritværktøjer til slutningen + Vandret afstand + Lodret mellemrum + Baglæns energi + Brug simpel gradientstørrelse energikort i stedet for fremadgående energialgoritme + Fjern det maskerede område + Sømme vil passere gennem det maskerede område og fjerner kun det valgte område i stedet for at beskytte det + VHS NTSC + Avancerede NTSC-indstillinger + Ekstra analog tuning for stærkere VHS og udsendelsesartefakter + Chroma blødning + Tape slid + Sporing + Luma udstrygning + Ringer + Sne + Brug felt + Filtertype + Input luma filter + Input chroma lowpass + Chromademodulation + Faseskift + Fase offset + Hovedskifte + Hovedskiftehøjde + Hovedskifteforskydning + Hovedskifteskift + Midtlinje position + Mid-line jitter + Sporing af støjhøjde + Sporingsbølge + Sporing af sne + Sporing af sneanisotropi + Sporingsstøj + Sporing af støjintensitet + Sammensat støj + Sammensat støjfrekvens + Sammensat støjintensitet + Komposit støj detalje + Ringefrekvens + Ringestyrke + Luma støj + Luma støjfrekvens + Luma støjintensitet + Luma støj detalje + Chroma støj + Chroma støj frekvens + Chroma støjintensitet + Chroma støj detalje + Sneintensitet + Sneanisotropi + Chroma fase støj + Chroma fase fejl + Chroma delay vandret + Chroma delay lodret + VHS-båndhastighed + VHS-chromatab + VHS skærper intensitet + VHS skærpe frekvens + VHS kantbølgeintensitet + VHS kantbølgehastighed + VHS kantbølgefrekvens + VHS kantbølgedetalje + Output chroma lowpass + Skala vandret + Skala lodret + Skalafaktor X + Skalafaktor Y + Registrerer almindelige billedvandmærker og maler dem med LaMa. Downloader detekterings- og indmalingsmodeller automatisk + Deuteranopia + Udvid billede + Objektsegmenteringsbaseret baggrundsfjerner ved hjælp af YOLO v11 Segmentation + Viser den aktuelle linjerotation i grader under tegning + Animerede emojis + Vis tilgængelige emoji som animationer + Vis linievinkel + Gem i original mappe + Gem nye filer ved siden af ​​den originale fil i stedet for den valgte mappe + Vandmærke pdf + Ikke nok hukommelse + Billedet er sandsynligvis for stort til at behandle på denne enhed, eller systemet er løbet tør for tilgængelig hukommelse. Prøv at reducere billedopløsningen, lukke andre apps, eller vælg en mindre fil. + Fejlfindingsmenu + Menu til at teste app-funktioner, dette er ikke beregnet til at blive vist i produktionsudgivelsen + Udbyder + PaddleOCR kræver yderligere ONNX-modeller på din enhed. Vil du downloade %1$s data? + Universel + koreansk + latin + østslavisk + Thai + græsk + engelsk + Kyrillisk + arabisk + Devanagari + Tamil + Telugu + Shader + Shader forudindstillet + Ingen skygge er valgt + Shader Studio + Opret, rediger, valider, importer og eksportér tilpassede fragmentskyggere + Gemte shaders + Åbn gemte shaders, dupliker, eksporter, del eller slet + Ny shader + Shader fil + .itshader JSON + %1$d parametre + Shader kilde + Skriv kun brødteksten af ​​void main(). Brug textureCoordinate til UV-koordinater og inputImageTexture som kildesampler. + Funktioner + Valgfri hjælpefunktioner, konstanter og strukturer indsat før void main(). Uniformer genereres fra params. + Tilføj uniformer her, når shaderen har brug for redigerbare værdier. + Nulstil skygge + Det aktuelle skyggeudkast vil blive ryddet + Ugyldig skygge + Ikke-understøttet shader-version %1$d. Understøttet version: %2$d. + Shader-navnet må ikke være tomt. + Shader-kilde må ikke være tom. + Shader-kilden indeholder ikke-understøttede tegn: %1$s. Brug kun ASCII GLSL-kilde. + Parameteren \\\"%1$s\\\" erklæres mere end én gang. + Parameternavne må ikke være tomme. + Parameteren \\\"%1$s\\\" bruger et reserveret GPUIimage-navn. + Parameteren \\\"%1$s\\\" skal være et gyldigt GLSL-id. + Parameteren \\\"%1$s\\\" har %2$s værditypen \\\"%3$s\\\", forventet \\\"%4$s\\\". + Parameteren \\\"%1$s\\\" kan ikke definere min eller max for bool-værdier. + Parameteren \\\"%1$s\\\" har min. større end maks. + Parameter \\\"%1$s\\\" standard er lavere end min. + Parameter \\\"%1$s\\\" standard er større end max. + Shader skal erklære \\\"uniform sampler2D %1$s;\\\". + Shader skal erklære \\\"varierende vec2 %1$s;\\\". + Shader skal definere \\\"void main()\\\". + Shader skal skrive en farve til gl_FragColor. + ShaderToy mainImage shaders understøttes ikke. Brug GPUImage\'s void main()-kontrakt. + Animeret ShaderToy-uniform \\\"iTime\\\" understøttes ikke. + Animeret ShaderToy-uniform \\\"iFrame\\\" understøttes ikke. + ShaderToy uniform \\\"iResolution\\\" understøttes ikke. + ShaderToy iChannel-teksturer understøttes ikke. + Eksterne teksturer understøttes ikke. + Eksterne teksturudvidelser understøttes ikke. + Libretro shader-parametre understøttes ikke. + Kun én inputtekstur understøttes. Fjern \\\"uniform %1$s %2$s;\\\". + Parameter \\\"%1$s\\\" skal erklæres som \\\"uniform %2$s %1$s;\\\". + Parameter \\\"%1$s\\\" er erklæret som \\\"uniform %2$s %1$s;\\\", forventet \\\"uniform %3$s %1$s;\\\". + Shader-filen er tom. + Shader-fil skal være et .itshader JSON-objekt med version, navn og shader-felter. + Shader-filen er ikke gyldig JSON eller matcher ikke .itshader-formatet. + Feltet \\\"%1$s\\\" er påkrævet. + %1$s er påkrævet. + %1$s \\\"%2$s\\\" understøttes ikke. Understøttede typer: %3$s. + %1$s er påkrævet for \\\"%2$s\\\" parametre. + %1$s skal være et endeligt tal. + %1$s skal være et heltal. + %1$s skal være sand eller falsk. + %1$s skal være en farvestreng, RGB/RGBA-array eller farveobjekt. + %1$s skal bruge formatet #RRGGBB eller #RRGGBBAA. + %1$s skal indeholde gyldige hexadecimale farvekanaler. + %1$s skal indeholde 3 eller 4 farvekanaler. + %1$s skal være mellem 0 og 255. + %1$s skal være en matrix med to tal eller et objekt med x og y. + %1$s skal indeholde præcis 2 tal. + Duplikere + Ryd altid EXIF + Fjern billed-EXIF-data ved lagring, selv når et værktøj anmoder om at beholde metadata + Hjælp og tips + %1$d selvstudier + Åbn værktøj + Lær de vigtigste værktøjer, eksportmuligheder, PDF-flows, farveværktøjer og rettelser til almindelige problemer + Kom godt i gang + Vælg værktøjer, importer filer, gem resultater, og genbrug indstillinger hurtigere + Billedredigering + Tilpas størrelse, beskær, filtrer, slet baggrunde, tegn og vandmærke billeder + Filer og metadata + Konverter formater, sammenlign output, beskyt privatlivets fred og kontroller filnavne + PDF og dokumenter + Opret PDF\'er, scan dokumenter, OCR-sider, og klargør filer til deling + Tekst, QR og data + Genkend tekst, scan koder, indkod Base64, tjek hashes og pakkefiler + Farveværktøjer + Vælg farver, byg paletter, udforsk farvebiblioteker og skab forløb + Kreative værktøjer + Generer SVG, ASCII-kunst, støjteksturer, shaders og eksperimentelle billeder + Fejlfinding + Løs problemer med tunge filer, gennemsigtighed, deling, import og rapportering + Vælg det rigtige værktøj + Brug kategorier, søgning, favoritter og del handlinger for at starte det rigtige sted + Start fra det bedste indgangspunkt + Image Toolbox har mange fokuserede værktøjer, og mange af dem overlapper ved første øjekast. Start fra den opgave, du vil afslutte, og vælg derefter det værktøj, der styrer den vigtigste del af resultatet: størrelse, format, metadata, tekst, PDF-sider, farver eller layout. + Brug søgning, når du kender handlingens navn, såsom resize, PDF, EXIF, OCR, QR eller color. + Åbn en kategori, når du kun kender opgavetypen, og fastgør derefter hyppige værktøjer som favoritter. + Når en anden app deler et billede i Image Toolbox, skal du vælge værktøjet fra delearkforløbet. + Importer, gem og del resultater + Forstå det sædvanlige flow fra at vælge input til eksport af den redigerede fil + Følg det almindelige redigeringsforløb + De fleste værktøjer følger den samme rytme: vælg input, juster indstillinger, forhåndsvisning og gem eller del. Den vigtige detalje er, at lagring opretter en outputfil, mens deling normalt er bedst til hurtig overdragelse til en anden app. + Vælg én fil til enkeltbilledværktøjer eller flere filer til batchværktøjer, når vælgeren tillader det. + Kontroller forhåndsvisnings- og outputkontrollerne, før du gemmer, især format-, kvalitet- og filnavnsindstillinger. + Brug del til en hurtig overdragelse, eller gem, når du har brug for en vedvarende kopi i den valgte mappe. + Arbejd med mange billeder på én gang + Batchværktøjer er bedst til gentagne ændring af størrelse, konvertering, komprimering og navngivningsopgaver + Forbered en forudsigelig batch + Batchbehandling er hurtigst, når hvert output skal følge de samme regler. Det er også det nemmeste sted at lave en stor fejl, så vælg navngivning, kvalitet, metadata og mappeadfærd, før du starter en lang eksport. + Vælg alle relaterede billeder sammen, og behold rækkefølgen, hvis output afhænger af rækkefølgen. + Indstil regler for ændring af størrelse, format, kvalitet, metadata og filnavn én gang, før du starter eksporten. + Kør først en lille testbatch, når kildefilerne er store, eller når målformatet er nyt for dig. + Hurtig enkelt redigering + Brug alt-i-en-editoren, når du har brug for flere simple ændringer på ét billede + Afslut ét billede uden værktøjshop + Enkel redigering er nyttig, når billedet har brug for en lille kæde af ændringer i stedet for én specialiseret handling. Det er normalt hurtigere til hurtig oprydning, forhåndsvisning og eksport af en endelig kopi uden at bygge en batch-workflow. + Åbn Single Edit for et billede, der har brug for almindelige justeringer, markeringer, beskæring, rotation eller eksportændringer. + Anvend redigeringer i den rækkefølge, der ændrer de færreste pixels først, såsom beskæring før filtre og filtre før endelig komprimering. + Brug i stedet et specialiseret værktøj, når du har brug for batchbehandling, nøjagtige filstørrelsesmål, PDF-output, OCR eller oprydning af metadata. + Brug forudindstillinger og indstillinger + Gem gentagne valg, og juster appen til din foretrukne arbejdsgang + Undgå at gentage den samme opsætning + Forudindstillinger og indstillinger er nyttige, når du ofte eksporterer den samme type fil. En god forudindstilling forvandler en gentagen manuel tjekliste til ét tryk, mens globale indstillinger definerer standardindstillingerne, som nye værktøjer starter med. + Opret forudindstillinger til almindelige outputstørrelser, formater, kvalitetsværdier eller navngivningsmønstre. + Gennemgå indstillinger for standardformat, kvalitet, gem mappe, filnavn, vælger og grænsefladeadfærd. + Sikkerhedskopier indstillinger, før du geninstallerer appen eller flytter til en anden enhed. + Indstil nyttige standardindstillinger + Vælg standardformat, kvalitet, skaleringstilstand, ændring af størrelsestype og farverum én gang + Få nye værktøjer til at starte tættere på dit mål + Standardværdier er ikke kun kosmetiske præferencer. De bestemmer, hvilket format, kvalitet, tilpasningsadfærd, skaleringstilstand og farverum, der vises først i mange værktøjer, hvilket hjælper med at undgå genvalg af de samme valg ved hver eksport. + Indstil standard billedformat og -kvalitet til at matche din sædvanlige destination, såsom JPEG til fotos eller PNG/WebP til alfa. + Indstil standardstørrelsestype og skaleringstilstand, hvis du ofte eksporterer til strenge dimensioner, sociale platforme eller dokumentsider. + Skift kun standardindstillinger for farverum, når dit arbejdsflow kræver ensartet farvehåndtering på tværs af skærme, print eller eksterne editorer. + Picker og launcher + Brug vælgerindstillinger, når appen åbner for mange dialogbokse eller starter det forkerte sted + Få åbningsfiler til at matche din vane + Vælger- og startindstillinger styrer, om Image Toolbox starter fra hovedskærmen, et værktøj eller et filvalgsforløb. Disse muligheder er især nyttige, når du normalt går ind fra Android-delearket, eller når du ønsker, at appen skal føles mere som en værktøjsstarter. + Brug billedvælgerindstillinger, hvis systemvælgeren skjuler filer, viser for mange seneste elementer eller håndterer skyfiler dårligt. + Aktiver kun spring over for værktøjer, hvor du normalt indtaster fra share eller allerede kender kildefilen. + Gennemgå opstartstilstand, hvis du ønsker, at Image Toolbox skal opføre sig mere som en værktøjsvælger end en normal startskærm. + Billedkilde + Vælg den vælgertilstand, der fungerer bedst med gallerier, filadministratorer og skyfiler + Vælg filer fra den rigtige kilde + Indstillinger for billedkilde påvirker, hvordan appen beder Android om billeder. Det bedste valg afhænger af, om dine filer findes i systemgalleriet, en filhåndteringsmappe, en cloud-udbyder eller en anden app, der deler midlertidigt indhold. + Brug standardvælgeren, når du vil have den mest systemintegrerede oplevelse for almindelige galleribilleder. + Skift vælgertilstand, hvis album, mapper, skyfiler eller ikke-standardformater ikke vises, hvor du forventer. + Hvis en delt fil forsvinder efter at have forladt kildeappen, skal du genåbne den gennem en vedvarende vælger eller gemme en lokal kopi først. + Favoritter og dele værktøjer + Hold hovedskærmen fokuseret, og skjul værktøjer, der ikke giver mening i delestrømme + Reducer støj fra værktøjsliste + Foretrukne og skjulte indstillinger er nyttige, når du kun bruger nogle få værktøjer hver dag. De holder også deleflowet praktisk ved at skjule værktøjer, der ikke kan bruge den type fil, en anden app sender. + Fastgør hyppige værktøjer, så de forbliver nemme at nå, selv når værktøjer er grupperet efter kategori. + Skjul værktøjer fra delestrømme, når de er irrelevante for filer, der kommer fra en anden app. + Brug indstillingerne for favoritbestilling, hvis du foretrækker favoritter i slutningen eller grupperet med relaterede værktøjer. + Sikkerhedskopier indstillinger + Flyt forudindstillinger, præferencer og app-adfærd til en anden installation sikkert + Hold din opsætning bærbar + Sikkerhedskopiering og gendannelse er mest nyttigt, når du har indstillet forudindstillinger, mapper, filnavnsregler, værktøjsrækkefølge og visuelle præferencer. En sikkerhedskopi giver dig mulighed for at eksperimentere med indstillinger eller geninstallere appen uden at genopbygge din arbejdsgang fra hukommelsen. + Opret en sikkerhedskopi efter ændring af forudindstillinger, filnavnadfærd, standardformater eller værktøjsarrangement. + Gem sikkerhedskopien et sted uden for app-mappen, hvis du planlægger at rydde data, geninstallere eller flytte enheder. + Gendan indstillinger, før du behandler vigtige batches, så standardindstillinger og gemmeadfærd matcher din gamle opsætning. + Ændre størrelse og konverter billeder + Skift størrelse, format, kvalitet og metadata for et eller flere billeder + Opret kopier i ændret størrelse + Tilpas størrelse og konverter er hovedværktøjet til forudsigelige dimensioner og lettere outputfiler. Brug det, når størrelsen på billedet, outputformatet og metadata-adfærden har betydning på samme tid. + Vælg et eller flere billeder, og vælg derefter, om dimensioner, skala eller filstørrelse betyder mest. + Vælg outputformat og kvalitet; brug PNG eller WebP, når gennemsigtigheden skal bevares. + Se et eksempel på resultatet, og gem eller del derefter de genererede kopier uden at ændre originalerne. + Tilpas størrelse efter filstørrelse + Målret en størrelsesgrænse, når et websted, en messenger eller en formular afviser tunge billeder + Overhold strenge uploadgrænser + Ændring af størrelse efter filstørrelse er bedre end at gætte kvalitetsværdier, når destinationen kun accepterer filer under en specifik grænse. Værktøjet kan justere kompression og dimensioner for at nå målet, mens det forsøger at holde resultatet brugbart. + Indtast målstørrelsen lidt under den reelle uploadgrænse for at give plads til metadata og udbyderændringer. + Foretrækker tabsgivende formater til fotos, når målet er lille; PNG kan forblive stor selv efter størrelsesændring. + Undersøg ansigter, tekst og kanter efter eksport, fordi aggressive størrelsesmål kan introducere synlige artefakter. + Begræns ændring af størrelse + Formindsk kun billeder, der overstiger dine maksimale bredde-, højde- eller filbegrænsninger + Undgå at ændre størrelsen på billeder, der allerede er fine + Limit Resize er nyttig til blandede mapper, hvor nogle billeder er enorme, og andre allerede er forberedte. I stedet for at tvinge hver fil gennem den samme størrelsesændring, holder den acceptable billeder tættere på deres originale kvalitet. + Indstil maksimale dimensioner eller grænser baseret på destinationen i stedet for at ændre størrelsen på hver fil blindt. + Aktiver stiladfærd overspring-hvis-større, når du vil undgå output, der bliver tungere end kilder. + Brug dette til batches fra forskellige kameraer, skærmbilleder eller downloads, hvor kildestørrelserne varierer meget. + Beskær, drej og ret + Indram det vigtige område, før du eksporterer eller bruger billedet i andre værktøjer + Ryd op i sammensætningen + Beskæring først gør senere filtre, OCR, PDF-sider og delingsresultater renere. + Åbn Beskær og vælg det billede, du vil justere. + Brug gratis beskæring eller et billedformat, når outputtet skal passe til et indlæg, dokument eller avatar. + Roter eller vend, før du gemmer, så senere værktøjer modtager det korrigerede billede. + Anvend filtre og justeringer + Byg en redigerbar filterstak og se et eksempel på resultatet før eksport + Juster billedets udseende + Filtre er nyttige til hurtige rettelser, stiliseret output og klargøring af billeder til OCR eller udskrivning. Små ændringer i kontrast, skarphed og lysstyrke kan have mere betydning end tunge effekter, når billedet indeholder tekst eller fine detaljer. + Tilføj filtre et efter et, og hold øje med forhåndsvisningen efter hver ændring. + Juster lysstyrke, kontrast, skarphed, sløring, farve eller masker afhængigt af opgaven. + Eksporter kun, når forhåndsvisningen matcher din målenhed, dokument eller sociale platform. + Forhåndsvisning først + Undersøg billeder, før du vælger et tungere redigerings-, konverterings- eller oprydningsværktøj + Tjek hvad du rent faktisk har modtaget + Billedforhåndsvisning er nyttig, når en fil kommer fra chat, cloud-lagring eller en anden app, og du ikke er sikker på, hvad den indeholder. Kontrol af dimensioner, gennemsigtighed, orientering og visuel kvalitet hjælper først med at vælge det korrekte opfølgningsværktøj. + Åbn Billedeksempel, når du skal inspicere flere billeder, før du beslutter dig for, hvad du skal gøre med dem. + Se efter rotationsproblemer, gennemsigtige områder, kompressionsartefakter og uventet store dimensioner. + Flyt først til Resize, Crop, Compare, EXIF ​​eller Background Remover, når du ved, hvad der skal rettes. + Fjern eller gendan en baggrund + Slet baggrunde automatisk eller finjuster kanter manuelt med gendannelsesværktøjer + Behold emnet, fjern resten + Baggrundsfjernelse fungerer bedst, når du kombinerer automatisk valg med omhyggelig manuel oprydning. Det endelige eksportformat betyder lige så meget som masken, fordi JPEG vil udjævne gennemsigtige områder, selvom forhåndsvisningen ser korrekt ud. + Start med automatisk fjernelse, hvis motivet er tydeligt adskilt fra baggrunden. + Zoom ind og skift mellem slet og gendannelse for at rette hår, hjørner, skygger og små detaljer. + Gem som PNG eller WebP, når du har brug for gennemsigtighed i den endelige fil. + Tegn, markér og vandmærke + Tilføj pile, noter, fremhævelser, signaturer eller gentagne vandmærkeoverlejringer + Tilføj synlig information + Markeringsværktøjer hjælper med at forklare et billede, mens vandmærkning hjælper med at mærke eller beskytte eksporterede filer. + Brug Draw, når du har brug for frihåndsnoter, figurer, fremhævning eller hurtige anmærkninger. + Brug vandmærke, når det samme tekst- eller billedmærke skal vises konsekvent på output. + Kontroller opacitet, position og skala før eksport, så mærket er synligt, men ikke distraherende. + Tegn standardindstillinger + Indstil linjebredde, farve, stitilstand, orienteringslås og forstørrelsesglas for hurtigere markering + Få tegningen til at føles forudsigelig + Tegneindstillinger er nyttige, når du ofte kommenterer skærmbilleder, markerer dokumenter eller signerer billeder. Standardindstillinger reducerer opsætningstiden, mens forstørrelsesglasset og orienteringslåsen gør præcise redigeringer nemmere på små skærme. + Indstil standardlinjebredden, og tegn farve til de værdier, du bruger mest til pile, fremhævelser eller signaturer. + Brug standardstitilstand, når du normalt tegner den samme slags streger, former eller markeringer. + Aktiver forstørrelsesglas eller orienteringslås, når fingerinput gør små detaljer svære at placere præcist. + Multi-billede layouts + Vælg det rigtige multibilledværktøj, før du arrangerer skærmbilleder, scanninger eller sammenligningsstrimler + Brug det rigtige layoutværktøj + Disse værktøjer lyder ens, men hver af dem er indstillet til en anden slags multi-billed-output. Ved at vælge den rigtige undgår du først at kæmpe mod layoutkontroller, der er designet til et andet resultat. + Brug Collage Maker til designet layout med flere billeder i én komposition. + Brug Image Stitching eller Stabling, når billederne skal blive en lang lodret eller vandret strimmel. + Brug billedopdeling, når et stort billede skal blive til flere mindre stykker. + Konverter billedformater + Opret JPEG, PNG, WebP, AVIF, JXL og andre kopier uden at redigere pixels manuelt + Vælg formatet til jobbet + Forskellige formater er bedre til billeder, skærmbilleder, gennemsigtighed, komprimering eller kompatibilitet. Konvertering er sikrest, når du forstår, hvad destinationsappen understøtter, og om kilden indeholder alfa-, animations- eller metadata, du vil beholde. + Brug JPEG til kompatible billeder, PNG til tabsfri billeder og alfa og WebP til kompakt deling. + Juster kvaliteten, når formatet understøtter det; lavere værdier reducerer størrelsen, men kan tilføje artefakter. + Behold originalerne, indtil du bekræfter, at de konverterede filer er åbne korrekt i målappen. + Tjek EXIF ​​og privatliv + Se, rediger eller fjern metadata, før du deler følsomme billeder + Styr skjulte billeddata + EXIF kan indeholde kameradata, datoer, placering, orientering og andre detaljer, der ikke er synlige på billedet. Det er værd at tjekke før offentlig deling, supportrapporter, markedspladslister eller ethvert billede, der kan afsløre et privat sted. + Åbn EXIF-værktøjer, før du deler billeder, der kan indeholde oplysninger om placering eller privat enhed. + Fjern følsomme felter, eller rediger forkerte tags såsom dato, orientering, forfatter eller kameradata. + Gem en ny kopi, og del denne kopi i stedet for originalen, når privatlivets fred er vigtigt. + Automatisk EXIF-oprydning + Fjern metadata som standard, mens du beslutter, om datoer skal bevares + Gør privatliv til standard + EXIF-indstillingsgruppen er nyttig, når du ønsker, at privatlivsoprydning skal ske automatisk i stedet for at huske det for hver eksport. Hold dato Tid er en separat beslutning, fordi datoer kan være nyttige til sortering, men følsomme til deling. + Aktiver altid rydde EXIF, når de fleste af dine eksporter er beregnet til offentlig eller semi-offentlig deling. + Aktiver kun Behold datoklokkeslæt, når det er vigtigere at bevare optagelsestiden end at fjerne hvert tidsstempel. + Brug manuel EXIF-redigering til engangsfiler, hvor du skal beholde nogle felter og fjerne andre. + Styr filnavne + Brug præfikser, suffikser, tidsstempler, forudindstillinger, kontrolsummer og sekvensnumre + Gør eksporter nemme at finde + Et godt filnavnmønster hjælper med at sortere batches og forhindrer utilsigtede overskrivninger. Filnavnindstillinger er især nyttige, når eksporter går tilbage til kildemappen, hvor lignende navne hurtigt kan blive forvirrende. + Indstil filnavnpræfiks, suffiks, tidsstempel, originalt navn, forudindstillede oplysninger eller sekvensindstillinger i Indstillinger. + Brug sekvensnumre til batches, hvor rækkefølgen har betydning, såsom sider, rammer eller collager. + Aktiver kun overskrivning, når du er sikker på at udskiftning af eksisterende filer er sikkert for den aktuelle mappe. + Overskriv filer + Erstat kun output med matchende navne, når din arbejdsgang er bevidst ødelæggende + Brug overskrivningstilstand forsigtigt + Overskriv filer ændrer, hvordan navnekonflikter håndteres. Det er nyttigt til gentagne eksporter til den samme mappe, men det kan også erstatte tidligere resultater, hvis dit filnavnsmønster producerer det samme navn igen. + Aktiver kun overskrivning for mapper, hvor udskiftning af ældre genererede filer forventes og kan gendannes. + Hold overskrivning deaktiveret, når du eksporterer originaler, klientfiler, engangsscanninger eller andet uden en sikkerhedskopi. + Kombiner overskrivning med et bevidst filnavnmønster, så kun de tilsigtede output erstattes. + Filnavne mønstre + Kombiner originale navne, præfikser, suffikser, tidsstempler, forudindstillinger, skaleringstilstand og kontrolsummer + Byg navne, der forklarer outputtet + Indstillinger for filnavnemønster kan gøre eksporterede filer til en nyttig historie om, hvad der skete med dem. Dette har betydning i batches, fordi mappevisningen kan være det eneste sted, hvor du kan skelne originalstørrelse, forudindstilling, format og behandlingsrækkefølge. + Brug originalt filnavn, præfiks, suffiks og tidsstempel, når du har brug for læsbare navne til manuel sortering. + Tilføj forudindstilling, skaleringstilstand, filstørrelse eller kontrolsum-oplysninger, når output skal dokumentere, hvordan de blev produceret. + Undgå tilfældige navne til arbejdsgange, hvor filer skal forblive parret med originaler eller siderækkefølge. + Vælg, hvor filer skal gemmes + Undgå at miste eksporter ved at indstille mapper, gemme originalmapper og engangslagringssteder + Hold output forudsigelige + Gem adfærd betyder mest, når du behandler mange filer eller deler filer fra cloud-udbydere. Mappeindstillinger bestemmer, om output samles ét kendt sted, vises ved siden af ​​originaler eller midlertidigt går et andet sted hen til en bestemt opgave. + Indstil en standard gemmemappe, hvis du altid vil have eksporter ét sted. + Brug gem-til-original-mappe til oprydningsarbejdsgange, hvor output skal forblive i nærheden af ​​kildefilen. + Brug engangslagringsplacering, når en enkelt opgave har brug for en anden destination uden at ændre din standard. + Engangs gemme mapper + Send de næste eksporter til en midlertidig mappe uden at ændre din normale destination + Hold specielle job adskilt + Engangslagringsplacering er nyttig til midlertidige projekter, klientmapper, oprydningssessioner eller eksporter, der ikke bør blandes med din almindelige Image Toolbox-mappe. Det giver en kortvarig destination uden at omskrive din standardopsætning. + Vælg en engangsmappe, før du starter en opgave, der hører til uden for din normale eksportplacering. + Brug det til korte projekter, delte mapper eller batches, hvor output skal forblive grupperet sammen. + Vend tilbage til standardmappen efter jobbet, så senere eksporter ikke uventet lander på den midlertidige placering. + Afbalancere kvalitet og filstørrelse + Forstå, hvornår du skal ændre dimensioner, format eller kvalitet i stedet for at gætte + Formindsk filer med vilje + Filstørrelsen afhænger af billeddimensioner, format, kvalitet, gennemsigtighed og indholdskompleksitet. Hvis en fil stadig er for tung, hjælper ændring af dimensioner normalt mere end at sænke kvaliteten gentagne gange. + Tilpas først dimensionerne, når billedet er større end destinationen kan vise. + Lavere kvalitet kun for formater med tab, og kontroller tekst, kanter, gradienter og ansigter efter eksport. + Skift format, når kvalitetsændringer ikke er nok, for eksempel WebP til deling eller PNG til skarpe gennemsigtige aktiver. + Arbejd med animerede formater + Brug GIF-, APNG-, WebP- og JXL-værktøjer, når en fil har rammer i stedet for én bitmap + Behandl ikke hvert billede som stillestående + Animerede formater har brug for værktøjer, der forstår rammer, varighed og kompatibilitet. + Brug GIF- eller APNG-værktøjer, når du har brug for handlinger på rammeniveau for disse formater. + Brug WebP-værktøjer, når du har brug for moderne komprimering eller animeret WebP-output. + Se forhåndsvisning af animationstiming før deling, fordi nogle platforme konverterer eller udjævner animerede billeder. + Sammenlign og se et eksempel på output + Undersøg ændringer, filstørrelse og visuelle forskelle, før du beholder en eksport + Bekræft før deling + Sammenligningsværktøjer hjælper med at fange kompressionsartefakter, forkerte farver eller uønskede redigeringer tidligt. + Åbn Sammenlign, når du vil inspicere to versioner side om side. + Zoom ind i kanter, tekst, gradienter og gennemsigtige områder, hvor problemer er nemmest at gå glip af. + Hvis outputtet er for tungt eller sløret, skal du vende tilbage til det forrige værktøj og justere kvalitet eller format. + Brug PDF-værktøjshubben + Flet, del, roter, fjern sider, komprimer, beskyt, OCR og vandmærke PDF-filer + Vælg en PDF-handling + PDF-hubben grupperer dokumenthandlinger bag ét indgangspunkt, så du kan vælge den nøjagtige handling. Start der, når filen allerede er en PDF, og brug Billeder til PDF eller Dokumentscanner, når din kilde stadig er et sæt billeder. + Åbn PDF-værktøjer, og vælg den handling, der matcher dit mål. + Vælg kilde-PDF eller billeder, og gennemgå derefter siderækkefølge, rotation, beskyttelse eller OCR-indstillinger. + Gem den genererede PDF og åbn den én gang for at bekræfte sideantal, rækkefølge og læsbarhed. + Gør billeder til en PDF + Kombiner scanninger, skærmbilleder, kvitteringer eller fotos i ét dokument, der kan deles + Byg et rent dokument + Billeder bliver bedre PDF-sider, når de beskæres, bestilles og tilpasses ensartet. Behandl billedlisten som en sidestak: hver rotation, margen og sidestørrelsesvalg påvirker, hvor læsbart det endelige dokument føles. + Beskær eller roter billeder først, når sidens kanter, skygger eller retning er forkerte. + Vælg billeder i den tilsigtede siderækkefølge, og juster sidestørrelse eller marginer, hvis værktøjet tilbyder dem. + Eksporter PDF\'en, og kontroller derefter, at alle sider er læsbare, før du sender den. + Scan dokumenter + Optag papirsider og klargør dem til PDF, OCR eller deling + Optag læsbare sider + Dokumentscanning fungerer bedst med flade sider, god belysning og korrigeret perspektiv. En ren scanning før OCR- eller PDF-eksport sparer tid, fordi tekstgenkendelse og komprimering begge afhænger af sidekvaliteten. + Placer dokumentet på en kontrasterende overflade med nok lys og minimale skygger. + Juster registrerede hjørner eller beskær manuelt, så siden fylder rammen rent. + Eksporter som billeder eller PDF, og kør derefter OCR, hvis du har brug for valgbar tekst. + Beskyt eller lås op for PDF-filer + Brug adgangskoder omhyggeligt og behold en redigerbar kildekopi, når det er muligt + Håndter PDF-adgang sikkert + Beskyttelsesværktøjer er nyttige til kontrolleret deling, men adgangskoder er nemme at miste og svære at gendanne. Beskyt kopier til distribution, hold ubeskyttede kildefiler adskilt, og bekræft resultatet, før du sletter noget. + Beskyt en kopi af PDF\'en, ikke dit eneste kildedokument. + Gem adgangskoden et sikkert sted, før du deler den beskyttede fil. + Brug kun oplåsning til filer, du har tilladelse til at redigere, og kontroller derefter, at den ulåste kopi åbner korrekt. + Organiser PDF-sider + Flet, opdel, omarranger, roter, beskær, fjern sider og tilføj sidetal bevidst + Fastgør dokumentstruktur før dekoration + PDF-sideværktøjer er nemmest at bruge i samme rækkefølge, som du ville forberede et papirdokument: saml sider, fjern de forkerte, sæt dem i rækkefølge, korrekt orientering, og tilføj derefter sidste detaljer såsom sidetal eller vandmærker. + Brug fletning eller billeder-til-PDF først, når dokumentet stadig er opdelt på flere filer. + Omarranger, roter, beskær eller fjern sider, før du tilføjer sidetal, signaturer eller vandmærker. + Forhåndsvis den endelige PDF efter strukturelle redigeringer, fordi en manglende eller roteret side kan være svær at bemærke senere. + PDF-anmærkninger + Fjern annoteringer eller udglat dem, når seere viser kommentarer og markeringer anderledes + Styr, hvad der forbliver synligt + Annoteringer kan være kommentarer, fremhævelser, tegninger, formularmærker eller andre ekstra PDF-objekter. Nogle fremvisere viser dem anderledes, så fjernelse eller udjævning af dem kan gøre delte dokumenter mere forudsigelige. + Fjern anmærkninger, når kommentarer, fremhævninger eller anmeldelsesmærker ikke skal medtages i den delte kopi. + Flad ud, når synlige mærker skal forblive på siden, men ikke længere opfører sig som redigerbare annoteringer. + Behold en originalkopi, fordi det kan være vanskeligt at fjerne eller udjævne anmærkninger. + Genkend tekst + Uddrag tekst fra billeder med valgbare OCR-motorer og sprog + Få bedre OCR-resultater + OCR-nøjagtighed afhænger af billedskarphed, sprogdata, kontrast og sideretning. De bedste resultater kommer normalt ved at forberede billedet først: beskær støj, drej oprejst, øg læsbarheden og genkend derefter tekst. + Beskær billedet til tekstområdet og drej det oprejst før genkendelse. + Vælg det korrekte sprog, og download sprogdata, hvis appen anmoder om det. + Kopier eller del den genkendte tekst, og kontroller derefter manuelt navne, tal og tegnsætning. + Vælg OCR-sprogdata + Forbedre genkendelsen ved at matche scripts, sprog og downloadede OCR-modeller + Match OCR til teksten + Det forkerte OCR-sprog kan få gode billeder til at ligne dårlig genkendelse. Sprogdata er vigtige for scripts, tegnsætning, tal og blandede dokumenter, så vælg det sprog, der vises på billedet i stedet for sproget på telefonens brugergrænseflade. + Vælg det sprog eller script, der vises på billedet, ikke kun telefonsproget. + Download manglende data, før du arbejder offline eller behandler en batch. + For blandede sprogdokumenter skal du køre det vigtigste sprog først og manuelt kontrollere navne og numre. + Søgbare PDF\'er + Skriv OCR-tekst tilbage i filer, så scannede sider kan søges og kopieres + Gør scanninger til søgbare dokumenter + OCR kan mere end at kopiere tekst til udklipsholderen. Når du skriver genkendt tekst til en fil eller en søgbar PDF, forbliver billedet af siden synligt, mens tekst bliver lettere at søge, vælge, indeksere eller arkivere. + Brug søgbart PDF-output til scannede dokumenter, der stadig skal ligne de originale sider. + Brug skriv til fil, når du kun har brug for udtrukket tekst og er ligeglad med sidebilledet. + Tjek vigtige tal, navne og tabeller manuelt, fordi OCR-tekst kan være søgbar, men stadig ufuldkommen. + Scan QR-koder + Læs QR-indhold fra kamerainput eller gemte billeder, når det er tilgængeligt + Læs koder sikkert + QR-koder kan indeholde links, Wi-Fi-data, kontakter, tekst eller anden nyttelast. + Åbn Scan QR-kode, og hold koden skarp, flad og helt inde i rammen. + Gennemgå det afkodede indhold, før du åbner links eller kopierer følsomme data. + Hvis scanningen mislykkes, kan du forbedre belysningen, beskære koden eller prøve et kildebillede i højere opløsning. + Brug Base64 og kontrolsummer + Indkode billeder som tekst, afkode tekst tilbage til billeder, og bekræft filens integritet + Flyt mellem filer og tekst + Base64 er nyttig til små billeddata, mens kontrolsummer hjælper med at bekræfte, at filer ikke har ændret sig. Disse værktøjer er mest nyttige i tekniske arbejdsgange, supportrapporter, filoverførsler og steder, hvor binære filer midlertidigt skal blive til tekst. + Brug Base64-værktøjer til at kode et lille billede, når en arbejdsgang har brug for tekst i stedet for en binær fil. + Afkod kun Base64 fra pålidelige kilder, og bekræft forhåndsvisningen, før du gemmer. + Brug kontrolsumværktøjer, når du skal sammenligne eksporterede filer eller bekræfte en upload/download. + Pak eller beskyt data + Brug ZIP- og krypteringsværktøjer til at samle filer eller transformere tekstdata + Hold relaterede filer sammen + Emballageværktøjer er nyttige, når flere udgange skal køre som en fil. De er også gode til at holde genererede billeder, PDF\'er, logfiler og metadata sammen, når du skal sende et komplet resultatsæt. + Brug ZIP, når du skal sende mange eksporterede billeder eller dokumenter sammen. + Brug kun krypteringsværktøjer, når du forstår den valgte metode og kan opbevare de nødvendige nøgler sikkert. + Test det oprettede arkiv eller den transformerede tekst, før du sletter kildefiler. + Kontrolsummer i navne + Brug kontrolsum-baserede filnavne, når unikhed og integritet betyder mere end læsbarhed + Navngiv filer efter indhold + Kontrolsum filnavne er nyttige, når eksporter kan have dobbelte synlige navne, eller når du skal bevise, at to filer er identiske. De er mindre venlige til manuel browsing, så brug dem til tekniske arkiver og verifikationsarbejdsgange. + Aktiver kontrolsum som filnavn, når indholdsidentitet er vigtigere end en titel, der kan læses af mennesker. + Brug det til arkiver, deduplikering, gentagelige eksporter eller filer, der kan uploades og downloades igen. + Par tjeksumnavne med mapper eller metadata, når folk stadig skal forstå, hvad filen repræsenterer. + Vælg farver fra billeder + Prøve pixels, kopier farvekoder, og genbrug farver i paletter eller designs + Prøv den nøjagtige farve + Farvevalg er nyttigt til at matche et design, udtrække mærkefarver eller kontrollere billedværdier. Zoom er vigtigt, fordi antialiasing, skygger og komprimering kan gøre nabopixels en smule anderledes end den farve, du forventer. + Åbn Vælg farve fra billede, og vælg et kildebillede med den farve, du har brug for. + Zoom ind, før du prøver små detaljer, så nærliggende pixels ikke påvirker resultatet. + Kopier farven i det format, der kræves af din destination, såsom HEX, RGB eller HSV. + Opret paletter og brug farvebiblioteket + Udpak paletter, gennemse gemte farver, og bevar ensartede visuelle systemer + Byg en genanvendelig palette + Paletter hjælper med at holde eksporteret grafik, vandmærker og tegninger visuelt konsistente. De er især nyttige, når du gentagne gange annoterer skærmbilleder, forbereder varemærkeaktiver eller har brug for de samme farver på tværs af flere værktøjer. + Generer en palet fra et billede eller tilføj farver manuelt, når du allerede kender værdierne. + Navngiv eller organiser vigtige farver, så du kan finde dem igen senere. + Brug kopierede værdier i tegne-, vandmærke-, gradient-, SVG- eller eksterne designværktøjer. + Opret gradienter + Byg gradientbilleder til baggrunde, pladsholdere og visuelle eksperimenter + Styr farveovergange + Gradientværktøjer er nyttige, når du har brug for et genereret billede i stedet for at redigere et eksisterende. De kan blive rene baggrunde, pladsholdere, tapeter, masker eller simple aktiver til eksterne designværktøjer. + Vælg gradienttypen og indstil de vigtige farver først. + Juster retning, stop, glathed og outputstørrelse for målets anvendelsestilfælde. + Eksporter i et format, der matcher destinationen, normalt PNG til ren genereret grafik. + Farve tilgængelighed + Brug dynamiske farver, kontrast og farveblinde muligheder, når brugergrænsefladen er svær at læse + Gør farver nemmere at bruge + Farveindstillinger påvirker komfort, læsbarhed og hvor hurtigt du kan scanne kontroller. Hvis værktøjschips, skydere eller udvalgte tilstande føles svære at skelne, kan tema- og tilgængelighedsindstillinger være mere nyttige end blot at ændre mørk tilstand. + Prøv dynamiske farver, når du vil have appen til at følge den aktuelle tapetpalet. + Øg kontrasten eller skift skema, når knapper og overflader ikke skiller sig ud nok. + Brug farveblinde skemaindstillinger, hvis nogle tilstande eller accenter er svære at skelne. + Alpha baggrund + Kontroller forhåndsvisningen eller fyldfarven, der bruges omkring transparent PNG, WebP og lignende output + Forstå gennemsigtige forhåndsvisninger + Baggrundsfarve til alfaformater kan gøre gennemsigtige billeder nemmere at inspicere, men det er vigtigt at vide, om du ændrer en preview/fill-adfærd eller udjævner det endelige resultat. Dette har betydning for klistermærker, logoer og baggrundsfjernede billeder. + Brug først et alfa-understøttende format, såsom PNG eller WebP, når transparente pixels skal overleve eksport. + Juster baggrundsfarveindstillingerne, når gennemsigtige kanter er svære at se mod appens overflade. + Eksporter en lille test, og åbn den igen i en anden app for at bekræfte, om gennemsigtigheden eller det valgte fyld blev gemt. + Valg af AI-model + Vælg en model efter billedtype og defekt i stedet for at vælge den største først + Match modellen til skaden + AI Tools kan downloade eller importere forskellige ONNX/ORT-modeller, og hver model er trænet til en bestemt type problem. En model til JPEG-blokke, oprydning af anime-linjer, fornedring, fjernelse af baggrund, farvelægning eller opskalering kan give dårligere resultater, når den bruges på den forkerte billedtype. + Læs modelbeskrivelsen før download; vælg den model, der navngiver dit egentlige problem, såsom komprimering, støj, halvtone, sløring eller fjernelse af baggrund. + Start med en mindre eller mere specifik model, når du tester en ny billedtype, og sammenlign kun med tungere modeller, hvis resultatet ikke er godt nok. + Behold kun modeller, du virkelig bruger, fordi downloadede og importerede modeller kan tage mærkbar lagerplads. + Forstå modelfamilier + Modelnavne antyder ofte deres mål: Fornemme modeller i RealESRGAN-stil, SCUNet- og FBCNN-modeller renser støj eller komprimering, baggrundsmodeller skaber masker, og farvelæggere tilføjer farve til input i gråtoner. Importerede brugerdefinerede modeller kan være kraftfulde, men de bør matche den forventede input/output-form og bruge understøttet ONNX- eller ORT-format. + Brug opskalere, når du har brug for flere pixels, denoisers, når billedet er kornet, og artefaktfjernere, når komprimeringsblokke eller striber er hovedproblemet. + Brug kun baggrundsmodeller til motivadskillelse; de er ikke det samme som generel fjernelse af objekter eller billedforbedring. + Importér kun tilpassede modeller fra kilder, du har tillid til, og test dem på et lille billede, før du bruger vigtige filer. + AI-parametre + Juster chunk-størrelse, overlapning og parallelle arbejdere for at balancere kvalitet, hastighed og hukommelse + Balancer hastighed med stabilitet + Chunk size styrer, hvor store billedstykker er, før de behandles af modellen. Overlap blander tilstødende bidder for at skjule kanter. Parallelle arbejdere kan fremskynde behandlingen, men hver medarbejder har brug for hukommelse, så høje værdier kan gøre store billeder ustabile på telefoner med begrænset RAM. + Brug større bidder for en mere jævn kontekst, når din enhed håndterer modellen pålideligt, og billedet ikke er stort. + Øg overlapningen, når chunk-kanter bliver synlige, men husk, at mere overlap betyder mere gentaget arbejde. + Løft kun parallelarbejdere efter en stabil test; hvis behandlingen bliver langsommere, opvarmer enheden eller går ned, skal du først reducere antallet af medarbejdere. + Undgå chunk artefakter + Chunking lader appen behandle billeder, der er større, end modellen komfortabelt kan håndtere på én gang. Afvejningen er, at hver flise har mindre omgivende kontekst, så lav overlapning eller for små bidder kan skabe synlige kanter, teksturændringer eller inkonsistente detaljer mellem tilstødende områder. + Forøg overlapning, hvis du ser rektangulære kanter, gentagne teksturændringer eller detaljespring mellem behandlede områder. + Reducer chunk-størrelsen, hvis processen mislykkes, før du producerer output, især på store billeder eller tunge modeller. + Gem en lille beskæringstest, før du behandler det fulde billede, så du hurtigt kan sammenligne parametre. + AI stabilitet + Lavere chunkstørrelse, arbejdere og kildeopløsning, når AI-behandling går ned eller fryser + Kom dig efter tunge AI-job + AI-behandling er en af ​​de tungeste arbejdsgange i appen. Nedbrud, mislykkede sessioner, fryser eller pludselig genstart af app betyder normalt, at modellen, kildeopløsningen, chunkstørrelsen eller antallet af parallelle arbejdere er for krævende for den aktuelle enhedstilstand. + Hvis appen går ned eller ikke åbner en modelsession, sænk parallelarbejdere og chunk-størrelse, og prøv derefter det samme billede igen. + Tilpas størrelsen på meget store billeder før AI-behandling, når målet ikke kræver den originale opløsning. + Luk andre tunge apps, brug en mindre model, eller bearbejd færre filer på én gang, når hukommelsestrykket bliver ved med at vende tilbage. + Diagnosticer modelfejl + En mislykket AI-kørsel betyder ikke altid, at billedet er dårligt. Modelfilen kan være ufuldstændig, ikke-understøttet, for stor til hukommelse eller stemmer ikke overens med handlingen. Når appen rapporterer en mislykket session, skal du behandle det som et signal om at forenkle modellen og parametrene først. + Slet og genindlæs en model, hvis den fejler umiddelbart efter download eller opfører sig, som om filen er beskadiget. + Prøv en indbygget model, der kan downloades, før du giver skylden for en importeret brugerdefineret model, fordi importerede modeller kan have inkompatible input. + Brug applogs efter gengivelse af fejlen, hvis du har brug for at rapportere et nedbrud eller en sessionsfejl. + Eksperimenter i Shader Studio + Brug GPU shader-effekter til avanceret billedbehandling og brugerdefinerede udseende + Arbejd omhyggeligt med shaders + Shader Studio er kraftfuldt, men shader-kode og parametre kræver små, testbare ændringer. Behandl det som et eksperimentelt værktøj: Hold en fungerende baseline, skift én ting ad gangen, og test med forskellige billedstørrelser, før du stoler på en forudindstilling. + Start fra en kendt fungerende shader eller en simpel effekt, før du tilføjer parametre. + Skift én parameter eller kodeblok ad gangen, og kontroller forhåndsvisningen for fejl. + Eksporter kun forudindstillinger efter at have testet dem på et par forskellige billedstørrelser. + Lav SVG- eller ASCII-kunst + Generer vektorlignende aktiver eller tekstbaserede billeder til kreativt output + Vælg den kreative outputtype + SVG- og ASCII-værktøjer tjener forskellige mål: skalerbar grafik eller gengivelse i tekststil. Begge er kreative konverteringer, så forhåndsvisning i den endelige størrelse er vigtigere end at bedømme resultatet ud fra et lille miniaturebillede. + Brug SVG Maker, når resultatet skal skaleres rent eller genbruges i designworkflows. + Brug ASCII Art, når du ønsker en stiliseret tekstgengivelse af et billede. + Forhåndsvisning i den endelige størrelse, fordi små detaljer kan forsvinde i genereret output. + Generer støjteksturer + Opret proceduremæssige teksturer til baggrunde, masker, overlejringer eller eksperimenter + Indstil procedureoutput + Støjgenerering skaber nye billeder ud fra parametre, så små ændringer kan give meget forskellige resultater. Det er nyttigt til teksturer, masker, overlejringer, pladsholdere eller test af kompression med detaljerede mønstre. + Vælg en støjtype og outputstørrelse, før du tuner fine detaljer. + Juster skala, intensitet, farver eller frø, indtil mønsteret passer til målet. + Gem nyttige resultater og skriv parametrene ned, hvis du skal genskabe teksturen senere. + Markup projekter + Brug projektfiler, når annoteringer skal forblive redigerbare efter lukning af appen + Hold lagdelte redigeringer genanvendelige + Markup-projektfiler er nyttige, når et skærmbillede, diagram eller instruktionsbillede muligvis skal ændres senere. Eksporterede PNG/JPEG-filer er endelige billeder, mens projektfiler bevarer redigerbare lag og redigeringstilstand til fremtidige justeringer. + Brug Markup Layers, når annoteringer, billedforklaringer eller layoutelementer skal forblive redigerbare. + Gem eller genåbn projektfilen, før du eksporterer et endeligt billede, hvis du forventer flere revisioner. + Eksportér kun et normalt billede, når du er klar til at dele en fladtrykt endelig version. + Krypter filer + Beskyt filer med en adgangskode og test dekryptering, før du sletter en kildekopi + Håndter krypterede output forsigtigt + Cipher-værktøjer kan beskytte filer med adgangskodebaseret kryptering, men de er ikke en erstatning for at huske nøglen eller opbevare sikkerhedskopier. Kryptering er nyttig til transport og opbevaring, mens ZIP er bedre, når hovedmålet er at samle flere output. + Krypter først en kopi, og behold originalen, indtil du har testet, at dekryptering virker med den adgangskode, du har gemt. + Brug en adgangskodeadministrator eller et andet sikkert sted til nøglen; appen kan ikke gætte en mistet adgangskode for dig. + Del kun krypterede filer med personer, der også har adgangskoden og ved, hvilket værktøj eller hvilken metode der skal dekryptere dem. + Arranger værktøjer + Gruppér, bestil, søg og fastgør værktøjer, så hovedskærmen matcher din rigtige arbejdsgang + Gør hovedskærmen hurtigere + Indstillinger for værktøjsarrangement er værd at justere, når du ved, hvilke værktøjer du bruger mest. Gruppering hjælper med udforskning, tilpasset rækkefølge hjælper muskelhukommelse, og favoritter holder daglige værktøjer tilgængelige, selv når den fulde liste bliver stor. + Brug grupperet tilstand, mens du lærer appen, eller når du foretrækker værktøjer organiseret efter opgavetype. + Skift til brugerdefineret rækkefølge, når du allerede kender dine hyppige værktøjer og vil have færre ruller. + Brug søgeindstillinger og favoritter sammen for sjældne værktøjer, du husker ved navn, men ikke vil have synlige hele tiden. + Håndter store filer + Undgå langsom eksport, pres på hukommelsen og uventet stor produktion + Reducer arbejdet før eksport + Meget store billeder, lange batches og højkvalitetsformater kan tage mere hukommelse og tid. Den hurtigste løsning er normalt at reducere mængden af ​​arbejde før den tungeste operation, ikke at vente på, at det samme overdimensionerede input er færdigt. + Tilpas størrelsen på store billeder, før du anvender tunge filtre, OCR eller PDF-konvertering. + Brug først en mindre batch til at bekræfte indstillingerne, og bearbejd derefter resten med den samme forudindstilling. + Lavere kvalitet eller skift format, når outputfilen er større end forventet. + Cache og forhåndsvisninger + Forstå genererede forhåndsvisninger, cacheoprydning og lagerbrug efter tunge sessioner + Hold opbevaring og hastighed afbalanceret + Generering af forhåndsvisning og cache kan gøre gentagen browsing hurtigere, men tunge redigeringssessioner kan efterlade midlertidige data. Cacheindstillinger hjælper, når appen føles langsom, lagerpladsen er stram, eller forhåndsvisninger ser forældede ud efter mange eksperimenter. + Hold forhåndsvisninger aktiveret, når du gennemser mange billeder, er vigtigere end at minimere midlertidig lagring. + Ryd cache efter store batches, mislykkede eksperimenter eller lange sessioner med mange højopløselige filer. + Brug automatisk cacherydning, hvis du foretrækker lageroprydning frem for at holde forhåndsvisninger varme mellem lanceringer. + Juster skærmens adfærd + Brug fuld skærm, sikker tilstand, lysstyrke og systembjælke til fokuseret arbejde + Få grænsefladen til at passe til situationen + Skærmindstillinger hjælper, når du redigerer i liggende format, præsenterer private billeder eller har brug for ensartet lysstyrke. De er ikke nødvendige til normal brug, men de løser akavede situationer såsom QR-inspektion, private forhåndsvisninger eller trange landskabsredigering. + Brug fuldskærms- eller systemlinjeindstillinger, når redigering af plads er vigtigere end navigation i Chrome. + Brug sikker tilstand, når private billeder ikke skal vises i skærmbilleder eller app-forhåndsvisninger. + Brug lysstyrkehåndhævelse til værktøjer, hvor mørke billeder eller QR-koder er svære at inspicere. + Bevar gennemsigtigheden + Undgå, at gennemsigtige baggrunde bliver hvide, sorte eller fladtrykte + Brug alfa-bevidst output + Gennemsigtighed overlever kun, når det valgte værktøj og outputformat understøtter alfa. Hvis en eksporteret baggrund bliver hvid eller sort, er problemet normalt outputformatet, en udfyldnings-/baggrundsindstilling eller en destinationsapp, der har gjort billedet fladt. + Vælg PNG eller WebP, når du eksporterer baggrundsfjernede billeder, klistermærker, logoer eller overlejringer. + Undgå JPEG for gennemsigtigt output, fordi det ikke gemmer en alfakanal. + Kontroller indstillinger relateret til baggrundsfarve for alfaformater, hvis forhåndsvisningen viser en udfyldt baggrund. + Løs problemer med deling og import + Brug vælgerindstillinger og understøttede formater, når filer ikke vises eller åbnes korrekt + Få filen ind i appen + Importproblemer skyldes ofte udbydertilladelser, ikke-understøttede formater eller vælgeradfærd. Filer, der deles fra cloud-apps og messengers, kan være midlertidige, så det kan være mere pålideligt at vælge en vedvarende kilde til længere redigeringer. + Prøv at åbne filen gennem systemvælgeren i stedet for en genvej til de seneste filer. + Hvis et format ikke understøttes af et værktøj, skal du først konvertere det eller åbne et mere specifikt værktøj. + Gennemgå indstillingerne for billedvælger og launcher, hvis delestrømme eller direkte værktøjsåbning opfører sig anderledes end forventet. + Afslut bekræftelse + Undgå at miste ikke-gemte redigeringer, når bevægelser, tilbagetryk eller værktøjsskift sker ved et uheld + Beskyt ufærdigt arbejde + Bekræftelse af værktøjsafslutning er nyttig, når du bruger bevægelsesnavigation, redigerer store filer eller ofte skifter mellem apps. Den tilføjer en lille pause, før den forlader et værktøj, så ufærdige indstillinger og forhåndsvisninger ikke går tabt ved et uheld. + Aktiver bekræftelse, hvis utilsigtede tilbagebevægelser nogensinde har lukket et værktøj, før du eksporterede. + Hold det deaktiveret, hvis du for det meste laver hurtige et-trins konverteringer og foretrækker hurtigere navigation. + Brug det sammen med forudindstillinger til længere arbejdsgange, hvor genopbygningsindstillinger ville være irriterende. + Brug logfiler, når du rapporterer problemer + Indsaml nyttige oplysninger, før du åbner et problem eller kontakter udvikleren + Rapportér problemer med kontekst + En klar rapport med trin, appversion, inputtype og logfiler er meget nemmere at diagnosticere. Logfiler er mest nyttige lige efter at have reproduceret et problem, fordi de holder den omgivende kontekst frisk. + Bemærk værktøjsnavnet, den nøjagtige handling, og om det sker med én fil eller hver fil. + Åbn applogs efter gengivelse af problemet, og del det relevante logoutput, når det er muligt. + Vedhæft kun skærmbilleder eller prøvefiler, når de ikke indeholder private oplysninger. + Baggrundsbehandling blev stoppet + Android lod ikke baggrundsbehandlingsmeddelelsen starte i tide. Dette er en systemtidsbegrænsning, som ikke kan rettes pålideligt. Genstart appen og prøv igen; Det kan hjælpe at holde appen åben og tillade meddelelser eller baggrundsarbejde. + Spring over filplukning + Åbn værktøjer hurtigere, når input normalt kommer fra share, udklipsholder eller en tidligere skærm + Gør gentagne værktøjslanceringer hurtigere + Spring over filvalg ændrer det første trin af understøttede værktøjer. Det er nyttigt, når du normalt indtaster et værktøj med et allerede valgt billede eller fra Android-delehandlinger, men det kan føles forvirrende, hvis du forventer, at hvert værktøj beder om en fil med det samme. + Aktiver det kun, når dit sædvanlige flow allerede leverer kildebilledet, før værktøjet åbnes. + Hold det deaktiveret, mens du lærer appen, fordi eksplicit valg gør hvert værktøjsflow lettere at forstå. + Hvis et værktøj åbner uden indlysende input, skal du deaktivere denne indstilling eller starte fra Del/Åbn med, så Android sender filen først. + Åbn editoren først + Spring forhåndsvisning over, når et delt billede skal gå direkte til redigering + Vælg forhåndsvisning eller rediger som første stop + Åbn redigering i stedet for forhåndsvisning er for brugere, der allerede ved, at de vil ændre billedet. Forhåndsvisning er sikrere, når du inspicerer filer fra chat eller skylager; Direkte redigering er hurtigere til skærmbilleder, hurtige beskæringer, annoteringer og engangsrettelser. + Aktiver direkte redigering, hvis de fleste delte billeder straks har brug for beskæring, markering, filtre eller eksportændringer. + Forlad forhåndsvisning først, når du ofte inspicerer metadata, dimensioner, gennemsigtighed eller billedkvalitet, før du beslutter dig. + Brug dette sammen med favoritter, så delte billeder lander tæt på de værktøjer, du rent faktisk bruger. + Forudindstillet tekstindtastning + Indtast nøjagtige forudindstillede værdier i stedet for at justere kontroller gentagne gange + Brug præcise værdier, når skyderne er langsomme + Forudindstillet tekstindtastning hjælper, når du har brug for nøjagtige tal til gentaget arbejde: sociale billedstørrelser, dokumentmargener, komprimeringsmål, linjebredder eller andre værdier, der er irriterende at nå med bevægelser. Det er også nyttigt på små skærme, hvor fine skyderbevægelser er sværere. + Aktiver tekstindtastning, hvis du ofte genbruger nøjagtige størrelser, procenter, kvalitetsværdier eller værktøjsparametre. + Gem værdien som en forudindstilling, efter du har indtastet den én gang, og genbrug den derefter i stedet for at genopbygge den samme opsætning. + Behold bevægelseskontroller for grov visuel justering og indtastede værdier for strenge outputkrav. + Gem næsten original + Sæt genererede filer ved siden af ​​kildebilleder, når mappekontekst betyder noget + Behold output med deres kilder + Gem til originalmappe er praktisk til kameramapper, projektmapper, dokumentscanninger og klientbatches, hvor den redigerede kopi skal forblive ved siden af ​​kilden. Det kan være mindre ryddeligt end en dedikeret outputmappe, så kombiner den med klare filnavnsuffikser eller mønstre. + Aktiver det, når hver kildemappe allerede er meningsfuld, og output bør forblive grupperet der. + Brug filnavnssuffikser, præfikser, tidsstempler eller mønstre, så redigerede kopier er nemme at adskille fra originaler. + Deaktiver det for blandede batches, når du vil have alle genererede filer samlet i én eksportmappe. + Spring over større output + Undgå at gemme konverteringer, der uventet bliver tungere end kilden + Beskyt batcher mod overraskelser i dårlig størrelse + Nogle konverteringer gør filer større, især PNG-skærmbilleder, allerede komprimerede billeder, WebP af høj kvalitet eller billeder med bevarede metadata. Tillad Spring over hvis større forhindrer disse output i at erstatte en mindre kilde i arbejdsgange, hvor reduktion af størrelse er hovedmålet. + Aktiver det, når eksporten er beregnet til at komprimere, ændre størrelse på eller forberede filer til uploadgrænser. + Gennemgå oversprungne filer efter en batch; de er muligvis allerede optimeret eller har brug for et andet format. + Deaktiver det, når kvalitet, gennemsigtighed eller formatkompatibilitet betyder mere end den endelige filstørrelse. + Udklipsholder og links + Styr automatisk udklipsholderinput og linkforhåndsvisninger for hurtigere tekstbaserede værktøjer + Gør automatisk input forudsigelig + Udklipsholder og linkindstillinger er praktiske til QR, Base64, URL og teksttunge arbejdsgange, men automatisk input bør forblive tilsigtet. Hvis appen ser ud til at udfylde felter af sig selv, skal du kontrollere udklipsholderens adfærd; Hvis linkkort føles støjende eller langsomme, skal du justere opførselen til forhåndsvisning af link. + Tillad automatisk udklipsholder, når du ofte kopierer tekst, og åbner straks et matchende værktøj. + Deaktiver automatisk indsæt, hvis privat udklipsholderindhold vises i værktøjer, hvor du ikke havde forventet det. + Slå forhåndsvisning af link fra, når hentning af forhåndsvisning er langsom, distraherende eller unødvendig for din arbejdsgang. + Kompakt styring + Brug tættere vælgere og hurtig indstillingsplacering, når skærmpladsen er knap + Tilpas flere kontroller på små skærme + Kompakte vælgere og hurtig indstillingsplacering hjælper på små telefoner, landskabsredigering og værktøjer med mange parametre. Afvejningen er, at kontroller kan føles tættere, så dette er bedst, når du allerede har genkendt de almindelige muligheder. + Aktiver kompakte vælgere, når dialoger eller valglister føles for høje til skærmen. + Juster hurtige indstillinger, hvis værktøjskontrollerne er nemmere at nå fra den ene side af skærmen. + Vend tilbage til mere rummelige kontroller, hvis du stadig lærer appen eller ofte mangler at trykke tætte muligheder. + Indlæs billeder fra nettet + Indsæt en side eller billed-URL, se et eksempel på parsede billeder, og gem eller del derefter valgte resultater + Brug webbilleder bevidst + Indlæsning af webbilleder analyserer billedkilder fra den angivne URL, forhåndsviser det første tilgængelige billede og lader dig gemme eller dele udvalgte parsede billeder. Nogle websteder bruger midlertidige, beskyttede eller script-genererede links, så en side, der fungerer i en browser, returnerer muligvis stadig ingen brugbare billeder. + Indsæt en direkte billed-URL eller en side-URL, og vent på, at den parsede billedliste vises. + Brug ramme- eller valgknapperne, når siden indeholder flere billeder, og du kun har brug for nogle af dem. + Hvis intet indlæses, prøv et direkte billedlink eller download gennem browseren først; webstedet kan blokere parsing eller bruge private links. + Vælg størrelsesændringstype + Forstå Eksplicit, Fleksibel, Beskær og Tilpas, før du tvinger et billede til nye dimensioner + Styr form, lærred og billedformat + Tilpas størrelsestype bestemmer, hvad der sker, når den ønskede bredde og højde ikke matcher det oprindelige billedformat. Det er forskellen mellem at strække pixels, bevare proportioner, beskære ekstra område eller tilføje et baggrundslærred. + Brug kun Eksplicit, når forvrængning er acceptabel, eller kilden allerede har samme billedformat som målet. + Brug Fleksibel til at holde proportionerne ved at ændre størrelsen fra den vigtige side, især for fotos og blandede batches. + Brug Beskær til strenge rammer uden rammer, eller Tilpas, når hele billedet skal forblive synligt inde i et fast lærred. + Vælg billedskalatilstand + Vælg resampling-filteret, der styrer skarphed, glathed, hastighed og kantartefakter + Tilpas størrelse uden utilsigtet blødhed + Billedskaleringstilstand styrer, hvordan nye pixels beregnes under ændring af størrelse. Simple tilstande er hurtige og forudsigelige, mens avancerede filtre kan bevare detaljer bedre, men kan skærpe kanter, skabe glorier eller tage længere tid på store billeder. + Brug Basic eller Bilinear til hurtig hverdagsændring, når resultatet kun skal være mindre og rent. + Brug tilstande i Lanczos-, Robidoux-, Spline- eller EWA-stil, når fine detaljer, tekst eller nedskalering af høj kvalitet betyder noget. + Brug Nærmeste til pixelkunst, ikoner og grafik med hårde kanter, hvor slørede pixels ville ødelægge udseendet. + Skala farverum + Bestem, hvordan farver skal blandes, mens pixels samples under ændring af størrelse + Hold gradienter og kanter naturlige + Skala farverum ændrer den matematik, der bruges, mens størrelsen ændres. De fleste billeder er fine med standarden, men lineære eller perceptuelle rum kan få gradienter, mørke kanter og mættede farver til at se renere ud efter kraftig skalering. + Forlad standarden, når hastighed og kompatibilitet betyder mere end små farveforskelle. + Prøv lineære eller perceptuelle tilstande, når mørke konturer, UI-skærmbilleder, gradienter eller farvebånd ser forkerte ud efter ændring af størrelse. + Sammenlign før og efter på den rigtige målskærm, fordi farve-rumsændringer kan være subtile og indholdsafhængige. + Begræns Resize-tilstande + Ved, hvornår overbegrænsede billeder skal springes over, omkodes eller skaleres ned, så de passer + Vælg, hvad der skal ske ved grænsen + Limit Resize er ikke kun et værktøj til mindre billeder. Dens tilstand bestemmer, hvad appen gør, når en kilde allerede er inden for dine grænser, eller når kun den ene side bryder reglen, hvilket er vigtigt for blandede mapper og uploadforberedelse. + Brug Spring over, når billeder inden for grænserne skal forblive uberørte og ikke eksporteres igen. + Brug Recode, når dimensioner er acceptable, men du stadig har brug for et nyt format, metadataadfærd, kvalitet eller filnavn. + Brug Zoom, når billeder, der bryder grænserne, skal nedskaleres proportionalt, indtil de passer til det tilladte felt. + Mål for billedformat + Undgå strakte flader, afskårne kanter og uventede kanter i strenge outputstørrelser + Match formen før størrelsen ændres + Bredde og højde er kun halvdelen af ​​et mål til at ændre størrelse. Størrelsesforholdet afgør, om billedet kan passe naturligt, skal beskæres eller skal have et lærred omkring det. Hvis du først tjekker formen, forhindrer du de mest overraskende resultater med ændring af størrelse. + Sammenlign kildeformen med målformen, før du vælger Eksplicit, Beskær eller Tilpas. + Beskær først, når motivet kan miste ekstra baggrund, og destinationen har brug for en stram ramme. + Brug Tilpas eller Fleksibel, når alle dele af det originale billede skal forblive synlige. + Opskalere eller normal ændring af størrelse + Ved, hvornår tilføjelse af pixels kræver AI, og hvornår simpel skalering er nok + Forveksle ikke størrelse med detaljer + Normal ændring af størrelse ændrer dimensioner ved at interpolere pixels; den kan ikke opfinde rigtige detaljer. AI-opskalering kan skabe skarpere detaljer, men den er langsommere og kan hallucinere tekstur, så det rigtige valg afhænger af, om du har brug for kompatibilitet, hastighed eller visuel restaurering. + Brug normal størrelsesændring til miniaturebilleder, uploadgrænser, skærmbilleder og simple dimensionsændringer. + Brug AI-opskalering, når et lille eller blødt billede skal se bedre ud i en større størrelse. + Sammenlign ansigter, tekst og mønstre efter AI-opskalering, fordi genererede detaljer kan se overbevisende, men forkerte ud. + Filterrækkefølgen har betydning + Påfør oprydning, farve, skarphed og endelig komprimering i en bevidst rækkefølge + Byg en renere filterstak + Filtre er ikke altid udskiftelige. En sløring før skarphed, en kontrastforøgelse før OCR eller en farveændring før komprimering kan producere meget forskelligt output fra de samme kontroller. + Beskær og drej først, så senere filtre kun virker på de pixels, der bliver tilbage. + Anvend eksponering, hvidbalance og kontrast før skarphed eller stiliserede effekter. + Hold den endelige størrelsesændring og komprimering nær slutningen, så forhåndsviste detaljer overlever eksport forudsigeligt. + Reparer baggrundskanter + Rens glorier, rester af skygger, hår, huller og bløde konturer efter automatisk fjernelse + Få udskæringer til at se tilsigtede ud + Automatiske masker ser ofte godt ud ved et blik, men afslører problemer på lyse, mørke eller mønstrede baggrunde. Kantoprydning er forskellen mellem en hurtig udskæring og et genanvendeligt klistermærke, logo eller produktbillede. + Se et eksempel på resultatet mod kontrasterende baggrunde for at afsløre glorier og manglende kantdetaljer. + Brug restore til hår, hjørner og gennemsigtige genstande, før du sletter omgivende rester. + Eksporter en lille test på den endelige baggrundsfarve, når udskæringen vil blive placeret i et design. + Læsbare vandmærker + Gør mærker synlige på lyse, mørke, travle og beskårne billeder uden at ødelægge billedet + Beskyt billedet uden at skjule det + Et vandmærke, der ser godt ud på et billede, kan forsvinde på et andet. Størrelse, opacitet, kontrast, placering og gentagelse skal vælges for hele batchen, ikke kun den første forhåndsvisning. + Test mærket på de lyseste og mørkeste billeder i partiet, før du eksporterer alle filer. + Brug lavere opacitet til store gentagne mærker og stærkere kontrast til små hjørnemærker. + Hold vigtige ansigter, tekst og produktdetaljer tydelige, medmindre mærket er beregnet til at forhindre genbrug. + Opdel et billede i fliser + Klip et enkelt billede efter rækker, kolonner, brugerdefinerede procenter, format og kvalitet + Forbered gitre og bestilte stykker + Billedopdeling skaber flere output fra ét kildebillede. Det kan opdele efter række- og kolonneantal, justere række- eller kolonneprocenter og gemme stykker med det valgte outputformat og -kvalitet, hvilket er nyttigt til gitter, sideudsnit, panoramaer og ordnede sociale indlæg. + Vælg kildebilledet, og indstil derefter antallet af rækker og kolonner, du har brug for. + Juster række- eller kolonneprocenter, når brikkerne ikke alle skal være lige store. + Vælg outputformat og kvalitet, før du gemmer, så hvert genereret stykke bruger de samme eksportregler. + Klip billedstrimler ud + Fjern lodrette eller vandrette områder og flet de resterende dele sammen igen + Fjern et område uden at efterlade et hul + Billedskæring er forskellig fra normal beskæring. Den kan fjerne en valgt lodret eller vandret strimmel og derefter flette de resterende billeddele sammen. Omvendt tilstand beholder det valgte område i stedet, og brug af begge omvendte retninger bevarer det valgte rektangel. + Brug lodret skæring til sideområder, søjler eller midterstrimler; brug vandret udskæring til bannere, mellemrum eller rækker. + Aktiver invers på en akse, når du vil beholde den valgte del i stedet for at fjerne den. + Forhåndsvis kanter efter klipning, fordi flettet indhold kan se pludseligt ud, når det fjernede område krydser vigtige detaljer. + Vælg outputformat + Vælg JPEG, PNG, WebP, AVIF, JXL eller PDF baseret på indhold og destination + Lad destinationen bestemme formatet + Det bedste format afhænger af, hvad billedet indeholder, og hvor det skal bruges. Fotos, skærmbilleder, gennemsigtige aktiver, dokumenter og animerede filer fejler alle på forskellige måder, når de tvinges til det forkerte format. + Brug JPEG til bred fotokompatibilitet, når gennemsigtighed og nøjagtige pixels ikke er påkrævet. + Brug PNG til skarpe skærmbilleder, UI-optagelser, gennemsigtig grafik og tabsfri redigeringer. + Brug WebP, AVIF eller JXL, når kompakt moderne output betyder noget, og den modtagende app understøtter det. + Udtræk lydcovers + Gem indlejret albumcover eller tilgængelige medierammer fra lydfiler som PNG-billeder + Træk illustrationer ud af lydfiler + Audio Covers bruger Android-mediemetadata til at læse indlejrede illustrationer fra lydfiler. Når indlejret illustration ikke er tilgængeligt, kan retrieveren falde tilbage til en medieramme, hvis Android kan levere en; hvis ingen af ​​dem findes, rapporterer værktøjet, at der ikke er noget billede at udtrække. + Åbn Audio Covers og vælg en eller flere lydfiler med forventet coverart. + Gennemgå det udpakkede cover, før du gemmer eller deler, især for filer fra streaming- eller optagelsesapps. + Hvis der ikke findes et billede, har lydfilen sandsynligvis ikke noget indlejret cover, eller Android kunne ikke eksponere en brugbar ramme. + Datoer og lokationsmetadata + Beslut, hvad der skal bevares, når optagelsestiden er vigtig, men GPS- eller enhedsdata bør ikke lække + Adskil nyttige metadata fra private metadata + Metadata er ikke alle lige risikable. Optagelsesdatoer kan være nyttige til arkivering og sortering, mens GPS, enhedsserielignende felter, softwaretags eller ejerfelter kan afsløre mere end beregnet. + Behold dato- og tidsmærker, når kronologisk rækkefølge har betydning for album, scanninger eller projekthistorik. + Fjern GPS og enhedsidentificerende tags, før du deler eller sender billeder til fremmede offentligt. + Eksporter en prøve, og inspicér EXIF ​​igen, når kravene til beskyttelse af personlige oplysninger er strenge. + Undgå filnavnekollisioner + Beskyt batch-output, når mange filer deler navne som image.jpg eller screenshot.png + Gør hvert outputnavn unikt + Duplikerede filnavne er almindelige, når billeder kommer fra messengers, skærmbilleder, skymapper eller flere kameraer. Et godt mønster forhindrer utilsigtet udskiftning og holder output sporbare tilbage til deres kilder. + Inkluder originalnavn plus et suffiks, når den redigerede kopi skal kunne genkendes. + Tilføj sekvens, tidsstempel, forudindstilling eller dimensioner, når du behandler store blandede batches. + Undgå at overskrive arbejdsgange, indtil du har bekræftet, at navngivningsmønsteret ikke kan kollidere. + Gem eller del + Vælg mellem en vedvarende fil og en midlertidig overdragelse til en anden app + Eksporter med den rigtige hensigt + Gem og del kan føles ens, men de løser forskellige problemer. Ved at gemme oprettes en fil, du kan finde senere; deling sender et genereret resultat gennem Android til en anden app og efterlader muligvis ikke en holdbar lokal kopi. + Brug Gem, når outputtet skal forblive i en kendt mappe, sikkerhedskopieres eller genbruges senere. + Brug Del til hurtige beskeder, vedhæftede filer i e-mails, sociale opslag eller til at sende resultatet til en anden redaktør. + Gem først, når den modtagende app er upålidelig, eller når en mislykket deling ville være irriterende at genskabe. + PDF-sidestørrelse og marginer + Vælg papirstørrelse, pasform og vejrtrækning, før du opretter et dokument + Gør billedsider printbare og læsbare + Billeder kan blive akavede PDF-sider, når deres form ikke passer til den valgte papirstørrelse. Marginer, tilpasningstilstand og sideretning afgør, om indholdet er beskåret, lille, udstrakt eller behageligt at læse. + Brug en rigtig papirstørrelse, når PDF\'en kan udskrives eller sendes til en formular. + Brug margener til scanninger og skærmbilleder, så teksten ikke ligger mod sidens kant. + Forhåndsvis stående og liggende sider sammen, når kildebillederne har blandet orientering. + Ryd op i scanninger + Forbedre papirkanter, skygger, kontrast, rotation og læsbarhed før PDF eller OCR + Forbered sider før arkivering + En scanning kan være teknisk fanget, men stadig svær at læse. Små oprydningstrin før PDF-eksport betyder ofte mere end komprimeringsindstillinger, især for kvitteringer, håndskrevne noter og sider med svag belysning. + Ret perspektiv og beskær bordkanter, fingre, skygger og baggrundsrod væk. + Øg kontrasten omhyggeligt, så teksten bliver klarere uden at ødelægge stempler, signaturer eller blyantmærker. + Kør kun OCR, når siden er lodret og læsbar, og bekræft derefter vigtige tal manuelt. + Komprimer, gråtoner eller reparer PDF-filer + Brug én-fil PDF-handlinger til lettere, printbare eller genopbyggede dokumentkopier + Vælg PDF-oprydningsoperationen + PDF-værktøjer inkluderer separate operationer til komprimering, gråtonekonvertering og reparation. Kompression og gråtoner fungerer hovedsageligt gennem indlejrede billeder, mens reparation genopbygger PDF-strukturen; ingen af ​​disse bør behandles som en garanteret rettelse for hvert dokument. + Brug Komprimer PDF, når dokumentet indeholder billeder og filstørrelsen har betydning for deling. + Brug gråtoner, når dokumentet skal være nemmere at udskrive eller ikke har brug for farvebilleder. + Brug Reparer PDF på en kopi, når et dokument ikke åbnes eller har beskadiget intern struktur, og bekræft derefter den genopbyggede fil. + Uddrag billeder fra PDF-filer + Gendan indlejrede PDF-billeder og pak de udpakkede resultater ind i et arkiv + Gem kildebilleder fra dokumenter + Extract Images scanner PDF\'en for indlejrede billedobjekter, springer dubletter over, hvor det er muligt, og gemmer gendannede billeder i et genereret ZIP-arkiv. Det udtrækker kun billeder, der faktisk er indlejret i PDF\'en, ikke tekst, vektortegninger eller hver synlig side som et skærmbillede. + Brug Udtræk billeder, når du har brug for billeder gemt i en PDF, såsom fotos fra et katalog eller scannede aktiver. + Brug PDF til billeder eller sideudtræk i stedet, når du har brug for hele sider, inklusive tekst og layout. + Hvis værktøjet ikke rapporterer nogen indlejrede billeder, kan siden være tekst-/vektorindhold, eller billederne gemmes muligvis ikke som udtrækbare objekter. + Metadata, udfladning og printoutput + Rediger dokumentegenskaber, raster sider, eller klargør brugerdefinerede printlayouts med vilje + Vælg handlingen for det endelige dokument + PDF-metadata, udfladning og udskriftsforberedelse løser forskellige problemer i sidste fase. Metadata styrer dokumentegenskaber, Flatten PDF rasteriserer sider til mindre redigerbare output, og Print PDF forbereder et nyt layout med sidestørrelse, retning, margen og sider pr. ark-kontroller. + Brug Metadata, når forfatter, titel, søgeord, producent eller privatlivsrensning er vigtige. + Brug flad PDF, når synligt sideindhold skulle blive sværere at redigere som separate PDF-objekter. + Brug Udskriv PDF, når du har brug for tilpasset sidestørrelse, orientering, margener eller flere sider pr. ark. + Forbered billeder til OCR + Brug beskæring, rotation, kontrast, denoise og skalering, før du beder OCR om at læse tekst + Giv OCR-renere pixels + OCR-fejl kommer ofte fra inputbilledet snarere end OCR-motoren. Skæve sider, lav kontrast, sløring, skygger, lille tekst og blandede baggrunde reducerer alle genkendelseskvaliteten. + Beskær til tekstområdet, og drej billedet, så linjer er vandrette før genkendelse. + Øg kontrasten eller konverter kun til renere sort-hvid, når det gør bogstaver nemmere at læse. + Ændr størrelsen på lille tekst moderat opad, men undgå aggressiv skærpning, der skaber falske kanter. + Tjek QR-indhold + Gennemgå links, Wi-Fi-legitimationsoplysninger, kontakter og handlinger, før du stoler på en kode + Behandl afkodet tekst som upålidelig input + En QR-kode kan skjule en URL, et kontaktkort, en Wi-Fi-adgangskode, en kalenderbegivenhed, et telefonnummer eller almindelig tekst. Den synlige etiket i nærheden af ​​koden matcher muligvis ikke den faktiske nyttelast, så gennemgå det afkodede indhold, før du handler på det. + Undersøg hele den afkodede URL, før du åbner den, især forkortede eller ukendte domæner. + Vær forsigtig med Wi-Fi-, kontakt-, kalender-, telefon- og SMS-koder, fordi de kan udløse følsomme handlinger. + Kopier først indholdet, når du skal bekræfte det et andet sted i stedet for at åbne det med det samme. + Generer QR-koder + Opret koder fra almindelig tekst, URL\'er, Wi-Fi, kontakter, e-mail, telefon, SMS og placeringsdata + Byg den rigtige QR-nyttelast + QR-skærmen kan generere koder fra indtastet indhold samt scanne eksisterende. Strukturerede QR-typer hjælper med at kode data i et format, som andre apps forstår, såsom Wi-Fi-loginoplysninger, kontaktkort, e-mail-felter, telefonnumre, SMS-indhold, URL\'er eller geografiske koordinater. + Vælg almindelig tekst til enkle noter, eller brug en struktureret type, når koden skal åbne som Wi-Fi, kontakt, URL, e-mail, telefon, SMS eller placeringsdata. + Tjek hvert felt, før du deler den genererede kode, fordi den synlige forhåndsvisning kun afspejler den nyttelast, du har indtastet. + Test den gemte QR-kode med en scanner, når den vil blive udskrevet, offentliggjort eller brugt af andre. + Vælg en kontrolsum-tilstand + Beregn hashes fra filer eller tekst, sammenlign én fil, eller batch-tjek mange filer + Bekræft indhold, ikke udseende + Checksum Tools har separate faner til fil-hash, teksthash, sammenligning af en fil med en kendt hash og batch-sammenligning. En kontrolsum beviser byte-for-byte identitet for den valgte algoritme; det beviser ikke, at to billeder blot ligner hinanden. + Brug Calculate for filer og Text Hash til indtastede eller indsatte tekstdata. + Brug Sammenlign, når nogen giver dig en forventet kontrolsum for én fil. + Brug Batch Compare, når mange filer har brug for hashes eller verifikation i én omgang, og hold derefter den valgte algoritme konsekvent. + Udklipsholder privatliv + Brug automatiske udklipsholderfunktioner uden ved et uheld at udsætte private kopierede data + Hold kopieret indhold med vilje + Automatisering af klippebord er praktisk, men kopieret tekst kan indeholde adgangskoder, links, adresser, tokens eller private noter. Behandl udklipsholder-baseret input som en hastighedsfunktion, der bør matche dine vaner og forventninger til privatliv. + Deaktiver automatisk brug af udklipsholder, hvis privat tekst dukker op i værktøjer uventet. + Brug kun udklipsholder, når du bevidst ønsker, at resultatet skal undgå lagring. + Ryd eller udskift udklipsholderen efter håndtering af følsom tekst, QR-data eller Base64-nyttelast. + Farveformater + Forstå HEX-, RGB-, HSV-, alfa- og kopierede farveværdier, før du indsætter andre steder + Kopier det format, som målet forventer + Den samme synlige farve kan skrives på flere måder. Et designværktøj, CSS-felt, Android-farveværdi eller billedredigering kan forvente en anden rækkefølge, alfahåndtering eller farvemodel. + Brug HEX, når du indsætter i web-, design- eller temafelter, der forventer kompakte farvekoder. + Brug RGB eller HSV, når du skal justere kanaler, lysstyrke, mætning eller farvetone manuelt. + Tjek alfa separat, når gennemsigtighed er vigtig, fordi nogle destinationer ignorerer det eller bruger en anden rækkefølge. + Gennemse farvebiblioteket + Søg navngivne farver efter navn eller HEX, og hold favoritter nær toppen + Find genbrugelige navngivne farver + Color Library indlæser en navngivet farvesamling, sorterer den efter navn, filtrerer efter farvenavn eller HEX-værdi og gemmer yndlingsfarver, så vigtige poster forbliver nemmere at nå. Det er nyttigt, når du har brug for en rigtig navngivet farve i stedet for at tage prøver fra et billede. + Søg efter et farvenavn, når du kender familien, såsom rød, blå, skifer eller mint. + Søg med HEX, når du allerede har en numerisk farve og ønsker at finde matchende eller nærliggende navngivne poster. + Marker hyppige farver som favoritter, så de bevæger sig foran det generelle bibliotek, mens du browser. + Brug mesh gradient samlinger + Download tilgængelige mesh-gradientressourcer og del valgte billeder + Brug eksterne mesh-gradientaktiver + Mesh Gradienter kan indlæse en ekstern ressourcesamling, downloade manglende gradientbilleder, vise downloadfremskridt og dele valgte gradienter. Tilgængelighed afhænger af netværksadgang og det eksterne ressourcelager, så en tom liste betyder normalt, at ressourcer ikke var tilgængelige endnu. + Åbn Mesh Gradienter fra gradientværktøjerne, når du vil have færdige mesh gradientbilleder. + Vent på ressourceindlæsning eller download-fremskridt, før du antager, at samlingen er tom. + Vælg og del de gradienter, du har brug for, eller vend tilbage til Gradient Maker, når du vil bygge en brugerdefineret gradient manuelt. + Genereret aktivopløsning + Vælg outputstørrelse, før du genererer gradienter, støj, tapeter, SVG-lignende kunst eller teksturer + Generer i den størrelse, du har brug for + Genererede billeder har ikke en kameraoriginal at falde tilbage på. Hvis outputtet er for lille, kan senere skalering blødgøre mønstre, flytte detaljer eller ændre, hvordan aktivet fliser og komprimeres. + Indstil først dimensioner for den endelige brug, såsom tapet, ikon, baggrund, print eller overlejring. + Brug større størrelser til teksturer, der kan beskæres, zoomes ind eller genbruges i flere layouts. + Eksporter testversioner før enorme output, fordi proceduremæssige detaljer kan ændre, hvordan komprimering opfører sig. + Eksporter aktuelle baggrunde + Hent tilgængelige Hjem, Lås og indbyggede baggrunde fra enheden + Gem eller del installerede tapeter + Wallpapers Export læser de wallpapers Android eksponerer gennem systemets tapet API\'er. Afhængigt af enhed, Android-version, tilladelser og byggevariant, kan Home, Lock eller indbyggede tapeter være tilgængelige separat eller kan mangle. + Åbn Wallpapers Export for at indlæse tapeterne, som systemet tillader appen at læse. + Vælg de tilgængelige Hjem, Lås eller indbyggede tapetposter, du vil gemme, kopiere eller dele. + Hvis et tapet mangler, eller funktionen ikke er tilgængelig, er det normalt en system-, tilladelses- eller build-variant-begrænsning snarere end en redigeringsindstilling. + Corners Animation Throttle + Kopier farve som + HEX + navn + Portræt mat model til hurtig personudskæring med blødere hår og kanthåndtering. + Hurtig forbedring i svagt lys ved hjælp af Zero-DCE++ kurvestimering. + Restormer-billedgendannelsesmodel til reduktion af regnstriber og artefakter i vådt vejr. + Dokumentudviklende model, der forudsiger et koordinatgitter og omformer buede sider til en fladere scanning. + Bevægelsesvarighedskala + Styrer app-animationshastighed: 0 deaktiverer bevægelse, 1 er normal, højere værdier sænker animationerne + Vis seneste værktøjer + Vis hurtig adgang til nyligt brugte værktøjer på hovedskærmen + Brugsstatistik + Udforsk appåbninger, værktøjslanceringer og dine mest brugte værktøjer + App åbner + Værktøjet åbner + Sidste værktøj + Vellykkede redninger + Aktivitetsrække + Data gemt + Top format + Brugte redskaber + %1$d åbner • %2$s + Ingen brugsstatistik endnu + Åbn nogle få værktøjer, og de vises her automatisk + Din brugsstatistik gemmes kun lokalt på din enhed. ImageToolbox bruger dem kun til at vise din personlige aktivitet, yndlingsværktøjer og gemte dataindsigter + editor rediger foto billede retouchering juster + ændre størrelse konverter skala opløsning dimensioner bredde højde jpg jpeg png webp komprimere + komprimere størrelse vægt bytes mb kb reducere kvaliteten optimere + beskær trim cut billedformat + filter effekt farvekorrektion juster lut maske + tegne pensel blyant annotate markup + cipher krypter dekrypter adgangskodehemmelighed + baggrundsfjerner slet fjern udskæring gennemsigtig alpha + preview seer galleri inspicere åben + stitch flet kombinere panorama langt skærmbillede + url web download internet link billede + picker pipette prøve hex rgb farve + palette farver farveprøver uddrag dominerende + exif metadata privatliv fjern strip ren gps placering + sammenligne diff forskel før efter + grænse ændre størrelse maks. min. megapixel dimensioner klemme + Værktøjer til pdf-dokumentsider + ocr tekst genkend udtræk scanning + gradient baggrundsfarve mesh + vandmærke logo tekst stempel overlay + gif animation animerede konverter rammer + apng animation animeret png konverter rammer + zip-arkiv komprimere pakke udpak filer + jxl jpeg xl konverter jpg-billedformat + svg vektor spor konverter xml + format konverter jpg jpeg png webp heic avif jxl bmp + dokument scanner scan papir perspektiv beskæring + qr stregkode scanner scanning + stabling overlay gennemsnitlig median astrofotografering + delte fliser gitter skive dele + farvekonverter hex rgb hsl hsv cmyk lab + webp konverter animeret billedformat + støjgenerator tilfældig tekstur korn + collage gitter layout kombinere + opmærkningslag annoter overlejringsredigering + base64 kode afkode data uri + checksum hash md5 sha sha1 sha256 verificere + mesh gradient baggrund + exif metadata rediger gps-datokamera + skære opdelte gitterstykker fliser + lydcover albumcover musik metadata + tapet eksport baggrund + ascii tekst kunst tegn + ai ml neural forbedre opskalere segment + farve bibliotek materiale palet navngivet farver + shader glsl fragment effekt studie + pdf flette kombinere join + pdf opdelt separate sider + Pdf rotere sider orientering + pdf omarranger omarranger sider sorter + pdf sidetal paginering + pdf ocr tekst genkender søgbart uddrag + Pdf vandmærke stempel logo tekst + Pdf signatur tegn tegne + pdf-beskytte adgangskode krypter lås + pdf unlock password dekryptere fjern beskyttelse + Pdf komprimere reducere størrelse optimere + pdf gråtoner sort hvid monokrom + Pdf reparation rettelse gendanne brudt + pdf metadata egenskaber exif forfatter titel + pdf fjern slette sider + Pdf-beskæringsmargener + Pdf flade annoteringer danner lag + Pdf udtræk billeder billeder + Komprimering af pdf-zip-arkiv + Printer pdf + Læs pdf preview viewer + Billeder til pdf konverter jpg jpeg png-dokument + pdf til billeder sider eksport jpg png + pdf-anmærkninger kommentarer fjern rene + Neutral variant + Fejl + Drop Shadow + Tandhøjde + Vandret tandområde + Lodret tandområde + Øverste kant + Højre kant + Nederste kant + Venstre kant + Revet kant + Nulstil brugsstatistikker + Værktøjet åbner, gem statistik, streak, gemte data og topformat nulstilles. App-åbninger forbliver uændrede. + Lyd + Fil + Eksporter profiler + Gem den aktuelle profil + Slet eksportprofil + Vil du slette eksportprofilen \\"%1$s\\"? + Tegning kant + Vis en tynd kant rundt om tegne- og viskelærredet + Bølget + Afrundede UI-elementer med en blød bølget kant + Scoop + Hak + Afrundede hjørner udskåret indad + Aftrappede hjørner med dobbelt snit + Pladsholder + Brug {filnavn} til at indsætte det originale filnavn uden filtypenavn + Flare + Grundbeløb + Ring beløb + Ray mængde + Ringbredde + Forvrænget perspektiv + Java Look and Feel + Klippe + X vinkel + og vinkel + Ændr størrelse på output + Vanddråbe + Bølgelængde + Fase + Højpas + Farve maske + Chrome + Opløse + Blødhed + Feedback + Linse sløring + Lav input + Høj input + Lavt output + Høj output + Lyseffekter + Bump højde + Bump blødhed + Ripple + X amplitude + Y amplitude + X bølgelængde + Y bølgelængde + Adaptiv sløring + Teksturgenerering + Generer forskellige proceduremæssige teksturer og gem dem som billeder + Tekstur type + Bias + Tid + Skinne + Spredning + Prøver + Vinkelkoefficient + Gradientkoefficient + Grid type + Afstandskraft + Skala Y + Uklarhed + Basistype + Turbulensfaktor + Skalering + Gentagelser + Ringe + Børstet metal + Ætsende + Cellulær + Skaktern + fBm + Marmor + Plasma + Dyne + Træ + tilfældig + Firkant + Sekskantet + Ottekantet + Trekantet + Rammet + VL Støj + SC Støj + Nylig + Ingen nyligt brugte filtre endnu + tekstur generator børstet metal kaustics cellulært skakternet marmor plasma quilt træ mursten camouflage celle sky revne stof løv honeycomb is lava nebula papir rust sand røg sten terræn topografi vand krusning + Mursten + Camouflage + Celle + Sky + Sprække + Stof + Løv + Bikage + Is + Lava + Tåge + Papir + Rust + Sand + Røg + Sten + Terræn + Topografi + Vand Ripple + Avanceret træ + Mørtelbredde + Uregelmæssighed + Fasning + Ruhed + Første tærskel + Anden tærskel + Tredje tærskel + Kant blødhed + Kantbredde + Dækning + Detalje + Dybde + Forgrening + Vandrette tråde + Lodrette tråde + Fuzz + Vener + Belysning + Revnebredde + Frost + Flyde + Skorpe + Skydensitet + Stjerner + Fiberdensitet + Fiberstyrke + Pletter + Korrosion + Pitting + Flager + Dune frekvens + Vindvinkel + Ripples + Wisps + Vene skala + Vandstand + Bjergniveau + Erosion + Sneniveau + Linjeantal + Linjetykkelse + Skygge + Pore ​​farve + Mørtel farve + Mørk murstensfarve + Mursten farve + Fremhæv farve + Mørk farve + Skov farve + Jordens farve + Sand farve + Baggrundsfarve + Cellefarve + Kantfarve + Himmelfarve + Skygge farve + Lys farve + Overfladefarve + Variation farve + Revne farve + Warp farve + Indslagsfarve + Mørk bladfarve + Bladfarve + Kantfarve + Honning farve + Dyb farve + Is farve + Frost farve + Skorpe farve + Vask farve + Glød farve + Rumfarve + Violet farve + Blå farve + Grundfarve + Fiber farve + Plet farve + Metal farve + Mørk rustfarve + Rust farve + Orange farve + Røgfarve + Vene farve + Lavland farve + Vandfarve + Rock farve + Sne farve + Lav farve + Høj farve + Linje farve + Lav farve + Græs + Smuds + Læder + Beton + Asfalt + Mos + Brand + Aurora + Olieudslip + Akvarel + Abstrakt flow + Opal + Damaskus stål + Lyn + Fløjl + Blækmarmorering + Holografisk folie + Bioluminescens + Kosmisk Vortex + Lava lampe + Begivenhedshorisont + Fraktal Bloom + Kromatisk tunnel + Corona-formørkelse + Mærkelig Tiltrækker + Ferrofluid krone + Supernova + Iris + Påfuglefjer + Nautilus Shell + Ringet Planet + Bladtæthed + Bladlængde + Vind + Latterlighed + Klumper + Fugtighed + Småsten + Rynker + Porer + Samlet + Revner + Tjære + Slid + Pletter + Fibre + Flammefrekvens + Røg + Intensitet + Bånd + Bands + Iriserende + Mørke + Blomstrer + Pigment + Kanter + Papir + Diffusion + Symmetri + Skarphed + Farvespil + Mælkeagtighed + Lag + Foldning + Polere + Grene + Retning + Sheen + Folder + Fjerning + Blækbalance + Spektrum + Krøller + Diffraktion + Våben + Vride + Kerneglød + Klatter + Skive vipning + Horisont størrelse + Diskens bredde + Lensing + Kronblade + Krølle + Filigran + Facetter + Krumning + Månestørrelse + Corona størrelse + Stråler + Diamant ring + Lapper + Banetæthed + Tykkelse + Pigge + Piglængde + Kropsstørrelse + Metallisk + Stødradius + Skal bredde + Smidt ud + Pupilstørrelse + Iris størrelse + Farvevariation + Catchlight + Øjenstørrelse + Modhager tæthed + Drejninger + Kamre + Åbning + Kamme + Perleskinn + Planet størrelse + Ring tilt + Ringbredde + Atmosfære + Snavs farve + Mørk græsfarve + Græsfarve + Tip farve + Mørk jordfarve + Tør farve + Småsten farve + Læder farve + Beton farve + Tjære farve + Asfalt farve + Sten farve + Støvfarve + Jordfarve + Mørk mosfarve + Mos farve + Rød farve + Kernefarve + Grøn farve + Cyan farve + Magenta farve + Guld farve + Papir farve + Pigment farve + Sekundær farve + Første farve + Anden farve + Pink farve + Mørk stål farve + Stål farve + Lys stål farve + Oxid farve + Halo farve + Bolt farve + Fløjlsfarve + Glans farve + Blå blæk farve + Rød blæk farve + Mørk blæk farve + Sølv farve + Gul farve + Vævsfarve + Disk farve + Varm farve + Linse farve + Udvendig farve + Indvendig farve + Corona farve + Kold farve + Varm farve + Sky farve + Flamme farve + Fjer farve + Skal farve + Perle farve + Planet farve + Ring farve + Slet originale filer efter lagring + Kun vellykket behandlede kildefiler vil blive slettet + Originale filer slettet: %1$d + Kunne ikke slette originale filer: %1$d + Originale filer slettet: %1$d, mislykkedes: %2$d + Arkbevægelser + Tillad at trække udvidede indstillingsark + Chroma subsampling + Lidt dybde + Tabsfri + %1$d-bit + Batch Omdøb + Omdøb flere filer ved hjælp af filnavnemønstre + batch omdøb filer filnavn mønster sekvens dato forlængelse + Vælg filer, der skal omdøbes + Filnavn mønster + Omdøb + Datokilde + EXIF-dato taget + Filens ændringsdato + Dato for oprettelse af fil + Nuværende dato + Manuel dato + Manuel dato + Manuel tid + Den valgte dato er ikke tilgængelig for nogle filer. Den aktuelle dato vil blive brugt til dem. + Indtast et filnavnmønster + Mønsteret producerer et ugyldigt eller for langt filnavn + Mønsteret producerer duplikerede filnavne + Alle filnavne matcher allerede mønsteret + Dette mønster bruger tokens, der ikke er tilgængelige for batch-omdøbning + Navnet forbliver det samme + Filer blev omdøbt + Nogle filer kunne ikke omdøbes + Tilladelse blev ikke givet + Bruger det originale filnavn uden filtypenavn, hvilket hjælper dig med at holde kildeidentifikationen intakt. Understøtter også udskæring med \\o{start:slut}, translitteration med \\o{t} og erstat med \\o{s/gamle/nye/}. + Auto-inkrementerende tæller. Understøtter brugerdefineret formatering: \\c{udfyldning} (f.eks. \\c{3} -&gt; 001), \\c{start:trin} eller \\c{start:trin:udfyldning}. + Navnet på den overordnede mappe, hvor den originale fil var placeret. + Størrelsen af ​​den originale fil. Understøtter enheder: \\z{b}, \\z{kb}, \\z{mb} eller bare \\z for mennesker, der kan læses. + Genererer en tilfældig Universally Unique Identifier (UUID) for at sikre filnavnets unikke karakter. + Omdøbning af filer kan ikke fortrydes. Sørg for, at de nye navne er korrekte, før du fortsætter. + Bekræft omdøb + Sidemenuindstillinger + Menu skala + Menu alfa + Automatisk hvidbalance + Klipning + Kontrollerer for skjulte vandmærker + Opbevaring + Cachegrænse + Ryd cachen automatisk, når den vokser ud over denne størrelse + Oprydningsinterval + Hvor ofte skal man kontrollere, om cachen skal ryddes + Ved app lancering + 1 dag + 1 uge + 1 måned + Ryd appens cache automatisk i henhold til den valgte grænse og interval + Bevægeligt afgrødeareal + Tillader flytning af hele beskæringsområdet ved at trække ind i det + Flet GIF + Kombiner flere GIF-klip til én animation + GIF-klip rækkefølge + Bagside + Spil som omvendt + Boomerang + Spil frem og derefter tilbage + Normaliser rammestørrelsen + Skaler rammer, så de passer til den største klips; slukke for at bevare deres oprindelige skala + Forsinkelse mellem klip (ms) + Folder + Vælg alle billeder fra en mappe og dens undermapper + Duplicate Finder + Find nøjagtige kopier og visuelt lignende billeder + duplicate finder lignende billeder nøjagtige kopier fotos oprydning opbevaring dHash SHA-256 + Vælg billeder for at finde dubletter + Lighedsfølsomhed + Præcise kopier + Lignende billeder + Vælg alle nøjagtige dubletter + Vælg alle undtagen anbefalet + Ingen dubletter fundet + Prøv at tilføje flere billeder eller øge lighedsfølsomheden + Kunne ikke læse %1$d billede(r) + %1$s • %2$s kan kræves tilbage + %1$d grupper • %2$s kan kræves tilbage + %1$d valgt • %2$s + Dette kan ikke fortrydes. Android beder dig muligvis om at bekræfte sletningen i en systemdialog. + Ikke fundet + Flyt for at starte + Flyt til slutningen + \ No newline at end of file diff --git a/core/resources/src/main/res/values-de/strings.xml b/core/resources/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..191a269 --- /dev/null +++ b/core/resources/src/main/res/values-de/strings.xml @@ -0,0 +1,3740 @@ + + + + + Größe %1$s + Höhe %1$s + Qualität + Methode + Explizit + Kann Farbpalette nicht aus gewähltem Bild erzeugen + Ausgabeordner + Standardeinstellung + Benutzerdefiniert + Ändere die 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 + Laden… + 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 wählen + App wird geschlossen + 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 + Wird gespeichert + 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 Vorgang jetzt schließt + Farbwähler + Aktualisierung + Quellcode + Bild + EXIF behalten + Erhalte die neuesten Updates, diskutiere Probleme und vieles mehr + Farbe aus Bild wählen, 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 + Zwei Bilder zum Starten wählen + Bilder wählen + 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 + 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 + 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. + 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. + 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 + Inhaltsbewusste Verzerrung + Nicht genügend Arbeitsspeicher, um diese Aktion abzuschließen. Bitte versuchen Sie, ein kleineres Bild zu verwenden, andere Apps zu schließen oder die App neu zu starten. + Parallelarbeiter + Diese Bilder wurden nicht gespeichert, da die konvertierten Dateien größer als die Originale sein würden. Überprüfen Sie diese Liste, bevor Sie Originalbilder löschen. + Übersprungene Dateien: %1$s + App-Protokolle + Sehen Sie sich die Protokolle der App an, um die Probleme zu erkennen + Favoriten in gruppierten Werkzeugen + Fügt Favoriten als Registerkarte hinzu, wenn Werkzeuge nach Typ gruppiert werden + Favorit als letztes anzeigen + Verschiebt die Registerkarte „Favoriten-Tools“ ans Ende + Horizontaler Abstand + Vertikaler Abstand + Rückwärts gerichtete Energie + Verwenden Sie anstelle des Vorwärtsenergiealgorithmus eine einfache Gradienten-Magnituden-Energiekarte + Maskierten Bereich entfernen + Nähte verlaufen durch den maskierten Bereich und entfernen nur den ausgewählten Bereich, anstatt ihn zu schützen + VHS NTSC + Erweiterte NTSC-Einstellungen + Zusätzliche analoge Abstimmung für stärkere VHS- und Broadcast-Artefakte + Chroma-Blutung + Bandverschleiß + Verfolgung + Luma-Abstrich + Klingeln + Schnee + Feld verwenden + Filtertyp + Eingabe-Luma-Filter + Eingang Chroma-Tiefpass + Chroma-Demodulation + Phasenverschiebung + Phasenversatz + Kopfwechsel + Kopfwechselhöhe + Kopfwechsel-Offset + Kopfwechselverschiebung + Mittellinienposition + Jitter in der Mittellinie + Verfolgung der Geräuschhöhe + Verfolgungswelle + Schnee verfolgen + Verfolgung der Schneeanisotropie + Tracking-Lärm + Verfolgung der Lärmintensität + Zusammengesetztes Rauschen + Zusammengesetzte Rauschfrequenz + Intensität des zusammengesetzten Rauschens + Zusammengesetztes Geräuschdetail + Klingelfrequenz + Klingelleistung + Luma-Rauschen + Luma-Rauschfrequenz + Intensität des Luma-Rauschens + Details zum Luma-Rauschen + Chroma-Rauschen + Chroma-Rauschenfrequenz + Intensität des Chroma-Rauschens + Details zum Chroma-Rauschen + Schneeintensität + Schneeanisotropie + Chroma-Phasenrauschen + Chroma-Phasenfehler + Horizontale Chroma-Verzögerung + Chroma-Verzögerung vertikal + VHS-Bandgeschwindigkeit + VHS-Chromaverlust + VHS-Schärfintensität + VHS-Schärffrequenz + VHS-Randwellenintensität + VHS-Randwellengeschwindigkeit + VHS-Randwellenfrequenz + VHS-Randwellendetail + Ausgabe-Chroma-Tiefpass + Horizontal skalieren + Vertikal skalieren + Skalierungsfaktor X + Skalierungsfaktor Y + Erkennt gängige Bildwasserzeichen und malt sie mit LaMa ein. Lädt Erkennungs- und Inpainting-Modelle automatisch herunter + Deuteranopie + Bild erweitern + Objektsegmentierungsbasierter Hintergrundentferner mit YOLO v11-Segmentierung + Linienwinkel anzeigen + Zeigt beim Zeichnen die aktuelle Liniendrehung in Grad an + Animierte Emojis + Verfügbare Emojis als Animationen anzeigen + Im Originalordner speichern + Speichern Sie neue Dateien neben der Originaldatei statt im ausgewählten Ordner + Wasserzeichen-PDF + Nicht genügend Speicher + Das Bild ist wahrscheinlich zu groß, um es auf diesem Gerät zu verarbeiten, oder das System verfügt nicht über genügend Speicher. Versuchen Sie, die Bildauflösung zu reduzieren, andere Apps zu schließen oder eine kleinere Datei auszuwählen. + Debug-Menü + Menü zum Testen von App-Funktionen. Dies soll nicht in der Produktionsversion angezeigt werden + Anbieter + PaddleOCR erfordert zusätzliche ONNX-Modelle auf Ihrem Gerät. Möchten Sie %1$s-Daten herunterladen? + Universal + Koreanisch + lateinisch + Ostslawisch + Thailändisch + griechisch + Englisch + kyrillisch + Arabisch + Devanagari + Tamilisch + Telugu + Shader + Shader-Voreinstellung + Kein Shader ausgewählt + Shader Studio + Erstellen, bearbeiten, validieren, importieren und exportieren Sie benutzerdefinierte Fragment-Shader + Gespeicherte Shader + Öffnen Sie gespeicherte Shader, duplizieren, exportieren, teilen oder löschen Sie + Neuer Shader + Shader-Datei + .itshader JSON + %1$d Parameter + Shader-Quelle + Schreiben Sie nur den Hauptteil von void main(). Verwenden Sie „textureCoordinate“ für UV-Koordinaten und „inputImageTexture“ als Quellsampler. + Funktionen + Optionale Hilfsfunktionen, Konstanten und Strukturen, die vor void main() eingefügt werden. Uniformen werden aus Parametern generiert. + Fügen Sie hier Uniformen hinzu, wenn der Shader bearbeitbare Werte benötigt. + Shader zurücksetzen + Der aktuelle Shader-Entwurf wird gelöscht + Ungültiger Shader + Nicht unterstützte Shader-Version %1$d. Unterstützte Version: %2$d. + Der Shader-Name darf nicht leer sein. + Die Shader-Quelle darf nicht leer sein. + Die Shader-Quelle enthält nicht unterstützte Zeichen: %1$s. Verwenden Sie nur die ASCII-GLSL-Quelle. + Der Parameter „%1$s“ wird mehr als einmal deklariert. + Parameternamen dürfen nicht leer sein. + Der Parameter „%1$s“ verwendet einen reservierten GPUImage-Namen. + Der Parameter „%1$s“ muss ein gültiger GLSL-Bezeichner sein. + Parameter „%1$s“ hat den %2$s-Werttyp „%3$s“, erwarteter Wert „%4$s“. + Der Parameter „%1$s“ kann weder Min noch Max für Bool-Werte definieren. + Parameter „%1$s“ hat Min. größer als Max. + Der Standardwert des Parameters „%1$s“ liegt unter dem Mindestwert. + Der Standardwert des Parameters „%1$s“ ist größer als max. + Der Shader muss „uniform sampler2D %1$s;“ deklarieren. + Der Shader muss „varying vec2 %1$s;“ deklarieren. + Der Shader muss „void main()“ definieren. + Der Shader muss eine Farbe in gl_FragColor schreiben. + ShaderToy mainImage-Shader werden nicht unterstützt. Verwenden Sie den void main()-Vertrag von GPUImage. + Die animierte ShaderToy-Uniform „iTime“ wird nicht unterstützt. + Animiertes ShaderToy-Uniform „iFrame“ wird nicht unterstützt. + Die ShaderToy-Uniform „iResolution“ wird nicht unterstützt. + ShaderToy iChannel-Texturen werden nicht unterstützt. + Externe Texturen werden nicht unterstützt. + Externe Texturerweiterungen werden nicht unterstützt. + Libretro-Shader-Parameter werden nicht unterstützt. + Es wird nur eine Eingabetextur unterstützt. Entfernen Sie \\\"einheitlich %1$s %2$s;\\\". + Der Parameter „%1$s“ muss als „uniform %2$s %1$s;“ deklariert werden. + Parameter „%1$s“ wird als „uniform %2$s %1$s; deklariert, erwartet als „uniform %3$s %1$s;. + Shader-Datei ist leer. + Die Shader-Datei muss ein .itshader-JSON-Objekt mit Versions-, Namens- und Shader-Feldern sein. + Die Shader-Datei ist kein gültiges JSON oder entspricht nicht dem .itshader-Format. + Das Feld „%1$s“ ist erforderlich. + %1$s ist erforderlich. + %1$s \\\"%2$s\\\" wird nicht unterstützt. Unterstützte Typen: %3$s. + %1$s ist für die Parameter „%2$s“ erforderlich. + %1$s muss eine endliche Zahl sein. + %1$s muss eine Ganzzahl sein. + %1$s muss wahr oder falsch sein. + %1$s muss eine Farbzeichenfolge, ein RGB/RGBA-Array oder ein Farbobjekt sein. + %1$s muss das Format #RRGGBB oder #RRGGBBAA verwenden. + %1$s muss gültige hexadezimale Farbkanäle enthalten. + %1$s muss 3 oder 4 Farbkanäle enthalten. + %1$s muss zwischen 0 und 255 liegen. + %1$s muss ein Array mit zwei Zahlen oder ein Objekt mit x und y sein. + %1$s muss genau 2 Zahlen enthalten. + Duplikat + EXIF immer löschen + Entfernen Sie die EXIF-Daten des Bildes beim Speichern, auch wenn ein Tool die Beibehaltung von Metadaten anfordert + Hilfe und Tipps + %1$d Tutorials + Werkzeug öffnen + Lernen Sie die wichtigsten Tools, Exportoptionen, PDF-Abläufe, Farbdienstprogramme und Lösungen für häufige Probleme kennen + Erste Schritte + Wählen Sie Tools aus, importieren Sie Dateien, speichern Sie Ergebnisse und verwenden Sie Einstellungen schneller wieder + Bildbearbeitung + Ändern Sie die Größe, schneiden Sie Bilder zu, filtern Sie sie, löschen Sie Hintergründe, zeichnen Sie Bilder und versehen Sie sie mit Wasserzeichen + Dateien und Metadaten + Konvertieren Sie Formate, vergleichen Sie die Ausgabe, schützen Sie die Privatsphäre und kontrollieren Sie Dateinamen + PDF und Dokumente + Erstellen Sie PDFs, scannen Sie Dokumente, OCR-Seiten und bereiten Sie Dateien für die Freigabe vor + Text, QR und Daten + Erkennen Sie Text, scannen Sie Codes, kodieren Sie Base64, überprüfen Sie Hashes und verpacken Sie Dateien + Farbwerkzeuge + Wählen Sie Farben aus, erstellen Sie Paletten, erkunden Sie Farbbibliotheken und erstellen Sie Farbverläufe + Kreative Werkzeuge + Generieren Sie SVG-, ASCII-Grafiken, Rauschtexturen, Shader und experimentelle Grafiken + Fehlerbehebung + Beheben Sie umfangreiche Datei-, Transparenz-, Freigabe-, Import- und Berichtsprobleme + Wählen Sie das richtige Werkzeug + Verwenden Sie Kategorien, Suche, Favoriten und Freigabeaktionen, um an der richtigen Stelle zu beginnen + Beginnen Sie am besten Einstiegspunkt + Image Toolbox verfügt über viele gezielte Tools, von denen sich viele auf den ersten Blick überschneiden. Beginnen Sie mit der Aufgabe, die Sie erledigen möchten, und wählen Sie dann das Tool aus, das den wichtigsten Teil des Ergebnisses steuert: Größe, Format, Metadaten, Text, PDF-Seiten, Farben oder Layout. + Verwenden Sie die Suche, wenn Sie den Aktionsnamen kennen, z. B. Größenänderung, PDF, EXIF, OCR, QR oder Farbe. + Öffnen Sie eine Kategorie, wenn Sie nur den Aufgabentyp kennen, und heften Sie dann häufig verwendete Tools als Favoriten an. + Wenn eine andere App ein Bild in die Image Toolbox teilt, wähle das Tool aus dem Freigabeblatt-Flow aus. + Ergebnisse importieren, speichern und teilen + Verstehen Sie den üblichen Ablauf von der Auswahl der Eingabe bis zum Exportieren der bearbeiteten Datei + Befolgen Sie den allgemeinen Bearbeitungsablauf + Die meisten Tools folgen demselben Rhythmus: Eingabe auswählen, Optionen anpassen, Vorschau anzeigen und dann speichern oder teilen. Das wichtige Detail ist, dass beim Speichern eine Ausgabedatei erstellt wird, während das Teilen normalerweise am besten für eine schnelle Übergabe an eine andere App geeignet ist. + Wähle eine Datei für Einzelbild-Tools oder mehrere Dateien für Batch-Tools aus, wenn die Auswahl dies zulässt. + Überprüfen Sie vor dem Speichern die Vorschau- und Ausgabesteuerung, insbesondere die Optionen für Format, Qualität und Dateinamen. + Verwenden Sie die Freigabe für eine schnelle Übergabe oder speichern Sie, wenn Sie eine dauerhafte Kopie im ausgewählten Ordner benötigen. + Arbeiten Sie mit vielen Bildern gleichzeitig + Batch-Tools eignen sich am besten für wiederholte Größenänderungs-, Konvertierungs-, Komprimierungs- und Benennungsaufgaben + Bereiten Sie eine vorhersehbare Charge vor + Die Stapelverarbeitung ist am schnellsten, wenn jede Ausgabe denselben Regeln folgen sollte. Es ist auch der einfachste Ort, um einen großen Fehler zu machen. Wählen Sie daher Namen, Qualität, Metadaten und Ordnerverhalten aus, bevor Sie mit einem langen Export beginnen. + Wählen Sie alle zugehörigen Bilder zusammen aus und behalten Sie die Reihenfolge bei, wenn die Ausgabe von der Reihenfolge abhängt. + Legen Sie die Regeln für Größe, Format, Qualität, Metadaten und Dateinamen einmal fest, bevor Sie mit dem Export beginnen. + Führen Sie zunächst einen kleinen Teststapel aus, wenn die Quelldateien groß sind oder das Zielformat für Sie neu ist. + Schnelle Einzelbearbeitung + Verwenden Sie den All-in-One-Editor, wenn Sie mehrere einfache Änderungen an einem Bild benötigen + Beenden Sie ein Bild ohne Tool-Hopping + „Einzelbearbeitung“ ist nützlich, wenn das Bild eine kleine Kette von Änderungen anstelle eines speziellen Vorgangs benötigt. Normalerweise geht es schneller, wenn Sie schnell bereinigen, eine Vorschau anzeigen und eine endgültige Kopie exportieren möchten, ohne einen Batch-Workflow erstellen zu müssen. + Öffnen Sie „Einzelbearbeitung“ für ein Bild, das allgemeine Anpassungen, Markierungen, Zuschnitte, Drehungen oder Exportänderungen benötigt. + Wenden Sie Änderungen in der Reihenfolge an, in der zuerst die wenigsten Pixel geändert werden, z. B. Zuschneiden vor Filtern und Filter vor endgültiger Komprimierung. + Verwenden Sie stattdessen ein spezielles Tool, wenn Sie Stapelverarbeitung, genaue Dateigrößenziele, PDF-Ausgabe, OCR oder Metadatenbereinigung benötigen. + Verwenden Sie Voreinstellungen und Einstellungen + Speichern Sie wiederholte Auswahlmöglichkeiten und passen Sie die App an Ihren bevorzugten Arbeitsablauf an + Vermeiden Sie es, dasselbe Setup zu wiederholen + Voreinstellungen und Einstellungen sind nützlich, wenn Sie häufig dieselbe Art von Datei exportieren. Eine gute Voreinstellung verwandelt eine wiederholte manuelle Checkliste in einen einzigen Tastendruck, während globale Einstellungen die Standardeinstellungen definieren, mit denen neue Tools beginnen. + Erstellen Sie Voreinstellungen für gängige Ausgabegrößen, Formate, Qualitätswerte oder Benennungsmuster. + Überprüfen Sie die Einstellungen für Standardformat, Qualität, Speicherordner, Dateinamen, Auswahl und Schnittstellenverhalten. + Sichern Sie die Einstellungen, bevor Sie die App neu installieren oder auf ein anderes Gerät wechseln. + Legen Sie nützliche Standardeinstellungen fest + Wählen Sie einmal das Standardformat, die Qualität, den Skalierungsmodus, den Größenänderungstyp und den Farbraum + Bringen Sie neue Tools näher an Ihr Ziel heran + Standardwerte sind nicht nur kosmetische Präferenzen. Sie entscheiden, welches Format, welche Qualität, welches Größenänderungsverhalten, welcher Skalierungsmodus und welcher Farbraum in vielen Tools zuerst angezeigt werden, wodurch vermieden wird, dass bei jedem Export dieselben Optionen erneut ausgewählt werden müssen. + Stellen Sie das Standardbildformat und die Standardqualität so ein, dass sie Ihrem üblichen Ziel entsprechen, z. B. JPEG für Fotos oder PNG/WebP für Alpha. + Optimieren Sie den standardmäßigen Größenänderungstyp und Skalierungsmodus, wenn Sie häufig für strenge Abmessungen, soziale Plattformen oder Dokumentseiten exportieren. + Ändern Sie die Standardeinstellungen für den Farbraum nur, wenn Ihr Workflow eine konsistente Farbverarbeitung auf allen Displays, im Druck oder in externen Editoren erfordert. + Picker und Launcher + Verwenden Sie Auswahleinstellungen, wenn die App zu viele Dialoge öffnet oder an der falschen Stelle startet + Passen Sie das Öffnen von Dateien an Ihre Gewohnheit an + Die Auswahl- und Starteinstellungen steuern, ob Image Toolbox vom Hauptbildschirm, einem Tool oder einem Dateiauswahlablauf aus gestartet wird. Diese Optionen sind besonders nützlich, wenn du normalerweise über das Android-Freigabemenü zugreifst oder wenn du möchtest, dass sich die App eher wie ein Tool-Launcher anfühlt. + Verwenden Sie die Fotoauswahleinstellungen, wenn die Systemauswahl Dateien verbirgt, zu viele aktuelle Elemente anzeigt oder Cloud-Dateien schlecht verarbeitet. + Aktivieren Sie die Option „Auswahl überspringen“ nur für Tools, bei denen Sie normalerweise aus der Freigabe eingeben oder die Quelldatei bereits kennen. + Sieh dir den Launcher-Modus an, wenn du möchtest, dass sich Image Toolbox eher wie eine Werkzeugauswahl als wie ein normaler Startbildschirm verhält. + Bildquelle + Wähle den Auswahlmodus, der am besten mit Galerien, Dateimanagern und Cloud-Dateien funktioniert + Wähle Dateien aus der richtigen Quelle aus + Die Einstellungen für die Bildquelle beeinflussen, wie die App Android nach Bildern fragt. Die beste Wahl hängt davon ab, ob sich Ihre Dateien in der Systemgalerie, einem Dateimanagerordner, einem Cloud-Anbieter oder einer anderen App befinden, die temporäre Inhalte teilt. + Verwende die Standardauswahl, wenn du für gängige Galeriebilder ein möglichst systemintegriertes Erlebnis wünschst. + Wechsel den Auswahlmodus, wenn Alben, Ordner, Cloud-Dateien oder nicht standardmäßige Formate nicht dort angezeigt werden, wo du es erwartest. + Wenn eine freigegebene Datei nach dem Verlassen der Quell-App verschwindet, öffne sie erneut über eine dauerhafte Auswahl oder speichere zunächst eine lokale Kopie. + Favoriten und Tools zum Teilen + Behalten Sie den Fokus des Hauptbildschirms bei und blenden Sie Tools aus, die in Freigabeflüssen keinen Sinn ergeben + Reduzieren Sie das Rauschen der Werkzeugliste + Favoriten und zum Teilen ausgeblendete Einstellungen sind nützlich, wenn Sie täglich nur wenige Tools verwenden. Außerdem halten sie den Freigabefluss praktisch, indem sie Tools ausblenden, die den von einer anderen App gesendeten Dateityp nicht verwenden können. + Heften Sie häufig verwendete Werkzeuge an, damit sie auch dann leicht zu erreichen sind, wenn die Werkzeuge nach Kategorien gruppiert sind. + Blenden Sie Tools aus Freigabeflüssen aus, wenn sie für Dateien aus einer anderen App irrelevant sind. + Verwenden Sie die Sortiereinstellungen für Favoriten, wenn Sie Favoriten am Ende oder gruppiert mit zugehörigen Tools bevorzugen. + Einstellungen sichern + Verschieben Sie Voreinstellungen, Einstellungen und App-Verhalten sicher in eine andere Installation + Halten Sie Ihr Setup portabel + „Sichern und Wiederherstellen“ ist am hilfreichsten, nachdem Sie Voreinstellungen, Ordner, Dateinamensregeln, Werkzeugreihenfolge und visuelle Einstellungen angepasst haben. Mit einem Backup können Sie mit Einstellungen experimentieren oder die App neu installieren, ohne Ihren Workflow aus dem Speicher neu erstellen zu müssen. + Erstellen Sie ein Backup, nachdem Sie Voreinstellungen, Dateinamenverhalten, Standardformate oder Werkzeuganordnung geändert haben. + Speichern Sie das Backup irgendwo außerhalb des App-Ordners, wenn Sie Daten löschen, Geräte neu installieren oder verschieben möchten. + Stellen Sie die Einstellungen wieder her, bevor Sie wichtige Stapel verarbeiten, damit die Standardeinstellungen und das Speicherverhalten mit Ihrem alten Setup übereinstimmen. + Ändern Sie die Größe und konvertieren Sie Bilder + Ändern Sie Größe, Format, Qualität und Metadaten für ein oder mehrere Bilder + Erstellen Sie verkleinerte Kopien + Resize and Convert ist das Hauptwerkzeug für vorhersehbare Abmessungen und kleinere Ausgabedateien. Verwenden Sie es, wenn die Größe des Bildes, das Ausgabeformat und das Metadatenverhalten gleichzeitig wichtig sind. + Wähle ein oder mehrere Bilder aus und entscheide dann, ob Abmessungen, Maßstab oder Dateigröße am wichtigsten sind. + Wählen Sie das Ausgabeformat und die Qualität aus. Verwenden Sie PNG oder WebP, wenn die Transparenz erhalten bleiben muss. + Zeigen Sie eine Vorschau des Ergebnisses an und speichern oder teilen Sie dann die erstellten Kopien, ohne die Originale zu ändern. + Größe nach Dateigröße ändern + Legen Sie eine Größenbeschränkung fest, wenn eine Website, ein Messenger oder ein Formular umfangreiche Bilder ablehnt + Setzen Sie strenge Upload-Limits ein + Die Größenänderung anhand der Dateigröße ist besser als das Erraten von Qualitätswerten, wenn das Ziel nur Dateien akzeptiert, die unter einem bestimmten Grenzwert liegen. Das Tool passt möglicherweise die Komprimierung und die Abmessungen an, um das Ziel zu erreichen und gleichzeitig zu versuchen, das Ergebnis verwendbar zu halten. + Geben Sie die Zielgröße etwas unterhalb des tatsächlichen Upload-Limits ein, um Spielraum für Metadaten und Anbieteränderungen zu lassen. + Bevorzugen Sie verlustbehaftete Formate für Fotos, wenn das Ziel klein ist; PNG kann auch nach einer Größenänderung groß bleiben. + Überprüfen Sie Flächen, Text und Kanten nach dem Export, da Ziele mit aggressiver Größe zu sichtbaren Artefakten führen können. + Begrenzen Sie die Größenänderung + Verkleinern Sie nur Bilder, die Ihre maximale Breite, Höhe oder Dateibeschränkungen überschreiten + Vermeiden Sie es, die Größe von Bildern zu ändern, die bereits in Ordnung sind + Die Option „Größenänderung begrenzen“ ist für gemischte Ordner nützlich, in denen einige Bilder riesig sind und andere bereits vorbereitet sind. Anstatt die gleiche Größenänderung für jede Datei zu erzwingen, bleiben akzeptable Bilder näher an ihrer Originalqualität. + Legen Sie maximale Abmessungen oder Grenzwerte basierend auf dem Ziel fest, anstatt die Größe jeder Datei blind zu ändern. + Aktivieren Sie das Stilverhalten „Bei größerer Größe überspringen“, wenn Sie Ausgaben vermeiden möchten, die schwerer als Quellen werden. + Verwenden Sie dies für Stapel von verschiedenen Kameras, Screenshots oder Downloads, bei denen die Quellgrößen stark variieren. + Zuschneiden, drehen und begradigen + Rahmen Sie den wichtigen Bereich ein, bevor Sie das Bild exportieren oder in anderen Tools verwenden + Bereinigen Sie die Komposition + Wenn Sie zuerst zuschneiden, werden spätere Filter, OCR, PDF-Seiten und die Freigabeergebnisse sauberer. + Öffnen Sie Zuschneiden und wählen Sie das Bild aus, das Sie anpassen möchten. + Verwenden Sie den freien Zuschnitt oder ein Seitenverhältnis, wenn die Ausgabe in einen Beitrag, ein Dokument oder einen Avatar passen muss. + Vor dem Speichern drehen oder spiegeln, damit spätere Werkzeuge das korrigierte Bild erhalten. + Wenden Sie Filter und Anpassungen an + Erstellen Sie einen bearbeitbaren Filterstapel und zeigen Sie vor dem Export eine Vorschau des Ergebnisses an + Optimieren Sie das Erscheinungsbild des Bildes + Filter sind nützlich für schnelle Korrekturen, eine stilisierte Ausgabe und die Vorbereitung von Bildern für OCR oder den Druck. Wenn das Bild Text oder feine Details enthält, können kleine Änderungen an Kontrast, Schärfe und Helligkeit wichtiger sein als große Effekte. + Fügen Sie nacheinander Filter hinzu und behalten Sie nach jeder Änderung die Vorschau im Auge. + Passen Sie Helligkeit, Kontrast, Schärfe, Unschärfe, Farbe oder Masken je nach Aufgabe an. + Exportieren Sie nur, wenn die Vorschau mit Ihrem Zielgerät, Dokument oder Ihrer sozialen Plattform übereinstimmt. + Zuerst Vorschau + Überprüfen Sie die Bilder, bevor Sie ein umfangreicheres Bearbeitungs-, Konvertierungs- oder Bereinigungstool auswählen + Überprüfen Sie, was Sie tatsächlich erhalten haben + Die Bildvorschau ist nützlich, wenn eine Datei aus einem Chat, einem Cloud-Speicher oder einer anderen App stammt und Sie nicht sicher sind, was sie enthält. Die Prüfung von Abmessungen, Transparenz, Ausrichtung und visueller Qualität hilft zunächst bei der Auswahl des richtigen Folgewerkzeugs. + Öffnen Sie die Bildvorschau, wenn Sie mehrere Bilder prüfen müssen, bevor Sie entscheiden, was mit ihnen geschehen soll. + Suchen Sie nach Rotationsproblemen, transparenten Bereichen, Komprimierungsartefakten und unerwartet großen Abmessungen. + Gehen Sie erst dann zu „Größe ändern“, „Zuschneiden“, „Vergleichen“, „EXIF“ oder „Hintergrund entfernen“, wenn Sie wissen, was korrigiert werden muss. + Entfernen Sie einen Hintergrund oder stellen Sie ihn wieder her + Löschen Sie Hintergründe automatisch oder verfeinern Sie Kanten manuell mit Wiederherstellungstools + Behalten Sie das Motiv, entfernen Sie den Rest + Die Hintergrundentfernung funktioniert am besten, wenn Sie die automatische Auswahl mit einer sorgfältigen manuellen Bereinigung kombinieren. Das endgültige Exportformat ist genauso wichtig wie die Maske, da JPEG transparente Bereiche reduziert, selbst wenn die Vorschau korrekt aussieht. + Beginnen Sie mit der automatischen Entfernung, wenn das Motiv klar vom Hintergrund getrennt ist. + Zoomen Sie hinein und wechseln Sie zwischen Löschen und Wiederherstellen, um Haare, Ecken, Schatten und kleine Details zu korrigieren. + Speichern Sie als PNG oder WebP, wenn Sie in der endgültigen Datei Transparenz benötigen. + Zeichnen, markieren und mit Wasserzeichen versehen + Fügen Sie Pfeile, Notizen, Hervorhebungen, Signaturen oder wiederholte Wasserzeichenüberlagerungen hinzu + Fügen Sie sichtbare Informationen hinzu + Markup-Tools helfen dabei, ein Bild zu erklären, während Wasserzeichen dabei helfen, exportierte Dateien zu kennzeichnen oder zu schützen. + Verwenden Sie Draw, wenn Sie Freihandnotizen, Formen, Hervorhebungen oder schnelle Anmerkungen benötigen. + Verwenden Sie Wasserzeichen, wenn dieselbe Text- oder Bildmarkierung konsistent auf Ausgaben erscheinen soll. + Überprüfen Sie vor dem Exportieren die Deckkraft, Position und Skalierung, damit die Markierung sichtbar, aber nicht störend ist. + Standardwerte zeichnen + Legen Sie Linienbreite, Farbe, Pfadmodus, Ausrichtungssperre und Lupe fest, um die Markierung zu beschleunigen + Sorgen Sie dafür, dass sich das Zeichnen vorhersehbar anfühlt + Zeicheneinstellungen sind hilfreich, wenn Sie häufig Screenshots mit Anmerkungen versehen, Dokumente markieren oder Bilder signieren. Standardeinstellungen verkürzen die Einrichtungszeit, während die Lupe und die Ausrichtungssperre präzise Bearbeitungen auf kleinen Bildschirmen erleichtern. + Legen Sie die Standardlinienbreite und die Zeichenfarbe auf die Werte fest, die Sie am häufigsten für Pfeile, Hervorhebungen oder Signaturen verwenden. + Verwenden Sie den Standardpfadmodus, wenn Sie normalerweise die gleichen Striche, Formen oder Markierungen zeichnen. + Aktivieren Sie die Lupe oder die Ausrichtungssperre, wenn die Fingereingabe es schwierig macht, kleine Details genau zu platzieren. + Mehrbild-Layouts + Wählen Sie das richtige Multi-Image-Tool aus, bevor Sie Screenshots, Scans oder Vergleichsstreifen anordnen + Verwenden Sie das richtige Layout-Tool + Diese Tools klingen ähnlich, aber jedes ist auf eine andere Art der Mehrbildausgabe abgestimmt. Wenn Sie sich zunächst für die richtige Variante entscheiden, vermeiden Sie den Kampf mit Layout-Steuerelementen, die für ein anderes Ergebnis konzipiert wurden. + Verwenden Sie Collage Maker für gestaltete Layouts mit mehreren Bildern in einer Komposition. + Verwenden Sie Bildzusammenfügung oder Stapelung, wenn Bilder zu einem langen vertikalen oder horizontalen Streifen werden sollen. + Verwenden Sie die Bildaufteilung, wenn ein großes Bild in mehrere kleinere Teile zerlegt werden muss. + Bildformate konvertieren + Erstellen Sie JPEG-, PNG-, WebP-, AVIF-, JXL- und andere Kopien, ohne Pixel manuell zu bearbeiten + Wählen Sie das Format für den Job aus + Unterschiedliche Formate eignen sich besser für Fotos, Screenshots, Transparenz, Komprimierung oder Kompatibilität. Die Konvertierung ist am sichersten, wenn Sie wissen, was die Ziel-App unterstützt und ob die Quelle Alpha-, Animations- oder Metadaten enthält, die Sie behalten möchten. + Verwenden Sie JPEG für kompatible Fotos, PNG für verlustfreie Bilder und Alpha und WebP für die kompakte Weitergabe. + Passen Sie die Qualität an, wenn das Format dies unterstützt; Niedrigere Werte verringern die Größe, können aber zu Artefakten führen. + Bewahren Sie die Originale auf, bis Sie überprüft haben, ob die konvertierten Dateien in der Ziel-App ordnungsgemäß geöffnet werden. + Überprüfen Sie EXIF ​​und Datenschutz + Zeigen Sie Metadaten an, bearbeiten oder entfernen Sie sie, bevor Sie vertrauliche Fotos freigeben + Kontrollieren Sie versteckte Bilddaten + EXIF kann Kameradaten, Daten, Standort, Ausrichtung und andere Details enthalten, die im Bild nicht sichtbar sind. Es lohnt sich, dies zu überprüfen, bevor Sie Support-Berichte, Marktplatz-Einträge oder Fotos, die einen privaten Ort offenbaren könnten, öffentlich teilen. + Öffnen Sie die EXIF-Tools, bevor Sie Fotos freigeben, die möglicherweise Standort- oder private Geräteinformationen enthalten. + Entfernen Sie vertrauliche Felder oder bearbeiten Sie falsche Tags wie Datum, Ausrichtung, Autor oder Kameradaten. + Speichern Sie eine neue Kopie und teilen Sie diese Kopie anstelle des Originals, wenn der Datenschutz wichtig ist. + Automatische EXIF-Bereinigung + Entfernen Sie standardmäßig Metadaten, während Sie entscheiden, ob Daten beibehalten werden sollen + Machen Sie den Datenschutz zur Standardeinstellung + Die EXIF-Einstellungsgruppe ist nützlich, wenn Sie möchten, dass die Datenschutzbereinigung automatisch erfolgt, anstatt sie sich bei jedem Export zu merken. „Datum und Uhrzeit beibehalten“ ist eine separate Entscheidung, da Datumsangaben für die Sortierung nützlich, für die Weitergabe jedoch kritisch sein können. + Aktivieren Sie immer klares EXIF, wenn die meisten Ihrer Exporte für die öffentliche oder halböffentliche Freigabe bestimmt sind. + Aktivieren Sie „Datum und Uhrzeit beibehalten“ nur, wenn die Beibehaltung der Aufnahmezeit wichtiger ist als das Entfernen jedes Zeitstempels. + Verwenden Sie die manuelle EXIF-Bearbeitung für einmalige Dateien, bei denen Sie einige Felder behalten und andere entfernen müssen. + Kontrollieren Sie Dateinamen + Verwenden Sie Präfixe, Suffixe, Zeitstempel, Voreinstellungen, Prüfsummen und Sequenznummern + Machen Sie Exporte leicht auffindbar + Ein gutes Dateinamenmuster hilft beim Sortieren von Stapeln und verhindert versehentliches Überschreiben. Dateinameneinstellungen sind besonders nützlich, wenn Exporte zurück in den Quellordner erfolgen, wo ähnliche Namen schnell verwirrend werden können. + Legen Sie in den Einstellungen Dateinamenpräfix, Suffix, Zeitstempel, Originalnamen, voreingestellte Informationen oder Sequenzoptionen fest. + Verwenden Sie Sequenznummern für Stapel, bei denen die Reihenfolge wichtig ist, z. B. Seiten, Rahmen oder Collagen. + Aktivieren Sie das Überschreiben nur, wenn Sie sicher sind, dass das Ersetzen vorhandener Dateien für den aktuellen Ordner sicher ist. + Dateien überschreiben + Ersetzen Sie Ausgaben nur dann durch übereinstimmende Namen, wenn Ihr Workflow absichtlich destruktiv ist + Verwenden Sie den Überschreibmodus sorgfältig + „Dateien überschreiben“ ändert die Art und Weise, wie Namenskonflikte gehandhabt werden. Dies ist nützlich für wiederholte Exporte in denselben Ordner, kann aber auch frühere Ergebnisse ersetzen, wenn Ihr Dateinamenmuster erneut denselben Namen erzeugt. + Aktivieren Sie das Überschreiben nur für Ordner, bei denen das Ersetzen älterer generierter Dateien erwartet und wiederherstellbar ist. + Lassen Sie das Überschreiben deaktiviert, wenn Sie Originale, Clientdateien, einmalige Scans oder alles ohne Backup exportieren. + Kombinieren Sie das Überschreiben mit einem bewussten Dateinamenmuster, sodass nur die beabsichtigten Ausgaben ersetzt werden. + Dateinamenmuster + Kombinieren Sie Originalnamen, Präfixe, Suffixe, Zeitstempel, Voreinstellungen, Skalierungsmodus und Prüfsummen + Erstellen Sie Namen, die die Ausgabe erklären + Dateinamensmustereinstellungen können aus exportierten Dateien einen nützlichen Verlauf dessen machen, was mit ihnen passiert ist. Dies ist bei Stapelverarbeitungen wichtig, da die Ordneransicht möglicherweise der einzige Ort ist, an dem Sie Originalgröße, Voreinstellung, Format und Verarbeitungsreihenfolge unterscheiden können. + Verwenden Sie den ursprünglichen Dateinamen, das Präfix, das Suffix und den Zeitstempel, wenn Sie lesbare Namen für die manuelle Sortierung benötigen. + Fügen Sie Informationen zu Voreinstellungen, Skalierungsmodus, Dateigröße oder Prüfsumme hinzu, wenn Ausgaben dokumentieren müssen, wie sie erstellt wurden. + Vermeiden Sie zufällige Namen für Arbeitsabläufe, bei denen Dateien mit Originalen oder der Seitenreihenfolge verknüpft bleiben müssen. + Wählen Sie, wo Dateien gespeichert werden + Vermeiden Sie den Verlust von Exporten, indem Sie Ordner festlegen, Originalordner speichern und einmalige Speicherorte festlegen + Halten Sie die Ergebnisse vorhersehbar + Das Speicherverhalten ist am wichtigsten, wenn Sie viele Dateien verarbeiten oder Dateien von Cloud-Anbietern teilen. Ordnereinstellungen entscheiden darüber, ob Ausgaben an einem bekannten Ort gesammelt werden, neben den Originalen angezeigt werden oder für eine bestimmte Aufgabe vorübergehend an einen anderen Ort verschoben werden. + Legen Sie einen Standardspeicherordner fest, wenn Sie Exporte immer an einem Ort wünschen. + Verwenden Sie „im Originalordner speichern“ für Bereinigungsworkflows, bei denen die Ausgabe in der Nähe der Quelldatei bleiben soll. + Verwenden Sie einen einmaligen Speicherort, wenn eine einzelne Aufgabe ein anderes Ziel benötigt, ohne Ihren Standardspeicherort zu ändern. + Einmalige Speicherung von Ordnern + Senden Sie die nächsten Exporte an einen temporären Ordner, ohne Ihr normales Ziel zu ändern + Halten Sie Sonderaufgaben getrennt + Ein einmaliger Speicherort ist nützlich für temporäre Projekte, Kundenordner, Bereinigungssitzungen oder Exporte, die nicht mit Ihrem regulären Image Toolbox-Ordner vermischt werden sollen. Es gibt ein kurzlebiges Ziel, ohne Ihr Standard-Setup neu zu schreiben. + Wählen Sie einen einmaligen Ordner aus, bevor Sie eine Aufgabe starten, die außerhalb Ihres normalen Exportspeicherorts liegt. + Verwenden Sie es für kurze Projekte, freigegebene Ordner oder Stapel, bei denen die Ausgaben gruppiert bleiben müssen. + Kehren Sie nach dem Auftrag zum Standardordner zurück, damit spätere Exporte nicht unerwartet am temporären Speicherort landen. + Balance zwischen Qualität und Dateigröße + Verstehen Sie, wann Abmessungen, Format oder Qualität geändert werden müssen, anstatt zu raten + Dateien absichtlich verkleinern + Die Dateigröße hängt von den Bildabmessungen, dem Format, der Qualität, der Transparenz und der Komplexität des Inhalts ab. Wenn eine Datei immer noch zu schwer ist, hilft in der Regel eine Änderung der Abmessungen mehr als eine wiederholte Verschlechterung der Qualität. + Ändern Sie die Größe zuerst, wenn das Bild größer ist, als das Ziel anzeigen kann. + Geringere Qualität nur für verlustbehaftete Formate und Überprüfung von Text, Kanten, Verläufen und Flächen nach dem Export. + Wechseln Sie das Format, wenn Qualitätsänderungen nicht ausreichen, z. B. WebP zum Teilen oder PNG für gestochen scharfe transparente Assets. + Arbeiten Sie mit animierten Formaten + Verwenden Sie GIF-, APNG-, WebP- und JXL-Tools, wenn eine Datei Frames anstelle einer Bitmap enthält + Behandeln Sie nicht jedes Bild als Standbild + Animierte Formate benötigen Tools, die Frames, Dauer und Kompatibilität verstehen. + Verwenden Sie GIF- oder APNG-Tools, wenn Sie für diese Formate Vorgänge auf Frame-Ebene benötigen. + Verwenden Sie WebP-Tools, wenn Sie eine moderne Komprimierung oder eine animierte WebP-Ausgabe benötigen. + Zeigen Sie vor dem Teilen eine Vorschau des Animations-Timings an, da einige Plattformen animierte Bilder konvertieren oder reduzieren. + Vergleichen Sie die Ausgabe und zeigen Sie eine Vorschau an + Überprüfen Sie Änderungen, Dateigröße und visuelle Unterschiede, bevor Sie einen Export durchführen + Vor dem Teilen überprüfen + Vergleichstools helfen dabei, Komprimierungsartefakte, falsche Farben oder unerwünschte Bearbeitungen frühzeitig zu erkennen. + Öffnen Sie „Vergleichen“, wenn Sie zwei Versionen nebeneinander prüfen möchten. + Zoomen Sie in Kanten, Text, Verläufe und transparente Bereiche, in denen Probleme am leichtesten zu übersehen sind. + Wenn die Ausgabe zu schwer oder verschwommen ist, kehren Sie zum vorherigen Tool zurück und passen Sie Qualität oder Format an. + Nutzen Sie den PDF-Tools-Hub + PDF-Dateien zusammenführen, teilen, drehen, Seiten entfernen, komprimieren, schützen, mit OCR versehen und mit Wasserzeichen versehen + Wählen Sie einen PDF-Vorgang + Der PDF-Hub gruppiert Dokumentaktionen hinter einem Einstiegspunkt, sodass Sie den genauen Vorgang auswählen können. Beginnen Sie dort, wenn es sich bei der Datei bereits um eine PDF-Datei handelt, und verwenden Sie „Bilder in PDF“ oder „Dokumentenscanner“, wenn es sich bei Ihrer Quelle noch um eine Reihe von Bildern handelt. + Öffnen Sie PDF Tools und wählen Sie den Vorgang aus, der Ihrem Ziel entspricht. + Wählen Sie das Quell-PDF oder die Quellbilder aus und überprüfen Sie dann die Optionen für Seitenreihenfolge, Drehung, Schutz oder OCR. + Speichern Sie das generierte PDF und öffnen Sie es einmal, um die Seitenzahl, Reihenfolge und Lesbarkeit zu überprüfen. + Verwandeln Sie Bilder in ein PDF + Kombinieren Sie Scans, Screenshots, Quittungen oder Fotos in einem gemeinsam nutzbaren Dokument + Erstellen Sie ein sauberes Dokument + Bilder werden zu besseren PDF-Seiten, wenn sie konsistent zugeschnitten, sortiert und in der Größe angepasst werden. Behandeln Sie die Bildliste wie einen Seitenstapel: Jede Drehung, jeder Rand und jede Wahl der Seitengröße wirkt sich darauf aus, wie gut das endgültige Dokument lesbar ist. + Beschneiden oder drehen Sie Bilder zuerst, wenn Seitenränder, Schatten oder Ausrichtung falsch sind. + Wählen Sie Bilder in der vorgesehenen Seitenreihenfolge aus und passen Sie die Seitengröße oder die Ränder an, sofern das Tool dies anbietet. + Exportieren Sie die PDF-Datei und prüfen Sie, ob alle Seiten lesbar sind, bevor Sie sie senden. + Dokumente scannen + Erfassen Sie Papierseiten und bereiten Sie sie für PDF, OCR oder die Weitergabe vor + Erfassen Sie lesbare Seiten + Das Scannen von Dokumenten funktioniert am besten mit flachen Seiten, guter Beleuchtung und korrigierter Perspektive. Ein sauberer Scan vor dem OCR- oder PDF-Export spart Zeit, da sowohl Texterkennung als auch Komprimierung von der Seitenqualität abhängen. + Legen Sie das Dokument auf eine kontrastierende Oberfläche mit ausreichend Licht und minimalen Schatten. + Passen Sie die erkannten Ecken an oder beschneiden Sie sie manuell, damit die Seite den Rahmen sauber ausfüllt. + Exportieren Sie als Bilder oder PDF und führen Sie dann OCR aus, wenn Sie auswählbaren Text benötigen. + PDF-Dateien schützen oder entsperren + Gehen Sie mit Passwörtern sorgfältig um und bewahren Sie nach Möglichkeit eine bearbeitbare Quellkopie auf + Gehen Sie sicher mit dem PDF-Zugriff um + Schutztools sind für den kontrollierten Austausch nützlich, Passwörter können jedoch leicht verloren gehen und nur schwer wiederhergestellt werden. Schützen Sie Kopien für die Verteilung, bewahren Sie ungeschützte Quelldateien getrennt auf und überprüfen Sie das Ergebnis, bevor Sie etwas löschen. + Schützen Sie eine Kopie der PDF-Datei, nicht Ihr einziges Quelldokument. + Bewahren Sie das Passwort an einem sicheren Ort auf, bevor Sie die geschützte Datei freigeben. + Verwenden Sie die Entsperrung nur für Dateien, die Sie bearbeiten dürfen, und überprüfen Sie dann, ob die entsperrte Kopie korrekt geöffnet wird. + Organisieren Sie PDF-Seiten + Seiten zusammenführen, teilen, neu anordnen, drehen, zuschneiden, entfernen und Seitenzahlen absichtlich hinzufügen + Korrigieren Sie die Dokumentstruktur vor der Dekoration + PDF-Seitentools lassen sich am einfachsten in der gleichen Reihenfolge verwenden, in der Sie ein Papierdokument vorbereiten würden: Seiten sammeln, die falschen entfernen, sie in die richtige Reihenfolge bringen, die Ausrichtung korrigieren und dann letzte Details wie Seitenzahlen oder Wasserzeichen hinzufügen. + Verwenden Sie „Zusammenführen“ oder „Bilder in PDF“ zuerst, wenn das Dokument noch auf mehrere Dateien aufgeteilt ist. + Ordnen Sie Seiten neu an, drehen Sie sie, beschneiden Sie sie oder entfernen Sie sie, bevor Sie Seitenzahlen, Signaturen oder Wasserzeichen hinzufügen. + Zeigen Sie nach strukturellen Änderungen eine Vorschau der endgültigen PDF-Datei an, da eine fehlende oder gedrehte Seite später schwer zu erkennen sein kann. + PDF-Anmerkungen + Entfernen Sie Anmerkungen oder reduzieren Sie sie, wenn Betrachter Kommentare und Markierungen unterschiedlich anzeigen + Kontrollieren Sie, was sichtbar bleibt + Anmerkungen können Kommentare, Hervorhebungen, Zeichnungen, Formularmarkierungen oder andere zusätzliche PDF-Objekte sein. Bei manchen Betrachtern werden sie anders angezeigt, sodass das Entfernen oder Reduzieren geteilter Dokumente die Vorhersehbarkeit gemeinsamer Dokumente erhöhen kann. + Entfernen Sie Anmerkungen, wenn Kommentare, Hervorhebungen oder Bewertungsmarkierungen nicht in der freigegebenen Kopie enthalten sein sollen. + Reduzieren, wenn sichtbare Markierungen auf der Seite bleiben sollen, sich aber nicht mehr wie bearbeitbare Anmerkungen verhalten. + Behalten Sie eine Originalkopie, da sich das Entfernen oder Reduzieren von Anmerkungen nur schwer rückgängig machen lässt. + Text erkennen + Extrahieren Sie Text aus Bildern mit auswählbaren OCR-Engines und Sprachen + Erhalten Sie bessere OCR-Ergebnisse + Die OCR-Genauigkeit hängt von der Bildschärfe, den Sprachdaten, dem Kontrast und der Seitenausrichtung ab. Die besten Ergebnisse werden in der Regel erzielt, wenn das Bild zuerst vorbereitet wird: Rauschen entfernen, aufrecht drehen, Lesbarkeit verbessern und dann Text erkennen. + Schneiden Sie das Bild auf den Textbereich zu und drehen Sie es vor der Erkennung aufrecht. + Wählen Sie die richtige Sprache und laden Sie Sprachdaten herunter, wenn die App dies anfordert. + Kopieren oder teilen Sie den erkannten Text und überprüfen Sie dann manuell Namen, Zahlen und Satzzeichen. + Wählen Sie OCR-Sprachdaten aus + Verbessern Sie die Erkennung durch den Abgleich von Skripten, Sprachen und heruntergeladenen OCR-Modellen + Ordnen Sie OCR dem Text zu + Die falsche OCR-Sprache kann dazu führen, dass gute Fotos wie eine schlechte Erkennung aussehen. Sprachdaten sind für Skripte, Zeichensetzung, Zahlen und gemischte Dokumente wichtig. Wählen Sie daher die Sprache aus, die im Bild angezeigt wird, und nicht die Sprache der Telefon-Benutzeroberfläche. + Wählen Sie die Sprache oder das Skript aus, das im Bild angezeigt wird, nicht nur die Telefonsprache. + Laden Sie fehlende Daten herunter, bevor Sie offline arbeiten oder einen Stapel verarbeiten. + Führen Sie bei mehrsprachigen Dokumenten zuerst die wichtigste Sprache aus und überprüfen Sie Namen und Nummern manuell. + Durchsuchbare PDFs + Schreiben Sie OCR-Text zurück in Dateien, damit gescannte Seiten durchsucht und kopiert werden können + Verwandeln Sie Scans in durchsuchbare Dokumente + OCR kann mehr als nur Text in die Zwischenablage kopieren. Wenn Sie erkannten Text in eine Datei oder ein durchsuchbares PDF schreiben, bleibt das Bild der Seite sichtbar, während Text leichter durchsucht, ausgewählt, indiziert oder archiviert werden kann. + Verwenden Sie eine durchsuchbare PDF-Ausgabe für gescannte Dokumente, die immer noch wie die Originalseiten aussehen sollten. + Verwenden Sie „In Datei schreiben“, wenn Sie nur extrahierten Text benötigen und sich nicht um das Seitenbild kümmern. + Überprüfen Sie wichtige Nummern, Namen und Tabellen manuell, da OCR-Text zwar durchsuchbar, aber dennoch unvollständig ist. + QR-Codes scannen + Lesen Sie QR-Inhalte von der Kameraeingabe oder gespeicherten Bildern, sofern verfügbar + Codes sicher lesen + QR-Codes können Links, WLAN-Daten, Kontakte, Text oder andere Nutzdaten enthalten. + Öffnen Sie „QR-Code scannen“ und halten Sie den Code scharf, flach und vollständig im Rahmen. + Überprüfen Sie den entschlüsselten Inhalt, bevor Sie Links öffnen oder vertrauliche Daten kopieren. + Wenn das Scannen fehlschlägt, verbessern Sie die Beleuchtung, schneiden Sie den Code zu oder versuchen Sie es mit einem Quellbild mit höherer Auflösung. + Verwenden Sie Base64 und Prüfsummen + Kodieren Sie Bilder als Text, dekodieren Sie Text wieder in Bilder und überprüfen Sie die Dateiintegrität + Wechseln Sie zwischen Dateien und Text + Base64 ist für kleine Bildnutzlasten nützlich, während Prüfsummen dabei helfen, zu bestätigen, dass sich Dateien nicht geändert haben. Diese Tools sind besonders hilfreich bei technischen Arbeitsabläufen, Supportberichten, Dateiübertragungen und an Orten, an denen Binärdateien vorübergehend in Text umgewandelt werden müssen. + Verwenden Sie Base64-Tools, um ein kleines Bild zu kodieren, wenn ein Workflow Text anstelle einer Binärdatei benötigt. + Dekodieren Sie Base64 nur aus vertrauenswürdigen Quellen und überprüfen Sie die Vorschau vor dem Speichern. + Verwenden Sie Prüfsummentools, wenn Sie exportierte Dateien vergleichen oder einen Upload/Download bestätigen müssen. + Daten verpacken oder schützen + Verwenden Sie ZIP- und Verschlüsselungstools zum Bündeln von Dateien oder zum Transformieren von Textdaten + Halten Sie zusammengehörige Dateien zusammen + Paketierungstools sind hilfreich, wenn mehrere Ausgaben als eine Datei übertragen werden sollen. Sie eignen sich auch gut zum Zusammenhalten generierter Bilder, PDFs, Protokolle und Metadaten, wenn Sie einen vollständigen Ergebnissatz senden müssen. + Verwenden Sie ZIP, wenn Sie viele exportierte Bilder oder Dokumente zusammen senden müssen. + Verwenden Sie Verschlüsselungstools nur, wenn Sie die ausgewählte Methode verstehen und die erforderlichen Schlüssel sicher speichern können. + Testen Sie das erstellte Archiv oder den umgewandelten Text, bevor Sie Quelldateien löschen. + Prüfsummen in Namen + Verwenden Sie prüfsummenbasierte Dateinamen, wenn Einzigartigkeit und Integrität wichtiger sind als Lesbarkeit + Benennen Sie Dateien nach Inhalt + Prüfsummendateinamen sind nützlich, wenn Exporte möglicherweise doppelte sichtbare Namen haben oder wenn Sie nachweisen müssen, dass zwei Dateien identisch sind. Sie eignen sich weniger gut zum manuellen Durchsuchen und sollten daher für technische Archive und Verifizierungsworkflows verwendet werden. + Aktivieren Sie die Prüfsumme als Dateiname, wenn die Inhaltsidentität wichtiger ist als ein für Menschen lesbarer Titel. + Verwenden Sie es für Archive, Deduplizierung, wiederholbare Exporte oder Dateien, die möglicherweise hochgeladen und erneut heruntergeladen werden. + Kombinieren Sie Prüfsummennamen mit Ordnern oder Metadaten, wenn die Benutzer noch verstehen müssen, was die Datei darstellt. + Wählen Sie Farben aus Bildern aus + Probieren Sie Pixel aus, kopieren Sie Farbcodes und verwenden Sie Farben in Paletten oder Designs wieder + Probieren Sie die genaue Farbe aus + Die Farbauswahl ist nützlich, um ein Design anzupassen, Markenfarben zu extrahieren oder Bildwerte zu überprüfen. Das Zoomen ist wichtig, da Antialiasing, Schatten und Komprimierung dazu führen können, dass benachbarte Pixel geringfügig von der erwarteten Farbe abweichen. + Öffnen Sie „Farbe aus Bild auswählen“ und wählen Sie ein Quellbild mit der gewünschten Farbe aus. + Zoomen Sie hinein, bevor Sie kleine Details aufnehmen, damit benachbarte Pixel das Ergebnis nicht beeinflussen. + Kopieren Sie die Farbe in das für Ihr Ziel erforderliche Format, z. B. HEX, RGB oder HSV. + Erstellen Sie Paletten und nutzen Sie die Farbbibliothek + Extrahieren Sie Paletten, durchsuchen Sie gespeicherte Farben und sorgen Sie für konsistente visuelle Systeme + Erstellen Sie eine wiederverwendbare Palette + Paletten tragen dazu bei, dass exportierte Grafiken, Wasserzeichen und Zeichnungen optisch konsistent bleiben. Sie sind besonders nützlich, wenn Sie Screenshots wiederholt mit Anmerkungen versehen, Marken-Assets vorbereiten oder dieselben Farben für mehrere Tools benötigen. + Erstellen Sie eine Palette aus einem Bild oder fügen Sie Farben manuell hinzu, wenn Sie die Werte bereits kennen. + Benennen oder organisieren Sie wichtige Farben, damit Sie sie später wiederfinden. + Verwenden Sie kopierte Werte in Zeichnen, Wasserzeichen, Farbverläufen, SVG oder externen Designtools. + Erstellen Sie Farbverläufe + Erstellen Sie Verlaufsbilder für Hintergründe, Platzhalter und visuelle Experimente + Kontrollieren Sie Farbübergänge + Verlaufswerkzeuge sind nützlich, wenn Sie ein generiertes Bild benötigen, anstatt ein vorhandenes zu bearbeiten. Sie können zu sauberen Hintergründen, Platzhaltern, Hintergrundbildern, Masken oder einfachen Elementen für externe Designtools werden. + Wählen Sie den Verlaufstyp und legen Sie zunächst die wichtigen Farben fest. + Passen Sie Richtung, Stopps, Glätte und Ausgabegröße für den Zielanwendungsfall an. + Exportieren Sie in ein Format, das dem Ziel entspricht, normalerweise PNG für sauber generierte Grafiken. + Farbzugänglichkeit + Verwenden Sie dynamische Farben, Kontrast und farbenblinde Optionen, wenn die Benutzeroberfläche schwer zu lesen ist + Erleichtern Sie die Verwendung von Farben + Farbeinstellungen beeinflussen den Komfort, die Lesbarkeit und die Geschwindigkeit, mit der Sie Steuerelemente scannen können. Wenn Toolchips, Schieberegler oder ausgewählte Zustände schwer zu unterscheiden sind, können Themen- und Barrierefreiheitsoptionen nützlicher sein, als einfach den Dunkelmodus zu ändern. + Probieren Sie dynamische Farben aus, wenn die App der aktuellen Hintergrundpalette folgen soll. + Erhöhen Sie den Kontrast oder ändern Sie das Schema, wenn Tasten und Oberflächen nicht ausreichend hervorstechen. + Verwenden Sie farbenblinde Schemaoptionen, wenn einige Staaten oder Akzente schwer zu unterscheiden sind. + Alpha-Hintergrund + Steuern Sie die Vorschau- oder Füllfarbe, die für transparente PNG-, WebP- und ähnliche Ausgaben verwendet wird + Verstehen Sie transparente Vorschauen + Die Hintergrundfarbe für Alphaformate kann die Inspektion transparenter Bilder erleichtern. Es ist jedoch wichtig zu wissen, ob Sie das Vorschau-/Füllverhalten ändern oder das Endergebnis verflachen. Dies gilt für Aufkleber, Logos und Bilder ohne Hintergrund. + Verwenden Sie zunächst ein Alpha-unterstützendes Format wie PNG oder WebP, wenn transparente Pixel den Export überleben müssen. + Passen Sie die Hintergrundfarbeinstellungen an, wenn transparente Kanten auf der App-Oberfläche schwer zu erkennen sind. + Exportieren Sie einen kleinen Test und öffnen Sie ihn erneut in einer anderen App, um zu überprüfen, ob die Transparenz oder die ausgewählte Füllung gespeichert wurde. + Auswahl des KI-Modells + Wählen Sie ein Modell nach Bildtyp und Defekt aus, anstatt zuerst das größte Modell auszuwählen + Passen Sie das Modell an den Schaden an + AI Tools können verschiedene ONNX/ORT-Modelle herunterladen oder importieren, und jedes Modell wird für eine bestimmte Art von Problem trainiert. Ein Modell für JPEG-Blöcke, Anime-Linienbereinigung, Rauschunterdrückung, Hintergrundentfernung, Kolorierung oder Hochskalierung kann zu schlechteren Ergebnissen führen, wenn es für den falschen Bildtyp verwendet wird. + Lesen Sie vor dem Herunterladen die Modellbeschreibung. Wählen Sie das Modell, das Ihr eigentliches Problem benennt, z. B. Komprimierung, Rauschen, Halbton, Unschärfe oder Hintergrundentfernung. + Beginnen Sie beim Testen eines neuen Bildtyps mit einem kleineren oder spezifischeren Modell und vergleichen Sie es nur dann mit schwereren Modellen, wenn das Ergebnis nicht gut genug ist. + Behalten Sie nur Modelle, die Sie wirklich nutzen, da heruntergeladene und importierte Modelle erheblichen Speicherplatz beanspruchen können. + Modellfamilien verstehen + Modellnamen weisen häufig auf ihr Ziel hin: Hochskalierte Modelle im RealESRGAN-Stil, SCUNet- und FBCNN-Modelle bereinigen Rauschen oder Komprimierung, Hintergrundmodelle erstellen Masken und Kolorierer fügen der Graustufeneingabe Farbe hinzu. Importierte benutzerdefinierte Modelle können leistungsstark sein, sie sollten jedoch der erwarteten Eingabe-/Ausgabeform entsprechen und das unterstützte ONNX- oder ORT-Format verwenden. + Verwenden Sie Upscaler, wenn Sie mehr Pixel benötigen, Entrauscher, wenn das Bild körnig ist, und Artefaktentferner, wenn Komprimierungsblöcke oder Streifenbildung das Hauptproblem darstellen. + Verwenden Sie Hintergrundmodelle nur zur Motivtrennung; Sie sind nicht dasselbe wie das allgemeine Entfernen von Objekten oder die Bildverbesserung. + Importieren Sie benutzerdefinierte Modelle nur aus Quellen, denen Sie vertrauen, und testen Sie sie an einem kleinen Bild, bevor Sie wichtige Dateien verwenden. + KI-Parameter + Passen Sie Blockgröße, Überlappung und parallele Worker an, um Qualität, Geschwindigkeit und Speicher auszugleichen + Balance zwischen Geschwindigkeit und Stabilität + Die Chunk-Größe steuert, wie groß Bildteile sind, bevor sie vom Modell verarbeitet werden. Überlappung verschmilzt benachbarte Teile, um Grenzen zu verbergen. Parallele Worker können die Verarbeitung beschleunigen, aber jeder Worker benötigt Speicher, sodass hohe Werte dazu führen können, dass große Bilder auf Telefonen mit begrenztem RAM instabil werden. + Verwenden Sie größere Blöcke für einen flüssigeren Kontext, wenn Ihr Gerät das Modell zuverlässig verarbeitet und das Bild nicht riesig ist. + Erhöhen Sie die Überlappung, wenn die Blockränder sichtbar werden. Denken Sie jedoch daran, dass mehr Überlappung häufigere Arbeitswiederholungen bedeutet. + Parallelarbeiter erst nach einem stabilen Test aufziehen; Wenn die Verarbeitung langsamer wird, sich das Gerät erwärmt oder abstürzt, reduzieren Sie zunächst die Anzahl der Mitarbeiter. + Vermeiden Sie Chunk-Artefakte + Durch Chunking kann die App Bilder verarbeiten, die größer sind, als das Modell gleichzeitig verarbeiten kann. Der Nachteil besteht darin, dass jede Kachel weniger Umgebungskontext hat, sodass geringe Überlappungen oder zu kleine Stücke zu sichtbaren Rändern, Texturänderungen oder inkonsistenten Details zwischen benachbarten Bereichen führen können. + Erhöhen Sie die Überlappung, wenn Sie rechteckige Ränder, wiederholte Texturänderungen oder Detailsprünge zwischen verarbeiteten Bereichen sehen. + Reduzieren Sie die Blockgröße, wenn der Prozess vor der Ausgabe fehlschlägt, insbesondere bei großen Bildern oder schweren Modellen. + Speichern Sie einen kleinen Zuschnitttest, bevor Sie das Gesamtbild verarbeiten, damit Sie die Parameter schnell vergleichen können. + KI-Stabilität + Reduzieren Sie Blockgröße, Worker und Quellauflösung, wenn die KI-Verarbeitung abstürzt oder einfriert + Erholen Sie sich von schweren KI-Aufgaben + Die KI-Verarbeitung ist einer der umfangreichsten Arbeitsabläufe in der App. Abstürze, fehlgeschlagene Sitzungen, Einfrieren oder plötzliche App-Neustarts bedeuten normalerweise, dass das Modell, die Quellauflösung, die Blockgröße oder die Anzahl paralleler Worker für den aktuellen Gerätestatus zu anspruchsvoll sind. + Wenn die App abstürzt oder keine Modellsitzung öffnen kann, verringern Sie die parallelen Worker und die Blockgröße und versuchen Sie es dann erneut mit demselben Image. + Ändern Sie die Größe sehr großer Bilder vor der KI-Verarbeitung, wenn das Ziel nicht die Originalauflösung erfordert. + Schließen Sie andere schwere Apps, verwenden Sie ein kleineres Modell oder verarbeiten Sie weniger Dateien auf einmal, wenn der Speicherdruck immer wieder steigt. + Modellfehler diagnostizieren + Ein fehlgeschlagener KI-Lauf bedeutet nicht immer, dass das Bild schlecht ist. Die Modelldatei ist möglicherweise unvollständig, wird nicht unterstützt, ist zu groß für den Speicher oder stimmt nicht mit dem Vorgang überein. Wenn die App eine fehlgeschlagene Sitzung meldet, betrachten Sie dies als Signal, um zunächst das Modell und die Parameter zu vereinfachen. + Löschen Sie ein Modell und laden Sie es erneut herunter, wenn es unmittelbar nach dem Herunterladen fehlschlägt oder sich so verhält, als wäre die Datei beschädigt. + Probieren Sie ein integriertes herunterladbares Modell aus, bevor Sie einem importierten benutzerdefinierten Modell die Schuld geben, da importierte Modelle möglicherweise inkompatible Eingaben haben. + Verwenden Sie App-Protokolle nach der Reproduktion des Fehlers, wenn Sie einen Absturz oder einen Sitzungsfehler melden müssen. + Experimentieren Sie im Shader Studio + Nutzen Sie GPU-Shader-Effekte für erweiterte Bildverarbeitung und individuelle Looks + Arbeiten Sie sorgfältig mit Shadern + Shader Studio ist leistungsstark, aber Shader-Code und -Parameter erfordern kleine, testbare Änderungen. Behandeln Sie es wie ein experimentelles Werkzeug: Behalten Sie eine funktionierende Grundlinie bei, ändern Sie jeweils eine Sache und testen Sie sie mit verschiedenen Bildgrößen, bevor Sie einer Voreinstellung vertrauen. + Beginnen Sie mit einem bekanntermaßen funktionierenden Shader oder einem einfachen Effekt, bevor Sie Parameter hinzufügen. + Ändern Sie jeweils einen Parameter oder Codeblock und überprüfen Sie die Vorschau auf Fehler. + Exportieren Sie Voreinstellungen erst, nachdem Sie sie mit einigen verschiedenen Bildgrößen getestet haben. + Erstellen Sie SVG- oder ASCII-Grafiken + Generieren Sie vektorähnliche Assets oder textbasierte Bilder für die kreative Ausgabe + Wählen Sie den kreativen Ausgabetyp + SVG- und ASCII-Tools dienen unterschiedlichen Zielen: skalierbare Grafiken oder Textdarstellung. Da es sich bei beiden um kreative Konvertierungen handelt, ist die Vorschau in der endgültigen Größe wichtiger als die Beurteilung des Ergebnisses anhand einer winzigen Miniaturansicht. + Verwenden Sie SVG Maker, wenn das Ergebnis sauber skaliert oder in Design-Workflows wiederverwendet werden soll. + Verwenden Sie ASCII Art, wenn Sie eine stilisierte Textdarstellung eines Bildes wünschen. + Vorschau in der endgültigen Größe, da kleine Details in der generierten Ausgabe verschwinden können. + Generieren Sie Geräuschtexturen + Erstellen Sie prozedurale Texturen für Hintergründe, Masken, Überlagerungen oder Experimente + Optimieren Sie die prozedurale Ausgabe + Durch die Rauscherzeugung werden aus Parametern neue Bilder erstellt, sodass kleine Änderungen zu sehr unterschiedlichen Ergebnissen führen können. Es ist nützlich für Texturen, Masken, Überlagerungen, Platzhalter oder zum Testen der Komprimierung mit detaillierten Mustern. + Wählen Sie einen Rauschtyp und eine Ausgabegröße, bevor Sie feine Details anpassen. + Passen Sie Maßstab, Intensität, Farben oder Saatgut an, bis das Muster zur Zielverwendung passt. + Speichern Sie nützliche Ergebnisse und notieren Sie die Parameter, wenn Sie die Textur später neu erstellen müssen. + Markup-Projekte + Verwenden Sie Projektdateien, wenn Anmerkungen nach dem Schließen der App bearbeitbar bleiben müssen + Halten Sie geschichtete Bearbeitungen wiederverwendbar + Markup-Projektdateien sind nützlich, wenn ein Screenshot, ein Diagramm oder ein Anleitungsbild später möglicherweise geändert werden muss. Exportierte PNG-/JPEG-Dateien sind endgültige Bilder, während Projektdateien bearbeitbare Ebenen und Bearbeitungsstatus für zukünftige Anpassungen beibehalten. + Verwenden Sie Markup-Ebenen, wenn Anmerkungen, Beschriftungen oder Layoutelemente bearbeitbar bleiben sollen. + Speichern Sie die Projektdatei oder öffnen Sie sie erneut, bevor Sie ein endgültiges Bild exportieren, wenn Sie weitere Überarbeitungen erwarten. + Exportieren Sie ein normales Bild nur, wenn Sie bereit sind, eine reduzierte Endversion zu teilen. + Dateien verschlüsseln + Schützen Sie Dateien mit einem Passwort und testen Sie die Entschlüsselung, bevor Sie eine Quellkopie löschen + Behandeln Sie verschlüsselte Ausgaben sorgfältig + Verschlüsselungstools können Dateien mit passwortbasierter Verschlüsselung schützen, sie sind jedoch kein Ersatz dafür, sich den Schlüssel zu merken oder Backups zu erstellen. Für den Transport und die Speicherung eignet sich die Verschlüsselung, während ZIP besser geeignet ist, wenn das Hauptziel darin besteht, mehrere Ausgaben zu bündeln. + Verschlüsseln Sie zunächst eine Kopie und bewahren Sie das Original auf, bis Sie getestet haben, dass die Entschlüsselung mit dem von Ihnen gespeicherten Passwort funktioniert. + Benutzen Sie einen Passwort-Manager oder einen anderen sicheren Ort für den Schlüssel; Die App kann ein verlorenes Passwort nicht für Sie erraten. + Teilen Sie verschlüsselte Dateien nur mit Personen, die auch das Passwort haben und wissen, welches Tool oder welche Methode sie entschlüsseln soll. + Werkzeuge anordnen + Gruppieren, ordnen, suchen und pinnen Sie Werkzeuge, damit der Hauptbildschirm Ihrem tatsächlichen Arbeitsablauf entspricht + Machen Sie den Hauptbildschirm schneller + Es lohnt sich, die Werkzeuganordnungseinstellungen anzupassen, wenn Sie wissen, welche Werkzeuge Sie am häufigsten verwenden. Die Gruppierung erleichtert die Erkundung, die benutzerdefinierte Reihenfolge unterstützt das Muskelgedächtnis und Favoriten sorgen dafür, dass die täglichen Tools auch dann erreichbar sind, wenn die vollständige Liste umfangreich wird. + Verwenden Sie den Gruppierungsmodus, während Sie die App erlernen oder wenn Sie nach Aufgabentyp geordnete Tools bevorzugen. + Wechseln Sie zur benutzerdefinierten Bestellung, wenn Sie Ihre häufig verwendeten Werkzeuge bereits kennen und weniger Schriftrollen benötigen. + Verwenden Sie Sucheinstellungen und Favoriten gemeinsam für seltene Werkzeuge, an die Sie sich namentlich erinnern, die Sie aber nicht immer sichtbar sehen möchten. + Behandeln Sie große Dateien + Vermeiden Sie langsame Exporte, Speicherdruck und unerwartet große Ausgaben + Reduzieren Sie den Aufwand vor dem Export + Sehr große Bilder, lange Stapel und hochwertige Formate können mehr Speicher und Zeit beanspruchen. Die schnellste Lösung besteht in der Regel darin, den Arbeitsaufwand vor dem schwersten Vorgang zu reduzieren und nicht darauf zu warten, dass die gleiche übergroße Eingabe abgeschlossen wird. + Ändern Sie die Größe großer Bilder, bevor Sie starke Filter, OCR oder PDF-Konvertierung anwenden. + Verwenden Sie zunächst eine kleinere Charge, um die Einstellungen zu bestätigen, und verarbeiten Sie dann den Rest mit derselben Voreinstellung. + Reduzieren Sie die Qualität oder ändern Sie das Format, wenn die Ausgabedatei größer als erwartet ist. + Cache und Vorschauen + Verstehen Sie generierte Vorschauen, Cache-Bereinigung und Speichernutzung nach intensiven Sitzungen + Halten Sie Speicher und Geschwindigkeit im Gleichgewicht + Durch die Vorschaugenerierung und den Cache kann das wiederholte Durchsuchen beschleunigt werden, bei intensiven Bearbeitungssitzungen können jedoch temporäre Daten zurückbleiben. Cache-Einstellungen helfen, wenn sich die App langsam anfühlt, der Speicherplatz knapp ist oder die Vorschau nach vielen Experimenten veraltet aussieht. + Beim Durchsuchen vieler Bilder ist es wichtiger, die Vorschau aktiviert zu lassen, als den temporären Speicher zu minimieren. + Leeren Sie den Cache nach großen Stapeln, fehlgeschlagenen Experimenten oder langen Sitzungen mit vielen hochauflösenden Dateien. + Verwenden Sie die automatische Cache-Löschung, wenn Sie die Speicherbereinigung dem Warmhalten der Vorschau zwischen den Starts vorziehen. + Passen Sie das Bildschirmverhalten an + Nutzen Sie die Optionen Vollbild, Sicherheitsmodus, Helligkeit und Systemleiste für konzentriertes Arbeiten + Passen Sie die Schnittstelle an die Situation an + Bildschirmeinstellungen sind hilfreich, wenn Sie im Querformat bearbeiten, private Bilder präsentieren oder eine konstante Helligkeit benötigen. Sie sind für den normalen Gebrauch nicht erforderlich, lösen aber schwierige Situationen wie QR-Inspektion, private Vorschauen oder beengte Landschaftsbearbeitung. + Verwenden Sie Vollbild- oder Systemleisteneinstellungen, wenn der Bearbeitungsbereich wichtiger ist als das Navigationschrom. + Verwenden Sie den sicheren Modus, wenn private Bilder nicht in Screenshots oder App-Vorschauen angezeigt werden sollen. + Verwenden Sie die Helligkeitserzwingung für Werkzeuge, bei denen dunkle Bilder oder QR-Codes schwer zu prüfen sind. + Behalten Sie Transparenz + Verhindern Sie, dass transparente Hintergründe weiß, schwarz oder abgeflacht werden + Verwenden Sie eine Alpha-fähige Ausgabe + Die Transparenz bleibt nur bestehen, wenn das ausgewählte Tool und das Ausgabeformat Alpha unterstützen. Wenn ein exportierter Hintergrund weiß oder schwarz wird, liegt das Problem normalerweise am Ausgabeformat, an einer Füll-/Hintergrundeinstellung oder an einer Ziel-App, die das Bild reduziert hat. + Wählen Sie PNG oder WebP, wenn Sie Bilder, Aufkleber, Logos oder Overlays ohne Hintergrund exportieren. + Vermeiden Sie JPEG für eine transparente Ausgabe, da es keinen Alphakanal speichert. + Überprüfen Sie die Einstellungen zur Hintergrundfarbe für Alphaformate, wenn in der Vorschau ein gefüllter Hintergrund angezeigt wird. + Beheben Sie Freigabe- und Importprobleme + Verwenden Sie Auswahleinstellungen und unterstützte Formate, wenn Dateien nicht richtig angezeigt oder geöffnet werden + Holen Sie sich die Datei in die App + Importprobleme werden häufig durch Anbieterberechtigungen, nicht unterstützte Formate oder Auswahlverhalten verursacht. Von Cloud-Apps und Messengern freigegebene Dateien können temporär sein, sodass die Auswahl einer dauerhaften Quelle bei längeren Bearbeitungen zuverlässiger sein kann. + Versuchen Sie, die Datei über die Systemauswahl statt über die Verknüpfung „Zuletzt verwendete Dateien“ zu öffnen. + Wenn ein Format von einem Tool nicht unterstützt wird, konvertieren Sie es zuerst oder öffnen Sie ein spezifischeres Tool. + Überprüfen Sie die Bildauswahl- und Launcher-Einstellungen, wenn sich Freigabeflüsse oder das direkte Öffnen von Werkzeugen anders als erwartet verhalten. + Ausgangsbestätigung + Vermeiden Sie den Verlust nicht gespeicherter Bearbeitungen, wenn versehentlich Gesten, Rückwärtsdrücke oder Werkzeugwechsel erfolgen + Schützen Sie unvollendete Arbeiten + Die Bestätigung des Beendens des Tools ist hilfreich, wenn Sie die Gestennavigation verwenden, große Dateien bearbeiten oder häufig zwischen Apps wechseln. Vor dem Verlassen eines Tools wird eine kleine Pause eingefügt, damit nicht abgeschlossene Einstellungen und Vorschauen nicht versehentlich verloren gehen. + Aktivieren Sie die Bestätigung, wenn ein Werkzeug vor dem Export durch versehentliche Zurück-Gesten geschlossen wurde. + Lassen Sie es deaktiviert, wenn Sie hauptsächlich schnelle Konvertierungen in einem Schritt durchführen und eine schnellere Navigation bevorzugen. + Verwenden Sie es zusammen mit Voreinstellungen für längere Arbeitsabläufe, bei denen das Wiederherstellen von Einstellungen lästig wäre. + Verwenden Sie Protokolle, wenn Sie Probleme melden + Sammeln Sie nützliche Details, bevor Sie ein Problem eröffnen oder den Entwickler kontaktieren + Melden Sie Probleme mit dem Kontext + Ein klarer Bericht mit Schritten, App-Version, Eingabetyp und Protokollen ist viel einfacher zu diagnostizieren. Protokolle sind direkt nach der Reproduktion eines Problems am nützlichsten, da sie den umgebenden Kontext aktuell halten. + Notieren Sie sich den Namen des Tools, die genaue Aktion und ob sie bei einer Datei oder bei jeder Datei ausgeführt wird. + Öffnen Sie App Logs, nachdem Sie das Problem reproduziert haben, und geben Sie nach Möglichkeit die entsprechende Protokollausgabe frei. + Hängen Sie Screenshots oder Beispieldateien nur an, wenn diese keine privaten Informationen enthalten. + Die Hintergrundverarbeitung wurde gestoppt + Android ließ die Hintergrundverarbeitungsbenachrichtigung nicht rechtzeitig starten. Hierbei handelt es sich um eine zeitliche Einschränkung des Systems, die nicht zuverlässig behoben werden kann. Starten Sie die App neu und versuchen Sie es erneut. Es kann hilfreich sein, die App geöffnet zu lassen und Benachrichtigungen oder Hintergrundarbeiten zuzulassen. + Überspringen Sie die Dateiauswahl + Öffnen Sie Tools schneller, wenn die Eingabe normalerweise aus der Freigabe, der Zwischenablage oder einem vorherigen Bildschirm stammt + Beschleunigen Sie wiederholte Werkzeugstarts + „Dateiauswahl überspringen“ ändert den ersten Schritt der unterstützten Tools. Dies ist nützlich, wenn du normalerweise ein Tool mit einem bereits ausgewählten Bild oder über Android-Freigabeaktionen aufrufst. Es kann jedoch verwirrend sein, wenn du erwartest, dass jedes Tool sofort nach einer Datei fragt. + Aktiviere es nur, wenn dein üblicher Ablauf bereits das Quellbild bereitstellt, bevor das Tool geöffnet wird. + Lassen Sie es beim Erlernen der App deaktiviert, da die explizite Auswahl den Ablauf jedes Werkzeugs leichter verständlich macht. + Wenn ein Tool ohne offensichtliche Eingabe geöffnet wird, deaktivieren Sie diese Einstellung oder starten Sie mit „Teilen/Öffnen mit“, damit Android die Datei zuerst übergibt. + Öffnen Sie zuerst den Editor + Überspringen Sie die Vorschau, wenn ein freigegebenes Bild direkt in die Bearbeitung gehen soll + Wählen Sie „Vorschau“ oder „Bearbeiten“ als ersten Stopp + „Bearbeiten statt Vorschau öffnen“ ist für Benutzer gedacht, die bereits wissen, dass sie das Bild ändern möchten. Die Vorschau ist sicherer, wenn Sie Dateien aus dem Chat oder Cloud-Speicher prüfen. Die direkte Bearbeitung von Screenshots, schnellen Zuschnitten, Anmerkungen und einmaligen Korrekturen ist schneller. + Aktivieren Sie die direkte Bearbeitung, wenn die meisten freigegebenen Bilder sofort zugeschnitten, markiert, gefiltert oder exportiert werden müssen. + Lassen Sie zunächst die Vorschau, wenn Sie häufig Metadaten, Abmessungen, Transparenz oder Bildqualität überprüfen, bevor Sie eine Entscheidung treffen. + Verwenden Sie dies zusammen mit Favoriten, damit geteilte Bilder in der Nähe der Tools landen, die Sie tatsächlich verwenden. + Voreingestellte Texteingabe + Geben Sie exakte voreingestellte Werte ein, anstatt die Bedienelemente wiederholt anzupassen + Verwenden Sie präzise Werte, wenn die Schieberegler langsam sind + Die voreingestellte Texteingabe hilft, wenn Sie für wiederholte Arbeiten genaue Zahlen benötigen: soziale Bildgrößen, Dokumentränder, Komprimierungsziele, Linienbreiten oder andere Werte, die mit Gesten nur schwer zu erreichen sind. Dies ist auch auf kleinen Bildschirmen nützlich, bei denen die Feinbewegung des Schiebereglers schwieriger ist. + Aktivieren Sie die Texteingabe, wenn Sie häufig genaue Größen, Prozentsätze, Qualitätswerte oder Werkzeugparameter wiederverwenden. + Speichern Sie den Wert als Voreinstellung, nachdem Sie ihn einmal eingegeben haben, und verwenden Sie ihn dann erneut, anstatt dasselbe Setup erneut zu erstellen. + Behalten Sie Gestensteuerungen für eine grobe visuelle Abstimmung und eingegebene Werte für strenge Ausgabeanforderungen bei. + Nahezu original speichern + Platzieren Sie generierte Dateien neben Quellbildern, wenn der Ordnerkontext wichtig ist + Behalten Sie die Ausgaben mit ihren Quellen bei + „Im Originalordner speichern“ ist praktisch für Kameraordner, Projektordner, Dokumentscans und Client-Stapel, bei denen die bearbeitete Kopie neben der Quelle bleiben soll. Es kann weniger aufgeräumt sein als ein dedizierter Ausgabeordner. Kombinieren Sie es daher mit eindeutigen Dateinamensuffixen oder -mustern. + Aktivieren Sie es, wenn jeder Quellordner bereits aussagekräftig ist und die Ausgaben dort gruppiert bleiben sollen. + Verwenden Sie Dateinamensuffixe, Präfixe, Zeitstempel oder Muster, damit bearbeitete Kopien leicht von Originalen getrennt werden können. + Deaktivieren Sie es für gemischte Stapel, wenn alle generierten Dateien in einem Exportordner gesammelt werden sollen. + Überspringen Sie eine größere Ausgabe + Vermeiden Sie das Speichern von Konvertierungen, die unerwartet umfangreicher werden als die Quelle + Schützen Sie Chargen vor bösen Größenüberraschungen + Bei einigen Konvertierungen werden Dateien größer, insbesondere PNG-Screenshots, bereits komprimierte Fotos, hochwertiges WebP oder Bilder mit erhaltenen Metadaten. Überspringen zulassen, wenn größer verhindert, dass diese Ausgaben eine kleinere Quelle in Arbeitsabläufen ersetzen, bei denen die Reduzierung der Größe das Hauptziel ist. + Aktivieren Sie es, wenn der Export Dateien komprimieren, in der Größe ändern oder für Upload-Limits vorbereiten soll. + Überprüfen Sie übersprungene Dateien nach einem Stapel. Möglicherweise sind sie bereits optimiert oder benötigen ein anderes Format. + Deaktivieren Sie es, wenn Qualität, Transparenz oder Formatkompatibilität wichtiger sind als die endgültige Dateigröße. + Zwischenablage und Links + Steuern Sie die automatische Eingabe in die Zwischenablage und die Linkvorschau für schnellere textbasierte Tools + Machen Sie die automatische Eingabe vorhersehbar + Zwischenablage- und Linkeinstellungen sind praktisch für QR-, Base64-, URL- und textlastige Arbeitsabläufe, die automatische Eingabe sollte jedoch beabsichtigt bleiben. Wenn die App Felder scheinbar selbstständig ausfüllt, überprüfen Sie das Verhalten beim Einfügen in die Zwischenablage. Wenn sich Linkkarten laut oder langsam anfühlen, passen Sie das Verhalten der Linkvorschau an. + Erlauben Sie das automatische Einfügen in die Zwischenablage, wenn Sie häufig Text kopieren, und öffnen Sie sofort ein passendes Tool. + Deaktivieren Sie das automatische Einfügen, wenn private Inhalte der Zwischenablage in Tools angezeigt werden, wo Sie sie nicht erwartet haben. + Deaktivieren Sie die Linkvorschau, wenn das Abrufen der Vorschau langsam, ablenkend oder für Ihren Arbeitsablauf unnötig ist. + Kompakte Steuerung + Verwenden Sie dichtere Auswahlmöglichkeiten und eine schnelle Einstellungsplatzierung, wenn der Platz auf dem Bildschirm knapp ist + Platzieren Sie mehr Steuerelemente auf kleinen Bildschirmen + Kompakte Selektoren und schnelle Einstellungsplatzierung helfen bei kleinen Telefonen, bei der Landschaftsbearbeitung und bei Werkzeugen mit vielen Parametern. Der Nachteil besteht darin, dass sich die Bedienelemente dichter anfühlen können. Daher ist dies am besten, wenn Sie die gängigen Optionen bereits kennen. + Aktivieren Sie kompakte Selektoren, wenn Dialoge oder Optionslisten zu groß für den Bildschirm erscheinen. + Passen Sie die Schnelleinstellungsseite an, wenn die Werkzeugsteuerung von einer Seite des Displays leichter zu erreichen ist. + Kehren Sie zu den großzügigeren Steuerelementen zurück, wenn Sie die App noch nicht kennengelernt haben oder häufig dicht besiedelte Optionen falsch antippen. + Laden Sie Bilder aus dem Internet + Fügen Sie eine Seiten- oder Bild-URL ein, zeigen Sie eine Vorschau der analysierten Bilder an und speichern oder teilen Sie ausgewählte Ergebnisse + Verwenden Sie bewusst Webbilder + Web Image Loading analysiert Bildquellen von der bereitgestellten URL, zeigt eine Vorschau des ersten verfügbaren Bildes an und ermöglicht Ihnen das Speichern oder Freigeben ausgewählter analysierter Bilder. Einige Websites verwenden temporäre, geschützte oder skriptgenerierte Links, sodass eine Seite, die in einem Browser funktioniert, möglicherweise dennoch keine verwendbaren Bilder zurückgibt. + Fügen Sie eine direkte Bild-URL oder eine Seiten-URL ein und warten Sie, bis die analysierte Bildliste angezeigt wird. + Verwenden Sie die Rahmen- oder Auswahlsteuerelemente, wenn die Seite mehrere Bilder enthält und Sie nur einige davon benötigen. + Wenn nichts geladen wird, versuchen Sie es zunächst mit einem direkten Bildlink oder laden Sie es über den Browser herunter. Die Website blockiert möglicherweise das Parsen oder verwendet private Links. + Wählen Sie den Größenänderungstyp + Machen Sie sich mit „Explizit“, „Flexibel“, „Zuschneiden“ und „Anpassen“ vertraut, bevor Sie ein Bild in neue Dimensionen zwingen + Steuern Sie Form, Leinwand und Seitenverhältnis + Der Größenänderungstyp entscheidet, was passiert, wenn die angeforderte Breite und Höhe nicht mit dem ursprünglichen Seitenverhältnis übereinstimmen. Es ist der Unterschied zwischen dem Strecken von Pixeln, dem Beibehalten von Proportionen, dem Zuschneiden zusätzlicher Bereiche oder dem Hinzufügen einer Hintergrundleinwand. + Verwenden Sie „Explizit“ nur, wenn die Verzerrung akzeptabel ist oder die Quelle bereits das gleiche Seitenverhältnis wie das Ziel hat. + Verwenden Sie „Flexibel“, um die Proportionen beizubehalten, indem Sie die Größe von der wichtigen Seite ändern, insbesondere bei Fotos und gemischten Stapeln. + Verwenden Sie „Zuschneiden“ für strenge Rahmen ohne Ränder oder „Anpassen“, wenn das gesamte Bild innerhalb einer festen Leinwand sichtbar bleiben muss. + Wähle den Bildskalierungsmodus + Wählen Sie den Resampling-Filter, der Schärfe, Glätte, Geschwindigkeit und Kantenartefakte steuert + Ändern Sie die Größe ohne versehentliche Weichheit + Der Bildskalierungsmodus steuert, wie neue Pixel bei der Größenänderung berechnet werden. Einfache Modi sind schnell und vorhersehbar, während erweiterte Filter Details besser bewahren können, aber möglicherweise Kanten schärfen, Lichthöfe erzeugen oder bei großen Bildern länger dauern. + Verwenden Sie „Basic“ oder „Bilinear“ für eine schnelle tägliche Größenänderung, wenn das Ergebnis nur kleiner und sauberer sein muss. + Verwenden Sie die Modi Lanczos, Robidoux, Spline oder EWA, wenn es auf feine Details, Text oder hochwertiges Herunterskalieren ankommt. + Verwenden Sie „Nächste“ für Pixelkunst, Symbole und Grafiken mit scharfen Kanten, bei denen unscharfe Pixel das Erscheinungsbild beeinträchtigen würden. + Farbraum skalieren + Entscheiden Sie, wie die Farben gemischt werden, während die Pixel bei der Größenänderung neu berechnet werden + Halten Sie Farbverläufe und Kanten natürlich + Durch die Skalierung des Farbraums wird die bei der Größenänderung verwendete Mathematik geändert. Die meisten Bilder sind mit der Standardeinstellung in Ordnung, aber lineare oder wahrnehmungsbezogene Räume können dazu führen, dass Farbverläufe, dunkle Kanten und gesättigte Farben nach einer starken Skalierung sauberer aussehen. + Behalten Sie die Standardeinstellung bei, wenn Geschwindigkeit und Kompatibilität wichtiger sind als winzige Farbunterschiede. + Probieren Sie den linearen oder den Wahrnehmungsmodus aus, wenn dunkle Umrisse, UI-Screenshots, Verläufe oder Farbbänder nach der Größenänderung falsch aussehen. + Vergleichen Sie Vorher und Nachher auf dem echten Zielbildschirm, da Farbraumänderungen subtil und inhaltsabhängig sein können. + Beschränken Sie die Größenänderungsmodi + Erfahren Sie, wann Bilder, die den Grenzwert überschreiten, übersprungen, neu kodiert oder entsprechend verkleinert werden sollten + Wählen Sie, was am Limit passiert + Limit Resize ist nicht nur ein Tool zum Verkleinern von Bildern. Sein Modus entscheidet, was die App tut, wenn eine Quelle bereits innerhalb Ihrer Grenzen liegt oder wenn nur eine Seite gegen die Regel verstößt, was für gemischte Ordner und die Upload-Vorbereitung wichtig ist. + Verwenden Sie Überspringen, wenn Bilder innerhalb der Grenzen unberührt bleiben und nicht erneut exportiert werden sollen. + Verwenden Sie „Neu kodieren“, wenn die Abmessungen akzeptabel sind, Sie aber dennoch ein neues Format, Metadatenverhalten, eine neue Qualität oder einen neuen Dateinamen benötigen. + Verwenden Sie Zoom, wenn Bilder, die die Grenzwerte überschreiten, proportional verkleinert werden sollen, bis sie in den zulässigen Rahmen passen. + Seitenverhältnisziele + Vermeiden Sie gestreckte Flächen, abgeschnittene Kanten und unerwartete Ränder bei strengen Ausgabegrößen + Passen Sie die Form an, bevor Sie die Größe ändern + Breite und Höhe betragen nur die Hälfte eines Größenänderungsziels. Das Seitenverhältnis entscheidet darüber, ob das Bild natürlich passt, zugeschnitten werden muss oder ob eine Leinwand darum herum benötigt wird. Wenn Sie zunächst die Form überprüfen, werden überraschende Ergebnisse bei der Größenänderung vermieden. + Vergleichen Sie die Quellform mit der Zielform, bevor Sie „Explizit“, „Zuschneiden“ oder „Anpassen“ wählen. + Schneiden Sie zuerst zu, wenn das Motiv zusätzlichen Hintergrund verlieren kann und das Ziel einen strengen Rahmen benötigt. + Verwenden Sie „Anpassen“ oder „Flexibel“, wenn jeder Teil des Originalbilds sichtbar bleiben muss. + Hochskalierte oder normale Größenänderung + Wissen Sie, wann das Hinzufügen von Pixeln KI erfordert und wann eine einfache Skalierung ausreicht + Verwechseln Sie Größe nicht mit Detail + Bei normaler Größenänderung werden die Abmessungen durch Interpolation der Pixel geändert. es kann keine wirklichen Details erfinden. Die hochskalierte KI kann schärfere Details erzeugen, ist jedoch langsamer und kann Texturen halluzinieren. Daher hängt die richtige Wahl davon ab, ob Sie Kompatibilität, Geschwindigkeit oder visuelle Wiederherstellung benötigen. + Verwenden Sie die normale Größenänderung für Miniaturansichten, Upload-Limits, Screenshots und einfache Dimensionsänderungen. + Verwenden Sie AI Upscale, wenn ein kleines oder weiches Bild in einer größeren Größe besser aussehen soll. + Vergleichen Sie Gesichter, Texte und Muster nach der KI-Hochskalierung, da generierte Details überzeugend, aber falsch aussehen können. + Die Filterreihenfolge ist wichtig + Wenden Sie Bereinigung, Farbe, Schärfe und endgültige Komprimierung in einer bestimmten Reihenfolge an + Bauen Sie einen saubereren Filterstapel auf + Filter sind nicht immer austauschbar. Eine Unschärfe vor dem Schärfen, eine Kontrastverstärkung vor der OCR oder eine Farbänderung vor der Komprimierung können bei denselben Steuerelementen zu sehr unterschiedlichen Ergebnissen führen. + Zuerst zuschneiden und drehen, damit spätere Filter nur auf die verbleibenden Pixel wirken. + Wenden Sie Belichtung, Weißabgleich und Kontrast an, bevor Sie Schärfungs- oder Stilisierungseffekte vornehmen. + Behalten Sie die endgültige Größenänderung und Komprimierung gegen Ende bei, damit die in der Vorschau angezeigten Details den Export vorhersehbar überleben. + Hintergrundkanten korrigieren + Reinigen Sie Lichthöfe, übrig gebliebene Schatten, Haare, Löcher und weiche Konturen nach der automatischen Entfernung + Lassen Sie Ausschnitte gewollt aussehen + Automatische Masken sehen auf den ersten Blick oft gut aus, zeigen aber Probleme auf hellen, dunklen oder gemusterten Hintergründen. Die Kantenreinigung macht den Unterschied zwischen einem schnellen Ausschnitt und einem wiederverwendbaren Aufkleber, Logo oder Produktbild aus. + Sehen Sie sich das Ergebnis vor einem kontrastierenden Hintergrund in der Vorschau an, um Lichthöfe und fehlende Kantendetails sichtbar zu machen. + Verwenden Sie „Restore“ für Haare, Ecken und transparente Objekte, bevor Sie umliegende Reste entfernen. + Exportieren Sie einen kleinen Test der endgültigen Hintergrundfarbe, wenn der Ausschnitt in ein Design eingefügt wird. + Lesbare Wasserzeichen + Machen Sie Markierungen auf hellen, dunklen, belebten und beschnittenen Bildern sichtbar, ohne das Foto zu ruinieren + Schützen Sie das Bild, ohne es zu verbergen + Ein Wasserzeichen, das auf einem Bild gut aussieht, kann auf einem anderen verschwinden. Größe, Deckkraft, Kontrast, Platzierung und Wiederholung sollten für den gesamten Stapel ausgewählt werden, nicht nur für die erste Vorschau. + Testen Sie die Markierung an den hellsten und dunkelsten Bildern im Stapel, bevor Sie alle Dateien exportieren. + Verwenden Sie eine geringere Deckkraft für große wiederholte Markierungen und einen stärkeren Kontrast für kleine Eckmarkierungen. + Halten Sie wichtige Gesichter, Texte und Produktdetails deutlich sichtbar, es sei denn, die Markierung soll eine Wiederverwendung verhindern. + Teilen Sie ein Bild in Kacheln auf + Schneiden Sie ein einzelnes Bild nach Zeilen, Spalten, benutzerdefinierten Prozentsätzen, Format und Qualität aus + Bereiten Sie Gitter und geordnete Stücke vor + Durch die Bildaufteilung werden mehrere Ausgaben aus einem Quellbild erstellt. Es kann nach Zeilen- und Spaltenanzahl aufteilen, Zeilen- oder Spaltenprozentsätze anpassen und Teile mit dem ausgewählten Ausgabeformat und der ausgewählten Ausgabequalität speichern, was für Raster, Seitenausschnitte, Panoramen und geordnete Social-Media-Beiträge nützlich ist. + Wählen Sie das Quellbild aus und legen Sie dann die Anzahl der benötigten Zeilen und Spalten fest. + Passen Sie die Zeilen- oder Spaltenprozentsätze an, wenn die Teile nicht alle gleich groß sein sollen. + Wählen Sie vor dem Speichern das Ausgabeformat und die Qualität aus, damit für jedes generierte Stück dieselben Exportregeln gelten. + Bildstreifen ausschneiden + Entfernen Sie vertikale oder horizontale Bereiche und fügen Sie die verbleibenden Teile wieder zusammen + Entfernen Sie einen Bereich, ohne eine Lücke zu hinterlassen + Das Zuschneiden von Bildern unterscheidet sich vom normalen Zuschneiden. Es kann einen ausgewählten vertikalen oder horizontalen Streifen entfernen und dann die verbleibenden Bildteile zusammenführen. Im umgekehrten Modus bleibt stattdessen der ausgewählte Bereich erhalten, und bei Verwendung beider umgekehrter Richtungen bleibt das ausgewählte Rechteck erhalten. + Verwenden Sie vertikales Schneiden für Seitenbereiche, Säulen oder Mittelstreifen. Verwenden Sie den horizontalen Schnitt für Banner, Lücken oder Reihen. + Aktivieren Sie die Umkehrung einer Achse, wenn Sie das ausgewählte Teil behalten möchten, anstatt es zu entfernen. + Zeigen Sie nach dem Schneiden eine Vorschau der Kanten an, da zusammengeführte Inhalte abrupt aussehen können, wenn der entfernte Bereich wichtige Details überschreitet. + Wählen Sie das Ausgabeformat + Wählen Sie je nach Inhalt und Ziel JPEG, PNG, WebP, AVIF, JXL oder PDF aus + Lassen Sie das Ziel über das Format entscheiden + Das beste Format hängt davon ab, was das Bild enthält und wo es verwendet wird. Fotos, Screenshots, transparente Assets, Dokumente und animierte Dateien scheitern alle auf unterschiedliche Weise, wenn sie in das falsche Format gezwungen werden. + Verwenden Sie JPEG für eine umfassende Fotokompatibilität, wenn Transparenz und genaue Pixel nicht erforderlich sind. + Verwenden Sie PNG für scharfe Screenshots, UI-Erfassungen, transparente Grafiken und verlustfreie Bearbeitungen. + Verwenden Sie WebP, AVIF oder JXL, wenn eine kompakte, moderne Ausgabe wichtig ist und die empfangende App dies unterstützt. + Extrahieren Sie Audiocover + Speichern Sie eingebettete Albumcover oder verfügbare Medienrahmen aus Audiodateien als PNG-Bilder + Ziehen Sie Bildmaterial aus Audiodateien heraus + Audio Covers verwendet Android-Medienmetadaten, um eingebettete Grafiken aus Audiodateien zu lesen. Wenn eingebettete Grafiken nicht verfügbar sind, greift der Retriever möglicherweise auf einen Medienrahmen zurück, sofern Android einen bereitstellen kann. Wenn keines vorhanden ist, meldet das Tool, dass kein Bild zum Extrahieren vorhanden ist. + Öffnen Sie Audio Covers und wählen Sie eine oder mehrere Audiodateien mit dem erwarteten Coverbild aus. + Überprüfen Sie das extrahierte Cover, bevor Sie es speichern oder teilen, insbesondere bei Dateien aus Streaming- oder Aufnahme-Apps. + Wenn kein Bild gefunden wird, verfügt die Audiodatei wahrscheinlich über kein eingebettetes Cover oder Android konnte keinen verwendbaren Frame anzeigen. + Daten und Standortmetadaten + Entscheiden Sie, was aufbewahrt werden soll, wenn die Aufnahmezeit wichtig ist, aber GPS- oder Gerätedaten nicht verloren gehen sollen + Trennen Sie nützliche Metadaten von privaten Metadaten + Metadaten sind nicht alle gleichermaßen riskant. Erfassungsdaten können für Archivierungen und Sortierungen nützlich sein, während GPS, Geräteserien-ähnliche Felder, Software-Tags oder Besitzerfelder mehr verraten können als beabsichtigt. + Behalten Sie Datums- und Uhrzeitmarkierungen bei, wenn die chronologische Reihenfolge für Alben, Scans oder den Projektverlauf wichtig ist. + Entfernen Sie GPS- und geräteidentifizierende Tags, bevor Sie Bilder öffentlich teilen oder an Fremde senden. + Exportieren Sie ein Beispiel und überprüfen Sie EXIF ​​erneut, wenn die Datenschutzanforderungen streng sind. + Vermeiden Sie Dateinamenkollisionen + Schützen Sie Batch-Ausgaben, wenn viele Dateien Namen wie image.jpg oder Screenshot.png haben + Machen Sie jeden Ausgabenamen eindeutig + Doppelte Dateinamen kommen häufig vor, wenn Bilder von Messengern, Screenshots, Cloud-Ordnern oder mehreren Kameras stammen. Ein gutes Muster verhindert ein versehentliches Ersetzen und sorgt dafür, dass die Ausgaben bis zu ihren Quellen zurückverfolgt werden können. + Geben Sie den Originalnamen und ein Suffix an, wenn die bearbeitete Kopie erkennbar sein soll. + Fügen Sie Sequenz, Zeitstempel, Voreinstellung oder Dimensionen hinzu, wenn Sie große gemischte Chargen verarbeiten. + Vermeiden Sie das Überschreiben von Workflows, bis Sie sichergestellt haben, dass das Benennungsmuster nicht kollidieren kann. + Speichern oder teilen + Wählen Sie zwischen einer dauerhaften Datei und einer temporären Übergabe an eine andere App + Exportieren Sie mit der richtigen Absicht + Speichern und Teilen können sich ähnlich anfühlen, lösen aber unterschiedliche Probleme. Durch das Speichern wird eine Datei erstellt, die Sie später finden können. Beim Teilen wird ein generiertes Ergebnis über Android an eine andere App gesendet und möglicherweise keine dauerhafte lokale Kopie hinterlassen. + Verwenden Sie „Speichern“, wenn die Ausgabe in einem bekannten Ordner bleiben, gesichert oder später wiederverwendet werden muss. + Verwenden Sie „Teilen“ für schnelle Nachrichten, E-Mail-Anhänge, Beiträge in sozialen Netzwerken oder das Senden des Ergebnisses an einen anderen Redakteur. + Speichern Sie zuerst, wenn die empfangende App unzuverlässig ist oder wenn die Wiederherstellung einer fehlgeschlagenen Freigabe mühsam wäre. + PDF-Seitengröße und Ränder + Wählen Sie Papiergröße, Passform und Freiraum, bevor Sie ein Dokument erstellen + Machen Sie Bildseiten druckbar und lesbar + Bilder können zu unhandlichen PDF-Seiten werden, wenn ihre Form nicht mit der gewählten Papiergröße übereinstimmt. Ränder, Anpassungsmodus und Seitenausrichtung entscheiden darüber, ob der Inhalt beschnitten, klein, gestreckt oder angenehm lesbar ist. + Verwenden Sie ein echtes Papierformat, wenn das PDF gedruckt oder an ein Formular gesendet werden soll. + Verwenden Sie Ränder für Scans und Screenshots, damit der Text nicht am Seitenrand anliegt. + Zeigen Sie Seiten im Hoch- und Querformat gleichzeitig in der Vorschau an, wenn die Quellbilder eine gemischte Ausrichtung haben. + Bereinigen Sie Scans + Verbessern Sie Papierkanten, Schatten, Kontrast, Drehung und Lesbarkeit vor PDF oder OCR + Bereiten Sie Seiten vor der Archivierung vor + Ein Scan lässt sich zwar technisch erfassen, ist aber dennoch schwer lesbar. Kleine Bereinigungsschritte vor dem PDF-Export sind oft wichtiger als Komprimierungseinstellungen, insbesondere bei Quittungen, handschriftlichen Notizen und Seiten mit wenig Licht. + Korrigieren Sie die Perspektive und entfernen Sie Tischkanten, Finger, Schatten und Hintergrundunordnung. + Erhöhen Sie den Kontrast vorsichtig, damit der Text klarer wird, ohne Stempel, Unterschriften oder Bleistiftmarkierungen zu zerstören. + Führen Sie OCR erst aus, wenn die Seite aufrecht und lesbar ist, und überprüfen Sie dann wichtige Zahlen manuell. + Komprimieren, Graustufen oder Reparieren von PDFs + Verwenden Sie PDF-Vorgänge mit nur einer Datei für leichtere, druckbare oder neu erstellte Dokumentkopien + Wählen Sie den PDF-Bereinigungsvorgang aus + PDF Tools umfasst separate Vorgänge für Komprimierung, Graustufenkonvertierung und Reparatur. Komprimierung und Graustufen funktionieren hauptsächlich über eingebettete Bilder, während die Reparatur die PDF-Struktur neu aufbaut; Nichts davon sollte als garantierte Lösung für jedes Dokument angesehen werden. + Verwenden Sie „PDF komprimieren“, wenn das Dokument Bilder enthält und die Dateigröße für die Weitergabe wichtig ist. + Verwenden Sie Graustufen, wenn das Dokument einfacher zu drucken sein soll oder keine Farbbilder benötigt. + Verwenden Sie „PDF reparieren“ für eine Kopie, wenn ein Dokument nicht geöffnet werden kann oder die interne Struktur beschädigt ist, und überprüfen Sie dann die wiederhergestellte Datei. + Extrahieren Sie Bilder aus PDFs + Stellen Sie eingebettete PDF-Bilder wieder her und packen Sie die extrahierten Ergebnisse in ein Archiv + Speichern Sie Quellbilder aus Dokumenten + „Bilder extrahieren“ durchsucht die PDF-Datei nach eingebetteten Bildobjekten, überspringt Duplikate nach Möglichkeit und speichert wiederhergestellte Bilder in einem generierten ZIP-Archiv. Es werden nur Bilder extrahiert, die tatsächlich in das PDF eingebettet sind, nicht jedoch Text, Vektorzeichnungen oder jede sichtbare Seite als Screenshot. + Verwenden Sie „Bilder extrahieren“, wenn Sie Bilder benötigen, die in einer PDF-Datei gespeichert sind, z. B. Fotos aus einem Katalog oder gescannte Assets. + Verwenden Sie stattdessen „PDF in Bilder“ oder die Seitenextraktion, wenn Sie vollständige Seiten einschließlich Text und Layout benötigen. + Wenn das Tool keine eingebetteten Bilder meldet, besteht die Seite möglicherweise aus Text-/Vektorinhalten oder die Bilder sind möglicherweise nicht als extrahierbare Objekte gespeichert. + Metadaten, Reduzierung und Druckausgabe + Bearbeiten Sie Dokumenteigenschaften, rastern Sie Seiten oder erstellen Sie gezielt benutzerdefinierte Drucklayouts + Wählen Sie die Aktion „Abschließendes Dokument“. + PDF-Metadaten, Reduzierung und Druckvorbereitung lösen verschiedene Probleme im Endstadium. Metadaten steuern Dokumenteigenschaften, Flatten PDF rastern Seiten in eine weniger bearbeitbare Ausgabe und Print PDF bereitet ein neues Layout mit Seitengröße, Ausrichtung, Rand und Seiten-pro-Blatt-Steuerelementen vor. + Verwenden Sie Metadaten, wenn Autor, Titel, Schlüsselwörter, Produzent oder Datenschutzbereinigung wichtig sind. + Verwenden Sie „PDF reduzieren“, wenn sichtbare Seiteninhalte schwieriger als separate PDF-Objekte bearbeitet werden sollen. + Verwenden Sie „PDF drucken“, wenn Sie eine benutzerdefinierte Seitengröße, Ausrichtung, Ränder oder mehrere Seiten pro Blatt benötigen. + Bereiten Sie Bilder für OCR vor + Verwenden Sie Zuschneiden, Drehen, Kontrast, Rauschunterdrückung und Skalierung, bevor Sie OCR zum Lesen von Text auffordern + Sorgen Sie für sauberere OCR-Pixel + OCR-Fehler entstehen oft durch das Eingabebild und nicht durch die OCR-Engine. Schiefe Seiten, geringer Kontrast, Unschärfe, Schatten, kleiner Text und gemischte Hintergründe beeinträchtigen die Erkennungsqualität. + Schneiden Sie es auf den Textbereich zu und drehen Sie das Bild, sodass die Linien vor der Erkennung horizontal verlaufen. + Erhöhen Sie den Kontrast oder wechseln Sie nur dann zu saubererem Schwarzweiß, wenn dies die Lesbarkeit der Buchstaben erleichtert. + Verkleinern Sie winzigen Text moderat nach oben, vermeiden Sie jedoch aggressives Schärfen, das falsche Kanten erzeugt. + Überprüfen Sie den QR-Inhalt + Überprüfen Sie Links, WLAN-Anmeldeinformationen, Kontakte und Aktionen, bevor Sie einem Code vertrauen + Behandeln Sie dekodierten Text als nicht vertrauenswürdige Eingabe + Ein QR-Code kann eine URL, eine Kontaktkarte, ein WLAN-Passwort, ein Kalenderereignis, eine Telefonnummer oder einfachen Text verbergen. Das sichtbare Etikett neben dem Code stimmt möglicherweise nicht mit der tatsächlichen Nutzlast überein. Überprüfen Sie daher den entschlüsselten Inhalt, bevor Sie darauf reagieren. + Überprüfen Sie vor dem Öffnen die vollständig entschlüsselte URL, insbesondere verkürzte oder unbekannte Domänen. + Seien Sie vorsichtig mit WLAN-, Kontakt-, Kalender-, Telefon- und SMS-Codes, da diese sensible Aktionen auslösen können. + Kopieren Sie den Inhalt zuerst, wenn Sie ihn an anderer Stelle überprüfen müssen, anstatt ihn sofort zu öffnen. + Generieren Sie QR-Codes + Erstellen Sie Codes aus reinem Text, URLs, WLAN, Kontakten, E-Mail, Telefon, SMS und Standortdaten + Erstellen Sie die richtige QR-Nutzlast + Der QR-Bildschirm kann sowohl Codes aus eingegebenen Inhalten generieren als auch vorhandene scannen. Strukturierte QR-Typen helfen dabei, Daten in einem Format zu kodieren, das andere Apps verstehen, z. B. WLAN-Anmeldedaten, Kontaktkarten, E-Mail-Felder, Telefonnummern, SMS-Inhalte, URLs oder geografische Koordinaten. + Wählen Sie einfachen Text für einfache Notizen oder verwenden Sie einen strukturierten Typ, wenn der Code als WLAN, Kontakt, URL, E-Mail, Telefon, SMS oder Standortdaten geöffnet werden soll. + Überprüfen Sie jedes Feld, bevor Sie den generierten Code teilen, da die sichtbare Vorschau nur die von Ihnen eingegebene Nutzlast widerspiegelt. + Testen Sie den gespeicherten QR-Code mit einem Scanner, wenn er gedruckt, öffentlich veröffentlicht oder von anderen Personen verwendet wird. + Wählen Sie einen Prüfsummenmodus + Berechnen Sie Hashes aus Dateien oder Text, vergleichen Sie eine Datei oder führen Sie eine Stapelprüfung mehrerer Dateien durch + Überprüfen Sie den Inhalt, nicht das Erscheinungsbild + Checksum Tools verfügt über separate Registerkarten für Datei-Hashing, Text-Hashing, Vergleich einer Datei mit einem bekannten Hash und Stapelvergleich. Eine Prüfsumme beweist die Byte-für-Byte-Identität des ausgewählten Algorithmus. es beweist nicht, dass zwei Bilder lediglich ähnlich aussehen. + Verwenden Sie „Berechnen“ für Dateien und „Text-Hash“ für eingegebene oder eingefügte Textdaten. + Verwenden Sie „Vergleichen“, wenn Ihnen jemand eine erwartete Prüfsumme für eine Datei gibt. + Verwenden Sie den Stapelvergleich, wenn viele Dateien Hashes oder eine Überprüfung in einem Durchgang benötigen, und halten Sie dann den ausgewählten Algorithmus konsistent. + Datenschutz in der Zwischenablage + Nutzen Sie automatische Zwischenablagefunktionen, ohne versehentlich private kopierte Daten preiszugeben + Kopieren Sie Inhalte absichtlich + Die Automatisierung der Zwischenablage ist praktisch, kopierter Text kann jedoch Passwörter, Links, Adressen, Token oder private Notizen enthalten. Behandeln Sie die Eingabe in der Zwischenablage als eine Geschwindigkeitsfunktion, die Ihren Gewohnheiten und Datenschutzerwartungen entsprechen sollte. + Deaktivieren Sie die automatische Verwendung der Zwischenablage, wenn privater Text unerwartet in Tools angezeigt wird. + Verwenden Sie die reine Zwischenablage nur dann, wenn Sie bewusst möchten, dass das Ergebnis nicht gespeichert wird. + Löschen oder ersetzen Sie die Zwischenablage, nachdem Sie vertraulichen Text, QR-Daten oder Base64-Nutzlasten verarbeitet haben. + Farbformate + Machen Sie sich mit HEX-, RGB-, HSV-, Alpha- und kopierten Farbwerten vertraut, bevor Sie sie an anderer Stelle einfügen + Kopieren Sie das Format, das Ziel erwartet + Die gleiche sichtbare Farbe kann auf verschiedene Arten geschrieben werden. Ein Designtool, ein CSS-Feld, ein Android-Farbwert oder ein Bildeditor erwarten möglicherweise eine andere Reihenfolge, Alpha-Behandlung oder ein anderes Farbmodell. + Verwenden Sie HEX beim Einfügen in Web-, Design- oder Theme-Felder, die kompakte Farbcodes erwarten. + Verwenden Sie RGB oder HSV, wenn Sie Kanäle, Helligkeit, Sättigung oder Farbton manuell anpassen müssen. + Überprüfen Sie Alpha separat, wenn Transparenz wichtig ist, da einige Ziele es ignorieren oder eine andere Reihenfolge verwenden. + Durchsuchen Sie die Farbbibliothek + Suchen Sie benannte Farben nach Namen oder HEX und behalten Sie die Favoriten ganz oben bei + Finden Sie wiederverwendbare benannte Farben + Die Farbbibliothek lädt eine benannte Farbsammlung, sortiert sie nach Namen, filtert nach Farbnamen oder HEX-Wert und speichert Lieblingsfarben, damit wichtige Einträge leichter zu erreichen sind. Dies ist nützlich, wenn Sie eine tatsächlich benannte Farbe benötigen, anstatt sie aus einem Bild abzutasten. + Suchen Sie nach einem Farbnamen, wenn Sie die Familie kennen, z. B. Rot, Blau, Schiefer oder Minze. + Suchen Sie nach HEX, wenn Sie bereits über eine numerische Farbe verfügen und übereinstimmende oder in der Nähe befindliche benannte Einträge finden möchten. + Markieren Sie häufige Farben als Favoriten, damit sie beim Durchsuchen vor der allgemeinen Bibliothek angezeigt werden. + Verwenden Sie Mesh-Verlaufssammlungen + Laden Sie verfügbare Mesh-Verlaufsressourcen herunter und teilen Sie ausgewählte Bilder + Verwenden Sie Remote-Mesh-Verlaufselemente + Mesh Gradients kann eine Remote-Ressourcensammlung laden, fehlende Verlaufsbilder herunterladen, den Download-Fortschritt anzeigen und ausgewählte Verläufe teilen. Die Verfügbarkeit hängt vom Netzwerkzugriff und dem Remote-Ressourcenspeicher ab. Eine leere Liste bedeutet daher normalerweise, dass Ressourcen noch nicht verfügbar waren. + Öffnen Sie „Mesh Gradients“ über die Verlaufswerkzeuge, wenn Sie fertige Mesh-Verlaufsbilder benötigen. + Warten Sie, bis die Ressource geladen oder heruntergeladen wurde, bevor Sie davon ausgehen, dass die Sammlung leer ist. + Wählen Sie die benötigten Farbverläufe aus und geben Sie sie frei, oder kehren Sie zu Gradient Maker zurück, wenn Sie manuell einen benutzerdefinierten Farbverlauf erstellen möchten. + Generierte Asset-Auflösung + Wählen Sie die Ausgabegröße aus, bevor Sie Farbverläufe, Rauschen, Hintergrundbilder, SVG-ähnliche Grafiken oder Texturen erzeugen + Generieren Sie es in der von Ihnen benötigten Größe + Generierte Bilder verfügen nicht über ein Kameraoriginal, auf das zurückgegriffen werden kann. Wenn die Ausgabe zu klein ist, können durch eine spätere Skalierung Muster weicher gemacht, Details verschoben oder die Kachelung und Komprimierung des Assets geändert werden. + Legen Sie zunächst die Abmessungen für den endgültigen Anwendungsfall fest, z. B. Hintergrundbild, Symbol, Hintergrund, Druck oder Overlay. + Verwenden Sie größere Größen für Texturen, die zugeschnitten, gezoomt oder in mehreren Layouts wiederverwendet werden können. + Exportieren Sie Testversionen vor großen Ausgaben, da Verfahrensdetails das Komprimierungsverhalten ändern können. + Aktuelle Hintergrundbilder exportieren + Rufen Sie verfügbare Home-, Lock- und integrierte Hintergrundbilder vom Gerät ab + Speichern oder teilen Sie installierte Hintergrundbilder + Der Hintergrundbild-Export liest die Hintergrundbilder, die Android über die Hintergrundbild-APIs des Systems bereitstellt. Je nach Gerät, Android-Version, Berechtigungen und Build-Variante können Home-, Lock- oder integrierte Hintergrundbilder separat verfügbar sein oder fehlen. + Öffnen Sie „Hintergrundbilder exportieren“, um die Hintergrundbilder zu laden, die das System der App zum Lesen zulässt. + Wählen Sie die verfügbaren Home-, Lock- oder integrierten Hintergrundbildeinträge aus, die Sie speichern, kopieren oder teilen möchten. + Wenn ein Hintergrundbild fehlt oder die Funktion nicht verfügbar ist, handelt es sich in der Regel eher um eine System-, Berechtigungs- oder Build-Varianten-Einschränkung als um eine Bearbeitungseinstellung. + Ecken-Animationsdrossel + Farbe kopieren als + HEX + Name + Portrait-Mattenmodell für schnelle Personenausschnitte mit weicherem Haar- und Kantenhandling. + Schnelle Verbesserung bei schwachem Licht mithilfe der Zero-DCE++-Kurvenschätzung. + Restormer-Bildwiederherstellungsmodell zur Reduzierung von Regenstreifen und Artefakten bei nassem Wetter. + Dokumententzerrungsmodell, das ein Koordinatengitter vorhersagt und gekrümmte Seiten in einen flacheren Scan umwandelt. + Bewegungsdauerskala + Steuert die Geschwindigkeit der App-Animation: 0 deaktiviert die Bewegung, 1 ist normal, höhere Werte verlangsamen die Animationen + Aktuelle Tools anzeigen + Zeigen Sie den Schnellzugriff auf zuletzt verwendete Tools auf dem Hauptbildschirm an + Nutzungsstatistik + Entdecke App-Öffnungen, Tool-Starts und deine am häufigsten verwendeten Tools + App geöffnet + Tool-Öffnungen + Letztes Werkzeug + Erfolgreiche Speicherungen + Aktivitätssträhne + Daten gespeichert + Top-Format + Verwendete Werkzeuge + %1$d Öffnungen • %2$s + Noch keine Nutzungsstatistiken + Öffnen Sie einige Tools und sie werden hier automatisch angezeigt + Ihre Nutzungsstatistiken werden nur lokal auf Ihrem Gerät gespeichert. ImageToolbox verwendet sie nur, um Ihre persönlichen Aktivitäten, bevorzugten Tools und gespeicherten Dateneinblicke anzuzeigen + Editor Foto bearbeiten Bild retuschieren anpassen + Größe ändern, konvertieren, skalieren, Auflösung, Abmessungen, Breite, Höhe, JPG, JPEG, PNG, WebP, komprimieren + Größe komprimieren, Gewicht Bytes, MB, kb, Qualität reduzieren, optimieren + Seitenverhältnis des Zuschnitts + Filtereffekt Farbkorrektur Lut-Maske anpassen + Zeichnen, Pinsel, Bleistift, Kommentieren, Markieren + Chiffre verschlüsseln entschlüsseln Passwort geheim + Hintergrundentferner löschen Ausschnitt entfernen transparentes Alpha + Vorschau-Viewer-Galerie, Inspektion, geöffnet + Stitch Merge Combine Panorama langer Screenshot + URL-Web-Download-Internet-Link-Bild + Picker Pipette Probe Hex RGB-Farbe + Farbfelder der Palette extrahieren dominant + Exif-Metadaten-Datenschutz entfernen, GPS-Standort entfernen und bereinigen + vergleiche diff differenz vorher nachher + Begrenzung der Größenänderung, max. min. Megapixel, Abmessungen, Klemme + Tools für PDF-Dokumentseiten + OCR-Texterkennung, Extrakt-Scan + Hintergrundfarbverlaufsnetz + Wasserzeichen-Logo-Textstempel-Overlay + GIF-Animation animierte Frames konvertieren + apng-Animation animierte PNG-Frames konvertieren + ZIP-Archiv komprimieren Pack Dateien entpacken + jxl jpeg xl konvertiert das JPG-Bildformat + SVG-Vektor-Trace-Konvertierung in XML + Format konvertieren jpg jpeg png webp heic avif jxl bmp + Dokumentenscanner, Papierperspektive, Zuschnitt scannen + QR-Barcode-Code-Scanner-Scan + Stacking Overlay durchschnittliche mittlere Astrofotografie + Geteilte Kachelgitter-Scheibenteile + Farbe konvertieren hex rgb hsl hsv cmyk lab + WebP konvertiert animiertes Bildformat + Rauschgenerator, zufällige Texturkörnung + Collage-Raster-Layout kombinieren + Markup-Ebenen kommentieren Overlay-Bearbeitung + Base64-Codierung, Decodierung der Daten-URI + Prüfsumme Hash MD5 sha1 sha256 überprüfen + Mesh-Hintergrund mit Farbverlauf + Exif-Metadaten bearbeiten GPS-Datumskamera + Schneiden Sie geteilte Gitterstücke aus Fliesen + Audio-Cover, Albumcover, Musik-Metadaten + Hintergrundbild-Exporthintergrund + ASCII-Textkunstzeichen + AI ml neuronale Verbesserung des gehobenen Segments + Farbbibliothek-Materialpalette mit dem Namen „Farben“. + Shader-Glsl-Fragment-Effekt-Studio + PDF zusammenführen, kombinieren, verbinden + PDF trennt einzelne Seiten + PDF-Seitenausrichtung drehen + PDF neu anordnen, Seiten neu anordnen, sortieren + Paginierung von PDF-Seitenzahlen + PDF-OCR-Text erkennt durchsuchbaren Extrakt + PDF-Wasserzeichen-Stempel-Logo-Text + PDF-Signaturzeichen zeichnen + PDF-Passwort schützen, verschlüsseln, sperren + PDF-Passwort entsperren, entschlüsseln, Schutz entfernen + PDF komprimieren, Größe reduzieren, optimieren + pdf Graustufen schwarz weiß monochrom + PDF-Reparatur reparieren, Wiederherstellung kaputt + PDF-Metadateneigenschaften Exif-Autortitel + pdf entfernen Seiten löschen + PDF-Zuschnittsränder + PDF-Anmerkungen reduzieren Formularebenen + PDF-Extrahieren von Bildern, Bildern + PDF-Zip-Archiv komprimieren + PDF-Drucker + PDF-Vorschau-Viewer lesen + Bilder in PDF konvertieren, JPG, JPEG, PNG-Dokument + PDF in Bilderseiten exportieren, JPG, PNG + PDF-Anmerkungen Kommentare sauber entfernen + Neutrale Variante + Fehler + Schlagschatten + Zahnhöhe + Horizontaler Zahnbereich + Vertikaler Zahnbereich + Oberkante + Rechter Rand + Unterkante + Linker Rand + Abgerissener Rand + Nutzungsstatistik zurücksetzen + Tool-Öffnungen, Speicherstatistiken, Streak, gespeicherte Daten und das Topformat werden zurückgesetzt. App-Öffnungen bleiben unverändert. + Audio + Datei + Profile exportieren + Aktuelles Profil speichern + Exportprofil löschen + Möchten Sie das Exportprofil „%1$s“ löschen? + Rahmen zeichnen + Zeigen Sie einen dünnen Rand um die Zeichen- und Löschfläche an + Wellig + Abgerundete UI-Elemente mit einer weichen, gewellten Kante + Scoop + Kerbe + Abgerundete Ecken nach innen geschnitzt + Abgesetzte Ecken mit Doppelschnitt + Platzhalter + Verwenden Sie {filename}, um den ursprünglichen Dateinamen ohne Erweiterung einzufügen + Fackel + Grundbetrag + Ringbetrag + Ray-Menge + Ringbreite + Perspektive verzerren + Java-Look and Feel + Scheren + X-Winkel + und Winkel + Ausgabegröße ändern + Wassertropfen + Wellenlänge + Phase + Hochpass + Farbmaske + Chrom + Lösen + Weichheit + Rückmeldung + Linsenunschärfe + Geringer Input + Hoher Input + Geringe Leistung + Hohe Leistung + Lichteffekte + Stoßhöhe + Beulenweichheit + Welligkeit + X-Amplitude + Y-Amplitude + X-Wellenlänge + Y-Wellenlänge + Adaptive Unschärfe + Texturgenerierung + Generieren Sie verschiedene prozedurale Texturen und speichern Sie sie als Bilder + Texturtyp + Voreingenommenheit + Zeit + Glanz + Streuung + Proben + Winkelkoeffizient + Gradientenkoeffizient + Gittertyp + Distanzkraft + Skala Y + Unschärfe + Basistyp + Turbulenzfaktor + Skalierung + Iterationen + Ringe + Gebürstetes Metall + Ätzmittel + Mobilfunk + Schachbrett + fBm + Marmor + Plasma + Decke + Holz + zufällig + Quadrat + Sechseckig + Achteckig + Dreieckig + Geriffelt + VL-Rauschen + SC-Rauschen + Jüngste + Noch keine kürzlich verwendeten Filter + Texturgenerator, gebürstetes Metall, Ätzmittel, Zellular, Schachbrett, Marmor, Plasma, Steppdecke, Holz, Ziegel, Tarnung, Zelle, Wolke, Riss, Stoff, Blattwerk, Wabe, Eis, Lava, Nebel, Papier, Rost, Sand, Rauch, Stein, Gelände, Topographie, Wasser, Wellen + Ziegel + Tarnung + Zelle + Wolke + Riss + Stoff + Laub + Bienenwabe + Eis + Lava + Nebel + Papier + Rost + Sand + Rauch + Stein + Terrain + Topographie + Wasserwelligkeit + Fortgeschrittenes Holz + Mörtelbreite + Unregelmäßigkeit + Fase + Rauheit + Erste Schwelle + Zweiter Schwellenwert + Dritte Schwelle + Kantenweichheit + Randbreite + Abdeckung + Detail + Tiefe + Verzweigung + Horizontale Fäden + Vertikale Fäden + Flaum + Venen + Beleuchtung + Rissbreite + Frost + Fließen + Kruste + Wolkendichte + Sterne + Faserdichte + Faserstärke + Flecken + Korrosion + Lochfraß + Flocken + Dünenfrequenz + Windwinkel + Wellen + Irrlichter + Venenskala + Wasserstand + Bergniveau + Erosion + Schneehöhe + Zeilenanzahl + Linienstärke + Schattierung + Porenfarbe + Mörtelfarbe + Dunkle Ziegelfarbe + Ziegelfarbe + Farbe hervorheben + Dunkle Farbe + Waldfarbe + Erdfarbe + Sandfarbe + Hintergrundfarbe + Zellfarbe + Kantenfarbe + Himmelsfarbe + Schattenfarbe + Helle Farbe + Oberflächenfarbe + Variationsfarbe + Rissfarbe + Warpfarbe + Schussfarbe + Dunkle Blattfarbe + Blattfarbe + Randfarbe + Honigfarbe + Tiefe Farbe + Eisfarbe + Frostfarbe + Krustenfarbe + Farbe waschen + Leuchtende Farbe + Raumfarbe + Violette Farbe + Blaue Farbe + Grundfarbe + Faserfarbe + Fleckenfarbe + Metallfarbe + Dunkle Rostfarbe + Rostfarbe + Orange Farbe + Rauchfarbe + Venenfarbe + Tieflandfarbe + Wasserfarbe + Felsfarbe + Schneefarbe + Niedrige Farbe + Hohe Farbe + Linienfarbe + Flache Farbe + Gras + Schmutz + Leder + Beton + Asphalt + Moos + Feuer + Aurora + Ölteppich + Aquarell + Abstrakter Fluss + Opal + Damaststahl + Blitz + Samt + Tintenmarmorierung + Holografische Folie + Biolumineszenz + Kosmischer Wirbel + Lavalampe + Ereignishorizont + Fraktale Blüte + Chromatischer Tunnel + Sonnenfinsternis Corona + Seltsamer Attraktor + Ferrofluid-Krone + Supernova + Iris + Pfauenfeder + Nautilus-Muschel + Ringplanet + Klingendichte + Klingenlänge + Wind + Lückenhaft + Klumpen + Feuchtigkeit + Kieselsteine + Falten + Poren + Aggregat + Risse + Teer + Tragen + Flecken + Fasern + Flammenfrequenz + Rauch + Intensität + Bänder + Bands + Irisieren + Dunkelheit + Blüht + Pigment + Kanten + Papier + Diffusion + Symmetrie + Schärfe + Farbspiel + Milchigkeit + Schichten + Falten + Polieren + Zweige + Richtung + Glanz + Falten + Federn + Tintenbilanz + Spektrum + Falten + Beugung + Waffen + Twist + Kernglühen + Kleckse + Scheibenneigung + Horizontgröße + Scheibenbreite + Linseneffekt + Blütenblätter + Locken + Filigran + Facetten + Krümmung + Mondgröße + Corona-Größe + Strahlen + Diamantring + Lappen + Orbitdichte + Dicke + Spikes + Spikelänge + Körpergröße + Metallisch + Stoßradius + Schalenbreite + Rausgeworfen + Schülergröße + Irisgröße + Farbvariation + Catchlight + Augengröße + Widerhakendichte + Wendet sich + Kammern + Öffnung + Grate + Perlglanz + Planetengröße + Ringneigung + Ringbreite + Atmosphäre + Schmutzfarbe + Dunkle Grasfarbe + Grasfarbe + Spitzenfarbe + Dunkle Erdfarbe + Trockene Farbe + Kieselsteinfarbe + Lederfarbe + Betonfarbe + Teerfarbe + Asphaltfarbe + Steinfarbe + Staubfarbe + Bodenfarbe + Dunkle Moosfarbe + Moosfarbe + Rote Farbe + Kernfarbe + Grüne Farbe + Cyanfarbene Farbe + Magenta-Farbe + Goldfarbe + Papierfarbe + Pigmentfarbe + Sekundärfarbe + Erste Farbe + Zweite Farbe + Rosa Farbe + Dunkle Stahlfarbe + Stahlfarbe + Helle Stahlfarbe + Oxidfarbe + Halo-Farbe + Schraubenfarbe + Samtfarbe + Glanzfarbe + Blaue Tintenfarbe + Rote Tintenfarbe + Dunkle Tintenfarbe + Silberne Farbe + Gelbe Farbe + Gewebefarbe + Festplattenfarbe + Heiße Farbe + Linsenfarbe + Außenfarbe + Innenfarbe + Corona-Farbe + Kalte Farbe + Warme Farbe + Wolkenfarbe + Flammenfarbe + Federfarbe + Schalenfarbe + Perlenfarbe + Planetenfarbe + Ringfarbe + Originaldateien nach dem Speichern löschen + Nur erfolgreich verarbeitete Quelldateien werden gelöscht + Originaldateien gelöscht: %1$d + Originaldateien konnten nicht gelöscht werden: %1$d + Originaldateien gelöscht: %1$d, fehlgeschlagen: %2$d + Blattgesten + Erlauben Sie das Ziehen erweiterter Optionsblätter + Chroma-Unterabtastung + Bittiefe + Verlustfrei + %1$d-Bit + Stapelumbenennung + Benennen Sie mehrere Dateien mithilfe von Dateinamenmustern um + Batch-Umbenennung von Dateien, Dateinamen, Muster, Sequenz, Datumserweiterung + Wählen Sie Dateien zum Umbenennen aus + Dateinamenmuster + Umbenennen + Datumsquelle + EXIF-Datum übernommen + Änderungsdatum der Datei + Erstellungsdatum der Datei + Aktuelles Datum + Manuelles Datum + Manuelles Datum + Manuelle Zeit + Das ausgewählte Datum ist für einige Dateien nicht verfügbar. Für sie wird das aktuelle Datum verwendet. + Geben Sie ein Dateinamenmuster ein + Das Muster erzeugt einen ungültigen oder zu langen Dateinamen + Das Muster erzeugt doppelte Dateinamen + Alle Dateinamen stimmen bereits mit dem Muster überein + Dieses Muster verwendet Token, die nicht für die Stapelumbenennung verfügbar sind + Der Name bleibt gleich + Dateien erfolgreich umbenannt + Einige Dateien konnten nicht umbenannt werden + Die Erlaubnis wurde nicht erteilt + Verwendet den ursprünglichen Dateinamen ohne Erweiterung, sodass die Quellenidentifizierung erhalten bleibt. Unterstützt auch Slicing mit \\o{start:end}, Transliteration mit \\o{t} und Ersetzen durch \\o{s/old/new/}. + Automatisch inkrementierender Zähler. Unterstützt benutzerdefinierte Formatierung: \\c{padding} (z. B. \\c{3} -&gt; 001), \\c{start:step} oder \\c{start:step:padding}. + Der Name des übergeordneten Ordners, in dem sich die Originaldatei befand. + Die Größe der Originaldatei. Unterstützt Einheiten: \\z{b}, \\z{kb}, \\z{mb} oder einfach \\z für ein menschenlesbares Format. + Erzeugt einen zufälligen Universally Unique Identifier (UUID), um die Eindeutigkeit des Dateinamens sicherzustellen. + Das Umbenennen von Dateien kann nicht rückgängig gemacht werden. Stellen Sie sicher, dass die neuen Namen korrekt sind, bevor Sie fortfahren. + Umbenennen bestätigen + Seitenmenüeinstellungen + Menüskala + Menü Alpha + Automatischer Weißabgleich + Ausschnitt + Suche nach versteckten Wasserzeichen + Lagerung + Cache-Limit + Leeren Sie den Cache automatisch, wenn er diese Größe überschreitet + Reinigungsintervall + Wie oft soll überprüft werden, ob der Cache geleert werden soll + Beim App-Start + 1 Tag + 1 Woche + 1 Monat + Leeren Sie den App-Cache automatisch entsprechend dem ausgewählten Limit und Intervall + Beweglicher Anbaubereich + Ermöglicht das Verschieben des gesamten Zuschneidebereichs durch Ziehen innerhalb des Bereichs + GIF zusammenführen + Kombinieren Sie mehrere GIF-Clips zu einer Animation + Reihenfolge der GIF-Clips + Umkehren + Spielen Sie umgekehrt + Boomerang + Spielen Sie vorwärts und dann rückwärts + Bildgröße normalisieren + Rahmen so skalieren, dass sie in den größten Clip passen; ausschalten, um den Originalmaßstab beizubehalten + Verzögerung zwischen Clips (ms) + Ordner + Wählen Sie alle Bilder aus einem Ordner und seinen Unterordnern aus + Duplikat-Finder + Finden Sie exakte Kopien und optisch ähnliche Bilder + Duplikat-Finder, ähnliche Bilder, exakte Kopien, Fotos, Bereinigung, Speicher, dHash SHA-256 + Wählen Sie Bilder aus, um Duplikate zu finden + Ähnlichkeitsempfindlichkeit + Exakte Kopien + Ähnliche Bilder + Wählen Sie alle exakten Duplikate aus + Wählen Sie alle außer den empfohlenen aus + Keine Duplikate gefunden + Versuchen Sie, weitere Bilder hinzuzufügen oder die Ähnlichkeitsempfindlichkeit zu erhöhen + %1$d Bild(er) konnten nicht gelesen werden. + %1$s • %2$s rückforderbar + %1$d Gruppen • %2$s rückgewinnbar + %1$d ausgewählt • %2$s + Dies kann nicht rückgängig gemacht werden. Android fordert Sie möglicherweise in einem Systemdialog auf, den Löschvorgang zu bestätigen. + Nicht gefunden + Zum Start bewegen + Zum Ende bewegen + \ No newline at end of file diff --git a/core/resources/src/main/res/values-es/strings.xml b/core/resources/src/main/res/values-es/strings.xml new file mode 100644 index 0000000..abcf476 --- /dev/null +++ b/core/resources/src/main/res/values-es/strings.xml @@ -0,0 +1,3739 @@ + + + + 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. + 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 + 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. + 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. + 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 + Distorsión consciente del contenido + No hay suficiente memoria para completar esta acción. Intente usar una imagen más pequeña, cerrar otras aplicaciones o reiniciar la aplicación. + Trabajadores paralelos + Estas imágenes no se guardaron porque los archivos convertidos serían más grandes que los originales. Consulte esta lista antes de eliminar imágenes originales. + Archivos omitidos: %1$s + Registros de aplicaciones + Ver registros de la aplicación para detectar los problemas + Favoritos en herramientas agrupadas + Agrega favoritos como una pestaña cuando las herramientas están agrupadas por tipo + Mostrar favorito como último + Mueve la pestaña de herramientas favoritas al final. + Espaciado horizontal + Espaciado vertical + Energía hacia atrás + Utilice un mapa de energía de magnitud de gradiente simple en lugar de un algoritmo de energía directa + Quitar el área enmascarada + Las costuras pasarán a través de la región enmascarada, eliminando solo el área seleccionada en lugar de protegerla. + NTSC VHS + Configuración NTSC avanzada + Sintonización analógica adicional para VHS y artefactos de transmisión más potentes + Sangrado de croma + Desgaste de la cinta + Seguimiento + frotis luminoso + Zumbido + Nieve + Usar campo + Tipo de filtro + Filtro de luma de entrada + Paso bajo de croma de entrada + demodulación cromática + cambio de fase + Desfase de fase + Cambio de cabeza + Altura de conmutación del cabezal + Compensación de conmutación de cabezal + Cambio de cabeza + Posición de línea media + Jitter de línea media + Altura del ruido de seguimiento + Ola de seguimiento + Seguimiento de la nieve + Seguimiento de la anisotropía de la nieve + Ruido de seguimiento + Seguimiento de la intensidad del ruido + Ruido compuesto + Frecuencia de ruido compuesto + Intensidad de ruido compuesto + Detalle de ruido compuesto + Frecuencia de timbre + poder de timbre + Ruido luminoso + Frecuencia de ruido luminoso + Intensidad del ruido luminoso + Detalle del ruido luminoso + Ruido cromático + Frecuencia del ruido cromático + Intensidad del ruido cromático + Detalle del ruido cromático + Intensidad de la nieve + Anisotropía de nieve + Ruido de fase cromática + Error de fase de croma + Retardo de croma horizontal + Retardo de croma vertical + velocidad de la cinta VHS + Pérdida de croma VHS + VHS agudiza la intensidad + Frecuencia de nitidez VHS + Intensidad de onda de borde VHS + Velocidad de onda de borde VHS + Frecuencia de onda de borde VHS + Detalle de onda de borde VHS + Paso bajo croma de salida + Escala horizontal + Escala vertical + Factor de escala X + Factor de escala Y + Detecta marcas de agua de imágenes comunes y las pinta con LaMa. Descarga modelos de detección y pintura automáticamente. + Deuteranopía + Ampliar imagen + Eliminador de fondo basado en segmentación de objetos usando la segmentación YOLO v11 + Mostrar ángulo de línea + Emojis animados + Mostrar emoji disponibles como animaciones + Muestra la rotación de la línea actual en grados mientras dibuja + Guardar en la carpeta original + Guarde archivos nuevos junto al archivo original en lugar de la carpeta seleccionada + PDF de marca de agua + Hace falta memoria + Es probable que la imagen sea demasiado grande para procesarla en este dispositivo o que el sistema se haya quedado sin memoria disponible. Intente reducir la resolución de la imagen, cerrar otras aplicaciones o elegir un archivo más pequeño. + Menú de depuración + Menú para probar las funciones de la aplicación; no está previsto que aparezca en la versión de producción. + Proveedor + PaddleOCR requiere modelos ONNX adicionales en su dispositivo. ¿Quieres descargar %1$s datos? + Universal + coreano + latín + eslavo oriental + tailandés + Griego + Inglés + cirílico + árabe + Devanagari + Tamil + telugu + sombreador + Preajuste de sombreador + No se seleccionó ningún sombreador + Estudio de sombreado + Cree, edite, valide, importe y exporte sombreadores de fragmentos personalizados + Sombreadores guardados + Abra sombreadores guardados, duplíquelos, exporte, comparta o elimine + Nuevo sombreador + Archivo de sombreado + .itshader JSON + %1$d parámetros + Fuente de sombreado + Escriba solo el cuerpo de void main(). Utilice texturaCoordinate para las coordenadas UV y inputImageTexture como muestra de origen. + Funciones + Funciones auxiliares opcionales, constantes y estructuras insertadas antes de void main(). Los uniformes se generan a partir de parámetros. + Agregue uniformes aquí cuando el sombreador necesite valores editables. + Restablecer sombreador + Se borrará el borrador del sombreador actual. + Sombreador no válido + Versión de sombreador no compatible %1$d. Versión compatible: %2$d. + El nombre del sombreador no debe estar en blanco. + La fuente del sombreador no debe estar en blanco. + La fuente del sombreador contiene caracteres no admitidos: %1$s. Utilice únicamente fuente ASCII GLSL. + El parámetro \\\"%1$s\\\" se declara más de una vez. + Los nombres de los parámetros no deben estar en blanco. + El parámetro \\\"%1$s\\\" utiliza un nombre GPUImage reservado. + El parámetro \\\"%1$s\\\" debe ser un identificador GLSL válido. + El parámetro \\\"%1$s\\\" tiene %2$s tipo de valor \\\"%3$s\\\", esperado \\\"%4$s\\\". + El parámetro \\\"%1$s\\\" no puede definir el mínimo o el máximo para los valores booleanos. + El parámetro \\\"%1$s\\\" tiene un mínimo mayor que un máximo. + El parámetro predeterminado \\\"%1$s\\\" es inferior al mínimo. + El parámetro predeterminado \\\"%1$s\\\" es mayor que el máximo. + El sombreador debe declarar \\\"muestra uniforme2D %1$s;\\\". + El sombreador debe declarar \\\"varying vec2 %1$s;\\\". + El sombreador debe definir \\\"void main()\\\". + Shader debe escribir un color en gl_FragColor. + Los sombreadores de imagen principal de ShaderToy no son compatibles. Utilice el contrato void main() de GPUImage. + El uniforme animado de ShaderToy \\\"iTime\\\" no es compatible. + El uniforme animado de ShaderToy \\\"iFrame\\\" no es compatible. + El uniforme \\\"iResolution\\\" de ShaderToy no es compatible. + Las texturas ShaderToy iChannel no son compatibles. + No se admiten texturas externas. + No se admiten extensiones de textura externas. + Los parámetros del sombreador Libretro no son compatibles. + Sólo se admite una textura de entrada. Quitar \\\"uniforme %1$s %2$s;\\\". + El parámetro \\\"%1$s\\\" debe declararse como \\\"uniforme %2$s %1$s;\\\". + El parámetro \\\"%1$s\\\" se declara como \\\"uniforme %2$s %1$s;\\\", esperado \\\"uniforme %3$s %1$s;\\\". + El archivo de sombreador está vacío. + El archivo de sombreado debe ser un objeto JSON .itshader con versión, nombre y campos de sombreado. + El archivo Shader no es JSON válido o no coincide con el formato .itshader. + El campo \\\"%1$s\\\" es obligatorio. + Se requiere %1$s. + %1$s \\\"%2$s\\\" no es compatible. Tipos admitidos: %3$s. + %1$s es necesario para los parámetros \\\"%2$s\\\". + %1$s debe ser un número finito. + %1$s debe ser un número entero. + %1$s debe ser verdadero o falso. + %1$s debe ser una cadena de color, una matriz RGB/RGBA u un objeto de color. + %1$s debe utilizar el formato #RRGGBB o #RRGGBBAA. + %1$s debe contener canales de color hexadecimales válidos. + %1$s debe contener 3 o 4 canales de color. + %1$s debe estar entre 0 y 255. + %1$s debe ser una matriz de dos números o un objeto con xey. + %1$s debe contener exactamente 2 números. + Duplicado + Siempre borrar EXIF + Elimine los datos EXIF ​​de la imagen al guardar, incluso cuando una herramienta solicite mantener los metadatos + Ayuda y consejos + %1$d tutoriales + Abrir herramienta + Conozca las principales herramientas, opciones de exportación, flujos de PDF, utilidades de color y soluciones para problemas comunes. + Empezando + Elija herramientas, importe archivos, guarde resultados y reutilice configuraciones más rápido + Edición de imágenes + Cambiar el tamaño, recortar, filtrar, borrar fondos, dibujar y marcar imágenes con marcas de agua + Archivos y metadatos + Convierta formatos, compare resultados, proteja la privacidad y controle los nombres de archivos + PDF y documentos + Cree archivos PDF, escanee documentos, páginas OCR y prepare archivos para compartir + Texto, QR y datos + Reconocer texto, escanear códigos, codificar Base64, comprobar hashes y empaquetar archivos + herramientas de color + Elija colores, cree paletas, explore bibliotecas de colores y cree degradados + Herramientas creativas + Genere SVG, arte ASCII, texturas de ruido, sombreadores y elementos visuales experimentales. + Solución de problemas + Solucione problemas de archivos pesados, transparencia, uso compartido, importaciones y generación de informes + Elija la herramienta adecuada + Utilice categorías, busque, favoritos y comparta acciones para comenzar en el lugar correcto + Comience desde el mejor punto de entrada + Image Toolbox tiene muchas herramientas específicas y muchas de ellas se superponen a primera vista. Comience desde la tarea que desea finalizar, luego elija la herramienta que controla la parte más importante del resultado: tamaño, formato, metadatos, texto, páginas PDF, colores o diseño. + Utilice la búsqueda cuando conozca el nombre de la acción, como cambiar tamaño, PDF, EXIF, OCR, QR o color. + Abra una categoría cuando solo conozca el tipo de tarea y luego fije las herramientas frecuentes como favoritas. + Cuando otra aplicación comparte una imagen en Image Toolbox, seleccione la herramienta del flujo de la hoja para compartir. + Importar, guardar y compartir resultados + Comprender el flujo habitual desde la selección de datos hasta la exportación del archivo editado. + Siga el flujo de edición común + La mayoría de las herramientas siguen el mismo ritmo: elija la entrada, ajuste las opciones, obtenga una vista previa y luego guarde o comparta. El detalle importante es que guardar crea un archivo de salida, mientras que compartir suele ser mejor para transferirlo rápidamente a otra aplicación. + Elija un archivo para herramientas de una sola imagen o varios archivos para herramientas por lotes cuando el selector lo permita. + Verifique los controles de vista previa y salida antes de guardar, especialmente las opciones de formato, calidad y nombre de archivo. + Utilice compartir para una transferencia rápida o guárdelo cuando necesite una copia persistente en la carpeta seleccionada. + Trabaja con muchas imágenes a la vez + Las herramientas por lotes son mejores para tareas repetidas de cambio de tamaño, conversión, compresión y denominación. + Prepare un lote predecible + El procesamiento por lotes es más rápido cuando cada salida debe seguir las mismas reglas. También es el lugar más fácil para cometer un gran error, así que elija el nombre, la calidad, los metadatos y el comportamiento de la carpeta antes de iniciar una exportación larga. + Seleccione todas las imágenes relacionadas juntas y mantenga el orden si la salida depende de la secuencia. + Establezca reglas de cambio de tamaño, formato, calidad, metadatos y nombre de archivo una vez antes de comenzar la exportación. + Primero ejecute un pequeño lote de prueba cuando los archivos de origen sean grandes o cuando el formato de destino sea nuevo para usted. + Edición única rápida + Utilice el editor todo en uno cuando necesite varios cambios simples en una imagen + Terminar una imagen sin saltar herramientas + La edición única es útil cuando la imagen necesita una pequeña cadena de cambios en lugar de una operación especializada. Por lo general, es más rápido realizar una limpieza, una vista previa y una exportación rápidas de una copia final sin crear un flujo de trabajo por lotes. + Abra Edición única para una imagen que necesite ajustes comunes, marcado, recorte, rotación o cambios de exportación. + Aplique las ediciones en el orden en que cambie primero la menor cantidad de píxeles, como recortar antes de los filtros y filtrar antes de la compresión final. + Utilice una herramienta especializada cuando necesite procesamiento por lotes, objetivos de tamaño de archivo exacto, salida de PDF, OCR o limpieza de metadatos. + Usar ajustes preestablecidos y configuraciones + Guarde opciones repetidas y ajuste la aplicación según su flujo de trabajo preferido + Evite repetir la misma configuración + Los ajustes preestablecidos y las configuraciones son útiles cuando exportas el mismo tipo de archivo con frecuencia. Un buen ajuste preestablecido convierte una lista de verificación manual repetida en un solo toque, mientras que la configuración global define los valores predeterminados con los que comienzan las nuevas herramientas. + Cree ajustes preestablecidos para tamaños de salida, formatos, valores de calidad o patrones de nombres comunes. + Revise la configuración para conocer el formato predeterminado, la calidad, la carpeta para guardar, el nombre de archivo, el selector y el comportamiento de la interfaz. + Haga una copia de seguridad de la configuración antes de reinstalar la aplicación o pasar a otro dispositivo. + Establecer valores predeterminados útiles + Elija el formato, la calidad, el modo de escala, el tipo de cambio de tamaño y el espacio de color predeterminados una vez + Haga que las nuevas herramientas comiencen a acercarse a su objetivo + Los valores predeterminados no son sólo preferencias cosméticas. Ellos deciden qué formato, calidad, comportamiento de cambio de tamaño, modo de escala y espacio de color aparecen primero en muchas herramientas, lo que ayuda a evitar volver a seleccionar las mismas opciones en cada exportación. + Configure el formato y la calidad de imagen predeterminados para que coincidan con su destino habitual, como JPEG para fotos o PNG/WebP para alfa. + Ajuste el tipo de cambio de tamaño y el modo de escala predeterminados si exporta con frecuencia para dimensiones estrictas, plataformas sociales o páginas de documentos. + Cambie los valores predeterminados del espacio de color solo cuando su flujo de trabajo necesite un manejo uniforme del color en pantallas, impresiones o editores externos. + Selector y lanzador + Utilice la configuración del selector cuando la aplicación abra demasiados cuadros de diálogo o se inicie en el lugar equivocado + Haga que abrir archivos coincida con su hábito + La configuración del selector y del iniciador controla si Image Toolbox se inicia desde la pantalla principal, una herramienta o un flujo de selección de archivos. Estas opciones son especialmente útiles cuando normalmente ingresas desde la hoja para compartir de Android o cuando quieres que la aplicación se parezca más a un iniciador de herramientas. + Utilice la configuración del selector de fotos si el selector del sistema oculta archivos, muestra demasiados elementos recientes o maneja mal los archivos en la nube. + Habilite la omisión de selección solo para herramientas en las que normalmente ingresa desde el recurso compartido o donde ya conoce el archivo fuente. + Revise el modo de inicio si desea que Image Toolbox se comporte más como un selector de herramientas que como una pantalla de inicio normal. + Fuente de la imagen + Elija el modo de selección que funcione mejor con galerías, administradores de archivos y archivos en la nube + Elija archivos de la fuente correcta + La configuración de Fuente de imagen afecta la forma en que la aplicación solicita imágenes a Android. La mejor opción depende de si sus archivos se encuentran en la galería del sistema, una carpeta del administrador de archivos, un proveedor de la nube u otra aplicación que comparte contenido temporal. + Utilice el selector predeterminado cuando desee la experiencia más integrada en el sistema para las imágenes de galería comunes. + Cambie el modo de selección si los álbumes, carpetas, archivos en la nube o formatos no estándar no aparecen donde espera. + Si un archivo compartido desaparece después de salir de la aplicación de origen, vuelva a abrirlo mediante un selector persistente o guarde primero una copia local. + Favoritos y herramientas para compartir + Mantenga enfocada la pantalla principal y oculte herramientas que no tengan sentido en los flujos de compartir + Reducir el ruido de la lista de herramientas + Los favoritos y las configuraciones ocultas para compartir son útiles cuando solo usas unas pocas herramientas todos los días. También mantienen práctico el flujo de compartir al ocultar herramientas que no pueden usar el tipo de archivo que envía otra aplicación. + Fije herramientas frecuentes para que sean fáciles de alcanzar incluso cuando las herramientas estén agrupadas por categoría. + Oculte herramientas de los flujos compartidos cuando sean irrelevantes para archivos provenientes de otra aplicación. + Utilice la configuración de orden de favoritos si prefiere los favoritos al final o agrupados con herramientas relacionadas. + Configuración de copia de seguridad + Mueva los ajustes preestablecidos, las preferencias y el comportamiento de la aplicación a otra instalación de forma segura + Mantenga su configuración portátil + Copia de seguridad y restauración es más útil después de haber ajustado los ajustes preestablecidos, las carpetas, las reglas de nombres de archivos, el orden de las herramientas y las preferencias visuales. Una copia de seguridad le permite experimentar con la configuración o reinstalar la aplicación sin reconstruir su flujo de trabajo desde la memoria. + Cree una copia de seguridad después de cambiar los ajustes preestablecidos, el comportamiento de los nombres de archivos, los formatos predeterminados o la disposición de las herramientas. + Guarde la copia de seguridad en algún lugar fuera de la carpeta de la aplicación si planea borrar datos, reinstalar o mover dispositivos. + Restaure la configuración antes de procesar lotes importantes para que los valores predeterminados y el comportamiento de guardado coincidan con su configuración anterior. + Cambiar el tamaño y convertir imágenes + Cambie el tamaño, formato, calidad y metadatos de una o varias imágenes + Crear copias redimensionadas + Resize and Convert es la herramienta principal para dimensiones predecibles y archivos de salida más livianos. Úselo cuando el tamaño de la imagen, el formato de salida y el comportamiento de los metadatos sean importantes al mismo tiempo. + Elija una o más imágenes y luego elija si las dimensiones, la escala o el tamaño del archivo son lo más importante. + Seleccione el formato y la calidad de salida; utilice PNG o WebP cuando deba preservar la transparencia. + Obtenga una vista previa del resultado, luego guarde o comparta las copias generadas sin cambiar los originales. + Cambiar el tamaño por tamaño de archivo + Establezca un límite de tamaño cuando un sitio web, mensajería o formulario rechace imágenes pesadas + Ajustar límites de carga estrictos + Cambiar el tamaño por tamaño de archivo es mejor que adivinar los valores de calidad cuando el destino solo acepta archivos por debajo de un límite específico. La herramienta puede ajustar la compresión y las dimensiones para alcanzar el objetivo mientras intenta mantener el resultado utilizable. + Ingrese el tamaño objetivo ligeramente por debajo del límite de carga real para dejar espacio para metadatos y cambios de proveedor. + Prefiera formatos con pérdida para fotografías cuando el objetivo sea pequeño; PNG puede permanecer grande incluso después de cambiar el tamaño. + Inspeccione caras, texto y bordes después de la exportación porque los objetivos de tamaño agresivo pueden introducir artefactos visibles. + Limitar el cambio de tamaño + Reduzca solo las imágenes que excedan su ancho, alto máximo o restricciones de archivo + Evite cambiar el tamaño de imágenes que ya están bien + Limitar cambio de tamaño es útil para carpetas mixtas donde algunas imágenes son enormes y otras ya están preparadas. En lugar de forzar el mismo cambio de tamaño en todos los archivos, mantiene las imágenes aceptables más cerca de su calidad original. + Establezca dimensiones o límites máximos según el destino en lugar de cambiar el tamaño de cada archivo a ciegas. + Habilite el comportamiento de estilo omitir si es más grande cuando desee evitar resultados que se vuelvan más pesados ​​que las fuentes. + Úselo para lotes de diferentes cámaras, capturas de pantalla o descargas donde los tamaños de fuente varían mucho. + Recortar, rotar y enderezar + Encuadre el área importante antes de exportar o usar la imagen en otras herramientas + Limpiar la composición. + Recortar primero hace que los filtros posteriores, el OCR, las páginas PDF y el uso compartido de resultados sean más limpios. + Abra Recortar y elija la imagen que desea ajustar. + Utilice un recorte libre o una relación de aspecto cuando el resultado deba ajustarse a una publicación, documento o avatar. + Gire o voltee antes de guardar para que las herramientas posteriores reciban la imagen corregida. + Aplicar filtros y ajustes. + Cree una pila de filtros editable y obtenga una vista previa del resultado antes de exportarlo + Ajustar la apariencia de la imagen + Los filtros son útiles para correcciones rápidas, resultados estilizados y preparación de imágenes para OCR o impresión. Pequeños cambios en el contraste, la nitidez y el brillo pueden ser más importantes que los efectos intensos cuando la imagen contiene texto o detalles finos. + Agregue filtros uno por uno y esté atento a la vista previa después de cada cambio. + Ajuste el brillo, el contraste, la nitidez, el desenfoque, el color o las máscaras según la tarea. + Exporte solo cuando la vista previa coincida con su dispositivo, documento o plataforma social de destino. + Vista previa primero + Inspeccione las imágenes antes de elegir una herramienta de edición, conversión o limpieza más pesada + Comprueba lo que realmente recibiste + La vista previa de imagen es útil cuando un archivo proviene de un chat, almacenamiento en la nube u otra aplicación y no está seguro de lo que contiene. Verificar primero las dimensiones, la transparencia, la orientación y la calidad visual ayuda a elegir la herramienta de seguimiento correcta. + Abra Vista previa de imagen cuando necesite inspeccionar varias imágenes antes de decidir qué hacer con ellas. + Busque problemas de rotación, áreas transparentes, artefactos de compresión y dimensiones inesperadamente grandes. + Vaya a Cambiar tamaño, Recortar, Comparar, EXIF ​​o Eliminador de fondo solo después de saber qué es necesario arreglar. + Eliminar o restaurar un fondo + Borre fondos automáticamente o refine los bordes manualmente con herramientas de restauración + Mantener el tema, eliminar el resto. + La eliminación del fondo funciona mejor cuando combina la selección automática con una limpieza manual cuidadosa. El formato de exportación final es tan importante como la máscara, porque JPEG aplanará las áreas transparentes incluso si la vista previa parece correcta. + Comience con la eliminación automática si el sujeto está claramente separado del fondo. + Acérquese y cambie entre borrar y restaurar para arreglar el cabello, las esquinas, las sombras y los pequeños detalles. + Guárdelo como PNG o WebP cuando necesite transparencia en el archivo final. + Dibujar, marcar y marcar con agua + Agregue flechas, notas, resaltados, firmas o superposiciones repetidas de marcas de agua + Agregar información visible + Las herramientas de marcado ayudan a explicar una imagen, mientras que la marca de agua ayuda a marcar o proteger los archivos exportados. + Utilice Draw cuando necesite notas a mano alzada, formas, resaltados o anotaciones rápidas. + Utilice Marcas de agua cuando la misma marca de texto o imagen deba aparecer de manera consistente en las salidas. + Verifique la opacidad, la posición y la escala antes de exportar para que la marca sea visible pero no distraiga. + Dibujar valores predeterminados + Configure el ancho de línea, el color, el modo de ruta, el bloqueo de orientación y la lupa para un marcado más rápido + Haz que el dibujo parezca predecible + La configuración de dibujo es útil cuando anota capturas de pantalla, marca documentos o firma imágenes con frecuencia. Los valores predeterminados reducen el tiempo de configuración, mientras que la lupa y el bloqueo de orientación facilitan las ediciones precisas en pantallas pequeñas. + Establezca el ancho de línea predeterminado y el color de dibujo en los valores que más utiliza para flechas, resaltados o firmas. + Utilice el modo de ruta predeterminado cuando normalmente dibuja el mismo tipo de trazos, formas o marcas. + Habilite la lupa o el bloqueo de orientación cuando la entrada con el dedo dificulte la colocación precisa de pequeños detalles. + Diseños de múltiples imágenes + Elija la herramienta de múltiples imágenes adecuada antes de organizar capturas de pantalla, escaneos o tiras comparativas + Utilice la herramienta de diseño adecuada + Estas herramientas suenan similares, pero cada una está diseñada para un tipo diferente de salida de múltiples imágenes. Elegir el correcto primero evita luchar contra los controles de diseño que fueron diseñados para un resultado diferente. + Utilice Collage Maker para diseños diseñados con varias imágenes en una composición. + Utilice Unión o Apilamiento de imágenes cuando las imágenes deban convertirse en una franja larga vertical u horizontal. + Utilice la división de imágenes cuando una imagen grande necesite convertirse en varias partes más pequeñas. + Convertir formatos de imagen + Cree copias JPEG, PNG, WebP, AVIF, JXL y otras copias sin editar píxeles manualmente + Elija el formato para el trabajo + Los diferentes formatos son mejores para fotografías, capturas de pantalla, transparencia, compresión o compatibilidad. La conversión es más segura cuando comprende qué admite la aplicación de destino y si la fuente contiene alfa, animación o metadatos que desea conservar. + Utilice JPEG para fotografías compatibles, PNG para imágenes alfa y sin pérdida y WebP para compartir de forma compacta. + Ajustar la calidad cuando el formato lo admita; los valores más bajos reducen el tamaño pero pueden agregar artefactos. + Conserve los originales hasta que verifique que los archivos convertidos se abran correctamente en la aplicación de destino. + Comprueba EXIF ​​y privacidad + Ver, editar o eliminar metadatos antes de compartir fotos confidenciales + Controlar datos de imágenes ocultas + EXIF puede contener datos de la cámara, fechas, ubicación, orientación y otros detalles no visibles en la imagen. Vale la pena comprobarlo antes de compartirlo públicamente, informar de soporte, listados de mercados o cualquier fotografía que pueda revelar un lugar privado. + Abra las herramientas EXIF ​​antes de compartir fotos que puedan contener información de ubicación o privada del dispositivo. + Elimine campos confidenciales o edite etiquetas incorrectas como fecha, orientación, autor o datos de la cámara. + Guarde una copia nueva y compártala en lugar del original cuando la privacidad sea importante. + Limpieza automática EXIF + Elimine los metadatos de forma predeterminada al decidir si se deben conservar las fechas. + Hacer que la privacidad sea la opción predeterminada + El grupo de configuración EXIF ​​es útil cuando desea que la limpieza de la privacidad se realice automáticamente en lugar de recordarla para cada exportación. Mantener fecha y hora es una decisión independiente porque las fechas pueden ser útiles para ordenar pero sensibles para compartir. + Habilite EXIF ​​siempre claro cuando la mayoría de sus exportaciones estén destinadas a compartirse de forma pública o semipública. + Habilite Mantener fecha y hora solo cuando preservar la hora de captura sea más importante que eliminar cada marca de tiempo. + Utilice la edición EXIF ​​manual para archivos únicos en los que necesite conservar algunos campos y eliminar otros. + Controlar nombres de archivos + Utilice prefijos, sufijos, marcas de tiempo, ajustes preestablecidos, sumas de verificación y números de secuencia + Haga que las exportaciones sean fáciles de encontrar + Un buen patrón de nombre de archivo ayuda a ordenar lotes y evita sobrescrituras accidentales. La configuración del nombre de archivo es especialmente útil cuando las exportaciones regresan a la carpeta de origen, donde nombres similares pueden resultar confusos rápidamente. + Establezca el prefijo, el sufijo, la marca de tiempo, el nombre original, la información preestablecida o las opciones de secuencia del nombre de archivo en Configuración. + Utilice números de secuencia para lotes en los que el orden sea importante, como páginas, marcos o collages. + Habilite la sobrescritura solo cuando esté seguro de que reemplazar archivos existentes es seguro para la carpeta actual. + Sobrescribir archivos + Reemplace las salidas con nombres coincidentes solo cuando su flujo de trabajo sea intencionalmente destructivo + Utilice el modo de sobrescritura con cuidado + Sobrescribir archivos cambia la forma en que se manejan los conflictos de nombres. Es útil para repetir exportaciones a la misma carpeta, pero también puede reemplazar resultados anteriores si su patrón de nombre de archivo produce el mismo nombre nuevamente. + Habilite la sobrescritura solo para carpetas donde se espera y es recuperable el reemplazo de archivos generados más antiguos. + Mantenga la sobrescritura desactivada al exportar originales, archivos de clientes, escaneos únicos o cualquier cosa sin una copia de seguridad. + Combine la sobrescritura con un patrón de nombre de archivo deliberado para que solo se reemplacen las salidas deseadas. + Patrones de nombres de archivos + Combine nombres originales, prefijos, sufijos, marcas de tiempo, ajustes preestablecidos, modo de escala y sumas de verificación + Crear nombres que expliquen el resultado. + La configuración del patrón de nombre de archivo puede convertir los archivos exportados en un historial útil de lo que les sucedió. Esto es importante en lotes porque la vista de carpetas puede ser el único lugar donde puede distinguir el tamaño del original, el valor preestablecido, el formato y el orden de procesamiento. + Utilice el nombre de archivo, el prefijo, el sufijo y la marca de tiempo originales cuando necesite nombres legibles para la clasificación manual. + Agregue información preestablecida, modo de escala, tamaño de archivo o suma de verificación cuando los resultados deban documentar cómo se produjeron. + Evite nombres aleatorios para flujos de trabajo en los que los archivos deben permanecer emparejados con los originales o el orden de las páginas. + Elija dónde se guardan los archivos + Evite perder exportaciones configurando carpetas, guardando la carpeta original y guardando ubicaciones únicas + Mantenga los resultados predecibles + El comportamiento de guardado es más importante cuando procesa muchos archivos o comparte archivos de proveedores de nube. La configuración de carpeta decide si los resultados se recopilan en un lugar conocido, aparecen junto a los originales o van temporalmente a otro lugar para una tarea específica. + Establezca una carpeta para guardar predeterminada si siempre desea exportar en un solo lugar. + Utilice la carpeta Guardar en original para flujos de trabajo de limpieza donde la salida debe permanecer cerca del archivo fuente. + Utilice una ubicación para guardar una sola vez cuando una sola tarea necesite un destino diferente sin cambiar su valor predeterminado. + Carpetas guardadas una sola vez + Envíe las próximas exportaciones a una carpeta temporal sin cambiar su destino normal + Mantenga los trabajos especiales separados + La ubicación para guardar de una sola vez es útil para proyectos temporales, carpetas de clientes, sesiones de limpieza o exportaciones que no deben mezclarse con su carpeta habitual de Image Toolbox. Ofrece un destino de corta duración sin reescribir su configuración predeterminada. + Elija una carpeta de uso único antes de comenzar una tarea que pertenezca fuera de su ubicación de exportación normal. + Úselo para proyectos cortos, carpetas compartidas o lotes donde los resultados deben permanecer agrupados. + Regrese a la carpeta predeterminada después del trabajo para que las exportaciones posteriores no acaben inesperadamente en la ubicación temporal. + Equilibre la calidad y el tamaño del archivo + Comprenda cuándo cambiar las dimensiones, el formato o la calidad en lugar de adivinar + Reducir archivos intencionalmente + El tamaño del archivo depende de las dimensiones, el formato, la calidad, la transparencia y la complejidad del contenido de la imagen. Si una lima sigue siendo demasiado pesada, cambiar las dimensiones suele ayudar más que reducir la calidad repetidamente. + Cambie el tamaño de las dimensiones primero cuando la imagen sea más grande de lo que puede mostrar el destino. + Menor calidad solo para formatos con pérdida y verifique el texto, los bordes, los degradados y las caras después de la exportación. + Cambie de formato cuando los cambios de calidad no sean suficientes, por ejemplo, WebP para compartir o PNG para recursos transparentes y nítidos. + Trabajar con formatos animados. + Utilice las herramientas GIF, APNG, WebP y JXL cuando un archivo tenga marcos en lugar de un mapa de bits + No trates cada imagen como fija + Los formatos animados necesitan herramientas que comprendan los fotogramas, la duración y la compatibilidad. + Utilice herramientas GIF o APNG cuando necesite operaciones a nivel de fotograma para esos formatos. + Utilice herramientas WebP cuando necesite compresión moderna o salida WebP animada. + Obtenga una vista previa del tiempo de la animación antes de compartirla porque algunas plataformas convierten o aplanan imágenes animadas. + Comparar y obtener una vista previa de la salida + Inspeccione los cambios, el tamaño del archivo y las diferencias visuales antes de mantener una exportación + Verificar antes de compartir + Las herramientas de comparación ayudan a detectar con antelación artefactos de compresión, colores incorrectos o ediciones no deseadas. + Abra Comparar cuando desee inspeccionar dos versiones una al lado de la otra. + Amplíe los bordes, el texto, los degradados y las regiones transparentes donde es más fácil pasar por alto los problemas. + Si el resultado es demasiado pesado o borroso, regrese a la herramienta anterior y ajuste la calidad o el formato. + Utilice el centro de herramientas de PDF + Fusionar, dividir, rotar, eliminar páginas, comprimir, proteger, OCR y marcar archivos PDF + Elija una operación PDF + El centro de PDF agrupa acciones de documentos detrás de un punto de entrada para que pueda elegir la operación exacta. Comience allí cuando el archivo ya sea un PDF y use Imágenes a PDF o Escáner de documentos cuando su fuente todavía sea un conjunto de imágenes. + Abra PDF Tools y elija la operación que coincida con su objetivo. + Seleccione el PDF o las imágenes de origen y luego revise el orden de las páginas, la rotación, la protección o las opciones de OCR. + Guarde el PDF generado y ábralo una vez para confirmar el número de páginas, el orden y la legibilidad. + Convertir imágenes en un PDF + Combine escaneos, capturas de pantalla, recibos o fotografías en un documento que se puede compartir + Construya un documento limpio + Las imágenes se convierten en mejores páginas PDF cuando se recortan, ordenan y dimensionan de manera consistente. Trate la lista de imágenes como una pila de páginas: cada elección de rotación, margen y tamaño de página afecta la legibilidad del documento final. + Recorte o gire las imágenes primero cuando los bordes, las sombras o la orientación de la página sean incorrectos. + Seleccione imágenes en el orden de páginas deseado y ajuste el tamaño de página o los márgenes si la herramienta los ofrece. + Exporte el PDF y luego verifique que todas las páginas sean legibles antes de enviarlo. + Escanear documentos + Capture páginas en papel y prepárelas para PDF, OCR o para compartirlas + Capturar páginas legibles + El escaneo de documentos funciona mejor con páginas planas, buena iluminación y perspectiva corregida. Un escaneo limpio antes de exportar OCR o PDF ahorra tiempo porque tanto el reconocimiento como la compresión del texto dependen de la calidad de la página. + Coloque el documento sobre una superficie contrastante con suficiente luz y sombras mínimas. + Ajuste las esquinas detectadas o recorte manualmente para que la página llene el marco limpiamente. + Exporte como imágenes o PDF, luego ejecute OCR si necesita texto seleccionable. + Proteger o desbloquear archivos PDF + Utilice las contraseñas con cuidado y conserve una copia fuente editable cuando sea posible + Maneje el acceso a PDF de forma segura + Las herramientas de protección son útiles para compartir de forma controlada, pero las contraseñas son fáciles de perder y difíciles de recuperar. Proteja las copias para su distribución, mantenga los archivos fuente desprotegidos por separado y verifique el resultado antes de eliminar nada. + Proteja una copia del PDF, no su único documento fuente. + Guarde la contraseña en un lugar seguro antes de compartir el archivo protegido. + Utilice el desbloqueo solo para los archivos que pueda editar y luego verifique que la copia desbloqueada se abra correctamente. + Organizar páginas PDF + Fusionar, dividir, reorganizar, rotar, recortar, eliminar páginas y agregar números de página deliberadamente + Arreglar la estructura del documento antes de la decoración. + Las herramientas de páginas PDF son más fáciles de usar en el mismo orden en que prepararía un documento en papel: recopile las páginas, elimine las incorrectas, ordénelas, corrija la orientación y luego agregue los detalles finales, como números de página o marcas de agua. + Utilice primero la combinación o la conversión de imágenes a PDF cuando el documento todavía esté dividido en varios archivos. + Reorganice, gire, recorte o elimine páginas antes de agregar números de página, firmas o marcas de agua. + Obtenga una vista previa del PDF final después de las ediciones estructurales porque puede ser difícil notar una página faltante o rotada más adelante. + Anotaciones en PDF + Elimine anotaciones o aplánelas cuando los espectadores muestren comentarios y marcas de manera diferente. + Controla lo que permanece visible + Las anotaciones pueden ser comentarios, resaltados, dibujos, marcas de formulario u otros objetos PDF adicionales. Algunos visores los muestran de forma diferente, por lo que eliminarlos o aplanarlos puede hacer que los documentos compartidos sean más predecibles. + Elimine las anotaciones cuando los comentarios, resaltados o marcas de revisión no deban incluirse en la copia compartida. + Aplanar cuando las marcas visibles deban permanecer en la página pero ya no se comporten como anotaciones editables. + Conserve una copia original porque eliminar o aplanar anotaciones puede resultar difícil de revertir. + Reconocer texto + Extraiga texto de imágenes con motores e idiomas de OCR seleccionables + Obtenga mejores resultados de OCR + La precisión del OCR depende de la nitidez de la imagen, los datos del idioma, el contraste y la orientación de la página. Los mejores resultados generalmente se obtienen al preparar primero la imagen: recortar el ruido, rotarla en posición vertical, aumentar la legibilidad y luego reconocer el texto. + Recorta la imagen en el área de texto y gírala en posición vertical antes del reconocimiento. + Elija el idioma correcto y descargue los datos de idioma si la aplicación lo solicita. + Copie o comparta el texto reconocido y luego verifique manualmente los nombres, números y puntuación. + Elija datos de idioma OCR + Mejore el reconocimiento haciendo coincidir secuencias de comandos, idiomas y modelos de OCR descargados + Haga coincidir el OCR con el texto + Un lenguaje de OCR incorrecto puede hacer que las buenas fotografías parezcan un mal reconocimiento. Los datos de idioma son importantes para escrituras, puntuación, números y documentos mixtos, así que elija el idioma que aparece en la imagen en lugar del idioma de la interfaz de usuario del teléfono. + Elija el idioma o escritura que aparece en la imagen, no solo el idioma del teléfono. + Descargue los datos faltantes antes de trabajar sin conexión o procesar un lote. + Para documentos en varios idiomas, ejecute primero el idioma más importante y verifique manualmente los nombres y números. + PDF con capacidad de búsqueda + Vuelva a escribir texto OCR en archivos para que las páginas escaneadas se puedan buscar y copiar + Convierta los escaneos en documentos con capacidad de búsqueda + OCR puede hacer más que copiar texto al portapapeles. Cuando escribe texto reconocido en un archivo o PDF con capacidad de búsqueda, la imagen de la página permanece visible mientras que el texto se vuelve más fácil de buscar, seleccionar, indexar o archivar. + Utilice resultados PDF con capacidad de búsqueda para documentos escaneados que aún deberían parecerse a las páginas originales. + Utilice la escritura en archivo cuando solo necesite texto extraído y no le importe la imagen de la página. + Verifique números, nombres y tablas importantes manualmente porque el texto OCR se puede buscar pero aún es imperfecto. + Escanea códigos QR + Lea el contenido QR de la entrada de la cámara o imágenes guardadas cuando estén disponibles + Leer códigos de forma segura + Los códigos QR pueden contener enlaces, datos de Wi-Fi, contactos, texto u otras cargas útiles. + Abra Escanear código QR y mantenga el código nítido, plano y completamente dentro del marco. + Revise el contenido decodificado antes de abrir enlaces o copiar datos confidenciales. + Si el escaneo falla, mejore la iluminación, recorte el código o pruebe con una imagen fuente de mayor resolución. + Utilice Base64 y sumas de comprobación + Codifique imágenes como texto, decodifique texto en imágenes y verifique la integridad del archivo + Moverse entre archivos y texto + Base64 es útil para cargas de imágenes pequeñas, mientras que las sumas de verificación ayudan a confirmar que los archivos no cambiaron. Estas herramientas son más útiles en flujos de trabajo técnicos, informes de soporte, transferencias de archivos y lugares donde los archivos binarios deben convertirse en texto temporalmente. + Utilice herramientas Base64 para codificar una imagen pequeña cuando un flujo de trabajo necesite texto en lugar de un archivo binario. + Decodifica Base64 solo de fuentes confiables y verifica la vista previa antes de guardar. + Utilice herramientas de suma de comprobación cuando necesite comparar archivos exportados o confirmar una carga/descarga. + Empaquetar o proteger datos + Utilice herramientas de cifrado y ZIP para agrupar archivos o transformar datos de texto + Mantener juntos los archivos relacionados + Las herramientas de empaquetado son útiles cuando varias salidas deben viajar como un solo archivo. También son buenos para mantener juntos las imágenes, archivos PDF, registros y metadatos generados cuando necesita enviar un conjunto de resultados completo. + Utilice ZIP cuando necesite enviar muchas imágenes o documentos exportados juntos. + Utilice herramientas de cifrado solo cuando comprenda el método seleccionado y pueda almacenar las claves necesarias de forma segura. + Pruebe el archivo creado o el texto transformado antes de eliminar los archivos fuente. + Sumas de verificación en nombres + Utilice nombres de archivos basados ​​en sumas de comprobación cuando la unicidad y la integridad sean más importantes que la legibilidad + Nombrar archivos por contenido + Los nombres de archivos de suma de verificación son útiles cuando las exportaciones pueden tener nombres visibles duplicados o cuando necesita demostrar que dos archivos son idénticos. Son menos amigables para la navegación manual, así que utilícelos para archivos técnicos y flujos de trabajo de verificación. + Habilite la suma de comprobación como nombre de archivo cuando la identidad del contenido sea más importante que un título legible por humanos. + Úselo para archivos, deduplicación, exportaciones repetibles o archivos que se pueden cargar y descargar nuevamente. + Empareje nombres de sumas de verificación con carpetas o metadatos cuando las personas aún necesiten comprender qué representa el archivo. + Elige colores de las imágenes. + Pruebe píxeles, copie códigos de color y reutilice colores en paletas o diseños + Muestra el color exacto + La selección de colores es útil para hacer coincidir un diseño, extraer colores de marca o verificar valores de imágenes. El zoom es importante porque el antialiasing, las sombras y la compresión pueden hacer que los píxeles vecinos sean ligeramente diferentes del color esperado. + Abra Seleccionar color de la imagen y elija una imagen de origen con el color que necesita. + Haga zoom antes de muestrear pequeños detalles para que los píxeles cercanos no afecten el resultado. + Copie el color en el formato requerido por su destino, como HEX, RGB o HSV. + Crea paletas y usa la biblioteca de colores. + Extraiga paletas, explore colores guardados y mantenga sistemas visuales consistentes + Construye una paleta reutilizable + Las paletas ayudan a mantener visualmente consistentes los gráficos, marcas de agua y dibujos exportados. Son especialmente útiles cuando anotas repetidamente capturas de pantalla, preparas activos de marca o necesitas los mismos colores en varias herramientas. + Genera una paleta a partir de una imagen o agrega colores manualmente cuando ya conozcas los valores. + Nombra u organiza colores importantes para poder encontrarlos nuevamente más tarde. + Utilice valores copiados en Dibujar, marca de agua, degradado, SVG o herramientas de diseño externas. + Crear degradados + Cree imágenes degradadas para fondos, marcadores de posición y experimentos visuales. + Controlar las transiciones de color + Las herramientas de degradado son útiles cuando necesitas una imagen generada en lugar de editar una existente. Pueden convertirse en fondos limpios, marcadores de posición, fondos de pantalla, máscaras o recursos simples para herramientas de diseño externas. + Elija el tipo de degradado y configure primero los colores importantes. + Ajuste la dirección, las paradas, la suavidad y el tamaño de salida para el caso de uso objetivo. + Exporte en un formato que coincida con el destino, generalmente PNG para gráficos generados limpios. + Accesibilidad del color + Utilice colores dinámicos, contraste y opciones daltónicas cuando la interfaz de usuario sea difícil de leer. + Haz que los colores sean más fáciles de usar + La configuración de color afecta la comodidad, la legibilidad y la rapidez con la que se pueden escanear los controles. Si resulta difícil distinguir los chips de herramientas, los controles deslizantes o los estados seleccionados, las opciones de tema y accesibilidad pueden ser más útiles que simplemente cambiar el modo oscuro. + Pruebe colores dinámicos cuando desee que la aplicación siga la paleta de fondo de pantalla actual. + Aumente el contraste o cambie el esquema cuando los botones y las superficies no resalten lo suficiente. + Utilice opciones de esquemas daltónicos si algunos estados o acentos son difíciles de distinguir. + Fondo alfa + Controle la vista previa o el color de relleno utilizado en PNG, WebP y salidas similares transparentes + Comprender las vistas previas transparentes + El color de fondo para los formatos alfa puede hacer que las imágenes transparentes sean más fáciles de inspeccionar, pero es importante saber si está cambiando el comportamiento de vista previa/relleno o aplanando el resultado final. Esto es importante para pegatinas, logotipos e imágenes sin fondo. + Utilice primero un formato compatible con alfa, como PNG o WebP, cuando los píxeles transparentes deban sobrevivir a la exportación. + Ajuste la configuración del color de fondo cuando los bordes transparentes sean difíciles de ver en la superficie de la aplicación. + Exporte una pequeña prueba y vuelva a abrirla en otra aplicación para confirmar si se guardó la transparencia o el relleno elegido. + Elección del modelo de IA + Elija un modelo por tipo de imagen y defecto en lugar de elegir primero el más grande + Relaciona el modelo con el daño. + AI Tools puede descargar o importar diferentes modelos ONNX/ORT, y cada modelo está entrenado para un tipo particular de problema. Un modelo para bloques JPEG, limpieza de líneas de anime, eliminación de ruido, eliminación de fondo, coloración o ampliación de escala puede producir peores resultados cuando se utiliza en el tipo de imagen incorrecto. + Lea la descripción del modelo antes de descargarlo; elija el modelo que nombre su problema real, como compresión, ruido, medios tonos, desenfoque o eliminación de fondo. + Comience con un modelo más pequeño o más específico cuando pruebe un nuevo tipo de imagen, luego compárelo con modelos más pesados ​​solo si el resultado no es lo suficientemente bueno. + Conserve sólo los modelos que realmente utilice, ya que los modelos descargados e importados pueden ocupar un espacio de almacenamiento considerable. + Comprender las familias de modelos + Los nombres de los modelos a menudo insinúan su objetivo: los modelos de estilo RealESRGAN son exclusivos, los modelos SCUNet y FBCNN limpian el ruido o la compresión, los modelos de fondo crean máscaras y los colorizadores agregan color a la entrada en escala de grises. Los modelos personalizados importados pueden ser potentes, pero deben coincidir con la forma de entrada/salida esperada y utilizar el formato ONNX u ORT compatible. + Utilice escaladores cuando necesite más píxeles, eliminadores de ruido cuando la imagen sea granulada y eliminadores de artefactos cuando los bloques de compresión o las bandas sean el problema principal. + Utilice modelos de fondo sólo para la separación de sujetos; no son lo mismo que la eliminación general de objetos o la mejora de imágenes. + Importe modelos personalizados solo de fuentes en las que confíe y pruébelos en una imagen pequeña antes de utilizar archivos importantes. + Parámetros de IA + Ajuste el tamaño de los fragmentos, la superposición y los trabajadores paralelos para equilibrar la calidad, la velocidad y la memoria. + Equilibra la velocidad con la estabilidad + El tamaño del fragmento controla el tamaño de las piezas de la imagen antes de que el modelo las procese. La superposición combina fragmentos vecinos para ocultar los bordes. Los trabajadores paralelos pueden acelerar el procesamiento, pero cada trabajador necesita memoria, por lo que los valores altos pueden hacer que las imágenes grandes sean inestables en teléfonos con RAM limitada. + Utilice fragmentos más grandes para un contexto más fluido cuando su dispositivo maneje el modelo de manera confiable y la imagen no sea enorme. + Aumente la superposición cuando los bordes de los fragmentos se vuelvan visibles, pero recuerde que una mayor superposición significa más trabajo repetido. + Levantar trabajadores paralelos sólo después de una prueba estable; Si el procesamiento se ralentiza, calienta el dispositivo o falla, reduzca los trabajadores primero. + Evite los artefactos fragmentados + La fragmentación permite que la aplicación procese a la vez imágenes que son más grandes de lo que el modelo puede manejar cómodamente. La desventaja es que cada mosaico tiene menos contexto circundante, por lo que una superposición baja o trozos demasiado pequeños pueden crear bordes visibles, cambios de textura o detalles inconsistentes entre áreas vecinas. + Aumente la superposición si ve bordes rectangulares, cambios de textura repetidos o saltos de detalles entre regiones procesadas. + Reduzca el tamaño del fragmento si el proceso falla antes de generar resultados, especialmente en imágenes grandes o modelos pesados. + Guarde una pequeña prueba de recorte antes de procesar la imagen completa para poder comparar los parámetros rápidamente. + Estabilidad de la IA + Reduzca el tamaño de los fragmentos, los trabajadores y la resolución de la fuente cuando el procesamiento de IA falla o se congela + Recuperarse de trabajos pesados ​​de IA + El procesamiento de IA es uno de los flujos de trabajo más pesados ​​de la aplicación. Los bloqueos, sesiones fallidas, congelaciones o reinicios repentinos de aplicaciones generalmente significan que el modelo, la resolución de origen, el tamaño del fragmento o el recuento de trabajadores paralelos son demasiado exigentes para el estado actual del dispositivo. + Si la aplicación falla o no puede abrir una sesión de modelo, reduzca los trabajadores paralelos y el tamaño del fragmento, luego intente con la misma imagen nuevamente. + Cambie el tamaño de imágenes muy grandes antes del procesamiento de IA cuando el objetivo no requiera la resolución original. + Cierre otras aplicaciones pesadas, use un modelo más pequeño o procese menos archivos a la vez cuando la presión de la memoria siga regresando. + Diagnosticar fallas del modelo + Una ejecución fallida de la IA no siempre significa que la imagen sea mala. El archivo del modelo puede estar incompleto, no ser compatible, ser demasiado grande para la memoria o no coincidir con la operación. Cuando la aplicación informa de una sesión fallida, trátela como una señal para simplificar primero el modelo y los parámetros. + Elimine y vuelva a descargar un modelo si falla inmediatamente después de la descarga o se comporta como si el archivo estuviera dañado. + Pruebe un modelo descargable integrado antes de culpar a un modelo personalizado importado, porque los modelos importados pueden tener entradas incompatibles. + Utilice los registros de aplicaciones después de reproducir el error si necesita informar un bloqueo o un error de sesión. + Experimente en Shader Studio + Utilice efectos de sombreado de GPU para procesamiento de imágenes avanzado y apariencia personalizada + Trabaja con cuidado con los sombreadores + Shader Studio es potente, pero el código y los parámetros del sombreador necesitan cambios pequeños y comprobables. Trátelo como una herramienta experimental: mantenga una línea de base funcional, cambie una cosa a la vez y pruebe con diferentes tamaños de imagen antes de confiar en un ajuste preestablecido. + Comience con un sombreador que funcione bien o un efecto simple antes de agregar parámetros. + Cambie un parámetro o bloque de código a la vez y verifique la vista previa en busca de errores. + Exporte ajustes preestablecidos solo después de probarlos en algunos tamaños de imagen diferentes. + Haz arte SVG o ASCII + Genere activos de tipo vectorial o imágenes basadas en texto para resultados creativos. + Elija el tipo de salida creativa + Las herramientas SVG y ASCII sirven para diferentes objetivos: gráficos escalables o representación de estilo de texto. Ambas son conversiones creativas, por lo que obtener una vista previa del tamaño final es más importante que juzgar el resultado a partir de una pequeña miniatura. + Utilice SVG Maker cuando el resultado deba escalarse limpiamente o reutilizarse en flujos de trabajo de diseño. + Utilice Arte ASCII cuando desee una representación de texto estilizado de una imagen. + Obtenga una vista previa en el tamaño final porque los pequeños detalles pueden desaparecer en la salida generada. + Generar texturas de ruido + Cree texturas procesales para fondos, máscaras, superposiciones o experimentos. + Sintonizar la salida procesal + La generación de ruido crea nuevas imágenes a partir de parámetros, por lo que pequeños cambios pueden producir resultados muy diferentes. Es útil para texturas, máscaras, superposiciones, marcadores de posición o para probar la compresión con patrones detallados. + Elija un tipo de ruido y un tamaño de salida antes de ajustar los detalles finos. + Ajuste la escala, la intensidad, los colores o la semilla hasta que el patrón se ajuste al uso objetivo. + Guarde los resultados útiles y anote los parámetros si necesita recrear la textura más adelante. + Proyectos de marcado + Utilice archivos de proyecto cuando las anotaciones necesiten seguir siendo editables después de cerrar la aplicación + Mantenga las ediciones en capas reutilizables + Los archivos de proyecto de marcado son útiles cuando una captura de pantalla, un diagrama o una imagen de instrucciones pueden necesitar cambios más adelante. Los archivos PNG/JPEG exportados son imágenes finales, mientras que los archivos de proyecto conservan las capas editables y el estado de edición para futuros ajustes. + Utilice capas de marcado cuando las anotaciones, las leyendas o los elementos de diseño deban seguir siendo editables. + Guarde o vuelva a abrir el archivo del proyecto antes de exportar una imagen final si espera más revisiones. + Exporte una imagen normal solo cuando esté listo para compartir una versión final aplanada. + Cifrar archivos + Proteja los archivos con una contraseña y pruebe el descifrado antes de eliminar cualquier copia de origen + Maneje las salidas cifradas con cuidado + Las herramientas de cifrado pueden proteger archivos con cifrado basado en contraseña, pero no reemplazan recordar la clave o mantener copias de seguridad. El cifrado es útil para el transporte y el almacenamiento, mientras que el ZIP es mejor cuando el objetivo principal es agrupar varias salidas. + Primero cifre una copia y conserve el original hasta que haya probado que el descifrado funciona con la contraseña que almacenó. + Utilice un administrador de contraseñas u otro lugar seguro para la clave; la aplicación no puede adivinar una contraseña perdida por usted. + Comparta archivos cifrados sólo con personas que también tengan la contraseña y sepan qué herramienta o método debería descifrarlos. + Organizar herramientas + Herramientas para agrupar, ordenar, buscar y fijar para que la pantalla principal coincida con su flujo de trabajo real + Haz que la pantalla principal sea más rápida + Vale la pena ajustar la configuración de la disposición de las herramientas una vez que sepa qué herramientas utiliza más. La agrupación ayuda a la exploración, el orden personalizado ayuda a la memoria muscular y los favoritos mantienen las herramientas diarias accesibles incluso cuando la lista completa se vuelve grande. + Utilice el modo agrupado mientras aprende a utilizar la aplicación o cuando prefiera herramientas organizadas por tipo de tarea. + Cambie al orden personalizado cuando ya conozca sus herramientas frecuentes y desee menos desplazamientos. + Utilice la configuración de búsqueda y los favoritos juntos para herramientas poco comunes que recuerde por su nombre pero que no quiera que estén visibles todo el tiempo. + Manejar archivos grandes + Evite exportaciones lentas, presión de memoria y resultados inesperadamente grandes + Reducir el trabajo antes de exportar + Las imágenes muy grandes, los lotes largos y los formatos de alta calidad pueden consumir más memoria y tiempo. La solución más rápida suele ser reducir la cantidad de trabajo antes de la operación más pesada, sin esperar a que termine la misma entrada de gran tamaño. + Cambie el tamaño de imágenes enormes antes de aplicar filtros pesados, OCR o conversión de PDF. + Utilice primero un lote más pequeño para confirmar la configuración y luego procese el resto con el mismo ajuste preestablecido. + Bajar la calidad o cambiar el formato cuando el archivo de salida sea más grande de lo esperado. + Caché y vistas previas + Comprender las vistas previas generadas, la limpieza de caché y el uso del almacenamiento después de sesiones intensas + Mantenga el almacenamiento y la velocidad equilibrados + La generación de vistas previas y el caché pueden hacer que la navegación repetida sea más rápida, pero las sesiones de edición intensas pueden dejar datos temporales. La configuración de caché ayuda cuando la aplicación se siente lenta, el almacenamiento es limitado o las vistas previas parecen obsoletas después de muchos experimentos. + Mantener las vistas previas habilitadas cuando se exploran muchas imágenes es más importante que minimizar el almacenamiento temporal. + Borre el caché después de lotes grandes, experimentos fallidos o sesiones largas con muchos archivos de alta resolución. + Utilice el borrado automático de caché si prefiere limpiar el almacenamiento en lugar de mantener las vistas previas activas entre lanzamientos. + Ajustar el comportamiento de la pantalla + Utilice las opciones de pantalla completa, modo seguro, brillo y barra del sistema para concentrarse en el trabajo + Haga que la interfaz se ajuste a la situación + La configuración de pantalla ayuda cuando editas en horizontal, presentas imágenes privadas o necesitas un brillo constante. No son necesarios para un uso normal, pero resuelven situaciones incómodas como la inspección de QR, vistas previas privadas o edición de paisajes abarrotados. + Utilice la configuración de pantalla completa o de la barra del sistema cuando editar el espacio sea más importante que la navegación cromada. + Utilice el modo seguro cuando las imágenes privadas no deban aparecer en capturas de pantalla o vistas previas de aplicaciones. + Utilice la aplicación de brillo para herramientas en las que las imágenes oscuras o los códigos QR son difíciles de inspeccionar. + Mantenga la transparencia + Evite que los fondos transparentes se vuelvan blancos, negros o aplanados + Utilice salida compatible con alfa + La transparencia sobrevive sólo cuando la herramienta seleccionada y el formato de salida admiten alfa. Si un fondo exportado se vuelve blanco o negro, el problema suele ser el formato de salida, una configuración de relleno/fondo o una aplicación de destino que aplanó la imagen. + Elija PNG o WebP al exportar imágenes, adhesivos, logotipos o superposiciones sin fondo. + Evite JPEG para una salida transparente porque no almacena un canal alfa. + Verifique la configuración relacionada con el color de fondo para los formatos alfa si la vista previa muestra un fondo relleno. + Solucionar problemas de intercambio e importación + Utilice la configuración del selector y los formatos compatibles cuando los archivos no aparezcan o no se abran correctamente + Obtener el archivo en la aplicación + Los problemas de importación suelen deberse a permisos del proveedor, formatos no compatibles o comportamiento del selector. Los archivos compartidos desde aplicaciones de mensajería y aplicaciones en la nube pueden ser temporales, por lo que elegir una fuente persistente puede ser más confiable para ediciones más largas. + Intente abrir el archivo a través del selector del sistema en lugar de un acceso directo a archivos recientes. + Si un formato no es compatible con una herramienta, conviértalo primero o abra una herramienta más específica. + Revise la configuración del selector y del iniciador de imágenes si los flujos compartidos o la apertura directa de herramientas se comportan de manera diferente a lo esperado. + Confirmación de salida + Evite perder ediciones no guardadas cuando se realizan gestos, pulsaciones hacia atrás o cambios de herramientas accidentalmente + Proteger el trabajo inacabado + La confirmación de salida de la herramienta es útil cuando utiliza la navegación por gestos, edita archivos grandes o cambia con frecuencia entre aplicaciones. Agrega una pequeña pausa antes de abandonar una herramienta para que las configuraciones y vistas previas sin terminar no se pierdan por accidente. + Habilite la confirmación si algún gesto accidental hacia atrás cerró una herramienta antes de exportarla. + Mantenlo deshabilitado si realizas principalmente conversiones rápidas en un solo paso y prefieres una navegación más rápida. + Úselo junto con ajustes preestablecidos para flujos de trabajo más largos donde reconstruir la configuración sería molesto. + Utilice registros al informar problemas + Recopile detalles útiles antes de abrir un problema o contactar al desarrollador. + Informar problemas con el contexto + Un informe claro con pasos, versión de la aplicación, tipo de entrada y registros es mucho más fácil de diagnosticar. Los registros son más útiles justo después de reproducir un problema porque mantienen actualizado el contexto circundante. + Tenga en cuenta el nombre de la herramienta, la acción exacta y si ocurre con un archivo o con todos los archivos. + Abra App Logs después de reproducir el problema y comparta el resultado del registro relevante cuando sea posible. + Adjunte capturas de pantalla o archivos de muestra solo cuando no contengan información privada. + Se detuvo el procesamiento en segundo plano + Android no permitió que la notificación de procesamiento en segundo plano comenzara a tiempo. Esta es una limitación de sincronización del sistema que no se puede solucionar de manera confiable. Reinicie la aplicación e inténtelo nuevamente; Mantener la aplicación abierta y permitir notificaciones o trabajo en segundo plano puede ser útil. + Saltar selección de archivos + Abra herramientas más rápido cuando la entrada generalmente proviene de compartir, portapapeles o una pantalla anterior + Haga que los lanzamientos repetidos de herramientas sean más rápidos + Omitir selección de archivos cambia el primer paso de las herramientas compatibles. Es útil cuando normalmente ingresa a una herramienta con una imagen ya seleccionada o desde acciones para compartir de Android, pero puede resultar confuso si espera que cada herramienta solicite un archivo de inmediato. + Habilítelo solo cuando su flujo habitual ya proporcione la imagen de origen antes de que se abra la herramienta. + Manténgalo deshabilitado mientras aprende la aplicación, porque la selección explícita hace que cada flujo de herramientas sea más fácil de entender. + Si se abre una herramienta sin una entrada obvia, deshabilite esta configuración o comience desde Compartir/Abrir con para que Android pase el archivo primero. + Abrir el editor primero + Saltar vista previa cuando una imagen compartida debería pasar directamente a edición + Elija vista previa o edición como primera parada + Abrir edición en lugar de vista previa es para usuarios que ya saben que quieren modificar la imagen. La vista previa es más segura cuando inspecciona archivos desde el chat o el almacenamiento en la nube; La edición directa es más rápida para capturas de pantalla, recortes rápidos, anotaciones y correcciones únicas. + Habilite la edición directa si la mayoría de las imágenes compartidas necesitan inmediatamente cambios de recorte, marcado, filtros o exportación. + Deje primero la vista previa cuando inspeccione con frecuencia metadatos, dimensiones, transparencia o calidad de la imagen antes de tomar una decisión. + Utilízalo junto con tus favoritos para que las imágenes compartidas lleguen cerca de las herramientas que realmente utilizas. + Entrada de texto preestablecida + Ingrese valores preestablecidos exactos en lugar de ajustar los controles repetidamente + Utilice valores precisos cuando los controles deslizantes sean lentos + La entrada de texto preestablecida ayuda cuando necesita números exactos para trabajos repetidos: tamaños de imágenes sociales, márgenes de documentos, objetivos de compresión, anchos de línea u otros valores que resulta molesto alcanzar con gestos. También es útil en pantallas pequeñas donde el movimiento fino del control deslizante es más difícil. + Habilite la entrada de texto si reutiliza con frecuencia tamaños exactos, porcentajes, valores de calidad o parámetros de herramientas. + Guarde el valor como valor preestablecido después de escribirlo una vez y luego reutilícelo en lugar de reconstruir la misma configuración. + Mantenga controles de gestos para un ajuste visual aproximado y valores escritos para requisitos de salida estrictos. + Guardar cerca del original + Coloque los archivos generados junto a las imágenes de origen cuando el contexto de la carpeta sea importante + Mantenga las salidas con sus fuentes. + Guardar en la carpeta original es útil para carpetas de cámaras, carpetas de proyectos, escaneos de documentos y lotes de clientes donde la copia editada debe permanecer junto a la fuente. Puede ser menos ordenado que una carpeta de salida dedicada, así que combínelo con patrones o sufijos de nombres de archivos claros. + Habilítelo cuando cada carpeta de origen ya sea significativa y las salidas deban permanecer agrupadas allí. + Utilice sufijos, prefijos, marcas de tiempo o patrones de nombres de archivos para que las copias editadas sean fáciles de separar de los originales. + Desactívelo para lotes mixtos cuando desee que todos los archivos generados se recopilen en una carpeta de exportación. + Saltar producción mayor + Evite guardar conversiones que inesperadamente se vuelvan más pesadas que la fuente + Proteja los lotes de sorpresas de mal tamaño + Algunas conversiones hacen que los archivos sean más grandes, especialmente capturas de pantalla PNG, fotos ya comprimidas, WebP de alta calidad o imágenes con metadatos conservados. Permitir omitir si es más grande evita que esas salidas reemplacen una fuente más pequeña en flujos de trabajo donde reducir el tamaño es el objetivo principal. + Habilítelo cuando la exportación esté destinada a comprimir, cambiar el tamaño o preparar archivos para los límites de carga. + Revisar archivos omitidos después de un lote; Es posible que ya estén optimizados o que necesiten un formato diferente. + Desactívelo cuando la calidad, la transparencia o la compatibilidad de formato sean más importantes que el tamaño final del archivo. + Portapapeles y enlaces + Controle la entrada automática del portapapeles y las vistas previas de enlaces para obtener herramientas basadas en texto más rápidas + Haga que la entrada automática sea predecible + La configuración del portapapeles y los enlaces es conveniente para QR, Base64, URL y flujos de trabajo con mucho texto, pero la entrada automática debe ser intencional. Si la aplicación parece llenar los campos por sí sola, verifique el comportamiento de pegado del portapapeles; Si las tarjetas de enlace parecen ruidosas o lentas, ajuste el comportamiento de vista previa del enlace. + Permita el pegado automático del portapapeles cuando copie texto con frecuencia y abra inmediatamente una herramienta de coincidencia. + Desactive el pegado automático si aparece contenido privado del portapapeles en herramientas donde no lo esperaba. + Desactive las vistas previas de enlaces cuando la obtención de vistas previas sea lenta, distraiga o sea innecesaria para su flujo de trabajo. + Controles compactos + Utilice selectores más densos y colocación rápida de configuraciones cuando el espacio en la pantalla sea reducido + Coloque más controles en pantallas pequeñas + Los selectores compactos y la colocación rápida de configuraciones ayudan en teléfonos pequeños, edición horizontal y herramientas con muchos parámetros. La desventaja es que los controles pueden parecer más densos, por lo que esto es mejor después de que ya reconozca las opciones comunes. + Habilite selectores compactos cuando los cuadros de diálogo o las listas de opciones parezcan demasiado altos para la pantalla. + Ajuste el lado de configuración rápida si los controles de la herramienta son más fáciles de alcanzar desde un lado de la pantalla. + Regrese a controles más espaciosos si todavía está aprendiendo a usar la aplicación o si con frecuencia omite opciones densas. + Cargar imágenes desde la web. + Pegue la URL de una página o imagen, obtenga una vista previa de las imágenes analizadas y luego guarde o comparta los resultados seleccionados + Utilice imágenes web deliberadamente + Web Image Loading analiza las fuentes de imágenes desde la URL proporcionada, obtiene una vista previa de la primera imagen disponible y le permite guardar o compartir imágenes analizadas seleccionadas. Algunos sitios utilizan enlaces temporales, protegidos o generados mediante scripts, por lo que es posible que una página que funcione en un navegador aún no devuelva imágenes utilizables. + Pegue una URL de imagen directa o una URL de página y espere a que aparezca la lista de imágenes analizadas. + Utilice los controles de marco o selección cuando la página contenga varias imágenes y solo necesite algunas de ellas. + Si no se carga nada, intente primero con un enlace de imagen directo o descárguelo a través del navegador; el sitio puede bloquear el análisis o utilizar enlaces privados. + Elija el tipo de cambio de tamaño + Comprenda Explícito, Flexible, Recortar y Ajustar antes de forzar una imagen a nuevas dimensiones + Controlar la forma, el lienzo y la relación de aspecto + El tipo de cambio de tamaño decide qué sucede cuando el ancho y el alto solicitados no coinciden con la relación de aspecto original. Es la diferencia entre estirar píxeles, conservar proporciones, recortar un área adicional o agregar un lienzo de fondo. + Utilice Explícito sólo cuando la distorsión sea aceptable o la fuente ya tenga la misma relación de aspecto que el destino. + Utilice Flexible para mantener las proporciones cambiando el tamaño desde el lado importante, especialmente para fotografías y lotes mixtos. + Utilice Recortar para marcos estrictos sin bordes, o Ajustar cuando toda la imagen deba permanecer visible dentro de un lienzo fijo. + Elija el modo de escala de imagen + Elija el filtro de remuestreo que controla la nitidez, la suavidad, la velocidad y los artefactos de los bordes. + Cambiar el tamaño sin suavidad accidental + El modo de escala de imagen controla cómo se calculan los nuevos píxeles durante el cambio de tamaño. Los modos simples son rápidos y predecibles, mientras que los filtros avanzados pueden preservar mejor los detalles, pero pueden afinar los bordes, crear halos o tardar más en imágenes grandes. + Utilice Básico o Bilineal para cambiar el tamaño rápidamente todos los días cuando el resultado sólo necesita ser más pequeño y limpio. + Utilice los modos Lanczos, Robidoux, Spline o estilo EWA cuando los detalles finos, el texto o la reducción de escala de alta calidad sean importantes. + Utilice Más cercano para pixel art, íconos y gráficos con bordes duros donde los píxeles borrosos arruinarían la apariencia. + Escalar el espacio de color + Decida cómo se combinan los colores mientras se vuelven a muestrear los píxeles durante el cambio de tamaño + Mantenga los degradados y los bordes naturales + Escalar el espacio de color cambia las matemáticas utilizadas al cambiar el tamaño. La mayoría de las imágenes están bien con el valor predeterminado, pero los espacios lineales o perceptivos pueden hacer que los degradados, los bordes oscuros y los colores saturados parezcan más limpios después de un escalado fuerte. + Deje el valor predeterminado cuando la velocidad y la compatibilidad importen más que pequeñas diferencias de color. + Pruebe los modos lineal o de percepción cuando los contornos oscuros, las capturas de pantalla de la interfaz de usuario, los degradados o las bandas de color se vean mal después de cambiar el tamaño. + Compare el antes y el después en la pantalla de destino real, porque los cambios en el espacio de color pueden ser sutiles y depender del contenido. + Limitar los modos de cambio de tamaño + Sepa cuándo se deben omitir, recodificar o reducir las imágenes que exceden el límite para que quepan + Elige lo que sucede en el límite. + Limit Resize no es solo una herramienta para imágenes más pequeñas. Su modo decide qué hace la aplicación cuando una fuente ya está dentro de sus límites o cuando solo un lado infringe la regla, lo cual es importante para carpetas mixtas y preparación de carga. + Utilice Omitir cuando las imágenes dentro de los límites deban dejarse intactas y no exportarse nuevamente. + Utilice Recode cuando las dimensiones sean aceptables pero aún necesite un nuevo formato, comportamiento de metadatos, calidad o nombre de archivo. + Utilice Zoom cuando las imágenes que superen los límites deban reducirse proporcionalmente hasta que se ajusten al cuadro permitido. + Objetivos de relación de aspecto + Evite caras estiradas, bordes cortados y bordes inesperados en tamaños de salida estrictos + Haga coincidir la forma antes de cambiar el tamaño + El ancho y el alto son solo la mitad de un objetivo de cambio de tamaño. La relación de aspecto decide si la imagen puede encajar de forma natural, necesita recortarse o necesita un lienzo a su alrededor. Verificar primero la forma evita resultados de cambio de tamaño más sorprendentes. + Compare la forma de origen con la forma de destino antes de elegir Explícito, Recortar o Ajustar. + Recorte primero cuando el sujeto pueda perder fondo adicional y el destino necesite un encuadre estricto. + Utilice Ajustar o Flexible cuando cada parte de la imagen original deba permanecer visible. + Cambio de tamaño normal o exclusivo + Sepa cuándo agregar píxeles necesita IA y cuándo es suficiente con un simple escalado + No confundas tamaño con detalle + El cambio de tamaño normal cambia las dimensiones interpolando píxeles; no puede inventar detalles reales. La IA mejorada puede crear detalles más nítidos, pero es más lenta y puede alucinar la textura, por lo que la elección correcta depende de si necesita compatibilidad, velocidad o restauración visual. + Utilice el cambio de tamaño normal para miniaturas, límites de carga, capturas de pantalla y cambios de dimensiones simples. + Utilice la IA mejorada cuando una imagen pequeña o suave necesite verse mejor en un tamaño más grande. + Compare caras, texto y patrones después de mejorar la IA porque los detalles generados pueden parecer convincentes pero incorrectos. + El orden de los filtros importa + Aplicar limpieza, color, nitidez y compresión final en una secuencia intencional. + Construya una pila de filtros más limpia + Los filtros no siempre son intercambiables. Un desenfoque antes de la nitidez, un aumento del contraste antes del OCR o un cambio de color antes de la compresión pueden producir resultados muy diferentes desde los mismos controles. + Recorte y gire primero para que los filtros posteriores funcionen solo en los píxeles que quedarán. + Aplique exposición, balance de blancos y contraste antes de aplicar efectos de nitidez o estilización. + Mantenga el cambio de tamaño y la compresión finales cerca del final para que los detalles de la vista previa sobrevivan a la exportación de manera predecible. + Arreglar los bordes del fondo + Limpia halos, restos de sombras, pelos, agujeros y contornos suaves después de la eliminación automática. + Haz que los recortes parezcan intencionados + Las máscaras automáticas suelen verse bien a primera vista, pero revelan problemas en fondos brillantes, oscuros o estampados. La limpieza de bordes marca la diferencia entre un recorte rápido y una pegatina, un logotipo o una imagen de producto reutilizables. + Obtenga una vista previa del resultado contra fondos contrastantes para revelar halos y detalles de bordes faltantes. + Utilice restauración para cabello, esquinas y objetos transparentes antes de borrar los restos circundantes. + Exporte una pequeña prueba del color de fondo final cuando el recorte se colocará en un diseño. + Marcas de agua legibles + Haga marcas visibles en imágenes brillantes, oscuras, ocupadas y recortadas sin arruinar la foto + Protege la imagen sin ocultarla + Una marca de agua que se ve bien en una imagen puede desaparecer en otra. El tamaño, la opacidad, el contraste, la ubicación y la repetición deben elegirse para todo el lote, no sólo para la primera vista previa. + Pruebe la marca en las imágenes más brillantes y más oscuras del lote antes de exportar todos los archivos. + Utilice una opacidad más baja para marcas repetidas grandes y un contraste más fuerte para marcas de esquina pequeñas. + Mantenga claras las caras, el texto y los detalles importantes del producto, a menos que la marca tenga como objetivo evitar su reutilización. + Dividir una imagen en mosaicos + Corta una sola imagen por filas, columnas, porcentajes personalizados, formato y calidad + Preparar rejillas y piezas ordenadas. + Image Splitting crea múltiples resultados a partir de una imagen de origen. Puede dividir por recuentos de filas y columnas, ajustar porcentajes de filas o columnas y guardar piezas con el formato y la calidad de salida seleccionados, lo cual es útil para cuadrículas, sectores de páginas, panoramas y publicaciones sociales ordenadas. + Elija la imagen de origen y luego establezca la cantidad de filas y columnas que necesita. + Ajuste los porcentajes de filas o columnas cuando no todas las piezas deban tener el mismo tamaño. + Elija el formato y la calidad de salida antes de guardar para que cada pieza generada utilice las mismas reglas de exportación. + Recortar tiras de imágenes + Elimine las regiones verticales u horizontales y vuelva a fusionar las partes restantes + Eliminar una región sin dejar espacios + El corte de imágenes es diferente del recorte normal. Puede eliminar una franja vertical u horizontal seleccionada y luego fusionar las partes restantes de la imagen. En cambio, el modo inverso mantiene la región seleccionada y el uso de ambas direcciones inversas mantiene el rectángulo seleccionado. + Utilice corte vertical para regiones laterales, columnas o franjas intermedias; utilice corte horizontal para pancartas, espacios o filas. + Habilite la inversión en un eje cuando desee conservar la pieza seleccionada en lugar de eliminarla. + Obtenga una vista previa de los bordes después del corte porque el contenido fusionado puede parecer abrupto cuando el área eliminada cruza detalles importantes. + Elija el formato de salida + Elija JPEG, PNG, WebP, AVIF, JXL o PDF según el contenido y el destino + Deja que el destino decida el formato + El mejor formato depende de lo que contenga la imagen y dónde se utilizará. Las fotos, las capturas de pantalla, los recursos transparentes, los documentos y los archivos animados fallan de diferentes maneras cuando se los fuerza a utilizar el formato incorrecto. + Utilice JPEG para obtener una amplia compatibilidad fotográfica cuando no se requieran transparencia ni píxeles exactos. + Utilice PNG para obtener capturas de pantalla nítidas, capturas de UI, gráficos transparentes y ediciones sin pérdidas. + Utilice WebP, AVIF o JXL cuando la salida moderna y compacta sea importante y la aplicación receptora la admita. + Extraer portadas de audio + Guarde la carátula del álbum incrustada o los fotogramas multimedia disponibles de archivos de audio como imágenes PNG + Sacar ilustraciones de archivos de audio + Audio Covers utiliza metadatos multimedia de Android para leer ilustraciones incrustadas de archivos de audio. Cuando las ilustraciones incrustadas no están disponibles, el recuperador puede recurrir a un marco multimedia si Android puede proporcionar uno; si no existe ninguna, la herramienta informa que no hay ninguna imagen para extraer. + Abra Cubiertas de audio y seleccione uno o más archivos de audio con la portada esperada. + Revise la portada extraída antes de guardarla o compartirla, especialmente para archivos de aplicaciones de transmisión o grabación. + Si no se encuentra ninguna imagen, es probable que el archivo de audio no tenga una cubierta incrustada o que Android no pueda exponer un marco utilizable. + Metadatos de fechas y ubicación + Decida qué preservar cuando el tiempo de captura es importante pero los datos del GPS o del dispositivo no deben filtrarse + Separe los metadatos útiles de los privados + No todos los metadatos son igualmente riesgosos. La captura de fechas puede resultar útil para archivar y clasificar, mientras que el GPS, los campos tipo serie del dispositivo, las etiquetas de software o los campos de propietario pueden revelar más de lo previsto. + Mantenga etiquetas de fecha y hora cuando el orden cronológico sea importante para álbumes, escaneos o historial de proyectos. + Elimine las etiquetas de identificación de dispositivos y GPS antes de compartir públicamente o enviar imágenes a extraños. + Exporte una muestra e inspeccione EXIF ​​nuevamente cuando los requisitos de privacidad sean estrictos. + Evite colisiones de nombres de archivos + Proteja las salidas por lotes cuando muchos archivos comparten nombres como imagen.jpg o captura de pantalla.png + Haga que cada nombre de salida sea único + Los nombres de archivos duplicados son comunes cuando las imágenes provienen de mensajeros, capturas de pantalla, carpetas en la nube o varias cámaras. Un buen patrón evita el reemplazo accidental y mantiene los resultados rastreables hasta sus fuentes. + Incluya el nombre original más un sufijo cuando la copia editada deba ser reconocible. + Agregue secuencia, marca de tiempo, valor preestablecido o dimensiones al procesar lotes mixtos grandes. + Evite sobrescribir flujos de trabajo hasta que haya verificado que el patrón de nombres no puede colisionar. + Guardar o compartir + Elija entre un archivo persistente y una transferencia temporal a otra aplicación + Exportar con la intención correcta + Guardar y Compartir pueden parecer similares, pero resuelven problemas diferentes. Al guardar se crea un archivo que podrá encontrar más tarde; compartir envía un resultado generado a través de Android a otra aplicación y es posible que no deje una copia local duradera. + Utilice Guardar cuando la salida deba permanecer en una carpeta conocida, realizar una copia de seguridad o reutilizarse más adelante. + Utilice Compartir para mensajes rápidos, archivos adjuntos de correo electrónico, publicaciones en redes sociales o enviar el resultado a otro editor. + Guarde primero cuando la aplicación receptora no sea confiable o cuando sea molesto recrear un recurso compartido fallido. + Tamaño de página PDF y márgenes + Elija el tamaño del papel, el comportamiento de ajuste y el espacio para respirar antes de crear un documento. + Haga que las páginas de imágenes sean imprimibles y legibles + Las imágenes pueden convertirse en páginas PDF incómodas cuando su forma no coincide con el tamaño de papel elegido. Los márgenes, el modo de ajuste y la orientación de la página deciden si el contenido está recortado, es pequeño, estirado o cómodo de leer. + Utilice un tamaño de papel real cuando el PDF pueda imprimirse o enviarse a un formulario. + Utilice márgenes para escaneos y capturas de pantalla para que el texto no quede contra el borde de la página. + Obtenga una vista previa de las páginas verticales y horizontales juntas cuando las imágenes de origen tengan una orientación mixta. + Limpiar escaneos + Mejore los bordes, las sombras, el contraste, la rotación y la legibilidad del papel antes que PDF u OCR + Prepare las páginas antes de archivarlas + Técnicamente, un escaneo puede capturarse pero aún así es difícil de leer. Los pequeños pasos de limpieza antes de la exportación de PDF suelen ser más importantes que la configuración de compresión, especialmente para recibos, notas escritas a mano y páginas con poca luz. + Corrija la perspectiva y recorte los bordes de la mesa, los dedos, las sombras y el desorden del fondo. + Aumente el contraste con cuidado para que el texto quede más claro sin destruir sellos, firmas o marcas de lápiz. + Ejecute OCR solo después de que la página esté en posición vertical y legible, luego verifique los números importantes manualmente. + Comprimir, escalar grises o reparar archivos PDF + Utilice operaciones PDF de un solo archivo para copias de documentos más ligeros, imprimibles o reconstruidos + Elija la operación de limpieza de PDF + PDF Tools incluye operaciones independientes para compresión, conversión en escala de grises y reparación. La compresión y la escala de grises funcionan principalmente a través de imágenes incrustadas, mientras que la reparación reconstruye la estructura del PDF; Ninguno de estos debe tratarse como una solución garantizada para cada documento. + Utilice Comprimir PDF cuando el documento contenga imágenes y el tamaño del archivo sea importante para compartir. + Utilice Escala de grises cuando el documento deba ser más fácil de imprimir o no necesite imágenes en color. + Utilice Reparar PDF en una copia cuando un documento no se abra o tenga una estructura interna dañada, luego verifique el archivo reconstruido. + Extraer imágenes de archivos PDF + Recupere imágenes PDF incrustadas y empaquete los resultados extraídos en un archivo + Guardar imágenes de origen de documentos + Extraer imágenes escanea el PDF en busca de objetos de imagen incrustados, omite duplicados siempre que sea posible y almacena las imágenes recuperadas en un archivo ZIP generado. Solo extrae imágenes que realmente están incrustadas en el PDF, no texto, dibujos vectoriales o cada página visible como captura de pantalla. + Utilice Extraer imágenes cuando necesite imágenes almacenadas dentro de un PDF, como fotografías de un catálogo o recursos escaneados. + Utilice PDF a imágenes o extracción de páginas cuando necesite páginas completas, incluido el texto y el diseño. + Si la herramienta no informa que no hay imágenes incrustadas, es posible que la página tenga contenido de texto/vector o que las imágenes no se almacenen como objetos extraíbles. + Metadatos, aplanamiento y salida de impresión + Edite propiedades de documentos, rasterice páginas o prepare diseños de impresión personalizados intencionalmente + Elija la acción del documento final + Los metadatos de PDF, el aplanamiento y la preparación de impresión resuelven diferentes problemas de la etapa final. Los metadatos controlan las propiedades del documento, Flatten PDF rasteriza las páginas en resultados menos editables e Print PDF prepara un nuevo diseño con controles de tamaño de página, orientación, margen y páginas por hoja. + Utilice metadatos cuando el autor, el título, las palabras clave, el productor o la limpieza de la privacidad sean importantes. + Utilice Aplanar PDF cuando el contenido de la página visible sea más difícil de editar como objetos PDF separados. + Utilice Imprimir PDF cuando necesite un tamaño de página, orientación, márgenes personalizados o varias páginas por hoja. + Preparar imágenes para OCR + Utilice recortar, rotar, contrastar, eliminar ruido y escalar antes de pedirle al OCR que lea el texto. + Dar píxeles más limpios al OCR + Los errores de OCR a menudo provienen de la imagen de entrada y no del motor de OCR. Las páginas torcidas, el bajo contraste, el desenfoque, las sombras, el texto pequeño y los fondos mixtos reducen la calidad del reconocimiento. + Recorte el área de texto y gire la imagen para que las líneas queden horizontales antes del reconocimiento. + Aumente el contraste o convierta a un blanco y negro más limpio sólo cuando esto facilite la lectura de las letras. + Cambie el tamaño del texto pequeño hacia arriba moderadamente, pero evite un enfoque agresivo que cree bordes falsos. + Consultar contenido QR + Revise enlaces, credenciales de Wi-Fi, contactos y acciones antes de confiar en un código + Trate el texto decodificado como entrada que no es de confianza + Un código QR puede ocultar una URL, una tarjeta de contacto, una contraseña de Wi-Fi, un evento del calendario, un número de teléfono o texto sin formato. Es posible que la etiqueta visible cerca del código no coincida con la carga útil real, así que revise el contenido decodificado antes de actuar en consecuencia. + Inspeccione la URL completamente decodificada antes de abrirla, especialmente los dominios acortados o desconocidos. + Tenga cuidado con los códigos de Wi-Fi, contactos, calendario, teléfono y SMS porque pueden desencadenar acciones sensibles. + Copie el contenido primero cuando necesite verificarlo en otro lugar en lugar de abrirlo inmediatamente. + Generar códigos QR + Cree códigos a partir de texto sin formato, URL, Wi-Fi, contactos, correo electrónico, teléfono, SMS y datos de ubicación + Cree la carga útil QR adecuada + La pantalla QR puede generar códigos a partir del contenido ingresado, así como escanear los existentes. Los tipos de QR estructurados ayudan a codificar datos en un formato que otras aplicaciones entienden, como detalles de inicio de sesión de Wi-Fi, tarjetas de contacto, campos de correo electrónico, números de teléfono, contenido de SMS, URL o coordenadas geográficas. + Elija texto sin formato para notas simples o use un tipo estructurado cuando el código deba abrirse como Wi-Fi, contacto, URL, correo electrónico, teléfono, SMS o datos de ubicación. + Verifique todos los campos antes de compartir el código generado porque la vista previa visible solo refleja la carga útil que ingresó. + Pruebe el código QR guardado con un escáner cuando sea impreso, publicado públicamente o utilizado por otras personas. + Elija un modo de suma de comprobación + Calcule hashes a partir de archivos o texto, compare un archivo o verifique por lotes muchos archivos + Verificar el contenido, no la apariencia + Checksum Tools tiene pestañas separadas para hash de archivos, hash de texto, comparación de un archivo con un hash conocido y comparación por lotes. Una suma de comprobación demuestra la identidad byte por byte del algoritmo seleccionado; no prueba que dos imágenes simplemente parezcan similares. + Utilice Calcular para archivos y Text Hash para datos de texto escritos o pegados. + Utilice Comparar cuando alguien le proporcione una suma de comprobación esperada para un archivo. + Utilice la comparación por lotes cuando muchos archivos necesiten hashes o verificación en una sola pasada y luego mantenga coherente el algoritmo seleccionado. + Privacidad del portapapeles + Utilice funciones automáticas del portapapeles sin exponer accidentalmente datos privados copiados + Mantenga el contenido copiado intencionalmente + La automatización del portapapeles es conveniente, pero el texto copiado puede contener contraseñas, enlaces, direcciones, tokens o notas privadas. Trate la entrada basada en el portapapeles como una función de velocidad que debe coincidir con sus hábitos y expectativas de privacidad. + Desactive el uso automático del portapapeles si aparece texto privado en las herramientas de forma inesperada. + Utilice el guardado solo en el portapapeles cuando desee deliberadamente que el resultado evite el almacenamiento. + Borre o reemplace el portapapeles después de manipular texto confidencial, datos QR o cargas útiles Base64. + Formatos de color + Comprenda los valores de color HEX, RGB, HSV, alfa y copiados antes de pegarlos en otro lugar + Copie el formato que espera el objetivo. + El mismo color visible se puede escribir de varias maneras. Una herramienta de diseño, un campo CSS, un valor de color de Android o un editor de imágenes pueden esperar un orden, manejo alfa o modelo de color diferente. + Utilice HEX al pegar en campos web, de diseño o temáticos que requieran códigos de color compactos. + Utilice RGB o HSV cuando necesite ajustar canales, brillo, saturación o tono manualmente. + Marque alfa por separado cuando la transparencia sea importante porque algunos destinos la ignoran o usan un orden diferente. + Explora la biblioteca de colores + Busque colores con nombre por nombre o HEX y mantenga los favoritos cerca de la parte superior + Encuentra colores con nombre reutilizables + La biblioteca de colores carga una colección de colores con nombre, la clasifica por nombre, filtra por nombre de color o valor HEX y almacena los colores favoritos para que sea más fácil acceder a las entradas importantes. Es útil cuando necesita un color con nombre real en lugar de tomar una muestra de una imagen. + Busque por nombre de color cuando conozca la familia, como rojo, azul, pizarra o menta. + Busque por HEX cuando ya tenga un color numérico y desee encontrar entradas con nombres coincidentes o cercanos. + Marque los colores frecuentes como favoritos para que estén por delante de la biblioteca general mientras navega. + Utilice colecciones de degradado de malla + Descargue recursos de degradado de malla disponibles y comparta imágenes seleccionadas + Utilice activos de gradiente de malla remotos + Mesh Gradients puede cargar una colección de recursos remotos, descargar imágenes de degradado que faltan, mostrar el progreso de la descarga y compartir degradados seleccionados. La disponibilidad depende del acceso a la red y del almacén de recursos remoto, por lo que una lista vacía generalmente significa que los recursos aún no estaban disponibles. + Abra Degradados de malla desde las herramientas de degradado cuando desee imágenes de degradado de malla listas para usar. + Espere a que se carguen los recursos o progrese la descarga antes de asumir que la colección está vacía. + Seleccione y comparta los degradados que necesita, o regrese a Gradient Maker cuando desee crear un degradado personalizado manualmente. + Resolución de activos generados + Elija el tamaño de salida antes de generar degradados, ruido, fondos de pantalla, arte tipo SVG o texturas + Genera al tamaño que necesitas + Las imágenes generadas no tienen una cámara original a la que recurrir. Si la salida es demasiado pequeña, el escalado posterior puede suavizar patrones, cambiar detalles o cambiar la forma en que el recurso se coloca en mosaico y se comprime. + Primero establezca las dimensiones para el caso de uso final, como fondo de pantalla, icono, fondo, impresión o superposición. + Utilice tamaños más grandes para texturas que puedan recortarse, ampliarse o reutilizarse en varios diseños. + Exporte versiones de prueba antes de producir resultados enormes porque los detalles del procedimiento pueden cambiar el comportamiento de la compresión. + Exportar fondos de pantalla actuales + Recuperar los fondos de pantalla integrados, de inicio y de bloqueo disponibles desde el dispositivo + Guardar o compartir fondos de pantalla instalados + Wallpapers Export lee los fondos de pantalla que Android expone a través de las API de fondos de pantalla del sistema. Según el dispositivo, la versión de Android, los permisos y la variante de compilación, los fondos de pantalla de Inicio, Bloqueo o integrados pueden estar disponibles por separado o pueden faltar. + Abra Exportación de fondos de pantalla para cargar los fondos de pantalla que el sistema permite leer a la aplicación. + Seleccione las entradas disponibles de Inicio, Bloqueo o fondo de pantalla integrado que desea guardar, copiar o compartir. + Si falta un fondo de pantalla o la función no está disponible, generalmente se trata de una limitación del sistema, de un permiso o de una variante de compilación, en lugar de una configuración de edición. + Acelerador de animación de esquinas + Copiar color como + HEX + nombre + Modelo de retrato mate para recortes rápidos de personas con cabello más suave y manejo de bordes. + Mejora rápida en condiciones de poca luz mediante la estimación de curva Zero-DCE++. + Modelo de restauración de imágenes de Restormer para reducir las rayas de lluvia y los artefactos de clima húmedo. + Modelo de corrección de documentos que predice una cuadrícula de coordenadas y reasigna páginas curvas en un escaneo más plano. + Escala de duración del movimiento + Controla la velocidad de animación de la aplicación: 0 desactiva el movimiento, 1 es normal, los valores más altos ralentizan las animaciones + Mostrar herramientas recientes + Mostrar acceso rápido a herramientas utilizadas recientemente en la pantalla principal + Estadísticas de uso + Explore las aperturas de aplicaciones, los lanzamientos de herramientas y las herramientas más utilizadas + Se abre la aplicación + Se abre la herramienta + última herramienta + Guardadas exitosas + Racha de actividad + Datos guardados + formato superior + Herramientas utilizadas + %1$d abre • %2$s + Aún no hay estadísticas de uso + Abra algunas herramientas y aparecerán aquí automáticamente. + Sus estadísticas de uso se almacenan solo localmente en su dispositivo. ImageToolbox los usa solo para mostrar su actividad personal, herramientas favoritas e información sobre datos guardados. + editor editar foto retoque de imagen ajustar + cambiar tamaño convertir escala resolución dimensiones ancho alto jpg jpeg png webp comprimir + comprimir tamaño peso bytes mb kb reducir calidad optimizar + relación de aspecto de recorte de recorte + efecto de filtro corrección de color ajustar máscara lut + dibujar pincel lápiz anotar marcado + cifrar descifrar contraseña secreto + eliminador de fondo borrar eliminar recorte transparente alfa + vista previa de la galería del visor inspeccionar abrir + puntada fusionar combinar panorama captura de pantalla larga + url web descargar internet enlace imagen + selector cuentagotas muestra hex rgb color + paleta colores muestras extraer dominante + privacidad de metadatos exif eliminar tira limpiar ubicación gps + comparar diferencia diferencial antes después + límite cambiar tamaño máximo mínimo megapíxeles dimensiones abrazadera + herramientas de páginas de documentos pdf + ocr texto reconocer extraer escaneo + malla de color de fondo degradado + superposición de sello de texto del logotipo de marca de agua + gif animación animada convertir cuadros + apng animación animada png convertir marcos + archivo zip comprimir paquete descomprimir archivos + jxl jpeg xl convertir formato de imagen jpg + svg vector traza convertir xml + formato convertir jpg jpeg png webp heic avif jxl bmp + documento escáner escanear papel perspectiva recorte + escaneo de código de barras qr + superposición de apilamiento astrofotografía mediana promedio + dividir piezas de corte de cuadrícula de azulejos + convertir color hexadecimal rgb hsl hsv cmyk laboratorio + webp convierte formato de imagen animada + generador de ruido textura aleatoria grano + diseño de cuadrícula de collage combinar + capas de marcado anotar superposición editar + base64 codificar decodificar datos uri + suma de comprobación hash md5 sha1 sha256 verificar + fondo degradado de malla + metadatos exif editar gps fecha cámara + cortar piezas de rejilla divididas en azulejos + metadatos de la música de la carátula del álbum de la portada del audio + fondo de pantalla exportar fondo + caracteres de arte de texto ascii + ai ml neural mejora segmento de lujo + paleta de materiales de la biblioteca de colores denominada colores + estudio de efecto de fragmento de sombreador glsl + pdf fusionar combinar unirse + pdf dividir páginas separadas + pdf rotar orientación de páginas + pdf reorganizar reordenar páginas ordenar + paginación de números de página en pdf + pdf ocr texto reconocer extracto con capacidad de búsqueda + texto del logotipo del sello de marca de agua en pdf + dibujo de firma en pdf + pdf proteger contraseña cifrar bloqueo + pdf desbloquear contraseña descifrar eliminar protección + pdf comprimir reducir tamaño optimizar + pdf escala de grises negro blanco monocromo + pdf reparar arreglar recuperar roto + propiedades de metadatos pdf exif título del autor + pdf eliminar páginas + márgenes de recorte de recorte pdf + pdf aplanar anotaciones forma capas + pdf extraer imágenes fotos + comprimir archivo pdf zip + impresora de impresión pdf + visor de vista previa de pdf leído + imágenes a pdf convertir jpg jpeg png documento + pdf a imágenes páginas exportar jpg png + pdf anotaciones comentarios eliminar limpiar + Variante neutra + Error + Sombra paralela + Altura del diente + Rango de dientes horizontales + Rango de dientes verticales + Borde superior + Borde derecho + Borde inferior + Borde izquierdo + Borde rasgado + Restablecer estadísticas de uso + Se abre la herramienta, se restablecen las estadísticas guardadas, la racha, los datos guardados y el formato superior. Las aperturas de aplicaciones permanecerán sin cambios. + Audio + Archivo + Exportar perfiles + Guardar perfil actual + Eliminar perfil de exportación + ¿Quiere eliminar el perfil de exportación \\"%1$s\\"? + Borde de dibujo + Mostrar un borde fino alrededor del lienzo de dibujo y borrado. + Ondulado + Elementos de interfaz de usuario redondeados con un borde ondulado suave + Cuchara + Muesca + Esquinas redondeadas talladas hacia adentro + Esquinas escalonadas con doble corte. + Marcador de posición + Utilice {nombre de archivo} para insertar el nombre de archivo original sin extensión + Llamarada + Monto base + Cantidad de timbre + cantidad de rayos + Ancho del anillo + Distorsionar la perspectiva + Aspecto y sensación de Java + Cortar + ángulo X + Y angle + Cambiar el tamaño de la salida + Gota de agua + Longitud de onda + Fase + Pase alto + Máscara de color + Cromo + Disolver + Blandura + Comentario + Desenfoque de lente + entrada baja + Entrada alta + Baja producción + Alto rendimiento + Efectos de luz + Altura del bulto + Suavidad de golpes + Onda + amplitud X + amplitud Y + longitud de onda X + longitud de onda Y + Desenfoque adaptativo + Generación de texturas + Genere varias texturas procesales y guárdelas como imágenes. + Tipo de textura + Inclinación + Tiempo + Brillar + Dispersión + Muestras + Coeficiente de ángulo + Coeficiente de gradiente + Tipo de cuadrícula + poder de distancia + Escala Y + Borrosidad + tipo de base + factor de turbulencia + Escalada + Iteraciones + Anillos + Metal cepillado + Cáusticos + Celular + Tablero de damas + fbm + Mármol + Plasma + Colcha + Madera + aleatorio + Cuadrado + Hexagonal + Octagonal + Triangular + Puntiagudo + Ruido VL + Ruido SC + Reciente + Aún no hay filtros usados ​​recientemente + textura generador cepillado metal cáusticos celular tablero de ajedrez mármol plasma colcha madera ladrillo camuflaje celda nube grieta tela follaje panal hielo lava nebulosa papel óxido arena fumar piedra terreno topografía agua onda + Ladrillo + Camuflaje + Celúla + Nube + Grieta + Tela + Follaje + Panal + Hielo + Lava + Nebulosa + Papel + Óxido + Arena + Fumar + Piedra + Terreno + Topografía + Ondulación del agua + Madera avanzada + Ancho del mortero + Irregularidad + Bisel + Aspereza + Primer umbral + Segundo umbral + Tercer umbral + Suavidad de los bordes + Ancho del borde + Cobertura + Detalle + Profundidad + Derivación + Hilos horizontales + Hilos verticales + Pelusa + venas + Iluminación + Ancho de grieta + Helada + Fluir + Corteza + Densidad de nubes + estrellas + Densidad de fibra + Fuerza de la fibra + Manchas + Corrosión + picaduras + copos + Frecuencia de dunas + Ángulo del viento + ondulaciones + briznas + escala de vena + Nivel del agua + nivel de montaña + Erosión + Nivel de nieve + recuento de líneas + Grosor de la línea + Sombreado + Color de poro + Color del mortero + Color ladrillo oscuro + color ladrillo + Color de resaltado + color oscuro + color bosque + color tierra + Color arena + Color de fondo + color de celda + Color del borde + color cielo + color de sombra + color claro + Color de superficie + color de variación + color de grieta + color de deformación + color de trama + Color de hoja oscuro + Color de la hoja + Color del borde + color miel + color profundo + color hielo + color escarcha + color de la corteza + Lava color + color brillante + color del espacio + color violeta + color azul + color base + Color de fibra + color de mancha + Color metálico + Color óxido oscuro + Color óxido + color naranja + color humo + color de vena + color de las tierras bajas + color de agua + color roca + color nieve + color bajo + color alto + Color de línea + color superficial + Césped + Suciedad + Cuero + Concreto + Asfalto + Musgo + Fuego + Aurora + Marea negra + Acuarela + Flujo abstracto + Ópalo + Acero de Damasco + Iluminación + Terciopelo + Marmoleado de tinta + Lámina holográfica + Bioluminiscencia + Vórtice cósmico + Lámpara de lava + Horizonte de eventos + Floración fractal + Túnel cromático + eclipse corona + Atractor extraño + Corona de ferrofluido + supernova + Iris + pluma de pavo real + Concha de nautilo + Planeta anillado + Densidad de la hoja + Longitud de la hoja + Viento + parches + Grupos + Humedad + guijarros + Arrugas + poros + Agregar + Grietas + Alquitrán + Tener puesto + motas + Fibras + Frecuencia de llama + Fumar + Intensidad + Cintas + Alzacuello + Iridiscencia + Oscuridad + Florece + Pigmento + Bordes + Papel + Difusión + Simetría + Nitidez + juego de colores + lechoso + capas + Plegable + Polaco + Sucursales + Dirección + Brillo + Pliegues + plumaje + equilibrio de tinta + Espectro + Arrugas + Difracción + Brazos + Girar + Resplandor central + manchas + Inclinación del disco + Tamaño del horizonte + Ancho del disco + lentes + Pétalos + Rizo + Filigrana + facetas + Curvatura + Tamaño de la luna + Tamaño de la corona + rayos + anillo de diamantes + Lóbulos + Densidad de órbita + Espesor + Zapatillas con clavos + longitud de la punta + Tamaño del cuerpo + Metálico + Radio de choque + Ancho de la concha + desechado + tamaño de pupila + tamaño del iris + variación de color + Luz de captura + Tamaño de los ojos + Densidad de púas + vueltas + Cámaras + Apertura + Crestas + Perlescencia + Tamaño del planeta + Inclinación del anillo + Ancho del anillo + Atmósfera + color tierra + Color hierba oscura + Color de la hierba + Color de la punta + Color tierra oscuro + color seco + Color guijarro + Color cuero + color concreto + color alquitrán + color asfalto + Color piedra + color del polvo + color del suelo + Color musgo oscuro + color musgo + color rojo + Color del núcleo + color verde + color cian + color magenta + color dorado + Color del papel + Color del pigmento + color secundario + primer color + segundo color + color rosa + Color acero oscuro + Color acero + Color acero claro + color óxido + color halo + Color del perno + Color terciopelo + color brillo + color de tinta azul + color de tinta roja + color de tinta oscuro + color plata + color amarillo + Color del tejido + Color del disco + color caliente + Color de lente + Color exterior + color interior + color corona + color frio + color cálido + color de la nube + Color de llama + color pluma + Color de concha + color perla + color del planeta + Color del anillo + Eliminar archivos originales después de guardar + Sólo se eliminarán los archivos fuente procesados ​​correctamente. + Archivos originales eliminados: %1$d + No se pudieron eliminar los archivos originales: %1$d + Archivos originales eliminados: %1$d, fallidos: %2$d + Gestos de hoja + Permitir arrastrar hojas de opciones expandidas + Submuestreo de croma + Profundidad de bits + Sin pérdidas + %1$d-bit + Cambiar nombre por lotes + Cambie el nombre de varios archivos usando patrones de nombre de archivo + cambiar el nombre de los archivos por lotes nombre de archivo patrón secuencia extensión de fecha + Elija archivos para cambiar el nombre + Patrón de nombre de archivo + Rebautizar + Fuente de fecha + fecha EXIF ​​tomada + Fecha de modificación del archivo + Fecha de creación del archivo + fecha actual + fecha manual + fecha manual + tiempo manual + La fecha seleccionada no está disponible para algunos archivos. Para ellos se utilizará la fecha actual. + Ingrese un patrón de nombre de archivo + El patrón produce un nombre de archivo no válido o demasiado largo + El patrón produce nombres de archivos duplicados. + Todos los nombres de archivos ya coinciden con el patrón. + Este patrón utiliza tokens que no están disponibles para cambiar el nombre por lotes. + El nombre seguirá siendo el mismo. + Archivos renombrados exitosamente + No se pudo cambiar el nombre de algunos archivos + No se concedió el permiso + Utiliza el nombre del archivo original sin extensión, lo que le ayuda a mantener intacta la identificación de la fuente. También admite el corte con \\o{start:end}, la transliteración con \\o{t} y el reemplazo con \\o{s/old/new/}. + Contador autoincremental. Admite formato personalizado: \\c{padding} (por ejemplo, \\c{3} -&gt; 001), \\c{start:step} o \\c{start:step:padding}. + El nombre de la carpeta principal donde se encontraba el archivo original. + El tamaño del archivo original. Admite unidades: \\z{b}, \\z{kb}, \\z{mb} o simplemente \\z para formato legible por humanos. + Genera un identificador único universal (UUID) aleatorio para garantizar la unicidad del nombre de archivo. + El cambio de nombre de archivos no se puede deshacer. Asegúrese de que los nuevos nombres sean correctos antes de continuar. + Confirmar cambio de nombre + Configuración del menú lateral + Escala de menú + Menú alfa + Balance de blancos automático + Recorte + Comprobando marcas de agua ocultas + Almacenamiento + Límite de caché + Borre el caché automáticamente cuando supere este tamaño + Intervalo de limpieza + Con qué frecuencia comprobar si se debe borrar el caché + Al iniciar la aplicación + 1 día + 1 semana + 1 mes + Borre el caché de la aplicación automáticamente según el límite y el intervalo seleccionados + Superficie de cultivo móvil + Permite mover toda el área de cultivo arrastrando dentro de ella + Fusionar GIF + Combine varios clips GIF en una animación + Orden de clips GIF + Contrarrestar + Jugar al revés + Bumerang + Reproducir hacia adelante y luego hacia atrás + Normalizar el tamaño del marco + Escale los marcos para que quepan en el clip más grande; apague para conservar su escala original + Retraso entre clips (ms) + Carpeta + Seleccionar todas las imágenes de una carpeta y sus subcarpetas + Buscador de duplicados + Encuentre copias exactas e imágenes visualmente similares + buscador de duplicados imágenes similares copias exactas fotos limpieza almacenamiento dHash SHA-256 + Elija imágenes para encontrar duplicados + Sensibilidad a la similitud + Copias exactas + Imágenes similares + Seleccionar todos los duplicados exactos + Seleccionar todo excepto recomendado + No se encontraron duplicados + Intente agregar más imágenes o aumentar la sensibilidad a la similitud + No se pudieron leer %1$d imágenes + %1$s • %2$s recuperable + %1$d grupos • %2$s recuperable + %1$d seleccionado • %2$s + Esto no se puede deshacer. Es posible que Android le solicite que confirme la eliminación en un cuadro de diálogo del sistema. + Extraviado + Mover para empezar + Mover al final + \ No newline at end of file diff --git a/core/resources/src/main/res/values-et/strings.xml b/core/resources/src/main/res/values-et/strings.xml new file mode 100644 index 0000000..561304f --- /dev/null +++ b/core/resources/src/main/res/values-et/strings.xml @@ -0,0 +1,3739 @@ + + + + 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 + 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 + 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. + 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. + 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 + Sisu teadlik moonutamine + Selle toimingu sooritamiseks pole piisavalt mälu. Proovige kasutada väiksemat pilti, sulgeda teised rakendused või taaskäivitada rakendus. + Paralleelsed töötajad + Neid pilte ei salvestatud, kuna teisendatud failid oleksid originaalidest suuremad. Kontrollige seda loendit enne originaalpiltide kustutamist. + Vahele jäetud failid: %1$s + Rakenduste logid + Probleemide tuvastamiseks vaadake rakenduse logisid + Lemmikud rühmitatud tööriistades + Lisab lemmikud vahekaardina, kui tööriistad on tüübi järgi rühmitatud + Näita lemmikut viimasena + Liigutab lemmiktööriistade vahekaardi lõppu + Horisontaalne vahekaugus + Vertikaalne vahekaugus + Tagurpidi energia + Kasutage edasisuunalise energia algoritmi asemel lihtsat gradiendi suurusjärgu energiakaarti + Eemaldage maskeeritud ala + Õmblused läbivad maskeeritud ala, eemaldades selle kaitsmise asemel ainult valitud ala + VHS NTSC + NTSC täpsemad sätted + Täiendav analooghäälestus tugevama VHS-i ja ülekandeartefaktide jaoks + Kroomiline verejooks + Teibi kulumine + Jälgimine + Luma määrimine + Helin + Lumi + Kasutage välja + Filtri tüüp + Sisend luma filter + Sisend chroma lowpass + Kroomi demodulatsioon + Faasi nihe + Faasi nihe + Pea ümberlülitamine + Pea lülituskõrgus + Pea lülitamise nihe + Pea ümberlülitamise vahetus + Keskjoone asend + Keskjoone värin + Müra kõrguse jälgimine + Jälgimislaine + Lume jälgimine + Lume anisotroopia jälgimine + Jälgimismüra + Müra intensiivsuse jälgimine + Komposiitmüra + Komposiitmüra sagedus + Komposiitmüra intensiivsus + Komposiitmüra detail + Helina sagedus + Helina võimsus + Luma müra + Luma müra sagedus + Luma müra intensiivsus + Luma müra detail + Kroomiline müra + Kroomi müra sagedus + Kroomi müra intensiivsus + Kroomi müra detail + Lume intensiivsus + Lume anisotroopia + Kroomifaasi müra + Kroomifaasi viga + Kroomi viivitus horisontaalne + Kroomi viivitus vertikaalne + VHS lindi kiirus + VHS-i värvikadu + VHS-i teravustamise intensiivsus + VHS-i teravussagedus + VHS-i serva laine intensiivsus + VHS servalaine kiirus + VHS servalaine sagedus + VHS servalaine detail + Väljundi kroma madalpääs + Skaala horisontaalselt + Skaala vertikaalne + Skaalategur X + Skaalategur Y + Tuvastab tavalised piltide vesimärgid ja värvib need LaMa abil. Laadib tuvastamis- ja värvimismudelid automaatselt alla + Deuteranoopia + Laienda pilti + Objekti segmenteerimisel põhinev tausta eemaldaja YOLO v11 segmenteerimise abil + Näita joone nurka + Näitab joonistamise ajal praegust joone pööramist kraadides + Animeeritud emotikonid + Kuva saadaolevad emotikonid animatsioonidena + Salvesta originaalkausta + Salvestage uued failid valitud kausta asemel algse faili kõrvale + Vesimärgi PDF + Pole piisavalt mälu + Tõenäoliselt on pilt selles seadmes töötlemiseks liiga suur või süsteemi vaba mälumaht on otsa saanud. Proovige vähendada pildi eraldusvõimet, sulgeda teised rakendused või valida väiksem fail. + Silumismenüü + Menüü rakenduse funktsioonide testimiseks, see ei ole mõeldud tootmisväljaandes kuvamiseks + Pakkuja + PaddleOCR nõuab teie seadmes täiendavaid ONNX-mudeleid. Kas soovite %1$s andmeid alla laadida? + Universaalne + korea keel + ladina keel + idaslaavi + Tai + kreeka keel + inglise keel + kirillitsa + araabia keel + Devanagari + tamili keel + telugu + Varjutaja + Varjundi eelseadistus + Varjutajat pole valitud + Shaderi stuudio + Looge, redigeerige, kinnitage, importige ja eksportige kohandatud fragmentide varjutajaid + Salvestatud varjutajad + Avage salvestatud varjutajad, kopeerige, eksportige, jagage või kustutage + Uus varjutaja + Shader fail + .itshader JSON + %1$d parameetrid + Varjutaja allikas + Kirjutage ainult void main() keha. Kasutage UV-koordinaatide jaoks textureCoordinate\'i ja allika proovivõtturina inputImageTexture. + Funktsioonid + Valikulised abifunktsioonid, konstandid ja struktuurid sisestatakse enne käsku void main(). Vormiriietus genereeritakse parameetritest. + Lisage siia vormirõivad, kui varjutaja vajab redigeeritavaid väärtusi. + Lähtestage varjutaja + Praegune varjutaja mustand kustutatakse + Vale varjutaja + Toetamata varjutaja versioon %1$d. Toetatud versioon: %2$d. + Varjundi nime väli ei tohi olla tühi. + Varjundi allika väli ei tohi olla tühi. + Varjutuse allikas sisaldab toetamata märke: %1$s. Kasutage ainult ASCII GLSL-i allikat. + Parameeter \\\"%1$s\\\" deklareeritakse rohkem kui üks kord. + Parameetrite nimed ei tohi olla tühjad. + Parameeter \\\"%1$s\\\" kasutab reserveeritud GPUI-pildi nime. + Parameeter \\\"%1$s\\\" peab olema kehtiv GLSL-i identifikaator. + Parameetril \\\"%1$s\\\" on %2$s väärtuse tüüp \\\"%3$s\\\", eeldatav \\\"%4$s\\\". + Parameeter \\\"%1$s\\\" ei saa määratleda tõeväärtuste miinimum- või maksimumväärtust. + Parameetri \\\"%1$s\\\" min on suurem kui max. + Vaikimisi parameetri \\\"%1$s\\\" väärtus on väiksem kui min. + Parameeter \\\"%1$s\\\" on vaikimisi suurem kui max. + Shader peab deklareerima \\\"uniform sampler2D %1$s;\\\". + Shader peab deklareerima \\\"muutuv vec2 %1$s;\\\". + Varjutaja peab defineerima \\\"void main()\\\". + Shader peab kirjutama gl_FragColorile värvi. + ShaderToy mainImage varjutajaid ei toetata. Kasutage GPUImage\'i tühist main() lepingut. + Animeeritud ShaderToy vormiriietust \\\"iTime\\\" ei toetata. + Animeeritud ShaderToy vormiriietust \\\"iFrame\\\" ei toetata. + ShaderToy vormiriietust \\\"iResolution\\\" ei toetata. + ShaderToy iChanneli tekstuure ei toetata. + Väliseid tekstuure ei toetata. + Väliseid tekstuurilaiendeid ei toetata. + Libretro varjutaja parameetreid ei toetata. + Toetatud on ainult üks sisendtekstuur. Eemalda \\\"ühine %1$s %2$s;\\\". + Parameeter \\\"%1$s\\\" tuleb deklareerida kui \\\"ühtne %2$s %1$s;\\\". + Parameeter \\\"%1$s\\\" on deklareeritud kui \\\"ühtne %2$s %1$s;\\\", eeldatav \\\"ühtne %3$s %1$s;\\\". + Shaderi fail on tühi. + Shaderi fail peab olema .itshader JSON-objekt, millel on versiooni, nime ja varjundi väljad. + Shaderi fail ei ole kehtiv JSON või ei ühti .itshaderi vorminguga. + Väli \\\"%1$s\\\" on kohustuslik. + %1$s on nõutav. + %1$s \\\"%2$s\\\" ei toetata. Toetatud tüübid: %3$s. + %1$s on vajalik \\\"%2$s\\\" parameetrite jaoks. + %1$s peab olema lõplik arv. + %1$s peab olema täisarv. + %1$s peab olema tõene või väär. + %1$s peab olema värvistring, RGB/RGBA massiiv või värviobjekt. + %1$s peab kasutama vormingut #RRGGBAB või #RRGGBBAA. + %1$s peab sisaldama kehtivaid kuueteistkümnendsüsteemi värvikanaleid. + %1$s peab sisaldama 3 või 4 värvikanalit. + %1$s peab olema vahemikus 0 kuni 255. + %1$s peab olema kahenumbriline massiiv või objekt, millel on x ja y. + %1$s peab sisaldama täpselt 2 numbrit. + Duplikaat + Tühjendage alati EXIF + Eemaldage pildi EXIF-andmed salvestamisel, isegi kui tööriist nõuab metaandmete säilitamist + Abi ja näpunäited + %1$d õpetused + Ava tööriist + Õppige peamisi tööriistu, ekspordisuvandeid, PDF-vooge, värviutiliite ja tavaprobleemide parandusi + Alustamine + Valige tööriistad, importige faile, salvestage tulemusi ja kasutage seadeid kiiremini + Pilditöötlus + Piltide suuruse muutmine, kärpimine, filtreerimine, tausta kustutamine, joonistamine ja vesimärgistamine + Failid ja metaandmed + Teisendage vorminguid, võrrelge väljundit, kaitske privaatsust ja kontrollige failinimesid + PDF ja dokumendid + Looge PDF-e, skannige dokumente, OCR-lehti ja valmistage faile ette jagamiseks + Tekst, QR ja andmed + Tuvastage tekst, skannige koode, kodeerige Base64, kontrollige räsi ja pakendage faile + Värvitööriistad + Valige värve, koostage palett, uurige värviteeke ja looge gradiente + Loomingulised tööriistad + Looge SVG-d, ASCII-kunsti, müratekstuure, varjutajaid ja eksperimentaalseid visuaale + Veaotsing + Parandage rasked failid, läbipaistvus, jagamine, importimine ja aruandlus + Valige õige tööriist + Kasutage kategooriaid, otsingut, lemmikuid ja jagage toiminguid, et alustada õigest kohast + Alustage parimast sisenemispunktist + Image Toolboxil on palju keskendunud tööriistu ja paljud neist kattuvad esmapilgul. Alustage ülesandest, mille soovite lõpetada, seejärel valige tööriist, mis juhib tulemuse kõige olulisemat osa: suurus, vorming, metaandmed, tekst, PDF-lehed, värvid või paigutus. + Kasutage otsingut, kui teate toimingu nime (nt suuruse muutmine, PDF, EXIF, OCR, QR või värv). + Avage kategooria, kui teate ainult ülesande tüüpi, seejärel kinnitage sagedased tööriistad lemmikuteks. + Kui mõni muu rakendus jagab pilti Image Toolboxis, valige tööriist jagamislehe voost. + Importige, salvestage ja jagage tulemusi + Mõistke tavalist voogu alates sisendi valimisest kuni redigeeritud faili eksportimiseni + Järgige tavalist redigeerimisvoogu + Enamik tööriistu järgib sama rütmi: valige sisend, kohandage valikuid, vaadake eelvaadet, seejärel salvestage või jagage. Oluline detail on see, et salvestamine loob väljundfaili, samas kui jagamine on tavaliselt parim kiireks üleandmiseks teisele rakendusele. + Valige ühe pildiga tööriistade jaoks üks fail või pakktööriistade jaoks mitu faili, kui valija seda lubab. + Enne salvestamist kontrollige eelvaate ja väljundi juhtelemente, eriti vormingu, kvaliteedi ja failinime valikuid. + Kasutage ühiskasutust kiireks üleandmiseks või salvestage, kui vajate valitud kausta püsivat koopiat. + Töötage korraga paljude piltidega + Paketttööriistad sobivad kõige paremini korduvate suuruse muutmise, teisendamise, tihendamise ja nimetamise ülesannete jaoks + Valmistage ette ennustatav partii + Paketttöötlus on kiireim, kui iga väljund peaks järgima samu reegleid. See on ka kõige lihtsam koht suurte vigade tegemiseks, seega valige enne pika eksportimise alustamist nimed, kvaliteet, metaandmed ja kaustade käitumine. + Valige kõik seotud pildid koos ja säilitage järjekord, kui väljund sõltub järjestusest. + Enne eksportimise alustamist määrake suuruse muutmise, vormingu, kvaliteedi, metaandmete ja failinime reeglid. + Käivitage esmalt väike testpartii, kui lähtefailid on suured või kui sihtvorming on teie jaoks uus. + Kiire ühekordne redigeerimine + Kasutage kõik-ühes redaktorit, kui vajate ühel pildil mitut lihtsat muudatust + Lõpetage üks pilt ilma tööriista hüppamiseta + Üksik redigeerimine on kasulik, kui pilt vajab pigem väikest muudatuste ahelat kui ühte eritoimingut. Tavaliselt on see kiireks puhastamiseks, eelvaateks ja ühe lõpliku koopia eksportimiseks kiirem ilma paketttöövoogu koostamata. + Avage üks redigeerimine ühe pildi jaoks, mis vajab tavalisi kohandusi, märgistamist, kärpimist, pööramist või eksportimise muudatusi. + Rakendage muudatused järjekorras, mis muudab kõigepealt kõige vähem piksleid, nt kärpimine enne filtreid ja filtrid enne lõplikku tihendamist. + Kasutage selle asemel spetsiaalset tööriista, kui vajate paketttöötlust, täpseid failisuuruse sihtmärke, PDF-i väljundit, OCR-i või metaandmete puhastamist. + Kasutage eelseadeid ja sätteid + Salvestage korduvad valikud ja häälestage rakendus oma eelistatud töövoo jaoks + Vältige sama seadistuse kordamist + Eelseaded ja sätted on kasulikud, kui ekspordite sageli sama tüüpi faile. Hea eelseade muudab korduva käsitsi kontrollnimekirja üheks puudutuseks, samas kui globaalsed sätted määravad vaikeseaded, millest uued tööriistad algavad. + Looge eelseadistused levinud väljundsuuruste, vormingute, kvaliteediväärtuste või nimetamismustrite jaoks. + Vaadake üle vaikevormingu, kvaliteedi, salvestuskausta, failinime, valija ja liidese käitumise sätted. + Varundage seaded enne rakenduse uuesti installimist või teise seadmesse teisaldamist. + Määrake kasulikud vaikeseaded + Valige üks kord vaikevorming, kvaliteet, skaalarežiim, suuruse muutmise tüüp ja värviruum + Pange uued tööriistad oma eesmärgile lähemale + Vaikeväärtused ei ole ainult kosmeetilised eelistused. Nad otsustavad, milline vorming, kvaliteet, suuruse muutmise käitumine, mõõtkava režiim ja värviruum kuvatakse paljudes tööriistades esimesena, mis aitab vältida samade valikute uuesti valimist igal ekspordil. + Määrake pildi vaikevorming ja kvaliteet, et need vastaksid teie tavapärasele sihtkohale, nt JPEG fotode jaoks või PNG/WebP alfa jaoks. + Kui ekspordite sageli rangete mõõtmete, sotsiaalsete platvormide või dokumendilehtede jaoks, kohandage vaikesuuruse muutmise tüüpi ja mastaabirežiimi. + Muutke värviruumi vaikeseadeid ainult siis, kui teie töövoog vajab ühtlast värvikäsitlust kuvarites, printides või välistes redaktorites. + Valija ja käivitaja + Kasutage valija seadeid, kui rakendus avab liiga palju dialooge või käivitub valest kohast + Muutke failide avamine teie harjumusega sobivaks + Valija ja käivitaja sätted määravad, kas Image Toolbox käivitub põhiekraanilt, tööriistast või failivaliku voost. Need valikud on eriti kasulikud, kui sisestate tavaliselt Androidi jagamislehelt või kui soovite, et rakendus tunneks end rohkem tööriistakäivitajana. + Kasutage fotovalija seadeid, kui süsteemivalija peidab failid, näitab liiga palju hiljutisi üksusi või käsitleb pilvefaile halvasti. + Lubage vahelejätmine ainult nende tööriistade puhul, kuhu tavaliselt sisestate jagamise kaudu või teate juba lähtefaili. + Vaadake üle käivitusrežiim, kui soovite, et Image Toolbox käituks rohkem nagu tööriistavalija kui tavaline avakuva. + Pildi allikas + Valige valijarežiim, mis töötab kõige paremini galeriide, failihaldurite ja pilvefailidega + Valige failid õigest allikast + Pildiallika seaded mõjutavad seda, kuidas rakendus Androidilt pilte küsib. Parim valik sõltub sellest, kas teie failid asuvad süsteemigaleriis, failihalduri kaustas, pilveteenuse pakkujas või mõnes muus ajutist sisu jagavas rakenduses. + Kasutage vaikevalijat, kui soovite tavaliste galeriipiltide jaoks kõige rohkem süsteemi integreeritud kogemust. + Kui albumeid, kaustu, pilvefaile või mittestandardseid vorminguid ei kuvata seal, kus ootate, lülitage valija režiimi. + Kui jagatud fail kaob pärast lähterakendusest lahkumist, avage see uuesti püsiva valija kaudu või salvestage esmalt kohalik koopia. + Lemmikud ja jagage tööriistu + Hoidke põhiekraan keskendunud ja peitke tööriistad, millel pole jagamise voogudes mõtet + Vähendage tööriistaloendi müra + Lemmikud ja jagamiseks peidetud seaded on kasulikud, kui kasutate iga päev vaid mõnda tööriista. Samuti hoiavad nad jagamisvoo praktilisena, peites tööriistad, mis ei saa kasutada teise rakenduse saadetud failitüüpi. + Kinnitage sagedased tööriistad, et need oleksid hõlpsasti juurdepääsetavad isegi siis, kui tööriistad on kategooriate kaupa rühmitatud. + Peida tööriistad jagamisvoogude eest, kui need pole teisest rakendusest pärinevate failide jaoks asjakohased. + Kasutage lemmikute järjestamise seadeid, kui eelistate lemmikuid lõpus või seotud tööriistadega rühmitatuna. + Varunda seaded + Teisaldage eelseaded, eelistused ja rakenduse käitumine ohutult teisele installile + Hoidke oma seadistus kaasaskantavana + Varundamine ja taastamine on kõige kasulikum pärast eelseadete, kaustade, failinimereeglite, tööriistade järjestuse ja visuaalsete eelistuste häälestamist. Varukoopia abil saate seadetega katsetada või rakendust uuesti installida ilma oma töövoogu mälust uuesti üles ehitamata. + Looge varukoopia pärast eelseadete, failinime käitumise, vaikevormingute või tööriistade paigutuse muutmist. + Kui kavatsete andmeid kustutada, seadmeid uuesti installida või teisaldada, salvestage varukoopia kusagil väljaspool rakenduse kausta. + Taastage sätted enne oluliste partiide töötlemist, et vaike- ja salvestamiskäitumine vastaksid teie vanale seadistusele. + Piltide suuruse muutmine ja teisendamine + Muutke ühe või mitme pildi suurust, vormingut, kvaliteeti ja metaandmeid + Looge muudetud suurusega koopiaid + Suuruse muutmine ja teisendamine on ennustatavate mõõtmete ja kergemate väljundfailide peamine tööriist. Kasutage seda siis, kui pildi suurus, väljundvorming ja metaandmete käitumine on samaaegselt olulised. + Valige üks või mitu pilti ja seejärel valige, kas mõõtmed, skaala või faili suurus on kõige olulisemad. + Valige väljundvorming ja kvaliteet; kasutage PNG-d või WebP-d, kui tuleb säilitada läbipaistvus. + Vaadake tulemuse eelvaadet, seejärel salvestage või jagage loodud koopiaid ilma originaale muutmata. + Muutke faili suuruse järgi + Sihtige suurusepiirangut, kui veebisait, messenger või vorm lükkab raskeid pilte tagasi + Järgige rangeid üleslaadimispiiranguid + Faili suuruse järgi suuruse muutmine on parem kui kvaliteediväärtuste arvamine, kui sihtkoht aktsepteerib ainult faile, mis jäävad alla teatud piiri. Tööriist võib kohandada tihendust ja mõõtmeid eesmärgi saavutamiseks, püüdes samal ajal tulemust kasutatavana hoida. + Sisestage sihtsuurus veidi alla tegeliku üleslaadimise limiidi, et jätta ruumi metaandmete ja pakkuja muutmiseks. + Eelistage fotode kadudega vorminguid, kui sihtmärk on väike; PNG võib jääda suureks ka pärast suuruse muutmist. + Kontrollige pärast eksportimist nägusid, teksti ja servi, sest agressiivse suurusega sihtmärgid võivad tekitada nähtavaid artefakte. + Piira suurust + Kahandage ainult pilte, mis ületavad teie maksimaalset laiust, kõrgust või failipiiranguid + Vältige piltide suuruse muutmist, mis on juba korras + Limit Resize on kasulik segakaustade jaoks, kus mõned pildid on suured ja teised on juba ette valmistatud. Selle asemel, et sundida iga faili sama suurust muutma, hoiab see vastuvõetavad pildid nende algkvaliteedile lähemal. + Määrake maksimaalsed mõõtmed või piirangud sihtkoha põhjal, selle asemel, et muuta iga faili suurust pimesi. + Lubage stiilikäitumine vahelejätmise korral, kui soovite vältida väljundeid, mis muutuvad allikatest raskemaks. + Kasutage seda erinevate kaamerate, ekraanipiltide või allalaadimiste jaoks, mille allika suurused on väga erinevad. + Kärpige, pöörake ja sirutage + Kadreerige oluline ala enne pildi eksportimist või kasutamist muudes tööriistades + Puhastage kompositsioon + Esmalt kärpimine muudab hilisemad filtrid, OCR-i, PDF-lehed ja jagamise tulemused puhtamaks. + Avage Kärbi ja valige pilt, mida soovite kohandada. + Kasutage vaba kärpimist või kuvasuhet, kui väljund peab sobima postituse, dokumendi või avatariga. + Pöörake või pöörake enne salvestamist, et hilisemad tööriistad saaksid parandatud pildi. + Rakendage filtreid ja seadistusi + Looge redigeeritav filtripakk ja vaadake tulemust enne eksportimist + Pildi välimuse häälestamine + Filtrid on kasulikud kiireks korrigeerimiseks, stiliseeritud väljastamiseks ja piltide ettevalmistamiseks OCR-i või printimiseks. Kui pilt sisaldab teksti või peeneid detaile, võivad väikesed kontrasti, teravuse ja heleduse muudatused olla olulisemad kui tugevad efektid. + Lisage filtreid ükshaaval ja jälgige pärast iga muudatust eelvaadet. + Reguleerige heledust, kontrasti, teravust, hägusust, värvi või maske olenevalt ülesandest. + Eksportige ainult siis, kui eelvaade vastab teie sihtseadmele, dokumendile või suhtlusplatvormile. + Esmalt eelvaade + Enne raskema redigeerimis-, teisendus- või puhastustööriista valimist kontrollige pilte + Kontrollige, mida te tegelikult saite + Pildi eelvaade on kasulik, kui fail pärineb vestlusest, pilvesalvestusest või muust rakendusest ja te pole kindel, mida see sisaldab. Mõõtmete, läbipaistvuse, orientatsiooni ja visuaalse kvaliteedi kontrollimine aitab kõigepealt valida õige järelkontrolli tööriista. + Avage pildi eelvaade, kui peate enne mitut pilti kontrollima, enne kui otsustate, mida nendega teha. + Otsige pöörlemisprobleeme, läbipaistvaid alasid, tihendusartefakte ja ootamatult suuri mõõtmeid. + Liikuge valikusse Suuruse muutmine, Kärbimine, Võrdlemine, EXIF ​​või Tausta eemaldaja alles siis, kui teate, mis vajab parandamist. + Tausta eemaldamine või taastamine + Kustutage taustad automaatselt või viimistlege servi käsitsi taastamistööriistade abil + Jätke teema, eemaldage ülejäänud + Tausta eemaldamine toimib kõige paremini, kui kombineerite automaatse valiku hoolika käsitsi puhastamisega. Lõplik ekspordivorming on sama oluline kui mask, sest JPEG tasandab läbipaistvad alad isegi siis, kui eelvaade tundub õige. + Alustage automaatse eemaldamisega, kui objekt on taustast selgelt eraldatud. + Juuste, nurkade, varjude ja väikeste detailide parandamiseks suumige sisse ja vahetage kustutamise ja taastamise vahel. + Kui vajate lõplikus failis läbipaistvust, salvestage see PNG- või WebP-vormingus. + Joonista, märgi üles ja vesimärk + Lisage nooli, märkmeid, esiletõsteid, allkirju või korduvaid vesimärkide ülekatteid + Lisage nähtav teave + Märgistustööriistad aitavad pilti selgitada, samas kui vesimärk aitab eksporditud faile kaubamärgiga tähistada või kaitsta. + Kasutage Draw\'i, kui vajate vabakäemärkmeid, kujundeid, esiletõstmist või kiireid märkusi. + Kasutage vesimärki, kui sama teksti- või pildimärk peaks väljunditel järjepidevalt ilmuma. + Enne eksportimist kontrollige läbipaistmatust, asendit ja skaalat, et märk oleks nähtav, kuid ei segaks tähelepanu. + Joonista vaikesätted + Kiiremaks märgistamiseks määrake joone laius, värv, teerežiim, orientatsioonilukk ja luup + Muutke joonistamine etteaimatavaks + Joonistamise sätted on abiks siis, kui teete ekraanipiltidele märkusi, märgite dokumente või allkirjastate pilte sageli. Vaikimisi lühendavad seadistusaega, samal ajal kui luup ja orientatsioonilukk muudavad täpsete muudatuste tegemise väikestel ekraanidel lihtsamaks. + Määrake joone vaikelaius ja joonistage värv väärtustele, mida kasutate enim noolte, esiletõstude või allkirjade jaoks. + Kasutage vaiketeerežiimi, kui joonistate tavaliselt samu jooni, kujundeid või märgistusi. + Lubage luup või orientatsioonilukk, kui sõrmega sisestamine muudab väikeste detailide täpse paigutamise raskeks. + Mitme pildiga paigutused + Enne ekraanipiltide, skaneeringute või võrdlusribade korraldamist valige õige mitme pildi tööriist + Kasutage õiget paigutustööriista + Need tööriistad kõlavad sarnaselt, kuid igaüks neist on häälestatud erinevat tüüpi mitme kujutise väljundi jaoks. Esmalt õige valimine väldib võitlust paigutuse juhtelementidega, mis on loodud teistsuguse tulemuse saavutamiseks. + Kasutage Collage Makerit kujundatud paigutuste jaoks, millel on mitu pilti ühes kompositsioonis. + Kasutage piltide ühendamist või virnastamine, kui pildid peaksid muutuma üheks pikaks vertikaalseks või horisontaalseks ribaks. + Kasutage Pildi jagamist, kui ühest suurest pildist peab saama mitu väiksemat tükki. + Teisendage pildivormingud + Looge JPEG, PNG, WebP, AVIF, JXL ja muid koopiaid ilma piksleid käsitsi muutmata + Valige töö vorming + Erinevad vormingud sobivad paremini fotode, ekraanipiltide, läbipaistvuse, tihendamise või ühilduvuse jaoks. Teisendamine on kõige turvalisem, kui mõistate, mida sihtrakendus toetab ja kas allikas sisaldab alfa-, animatsiooni- või metaandmeid, mida soovite säilitada. + Kasutage ühilduvate fotode jaoks JPEG-vormingut, kadudeta piltide ja alfafailide jaoks PNG-vormingut ning kompaktseks jagamiseks WebP-d. + Reguleerige kvaliteeti, kui vorming seda toetab; väiksemad väärtused vähendavad suurust, kuid võivad lisada artefakte. + Hoidke originaale seni, kuni kinnitate, et teisendatud failid on sihtrakenduses õigesti avatud. + Kontrollige EXIF-i ja privaatsust + Enne tundlike fotode jagamist vaadake, muutke või eemaldage metaandmeid + Peidetud pildiandmete juhtimine + EXIF võib sisaldada kaamera andmeid, kuupäevi, asukohta, orientatsiooni ja muid detaile, mida pildil ei näe. Tasub kontrollida enne avalikku jagamist, tugiaruandeid, turuplatsi kirjeid või mis tahes fotot, mis võib paljastada privaatse koha. + Avage EXIF-tööriistad enne fotode jagamist, mis võivad sisaldada asukohateavet või privaatse seadme teavet. + Eemaldage tundlikud väljad või muutke valesid silte, nagu kuupäev, suund, autor või kaamera andmed. + Kui privaatsus on oluline, salvestage uus koopia ja jagage seda originaali asemel. + Automaatne EXIF-puhastus + Eemaldage vaikimisi metaandmed, otsustades, kas kuupäevad tuleks säilitada + Muutke privaatsus vaikeseadeks + EXIF-i seadete rühm on kasulik, kui soovite, et privaatsuse puhastamine toimuks automaatselt, selle asemel et seda iga ekspordi puhul meeles pidada. Kuupäeva säilitamine Kellaaeg on eraldi otsus, kuna kuupäevad võivad olla kasulikud sortimisel, kuid tundlikud jagamisel. + Lubage alati selge EXIF, kui suurem osa teie ekspordist on mõeldud avalikuks või poolavalikuks jagamiseks. + Lubage Kuupäeva aja säilitamine ainult siis, kui jäädvustamisaja säilitamine on olulisem kui iga ajatempli eemaldamine. + Kasutage käsitsi EXIF-i redigeerimist ühekordsete failide jaoks, kui peate mõned väljad alles jätma ja teised eemaldama. + Kontrolli failinimesid + Kasutage eesliiteid, järelliiteid, ajatempleid, eelseadistusi, kontrollsummasid ja järjenumbreid + Muutke eksporditud tooted hõlpsasti leitavaks + Hea failinimemuster aitab sorteerida partiisid ja hoiab ära juhuslikud ülekirjutused. Failinime sätted on eriti kasulikud, kui ekspordid lähevad tagasi lähtekausta, kus sarnased nimed võivad kiiresti segadusse ajada. + Seadistage jaotises Seaded failinime eesliide, järelliide, ajatempel, algne nimi, eelseadistatud teave või järjestuse valikud. + Kasutage järjekorranumbreid partiide jaoks, kus järjestus on oluline, näiteks leheküljed, raamid või kollaažid. + Lubage ülekirjutamine ainult siis, kui olete kindel, et olemasolevate failide asendamine on praeguse kausta jaoks ohutu. + Failide ülekirjutamine + Asendage väljundid vastavate nimedega ainult siis, kui teie töövoog on tahtlikult hävitav + Kasutage ülekirjutamisrežiimi ettevaatlikult + Failide ülekirjutamine muudab nimekonfliktide käsitlemist. See on kasulik samasse kausta korduvaks eksportimiseks, kuid see võib asendada ka varasemaid tulemusi, kui teie failinime muster annab uuesti sama nime. + Luba ülekirjutamine ainult nende kaustade puhul, kus eeldatakse vanemate loodud failide asendamist ja mida saab taastada. + Originaalide, kliendifailide, ühekordsete skannimiste või muu ilma varukoopiata eksportimisel hoidke ülekirjutamine keelatud. + Kombineerige ülekirjutamine tahtliku failinime mustriga, nii et asendatakse ainult ettenähtud väljundid. + Failinime mustrid + Kombineerige algsed nimed, eesliited, järelliited, ajatemplid, eelseadistused, mastaabirežiim ja kontrollsummad + Koostage nimed, mis selgitavad väljundit + Failinime mustri seaded võivad muuta eksporditud failid nendega juhtunu kasulikuks ajalooks. See on partiidena oluline, sest kaustavaade võib olla ainus koht, kus saate eristada algset suurust, eelseadistust, vormingut ja töötlemisjärjekorda. + Kasutage algset failinime, eesliidet, järelliidet ja ajatemplit, kui vajate käsitsi sortimiseks loetavaid nimesid. + Lisage eelseadistus, mastaabirežiim, faili suurus või kontrollsumma teave, kui väljundid peavad dokumenteerima, kuidas need toodeti. + Vältige juhuslikke nimesid töövoogudele, kus failid peavad jääma seotud originaalide või lehtede järjestusega. + Valige, kuhu failid salvestatakse + Vältige eksportimise kaotamist, määrates kaustad, salvestades algse kausta ja salvestades ühekordsed salvestuskohad + Hoidke väljundid prognoositavad + Salvestuskäitumine on kõige olulisem, kui töötlete palju faile või jagate faile pilveteenuse pakkujatelt. Kausta sätted otsustavad, kas väljundid kogunevad ühte teadaolevasse kohta, kuvatakse originaalide kõrval või lähevad konkreetse ülesande jaoks ajutiselt mujale. + Määrake vaikesalvestuskaust, kui soovite eksportida alati ühes kohas. + Kasutage töövoogude puhastamiseks algkausta salvestamise kausta, kus väljund peaks jääma lähtefaili lähedale. + Kasutage ühekordset salvestuskohta, kui üks ülesanne vajab teist sihtkohta, muutmata vaikeseadet. + Ühekordsed salvestuskaustad + Saatke järgmised ekspordid ajutisse kausta tavalist sihtkohta muutmata + Hoidke eritööd eraldi + Ühekordne salvestamise asukoht on kasulik ajutiste projektide, kliendikaustade, puhastusseansside või eksportimise jaoks, mis ei tohiks segada teie tavalise Image Toolboxi kaustaga. See annab lühiajalise sihtkoha ilma vaikeseadet ümber kirjutamata. + Valige ühekordne kaust enne toimingu alustamist, mis asub väljaspool teie tavapärast ekspordikohta. + Kasutage seda lühikeste projektide, jagatud kaustade või partiide jaoks, kus väljundid peavad jääma koos rühmitatuks. + Naaske pärast tööd vaikekausta, et hilisem eksport ei satuks ootamatult ajutisse asukohta. + Tasakaalustage kvaliteet ja faili suurus + Saate aru, millal tuleb oletamise asemel muuta mõõtmeid, vormingut või kvaliteeti + Kahandage faile tahtlikult + Faili suurus sõltub pildi mõõtmetest, vormingust, kvaliteedist, läbipaistvusest ja sisu keerukusest. Kui fail on endiselt liiga raske, aitab mõõtmete muutmine tavaliselt rohkem kui kvaliteedi korduv langetamine. + Muutke kõigepealt mõõtmeid, kui pilt on suurem, kui sihtkoht suudab kuvada. + Madalam kvaliteet ainult kadudega vormingute puhul ja pärast eksportimist kontrollige teksti, servi, kaldeid ja külgi. + Vahetage vormingut, kui kvaliteedimuudatustest ei piisa, näiteks WebP jagamiseks või PNG selgete ja läbipaistvate varade jaoks. + Töötage animeeritud vormingutega + Kasutage GIF-, APNG-, WebP- ja JXL-tööriistu, kui failil on ühe bitmapi asemel raamid + Ärge käsitlege iga pilti liikumatuna + Animeeritud vormingud vajavad tööriistu, mis mõistavad kaadreid, kestust ja ühilduvust. + Kasutage GIF- või APNG-tööriistu, kui vajate nende vormingute jaoks kaadri tasemel toiminguid. + Kasutage WebP tööriistu, kui vajate kaasaegset tihendamist või animeeritud WebP väljundit. + Enne jagamist vaadake animatsiooni ajastust, kuna mõned platvormid teisendavad või lamedavad animeeritud pilte. + Võrrelge ja vaadake väljundit + Enne ekspordi jätkamist kontrollige muudatusi, faili suurust ja visuaalseid erinevusi + Enne jagamist kontrollige + Võrdlustööriistad aitavad varakult tabada tihendusartefakte, valed värvid või soovimatud muudatused. + Avage Võrdle, kui soovite kontrollida kahte versiooni kõrvuti. + Suumige servi, teksti, kaldeid ja läbipaistvaid piirkondi, kus probleemidest on kõige lihtsam mööda vaadata. + Kui väljund on liiga raske või udune, pöörduge tagasi eelmise tööriista juurde ja reguleerige kvaliteeti või vormingut. + Kasutage PDF-tööriistade jaoturit + Ühendage, tükeldage, pöörake, eemaldage lehti, tihendage, kaitske, OCR ja vesimärgiga PDF-faile + Valige PDF-toiming + PDF-jaotur koondab dokumenditoimingud ühe sisestuspunkti taha, et saaksite valida täpse toimingu. Alustage sealt, kui fail on juba PDF-fail, ja kasutage Pilte PDF-iks või Dokumendiskannerit, kui teie allikas on ikka piltide komplekt. + Avage PDF-tööriistad ja valige teie eesmärgile vastav toiming. + Valige lähte-PDF või pildid, seejärel vaadake üle lehtede järjekord, pööramine, kaitse või OCR-i suvandid. + Salvestage loodud PDF ja avage see üks kord, et kinnitada lehekülgede arvu, järjestust ja loetavust. + Muutke pildid PDF-iks + Ühendage skaneeringud, ekraanipildid, kviitungid või fotod üheks jagatavaks dokumendiks + Koostage puhas dokument + Piltidest saavad paremad PDF-lehed, kui neid järjepidevalt kärbitakse, järjestatakse ja suurust muudetakse. Käsitlege piltide loendit nagu lehtede virna: iga pööramine, veeris ja lehe suuruse valik mõjutab lõpliku dokumendi loetavust. + Kui lehe servad, varjud või suund on valed, kärpige või pöörake pilte esmalt. + Valige pildid ettenähtud lehtede järjekorras ja kohandage lehe suurust või veerisid, kui tööriist neid pakub. + Eksportige PDF-fail ja kontrollige enne saatmist, et kõik lehed oleksid loetavad. + Skannige dokumente + Jäädvustage paberlehti ja valmistage need ette PDF-i, OCR-i või jagamiseks + Jäädvustage loetavaid lehti + Dokumentide skannimine toimib kõige paremini lamedate lehtede, hea valgustuse ja korrigeeritud perspektiiviga. Puhas skannimine enne OCR-i või PDF-i eksportimist säästab aega, sest nii tekstituvastus kui ka tihendamine sõltuvad lehe kvaliteedist. + Asetage dokument kontrastsele pinnale, kus on piisavalt valgust ja minimaalseid varje. + Reguleerige tuvastatud nurki või kärpige käsitsi, et leht täidaks raami puhtalt. + Eksportige piltidena või PDF-vormingus ja seejärel käivitage OCR, kui vajate valitavat teksti. + Kaitske või avage PDF-faile + Kasutage paroole ettevaatlikult ja võimalusel säilitage redigeeritav lähtekoopia + Käsitsege PDF-i juurdepääsu turvaliselt + Kaitsetööriistad on kasulikud kontrollitud jagamiseks, kuid paroole on lihtne kaotada ja neid on raske taastada. Kaitske koopiaid levitamiseks, hoidke kaitsmata lähtefaile eraldi ja kontrollige tulemust enne millegi kustutamist. + Kaitske PDF-i koopiat, mitte teie ainsat lähtedokumenti. + Enne kaitstud faili jagamist hoidke parool kindlas kohas. + Kasutage avamist ainult nende failide puhul, mida teil on lubatud redigeerida, seejärel kontrollige, kas lukustamata koopia avaneb õigesti. + PDF-lehtede korraldamine + Ühendage, tükeldage, ümber korraldage, pöörake, kärpige, eemaldage lehti ja lisage tahtlikult leheküljenumbreid + Parandage dokumendi struktuur enne kaunistamist + PDF-i lehtede tööriistu on kõige lihtsam kasutada samas järjekorras nagu paberdokumendi ettevalmistamine: koguge lehed, eemaldage valed, pange need järjekorda, korrigeerige orientatsiooni ja lisage seejärel lõplikud üksikasjad, nagu leheküljenumbrid või vesimärgid. + Kui dokument on ikka veel mitmeks failiks jagatud, kasutage esmalt liitmist või piltide PDF-vormingut. + Enne leheküljenumbrite, allkirjade või vesimärkide lisamist saate lehti ümber korraldada, pöörata, kärpida või eemaldada. + Pärast struktuurimuudatusi vaadake lõplikku PDF-i eelvaadet, sest üht puuduvat või pööratud lehte võib olla hiljem raske märgata. + PDF-i annotatsioonid + Eemaldage märkused või tasandage need, kui vaatajad näitavad kommentaare ja märke erinevalt + Kontrollige, mis jääb nähtavaks + Märkused võivad olla kommentaarid, esiletõstmised, joonised, vormimärgid või muud täiendavad PDF-objektid. Mõned vaatajad kuvavad neid erinevalt, nii et nende eemaldamine või tasandamine võib muuta jagatud dokumendid prognoositavamaks. + Eemaldage märkused, kui jagatud koopiasse ei tohiks lisada kommentaare, esiletõstetud või arvustusmärke. + Tasandage, kui nähtavad märgid peaksid lehele jääma, kuid ei käitu enam redigeeritavate märkustena. + Hoidke originaalkoopia alles, sest märkuste eemaldamist või tasandamist võib olla raske tagasi pöörata. + Tunne teksti ära + Valitavate OCR-mootorite ja keelte abil saate piltidelt teksti eraldada + Saate paremaid OCR-i tulemusi + OCR-i täpsus sõltub pildi teravusest, keeleandmetest, kontrastist ja lehe orientatsioonist. Parimad tulemused saavutatakse tavaliselt siis, kui pildi esmalt ette valmistate: kärpige müra ära, pöörake püsti, suurendage loetavust ja seejärel tuvastage tekst. + Kärbi pilti tekstialaks ja pööra seda enne tuvastamist püsti. + Valige õige keel ja laadige alla keeleandmed, kui rakendus seda nõuab. + Kopeerige või jagage tuvastatud tekst, seejärel kontrollige käsitsi nimesid, numbreid ja kirjavahemärke. + Valige OCR-keele andmed + Parandage äratundmist, sobitades skripte, keeli ja allalaaditud OCR-mudeleid + Sobitage OCR tekstiga + Vale OCR-keelega võivad head fotod tunduda halva äratundmisena. Keeleandmed on olulised skriptide, kirjavahemärkide, numbrite ja segadokumentide puhul, seega valige telefoni kasutajaliidese keele asemel pildil kuvatav keel. + Valige pildil kuvatav keel või skript, mitte ainult telefoni keel. + Enne võrguühenduseta töötamist või partii töötlemist laadige alla puuduvad andmed. + Segakeelsete dokumentide puhul käivitage esmalt kõige olulisem keel ja kontrollige nimesid ja numbreid käsitsi. + Otsitavad PDF-id + Kirjutage OCR-tekst failidesse tagasi, et skannitud lehti saaks otsida ja kopeerida + Muutke skaneeringud otsitavateks dokumentideks + OCR saab teha enamat kui teksti lõikepuhvrisse kopeerida. Kui kirjutate tuvastatud teksti faili või otsitavasse PDF-i, jääb lehe kujutis nähtavaks, samas kui teksti on lihtsam otsida, valida, indekseerida või arhiivida. + Kasutage otsitavat PDF-väljundit skannitud dokumentide jaoks, mis peaksid siiski välja nägema nagu originaallehed. + Kasutage faili kirjutamist, kui vajate ainult ekstraheeritud teksti ja te ei hooli lehepildist. + Kontrollige olulisi numbreid, nimesid ja tabeleid käsitsi, sest OCR-tekst võib olla otsitav, kuid siiski ebatäiuslik. + QR-koodide skannimine + Lugege QR-sisu kaamera sisendist või salvestatud pilte, kui need on saadaval + Lugege koode turvaliselt + QR-koodid võivad sisaldada linke, WiFi-andmeid, kontakte, teksti või muid kasulikke andmeid. + Avage QR-koodi skannimine ja hoidke kood teravana, lamedana ja täielikult raami sees. + Enne linkide avamist või tundlike andmete kopeerimist vaadake dekodeeritud sisu üle. + Kui skannimine ebaõnnestub, parandage valgustust, kärpige koodi või proovige kasutada kõrgema eraldusvõimega lähtepilti. + Kasutage Base64 ja kontrollsummasid + Kodeerige pildid tekstina, dekodeerige tekst tagasi kujutisteks ja kontrollige faili terviklikkust + Failide ja teksti vahel liikumine + Base64 on kasulik väikeste piltide jaoks, samas kui kontrollsummad aitavad kinnitada, et failid ei muutunud. Need tööriistad on kõige kasulikumad tehnilistes töövoogudes, tugiaruannetes, failiedastustes ja kohtades, kus binaarfailid peavad ajutiselt tekstiks muutuma. + Kasutage Base64 tööriistu väikese pildi kodeerimiseks, kui töövoog vajab binaarfaili asemel teksti. + Dekodeerige Base64 ainult usaldusväärsetest allikatest ja kontrollige eelvaadet enne salvestamist. + Kasutage kontrollsumma tööriistu, kui teil on vaja võrrelda eksporditud faile või kinnitada üles-/allalaadimist. + Andmete pakkimine või kaitsmine + Kasutage failide koondamiseks või tekstiandmete teisendamiseks ZIP- ja šifreerimistööriistu + Hoidke seotud faile koos + Pakkimistööriistad on abiks siis, kui mitu väljundit peaks liikuma ühe failina. Need sobivad hästi ka loodud piltide, PDF-ide, logide ja metaandmete kooshoidmiseks, kui teil on vaja saata täielik tulemuste komplekt. + Kasutage ZIP-i, kui peate koos saatma palju eksporditud pilte või dokumente. + Kasutage šifritööriistu ainult siis, kui mõistate valitud meetodit ja suudate vajalikud võtmed turvaliselt salvestada. + Enne lähtefailide kustutamist testige loodud arhiivi või muudetud teksti. + Kontrollsummad nimedes + Kasutage kontrollsummal põhinevaid failinimesid, kui ainulaadsus ja terviklikkus on loetavusest olulisemad + Nimetage faile sisu järgi + Kontrollsumma failinimed on kasulikud, kui ekspordil võib olla dubleerivaid nähtavaid nimesid või kui peate tõestama, et kaks faili on identsed. Need on käsitsi sirvimiseks vähem sõbralikud, seega kasutage neid tehniliste arhiivide ja kontrollimise töövoogude jaoks. + Luba failinimena kontrollsumma, kui sisu identiteet on inimloetavast pealkirjast olulisem. + Kasutage seda arhiivide, dubleerimise, korratavate eksportimise või failide jaoks, mida saab üles laadida ja uuesti alla laadida. + Siduge kontrollsummade nimed kaustade või metaandmetega, kui inimesed peavad ikkagi aru saama, mida fail esindab. + Valige piltidelt värvid + Proovige piksleid, kopeerige värvikoode ja taaskasutage värve palettides või kujundustes + Proovige täpset värvi + Värvivalik on kasulik kujunduse sobitamiseks, brändi värvide eraldamiseks või pildiväärtuste kontrollimiseks. Suumimine on oluline, sest antialias, varjud ja tihendamine võivad muuta naaberpikslid veidi teistsuguseks, kui ootate. + Avage Vali pildist värv ja valige soovitud värviga lähtepilt. + Suumige enne väikeste detailide proovivõtmist sisse, et läheduses asuvad pikslid tulemust ei mõjutaks. + Kopeerige värv sihtkoha poolt nõutavas vormingus, näiteks HEX, RGB või HSV. + Looge palett ja kasutage värviteeki + Ekstraheerige paletid, sirvige salvestatud värve ja hoidke visuaalseid süsteeme järjepidevana + Ehitage korduvkasutatav palett + Paletid aitavad hoida eksporditud graafikat, vesimärke ja jooniseid visuaalselt ühtsetena. Need on eriti kasulikud, kui lisate korduvalt ekraanipilte, valmistate ette brändivarasid või vajate samu värve mitme tööriista jaoks. + Looge pildist palett või lisage värve käsitsi, kui väärtusi juba teate. + Nimetage või korraldage olulised värvid, et saaksite need hiljem uuesti üles leida. + Kasutage kopeeritud väärtusi Draw\'s, vesimärgis, gradiendis, SVG-s või välistes kujundustööriistades. + Loo gradiente + Looge taustade, kohahoidjate ja visuaalsete katsete jaoks gradientpilte + Kontrollige värvide üleminekuid + Gradiendi tööriistad on kasulikud, kui vajate olemasoleva redigeerimise asemel loodud pilti. Nendest võib saada puhas taust, kohahoidjad, taustapildid, maskid või lihtsad väliste kujundustööriistade varad. + Valige gradiendi tüüp ja määrake esmalt olulised värvid. + Reguleerige sihtkasutusjuhtumi suunda, peatusi, sujuvust ja väljundi suurust. + Ekspordi vormingus, mis vastab sihtkohale, tavaliselt PNG-vormingus puhta genereeritud graafika jaoks. + Värvi juurdepääsetavus + Kui kasutajaliidest on raske lugeda, kasutage dünaamilisi värve, kontrasti ja värvipimeduse valikuid + Muutke värvide kasutamine lihtsamaks + Värvisätted mõjutavad mugavust, loetavust ja juhtnuppude skannimise kiirust. Kui tööriistakiipe, liugureid või valitud olekuid on raske eristada, võivad teema ja juurdepääsetavuse valikud olla kasulikumad kui lihtsalt tumeda režiimi muutmine. + Proovige dünaamilisi värve, kui soovite, et rakendus järgiks praegust taustapildipaletti. + Suurendage kontrasti või muutke skeemi, kui nupud ja pinnad ei paista piisavalt silma. + Kasutage värvipimeda skeemi valikuid, kui mõnda olekut või aktsenti on raske eristada. + Alfa taust + Juhtige läbipaistva PNG, WebP ja sarnaste väljundite ümber kasutatavat eelvaate- või täitevärvi + Mõistke läbipaistvaid eelvaateid + Alfavormingute taustavärv võib muuta läbipaistvate piltide kontrollimise lihtsamaks, kuid oluline on teada, kas muudate eelvaate/täite käitumist või tasandate lõpptulemust. See on oluline kleebiste, logode ja taustalt eemaldatud piltide puhul. + Kui läbipaistvad pikslid peavad eksportima, kasutage esmalt alfa-toega vormingut, nagu PNG või WebP. + Reguleerige taustavärvi sätteid, kui läbipaistvaid servi on rakenduse pinna vastu raske näha. + Eksportige väike test ja avage see uuesti mõnes teises rakenduses, et kontrollida, kas läbipaistvus või valitud täide on salvestatud. + AI mudeli valik + Valige mudel pildi tüübi ja defekti järgi, selle asemel, et valida esmalt suurim + Sobitage mudel kahjustusega + AI-tööriistad saavad alla laadida või importida erinevaid ONNX/ORT-mudeleid ning iga mudelit õpetatakse konkreetset tüüpi probleemide jaoks. JPEG-plokkide, animerea puhastamise, müra vähendamise, tausta eemaldamise, värvimise või skaleerimise mudel võib vale pilditüübi puhul anda halvemaid tulemusi. + Enne allalaadimist lugege mudeli kirjeldust; valige mudel, mis nimetab teie tegelikku probleemi (nt tihendus, müra, pooltoonid, hägusus või tausta eemaldamine). + Alustage uue pilditüübi testimisel väiksema või spetsiifilisema mudeliga, seejärel võrrelge raskemate mudelitega ainult siis, kui tulemus pole piisavalt hea. + Hoidke alles ainult mudelid, mida te tõesti kasutate, sest allalaaditud ja imporditud mudelid võivad võtta märkimisväärselt salvestusruumi. + Mõistke mudelperekondi + Mudelite nimed vihjavad sageli nende sihtmärgile: RealESRGAN-stiilis mudelid kõrgetasemelised, SCUNeti ja FBCNN-i mudelid puhastavad müra või tihendust, taustmudelid loovad maske ja värvimisseadmed lisavad halltoonide sisendile värvi. Imporditud kohandatud mudelid võivad olla võimsad, kuid need peaksid vastama eeldatavale sisend-/väljundkujule ja kasutama toetatud ONNX- või ORT-vormingut. + Kasutage suurendajaid, kui vajate rohkem piksleid, müra summutajaid, kui pilt on teraline, ja artefaktide eemaldajaid, kui põhiprobleemiks on tihendusplokid või ribad. + Kasutage taustamudeleid ainult objektide eraldamiseks; need ei ole samad, mis üldine objektide eemaldamine või pildi täiustamine. + Importige kohandatud mudeleid ainult usaldusväärsetest allikatest ja testige neid enne oluliste failide kasutamist väikese pildi peal. + AI parameetrid + Kvaliteedi, kiiruse ja mälu tasakaalustamiseks häälestage tükkide suurust, kattumist ja paralleelseid töötajaid + Tasakaalusta kiirust stabiilsusega + Tüki suurus määrab, kui suured pilditükid on enne, kui mudel neid töötleb. Kattuvus segab naabertükke, et peita piire. Paralleelsed töötajad võivad töötlemist kiirendada, kuid iga töötaja vajab mälu, nii et suured väärtused võivad muuta suured pildid piiratud RAM-iga telefonides ebastabiilseks. + Kasutage suuremaid tükke sujuvamaks kontekstiks, kui teie seade käsitleb mudelit usaldusväärselt ja pilt pole tohutu. + Suurendage kattumist, kui tükkide piirid muutuvad nähtavaks, kuid pidage meeles, et suurem kattumine tähendab rohkem korduvat tööd. + Paralleelseid töötajaid tõsta alles pärast stabiilset katset; kui töötlemine aeglustub, seade kuumeneb või jookseb kokku, vähendage esmalt töötajaid. + Vältige tükkide artefakte + Tükeldamine võimaldab rakendusel töödelda pilte, mis on suuremad, kui mudel mugavalt korraga hakkama saab. Kompromiss seisneb selles, et igal paanil on vähem ümbritsevat konteksti, nii et väike kattuvus või liiga väikesed tükid võivad tekitada naaberalade vahel nähtavaid piire, tekstuurimuutusi või ebaühtlaseid detaile. + Suurendage kattumist, kui näete ristkülikukujulisi ääriseid, korduvaid tekstuurimuutusi või detailide hüppeid töödeldud piirkondade vahel. + Kui protsess ebaõnnestub enne väljundi loomist, vähendage tükkide suurust, eriti suurte piltide või raskete mudelite puhul. + Salvestage enne täispildi töötlemist väike kärpimistesti, et saaksite parameetreid kiiresti võrrelda. + AI stabiilsus + Väiksem tükki suurus, töötajad ja allika eraldusvõime, kui tehisintellekti töötlemine jookseb kokku või hangub + Toibuge rasketest tehisintellekti töödest + AI töötlemine on rakenduse üks raskemaid töövooge. Kokkujooksmised, ebaõnnestunud seansid, hangumised või rakenduse äkilised taaskäivitused tähendavad tavaliselt, et mudel, allika eraldusvõime, tüki suurus või paralleelsete töötajate arv on seadme praeguse oleku jaoks liiga nõudlik. + Kui rakendus jookseb kokku või ei suuda mudeliseanssi avada, vähendage paralleeltöötajaid ja tüki suurust, seejärel proovige sama pilti uuesti. + Muutke väga suurte piltide suurust enne tehisintellekti töötlemist, kui eesmärk ei nõua algset eraldusvõimet. + Sulgege teised rasked rakendused, kasutage väiksemat mudelit või töötlege korraga vähem faile, kui mälumaht taastub. + Mudeli tõrgete diagnoosimine + Ebaõnnestunud tehisintellekt ei tähenda alati, et pilt on halb. Mudelifail võib olla mittetäielik, toetamata, mälu jaoks liiga suur või toiminguga mitteühilduv. Kui rakendus teatab ebaõnnestunud seansist, käsitlege seda signaalina, et kõigepealt lihtsustada mudelit ja parameetreid. + Kustutage mudel ja laadige see uuesti alla, kui see ebaõnnestub kohe pärast allalaadimist või käitub nii, nagu oleks fail rikutud. + Enne imporditud kohandatud mudeli süüdistamist proovige sisseehitatud allalaaditavat mudelit, kuna imporditud mudelitel võivad olla ühildumatud sisendid. + Kasutage pärast tõrke taasesitamist rakenduste logisid, kui peate teatama krahhist või seansi veast. + Katsetage Shader Studios + Kasutage täiustatud pilditöötluse ja kohandatud välimuse jaoks GPU varjutusefekte + Töötage varjunditega ettevaatlikult + Shader Studio on võimas, kuid Shaderi kood ja parameetrid vajavad väikseid testitavaid muudatusi. Käsitlege seda nagu eksperimentaalset tööriista: hoidke töötavat baasjoont, muutke ühte asja korraga ja testige enne eelseadistuse usaldamist erinevate pildisuurustega. + Enne parameetrite lisamist alustage tuntud töötavast varjundist või lihtsast efektist. + Muutke korraga ühte parameetrit või koodiplokki ja kontrollige eelvaadet vigade suhtes. + Eksportige eelseadistused alles pärast nende katsetamist mõne erineva pildisuurusega. + Tehke SVG või ASCII kujundus + Looge loominguliseks väljundiks vektoritaolisi varasid või tekstipõhiseid pilte + Valige reklaami väljundi tüüp + SVG ja ASCII tööriistad teenindavad erinevaid eesmärke: skaleeritav graafika või tekstistiilis renderdus. Mõlemad on loomingulised konversioonid, seega on lõpliku suuruse eelvaate vaatamine olulisem kui tulemuse otsustamine väikese pisipildi järgi. + Kasutage SVG Makerit, kui tulemus peaks skaleerima puhtalt või seda tuleks uuesti kasutada disaini töövoogudes. + Kasutage ASCII kunsti, kui soovite pildile stiliseeritud teksti. + Eelvaade lõplikus suuruses, sest väikesed detailid võivad genereeritud väljundis kaduda. + Looge müratekstuure + Looge taustade, maskide, ülekatete või katsete jaoks protseduurilisi tekstuure + Häälestage protseduuriline väljund + Müra tekitamine loob parameetritest uusi pilte, nii et väikesed muudatused võivad anda väga erinevaid tulemusi. See on kasulik tekstuuride, maskide, ülekatete, kohahoidjate jaoks või üksikasjalike mustritega tihendamise testimiseks. + Valige enne peente detailide häälestamist müra tüüp ja väljundi suurus. + Reguleerige skaalat, intensiivsust, värve või seemet, kuni muster sobib sihtotstarbega. + Salvestage kasulikud tulemused ja kirjutage parameetrid üles, kui peate tekstuuri hiljem uuesti looma. + Märgistusprojektid + Kasutage projektifaile, kui märkused peavad pärast rakenduse sulgemist olema muudetavad + Hoidke kihilised muudatused korduvkasutatavad + Märgistusprojekti failid on kasulikud, kui ekraanipilti, diagrammi või juhiste kujutist võib hiljem vaja muuta. Eksporditud PNG/JPEG-failid on lõplikud pildid, samas kui projektifailid säilitavad redigeeritavad kihid ja redigeerimisoleku edaspidiseks kohandamiseks. + Kasutage märgistuskihte, kui märkused, tähelepanulaiendid või paigutuselemendid peaksid jääma redigeeritavaks. + Kui ootate rohkem muudatusi, salvestage või avage projektifail uuesti enne lõpliku pildi eksportimist. + Eksportige tavaline pilt ainult siis, kui olete valmis lamestatud lõplikku versiooni jagama. + Krüpteeri failid + Kaitske faile parooliga ja testige dekrüpteerimist enne lähtekoopia kustutamist + Käsitsege krüptitud väljundeid ettevaatlikult + Šifreerimistööriistad võivad faile kaitsta paroolipõhise krüptimisega, kuid need ei asenda võtme meeldejätmist ega varukoopiate hoidmist. Krüpteerimine on kasulik transpordiks ja ladustamiseks, samas kui ZIP on parem, kui peamine eesmärk on mitme väljundi komplekteerimine. + Kõigepealt krüptige koopia ja hoidke originaali alles, kuni olete kontrollinud, kas dekrüpteerimine teie salvestatud parooliga töötab. + Kasutage võtme jaoks paroolihaldurit või muud turvalist kohta; rakendus ei saa teie jaoks kadunud parooli ära arvata. + Jagage krüptitud faile ainult inimestega, kellel on ka parool ja kes teavad, milline tööriist või meetod peaks need dekrüpteerima. + Korraldage tööriistad + Rühmitage, tellige, otsige ja kinnitage tööriistu, et põhiekraan sobiks teie tegeliku töövooga + Muutke põhiekraan kiiremaks + Tööriistade paigutuse sätteid tasub häälestada, kui teate, milliseid tööriistu te kõige rohkem kasutate. Rühmitamine aitab uurida, kohandatud järjestus aitab lihasmälu ja lemmikud hoiavad igapäevased tööriistad kättesaadavana isegi siis, kui loend muutub suureks. + Kasutage rühmitatud režiimi rakenduse õppimisel või kui eelistate ülesande tüübi järgi korraldatud tööriistu. + Lülituge kohandatud järjestusele, kui tunnete juba kasutatavaid tööriistu ja soovite vähem kerimisi. + Kasutage koos otsinguseadeid ja lemmikuid haruldaste tööriistade jaoks, mida mäletate nime järgi, kuid mida ei soovi kogu aeg näha. + Käsitsege suuri faile + Vältige aeglast eksporti, mälu survet ja ootamatult suurt väljundit + Vähendage tööd enne eksporti + Väga suured pildid, pikad partiid ja kvaliteetsed vormingud võivad võtta rohkem mälu ja aega. Kiireim lahendus on tavaliselt töömahu vähendamine enne raskeimat toimingut, mitte oodata sama suure sisendi lõpetamist. + Muutke suurte piltide suurust enne raskete filtrite, OCR-i või PDF-i teisendamist. + Kasutage seadete kinnitamiseks esmalt väiksemat partiid, seejärel töötlege ülejäänud sama eelseadega. + Madalam kvaliteet või muutke vormingut, kui väljundfail on oodatust suurem. + Vahemälu ja eelvaated + Mõistke loodud eelvaateid, vahemälu puhastamist ja salvestusruumi kasutamist pärast raskeid seansse + Hoidke salvestusruum ja kiirus tasakaalus + Eelvaate loomine ja vahemälu võivad korduvat sirvimist kiirendada, kuid rasked redigeerimisseansid võivad jätta ajutisi andmeid maha. Vahemälu seaded aitavad, kui rakendus tundub aeglane, salvestusruum on kitsas või kui eelvaated näevad pärast paljusid katseid vananenud. + Eelvaadete lubamine paljude piltide sirvimisel on olulisem kui ajutise salvestusruumi minimeerimine. + Tühjendage vahemälu pärast suuri partiisid, ebaõnnestunud katseid või pikki seansse paljude kõrge eraldusvõimega failidega. + Kasutage automaatset vahemälu tühjendamist, kui eelistate salvestusruumi puhastamist eelvaadete soojana hoidmisele käivitamiste vahel. + Reguleerige ekraani käitumist + Kasutage keskendunud tööks täisekraani, turvarežiimi, heleduse ja süsteemiriba valikuid + Muutke liides olukorraga sobivaks + Ekraaniseaded aitavad horisontaalasendis redigeerimisel, privaatsete piltide esitamisel või ühtlast heledust vajades. Neid pole tavakasutuseks vaja, kuid need lahendavad ebamugavaid olukordi, nagu QR-kontroll, privaatsed eelvaated või kitsas maastikutöötlus. + Kasutage täisekraani või süsteemiriba sätteid, kui ruumi redigeerimine on navigeerimiskroomist olulisem. + Kasutage turvarežiimi, kui privaatseid pilte ei tohiks kuvatõmmistes ega rakenduste eelvaadetes kuvada. + Kasutage heleduse jõustamist tööriistade puhul, kus tumedaid pilte või QR-koode on raske kontrollida. + Säilitage läbipaistvus + Vältige läbipaistva tausta valgeks, mustaks või tasaseks muutumist + Kasutage alfa-teadlikku väljundit + Läbipaistvus säilib ainult siis, kui valitud tööriist ja väljundvorming toetavad alfat. Kui eksporditud taust muutub valgeks või mustaks, on probleem tavaliselt väljundvormingus, täite-/taustasättes või sihtkoha rakenduses, mis pildi tasandas. + Valige taustalt eemaldatud piltide, kleebiste, logode või ülekatete eksportimisel PNG või WebP. + Vältige läbipaistva väljundi jaoks JPEG-vormingut, kuna see ei salvesta alfakanalit. + Kontrollige alfavormingute taustavärviga seotud sätteid, kui eelvaade näitab täidetud tausta. + Parandage jagamise ja impordi probleemid + Kasutage valija seadeid ja toetatud vorminguid, kui failid ei kuvata või avanevad õigesti + Hankige fail rakendusse + Impordiprobleemid on sageli põhjustatud pakkuja lubadest, toetamata vormingutest või valija käitumisest. Pilverakendustest ja sõnumitootest jagatud failid võivad olla ajutised, seega võib püsiva allika valimine olla pikemate muudatuste jaoks usaldusväärsem. + Proovige faili hiljutiste failide otsetee asemel avada süsteemivalija kaudu. + Kui üks tööriist vormingut ei toeta, teisendage see esmalt või avage mõni spetsiifilisem tööriist. + Vaadake üle pildivalija ja käivitusprogrammi seaded, kui jagamisvood või otsetööriistade avamine käituvad oodatust erinevalt. + Väljumise kinnitus + Vältige salvestamata muudatuste kaotamist, kui liigutused, tagasivajutused või tööriistade vahetamine juhtuvad kogemata + Kaitske tegemata tööd + Tööriistast väljumise kinnitus on kasulik, kui kasutate liigutustega navigeerimist, redigeerite suuri faile või vahetate sageli rakenduste vahel. See lisab väikese pausi enne tööriistast lahkumist, et lõpetamata sätted ja eelvaated kogemata kaduma ei läheks. + Lubage kinnitus, kui juhuslikud tagasiliigutused on kunagi enne eksportimist tööriista sulgenud. + Hoidke see keelatud, kui teete enamasti kiireid üheastmelisi konversioone ja eelistate kiiremat navigeerimist. + Kasutage seda koos eelseadetega pikemate töövoogude jaoks, kus seadete taastamine oleks tüütu. + Kasutage probleemidest teatamisel logisid + Enne probleemi avamist või arendajaga ühenduse võtmist koguge kasulikke üksikasju + Teatage kontekstiga seotud probleemidest + Selget aruannet koos sammude, rakenduse versiooni, sisenditüübi ja logidega on palju lihtsam diagnoosida. Logid on kõige kasulikumad kohe pärast probleemi taasesitamist, kuna need hoiavad ümbritseva konteksti värskena. + Märkige üles tööriista nimi, täpne toiming ja see, kas see toimub ühe faili või iga failiga. + Pärast probleemi taasesitamist avage rakenduste logid ja jagage võimaluse korral asjakohast logiväljundit. + Manustage ekraanipilte või näidisfaile ainult siis, kui need ei sisalda privaatset teavet. + Taustatöötlus peatati + Android ei lasknud taustatöötluse teatisel õigel ajal alata. See on süsteemi ajastuse piirang, mida ei saa usaldusväärselt parandada. Taaskäivitage rakendus ja proovige uuesti; rakenduse avatuna hoidmine ja märguannete lubamine või taustatöö võib aidata. + Jätke faili valimine vahele + Avage tööriistad kiiremini, kui sisend pärineb tavaliselt jagamiselt, lõikelaualt või eelmiselt ekraanilt + Muutke tööriista korduvad käivitamised kiiremaks + Faili valimise vahelejätmine muudab toetatud tööriistade esimest sammu. See on kasulik, kui sisestate tööriista tavaliselt juba valitud pildiga või Androidi jagamistoimingutest, kuid see võib tekitada segadust, kui eeldate, et iga tööriist küsib kohe faili. + Lubage see ainult siis, kui teie tavaline voog pakub juba enne tööriista avamist lähtepildi. + Hoidke see rakenduse õppimise ajal keelatud, sest selge valik muudab iga tööriista voogu hõlpsamini mõistetavaks. + Kui tööriist avaneb ilma ilmse sisendita, keelake see seade või alustage jaotisest Jaga/Ava koos, et Android edastaks faili esimesena. + Avage esmalt redaktor + Jäta eelvaade vahele, kui jagatud pilt peaks kohe redigeerimisele minema + Valige esimeseks peatuseks eelvaade või muutmine + Ava eelvaate asemel redigeerimine on mõeldud kasutajatele, kes juba teavad, et soovivad pilti muuta. Eelvaade on turvalisem, kui kontrollite faile vestlusest või pilvesalvestusest; otseredigeerimine on kiirem ekraanipiltide, kiirete kärpimiste, märkuste ja ühekordsete paranduste puhul. + Lubage otseredigeerimine, kui enamik jagatud pilte vajab kohe kärpimist, märgistamist, filtreid või eksportimist. + Jätke esmalt eelvaade, kui kontrollite sageli enne otsustamist metaandmeid, mõõtmeid, läbipaistvust või pildikvaliteeti. + Kasutage seda koos lemmikutega, et jagatud pildid jõuaksid tegelikult kasutatavate tööriistade lähedale. + Eelseadistatud tekstisisestus + Juhtnuppude korduva reguleerimise asemel sisestage täpsed eelseadistatud väärtused + Kasutage täpseid väärtusi, kui liugurid on aeglased + Eelseadistatud tekstisisestus aitab, kui vajate korduva töö jaoks täpseid numbreid: sotsiaalsete piltide suurused, dokumendi veerised, tihendamise sihtmärgid, joonte laiused või muud väärtused, milleni jõudmine on tüütu žestidega. See on kasulik ka väikestel ekraanidel, kus liuguri peen liikumine on raskem. + Lubage tekstisisestus, kui kasutate sageli täpseid suurusi, protsente, kvaliteediväärtusi või tööriista parameetreid. + Salvestage väärtus eelseadistusena pärast selle ühekordset tippimist ja seejärel kasutage seda sama seadistuse taastamise asemel uuesti. + Hoidke liigutuste juhtnuppe visuaalse jämeda häälestamiseks ja sisestatud väärtusi rangete väljundnõuete jaoks. + Salvesta originaali lähedale + Kui kausta kontekst on oluline, asetage loodud failid lähtepiltide kõrvale + Hoidke väljundid koos nende allikatega + Salvesta originaalkausta on mugav kaamera kaustade, projektikaustade, dokumentide skannimiseks ja kliendipakkide jaoks, kus redigeeritud koopia peaks jääma allika kõrvale. See võib olla vähem korras kui spetsiaalne väljundkaust, nii et ühendage see selgete failinimede järelliidete või mustritega. + Lubage see, kui iga lähtekaust on juba tähendusrikas ja väljundid peaksid jääma sinna rühmitatuks. + Kasutage failinimede järelliiteid, eesliiteid, ajatempleid või mustreid, et redigeeritud koopiaid oleks lihtne originaalidest eraldada. + Keelake see segapakettide jaoks, kui soovite, et kõik loodud failid kogutaks ühte ekspordikausta. + Jäta suurem väljund vahele + Vältige konversioonide salvestamist, mis muutuvad ootamatult allikast raskemaks + Kaitske partiisid halva suurusega üllatuste eest + Mõned teisendused muudavad failid suuremaks, eriti PNG-ekraanipildid, juba tihendatud fotod, kvaliteetne WebP või metaandmetega pildid. Luba vahelejätmine, kui suurem takistab neid väljundeid asendamast väiksemat allikat töövoogudes, mille peamiseks eesmärgiks on suuruse vähendamine. + Lubage see, kui eksport on mõeldud failide tihendamiseks, suuruse muutmiseks või üleslaadimispiirangute jaoks ettevalmistamiseks. + Vaata vahele jäetud failid pärast partii; need võivad olla juba optimeeritud või vajada teistsugust vormingut. + Keelake see, kui kvaliteet, läbipaistvus või vormingu ühilduvus on olulisem kui faili lõplik suurus. + Lõikelaud ja lingid + Kiiremate tekstipõhiste tööriistade jaoks saate juhtida lõikelaua automaatset sisestust ja linkide eelvaateid + Muutke automaatne sisestus ennustatavaks + Lõikepuhvri ja lingi seaded on mugavad QR-, Base64-, URL-i ja tekstirohkete töövoogude jaoks, kuid automaatne sisestus peaks jääma tahtlikuks. Kui näib, et rakendus täidab välju ise, kontrollige lõikelauale kleepimise käitumist; kui lingikaardid tunduvad mürarikkad või aeglased, kohandage lingi eelvaate käitumist. + Lubage automaatne lõikelauale kleepimine, kui kopeerite sageli teksti ja avate kohe sobitamistööriista. + Keelake automaatne kleepimine, kui privaatne lõikelaua sisu kuvatakse tööriistades, kus te seda ei oodanud. + Lülitage linkide eelvaated välja, kui eelvaate toomine on aeglane, häirib tähelepanu või pole teie töövoo jaoks vajalik. + Kompaktsed juhtnupud + Kasutage tihedamaid valijaid ja kiireid seadete paigutust, kui ekraaniruumi on vähe + Paigaldage väikestele ekraanidele rohkem juhtnuppe + Kompaktsed valijad ja kiire seadete paigutus aitavad väikestel telefonidel, maastiku redigeerimisel ja paljude parameetritega tööriistadel. Kompromiss seisneb selles, et juhtnupud võivad tunduda tihedamad, nii et see on kõige parem pärast seda, kui olete levinud valikud juba ära tundnud. + Lubage kompaktsed valijad, kui dialoogid või valikute loendid tunduvad ekraani jaoks liiga kõrged. + Reguleerige kiirseadete poolt, kui tööriista juhtnuppe on ekraani ühelt küljelt lihtsam kätte saada. + Naaske ruumikamate juhtnuppude juurde, kui alles õpite rakendust või puudutate sageli tihedaid valikuid. + Laadige pilte veebist + Kleepige lehe või pildi URL, vaadake sõelutud pilte ja seejärel salvestage või jagage valitud tulemusi + Kasutage veebipilte teadlikult + Veebikujutise laadimine analüüsib pildiallikaid esitatud URL-ist, kuvab esimese saadaoleva pildi eelvaate ja võimaldab salvestada või jagada valitud sõelutud pilte. Mõned saidid kasutavad ajutisi, kaitstud või skriptiga loodud linke, nii et brauseris töötav leht ei pruugi siiski kasutada kasutatavaid pilte tagastada. + Kleepige pildi otsene URL või lehe URL ja oodake, kuni kuvatakse sõelutud piltide loend. + Kasutage raami või valiku juhtelemente, kui leht sisaldab mitut pilti ja vajate neist ainult mõnda. + Kui midagi ei laadita, proovige esmalt otsest pildilinki või laadige alla brauseri kaudu; sait võib parsimise blokeerida või kasutada privaatseid linke. + Valige suuruse muutmise tüüp + Enne pildi uute mõõtmete muutmist mõistke selgesõnalist, paindlikku, kärpimist ja sobitavust + Hallake kuju, lõuendit ja kuvasuhet + Suuruse muutmise tüüp otsustab, mis juhtub, kui soovitud laius ja kõrgus ei vasta algsele kuvasuhtele. See on erinevus pikslite venitamise, proportsioonide säilitamise, lisaala kärpimise või taustalõuendi lisamise vahel. + Kasutage selgesõnalist ainult siis, kui moonutus on vastuvõetav või allikal on juba sihtmärgiga sama kuvasuhe. + Kasutage funktsiooni Flexible proportsioonide säilitamiseks, muutes suurust olulisest küljest, eriti fotode ja segapartiide puhul. + Kasutage kärpimist rangete ääristeta raamide jaoks või Fit, kui kogu pilt peab jääma fikseeritud lõuendil nähtavaks. + Valige pildi skaala režiim + Valige uuesti proovivõtufilter, mis kontrollib teravust, sujuvust, kiirust ja servade artefakte + Muutke suurust ilma juhusliku pehmuseta + Kujutise skaala režiim juhib uute pikslite arvutamist suuruse muutmise ajal. Lihtsad režiimid on kiired ja etteaimatavad, samas kui täiustatud filtrid suudavad detaile paremini säilitada, kuid võivad teravdada servi, luua halosid või võtta suurte piltide jaoks kauem aega. + Kasutage Basic või Bilinear kiireks igapäevaseks suuruse muutmiseks, kui tulemus peab olema väiksem ja puhas. + Kasutage Lanczose, Robidoux\', Spline\'i või EWA stiilis režiime, kui peened detailid, tekst või kvaliteetne skaleerimine on olulised. + Kasutage suvandit Lähim pikslipildi, ikoonide ja teravate servadega graafika jaoks, kus udused pikslid rikuvad välimust. + Värviruumi skaala + Otsustage, kuidas värve segatakse, samal ajal kui piksleid suuruse muutmise ajal uuesti valitakse + Hoidke kalded ja servad loomulikud + Skaala värviruum muudab suuruse muutmisel kasutatavat matemaatikat. Enamik pilte on vaikeväärtusega korras, kuid lineaarsed või tajutavad ruumid võivad muuta gradiendid, tumedad servad ja küllastunud värvid pärast tugevat skaleerimist puhtamaks. + Jätke vaikeseade, kui kiirus ja ühilduvus on olulisemad kui väikesed värvierinevused. + Proovige lineaarset või tajurežiimi, kui tumedad piirjooned, kasutajaliidese ekraanipildid, gradiendid või värviribad näevad pärast suuruse muutmist valed välja. + Võrrelge enne ja pärast tegelikul sihtekraanil, sest värviruumi muutused võivad olla peened ja sisust sõltuvad. + Piira suuruse muutmise režiime + Tea, millal tuleks ülepiiranguga pilte vahele jätta, ümber kodeerida või mahutamiseks vähendada + Valige, mis juhtub piiranguga + Limit Resize ei ole ainult väiksemate piltide tööriist. Selle režiim otsustab, mida rakendus teeb, kui allikas on juba teie piirides või kui ainult üks pool rikub reeglit, mis on oluline segakaustade ja üleslaadimise ettevalmistamise jaoks. + Kasutage valikut Jäta vahele, kui piirangute piires olevad pildid tuleks puutumata jätta ja neid uuesti eksportida. + Kasutage Recode\'i, kui mõõtmed on vastuvõetavad, kuid vajate siiski uut vormingut, metaandmete käitumist, kvaliteeti või failinime. + Kasutage suumi, kui piire ületavaid pilte tuleks proportsionaalselt vähendada, kuni need mahuvad lubatud kasti. + Kuvasuhte sihtmärgid + Vältige venitatud nägusid, äralõigatud servi ja ootamatuid ääriseid rangete väljundsuuruste puhul + Enne suuruse muutmist sobitage kuju + Laius ja kõrgus on vaid pool suuruse muutmise sihtmärgist. Kuvasuhe määrab, kas pilt mahub loomulikult ära, vajab kärpimist või vajab selle ümber lõuendit. Kuju esmane kontrollimine hoiab ära kõige üllatavamad suuruse muutmise tulemused. + Enne kui valite Selgesõnaline, Kärbi või Fit, võrrelge lähtekuju sihtkujuga. + Kärpige esmalt, kui objekt võib kaotada täiendava tausta ja sihtkoht vajab ranget raami. + Kasutage Fit või Flexible, kui originaalpildi kõik osad peavad jääma nähtavaks. + Suurepärane või tavaline suurus + Tea, millal pikslite lisamine vajab AI-d ja millal piisab lihtsast skaleerimisest + Ärge ajage suurust segamini detailidega + Tavaline suuruse muutmine muudab pikslite interpoleerimise teel mõõtmeid; see ei suuda välja mõelda tõelisi detaile. Kõrgetasemeline tehisintellekt võib luua teravama välimusega detaile, kuid see on aeglasem ja võib tekstuuri hallutsineerida, seega sõltub õige valik sellest, kas vajate ühilduvust, kiirust või visuaalset taastamist. + Kasutage pisipiltide, üleslaadimispiirangute, ekraanipiltide ja lihtsate mõõtmete muutmiseks tavalist suurust. + Kasutage tehisintellekti kõrgemat skaalat, kui väike või pehme pilt peab suuremas suuruses parem välja nägema. + Võrrelge nägusid, teksti ja mustreid pärast tehisintellekti täiustamist, sest loodud detailid võivad tunduda veenvad, kuid valed. + Filtri järjekord on oluline + Rakendage tahtlikus järjestuses puhastamist, värvi, teravust ja lõplikku tihendamist + Ehitage puhtam filtrivirn + Filtrid ei ole alati vahetatavad. Hägusus enne teritamist, kontrasti suurendamine enne OCR-i või värvimuutus enne tihendamist võivad anda samade juhtnuppude abil väga erineva väljundi. + Kärpige ja pöörake kõigepealt, nii et hiljem töötavad filtrid ainult allesjäävate pikslite puhul. + Enne teravustamist või stiliseeritud efekte rakendage säritust, valge tasakaalu ja kontrasti. + Hoidke lõplik suurus ja tihendamine lõpu lähedal, et eelvaatelised üksikasjad säiliksid prognoositavalt ekspordina. + Parandage tausta servad + Pärast automaatset eemaldamist puhastage halod, ülejäänud varjud, juuksed, augud ja pehmed piirjooned + Muutke väljalõiked tahtlikuks + Automaatsed maskid näevad sageli hea välja, kuid paljastavad probleemid heledal, tumedal või mustrilisel taustal. Servade puhastamine on erinevus kiire väljalõike ja korduvkasutatava kleebise, logo või tootepildi vahel. + Vaadake tulemust kontrastse tausta taustal, et paljastada halod ja puuduvad servadetailid. + Enne ümbritsevate jääkide kustutamist kasutage juuste, nurkade ja läbipaistvate objektide taastamist. + Kui väljalõige kujundusse paigutatakse, eksportige lõpliku taustavärvi väike test. + Loetavad vesimärgid + Muutke märgid nähtavaks eredatel, tumedatel, hõivatud ja kärbitud piltidel ilma fotot rikkumata + Kaitske pilti ilma seda peitmata + Vesimärk, mis ühel pildil hea välja näeb, võib teisel kaduda. Suurus, läbipaistmatus, kontrastsus, paigutus ja kordus tuleks valida kogu partii jaoks, mitte ainult esimese eelvaate jaoks. + Enne kõigi failide eksportimist testige partii kõige heledamate ja tumedamate piltide märgistust. + Kasutage suurte korduvate märkide jaoks väiksemat läbipaistmatust ja väikeste nurgajälgede jaoks tugevamat kontrasti. + Hoidke olulised näod, tekst ja toote üksikasjad selged, välja arvatud juhul, kui märgise eesmärk on takistada taaskasutamist. + Jagage üks pilt plaatideks + Lõika üks pilt ridade, veergude, kohandatud protsentide, vormingu ja kvaliteedi järgi + Valmistage ette võred ja tellitud tükid + Pildi jagamine loob ühest lähtepildist mitu väljundit. Seda saab jagada ridade ja veergude arvu järgi, kohandada ridade või veergude protsente ning salvestada valitud väljundvormingu ja kvaliteediga osi, mis on kasulikud ruudustikute, lehelõikude, panoraamide ja järjestatud suhtluspostituste jaoks. + Valige lähtekujutis ja seejärel määrake vajalike ridade ja veergude arv. + Reguleerige ridade või veergude protsente, kui kõik tükid ei peaks olema võrdse suurusega. + Valige enne salvestamist väljundvorming ja kvaliteet, et iga loodud tükk kasutaks samu ekspordireegleid. + Lõika välja pildiribad + Eemaldage vertikaalsed või horisontaalsed piirkonnad ja ühendage ülejäänud osad uuesti kokku + Eemaldage piirkond tühikut jätmata + Pildi lõikamine erineb tavalisest kärpimisest. See võib eemaldada valitud vertikaalse või horisontaalse riba ja seejärel ülejäänud pildiosad kokku liita. Pöördrežiim säilitab selle asemel valitud piirkonna ja mõlema pöördsuuna kasutamisel säilib valitud ristkülik. + Kasutage vertikaalset lõikamist külgmiste piirkondade, veergude või keskmiste ribade jaoks; kasutage bännerite, tühikute või ridade jaoks horisontaalset lõikamist. + Kui soovite valitud osa eemaldada, mitte jätta, lubage teljel pöördväärtus. + Pärast lõikamist vaadake servade eelvaadet, sest liidetud sisu võib tunduda äkiline, kui eemaldatud ala ristub oluliste detailidega. + Valige väljundvorming + Valige sisu ja sihtkoha põhjal JPEG, PNG, WebP, AVIF, JXL või PDF + Laske sihtkohal vormingu üle otsustada + Parim vorming sõltub sellest, mida pilt sisaldab ja kus seda kasutatakse. Fotod, ekraanipildid, läbipaistvad varad, dokumendid ja animeeritud failid ebaõnnestuvad erineval viisil, kui need sunnitakse valesse vormingusse. + Kasutage JPEG-vormingut laialdase fotode ühilduvuse tagamiseks, kui läbipaistvust ja täpseid piksleid pole vaja. + Kasutage PNG-d teravate ekraanipiltide, kasutajaliidese jäädvustuste, läbipaistva graafika ja kadudeta redigeerimiseks. + Kasutage WebP-d, AVIF-i või JXL-i, kui kompaktne kaasaegne väljund on oluline ja vastuvõttev rakendus seda toetab. + Eemaldage helikaaned + Salvestage manustatud albumipildid või saadaolevad meediumiraamid helifailidest PNG-piltidena + Tõmmake kunstiteosed helifailidest välja + Audio Covers kasutab Androidi meedia metaandmeid, et lugeda helifailidest manustatud kunstiteoseid. Kui manustatud kunstiteos pole saadaval, võib retriiver tagasi minna meediumiraami, kui Android suudab seda pakkuda; kui kumbagi pole, teatab tööriist, et ekstraktimiseks pole pilti. + Avage helikaaned ja valige üks või mitu eeldatava kaanepildiga helifaili. + Enne salvestamist või jagamist vaadake ekstraheeritud kaas üle, eriti voogesituse või salvestusrakenduste failide puhul. + Kui pilti ei leita, pole helifailil tõenäoliselt manustatud katet või Android ei saanud kasutatavat raami paljastada. + Kuupäevad ja asukoha metaandmed + Otsustage, mida säilitada, kui jäädvustamisaeg on oluline, kuid GPS-i või seadme andmed ei tohiks lekkida + Eraldage kasulikud metaandmed privaatsetest metaandmetest + Metaandmed ei ole kõik võrdselt riskantsed. Jäädvustamiskuupäevad võivad olla kasulikud arhiivimisel ja sortimisel, samas kui GPS, seadme jadalaadsed väljad, tarkvarasildid või omanikuväljad võivad paljastada rohkem, kui ette nähtud. + Hoidke kuupäeva ja kellaaja sildid alles, kui kronoloogiline järjekord on albumite, skaneeringute või projekti ajaloo jaoks oluline. + Eemaldage GPS ja seadet tuvastavad sildid enne piltide avalikku jagamist või võõrastele saatmist. + Eksportige näidis ja kontrollige EXIF-i uuesti, kui privaatsusnõuded on ranged. + Vältige failinimede kokkupõrkeid + Kaitske partiiväljundeid, kui paljud failid jagavad nimesid nagu image.jpg või screenshot.png + Muutke iga väljundi nimi ainulaadseks + Dubleerivad failinimed on tavalised, kui pildid pärinevad sõnumitoojatest, ekraanipiltidest, pilvekaustadest või mitmest kaamerast. Hea muster hoiab ära juhusliku asendamise ja hoiab väljundid jälgitavad nende allikateni. + Kui redigeeritud koopia peaks olema äratuntav, lisage originaalnimi ja järelliide. + Suurte segapartiide töötlemisel lisage järjestus, ajatempel, eelseadistus või mõõtmed. + Vältige töövooge ülekirjutamist seni, kuni olete veendunud, et nimetamismuster ei saa põrkuda. + Salvesta või jaga + Valige püsiva faili ja ajutise üleandmise vahel teisele rakendusele + Eksportige õige kavatsusega + Salvestamine ja jagamine võivad tunduda sarnased, kuid need lahendavad erinevaid probleeme. Salvestamisel luuakse fail, mille leiate hiljem; jagamine saadab loodud tulemuse Androidi kaudu teise rakendusse ja ei pruugi jätta püsivat kohalikku koopiat. + Kasutage Salvesta, kui väljund peab jääma teadaolevasse kausta, sellest tuleb varundada või hiljem uuesti kasutada. + Kasutage jagamist kiirsõnumite, meilimanuste, suhtlusvõrgustike postitamiseks või tulemuste teisele redaktorile saatmiseks. + Salvestage esmalt, kui vastuvõttev rakendus on ebausaldusväärne või kui ebaõnnestunud jagamist oleks tüütu uuesti luua. + PDF-i lehe suurus ja veerised + Valige enne dokumendi loomist paberi suurus, sobivus ja hingamisruum + Muutke pildilehed prinditavaks ja loetavaks + Kujutised võivad muutuda kohmakateks PDF-lehtedeks, kui nende kuju ei vasta valitud paberiformaadile. Veerised, sobitusrežiim ja lehe suund määravad, kas sisu on kärbitud, väike, venitatud või mugav lugeda. + Kui PDF-i saab printida või vormile esitada, kasutage tõelist paberiformaati. + Kasutage skannimisel ja ekraanipiltidel veeriseid, et tekst ei jääks vastu lehe serva. + Kui lähtekujutised on segase orientatsiooniga, vaadake koos portree- ja rõhtpaigutuslehti. + Puhastage skaneeringud + Parandage paberi servi, varje, kontrasti, pööramist ja loetavust enne PDF-i või OCR-i + Enne arhiveerimist valmistage lehed ette + Skannimist saab tehniliselt jäädvustada, kuid siiski raskesti loetav. Väikesed puhastustoimingud enne PDF-i eksportimist on sageli olulisemad kui tihendusseaded, eriti kviitungite, käsitsi kirjutatud märkmete ja hämarate lehtede puhul. + Parandage perspektiivi ja kärpige ära laua servad, sõrmed, varjud ja tausta segadus. + Suurendage kontrasti ettevaatlikult, et tekst muutuks selgemaks ilma templeid, allkirju või pliiatsijälgi hävitamata. + Käivitage OCR alles siis, kui leht on püsti ja loetav, seejärel kontrollige olulisi numbreid käsitsi. + Tihendage, halltoonides või parandage PDF-faile + Kasutage ühe faili PDF-toiminguid kergemate, prinditavate või ümberehitatud dokumentide koopiate tegemiseks + Valige PDF-i puhastamise toiming + PDF-tööriistad sisaldavad eraldi toiminguid tihendamiseks, halltoonide teisendamiseks ja parandamiseks. Tihendus ja halltoonid töötavad peamiselt manustatud piltide kaudu, samal ajal kui remont taastab PDF-i struktuuri; ühtegi neist ei tohiks käsitleda iga dokumendi garanteeritud parandina. + Kasutage PDF-i tihendamist, kui dokument sisaldab pilte ja faili suurus on jagamiseks oluline. + Kasutage halltoonid, kui dokumenti peaks olema lihtsam printida või kui see ei vaja värvipilte. + Kasutage PDF-i parandamist koopia puhul, kui dokument ei avane või see on kahjustatud, seejärel kontrollige uuesti üles ehitatud faili. + Ekstraktige PDF-failidest pilte + Taastage manustatud PDF-pildid ja pakkige ekstraktitud tulemused arhiivi + Salvestage dokumentidest lähtepildid + Funktsioon Extract Images skannib PDF-faili manustatud pildiobjektide leidmiseks, jätab võimaluse korral duplikaadid vahele ja salvestab taastatud pildid loodud ZIP-arhiivi. See ekstraheerib ainult pilte, mis on tegelikult PDF-i manustatud, mitte teksti, vektorjooniseid ega kõiki nähtavaid lehti ekraanipildina. + Kasutage piltide ekstrakti, kui vajate PDF-faili salvestatud pilte, näiteks fotosid kataloogist või skannitud varadest. + Kui vajate täislehekülgi, sealhulgas teksti ja paigutust, kasutage PDF-i piltidena või lehtede ekstraktimist. + Kui tööriist ei teata manustatud kujutistest, võib leht olla teksti-/vektorisisu või pilte ei pruugita ekstraheeritavate objektidena salvestada. + Metaandmed, lamendamine ja prindiväljund + Muutke dokumendi atribuute, rastereerige lehti või valmistage ette kohandatud prindipaigutusi + Valige lõppdokumendi toiming + PDF-i metaandmed, lamendamine ja trükiettevalmistus lahendavad erinevaid viimase etapi probleeme. Metaandmed juhivad dokumendi atribuute, Flatten PDF rastereerib lehed vähem redigeeritavaks väljundiks ja Print PDF valmistab ette uue paigutuse lehe suuruse, suuna, veerise ja lehekülgede kohta lehel juhtelementidega. + Kasutage metaandmeid, kui autor, pealkiri, märksõnad, tootja või privaatsuse puhastamine on olulised. + Kasutage PDF-i lamedamaks muutmist, kui nähtava lehe sisu eraldi PDF-objektidena on raskem redigeerida. + Kasutage PDF-i printimist, kui vajate kohandatud lehe suurust, suunda, veerisid või mitut lehekülge lehel. + Valmistage pildid OCR-i jaoks ette + Kasutage enne OCR-i teksti lugemiseks palumist kärpimist, pööramist, kontrasti, müra summutamist ja skaalat + Andke OCR-ile puhtamad pikslid + OCR-vead tulenevad sageli pigem sisendpildist kui OCR-mootorist. Viltused leheküljed, madal kontrastsus, hägusus, varjud, pisike tekst ja segatud taust – kõik need vähendavad tuvastamise kvaliteeti. + Kärpige tekstialale ja pöörake pilti nii, et jooned oleksid enne tuvastamist horisontaalsed. + Suurendage kontrasti või muutke puhtamaks must-valgeks ainult siis, kui see muudab tähtede hõlpsamini loetavaks. + Muutke väikese teksti suurust mõõdukalt ülespoole, kuid vältige agressiivset teritamist, mis tekitab võltsservi. + Kontrollige QR-i sisu + Enne koodi usaldamist vaadake üle lingid, WiFi-mandaadid, kontaktid ja toimingud + Käsitlege dekodeeritud teksti kui ebausaldusväärset sisendit + QR-kood võib peita URL-i, kontaktikaardi, WiFi-parooli, kalendrisündmuse, telefoninumbri või lihtteksti. Koodi lähedal olev nähtav silt ei pruugi tegeliku kasuliku koormusega ühtida, seega vaadake enne selle järgi tegutsemist dekodeeritud sisu üle. + Kontrollige enne avamist täielikku dekodeeritud URL-i, eriti lühendatud või tundmatuid domeene. + Olge WiFi-, kontakti-, kalendri-, telefoni- ja SMS-koodidega ettevaatlik, kuna need võivad käivitada tundlikke toiminguid. + Kui peate selle kohe avamise asemel mujal kinnitama, kopeerige esmalt sisu. + Genereerige QR-koode + Looge koode lihtteksti, URL-ide, WiFi, kontaktide, meili, telefoni, SMS-ide ja asukohaandmete põhjal + Ehitage õige QR-koormus + QR-ekraan suudab genereerida koode sisestatud sisust ja skannida olemasolevaid. Struktureeritud QR-tüübid aitavad kodeerida andmeid vormingus, mida teised rakendused mõistavad, nagu WiFi sisselogimisandmed, kontaktkaardid, meiliväljad, telefoninumbrid, SMS-i sisu, URL-id või geograafilised koordinaadid. + Valige lihtsate märkmete jaoks lihttekst või kasutage struktureeritud tüüpi, kui kood peaks avanema WiFi, kontakti, URL-i, meili, telefoni, SMS-i või asukohaandmetena. + Kontrollige enne loodud koodi jagamist iga välja, sest nähtav eelvaade kajastab ainult teie sisestatud kasulikku koormust. + Testige salvestatud QR-koodi skanneriga, kui see prinditakse, postitatakse avalikult või kasutatakse teiste inimeste poolt. + Valige kontrollsumma režiim + Arvutage failidest või tekstist räsi, võrrelge ühte faili või kontrollige mitut faili partiidena + Kontrollige sisu, mitte välimust + Kontrollsumma tööriistadel on eraldi vahekaardid failide räsimiseks, teksti räsimiseks, faili võrdlemiseks teadaoleva räsiga ja partiide võrdlemiseks. Kontrollsumma tõestab valitud algoritmi bait-baidi identiteeti; see ei tõesta, et kaks pilti lihtsalt näevad sarnased välja. + Kasutage failide jaoks valikut Arvuta ja trükitud või kleebitud tekstiandmete jaoks tekstiräsi. + Kasutage Võrdlemist, kui keegi annab teile ühe faili jaoks eeldatava kontrollsumma. + Kasutage partiivõrdlust, kui paljud failid vajavad räsi või kinnitamist ühe käiguga, seejärel hoidke valitud algoritm järjepidev. + Lõikelaua privaatsus + Kasutage automaatseid lõikepuhvri funktsioone ilma privaatseid kopeeritud andmeid kogemata paljastamata + Hoidke kopeeritud sisu tahtlikult + Lõikelaua automatiseerimine on mugav, kuid kopeeritud tekst võib sisaldada paroole, linke, aadresse, märke või privaatseid märkmeid. Käsitle lõikelauapõhist sisendit kiirusfunktsioonina, mis peaks vastama teie harjumustele ja privaatsusootustele. + Keelake automaatne lõikepuhvri kasutamine, kui privaatne tekst ilmub ootamatult tööriistadesse. + Kasutage ainult lõikepuhvrisse salvestamist ainult siis, kui soovite teadlikult, et tulemus väldiks salvestamist. + Tühjendage või asendage lõikelaud pärast tundliku teksti, QR-andmete või Base64 kasuliku koorma käsitlemist. + Värvivormingud + Enne mujale kleepimist mõistke HEX-, RGB-, HSV-, alfa- ja kopeeritud värviväärtusi + Kopeerige vorming, mida sihtmärk ootab + Sama nähtavat värvi saab kirjutada mitmel viisil. Kujundustööriist, CSS-väli, Androidi värviväärtus või pildiredaktor võib eeldada teistsugust järjestust, alfakäsitlust või värvimudelit. + Kasutage HEX-i, kui kleepite veebi-, kujundus- või teemaväljadele, mis eeldavad kompaktseid värvikoode. + Kasutage RGB-d või HSV-d, kui peate kanaleid, heledust, küllastust või tooni käsitsi reguleerima. + Kontrollige alfat eraldi, kui läbipaistvus on oluline, kuna mõned sihtkohad ignoreerivad seda või kasutavad teistsugust järjestust. + Sirvige värviteeki + Otsige nimega värve nime või HEX järgi ja hoidke lemmikud ülaosas + Leidke korduvkasutatavad nimega värvid + Värviteek laadib nimega värvikogu, sorteerib selle nime järgi, filtreerib värvinime või HEX-väärtuse järgi ja salvestab lemmikvärvid, et olulised kirjed oleksid hõlpsamini juurdepääsetavad. See on kasulik, kui pildilt proovide võtmise asemel vajate pärisnimelist värvi. + Kui teate perekonda, otsige värvinime järgi (nt punane, sinine, kiltkivi või piparmünt). + Otsige HEX-i järgi, kui teil on juba numbriline värv ja soovite leida sobivaid või läheduses olevaid nimelisi kirjeid. + Märkige sagedased värvid lemmikuteks, et need sirvimise ajal üldisest teegist ette jõuaksid. + Kasutage võrgugradientide kogusid + Laadige alla saadaolevad võrgugradiendi ressursid ja jagage valitud pilte + Kasutage kaugvõrgu gradiendi varasid + Mesh Gradients saab laadida kaugressursikogu, alla laadida puuduvad gradiendi kujutised, näidata allalaadimise edenemist ja jagada valitud gradiente. Kättesaadavus sõltub võrgule juurdepääsust ja kaugressursside laost, nii et tühi loend tähendab tavaliselt, et ressursid polnud veel saadaval. + Kui soovite valmis võrgusilma gradiendi pilte, avage gradienditööriistadest võrgugradientid. + Oodake ressursside laadimist või allalaadimise edenemist, enne kui eeldate, et kogu on tühi. + Valige ja jagage vajalikke gradiente või naaske Gradient Makerisse, kui soovite kohandatud gradienti käsitsi luua. + Loodud varade eraldusvõime + Valige väljundi suurus enne gradientide, müra, taustapiltide, SVG-laadse kunsti või tekstuuride loomist + Looge vajalikus suuruses + Loodud piltidel ei ole kaamera originaali, millele tagasi pöörduda. Kui väljund on liiga väike, võib hilisem skaleerimine pehmendada mustreid, nihutada üksikasju või muuta varade paane ja tihendamist. + Esmalt määrake lõppkasutuse mõõtmed, nagu taustpilt, ikoon, taust, print või ülekate. + Kasutage suuremaid suurusi tekstuuride jaoks, mida saab kärpida, suumida või mitmes paigutuses uuesti kasutada. + Eksportige testversioonid enne suuri väljundeid, sest protseduuride üksikasjad võivad muuta tihendamise käitumist. + Ekspordi praegused taustapildid + Hankige seadmest saadaolevad kodu-, luku- ja sisseehitatud taustapildid + Salvestage või jagage installitud taustapilte + Wallpapers Export loeb taustapilte, mida Android avaldab süsteemi taustapildi API-de kaudu. Olenevalt seadmest, Androidi versioonist, lubadest ja järgu variandist võivad Home, Lock või sisseehitatud taustapildid olla saadaval eraldi või puududa. + Avage Taustapiltide eksport, et laadida taustapilte, mida süsteem võimaldab rakendusel lugeda. + Valige saadaolevad Avaleht, Lukk või sisseehitatud taustapildi kirjed, mida soovite salvestada, kopeerida või jagada. + Kui taustpilt puudub või funktsioon pole saadaval, on see tavaliselt pigem süsteemi, loa või järgu variandi piirang kui redigeerimisseade. + Corners Animatsioon Gaasihoovastik + Kopeeri värv kui + HEX + nimi + Portree mattmudel kiireks inimese väljalõigeteks pehmemate juuste ja servade käsitsemisega. + Kiire täiustamine hämaras, kasutades null-DCE++ kõvera hinnangut. + Restormeri kujutise taastamise mudel vihmatriibude ja märja ilmaga seotud esemete vähendamiseks. + Dokumenteerige kõveruse kaotamise mudel, mis ennustab koordinaatide ruudustikku ja kaardistab kõverad leheküljed lamedamaks skannimiseks. + Liikumise kestuse skaala + Juhib rakenduse animatsiooni kiirust: 0 keelab liikumise, 1 on normaalne, kõrgemad väärtused aeglustavad animatsioone + Näita hiljutisi tööriistu + Kuvage põhiekraanil kiire juurdepääs hiljuti kasutatud tööriistadele + Kasutusstatistika + Uurige rakenduste avamisi, tööriistade käivitamisi ja enimkasutatud tööriistu + Rakendus avaneb + Tööriist avaneb + Viimane tööriist + Edukad salvestused + Aktiivsuse jada + Andmed salvestatud + Tippformaat + Kasutatud tööriistad + %1$d avaneb • %2$s + Kasutusstatistikat veel pole + Avage mõned tööriistad ja need kuvatakse siin automaatselt + Teie kasutusstatistika salvestatakse teie seadmesse ainult kohapeal. ImageToolbox kasutab neid ainult teie isikliku tegevuse, lemmiktööriistade ja salvestatud andmete ülevaate kuvamiseks + redaktori redigeerimine foto pildi retušeerimine kohandamine + suuruse muutmine teisenda skaala eraldusvõime mõõtmed laius kõrgus jpg jpeg png webp tihendus + tihenda suurus kaal baiti mb kb kvaliteedi vähendamine optimeeri + kärpimine kärpimine lõigatud kuvasuhe + filtri efekti värviparandus reguleeri lut mask + joonistama pintsliga pliiatsiga märkima märgistuse + šifri krüptimine dekrüpteerima parooli saladus + tausta eemaldaja kustutamine eemalda väljalõige läbipaistev alfa + eelvaade vaataja galerii kontrollimine avatud + stitch merge kombineeri panoraam pikk ekraanipilt + url veebi allalaadimine Interneti-lingi pilt + korjaja silmatilguti näidis hex rgb värv + palett värvid proovid väljavõte domineeriv + exif metaandmete privaatsus eemalda riba puhas gps-i asukoht + võrrelda erinevust enne pärast + piirata suurust max min megapikslid mõõtmed klamber + pdf-dokumentide lehtede tööriistad + ocr teksti tuvastamine väljavõtte skannimine + gradiendi taustavärvi võrk + vesimärgi logo teksti templi ülekate + gif animatsioon animeeritud teisendada kaadreid + apng animatsioon animeeritud png teisendada kaadreid + zip-arhiivi tihenduspakk pakkige failid lahti + jxl jpeg xl teisendab jpg pildivormingut + svg vektorjälg teisendab xml + formaadis teisendage jpg jpeg png webp heic avif jxl bmp + dokumendiskanner skannimine paberi perspektiiv kärpimine + qr vöötkoodi skanneri skannimine + virnastamine ülekatte keskmine mediaan astrofotograafia + split plaadid grid viil osad + värvide teisendamine hex rgb hsl hsv cmyk lab + webp teisendada animeeritud pildivormingus + mürageneraator juhuslik tekstuuritera + kollaaž ruudustiku paigutuse kombineerida + märgistuskihid märgivad ülekatte redigeerimise + base64 kodeerib andmete uri dekodeerimiseks + kontrollsumma räsi md5 sha sha1 sha256 kontrollida + võrgusilma gradiendi taust + exif metaandmed redigeeri gps-i kuupäevakaamerat + lõigake jagatud võretükid plaadid + heliplaadi kaanepildi muusika metaandmed + taustapildi ekspordi taust + ascii tekstikunsti tegelased + ai ml neuraalne võimendab kõrgetasemelist segmenti + värviraamatukogu materjalide palett nimega värvid + shader glsl fragmendiefekti stuudio + pdf-ühenda ja ühenda + pdf jagage eraldi lehed + pdf lehekülgede pööramise suund + pdf ümberkorralda lehekülgi sorteeri ümber + pdf leheküljenumbrite lehekülgede lehekülg + pdf ocr tekst tuvastab otsitava väljavõtte + pdf vesimärgi templi logo tekst + pdf allkirjade loosimine + pdf kaitsta parooliga krüptilukk + pdf avamisparooli dekrüpteerimine eemalda kaitse + pdf tihendamine vähenda suurust optimeeri + pdf halltoonides must valge ühevärviline + pdf-i parandusparandus, taastage katki + pdf metaandmete atribuudid exif autori pealkiri + pdf eemaldada kustutada lehti + pdf kärpimise veerised + pdf lameda annotatsioonid moodustavad kihid + pdf väljavõte pildid pildid + pdf zip-arhiivi tihendamine + pdf-printer + pdf-i eelvaate vaataja lugeda + pildid pdf-iks teisendage jpg jpeg png-dokument + pdf piltide lehtedeks eksport jpg png + pdf märkused kommentaarid eemalda puhas + Neutraalne variant + Viga + Langev vari + Hamba kõrgus + Horisontaalne hammaste vahemik + Vertikaalne hammaste vahemik + Ülemine serv + Parem serv + Alumine serv + Vasak serv + Rebenenud serv + Lähtesta kasutusstatistika + Tööriist avaneb, statistika salvestamine, vööt, salvestatud andmed ja ülemine vorming lähtestatakse. Rakenduse avamised jäävad muutumatuks. + Heli + Fail + Ekspordi profiilid + Salvesta praegune profiil + Kustuta ekspordiprofiil + Kas soovite kustutada ekspordiprofiili \\"%1$s\\"? + Piiri joonistamine + Näidake joonistus- ja kustutamislõuendi ümber õhukest äärist + Laineline + Ümardatud kasutajaliidese elemendid pehme lainelise servaga + Kühvel + Sälk + Sissepoole nikerdatud ümarad nurgad + Topeltlõikega astmelised nurgad + Kohatäide + Kasutage faili {filename}, et sisestada algne failinimi ilma laiendita + Põletik + Baassumma + Sõrmuse summa + Kiirte kogus + Rõnga laius + Perspektiivi moonutamine + Java välimus ja tunnetus + Lõikamine + X nurk + ja nurk + Muuda väljundi suurust + Veetilk + Lainepikkus + Faas + Kõrgpääs + Värviline mask + Chrome + Lahustage + Pehmus + Tagasiside + Objektiivi hägustamine + Madal sisend + Kõrge sisend + Madal väljund + Kõrge väljund + Valgusefektid + Muhvi kõrgus + Muhke pehmus + Ripple + X amplituud + Y amplituud + X lainepikkus + Y lainepikkus + Kohanduv hägu + Tekstuuri genereerimine + Looge erinevaid protseduurilisi tekstuure ja salvestage need piltidena + Tekstuuri tüüp + Eelarvamus + Aeg + Sära + Dispersioon + Näidised + Nurga koefitsient + Gradiendi koefitsient + Võre tüüp + Kauguse võimsus + Skaala Y + Hägusus + Aluse tüüp + Turbulentsustegur + Skaleerimine + Iteratsioonid + Sõrmused + Harjatud metall + Kaustikud + Mobiilne + Kabelaud + fBm + Marmor + Plasma + Tekk + Puit + juhuslik + Ruut + Kuusnurkne + Kaheksanurkne + Kolmnurkne + Rihveldatud + VL Müra + SC müra + Viimased + Hiljuti kasutatud filtreid veel pole + tekstuurigeneraator harjatud metall kaustikud raku kabelaud marmor plasma tekk puidust telliskivi kamuflaaž rakupilv pragu kangas lehestik kärgstruktuuri jäälaava udukogu paber rooste liiv suits kivi maastik topograafia vesi lainetus + Telliskivi + Kamuflaaž + Lahter + Pilv + Pragu + Kangas + Lehestik + Kärgstruktuuriga + Jää + Laava + udukogu + Paber + Rooste + Liiv + Suits + Kivi + Maastik + Topograafia + Vesi Ripple + Täiustatud puit + Mördi laius + Ebakorrapärasus + Kaldus + Karedus + Esimene lävi + Teine lävi + Kolmas lävi + Serva pehmus + Piiri laius + Katvus + Detail + Sügavus + Hargnemine + Horisontaalsed niidid + Vertikaalsed niidid + Fuzz + Veenid + Valgustus + Pragude laius + Härmatis + Voolu + Koorik + Pilvede tihedus + Tähed + Kiudude tihedus + Kiu tugevus + Plekid + Korrosioon + Pitting + Helbed + Luitesagedus + Tuule nurk + Ripples + Wisps + Veenide skaala + Veetase + Mäe tase + Erosioon + Lume tase + Ridade arv + Joone paksus + Varjutus + Pooride värv + Mördi värv + Tume tellise värv + Telliskivi värv + Tõstke esile värv + Tume värv + Metsa värv + Maa värv + Liiva värv + Taustavärv + Raku värv + Serva värv + Taevavärv + Varju värv + Hele värv + Pinna värv + Variatsioon värv + Pragude värv + Lõime värv + Koe värv + Lehtede tume värv + Lehtede värv + Äärise värv + Mesi värvi + Sügav värv + Jää värv + Härmavärv + Kooriku värv + Pesu värv + Helendav värv + Ruumi värv + Violetne värv + Sinine värv + Põhivärv + Kiudude värv + Peitsi värv + Metallist värvi + Tume rooste värv + Rooste värv + Oranž värv + Suitsu värv + Veenide värv + Madalmaa värv + Vesivärv + Kalju värv + Lume värv + Madal värv + Kõrge värvus + Joone värv + Madal värv + Muru + Mustus + Nahk + Betoonist + Asfalt + Sammal + Tulekahju + Aurora + Õlilaik + Akvarell + Abstraktne voog + Opaal + Damaskuse teras + Välk + Velvet + Tindi marmoreerimine + Holograafiline foolium + Bioluminestsents + Kosmiline keeris + Lava lamp + Sündmuste horisont + Fractal Bloom + Kromaatiline tunnel + Eclipse Corona + Kummaline ligitõmbaja + Ferrofluid kroon + Supernoova + Iris + Paabulinnu sulg + Nautilus Shell + Rõngastatud planeet + Tera tihedus + Tera pikkus + Tuul + Laigulisus + Klombid + Niiskus + Kivikesed + Kortsud + Poorid + Agregaat + Praod + Tõrva + Kanda + Täpid + Kiudained + Leegi sagedus + Suitsu + Intensiivsus + Paelad + Bändid + Iriseerumine + Pimedus + Õitseb + Pigment + Servad + Paber + Difusioon + Sümmeetria + Teravus + Värvimäng + Piimalisus + Kihid + Kokkupandav + poola keel + Filiaalid + Suund + Sära + Voldid + Sulgistamine + Tindi tasakaal + Spekter + Kortsud + Difraktsioon + Relvad + Keerake + Tuuma sära + Plekid + Plaadi kalle + Horisondi suurus + Ketta laius + Objektiivid + Kroonlehed + Curl + Filigraan + Tahked + Kumerus + Kuu suurus + Korona suurus + Kiired + Teemantsõrmus + Lobes + Orbiidi tihedus + Paksus + Naelu + Tera pikkus + Keha suurus + Metallik + Löögi raadius + Korpuse laius + Välja visatud + Pupilli suurus + Iirise suurus + Värvi variatsioon + Päikevalgus + Silma suurus + Barbi tihedus + Pöörded + Kambrid + Avamine + Ridges + Pärlivärv + Planeedi suurus + Rõnga kalle + Rõnga laius + Atmosfäär + Mustuse värv + Tume muru värv + Muru värv + Otsa värv + Tume maa värv + Kuiv värv + Kivikivi värv + Naha värvi + Betooni värv + Tõrva värv + Asfaldi värv + Kivi värv + Tolmuvärv + Mulla värv + Tume sambla värv + Samblavärv + Punane värv + Põhivärv + Roheline värv + Tsüaani värv + Magenta värv + Kuldne värv + Paberi värv + Pigmendi värv + Sekundaarne värv + Esimene värv + Teine värv + Roosa värv + Tume terasest värv + Terase värv + Hele terasest värv + Oksiidvärv + Halo värv + Poldi värv + Sametine värv + Läikiv värv + Sinine tindi värv + Punase tindi värv + Tume tindi värv + Hõbedane värv + Kollane värv + Kudede värv + Ketta värv + Kuum värv + Objektiivi värv + Väline värv + Sisemine värv + Koroona värv + Külm värv + Soe värv + Pilve värv + Leegi värv + Sulgede värv + Korpuse värv + Pärlivärvi + Planeedi värv + Sõrmuse värv + Pärast salvestamist kustutage originaalfailid + Kustutatakse ainult edukalt töödeldud lähtefailid + Algsed failid kustutatud: %1$d + Algsete failide kustutamine ebaõnnestus: %1$d + Algsed failid kustutatud: %1$d, ebaõnnestunud: %2$d + Lehe žestid + Laiendatud valikulehtede lohistamise lubamine + Kroomi alamproovide võtmine + Biti sügavus + Kadudeta + %1$d-bitine + Partii ümbernimetamine + Nimetage mitu faili ümber, kasutades failinime mustreid + failide partii ümbernimetamine failinimi muster jada kuupäevalaiend + Valige ümbernimetamiseks failid + Failinime muster + Nimeta ümber + Kuupäeva allikas + EXIF-i kuupäev võetud + Faili muutmise kuupäev + Faili loomise kuupäev + Praegune kuupäev + Käsitsi kuupäev + Käsitsi kuupäev + Käsitsi aeg + Valitud kuupäev pole mõne faili puhul saadaval. Nende jaoks kasutatakse praegust kuupäeva. + Sisestage failinime muster + Muster tekitab kehtetu või liiga pika failinime + Muster tekitab dubleerivaid failinimesid + Kõik failinimed juba vastavad mustrile + See muster kasutab märke, mis pole partii ümbernimetamiseks saadaval + Nimi jääb samaks + Failide ümbernimetamine õnnestus + Mõnda faili ei saanud ümber nimetada + Luba ei antud + Kasutab algset failinime ilma laiendita, mis aitab teil hoida allika identifitseerimist puutumata. Toetab ka viilutamist \\o{start:end}, transliteratsiooni \\o{t}-ga ja asendamist \\o{s/old/new/}-ga. + Automaatselt suurenev loendur. Toetab kohandatud vormingut: \\c{padding} (nt \\c{3} -&gt; 001), \\c{start:step} või \\c{start:step:padding}. + Selle ülemkausta nimi, kus algne fail asus. + Algfaili suurus. Toetab ühikuid: \\z{b}, \\z{kb}, \\z{mb} või lihtsalt \\z inimloetava vormingu jaoks. + Loob juhusliku universaalse unikaalse identifikaatori (UUID), et tagada failinime kordumatus. + Failide ümbernimetamist ei saa tagasi võtta. Enne jätkamist veenduge, et uued nimed on õiged. + Kinnitage ümbernimetamine + Külgmenüü seaded + Menüü skaala + Alfa menüü + Automaatne valge tasakaal + Lõikamine + Peidetud vesimärkide kontrollimine + Säilitamine + Vahemälu limiit + Tühjendage vahemälu automaatselt, kui see kasvab sellest suurusest kaugemale + Puhastusintervall + Kui sageli kontrollida, kas vahemälu tuleks tühjendada + Rakenduse käivitamisel + 1 päev + 1 nädal + 1 kuu + Tühjendage rakenduse vahemälu automaatselt vastavalt valitud limiidile ja intervallile + Teisaldatav põllukultuuri ala + Võimaldab liigutada kogu kärpimisala selle sees lohistades + Ühendage GIF + Ühendage mitu GIF-klippi üheks animatsiooniks + GIF-klippide tellimine + Tagurpidi + Mängi tagurpidi + Bumerang + Mängi edasi ja siis tagasi + Normaliseerige raami suurus + Mõõtke raamid, et need sobiksid suurima klambriga; algse skaala säilitamiseks välja lülitada + Klippide vaheline viivitus (ms) + Kaust + Valige kaustast ja selle alamkaustadest kõik pildid + Duplikaatide otsija + Otsige täpseid koopiaid ja visuaalselt sarnaseid pilte + duplikaatide leidja sarnased pildid täpsed koopiad fotod puhastus salvestusruum dHash SHA-256 + Duplikaatide leidmiseks valige pildid + Sarnasuse tundlikkus + Täpsed koopiad + Sarnased pildid + Valige kõik täpsed duplikaadid + Valige kõik, välja arvatud soovitatav + Duplikaate ei leitud + Proovige lisada rohkem pilte või suurendada sarnasuse tundlikkust + %1$d pilti ei õnnestunud lugeda + %1$s • %2$s tagasinõutav + %1$d rühmad • %2$s tagasinõutavad + %1$d valitud • %2$s + Seda ei saa tagasi võtta. Android võib paluda teil süsteemi dialoogiaknas kustutamise kinnitada. + Ei leitud + Alustamiseks liigutage + Liigu lõpuni + \ No newline at end of file diff --git a/core/resources/src/main/res/values-eu/strings.xml b/core/resources/src/main/res/values-eu/strings.xml new file mode 100644 index 0000000..2540cfa --- /dev/null +++ b/core/resources/src/main/res/values-eu/strings.xml @@ -0,0 +1,3739 @@ + + + + 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 + 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 + 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. + 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. + 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 + Edukiari buruzko distortsioa + Ez dago nahikoa memoria ekintza hau burutzeko. Mesedez, saiatu irudi txikiagoa erabiltzen, beste aplikazio batzuk ixten edo aplikazioa berrabiarazi. + Langile Paraleloak + Irudi hauek ez ziren gorde bihurtutako fitxategiak jatorrizkoak baino handiagoak izango zirelako. Egiaztatu zerrenda hau jatorrizko irudiak ezabatu aurretik. + Saltatu diren fitxategiak: %1$s + Aplikazioen erregistroak + Ikusi aplikazioaren erregistroak arazoak antzemateko + Taldekako tresnetan gogokoak + Gogokoak fitxa gisa gehitzen ditu tresnak motaren arabera taldekatzen direnean + Erakutsi gogokoena azken gisa + Gogoko tresnen fitxa amaierara eramaten du + Tarte horizontala + Tarte bertikala + Atzerako energia + Erabili gradiente-magnitudearen energia-mapa sinplea aurrerako energia-algoritmoaren ordez + Kendu maskaratutako eremua + Josturak maskaratutako eskualdetik igaroko dira, hautatutako eremua bakarrik kenduz babestu beharrean + VHS NTSC + NTSC ezarpen aurreratuak + Sintonizazio analogiko gehigarria VHS eta emisio artefaktu indartsuagoetarako + Chroma odoljarioa + Zintaren higadura + Jarraipena + Luma lohitua + Eraztunketa + Elurra + Erabili eremua + Iragazki mota + Sarrerako luma iragazkia + Sarrerako kroma pasabide baxua + Kroma demodulazioa + Fase-aldaketa + Fase-desplazamendua + Buru aldaketa + Burua aldatzeko altuera + Burua aldatzeko offset + Buruz aldatzeko aldaketa + Erdiko lerroko posizioa + Erdiko lerroko jitter + Zarataren altueraren jarraipena + Jarraipen uhina + Elurraren jarraipena + Elurraren anisotropiaren jarraipena + Jarraipen zarata + Zarataren intentsitatearen jarraipena + Zarata konposatua + Zarata-maiztasun konposatua + Zarata-intentsitate konposatua + Zarata konposatuaren xehetasuna + Dei-maiztasuna + Dei-boterea + Luma zarata + Luma zarataren maiztasuna + Luma zarataren intentsitatea + Luma zarata xehetasuna + Kroma zarata + Chroma zarataren maiztasuna + Chroma zarataren intentsitatea + Chroma zarataren xehetasuna + Elurraren intentsitatea + Elur anisotropia + Kroma faseko zarata + Kroma faseko errorea + Kroma atzerapena horizontala + Kroma atzerapena bertikala + VHS zintaren abiadura + VHS kroma galera + VHS zorroztu intentsitatea + VHS zorroztu maiztasuna + VHS ertz-uhinaren intentsitatea + VHS ertz-uhinaren abiadura + VHS ertz-uhin-maiztasuna + VHS ertz-uhinaren xehetasuna + Irteerako chroma behe-pass + Eskala horizontala + Eskala bertikala + X eskala-faktorea + Y eskala-faktorea + Ohiko irudien ur-markak detektatzen ditu eta LaMa-rekin margotzen ditu. Automatikoki deskargatzen ditu detektatzeko eta margotzeko ereduak + Deuteranopia + Zabaldu Irudia + Objektuen segmentazioan oinarritutako atzeko planoa kentzeko YOLO v11 Segmentation erabiliz + Erakutsi lerro angelua + Uneko lerroaren biraketa gradutan erakusten du marrazten ari zaren bitartean + Animaziozko emojiak + Erakutsi erabilgarri dauden emojiak animazio gisa + Gorde jatorrizko karpetan + Gorde fitxategi berriak hautatutako karpetaren ordez jatorrizko fitxategiaren ondoan + Ur-marka PDFa + Memoria nahikoa ez + Litekeena da irudia handiegia izatea gailu honetan prozesatzeko, edo sistema memoria erabilgarririk gabe geratu da. Saiatu irudiaren bereizmena murrizten, beste aplikazio batzuk ixten edo fitxategi txikiagoa aukeratzen. + Araztu menua + Aplikazioen funtzioak probatzeko menua, hau ez da ekoizpen-oharra agertzea + Hornitzailea + PaddleOCR-k ONNX modelo gehigarriak behar ditu zure gailuan. %1$s datuak deskargatu nahi dituzu? + Unibertsala + Korean + latina + Ekialdeko eslaviarra + thailandiera + grekoa + ingelesa + Zirilikoa + arabiera + Devanagari + Tamila + Telugu + Itzalgailua + Itzalaren aurrezarpena + Ez dago itzalgailurik hautatu + Shader Studio + Sortu, editatu, balioztatu, inportatu eta esportatu zatien itzala pertsonalizatuak + Gordetako itzalak + Ireki gordetako itzalak, bikoiztu, esportatu, partekatu edo ezabatu + Itzalgailu berria + Shader fitxategia + .itshader JSON + %1$d parametroak + Itzalaren iturria + Idatzi void main()-ren gorputza bakarrik. Erabili textureCoordinate UV koordenatuetarako eta inputImageTexture iturburu-lagin gisa. + Funtzioak + Aukerako funtzio laguntzaileak, konstanteak eta egiturak void main() aurretik txertatu dira. Uniformeak parametroetatik sortzen dira. + Gehitu uniformeak hemen itzalgailuak balio editagarriak behar dituenean. + Berrezarri itzala + Uneko itzalaren zirriborroa garbituko da + Itzalgailu baliogabea + Onartzen ez den %1$d itzalgailuaren bertsioa. Onartutako bertsioa: %2$d. + Erabiltzailearen izenak ez du hutsik egon behar. + Itzalaren iturria ez da hutsik egon behar. + Itzalaren iturriak onartzen ez diren karaktereak ditu: %1$s. Erabili ASCII GLSL iturria soilik. + \\\"%1$s\\\" parametroa behin baino gehiagotan deklaratzen da. + Parametroen izenak ez dira hutsik egon behar. + \\\"%1$s\\\" parametroak GPUImage izen erreserbatua erabiltzen du. + \\\"%1$s\\\" parametroak baliozko GLSL identifikatzaile bat izan behar du. + \\\"%1$s\\\" parametroak %2$s balio mota du \\\"%3$s\\\", espero den \\\"%4$s\\\". + \\\"%1$s\\\" parametroak ezin du definitu min edo max balio booletarako. + \\\"%1$s\\\" parametroak gehienez baino min handiagoa du. + \\\"%1$s\\\" parametro lehenetsia min baino txikiagoa da. + \\\"%1$s\\\" parametro lehenetsia gehienezkoa baino handiagoa da. + Shader-ek \\\"uniform sampler2D %1$s;\\\" deklaratu behar du. + Shader-ek \\\"vec2 %1$s aldakorra\" deklaratu behar du. + Shader-ek \\\"void main()\\\" definitu behar du. + Shader-ek kolore bat idatzi behar du gl_FragColor-en. + ShaderToy mainImage itzalgailuak ez dira onartzen. Erabili GPUImage-ren void main() kontratua. + \\\"iTime\\\" ShaderToy uniforme animatua ez da onartzen. + ShaderToy animaziozko \\\"iFrame\\\" uniformea ​​ez da onartzen. + ShaderToy uniforme \\\"iResolution\\\" ez da onartzen. + ShaderToy iChannel testurak ez dira onartzen. + Kanpoko ehundurak ez dira onartzen. + Kanpoko ehundura luzapenak ez dira onartzen. + Libretro itzalduraren parametroak ez dira onartzen. + Sarrerako testura bakarra onartzen da. Kendu \\\"uniformea ​​%1$s %2$s;\\\". + \\\"%1$s\\\" parametroa \\\"uniforme %2$s %1$s;\\\" gisa deklaratu behar da. + \\\"%1$s\\\" parametroa \\\"uniforme %2$s %1$s;\\\", espero den \\\"uniforme %3$s %1$s;\\\". + Shader fitxategia hutsik dago. + Shader fitxategiak .itshader JSON objektu bat izan behar du, bertsio, izena eta itzal-eremuekin. + Shader fitxategia ez da JSON baliozkoa edo ez dator bat .itshader formatuarekin. + \\\"%1$s\\\" eremua beharrezkoa da. + %1$s beharrezkoa da. + %1$s \\\"%2$s\\\" ez da onartzen. Onartutako motak: %3$s. + %1$s beharrezkoa da \\\"%2$s\\\" parametroetarako. + %1$s zenbaki finitua izan behar du. + %1$s zenbaki oso bat izan behar du. + %1$s egia ala gezurra izan behar du. + %1$s kolore-katea, RGB/RGBA array edo kolore-objektua izan behar du. + %1$s #RRGGBB edo #RRGGBBAA formatua erabili behar du. + %1$s baliozko kolore hamaseitar kanalak izan behar ditu. + %1$s koloretako 3 edo 4 kanal izan behar ditu. + %1$s 0 eta 255 artean egon behar du. + %1$s bi zenbakiko matrizea edo x eta y dituen objektua izan behar du. + %1$s 2 zenbaki izan behar ditu zehazki. + Bikoiztu + Garbitu beti EXIF + Kendu irudiaren EXIF ​​datuak gordetzean, tresna batek metadatuak mantentzea eskatzen duenean ere + Laguntza eta aholkuak + %1$d tutoretzak + Ireki tresna + Ikasi tresna nagusiak, esportazio-aukerak, PDF fluxuak, kolore-utilitateak eta arazo arrunten konponketak + Hasteko + Aukeratu tresnak, inportatu fitxategiak, gorde emaitzak eta berrerabili ezarpenak azkarrago + Irudien edizioa + Tamaina aldatu, moztu, iragazi, ezabatu atzeko planoak, marraztu eta ur-marka irudiak + Fitxategiak eta metadatuak + Bihurtu formatuak, alderatu irteera, babestu pribatutasuna eta kontrolatu fitxategi-izenak + PDF eta dokumentuak + Sortu PDFak, eskaneatu dokumentuak, OCR orriak eta prestatu fitxategiak partekatzeko + Testua, QR eta datuak + Testua ezagutu, eskaneatu kodeak, kodetu Base64, egiaztatu hashak eta pakete-fitxategiak + Kolore-tresnak + Aukeratu koloreak, sortu paletak, arakatu kolore liburutegiak eta sortu gradienteak + Sormen-tresnak + Sortu SVG, ASCII artea, zarata-ehundura, itzalak eta irudi esperimentalak + Arazoak konpontzea + Konpondu fitxategi astunak, gardentasuna, partekatzea, inportazioak eta txostenak egiteko arazoak + Aukeratu tresna egokia + Erabili kategoriak, bilaketak, gogokoak eta partekatu ekintzak leku egokian hasteko + Hasi sarrera-puntu onenetik + Image Toolbox tresna bideratu asko ditu, eta horietako asko gainjartzen dira lehen begiratuan. Hasi amaitu nahi duzun atazatik, eta aukeratu emaitzaren zatirik garrantzitsuena kontrolatzen duen tresna: tamaina, formatua, metadatuak, testua, PDF orriak, koloreak edo diseinua. + Erabili bilaketa ekintzaren izena ezagutzen duzunean, adibidez, tamaina aldatu, PDF, EXIF, OCR, QR edo kolorea. + Ireki kategoria bat zeregin mota bakarrik ezagutzen duzunean, eta ainguratu maiz tresnak gogoko gisa. + Beste aplikazio batek irudi bat Image Toolbox-en partekatzen duenean, hautatu tresna partekatzeko orrien fluxutik. + Inportatu, gorde eta partekatu emaitzak + Ulertu ohiko fluxua sarrera hautatzen denetik editatutako fitxategia esportatzen arte + Jarraitu ohiko edizio-fluxuari + Tresna gehienek erritmo bera jarraitzen dute: aukeratu sarrera, doitu aukerak, aurrebista, gero gorde edo partekatu. Xehetasun garrantzitsuena da gordetzeak irteerako fitxategi bat sortzen duela, partekatzea normalean onena beste aplikazio batera transferitzeko. + Aukeratu fitxategi bat irudi bakarreko tresnetarako edo batch tresnetarako hainbat fitxategi, hautatzaileak baimentzen duenean. + Egiaztatu aurrebista eta irteera kontrolak gorde aurretik, batez ere formatu, kalitate eta fitxategi-izen aukerak. + Erabili partekatzea eskualdaketa azkar bat egiteko, edo gorde hautatutako karpetan kopia iraunkor bat behar duzunean. + Irudi askorekin lan egin aldi berean + Batch tresnak hobeak dira behin eta berriz tamaina aldatzeko, bihurtzeko, konprimitzeko eta izendatzeko zereginetarako + Aurreikusi daitekeen lote bat prestatu + Batch prozesatzea azkarrena da irteera guztiek arau berdinak jarraitu behar dituztenean. Akats handi bat egiteko lekurik errazena da, beraz, aukeratu izena, kalitatea, metadatuak eta karpetaren portaera esportazio luze bat hasi aurretik. + Hautatu erlazionatutako irudi guztiak batera eta mantendu ordena irteera sekuentziaren araberakoa bada. + Ezarri tamaina aldatzea, formatua, kalitatea, metadatuak eta fitxategi-izen arauak behin esportatzen hasi aurretik. + Exekutatu proba-sorta txiki bat lehenik iturburu-fitxategiak handiak direnean edo xede-formatua zuretzat berria denean. + Edizio bakarra azkarra + Erabili bakarreko editorea irudi batean hainbat aldaketa erraz behar dituzunean + Amaitu irudi bat tresna jauzi gabe + Edizio bakarra erabilgarria da irudiak eragiketa espezializatu bat baino aldaketa kate txiki bat behar duenean. Azkarragoa izan ohi da garbiketa azkarra, aurrebista eta azken kopia bat esportatzeko lotekako lan-fluxu bat sortu gabe. + Ireki Edizio bakarra doikuntza, markaketa, mozketa, biraketa edo esportazio aldaketak behar dituen irudi baterako. + Aplikatu aldaketak lehenik pixel gutxien aldatzen diren ordenan, hala nola, moztu iragazkiak baino lehen eta iragazkiak azken konpresioaren aurretik. + Erabili tresna espezializatu bat, horren ordez, loteen prozesaketa, fitxategi-tamainaren helburu zehatzak, PDF irteera, OCR edo metadatuen garbiketa behar dituzunean. + Erabili aurrezarpenak eta ezarpenak + Gorde behin eta berriz errepikatutako aukerak eta sintonizatu aplikazioa zure lan-fluxu nahiagorako + Saihestu konfigurazio bera errepikatzea + Aurrez aurreko ezarpenak eta ezarpenak erabilgarriak dira fitxategi mota bera maiz esportatzen duzunean. Aurrez ezarritako on batek eskuzko kontrol-zerrenda errepikatu bat ukitu batean bihurtzen du, eta ezarpen orokorrek tresna berriak hasten diren lehenetsiak definitzen dituzte. + Sortu aurrezarpenak ohiko irteerako tamaina, formatu, kalitate-balioak edo izendatzeko ereduetarako. + Berrikusi formatu, kalitate, gorde karpeta, fitxategi-izena, hautatzailea eta interfazearen portaera lehenetsiaren ezarpenak. + Egin ezarpenen babeskopiak aplikazioa berriro instalatu edo beste gailu batera eraman aurretik. + Ezarri balio lehenetsiak + Aukeratu lehenetsitako formatua, kalitatea, eskala modua, tamaina aldatzeko mota eta kolore-espazioa behin + Egin tresna berriak zure helburutik hurbilago + Lehenetsitako balioak ez dira hobespen kosmetikoak soilik. Beraiek erabakitzen dute zer formatua, kalitatea, tamaina aldatzeko portaera, eskala modua eta kolore-espazioa agertzen diren tresna askotan, eta horrek esportazio guztietan aukera berdinak berriro hautatzea saihesten du. + Ezarri lehenetsitako irudi-formatua eta kalitatea zure ohiko helmugara bat etor dadin, adibidez, JPEG argazkietarako edo PNG/WebP alpharako. + Egokitu tamaina aldatzeko mota eta eskala modu lehenetsia maiz esportatzen badituzu dimentsio zorrotzetarako, plataforma sozialetarako edo dokumentu-orrietarako. + Aldatu kolore-espazioen lehenetsiak zure lan-fluxuak kolore-kudeaketa koherentea behar duenean soilik pantailetan, inprimatuetan edo kanpoko editoreetan. + Hautatzailea eta abiarazlea + Erabili hautatzailearen ezarpenak aplikazioak elkarrizketa-koadro gehiegi irekitzen dituenean edo leku okerrean hasten denean + Egin fitxategiak irekitzea zure ohiturarekin bat datozen + Hautatzaile eta abiarazlearen ezarpenek Image Toolbox pantaila nagusitik, tresna batetik edo fitxategi hautatzeko fluxutik abiarazten den kontrolatzen dute. Aukera hauek bereziki erabilgarriak dira normalean Android partekatzeko orritik sartzen zarenean edo aplikazioa tresna abiarazle bat bezala sentitzea nahi duzunean. + Erabili argazki-hautatzailearen ezarpenak sistema-hautatzaileak fitxategiak ezkutatzen baditu, azken elementu gehiegi erakusten baditu edo hodeiko fitxategiak gaizki kudeatzen baditu. + Gaitu partekatzetik sartu ohi dituzun edo iturburu-fitxategia dagoeneko ezagutzen dituzun tresnetan soilik aukeratzea. + Berrikusi abiarazle modua Image Toolbox-ek tresna-hautatzaile baten antzera jokatzea nahi baduzu hasierako pantaila arrunt bat baino. + Irudiaren iturria + Aukeratu galeriekin, fitxategi-kudeatzaileekin eta hodeiko fitxategiekin ondoen funtzionatzen duen hautatzaile modua + Aukeratu fitxategiak iturri egokitik + Irudi-iturburuaren ezarpenek aplikazioak Android-i irudiak eskatzeko moduan eragiten du. Aukerarik onena zure fitxategiak sistemaren galerian, fitxategi-kudeatzaileen karpetan, hodeiko hornitzaile batean edo aldi baterako edukia partekatzen duen beste aplikazio batean bizi direnaren araberakoa da. + Erabili hautatzaile lehenetsia galeria-irudi arruntetarako sisteman integratutako esperientziarik handiena nahi duzunean. + Aldatu hautatzaile modua albumak, karpetak, hodeiko fitxategiak edo formatu estandarrak ez badira espero duzun lekuan agertzen. + Iturburuko aplikaziotik irten ondoren partekatutako fitxategi bat desagertzen bada, ireki berriro hautatzaile iraunkor baten bidez edo gorde tokiko kopia bat lehenik. + Gogokoak eta partekatzeko tresnak + Mantendu pantaila nagusia zentratuta eta ezkutatu partekatze-fluxuetan zentzurik ez duten tresnak + Murriztu erreminten zerrendako zarata + Gogokoak eta partekatzeko ezkutuko ezarpenak erabilgarriak dira egunero tresna gutxi batzuk erabiltzen dituzunean. Partekatze-fluxua praktikoa mantentzen dute, beste aplikazio batek bidaltzen duen fitxategi mota erabili ezin duten tresnak ezkutatuz. + Ainguratu maiz tresnak, tresnak kategoriaren arabera sailkatuta egon arren erraz eskura ditzaten. + Ezkutatu tresnak partekatze-fluxuetatik beste aplikazio batetik datozen fitxategietarako garrantzirik ez dutenean. + Erabili gogoko ordenatzeko ezarpenak amaieran gogokoak nahiago badituzu edo erlazionatutako tresnekin taldekatuta. + Egin ezarpenen babeskopiak + Mugitu aurrezarpenak, hobespenak eta aplikazioen portaera beste instalazio batera modu seguruan + Mantendu zure konfigurazioa eramangarria + Babeskopia eta leheneratzea lagungarriena da aurrez ezarpenak, karpetak, fitxategi-izenen arauak, tresnen ordena eta ikusizko hobespenak sintonizatu ondoren. Babeskopia batek ezarpenekin esperimentatzeko edo aplikazioa berriro instalatzeko aukera ematen dizu zure lan-fluxua memoriatik berreraiki gabe. + Sortu babeskopia bat aurrez ezarpenak, fitxategi-izenaren portaera, formatu lehenetsiak edo tresnaren antolaketa aldatu ondoren. + Gorde babeskopia aplikazioaren karpetatik kanpo datuak garbitu, berriro instalatu edo gailuak mugitzeko asmoa baduzu. + Berrezarri ezarpenak lote garrantzitsuak prozesatu aurretik, lehenetsiak eta gordetzeko portaera zure konfigurazio zaharrarekin bat datozen. + Irudiak tamaina aldatu eta bihurtu + Aldatu tamaina, formatua, kalitatea eta metadatuak irudi bat edo askorentzat + Sortu tamainaz aldatutako kopiak + Tamaina aldatu eta Bihurtu dimentsio aurreikusteko eta irteerako fitxategi arinagoetarako tresna nagusia da. Erabili ezazu irudiaren tamainak, irteerako formatuak eta metadatuen portaerak aldi berean garrantzitsuak direnean. + Aukeratu irudi bat edo gehiago, eta aukeratu neurriak, eskalak edo fitxategi-tamainak garrantzitsuenak diren. + Aukeratu irteera formatua eta kalitatea; erabili PNG edo WebP gardentasuna gorde behar denean. + Aurreikusi emaitza, gero gorde edo partekatu sortutako kopiak jatorrizkoak aldatu gabe. + Tamaina aldatu fitxategiaren tamainaren arabera + Xedetu tamaina-muga bat webgune, mezulari edo inprimaki batek irudi astunak baztertzen dituenean + Egokitu kargatzeko muga zorrotzak + Fitxategiaren tamainaren arabera tamaina aldatzea hobe da kalitate-balioak asmatzea helmugak muga zehatz batetik beherako fitxategiak soilik onartzen dituenean. Tresnak konpresioa eta dimentsioak doi ditzake helburura iristeko, emaitza erabilgarri mantentzen saiatzen den bitartean. + Sartu xede-tamaina benetako karga-mugaren apur bat azpitik metadatuak eta hornitzaileak aldatzeko tartea uzteko. + Hobetu argazkietarako galera-formatuak xede txikia denean; PNG handia gera daiteke tamaina aldatu ondoren ere. + Ikuskatu aurpegiak, testua eta ertzak esportatu ondoren, tamaina erasokorrak diren artefaktuak sar ditzaketelako. + Mugatu tamaina aldatzea + Murriztu gehienezko zabalera, altuera edo fitxategien mugak gainditzen dituzten irudiak soilik + Saihestu lehendik ondo dauden irudien tamaina aldatzea + Muga aldatzea erabilgarria da irudi batzuk handiak diren eta beste batzuk dagoeneko prestatuta dauden karpeta mistoetarako. Fitxategi guztiak tamaina aldatzera behartu beharrean, irudi onargarriak jatorrizko kalitatetik gertuago mantentzen ditu. + Ezarri gehienezko dimentsioak edo mugak helmugaren arabera, fitxategi bakoitza itsu-itsuan aldatu beharrean. + Gaitu saltatzeko estilo-portaera handiagoa bada iturriak baino astunagoak diren irteerak saihestu nahi dituzunean. + Erabili hau kamera, pantaila-argazki edo deskarga desberdinetako loteetarako, non iturburuaren tamainak asko aldatzen diren. + Moztu, biratu eta zuzendu + Markatu eremu garrantzitsua irudia esportatu edo beste tresna batzuetan erabili aurretik + Garbitu konposizioa + Moztuz gero, iragazkiak, OCR, PDF orriak eta emaitzak partekatzeak garbiago egiten ditu. + Ireki Moztu eta aukeratu doitu nahi duzun irudia. + Erabili doako mozketa edo aspektu-erlazioa irteerak argitalpen, dokumentu edo avatar batera egokitu behar duenean. + Biratu edo irauli gorde aurretik, geroago tresnak zuzendutako irudia jasotzeko. + Aplikatu iragazkiak eta doikuntzak + Eraiki iragazkien pila editagarria eta ikusi emaitza esportatu aurretik + Sintonizatu irudiaren itxura + Iragazkiak zuzenketa azkarrak egiteko, irteera estilizatua egiteko eta OCRrako edo inprimatzeko irudiak prestatzeko erabilgarriak dira. Kontraste, zorroztasun eta distira aldaketa txikiek efektu astunek baino gehiago axola dezakete irudiak testua edo xehetasun finak dituenean. + Gehitu iragazkiak banan-banan eta begiratu aldaketa bakoitzaren ondoren aurrebista. + Doitu distira, kontrastea, zorroztasuna, lausotasuna, kolorea edo maskarak zereginaren arabera. + Esportatu soilik aurrebista zure helburuko gailu, dokumentu edo plataforma sozialarekin bat datorrenean. + Aurreikusi lehenik + Ikuskatu irudiak editatzeko, bihurtzeko edo garbitzeko tresna astunagoa aukeratu aurretik + Egiaztatu benetan jaso duzuna + Irudiaren aurrebista erabilgarria da fitxategi bat txatetik, hodeiko biltegiratzetik edo beste aplikazio batetik datorrenean eta zer daukan ziur ez zaudenean. Neurriak, gardentasuna, orientazioa eta ikusizko kalitatea egiaztatzeak lehenik jarraipen-tresna egokia aukeratzen laguntzen du. + Ireki Irudiaren Aurrebista hainbat irudi ikuskatu behar dituzunean haiekin zer egin erabaki aurretik. + Bilatu biraketa-arazoak, eremu gardenak, konpresio-artefaktuak eta ustekabeko dimentsio handiak. + Mugitu Tamaina aldatu, Moztu, Konparatu, EXIF ​​edo Atzeko planoa kentzera zer konpondu behar den jakin ondoren. + Kendu edo leheneratu atzeko planoa + Ezabatu atzeko planoak automatikoki edo findu ertzak eskuz leheneratzeko tresnekin + Mantendu gaia, kendu gainerakoak + Atzeko planoa kentzeak ondoen funtzionatzen du hautaketa automatikoa eskuzko garbiketa zainduarekin konbinatzen duzunean. Azken esportazio formatuak maskarak bezainbeste axola du, JPEG-k eremu gardenak berdindu egingo dituelako aurrebista zuzena izan arren. + Hasi kentze automatikoarekin gaia atzealdetik argi bereizten bada. + Handitu eta aldatu ezabatu eta leheneratu batetik bestera ilea, bazterrak, itzalak eta xehetasun txikiak konpontzeko. + Gorde PNG edo WebP gisa azken fitxategian gardentasuna behar duzunean. + Marraztu, markatu eta ur-marka + Gehitu geziak, oharrak, nabarmenduak, sinadurak edo errepikatutako ur-markaren gainjartzeak + Gehitu ikusgai dagoen informazioa + Markatze-tresnek irudi bat azaltzen laguntzen dute, eta ur-markak esportatutako fitxategiak marka edo babesten laguntzen du. + Erabili Draw eskuz libreko oharrak, formak, nabarmenduak edo oharrak azkar behar dituzunean. + Erabili Ur-marka irteeretan testu- edo irudi-marka bera etengabe agertu behar denean. + Egiaztatu opakutasuna, posizioa eta eskala esportatu baino lehen, marka ikusgai egon dadin, baina arreta eragozteko. + Marraztu lehenetsiak + Ezarri lerroaren zabalera, kolorea, bide-modua, orientazio-blokeoa eta lupa marka azkarrago egiteko + Egin marrazkia aurreikusteko sentipena + Marraztu ezarpenak lagungarriak dira pantaila-argazkiak oharrak idazten dituzunean, dokumentuak markatzen dituzunean edo irudiak maiz sinatzen dituzunean. Lehenetsiek konfigurazio-denbora murrizten dute, eta lupak eta orientazio-blokeoak pantaila txikietan edizio zehatzak errazten dituzte. + Ezarri lerro-zabalera lehenetsia eta marraztu kolorea gezietarako, nabarmentzeko edo sinaduretarako gehien erabiltzen dituzun balioetara. + Erabili bide-modu lehenetsia trazu, forma edo marka mota berdinak marrazten dituzunean. + Gaitu lupa edo orientazio blokeoa hatzak sartzean xehetasun txikiak zehaztasunez jartzea zaila egiten denean. + Irudi anitzeko diseinuak + Aukeratu irudi anitzeko tresna egokia pantaila-argazkiak, eskaneatzea edo konparazio-zerrendak antolatu aurretik + Erabili diseinu-tresna egokia + Tresna hauek antzeko soinua dute, baina bakoitza irudi anitzeko irteera mota ezberdin baterako sintonizatuta dago. Lehenik eta behin egokia aukeratzeak beste emaitza baterako diseinatutako diseinu-kontrolen aurka borrokatzea saihesten du. + Erabili Collage Maker hainbat irudi konposizio bakarrean diseinatutako diseinuetarako. + Erabili Irudiak jostea edo pilatzea irudiak zerrenda bertikal edo horizontal luze bat bihurtu behar direnean. + Erabili Irudien zatiketa irudi handi bat hainbat zati txikiago bihurtu behar denean. + Bihurtu irudi formatuak + Sortu JPEG, PNG, WebP, AVIF, JXL eta beste kopiak pixelak eskuz editatu gabe + Aukeratu lanaren formatua + Formatu desberdinak hobeak dira argazkiak, pantaila-argazkiak, gardentasuna, konpresioa edo bateragarritasuna lortzeko. Bihurketa seguruena da helmugako aplikazioak zer onartzen duen eta iturburuak gorde nahi dituzun alfa, animazioa edo metadatuak dituen ulertzen duzunean. + Erabili JPEG argazki bateragarriak egiteko, PNG galerarik gabeko irudiak eta alfa eta WebP trinkoa partekatzeko. + Doitu kalitatea formatuak onartzen duenean; balio baxuagoek tamaina murrizten dute baina artefaktuak gehi ditzakete. + Mantendu jatorrizkoak bihurtutako fitxategiak helburu aplikazioan behar bezala irekita egiaztatu arte. + Egiaztatu EXIF ​​eta pribatutasuna + Ikusi, editatu edo kendu metadatuak argazki sentikorrak partekatu aurretik + Kontrolatu ezkutuko irudien datuak + EXIFek kameraren datuak, datak, kokapena, orientazioa eta irudian ikusten ez diren beste xehetasun batzuk izan ditzake. Merezi du partekatu publikoak, laguntza-txostenak, merkatu-zerrendak edo leku pribatu bat ager dezakeen edozein argazki baino lehen egiaztatzea. + Ireki EXIF ​​tresnak kokapena edo gailu pribatuaren informazioa izan dezaketen argazkiak partekatu aurretik. + Kendu eremu sentikorrak edo editatu etiketa okerrak, hala nola data, orientazioa, egilea edo kameraren datuak. + Gorde kopia berri bat eta partekatu kopia hori jatorrizkoaren ordez, pribatutasuna garrantzitsua denean. + EXIF garbiketa automatikoa + Kendu metadatuak lehenespenez, datak gorde behar diren erabaki bitartean + Ezarri pribatutasuna lehenetsia + EXIF ezarpenen taldea erabilgarria da pribatutasunaren garbiketa automatikoki egitea nahi duzunean, esportazio bakoitzean gogoratu beharrean. Mantendu data-ordua bereizitako erabakia da, datak erabilgarriak izan daitezkeelako ordenatzeko baina sentikorra partekatzeko. + Gaitu beti argi eta garbi EXIF ​​zure esportazio gehienak partekatze publiko edo erdi publikorako direnean. + Gaitu Gorde data-ordua harrapatzeko denbora gordetzea denbora-zigilu guztiak kentzea baino garrantzitsuagoa denean soilik. + Erabili eskuzko EXIF ​​edizioa eremu batzuk gorde eta beste batzuk kendu behar dituzun fitxategi bakanetarako. + Kontrolatu fitxategi-izenak + Erabili aurrizkiak, atzizkiak, denbora-zigiluak, aurrezarpenak, egiaztapen-bariak eta sekuentzia-zenbakiak + Egin esportazioak erraz aurkitzeko + Fitxategi-izen eredu on batek loteak ordenatzen laguntzen du eta ustekabeko gainidazketa saihesten du. Fitxategi-izenen ezarpenak bereziki erabilgarriak dira esportazioak iturburuko karpetara itzultzen direnean, non antzeko izenak nahasgarriak izan daitezkeen azkar. + Ezarri fitxategi-izenaren aurrizkia, atzizkia, denbora-zigilua, jatorrizko izena, aurrez ezarritako informazioa edo sekuentzia-aukerak Ezarpenak atalean. + Erabili sekuentzia-zenbakiak ordena garrantzitsuak diren loteetarako, hala nola orrialdeak, markoak edo collageak. + Gainitu gainidazketa uneko karpetarako lehendik dauden fitxategiak ordezkatzea segurua dela ziur zaudenean bakarrik. + Gainidatzi fitxategiak + Ordeztu irteerak bat datozen izenekin soilik zure lan-fluxua nahita suntsitzailea denean + Erabili arretaz gainidazteko modua + Gainidatzi fitxategiak izendapen-gatazkak nola kudeatzen diren aldatzen du. Karpeta berera errepikatu esportatzeko erabilgarria da, baina lehenagoko emaitzak ere ordezka ditzake zure fitxategi-izenaren ereduak izen bera berriro sortzen badu. + Gaitu gainidaztea sortutako fitxategi zaharragoak ordezkatzea espero eta berreskura daitekeen karpetetan soilik. + Mantendu gainidazketa desgaituta jatorrizkoak, bezero-fitxategiak, behin-behineko eskaneatzeak edo babeskopiarik gabe esportatzen dituzunean. + Konbinatu gainidazketa fitxategi-izen eredu batekin nahita, nahitako irteerak bakarrik ordezkatuko dira. + Fitxategi-izen ereduak + Konbinatu jatorrizko izenak, aurrizkiak, atzizkiak, denbora-zigiluak, aurrezarpenak, eskala modua eta kontrol-batuak + Eraiki irteera azaltzen duten izenak + Fitxategi-izenen ereduaren ezarpenek esportatutako fitxategiak gertatutakoaren historia erabilgarria bihur ditzakete. Honek loteka du garrantzia, karpeta-ikuspegia izan daitekeelako jatorrizko tamaina, aurrez ezarritako, formatua eta prozesatzeko ordena bereizteko leku bakarra. + Erabili jatorrizko fitxategi-izena, aurrizkia, atzizkia eta denbora-zigilua eskuz ordenatzeko izen irakurgarriak behar dituzunean. + Gehitu aurrez ezarritako, eskala modua, fitxategi-tamaina edo checksum informazioa irteerak nola sortu diren dokumentatu behar dutenean. + Saihestu ausazko izenak fitxategiak jatorrizkoekin edo orriaren ordenarekin parekatuta egon behar diren lan-fluxuetarako. + Aukeratu non gordeko diren fitxategiak + Saihestu esportazioak galtzea karpetak ezarriz, jatorrizko karpetak gordez eta behin betiko gordetzeko kokapenak + Mantendu irteerak aurreikusteko + Gorde jokabidea da garrantzitsuena fitxategi asko prozesatzen dituzunean edo hodeiko hornitzaileen fitxategiak partekatzen dituzunean. Karpeten ezarpenek erabakitzen dute irteerak leku ezagun batean bildu, jatorrizkoen ondoan agertzen diren edo aldi baterako beste leku batera joan zeregin zehatz baterako. + Ezarri gordetzeko karpeta lehenetsi bat esportazioak beti leku bakarrean nahi badituzu. + Erabili gorde-jatorrizko karpetan garbiketa-fluxuetarako, non irteerak iturburu-fitxategitik gertu egon behar duen. + Erabili behin-behineko gordetzeko kokapena zeregin bakar batek beste helmuga bat behar duenean zure lehenetsia aldatu gabe. + Behin betiko gordetzeko karpetak + Bidali hurrengo esportazioak aldi baterako karpeta batera zure ohiko helmuga aldatu gabe + Mantendu lan bereziak bereizita + Behin-behineko gordetzeko kokapena erabilgarria da zure ohiko Image Toolbox karpetarekin nahasi behar ez diren aldi baterako proiektuetarako, bezero-karpetetarako, garbiketa-saioetarako edo esportazioetarako. Iraupen laburreko helmuga ematen du zure konfigurazio lehenetsia berridatzi gabe. + Aukeratu behin-behineko karpeta bat zure esportazio-kokapen arruntetik kanpo dagoen zeregin bat hasi aurretik. + Erabili proiektu laburrak, partekatutako karpetak edo sortak, non irteerak elkarrekin taldekatuta egon behar duten. + Lanaren ondoren, itzuli lehenetsitako karpetara, gero esportazioak ustekabean aldi baterako kokapenera ez gera daitezen. + Orekatu kalitatea eta fitxategiaren tamaina + Ulertu dimentsioak, formatuak edo kalitateak noiz aldatu behar diren asmatu beharrean + Txikitu fitxategiak nahita + Fitxategiaren tamaina irudiaren dimentsioen, formatuaren, kalitatearen, gardentasunaren eta edukiaren konplexutasunaren araberakoa da. Fitxategi bat oraindik astunegia bada, dimentsioak aldatzeak kalitatea behin eta berriz jaisten baino gehiago laguntzen du normalean. + Lehenik eta behin, tamaina aldatu irudia helmugak bistaratu dezakeena baino handiagoa denean. + Kalitate baxuagoa formatu galeretan soilik eta egiaztatu testua, ertzak, gradienteak eta aurpegiak esportatu ondoren. + Aldatu formatua kalitate-aldaketak nahikoa ez direnean, adibidez WebP partekatzeko edo PNG aktibo garden garbietarako. + Animaziozko formatuekin lan egin + Erabili GIF, APNG, WebP eta JXL tresnak fitxategi batek markoak dituenean bit-mapa bat izan beharrean + Ez tratatu irudi guztiak geldi gisa + Formatu animatuek fotogramak, iraupena eta bateragarritasuna ulertzen dituzten tresnak behar dituzte. + Erabili GIF edo APNG tresnak formatu horietarako fotograma-mailako eragiketak behar dituzunean. + Erabili WebP tresnak konpresio modernoa edo WebP irteera animatua behar duzunean. + Aurreikusi animazioaren denbora partekatu aurretik, plataforma batzuek irudi animatuak bihurtzen edo berdindu egiten dituztelako. + Alderatu eta aurrebista irteera + Ikuskatu aldaketak, fitxategien tamaina eta ikusizko desberdintasunak esportazio bat mantendu aurretik + Egiaztatu partekatu aurretik + Konparazio tresnek konpresio-artefaktuak, kolore okerrak edo nahi ez diren aldaketak atzematen laguntzen dute. + Ireki Konparatu bi bertsio elkarren ondoan ikuskatu nahi dituzunean. + Handitu ertzak, testua, gradienteak eta eskualde gardenak non arazoak errazen galtzen diren. + Irteera astunegia edo lausoegia bada, itzuli aurreko tresnara eta egokitu kalitatea edo formatua. + Erabili PDF tresnen zentroa + Batu, zatitu, biratu, kendu orriak, konprimitu, babestu, OCR eta ur-marka PDF fitxategiak + Aukeratu PDF eragiketa bat + PDF zentroak dokumentu-ekintzak taldekatzen ditu sarrera-puntu baten atzean, eragiketa zehatza hauta dezazun. Hasi fitxategia dagoeneko PDF bat denean, eta erabili Irudiak PDFra edo Dokumentuen eskanerra zure iturburua oraindik irudi multzo bat denean. + Ireki PDF Tools eta aukeratu zure helburuarekin bat datorren eragiketa. + Hautatu iturburuko PDFa edo irudiak, eta berrikusi orriaren ordena, biraketa, babesa edo OCR aukerak. + Gorde sortutako PDFa eta ireki behin orrialde kopurua, ordena eta irakurgarritasuna berresteko. + Bihurtu irudiak PDF batean + Konbinatu eskaneatzea, pantaila-argazkiak, ordainagiriak edo argazkiak parteka daitekeen dokumentu batean + Eraiki dokumentu garbi bat + Irudiak PDF orri hobeak bihurtzen dira koherentziaz moztu, ordenatu eta tamainan jartzen direnean. Tratatu irudi-zerrenda orri-pila bat bezala: biraketa, marjina eta orrialde-tamainaren aukera bakoitzak eragina du azken dokumentua nola irakurtzen den. + Moztu edo biratu irudiak lehenik orriaren ertzak, itzalak edo orientazioa okerrak direnean. + Hautatu irudiak nahi den orriaren ordenan eta egokitu orriaren tamaina edo marjinak tresnak eskaintzen baditu. + Esportatu PDFa, eta egiaztatu orrialde guztiak irakur daitezkeela bidali aurretik. + Eskaneatu dokumentuak + Hartu paperezko orriak eta prestatu PDF, OCR edo partekatzeko + Harrapatu irakur daitezkeen orrialdeak + Dokumentuen eskaneatzea hobe da orri lauekin, argiztapen onarekin eta perspektiba zuzenduarekin. OCR edo PDF esportatu aurretik eskaneatu garbi batek denbora aurrezten du, testuaren ezagutza eta konpresioa orriaren kalitatearen araberakoak direlako. + Jarri dokumentua nahikoa argi eta itzal gutxiko kontraste-gainazal batean. + Doitu detektatutako ertzak edo moztu eskuz orrialdeak markoa garbi bete dezan. + Esportatu irudi edo PDF gisa, gero exekutatu OCR testu hautagarria behar baduzu. + Babestu edo desblokeatu PDF fitxategiak + Erabili pasahitzak kontu handiz eta gorde iturburu kopia editagarria ahal denean + Kudeatu PDF sarbidea modu seguruan + Babes-tresnak erabilgarriak dira kontrolatuta partekatzeko, baina pasahitzak erraz galtzen dira eta zailak dira berreskuratzen. Babestu kopiak banatzeko, gorde babesik gabeko iturburu-fitxategiak bereizita eta egiaztatu emaitza ezer ezabatu aurretik. + Babestu PDFaren kopia bat, ez zure iturri-dokumentu bakarra. + Gorde pasahitza leku seguru batean babestutako fitxategia partekatu aurretik. + Erabili desblokeatzea editatzeko baimena daukazun fitxategietarako soilik, eta egiaztatu desblokeatutako kopia behar bezala irekitzen dela. + Antolatu PDF orriak + Batu, zatitu, berrantolatu, biratu, moztu, kendu orriak eta gehitu orrialde-zenbakiak nahita + Konpondu dokumentuaren egitura apaindu aurretik + PDF orrirako tresnak paperezko dokumentu bat prestatuko zenukeen ordena berean erabiltzeko errazenak dira: bildu orriak, kendu okerrak, ordenatu, orientatu, eta, ondoren, gehitu azken xehetasunak, hala nola orrialde zenbakiak edo ur-markak. + Erabili lehenik bateratzea edo irudiak PDFra dokumentua hainbat fitxategitan banatuta dagoenean. + Berrantolatu, biratu, moztu edo kendu orriak orrialde zenbakiak, sinadurak edo ur-markak gehitu aurretik. + Aurreikusi azken PDFa egitura-edizioen ondoren, falta den edo biratu den orri bat geroago antzematea zaila izan daitekeelako. + PDF oharrak + Kendu oharrak edo berdindu ikusleek iruzkinak eta markak modu ezberdinean erakusten dituztenean + Kontrolatu ikusgai geratzen dena + Oharpenak iruzkinak, nabarmenduak, marrazkiak, inprimaki-markak edo bestelako PDF objektu gehigarriak izan daitezke. Ikusle batzuek modu ezberdinean bistaratzen dituzte, beraz, kendu edo berdinduz gero, partekatutako dokumentuak aurreikusgarriagoak izan daitezke. + Kendu oharrak iruzkinak, nabarmenduak edo berrikuspen-markak partekatutako kopian sartu behar ez direnean. + Laundu ikusgai dauden markak orrialdean geratu behar direnean, baina ez dute jada oharpen editagarri gisa jokatzen. + Gorde jatorrizko kopia bat, oharrak kentzea edo berdintzea zaila izan daitekeelako atzera egitea. + Testua ezagutu + Atera testua irudietatik OCR motor eta hizkuntza hautagarriekin + Lortu OCR emaitza hobeak + OCR zehaztasuna irudiaren zorroztasunaren, hizkuntzaren datuen, kontrastearen eta orriaren orientazioaren araberakoa da. Emaitza onenak normalean irudia lehenik prestatzetik etortzen dira: zarata moztu, zutik biratu, irakurgarritasuna areagotu, gero testua ezagutu. + Moztu irudia testuaren eremura eta biratu zutik ezagutu aurretik. + Aukeratu hizkuntza egokia eta deskargatu hizkuntza datuak aplikazioak hala eskatzen badu. + Kopiatu edo partekatu aitortutako testua, gero eskuz egiaztatu izenak, zenbakiak eta puntuazioa. + Aukeratu OCR hizkuntzako datuak + Hobetu ezagupena gidoiak, hizkuntzak eta deskargatutako OCR ereduak lotuz + Lotu OCR testuarekin + OCR hizkuntza okerrak argazki onak errekonozimendu txarra dirudi. Hizkuntza-datuek garrantzi handia dute gidoietarako, puntuazioetarako, zenbakietarako eta dokumentu mistoetarako; beraz, aukeratu irudian agertzen den hizkuntza telefonoaren interfazearen hizkuntza baino. + Aukeratu irudian agertzen den hizkuntza edo gidoia, ez telefonoaren hizkuntza bakarrik. + Deskargatu falta diren datuak lineaz kanpo lan egin edo lote bat prozesatu aurretik. + Hizkuntza mistoko dokumentuetarako, exekutatu hizkuntza garrantzitsuena lehenik eta eskuz egiaztatu izenak eta zenbakiak. + Bila daitezkeen PDFak + Idatzi berriro OCR testua fitxategietan, eskaneatutako orriak bilatu eta kopiatu ahal izateko + Bihurtu eskaneatzea dokumentu bilagarriak + OCR-k testua arbelean kopiatu baino gehiago egin dezake. Fitxategi batean edo bilatu daitekeen PDF batean ezagutzen duzun testua idazten duzunean, orriaren irudia ikusgai geratzen da testua bilatzeko, hautatzeko, indexatzeko edo artxibatzeko errazagoa den bitartean. + Erabili bilaketak egiteko PDF irteera oraindik jatorrizko orrien itxura izan behar duten eskaneatutako dokumentuetarako. + Erabili idatzi-fitxategira ateratako testua soilik behar duzunean eta orriaren irudia axola ez zaizunean. + Egiaztatu zenbaki, izen eta taula garrantzitsuak eskuz, OCR testua bilatu daitekeelako baina oraindik inperfektua izan daitekeelako. + Eskaneatu QR kodeak + Irakurri QR edukia kameraren sarreratik edo gordetako irudiak erabilgarri daudenean + Irakurri kodeak segurtasunez + QR kodeak estekak, Wi-Fi datuak, kontaktuak, testuak edo bestelako karga izan ditzake. + Ireki Eskaneatu QR kodea eta mantendu kodea zorrotz, laua eta guztiz markoaren barruan. + Berrikusi deskodetutako edukia estekak ireki edo datu sentikorrak kopiatu aurretik. + Eskaneatzeak huts egiten badu, hobetu argiztapena, moztu kodea edo probatu bereizmen handiagoko iturburu-irudia. + Erabili Base64 eta checksums + Kodetu irudiak testu gisa, deskodetu testua irudietara eta egiaztatu fitxategiaren osotasuna + Mugitu fitxategien eta testuen artean + Base64 erabilgarria da irudien karga txikietarako, kontrol-sumek fitxategiak aldatu ez direla egiaztatzen laguntzen duten bitartean. Tresna hauek lagungarrienak dira lan-fluxu teknikoetan, laguntza-txostenetan, fitxategien transferentzian eta fitxategi bitar testu bihurtu behar diren lekuetan. + Erabili Base64 tresnak irudi txiki bat kodetzeko lan-fluxu batek fitxategi bitar baten ordez testua behar duenean. + Deskodetu Base64 iturri fidagarrietatik soilik eta egiaztatu aurrebista gorde aurretik. + Erabili checksum-tresnak esportatutako fitxategiak alderatu behar dituzunean edo karga/deskarga bat baieztatu behar duzunean. + Datuak paketatu edo babestu + Erabili ZIP eta zifratzeko tresnak fitxategiak lotzeko edo testu-datuak eraldatzeko + Mantendu erlazionatutako fitxategiak elkarrekin + Paketatze-tresnak lagungarriak dira hainbat irteera fitxategi bakar gisa bidaiatu behar direnean. Sortutako irudiak, PDFak, erregistroak eta metadatuak elkarrekin mantentzeko ere egokiak dira emaitza multzo osoa bidali behar duzunean. + Erabili ZIP esportatutako irudi edo dokumentu asko batera bidali behar dituzunean. + Erabili zifratzeko tresnak hautatutako metodoa ulertzen duzunean eta beharrezko gakoak modu seguruan gorde ditzakezunean soilik. + Probatu sortutako artxiboa edo eraldatutako testua iturburu-fitxategiak ezabatu aurretik. + Checksumak izenetan + Erabili checksum-oinarritutako fitxategi-izenak unikortasuna eta osotasuna irakurgarritasuna baino garrantzitsuago denean + Izendatu fitxategiak edukiaren arabera + Checksum fitxategi-izenak erabilgarriak dira esportazioek izen ikusgai bikoiztuak izan ditzaketenean edo bi fitxategi berdinak direla frogatu behar duzunean. Eskuzko arakatzerako ez dira hain atseginak, beraz, erabili artxibo teknikoetarako eta egiaztapen-fluxuetarako. + Gaitu checksum fitxategi-izen gisa edukiaren identitatea gizakiek irakur daitekeen izenburua baino garrantzitsuagoa denean. + Erabili artxiboetarako, desbikoizketarako, esportazio errepikagarrietarako edo berriro kargatu eta deskargatu daitezkeen fitxategietarako. + Parekatu checksum izenak karpetekin edo metadatuekin jendeak oraindik fitxategiak zer adierazten duen ulertu behar duenean. + Aukeratu koloreak irudietatik + Lagin pixelak, kopiatu kolore-kodeak eta berrerabili koloreak paletetan edo diseinuetan + Probatu kolore zehatza + Kolorea hautatzea erabilgarria da diseinu batekin bat etortzeko, markaren koloreak ateratzeko edo irudien balioak egiaztatzeko. Zoomak garrantzi handia du antialiasak, itzalak eta konpresioak aldameneko pixelak espero duzun koloretik apur bat desberdinak izan ditzaketelako. + Ireki Aukeratu iruditik kolorea eta aukeratu behar duzun kolorea duen iturri-irudia. + Handiagotu xehetasun txikiak lagin aurretik, gertuko pixelek emaitzan eraginik izan ez dezaten. + Kopiatu kolorea helmugak eskatzen duen formatuan, hala nola HEX, RGB edo HSV. + Sortu paletak eta erabili kolore liburutegia + Atera paletak, arakatu gordetako koloreak eta mantendu ikus-sistema koherenteak + Eraiki berrerabil daitekeen paleta bat + Paletek esportatutako grafikoak, ur-markak eta marrazkiak ikusmen koherenteak izaten laguntzen dute. Batez ere erabilgarriak dira pantaila-argazkiak behin eta berriz ohartarazten dituzunean, markaren aktiboak prestatzen dituzunean edo hainbat tresnatan kolore berdinak behar dituzunean. + Sortu paleta bat irudi batetik edo gehitu koloreak eskuz balioak ezagutzen dituzunean. + Izena edo antolatu kolore garrantzitsuak gero berriro aurki ditzazun. + Erabili kopiatutako balioak Draw, ur-marka, gradiente, SVG edo kanpoko diseinu tresnetan. + Sortu gradienteak + Eraiki irudi gradienteak atzeko planoetarako, leku-marketarako eta ikusizko esperimentuetarako + Kontrolatu koloreen trantsizioak + Gradiente-tresnak erabilgarriak dira lehendik dagoen bat editatu beharrean sortutako irudi bat behar duzunean. Hondo garbiak, leku-markak, horma-paperak, maskarak edo aktibo soilak izan daitezke kanpoko diseinu-tresnetarako. + Aukeratu gradiente mota eta ezarri kolore garrantzitsuak lehenik. + Doitu norabidea, geldialdiak, leuntasuna eta irteera-tamaina helburuko erabilera-kasurako. + Esportatu helmugarekin bat datorren formatuan, normalean PNG sortutako grafiko garbietarako. + Koloreen irisgarritasuna + Erabili kolore dinamikoak, kontrastea eta daltonikoak aukerak UI irakurtzen zaila denean + Egin koloreak errazago erabiltzeko + Kolore-ezarpenek erosotasunari, irakurgarritasunari eta kontrolak eskaneatu ditzakezun azkartasunari eragiten diote. Tresna-txipak, graduatzaileak edo hautatutako egoerak bereizteko zailak iruditzen bazaizkio, gaia eta irisgarritasun-aukerak erabilgarriagoak izan daitezke modu iluna aldatzea baino. + Saiatu kolore dinamikoak aplikazioak uneko horma-paper-paleta jarraitzea nahi duzunean. + Gehitu kontrastea edo aldatu eskema botoiak eta gainazalak nahikoa nabarmentzen ez direnean. + Erabili daltonikoen eskema aukerak egoera edo azentu batzuk bereizten zailak badira. + Alfa atzeko planoa + Kontrolatu PNG, WebP eta antzeko irteera gardenetan erabiltzen den aurrebista edo betetze-kolorea + Aurrebista gardenak ulertu + Alfa formatuetarako atzeko planoko koloreak irudi gardenak erraztu ditzake ikuskatzea, baina garrantzitsua da aurrebista/betetze portaera bat aldatzen ari zaren edo azken emaitza berdintzen ari zaren jakitea. Horrek garrantzia du eranskailu, logotipo eta atzeko planoan kendutako irudietarako. + Erabili alfa onartzen duen formatu bat lehenik, PNG edo WebP adibidez, pixel gardenek esportaziotik iraun behar dutenean. + Doitu atzeko planoaren kolorearen ezarpenak ertz gardenak aplikazioaren gainazalean ikusten zailak direnean. + Esportatu proba txiki bat eta ireki berriro beste aplikazio batean gardentasuna edo aukeratutako betegarria gorde den baieztatzeko. + AI ereduaren aukeraketa + Aukeratu modelo bat irudi motaren eta akatsaren arabera, lehenik handiena aukeratu beharrean + Lotu eredua kaltearekin + AI Tools-ek ONNX/ORT eredu desberdinak deskargatu edo inporta ditzakete, eta eredu bakoitza arazo mota jakin baterako trebatuta dago. JPEG blokeetarako, anime-lerroen garbiketa, zarata kentzea, atzeko planoa kentzea, koloreztatzea edo eskalatzeko ereduak emaitza okerragoak sor ditzake irudi mota okerrean erabiltzen denean. + Irakurri ereduaren deskribapena deskargatu aurretik; aukeratu zure benetako arazoa izendatzen duen eredua, hala nola konpresioa, zarata, tonu erdiak, lausotzea edo atzeko planoa kentzea. + Hasi eredu txikiago edo zehatzago batekin irudi-mota berri bat probatzerakoan, gero konparatu eredu astunekin emaitza nahikoa ona ez bada. + Gorde benetan erabiltzen dituzun ereduak soilik, deskargatutako eta inportatutako modeloek biltegiratze-lekua nabarmena har dezaketelako. + Familia ereduak ulertzea + Ereduen izenek maiz iradokitzen dute beren helburua: RealESRGAN estiloko modeloek goi mailakoa, SCUNet eta FBCNN modeloek zarata edo konpresioa garbitzen dute, atzeko planoko modeloek maskarak sortzen dituzte eta koloreztatzaileek kolorea gehitzen diote gris-eskala sarrerari. Inportatutako eredu pertsonalizatuak indartsuak izan daitezke, baina esperotako sarrera/irteera formarekin bat etorri beharko lukete eta onartutako ONNX edo ORT formatua erabili. + Erabili eskalatzaileak pixel gehiago behar dituzunean, denoiser-ak irudia pikortsua denean eta artefaktuak kentzaileak konpresio-blokeak edo bandak direnean arazo nagusia. + Erabili atzeko planoko ereduak gaiak bereizteko soilik; ez dira objektu orokorrak kentzea edo irudia hobetzea bezalakoak. + Inportatu eredu pertsonalizatuak konfiantzazko iturrietatik soilik eta probatu irudi txiki batean fitxategi garrantzitsuak erabili aurretik. + AI parametroak + Sintonizatu zatien tamaina, gainjartzea eta langile paraleloak kalitatea, abiadura eta memoria orekatzeko + Orekatu abiadura egonkortasunarekin + Chunk tamainak kontrolatzen du zein handiak diren irudi piezak ereduak prozesatu aurretik. Gainjartzeak ondoko zatiak nahasten ditu ertzak ezkutatzeko. Langile paraleloek prozesatzea bizkortu dezakete, baina langile bakoitzak memoria behar du, beraz, balio handiek irudi handiak ezegonkor bihur ditzakete RAM mugatua duten telefonoetan. + Erabili zati handiagoak testuinguru leunagoa izateko zure gailuak eredua modu fidagarrian maneiatzen duenean eta irudia handia ez denean. + Handitu gainjartzea zatien ertzak ikusten direnean, baina gogoratu gainjartzea gehiago errepikatu egiten dela lan. + Altxatu langile paraleloak proba egonkor baten ondoren bakarrik; prozesatzea moteltzen bada, gailua berotzen badu edo huts egiten badu, murriztu langileak lehenik. + Saihestu piezaren artefaktuak + Chunking-ek aplikazioari ereduak eroso kudeatu ditzakeen irudiak aldi berean prozesatzen uzten dio. Konpromisoa da fitxa bakoitzak testuinguru gutxiago duela, eta, beraz, gainjartze baxuak edo zati txikiegiek ertz ikusgaiak, ehundura aldaketak edo xehetasun ez-koherenteak sor ditzakete ondoko eremuen artean. + Handitu gainjartzea ertz angeluzuzenak, ehundura-aldaketak edo xehetasun-jauziak ikusten badituzu prozesatutako eskualdeen artean. + Murriztu zatiaren tamaina prozesuak huts egiten badu irteera sortu aurretik, batez ere irudi handietan edo modelo astunetan. + Gorde mozketa proba txiki bat irudi osoa prozesatu aurretik, parametroak azkar alderatu ahal izateko. + AI egonkortasuna + Txikitu zatien tamaina, langileak eta iturburu-bereizmena AI prozesatzeak huts egiten duenean edo izozten denean + Berreskuratu AI lan astunetatik + AI prozesatzea aplikazioko lan-fluxurik astunenetako bat da. Hutsegiteek, huts egindako saioek, izozteek edo bat-bateko aplikazioak berrabiarazten dituztenek, normalean, eredua, iturburuaren bereizmena, zatien tamaina edo langile paraleloen kopurua zorrotzegia da gailuaren uneko egoerarako. + Aplikazioak huts egiten badu edo saio eredu bat irekitzen ez badu, murriztu langile paraleloak eta zatiaren tamaina, eta saiatu berriro irudi berdinarekin. + Aldatu irudi handiak AI prozesatu aurretik, helburuak jatorrizko bereizmena behar ez duenean. + Itxi beste aplikazio astunak, erabili eredu txikiagoa edo prozesatu fitxategi gutxiago aldi berean memoriaren presioa itzultzen doanean. + Ereduaren hutsegiteak diagnostikatzea + AI exekutatu huts batek ez du beti esan nahi irudia txarra denik. Baliteke eredu-fitxategia osatu gabe egotea, onartzen ez izatea, memoriarako handiegia edo eragiketarekin bat ez datorrena. Aplikazioak huts egindako saio baten berri ematen duenean, trata ezazu seinale gisa eredua eta parametroak sinplifikatzeko lehenik. + Ezabatu eta deskargatu berriro eredu bat deskargatu eta berehala huts egiten badu edo fitxategia hondatuta egongo balitz bezala jokatzen badu. + Saiatu deskarga daitekeen eredu integratua inportatutako eredu pertsonalizatu bati errua bota aurretik, inportatutako modeloek sarrera bateraezinak izan ditzaketelako. + Erabili Aplikazioen Erregistroak hutsegitea erreproduzitu ondoren, hutsegite edo saioko errore baten berri eman behar baduzu. + Probatu Shader Studio-n + Erabili GPU itzala-efektuak irudien prozesamendu aurreratuetarako eta itxura pertsonalizatuetarako + Lan egin arretaz itzalgailuekin + Shader Studio indartsua da, baina itzaltze-kodeak eta parametroek aldaketa txikiak eta probagarriak behar dituzte. Trata ezazu tresna esperimental bat bezala: mantendu lan-oinarri bat, aldatu gauza bat aldi berean eta probatu irudi-tamaina ezberdinekin aurrez ezarritako batekin fidatu aurretik. + Hasi parametroak gehitu aurretik lan egiten duen itzalgailu ezagun batetik edo efektu soil batetik. + Aldatu parametro edo kode bloke bat aldi berean eta egiaztatu aurrebista akatsik dagoen. + Esportatu aurrezarpenak irudi-tamaina desberdin batzuetan probatu ondoren soilik. + Egin SVG edo ASCII artea + Sortu bektore-itxurako aktiboak edo testuan oinarritutako irudiak sormenerako + Aukeratu sormen-irteera mota + SVG eta ASCII tresnek helburu ezberdinetarako balio dute: grafiko eskalagarriak edo testu estiloko errendatzea. Biak sormenezko bihurketak dira, beraz, azken tamainan aurreikustea garrantzitsuagoa da emaitza txiki-txiki batetik epaitzea baino. + Erabili SVG Maker emaitza garbi eskalatu behar denean edo diseinu-fluxuetan berrerabili behar denean. + Erabili ASCII Art irudi baten testu-irudikapen estilizatua nahi duzunean. + Aurrebista azken tamainan, sortutako irteeran xehetasun txikiak desager daitezkeelako. + Sortu zarata ehundurak + Sortu prozedurazko testurak atzeko planoetarako, maskaretarako, gainjartzeko edo esperimentuetarako + Sintonizatu irteera prozedura + Zarata sortzeak irudi berriak sortzen ditu parametroetatik, beraz, aldaketa txikiek emaitza oso desberdinak sor ditzakete. Ehundurak, maskarak, gainjartzeak, leku-markak edo eredu zehatzekin konpresioa probatzeko erabilgarria da. + Aukeratu zarata-mota eta irteera-tamaina xehetasunak doitzeko aurretik. + Doitu eskala, intentsitatea, koloreak edo hazia eredua xede erabilerara egokitzen den arte. + Gorde emaitza erabilgarriak eta idatzi parametroak geroago testura birsortu behar baduzu. + Markatu proiektuak + Erabili proiektu-fitxategiak oharrak aplikazioa itxi ondoren editagarriak izan behar direnean + Mantendu geruzatutako aldaketak berrerabili + Markatu proiektuaren fitxategiak erabilgarriak dira pantaila-argazkiak, diagramak edo argibide-irudi batek geroago aldaketak behar dituenean. Esportatutako PNG/JPEG fitxategiak azken irudiak dira, eta proiektuaren fitxategiek, berriz, geruza editagarriak eta edizio-egoera gordetzen dituzte etorkizuneko doikuntzak egiteko. + Erabili markatze-geruzak oharrak, deialdiak edo diseinu-elementuak editagarriak izaten jarraitu behar dutenean. + Gorde edo ireki berriro proiektuaren fitxategia azken irudi bat esportatu aurretik, berrikuspen gehiago espero badituzu. + Esportatu irudi normal bat azken bertsio berdindu bat partekatzeko prest zaudenean soilik. + Fitxategiak enkriptatu + Babestu fitxategiak pasahitz batekin eta probatu deszifratzea edozein iturburuko kopia ezabatu aurretik + Kudeatu enkriptatutako irteerak arretaz + Zifratze-tresnek fitxategiak babestu ditzakete pasahitzetan oinarritutako enkriptatzearekin, baina ez dira gakoa gogoratzeko edo babeskopiak gordetzeko ordezkoak. Enkriptatzea erabilgarria da garraiatzeko eta biltegiratzeko, eta ZIP, berriz, hobea da helburu nagusia hainbat irteera biltzea denean. + Zifratu kopia bat lehenik eta gorde jatorrizkoa deszifratzeak gordetako pasahitzarekin funtzionatzen duela probatu arte. + Erabili pasahitz-kudeatzailea edo beste leku seguru bat giltzarako; aplikazioak ezin dizu galdutako pasahitz bat asmatu. + Partekatu enkriptatutako fitxategiak pasahitza duten pertsonekin soilik eta zein tresna edo metodo deszifratu behar dituen ezagutzen duten pertsonekin. + Tresnak antolatu + Taldekatu, ordenatu, bilatu eta ainguratu tresnak, pantaila nagusia zure benetako lan-fluxua bat etor dadin + Egin pantaila nagusia azkarrago + Tresnaren antolaketa-ezarpenak doitzea merezi du gehien erabiltzen dituzun tresnak zeintzuk diren jakitean. Taldekatzeak esplorazioa laguntzen du, ordena pertsonalizatuak muskulu-memoria laguntzen du eta gogokoek eguneroko tresnak eskura mantentzen dituzte zerrenda osoa handia izan arren. + Erabili taldekatu modua aplikazioa ikasten duzun bitartean edo zeregin motaren arabera antolatutako tresnak nahiago dituzunean. + Aldatu ordena pertsonalizatura zure ohiko tresnak ezagutzen dituzunean eta korritu gutxiago nahi dituzunean. + Erabili bilaketa-ezarpenak eta gogokoak elkarrekin izenez gogoratzen dituzun baina denbora guztian ikusgai ez dituzun tresna arraroetarako. + Kudeatu fitxategi handiak + Saihestu esportazio motelak, memoria-presioa eta ustekabeko irteera handiak + Murriztu lana esportatu aurretik + Irudi oso handiek, sorta luzeek eta kalitate handiko formatuek memoria eta denbora gehiago hartu dezakete. Konponketarik azkarrena normalean lan kopurua murriztea da eragiketarik astunena baino lehen, tamaina handiko sarrera bera bukatzeko itxaron gabe. + Aldatu irudi handiak iragazki astunak, OCR edo PDF bihurketa aplikatu aurretik. + Erabili lote txikiagoa lehendabizi ezarpenak berresteko, eta gero prozesatu gainerakoa aurrez ezarritako berarekin. + Kalitate baxuagoa edo formatua aldatu irteerako fitxategia espero baino handiagoa denean. + Cachea eta aurrebistak + Ulertu saio astunen ondoren sortutako aurrebistak, cache-garbiketa eta biltegiratzeko erabilera + Mantendu biltegiratzea eta abiadura orekatua + Aurrebista-sorkuntzak eta cache-ak behin eta berriz nabigazioa azkarrago egin dezake, baina edizio-saio astunek aldi baterako datuak atzean utzi ditzakete. Cachearen ezarpenek aplikazioa motel sentitzen denean, biltegiratzea estua denean edo aurrebistak zaharkituta ikusten direnean esperimentu askoren ondoren. + Irudi asko arakatzean aurrebistak gaituta edukitzea garrantzitsuagoa da aldi baterako biltegiratzea gutxitzea baino. + Garbitu cachea lote handien, huts egindako esperimentuen edo bereizmen handiko fitxategi askoren saio luzeen ondoren. + Erabili cachearen garbiketa automatikoa nahiago baduzu biltegiratze garbiketa abiarazteko aurrebistak bero mantentzea baino. + Doitu pantailaren portaera + Erabili pantaila osoko, modu segurua, distira eta sistema-barra aukerak bideratu lan egiteko + Egin interfazea egoerara egokitzeko + Pantailaren ezarpenek paisaian editatzen, irudi pribatuak aurkezten edo distira koherentea behar duzunean laguntzen dute. Ez dira beharrezkoak ohiko erabilerarako, baina egoera deserosoak konpontzen dituzte, hala nola, QR ikuskapena, aurrebista pribatuak edo paisaia editatzea. + Erabili pantaila osoko edo sistemako barraren ezarpenak espazioa editatzea nabigazio Chrome baino garrantzitsuagoa denean. + Erabili modu segurua pantaila-argazkietan edo aplikazioen aurrebistan irudi pribatuak agertu behar ez direnean. + Erabili distira betearaztea irudi ilunak edo QR kodeak ikuskatzea zaila den tresnetarako. + Mantendu gardentasuna + Saihestu hondo gardenak zuri, beltz edo berdindu ez daitezen + Erabili alfa-jakintzen den irteera + Gardentasunak bizirik iraungo du hautatutako tresnak eta irteerako formatuak alfa onartzen dutenean. Esportatutako atzeko planoa zuria edo beltza bihurtzen bada, arazoa normalean irteera-formatua, betetze/atzealdearen ezarpena edo irudia berdindu duen helmuga-aplikazioa izan ohi da. + Aukeratu PNG edo WebP atzeko planoan kendutako irudiak, eranskailuak, logotipoak edo gainjartzeak esportatzerakoan. + Saihestu JPEG irteera gardenerako, ez baitu alfa kanalik gordetzen. + Egiaztatu atzeko planoko kolorearekin erlazionatutako ezarpenak alfa formatuetarako aurrebistak atzeko planoa erakusten badu. + Konpondu partekatzeko eta inportatzeko arazoak + Erabili hautatzailearen ezarpenak eta onartzen diren formatuak fitxategiak behar bezala agertzen edo irekitzen ez direnean + Sartu fitxategia aplikazioan + Inportazio-arazoak hornitzaileen baimenek, onartzen ez diren formatuek edo hautatzailearen portaerek sortzen dituzte sarritan. Hodeiko aplikazioetatik eta mezularietatik partekatutako fitxategiak aldi baterakoak izan daitezke, beraz, iturri iraunkor bat hautatzea fidagarriagoa izan daiteke aldaketa luzeagoetarako. + Saiatu fitxategia irekitzen sistema-hautatzailearen bidez, azken fitxategien lasterbide baten ordez. + Formatu bat tresna batek onartzen ez badu, lehenik eta behin bihurtu edo ireki tresna zehatzago bat. + Berrikusi irudi-hautatzailearen eta abiarazlearen ezarpenak, partekatze-fluxuek edo zuzeneko tresnaren irekierak espero baino desberdin jokatzen badute. + Irteera berrespena + Saihestu gorde gabeko aldaketak galdu keinuak, atzera sakatzeak edo tresna-aldaketak ustekabean gertatzen direnean + Babestu amaitu gabeko lanak + Tresnaren irteeraren berrespena lagungarria da keinu bidezko nabigazioa erabiltzen duzunean, fitxategi handiak editatzen dituzunean edo askotan aplikazio batetik bestera aldatzean. Tresna bat utzi aurretik etenaldi txiki bat gehitzen du, amaitu gabeko ezarpenak eta aurrebistak ustekabean gal ez daitezen. + Gaitu berrespena, ustekabeko atzera keinuek tresna bat itxi badute esportatu aurretik. + Mantendu desgaituta urrats bakarreko bihurketa azkarrak egiten badituzu eta nabigazio azkarrago nahiago baduzu. + Erabili aurrezarpenekin batera lan-fluxu luzeagoetarako, non berreraikitze ezarpenak gogaikarriak izango liratekeen. + Erabili erregistroak arazoen berri emateko + Bildu xehetasun erabilgarriak arazo bat ireki aurretik edo garatzailearekin harremanetan jarri aurretik + Testuinguruarekin lotutako arazoak salatu + Urratsak, aplikazioaren bertsioa, sarrera mota eta erregistroak dituen txosten argia askoz errazagoa da diagnostikatzeko. Erregistroak arazo bat erreproduzitu ondoren erabilgarriak dira, inguruko testuingurua fresko mantentzen dutelako. + Kontuan izan tresnaren izena, ekintza zehatza eta fitxategi batekin edo fitxategi guztietan gertatzen den. + Ireki Aplikazioen erregistroak arazoa erreproduzitu ondoren eta partekatu dagokion erregistro-irteera posible denean. + Erantsi pantaila-argazkiak edo lagin-fitxategiak informazio pribaturik ez dutenean soilik. + Atzeko planoko prozesamendua gelditu da + Android-ek ez zuen atzeko planoko prozesatzeko jakinarazpena garaiz hasten utzi. Hau sistemaren denbora-muga bat da, modu fidagarrian konpondu ezin dena. Berrabiarazi aplikazioa eta saiatu berriro; aplikazioa irekita mantentzeak eta jakinarazpenak edo atzeko planoko lanak baimentzea lagun dezake. + Saltatu fitxategiak hautatzea + Ireki tresnak azkarrago sarrerak partekatzetik, arbeletik edo aurreko pantailatik datozenean + Egin tresna errepikatuak abiarazte azkarrago + Saltatu fitxategiak hautatzea onartzen diren tresnen lehen urratsa aldatzen du. Erabilgarria da normalean dagoeneko hautatutako irudi batekin edo Android partekatzeko ekintzetatik tresna bat sartzen duzunean, baina nahasgarria izan daiteke tresna guztiek fitxategi bat berehala eskatzea espero baduzu. + Gaitu zure ohiko fluxuak tresna ireki aurretik iturburu-irudia ematen duenean soilik. + Mantendu desgaituta aplikazioa ikasten duzun bitartean, aukeraketa esplizituan tresna-fluxua errazago ulertzen delako. + Tresna bat ageriko sarrerarik gabe irekitzen bada, desgaitu ezarpen hau edo hasi Partekatu/Ireki With-etik, Android-ek fitxategia lehenbailehen pasa dezan. + Ireki editorea lehenik + Saltatu aurrebista partekatutako irudi bat zuzenean editatzen joan behar denean + Aukeratu aurrebista edo editatu lehen geldialdi gisa + Ireki Editatu Aurrebistaren ordez irudia aldatu nahi duten erabiltzaileentzat da. Aurrebista seguruagoa da txatetik edo hodeiko biltegiratzetik fitxategiak ikuskatzen dituzunean; zuzeneko edizioa azkarragoa da pantaila-argazkietarako, mozketa azkarrerako, oharpenetarako eta zuzenketa puntualetarako. + Gaitu zuzeneko edizioa, partekatutako irudi gehienek berehala mozketak, markaketak, iragazkiak edo esportatu aldaketak behar badituzte. + Utzi aurrebista lehenik erabaki aurretik metadatuak, dimentsioak, gardentasuna edo irudiaren kalitatea maiz ikuskatzen dituzunean. + Erabili hau gogokoekin batera, partekatutako irudiak benetan erabiltzen dituzun tresnetatik hurbil uzteko. + Aurrez ezarritako testu-sarrera + Sartu aurrez ezarritako balio zehatzak kontrolak behin eta berriro doitu beharrean + Erabili balio zehatzak graduatzaileak motelak direnean + Aurrez ezarritako testu-sarrerak hainbat lan egiteko zenbaki zehatzak behar dituzunean laguntzen du: irudi sozialen tamainak, dokumentuen marjinak, konpresio-helburuak, lerro-zabalera edo keinuekin iristeko gogaikarria diren beste balio batzuk. Pantaila txikietan ere erabilgarria da irristagailu finaren mugimendua zailagoa den. + Gaitu testua sartzea tamaina zehatzak, ehunekoak, kalitate-balioak edo tresna-parametroak maiz berrerabiltzen badituzu. + Gorde balioa aurrez ezarritako moduan behin idatzi ondoren, eta gero berrerabili konfigurazio bera berreraiki beharrean. + Mantendu keinu-kontrolak doikuntza bisual zakarra izateko eta idatzitako balioak irteera-eskakizun zorrotzetarako. + Gorde jatorrizkotik gertu + Jarri sortutako fitxategiak sorburuko irudien ondoan karpetaren testuingurua garrantzitsua denean + Mantendu irteerak beren iturriekin + Gorde jatorrizko karpetan erabilgarria da kamera-karpetetan, proiektuen karpetetan, dokumentuen eskaneatzen eta bezeroen sortetan, editatutako kopia iturriaren ondoan egon behar baita. Irteerako karpeta dedikatu bat baino txukunagoa izan daiteke, beraz, konbinatu fitxategi-izenen atzizki edo eredu argiekin. + Gaitu iturburu-karpeta bakoitza esanguratsua denean eta irteerak bertan taldekatuta egon behar direnean. + Erabili fitxategi-izenen atzizkiak, aurrizkiak, denbora-zigiluak edo ereduak, editatutako kopiak jatorrizkoetatik erraz bereiz daitezen. + Desgaitu sorta mistoetarako sortutako fitxategi guztiak esportazio karpeta batean bildu nahi dituzunean. + Saltatu irteera handiagoa + Saihestu iturria baino ustekabean astunagoak diren bihurketak gordetzea + Babestu loteak tamaina txarren ezustekoetatik + Bihurketa batzuek fitxategiak handiagoak egiten dituzte, batez ere PNG pantaila-argazkiak, dagoeneko konprimitutako argazkiak, kalitate handiko WebP edo metadatuak gordeta dituzten irudiak. Baimendu Saltatu handiagoa bada, irteera horiek iturri txikiagoa ordezkatzea eragozten du tamaina murriztea helburu nagusia den lan-fluxuetan. + Gaitu esportazioa kargatzeko mugetarako fitxategiak konprimitzeko, tamainaz aldatzeko edo prestatzeko xedea denean. + Berrikusi saltatu diren fitxategiak lote baten ondoren; baliteke dagoeneko optimizatuta egotea edo beste formatu bat behar izatea. + Desgaitu kalitatea, gardentasuna edo formatuaren bateragarritasuna azken fitxategiaren tamaina baino garrantzitsuagoa denean. + Arbela eta estekak + Kontrolatu arbeleko sarrera automatikoa eta estekaren aurrebista testuan oinarritutako tresna azkarragoetarako + Egin sarrera automatikoa aurreikusteko + Arbelaren eta esteken ezarpenak erosoak dira QR, Base64, URL eta testu askoko lan-fluxuetarako, baina sarrera automatikoak nahita egon beharko luke. Aplikazioak berez eremuak betetzen dituela dirudi, egiaztatu arbeleko itsatsi portaera; esteka-txartelak zaratatsu edo motel sentitzen badira, egokitu esteken aurrebistaren portaera. + Onartu arbeleko itsatsi automatikoa testua maiz kopiatzen duzunean eta bat datorren tresna berehala irekitzen duzunean. + Desgaitu itsatsi automatikoa arbeleko eduki pribatua espero ez zenuen tresnetan agertzen bada. + Desaktibatu esteken aurrebistak aurrebista eskuratzea motela, distraitzen duena edo zure lan-fluxurako beharrezkoa ez denean. + Kontrol trinkoak + Erabili hautatzaile trinkoagoak eta ezarpen bizkorrak kokatzea pantailako espazioa estua denean + Egokitu kontrol gehiago pantaila txikietan + Hautatzaile trinkoak eta ezarpen bizkorrak jartzeko telefono txikietan, paisaia editatzen eta parametro asko dituzten tresnetan laguntzen dute. Konpromisoa da kontrolak trinkoagoak izan daitezkeela, beraz, hau da onena aukera arruntak ezagutu ondoren. + Gaitu hautatzaile trinkoak elkarrizketa-koadroak edo aukera-zerrendak pantailarako altuegiak iruditzen zaizkionean. + Doitu ezarpen bizkorrak pantailaren alde batetik tresnaren kontrolak errazago iristen badira. + Itzuli kontrol zabalagoetara oraindik aplikazioa ikasten ari bazara edo sarritan ukitzen dituzun aukera trinkoak huts egiten badituzu. + Kargatu irudiak webetik + Itsatsi orri bat edo irudiaren URLa, ikusi analizatutako irudiak, gero gorde edo partekatu hautatutako emaitzak + Erabili web-irudiak nahita + Web Image Loading-ek irudien iturriak analizatzen ditu emandako URLtik, eskuragarri dagoen lehen irudiaren aurrebista ematen du eta hautatutako analizatutako irudiak gorde edo partekatzen uzten dizu. Gune batzuek aldi baterako estekak, babestuak edo script bidez sortutako estekak erabiltzen dituzte; beraz, arakatzaile batean funtzionatzen duen orrialdeak baliteke irudi erabilgarririk ez itzultzea. + Itsatsi zuzeneko irudiaren URLa edo orri baten URLa eta itxaron analizatutako irudien zerrenda agertu arte. + Erabili markoa edo hautapen kontrolak orrialdeak hainbat irudi dituenean eta horietako batzuk bakarrik behar dituzunean. + Ezer ez bada kargatzen, saiatu irudien esteka zuzena edo deskargatu arakatzailearen bidez lehenik; guneak analisia blokeatu edo esteka pribatuak erabil ditzake. + Aukeratu tamaina aldatzeko mota + Ulertu esplizitua, malgua, moztu eta doitzea irudi bat dimentsio berrietara behartu aurretik + Kontrolatu forma, mihisea eta aspektu-erlazioa + Tamaina aldatzeko motak erabakitzen du zer gertatuko den eskatutako zabalera eta altuera jatorrizko aspektu-erlazioarekin bat ez datozenean. Pixelak luzatzea, proportzioak gordetzea, eremu gehigarria moztea edo atzeko mihisea gehitzearen arteko aldea da. + Erabili esplizitua distortsioa onargarria denean edo iturburuak dagoeneko xedearen aspektu-erlazio bera duenean. + Erabili Malgua proportzioak mantentzeko alde garrantzitsu batetik tamaina aldatuz, batez ere argazkietarako eta sorta mistoetarako. + Erabili Moztu ertz gabeko marko zorrotzetarako edo Doitu irudi osoa mihise finko baten barruan ikusgai egon behar denean. + Aukeratu irudien eskala modua + Aukeratu zorroztasuna, leuntasuna, abiadura eta ertzeko artefaktuak kontrolatzen dituen birlaginketa-iragazkia + Tamaina aldatu ustekabeko leuntasunik gabe + Irudien eskala moduak pixel berriak nola kalkulatzen diren kontrolatzen du tamaina aldatzean. Modu sinpleak azkarrak eta aurreikusgarriak dira, eta iragazki aurreratuek xehetasunak hobeto gorde ditzakete, baina ertzak zorroztu, haloak sor ditzakete edo irudi handietan denbora gehiago hartu dezakete. + Erabili Oinarrizkoa edo Bilineala eguneroko tamaina azkar aldatzeko, emaitza txikiagoa eta garbia izan behar denean. + Erabili Lanczos, Robidoux, Spline edo EWA estiloko moduak xehetasun finak, testuak edo kalitate handiko beherakada axola denean. + Erabili Nearest pixel arteetarako, ikonoetarako eta ertz gogorreko grafikoetarako, non pixel lausotuek itxura hondatuko luketen. + Kolore-espazioa eskalatu + Erabaki koloreak nola nahasten diren tamaina aldatzean pixelak berriro lagintzen diren bitartean + Mantendu gradienteak eta ertzak naturalak + Eskala kolore-espazioak tamaina aldatzean erabiltzen den matematika aldatzen du. Irudi gehienak ondo daude lehenespenarekin, baina espazio lineal edo pertzeptiboek gradienteak, ertz ilunak eta kolore aseak garbiagoak izan ditzakete eskala sendoaren ondoren. + Utzi lehenetsia abiadurak eta bateragarritasunak kolore desberdintasun txikiek baino axola handiagoa dutenean. + Saiatu lineal edo pertzepzio moduak eskema ilunak, interfazearen pantaila-argazkiak, gradienteak edo kolore-bandak tamaina aldatu ondoren gaizki ikusten direnean. + Konparatu aurretik eta ondoren benetako xede-pantailan, kolore-espazio aldaketak sotilak eta edukiaren mendekoak izan daitezkeelako. + Mugatu tamaina aldatzeko moduak + Jakin muga gaindiko irudiak noiz saltatu, birkodetu edo txikitu behar diren egokitzeko + Aukeratu zer gertatzen den mugan + Mugatu tamaina ez da irudi txikiagoko tresna bat bakarrik. Bere moduak erabakitzen du aplikazioak zer egiten duen iturburu bat zure mugen barruan dagoenean edo alde bakar batek araua hausten duenean, hori garrantzitsua da karpeta mistoak eta kargak prestatzeko. + Erabili Saltatu mugen barruan dauden irudiak ukitu gabe eta berriro esportatu ez direnean. + Erabili birkodetzea dimentsioak onargarriak direnean baina formatu, metadatuen portaera, kalitate edo fitxategi-izen berri bat behar duzunean. + Erabili Zooma mugak hausten dituzten irudiak proportzionalki txikitu behar direnean baimendutako laukira egokitu arte. + Aspektu-erlazioaren helburuak + Saihestu aurpegiak, moztutako ertzak eta ustekabeko ertzak irteerako tamaina zorrotzetan + Lotu forma tamaina aldatu aurretik + Zabalera eta altuera tamaina aldatzeko helburu baten erdia baino ez dira. Aspektu-erlazioak erabakitzen du irudia modu naturalean egoki daitekeen, moztu behar duen edo inguruan mihise bat behar duen. Lehenengo forma egiaztatzeak tamaina aldatzeko emaitza harrigarrienak saihesten ditu. + Konparatu iturburu-forma xede-formarekin Esplizitua, Moztu edo Doitu aukeratu aurretik. + Moztu lehenik gaiak atzeko plano gehigarria gal dezakeenean eta helmugak marko zorrotza behar duenean. + Erabili Fit edo Flexible jatorrizko irudiaren zati guztiak ikusgai egon behar direnean. + Tamaina handitu edo normal aldatu + Jakin pixelak gehitzeak AI behar duen eta eskalatze sinplea nahikoa den + Ez nahastu tamaina xehetasunarekin + Tamaina aldatzeak dimentsioak aldatzen ditu pixelak interpolatuz; ezin du benetako xehetasunik asmatu. AI goi mailako xehetasunak itxura zorrotzagoak sor ditzake, baina motelagoa da eta ehundura haluzinatu dezake, beraz, aukera egokia bateragarritasuna, abiadura edo ikusizko zaharberritzea behar duzunaren araberakoa da. + Erabili tamaina aldatzeko normala irudi txikietarako, kargatzeko mugetarako, pantaila-argazkietarako eta dimentsio-aldaketa errazetarako. + Erabili AI maila altua irudi txiki edo leun batek tamaina handiago batean hobeto ikusi behar duenean. + Konparatu aurpegiak, testuak eta ereduak AI handitu ondoren, sortutako xehetasunak konbentzigarriak baina okerrak izan daitezkeelako. + Iragazkien ordenari garrantzia ematen dio + Aplikatu garbiketa, kolorea, zorroztasuna eta azken konpresioa nahita sekuentzia batean + Eraiki iragazki-pila garbiagoa + Iragazkiak ez dira beti trukagarriak. Zorroztu aurretik lausotzeak, OCR-ren aurretik kontrastearen areagotzeak edo konpresioaren aurretik kolore-aldaketak oso irteera desberdinak sor ditzakete kontrol beretik. + Moztu eta biratu lehenik, gero iragazkiek geratuko diren pixeletan bakarrik funtzionatzeko. + Aplikatu esposizioa, zurien balantzea eta kontrastea efektu zorroztu edo estilizatu aurretik. + Mantendu azken tamaina eta konpresioa amaieratik gertu, aurrebistatutako xehetasunek esportazioetan bizirik iraun dezaten. + Konpondu atzeko ertzak + Garbitu haloak, soberan dauden itzalak, ilea, zuloak eta eskema leunak automatikoki kendu ondoren + Egin mozketak nahita bezala + Maskara automatikoek sarritan itxura ona dute begirada batean, baina arazoak agerian uzten dituzte atzeko plano distiratsu, ilun edo ereduetan. Ertzak garbitzea ebaki azkar baten eta eranskailu, logotipo edo produktuaren irudi berrerabilgarri baten arteko aldea da. + Aurreikusi emaitza kontrastearen atzeko planoekin haloak eta falta diren ertzen xehetasunak agertzeko. + Erabili leheneratzea ilea, izkina eta objektu gardenak inguruko hondarrak ezabatu aurretik. + Esportatu proba txiki bat atzeko planoko azken koloreari ebakidura diseinu batean jarriko denean. + Ur-marka irakurgarriak + Egin markak ikusgai distiratsu, ilun, okupatu eta moztutako irudietan, argazkia hondatu gabe + Babestu irudia ezkutatu gabe + Irudi batean itxura ona duen ur-marka bat beste batean desager daiteke. Tamaina, opakutasuna, kontrastea, kokatzea eta errepikapena lote osorako aukeratu behar dira, ez bakarrik lehen aurrebistarako. + Probatu loteko irudi distiratsuenen eta ilunenen marka fitxategi guztiak esportatu aurretik. + Erabili opakutasun txikiagoa errepikatutako marka handietarako eta kontraste indartsuagoa izkina txikietarako. + Mantendu aurpegi garrantzitsuak, testua eta produktuaren xehetasunak argi eta garbi, marka berrerabilpena saihesteko xedea ez bada. + Zatitu irudi bat lauzetan + Moztu irudi bakarra errenkadak, zutabeak, ehuneko pertsonalizatuak, formatuak eta kalitateak + Prestatu sareak eta agindutako piezak + Irudien zatiketak irteera anitz sortzen ditu iturri-irudi batetik. Errenkada eta zutabe kopuruaren arabera zatitu daiteke, errenkada edo zutabe portzentajeak doitu eta hautatutako irteera formatuarekin eta kalitatearekin piezak gorde ditzake, hau da, sareetarako, orri-xerretarako, panoramikoetarako eta agindutako mezu sozialetarako erabilgarria. + Aukeratu sorburuko irudia, eta ezarri behar dituzun errenkada eta zutabe kopurua. + Doitu errenkada edo zutabeen ehunekoak pieza guztiak tamaina berdina izan behar ez dutenean. + Aukeratu irteera-formatua eta kalitatea gorde aurretik, sortutako pieza bakoitzak esportazio-arau berdinak erabiltzeko. + Moztu irudi-zerrendak + Kendu eskualde bertikalak edo horizontalak eta batu gainerako zatiak berriro elkarrekin + Eskualde bat kendu hutsunerik utzi gabe + Irudiaren mozketa laborantza normalaren desberdina da. Hautatutako zerrenda bertikal edo horizontal bat ken dezake eta, ondoren, gainerako irudi zatiak bateratu. Alderantzizko moduak hautatutako eskualdea mantentzen du ordez, eta alderantzizko norabide biak erabiliz hautatutako laukizuzena mantentzen da. + Erabili ebaketa bertikala alboko eskualdeetarako, zutabeetarako edo erdiko zerrendetarako; erabili ebaketa horizontala pankartak, hutsuneak edo errenkadak egiteko. + Gaitu alderantzizkoa ardatz batean hautatutako zatia kendu beharrean mantendu nahi duzunean. + Aurreikusi ertzak moztu ondoren, bateratutako edukiak bapateko itxura izan dezakeelako kendutako eremuak xehetasun garrantzitsuak zeharkatzen dituenean. + Aukeratu irteera formatua + Aukeratu JPEG, PNG, WebP, AVIF, JXL edo PDF edukiaren eta helmugaren arabera + Helmugak erabaki dezala formatua + Formatu onena irudiak zer duen eta non erabiliko den araberakoa da. Argazkiak, pantaila-argazkiak, aktibo gardenak, dokumentuak eta animazio-fitxategiak modu desberdinetan huts egiten dute formatu okerrera behartuta. + Erabili JPEG argazkien bateragarritasun handia lortzeko gardentasuna eta pixel zehatzak behar ez direnean. + Erabili PNG pantaila-argazki zorrotzak, UI-ak ateratzeko, grafiko gardenak eta galerarik gabeko edizioetarako. + Erabili WebP, AVIF edo JXL irteera moderno trinkoak garrantzitsuak direnean eta aplikazio hartzaileak onartzen duenean. + Atera audio-estalkiak + Gorde txertatutako albumaren artea edo eskuragarri dauden multimedia-markoak audio-fitxategietatik PNG irudi gisa + Atera artelanak audio fitxategietatik + Audio Covers-ek Android-eko multimedia metadatuak erabiltzen ditu audio-fitxategietatik kapsulatutako artelanak irakurtzeko. Kapsulatutako artelanak erabilgarri ez daudenean, retriever-a multimedia-marko batera eror daiteke Android-ek bat eman badezake; bata ez bada ere, tresnak adierazten du ez dagoela ateratzeko irudirik. + Ireki Audio-azalak eta hautatu espero diren azal-arteekin audio-fitxategi bat edo gehiago. + Berrikusi ateratako estalkia gorde edo partekatu aurretik, batez ere streaming edo grabazio aplikazioetako fitxategietarako. + Irudirik aurkitzen ez bada, litekeena da audio-fitxategiak estalkirik ez izatea edo Android-ek ezin izango du marko erabilgarririk agertu. + Datak eta kokapenaren metadatuak + Erabaki zer gorde behar den harrapatzeko denbora garrantzitsua denean baina GPS edo gailuaren datuek ez dute ihes egin behar + Bereizi metadatu erabilgarriak metadatu pribatuetatik + Metadatuak ez dira guztiak berdin arriskutsuak. Harrapatzeko datak baliagarriak izan daitezke artxibatzeko eta ordenatzeko, eta GPSak, gailuaren serie-eremuak, software-etiketak edo jabearen eremuak nahi baino gehiago ager dezakete. + Gorde data eta ordu etiketak albumetarako, eskaneatzeko edo proiektuen historiarako ordena kronologikoa garrantzitsua denean. + Kendu GPSa eta gailua identifikatzeko etiketak publikoki partekatu edo ezezagunei irudiak bidali aurretik. + Esportatu lagin bat eta ikuskatu EXIF ​​berriro pribatutasun-eskakizunak zorrotzak direnean. + Saihestu fitxategi-izen talkak + Babestu sorta-irteerak fitxategi askok image.jpg edo screenshot.png bezalako izenak partekatzen dituztenean + Egin irteera-izen bakoitza bakarra + Bikoiztutako fitxategi-izenak ohikoak dira irudiak mezularietatik, pantaila-argazkietatik, hodeiko karpetetatik edo hainbat kameratik datozenean. Eredu on batek ustekabeko ordezkapena saihesten du eta irteerak beren iturrietara bideratzen ditu. + Sartu jatorrizko izena gehi atzizki bat editatutako kopia ezagutu behar denean. + Gehitu sekuentzia, denbora-zigilua, aurrez ezarritakoa edo dimentsioak sorta misto handiak prozesatzen dituzunean. + Saihestu gainidatzi lan-fluxuak izendatzeko ereduak talka egin ezin duela egiaztatu arte. + Gorde edo partekatu + Aukeratu fitxategi iraunkor baten eta beste aplikazio batera aldi baterako eskualdatzearen artean + Esportatu asmo egokiarekin + Gorde eta Partekatu antzeko senti daitezke, baina arazo desberdinak konpontzen dituzte. Gordetzeak gero aurki dezakezun fitxategi bat sortzen du; partekatzeak Android bidez sortutako emaitza bat bidaltzen du beste aplikazio batera eta baliteke tokiko kopia iraunkorrik ez utzi. + Erabili Gorde irteerak karpeta ezagun batean egon behar duenean, babeskopia egin behar denean edo geroago berrerabili behar denean. + Erabili Partekatu mezu azkarrak, mezu elektronikoen eranskinak, sare sozialetan argitaratzeko edo emaitza beste editore batera bidaltzeko. + Gorde lehenik aplikazio hartzailea fidagarria ez denean edo partekatze huts bat birsortzea gogaikarria denean. + PDF orriaren tamaina eta marjinak + Aukeratu paperaren tamaina, egokitze-jokabidea eta arnasa hartzeko lekua dokumentu bat sortu aurretik + Egin irudi-orriak inprimagarriak eta irakurgarriak + Irudiak PDF orri deseroso bihur daitezke haien formak aukeratutako paperaren tamainarekin bat ez datozenean. Marjinek, egokitze-moduak eta orriaren orientazioak erabakitzen dute edukia moztua, txikia, luzatua edo irakurtzeko erosoa den. + Erabili benetako paper-tamaina PDFa inprimatu edo inprimaki batera bidal daitekeenean. + Erabili marjinak eskaneatzeko eta pantaila-argazkietarako, testua orriaren ertzaren kontra geldi ez dadin. + Aurreikusi bertikaleko eta horizontaleko orriak batera iturriko irudiek orientazio mistoa dutenean. + Garbitu eskaneatzea + Hobetu paperaren ertzak, itzalak, kontrastea, biraketa eta irakurgarritasuna PDF edo OCR baino lehen + Prestatu orriak artxibatu aurretik + Eskaneatu bat teknikoki atzeman daiteke, baina zaila da irakurtzen. PDF esportatu aurretik garbiketa-urrats txikiek konpresio ezarpenek baino garrantzi handiagoa dute sarritan, batez ere ordainagiriei, eskuz idatzitako oharrei eta argi gutxiko orriei dagokienez. + Zuzendu perspektiba eta moztu mahaiaren ertzak, hatzak, itzalak eta atzeko planoaren nahasmena. + Handitu kontrastea arretaz, testua argiagoa izan dadin, zigiluak, sinadurak edo arkatz-markak suntsitu gabe. + Exekutatu OCR orria zuzen eta irakurgarria izan ondoren soilik, eta egiaztatu zenbaki garrantzitsuak eskuz. + Konprimitu, gris-eskala edo konpondu PDFak + Erabili fitxategi bakarreko PDF eragiketak dokumentuen kopia arinagoak, inprimagarriak edo berreraikietarako + Aukeratu PDF garbiketa eragiketa + PDF Tresnak konpresio, gris-eskala bihurtzeko eta konpontzeko eragiketa bereiziak biltzen ditu. Konpresioak eta gris-eskala funtzionatzen dute batez ere irudi txertatuen bidez, konponketak PDF egitura berreraikitzen duen bitartean; hauetako bat ere ez da dokumentu guztien konponketa bermatu gisa tratatu behar. + Erabili Konprimitu PDF dokumentuak irudiak dituenean eta fitxategien tamainak partekatzeko garrantzitsuak direnean. + Erabili Gris-eskala dokumentua inprimatzeko errazagoa izan behar denean edo koloretako irudirik behar ez duenean. + Erabili Konpondu PDF kopia batean dokumentu bat irekitzen ez denean edo barne egitura hondatzen duenean, eta egiaztatu berreraikitako fitxategia. + Atera irudiak PDFetatik + Berreskuratu kapsulatutako PDF irudiak eta bildu ateratako emaitzak artxibo batean + Gorde iturburuko irudiak dokumentuetatik + Extract Images PDFa eskaneatzen du kapsulatutako irudi-objektuen bila, bikoiztuak saltatzen ditu ahal den neurrian eta berreskuratutako irudiak sortutako ZIP artxibo batean gordetzen ditu. PDFan txertatutako irudiak bakarrik ateratzen ditu, ez testuak, marrazki bektorialak edo ikusgai dauden orrialde guztiak pantaila-argazki gisa. + Erabili Atera Irudiak PDF batean gordetako argazkiak behar dituzunean, adibidez, katalogo bateko argazkiak edo eskaneatutako aktiboak. + Erabili PDF to Images edo orrien erauzketa horren ordez orrialde osoak behar dituzunean, testua eta diseinua barne. + Tresnak txertatutako irudirik ez badu adierazten, baliteke orria testu/bektore edukia izatea edo irudiak ez dira atera daitezkeen objektu gisa gordetzea. + Metadatuak, berdintzea eta inprimatzeko irteera + Editatu dokumentuaren propietateak, rasterizatu orriak edo prestatu inprimatzeko diseinu pertsonalizatuak nahita + Aukeratu azken dokumentuaren ekintza + PDF metadatuek, berdintzeak eta inprimatzeko prestaketak azken faseko arazo desberdinak konpontzen dituzte. Metadatuek dokumentuaren propietateak kontrolatzen dituzte, Flatten PDF-ek orriak rasterizatzen ditu irteera gutxiago editagarri batean eta Print PDF diseinu berri bat prestatzen du orriaren tamaina, orientazioa, marjina eta orri bakoitzeko orrien kontrolekin. + Erabili metadatuak egilea, izenburua, gako-hitzak, ekoizlea edo pribatutasuna garbitzea garrantzitsua denean. + Erabili Laudu PDFa ikusgai dagoen orrialdeko edukia PDF objektu bereizi gisa editatzea zailagoa izan behar denean. + Erabili Inprimatu PDF orria tamaina, orientazioa, marjinak edo orri bakoitzeko hainbat orrialde behar dituzunean. + Prestatu irudiak OCRrako + Erabili moztu, biraketa, kontrastea, zarata kendu eta eskalatu OCR testua irakurtzeko eskatu aurretik + Eman OCR pixel garbiagoak + OCR akatsak sarritan sarrerako iruditik datoz OCR motorretik baino. Orri okertuek, kontraste baxuek, lausotuek, itzalek, testu txikiek eta atzeko plano mistoek errekonozimenduaren kalitatea murrizten dute. + Moztu testuaren eremura eta biratu irudia lerroak horizontalak izan daitezen ezagutu aurretik. + Handitu kontrastea edo bihurtu zuri-beltza garbiagora letrak irakurtzea errazten duenean soilik. + Aldatu testu txikiaren tamaina gorantz neurriz, baina saihestu ertz faltsuak sortzen dituen zorroztasun oldarkorra. + Egiaztatu QR edukia + Berrikusi estekak, Wi-Fi kredentzialak, kontaktuak eta ekintzak kode bat fidatu aurretik + Tratatu deskodetutako testua fidagarritasunik gabeko sarrera gisa + QR kode batek URL bat, kontaktu txartela, Wi-Fi pasahitza, egutegiko gertaera, telefono zenbakia edo testu arrunta ezkutatu ditzake. Baliteke kodearen ondoan ikusgai dagoen etiketa benetako kargarekin ez bat etorri, beraz, berrikusi deskodetutako edukia horretan jardun aurretik. + Ikuskatu deskodetutako URL osoa ireki aurretik, batez ere domeinu laburtuak edo ezezagunak. + Kontuz Wi-Fi, kontaktu, egutegi, telefono eta SMS kodeekin, ekintza sentikorrak eragin ditzaketelako. + Kopiatu edukia lehenik beste nonbait egiaztatu behar duzunean berehala ireki beharrean. + Sortu QR kodeak + Sortu kodeak testu arruntetik, URLetatik, Wi-Fitik, kontaktuetatik, posta elektronikotik, telefonotik, SMSetatik eta kokapen datuetatik + Eraiki QR karga egokia + QR pantailak sartutako edukitik kodeak sor ditzake, baita lehendik daudenak eskaneatu ere. Egituratutako QR motak datuak beste aplikazioek ulertzen duten formatuan kodetzen laguntzen dute, hala nola, Wi-Fi saioa hasteko xehetasunak, kontaktu-txartelak, posta elektronikoaren eremuak, telefono-zenbakiak, SMS edukia, URLak edo koordenatu geografikoak. + Aukeratu testu arrunta ohar errazetarako, edo erabili mota egituratu bat kodea ireki behar denean Wi-Fi, kontaktu, URL, posta elektronikoa, telefono, SMS edo kokapen-datu gisa. + Egiaztatu eremu guztiak sortutako kodea partekatu aurretik, ikusgai dagoen aurrebistak sartu duzun karga soilik islatzen duelako. + Probatu gordetako QR kodea eskaner batekin inprimatu, publikoki argitaratu edo beste pertsonek erabiliko dutenean. + Aukeratu checksum modua + Kalkulatu hashak fitxategietatik edo testuetatik, konparatu fitxategi bat edo egiaztatu fitxategi asko multzoka + Egiaztatu edukia, ez itxura + Checksum Tools-ek fitxa bereiziak ditu fitxategi-hashing, testu-hashing, fitxategi bat hash ezagun batekin alderatzeko eta loteen konparaketa egiteko. Checksum batek aukeratutako algoritmoaren bytez byte identitatea frogatzen du; ez du frogatzen bi irudi antzeko itxura besterik ez dutenik. + Erabili Kalkulatu fitxategietarako eta Text Hash idatzitako edo itsatsitako testu-datuetarako. + Erabili Konparatu norbaitek fitxategi baterako espero den egiaztapen bat ematen dizunean. + Erabili Batch Compare fitxategi askok hash edo egiaztapena behar dutenean, eta gero mantendu hautatutako algoritmoa koherentea. + Arbeleko pribatutasuna + Erabili arbeleko funtzio automatikoak kopiatutako datu pribatuak nahi gabe agerian utzi gabe + Mantendu kopiatutako edukia nahita + Arbelaren automatizazioa erosoa da, baina kopiatutako testuak pasahitzak, estekak, helbideak, tokenak edo ohar pribatuak izan ditzake. Tratatu arbelean oinarritutako sarrera zure ohiturekin eta pribatutasun-itxaropenekin bat etorri behar den abiadura-eginbide gisa. + Desgaitu arbelaren erabilera automatikoa tresnetan ustekabean testu pribatua agertzen bada. + Erabili arbelean soilik gordetzea emaitza nahita biltegiratzea saihestu nahi duzunean soilik. + Garbitu edo ordeztu arbela testu sentikorra, QR datuak edo Base64 karga erabilgarriak kudeatu ondoren. + Kolore formatuak + Ulertu HEX, RGB, HSV, alfa eta kopiatutako kolore-balioak beste nonbait itsatsi aurretik + Kopiatu helburuak espero duen formatua + Ikusgai dagoen kolore bera hainbat modutan idatz daiteke. Diseinu tresnak, CSS eremuak, Android kolore-balioak edo irudi-editore batek beste ordena, alfa-kudeaketaren edo kolore-eredu bat espero dezake. + Erabili HEX kolore-kode trinkoak espero dituzten web, diseinu edo gai-eremuetan itsatsitakoan. + Erabili RGB edo HSV kanalak, distira, saturazioa edo ñabardura eskuz egokitu behar dituzunean. + Egiaztatu alfa bereizita gardentasuna garrantzitsua denean, helmuga batzuek ez diotelako jaramonik egiten edo beste ordena bat erabiltzen dutelako. + Arakatu kolore liburutegian + Bilatu izenaren koloreak izenaren edo HEXaren arabera eta gorde gogokoak goialdean + Aurkitu izendun kolore berrerabilgarriak + Koloreen liburutegiak kolore-bilduma izendun bat kargatzen du, izenaren arabera ordenatzen du, kolore-izenaren edo HEX balioaren arabera iragazten du eta kolore gogokoenak gordetzen ditu, sarrera garrantzitsuak errazago iristeko. Baliagarria da benetako izenaren kolorea behar duzunean irudi batetik lagin beharrean. + Bilatu kolore-izen baten arabera familia ezagutzen duzunean, adibidez, gorria, urdina, arbel edo menda. + Bilatu HEX bidez zenbakizko kolore bat baduzu eta bat datozen edo gertuko sarrera izendunak aurkitu nahi dituzunean. + Markatu maiz koloreak gogoko gisa, arakatzen ari zaren bitartean liburutegi orokorraren aurretik joan daitezen. + Erabili sare-gradienteen bildumak + Deskargatu eskuragarri dauden sare-gradiente-baliabideak eta partekatu hautatutako irudiak + Erabili urruneko sareko gradiente-aktiboak + Mesh Gradients-ek urruneko baliabide bilduma kargatu, falta diren gradienteen irudiak deskargatu, deskargaren aurrerapena erakutsi eta hautatutako gradienteak partekatu ditzake. Erabilgarritasuna sareko sarbidearen eta urruneko baliabideen biltegiaren araberakoa da; beraz, zerrenda huts batek baliabideak oraindik erabilgarri ez zeudela esan nahi du normalean. + Ireki Sare-gradienteak gradiente-tresnetatik prest egindako sare-gradiente-irudiak nahi dituzunean. + Itxaron baliabideak kargatu edo deskargatu arte bilduma hutsik dagoela suposatu aurretik. + Hautatu eta partekatu behar dituzun gradienteak, edo itzuli Gradient Maker-era gradiente pertsonalizatu bat eskuz eraiki nahi duzunean. + Sortutako aktiboen ebazpena + Aukeratu irteerako tamaina gradienteak, zarata, horma-paperak, SVG antzeko artea edo testurak sortu aurretik + Sortu behar duzun tamainan + Sortutako irudiek ez dute kameraren jatorrizkorik atzera erortzeko. Irteera txikiegia bada, geroago eskalatzeak ereduak leundu ditzake, xehetasunak alda ditzakete edo erreproduzigaien lauzak eta konprimitze-modua alda dezakete. + Ezarri lehenik azken erabilera kasurako dimentsioak, hala nola horma-papera, ikonoa, atzeko planoa, inprimatzea edo gainjartzea. + Erabili tamaina handiagoak hainbat diseinutan moztu, handitu edo berrerabili daitezkeen ehundurarako. + Esportatu proba-bertsioak irteera handien aurretik, prozedura-xehetasunak konpresioaren portaera alda dezakeelako. + Esportatu uneko horma-paperak + Berreskuratu eskuragarri dauden Home, Lock eta horma-paperak gailutik + Gorde edo partekatu instalatutako horma-paperak + Wallpapers Exportek Android-ek erakusten dituen horma-irudiak irakurtzen ditu sistemaren horma-paperen APIen bidez. Gailuaren, Android bertsioaren, baimenen eta eraikitze-aldaeraren arabera, baliteke hasiera, blokeoa edo horma-paperak bereizita eskuragarri egotea edo falta izatea. + Ireki Wallpapers Export sistemak aplikazioari irakurtzeko aukera ematen dion horma-paperak kargatzeko. + Hautatu gorde, kopiatu edo partekatu nahi dituzun hasierako, blokeo edo horma-paper-sarrera eskuragarriak. + Horma-paper bat falta bada edo eginbidea erabilgarri ez badago, normalean sistema, baimen edo eraikuntza-aldaeraren muga bat izan ohi da, editatzeko ezarpena baino. + Txokoak Animazioa Throttle + Kopiatu kolorea honela + HEX + izena + Erretratu estera eredua pertsona azkar mozteko ilea eta ertzak maneiatzeko. + Argi gutxiko hobekuntza azkarra Zero-DCE++ kurbaren estimazioa erabiliz. + Restormer irudia leheneratzeko eredua euri-marra eta eguraldi hezearen artefaktuak murrizteko. + Dokumentuen deformazio-eredua, koordenatu-sare bat aurreikusten duena eta orri kurbatuak birmapatzen dituena eskaneatu lauago batean. + Mugimenduaren Iraupen Eskala + Aplikazioaren animazio-abiadura kontrolatzen du: 0-k mugimendua desgaitzen du, 1 normala da, balio handiagoak animazioak moteltzen ditu + Erakutsi azken tresnak + Erakutsi azken erabilitako tresnetarako sarbide azkarra pantaila nagusian + Erabilera Estatistika + Arakatu aplikazioa irekitzen, tresna abiarazten eta gehien erabiltzen dituzun tresnak + Aplikazioa irekitzen da + Tresna irekitzen da + Azken tresna + Gorde arrakastatsuak + Jarduera bolada + Datuak gordeta + Goiko formatua + Erabilitako tresnak + %1$d irekitzen • %2$s + Oraindik ez dago erabilera-estatistikarik + Ireki tresna batzuk eta automatikoki agertuko dira hemen + Zure erabilera-estatistikak lokalean soilik gordetzen dira zure gailuan. ImageToolbox-ek zure jarduera pertsonala, gogoko tresnak eta gordetako datuei buruzko informazioa erakusteko soilik erabiltzen ditu + editorea editatu argazki argazkia ukitu doitzea + tamaina aldatu bihurtu eskala bereizmena neurriak zabalera altuera jpg jpeg png webp konprimitu + konprimitu tamaina pisua byte mb kb murriztea kalitatea optimizatu + moztu moztu moztu aspektu-erlazioa + iragazki-efektua kolore-zuzenketa egokitu lut maskara + marraztu pintzela arkatza ohartarazpen markaketa + zifratu enkriptatu deszifratu pasahitz sekretua + atzeko planoa kentzeko ezabatu kendu ebakidura alfa gardena + aurrebista ikuslea galeria ikuskatu irekita + stitch merge konbinatu panorama luzea pantaila-argazkia + url web deskarga Interneteko esteka irudia + paleta kolore-laginak atera nagusi + exif metadatuen pribatutasuna kendu banda garbia gps kokapena + muga aldatu tamaina gehienez megapixelak dimentsioak clamp + pdf dokumentuen orriak tresnak + gradientearen atzeko planoko koloreko sare + gif animazio animaziozko bihurketa fotogramak + formatua bihurtu jpg jpeg png webp heic avif jxl bmp + webp bihurtu animaziozko irudi formatua + sare gradientearen atzeko planoa + pdf batu konbinatu batu + pdf orrialdeak banatu + pdf sinadura seinale zozketa + pdf kendu ezabatu orriak + pdf moztu marjinak moztu + pdf zip artxiboa konprimitzea + pdf inprimagailua + pdf aurrebista ikuslea irakurri + Errorea + Aldaera Neutroa + Jaregin itzala + Hortzen Altuera + Hortz barruti horizontala + Hortz sorta bertikala + Goiko Ertza + Eskuineko Ertza + Beheko Ertza + Ezkerreko Ertza + Ertza Urratua + hautatzailea eyedropper lagina hex rgb kolorea + alderatu diferentzia aurretik eta ondoren + ocr testua ezagutzen ateratze eskaneatzea + ur-markaren logotipoaren testu zigiluaren gainjartzea + apng animazioa PNG bihurtzeko fotogramak + zip artxiboa konprimitu paketea deskonprimitu fitxategiak + jxl jpeg xl bihurtu jpg irudi formatua + svg traza bektorial bihurtzeko xml + dokumentu eskaner eskaneatu papera perspektiba moztu + qr barra-kodeen eskanerra eskaneatzea + pilaketa gainjartzea batez besteko astroargazkiaren mediana + zatitu fitxak sareta xerra zatiak + kolore bihurtzea hex rgb hsl hsv cmyk lab + zarata-sortzailea ausazko ehundura alea + collage-sarearen diseinua konbinatu + markatze geruzek gainjarri edizioa egiten dute + base64 kodetu datuak deskodetzen uri + checksum hash md5 sha sha1 sha256 egiaztatu + exif metadatuak editatu gps data kamera + zatitu sareta zati fitxak moztu + audioaren azala albumaren artearen musika metadatuak + horma-papera esportatzeko atzeko planoa + ascii testu-arteko pertsonaiak + ai ml neuronal hobetu goi mailako segmentua + kolore liburutegiko material paleta koloreak izenekoa + shader glsl fragment efektu estudioa + pdf biratu orriak orientazioa + pdf berrantolatu berrantolatu orriak ordenatu + pdf orrialde-zenbakien orria + pdf ocr testua ezagutu bilaketa daitekeen laburpena + pdf ur-marka zigiluaren logotipoaren testua + pdf babestu pasahitza enkriptatu blokeoa + pdf desblokeatu pasahitza deszifratu kendu babesa + pdf konprimitu tamaina murriztu optimizatu + pdf gris-eskala beltza zuri monokromoa + pdf konponketa konpondu berreskuratu hautsita + pdf metadatuen propietate exif egilearen izenburua + pdf berdindu oharrak geruzak eratzen ditu + pdf ateratzeko irudiak irudiak + irudiak pdf bihurtzeko jpg jpeg png dokumentua + pdf irudietara orrietara esportatu jpg png + pdf oharrak iruzkinak garbi kendu + Berrezarri erabilera-estatistikak + Tresna irekitzen da, gorde estatistikak, marra, gordetako datuak eta goi-formatua berrezarri egingo dira. Aplikazioen irekierak ez dira aldatuko. + Audioa + Fitxategia + Esportatu profilak + Gorde uneko profila + Ezabatu esportazio-profila + \\"%1$s\\" esportazio profila ezabatu nahi duzu? + Marrazketa ertza + Erakutsi ertz mehe bat marrazteko eta ezabatzeko mihisearen inguruan + Uhina + UI elementu biribilduak ertz uhin leunarekin + Scoop + Notch + Barrualdera zizelkaturiko ertz biribilduak + Ebaki bikoitzarekin ertz mailakatuak + Leku-marka + Erabili {filename} jatorrizko fitxategi-izena luzapenik gabe txertatzeko + Erlantz + Oinarrizko zenbatekoa + Eraztun kopurua + Izpi kopurua + Eraztunaren zabalera + Perspektiba desitxuratu + Java itxura eta sentipena + Zizaila + X angelua + eta angelua + Aldatu irteeraren tamaina + Ur Tanta + Uhin-luzera + Fasea + Goi Mendatea + Kolore Maskara + Chrome + Disolbatu + Leuntasuna + Iritzia + Lentea Lausotzea + Sarrera txikia + Sarrera handia + Irteera baxua + Irteera handia + Argi-efektuak + Kolpeen altuera + Bump leuntasuna + Uhina + X anplitudea + Y anplitudea + X uhin-luzera + Y uhin-luzera + Lausotze egokitzailea + Ehundura Sorkuntza + Sortu hainbat prozedurazko ehundura eta gorde irudi gisa + Ehundura mota + Alborapena + Denbora + Distira + Dispertsioa + Laginak + Angelu-koefizientea + Gradiente koefizientea + Sare-mota + Distantzia potentzia + Y eskala + Lausotasuna + Oinarri mota + Turbulentzia faktorea + Eskalatzea + Iterazioak + Eraztunak + Metal eskuilatua + Kaustikoak + Zelularra + Xake-taula + fBm + Marmola + Plasma + Edredoia + Egurra + ausaz + Plaza + Hexagonala + Oktogonala + Triangelua + Ertzatua + VL Zarata + SC Zarata + Duela gutxi + Oraindik ez dago erabili berri den iragazkirik + ehundura sorgailua eskuilatua metal kaustika zelula koadroa marmola plasma edredoia egurra adreilua kamuflaje zelula hodeia pitzadura ehuna hostoa abaraska izotza laba nebulosa papera herdoila harea kea harria orografia topografia ura uhina + Adreilua + Kamuflajea + Zelula + Hodeia + Crack + Ehuna + Hostoa + Eztia + Izotza + Laba + Nebulosa + Papera + Herdoila + Harea + Kea + Harria + Lurra + Topografia + Ur Ripple + Egur aurreratua + Morteroaren zabalera + Irregulartasuna + Alaka + Zimurtasuna + Lehenengo atalasea + Bigarren atalasea + Hirugarren atalasea + Ertz leuntasuna + Ertzaren zabalera + Estaldura + Xehetasuna + Sakonera + Adarrak + Hari horizontalak + Hari bertikalak + Fuzz + Zainak + Argiztapena + Pitzaduraren zabalera + Izoztea + Emaria + Lurrazala + Hodeien dentsitatea + Izarrak + Zuntzaren dentsitatea + Zuntzaren indarra + Orbanak + Korrosioa + Pitting + Malutak + Dunen maiztasuna + Haizearen angelua + Uhinak + Wisps + Zain eskala + Ur maila + Mendi maila + Higadura + Elur maila + Lerro kopurua + Lerro lodiera + Itzaltzea + Poroen kolorea + Mortero kolorea + Adreilu kolore iluna + Adreilu kolorea + Nabarmendu kolorea + Kolore iluna + Baso kolorea + Lurraren kolorea + Harea kolorea + Atzeko planoaren kolorea + Zelula kolorea + Ertzaren kolorea + Zeruaren kolorea + Itzalen kolorea + Kolore argia + Gainazaleko kolorea + Aldaketa kolorea + Crack kolorea + Warp kolorea + Trama kolorea + Hosto kolore iluna + Hostoen kolorea + Ertzaren kolorea + Ezti kolorea + Kolore sakona + Izotz kolorea + Izozte kolorea + Lurrazalaren kolorea + Garbitu kolorea + Kolore distiratsua + Espazio kolorea + Kolore morea + Kolore urdina + Oinarrizko kolorea + Zuntz kolorea + Orbanaren kolorea + Metal kolorea + Herdoil kolore iluna + Herdoil kolorea + Kolore laranja + Kearen kolorea + Zainaren kolorea + Lur baxuaren kolorea + Ur kolorea + Arroka kolorea + Elur kolorea + Kolore baxua + Kolore altua + Lerroaren kolorea + Azaleko kolorea + Belarra + Zikinkeria + Larrua + Hormigoia + Asfaltoa + Goroldioa + Sua + Aurora + Olio-orria + Akuarela + Fluxu Abstraktua + Opala + Damasko altzairua + Tximista + Belusa + Ink Marbling + Paper Holografikoa + Bioluminiszentzia + Zurrunbilo kosmikoa + Laba Lanpara + Gertaeren Horizontea + Loraldia Fraktala + Tunel kromatikoa + Eklipse Koroa + Erakarri bitxia + Ferrofluidoen koroa + Supernoba + Iris + Paumaren Luma + Nautilus Shell + Eraztundun Planeta + Pala dentsitatea + Pala luzera + Haizea + Adabakia + Txokotxoak + Hezetasuna + Harritxoak + Zimurrak + Poroak + Agregatua + Pitzadurak + Tar + Jantzi + Motxilak + Zuntzak + Suaren maiztasuna + Kea + Intentsitatea + Zintak + Bandak + Iridetasuna + Iluntasuna + Loreak + Pigmentua + Ertzak + Papera + Zabalkundea + Simetria + Zorroztasuna + Kolore jolasa + Esnetasuna + Geruzak + Tolesgarria + poloniarra + Adarrak + Norabidea + Distira + Tolesturak + Lumak + Tinta balantzea + Espektroa + Kimurak + Difrakzioa + Besoak + Bihurritu + Core distira + Blobs + Diskoaren okertzea + Horizontearen tamaina + Diskoaren zabalera + Lentea + Petaloak + Kizkurra + Filigrana + Fazetak + Kurbadura + Ilargiaren tamaina + Koroaren tamaina + Izpiak + Diamantezko eraztuna + Lobuluak + Orbitaren dentsitatea + Lodiera + Iltzeak + Puntaren luzera + Gorputzaren tamaina + Metalikoa + Talkaren erradioa + Maskorraren zabalera + Kanpora botata + Pupilaren tamaina + Irisaren tamaina + Koloreen aldakuntza + Harrapatzeko argia + Begiaren tamaina + Barb dentsitatea + Txandak + Ganberak + Irekiera + Ertzak + Perleszentzia + Planetaren tamaina + Eraztunaren okertzea + Eraztunaren zabalera + Giroa + Zikinkeriaren kolorea + Belar kolore iluna + Belar kolorea + Puntaren kolorea + Lurraren kolore iluna + Kolore lehorra + Pebble kolorea + Larru kolorea + Hormigoizko kolorea + Tar kolorea + Asfalto kolorea + Harri kolorea + Hauts kolorea + Lurzoruaren kolorea + Goroldio kolore iluna + Goroldioaren kolorea + Kolore gorria + Core kolorea + Kolore berdea + Kolore ziana + Magenta kolorea + Urre kolorea + Paper kolorea + Pigmentuaren kolorea + Bigarren mailako kolorea + Lehenengo kolorea + Bigarren kolorea + Kolore arrosa + Altzairu kolore iluna + Altzairu kolorea + Altzairu kolore argia + Oxido kolorea + Halo kolorea + Bolt kolorea + Belusezko kolorea + Distira kolorea + Tinta kolore urdina + Tinta kolore gorria + Tinta kolore iluna + Zilar kolorea + Kolore horia + Ehun kolorea + Diskoaren kolorea + Kolore beroa + Lentearen kolorea + Kanpoko kolorea + Barne kolorea + Koroa kolorea + Kolore hotza + Kolore beroa + Hodei kolorea + Suaren kolorea + Lumaren kolorea + Maskorraren kolorea + Perla kolorea + Planetaren kolorea + Eraztun kolorea + Ezabatu jatorrizko fitxategiak gorde ondoren + Behar bezala prozesatutako iturburu-fitxategiak soilik ezabatuko dira + Jatorrizko fitxategiak ezabatu dira: %1$d + Ezin izan dira jatorrizko fitxategiak ezabatu: %1$d + Jatorrizko fitxategiak ezabatu dira: %1$d, huts egin dute: %2$d + Xafla keinuak + Baimendu zabaldutako aukera-orriak arrastatzea + Kroma azpilaginketa + Bit-sakonera + Galerarik gabekoa + %1$d-bit + Lotaren izena aldatu + Aldatu izena hainbat fitxategi fitxategi-izen ereduak erabiliz + sorta aldatu fitxategiak fitxategi-izena eredua sekuentzia data luzapena + Aukeratu fitxategiak izena aldatzeko + Fitxategi-izen eredua + Aldatu izena + Data iturria + Hartutako EXIF ​​data + Fitxategia aldatutako data + Fitxategia sortu zen data + Egungo data + Eskuzko data + Eskuzko data + Eskuzko denbora + Hautatutako data ez dago erabilgarri fitxategi batzuetan. Uneko data erabiliko da haientzat. + Sartu fitxategi-izen eredu bat + Ereduak fitxategi-izen baliogabea edo luzeegia sortzen du + Ereduak fitxategi-izenak bikoiztuak sortzen ditu + Fitxategi-izen guztiak dagoeneko ereduarekin bat datoz + Eredu honek sorta aldatzeko erabilgarri ez dauden tokenak erabiltzen ditu + Izenak berdin jarraituko du + Fitxategiak ongi aldatu dira izena + Fitxategi batzuei ezin izan zaie izena aldatu + Ez zen baimenik eman + Jatorrizko fitxategi-izena luzapenik gabe erabiltzen du, iturriaren identifikazioa osorik mantentzen lagunduko dizu. Era berean, \\o{hasiera:amaiera}-rekin zatitzea, \\o{t}-rekin transliterazioa eta \\o{s/old/new/}-rekin ordezkatu. + Automatikoki gehitzen den kontagailua. Formateatu pertsonalizatua onartzen du: \\c{betegarria} (adibidez, \\c{3} -&gt; 001), \\c{hasi:urratsa} edo \\c{hasi:urrats:betegarria}. + Jatorrizko fitxategia dagoen karpeta nagusiaren izena. + Jatorrizko fitxategiaren tamaina. Unitateak onartzen ditu: \\z{b}, \\z{kb}, \\z{mb} edo besterik gabe \\z gizakiak irakurtzeko moduko formatuan. + Ausazko Identifikatzaile Unibertsal Bakarra (UUID) sortzen du fitxategi-izenen berezitasuna ziurtatzeko. + Fitxategien izena aldatzea ezin da desegin. Jarraitu aurretik, ziurtatu izen berriak zuzenak direla. + Berretsi izena aldatu + Alboko menuaren ezarpenak + Menuen eskala + Menu alfa + Zurien balantze automatikoa + Mozketa + Ezkutuko ur-markak egiaztatzea + Biltegiratzea + Cache muga + Garbitu cachea automatikoki tamaina horretatik gora hazten denean + Garbiketa tartea + Zenbat aldiz egiaztatu cachea garbitu behar den + Aplikazioa abiaraztean + 1 egun + 1 aste + 1 hilabete + Garbitu aplikazioaren cachea automatikoki hautatutako muga eta tartearen arabera + Laborantza eremu mugikorra + Mozte-eremu osoa mugitzea ahalbidetzen du barruan arrastatuz + Batu GIF + Konbinatu GIF hainbat klipak animazio batean + GIF klipak ordena + Alderantziz + Jokatu alderantziz bezala + Bumerang + Erreproduzitu aurrera eta gero atzera + Normalizatu markoaren tamaina + Eskalatu fotogramak klip handiena egokitzeko; itzali jatorrizko eskala gordetzeko + Klipen arteko atzerapena (ms) + Karpeta + Hautatu karpeta bateko eta bere azpikarpetetako irudi guztiak + Bikoiztu bilatzailea + Bilatu kopia zehatzak eta ikusmen antzeko irudiak + bikoiztu bilatzailea antzeko irudiak kopia zehatzak argazkiak garbiketa biltegiratzea dHash SHA-256 + Aukeratu irudiak bikoiztuak aurkitzeko + Antzeko sentikortasuna + Kopia zehatzak + Antzeko irudiak + Hautatu bikoiztu zehatz guztiak + Hautatu guztiak gomendatuak izan ezik + Ez da bikoizturik aurkitu + Saiatu irudi gehiago gehitzen edo antzekotasun-sentsibilitatea handitzen + Ezin izan dira irakurri %1$d irudiak + %1$s • %2$s berreskuragarria + %1$d taldeak • %2$s berreskuragarriak + %1$d hautatua • %2$s + Hau ezin da desegin. Baliteke Android-ek ezabatzea berresteko eskatuko dizu sistemako elkarrizketa-koadro batean. + Ez da aurkitu + Mugitu hasteko + Mugitu amaierara + \ No newline at end of file diff --git a/core/resources/src/main/res/values-fa/strings.xml b/core/resources/src/main/res/values-fa/strings.xml new file mode 100644 index 0000000..e454846 --- /dev/null +++ b/core/resources/src/main/res/values-fa/strings.xml @@ -0,0 +1,3739 @@ + + + + اندازه + تصویری برای شروع انتخاب کنید + مشکلی رخ داد: %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 حذف کنید. + هایپرلینک ها + فایل های پیوست + خطوط + پنجره های بازشو + تمبر + اشکال + یادداشت های متنی + نشانه گذاری متن + فیلدهای فرم + نشانه گذاری + ناشناس + حاشیه نویسی ها + لغو گروه کردن + سایه تاری پشت لایه را با رنگ قابل تنظیم و افست اضافه کنید + تحریف آگاهانه محتوا + حافظه کافی برای تکمیل این عمل وجود ندارد. لطفاً از یک تصویر کوچکتر استفاده کنید، برنامه های دیگر را ببندید یا برنامه را مجدداً راه‌اندازی کنید. + کارگران موازی + این تصاویر ذخیره نشدند زیرا فایل های تبدیل شده بزرگتر از نسخه اصلی خواهند بود. قبل از حذف تصاویر اصلی، این لیست را بررسی کنید. + فایل‌های حذف شده: %1$s + گزارش های برنامه + گزارش های برنامه را مشاهده کنید تا مشکلات را پیدا کنید + موارد دلخواه در ابزارهای گروه بندی شده + هنگامی که ابزارها بر اساس نوع گروه بندی می شوند، موارد دلخواه را به عنوان یک برگه اضافه می کند + نمایش مورد علاقه به عنوان آخرین + برگه ابزار مورد علاقه را به انتها منتقل می کند + فاصله افقی + فاصله عمودی + انرژی عقب مانده + به جای الگوریتم انرژی رو به جلو، از نقشه انرژی قدر گرادیان ساده استفاده کنید + ناحیه نقاب دار را بردارید + درزها از ناحیه نقاب دار عبور می کنند و به جای محافظت از آن، فقط ناحیه انتخاب شده را حذف می کنند + VHS NTSC + تنظیمات پیشرفته NTSC + تنظیم آنالوگ اضافی برای VHS قوی تر و مصنوعات پخش + خونریزی کروما + پوشیدن نوار + ردیابی + لوما اسمیر + زنگ زدن + برف + استفاده از فیلد + نوع فیلتر + فیلتر لوما ورودی + ورودی کروما پایین گذر + دمدولاسیون کروما + تغییر فاز + افست فاز + تعویض سر + ارتفاع تعویض سر + افست سوئیچینگ سر + تعویض سر + موقعیت خط وسط + جیتر خط وسط + ردیابی ارتفاع نویز + موج ردیابی + ردیابی برف + ردیابی ناهمسانگردی برف + نویز ردیابی + ردیابی شدت نویز + نویز ترکیبی + فرکانس نویز مرکب + شدت نویز مرکب + جزئیات نویز مرکب + فرکانس زنگ + قدرت زنگ + نویز لوما + فرکانس نویز لوما + شدت نویز لوما + جزئیات نویز لوما + نویز کروما + فرکانس نویز کروما + شدت نویز کروما + جزئیات نویز کروما + شدت برف + ناهمسانگردی برف + نویز فاز کروما + خطای فاز کروما + تاخیر کروما افقی + تاخیر کروما عمودی + سرعت نوار VHS + از دست دادن کروم VHS + شدت تیز کردن VHS + فرکانس شارپ VHS + شدت موج لبه VHS + سرعت موج لبه VHS + فرکانس موج لبه VHS + جزئیات موج لبه VHS + کروما پایین گذر خروجی + مقیاس افقی + مقیاس عمودی + ضریب مقیاس X + ضریب مقیاس Y + واترمارک های رایج تصویر را تشخیص می دهد و آنها را با LaMa رنگ آمیزی می کند. شناسایی و رنگ آمیزی مدل ها را به صورت خودکار دانلود می کند + دوترانوپیا + تصویر را بزرگ کنید + پاک کننده پس زمینه مبتنی بر تقسیم بندی اشیاء با استفاده از تقسیم بندی YOLO v11 + نمایش زاویه خط + چرخش خط فعلی را در حین ترسیم بر حسب درجه نشان می دهد + ایموجی های متحرک + ایموجی های موجود را به عنوان انیمیشن نمایش دهید + در پوشه اصلی ذخیره کنید + فایل های جدید را به جای پوشه انتخاب شده در کنار فایل اصلی ذخیره کنید + واترمارک PDF + حافظه کافی نیست + تصویر احتمالاً برای پردازش در این دستگاه بسیار بزرگ است یا حافظه موجود سیستم تمام شده است. سعی کنید وضوح تصویر را کاهش دهید، برنامه های دیگر را ببندید یا یک فایل کوچکتر انتخاب کنید. + منوی اشکال زدایی + منوی آزمایش عملکردهای برنامه، در نظر گرفته نشده است که در نسخه تولید نشان داده شود + ارائه دهنده + PaddleOCR به مدل‌های ONNX اضافی در دستگاه شما نیاز دارد. آیا می خواهید داده های %1$s را دانلود کنید؟ + جهانی + کره ای + لاتین + اسلاوی شرقی + تایلندی + یونانی + انگلیسی + سیریلیک + عربی + دوانگاری + تامیل + تلوگو + سایه بان + از پیش تنظیم سایه بان + سایه زن انتخاب نشده است + Shader Studio + ایجاد، ویرایش، اعتبارسنجی، وارد کردن، و صادرات شیدرهای قطعه سفارشی + سایه بان های ذخیره شده + سایه بان های ذخیره شده را باز کنید، کپی کنید، صادر کنید، به اشتراک بگذارید یا حذف کنید + سایه زن جدید + فایل سایه زن + .itshader JSON + %1$d پارامترها + منبع سایه زن + فقط بدنه void main() را بنویسید. از textureCoordinate برای مختصات UV و inputImageTexture به عنوان نمونه‌بردار منبع استفاده کنید. + توابع + توابع کمکی اختیاری، ثابت ها و ساختارها قبل از void main(). یونیفرم ها از پارامترها تولید می شوند. + هنگامی که سایه‌زن به مقادیر قابل ویرایش نیاز دارد، یونیفرم‌ها را در اینجا اضافه کنید. + ریست سایه زن + پیش نویس سایه زن فعلی پاک خواهد شد + سایه زن نامعتبر است + نسخه سایه زن پشتیبانی نشده %1$d. نسخه پشتیبانی شده: %2$d. + نام سایه زن نباید خالی باشد. + منبع سایه زن نباید خالی باشد. + منبع سایه زن حاوی نویسه های پشتیبانی نشده است: %1$s. فقط از منبع ASCII GLSL استفاده کنید. + پارامتر \\\"%1$s\\\" بیش از یک بار اعلام شده است. + نام پارامترها نباید خالی باشد. + پارامتر \\\"%1$s\\\" از یک نام GPImage رزرو شده استفاده می کند. + پارامتر \\\"%1$s\\\" باید یک شناسه معتبر GLSL باشد. + پارامتر \\\"%1$s\\\" دارای مقدار %2$s نوع \\\"%3$s\\\" است، مورد انتظار \\\"%4$s\\\". + پارامتر \\\"%1$s\\\" نمی تواند حداقل یا حداکثر را برای مقادیر bool تعریف کند. + پارامتر \\\"%1$s\\\" کمتر از حداکثر دارد. + پارامتر \\\"%1$s\\\" پیش‌فرض کمتر از حداقل است. + پارامتر \\\"%1$s\\\" پیش‌فرض بزرگ‌تر از حداکثر است. + Shader باید \\\"uniform sampler2D %1$s;\\\" را اعلام کند. + Shader باید \\\"varing vec2 %1$s;\\\" را اعلام کند. + Shader باید \\\"void main()\\\" را تعریف کند. + Shader باید یک رنگ به gl_FragColor بنویسد. + ShaderToy mainImage پشتیبانی نمی شود. از قرارداد void main() GPUImage استفاده کنید. + یونیفرم ShaderToy متحرک \\\"iTime\\\" پشتیبانی نمی شود. + یونیفرم ShaderToy متحرک \\\"iFrame\\\" پشتیبانی نمی شود. + لباس ShaderToy \\\"iResolution\\\" پشتیبانی نمی شود. + بافت های ShaderToy iChannel پشتیبانی نمی شوند. + بافت های خارجی پشتیبانی نمی شوند. + پسوندهای بافت خارجی پشتیبانی نمی شوند. + پارامترهای سایه زن Libretro پشتیبانی نمی شوند. + فقط یک بافت ورودی پشتیبانی می شود. \\\"یکنواخت %1$s %2$s;\\\" را حذف کنید. + پارامتر \\\"%1$s\\\" باید به عنوان \\\"یکنواخت %2$s %1$s;\\\" اعلام شود. + پارامتر \\\"%1$s\\\" به عنوان \\\"یکنواخت %2$s %1$s;\\\" اعلام شده است، \\\"یکنواخت %3$s %1$s;\\\" مورد انتظار است. + فایل Shader خالی است. + فایل Shader باید یک شی JSON .itshader با قسمت‌های نسخه، نام و سایه‌زن باشد. + فایل Shader JSON معتبر نیست یا با قالب .itshader مطابقت ندارد. + فیلد \\\"%1$s\\\" الزامی است. + %1$s مورد نیاز است. + %1$s \\\"%2$s\\\" پشتیبانی نمی شود. انواع پشتیبانی شده: %3$s. + %1$s برای پارامترهای \\\"%2$s\\\" مورد نیاز است. + %1$s باید یک عدد محدود باشد. + %1$s باید یک عدد صحیح باشد. + %1$s باید درست یا نادرست باشد. + %1$s باید یک رشته رنگ، آرایه RGB/RGBA یا شی رنگ باشد. + %1$s باید از قالب #RRGGBB یا #RRGGBBAA استفاده کند. + %1$s باید دارای کانال های رنگی هگزادسیمال معتبر باشد. + %1$s باید دارای 3 یا 4 کانال رنگی باشد. + %1$s باید بین 0 تا 255 باشد. + %1$s باید یک آرایه دو عددی یا یک شی با x و y باشد. + %1$s باید دقیقاً 2 عدد داشته باشد. + تکراری + همیشه EXIF ​​را پاک کنید + داده های EXIF ​​تصویر را در ذخیره حذف کنید، حتی زمانی که ابزاری درخواست نگه داشتن ابرداده را دارد + راهنما و نکات + %1$d آموزش + ابزار را باز کنید + ابزارهای اصلی، گزینه های صادرات، جریان های PDF، ابزارهای رنگی و رفع مشکلات رایج را بیاموزید + شروع کردن + ابزارها را انتخاب کنید، فایل‌ها را وارد کنید، نتایج را ذخیره کنید و از تنظیمات سریع‌تر استفاده کنید + ویرایش تصویر + تغییر اندازه، برش، فیلتر، پاک کردن پس‌زمینه، کشیدن و واترمارک تصاویر + فایل ها و ابرداده ها + تبدیل فرمت ها، مقایسه خروجی ها، محافظت از حریم خصوصی و کنترل نام فایل ها + PDF و اسناد + PDF ایجاد کنید، اسناد، صفحات OCR را اسکن کنید و فایل‌ها را برای اشتراک‌گذاری آماده کنید + متن، QR، و داده + متن را بشناسید، کدها را اسکن کنید، Base64 را رمزگذاری کنید، هش ها و فایل های بسته را بررسی کنید + ابزار رنگ + رنگ‌ها را انتخاب کنید، پالت‌ها بسازید، کتابخانه‌های رنگی را کاوش کنید و شیب ایجاد کنید + ابزارهای خلاقانه + SVG، هنر ASCII، بافت های نویز، سایه بان ها و تصاویر تجربی را ایجاد کنید + عیب یابی + مشکلات فایل های سنگین، شفافیت، اشتراک گذاری، واردات و گزارش را برطرف کنید + ابزار مناسب را انتخاب کنید + از دسته‌ها، جستجو، موارد دلخواه و اشتراک‌گذاری کنش‌ها برای شروع از مکان مناسب استفاده کنید + از بهترین نقطه ورودی شروع کنید + Image Toolbox ابزارهای متمرکز زیادی دارد و بسیاری از آنها در نگاه اول با هم همپوشانی دارند. از کاری که می‌خواهید به پایان برسانید، شروع کنید، سپس ابزاری را انتخاب کنید که مهم‌ترین بخش نتیجه را کنترل می‌کند: اندازه، قالب، ابرداده، متن، صفحات PDF، رنگ‌ها یا طرح‌بندی. + هنگامی که نام عمل را می دانید، مانند تغییر اندازه، PDF، EXIF، OCR، QR یا رنگ، از جستجو استفاده کنید. + زمانی که فقط نوع کار را می‌دانید، یک دسته را باز کنید، سپس ابزارهای مکرر را به عنوان موارد دلخواه پین ​​کنید. + وقتی برنامه دیگری تصویری را در جعبه ابزار تصویر به اشتراک می‌گذارد، ابزار را از جریان برگه اشتراک‌گذاری انتخاب کنید. + نتایج را وارد کنید، ذخیره کنید و به اشتراک بگذارید + جریان معمول از انتخاب ورودی تا صدور فایل ویرایش شده را درک کنید + جریان ویرایش رایج را دنبال کنید + اکثر ابزارها از یک ریتم پیروی می کنند: ورودی را انتخاب کنید، گزینه ها را تنظیم کنید، پیش نمایش دهید، سپس ذخیره کنید یا به اشتراک بگذارید. جزئیات مهم این است که ذخیره کردن یک فایل خروجی ایجاد می کند، در حالی که اشتراک گذاری معمولا برای انتقال سریع به برنامه دیگر بهترین است. + هنگامی که انتخابگر اجازه می دهد، یک فایل را برای ابزارهای تک تصویری یا چندین فایل را برای ابزارهای دسته ای انتخاب کنید. + پیش‌نمایش و کنترل‌های خروجی را قبل از ذخیره بررسی کنید، به خصوص گزینه‌های قالب، کیفیت و نام فایل. + از اشتراک‌گذاری برای انتقال سریع استفاده کنید، یا زمانی که به یک کپی دائمی در پوشه انتخابی نیاز دارید، ذخیره کنید. + همزمان با بسیاری از تصاویر کار کنید + ابزارهای دسته ای برای کارهای تکراری تغییر اندازه، تبدیل، فشرده سازی و نامگذاری بهترین هستند + یک دسته قابل پیش بینی آماده کنید + پردازش دسته‌ای سریع‌ترین زمانی است که هر خروجی باید از قوانین یکسانی پیروی کند. همچنین ساده ترین مکان برای انجام یک اشتباه بزرگ است، بنابراین قبل از شروع یک صادرات طولانی، نام، کیفیت، ابرداده و رفتار پوشه را انتخاب کنید. + تمام تصاویر مرتبط را با هم انتخاب کنید و اگر خروجی به ترتیب بستگی دارد، ترتیب را حفظ کنید. + قبل از شروع صادرات یکبار قوانین تغییر اندازه، قالب، کیفیت، فراداده و نام فایل را تنظیم کنید. + هنگامی که فایل های منبع بزرگ هستند یا زمانی که قالب مورد نظر برای شما جدید است ابتدا یک دسته آزمایشی کوچک را اجرا کنید. + ویرایش سریع تک + هنگامی که به چندین تغییر ساده در یک تصویر نیاز دارید از ویرایشگر همه کاره استفاده کنید + یک تصویر را بدون پرش ابزار تمام کنید + ویرایش واحد زمانی مفید است که تصویر به جای یک عملیات تخصصی، به زنجیره کوچکی از تغییرات نیاز دارد. معمولاً برای پاک‌سازی سریع، پیش‌نمایش و صادر کردن یک نسخه نهایی بدون ایجاد یک گردش کار دسته‌ای سریع‌تر است. + ویرایش تک را برای یک تصویر که نیاز به تنظیمات معمول، نشانه گذاری، برش، چرخش یا تغییرات صادراتی دارد باز کنید. + ویرایش‌ها را به ترتیبی اعمال کنید که ابتدا کمترین پیکسل را تغییر دهد، مانند برش قبل از فیلترها و فیلترها قبل از فشرده‌سازی نهایی. + هنگامی که به پردازش دسته ای، اهداف اندازه فایل دقیق، خروجی PDF، OCR یا پاکسازی ابرداده نیاز دارید، به جای آن از یک ابزار تخصصی استفاده کنید. + از تنظیمات و تنظیمات قبلی استفاده کنید + انتخاب های مکرر را ذخیره کنید و برنامه را برای گردش کار دلخواه خود تنظیم کنید + از تکرار همان تنظیمات خودداری کنید + تنظیمات از پیش تعیین شده و تنظیمات زمانی مفید هستند که یک نوع فایل را اغلب صادر می کنید. یک پیش تنظیم خوب یک چک لیست دستی مکرر را به یک ضربه تبدیل می کند، در حالی که تنظیمات جهانی پیش فرض هایی را تعریف می کنند که ابزارهای جدید با آن شروع می شوند. + از پیش تنظیم‌ها برای اندازه‌های خروجی، قالب‌ها، مقادیر کیفیت یا الگوهای نام‌گذاری رایج ایجاد کنید. + تنظیمات قالب، کیفیت، پوشه ذخیره، نام فایل، انتخابگر و رفتار رابط را مرور کنید. + قبل از نصب مجدد برنامه یا انتقال به دستگاه دیگری از تنظیمات نسخه پشتیبان تهیه کنید. + تنظیم پیش فرض های مفید + قالب، کیفیت، حالت مقیاس، نوع تغییر اندازه و فضای رنگی پیش‌فرض را یکبار انتخاب کنید + کاری کنید که ابزارهای جدید به هدف شما نزدیکتر شوند + مقادیر پیش‌فرض فقط اولویت‌های آرایشی نیستند. آن‌ها تصمیم می‌گیرند که چه قالب، کیفیت، رفتار تغییر اندازه، حالت مقیاس و فضای رنگ ابتدا در بسیاری از ابزارها ظاهر شود، که به جلوگیری از انتخاب مجدد گزینه‌های مشابه در هر صادرات کمک می‌کند. + قالب و کیفیت تصویر پیش‌فرض را برای مطابقت با مقصد معمول خود تنظیم کنید، مانند JPEG برای عکس‌ها یا PNG/WebP برای آلفا. + اگر اغلب برای ابعاد سخت، پلتفرم‌های اجتماعی یا صفحات سند صادر می‌کنید، نوع تغییر اندازه و حالت مقیاس پیش‌فرض را تنظیم کنید. + پیش‌فرض‌های فضای رنگی را تنها زمانی تغییر دهید که گردش کار شما به مدیریت رنگ ثابت در نمایشگرها، چاپ یا ویرایشگرهای خارجی نیاز دارد. + انتخاب کننده و پرتاب کننده + هنگامی که برنامه تعداد زیادی دیالوگ را باز می کند یا در مکان اشتباهی شروع به کار می کند، از تنظیمات انتخابگر استفاده کنید + کاری کنید باز کردن فایل ها با عادت شما مطابقت داشته باشد + تنظیمات انتخابگر و راه‌انداز کنترل می‌کنند که جعبه ابزار تصویر از صفحه اصلی شروع شود، یک ابزار یا جریان انتخاب فایل. این گزینه‌ها مخصوصاً زمانی مفید هستند که معمولاً از برگه اشتراک‌گذاری اندروید وارد می‌شوید یا زمانی که می‌خواهید برنامه بیشتر شبیه یک راه‌انداز ابزار باشد. + اگر انتخابگر سیستم فایل‌ها را مخفی می‌کند، موارد اخیر را بیش از حد نشان می‌دهد، یا فایل‌های ابری را ضعیف مدیریت می‌کند، از تنظیمات انتخابگر عکس استفاده کنید. + انتخاب پرش را فقط برای ابزارهایی که معمولاً از اشتراک‌گذاری وارد می‌کنید یا از قبل فایل منبع را می‌دانید، فعال کنید. + اگر می‌خواهید جعبه ابزار تصویر بیشتر شبیه انتخابگر ابزار باشد تا یک صفحه اصلی معمولی، حالت راه‌انداز را مرور کنید. + منبع تصویر + حالت انتخابگر را انتخاب کنید که با گالری‌ها، مدیریت فایل‌ها و فایل‌های ابری بهتر کار کند + فایل ها را از منبع مناسب انتخاب کنید + تنظیمات منبع تصویر بر نحوه درخواست برنامه از Android برای تصاویر تأثیر می گذارد. بهترین انتخاب بستگی به این دارد که آیا فایل‌های شما در گالری سیستم، پوشه مدیر فایل، ارائه‌دهنده ابر یا برنامه دیگری که محتوای موقت را به اشتراک می‌گذارد، زندگی می‌کنند. + هنگامی که می خواهید بیشترین تجربه یکپارچه سیستم را برای تصاویر گالری رایج داشته باشید، از انتخابگر پیش فرض استفاده کنید. + اگر آلبوم‌ها، پوشه‌ها، فایل‌های ابری یا قالب‌های غیراستاندارد در جایی که انتظار دارید ظاهر نمی‌شوند، حالت انتخابگر را تغییر دهید. + اگر یک فایل به اشتراک گذاشته شده پس از خروج از برنامه منبع ناپدید شد، آن را از طریق یک انتخابگر دائمی باز کنید یا ابتدا یک کپی محلی ذخیره کنید. + موارد دلخواه و ابزار به اشتراک گذاری + صفحه اصلی را متمرکز نگه دارید و ابزارهایی را که در جریان های اشتراک گذاری معنی ندارند پنهان کنید + نویز لیست ابزار را کاهش دهید + موارد دلخواه و تنظیمات پنهان برای اشتراک گذاری زمانی مفید هستند که هر روز فقط از چند ابزار استفاده می کنید. آنها همچنین با پنهان کردن ابزارهایی که نمی توانند از نوع فایلی که برنامه دیگری ارسال می کند استفاده کنند، جریان اشتراک گذاری را عملی نگه می دارند. + ابزارهای مکرر را پین کنید تا دسترسی به آنها آسان باشد حتی وقتی ابزارها بر اساس دسته بندی گروه بندی می شوند. + وقتی ابزارها برای فایل‌هایی که از برنامه دیگری می‌آیند نامربوط هستند، از جریان‌های اشتراک‌گذاری پنهان کنید. + اگر موارد دلخواه را در پایان ترجیح می دهید یا با ابزارهای مرتبط گروه بندی می شوند، از تنظیمات سفارش دلخواه استفاده کنید. + از تنظیمات نسخه پشتیبان تهیه کنید + تنظیمات از پیش تعیین شده، تنظیمات برگزیده و رفتار برنامه را با خیال راحت به نصب دیگری منتقل کنید + راه اندازی خود را قابل حمل نگه دارید + پشتیبان‌گیری و بازیابی پس از تنظیم پیش‌تنظیمات، پوشه‌ها، قوانین نام فایل، ترتیب ابزار و تنظیمات بصری بسیار مفید است. پشتیبان‌گیری به شما امکان می‌دهد تنظیمات را آزمایش کنید یا برنامه را مجدداً نصب کنید، بدون اینکه جریان کاری خود را از حافظه بازسازی کنید. + پس از تغییر تنظیمات پیش‌فرض، رفتار نام فایل، فرمت‌های پیش‌فرض یا ترتیب ابزار، یک نسخه پشتیبان ایجاد کنید. + اگر می‌خواهید داده‌ها را پاک کنید، دوباره نصب کنید یا دستگاه‌ها را جابه‌جا کنید، نسخه پشتیبان را در جایی خارج از پوشه برنامه ذخیره کنید. + تنظیمات را قبل از پردازش دسته‌های مهم بازیابی کنید تا رفتار پیش‌فرض و ذخیره با تنظیمات قبلی شما مطابقت داشته باشد. + تغییر اندازه و تبدیل تصاویر + اندازه، قالب، کیفیت و ابرداده را برای یک یا چند تصویر تغییر دهید + کپی های تغییر اندازه ایجاد کنید + تغییر اندازه و تبدیل ابزار اصلی برای ابعاد قابل پیش بینی و فایل های خروجی سبک تر است. زمانی از آن استفاده کنید که اندازه تصویر، فرمت خروجی و رفتار ابرداده به طور همزمان اهمیت داشته باشند. + یک یا چند تصویر را انتخاب کنید، سپس انتخاب کنید که آیا ابعاد، مقیاس یا اندازه فایل بیشتر اهمیت دارد. + فرمت و کیفیت خروجی را انتخاب کنید. زمانی که شفافیت باید حفظ شود از PNG یا WebP استفاده کنید. + پیش نمایش نتیجه را مشاهده کنید، سپس نسخه های تولید شده را بدون تغییر نسخه اصلی ذخیره یا به اشتراک بگذارید. + تغییر اندازه بر اساس اندازه فایل + زمانی که یک وب سایت، پیام رسان یا فرم تصاویر سنگین را رد می کند، محدودیت اندازه را هدف قرار دهید + متناسب با محدودیت های آپلود سخت + تغییر اندازه بر اساس اندازه فایل بهتر از حدس زدن مقادیر کیفیت زمانی است که مقصد فقط فایل‌های زیر یک محدودیت خاص را می‌پذیرد. ابزار ممکن است فشرده سازی و ابعاد را برای رسیدن به هدف تنظیم کند در حالی که سعی می کند نتیجه را قابل استفاده نگه دارد. + اندازه هدف را کمی کمتر از حد آپلود واقعی وارد کنید تا فضایی برای متادیتا و تغییرات ارائه دهنده باقی بماند. + وقتی هدف کوچک است، فرمت‌های با اتلاف را برای عکس‌ها ترجیح دهید. PNG حتی پس از تغییر اندازه می تواند بزرگ بماند. + پس از صادرات، صورت‌ها، متن و لبه‌ها را بررسی کنید زیرا اهداف با اندازه تهاجمی می‌توانند مصنوعات قابل مشاهده را معرفی کنند. + تغییر اندازه را محدود کنید + فقط تصاویری را کوچک کنید که بیش از حداکثر عرض، ارتفاع یا محدودیت فایل شما باشد + از تغییر اندازه تصاویری که از قبل خوب هستند خودداری کنید + Limit Resize برای پوشه های ترکیبی که برخی از تصاویر بزرگ هستند و برخی دیگر از قبل آماده شده اند مفید است. به جای اینکه هر فایلی را مجبور به تغییر اندازه یکسان کند، تصاویر قابل قبول را به کیفیت اصلی خود نزدیکتر نگه می دارد. + به جای تغییر اندازه هر فایل کورکورانه، حداکثر ابعاد یا محدودیت ها را بر اساس مقصد تنظیم کنید. + وقتی می‌خواهید از خروجی‌هایی که از منابع سنگین‌تر می‌شوند اجتناب کنید، رفتار سبک skip-if-larger را فعال کنید. + از این برای دسته‌هایی از دوربین‌های مختلف، اسکرین‌شات‌ها یا دانلودهایی استفاده کنید که اندازه منبع بسیار متفاوت است. + برش دهید، بچرخانید و صاف کنید + قبل از صادرات یا استفاده از تصویر در ابزارهای دیگر، ناحیه مهم را قاب کنید + ترکیب را تمیز کنید + برش ابتدا فیلترهای بعدی، OCR، صفحات PDF و نتایج اشتراک‌گذاری را پاک‌تر می‌کند. + Crop را باز کنید و تصویری را که می خواهید تنظیم کنید انتخاب کنید. + زمانی که خروجی باید با یک پست، سند یا آواتار مطابقت داشته باشد، از برش رایگان یا نسبت ابعاد استفاده کنید. + قبل از ذخیره، بچرخانید یا ورق بزنید تا ابزارهای بعدی تصویر اصلاح شده را دریافت کنند. + اعمال فیلترها و تنظیمات + یک پشته فیلتر قابل ویرایش بسازید و نتیجه را قبل از صادرات پیش نمایش کنید + تنظیم ظاهر تصویر + فیلترها برای اصلاحات سریع، خروجی سبک و آماده کردن تصاویر برای OCR یا چاپ مفید هستند. تغییرات کوچک در کنتراست، وضوح و روشنایی می تواند بیشتر از جلوه های سنگین مهم باشد، زمانی که تصویر حاوی متن یا جزئیات دقیق باشد. + فیلترها را یکی یکی اضافه کنید و پس از هر تغییر به پیش نمایش آن توجه کنید. + روشنایی، کنتراست، وضوح، تاری، رنگ یا ماسک را بسته به کار تنظیم کنید. + فقط زمانی صادر کنید که پیش‌نمایش با دستگاه، سند یا پلتفرم اجتماعی مورد نظر شما مطابقت داشته باشد. + ابتدا پیش نمایش + قبل از انتخاب ابزار ویرایش، تبدیل یا پاکسازی سنگین‌تر، تصاویر را بررسی کنید + آنچه را که واقعا دریافت کرده اید را بررسی کنید + پیش‌نمایش تصویر زمانی مفید است که فایلی از چت، فضای ذخیره‌سازی ابری یا برنامه دیگری می‌آید و مطمئن نیستید حاوی چه چیزی است. بررسی ابعاد، شفافیت، جهت‌گیری و کیفیت بصری ابتدا به انتخاب ابزار پیگیری صحیح کمک می‌کند. + زمانی که باید چندین تصویر را قبل از تصمیم گیری در مورد اینکه با آنها چه کاری انجام دهید، بررسی کنید، پیش نمایش تصویر را باز کنید. + به دنبال مشکلات چرخش، مناطق شفاف، مصنوعات فشرده سازی و ابعاد غیرمنتظره بزرگ باشید. + تنها زمانی به تغییر اندازه، برش، مقایسه، EXIF ​​یا Background Remover بروید که بدانید چه چیزی نیاز به تعمیر دارد. + حذف یا بازیابی پس زمینه + پس زمینه ها را به طور خودکار پاک کنید یا لبه ها را به صورت دستی با ابزارهای بازیابی اصلاح کنید + موضوع را نگه دارید، بقیه را حذف کنید + حذف پس‌زمینه زمانی بهترین کار را انجام می‌دهد که انتخاب خودکار را با پاک‌سازی دستی دقیق ترکیب کنید. فرمت صادرات نهایی به اندازه ماسک اهمیت دارد، زیرا JPEG حتی اگر پیش نمایش درست به نظر برسد، مناطق شفاف را صاف می کند. + اگر سوژه به وضوح از پس زمینه جدا شده است، با حذف خودکار شروع کنید. + بزرگنمایی کنید و بین پاک کردن و بازیابی جابه‌جا شوید تا موها، گوشه‌ها، سایه‌ها و جزئیات کوچک را اصلاح کنید. + در صورت نیاز به شفافیت در فایل نهایی به صورت PNG یا WebP ذخیره کنید. + رسم، علامت گذاری و واترمارک کنید + فلش‌ها، یادداشت‌ها، برجسته‌سازی‌ها، امضاها یا همپوشانی‌های مکرر واترمارک را اضافه کنید + اطلاعات قابل مشاهده را اضافه کنید + ابزارهای نشانه گذاری به توضیح تصویر کمک می کنند، در حالی که واترمارک به برندسازی یا محافظت از فایل های صادر شده کمک می کند. + هنگامی که به یادداشت ها، اشکال، برجسته سازی یا حاشیه نویسی سریع نیاز دارید، از Draw استفاده کنید. + هنگامی که همان علامت متن یا تصویر باید به طور مداوم در خروجی ها ظاهر شود از Watermarking استفاده کنید. + قبل از صادرات، کدورت، موقعیت و مقیاس را بررسی کنید تا علامت قابل مشاهده باشد اما باعث حواس پرتی نشود. + قرعه کشی پیش فرض ها + عرض خط، رنگ، حالت مسیر، قفل جهت و ذره بین را برای نشانه گذاری سریعتر تنظیم کنید + ایجاد حس قابل پیش بینی بودن نقاشی + هنگام حاشیه نویسی اسکرین شات، علامت گذاری اسناد، یا امضای تصاویر اغلب، تنظیمات ترسیم مفید هستند. پیش‌فرض‌ها زمان راه‌اندازی را کاهش می‌دهند، در حالی که ذره‌بین و قفل جهت، ویرایش‌های دقیق را در صفحه‌های کوچک آسان‌تر می‌کنند. + پهنای خط پیش‌فرض را تنظیم کنید و رنگ را روی مقادیری که بیشتر برای فلش‌ها، هایلایت‌ها یا امضاها استفاده می‌کنید بکشید. + زمانی که معمولاً همان نوع خطوط، اشکال یا نشانه گذاری را ترسیم می کنید، از حالت مسیر پیش فرض استفاده کنید. + زمانی که با ورود انگشت، قرار دادن دقیق جزئیات کوچک سخت می شود، ذره بین یا قفل جهت را فعال کنید. + طرح بندی چند تصویری + قبل از مرتب کردن اسکرین شات، اسکن یا نوارهای مقایسه، ابزار چند تصویری مناسب را انتخاب کنید + از ابزار طرح بندی مناسب استفاده کنید + این ابزارها شبیه به هم هستند، اما هر کدام برای نوع متفاوتی از خروجی چند تصویری تنظیم شده اند. انتخاب مناسب ابتدا از مبارزه با کنترل‌های طرح‌بندی که برای نتیجه‌ای متفاوت طراحی شده‌اند، اجتناب می‌کند. + از Collage Maker برای طرح بندی های طراحی شده با چندین تصویر در یک ترکیب استفاده کنید. + هنگامی که تصاویر باید به یک نوار بلند عمودی یا افقی تبدیل شوند، از Image Stitching یا Stacking استفاده کنید. + هنگامی که یک تصویر بزرگ باید چندین قطعه کوچکتر شود، از Image Splitting استفاده کنید. + تبدیل فرمت های تصویر + ایجاد JPEG، PNG، WebP، AVIF، JXL، و کپی های دیگر بدون ویرایش پیکسل ها به صورت دستی + قالب کار را انتخاب کنید + فرمت های مختلف برای عکس ها، اسکرین شات ها، شفافیت، فشرده سازی یا سازگاری بهتر هستند. تبدیل زمانی امن‌تر است که بدانید برنامه مقصد از چه چیزی پشتیبانی می‌کند و آیا منبع حاوی آلفا، انیمیشن یا ابرداده‌ای است که می‌خواهید نگه دارید. + از JPEG برای عکس‌های سازگار، PNG برای تصاویر بدون اتلاف و آلفا و WebP برای اشتراک‌گذاری فشرده استفاده کنید. + هنگامی که قالب از آن پشتیبانی می کند، کیفیت را تنظیم کنید. مقادیر کمتر اندازه را کاهش می دهد اما می تواند مصنوعات را اضافه کند. + نسخه‌های اصلی را تا زمانی که تأیید کنید فایل‌های تبدیل‌شده به درستی در برنامه هدف باز می‌شوند، نگه دارید. + EXIF و حریم خصوصی را بررسی کنید + قبل از اشتراک‌گذاری عکس‌های حساس، ابرداده‌ها را مشاهده، ویرایش یا حذف کنید + کنترل داده های تصویر پنهان + EXIF می‌تواند حاوی داده‌های دوربین، تاریخ، مکان، جهت و سایر جزئیات غیرقابل مشاهده در تصویر باشد. قبل از اشتراک‌گذاری عمومی، گزارش‌های پشتیبانی، فهرست‌های بازار یا هر عکسی که ممکن است یک مکان خصوصی را نشان دهد، ارزش آن را دارد. + قبل از اشتراک‌گذاری عکس‌هایی که ممکن است حاوی اطلاعات مکان یا دستگاه خصوصی باشند، ابزار EXIF ​​را باز کنید. + فیلدهای حساس را حذف کنید یا برچسب های نادرست مانند تاریخ، جهت، نویسنده یا داده های دوربین را ویرایش کنید. + یک کپی جدید ذخیره کنید و در صورت اهمیت به حریم خصوصی، آن کپی را به جای نسخه اصلی به اشتراک بگذارید. + پاکسازی خودکار EXIF + در حالی که تصمیم می گیرید آیا تاریخ ها باید حفظ شوند، به طور پیش فرض ابرداده را حذف کنید + حریم خصوصی را پیش فرض قرار دهید + گروه تنظیمات EXIF ​​زمانی مفید است که بخواهید به جای به خاطر سپردن آن برای هر صادرات، پاکسازی حریم خصوصی به طور خودکار انجام شود. Keep Date Time یک تصمیم جداگانه است زیرا تاریخ ها می توانند برای مرتب سازی مفید باشند اما برای اشتراک گذاری حساس هستند. + هنگامی که بیشتر صادرات شما برای اشتراک‌گذاری عمومی یا نیمه عمومی است، EXIF ​​همیشه پاک را فعال کنید. + فقط زمانی که حفظ زمان ضبط مهمتر از حذف هر مهر زمانی باشد، Keep Date Time را فعال کنید. + از ویرایش دستی EXIF ​​برای فایل‌های یکباره استفاده کنید، جایی که باید برخی از فیلدها را نگه دارید و برخی دیگر را حذف کنید. + کنترل نام فایل ها + استفاده از پیشوندها، پسوندها، مُهرهای زمانی، پیش‌تنظیم‌ها، جمع‌های کنترلی و اعداد ترتیبی + صادرات را به راحتی پیدا کنید + یک الگوی نام فایل خوب به مرتب کردن دسته ها کمک می کند و از بازنویسی تصادفی جلوگیری می کند. تنظیمات نام فایل به ویژه زمانی مفید است که صادرات به پوشه منبع باز می گردد، جایی که نام های مشابه می توانند به سرعت گیج کننده شوند. + پیشوند، پسوند، مُهر زمان، نام اصلی، اطلاعات از پیش تعیین شده یا گزینه های دنباله نام فایل را در تنظیمات تنظیم کنید. + از اعداد دنباله‌ای برای دسته‌هایی که ترتیب مهم است، مانند صفحات، فریم‌ها یا کلاژها استفاده کنید. + بازنویسی را فقط زمانی فعال کنید که مطمئن باشید جایگزین کردن فایل‌های موجود برای پوشه فعلی ایمن است. + بازنویسی فایل ها + تنها زمانی که گردش کار شما عمداً مخرب است، خروجی ها را با نام های منطبق جایگزین کنید + از حالت رونویسی با دقت استفاده کنید + بازنویسی فایل ها نحوه رسیدگی به تضادهای نامگذاری را تغییر می دهد. برای صادرات مکرر به یک پوشه مفید است، اما اگر الگوی نام فایل شما دوباره همان نام را تولید کند، می‌تواند جایگزین نتایج قبلی شود. + بازنویسی را فقط برای پوشه‌هایی فعال کنید که جایگزینی فایل‌های تولید شده قدیمی‌تر مورد انتظار و قابل بازیابی است. + هنگام صادرات نسخه‌های اصلی، فایل‌های مشتری، اسکن‌های یک‌باره یا هر چیزی بدون پشتیبان، رونویسی را غیرفعال نگه دارید. + بازنویسی را با یک الگوی نام فایل عمدی ترکیب کنید تا فقط خروجی های مورد نظر جایگزین شوند. + الگوهای نام فایل + ترکیب اسامی اصلی، پیشوندها، پسوندها، مُهرهای زمانی، از پیش تنظیم‌ها، حالت مقیاس و جمع‌بندی‌ها + نام هایی بسازید که خروجی را توضیح دهند + تنظیمات الگوی نام فایل می تواند فایل های صادر شده را به تاریخچه مفیدی از اتفاقاتی که برای آنها افتاده تبدیل کند. این به صورت دسته‌ای اهمیت دارد، زیرا نمای پوشه ممکن است تنها مکانی باشد که می‌توانید اندازه اصلی، از پیش تعیین شده، قالب و ترتیب پردازش را تشخیص دهید. + زمانی که برای مرتب‌سازی دستی به نام‌های قابل خواندن نیاز دارید، از نام، پیشوند، پسوند و مهر زمانی اصلی استفاده کنید. + هنگامی که خروجی ها باید نحوه تولید آنها را مستند کنند، از پیش تعیین شده، حالت مقیاس، اندازه فایل، یا اطلاعات چک جمع اضافه کنید. + از نام‌های تصادفی برای گردش‌های کاری که فایل‌ها باید با نسخه‌های اصلی یا ترتیب صفحه جفت شوند، خودداری کنید. + محل ذخیره فایل ها را انتخاب کنید + با تنظیم پوشه‌ها، ذخیره‌سازی پوشه اصلی و مکان‌های ذخیره یک‌باره از صادرات جلوگیری کنید + خروجی ها را قابل پیش بینی نگه دارید + وقتی فایل‌های زیادی را پردازش می‌کنید یا فایل‌هایی را از ارائه‌دهندگان ابری به اشتراک می‌گذارید، رفتار ذخیره بیشترین اهمیت را دارد. تنظیمات پوشه تصمیم می‌گیرد که آیا خروجی‌ها در یک مکان شناخته شده جمع‌آوری می‌شوند، در کنار نسخه‌های اصلی ظاهر می‌شوند یا به طور موقت برای یک کار خاص به جای دیگری می‌روند. + اگر می‌خواهید همیشه در یک مکان صادرات داشته باشید، یک پوشه ذخیره پیش‌فرض تنظیم کنید. + از save-to-original-folder برای گردش‌های کاری پاک‌سازی استفاده کنید، جایی که خروجی باید نزدیک فایل منبع بماند. + زمانی که یک کار به مقصد دیگری نیاز دارد بدون تغییر پیش‌فرض، از مکان ذخیره‌سازی یک بار استفاده کنید. + یکبار ذخیره پوشه ها + صادرات بعدی را بدون تغییر مقصد عادی خود به یک پوشه موقت ارسال کنید + مشاغل خاص را جدا نگه دارید + مکان ذخیره یکباره برای پروژه های موقت، پوشه های مشتری، جلسات پاکسازی یا صادراتی که نباید با پوشه جعبه ابزار تصویر معمولی شما ترکیب شود مفید است. بدون بازنویسی تنظیمات پیش فرض شما، یک مقصد کوتاه مدت را ارائه می دهد. + قبل از شروع کاری که به خارج از محل صادرات عادی شما تعلق دارد، یک پوشه یکبار مصرف انتخاب کنید. + از آن برای پروژه‌های کوتاه، پوشه‌های مشترک یا دسته‌هایی استفاده کنید که خروجی‌ها باید با هم گروه‌بندی شوند. + پس از انجام کار به پوشه پیش فرض برگردید تا صادرات بعدی به طور غیرمنتظره در مکان موقت قرار نگیرد. + تعادل کیفیت و اندازه فایل + درک کنید که به جای حدس زدن چه زمانی باید ابعاد، قالب یا کیفیت را تغییر دهید + فایل ها را عمدا کوچک کنید + اندازه فایل به ابعاد، فرمت، کیفیت، شفافیت و پیچیدگی محتوا بستگی دارد. اگر فایلی هنوز خیلی سنگین است، تغییر ابعاد معمولاً بیشتر از کاهش مکرر کیفیت کمک می کند. + وقتی تصویر بزرگتر از آن چیزی است که مقصد می تواند نمایش دهد، ابتدا ابعاد را تغییر دهید. + کیفیت پایین‌تر فقط برای قالب‌های با افت کیفیت و بررسی متن، لبه‌ها، گرادیان‌ها و چهره‌ها پس از صادرات. + زمانی که تغییرات کیفیت کافی نیست، قالب را تغییر دهید، برای مثال WebP برای اشتراک‌گذاری یا PNG برای دارایی‌های شفاف واضح. + با فرمت های متحرک کار کنید + وقتی یک فایل به جای یک بیت مپ، فریم دارد از ابزارهای GIF، APNG، WebP و JXL استفاده کنید. + با هر تصویری ثابت رفتار نکنید + فرمت های متحرک به ابزارهایی نیاز دارند که فریم ها، مدت زمان و سازگاری را درک کنند. + هنگامی که به عملیات سطح فریم برای آن فرمت ها نیاز دارید از ابزارهای GIF یا APNG استفاده کنید. + هنگامی که به فشرده سازی مدرن یا خروجی WebP متحرک نیاز دارید از ابزارهای WebP استفاده کنید. + پیش‌نمایش زمان‌بندی انیمیشن قبل از اشتراک‌گذاری، زیرا برخی از پلتفرم‌ها تصاویر متحرک را تبدیل یا مسطح می‌کنند. + مقایسه و پیش نمایش خروجی + تغییرات، اندازه فایل و تفاوت های بصری را قبل از حفظ صادرات بررسی کنید + قبل از اشتراک‌گذاری تأیید کنید + ابزارهای مقایسه کمک می کنند تا مصنوعات فشرده سازی، رنگ های اشتباه یا ویرایش های ناخواسته را زودتر شناسایی کنید. + وقتی می خواهید دو نسخه را در کنار هم بررسی کنید، Compare را باز کنید. + روی لبه‌ها، متن، شیب‌ها و مناطق شفاف بزرگنمایی کنید که در آن‌ها راحت‌تر از دست دادن مشکلات وجود دارد. + اگر خروجی خیلی سنگین یا تار است، به ابزار قبلی برگردید و کیفیت یا فرمت را تنظیم کنید. + از مرکز ابزار PDF استفاده کنید + ادغام، تقسیم، چرخش، حذف صفحات، فشرده سازی، محافظت، OCR و واترمارک فایل های PDF + یک عملیات PDF را انتخاب کنید + مرکز PDF اقدامات سند را در پشت یک نقطه ورودی گروه بندی می کند تا بتوانید عملیات دقیق را انتخاب کنید. هنگامی که فایل قبلاً PDF است از آنجا شروع کنید و از Images to PDF یا Document Scanner زمانی که منبع شما هنوز مجموعه ای از تصاویر است استفاده کنید. + PDF Tools را باز کنید و عملیاتی را انتخاب کنید که با هدف شما مطابقت دارد. + PDF یا تصاویر منبع را انتخاب کنید، سپس ترتیب صفحه، چرخش، حفاظت یا گزینه های OCR را مرور کنید. + PDF تولید شده را ذخیره کنید و یک بار آن را باز کنید تا تعداد صفحات، سفارش و خوانایی آن تایید شود. + تبدیل تصاویر به PDF + اسکن‌ها، اسکرین‌شات‌ها، رسیدها یا عکس‌ها را در یک سند قابل اشتراک‌گذاری ترکیب کنید + یک سند تمیز بسازید + وقتی تصاویر به طور مداوم برش داده شوند، سفارش داده شوند و اندازه آن ها اندازه گیری شود، به صفحات PDF بهتری تبدیل می شوند. با فهرست تصویر مانند یک پشته صفحه رفتار کنید: هر چرخش، حاشیه و انتخاب اندازه صفحه بر میزان خوانایی سند نهایی تأثیر می گذارد. + وقتی لبه‌های صفحه، سایه‌ها یا جهت‌گیری نادرست است، ابتدا تصاویر را برش دهید یا بچرخانید. + تصاویر را به ترتیب صفحه در نظر گرفته شده انتخاب کنید و در صورت ارائه ابزار اندازه یا حاشیه صفحه را تنظیم کنید. + PDF را صادر کنید، سپس قبل از ارسال بررسی کنید که همه صفحات قابل خواندن هستند. + اسناد را اسکن کنید + از صفحات کاغذی عکس بگیرید و آنها را برای PDF، OCR یا اشتراک گذاری آماده کنید + گرفتن صفحات قابل خواندن + اسکن اسناد با صفحات صاف، نور خوب و پرسپکتیو تصحیح شده بهترین کار را دارد. یک اسکن تمیز قبل از صادرات OCR یا PDF باعث صرفه جویی در زمان می شود زیرا تشخیص و فشرده سازی متن هر دو به کیفیت صفحه بستگی دارد. + سند را روی یک سطح متضاد با نور کافی و حداقل سایه قرار دهید. + گوشه های شناسایی شده را تنظیم کنید یا به صورت دستی برش دهید تا صفحه به طور تمیز کادر را پر کند. + به عنوان تصویر یا PDF صادر کنید، سپس در صورت نیاز به متن قابل انتخاب، OCR را اجرا کنید. + محافظت یا باز کردن قفل فایل های PDF + از رمزهای عبور با دقت استفاده کنید و در صورت امکان یک کپی منبع قابل ویرایش را نگه دارید + دسترسی به PDF را با خیال راحت مدیریت کنید + ابزارهای حفاظتی برای اشتراک گذاری کنترل شده مفید هستند، اما رمزهای عبور به راحتی گم می شوند و به سختی بازیابی می شوند. از کپی ها برای توزیع محافظت کنید، فایل های منبع محافظت نشده را جداگانه نگه دارید و قبل از حذف هر چیزی نتیجه را تأیید کنید. + از یک کپی از PDF محافظت کنید، نه تنها سند منبع خود. + قبل از اشتراک گذاری فایل محافظت شده، رمز عبور را در مکانی امن ذخیره کنید. + فقط برای فایل‌هایی که مجاز به ویرایش آن هستید از باز کردن قفل استفاده کنید، سپس بررسی کنید کپی قفل‌شده به درستی باز شود. + سازماندهی صفحات PDF + ادغام، تقسیم، تنظیم مجدد، چرخش، برش، حذف صفحات و افزودن عمدی شماره صفحات + ساختار سند را قبل از تزئین اصلاح کنید + استفاده از ابزارهای صفحه PDF به همان ترتیبی که یک سند کاغذی را تهیه می کنید ساده تر است: صفحات را جمع آوری کنید، صفحات اشتباه را حذف کنید، آنها را مرتب کنید، جهت گیری صحیح را انجام دهید، سپس جزئیات نهایی مانند شماره صفحه یا واترمارک را اضافه کنید. + وقتی سند هنوز بین چندین فایل تقسیم شده است، ابتدا از ادغام یا تصاویر به PDF استفاده کنید. + قبل از افزودن شماره صفحه، امضا یا واترمارک، صفحات را مرتب کنید، بچرخانید، برش دهید یا حذف کنید. + پیش‌نمایش PDF نهایی پس از ویرایش‌های ساختاری، زیرا یک صفحه از دست رفته یا چرخانده شده ممکن است بعداً به سختی متوجه شود. + حاشیه نویسی PDF + وقتی بینندگان نظرات و علامت‌ها را متفاوت نشان می‌دهند، حاشیه‌نویسی‌ها را حذف کنید یا آنها را صاف کنید + آنچه را که قابل مشاهده است کنترل کنید + حاشیه‌نویسی‌ها می‌توانند نظرات، نکات برجسته، نقاشی‌ها، علامت‌های فرم یا سایر اشیاء PDF اضافی باشند. برخی از بینندگان آنها را متفاوت نشان می دهند، بنابراین حذف یا صاف کردن آنها می تواند اسناد مشترک را قابل پیش بینی تر کند. + وقتی نظرات، نکات برجسته یا علامت های مرور نباید در کپی مشترک گنجانده شود، حاشیه نویسی را حذف کنید. + زمانی که علائم قابل مشاهده باید در صفحه باقی بمانند، صاف کنید اما دیگر مانند حاشیه نویسی قابل ویرایش رفتار نمی کنند. + یک کپی اصلی را نگه دارید زیرا حذف یا صاف کردن حاشیه نویسی ممکن است به سختی قابل برگشت باشد. + متن را تشخیص دهید + متن را از تصاویر با موتورهای OCR و زبان های قابل انتخاب استخراج کنید + نتایج OCR بهتری دریافت کنید + دقت OCR به وضوح تصویر، داده های زبان، کنتراست و جهت صفحه بستگی دارد. بهترین نتایج معمولاً از آماده‌سازی تصویر به دست می‌آید: حذف نویز، چرخش عمودی، افزایش خوانایی، سپس تشخیص متن. + تصویر را در قسمت متن برش دهید و قبل از تشخیص به صورت عمودی بچرخانید. + زبان صحیح را انتخاب کنید و در صورت درخواست برنامه، داده های زبان را دانلود کنید. + متن شناسایی شده را کپی یا به اشتراک بگذارید، سپس نام ها، اعداد و علائم نگارشی را به صورت دستی بررسی کنید. + داده های زبان OCR را انتخاب کنید + با تطبیق اسکریپت‌ها، زبان‌ها و مدل‌های OCR دانلود شده، تشخیص را بهبود بخشید + OCR را با متن مطابقت دهید + زبان OCR اشتباه می تواند عکس های خوب را مانند تشخیص بد جلوه دهد. داده های زبان برای اسکریپت ها، علائم نگارشی، اعداد و اسناد ترکیبی مهم هستند، بنابراین زبانی را که در تصویر نشان داده می شود به جای زبان رابط کاربری تلفن انتخاب کنید. + زبان یا اسکریپتی را که در تصویر نشان داده می‌شود، نه فقط زبان تلفن را انتخاب کنید. + قبل از کار آفلاین یا پردازش دسته ای، داده های از دست رفته را دانلود کنید. + برای اسناد با زبان ترکیبی، ابتدا مهم ترین زبان را اجرا کنید و نام ها و اعداد را به صورت دستی بررسی کنید. + PDF های قابل جستجو + متن OCR را دوباره در فایل‌ها بنویسید تا صفحات اسکن شده جستجو و کپی شوند + اسکن ها را به اسناد قابل جستجو تبدیل کنید + OCR می تواند بیشتر از کپی متن در کلیپ بورد انجام دهد. هنگامی که متن شناسایی شده را در یک فایل یا PDF قابل جستجو می نویسید، تصویر صفحه قابل مشاهده باقی می ماند در حالی که جستجو، انتخاب، فهرست بندی یا بایگانی متن آسان تر می شود. + از خروجی PDF قابل جستجو برای اسناد اسکن شده استفاده کنید که همچنان باید شبیه صفحات اصلی باشند. + زمانی که فقط به متن استخراج شده نیاز دارید و به تصویر صفحه اهمیت نمی دهید از نوشتن در فایل استفاده کنید. + اعداد، نام ها و جداول مهم را به صورت دستی بررسی کنید زیرا متن OCR قابل جستجو است اما همچنان ناقص است. + کدهای QR را اسکن کنید + در صورت وجود، محتوای QR را از ورودی دوربین یا تصاویر ذخیره شده بخوانید + کدها را با خیال راحت بخوانید + کدهای QR می‌توانند حاوی پیوندها، داده‌های Wi-Fi، مخاطبین، متن یا سایر محموله‌ها باشند. + Scan QR Code را باز کنید و کد را واضح، صاف و کاملاً درون قاب نگه دارید. + قبل از باز کردن پیوندها یا کپی کردن داده های حساس، محتوای رمزگشایی شده را مرور کنید. + اگر اسکن ناموفق بود، نور را بهبود ببخشید، کد را برش دهید، یا یک تصویر منبع با وضوح بالاتر را امتحان کنید. + از Base64 و checksums استفاده کنید + تصاویر را به صورت متن رمزگذاری کنید، متن را به تصاویر رمزگشایی کنید و یکپارچگی فایل را تأیید کنید + بین فایل ها و متن حرکت کنید + Base64 برای بارهای کوچک تصویر مفید است، در حالی که چک جمع ها به تأیید عدم تغییر فایل ها کمک می کند. این ابزارها در گردش‌های کاری فنی، گزارش‌های پشتیبانی، انتقال فایل و مکان‌هایی که فایل‌های باینری باید موقتاً متن شوند، بسیار مفید هستند. + هنگامی که یک گردش کار به جای فایل باینری به متن نیاز دارد، از ابزار Base64 برای رمزگذاری یک تصویر کوچک استفاده کنید. + Base64 را فقط از منابع قابل اعتماد رمزگشایی کنید و پیش نمایش را قبل از ذخیره بررسی کنید. + هنگامی که نیاز به مقایسه فایل‌های صادر شده یا تأیید آپلود/دانلود دارید، از ابزارهای checksum استفاده کنید. + بسته بندی یا محافظت از داده ها + از ابزارهای ZIP و رمزگذاری برای بسته بندی فایل ها یا تبدیل داده های متنی استفاده کنید + فایل های مرتبط را با هم نگه دارید + ابزارهای بسته بندی زمانی مفید هستند که چندین خروجی باید به صورت یک فایل حرکت کنند. آنها همچنین برای نگه‌داشتن تصاویر، فایل‌های PDF، گزارش‌ها، و ابرداده‌های تولید شده در کنار هم زمانی که نیاز به ارسال یک مجموعه نتایج کامل دارید، خوب هستند. + زمانی که نیاز دارید بسیاری از تصاویر یا اسناد صادر شده را با هم ارسال کنید از ZIP استفاده کنید. + تنها زمانی از ابزار رمز استفاده کنید که روش انتخابی را درک کرده باشید و بتوانید کلیدهای مورد نیاز را با خیال راحت ذخیره کنید. + آرشیو ایجاد شده یا متن تبدیل شده را قبل از حذف فایل های منبع آزمایش کنید. + چک در اسامی + زمانی که منحصر به فرد بودن و یکپارچگی بیش از خوانایی اهمیت دارد، از نام فایل‌های مبتنی بر جمع‌بندی استفاده کنید + نام گذاری فایل ها بر اساس محتوا + زمانی که صادرات ممکن است نام‌های قابل مشاهده تکراری داشته باشد یا زمانی که باید ثابت کنید دو فایل یکسان هستند، مفید هستند. آنها برای مرور دستی کمتر دوستانه هستند، بنابراین از آنها برای بایگانی های فنی و گردش کار تأیید استفاده کنید. + زمانی که هویت محتوا مهم‌تر از عنوان قابل خواندن برای انسان است، چک‌سوم را به‌عنوان نام فایل فعال کنید. + از آن برای بایگانی، کپی برداری، صادرات قابل تکرار، یا فایل هایی که ممکن است بارگذاری و دانلود شوند استفاده کنید. + زمانی که افراد هنوز نیاز دارند بفهمند فایل چه چیزی را نشان می‌دهد، نام‌های جمع‌بندی را با پوشه‌ها یا ابرداده‌ها مرتبط کنید. + رنگ ها را از تصاویر انتخاب کنید + پیکسل ها را نمونه برداری کنید، کدهای رنگی را کپی کنید و از رنگ ها در پالت ها یا طرح ها دوباره استفاده کنید + رنگ دقیق را نمونه برداری کنید + انتخاب رنگ برای تطبیق یک طرح، استخراج رنگ های برند یا بررسی مقادیر تصویر مفید است. بزرگ‌نمایی مهم است زیرا ضد تغییر، سایه‌ها و فشرده‌سازی می‌توانند پیکسل‌های همسایه را کمی متفاوت از رنگ مورد انتظارتان کنند. + Pick Color From Image را باز کنید و یک تصویر منبع با رنگ مورد نیاز خود انتخاب کنید. + قبل از نمونه‌برداری از جزئیات کوچک، بزرگ‌نمایی کنید تا پیکسل‌های نزدیک روی نتیجه تأثیری نگذارند. + رنگ را در قالب مورد نیاز مقصد خود مانند HEX، RGB یا HSV کپی کنید. + پالت ایجاد کنید و از کتابخانه رنگ استفاده کنید + پالت ها را استخراج کنید، رنگ های ذخیره شده را مرور کنید و سیستم های بصری را ثابت نگه دارید + یک پالت قابل استفاده مجدد بسازید + پالت‌ها کمک می‌کنند تا گرافیک‌ها، واترمارک‌ها و نقاشی‌های صادر شده از نظر بصری سازگار باشند. آنها به ویژه زمانی مفید هستند که به طور مکرر اسکرین شات ها را حاشیه نویسی می کنید، دارایی های برند را آماده می کنید یا به رنگ های یکسان در چندین ابزار نیاز دارید. + یک پالت از یک تصویر ایجاد کنید یا زمانی که مقادیر را از قبل می دانید رنگ ها را به صورت دستی اضافه کنید. + رنگ‌های مهم را نام‌گذاری یا سازماندهی کنید تا بتوانید بعداً دوباره آنها را پیدا کنید. + از مقادیر کپی شده در Draw، watermark، gradient، SVG یا ابزارهای طراحی خارجی استفاده کنید. + گرادیان ایجاد کنید + برای پس‌زمینه، مکان‌ها و آزمایش‌های بصری، تصاویر گرادیان بسازید + انتقال رنگ را کنترل کنید + ابزارهای گرادیان زمانی مفید هستند که به جای ویرایش تصویر موجود، به یک تصویر تولید شده نیاز دارید. آن‌ها می‌توانند پس‌زمینه تمیز، مکان‌نما، کاغذ دیواری، ماسک یا دارایی‌های ساده برای ابزارهای طراحی خارجی باشند. + نوع گرادیان را انتخاب کنید و ابتدا رنگ های مهم را تنظیم کنید. + جهت، توقف، صافی و اندازه خروجی را برای مورد استفاده هدف تنظیم کنید. + در قالبی صادر کنید که با مقصد مطابقت داشته باشد، معمولاً PNG برای گرافیک های تولید شده تمیز. + دسترسی به رنگ + زمانی که خواندن رابط کاربری سخت است از رنگ‌های پویا، کنتراست و گزینه‌های کوررنگ استفاده کنید + استفاده از رنگ ها را آسان تر کنید + تنظیمات رنگ بر راحتی، خوانایی و سرعت اسکن کنترل ها تأثیر می گذارد. اگر تشخیص تراشه‌های ابزار، لغزنده‌ها یا حالت‌های انتخابی دشوار است، گزینه‌های تم و دسترسی می‌توانند مفیدتر از تغییر حالت تاریک باشند. + زمانی که می‌خواهید برنامه از پالت کاغذدیواری فعلی پیروی کند، رنگ‌های پویا را امتحان کنید. + هنگامی که دکمه ها و سطوح به اندازه کافی برجسته نیستند، کنتراست را افزایش دهید یا طرح را تغییر دهید. + اگر تشخیص برخی حالت ها یا لهجه ها دشوار است، از گزینه های طرح کور رنگ استفاده کنید. + پس زمینه آلفا + رنگ پیش‌نمایش یا پر کردن مورد استفاده در اطراف PNG شفاف، WebP و خروجی‌های مشابه را کنترل کنید + پیش نمایش های شفاف را درک کنید + رنگ پس‌زمینه برای فرمت‌های آلفا می‌تواند بازرسی تصاویر شفاف را آسان‌تر کند، اما مهم است که بدانید آیا در حال تغییر رفتار پیش‌نمایش/پر کردن هستید یا نتیجه نهایی را صاف می‌کنید. این برای استیکرها، آرم‌ها و تصاویر حذف‌شده پس‌زمینه اهمیت دارد. + زمانی که پیکسل های شفاف باید از صادرات خارج شوند، ابتدا از یک قالب پشتیبانی کننده آلفا، مانند PNG یا WebP استفاده کنید. + تنظیمات رنگ پس‌زمینه را زمانی که لبه‌های شفاف به سختی روی سطح برنامه دیده می‌شوند، تنظیم کنید. + یک آزمایش کوچک را صادر کنید و آن را مجدداً در برنامه دیگری باز کنید تا تأیید کنید شفافیت یا پر کردن انتخاب شده ذخیره شده است. + انتخاب مدل هوش مصنوعی + به جای اینکه ابتدا بزرگترین مدل را انتخاب کنید، یک مدل را بر اساس نوع تصویر و نقص انتخاب کنید + مدل را با آسیب مطابقت دهید + ابزارهای هوش مصنوعی می توانند مدل های مختلف ONNX/ORT را دانلود یا وارد کنند و هر مدل برای نوع خاصی از مشکل آموزش داده شده است. یک مدل برای بلوک‌های JPEG، پاک‌سازی خط انیمه، حذف نویز، حذف پس‌زمینه، رنگ‌آمیزی یا افزایش مقیاس ممکن است در صورت استفاده در نوع تصویر اشتباه، نتایج بدتری ایجاد کند. + قبل از دانلود توضیحات مدل را بخوانید. مدلی را انتخاب کنید که مشکل واقعی شما را نام می‌برد، مانند فشرده‌سازی، نویز، نیمه‌تون، تاری یا حذف پس‌زمینه. + هنگام آزمایش یک نوع تصویر جدید با یک مدل کوچکتر یا خاص تر شروع کنید، سپس فقط در صورتی که نتیجه به اندازه کافی خوب نباشد، با مدل های سنگین تر مقایسه کنید. + فقط مدل هایی را که واقعا استفاده می کنید نگه دارید، زیرا مدل های دانلود شده و وارد شده می توانند فضای ذخیره سازی قابل توجهی را اشغال کنند. + خانواده های الگو را درک کنید + نام مدل‌ها اغلب به هدف خود اشاره می‌کنند: مدل‌های به‌سبک RealESRGAN، مدل‌های SCUNet و FBCNN تمیز کردن نویز یا فشرده‌سازی، مدل‌های پس‌زمینه ماسک ایجاد می‌کنند و رنگ‌کننده‌ها به ورودی‌های مقیاس خاکستری رنگ اضافه می‌کنند. مدل‌های سفارشی وارداتی می‌توانند قدرتمند باشند، اما باید با شکل ورودی/خروجی مورد انتظار مطابقت داشته باشند و از فرمت پشتیبانی شده ONNX یا ORT استفاده کنند. + هنگامی که به پیکسل‌های بیشتری نیاز دارید، از حذف‌کننده‌ها در زمانی که تصویر دانه‌دار است، از حذف‌کننده‌های آرتیفکت زمانی که بلوک‌های فشرده‌سازی یا باندینگ مشکل اصلی هستند، استفاده کنید. + از مدل های پس زمینه فقط برای جداسازی موضوع استفاده کنید. آنها با حذف کلی شی یا بهبود تصویر یکسان نیستند. + مدل‌های سفارشی را فقط از منابعی که به آنها اعتماد دارید وارد کنید و قبل از استفاده از فایل‌های مهم آن‌ها را روی یک تصویر کوچک آزمایش کنید. + پارامترهای هوش مصنوعی + اندازه قطعه، همپوشانی و کارگران موازی را تنظیم کنید تا کیفیت، سرعت و حافظه را متعادل کنید + تعادل سرعت با ثبات + اندازه تکه، اندازه قطعات تصویر را قبل از پردازش توسط مدل کنترل می کند. همپوشانی تکه های همسایه را برای پنهان کردن مرزها ترکیب می کند. کارگران موازی می توانند سرعت پردازش را افزایش دهند، اما هر کارگر به حافظه نیاز دارد، بنابراین مقادیر بالا می تواند تصاویر بزرگ را در تلفن هایی با RAM محدود ناپایدار کند. + هنگامی که دستگاه شما مدل را به طور قابل اعتمادی کنترل می کند و تصویر بزرگ نیست، از تکه های بزرگتر برای زمینه صاف تر استفاده کنید. + وقتی حاشیه‌های تکه‌ها قابل مشاهده می‌شوند، همپوشانی را افزایش دهید، اما به یاد داشته باشید که همپوشانی بیشتر به معنای تکرار بیشتر کار است. + کارگران موازی را فقط پس از یک آزمایش پایدار بالا ببرید. اگر پردازش کند شد، دستگاه را گرم کرد یا از کار افتاد، ابتدا کارگران را کاهش دهید. + از مصنوعات تکه ای اجتناب کنید + Chunking به برنامه اجازه می‌دهد تا تصاویری را که بزرگ‌تر از آن چیزی است که مدل می‌تواند به راحتی آن‌ها را در یک لحظه پردازش کند پردازش کند. مبادله این است که هر کاشی زمینه کمتری دارد، بنابراین همپوشانی کم یا تکه‌های خیلی کوچک ممکن است مرزهای قابل مشاهده، تغییرات بافت یا جزئیات ناسازگار را بین مناطق همسایه ایجاد کند. + اگر حاشیه‌های مستطیلی، تغییرات مکرر بافت، یا جهش جزئیات بین مناطق پردازش‌شده را دیدید، همپوشانی را افزایش دهید. + اگر فرآیند قبل از تولید خروجی با شکست مواجه شد، به خصوص در تصاویر بزرگ یا مدل های سنگین، اندازه قطعه را کاهش دهید. + قبل از پردازش تصویر کامل، یک آزمایش برش کوچک ذخیره کنید تا بتوانید پارامترها را به سرعت مقایسه کنید. + ثبات هوش مصنوعی + وقتی پردازش هوش مصنوعی از کار می افتد یا یخ می زند، اندازه قطعه، کارگران و وضوح منبع کمتر است + ریکاوری از کارهای سنگین هوش مصنوعی + پردازش هوش مصنوعی یکی از سنگین ترین جریان های کاری در برنامه است. خرابی‌ها، جلسات ناموفق، توقف‌ها یا راه‌اندازی مجدد ناگهانی برنامه معمولاً به این معنی است که مدل، وضوح منبع، اندازه قطعه یا تعداد کارگران موازی برای وضعیت فعلی دستگاه بیش از حد نیاز است. + اگر برنامه خراب شد یا یک جلسه مدل را باز نکرد، کارگران موازی و اندازه قطعه را کاهش دهید، سپس همان تصویر را دوباره امتحان کنید. + هنگامی که هدف نیازی به وضوح اصلی ندارد، اندازه تصاویر بسیار بزرگ را قبل از پردازش هوش مصنوعی تغییر دهید. + سایر برنامه‌های سنگین را ببندید، از مدل‌های کوچک‌تر استفاده کنید، یا فایل‌های کمتری را به‌طور هم‌زمان پردازش کنید، وقتی فشار حافظه همچنان برمی‌گردد. + عیب های مدل را تشخیص دهید + اجرای ناموفق هوش مصنوعی همیشه به معنای بد بودن تصویر نیست. فایل مدل ممکن است ناقص، پشتیبانی نشده، برای حافظه بیش از حد بزرگ باشد، یا با عملیات مطابقت نداشته باشد. وقتی برنامه یک جلسه ناموفق را گزارش می‌کند، ابتدا آن را به عنوان سیگنالی برای ساده‌سازی مدل و پارامترها در نظر بگیرید. + اگر یک مدل بلافاصله پس از دانلود با مشکل مواجه شد یا طوری رفتار کرد که گویی فایل خراب است، آن را حذف و دوباره دانلود کنید. + قبل از سرزنش یک مدل سفارشی وارداتی، یک مدل داخلی قابل دانلود را امتحان کنید، زیرا مدل های وارداتی می توانند ورودی های ناسازگاری داشته باشند. + در صورت نیاز به گزارش خرابی یا خطای جلسه، پس از بازتولید خرابی از App Logs استفاده کنید. + در Shader Studio آزمایش کنید + از جلوه های سایه زن GPU برای پردازش تصویر پیشرفته و ظاهر سفارشی استفاده کنید + با شیدرها به دقت کار کنید + Shader Studio قدرتمند است، اما کد سایه زن و پارامترها نیاز به تغییرات کوچک و قابل آزمایش دارند. مانند یک ابزار آزمایشی با آن رفتار کنید: یک خط پایه کار را حفظ کنید، هر بار یک چیز را تغییر دهید، و قبل از اعتماد به یک پیش تنظیم، با اندازه های مختلف تصویر آزمایش کنید. + قبل از اضافه کردن پارامترها، از یک سایه زن کار شناخته شده یا یک افکت ساده شروع کنید. + هر بار یک پارامتر یا بلوک کد را تغییر دهید و پیش نمایش را برای وجود خطا بررسی کنید. + پیش‌تنظیم‌ها را فقط پس از آزمایش آن‌ها روی چند اندازه تصویر مختلف صادر کنید. + هنر SVG یا ASCII بسازید + دارایی های بردار مانند یا تصاویر مبتنی بر متن را برای خروجی خلاق ایجاد کنید + نوع خروجی خلاقانه را انتخاب کنید + ابزارهای SVG و ASCII به اهداف مختلفی خدمت می کنند: گرافیک مقیاس پذیر یا رندر به سبک متن. هر دو تبدیل‌های خلاقانه‌ای هستند، بنابراین پیش‌نمایش در اندازه نهایی مهم‌تر از قضاوت نتیجه از روی یک تصویر کوچک است. + از SVG Maker زمانی استفاده کنید که نتیجه باید به طور تمیز مقیاس شود یا در جریان کار طراحی مجددا استفاده شود. + هنگامی که می خواهید یک نمایش متن سبک از یک تصویر داشته باشید، از ASCII Art استفاده کنید. + پیش نمایش را در اندازه نهایی ببینید زیرا جزئیات کوچک می توانند در خروجی تولید شده ناپدید شوند. + بافت های نویز ایجاد کنید + بافت‌های رویه‌ای برای پس‌زمینه، ماسک‌ها، پوشش‌ها یا آزمایش‌ها ایجاد کنید + تنظیم خروجی رویه ای + تولید نویز تصاویر جدیدی از پارامترها ایجاد می کند، بنابراین تغییرات کوچک می تواند نتایج بسیار متفاوتی ایجاد کند. برای بافت‌ها، ماسک‌ها، روکش‌ها، مکان‌ها یا تست فشرده‌سازی با الگوهای دقیق مفید است. + قبل از تنظیم جزئیات دقیق، نوع نویز و اندازه خروجی را انتخاب کنید. + مقیاس، شدت، رنگ‌ها یا دانه را تا زمانی تنظیم کنید که الگوی مورد نظر متناسب باشد. + نتایج مفید را ذخیره کنید و در صورت نیاز به ایجاد مجدد بافت، پارامترها را یادداشت کنید. + پروژه های نشانه گذاری + زمانی از فایل های پروژه استفاده کنید که پس از بستن برنامه، حاشیه نویسی ها باید قابل ویرایش باشند + ویرایش های لایه ای را قابل استفاده مجدد نگه دارید + فایل های پروژه نشانه گذاری زمانی مفید هستند که یک اسکرین شات، نمودار یا تصویر دستورالعمل ممکن است بعداً نیاز به تغییرات داشته باشد. فایل های PNG/JPEG صادر شده تصاویر نهایی هستند، در حالی که فایل های پروژه لایه های قابل ویرایش و حالت ویرایش را برای تنظیمات بعدی حفظ می کنند. + زمانی که یادداشت‌ها، پیام‌ها یا عناصر طرح‌بندی باید قابل ویرایش باقی بمانند، از لایه‌های نشانه‌گذاری استفاده کنید. + اگر انتظار ویرایش های بیشتری را دارید، قبل از صدور تصویر نهایی، فایل پروژه را ذخیره یا دوباره باز کنید. + یک تصویر معمولی را فقط زمانی صادر کنید که آماده به اشتراک گذاشتن نسخه نهایی مسطح شده باشید. + فایل ها را رمزگذاری کنید + قبل از حذف هر کپی منبع، از فایل ها با رمز عبور محافظت کنید و رمزگشایی را آزمایش کنید + خروجی های رمزگذاری شده را با دقت مدیریت کنید + ابزارهای رمزگذاری می‌توانند از فایل‌ها با رمزگذاری مبتنی بر رمز عبور محافظت کنند، اما جایگزینی برای به خاطر سپردن کلید یا نگه‌داشتن نسخه‌های پشتیبان نیستند. رمزگذاری برای حمل و نقل و ذخیره سازی مفید است، در حالی که ZIP زمانی بهتر است که هدف اصلی بسته بندی چندین خروجی باشد. + ابتدا یک کپی را رمزگذاری کنید و نسخه اصلی را نگه دارید تا زمانی که آزمایش کنید که رمزگشایی با رمز عبوری که ذخیره کرده اید کار می کند. + از یک مدیر رمز عبور یا مکان امن دیگری برای کلید استفاده کنید. برنامه نمی تواند رمز عبور گم شده را برای شما حدس بزند. + فایل های رمزگذاری شده را فقط با افرادی به اشتراک بگذارید که رمز عبور را نیز دارند و می دانند کدام ابزار یا روش باید آنها را رمزگشایی کند. + وسایل را مرتب کنید + ابزارهای گروه، سفارش، جستجو و پین کردن را انجام دهید تا صفحه اصلی با گردش کار واقعی شما مطابقت داشته باشد + صفحه اصلی را سریعتر کنید + هنگامی که بدانید از کدام ابزار بیشتر استفاده می کنید، تنظیمات چیدمان ابزار ارزش تنظیم را دارد. گروه‌بندی به کاوش کمک می‌کند، سفارش سفارشی به حافظه عضلانی کمک می‌کند، و موارد دلخواه ابزارهای روزانه را حتی زمانی که فهرست کامل بزرگ می‌شود، در دسترس نگه می‌دارند. + هنگام یادگیری برنامه یا زمانی که ابزارهای سازماندهی شده بر اساس نوع کار را ترجیح می دهید از حالت گروه بندی استفاده کنید. + وقتی ابزارهای متداول خود را می‌شناسید و اسکرول کمتری می‌خواهید، به ترتیب سفارشی بروید. + از تنظیمات جستجو و موارد دلخواه برای ابزارهای کمیابی که با نام به خاطر می آورید اما نمی خواهید همیشه قابل مشاهده باشند، استفاده کنید. + مدیریت فایل های حجیم + از صادرات کند، فشار حافظه و خروجی غیرمنتظره زیاد خودداری کنید + کاهش کار قبل از صادرات + تصاویر بسیار بزرگ، دسته های طولانی و فرمت های با کیفیت بالا می توانند حافظه و زمان بیشتری را به خود اختصاص دهند. سریع‌ترین راه حل معمولاً کاهش حجم کار قبل از سنگین‌ترین عملیات است، نه اینکه منتظر بمانید تا همان ورودی بزرگ به پایان برسد. + اندازه تصاویر بزرگ را قبل از اعمال فیلترهای سنگین، OCR یا تبدیل PDF تغییر دهید. + ابتدا از یک دسته کوچکتر برای تأیید تنظیمات استفاده کنید، سپس بقیه را با همان پیش تنظیم پردازش کنید. + کیفیت پایین تر یا تغییر فرمت زمانی که فایل خروجی بزرگتر از حد انتظار است. + کش و پیش نمایش + پیش نمایش های ایجاد شده، پاکسازی حافظه پنهان و استفاده از فضای ذخیره سازی پس از جلسات سنگین را درک کنید + ذخیره سازی و سرعت را متعادل نگه دارید + تولید پیش‌نمایش و حافظه پنهان می‌تواند مرور مکرر را سریع‌تر کند، اما جلسات ویرایش سنگین ممکن است داده‌های موقتی را پشت سر بگذارند. تنظیمات حافظه نهان به زمانی کمک می‌کند که برنامه کند باشد، فضای ذخیره‌سازی فشرده باشد، یا پیش‌نمایش‌ها پس از آزمایش‌های بسیار کهنه به نظر برسند. + فعال نگه داشتن پیش نمایش در هنگام مرور بسیاری از تصاویر مهمتر از به حداقل رساندن ذخیره سازی موقت است. + حافظه پنهان را پس از دسته‌های بزرگ، آزمایش‌های ناموفق یا جلسات طولانی با بسیاری از فایل‌های با وضوح بالا پاک کنید. + اگر ترجیح می‌دهید پاکسازی فضای ذخیره‌سازی را به گرم نگه‌داشتن پیش‌نمایش‌ها بین راه‌اندازی‌ها ترجیح می‌دهید، از پاک کردن حافظه پنهان خودکار استفاده کنید. + رفتار صفحه نمایش را تنظیم کنید + از گزینه های تمام صفحه، حالت ایمن، روشنایی و نوار سیستم برای کار متمرکز استفاده کنید + رابط کاربری را متناسب با موقعیت قرار دهید + تنظیمات صفحه هنگام ویرایش افقی، ارائه تصاویر خصوصی یا نیاز به روشنایی ثابت به شما کمک می کند. آنها برای استفاده معمولی مورد نیاز نیستند، اما موقعیت‌های ناخوشایند مانند بازرسی QR، پیش‌نمایش‌های خصوصی یا ویرایش منظره فشرده را حل می‌کنند. + هنگامی که ویرایش فضا مهمتر از ناوبری کروم است، از تنظیمات تمام صفحه یا نوار سیستم استفاده کنید. + زمانی که تصاویر خصوصی نباید در اسکرین شات ها یا پیش نمایش برنامه ها ظاهر شوند از حالت امن استفاده کنید. + برای ابزارهایی که بازرسی تصاویر تیره یا کدهای QR دشوار است از اعمال روشنایی استفاده کنید. + شفافیت را حفظ کنید + از سفید، سیاه یا مسطح شدن پس‌زمینه شفاف جلوگیری کنید + از خروجی آلفا آگاه استفاده کنید + شفافیت تنها زمانی زنده می ماند که ابزار انتخابی و فرمت خروجی از آلفا پشتیبانی کند. اگر پس‌زمینه صادر شده سفید یا سیاه شود، مشکل معمولاً فرمت خروجی، تنظیمات پر/پس‌زمینه یا برنامه مقصد است که تصویر را صاف کرده است. + هنگام صادرات تصاویر، برچسب‌ها، آرم‌ها یا همپوشانی‌های حذف‌شده پس‌زمینه، PNG یا WebP را انتخاب کنید. + برای خروجی شفاف از JPEG اجتناب کنید زیرا کانال آلفا را ذخیره نمی کند. + اگر پیش‌نمایش پس‌زمینه پر شده را نشان می‌دهد، تنظیمات مربوط به رنگ پس‌زمینه را برای قالب‌های آلفا بررسی کنید. + رفع مشکلات اشتراک گذاری و واردات + هنگامی که فایل ها به درستی ظاهر نمی شوند یا به درستی باز نمی شوند، از تنظیمات انتخابگر و قالب های پشتیبانی شده استفاده کنید + فایل را وارد برنامه کنید + مشکلات واردات اغلب به دلیل مجوزهای ارائه دهنده، قالب های پشتیبانی نشده، یا رفتار انتخاب کننده ایجاد می شوند. فایل‌های به اشتراک‌گذاشته‌شده از برنامه‌های ابری و پیام‌رسان‌ها ممکن است موقتی باشند، بنابراین انتخاب یک منبع دائمی برای ویرایش‌های طولانی‌تر قابل اعتمادتر است. + به جای میانبر فایل های اخیر، فایل را از طریق انتخابگر سیستم باز کنید. + اگر یک قالب توسط یک ابزار پشتیبانی نمی شود، ابتدا آن را تبدیل کنید یا ابزار خاصی را باز کنید. + اگر جریان‌های اشتراک‌گذاری یا باز کردن مستقیم ابزار متفاوت از حد انتظار رفتار می‌کنند، تنظیمات انتخابگر تصویر و راه‌انداز را بررسی کنید. + تایید خروج + از از دست دادن ویرایش‌های ذخیره‌نشده در زمانی که حرکات، فشار دادن پشت یا کلید ابزار به طور تصادفی اتفاق می‌افتند، خودداری کنید + از کارهای ناتمام محافظت کنید + تأیید خروج از ابزار زمانی که از پیمایش اشاره ای استفاده می کنید، فایل های بزرگ را ویرایش می کنید یا اغلب بین برنامه ها جابه جا می شوید، مفید است. قبل از ترک ابزار، یک مکث کوچک اضافه می کند تا تنظیمات و پیش نمایش های ناتمام به طور تصادفی از بین نرود. + اگر حرکت‌های برگشتی تصادفی ابزاری را قبل از صادرات بسته است، تأیید را فعال کنید. + اگر بیشتر تبدیل‌های یک مرحله‌ای سریع انجام می‌دهید و پیمایش سریع‌تر را ترجیح می‌دهید، آن را غیرفعال نگه دارید. + از آن همراه با تنظیمات از پیش تعیین شده برای گردش کار طولانی تر استفاده کنید که در آن بازسازی تنظیمات آزار دهنده خواهد بود. + هنگام گزارش مشکلات از گزارش ها استفاده کنید + قبل از باز کردن مشکل یا تماس با توسعه دهنده، جزئیات مفید را جمع آوری کنید + گزارش مشکلات مربوط به زمینه + تشخیص یک گزارش واضح با مراحل، نسخه برنامه، نوع ورودی و گزارش‌ها بسیار ساده‌تر است. لاگ‌ها درست بعد از بازتولید یک مشکل بسیار مفید هستند زیرا زمینه اطراف را تازه نگه می‌دارند. + به نام ابزار، عمل دقیق، و اینکه آیا با یک فایل یا هر فایل اتفاق می‌افتد، توجه کنید. + پس از بازتولید مشکل، App Logs را باز کنید و خروجی گزارش مربوطه را در صورت امکان به اشتراک بگذارید. + اسکرین شات ها یا نمونه فایل ها را فقط زمانی ضمیمه کنید که حاوی اطلاعات خصوصی نباشند. + پردازش پس‌زمینه متوقف شد + اندروید اجازه نداد اعلان پردازش پس‌زمینه به موقع شروع شود. این یک محدودیت زمان بندی سیستم است که نمی توان به طور قابل اعتماد آن را برطرف کرد. برنامه را مجددا راه اندازی کنید و دوباره امتحان کنید؛ باز نگه داشتن برنامه و اجازه دادن به اعلان‌ها یا کار در پس‌زمینه ممکن است کمک کند. + رد شدن از انتخاب فایل + وقتی ورودی معمولاً از اشتراک‌گذاری، کلیپ‌بورد یا صفحه قبلی می‌آید، ابزارها را سریع‌تر باز کنید + راه اندازی مکرر ابزار را سریعتر کنید + Skip File Picking مرحله اول ابزارهای پشتیبانی شده را تغییر می دهد. هنگامی که معمولاً ابزاری را با تصویری که قبلاً انتخاب شده یا از اقدامات اشتراک‌گذاری اندروید وارد می‌کنید مفید است، اما اگر انتظار داشته باشید هر ابزار فوراً یک فایل را درخواست کند، ممکن است گیج‌کننده باشد. + آن را فقط زمانی فعال کنید که جریان معمول شما از قبل تصویر منبع را قبل از باز شدن ابزار ارائه دهد. + هنگام یادگیری برنامه، آن را غیرفعال نگه دارید، زیرا انتخاب صریح جریان هر ابزار را برای درک آسان تر می کند. + اگر ابزاری بدون ورودی آشکار باز می‌شود، این تنظیم را غیرفعال کنید یا از Share/Open With شروع کنید تا اندروید ابتدا فایل را ارسال کند. + ابتدا ویرایشگر را باز کنید + زمانی که یک تصویر به اشتراک گذاشته شده باید مستقیماً وارد ویرایش شود، از پیش نمایش رد شوید + پیش نمایش یا ویرایش را به عنوان اولین ایستگاه انتخاب کنید + Open Edit Instead Of Preview برای کاربرانی است که از قبل می دانند می خواهند تصویر را تغییر دهند. زمانی که فایل‌ها را از چت یا فضای ذخیره‌سازی ابری بررسی می‌کنید، پیش‌نمایش امن‌تر است. ویرایش مستقیم برای اسکرین شات، برش سریع، حاشیه نویسی و اصلاحات یکباره سریعتر است. + اگر اکثر تصاویر به اشتراک گذاشته شده فوراً نیاز به برش، نشانه گذاری، فیلترها یا تغییرات صادراتی دارند، ویرایش مستقیم را فعال کنید. + هنگامی که اغلب ابرداده ها، ابعاد، شفافیت یا کیفیت تصویر را قبل از تصمیم گیری بررسی می کنید، ابتدا پیش نمایش را ترک کنید. + از این مورد همراه با موارد دلخواه استفاده کنید تا تصاویر به اشتراک گذاشته شده نزدیک به ابزارهایی که در واقع استفاده می کنید قرار بگیرند. + ورودی متن از پیش تعیین شده + به جای تنظیم مکرر کنترل ها، مقادیر دقیق از پیش تعیین شده را وارد کنید + وقتی لغزنده ها کند هستند از مقادیر دقیق استفاده کنید + وارد کردن متن از پیش تعیین شده در مواقعی که به اعداد دقیق برای کارهای مکرر نیاز دارید کمک می کند: اندازه تصویر اجتماعی، حاشیه سند، اهداف فشرده سازی، عرض خطوط یا مقادیر دیگری که رسیدن به آنها با حرکات آزاردهنده است. همچنین در صفحه نمایش های کوچک که حرکت لغزنده ظریف سخت تر است مفید است. + اگر اغلب از اندازه‌ها، درصدها، مقادیر کیفیت یا پارامترهای ابزار دقیق استفاده می‌کنید، ورود متن را فعال کنید. + پس از یک بار تایپ مقدار را به عنوان یک پیش تنظیم ذخیره کنید، سپس به جای بازسازی مجدد همان تنظیمات، از آن مجددا استفاده کنید. + کنترل حرکات را برای تنظیم تصویری خشن و مقادیر تایپ شده برای الزامات خروجی دقیق حفظ کنید. + ذخیره نزدیک اصلی + زمانی که زمینه پوشه اهمیت دارد، فایل های تولید شده را در کنار تصاویر منبع قرار دهید + خروجی ها را با منابع آنها نگه دارید + Save To Original Folder برای پوشه‌های دوربین، پوشه‌های پروژه، اسکن اسناد و دسته‌های سرویس گیرنده که کپی ویرایش شده باید در کنار منبع بماند مفید است. این می تواند کمتر از یک پوشه خروجی اختصاصی مرتب باشد، بنابراین آن را با پسوندها یا الگوهای نام فایل واضح ترکیب کنید. + زمانی که هر پوشه منبع از قبل معنادار است و خروجی ها باید در آنجا گروه بندی شوند، آن را فعال کنید. + از پسوندهای نام فایل، پیشوندها، مهرهای زمانی یا الگوها استفاده کنید تا کپی های ویرایش شده به راحتی از نسخه اصلی جدا شوند. + هنگامی که می خواهید همه فایل های تولید شده در یک پوشه صادراتی جمع آوری شوند، آن را برای دسته های مختلط غیرفعال کنید. + از خروجی بزرگتر صرف نظر کنید + از ذخیره تبدیل هایی که به طور غیرمنتظره ای سنگین تر از منبع می شوند خودداری کنید + از دسته ها در برابر غافلگیری با اندازه بد محافظت کنید + برخی از تبدیل‌ها، فایل‌ها را بزرگ‌تر می‌کنند، به‌ویژه اسکرین‌شات‌های PNG، عکس‌های از قبل فشرده‌شده، WebP با کیفیت بالا، یا تصاویر با ابرداده‌های حفظ شده. Allow Skip If Larger از جایگزینی آن خروجی‌ها با منبع کوچک‌تر در جریان‌های کاری که کاهش اندازه هدف اصلی است، جلوگیری می‌کند. + هنگامی که صادرات برای فشرده سازی، تغییر اندازه یا آماده سازی فایل ها برای محدودیت های آپلود، آن را فعال کنید. + بررسی فایل های حذف شده پس از یک دسته. ممکن است قبلاً بهینه شده باشند یا ممکن است به فرمت دیگری نیاز داشته باشند. + زمانی که کیفیت، شفافیت یا سازگاری فرمت مهمتر از اندازه نهایی فایل است، آن را غیرفعال کنید. + کلیپ بورد و پیوندها + کنترل ورودی خودکار کلیپ بورد و پیش نمایش پیوندها برای ابزارهای مبتنی بر متن سریعتر + ورودی خودکار را قابل پیش بینی کنید + تنظیمات کلیپ‌بورد و پیوند برای QR، Base64، URL و گردش‌های کاری سنگین مناسب هستند، اما ورودی خودکار باید عمدی باقی بماند. اگر به نظر می رسد برنامه به تنهایی فیلدها را پر می کند، رفتار چسباندن کلیپ بورد را بررسی کنید. اگر کارت‌های پیوند پر سر و صدا یا کند هستند، رفتار پیش‌نمایش پیوند را تنظیم کنید. + هنگامی که اغلب متن را کپی می کنید و بلافاصله ابزار منطبق را باز می کنید، به چسباندن خودکار کلیپ بورد اجازه دهید. + اگر محتوای کلیپ‌بورد خصوصی در ابزارهایی که انتظارش را نداشتید ظاهر می‌شود، جای‌گذاری خودکار را غیرفعال کنید. + هنگامی که واکشی پیش‌نمایش کند، حواس‌پرت‌کننده یا غیرضروری برای گردش کار شما است، پیش‌نمایش پیوند را خاموش کنید. + کنترل های فشرده + وقتی فضای صفحه نمایش کم است از انتخابگرهای متراکم تر و قرار دادن تنظیمات سریع استفاده کنید + کنترل های بیشتری را روی صفحه نمایش های کوچک قرار دهید + انتخابگرهای فشرده و قرار دادن تنظیمات سریع در تلفن‌های کوچک، ویرایش منظره و ابزارهایی با پارامترهای بسیار کمک می‌کنند. معاوضه این است که کنترل‌ها می‌توانند متراکم‌تر به نظر برسند، بنابراین بهتر است بعد از اینکه گزینه‌های رایج را شناسایی کردید. + هنگامی که دیالوگ ها یا لیست گزینه ها برای صفحه نمایش خیلی بلند هستند، انتخابگرهای فشرده را فعال کنید. + اگر دسترسی به کنترل‌های ابزار از یک طرف نمایشگر آسان‌تر است، سمت تنظیمات سریع را تنظیم کنید. + اگر هنوز برنامه را یاد می‌گیرید یا گزینه‌های متراکم را اغلب از دست می‌دهید، به کنترل‌های جادارتر بازگردید. + بارگیری تصاویر از وب + URL صفحه یا تصویر را جای‌گذاری کنید، تصاویر تجزیه‌شده را پیش‌نمایش کنید، سپس نتایج انتخاب‌شده را ذخیره یا به اشتراک بگذارید + از تصاویر وب به عمد استفاده کنید + Web Image Loading منابع تصویر را از URL ارائه شده تجزیه می کند، اولین تصویر موجود را پیش نمایش می کند و به شما امکان می دهد تصاویر تجزیه شده انتخاب شده را ذخیره یا به اشتراک بگذارید. برخی از سایت‌ها از پیوندهای موقت، محافظت‌شده یا ایجاد شده توسط اسکریپت استفاده می‌کنند، بنابراین صفحه‌ای که در مرورگر کار می‌کند ممکن است همچنان هیچ تصویر قابل استفاده‌ای برگرداند. + URL تصویر مستقیم یا URL صفحه را جایگذاری کنید و منتظر بمانید تا لیست تصاویر تجزیه شده ظاهر شود. + هنگامی که صفحه حاوی چندین تصویر است و فقط به برخی از آنها نیاز دارید از کنترل قاب یا انتخاب استفاده کنید. + اگر چیزی بارگذاری نشد، ابتدا یک لینک مستقیم تصویر را امتحان کنید یا ابتدا از طریق مرورگر دانلود کنید. سایت ممکن است تجزیه را مسدود کند یا از پیوندهای خصوصی استفاده کند. + نوع تغییر اندازه را انتخاب کنید + قبل از اجبار کردن تصویر به ابعاد جدید، موارد واضح، انعطاف پذیر، برش و تناسب را درک کنید + شکل، بوم و نسبت ابعاد را کنترل کنید + نوع تغییر اندازه تعیین می کند که وقتی عرض و ارتفاع درخواستی با نسبت تصویر اصلی مطابقت نداشته باشد چه اتفاقی می افتد. این تفاوت بین کشش پیکسل ها، حفظ نسبت ها، برش ناحیه اضافی یا اضافه کردن یک بوم پس زمینه است. + فقط زمانی از Explicit استفاده کنید که اعوجاج قابل قبول باشد یا منبع از قبل دارای نسبت تصویر یکسان با هدف باشد. + از Flexible برای حفظ نسبت ها با تغییر اندازه از سمت مهم، به خصوص برای عکس ها و دسته های ترکیبی استفاده کنید. + از Crop برای قاب‌های سخت‌گیرانه بدون حاشیه یا Fit زمانی که کل تصویر باید در داخل یک بوم ثابت قابل مشاهده باشد، استفاده کنید. + حالت مقیاس تصویر را انتخاب کنید + فیلتر نمونه‌گیری مجدد را انتخاب کنید که وضوح، صافی، سرعت و مصنوعات لبه را کنترل می‌کند + تغییر اندازه بدون نرمی تصادفی + حالت مقیاس تصویر نحوه محاسبه پیکسل های جدید را در حین تغییر اندازه کنترل می کند. حالت‌های ساده سریع و قابل پیش‌بینی هستند، در حالی که فیلترهای پیشرفته می‌توانند جزئیات را بهتر حفظ کنند، اما ممکن است لبه‌ها را تیز کنند، هاله ایجاد کنند یا در تصاویر بزرگ زمان بیشتری ببرد. + از Basic یا Bilinear برای تغییر اندازه سریع روزانه استفاده کنید، زمانی که نتیجه فقط باید کوچکتر و تمیز باشد. + هنگامی که جزئیات دقیق، متن یا کاهش مقیاس با کیفیت بالا مهم هستند از حالت‌های Lanczos، Robidoux، Spline یا EWA استفاده کنید. + از Nearest برای پیکسل آرت، نمادها و گرافیک های سخت استفاده کنید که در آن پیکسل های تار ظاهر را خراب می کنند. + فضای رنگ را مقیاس کنید + تصمیم بگیرید که چگونه رنگ ها ترکیب شوند در حالی که پیکسل ها در حین تغییر اندازه نمونه گیری مجدد می شوند + شیب ها و لبه ها را طبیعی نگه دارید + فضای رنگ مقیاس، ریاضی مورد استفاده در هنگام تغییر اندازه را تغییر می دهد. اکثر تصاویر با پیش فرض خوب هستند، اما فضاهای خطی یا ادراکی می توانند شیب ها، لبه های تیره و رنگ های اشباع شده را پس از مقیاس بندی قوی تمیزتر نشان دهند. + زمانی که سرعت و سازگاری بیشتر از تفاوت‌های کوچک رنگ مهم است، پیش‌فرض را ترک کنید. + وقتی خطوط تیره، اسکرین شات های رابط کاربری، گرادیان ها یا نوارهای رنگی پس از تغییر اندازه اشتباه به نظر می رسند، حالت های خطی یا ادراکی را امتحان کنید. + قبل و بعد را در صفحه هدف واقعی مقایسه کنید، زیرا تغییرات فضای رنگ می تواند ظریف و وابسته به محتوا باشد. + حالت های تغییر اندازه را محدود کنید + بدانید که چه زمانی تصاویر بیش از حد مجاز باید نادیده گرفته شوند، کدگذاری مجدد شوند، یا کوچک شوند تا متناسب شوند + آنچه را که در حد مجاز اتفاق می افتد انتخاب کنید + Limit Resize تنها یک ابزار کوچکتر تصویر نیست. حالت آن تصمیم می‌گیرد زمانی که یک منبع از قبل در محدوده شما قرار دارد یا زمانی که فقط یک طرف قانون را زیر پا می‌گذارد، برنامه چه کاری انجام دهد، که برای پوشه‌های مختلط و آماده‌سازی آپلود مهم است. + زمانی که تصاویر داخل محدودیت‌ها باید دست نخورده باقی بمانند و دوباره صادر نشوند، از Skip استفاده کنید. + زمانی که ابعاد قابل قبول هستند، از Recode استفاده کنید اما همچنان به قالب، رفتار فراداده، کیفیت یا نام فایل جدید نیاز دارید. + هنگامی که تصاویری که محدودیت ها را شکسته اند باید به طور متناسب کوچک شوند تا زمانی که با کادر مجاز مطابقت داشته باشند از زوم استفاده کنید. + اهداف نسبت ابعاد + از صورت های کشیده، لبه های بریده، و حاشیه های غیرمنتظره در اندازه های خروجی دقیق خودداری کنید. + قبل از تغییر اندازه، شکل را مطابقت دهید + عرض و ارتفاع فقط نیمی از هدف تغییر اندازه است. نسبت تصویر تعیین می کند که آیا تصویر می تواند به طور طبیعی جا بیفتد، نیاز به برش دارد یا نیاز به یک بوم دور آن دارد. ابتدا بررسی شکل از نتایج شگفت انگیز تغییر اندازه جلوگیری می کند. + قبل از انتخاب Explicit، Crop یا Fit شکل منبع را با شکل هدف مقایسه کنید. + زمانی که سوژه ممکن است پس‌زمینه اضافی را از دست بدهد و مقصد به یک قاب سخت‌گیرانه نیاز دارد، ابتدا برش دهید. + زمانی که هر قسمت از تصویر اصلی باید قابل مشاهده باشد، از Fit یا Flexible استفاده کنید. + تغییر اندازه یا اندازه معمولی + بدانید چه زمانی افزودن پیکسل ها به هوش مصنوعی نیاز دارد و چه زمانی مقیاس گذاری ساده کافی است + اندازه را با جزئیات اشتباه نگیرید + تغییر اندازه عادی با درون یابی پیکسل ها، ابعاد را تغییر می دهد. نمی تواند جزئیات واقعی را اختراع کند. سطح بالای هوش مصنوعی می تواند جزئیات واضح تری ایجاد کند، اما کندتر است و ممکن است بافت را دچار توهم کند، بنابراین انتخاب صحیح بستگی به این دارد که آیا به سازگاری، سرعت یا بازیابی بصری نیاز دارید. + از تغییر اندازه معمولی برای تصاویر کوچک، محدودیت‌های آپلود، اسکرین‌شات‌ها و تغییر ابعاد ساده استفاده کنید. + زمانی که یک تصویر کوچک یا نرم باید در اندازه بزرگتر بهتر به نظر برسد، از هوش مصنوعی استفاده کنید. + چهره‌ها، متن‌ها و الگوها را بعد از سطح بالای هوش مصنوعی مقایسه کنید زیرا جزئیات تولید شده می‌تواند قانع‌کننده اما نادرست به نظر برسد. + سفارش فیلتر اهمیت دارد + پاکسازی، رنگ، وضوح و فشرده سازی نهایی را در یک دنباله عمدی اعمال کنید + یک پشته فیلتر تمیزتر بسازید + فیلترها همیشه قابل تعویض نیستند. تاری قبل از واضح کردن، افزایش کنتراست قبل از OCR، یا تغییر رنگ قبل از فشرده‌سازی می‌تواند خروجی بسیار متفاوتی از کنترل‌های مشابه ایجاد کند. + ابتدا برش بزنید و بچرخانید تا فیلترهای بعدی فقط روی پیکسل هایی که باقی می مانند کار کنند. + نوردهی، تعادل رنگ سفید و کنتراست را قبل از تیز کردن یا جلوه‌های سبک اعمال کنید. + تغییر اندازه و فشرده‌سازی نهایی را نزدیک به پایان نگه دارید تا جزئیات پیش‌نمایش‌شده به‌طور قابل پیش‌بینی از صادرات جان سالم به در ببرند. + لبه های پس زمینه را برطرف کنید + هاله ها، سایه های باقیمانده، موها، سوراخ ها و خطوط نرم را پس از حذف خودکار تمیز کنید + برش ها را عمدی جلوه دهید + ماسک های خودکار اغلب در یک نگاه خوب به نظر می رسند، اما مشکلات را در زمینه های روشن، تاریک یا طرح دار نشان می دهند. پاکسازی لبه تفاوت بین برش سریع و برچسب، لوگو یا تصویر محصول قابل استفاده مجدد است. + پیش‌نمایش نتیجه در برابر پس‌زمینه‌های متضاد برای آشکار کردن هاله‌ها و جزئیات لبه از دست رفته را ببینید. + قبل از پاک کردن باقیمانده های اطراف، از ریستور برای مو، گوشه ها و اشیاء شفاف استفاده کنید. + هنگامی که برش در یک طرح قرار می گیرد، یک آزمایش کوچک روی رنگ پس زمینه نهایی صادر کنید. + واترمارک قابل خواندن + بدون اینکه عکس را خراب کنید، علامت‌ها را روی تصاویر روشن، تاریک، شلوغ و برش‌خورده قابل مشاهده کنید + از تصویر بدون پنهان کردن آن محافظت کنید + واترمارکی که در یک تصویر خوب به نظر می رسد ممکن است در تصویر دیگر ناپدید شود. اندازه، کدورت، کنتراست، قرارگیری و تکرار باید برای کل دسته انتخاب شود، نه تنها پیش نمایش اول. + قبل از صادرات همه فایل‌ها، علامت را روی روشن‌ترین و تاریک‌ترین تصاویر در دسته آزمایش کنید. + از کدورت کمتر برای علامت های تکراری بزرگ و کنتراست قوی تر برای علائم گوشه های کوچک استفاده کنید. + چهره های مهم، متن و جزئیات محصول را واضح نگه دارید مگر اینکه علامت برای جلوگیری از استفاده مجدد باشد. + یک تصویر را به کاشی تقسیم کنید + یک تصویر را بر اساس ردیف، ستون، درصد سفارشی، قالب و کیفیت برش دهید + شبکه ها و قطعات سفارش داده شده را آماده کنید + تقسیم تصویر چندین خروجی را از یک تصویر منبع ایجاد می کند. می‌تواند بر اساس تعداد ردیف و ستون تقسیم شود، درصد سطر یا ستون را تنظیم کند، و قطعات را با فرمت و کیفیت خروجی انتخاب‌شده ذخیره کند، که برای شبکه‌ها، برش‌های صفحه، پانوراما و پست‌های اجتماعی سفارش‌داده‌شده مفید است. + تصویر منبع را انتخاب کنید، سپس تعداد سطرها و ستون های مورد نیاز خود را تنظیم کنید. + زمانی که قطعات نباید اندازه همه یکسان باشند، درصد سطر یا ستون را تنظیم کنید. + قبل از ذخیره قالب و کیفیت خروجی را انتخاب کنید تا هر قطعه تولید شده از قوانین صادرات یکسان استفاده کند. + نوارهای تصویر را برش دهید + مناطق عمودی یا افقی را بردارید و قسمت های باقی مانده را دوباره با هم ادغام کنید + یک منطقه را بدون ایجاد شکاف حذف کنید + برش تصویر با برش معمولی متفاوت است. این می تواند یک نوار عمودی یا افقی انتخاب شده را حذف کند، سپس قسمت های باقی مانده تصویر را با هم ادغام کند. حالت معکوس به جای آن ناحیه انتخاب شده را نگه می دارد و استفاده از هر دو جهت معکوس مستطیل انتخاب شده را حفظ می کند. + از برش عمودی برای مناطق جانبی، ستون ها یا نوارهای میانی استفاده کنید. از برش افقی برای بنرها، شکاف ها یا ردیف ها استفاده کنید. + وقتی می‌خواهید قسمت انتخابی را به جای حذف آن نگه دارید، معکوس را روی یک محور فعال کنید. + پیش‌نمایش لبه‌ها پس از برش، زیرا محتوای ادغام‌شده می‌تواند ناگهانی به نظر برسد وقتی ناحیه حذف‌شده از جزئیات مهم عبور می‌کند. + فرمت خروجی را انتخاب کنید + JPEG، PNG، WebP، AVIF، JXL یا PDF را بر اساس محتوا و مقصد انتخاب کنید + اجازه دهید مقصد فرمت را تعیین کند + بهترین قالب بستگی به این دارد که تصویر حاوی چه چیزی است و در کجا استفاده می شود. عکس‌ها، اسکرین‌شات‌ها، دارایی‌های شفاف، اسناد و فایل‌های متحرک همگی به روش‌های متفاوتی از کار می‌افتند که مجبور به فرمت اشتباه شوند. + هنگامی که به شفافیت و پیکسل های دقیق نیاز ندارید، از JPEG برای سازگاری گسترده عکس استفاده کنید. + از PNG برای اسکرین شات‌های واضح، عکس‌برداری از رابط کاربری، گرافیک شفاف و ویرایش‌های بدون ضرر استفاده کنید. + زمانی که خروجی فشرده مدرن مهم است و برنامه دریافت کننده آن را پشتیبانی می کند، از WebP، AVIF یا JXL استفاده کنید. + کاورهای صوتی را استخراج کنید + هنر آلبوم جاسازی شده یا فریم های رسانه موجود را از فایل های صوتی به عنوان تصاویر PNG ذخیره کنید + آثار هنری را از فایل های صوتی بیرون بکشید + Audio Covers از فراداده رسانه Android برای خواندن آثار هنری جاسازی شده از فایل‌های صوتی استفاده می‌کند. هنگامی که اثر هنری جاسازی شده در دسترس نباشد، اگر Android بتواند فریم رسانه ای را ارائه دهد، بازیابی ممکن است دوباره به یک قاب رسانه ای برود. اگر هیچ کدام وجود نداشته باشد، ابزار گزارش می دهد که تصویری برای استخراج وجود ندارد. + جلدهای صوتی را باز کنید و یک یا چند فایل صوتی با پوشش مورد انتظار را انتخاب کنید. + قبل از ذخیره یا اشتراک‌گذاری، جلد استخراج‌شده را بررسی کنید، مخصوصاً برای فایل‌های برنامه‌های پخش جریانی یا ضبط. + اگر هیچ تصویری پیدا نشد، فایل صوتی احتمالاً پوشش تعبیه‌شده ندارد یا اندروید نمی‌تواند فریم قابل استفاده را نشان دهد. + فراداده تاریخ و مکان + زمانی که زمان ضبط مهم است، تصمیم بگیرید که چه چیزی را حفظ کنید، اما اطلاعات GPS یا دستگاه نباید درز کند + متادیتای مفید را از ابرداده خصوصی جدا کنید + ابرداده ها همه به یک اندازه خطرناک نیستند. تاریخ‌های ضبط می‌توانند برای بایگانی و مرتب‌سازی مفید باشند، در حالی که GPS، فیلدهای سریال‌مانند دستگاه، برچسب‌های نرم‌افزار یا فیلدهای مالک می‌توانند بیش از آنچه در نظر گرفته شده را نشان دهند. + وقتی ترتیب زمانی برای آلبوم‌ها، اسکن‌ها یا سابقه پروژه مهم است، برچسب‌های تاریخ و زمان را نگه دارید. + قبل از اشتراک گذاری عمومی یا ارسال تصاویر به افراد غریبه، GPS و برچسب های شناسایی دستگاه را حذف کنید. + یک نمونه را صادر کنید و زمانی که الزامات حفظ حریم خصوصی سخت است، دوباره EXIF ​​را بررسی کنید. + از برخورد نام فایل جلوگیری کنید + هنگامی که بسیاری از فایل ها نام هایی مانند image.jpg یا screenshot.png را به اشتراک می گذارند، از خروجی های دسته ای محافظت کنید + هر نام خروجی را منحصر به فرد کنید + زمانی که تصاویر از پیام‌رسان‌ها، اسکرین‌شات‌ها، پوشه‌های ابری یا چندین دوربین می‌آیند، نام فایل‌های تکراری رایج است. یک الگوی خوب از جایگزینی تصادفی جلوگیری می کند و خروجی ها را تا منابع خود قابل ردیابی نگه می دارد. + زمانی که نسخه ویرایش شده باید قابل تشخیص باشد، نام اصلی را به اضافه پسوند اضافه کنید. + هنگام پردازش دسته های مخلوط بزرگ، ترتیب، مهر زمانی، از پیش تعیین شده یا ابعاد را اضافه کنید. + تا زمانی که تأیید نکنید که الگوی نام‌گذاری نمی‌تواند با هم برخورد کند، از بازنویسی جریان‌های کاری خودداری کنید. + ذخیره یا به اشتراک بگذارید + بین یک فایل دائمی و انتقال موقت به برنامه دیگر یکی را انتخاب کنید + صادرات با هدف درست + ذخیره و اشتراک‌گذاری ممکن است شبیه به هم باشند، اما مشکلات مختلفی را حل می‌کنند. ذخیره فایلی را ایجاد می کند که می توانید بعداً آن را پیدا کنید. اشتراک‌گذاری یک نتیجه ایجاد شده را از طریق Android به برنامه دیگری ارسال می‌کند و ممکن است یک کپی محلی بادوام باقی نگذارد. + زمانی که خروجی باید در یک پوشه شناخته شده بماند، از آن نسخه پشتیبان تهیه شود یا بعداً دوباره استفاده شود، از Save استفاده کنید. + از Share برای پیام‌های سریع، پیوست‌های ایمیل، پست‌های اجتماعی یا ارسال نتیجه به ویرایشگر دیگر استفاده کنید. + هنگامی که برنامه دریافت کننده غیرقابل اعتماد است یا زمانی که یک اشتراک ناموفق برای ایجاد مجدد آزار دهنده است، ابتدا ذخیره کنید. + اندازه و حاشیه صفحه PDF + قبل از ایجاد سند، اندازه کاغذ، رفتار مناسب و اتاق تنفس را انتخاب کنید + صفحات تصویر را قابل چاپ و خواندن کنید + هنگامی که شکل آنها با اندازه کاغذ انتخابی مطابقت نداشته باشد، ممکن است به صفحات PDF ناخوشایند تبدیل شوند. حاشیه‌ها، حالت تناسب و جهت‌گیری صفحه تعیین می‌کنند که آیا محتوا برش، کوچک، کشیده یا راحت برای خواندن باشد. + هنگامی که PDF ممکن است چاپ شود یا به یک فرم ارسال شود، از اندازه کاغذ واقعی استفاده کنید. + برای اسکن و اسکرین شات از حاشیه استفاده کنید تا متن در لبه صفحه قرار نگیرد. + هنگامی که تصاویر منبع دارای جهت گیری مختلط هستند، صفحات عمودی و افقی را با هم پیش نمایش دهید. + اسکن ها را پاک کنید + لبه های کاغذ، سایه ها، کنتراست، چرخش و خوانایی را قبل از PDF یا OCR بهبود دهید + صفحات را قبل از بایگانی آماده کنید + اسکن را می توان از نظر فنی گرفت اما خواندن آن هنوز سخت است. مراحل پاکسازی کوچک قبل از صدور PDF اغلب بیشتر از تنظیمات فشرده سازی اهمیت دارند، به خصوص برای رسیدها، یادداشت های دست نویس و صفحات کم نور. + پرسپکتیو را درست کنید و لبه‌های میز، انگشتان، سایه‌ها و به هم ریختگی پس‌زمینه را برش دهید. + کنتراست را با دقت افزایش دهید تا متن بدون از بین بردن تمبر، امضا یا علامت مداد واضح‌تر شود. + OCR را فقط بعد از اینکه صفحه صاف و قابل خواندن است اجرا کنید، سپس اعداد مهم را به صورت دستی تأیید کنید. + فشرده سازی، مقیاس خاکستری، یا تعمیر فایل های PDF + از عملیات PDF یک فایل برای کپی های سبک تر، قابل چاپ یا بازسازی شده سند استفاده کنید + عملیات پاکسازی PDF را انتخاب کنید + PDF Tools شامل عملیات جداگانه برای فشرده سازی، تبدیل مقیاس خاکستری و تعمیر است. فشرده سازی و مقیاس خاکستری عمدتاً از طریق تصاویر جاسازی شده کار می کنند، در حالی که تعمیر ساختار PDF را بازسازی می کند. هیچ یک از اینها نباید به عنوان یک تعمیر تضمین شده برای هر سند تلقی شود. + هنگامی که سند حاوی تصاویر است و اندازه فایل برای اشتراک گذاری اهمیت دارد، از فشرده سازی PDF استفاده کنید. + زمانی که سند باید آسان‌تر چاپ شود یا به تصاویر رنگی نیاز نداشته باشد، از مقیاس خاکستری استفاده کنید. + هنگامی که سند باز نمی شود یا ساختار داخلی آسیب دیده است، از Repair PDF روی یک کپی استفاده کنید، سپس فایل بازسازی شده را تأیید کنید. + استخراج تصاویر از فایل های PDF + تصاویر پی دی اف تعبیه شده را بازیابی کنید و نتایج استخراج شده را در یک آرشیو قرار دهید + تصاویر منبع را از اسناد ذخیره کنید + Extract Images PDF را برای اشیاء تصویر جاسازی شده اسکن می کند، در صورت امکان از موارد تکراری پرش می کند و تصاویر بازیابی شده را در یک آرشیو ZIP ایجاد شده ذخیره می کند. این فقط تصاویری را که واقعاً در PDF جاسازی شده اند استخراج می کند، نه متن، نقشه های برداری یا هر صفحه قابل مشاهده را به عنوان یک اسکرین شات. + هنگامی که به تصاویر ذخیره شده در PDF نیاز دارید، مانند عکس‌های کاتالوگ یا دارایی‌های اسکن شده، از Extract Images استفاده کنید. + هنگامی که به صفحات کامل از جمله متن و طرح‌بندی نیاز دارید، به جای آن از PDF به تصاویر یا استخراج صفحه استفاده کنید. + اگر ابزار هیچ تصویر جاسازی شده ای را گزارش ندهد، ممکن است صفحه محتوای متنی/بردار باشد یا تصاویر ممکن است به عنوان اشیاء قابل استخراج ذخیره نشوند. + فراداده، صاف کردن، و خروجی چاپ + ویژگی‌های سند را ویرایش کنید، صفحات را شطرنجی کنید، یا طرح‌بندی‌های چاپ سفارشی را عمداً آماده کنید + اقدام سند نهایی را انتخاب کنید + فراداده PDF، مسطح کردن، و آماده سازی چاپ مشکلات مختلف مرحله نهایی را حل می کند. فراداده ویژگی‌های سند را کنترل می‌کند، Flatten PDF صفحات را به خروجی کمتر قابل ویرایش تبدیل می‌کند، و Print PDF یک طرح‌بندی جدید با کنترل‌های اندازه صفحه، جهت، حاشیه، و صفحات در هر صفحه آماده می‌کند. + هنگامی که نویسنده، عنوان، کلمات کلیدی، تولیدکننده یا پاکسازی حریم خصوصی اهمیت دارد، از فراداده استفاده کنید. + هنگامی که ویرایش محتوای صفحه قابل مشاهده به عنوان اشیاء PDF جداگانه دشوارتر می شود، از Flatten PDF استفاده کنید. + هنگامی که به اندازه صفحه، جهت، حاشیه یا چندین صفحه در هر برگ نیاز دارید، از Print PDF استفاده کنید. + تصاویر را برای OCR آماده کنید + قبل از درخواست از OCR برای خواندن متن، از برش، چرخش، کنتراست، حذف نویز و مقیاس استفاده کنید + پیکسل های پاک کننده OCR بدهید + اشتباهات OCR اغلب از تصویر ورودی به جای موتور OCR ناشی می شود. صفحات کج، کنتراست کم، تاری، سایه ها، متن های کوچک و پس زمینه های مختلط همگی کیفیت تشخیص را کاهش می دهند. + به قسمت متن برش دهید و تصویر را بچرخانید تا خطوط قبل از تشخیص افقی باشند. + کنتراست را افزایش دهید یا به سیاه و سفید تمیزتر تبدیل کنید فقط زمانی که خواندن حروف را آسان‌تر کند. + اندازه متن کوچک را به طور متوسط ​​به سمت بالا تغییر دهید، اما از شارپ کردن تهاجمی که لبه های جعلی ایجاد می کند اجتناب کنید. + محتوای QR را بررسی کنید + قبل از اعتماد کردن به کد، پیوندها، اعتبارنامه‌های Wi-Fi، مخاطبین و اقدامات را مرور کنید + متن رمزگشایی شده را به عنوان ورودی نامعتبر در نظر بگیرید + یک کد QR می تواند URL، کارت تماس، رمز عبور Wi-Fi، رویداد تقویم، شماره تلفن یا متن ساده را پنهان کند. برچسب قابل مشاهده در نزدیکی کد ممکن است با بار واقعی مطابقت نداشته باشد، بنابراین قبل از اقدام بر روی محتوای رمزگشایی شده آن را بررسی کنید. + URL کامل رمزگشایی شده را قبل از باز کردن آن بررسی کنید، به خصوص دامنه های کوتاه شده یا ناآشنا. + مراقب کدهای Wi-Fi، مخاطبین، تقویم، تلفن و پیامک باشید زیرا می‌توانند اقدامات حساسی را آغاز کنند. + به جای باز کردن فوراً، زمانی که باید آن را در جای دیگری تأیید کنید ابتدا آن را کپی کنید. + کدهای QR تولید کنید + ایجاد کد از متن ساده، نشانی‌های اینترنتی، Wi-Fi، مخاطبین، ایمیل، تلفن، پیامک و داده‌های موقعیت مکانی + محموله QR مناسب بسازید + صفحه QR می تواند کدهایی را از محتوای وارد شده تولید کند و همچنین کدهای موجود را اسکن کند. انواع QR ساختاریافته به رمزگذاری داده‌ها در قالبی که سایر برنامه‌ها می‌فهمند، کمک می‌کنند، مانند جزئیات ورود به سیستم Wi-Fi، کارت‌های تماس، فیلدهای ایمیل، شماره تلفن، محتوای پیامک، URL‌ها یا مختصات جغرافیایی. + متن ساده را برای یادداشت‌های ساده انتخاب کنید، یا زمانی که کد باید به‌عنوان داده‌های Wi-Fi، مخاطب، URL، ایمیل، تلفن، پیامک یا موقعیت مکانی باز شود، از نوع ساختار یافته استفاده کنید. + قبل از به اشتراک گذاشتن کد تولید شده، همه فیلدها را بررسی کنید زیرا پیش نمایش قابل مشاهده فقط باری را که وارد کرده اید نشان می دهد. + کد QR ذخیره شده را زمانی که چاپ، پست عمومی یا توسط افراد دیگر استفاده می شود، با یک اسکنر آزمایش کنید. + حالت چک جمع را انتخاب کنید + هش‌ها را از فایل‌ها یا متن محاسبه کنید، یک فایل را مقایسه کنید یا بسیاری از فایل‌ها را دسته‌ای بررسی کنید + محتوا را تأیید کنید، نه ظاهر + Checksum Tools دارای برگه‌های جداگانه برای هش کردن فایل، هش متن، مقایسه یک فایل با هش شناخته شده و مقایسه دسته‌ای است. جمع کنترل هویت بایت به بایت را برای الگوریتم انتخاب شده اثبات می کند. این ثابت نمی کند که دو تصویر صرفا شبیه هم هستند. + از Calculate برای فایل ها و Text Hash برای داده های متنی تایپ شده یا جایگذاری شده استفاده کنید. + زمانی که شخصی برای یک فایل به شما چک جمع مورد انتظار می دهد از Compare استفاده کنید. + هنگامی که بسیاری از فایل ها نیاز به هش یا تأیید در یک پاس دارند، از مقایسه دسته ای استفاده کنید، سپس الگوریتم انتخابی را ثابت نگه دارید. + حریم خصوصی کلیپ بورد + از ویژگی‌های کلیپ‌بورد خودکار بدون افشای تصادفی داده‌های کپی شده خصوصی استفاده کنید + محتوای کپی شده را عمدی نگه دارید + اتوماسیون کلیپ بورد راحت است، اما متن کپی شده ممکن است حاوی رمزهای عبور، پیوندها، آدرس‌ها، نشانه‌ها یا یادداشت‌های خصوصی باشد. ورودی مبتنی بر کلیپ بورد را به عنوان یک ویژگی سرعتی که باید با عادات و انتظارات حریم خصوصی شما مطابقت داشته باشد، در نظر بگیرید. + اگر متن خصوصی به طور غیرمنتظره ای در ابزار ظاهر شد، استفاده خودکار از کلیپ بورد را غیرفعال کنید. + فقط زمانی از ذخیره فقط بریده‌دان استفاده کنید که عمداً می‌خواهید نتیجه را ذخیره نکنید. + پس از مدیریت متن حساس، داده‌های QR یا بارهای Base64، کلیپ بورد را پاک یا جایگزین کنید. + فرمت های رنگی + قبل از چسباندن در جای دیگر، مقادیر رنگ HEX، RGB، HSV، آلفا و کپی شده را درک کنید + قالب مورد نظر را کپی کنید + همان رنگ قابل مشاهده را می توان به چندین روش نوشت. یک ابزار طراحی، فیلد CSS، مقدار رنگ Android یا ویرایشگر تصویر ممکن است انتظار نظم، مدیریت آلفا یا مدل رنگ متفاوتی داشته باشد. + هنگام چسباندن فیلدهای وب، طراحی یا طرح زمینه که انتظار کدهای رنگ فشرده را دارند، از HEX استفاده کنید. + هنگامی که نیاز به تنظیم دستی کانال ها، روشنایی، اشباع یا رنگ دارید، از RGB یا HSV استفاده کنید. + وقتی شفافیت مهم است، آلفا را جداگانه بررسی کنید زیرا برخی از مقصدها آن را نادیده می گیرند یا از ترتیب دیگری استفاده می کنند. + کتابخانه رنگ را مرور کنید + رنگ‌های نام‌گذاری شده را با نام یا HEX جستجو کنید و موارد دلخواه را در بالای صفحه نگه دارید + رنگ های نامگذاری شده قابل استفاده مجدد را پیدا کنید + کتابخانه رنگ یک مجموعه رنگ نام‌گذاری شده را بارگیری می‌کند، آن را بر اساس نام مرتب می‌کند، بر اساس نام رنگ یا مقدار HEX فیلتر می‌کند و رنگ‌های دلخواه را ذخیره می‌کند تا ورودی‌های مهم راحت‌تر به آن دسترسی پیدا کنند. زمانی مفید است که به جای نمونه برداری از یک تصویر، به یک رنگ با نام واقعی نیاز دارید. + هنگامی که خانواده را می شناسید، نام رنگی را جستجو کنید، مانند قرمز، آبی، تخته سنگ یا نعناع. + زمانی که از قبل یک رنگ عددی دارید و می‌خواهید ورودی‌های با نام منطبق یا نزدیک را پیدا کنید، بر اساس HEX جستجو کنید. + رنگ‌های مکرر را به‌عنوان موارد دلخواه علامت‌گذاری کنید تا هنگام مرور، جلوتر از کتابخانه عمومی حرکت کنند. + از مجموعه های گرادیان مش استفاده کنید + منابع شیب مش موجود را دانلود کنید و تصاویر انتخابی را به اشتراک بگذارید + از دارایی های گرادیان مش از راه دور استفاده کنید + Mesh Gradients می تواند یک مجموعه منابع راه دور را بارگیری کند، تصاویر گرادیان گمشده را دانلود کند، پیشرفت دانلود را نشان دهد و گرادیان های انتخاب شده را به اشتراک بگذارد. در دسترس بودن به دسترسی به شبکه و ذخیره منابع از راه دور بستگی دارد، بنابراین یک لیست خالی معمولاً به این معنی است که منابع هنوز در دسترس نبوده اند. + هنگامی که می خواهید تصاویر گرادینت مش را آماده کنید، Mesh Gradients را از ابزارهای گرادیان باز کنید. + قبل از اینکه فرض کنید مجموعه خالی است منتظر بارگیری منبع یا پیشرفت دانلود باشید. + گرادیان های مورد نیاز خود را انتخاب کرده و به اشتراک بگذارید، یا زمانی که می خواهید یک گرادینت سفارشی به صورت دستی بسازید، به Gradient Maker برگردید. + وضوح دارایی ایجاد شده + اندازه خروجی را قبل از ایجاد گرادیان، نویز، کاغذدیواری، هنر SVG مانند یا بافت انتخاب کنید. + در اندازه ای که نیاز دارید تولید کنید + تصاویر تولید شده دوربین اصلی ندارند تا دوباره روی آن قرار بگیرند. اگر خروجی خیلی کوچک باشد، مقیاس‌بندی بعدی می‌تواند الگوها را نرم کند، جزئیات را تغییر دهد یا نحوه فشرده‌سازی و کاشی‌های دارایی را تغییر دهد. + ابتدا ابعاد مورد استفاده نهایی را تنظیم کنید، مانند کاغذ دیواری، نماد، پس‌زمینه، چاپ یا روکش. + از اندازه‌های بزرگ‌تر برای بافت‌هایی که ممکن است برش داده شوند، بزرگ‌نمایی شوند یا در چندین طرح‌بندی دوباره استفاده شوند، استفاده کنید. + نسخه‌های آزمایشی را قبل از خروجی‌های عظیم صادر کنید، زیرا جزئیات رویه‌ای می‌تواند نحوه رفتار فشرده‌سازی را تغییر دهد. + صادرات تصاویر پس زمینه فعلی + والپیپرهای Home، Lock و داخلی موجود را از دستگاه بازیابی کنید + تصاویر پس زمینه نصب شده را ذخیره یا به اشتراک بگذارید + Wallpapers Export والپیپرهایی را که Android نمایش می دهد از طریق APIهای کاغذدیواری سیستم می خواند. بسته به دستگاه، نسخه Android، مجوزها و نوع ساخت، صفحه اصلی، قفل یا کاغذدیواری های داخلی ممکن است به طور جداگانه در دسترس باشند یا ممکن است وجود نداشته باشند. + برای بارگیری کاغذدیواری هایی که سیستم به برنامه اجازه خواندن را می دهد، صادرات تصاویر پس زمینه را باز کنید. + صفحه اصلی، قفل یا ورودی های کاغذدیواری داخلی موجود را که می خواهید ذخیره، کپی یا به اشتراک بگذارید، انتخاب کنید. + اگر کاغذدیواری وجود نداشته باشد یا ویژگی در دسترس نباشد، معمولاً یک محدودیت سیستم، مجوز یا نوع ساخت است تا تنظیم ویرایش. + دریچه گاز انیمیشن گوشه ها + کپی رنگ به عنوان + HEX + نام + مدل تشک پرتره برای برش افراد سریع با موها و دست زدن به لبه های نرم تر. + بهبود سریع در نور کم با استفاده از تخمین منحنی Zero-DCE++. + مدل بازسازی تصویر Restormer برای کاهش رگه های باران و مصنوعات آب و هوای مرطوب. + مدل انحرافی سند که یک شبکه مختصات را پیش‌بینی می‌کند و صفحات منحنی را به یک اسکن مسطح‌تر بازنگری می‌کند. + مقیاس مدت حرکت + سرعت انیمیشن برنامه را کنترل می کند: 0 حرکت را غیرفعال می کند، 1 عادی است، مقادیر بالاتر انیمیشن ها را کاهش می دهد + نمایش ابزارهای اخیر + نمایش دسترسی سریع به ابزارهای اخیراً استفاده شده در صفحه اصلی + آمار استفاده + باز شدن برنامه، راه‌اندازی ابزار و ابزارهای مورد استفاده خود را کاوش کنید + برنامه باز می شود + ابزار باز می شود + آخرین ابزار + سیوهای موفق + رگه فعالیت + داده ها ذخیره شد + فرمت برتر + ابزارهای مورد استفاده + %1$d باز می شود • %2$s + هنوز آمار استفاده وجود ندارد + چند ابزار را باز کنید و آنها به طور خودکار در اینجا ظاهر می شوند + آمار استفاده شما فقط به صورت محلی در دستگاه شما ذخیره می شود. ImageToolbox از آنها فقط برای نشان دادن فعالیت شخصی، ابزارهای مورد علاقه و اطلاعات اطلاعات ذخیره شده شما استفاده می کند + ویرایشگر ویرایش عکس روتوش عکس تنظیم + تغییر اندازه تبدیل مقیاس وضوح ابعاد عرض ارتفاع jpg jpeg png فشرده سازی وب + فشرده سازی اندازه وزن بایت mb kb کاهش کیفیت بهینه سازی + نسبت تصویر برش برش برش + تصحیح رنگ افکت فیلتر تنظیم lut mask + برس نقاشی مداد حاشیه نویسی نشانه گذاری + رمزگذاری رمزگشایی رمزگشایی رمز عبور رمزگشایی + پاک کننده پس زمینه پاک کردن برش آلفا شفاف + پیش نمایش بازرسی گالری بیننده باز است + دوخت ادغام ترکیب عکس پانوراما طولانی + آدرس وب دانلود تصویر لینک اینترنتی + نمونه قطره چکان هگز rgb رنگ + پالت رنگ نمونه استخراج غالب + حریم خصوصی ابرداده exif حذف نوار پاک کردن مکان GPS + مقایسه تفاوت تفاوت قبل از بعد + محدودیت تغییر اندازه حداکثر حداقل مگاپیکسل ابعاد گیره + ابزار صفحات سند پی دی اف + متن ocr تشخیص عصاره اسکن + مش رنگ پس زمینه گرادیان + روکش مهر متن لوگوی واترمارک + انیمیشن گیف متحرک تبدیل فریم + انیمیشن apng متحرک png تبدیل فریم + بایگانی فشرده فشرده سازی بسته باز کردن فایل ها + jxl jpeg xl تبدیل فرمت تصویر jpg + ردیابی بردار svg تبدیل xml + فرمت تبدیل jpg jpeg png webp heic avif jxl bmp + اسکنر اسناد اسکن کاغذ پرسپکتیو برش + اسکن اسکنر بارکد qr + عکس نجومی میانه متوسط ​​روی هم انباشته + قطعات برش شبکه کاشی های تقسیم شده + تبدیل رنگ هگز rgb hsl hsv آزمایشگاه cmyk + webp تبدیل فرمت تصویر متحرک + مولد نویز دانه بافت تصادفی + ترکیب طرح شبکه کلاژ + لایه‌های نشانه‌گذاری حاشیه‌نویسی ویرایش هم‌پوشانی + base64 کد رمزگشایی داده uri + چک‌سوم هش md5 ​​sha sha1 sha256 را تأیید کنید + پس زمینه شیب مش + ابرداده exif ویرایش gps تاریخ دوربین + کاشی های تکه های شبکه تقسیم شده را برش دهید + فراداده موسیقی هنری آلبوم جلد صوتی + پس زمینه صادرات کاغذ دیواری + شخصیت های هنری متن ascii + Ai ml بخش عصبی را تقویت می کند + پالت مواد کتابخانه رنگ به نام رنگ ها + استودیوی افکت قطعه shader glsl + pdf ادغام ترکیب پیوستن + pdf تقسیم صفحات جداگانه + جهت چرخش صفحات پی دی اف + پی دی اف مرتب سازی مجدد مرتب سازی صفحات + صفحه بندی شماره صفحات پی دی اف + متن pdf ocr عصاره قابل جستجو را تشخیص می دهد + متن لوگو تمبر pdf + رسم امضای pdf + pdf حفاظت از رمز عبور قفل رمزگذاری + pdf باز کردن رمز عبور رمزگشایی حذف حفاظت + فشرده سازی پی دی اف کاهش اندازه بهینه سازی + pdf سیاه و سفید سیاه و سفید تک رنگ + تعمیر پی دی اف ریکاوری خراب است + pdf خواص ابرداده عنوان نویسنده exif + pdf حذف صفحات حذف شده + حاشیه های برش pdf + pdf مسطح حاشیه نویسی لایه ها را تشکیل می دهد + pdf استخراج تصاویر تصاویر + فشرده سازی آرشیو زیپ پی دی اف + چاپگر pdf + نمایشگر پیش نمایش پی دی اف خوانده شود + تبدیل تصاویر به pdf سند jpg jpeg png + صفحات pdf به تصاویر صادرات jpg png + پی دی اف حاشیه نویسی نظرات حذف پاک + نوع خنثی + خطا + رها کردن سایه + ارتفاع دندان + محدوده دندان افقی + محدوده دندان عمودی + لبه بالا + لبه سمت راست + لبه پایین + لبه چپ + لبه پاره شده + بازنشانی آمار استفاده + ابزار باز می‌شود، آمار را ذخیره می‌کند، رگه‌ها، داده‌های ذخیره‌شده و قالب بالا بازنشانی می‌شوند. باز شدن برنامه بدون تغییر می ماند. + صوتی + فایل + صادرات پروفایل ها + ذخیره نمایه فعلی + حذف نمایه صادرات + آیا می‌خواهید نمایه صادراتی \\"%1$s\\" را حذف کنید؟ + ترسیم حاشیه + یک حاشیه نازک در اطراف نقاشی نشان دهید و بوم پاک کنید + موج دار + عناصر رابط کاربری گرد با لبه موج دار نرم + اسکوپ + شکاف + گوشه های گرد حک شده به داخل + گوشه های پلکانی با برش دوتایی + جای جای + از {filename} برای درج نام فایل اصلی بدون پسوند استفاده کنید + شعله ور شدن + مقدار پایه + مقدار انگشتر + مقدار اشعه + عرض حلقه + منحرف کردن دیدگاه + جاوا ظاهر و احساس + برش + زاویه X + و زاویه + تغییر اندازه خروجی + قطره آب + طول موج + فاز + گذرگاه بالا + ماسک رنگی + کروم + حل کند + نرمی + بازخورد + تاری لنز + ورودی کم + ورودی بالا + خروجی کم + خروجی بالا + جلوه های نور + ارتفاع دست انداز + نرمی ضربه + ریپل + دامنه X + دامنه Y + طول موج X + طول موج Y + تاری تطبیقی + تولید بافت + بافت های رویه ای مختلف را ایجاد کنید و آنها را به عنوان تصویر ذخیره کنید + نوع بافت + تعصب + زمان + بدرخشید + پراکندگی + نمونه ها + ضریب زاویه + ضریب گرادیان + نوع شبکه + قدرت فاصله + مقیاس Y + مبهم بودن + نوع پایه + عامل آشفتگی + مقیاس بندی + تکرارها + حلقه ها + فلز برس خورده + کاستیک + سلولی + تخته شطرنجی + fBm + سنگ مرمر + پلاسما + لحاف + چوب + تصادفی + مربع + شش ضلعی + هشت ضلعی + مثلثی + لبه دار + نویز VL + نویز SC + اخیر + هنوز فیلتری که اخیراً استفاده شده است وجود ندارد + مولد بافت برس خورده فلز سوزاننده سلولی شطرنجی مرمر پلاسما لحاف چوب آجر استتار ابر سلولی ترک پارچه شاخ و برگ لانه زنبوری گدازه یخ سحابی کاغذ زنگ زده شن دود سنگ زمین توپوگرافی آب موج + آجر + استتار + سلول + ابر + کرک + پارچه + شاخ و برگ + لانه زنبوری + یخ + گدازه + سحابی + کاغذ + زنگ زدگی + شن و ماسه + دود + سنگ + زمین + توپوگرافی + موج آب + چوب پیشرفته + عرض ملات + بی نظمی + اریب + زبری + آستانه اول + آستانه دوم + آستانه سوم + نرمی لبه + عرض حاشیه + پوشش + جزئیات + عمق + انشعاب + نخ های افقی + رشته های عمودی + Fuzz + رگها + نورپردازی + عرض ترک + فراست + جریان + پوسته + تراکم ابر + ستاره ها + تراکم فیبر + استحکام فیبر + لکه ها + خوردگی + سوراخ کردن + پولک + فرکانس تپه + زاویه باد + امواج + ویسپس + مقیاس ورید + سطح آب + سطح کوه + فرسایش + سطح برف + تعداد خطوط + ضخامت خط + سایه زدن + رنگ منافذ + رنگ ملات + رنگ آجری تیره + رنگ آجری + رنگ برجسته کنید + رنگ تیره + رنگ جنگلی + رنگ زمین + رنگ شنی + رنگ پس زمینه + رنگ سلولی + رنگ لبه + رنگ آسمان + رنگ سایه + رنگ روشن + رنگ سطح + تنوع رنگ + رنگ ترک + رنگ تار + رنگ پود + رنگ برگ تیره + رنگ برگ + رنگ حاشیه + رنگ عسلی + رنگ عمیق + رنگ یخی + رنگ یخبندان + رنگ پوسته + رنگ شستشو + رنگ درخشش + رنگ فضایی + رنگ بنفش + رنگ آبی + رنگ پایه + رنگ فیبر + رنگ لکه + رنگ فلزی + رنگ زنگ تیره + رنگ زنگ + رنگ نارنجی + رنگ دودی + رنگ رگ + رنگ دشتی + آب رنگ + رنگ سنگ + رنگ برفی + رنگ کم + رنگ بالا + رنگ خط + رنگ کم عمق + چمن + خاک + چرم + بتن + آسفالت + خزه + آتش + شفق قطبی + لکه روغن + آبرنگ + جریان چکیده + عقیق + فولاد دمشق + رعد و برق + مخملی + مرمریت جوهر + فویل هولوگرافیک + بیولومینسانس + گرداب کیهانی + لامپ گدازه + افق رویداد + بلوم فراکتال + تونل کروماتیک + کسوف کرونا + جاذبه عجیب + تاج فروسیال + ابرنواختر + زنبق + پر طاووس + پوسته ناتیلوس + سیاره حلقه دار + چگالی تیغه + طول تیغه + باد + لکه دار بودن + توده ها + رطوبت + سنگریزه ها + چین و چروک + منافذ + جمع + ترک ها + تار + بپوشید + لکه ها + الیاف + فرکانس شعله + دود + شدت + روبان + باندها + رنگین کمانی + تاریکی + شکوفه می دهد + رنگدانه + لبه ها + کاغذ + انتشار + تقارن + وضوح + بازی رنگ + شیری + لایه ها + تاشو + لهستانی + شاخه ها + جهت + شین + تا می شود + پر کردن + تعادل جوهر + طیف + چین و چروک + پراش + اسلحه + پیچ و تاب + درخشش هسته + حباب ها + شیب دیسک + اندازه افق + عرض دیسک + عدسی + گلبرگ + حلقه کردن + فیلیگرن + وجوه + انحنا + اندازه ماه + اندازه کرونا + اشعه ها + انگشتر الماس + لوب ها + چگالی مدار + ضخامت + سنبله + طول سنبله + اندازه بدن + فلزی + شعاع شوک + عرض پوسته + بیرون انداخته شد + اندازه مردمک + اندازه زنبق + تنوع رنگ + نورگیر + اندازه چشم + تراکم خار + می چرخد + اتاق ها + باز شدن + برآمدگی ها + مروارید + اندازه سیاره + شیب حلقه + عرض حلقه + جو + رنگ کثیف + رنگ چمن تیره + رنگ چمن + رنگ نوک + رنگ خاکی تیره + رنگ خشک + رنگ سنگریزه + رنگ چرم + رنگ بتن + رنگ قیر + رنگ آسفالت + رنگ سنگ + رنگ گرد و غبار + رنگ خاک + رنگ خزه تیره + رنگ خزه + رنگ قرمز + رنگ اصلی + رنگ سبز + رنگ فیروزه ای + رنگ سرخابی + رنگ طلایی + رنگ کاغذ + رنگ پیگمنت + رنگ ثانویه + رنگ اول + رنگ دوم + رنگ صورتی + رنگ استیل تیره + رنگ استیل + رنگ استیل روشن + رنگ اکسید + رنگ هاله + رنگ پیچ و مهره + رنگ مخملی + رنگ براق + رنگ جوهر آبی + رنگ جوهر قرمز + رنگ جوهر تیره + رنگ نقره ای + رنگ زرد + رنگ بافت + رنگ دیسک + رنگ داغ + رنگ لنز + رنگ بیرونی + رنگ داخلی + رنگ کرونا + رنگ سرد + رنگ گرم + رنگ ابر + رنگ شعله + رنگ پر + رنگ صدفی + رنگ مرواریدی + رنگ سیاره + رنگ انگشتر + پس از ذخیره فایل های اصلی را حذف کنید + فقط فایل های منبع با موفقیت پردازش شده حذف خواهند شد + فایل های اصلی حذف شده: %1$d + فایل های اصلی حذف نشد: %1$d + فایل های اصلی حذف شدند: %1$d، ناموفق: %2$d + حرکات ورق + کشیدن برگه‌های گزینه گسترش یافته مجاز است + زیر نمونه برداری کروما + عمق بیت + بدون ضرر + %1$d-bit + تغییر نام دسته ای + چندین فایل را با استفاده از الگوهای نام فایل تغییر نام دهید + دسته ای تغییر نام فایل ها نام فایل الگوی توالی تاریخ پسوند + فایل ها را برای تغییر نام انتخاب کنید + الگوی نام فایل + تغییر نام دهید + منبع تاریخ + تاریخ EXIF ​​گرفته شده است + تاریخ اصلاح فایل + تاریخ ایجاد فایل + تاریخ فعلی + تاریخ دستی + تاریخ دستی + زمان دستی + تاریخ انتخاب شده برای برخی از فایل ها در دسترس نیست. تاریخ فعلی برای آنها استفاده خواهد شد. + یک الگوی نام فایل وارد کنید + این الگو یک نام فایل نامعتبر یا بیش از حد طولانی ایجاد می کند + الگوی نام فایل های تکراری تولید می کند + همه نام فایل ها از قبل با الگو مطابقت دارند + این الگو از نشانه هایی استفاده می کند که برای تغییر نام دسته ای در دسترس نیستند + نام ثابت خواهد ماند + فایل ها با موفقیت تغییر نام دادند + برخی از فایل ها را نمی توان تغییر نام داد + اجازه داده نشد + از نام فایل اصلی بدون پسوند استفاده می کند و به شما کمک می کند تا شناسایی منبع را دست نخورده نگه دارید. همچنین از برش با \\o{start:end}، نویسه‌گردانی با \\o{t} و جایگزینی با \\o{s/old/new/} پشتیبانی می‌کند. + شمارنده افزایش خودکار از قالب بندی سفارشی پشتیبانی می کند: \\c{padding} (به عنوان مثال \\c{3} -&gt; 001)، \\c{start:step} یا \\c{start:step:padding}. + نام پوشه والد که فایل اصلی در آن قرار داشت. + اندازه فایل اصلی پشتیبانی از واحدهای: \\z{b}، \\z{kb}، \\z{mb} یا فقط \\z برای قالب قابل خواندن توسط انسان. + برای اطمینان از منحصر به فرد بودن نام فایل، یک شناسه منحصر به فرد جهانی (UUID) تصادفی ایجاد می کند. + تغییر نام فایل ها قابل بازگشت نیست. قبل از ادامه از صحت نام های جدید مطمئن شوید. + تغییر نام را تأیید کنید + تنظیمات منوی کناری + مقیاس منو + منوی آلفا + تراز سفیدی خودکار + بریدن + بررسی واترمارک های پنهان + ذخیره سازی + محدودیت حافظه پنهان + هنگامی که حافظه پنهان از این اندازه بزرگتر شد، به طور خودکار پاک کنید + فاصله پاکسازی + هر چند وقت یکبار بررسی شود که آیا حافظه پنهان باید پاک شود یا خیر + در راه اندازی برنامه + 1 روز + 1 هفته + 1 ماه + کش برنامه را به طور خودکار با توجه به محدودیت و بازه انتخابی پاک کنید + منطقه کشت متحرک + امکان جابجایی کل ناحیه برش را با کشیدن داخل آن فراهم می کند + GIF را ادغام کنید + چندین کلیپ GIF را در یک انیمیشن ترکیب کنید + سفارش کلیپ های GIF + معکوس + به صورت معکوس بازی کنید + بومرنگ + به جلو و سپس به عقب بازی کنید + سایز فریم را عادی کنید + قاب‌ها را برای بزرگ‌ترین گیره تنظیم کنید. برای حفظ مقیاس اصلی خود را خاموش کنید + تاخیر بین کلیپ ها (ms) + پوشه + همه تصاویر را از یک پوشه و زیرپوشه های آن انتخاب کنید + یاب تکراری + کپی های دقیق و تصاویر مشابه بصری را پیدا کنید + یاب تکراری تصاویر مشابه کپی های دقیق عکس پاکسازی ذخیره سازی dHash SHA-256 + تصاویر را برای یافتن موارد تکراری انتخاب کنید + حساسیت به شباهت + کپی های دقیق + تصاویر مشابه + تمام موارد تکراری دقیق را انتخاب کنید + همه به جز توصیه شده را انتخاب کنید + تکراری یافت نشد + سعی کنید تصاویر بیشتری اضافه کنید یا حساسیت شباهت را افزایش دهید + نمی توان %1$d تصویر(ها) را خواند + %1$s • %2$s قابل بازیابی + %1$d گروه • %2$s قابل بازیابی + %1$d انتخاب شده • %2$s + این قابل واگرد نیست. ممکن است اندروید از شما بخواهد که حذف را در یک گفتگوی سیستم تأیید کنید. + یافت نشد + برای شروع حرکت کنید + حرکت به پایان + \ No newline at end of file diff --git a/core/resources/src/main/res/values-fi/strings.xml b/core/resources/src/main/res/values-fi/strings.xml new file mode 100644 index 0000000..21bf954 --- /dev/null +++ b/core/resources/src/main/res/values-fi/strings.xml @@ -0,0 +1,3739 @@ + + + + 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 + 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 + 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 & 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 & 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. + 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. + 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 + Sisältötietoinen vääristyminen + Muisti ei riitä tämän toiminnon suorittamiseen. Yritä käyttää pienempää kuvaa, sulkea muita sovelluksia tai käynnistää sovellus uudelleen. + Rinnakkaistyöntekijät + Näitä kuvia ei tallennettu, koska muunnetut tiedostot olisivat suurempia kuin alkuperäiset. Tarkista tämä luettelo ennen alkuperäisten kuvien poistamista. + Ohitetut tiedostot: %1$s + Sovelluslokit + Tarkastele sovelluksen lokeja havaitaksesi ongelmat + Suosikit ryhmitellyissä työkaluissa + Lisää suosikit välilehdeksi, kun työkalut on ryhmitelty tyypin mukaan + Näytä suosikki viimeisenä + Siirtää suosikkityökalut-välilehden loppuun + Vaakavälit + Pystysuuntainen etäisyys + Energiaa taaksepäin + Käytä yksinkertaista gradientin magnitudienergiakarttaa eteenpäin suunnatun energian algoritmin sijaan + Poista peitetty alue + Saumat kulkevat peitetyn alueen läpi poistaen vain valitun alueen sen suojaamisen sijaan + VHS NTSC + NTSC:n lisäasetukset + Ylimääräinen analoginen viritys vahvempaan VHS:ään ja lähetysartefakteihin + Kroma verenvuoto + Nauha kuluma + Seuranta + Luma sively + Soitto + Lumi + Käytä kenttää + Suodattimen tyyppi + Sisääntulo luma-suodatin + Syöttö chroma alipäästö + Kromademodulaatio + Vaiheen siirto + Vaihepoikkeama + Pään vaihto + Pään vaihtokorkeus + Pään kytkentäpoikkeama + Pään vaihto + Keskilinjan asento + Keskilinjan värinää + Melun korkeuden seuranta + Seurantaaalto + Lumen seuranta + Lumen anisotropian seuranta + Seurantamelu + Melun voimakkuuden seuranta + Komposiittikohina + Komposiittikohinataajuus + Komposiittimelun voimakkuus + Komposiittimelun yksityiskohta + Soittotaajuus + Soittovoima + Luma melua + Luma melun taajuus + Luma melun voimakkuus + Luma melun yksityiskohta + Kroma kohina + Kromakohinataajuus + Kromakohinan intensiteetti + Kromameluyksityiskohdat + Lumen intensiteetti + Lumen anisotropia + Kromafaasikohina + Kromafaasivirhe + Kromaviive vaakasuunnassa + Väriviive pystysuorassa + VHS-nauhan nopeus + VHS-kromahäviö + VHS-terävyyden intensiteetti + VHS-terävöintitaajuus + VHS-reunaaallon intensiteetti + VHS reunaaallon nopeus + VHS-reuna-aaltotaajuus + VHS reunaaallon yksityiskohta + Ulostulon kroma-alipäästö + Mittakaava vaakatasossa + Mittakaava pystysuoraan + Skaalaustekijä X + Skaalauskerroin Y + Tunnistaa yleiset kuvavesileimat ja maalaa ne LaMalla. Lataa tunnistus- ja maalausmallit automaattisesti + Deuteranopia + Laajenna kuva + Objektien segmentointiin perustuva taustanpoistaja YOLO v11 -segmentaatiolla + Näytä viivakulma + Näyttää nykyisen viivan kierron asteina piirtämisen aikana + Animoidut emojit + Näytä saatavilla olevat emojit animaatioina + Tallenna alkuperäiseen kansioon + Tallenna uudet tiedostot alkuperäisen tiedoston viereen valitun kansion sijaan + Vesileima PDF + Muisti ei riitä + Kuva on todennäköisesti liian suuri käsiteltäväksi tällä laitteella tai järjestelmän vapaa muisti on loppunut. Kokeile pienentää kuvan resoluutiota, sulkea muita sovelluksia tai valita pienempi tiedosto. + Debug-valikko + Sovelluksen toimintojen testausvalikko, tämän ei ole tarkoitus näkyä tuotantojulkaisussa + Palveluntarjoaja + PaddleOCR vaatii lisää ONNX-malleja laitteellesi. Haluatko ladata %1$s tiedot? + Universaali + korealainen + latina + itäslaavilainen + thaimaalainen + kreikkalainen + englanti + Kyrillinen + arabia + Devanagari + tamili + telugu + Shader + Varjostimen esiasetus + Varjostajaa ei ole valittu + Shader Studio + Luo, muokkaa, vahvista, tuo ja vie mukautettuja fragmenttivarjostimia + Varjostimet tallennettu + Avaa tallennetut varjostimet, kopioi, vie, jaa tai poista + Uusi varjostin + Shader-tiedosto + .itshader JSON + %1$d parametreja + Varjostimen lähde + Kirjoita vain void main() -teksti. Käytä textureCoordinatea UV-koordinaateille ja inputImageTexture lähdenäytteenottimena. + Toiminnot + Valinnaiset apufunktiot, vakiot ja rakenteet lisätään ennen void main(a). Univormut luodaan parametreista. + Lisää univormut tähän, kun varjostin tarvitsee muokattavia arvoja. + Palauta varjostin + Nykyinen varjostimen luonnos tyhjennetään + Virheellinen varjostin + Shader-versiota %1$d ei tueta. Tuettu versio: %2$d. + Varjostimen nimi ei saa olla tyhjä. + Varjostimen lähde ei saa olla tyhjä. + Shader-lähde sisältää merkkejä, joita ei tueta: %1$s. Käytä vain ASCII GLSL -lähdettä. + Parametri \\\"%1$s\\\" ilmoitetaan useammin kuin kerran. + Parametrien nimet eivät saa olla tyhjiä. + Parametri \\\"%1$s\\\" käyttää varattua GPUI-kuvan nimeä. + Parametrin \\\"%1$s\\\" on oltava kelvollinen GLSL-tunniste. + Parametrilla \\\"%1$s\\\" on %2$s-arvotyyppi \\\"%3$s\\\", odotusarvo \\\"%4$s\\\". + Parametri \\\"%1$s\\\" ei voi määrittää vähimmäis- tai enimmäisarvoa loogisille arvoille. + Parametrin \\\"%1$s\\\" min on suurempi kuin max. + Parametrin \\\"%1$s\\\" oletusarvo on pienempi kuin min. + Parametrin \\\"%1$s\\\" oletusarvo on suurempi kuin max. + Shaderin on ilmoitettava \\\"uniform sampler2D %1$s;\\\". + Shaderin täytyy ilmoittaa \\\"varying vec2 %1$s;\\\". + Shaderin on määritettävä \\\"void main()\\\". + Shaderin on kirjoitettava väri kohtaan gl_FragColor. + ShaderToy mainImage -varjostimia ei tueta. Käytä GPUImagen mitätöntä main() -sopimusta. + Animoitua ShaderToy-pukua \\\"iTime\\\" ei tueta. + Animoitua ShaderToy-pukua \\\"iFrame\\\" ei tueta. + ShaderToy univormua \\\"iResolution\\\" ei tueta. + ShaderToy iChannel -kuvioita ei tueta. + Ulkoisia pintakuvioita ei tueta. + Ulkoisia pintakuviolaajennuksia ei tueta. + Libretro Shader -parametreja ei tueta. + Vain yhtä syöttötekstuuria tuetaan. Poista \\\"uniform %1$s %2$s;\\\". + Parametri \\\"%1$s\\\" on ilmoitettava muodossa \\\"yhdenmukainen %2$s %1$s;\\\". + Parametri \\\"%1$s\\\" on ilmoitettu \\\"yhtenäinen %2$s %1$s;\\\", odotettu \\\"yhtenäinen %3$s %1$s;\\\". + Shader-tiedosto on tyhjä. + Shader-tiedoston on oltava .itshader JSON-objekti, jossa on versio-, nimi- ja shader-kentät. + Shader-tiedosto ei ole kelvollinen JSON tai se ei vastaa .itshader-muotoa. + Kenttä \\\"%1$s\\\" on pakollinen. + %1$s vaaditaan. + %1$s \\\"%2$s\\\" ei ole tuettu. Tuetut tyypit: %3$s. + %1$s vaaditaan \\\"%2$s\\\" parametreille. + %1$s on oltava äärellinen luku. + %1$s on oltava kokonaisluku. + %1$s on oltava tosi tai epätosi. + %1$s on oltava värimerkkijono, RGB/RGBA-taulukko tai väriobjekti. + %1$s on käytettävä muotoa #RRGGBAB tai #RRGGBBAA. + %1$s tulee sisältää kelvollisia heksadesimaalivärikanavia. + %1$s sisältää 3 tai 4 värikanavaa. + %1$s on oltava välillä 0–255. + %1$s on oltava kahden numeron taulukko tai objekti, jossa on x ja y. + %1$s sisältää täsmälleen 2 numeroa. + Kopioi + Tyhjennä aina EXIF + Poista kuvan EXIF-tiedot tallennuksen yhteydessä, vaikka työkalu pyytää säilyttämään metatiedot + Ohjeita ja vinkkejä + %1$d opetusohjelmia + Avaa työkalu + Opi tärkeimmät työkalut, vientiasetukset, PDF-virrat, väriapuohjelmat ja korjaukset yleisiin ongelmiin + Aloittaminen + Valitse työkaluja, tuo tiedostoja, tallenna tuloksia ja käytä asetuksia nopeammin + Kuvankäsittely + Muuta kuvien kokoa, rajaa, suodata, poista taustat, piirrä ja vesileima + Tiedostot ja metatiedot + Muunna muotoja, vertaa tulosteita, suojaa yksityisyyttä ja hallitse tiedostonimiä + PDF ja asiakirjat + Luo PDF-tiedostoja, skannaa asiakirjoja, OCR-sivuja ja valmistele tiedostoja jakamista varten + Teksti, QR ja data + Tunnista teksti, skannaa koodit, koodaa Base64, tarkista tiivisteet ja pakettitiedostot + Värityökalut + Valitse värejä, luo paletteja, tutki värikirjastoja ja luo liukuvärejä + Luovat työkalut + Luo SVG-, ASCII-taidetta, kohinakuvioita, varjostimia ja kokeellisia visuaaleja + Vianetsintä + Korjaa painavia tiedostoja, läpinäkyvyyttä, jakamista, tuontia ja raportointia koskevia ongelmia + Valitse oikea työkalu + Käytä luokkia, hakua, suosikkeja ja jaa toimintoja aloittaaksesi oikeasta paikasta + Aloita parhaasta sisääntulopisteestä + Image Toolboxissa on monia kohdennettuja työkaluja, ja monet niistä ovat päällekkäisiä ensi silmäyksellä. Aloita tehtävästä, jonka haluat suorittaa loppuun, ja valitse sitten työkalu, joka hallitsee tuloksen tärkeintä osaa: kokoa, muotoa, metatietoja, tekstiä, PDF-sivuja, värejä tai asettelua. + Käytä hakua, kun tiedät toiminnon nimen, kuten kokoa, PDF, EXIF, OCR, QR tai väri. + Avaa luokka, kun tiedät vain tehtävätyypin, ja kiinnitä sitten usein työkalut suosikeiksi. + Kun toinen sovellus jakaa kuvan Image Toolboxiin, valitse työkalu jakotaulukon kulusta. + Tuo, tallenna ja jaa tuloksia + Ymmärrä tavallinen kulku syötteen poimimisesta muokatun tiedoston vientiin + Noudata yleistä muokkausprosessia + Useimmat työkalut noudattavat samaa rytmiä: valitse syöttö, säädä asetuksia, esikatsele ja tallenna tai jaa. Tärkeä yksityiskohta on, että tallentaminen luo tulostiedoston, kun taas jakaminen on yleensä parasta nopeaan kanavanvaihtoon toiseen sovellukseen. + Valitse yksi tiedosto yksikuvatyökaluille tai useita tiedostoja erätyökaluille, kun poimija sallii sen. + Tarkista esikatselu- ja tulostussäätimet ennen tallennusta, erityisesti muoto-, laatu- ja tiedostonimiasetukset. + Käytä jakoa nopeaan kanavanvaihtoon tai tallenna, kun tarvitset pysyvän kopion valittuun kansioon. + Työskentele useiden kuvien kanssa kerralla + Erätyökalut sopivat parhaiten toistuviin koonmuutos-, muunnos-, pakkaus- ja nimeämistehtäviin + Valmista ennakoitava erä + Eräkäsittely on nopein, kun jokaisen tulosteen tulee noudattaa samoja sääntöjä. Se on myös helpoin paikka tehdä suuria virheitä, joten valitse nimeäminen, laatu, metatiedot ja kansion toiminta ennen pitkän viennin aloittamista. + Valitse kaikki liittyvät kuvat yhdessä ja säilytä järjestys, jos tulos riippuu järjestyksestä. + Aseta koko-, muoto-, laatu-, metatieto- ja tiedostonimisäännöt kerran ennen viennin aloittamista. + Suorita ensin pieni testierä, kun lähdetiedostot ovat suuria tai kun kohdemuoto on sinulle uusi. + Nopea yksittäinen muokkaus + Käytä all-in-one-editoria, kun tarvitset useita yksinkertaisia ​​muutoksia yhteen kuvaan + Viimeistele yksi kuva ilman työkaluhyppelyä + Yksittäinen muokkaus on hyödyllinen, kun kuva tarvitsee pienen muutosketjun yhden erikoistoimenpiteen sijaan. Se on yleensä nopeampi nopeaan siivoukseen, esikatseluun ja yhden lopullisen kopion viemiseen ilman erätyönkulkua. + Avaa Yksi muokkaus yhdelle kuvalle, joka vaatii yleisiä säätöjä, merkintöjä, rajausta, kiertoa tai vientimuutoksia. + Käytä muokkauksia siinä järjestyksessä, jossa vähiten pikseleitä muutetaan ensin, kuten rajaa ennen suodattimia ja suodattimet ennen lopullista pakkausta. + Käytä sen sijaan erikoistyökalua, kun tarvitset eräkäsittelyä, tarkkoja tiedostokokokohteita, PDF-tulostusta, tekstintunnistusta tai metatietojen puhdistusta. + Käytä esiasetuksia ja asetuksia + Tallenna toistuvat valinnat ja säädä sovellus haluamaasi työnkulkuun + Vältä toistamasta samaa asetusta + Esiasetukset ja asetukset ovat hyödyllisiä, kun viet samantyyppistä tiedostoa usein. Hyvä esiasetus muuttaa toistuvan manuaalisen tarkistuslistan yhdeksi napautukseksi, kun taas yleiset asetukset määrittävät oletusasetukset, joista uudet työkalut alkavat. + Luo esiasetuksia yleisille tulosteen kokoille, muodoille, laatuarvoille tai nimeämismalleille. + Tarkista oletusmuodon, laadun, tallennuskansion, tiedostonimen, valitsimen ja käyttöliittymän toiminnan asetukset. + Varmuuskopioi asetukset ennen sovelluksen uudelleenasentamista tai toiseen laitteeseen siirtämistä. + Aseta hyödylliset oletusasetukset + Valitse oletusmuoto, laatu, skaalaustila, kokotyyppi ja väriavaruus kerran + Aloita uudet työkalut lähempänä tavoitettasi + Oletusarvot eivät ole vain kosmeettisia mieltymyksiä. He päättävät, mikä muoto, laatu, koonmuutoskäyttäytyminen, skaalaustila ja väriavaruus näkyvät ensimmäisenä monissa työkaluissa, mikä auttaa välttämään samojen valintojen uudelleenvalintaa jokaisessa viennissä. + Aseta oletuskuvamuoto ja -laatu vastaamaan tavallista kohdettasi, kuten JPEG valokuville tai PNG/WebP alfalle. + Säädä oletuskoon muutostyyppiä ja skaalaustilaa, jos viet usein tiukkoja mittoja, sosiaalisia alustoja tai asiakirjasivuja varten. + Muuta väriavaruuden oletusasetuksia vain, kun työnkulkusi tarvitsee johdonmukaista värien käsittelyä näyttöjen, tulosteiden tai ulkoisten editorien välillä. + Poimija ja kantoraketti + Käytä valitsinasetuksia, kun sovellus avaa liian monta valintaikkunaa tai käynnistyy väärästä paikasta + Tee tiedostojen avaamisesta tapasi mukaista + Valitsimen ja käynnistysohjelman asetukset määräävät, alkaako Image Toolbox päänäytöstä, työkalusta vai tiedoston valintaprosessista. Nämä vaihtoehdot ovat erityisen hyödyllisiä, kun syötät tavallisesti Android-jakosivulta tai kun haluat sovelluksen tuntuvan enemmän työkalun käynnistysohjelmalta. + Käytä valokuvavalitsin asetuksia, jos järjestelmävalitsin piilottaa tiedostoja, näyttää liian monta viimeaikaista kohdetta tai käsittelee pilvitiedostoja huonosti. + Ota ohitustoiminto käyttöön vain työkaluissa, joihin syötät tavallisesti jaettuna tai jo tiedät lähdetiedoston. + Tarkista käynnistystila, jos haluat Image Toolboxin toimivan enemmän työkaluvalitsimena kuin tavallisen aloitusnäytön tavoin. + Kuvan lähde + Valitse poimintatila, joka toimii parhaiten gallerioiden, tiedostonhallintaohjelmien ja pilvitiedostojen kanssa + Valitse tiedostot oikeasta lähteestä + Kuvalähdeasetukset vaikuttavat siihen, miten sovellus pyytää Androidilta kuvia. Paras valinta riippuu siitä, ovatko tiedostosi järjestelmägalleriassa, tiedostonhallintakansiossa, pilvipalveluntarjoajassa vai muussa väliaikaista sisältöä jakavassa sovelluksessa. + Käytä oletusvalitsinta, kun haluat eniten järjestelmään integroidun kokemuksen tavallisille galleriakuville. + Vaihda valitsintilaa, jos albumit, kansiot, pilvitiedostot tai ei-standardimuodot eivät näy siellä, missä odotat. + Jos jaettu tiedosto katoaa poistuttuaan lähdesovelluksesta, avaa se uudelleen jatkuvalla poimijalla tai tallenna ensin paikallinen kopio. + Suosikit ja jaa työkaluja + Pidä päänäyttö keskittyneenä ja piilota työkalut, joilla ei ole järkeä jakovirroissa + Vähennä työkaluluettelon melua + Suosikit ja jaettavaksi piilotetut asetukset ovat hyödyllisiä, kun käytät vain muutamia työkaluja päivittäin. Ne myös pitävät jakamisen käytännöllisenä piilottamalla työkalut, jotka eivät voi käyttää toisen sovelluksen lähettämää tiedostotyyppiä. + Kiinnitä työkalut usein, jotta ne pysyvät helposti saavutettavissa, vaikka työkalut on ryhmitelty luokkiin. + Piilota työkalut jakovirroilta, kun ne eivät ole merkityksellisiä toisesta sovelluksesta tuleville tiedostoille. + Käytä suosikkitilausasetuksia, jos haluat suosikkien lopussa tai ryhmiteltynä niihin liittyvien työkalujen kanssa. + Varmuuskopioi asetukset + Siirrä esiasetukset, asetukset ja sovelluksen toiminta toiseen asennukseen turvallisesti + Pidä kokoonpanosi kannettavana + Varmuuskopiointi ja palautus on hyödyllisin, kun olet virittänyt esiasetukset, kansiot, tiedostonimisäännöt, työkalujen järjestyksen ja visuaaliset asetukset. Varmuuskopion avulla voit kokeilla asetuksia tai asentaa sovelluksen uudelleen rakentamatta työnkulkua uudelleen muistista. + Luo varmuuskopio esiasetusten, tiedostonimen käyttäytymisen, oletusmuotojen tai työkalujen järjestelyn muuttamisen jälkeen. + Tallenna varmuuskopio jonnekin sovelluskansion ulkopuolelle, jos aiot tyhjentää tiedot, asentaa uudelleen tai siirtää laitteita. + Palauta asetukset ennen tärkeiden erien käsittelyä, jotta oletus- ja tallennuskäyttäytyminen vastaavat vanhoja asetuksiasi. + Muuta kuvien kokoa ja muunna niitä + Muuta yhden tai useamman kuvan kokoa, muotoa, laatua ja metatietoja + Luo muutettuja kopioita + Resize and Convert on tärkein työkalu ennustettavien mittojen ja kevyempien tulostetiedostojen luomiseen. Käytä sitä, kun kuvan koko, tulostusmuoto ja metatietojen käyttäytyminen ovat tärkeitä samanaikaisesti. + Valitse yksi tai useampi kuva ja valitse sitten, ovatko mitat, mittakaava vai tiedostokoko tärkein. + Valitse tulostusmuoto ja laatu; käytä PNG:tä tai WebP:tä, kun läpinäkyvyys on säilytettävä. + Esikatsele tulosta ja tallenna tai jaa sitten luodut kopiot vaihtamatta alkuperäisiä. + Muuta kokoa tiedoston koon mukaan + Kohdista kokorajoitus, kun verkkosivusto, messenger tai lomake hylkää raskaat kuvat + Noudata tiukkoja latausrajoituksia + Koon muuttaminen tiedostokoon mukaan on parempi kuin laatuarvojen arvailu, kun kohde hyväksyy vain tietyn rajan alapuolella olevat tiedostot. Työkalu voi säätää pakkausta ja mittoja saavuttaakseen tavoitteen samalla, kun se yrittää pitää tuloksen käyttökelpoisena. + Anna kohdekoko hieman todellista lähetysrajaa pienemmäksi jättääksesi tilaa metatietojen ja palveluntarjoajan muutoksille. + Suosi häviöllisiä valokuvien muotoja, kun kohde on pieni; PNG voi pysyä suurena jopa koon muuttamisen jälkeen. + Tarkista kasvot, teksti ja reunat viennin jälkeen, koska aggressiivisen kokoiset kohteet voivat aiheuttaa näkyviä esineitä. + Rajoita kokoa + Pienennä vain kuvia, jotka ylittävät enimmäisleveyden, -korkeuden tai tiedostorajoitukset + Vältä kuvien koon muuttamista, jotka ovat valmiiksi kunnossa + Limit Resize on hyödyllinen sekakansioille, joissa jotkut kuvat ovat valtavia ja toiset ovat jo valmiita. Sen sijaan, että jokainen tiedosto pakottaisi saman koon muutoksen, se pitää hyväksyttävät kuvat lähempänä niiden alkuperäistä laatua. + Aseta enimmäismitat tai -rajat määränpään perusteella sen sijaan, että muuttaisit jokaisen tiedoston kokoa sokeasti. + Ota käyttöön \"Ohita jos-suurempi\" -tyyli, kun haluat välttää lähteitä raskaampia tulosteita. + Käytä tätä eri kameroiden erissä, kuvakaappauksissa tai latauksissa, joissa lähteiden koot vaihtelevat paljon. + Rajaa, pyöritä ja suorista + Rajaa tärkeä alue ennen kuvan viemistä tai käyttämistä muissa työkaluissa + Puhdista koostumus + Rajaamalla ensin suodattimet, tekstintunnistus, PDF-sivut ja tulosten jakaminen ovat puhtaampia. + Avaa Rajaa ja valitse kuva, jota haluat säätää. + Käytä vapaata rajausta tai kuvasuhdetta, kun tulosteen on sovittava viestiin, asiakirjaan tai avatariin. + Kierrä tai käännä ennen tallentamista, jotta myöhemmät työkalut saavat korjatun kuvan. + Käytä suodattimia ja säätöjä + Luo muokattava suodatinpino ja esikatsele tulosta ennen vientiä + Säädä kuvan ulkonäköä + Suodattimet ovat hyödyllisiä nopeissa korjauksissa, tyylitellyissä tulosteissa ja kuvien valmistelussa tekstintunnistusta tai tulostusta varten. Pienet kontrastin, terävyyden ja kirkkauden muutokset voivat olla tärkeämpiä kuin voimakkaat tehosteet, kun kuva sisältää tekstiä tai hienoja yksityiskohtia. + Lisää suodattimet yksitellen ja pidä esikatselua silmällä jokaisen muutoksen jälkeen. + Säädä kirkkautta, kontrastia, terävyyttä, epäterävyyttä, väriä tai maskeja tehtävän mukaan. + Vie vain, kun esikatselu vastaa kohdelaitettasi, asiakirjaasi tai sosiaalista alustaasi. + Esikatsele ensin + Tarkista kuvat, ennen kuin valitset raskaamman muokkaus-, muunnos- tai puhdistustyökalun + Tarkista, mitä olet todella saanut + Kuvan esikatselu on hyödyllinen, kun tiedosto tulee chatista, pilvitallennustilasta tai muusta sovelluksesta etkä ole varma, mitä se sisältää. Mittojen, läpinäkyvyyden, suunnan ja visuaalisen laadun tarkistaminen auttaa ensin valitsemaan oikean seurantatyökalun. + Avaa kuvan esikatselu, kun sinun on tarkastettava useita kuvia ennen kuin päätät, mitä niille tehdä. + Etsi kiertoongelmia, läpinäkyviä alueita, pakkausartefakteja ja odottamattoman suuria mittoja. + Siirry kohtaan Resize, Crop, Compare, EXIF ​​tai Background Remover vasta, kun tiedät, mikä vaatii korjausta. + Poista tai palauta tausta + Poista taustat automaattisesti tai tarkenna reunoja manuaalisesti palautustyökaluilla + Pidä aihe, poista loput + Taustan poisto toimii parhaiten, kun yhdistät automaattisen valinnan huolelliseen manuaaliseen siivoukseen. Lopullinen vientimuoto on yhtä tärkeä kuin maski, koska JPEG tasoittaa läpinäkyviä alueita, vaikka esikatselu näyttää oikealta. + Aloita automaattisella poistamisella, jos kohde erottuu selvästi taustasta. + Lähennä ja vaihda poistamisen ja palautuksen välillä korjataksesi hiukset, kulmat, varjot ja pienet yksityiskohdat. + Tallenna PNG- tai WebP-muodossa, kun tarvitset läpinäkyvyyttä lopullisessa tiedostossa. + Piirrä, merkitse ja vesileima + Lisää nuolia, muistiinpanoja, korostuksia, allekirjoituksia tai toistuvia vesileimapeittokuvia + Lisää näkyvää tietoa + Merkintätyökalut auttavat selittämään kuvan, kun taas vesileima auttaa brändäämään tai suojaamaan vietyjä tiedostoja. + Käytä Draw-ohjelmaa, kun tarvitset vapaalla kädellä tehtyjä muistiinpanoja, muotoja, korostuksia tai nopeita huomautuksia. + Käytä vesileimaa, kun saman tekstin tai kuvamerkin pitäisi näkyä johdonmukaisesti tulosteissa. + Tarkista peittävyys, sijainti ja asteikko ennen vientiä, jotta merkki on näkyvissä, mutta ei häiritse. + Piirrä oletusarvot + Aseta viivan leveys, väri, polkutila, suunnan lukitus ja suurennuslasi nopeampaa merkintää varten + Tee piirtämisestä ennustettavan tuntua + Piirustusasetukset ovat hyödyllisiä, kun kirjoitat kuvakaappauksia, merkitset asiakirjoja tai allekirjoitat kuvia usein. Oletusasetukset lyhentävät asetusaikaa, kun taas suurennuslasi ja suunnan lukitus helpottavat tarkat muokkaukset pienillä näytöillä. + Aseta oletusviivan leveys ja piirrä värit arvoihin, joita käytät eniten nuolille, korostuksille tai allekirjoituksille. + Käytä oletuspolkutilaa, kun yleensä piirrät samanlaisia ​​viivoja, muotoja tai merkintöjä. + Ota suurennus tai suunnan lukitus käyttöön, kun sormisyöttö vaikeuttaa pienten yksityiskohtien sijoittamista tarkasti. + Usean kuvan asettelut + Valitse oikea monikuvatyökalu, ennen kuin järjestät kuvakaappauksia, skannauksia tai vertailuliuskoja + Käytä oikeaa asettelutyökalua + Nämä työkalut kuulostavat samanlaisilta, mutta jokainen niistä on viritetty erilaista monikuvatulostusta varten. Kun valitset ensin oikean, vältyt taistelemasta asettelusäätimistä, jotka on suunniteltu eri tulokseen. + Käytä Collage Makeria suunniteltuihin asetteluihin, joissa on useita kuvia yhdessä koostumuksessa. + Käytä kuvien yhdistämistä tai pinoamista, kun kuvista tulee yksi pitkä pysty- tai vaakasuora kaistale. + Käytä kuvan jakamista, kun yhdestä suuresta kuvasta on tehtävä useita pienempiä paloja. + Muunna kuvaformaatteja + Luo JPEG-, PNG-, WebP-, AVIF-, JXL- ja muita kopioita muokkaamatta pikseleitä manuaalisesti + Valitse työn muoto + Eri muodot ovat parempia valokuville, kuvakaappauksille, läpinäkyvyydelle, pakkaamiselle tai yhteensopivuudelle. Muuntaminen on turvallisinta, kun ymmärrät, mitä kohdesovellus tukee ja sisältääkö lähde alfa-, animaatio- tai metatietoja, jotka haluat säilyttää. + Käytä JPEG-tiedostoa yhteensopiviin kuviin, PNG-tiedosto häviöttömään kuviin ja alfa-tiedostoon ja WebP-tiedosto kompaktiin jakamiseen. + Säädä laatua, kun muoto tukee sitä; pienemmät arvot pienentävät kokoa, mutta voivat lisätä artefakteja. + Säilytä alkuperäiset, kunnes varmistat, että muunnetut tiedostot ovat auki oikein kohdesovelluksessa. + Tarkista EXIF ​​ja yksityisyys + Tarkastele, muokkaa tai poista metatietoja ennen arkaluonteisten valokuvien jakamista + Hallitse piilotettuja kuvatietoja + EXIF voi sisältää kameratietoja, päivämääriä, sijaintia, suuntaa ja muita yksityiskohtia, jotka eivät näy kuvassa. Se kannattaa tarkistaa ennen julkista jakamista, tukiraportteja, markkinapaikkaluetteloita tai kaikkia valokuvia, jotka saattavat paljastaa yksityisen paikan. + Avaa EXIF-työkalut ennen kuin jaat valokuvia, jotka voivat sisältää sijainti- tai yksityisen laitteen tietoja. + Poista arkaluontoiset kentät tai muokkaa vääriä tunnisteita, kuten päivämäärää, suuntaa, tekijää tai kameratietoja. + Tallenna uusi kopio ja jaa se alkuperäisen sijaan, kun yksityisyydellä on merkitystä. + Automaattinen EXIF-siivous + Poista metatiedot oletuksena, kun päätät, säilytetäänkö päivämäärät + Tee yksityisyydestä oletusarvo + EXIF-asetusryhmä on hyödyllinen, kun haluat yksityisyyden puhdistamisen tapahtuvan automaattisesti sen sijaan, että muistaisit sen jokaisen viennin yhteydessä. Säilytä päivämäärä Aika on erillinen päätös, koska päivämäärät voivat olla hyödyllisiä lajittelussa, mutta herkkiä jakamiseen. + Ota aina selkeä EXIF ​​käyttöön, kun suurin osa viennistäsi on tarkoitettu julkiseen tai puolijulkiseen jakamiseen. + Ota päivämäärän säilytysaika käyttöön vain, kun sieppausajan säilyttäminen on tärkeämpää kuin jokaisen aikaleiman poistaminen. + Käytä manuaalista EXIF-muokkausta kertaluonteisille tiedostoille, joissa sinun on säilytettävä jotkin kentät ja poistettava toiset. + Hallitse tiedostonimiä + Käytä etuliitteitä, jälkiliitteitä, aikaleimoja, esiasetuksia, tarkistussummia ja järjestysnumeroita + Tee viennistä helppo löytää + Hyvä tiedostonimimalli auttaa lajittelemaan eriä ja estää tahattomat päällekirjoitukset. Tiedostonimiasetukset ovat erityisen hyödyllisiä, kun vienti palautetaan lähdekansioon, jossa samankaltaiset nimet voivat hämmentää nopeasti. + Aseta tiedostonimen etuliite, pääte, aikaleima, alkuperäinen nimi, esiasetetut tiedot tai järjestysasetukset asetuksissa. + Käytä järjestysnumeroita erissä, joissa järjestyksellä on merkitystä, kuten sivuille, kehyksille tai kollaaseille. + Ota päällekirjoitus käyttöön vain, kun olet varma, että olemassa olevien tiedostojen korvaaminen on turvallista nykyiselle kansiolle. + Korvaa tiedostot + Korvaa tulosteet vastaavilla nimillä vain, kun työnkulkusi on tarkoituksella tuhoisa + Käytä päällekirjoitustilaa huolellisesti + Overwrite Files muuttaa tapaa, jolla nimiristiriidat käsitellään. Se on hyödyllinen toistuvassa viennissä samaan kansioon, mutta se voi myös korvata aikaisemmat tulokset, jos tiedostonimimalli tuottaa saman nimen uudelleen. + Ota päällekirjoitus käyttöön vain kansioissa, joissa on odotettavissa ja palautettavissa vanhempia luotuja tiedostoja. + Pidä päällekirjoitus pois käytöstä, kun viet alkuperäisiä asiakirjoja, asiakastiedostoja, kertatarkastuksia tai mitä tahansa ilman varmuuskopiota. + Yhdistä päällekirjoitus tahalliseen tiedostonimimalliin, jotta vain aiotut tulosteet korvataan. + Tiedostonimimallit + Yhdistä alkuperäiset nimet, etuliitteet, jälkiliitteet, aikaleimat, esiasetukset, skaalaustila ja tarkistussummat + Rakenna nimiä, jotka selittävät tulosteen + Tiedostonimimalliasetukset voivat muuttaa viedyt tiedostot hyödylliseksi historiaksi siitä, mitä niille on tapahtunut. Tällä on merkitystä erissä, koska kansionäkymä saattaa olla ainoa paikka, jossa voit erottaa alkuperäisen koon, esiasetuksen, muodon ja käsittelyjärjestyksen. + Käytä alkuperäistä tiedostonimeä, etuliitettä, päätettä ja aikaleimaa, kun tarvitset luettavia nimiä manuaalista lajittelua varten. + Lisää esiasetus, skaalaustila, tiedostokoko tai tarkistussummatiedot, kun tulosteiden on dokumentoitava, miten ne on tuotettu. + Vältä satunnaisia ​​nimiä työnkuluissa, joissa tiedostojen on pysyttävä pariksi alkuperäisten tai sivujärjestyksen kanssa. + Valitse, mihin tiedostot tallennetaan + Vältä viennin häviäminen asettamalla kansioita, tallentamalla alkuperäiseen kansioon ja tallentamalla kertaluonteisia paikkoja + Pidä tuotokset ennustettavissa + Tallennuskäyttäytyminen on tärkeintä, kun käsittelet useita tiedostoja tai jaat tiedostoja pilvipalveluntarjoajilta. Kansioasetukset määräävät, kerätäänkö tulosteet yhteen tunnettuun paikkaan, näkyvätkö ne alkuperäisten viereen vai siirretäänkö ne tilapäisesti muualle tiettyä tehtävää varten. + Aseta oletusarvoinen tallennuskansio, jos haluat aina viedä ne yhteen paikkaan. + Käytä tallennusta alkuperäiseen kansioon siivoamiseen työnkuluissa, joissa tulosteen tulee pysyä lähellä lähdetiedostoa. + Käytä kertaluontoista tallennuspaikkaa, kun yksittäinen tehtävä tarvitsee toisen määränpään muuttamatta oletusarvoa. + Kertatallennuskansiot + Lähetä seuraavat vientitiedostot väliaikaiseen kansioon muuttamatta normaalia kohdetta + Pidä erikoistyöt erillään + Kertatallennuspaikka on hyödyllinen tilapäisprojekteissa, asiakaskansioissa, puhdistusistunnoissa tai viennissä, joiden ei pitäisi sekoittua tavalliseen Image Toolbox -kansioosi. Se tarjoaa lyhytaikaisen määränpään ilman oletusasetusten uudelleenkirjoittamista. + Valitse kertaluonteinen kansio ennen kuin aloitat tehtävän, joka kuuluu normaalin vientisijaintisi ulkopuolelle. + Käytä sitä lyhyissä projekteissa, jaetuissa kansioissa tai erissä, joissa tulosteiden on pysyttävä ryhmiteltyinä. + Palaa oletuskansioon työn jälkeen, jotta myöhemmät viennit eivät yllättäen pääty väliaikaiseen sijaintiin. + Tasapainottaa laatu ja tiedostokoko + Ymmärrä, milloin kokoa, muotoa tai laatua on muutettava arvaamisen sijaan + Kutista tiedostoja tarkoituksella + Tiedoston koko riippuu kuvan koosta, muodosta, laadusta, läpinäkyvyydestä ja sisällön monimutkaisuudesta. Jos tiedosto on edelleen liian painava, mittojen muuttaminen auttaa yleensä enemmän kuin toistuva laadun heikentäminen. + Muuta mitat ensin, kun kuva on suurempi kuin kohde pystyy näyttämään. + Huonompi laatu vain häviöllisissä muodoissa ja tarkista teksti, reunat, liukuvärit ja pinnat viennin jälkeen. + Vaihda muotoa, kun laatumuutokset eivät riitä, esimerkiksi WebP jakamiseen tai PNG selkeään läpinäkyvään sisältöön. + Työskentele animoitujen muotojen kanssa + Käytä GIF-, APNG-, WebP- ja JXL-työkaluja, kun tiedostossa on kehyksiä yhden bittikartan sijaan + Älä käsittele jokaista kuvaa liikkumattomana + Animoidut muodot tarvitsevat työkaluja, jotka ymmärtävät kehykset, keston ja yhteensopivuuden. + Käytä GIF- tai APNG-työkaluja, kun tarvitset kehystason toimintoja näille muodoille. + Käytä WebP-työkaluja, kun tarvitset nykyaikaista pakkausta tai animoitua WebP-tulostusta. + Esikatsele animaation ajoitusta ennen jakamista, koska jotkin alustat muuntavat tai litistävät animoituja kuvia. + Vertaa ja esikatsele tulosta + Tarkista muutokset, tiedostokoko ja visuaaliset erot ennen viennin säilyttämistä + Tarkista ennen jakamista + Vertailutyökalut auttavat havaitsemaan pakkausvirheitä, vääriä värejä tai ei-toivottuja muokkauksia ajoissa. + Avaa Vertaa, kun haluat tarkastella kahta versiota vierekkäin. + Zoomaa reunoihin, tekstiin, liukuväreihin ja läpinäkyviin alueisiin, joilla ongelmat on helpoin ohittaa. + Jos tuloste on liian painava tai epäselvä, palaa edelliseen työkaluun ja säädä laatua tai muotoa. + Käytä PDF-työkalut -keskusta + Yhdistä, jaa, kierrä, poista sivuja, pakkaa, suojaa, tekstintunnistus ja vesileima PDF-tiedostoja + Valitse PDF-toiminto + PDF-keskus ryhmittelee asiakirjatoiminnot yhden aloituskohdan taakse, jotta voit valita tarkan toiminnon. Aloita siitä, kun tiedosto on jo PDF-tiedosto, ja käytä Kuvat PDF:ksi tai Document Scanneria, kun lähteesi on vielä joukko kuvia. + Avaa PDF-työkalut ja valitse tavoitettasi vastaava toiminto. + Valitse lähde-PDF tai -kuvat ja tarkista sitten sivujärjestys, kierto, suojaus tai tekstintunnistusasetukset. + Tallenna luotu PDF ja avaa se kerran vahvistaaksesi sivumäärän, järjestyksen ja luettavuuden. + Muuta kuvat PDF-tiedostoiksi + Yhdistä skannaukset, kuvakaappaukset, kuitit tai valokuvat yhdeksi jaettavaksi asiakirjaksi + Luo puhdas asiakirja + Kuvista tulee parempia PDF-sivuja, kun niitä rajataan, järjestetään ja kokoaa johdonmukaisesti. Käsittele kuvaluetteloa kuin sivupinoa: jokainen kierto, marginaali ja sivun kokovalinta vaikuttavat siihen, kuinka luettavalta lopullinen asiakirja tuntuu. + Rajaa tai kierrä kuvia ensin, jos sivun reunat, varjot tai suunta ovat väärät. + Valitse kuvat aiotussa sivujärjestyksessä ja säädä sivun kokoa tai marginaaleja, jos työkalu tarjoaa niitä. + Vie PDF ja tarkista, että kaikki sivut ovat luettavissa ennen sen lähettämistä. + Skannaa asiakirjoja + Kaappaa paperisivuja ja valmistele ne PDF-tiedostoa, tekstintunnistusta tai jakamista varten + Kaappaa luettavia sivuja + Asiakirjojen skannaus toimii parhaiten litteillä sivuilla, hyvällä valaistuksella ja korjatulla perspektiivillä. Puhdas skannaus ennen OCR- tai PDF-vientiä säästää aikaa, koska tekstin tunnistus ja pakkaus riippuvat sivun laadusta. + Aseta asiakirja kontrastiselle pinnalle, jossa on riittävästi valoa ja mahdollisimman vähän varjoja. + Säädä havaittuja kulmia tai rajaa käsin, jotta sivu täyttää kehyksen puhtaasti. + Vie kuvina tai PDF-muodossa ja suorita sitten OCR, jos tarvitset valittavaa tekstiä. + Suojaa tai avaa PDF-tiedostoja + Käytä salasanoja huolellisesti ja säilytä muokattava lähdekopio, jos mahdollista + Käsittele PDF-käyttöä turvallisesti + Suojaustyökalut ovat hyödyllisiä hallittuun jakamiseen, mutta salasanat on helppo kadottaa ja niitä on vaikea palauttaa. Suojaa kopiot jakelua varten, säilytä suojaamattomat lähdetiedostot erikseen ja tarkista tulos ennen kuin poistat mitään. + Suojaa PDF-kopio, ei ainoa lähdedokumenttisi. + Tallenna salasana turvalliseen paikkaan ennen suojatun tiedoston jakamista. + Käytä lukituksen avausta vain tiedostoille, joita sinulla on oikeus muokata, ja varmista sitten, että lukitsematon kopio avautuu oikein. + Järjestä PDF-sivut + Yhdistä, jaa, järjestä uudelleen, kierrä, rajaa, poista sivuja ja lisää sivunumeroita tarkoituksella + Korjaa asiakirjan rakenne ennen koristelua + PDF-sivutyökaluja on helpoin käyttää samassa järjestyksessä kuin valmistaisit paperidokumentin: kerää sivut, poista väärät, laita ne järjestykseen, korjaa suunta ja lisää sitten lopulliset tiedot, kuten sivunumerot tai vesileimat. + Käytä yhdistämistä tai kuvista PDF-muotoon ensin, kun asiakirja on edelleen jaettu useisiin tiedostoihin. + Järjestä, kierrä, rajaa tai poista sivuja ennen sivunumeroiden, allekirjoitusten tai vesileimojen lisäämistä. + Esikatsele lopullista PDF-tiedostoa rakennemuokkausten jälkeen, koska yhtä puuttuvaa tai kierrettyä sivua voi olla vaikea huomata myöhemmin. + PDF-merkinnät + Poista merkinnät tai tasoita ne, kun katsojat näyttävät kommentteja ja merkkejä eri tavalla + Hallitse, mikä jää näkyviin + Huomautukset voivat olla kommentteja, korostuksia, piirroksia, lomakemerkkejä tai muita ylimääräisiä PDF-objekteja. Jotkut katsojat näyttävät ne eri tavalla, joten niiden poistaminen tai litistäminen voi tehdä jaetuista asiakirjoista ennakoitavampia. + Poista merkinnät, kun kommentteja, korostuksia tai arvostelumerkkejä ei pitäisi sisällyttää jaettuun kopioon. + Tasoita, kun näkyvien merkintöjen pitäisi pysyä sivulla, mutta ne eivät enää toimi muokattavien merkintöjen tavoin. + Säilytä alkuperäinen kopio, koska huomautusten poistaminen tai litistäminen voi olla vaikea kääntää. + Tunnista teksti + Poimi tekstiä kuvista valittavissa olevilla OCR-moottoreilla ja kielillä + Saat parempia OCR-tuloksia + Tekstintunnistuksen tarkkuus riippuu kuvan terävyydestä, kielitiedoista, kontrastista ja sivun suunnasta. Parhaat tulokset saadaan yleensä valmistelemalla kuva ensin: rajaa kohina pois, käännä pystysuoraan, lisää luettavuutta ja tunnista sitten teksti. + Rajaa kuva tekstialueelle ja käännä sitä pystyasentoon ennen tunnistamista. + Valitse oikea kieli ja lataa kielitiedot, jos sovellus pyytää sitä. + Kopioi tai jaa tunnistettu teksti ja tarkista sitten nimet, numerot ja välimerkit manuaalisesti. + Valitse OCR-kielitiedot + Paranna tunnistusta yhdistämällä skriptejä, kieliä ja ladattuja OCR-malleja + Yhdistä OCR tekstiin + Väärä tekstintunnistuskieli voi saada hyvät valokuvat näyttämään huonolta tunnistukselta. Kielitiedoilla on merkitystä kirjoitusten, välimerkkien, numeroiden ja sekamuotoisten asiakirjojen kohdalla, joten valitse kuvassa näkyvä kieli puhelimen käyttöliittymän kielen sijaan. + Valitse kuvassa näkyvä kieli tai kirjoitus, ei vain puhelimen kieli. + Lataa puuttuvat tiedot ennen kuin työskentelet offline-tilassa tai käsittelet erää. + Sekakielisiä asiakirjoja varten suorita tärkein kieli ensin ja tarkista nimet ja numerot manuaalisesti. + Haettavat PDF-tiedostot + Kirjoita OCR-teksti takaisin tiedostoihin, jotta skannatut sivut voidaan etsiä ja kopioida + Muuta skannaukset haettavissa oleviksi asiakirjoiksi + OCR voi tehdä muutakin kuin kopioida tekstiä leikepöydälle. Kun kirjoitat tunnistettua tekstiä tiedostoon tai haettavaan PDF-tiedostoon, sivun kuva pysyy näkyvissä, kun taas tekstiä on helpompi etsiä, valita, indeksoida tai arkistoida. + Käytä haettavissa olevaa PDF-tulostetta skannatuille asiakirjoille, joiden pitäisi silti näyttää alkuperäisiltä sivuilta. + Käytä tiedostoon kirjoitusta, kun tarvitset vain purettua tekstiä etkä välitä sivun kuvasta. + Tarkista tärkeät numerot, nimet ja taulukot manuaalisesti, koska OCR-teksti voi olla haettavissa, mutta silti epätäydellinen. + Skannaa QR-koodit + Lue QR-sisältöä kamerasyötteestä tai tallennetuista kuvista, kun niitä on saatavilla + Lue koodit turvallisesti + QR-koodit voivat sisältää linkkejä, Wi-Fi-tietoja, yhteystietoja, tekstiä tai muita hyötykuormia. + Avaa Scan QR Code ja pidä koodi terävänä, tasaisena ja kokonaan kehyksen sisällä. + Tarkista dekoodattu sisältö ennen linkkien avaamista tai arkaluonteisten tietojen kopioimista. + Jos skannaus epäonnistuu, paranna valaistusta, rajaa koodia tai kokeile korkeamman resoluution lähdekuvaa. + Käytä Base64:ää ja tarkistussummia + Koodaa kuvat tekstiksi, purkaa teksti takaisin kuviksi ja tarkista tiedostojen eheys + Siirry tiedostojen ja tekstin välillä + Base64 on hyödyllinen pienille kuville, kun taas tarkistussummat auttavat varmistamaan, että tiedostot eivät muuttuneet. Nämä työkalut ovat hyödyllisimpiä teknisissä työnkuluissa, tukiraporteissa, tiedostojen siirroissa ja paikoissa, joissa binääritiedostoista on muutettava väliaikaisesti tekstiä. + Käytä Base64-työkaluja pienen kuvan koodaamiseen, kun työnkulku tarvitsee tekstiä binaaritiedoston sijaan. + Pura Base64 vain luotettavista lähteistä ja tarkista esikatselu ennen tallentamista. + Käytä tarkistussummatyökaluja, kun sinun on verrattava vietyjä tiedostoja tai vahvistettava lähetys/lataus. + Pakkaa tai suojaa tiedot + Käytä ZIP- ja salaustyökaluja tiedostojen niputtamiseksi tai tekstitietojen muuntamiseen + Pidä liittyvät tiedostot yhdessä + Pakkaustyökalut ovat hyödyllisiä, kun useiden tulosteiden pitäisi kulkea yhtenä tiedostona. Ne sopivat myös luotujen kuvien, PDF-tiedostojen, lokien ja metatietojen pitämiseen yhdessä, kun sinun on lähetettävä täydellinen tulossarja. + Käytä ZIP-tiedostoa, kun haluat lähettää useita vietyjä kuvia tai asiakirjoja yhdessä. + Käytä salaustyökaluja vain, kun ymmärrät valitun menetelmän ja pystyt tallentamaan tarvittavat avaimet turvallisesti. + Testaa luotua arkistoa tai muunnettua tekstiä ennen lähdetiedostojen poistamista. + Tarkistussummat nimissä + Käytä tarkistussummapohjaisia ​​tiedostonimiä, kun ainutlaatuisuus ja eheys ovat tärkeämpiä kuin luettavuus + Nimeä tiedostot sisällön mukaan + Tarkistussummatiedostojen nimet ovat hyödyllisiä, kun viennillä voi olla päällekkäisiä näkyviä nimiä tai kun sinun on todistettava, että kaksi tiedostoa ovat identtisiä. Ne eivät sovellu manuaaliseen selaamiseen, joten käytä niitä teknisiin arkistoihin ja varmistustyönkulkuihin. + Ota tarkistussumma käyttöön tiedostonimenä, kun sisällön identiteetti on tärkeämpi kuin ihmisen luettava otsikko. + Käytä sitä arkistoihin, kopioiden poistamiseen, toistettaviin vientiin tai tiedostoihin, jotka voidaan ladata ja ladata uudelleen. + Yhdistä tarkistussumman nimet kansioihin tai metatietoihin, kun ihmisten on vielä ymmärrettävä, mitä tiedosto edustaa. + Valitse kuvista värit + Ota näytteitä pikseleistä, kopioi värikoodeja ja käytä värejä uudelleen paleteissa tai malleissa + Kokeile tarkkaa väriä + Värien poiminta on hyödyllistä mallin sovittamiseksi, merkkivärien poimimiseen tai kuvan arvojen tarkistamiseen. Zoomauksella on merkitystä, koska antialiasing, varjot ja pakkaus voivat tehdä viereisistä pikseleistä hieman erilaisia ​​kuin odotat. + Avaa Valitse väri kuvasta ja valitse haluamasi värinen lähdekuva. + Zoomaa ennen kuin otat näytteitä pienistä yksityiskohdista, jotta lähellä olevat pikselit eivät vaikuta tulokseen. + Kopioi väri kohteen vaatimassa muodossa, kuten HEX, RGB tai HSV. + Luo paletteja ja käytä värikirjastoa + Pura paletteja, selaa tallennettuja värejä ja pidä yhtenäiset visuaaliset järjestelmät + Rakenna uudelleen käytettävä paletti + Paletit auttavat pitämään viedyt grafiikat, vesileimat ja piirustukset visuaalisesti yhtenäisinä. Ne ovat erityisen hyödyllisiä, kun kirjoitat toistuvasti kuvakaappauksia, valmistelet brändisisältöä tai tarvitset samoja värejä useissa työkaluissa. + Luo paletti kuvasta tai lisää värejä manuaalisesti, kun tiedät jo arvot. + Nimeä tai järjestä tärkeät värit, jotta löydät ne myöhemmin uudelleen. + Käytä kopioituja arvoja Draw-, vesileima-, liukuväri-, SVG- tai ulkoisissa suunnittelutyökaluissa. + Luo kaltevuudet + Luo gradienttikuvia taustoja, paikkamerkkejä ja visuaalisia kokeiluja varten + Hallitse värien siirtymiä + Gradienttityökalut ovat hyödyllisiä, kun tarvitset luodun kuvan olemassa olevan kuvan muokkaamisen sijaan. Niistä voi tulla puhtaita taustoja, paikkamerkkejä, taustakuvia, naamioita tai yksinkertaisia ​​ulkoisia suunnittelutyökaluja. + Valitse liukuvärityyppi ja aseta ensin tärkeät värit. + Säädä suunta, pysähdykset, tasaisuus ja tulosteen koko käyttötarkoituksen mukaan. + Vie muotoa, joka vastaa kohdetta, yleensä PNG puhdasta luotua grafiikkaa varten. + Värien saatavuus + Käytä dynaamisia värejä, kontrastia ja värisokea vaihtoehtoja, kun käyttöliittymää on vaikea lukea + Helpota värien käyttöä + Väriasetukset vaikuttavat mukavuuteen, luettavuuteen ja säätimien skannaamiseen. Jos työkalupalat, liukusäätimet tai valitut tilat tuntuvat vaikealta erottaa toisistaan, teema- ja esteettömyysvaihtoehdot voivat olla hyödyllisempiä kuin pelkkä tumman tilan vaihtaminen. + Kokeile dynaamisia värejä, kun haluat sovelluksen noudattavan nykyistä taustakuvapalettia. + Lisää kontrastia tai muuta mallia, kun painikkeet ja pinnat eivät erotu tarpeeksi. + Käytä värisokeamallivaihtoehtoja, jos joitain tiloja tai aksentteja on vaikea erottaa. + Alfa tausta + Hallitse läpinäkyvien PNG-, WebP- ja vastaavien tulosteiden esikatselu- tai täyttöväriä + Ymmärrä läpinäkyvät esikatselut + Alfa-muotojen taustavärit voivat helpottaa läpinäkyvien kuvien tarkastelua, mutta on tärkeää tietää, oletko muuttamassa esikatselu-/täyttökäyttäytymistä vai tasoitatko lopputulosta. Tällä on merkitystä tarroille, logoille ja taustalla poistettuille kuville. + Käytä ensin alfa-tukevaa muotoa, kuten PNG tai WebP, kun läpinäkyvien pikselien on säilyttävä viennissä. + Säädä taustaväriasetuksia, kun läpinäkyviä reunoja on vaikea nähdä sovelluksen pintaa vasten. + Vie pieni testi ja avaa se uudelleen toisessa sovelluksessa varmistaaksesi, onko läpinäkyvyys tai valittu täyttö tallennettu. + AI-mallin valinta + Valitse malli kuvatyypin ja vian mukaan sen sijaan, että valitset ensin suurimman + Yhdistä malli vaurioon + AI Tools voi ladata tai tuoda erilaisia ​​ONNX/ORT-malleja, ja jokainen malli on koulutettu tietyntyyppiseen ongelmaan. Malli JPEG-lohkoille, animelinjan puhdistukselle, kohinan poistolle, taustan poistamiselle, väritykselle tai skaalauslle voi tuottaa huonompia tuloksia, kun sitä käytetään väärälle kuvatyypille. + Lue mallin kuvaus ennen lataamista; Valitse malli, joka nimeää todellisen ongelmasi, kuten pakkaus, kohina, rasteri, sumeus tai taustan poisto. + Aloita pienemmällä tai tarkemmalla mallilla, kun testaat uutta kuvatyyppiä, ja vertaa sitten raskaampiin malleihin vain, jos tulos ei ole tarpeeksi hyvä. + Säilytä vain todella käyttämäsi mallit, koska ladatut ja tuodut mallit voivat viedä huomattavan paljon tallennustilaa. + Ymmärrä malliperheet + Mallien nimet viittaavat usein kohteeseensa: RealESRGAN-tyyliset korkealuokkaiset mallit, SCUNet- ja FBCNN-mallit puhdistavat kohinaa tai pakkausta, taustamallit luovat maskeja ja värisäimet lisäävät väriä harmaasävytuloon. Tuodut mukautetut mallit voivat olla tehokkaita, mutta niiden tulee vastata odotettua tulo/tulostusmuotoa ja käyttää tuettua ONNX- tai ORT-muotoa. + Käytä skaalaajia, kun tarvitset enemmän pikseleitä, vaimentimia, kun kuva on rakeinen, ja artefaktien poistajia, kun pakkauslohkot tai raidat ovat pääongelma. + Käytä taustamalleja vain kohteiden erottamiseen; ne eivät ole samoja kuin yleinen objektin poisto tai kuvan parantaminen. + Tuo mukautettuja malleja vain luotettavista lähteistä ja testaa niitä pienellä kuvalla ennen tärkeiden tiedostojen käyttöä. + AI-parametrit + Säädä kappaleen kokoa, päällekkäisyyttä ja rinnakkaisia ​​työntekijöitä tasapainottaaksesi laatua, nopeutta ja muistia + Tasapainottaa vakauden ja vakauden + Palakoko määrittää, kuinka suuria kuvakappaleet ovat ennen kuin malli käsittelee ne. Päällekkäisyys yhdistää viereiset palat piilottaakseen reunat. Rinnakkaistyöntekijät voivat nopeuttaa käsittelyä, mutta jokainen työntekijä tarvitsee muistia, joten suuret arvot voivat tehdä suurista kuvista epävakaita puhelimissa, joissa on rajoitettu RAM-muisti. + Käytä suurempia paloja tasaisemman kontekstin saamiseksi, kun laitteesi käsittelee mallia luotettavasti ja kuva ei ole valtava. + Lisää päällekkäisyyttä, kun osien reunat tulevat näkyviin, mutta muista, että enemmän päällekkäisyyksiä tarkoittaa enemmän toistuvaa työtä. + Nosta rinnakkaiset työntekijät vasta vakaan testin jälkeen; Jos käsittely hidastuu, lämmittää laitetta tai kaatuu, vähennä ensin työntekijöitä. + Vältä palasia esineitä + Chunking antaa sovelluksen käsitellä kuvia, jotka ovat suurempia kuin malli pystyy mukavasti käsittelemään kerralla. Kompromissi on, että jokaisella laatalla on vähemmän ympäröivää kontekstia, joten pieni päällekkäisyys tai liian pienet palat voivat luoda näkyviä reunoja, tekstuurimuutoksia tai epäjohdonmukaisia ​​yksityiskohtia viereisten alueiden välille. + Lisää päällekkäisyyttä, jos näet suorakaiteen muotoisia reunuksia, toistuvia tekstuurimuutoksia tai yksityiskohtien hyppyjä käsiteltyjen alueiden välillä. + Pienennä kappaleen kokoa, jos prosessi epäonnistuu ennen tulosteen tuottamista, erityisesti suurissa kuvissa tai painavissa malleissa. + Tallenna pieni rajaustesti ennen koko kuvan käsittelyä, jotta voit verrata parametreja nopeasti. + AI vakaus + Pienennä kappalekokoa, työntekijöitä ja lähteen resoluutiota, kun tekoälyn käsittely kaatuu tai jumiutuu + Toivu raskaista tekoälytöistä + AI-käsittely on yksi sovelluksen raskaimmista työnkulkuista. Kaatumiset, epäonnistuneet istunnot, jumiutuminen tai äkilliset sovelluksen uudelleenkäynnistykset tarkoittavat yleensä sitä, että malli, lähteen tarkkuus, kappaleen koko tai rinnakkaisten työntekijöiden määrä on liian vaativa laitteen nykyiseen tilaan. + Jos sovellus kaatuu tai ei avaa malliistuntoa, pienennä rinnakkaisia ​​työntekijöitä ja osan kokoa ja yritä sitten samaa kuvaa uudelleen. + Muuta erittäin suurten kuvien kokoa ennen tekoälyn käsittelyä, kun tavoite ei vaadi alkuperäistä resoluutiota. + Sulje muut raskaat sovellukset, käytä pienempää mallia tai käsittele vähemmän tiedostoja kerralla, kun muistin paine palaa jatkuvasti. + Diagnosoi mallin viat + Epäonnistunut tekoäly ei aina tarkoita, että kuva on huono. Mallitiedosto voi olla epätäydellinen, sitä ei tueta, liian suuri muistiin tai se ei täsmää toiminnon kanssa. Kun sovellus raportoi epäonnistuneesta istunnosta, käsittele sitä signaalina yksinkertaistaaksesi ensin mallia ja parametreja. + Poista malli ja lataa se uudelleen, jos se epäonnistuu heti lataamisen jälkeen tai käyttäytyy kuin tiedosto olisi vioittunut. + Kokeile sisäänrakennettua ladattavaa mallia ennen kuin syytät tuotua mukautettua mallia, koska tuoduissa malleissa voi olla yhteensopimattomia tuloja. + Käytä sovelluslokeja virheen toistamisen jälkeen, jos sinun on ilmoitettava kaatumis- tai istuntovirheestä. + Kokeile Shader Studiossa + Käytä GPU-varjostustehosteita edistyneeseen kuvankäsittelyyn ja mukautettuun ulkoasuun + Työskentele varovasti varjostimien kanssa + Shader Studio on tehokas, mutta Shader-koodi ja parametrit vaativat pieniä, testattavia muutoksia. Käsittele sitä kuin kokeellista työkalua: pidä toimiva lähtökohta, muuta yksi asia kerrallaan ja testaa eri kuvakoot ennen kuin luotat esiasetukseen. + Aloita tunnetusta toimivasta varjostimesta tai yksinkertaisesta tehosteesta ennen parametrien lisäämistä. + Muuta yksi parametri tai koodilohko kerrallaan ja tarkista esikatselu virheiden varalta. + Vie esiasetukset vasta testattuasi niitä muutamalla eri kuvakoolla. + Tee SVG- tai ASCII-taide + Luo vektorimaista sisältöä tai tekstipohjaisia ​​kuvia luovaa tulosta varten + Valitse mainoksen tulostyyppi + SVG- ja ASCII-työkalut palvelevat erilaisia ​​kohteita: skaalautuvaa grafiikkaa tai tekstityylistä renderöintiä. Molemmat ovat luovia tuloksia, joten esikatselu lopullisessa koossa on tärkeämpää kuin tuloksen päättäminen pienestä pikkukuvasta. + Käytä SVG Makeria, kun tuloksen pitäisi skaalata puhtaasti tai käyttää uudelleen suunnittelutyönkuluissa. + Käytä ASCII-taidetta, kun haluat tyylitellyn tekstiesityksen kuvasta. + Esikatsele lopullisessa koossa, koska pienet yksityiskohdat voivat kadota tuotetussa tulosteessa. + Luo kohinakuvioita + Luo menettelyllisiä pintakuvioita taustoja, maskeja, peittokuvia tai kokeiluja varten + Viritä prosessitulostus + Kohinan tuottaminen luo uusia kuvia parametreista, joten pienet muutokset voivat tuottaa hyvin erilaisia ​​tuloksia. Se on hyödyllinen pintakuvioissa, maskeissa, peittokuvissa, paikkamerkeissä tai pakkauksen testaamisessa yksityiskohtaisilla kuvioilla. + Valitse kohinan tyyppi ja lähtökoko ennen hienojen yksityiskohtien viritystä. + Säädä mittakaavaa, voimakkuutta, värejä tai siemeniä, kunnes kuvio sopii käyttötarkoitukseen. + Tallenna hyödyllisiä tuloksia ja kirjoita parametrit muistiin, jos haluat luoda tekstuurin uudelleen myöhemmin. + Merkintäprojektit + Käytä projektitiedostoja, kun merkintöjen on pysyttävä muokattavissa sovelluksen sulkemisen jälkeen + Pidä kerroksittain tehdyt muokkaukset uudelleenkäytettävinä + Merkintäprojektitiedostot ovat hyödyllisiä, kun kuvakaappaus, kaavio tai ohjekuva saattaa vaatia muutoksia myöhemmin. Viedyt PNG/JPEG-tiedostot ovat lopullisia kuvia, kun taas projektitiedostot säilyttävät muokattavat tasot ja muokkaustilan tulevia säätöjä varten. + Käytä merkintätasoja, kun merkintöjen, huomiotekstien tai asetteluelementtien pitäisi pysyä muokattavissa. + Tallenna tai avaa projektitiedosto uudelleen ennen lopullisen kuvan vientiä, jos odotat lisää versioita. + Vie normaali kuva vasta, kun olet valmis jakamaan litteän lopullisen version. + Salaa tiedostot + Suojaa tiedostot salasanalla ja testaa salauksen purku ennen lähdekopion poistamista + Käsittele salattuja tulosteita huolellisesti + Salaustyökalut voivat suojata tiedostoja salasanapohjaisella salauksella, mutta ne eivät korvaa avaimen muistamista tai varmuuskopioiden säilyttämistä. Salaus on hyödyllinen kuljetuksessa ja varastoinnissa, kun taas ZIP on parempi, kun päätavoitteena on niputtaa useita lähtöjä. + Salaa ensin kopio ja säilytä alkuperäinen, kunnes olet testannut, että salauksen purku toimii tallentamallasi salasanalla. + Käytä salasananhallintaohjelmaa tai muuta turvallista paikkaa avaimelle; sovellus ei voi arvata kadonnutta salasanaa puolestasi. + Jaa salattuja tiedostoja vain sellaisten ihmisten kanssa, joilla on myös salasana ja millä työkalulla tai menetelmällä niiden salaus tulee purkaa. + Järjestä työkalut + Ryhmittele, tilaa, hae ja kiinnitä työkalut, jotta päänäyttö vastaa todellista työnkulkuasi + Tee päänäytöstä nopeampi + Työkalujen järjestelyasetuksia kannattaa säätää, kun tietää, mitä työkaluja käytät eniten. Ryhmittely helpottaa tutkimista, mukautettu järjestys auttaa lihasmuistia ja suosikit pitävät päivittäiset työkalut tavoitettavissa, vaikka koko lista kasvaisi suureksi. + Käytä ryhmitettyä tilaa opitessasi sovellusta tai kun haluat mieluummin työkaluja, jotka on järjestetty tehtävätyypin mukaan. + Vaihda mukautettuun järjestykseen, kun tiedät jo usein käyttämäsi työkalut ja haluat vähemmän rullauksia. + Käytä hakuasetuksia ja suosikkeja yhdessä harvoille työkaluille, jotka muistat nimellä, mutta et halua näkyvän koko ajan. + Käsittele suuria tiedostoja + Vältä hidasta vientiä, muistin painetta ja odottamattoman suurta tulostusta + Vähennä työtä ennen vientiä + Erittäin suuret kuvat, pitkät erät ja korkealaatuiset tiedostot voivat viedä enemmän muistia ja aikaa. Nopein korjaus on yleensä työn määrän vähentäminen ennen raskainta toimintoa, ei odoteta saman ylimitoitettua syötteen valmistumista. + Muuta suurten kuvien kokoa ennen raskaita suodattimia, tekstintunnistusta tai PDF-muunnoksia. + Käytä ensin pienempää erää vahvistaaksesi asetukset ja käsittele sitten loput samalla esiasetuksella. + Huonompi laatu tai muuta muotoa, kun tulostetiedosto on odotettua suurempi. + Välimuisti ja esikatselut + Ymmärrä luodut esikatselut, välimuistin puhdistaminen ja tallennustilan käyttö raskaiden istuntojen jälkeen + Pidä varastointi ja nopeus tasapainossa + Esikatselun luominen ja välimuisti voivat nopeuttaa toistuvaa selaamista, mutta raskaat muokkausistunnot voivat jättää väliaikaisia ​​tietoja taakse. Välimuistiasetukset auttavat, kun sovellus tuntuu hitaalta, tallennustila on tiukka tai esikatselut näyttävät vanhentuneilta monien kokeilujen jälkeen. + Esikatselun pitäminen käytössä, kun selaat monia kuvia, on tärkeämpää kuin tilapäisen tallennustilan minimointi. + Tyhjennä välimuisti suurten erien, epäonnistuneiden kokeilujen tai pitkien istuntojen jälkeen monien korkearesoluutioisten tiedostojen kanssa. + Käytä automaattista välimuistin tyhjennystä, jos haluat mieluummin tallennustilan puhdistamisen kuin esikatselun pitämisen lämpimänä käynnistysten välillä. + Säädä näytön toimintaa + Käytä koko näytön, suojatun tilan, kirkkauden ja järjestelmäpalkin vaihtoehtoja keskittyäksesi työhön + Tee käyttöliittymästä tilanteeseen sopiva + Näyttöasetukset auttavat, kun muokkaat vaaka-asennossa, näytät yksityisiä kuvia tai tarvitset tasaista kirkkautta. Niitä ei vaadita normaaliin käyttöön, mutta ne ratkaisevat kiusallisia tilanteita, kuten QR-tarkistuksia, yksityisiä esikatseluja tai ahtaita maisemamuokkauksia. + Käytä koko näytön tai järjestelmäpalkin asetuksia, kun muokkaustila on tärkeämpää kuin navigointikromi. + Käytä suojattua tilaa, kun yksityisiä kuvia ei pitäisi näkyä kuvakaappauksissa tai sovellusten esikatseluissa. + Käytä kirkkauden valvontaa työkaluissa, joissa tummia kuvia tai QR-koodeja on vaikea tarkistaa. + Säilytä läpinäkyvyys + Estä läpinäkyviä taustoja muuttumasta valkoisiksi, mustiksi tai litteiksi + Käytä alfa-tietoista tulostusta + Läpinäkyvyys säilyy vain, kun valittu työkalu ja tulostusmuoto tukevat alfaa. Jos viety tausta muuttuu valkoiseksi tai mustaksi, ongelma on yleensä tulostusmuoto, täyttö/tausta-asetus tai kohdesovellus, joka tasoitti kuvan. + Valitse PNG tai WebP, kun viet taustalla poistettuja kuvia, tarroja, logoja tai peittokuvia. + Vältä JPEG-muotoa läpinäkyvä tuloste, koska se ei tallenna alfakanavaa. + Tarkista alfa-muotojen taustaväriin liittyvät asetukset, jos esikatselussa näkyy täytetty tausta. + Korjaa jakamiseen ja tuontiin liittyvät ongelmat + Käytä valitsinasetuksia ja tuettuja muotoja, kun tiedostot eivät näy tai avaudu oikein + Lataa tiedosto sovellukseen + Tuontiongelmat johtuvat usein palveluntarjoajan luvista, ei-tuetuista muodoista tai poimijan toiminnasta. Pilvisovelluksista ja lähettiläistä jaetut tiedostot voivat olla väliaikaisia, joten pysyvän lähteen valitseminen voi olla luotettavampaa pidempiä muokkauksia varten. + Yritä avata tiedosto järjestelmävalitsimella viimeaikaisten tiedostojen pikakuvakkeen sijaan. + Jos jokin työkalu ei tue muotoa, muunna se ensin tai avaa tarkempi työkalu. + Tarkista kuvanvalitsimen ja käynnistysohjelman asetukset, jos jakovirrat tai työkalun suora avaaminen käyttäytyvät odotettua eri tavalla. + Poistumisvahvistus + Vältä menettämästä tallentamattomia muokkauksia, kun eleitä, taaksepäin painamista tai työkalun vaihto tapahtuu vahingossa + Suojaa keskeneräiset työt + Työkalun poistumisen vahvistus on hyödyllinen, kun käytät ele-navigointia, muokkaat suuria tiedostoja tai vaihdat usein sovellusten välillä. Se lisää pienen tauon ennen kuin poistut työkalusta, jotta keskeneräiset asetukset ja esikatselut eivät katoa vahingossa. + Ota käyttöön vahvistus, jos tahattomat takaisineleet ovat joskus sulkeneet työkalun ennen vientiä. + Pidä se pois käytöstä, jos teet enimmäkseen nopeita yksivaiheisia muunnoksia ja haluat nopeamman navigoinnin. + Käytä sitä yhdessä esiasetusten kanssa pidempiin työnkulkuihin, joissa asetusten uudelleenrakentaminen olisi ärsyttävää. + Käytä lokeja, kun raportoit ongelmista + Kerää hyödyllisiä tietoja, ennen kuin avaat ongelman tai otat yhteyttä kehittäjään + Ilmoita kontekstiin liittyvistä ongelmista + Selkeä raportti, jossa on vaiheet, sovellusversio, syöttötyyppi ja lokit, on paljon helpompi diagnosoida. Lokit ovat hyödyllisimpiä heti ongelman toistamisen jälkeen, koska ne pitävät ympäröivän kontekstin tuoreena. + Huomaa työkalun nimi, tarkka toiminto ja tapahtuuko se yhdelle tiedostolle vai jokaiselle tiedostolle. + Avaa sovelluslokit ongelman toistamisen jälkeen ja jaa asiaankuuluva lokitulos, jos mahdollista. + Liitä kuvakaappauksia tai esimerkkitiedostoja vain, jos ne eivät sisällä henkilökohtaisia ​​tietoja. + Taustakäsittely lopetettiin + Android ei antanut taustakäsittelyilmoituksen käynnistyä ajoissa. Tämä on järjestelmän ajoitusrajoitus, jota ei voida luotettavasti korjata. Käynnistä sovellus uudelleen ja yritä uudelleen; sovelluksen pitäminen auki ja ilmoitusten tai taustatyön salliminen voi auttaa. + Ohita tiedostojen poiminta + Avaa työkalut nopeammin, kun syöte tulee yleensä jaetulta, leikepöydältä tai edelliseltä näytöltä + Nopeuta työkalujen toistuvia käynnistyksiä + Ohita tiedostojen poiminta muuttaa tuettujen työkalujen ensimmäistä vaihetta. Siitä on hyötyä, kun syötät yleensä työkalun jo valitulla kuvalla tai Android-jakotoiminnoista, mutta voi tuntua hämmentävältä, jos oletat jokaisen työkalun pyytävän tiedostoa välittömästi. + Ota se käyttöön vain, kun tavallinen työnkulkusi tarjoaa jo lähdekuvan ennen työkalun avaamista. + Pidä se poissa käytöstä, kun opettelet sovellusta, koska selkeä poiminta tekee jokaisesta työkaluvirrasta helpompi ymmärtää. + Jos työkalu avautuu ilman selvää syöttöä, poista tämä asetus käytöstä tai aloita Jaa/Avaa sovelluksella -kohdasta, jotta Android välittää tiedoston ensin. + Avaa editori ensin + Ohita esikatselu, kun jaetun kuvan pitäisi mennä suoraan muokattavaksi + Valitse esikatselu tai muokkaa ensimmäiseksi pysäkiksi + Avaa muokkaus esikatselun sijaan on tarkoitettu käyttäjille, jotka jo tietävät haluavansa muokata kuvaa. Esikatselu on turvallisempaa, kun tarkastelet tiedostoja chatista tai pilvitallennustilasta; suoramuokkaus on nopeampi kuvakaappauksille, pikarajauksille, huomautuksille ja kertakorjauksille. + Ota suoramuokkaus käyttöön, jos useimmat jaetut kuvat tarvitsevat välittömästi rajauksen, merkinnät, suodattimet tai muutokset. + Jätä esikatselu ensin, kun tarkastat usein metatiedot, mitat, läpinäkyvyyden tai kuvanlaadun ennen päätöksentekoa. + Käytä tätä yhdessä suosikkien kanssa, jotta jaetut kuvat osuvat lähelle todellisuudessa käyttämiäsi työkaluja. + Esiasetettu tekstinsyöttö + Anna tarkat esiasetetut arvot sen sijaan, että säädät säätimiä toistuvasti + Käytä tarkkoja arvoja, kun liukusäätimet ovat hitaita + Esiasetettu tekstinsyöttö auttaa, kun tarvitset tarkkoja lukuja toistuvaan työhön: sosiaalisten kuvien koot, asiakirjan marginaalit, pakkauskohteet, viivojen leveydet tai muut arvot, joita on ärsyttävää saavuttaa eleillä. Se on hyödyllinen myös pienillä näytöillä, joissa hieno liukusäätimen liike on vaikeampaa. + Ota tekstinsyöttö käyttöön, jos käytät usein uudelleen tarkkoja kokoja, prosentteja, laatuarvoja tai työkaluparametreja. + Tallenna arvo esiasetuksena, kun olet kirjoittanut sen kerran, ja käytä sitä sitten uudelleen sen sijaan, että rakentaisit uudelleen samat asetukset. + Säilytä ele-säätimet karkeaa visuaalista viritystä varten ja kirjoitetut arvot tiukkoja tulostusvaatimuksia varten. + Tallenna lähelle alkuperäistä + Aseta luodut tiedostot lähdekuvien viereen, kun kansion kontekstilla on merkitystä + Säilytä tulosteet lähteineen + Save To Original Folder on kätevä kameran kansioihin, projektikansioihin, asiakirjojen skannauksiin ja asiakaseriin, joissa muokatun kopion tulee pysyä lähteen vieressä. Se voi olla vähemmän siisti kuin erillinen tulostekansio, joten yhdistä se selkeisiin tiedostonimien jälkiliitteisiin tai kuvioihin. + Ota se käyttöön, kun jokainen lähdekansio on jo merkityksellinen ja tulosteiden pitäisi pysyä ryhmiteltyinä sinne. + Käytä tiedostonimien jälkiliitteitä, etuliitteitä, aikaleimoja tai kuvioita, jotta muokatut kopiot on helppo erottaa alkuperäisistä. + Poista se käytöstä sekaryhmille, kun haluat kerätä kaikki luodut tiedostot yhteen vientikansioon. + Ohita suurempi tulos + Vältä sellaisten tulosten tallentamista, joista tulee odottamatta raskaampia kuin lähde + Suojaa erät huonon kokoisilta yllätyksiltä + Jotkut muunnokset tekevät tiedostoista suurempia, erityisesti PNG-kuvakaappauksia, jo pakattuja valokuvia, korkealaatuisia WebP-tiedostoja tai kuvia, joiden metatiedot on säilytetty. Salli Ohita jos suurempi estää näitä lähtöjä korvaamasta pienempää lähdettä työnkuluissa, joissa koon pienentäminen on päätavoite. + Ota se käyttöön, kun viennin tarkoituksena on pakata, muuttaa kokoa tai valmistella tiedostoja lähetysrajoja varten. + Tarkista ohitetut tiedostot erän jälkeen; ne voivat olla jo optimoituja tai ne saattavat tarvita eri muotoa. + Poista se käytöstä, kun laatu, läpinäkyvyys tai muodon yhteensopivuus ovat tärkeämpiä kuin lopullinen tiedostokoko. + Leikepöytä ja linkit + Ohjaa automaattista leikepöydän syöttöä ja linkkien esikatselua nopeampia tekstipohjaisia ​​työkaluja varten + Tee automaattisesta syötöstä ennustettavaa + Leikepöytä- ja linkkiasetukset ovat käteviä QR-, Base64-, URL- ja paljon tekstiä sisältäviin työnkulkuihin, mutta automaattisen syöttämisen tulisi pysyä tarkoituksella. Jos sovellus näyttää täyttävän kentät itsestään, tarkista leikepöydän liittämistoiminta. Jos linkkikortit tuntuvat äänekkäiltä tai hitailta, säädä linkin esikatselun toimintaa. + Salli automaattinen leikepöydälle liittäminen, kun kopioit usein tekstiä ja avaat heti hakutyökalun. + Poista automaattinen liittäminen käytöstä, jos yksityistä leikepöydän sisältöä näkyy työkaluissa, joissa et odottanut sitä. + Poista linkkien esikatselut käytöstä, kun esikatselun nouto on hidasta, häiritsevää tai tarpeetonta työnkulkusi kannalta. + Kompaktit säätimet + Käytä tiheämpiä valitsimia ja nopeaa asetusten sijoittelua, kun näyttötilaa on vähän + Sovita enemmän säätimiä pienille näytöille + Kompaktit valitsimet ja nopeat asetusten sijoittelut auttavat pienissä puhelimissa, vaakamuokkauksessa ja työkaluissa, joissa on monia parametreja. Kompromissi on, että säätimet voivat tuntua tiheämmiltä, ​​joten tämä on parasta, kun olet jo tunnistanut yleiset vaihtoehdot. + Ota käyttöön kompaktit valitsimet, kun valintaikkunat tai vaihtoehtoluettelot tuntuvat liian korkeilta näytölle. + Säädä pika-asetuspuolta, jos työkalun säätimet on helpompi saavuttaa näytön toiselta puolelta. + Palaa tilavampiin säätimiin, jos opettelet edelleen sovellusta tai napautat usein tiheitä vaihtoehtoja. + Lataa kuvia verkosta + Liitä sivun tai kuvan URL-osoite, esikatsele jäsennettyjä kuvia ja tallenna tai jaa sitten valitut tulokset + Käytä verkkokuvia tietoisesti + Web Image Loading jäsentää kuvalähteet annetusta URL-osoitteesta, esikatselee ensimmäistä saatavilla olevaa kuvaa ja antaa sinun tallentaa tai jakaa valitut jäsennetyt kuvat. Jotkin sivustot käyttävät väliaikaisia, suojattuja tai komentosarjan luomia linkkejä, joten selaimessa toimiva sivu ei välttämättä silti palauta käyttökelpoisia kuvia. + Liitä suora kuvan URL-osoite tai sivun URL-osoite ja odota, että jäsennetty kuvaluettelo tulee näkyviin. + Käytä kehystä tai valintasäätimiä, kun sivulla on useita kuvia ja tarvitset vain osan niistä. + Jos mikään ei lataudu, kokeile suoraa kuvalinkkiä tai lataa ensin selaimen kautta. sivusto voi estää jäsentämisen tai käyttää yksityisiä linkkejä. + Valitse kokotyyppi + Ymmärrä yksiselitteinen, joustava, rajattava ja sovitettu, ennen kuin pakotat kuvan uusiin mittoihin + Hallitse muotoa, kangasta ja kuvasuhdetta + Koon muutostyyppi päättää, mitä tapahtuu, kun pyydetty leveys ja korkeus eivät vastaa alkuperäistä kuvasuhdetta. Se on ero pikselien venyttämisen, mittasuhteiden säilyttämisen, ylimääräisen alueen rajaamisen tai taustakankaan lisäämisen välillä. + Käytä Explicit vain silloin, kun vääristymä on hyväksyttävää tai lähteellä on jo sama kuvasuhde kuin kohteen. + Käytä Flexible-toimintoa säilyttääksesi mittasuhteet muuttamalla kokoa tärkeältä puolelta, erityisesti valokuvissa ja sekakuvissa. + Käytä Rajaa tiukkoja kehyksiä ilman reunuksia tai Sovita, kun koko kuvan on pysyttävä näkyvissä kiinteän kankaan sisällä. + Valitse kuvan mittakaavatila + Valitse uudelleennäytteenottosuodatin, joka säätelee terävyyttä, tasaisuutta, nopeutta ja reunojen artefakteja + Muuta kokoa ilman vahingossa tapahtuvaa pehmeyttä + Kuvan mittakaavatila ohjaa, kuinka uudet pikselit lasketaan koon muuttamisen aikana. Yksinkertaiset tilat ovat nopeita ja ennustettavia, kun taas edistyneet suodattimet voivat säilyttää yksityiskohdat paremmin, mutta ne voivat terävöittää reunoja, luoda sädekehyksiä tai kestää kauemmin suurissa kuvissa. + Käytä Basic tai Bilinear nopeaan päivittäiseen koon muuttamiseen, kun tuloksen tarvitsee vain olla pienempi ja puhdas. + Käytä Lanczos-, Robidoux-, Spline- tai EWA-tyylisiä tiloja, kun hienoilla yksityiskohdilla, tekstillä tai korkealaatuisella pienennyksellä on merkitystä. + Käytä Lähintä pikselitaidetta, kuvakkeita ja teräviä grafiikoita varten, joissa epäselvät pikselit pilaavat ulkoasun. + Skaalaa väriavaruutta + Päätä, kuinka värejä sekoitetaan, kun pikseleitä otetaan uudelleen koon muuttamisen aikana + Pidä kaltevuudet ja reunat luonnollisina + Skaalausväriavaruus muuttaa kokoa muuttaessa käytettyä matematiikkaa. Useimmat kuvat ovat kunnossa oletusasetuksen kanssa, mutta lineaariset tai havainnolliset tilat voivat saada kaltevuudet, tummat reunat ja kylläiset värit näyttämään puhtaammilta voimakkaan skaalan jälkeen. + Jätä oletusasetus, kun nopeus ja yhteensopivuus ovat tärkeämpiä kuin pienet värierot. + Kokeile lineaarista tai havaintotilaa, kun tummat ääriviivat, käyttöliittymän kuvakaappaukset, liukuvärit tai värinauhat näyttävät vääriltä koon muuttamisen jälkeen. + Vertaa ennen ja jälkeen todellisen kohdenäytön, koska väriavaruuden muutokset voivat olla hienovaraisia ​​ja sisällöstä riippuvia. + Rajoita koonmuutostiloja + Tiedä, milloin ylimääräiset kuvat tulee ohittaa, koodata uudelleen tai pienentää sopimaan + Valitse, mitä rajalla tapahtuu + Limit Resize ei ole vain pienempien kuvien työkalu. Sen tila päättää, mitä sovellus tekee, kun lähde on jo rajojen sisällä tai kun vain toinen puoli rikkoo sääntöä, mikä on tärkeää sekakansioiden ja latausten valmistelussa. + Käytä Ohita, kun rajojen sisällä olevat kuvat tulisi jättää koskemattomiksi eikä viedä uudelleen. + Käytä Recodea, kun mitat ovat hyväksyttäviä, mutta tarvitset silti uuden muodon, metatietojen käyttäytymisen, laadun tai tiedostonimen. + Käytä Zoomaa, kun rajoja rikkovia kuvia tulee pienentää suhteellisesti, kunnes ne sopivat sallittuun ruutuun. + Kuvasuhdekohteet + Vältä venytettyjä pintoja, leikkauksia ja odottamattomia reunuksia tiukoissa tulostekokoissa + Yhdistä muoto ennen koon muuttamista + Leveys ja korkeus ovat vain puolet koonmuutostavoitteesta. Kuvasuhde päättää, mahtuuko kuva luonnollisesti, tarvitseeko se rajausta vai tarvitseeko sen ympärille kangasta. Muodon tarkistaminen ensin estää yllättävimmät koonmuutostulokset. + Vertaa lähdemuotoa kohdemuotoon ennen kuin valitset Eksplicit-, Rajaa- tai Sovita. + Rajaa ensin, kun kohde voi menettää ylimääräistä taustaa ja kohde tarvitsee tiukat kehykset. + Käytä Fit- tai Flexible-toimintoa, kun alkuperäisen kuvan jokaisen osan on oltava näkyvissä. + Korkeatasoinen tai normaali koko + Tiedä, milloin pikselien lisääminen vaatii tekoälyä ja milloin yksinkertainen skaalaus riittää + Älä sekoita kokoa yksityiskohtiin + Normaali koonmuutos muuttaa mitat interpoloimalla pikseleitä; se ei voi keksiä todellisia yksityiskohtia. Korkeatasoinen tekoäly voi luoda terävämmän näköisiä yksityiskohtia, mutta se on hitaampaa ja saattaa hallusinoida tekstuuria, joten oikea valinta riippuu siitä, tarvitsetko yhteensopivuutta, nopeutta vai visuaalista restaurointia. + Käytä normaalia kokoa pikkukuville, lähetysrajoituksille, kuvakaappauksille ja yksinkertaisille mittojen muutoksille. + Käytä korkealuokkaista tekoälyä, kun pienen tai pehmeän kuvan on näytettävä paremmalta suuremmassa koossa. + Vertaa kasvoja, tekstiä ja kuvioita tekoälyn skaalauksen jälkeen, koska luodut yksityiskohdat voivat näyttää vakuuttavilta mutta virheellisiltä. + Suodatinjärjestyksellä on väliä + Käytä puhdistusta, väriä, terävyyttä ja lopullista pakkausta tarkoituksellisessa järjestyksessä + Rakenna puhtaampi suodatinpino + Suodattimet eivät aina ole vaihdettavissa. Sumeus ennen terävöitystä, kontrastin lisääminen ennen OCR:ää tai värin muutos ennen pakkausta voivat tuottaa hyvin erilaisia ​​tuloksia samoista säätimistä. + Rajaa ja kierrä ensin, jotta myöhemmin suodattimet toimivat vain jäljellä olevissa pikseleissä. + Käytä valotusta, valkotasapainoa ja kontrastia ennen terävöittämistä tai tyyliteltyjä tehosteita. + Pidä lopullinen koko ja pakkaus lähellä loppua, jotta esikatsellut tiedot säilyvät viennissä ennustettavasti. + Korjaa taustan reunat + Puhdista halot, jääneet varjot, hiukset, reiät ja pehmeät ääriviivat automaattisen poiston jälkeen + Tee leikkauksista tahallisen näköisiä + Automaattiset maskit näyttävät usein hyvältä yhdellä silmäyksellä, mutta paljastavat ongelmia kirkkailla, tummilla tai kuviollisilla taustoilla. Reunojen puhdistus on ero nopean leikkauksen ja uudelleenkäytettävän tarran, logon tai tuotekuvan välillä. + Esikatsele tulosta kontrastisia taustoja vasten paljastaaksesi halot ja puuttuvat reunan yksityiskohdat. + Käytä palautusta hiuksiin, kulmiin ja läpinäkyviin esineisiin ennen kuin poistat ympäröivät jäämät. + Vie pieni testi lopullisesta taustaväristä, kun leikkaus sijoitetaan malliin. + Luettavat vesileimat + Tee merkit näkyväksi kirkkaissa, tummissa, kiireisissä ja rajatuissa kuvissa tuhoamatta valokuvaa + Suojaa kuva piilottamatta sitä + Vesileima, joka näyttää hyvältä yhdessä kuvassa, voi kadota toiseen. Koko, peittävyys, kontrasti, sijoitus ja toisto tulee valita koko erälle, ei vain ensimmäiselle esikatselulle. + Testaa erän kirkkaimpien ja tummimpien kuvien merkki ennen kaikkien tiedostojen vientiä. + Käytä pienempää peittävyyttä suurille toistuville jälkille ja voimakkaampaa kontrastia pienille kulmajäljille. + Pidä tärkeät kasvot, teksti ja tuotetiedot selkeänä, ellei merkin tarkoitus ole estää uudelleenkäyttöä. + Jaa yksi kuva laatoiksi + Leikkaa yksi kuva rivien, sarakkeiden, mukautettujen prosenttiosuuksien, muodon ja laadun mukaan + Valmistele ristikot ja tilatut kappaleet + Image Splitting luo useita lähtöjä yhdestä lähdekuvasta. Se voi jakaa rivien ja sarakkeiden lukumäärän mukaan, säätää rivien tai sarakkeen prosenttiosuuksia ja tallentaa kappaleita valitulla tulostusmuodolla ja laadulla, mikä on hyödyllistä ruudukoille, sivuleikkauksille, panoraamille ja järjestetyille sosiaalisille viesteille. + Valitse lähdekuva ja aseta sitten tarvitsemasi rivien ja sarakkeiden määrä. + Säädä rivien tai sarakkeen prosenttiosuutta, kun kaikkien osien ei pitäisi olla samankokoisia. + Valitse tulostusmuoto ja laatu ennen tallentamista, jotta jokainen luotu kappale käyttää samoja vientisääntöjä. + Leikkaa kuvanauhat + Poista pysty- tai vaakasuuntaiset alueet ja yhdistä loput osat takaisin yhteen + Poista alue jättämättä aukkoa + Kuvan leikkaus eroaa tavallisesta rajauksesta. Se voi poistaa valitun pysty- tai vaakakaistaleen ja yhdistää sitten loput kuvaosat yhteen. Käänteinen tila säilyttää valitun alueen sen sijaan, ja molempien käänteisten suuntien käyttäminen säilyttää valitun suorakulmion. + Käytä pystysuoraa leikkausta sivualueille, pylväille tai keskiliuskoille; käytä vaakasuoraa leikkausta bannereihin, aukkoihin tai riveihin. + Ota käänteissuunta käyttöön akselilla, kun haluat säilyttää valitun osan sen poistamisen sijaan. + Esikatsele reunoja leikkauksen jälkeen, koska yhdistetty sisältö voi näyttää äkilliseltä, kun poistettu alue ylittää tärkeitä yksityiskohtia. + Valitse tulostusmuoto + Valitse JPEG, PNG, WebP, AVIF, JXL tai PDF sisällön ja kohteen perusteella + Anna kohteen päättää muoto + Paras muoto riippuu siitä, mitä kuva sisältää ja missä sitä käytetään. Valokuvat, kuvakaappaukset, läpinäkyvät resurssit, asiakirjat ja animoidut tiedostot epäonnistuvat eri tavoin, kun ne pakotetaan väärään muotoon. + Käytä JPEG-muotoa laajan valokuvien yhteensopivuuden varmistamiseksi, kun läpinäkyvyyttä ja tarkkoja pikseleitä ei vaadita. + Käytä PNG-muotoa teräviin kuvakaappauksiin, käyttöliittymäkaappauksiin, läpinäkyviin grafiikoihin ja häviöttömään muokkaukseen. + Käytä WebP:tä, AVIF:ää tai JXL:ää, kun kompaktilla nykyaikaisella lähdöllä on merkitystä ja vastaanottava sovellus tukee sitä. + Pura äänikannet + Tallenna upotettu albumin kansikuva tai käytettävissä olevat mediakehykset äänitiedostoista PNG-kuvina + Vedä taideteoksia ulos äänitiedostoista + Audio Covers käyttää Android-median metatietoja lukeakseen upotettuja taideteoksia äänitiedostoista. Kun upotettu taideteos ei ole käytettävissä, noutaja voi palata mediakehykseen, jos Android voi tarjota sellaisen. jos kumpaakaan ei ole, työkalu ilmoittaa, että poimittavaa kuvaa ei ole. + Avaa Audio Covers ja valitse yksi tai useampi äänitiedosto, jonka kansikuva on odotettu. + Tarkista purettu kansi ennen tallentamista tai jakamista, erityisesti striimaus- tai tallennussovellusten tiedostoille. + Jos kuvaa ei löydy, äänitiedostossa ei todennäköisesti ole upotettua kantta tai Android ei voinut paljastaa käyttökelpoista kehystä. + Päivämäärät ja sijainnin metatiedot + Päätä, mitä haluat säilyttää, kun tallennusajalla on merkitystä, mutta GPS- tai laitetiedot eivät saa vuotaa + Erota hyödylliset metatiedot yksityisistä metatiedoista + Kaikki metatiedot eivät ole yhtä riskialttiita. Tallennuspäivämäärät voivat olla hyödyllisiä arkistointia ja lajittelua varten, kun taas GPS, laitteen sarjatyyppiset kentät, ohjelmistotunnisteet tai omistajakentät voivat paljastaa enemmän kuin on tarkoitettu. + Säilytä päivämäärä- ja aikatunnisteet, kun kronologisella järjestyksellä on merkitystä albumien, skannausten tai projektihistorian kannalta. + Poista GPS- ja laitetunnisteet ennen kuvien julkista jakamista tai lähettämistä tuntemattomille. + Vie näyte ja tarkista EXIF ​​uudelleen, kun tietosuojavaatimukset ovat tiukat. + Vältä tiedostonimien törmäyksiä + Suojaa erätulosteet, kun monet tiedostot jakavat nimet, kuten image.jpg tai screenshot.png + Tee jokaisesta tulostenimestä yksilöllinen + Päällekkäiset tiedostonimet ovat yleisiä, kun kuvat tulevat lähettiläistä, kuvakaappauksista, pilvikansioista tai useista kameroista. Hyvä malli estää vahingossa tapahtuvan vaihdon ja pitää tulosteet jäljitettävissä niiden lähteisiin. + Sisällytä alkuperäinen nimi ja pääte, kun muokatun kopion pitäisi olla tunnistettavissa. + Lisää järjestys, aikaleima, esiasetus tai mitat, kun käsittelet suuria sekoitettuja eriä. + Vältä työnkulkujen korvaamista, kunnes olet varmistanut, että nimeämismalli ei voi törmätä. + Tallenna tai jaa + Valitse pysyvän tiedoston tai väliaikaisen kanavanvaihdon välillä toiseen sovellukseen + Vie oikealla tarkoituksella + Tallenna ja jaa voivat tuntua samanlaisilta, mutta ne ratkaisevat erilaisia ​​ongelmia. Tallentaminen luo tiedoston, jonka löydät myöhemmin. jakaminen lähettää luodun tuloksen Androidin kautta toiseen sovellukseen, eikä se välttämättä jätä kestävää paikallista kopiota. + Käytä Tallenna, kun tulosteen on pysyttävä tunnetussa kansiossa, varmuuskopioitava tai käytettävä myöhemmin uudelleen. + Käytä Jaa-toimintoa nopeisiin viesteihin, sähköpostin liitteisiin, julkaisemiseen sosiaalisessa mediassa tai tulosten lähettämiseen toiselle editorille. + Tallenna ensin, kun vastaanottava sovellus on epäluotettava tai kun epäonnistunut jako olisi ärsyttävää luoda uudelleen. + PDF-sivun koko ja marginaalit + Valitse paperikoko, istuvuus ja hengitystila ennen asiakirjan luomista + Tee kuvasivuista tulostettavia ja luettavia + Kuvista voi tulla hankalia PDF-sivuja, jos niiden muoto ei vastaa valittua paperikokoa. Marginaalit, sovitustila ja sivun suunta päättävät, onko sisältö rajattu, pieni, venytetty vai mukava lukea. + Käytä oikeaa paperikokoa, kun PDF voidaan tulostaa tai lähettää lomakkeelle. + Käytä marginaaleja skannauksissa ja kuvakaappauksissa, jotta teksti ei jää sivun reunaan. + Esikatsele pysty- ja vaakasuuntaisia ​​sivuja yhdessä, kun lähdekuvien suunta on sekoitettu. + Puhdista skannaukset + Paranna paperin reunoja, varjoja, kontrastia, kiertoa ja luettavuutta ennen PDF- tai tekstintunnistusta + Valmistele sivut ennen arkistointia + Skannaus voidaan kaapata teknisesti, mutta silti vaikea lukea. Pienet puhdistusvaiheet ennen PDF-vientiä ovat usein tärkeämpiä kuin pakkausasetukset, erityisesti kuittien, käsinkirjoitettujen muistiinpanojen ja hämärässä valaistujen sivujen kohdalla. + Korjaa perspektiivi ja leikkaa pois pöydän reunat, sormet, varjot ja taustasotku. + Lisää kontrastia varovasti, jotta tekstistä tulee selkeämpää tuhoamatta leimoja, allekirjoituksia tai kynän jälkiä. + Suorita OCR vasta, kun sivu on pystyssä ja luettavissa, ja tarkista sitten tärkeät numerot manuaalisesti. + Pakkaa, harmaasävyt tai korjaa PDF-tiedostoja + Käytä yhden tiedoston PDF-toimintoja kevyempiin, tulostettaviin tai uudelleen rakennettuihin asiakirjakopioihin + Valitse PDF-puhdistustoiminto + PDF-työkalut sisältävät erilliset toiminnot pakkausta, harmaasävymuunnoksia ja korjauksia varten. Pakkaus ja harmaasävy toimivat pääasiassa upotettujen kuvien kautta, kun taas korjaus rakentaa PDF-rakenteen uudelleen; mitään näistä ei pitäisi käsitellä takuukorjauksena jokaiselle asiakirjalle. + Käytä Pakkaa PDF -toimintoa, kun asiakirja sisältää kuvia ja tiedostokoolla on merkitystä jaettavaksi. + Käytä Harmaasävyä, kun asiakirjan pitäisi olla helpompi tulostaa tai kun se ei vaadi värikuvia. + Käytä Korjaa PDF kopiossa, kun asiakirja ei avaudu tai sen sisäinen rakenne on vaurioitunut, ja tarkista sitten uudelleen rakennettu tiedosto. + Poimi kuvia PDF-tiedostoista + Palauta upotetut PDF-kuvat ja pakkaa puretut tulokset arkistoon + Tallenna lähdekuvia asiakirjoista + Extract Images etsii PDF-tiedostosta upotettuja kuvaobjekteja, ohittaa kaksoiskappaleet mahdollisuuksien mukaan ja tallentaa palautetut kuvat luotuun ZIP-arkistoon. Se poimii vain kuvat, jotka on todella upotettu PDF-tiedostoon, ei tekstiä, vektoripiirroksia tai jokaista näkyvää sivua kuvakaappauksena. + Käytä Pura kuvat, kun tarvitset PDF-tiedostoon tallennettuja kuvia, kuten kuvia luettelosta tai skannattuista resursseista. + Käytä sen sijaan PDF-tiedostoa kuviksi tai sivujen purkamista, kun tarvitset kokonaisia ​​sivuja, mukaan lukien tekstin ja asettelun. + Jos työkalu ei ilmoita upotettuja kuvia, sivu voi olla teksti-/vektorisisältöä tai kuvia ei ehkä tallenneta poimittavissa olevina objekteina. + Metatiedot, litistys ja tulostus + Muokkaa asiakirjan ominaisuuksia, rasteroi sivuja tai valmistele mukautettuja tulostusasetteluja tarkoituksella + Valitse lopullisen asiakirjan toiminto + PDF-metatiedot, tasoitus ja tulostuksen valmistelu ratkaisevat erilaisia ​​loppuvaiheen ongelmia. Metadata hallitsee asiakirjan ominaisuuksia, Flatten PDF rasteroi sivut vähemmän muokattavaksi tulosteiksi ja Print PDF valmistelee uuden asettelun sivun koon, suunnan, marginaalin ja sivuja arkilta -säätimillä. + Käytä metatietoja, kun tekijällä, otsikolla, avainsanoilla, tuottajalla tai yksityisyyden puhdistamisella on merkitystä. + Käytä Flatten PDF -toimintoa, kun näkyvän sivun sisällön pitäisi olla vaikeampaa muokata erillisinä PDF-objekteina. + Käytä Tulosta PDF -toimintoa, kun tarvitset mukautetun sivukoon, suunnan, marginaalit tai useita sivuja arkille. + Valmistele kuvat tekstintunnistusta varten + Käytä rajausta, kiertoa, kontrastia, kohinaa ja skaalausta ennen kuin pyydät tekstintunnistusta lukemaan tekstiä + Anna OCR:lle puhtaampia pikseleitä + OCR-virheet johtuvat usein syöttökuvasta eikä OCR-moottorista. Vino sivut, alhainen kontrasti, epäterävyys, varjot, pieni teksti ja sekalaiset taustat heikentävät tunnistamisen laatua. + Rajaa tekstialueelle ja kierrä kuvaa niin, että viivat ovat vaakasuorassa ennen tunnistamista. + Lisää kontrastia tai muunna puhtaammaksi mustavalkoiseksi vain silloin, kun kirjaimia on helpompi lukea. + Muuta pienen tekstin kokoa ylöspäin kohtuullisesti, mutta vältä aggressiivista teroitusta, joka luo vääriä reunoja. + Tarkista QR-sisältö + Tarkista linkit, Wi-Fi-kirjautumistiedot, yhteystiedot ja toiminnot ennen kuin luotat koodiin + Käsittele dekoodattua tekstiä epäluotettavana syötteenä + QR-koodi voi piilottaa URL-osoitteen, yhteystietokortin, Wi-Fi-salasanan, kalenteritapahtuman, puhelinnumeron tai pelkkää tekstiä. Näkyvä etiketti koodin lähellä ei välttämättä vastaa todellista hyötykuormaa, joten tarkista dekoodattu sisältö ennen kuin ryhdyt siihen. + Tarkista koko purettu URL-osoite ennen sen avaamista, erityisesti lyhennetyt tai tuntemattomat verkkotunnukset. + Ole varovainen Wi-Fi-, yhteystieto-, kalenteri-, puhelin- ja tekstiviestikoodien kanssa, koska ne voivat laukaista arkaluonteisia toimintoja. + Kopioi sisältö ensin, kun sinun on vahvistettava se muualla sen sijaan, että avaat sen välittömästi. + Luo QR-koodeja + Luo koodeja pelkästä tekstistä, URL-osoitteista, Wi-Fi-verkosta, yhteystiedoista, sähköpostista, puhelimesta, tekstiviestistä ja sijaintitiedoista + Rakenna oikea QR-hyötykuorma + QR-näyttö voi luoda koodeja syötetystä sisällöstä sekä skannata olemassa olevia. Strukturoidut QR-tyypit auttavat koodaamaan tietoja muiden sovellusten ymmärtämään muotoon, kuten Wi-Fi-kirjautumistiedot, yhteystiedot, sähköpostikentät, puhelinnumerot, tekstiviestien sisältö, URL-osoitteet tai maantieteelliset koordinaatit. + Valitse pelkkä teksti yksinkertaisia ​​muistiinpanoja varten tai käytä jäsenneltyä tyyppiä, kun koodin pitäisi avautua Wi-Fi-, yhteystieto-, URL-, sähköposti-, puhelin-, tekstiviesti- tai sijaintitietona. + Tarkista jokainen kenttä ennen luodun koodin jakamista, koska näkyvä esikatselu heijastaa vain syöttämääsi hyötykuormaa. + Testaa tallennettu QR-koodi skannerilla, kun se tulostetaan, julkaistaan ​​tai kun muut ihmiset käyttävät sitä. + Valitse tarkistussummatila + Laske hajautusarvot tiedostoista tai tekstistä, vertaa yhtä tiedostoa tai tarkista useita tiedostoja erässä + Tarkista sisältö, älä ulkonäkö + Checksum Toolsissa on erilliset välilehdet tiedostojen tiivistämiseen, tekstin tiivistämiseen, tiedoston vertaamiseen tunnettuun tiivisteeseen ja erävertailulle. Tarkistussumma todistaa valitun algoritmin tavukohtaisen identiteetin; se ei todista, että kaksi kuvaa vain näyttävät samanlaisilta. + Käytä Laske-toimintoa tiedostoille ja Text Hashia kirjoitetuille tai liitetyille tekstitiedoille. + Käytä Vertaa, kun joku antaa sinulle odotetun tarkistussumman yhdelle tiedostolle. + Käytä Erävertailua, kun monet tiedostot tarvitsevat hajautusarvoja tai vahvistusta yhdellä kertaa, ja pidä sitten valittu algoritmi johdonmukaisena. + Leikepöydän yksityisyys + Käytä automaattisia leikepöydän ominaisuuksia paljastamatta vahingossa yksityisiä kopioituja tietoja + Pidä kopioitu sisältö tarkoituksella + Leikepöydän automatisointi on kätevää, mutta kopioitu teksti voi sisältää salasanoja, linkkejä, osoitteita, tunnuksia tai yksityisiä muistiinpanoja. Käsittele leikepöydälle perustuvaa syöttöä nopeusominaisuudena, jonka pitäisi vastata tottumuksiasi ja yksityisyyttäsi koskevia odotuksiasi. + Poista automaattinen leikepöydän käyttö käytöstä, jos yksityistä tekstiä tulee yllättäen työkaluihin. + Käytä leikepöydälle tallentamista vain silloin, kun haluat tietoisesti tuloksen välttävän tallennusta. + Tyhjennä tai vaihda leikepöytä, kun olet käsitellyt arkaluontoista tekstiä, QR-tietoja tai Base64-hyötykuormia. + Värimuodot + Ymmärrä HEX-, RGB-, HSV-, alfa- ja kopioitavat väriarvot ennen liittämistä muualle + Kopioi kohteen odottama muoto + Sama näkyvä väri voidaan kirjoittaa usealla tavalla. Suunnittelutyökalu, CSS-kenttä, Android-väriarvo tai kuvankäsittelyohjelma voi odottaa erilaista järjestystä, alfakäsittelyä tai värimallia. + Käytä HEX-muotoa liittäessäsi verkko-, suunnittelu- tai teemakenttiin, jotka edellyttävät kompakteja värikoodeja. + Käytä RGB:tä tai HSV:tä, kun haluat säätää kanavia, kirkkautta, kylläisyyttä tai sävyä manuaalisesti. + Tarkista alfa erikseen, kun läpinäkyvyydellä on merkitystä, koska jotkut kohteet jättävät sen huomiotta tai käyttävät eri järjestystä. + Selaa värikirjastoa + Hae nimettyjä värejä nimellä tai HEX ja pidä suosikit lähellä yläosaa + Etsi uudelleenkäytettäviä nimettyjä värejä + Värikirjasto lataa nimetyn värikokoelman, lajittelee sen nimen mukaan, suodattaa värin nimen tai HEX-arvon mukaan ja tallentaa suosikkivärit, jotta tärkeät merkinnät ovat helpommin tavoitettavissa. Se on hyödyllinen, kun tarvitset oikean nimen värin sen sijaan, että otat näytteitä kuvasta. + Hae värin nimellä, kun tiedät perheen, kuten punainen, sininen, liuskekivi tai minttu. + Tee HEX-haku, kun sinulla on jo numeroväri ja haluat löytää vastaavia tai lähellä olevia nimettyjä merkintöjä. + Merkitse usein värit suosikeiksi, jotta ne kulkevat yleisen kirjaston edellä selattaessa. + Käytä mesh-gradienttikokoelmia + Lataa käytettävissä olevat mesh-gradienttiresurssit ja jaa valitut kuvat + Käytä etäverkkogradienttisisältöjä + Mesh Gradients voi ladata etäresurssikokoelman, ladata puuttuvia liukuvärikuvia, näyttää latauksen edistymisen ja jakaa valitut liukuvärit. Saatavuus riippuu verkkoyhteydestä ja etäresurssivarastosta, joten tyhjä luettelo tarkoittaa yleensä sitä, että resursseja ei ollut vielä saatavilla. + Avaa Mesh Gradients liukuvärityökaluista, kun haluat valmiita mesh-gradienttikuvia. + Odota resurssien latautumista tai latauksen edistymistä, ennen kuin oletat, että kokoelma on tyhjä. + Valitse ja jaa tarvitsemasi liukuvärit tai palaa Gradient Makeriin, kun haluat luoda mukautetun liukuvärin manuaalisesti. + Luotu omaisuuden resoluutio + Valitse tulosteen koko ennen kuin luot liukuvärejä, kohinaa, taustakuvia, SVG-tyyppistä taidetta tai pintakuvioita + Luo tarvitsemassasi koossa + Luoduilla kuvilla ei ole alkuperäistä kameraa, johon voisi palata. Jos tulos on liian pieni, myöhempi skaalaus voi pehmentää kuvioita, siirtää yksityiskohtia tai muuttaa resurssien ruutuja ja pakkaamista. + Aseta ensin mitat lopulliselle käyttötapaukselle, kuten taustakuva, kuvake, tausta, tuloste tai peittokuva. + Käytä suurempia kokoja tekstuureille, joita voidaan rajata, zoomata tai käyttää uudelleen useissa asetteluissa. + Vie testiversiot ennen valtavia tulosteita, koska prosessin yksityiskohdat voivat muuttaa pakkaustavan toimintaa. + Vie nykyiset taustakuvat + Hae käytettävissä olevat Koti-, Lukitus- ja sisäänrakennetut taustakuvat laitteesta + Tallenna tai jaa asennettuja taustakuvia + Wallpapers Export lukee taustakuvat, jotka Android paljastaa järjestelmän taustakuvasovellusliittymien kautta. Laitteesta, Android-versiosta, luvista ja koontiversiosta riippuen Home, Lock tai sisäänrakennetut taustakuvat voivat olla saatavilla erikseen tai ne voivat puuttua. + Avaa Wallpapers Export ladataksesi taustakuvat, joita järjestelmä sallii sovelluksen lukea. + Valitse käytettävissä olevat Koti-, Lukitus- tai sisäänrakennetut taustakuvat, jotka haluat tallentaa, kopioida tai jakaa. + Jos taustakuva puuttuu tai ominaisuus ei ole käytettävissä, se on yleensä järjestelmän, luvan tai koontiversion rajoitus eikä muokkausasetus. + Corners Animaatio Kaasu + Kopioi väri muodossa + HEX + nimi + Muotokuva mattamalli nopeaan henkilöleikkaukseen pehmeämmällä hiuksilla ja reunojen käsittelyllä. + Nopea parannus hämärässä Zero-DCE++ käyräestimaatiolla. + Restormer-kuvanpalautusmalli saderaitojen ja märän sään esineiden vähentämiseen. + Dokumentoi vääristymätön malli, joka ennustaa koordinaattiruudukon ja muokkaa kaarevat sivut tasaisemmiksi skannauksiksi. + Liikkeen kestoasteikko + Ohjaa sovelluksen animaationopeutta: 0 poistaa liikkeen käytöstä, 1 on normaali, korkeammat arvot hidastavat animaatioita + Näytä viimeisimmät työkalut + Näytä äskettäin käytettyjen työkalujen nopea pääsy päänäytölle + Käyttötilastot + Tutustu sovellusten avauksiin, työkalujen käynnistämiseen ja eniten käytettyihin työkaluihisi + Sovellus avautuu + Työkalu avautuu + Viimeinen työkalu + Onnistuneet tallennukset + Aktiivisuusputki + Tiedot tallennettu + Huippumuoto + Käytetyt työkalut + %1$d avautuu • %2$s + Ei vielä käyttötilastoja + Avaa muutama työkalu, niin ne näkyvät täällä automaattisesti + Käyttötilastot tallennetaan vain paikallisesti laitteellesi. ImageToolbox käyttää niitä vain näyttääkseen henkilökohtaisen toimintasi, suosikkityökalusi ja tallennetut tiedot + editori muokkaa valokuvaa kuvaa retusoi säädä + kokoa muuntaa asteikko resoluutio mitat leveys korkeus jpg jpeg png webp pakkaus + pakkaa koko paino tavua mb kb vähentää laatua optimoida + rajaus leikkaa leikkauskuvasuhde + suodatintehoste värinkorjaus säädä lut maski + piirrä sivellin, lyijykynä, merkitse merkintä + salaus salaa purkaa salasanan salaus + taustan poisto poistaa poistoleikkauksen läpinäkyvä alfa + esikatselu katselu galleria tarkastaa auki + ommel yhdistäminen yhdistää panoraama pitkä kuvakaappaus + url web lataa Internet-linkin kuva + picker pipetti näyte hex rgb väri + paletin värit väritilkut ote hallitseva + exif-metatietojen yksityisyys poista nauha puhtaasta gps-sijainnista + vertaa eroa ennen jälkeen + rajoittaa kokoa max min megapikseliä mitat kiinnike + pdf-dokumenttisivujen työkalut + ocr teksti tunnistaa otteen skannaus + gradientti taustaväri mesh + vesileima logo teksti leima peitto + gif-animaatio animoituja muuntaa kehyksiä + apng animaatio animoitu png muuntaa kehyksiä + zip-arkisto pakkauspaketti pura tiedostot + jxl jpeg xl muuntaa jpg-kuvamuotoa + svg vector trace convert xml + muoto muuntaa jpg jpeg png webp heic avif jxl bmp + asiakirjaskanneri skannaa paperin perspektiivirajaus + qr-viivakoodin skanneri + pinoaminen peittokuvan keskimääräinen mediaaniastrovalokuvaus + jakaa laatat ruudukon siivuosat + värimuunnos hex rgb hsl hsv cmyk lab + webp muuntaa animoituja kuvamuotoja + noise generator satunnainen tekstuuri grain + kollaasi ruudukon asettelu yhdistää + merkintätasot merkitsevät peittokuvan muokkausta + base64 koodaa datan uri + tarkistussumma hash md5 sha sha1 sha256 vahvistaa + mesh gradientti tausta + exif-metatiedot muokkaa gps-päiväkameraa + leikata jaetut ruudukon kappaleet laatat + äänikannen albumin taiteen musiikin metatiedot + taustakuvan vienti tausta + ascii-tekstitaidehahmoja + ai ml hermo parantaa korkeatasoista segmenttiä + värikirjaston materiaalipaletti nimeltä värit + Shader glsl fragmenttiefekti studio + pdf yhdistäminen yhdistää liittyä + pdf jakaa erilliset sivut + pdf kääntää sivuja suunta + pdf järjestää uudelleen järjestellä sivut lajitella + pdf sivunumeroiden sivutus + pdf ocr teksti tunnistaa haettavan otteen + pdf vesileima leiman logo teksti + pdf-allekirjoituksen piirtäminen + pdf suojata salasanalla salauslukko + pdf avata salasana purkaa salaus poistaa suojauksen + pdf-pakkaus pienentää kokoa optimoida + Pdf harmaasävyinen mustavalkoinen yksivärinen + pdf-korjaus korjata rikki + Pdf-metatietojen ominaisuudet exif-tekijän otsikko + pdf poistaa poista sivut + pdf rajaus marginaalit + pdf litistävät huomautukset muodostavat tasoja + pdf ote kuvia kuvia + pdf-zip-arkistopakkaus + pdf-tulostin + pdf-esikatseluohjelma lukea + kuvat pdf-muotoon muuntaa jpg jpeg png-dokumentti + pdf kuviksi sivuille viedä jpg png + pdf-merkinnät kommentit poista puhdas + Neutraali vaihtoehto + Virhe + Varjo + Hampaan korkeus + Vaakasuuntainen hammasalue + Pystysuuntainen hammasalue + Yläreuna + Oikea reuna + Alareuna + Vasen reuna + Revitty reuna + Nollaa käyttötilastot + Työkalu avautuu, tilastot, viiva, tallennetut tiedot ja huippumuoto palautetaan. Sovellusten avaukset pysyvät ennallaan. + Audio + Tiedosto + Vie profiilit + Tallenna nykyinen profiili + Poista vientiprofiili + Haluatko poistaa vientiprofiilin \\"%1$s\\"? + Piirrä reunus + Näytä ohut reunus piirustus- ja pyyhkimiskankaan ympärillä + Aaltoileva + Pyöristetyt käyttöliittymäelementit pehmeällä aaltoilevalla reunalla + Kauha + Lovi + Pyöristetyt kulmat sisäänpäin kaiverrettu + Porrastetut kulmat kaksoisleikkauksella + Paikkamerkki + Käytä {filename} lisätäksesi alkuperäisen tiedostonimen ilman päätettä + Leimahdus + Perusmäärä + Sormuksen määrä + Säteen määrä + Renkaan leveys + Vääristää perspektiiviä + Java ulkoasu ja tuntuma + Leikkaus + X kulma + ja kulma + Muuta tulosteen kokoa + Vesipisara + Aallonpituus + Vaihe + High Pass + Värillinen naamio + Kromi + Liuota + Pehmeys + Palaute + Linssin sumennus + Matala syöttö + Korkea syöttö + Matala teho + Korkea teho + Valotehosteet + Puskurin korkeus + Pehmeyttä + Ripple + X amplitudi + Y amplitudi + X aallonpituus + Y aallonpituus + Mukautuva sumennus + Tekstuurien luominen + Luo erilaisia ​​proseduureja ja tallenna ne kuvina + Tekstuurityyppi + Bias + Aika + Loistaa + Dispersio + Näytteitä + Kulmakerroin + Gradienttikerroin + Ruudukkotyyppi + Etäisyys teho + Asteikko Y + Sumeus + Perustyyppi + Turbulenssitekijä + Skaalaus + Iteraatiot + Sormukset + Harjattua metallia + Kaustiset aineet + Mobiili + Shakkilauta + fBm + Marmori + Plasma + Peitto + Puu + satunnainen + Neliö + Kuusikulmainen + Kahdeksankulmainen + Kolmion muotoinen + Uurteinen + VL Melu + SC Melu + Viimeaikaiset + Ei vielä äskettäin käytettyjä suodattimia + tekstuurigeneraattori harjattu metalli kaustiikka solu shakkilauta marmori plasmapeitto puutiili naamiointi solupilvi halkeama kangas lehdet hunajakenno jäälaava sumu paperi ruoste hiekka savu kivi maasto topografia vesi aaltoilu + Tiili + Naamiointi + Cell + Pilvi + Crack + Kangas + Lehvistö + Hunajakenno + Jäätä + Laava + Tähtisumu + Paperi + Ruoste + Hiekka + Savu + Kivi + Maasto + Topografia + Water Ripple + Advanced Wood + Laastin leveys + Epäsäännöllisyys + Viiste + Epätasaisuus + Ensimmäinen kynnys + Toinen kynnys + Kolmas kynnys + Reunojen pehmeys + Reunuksen leveys + Kattavuus + Yksityiskohta + Syvyys + Haaroittuminen + Vaakasuuntaiset langat + Pystysuuntaiset kierteet + Fuzz + Suonet + Valaistus + Halkeaman leveys + Frost + Virtaus + Kuori + Pilvien tiheys + Tähdet + Kuidun tiheys + Kuitujen vahvuus + Tahrat + Korroosio + Pitting + Hiutaleet + Dyynitaajuus + Tuulen kulma + Ripples + Wisps + Suonen asteikko + Veden taso + Vuoren taso + Eroosio + Lumen taso + Rivien määrä + Viivan paksuus + Varjostus + Huokosten väri + Laastin väri + Tumman tiilen väri + Tiilen väri + Korosta väri + Tumma väri + Metsän väri + Maan väri + Hiekka väri + Taustaväri + Solun väri + Reunan väri + Taivaan väri + Varjon väri + Vaalea väri + Pinnan väri + Variantti väri + Halkeaman väri + Loimiväri + Kuteen väri + Tumma lehtien väri + Lehden väri + Reunuksen väri + Hunaja väri + Syvä väri + Jään väri + Pakkanen väri + Kuoren väri + Pesun väri + Hehkuva väri + Avaruuden väri + Violetti väri + Sininen väri + Pohjaväri + Kuitujen väri + Tahran väri + Metallin väri + Väri tumma ruoste + Ruosteen väri + Oranssi väri + Savun väri + Suonen väri + Alamaan väri + Veden väri + Kiven väri + Lumen väri + Matala väri + Korkea väri + Viivan väri + Matala väri + Ruoho + Lika + Nahka + Betoni + Asfaltti + Moss + Palo + Aurora + Oil Slick + Akvarelli + Abstrakti virtaus + Opaali + Damaskoksen terästä + Salama + Sametti + Musteen marmorointi + Holografinen kalvo + Bioluminesenssi + Kosminen Vortex + Lava lamppu + Tapahtumahorisontti + Fractal Bloom + Kromaattinen tunneli + Koronanpimennys + Outo houkuttelija + Ferrofluidinen kruunu + Supernova + Iiris + Riikinkukon sulka + Nautilus Shell + Rengastettu planeetta + Terän tiheys + Terän pituus + Tuuli + hajanaisuus + Möykkyjä + Kosteus + Kivet + Rypyt + huokoset + Aggregaatti + Halkeamia + Terva + Käyttää + Täpliä + Kuidut + Liekin taajuus + Savu + Intensiteetti + Nauhat + Bändit + Irisenssi + Pimeys + Kukkii + Pigmentti + reunat + Paperi + Diffuusio + Symmetria + Terävyys + Värileikkiä + Maitoisuus + Kerrokset + Taitettava + Kiillottaa + Haarat + Suunta + Kiilto + Taittuu + höyhenet + Musteen tasapaino + Spektri + Rypyt + Diffraktio + Aseet + Kierre + Ytimen hehku + Blobs + Levyn kallistus + Horisontin koko + Levyn leveys + Linssi + Terälehdet + Kiemura + Filigraanityö + Fasetit + Kaarevuus + Kuun koko + Koronan koko + Säteet + Timanttisormus + Lobes + Ratatiheys + Paksuus + Piikit + Piikin pituus + Rungon koko + Metallinen + Iskun säde + Kuoren leveys + Heitetty ulos + Pupillin koko + Iriksen koko + Värin vaihtelu + Catchlight + Silmien koko + Piikkien tiheys + Käännöksiä + Chambers + Avaaminen + Harjanteet + Helmivärjäys + Planeetan koko + Renkaan kallistus + Renkaan leveys + Tunnelma + Lian väri + Väri tumma ruoho + Ruohon väri + Kärjen väri + Tumma maan väri + Kuiva väri + Kiven väri + Väri nahka + Betonin väri + Tervan väri + Asfaltin väri + Kiven väri + Pölyväri + Maaperän väri + Tumma sammalväri + Sammaleen väri + Punainen väri + Ytimen väri + Vihreä väri + Syaani väri + Magenta väri + Kultainen väri + Paperin väri + Pigmentin väri + Toissijainen väri + Ensimmäinen väri + Toinen väri + Pinkki väri + Väri tumma teräs + Teräksen väri + Väri vaalea teräs + Oksidin väri + Halo väri + Pultin väri + Väri sametti + Kiiltävä väri + Sinisen musteen väri + Punaisen musteen väri + Tumman musteen väri + Hopean värinen + Keltainen väri + Kudosten väri + Levyn väri + Kuuma väri + Linssin väri + Ulkoinen väri + Sisäinen väri + Koronan väri + Kylmä väri + Lämmin väri + Pilven väri + Liekin väri + Höyhenen väri + Kuoren väri + Helmen väri + Planeetan väri + Sormuksen väri + Poista alkuperäiset tiedostot tallennuksen jälkeen + Vain onnistuneesti käsitellyt lähdetiedostot poistetaan + Alkuperäiset tiedostot poistettu: %1$d + Alkuperäisten tiedostojen poistaminen epäonnistui: %1$d + Alkuperäiset tiedostot poistettu: %1$d, epäonnistuneet: %2$d + Arkkieleet + Salli laajennettujen asetusarkkien vetäminen + Chroma alinäytteenotto + Bittinen syvyys + Häviötön + %1$d-bittiä + Nimeä erä uudelleen + Nimeä useita tiedostoja uudelleen käyttämällä tiedostonimimalleja + erä uudelleennimeä tiedostot tiedostonimi kuvio sekvenssi päivämääräpääte + Valitse uudelleennimettävät tiedostot + Tiedostonimen malli + Nimeä uudelleen + Päivämäärän lähde + EXIF-päivämäärä otettu + Tiedoston muokkauspäivämäärä + Tiedoston luontipäivämäärä + Nykyinen päivämäärä + Manuaalinen päivämäärä + Manuaalinen päivämäärä + Manuaalinen aika + Valittu päivämäärä ei ole käytettävissä joillekin tiedostoille. Heille käytetään nykyistä päivämäärää. + Anna tiedostonimimalli + Malli tuottaa virheellisen tai liian pitkän tiedostonimen + Malli tuottaa päällekkäisiä tiedostonimiä + Kaikki tiedostonimet vastaavat jo mallia + Tämä malli käyttää tunnuksia, jotka eivät ole käytettävissä erän uudelleennimeämisessä + Nimi pysyy ennallaan + Tiedostojen uudelleennimeäminen onnistui + Joitakin tiedostoja ei voitu nimetä uudelleen + Lupaa ei myönnetty + Käyttää alkuperäistä tiedostonimeä ilman päätettä, mikä auttaa sinua pitämään lähteen tunnisteen ennallaan. Tukee myös viipalointia \\o{start:end}, translitterointia \\o{t} ja korvaa \\o{s/old/new/}. + Automaattisesti kasvava laskuri. Tukee mukautettua muotoilua: \\c{padding} (esim. \\c{3} -&gt; 001), \\c{aloitus:vaihe} tai \\c{aloitus:vaihe:padding}. + Pääkansion nimi, jossa alkuperäinen tiedosto sijaitsi. + Alkuperäisen tiedoston koko. Tukee yksiköitä: \\z{b}, \\z{kb}, \\z{mb} tai vain \\z ihmisen luettavassa muodossa. + Luo satunnaisen UUID-tunnisteen (Universally Unique Identifier) ​​varmistaakseen tiedostonimen ainutlaatuisuuden. + Tiedostojen uudelleennimeämistä ei voi kumota. Varmista, että uudet nimet ovat oikein ennen kuin jatkat. + Vahvista uudelleennimeäminen + Sivuvalikon asetukset + Valikko asteikko + Valikko alfa + Automaattinen valkotasapaino + Leikkaaminen + Tarkistaa piilotettuja vesileimoja + Varastointi + Välimuistin raja + Tyhjennä välimuisti automaattisesti, kun se kasvaa tämän koon yli + Puhdistusväli + Kuinka usein tarkistaa, pitäisikö välimuisti tyhjentää + Sovelluksen käynnistyessä + 1 päivä + 1 viikko + 1 kuukausi + Tyhjennä sovellusvälimuisti automaattisesti valitun rajan ja aikavälin mukaisesti + Siirrettävä satoalue + Mahdollistaa koko rajausalueen siirtämisen vetämällä sen sisällä + Yhdistä GIF + Yhdistä useita GIF-leikkeitä yhdeksi animaatioksi + GIF-leikkeiden tilaus + Käänteinen + Pelaa toisinpäin + Bumerangi + Toista eteenpäin ja sitten taaksepäin + Normalisoi kehyksen koko + Skaalaa kehykset isoimpaan klipsiin sopivaksi; sammuta säilyttääksesi alkuperäisen mittakaavan + Leikkeiden välinen viive (ms) + Kansio + Valitse kaikki kuvat kansiosta ja sen alikansioista + Kaksoiskappaleetsin + Etsi tarkat kopiot ja visuaalisesti samankaltaiset kuvat + kaksoiskappaleet etsivät samanlaisia ​​kuvia tarkat kopiot valokuvat puhdistus tallennustila dHash SHA-256 + Valitse kuvat löytääksesi kaksoiskappaleet + Samankaltaisuusherkkyys + Tarkat kopiot + Samanlaisia ​​kuvia + Valitse kaikki tarkat kaksoiskappaleet + Valitse kaikki paitsi suositellut + Kopioita ei löytynyt + Yritä lisätä kuvia tai lisätä samankaltaisuusherkkyyttä + Ei voitu lukea %1$d kuvaa + %1$s • %2$s palautettavissa + %1$d ryhmät • %2$s palautettavissa + %1$d valittu • %2$s + Tätä ei voi kumota. Android voi pyytää sinua vahvistamaan poiston järjestelmävalintaikkunassa. + Ei löydy + Aloita siirtymällä + Siirrä loppuun + \ No newline at end of file diff --git a/core/resources/src/main/res/values-fil/strings.xml b/core/resources/src/main/res/values-fil/strings.xml new file mode 100644 index 0000000..11bdb23 --- /dev/null +++ b/core/resources/src/main/res/values-fil/strings.xml @@ -0,0 +1,3740 @@ + + + + + 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 + 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 + 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. + 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. + 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 + Pagbaluktot ng Kamalayan sa Nilalaman + Walang sapat na memorya upang makumpleto ang pagkilos na ito. Pakisubukang gumamit ng mas maliit na larawan, isara ang iba pang app, o i-restart ang app. + Parallel Workers + Ang mga larawang ito ay hindi na-save dahil ang mga na-convert na file ay mas malaki kaysa sa orihinal. Suriin ang listahang ito bago tanggalin ang mga orihinal na larawan. + Nilaktawan ang mga file: %1$s + Mga Log ng App + Tingnan ang mga log ng app upang makita ang mga isyu + Mga paborito sa mga nakagrupong tool + Nagdaragdag ng mga paborito bilang tab kapag ang mga tool ay pinagsama-sama ayon sa uri + Ipakita ang paborito bilang huli + Inilipat ang tab ng mga paboritong tool hanggang sa dulo + Pahalang na espasyo + Vertical spacing + Paatras na enerhiya + Gumamit ng simpleng gradient magnitude na mapa ng enerhiya sa halip na pasulong na algorithm ng enerhiya + Alisin ang masked area + Ang mga tahi ay dadaan sa nakamaskara na rehiyon, na aalisin lamang ang napiling lugar sa halip na protektahan ito + VHS NTSC + Mga advanced na setting ng NTSC + Extra analog tuning para sa mas malakas na VHS at broadcast artifact + Nagdugo ang Chroma + Pagsuot ng tape + Pagsubaybay + Luma smear + Nagri-ring + niyebe + Gamitin ang field + Uri ng filter + Ipasok ang luma filter + Input ang chroma lowpass + Demodulasyon ng Chroma + Paglipat ng yugto + Phase offset + Paglipat ng ulo + Taas ng pagpapalit ng ulo + Head switching offset + Paglipat ng ulo + Posisyon sa gitnang linya + Jitter sa kalagitnaan ng linya + Pagsubaybay sa taas ng ingay + Pagsubaybay sa alon + Pagsubaybay sa niyebe + Pagsubaybay sa snow anisotropy + Ingay sa pagsubaybay + Pagsubaybay sa intensity ng ingay + Pinagsama-samang ingay + Pinagsama-samang dalas ng ingay + Pinagsamang intensity ng ingay + Composite na detalye ng ingay + Dalas ng pag-ring + Lakas ng ring + Luma ingay + Luma dalas ng ingay + Luma intensity ng ingay + Luma ingay detalye + Chroma ingay + Dalas ng ingay ng Chroma + Sidhi ng ingay ng Chroma + Detalye ng ingay ng Chroma + Tindi ng niyebe + Anisotropy ng niyebe + Chroma phase ingay + Error sa yugto ng Chroma + Pahalang na pagkaantala ng Chroma + Vertical ang pagkaantala ng Chroma + Bilis ng VHS tape + Pagkawala ng chroma ng VHS + Patalasin ng VHS ang intensity + Ang dalas ng pagpapatalas ng VHS + VHS edge wave intensity + Bilis ng wave ng gilid ng VHS + VHS edge wave frequency + Detalye ng wave ng gilid ng VHS + Output chroma lowpass + I-scale nang pahalang + I-scale patayo + Scale factor X + Scale factor Y + Nakikita ang mga karaniwang watermark ng imahe at pinipintura ang mga ito gamit ang LaMa. Awtomatikong nagda-download ng detection at inpainting na mga modelo + Deuteranopia + Palawakin ang Larawan + Pag-alis ng background batay sa segmentation ng object gamit ang YOLO v11 Segmentation + Ipakita ang anggulo ng linya + Ipinapakita ang kasalukuyang pag-ikot ng linya sa mga degree habang gumuhit + Mga animated na Emoji + Ipakita ang available na emoji bilang mga animation + I-save sa Orihinal na Folder + I-save ang mga bagong file sa tabi ng orihinal na file sa halip na ang napiling folder + Watermark PDF + Hindi sapat ang memorya + Ang imahe ay malamang na masyadong malaki upang iproseso sa device na ito, o ang system ay naubusan ng magagamit na memorya. Subukang bawasan ang resolution ng larawan, isara ang iba pang app, o pumili ng mas maliit na file. + Debug Menu + Menu para subukan ang mga function ng app, hindi ito nilayon na lumabas sa production release + Provider + Nangangailangan ang PaddleOCR ng mga karagdagang modelo ng ONNX sa iyong device. Gusto mo bang mag-download ng %1$s data? + Pangkalahatan + Koreano + Latin + Silangang Slavic + Thai + Griyego + Ingles + Cyrillic + Arabic + Devanagari + Tamil + Telugu + Shader + Preset ng shader + Walang napiling shader + Shader Studio + Gumawa, mag-edit, mag-validate, mag-import, at mag-export ng mga custom na fragment shader + Naka-save na mga shader + Buksan ang mga naka-save na shader, i-duplicate, i-export, ibahagi, o tanggalin + Bagong shader + Shader file + .itshader JSON + %1$d params + Pinagmulan ng shader + Isulat lamang ang katawan ng void main(). Gamitin ang textureCoordinate para sa UV coordinates at inputImageTexture bilang source sampler. + Mga pag-andar + Ang mga opsyonal na function ng helper, constants, at structs ay ipinasok bago ang void main(). Ang mga uniporme ay nabuo mula sa params. + Magdagdag ng mga uniporme dito kapag kailangan ng shader ng mga nae-edit na halaga. + I-reset ang shader + Iki-clear ang kasalukuyang shader draft + Di-wastong shader + Hindi sinusuportahang bersyon ng shader %1$d. Sinusuportahang bersyon: %2$d. + Hindi dapat blangko ang pangalan ng shader. + Hindi dapat blangko ang pinagmulan ng shader. + Ang pinagmulan ng shader ay naglalaman ng mga hindi sinusuportahang character: %1$s. Gamitin lang ang ASCII GLSL source. + Ang parameter na \\\"%1$s\\\" ay idineklara nang higit sa isang beses. + Hindi dapat blangko ang mga pangalan ng parameter. + Gumagamit ang parameter na \\\"%1$s\\\" ng nakareserbang pangalan ng GPUImage. + Ang parameter na \\\"%1$s\\\" ay dapat na isang wastong GLSL identifier. + Ang parameter na \\\"%1$s\\\" ay may %2$s uri ng halaga na \\\"%3$s\\\", inaasahang \\\"%4$s\\\". + Hindi matukoy ng parameter na \\\"%1$s\\\" ang min o max para sa mga halaga ng bool. + Ang parameter na \\\"%1$s\\\" ay may min na mas malaki kaysa sa max. + Ang parameter na \\\"%1$s\\\" ay mas mababa sa min. + Ang parameter na \\\"%1$s\\\" ay mas malaki kaysa sa max. + Dapat ideklara ng shader ang \\\"uniform sampler2D %1$s;\\\". + Dapat ideklara ng shader ang \\\"varying vec2 %1$s;\\\". + Dapat tukuyin ng shader ang \\\"void main()\\\". + Dapat magsulat ng kulay ang shader sa gl_FragColor. + Ang ShaderToy mainImage shaders ay hindi suportado. Gamitin ang void main() na kontrata ng GPUImage. + Ang animated na ShaderToy uniform na \\\"iTime\\\" ay hindi suportado. + Ang animated na ShaderToy uniform na \\\"iFrame\\\" ay hindi suportado. + Hindi sinusuportahan ang unipormeng ShaderToy na \\\"iResolution\\\". + Hindi sinusuportahan ang mga texture ng ShaderToy iChannel. + Ang mga panlabas na texture ay hindi suportado. + Hindi sinusuportahan ang mga external na texture extension. + Ang mga parameter ng Libretro shader ay hindi suportado. + Isang input texture lang ang sinusuportahan. Alisin ang \\\"uniform %1$s %2$s;\\\". + Dapat ideklara ang parameter na \\\"%1$s\\\" bilang \\\"uniporme %2$s %1$s;\\\". + Ang parameter na \\\"%1$s\\\" ay idineklara bilang \\\"uniporme %2$s %1$s;\\\", inaasahang \\\"uniporme %3$s %1$s;\\\". + Walang laman ang shader file. + Ang shader file ay dapat na .itshader JSON object na may bersyon, pangalan, at shader na mga field. + Ang shader file ay hindi wastong JSON o hindi tumutugma sa .itshader na format. + Kinakailangan ang field na \\\"%1$s\\\". + Kinakailangan ang %1$s. + %1$s \\\"%2$s\\\" ay hindi suportado. Mga sinusuportahang uri: %3$s. + Kinakailangan ang %1$s para sa mga parameter na \\\"%2$s\\\". + Ang %1$s ay dapat na isang may hangganang numero. + Dapat ay isang integer ang %1$s. + Dapat totoo o mali ang %1$s. + Ang %1$s ay dapat na isang color string, RGB/RGBA array, o color object. + Dapat gumamit ang %1$s ng #RRGGBB o #RRGGBBAA na format. + Ang %1$s ay dapat maglaman ng wastong hexadecimal na mga channel ng kulay. + Ang %1$s ay dapat maglaman ng 3 o 4 na color channel. + Ang %1$s ay dapat nasa pagitan ng 0 at 255. + Ang %1$s ay dapat na isang array na may dalawang numero o isang bagay na may x at y. + Ang %1$s ay dapat maglaman ng eksaktong 2 numero. + Duplicate + Laging malinaw ang EXIF + Alisin ang data ng EXIF ​​na larawan sa pag-save, kahit na humiling ang isang tool na panatilihin ang metadata + Tulong at Mga Tip + %1$d mga tutorial + Buksan ang tool + Matutunan ang mga pangunahing tool, mga opsyon sa pag-export, mga daloy ng PDF, mga utility ng kulay, at mga pag-aayos para sa mga karaniwang problema + Pagsisimula + Pumili ng mga tool, mag-import ng mga file, mag-save ng mga resulta, at gumamit muli ng mga setting nang mas mabilis + Pag-edit ng larawan + Baguhin ang laki, i-crop, i-filter, burahin ang mga background, iguhit, at mga larawan ng watermark + Mga file at metadata + I-convert ang mga format, ihambing ang output, protektahan ang privacy, at kontrolin ang mga filename + PDF at mga dokumento + Gumawa ng mga PDF, mag-scan ng mga dokumento, OCR page, at maghanda ng mga file para sa pagbabahagi + Teksto, QR, at data + Kilalanin ang teksto, i-scan ang mga code, i-encode ang Base64, suriin ang mga hash, at mga file ng package + Mga tool sa kulay + Pumili ng mga kulay, bumuo ng mga palette, galugarin ang mga library ng kulay, at gumawa ng mga gradient + Mga malikhaing tool + Bumuo ng SVG, ASCII na sining, mga texture ng ingay, mga shader, at pang-eksperimentong visual + Pag-troubleshoot + Ayusin ang mabibigat na file, transparency, pagbabahagi, pag-import, at mga isyu sa pag-uulat + Piliin ang tamang tool + Gumamit ng mga kategorya, paghahanap, paborito, at magbahagi ng mga aksyon upang magsimula sa tamang lugar + Magsimula sa pinakamagandang entry point + Ang Image Toolbox ay may maraming nakatutok na tool, at marami sa mga ito ay nagsasapawan sa unang tingin. Magsimula sa gawaing gusto mong tapusin, pagkatapos ay piliin ang tool na kumokontrol sa pinakamahalagang bahagi ng resulta: laki, format, metadata, text, PDF page, kulay, o layout. + Gumamit ng paghahanap kapag alam mo ang pangalan ng pagkilos, gaya ng pagbabago ng laki, PDF, EXIF, OCR, QR, o kulay. + Magbukas ng kategorya kapag alam mo lang ang uri ng gawain, pagkatapos ay i-pin ang mga madalas na tool bilang mga paborito. + Kapag nagbahagi ang isa pang app ng larawan sa Image Toolbox, piliin ang tool mula sa daloy ng share sheet. + Mag-import, mag-save, at magbahagi ng mga resulta + Unawain ang karaniwang daloy mula sa pagpili ng input hanggang sa pag-export ng na-edit na file + Sundin ang karaniwang daloy ng pag-edit + Karamihan sa mga tool ay sumusunod sa parehong ritmo: pumili ng input, ayusin ang mga opsyon, i-preview, pagkatapos ay i-save o ibahagi. Ang mahalagang detalye ay ang pag-save ay lumilikha ng isang output file, habang ang pagbabahagi ay karaniwang pinakamainam para sa mabilis na paghahatid sa isa pang app. + Pumili ng isang file para sa mga single-image na tool o ilang file para sa batch tool kapag pinapayagan ito ng picker. + Suriin ang preview at mga kontrol sa output bago i-save, lalo na ang mga opsyon sa format, kalidad, at filename. + Gamitin ang share para sa isang mabilis na handoff, o i-save kapag kailangan mo ng paulit-ulit na kopya sa napiling folder. + Makipagtulungan sa maraming larawan nang sabay-sabay + Pinakamainam ang mga batch tool para sa paulit-ulit na pagbabago ng laki, conversion, compression, at mga gawain sa pagbibigay ng pangalan + Maghanda ng predictable batch + Ang batch processing ay pinakamabilis kapag ang bawat output ay dapat sumunod sa parehong mga panuntunan. Ito rin ang pinakamadaling lugar para makagawa ng malaking pagkakamali, kaya piliin ang pagbibigay ng pangalan, kalidad, metadata, at gawi ng folder bago magsimula ng mahabang pag-export. + Piliin ang lahat ng magkakaugnay na larawan nang magkasama at panatilihin ang pagkakasunud-sunod kung ang output ay depende sa pagkakasunud-sunod. + Magtakda ng mga panuntunan sa pagbabago ng laki, format, kalidad, metadata, at filename bago simulan ang pag-export. + Magpatakbo muna ng maliit na test batch kapag malaki ang source file o kapag bago para sa iyo ang target na format. + Mabilis na solong pag-edit + Gamitin ang all-in-one na editor kapag kailangan mo ng ilang simpleng pagbabago sa isang larawan + Tapusin ang isang larawan nang walang tool hopping + Ang Single Edit ay kapaki-pakinabang kapag ang larawan ay nangangailangan ng isang maliit na hanay ng mga pagbabago sa halip na isang espesyal na operasyon. Karaniwan itong mas mabilis para sa mabilis na paglilinis, pag-preview, at pag-export ng isang pangwakas na kopya nang hindi bumubuo ng isang batch na daloy ng trabaho. + Buksan ang Single Edit para sa isang larawan na nangangailangan ng mga karaniwang pagsasaayos, markup, pag-crop, pag-ikot, o mga pagbabago sa pag-export. + Ilapat ang mga pag-edit sa pagkakasunud-sunod na nagbabago muna ng pinakamababang pixel, gaya ng pag-crop bago ang mga filter at mga filter bago ang huling pag-compress. + Gumamit na lang ng espesyal na tool kapag kailangan mo ng batch processing, eksaktong file-size na mga target, PDF output, OCR, o metadata cleanup. + Gumamit ng mga preset at setting + I-save ang mga paulit-ulit na pagpipilian at ibagay ang app para sa iyong gustong workflow + Iwasang ulitin ang parehong setup + Ang mga preset at setting ay kapaki-pakinabang kapag madalas mong ine-export ang parehong uri ng file. Ang isang magandang preset ay ginagawang isang tap ang isang paulit-ulit na manual na checklist, habang ang mga pandaigdigang setting ay tumutukoy sa mga default na nagsisimula sa mga bagong tool. + Gumawa ng mga preset para sa mga karaniwang laki ng output, format, value ng kalidad, o pattern ng pagbibigay ng pangalan. + Suriin ang Mga Setting para sa default na format, kalidad, save folder, filename, picker, at gawi ng interface. + I-back up ang mga setting bago muling i-install ang app o lumipat sa ibang device. + Magtakda ng mga kapaki-pakinabang na default + Pumili ng default na format, kalidad, scale mode, resize type, at color space nang isang beses + Gawing mas malapit sa iyong layunin ang mga bagong tool + Ang mga default na halaga ay hindi lamang mga kagustuhan sa kosmetiko. Sila ang magpapasya kung anong format, kalidad, pagbabago ng laki ng gawi, scale mode, at color space ang unang lalabas sa maraming tool, na nakakatulong na maiwasan ang muling pagpili sa parehong mga pagpipilian sa bawat pag-export. + Itakda ang default na format at kalidad ng larawan upang tumugma sa iyong karaniwang destinasyon, gaya ng JPEG para sa mga larawan o PNG/WebP para sa alpha. + Tune default resize type at scale mode kung madalas kang nag-e-export para sa mga mahigpit na dimensyon, social platform, o mga pahina ng dokumento. + Baguhin ang mga default na espasyo ng kulay kapag ang iyong daloy ng trabaho ay nangangailangan ng pare-parehong paghawak ng kulay sa mga display, print, o external na editor. + Picker at launcher + Gumamit ng mga setting ng picker kapag nagbukas ang app ng masyadong maraming dialog o nagsimula sa maling lugar + Gawing tumutugma sa iyong ugali ang pagbubukas ng mga file + Kinokontrol ng mga setting ng picker at launcher kung magsisimula ang Image Toolbox sa pangunahing screen, tool, o daloy ng pagpili ng file. Lalo na kapaki-pakinabang ang mga opsyong ito kapag karaniwan kang pumapasok mula sa Android share sheet o kapag gusto mong maging parang tool launcher ang app. + Gumamit ng mga setting ng tagapili ng larawan kung ang tagapili ng system ay nagtatago ng mga file, nagpapakita ng masyadong maraming kamakailang mga item, o hindi maayos na pinangangasiwaan ang mga cloud file. + Paganahin ang laktawan ang pagpili para lang sa mga tool kung saan ka karaniwang pumapasok mula sa pagbabahagi o alam na ang source file. + Suriin ang launcher mode kung gusto mong kumilos ang Image Toolbox na mas katulad ng tool picker kaysa sa isang normal na home screen. + Pinagmulan ng larawan + Piliin ang picker mode na pinakamahusay na gumagana sa mga gallery, file manager, at cloud file + Pumili ng mga file mula sa tamang pinagmulan + Ang mga setting ng Pinagmulan ng Imahe ay nakakaapekto sa kung paano humihingi ng mga larawan ang app sa Android. Ang pinakamahusay na pagpipilian ay depende sa kung ang iyong mga file ay nakatira sa system gallery, isang folder ng file manager, isang cloud provider, o isa pang app na nagbabahagi ng pansamantalang nilalaman. + Gamitin ang default na picker kapag gusto mo ang pinaka-system-integrated na karanasan para sa mga karaniwang larawan ng gallery. + Lumipat sa picker mode kung ang mga album, folder, cloud file, o hindi karaniwang mga format ay hindi lalabas kung saan mo inaasahan. + Kung ang isang nakabahaging file ay nawala pagkatapos umalis sa pinagmulang app, buksan muli ito sa pamamagitan ng patuloy na picker o mag-save muna ng lokal na kopya. + Mga paborito at magbahagi ng mga tool + Panatilihing nakatutok ang pangunahing screen at itago ang mga tool na hindi makatwiran sa mga daloy ng pagbabahagi + Bawasan ang ingay sa listahan ng tool + Ang mga paborito at hidden-for-share na mga setting ay kapaki-pakinabang kapag gumagamit ka lamang ng ilang mga tool araw-araw. Pinapanatili din nilang praktikal ang daloy ng pagbabahagi sa pamamagitan ng pagtatago ng mga tool na hindi magagamit ang uri ng file na ipinapadala ng isa pang app. + I-pin ang madalas na mga tool upang manatiling madaling maabot ang mga ito kahit na ang mga tool ay nakagrupo ayon sa kategorya. + Itago ang mga tool mula sa mga daloy ng pagbabahagi kapag hindi nauugnay ang mga ito para sa mga file na nagmumula sa isa pang app. + Gamitin ang mga paboritong setting ng pag-order kung mas gusto mo ang mga paborito sa dulo o nakapangkat sa mga nauugnay na tool. + Mga setting ng pag-back up + Ligtas na ilipat ang mga preset, kagustuhan, at gawi ng app sa isa pang pag-install + Panatilihing portable ang iyong setup + Ang Backup at Restore ay pinaka-kapaki-pakinabang pagkatapos mong i-tune ang mga preset, folder, mga panuntunan sa filename, pagkakasunud-sunod ng tool, at mga visual na kagustuhan. Hinahayaan ka ng isang backup na mag-eksperimento sa mga setting o muling i-install ang app nang hindi muling itinatayo ang iyong daloy ng trabaho mula sa memorya. + Gumawa ng backup pagkatapos baguhin ang mga preset, pag-uugali ng filename, mga default na format, o pag-aayos ng tool. + I-imbak ang backup sa isang lugar sa labas ng folder ng app kung plano mong i-clear ang data, muling i-install, o ilipat ang mga device. + I-restore ang mga setting bago iproseso ang mahahalagang batch upang tumugma ang mga default at pag-save ng gawi sa iyong lumang setup. + Baguhin ang laki at i-convert ang mga imahe + Baguhin ang laki, format, kalidad, at metadata para sa isa o maraming mga larawan + Gumawa ng mga na-resize na kopya + Ang Resize at Convert ay ang pangunahing tool para sa mga predictable na dimensyon at mas magaan na output file. Gamitin ito kapag ang laki ng larawan, ang format ng output, at ang pag-uugali ng metadata ay mahalaga sa parehong oras. + Pumili ng isa o higit pang mga larawan, pagkatapos ay piliin kung ang mga sukat, sukat, o laki ng file ang pinakamahalaga. + Piliin ang format at kalidad ng output; gumamit ng PNG o WebP kapag kailangang mapanatili ang transparency. + I-preview ang resulta, pagkatapos ay i-save o ibahagi ang mga nabuong kopya nang hindi binabago ang mga orihinal. + Baguhin ang laki ayon sa laki ng file + Mag-target ng limitasyon sa laki kapag tinanggihan ng isang website, messenger, o form ang mabibigat na larawan + Pagkasyahin ang mahigpit na mga limitasyon sa pag-upload + Ang pag-resize ayon sa laki ng file ay mas mahusay kaysa sa paghula ng mga halaga ng kalidad kapag ang destinasyon ay tumatanggap lamang ng mga file na mas mababa sa isang partikular na limitasyon. Maaaring isaayos ng tool ang compression at mga sukat upang maabot ang target habang sinusubukang panatilihing magagamit ang resulta. + Ilagay ang target na laki nang bahagya sa ibaba ng tunay na limitasyon sa pag-upload upang mag-iwan ng puwang para sa metadata at mga pagbabago sa provider. + Mas gusto ang mga lossy na format para sa mga larawan kapag maliit ang target; Maaaring manatiling malaki ang PNG kahit na pagkatapos baguhin ang laki. + Siyasatin ang mga mukha, text, at mga gilid pagkatapos ng pag-export dahil ang mga target ng agresibong laki ay maaaring magpakilala ng mga nakikitang artifact. + Limitahan ang resize + Paliitin lamang ang mga larawang lumalampas sa iyong maximum na lapad, taas, o mga hadlang sa file + Iwasang baguhin ang laki ng mga larawang maayos na + Ang Limit Resize ay kapaki-pakinabang para sa mga halo-halong folder kung saan ang ilang mga imahe ay napakalaki at ang iba ay handa na. Sa halip na pilitin ang bawat file sa pamamagitan ng parehong pagbabago ng laki, pinapanatili nito ang mga katanggap-tanggap na imahe na mas malapit sa kanilang orihinal na kalidad. + Itakda ang maximum na mga sukat o limitasyon batay sa patutunguhan sa halip na baguhin ang laki ng bawat file nang walang taros. + I-enable ang skip-if-larger style behavior kapag gusto mong iwasan ang mga output na mas mabigat kaysa sa mga source. + Gamitin ito para sa mga batch mula sa iba\'t ibang camera, screenshot, o pag-download kung saan malaki ang pagkakaiba-iba ng mga laki ng pinagmulan. + I-crop, paikutin, at ituwid + I-frame ang mahalagang lugar bago i-export o gamitin ang larawan sa iba pang mga tool + Linisin ang komposisyon + Ang pag-crop muna ay ginagawang mas malinis ang mga filter sa ibang pagkakataon, OCR, PDF page, at pagbabahagi ng mga resulta. + Buksan ang I-crop at piliin ang larawang gusto mong ayusin. + Gumamit ng libreng crop o isang aspect ratio kapag ang output ay dapat magkasya sa isang post, dokumento, o avatar. + I-rotate o i-flip bago i-save para matanggap ng mga tool sa ibang pagkakataon ang naitama na larawan. + Ilapat ang mga filter at pagsasaayos + Bumuo ng nae-edit na stack ng filter at i-preview ang resulta bago i-export + Ibagay ang hitsura ng imahe + Ang mga filter ay kapaki-pakinabang para sa mabilis na pagwawasto, naka-istilong output, at paghahanda ng mga larawan para sa OCR o pag-print. Ang mga maliliit na pagbabago sa contrast, sharpness, at brightness ay maaaring mas mahalaga kaysa sa mabibigat na epekto kapag ang larawan ay naglalaman ng teksto o mga pinong detalye. + Magdagdag ng mga filter nang paisa-isa at bantayan ang preview pagkatapos ng bawat pagbabago. + Ayusin ang liwanag, contrast, sharpness, blur, kulay, o mask depende sa gawain. + I-export lang kapag tumugma ang preview sa iyong target na device, dokumento, o social platform. + Silipin muna + Suriin ang mga larawan bago pumili ng mas mabibigat na tool sa pag-edit, conversion, o paglilinis + Suriin kung ano talaga ang iyong natanggap + Ang Preview ng Imahe ay kapaki-pakinabang kapag ang isang file ay nagmula sa chat, cloud storage, o isa pang app at hindi ka sigurado kung ano ang nilalaman nito. Ang pagsuri sa mga sukat, transparency, oryentasyon, at visual na kalidad muna ay nakakatulong na piliin ang tamang follow-up na tool. + Buksan ang Preview ng Imahe kapag kailangan mong suriin ang ilang mga larawan bago magpasya kung ano ang gagawin sa mga ito. + Maghanap ng mga problema sa pag-ikot, transparent na lugar, compression artifact, at hindi inaasahang malalaking dimensyon. + Ilipat sa Baguhin ang laki, I-crop, Ikumpara, EXIF, o Background Remover pagkatapos mong malaman kung ano ang kailangang ayusin. + Alisin o i-restore ang isang background + Awtomatikong burahin ang mga background o manu-manong pinuhin ang mga gilid gamit ang mga tool sa pag-restore + Panatilihin ang paksa, alisin ang natitira + Pinakamahusay na gagana ang pag-alis ng background kapag pinagsama mo ang awtomatikong pagpili sa maingat na manu-manong paglilinis. Ang panghuling format ng pag-export ay mahalaga gaya ng mask, dahil papatagin ng JPEG ang mga transparent na lugar kahit na mukhang tama ang preview. + Magsimula sa awtomatikong pag-aalis kung ang paksa ay malinaw na nakahiwalay sa background. + Mag-zoom in at magpalipat-lipat sa pagitan ng burahin at i-restore para ayusin ang buhok, mga sulok, anino, at maliliit na detalye. + I-save bilang PNG o WebP kapag kailangan mo ng transparency sa huling file. + Gumuhit, markahan, at watermark + Magdagdag ng mga arrow, tala, highlight, lagda, o paulit-ulit na mga overlay ng watermark + Magdagdag ng nakikitang impormasyon + Tumutulong ang mga markup tool na ipaliwanag ang isang larawan, habang ang watermarking ay nakakatulong sa tatak o protektahan ang mga na-export na file. + Gamitin ang Draw kapag kailangan mo ng freehand na mga tala, hugis, pag-highlight, o mabilis na anotasyon. + Gumamit ng Watermarking kapag ang parehong teksto o marka ng imahe ay dapat na palaging lumabas sa mga output. + Suriin ang opacity, posisyon, at sukat bago i-export para makita ang marka ngunit hindi nakakagambala. + Gumuhit ng mga default + Itakda ang lapad ng linya, kulay, path mode, lock ng oryentasyon, at magnifier para sa mas mabilis na markup + Gawing mahuhulaan ang pagguhit + Nakakatulong ang mga setting ng pagguhit kapag nag-annotate ka ng mga screenshot, minarkahan ang mga dokumento, o madalas na pumirma ng mga larawan. Binabawasan ng mga default ang oras ng pag-setup, habang pinapadali ng magnifier at orientation lock ang mga tumpak na pag-edit sa maliliit na screen. + Itakda ang default na lapad ng linya at gumuhit ng kulay sa mga value na pinakamadalas mong ginagamit para sa mga arrow, highlight, o signature. + Gumamit ng default na path mode kapag karaniwan mong iginuhit ang parehong uri ng mga stroke, hugis, o markup. + I-enable ang magnifier o orientation lock kapag ang pag-input ng daliri ay ginagawang mahirap ilagay nang tumpak ang maliliit na detalye. + Mga layout ng maraming larawan + Piliin ang tamang multi-image tool bago ayusin ang mga screenshot, pag-scan, o paghahambing na strip + Gamitin ang tamang tool sa layout + Ang mga tool na ito ay magkatulad, ngunit ang bawat isa ay nakatutok para sa ibang uri ng multi-image na output. Ang pagpili muna ng tama ay maiiwasan ang pakikipaglaban sa mga kontrol sa layout na idinisenyo para sa ibang resulta. + Gumamit ng Collage Maker para sa mga dinisenyong layout na may ilang mga larawan sa isang komposisyon. + Gumamit ng Image Stitching o Stacking kapag ang mga imahe ay dapat na maging isang mahabang patayo o pahalang na strip. + Gamitin ang Image Splitting kapag ang isang malaking larawan ay kailangang maging ilang mas maliliit na piraso. + I-convert ang mga format ng imahe + Lumikha ng JPEG, PNG, WebP, AVIF, JXL, at iba pang mga kopya nang hindi manu-manong nag-e-edit ng mga pixel + Piliin ang format para sa trabaho + Mas mainam ang iba\'t ibang format para sa mga larawan, screenshot, transparency, compression, o compatibility. Ang conversion ay pinakaligtas kapag nauunawaan mo kung ano ang sinusuportahan ng patutunguhang app at kung ang pinagmulan ay naglalaman ng alpha, animation, o metadata na gusto mong panatilihin. + Gumamit ng JPEG para sa mga tugmang larawan, PNG para sa mga lossless na larawan at alpha, at WebP para sa compact na pagbabahagi. + Ayusin ang kalidad kapag sinusuportahan ito ng format; binabawasan ng mas mababang mga halaga ang laki ngunit maaaring magdagdag ng mga artifact. + Panatilihin ang mga orihinal hanggang sa ma-verify mo ang mga na-convert na file na nakabukas nang tama sa target na app. + Suriin ang EXIF ​​at privacy + Tingnan, i-edit, o alisin ang metadata bago magbahagi ng mga sensitibong larawan + Kontrolin ang nakatagong data ng imahe + Ang EXIF ​​ay maaaring maglaman ng data ng camera, mga petsa, lokasyon, oryentasyon, at iba pang mga detalyeng hindi nakikita sa larawan. Ito ay nagkakahalaga ng pagsusuri bago ang pampublikong pagbabahagi, mga ulat ng suporta, mga listahan ng marketplace, o anumang larawan na maaaring magbunyag ng isang pribadong lugar. + Buksan ang mga tool ng EXIF ​​bago magbahagi ng mga larawang maaaring naglalaman ng impormasyon ng lokasyon o pribadong device. + Alisin ang mga sensitibong field o i-edit ang mga maling tag gaya ng petsa, oryentasyon, may-akda, o data ng camera. + Mag-save ng bagong kopya at ibahagi ang kopya na iyon sa halip na ang orihinal kapag mahalaga ang privacy. + Auto EXIF ​​na paglilinis + Alisin ang metadata bilang default habang nagpapasya kung ang mga petsa ay dapat pangalagaan + Gawing default ang privacy + Kapaki-pakinabang ang pangkat ng mga setting ng EXIF ​​kapag gusto mong awtomatikong mangyari ang paglilinis ng privacy sa halip na alalahanin ito para sa bawat pag-export. Ang Panatilihin ang Oras ng Petsa ay isang hiwalay na desisyon dahil ang mga petsa ay maaaring maging kapaki-pakinabang para sa pag-uuri ngunit sensitibo para sa pagbabahagi. + I-enable ang palaging-clear na EXIF ​​kapag ang karamihan sa iyong mga pag-export ay para sa pampubliko o semi-pampublikong pagbabahagi. + I-enable lang ang Keep Date Time kapag mas mahalaga ang pagpepreserba sa oras ng pagkuha kaysa sa pag-alis ng bawat timestamp. + Gumamit ng manu-manong pag-edit ng EXIF ​​para sa mga one-off na file kung saan kailangan mong panatilihin ang ilang field at alisin ang iba. + Kontrolin ang mga filename + Gumamit ng mga prefix, suffix, timestamp, preset, checksum, at sequence number + Gawing madaling mahanap ang mga pag-export + Ang magandang pattern ng filename ay nakakatulong sa pag-uuri ng mga batch at pinipigilan ang mga hindi sinasadyang overwrite. Ang mga setting ng filename ay lalong kapaki-pakinabang kapag ang mga pag-export ay bumalik sa pinagmulang folder, kung saan ang mga katulad na pangalan ay maaaring maging mabilis na nakakalito. + Itakda ang prefix ng filename, suffix, timestamp, orihinal na pangalan, preset na impormasyon, o mga opsyon sa sequence sa Mga Setting. + Gumamit ng mga sequence number para sa mga batch kung saan mahalaga ang order, gaya ng mga page, frame, o collage. + Paganahin lamang ang pag-overwrite kapag sigurado kang ang pagpapalit ng mga umiiral na file ay ligtas para sa kasalukuyang folder. + I-overwrite ang mga file + Palitan ang mga output ng mga tumutugmang pangalan lamang kapag ang iyong daloy ng trabaho ay sadyang mapanira + Maingat na gamitin ang overwrite mode + Binabago ng Overwrite Files kung paano pinangangasiwaan ang mga salungatan sa pagbibigay ng pangalan. Ito ay kapaki-pakinabang para sa mga paulit-ulit na pag-export sa parehong folder, ngunit maaari rin nitong palitan ang mga naunang resulta kung ang iyong filename pattern ay gumagawa muli ng parehong pangalan. + I-enable ang overwrite para lang sa mga folder kung saan inaasahan at mababawi ang pagpapalit ng mga mas lumang nabuong file. + Panatilihing naka-disable ang overwrite kapag nag-e-export ng mga orihinal, mga file ng kliyente, isang beses na pag-scan, o anumang bagay na walang backup. + Pagsamahin ang pag-overwrite sa isang sinasadyang pattern ng filename upang ang mga nilalayon na output lamang ang papalitan. + Mga pattern ng filename + Pagsamahin ang mga orihinal na pangalan, prefix, suffix, timestamp, preset, scale mode, at checksum + Bumuo ng mga pangalan na nagpapaliwanag sa output + Ang mga setting ng pattern ng filename ay maaaring gawing kapaki-pakinabang na kasaysayan ang mga na-export na file sa kung ano ang nangyari sa kanila. Mahalaga ito sa mga batch dahil ang view ng folder ay maaaring ang tanging lugar kung saan maaari mong makilala ang orihinal na laki, preset, format, at pagkakasunud-sunod ng pagproseso. + Gumamit ng orihinal na filename, prefix, suffix, at timestamp kapag kailangan mo ng mga nababasang pangalan para sa manu-manong pag-uuri. + Magdagdag ng preset, scale mode, laki ng file, o checksum na impormasyon kapag dapat idokumento ng mga output kung paano ginawa ang mga ito. + Iwasan ang mga random na pangalan para sa mga daloy ng trabaho kung saan kailangang manatiling ipinares ang mga file sa mga orihinal o pagkakasunud-sunod ng page. + Piliin kung saan naka-save ang mga file + Iwasang mawalan ng mga pag-export sa pamamagitan ng pagtatakda ng mga folder, pag-save ng orihinal na folder, at isang beses na pag-save ng mga lokasyon + Panatilihing predictable ang mga output + Pinakamahalaga ang pag-save ng gawi kapag nagproseso ka ng maraming file o nagbabahagi ng mga file mula sa mga cloud provider. Ang mga setting ng folder ay nagpapasya kung ang mga output ay kinokolekta sa isang kilalang lugar, lalabas sa tabi ng mga orihinal, o pansamantalang pupunta sa ibang lugar para sa isang partikular na gawain. + Magtakda ng default na folder sa pag-save kung gusto mong palaging mag-export sa isang lugar. + Gumamit ng save-to-original-folder para sa mga workflow ng paglilinis kung saan dapat manatili ang output malapit sa source file. + Gumamit ng isang beses na pag-save ng lokasyon kapag ang isang gawain ay nangangailangan ng ibang destinasyon nang hindi binabago ang iyong default. + Isang beses na i-save ang mga folder + Ipadala ang mga susunod na pag-export sa isang pansamantalang folder nang hindi binabago ang iyong normal na destinasyon + Panatilihing hiwalay ang mga espesyal na trabaho + Ang isang beses na pag-save ng lokasyon ay kapaki-pakinabang para sa mga pansamantalang proyekto, mga folder ng kliyente, mga sesyon ng paglilinis, o mga pag-export na hindi dapat ihalo sa iyong regular na folder ng Image Toolbox. Nagbibigay ito ng panandaliang destinasyon nang hindi muling isinusulat ang iyong default na setup. + Pumili ng isang beses na folder bago simulan ang isang gawain na nasa labas ng iyong normal na lokasyon ng pag-export. + Gamitin ito para sa mga maiikling proyekto, nakabahaging folder, o mga batch kung saan dapat manatiling pinagsama-sama ang mga output. + Bumalik sa default na folder pagkatapos ng trabaho upang ang mga pag-export sa ibang pagkakataon ay hindi mapunta sa pansamantalang lokasyon. + Balanse ang kalidad at laki ng file + Unawain kung kailan dapat baguhin ang mga dimensyon, format, o kalidad sa halip na hulaan + Sadyang paliitin ang mga file + Ang laki ng file ay depende sa mga sukat ng larawan, format, kalidad, transparency, at pagiging kumplikado ng nilalaman. Kung ang isang file ay masyadong mabigat, ang pagbabago ng mga dimensyon ay kadalasang nakakatulong nang higit kaysa paulit-ulit na pagpapababa ng kalidad. + Baguhin muna ang laki ng mga dimensyon kapag mas malaki ang larawan kaysa sa maaaring ipakita ng patutunguhan. + Mas mababang kalidad lang para sa mga lossy na format at tingnan ang text, mga gilid, gradient, at mga mukha pagkatapos ng pag-export. + Lumipat ng format kapag hindi sapat ang mga pagbabago sa kalidad, halimbawa WebP para sa pagbabahagi o PNG para sa mga malinaw na transparent na asset. + Makipagtulungan sa mga animated na format + Gumamit ng GIF, APNG, WebP, at JXL na mga tool kapag ang isang file ay may mga frame sa halip na isang bitmap + Huwag ituring ang bawat larawan bilang pa rin + Ang mga animated na format ay nangangailangan ng mga tool na nakakaunawa sa mga frame, tagal, at compatibility. + Gumamit ng mga tool ng GIF o APNG kapag kailangan mo ng mga pagpapatakbo sa antas ng frame para sa mga format na iyon. + Gumamit ng mga tool sa WebP kapag kailangan mo ng modernong compression o animated na output ng WebP. + I-preview ang timing ng animation bago magbahagi dahil ang ilang platform ay nagko-convert o nag-flatten ng mga animated na larawan. + Ihambing at i-preview ang output + Suriin ang mga pagbabago, laki ng file, at mga visual na pagkakaiba bago panatilihin ang isang pag-export + I-verify bago ibahagi + Nakakatulong ang mga tool sa paghahambing na mahuli ang mga artifact ng compression, maling kulay, o hindi gustong mga pag-edit nang maaga. + Buksan ang Ihambing kapag gusto mong suriin ang dalawang bersyon na magkatabi. + Mag-zoom sa mga gilid, text, gradient, at transparent na mga rehiyon kung saan ang mga problema ay pinakamadaling makaligtaan. + Kung masyadong mabigat o malabo ang output, bumalik sa dating tool at ayusin ang kalidad o format. + Gamitin ang PDF tools hub + Pagsamahin, hatiin, paikutin, alisin ang mga pahina, i-compress, protektahan, OCR, at mga watermark na PDF file + Pumili ng isang PDF na operasyon + Pinapangkat ng PDF hub ang mga aksyon sa likod ng isang entry point para mapili mo ang eksaktong operasyon. Magsimula doon kapag ang file ay PDF na, at gamitin ang Images to PDF o Document Scanner kapag ang iyong source ay isang hanay pa rin ng mga larawan. + Buksan ang PDF Tools at piliin ang operasyon na tumutugma sa iyong layunin. + Piliin ang pinagmulang PDF o mga larawan, pagkatapos ay suriin ang pagkakasunud-sunod ng pahina, pag-ikot, proteksyon, o mga opsyon sa OCR. + I-save ang nabuong PDF at buksan ito nang isang beses upang kumpirmahin ang bilang ng pahina, pagkakasunud-sunod, at pagiging madaling mabasa. + Gawing PDF ang mga larawan + Pagsamahin ang mga pag-scan, screenshot, resibo, o mga larawan sa isang naibabahaging dokumento + Bumuo ng malinis na dokumento + Ang mga larawan ay nagiging mas mahusay na mga pahina ng PDF kapag ang mga ito ay na-crop, inayos, at pare-pareho ang laki. Tratuhin ang listahan ng larawan bilang isang stack ng pahina: bawat pag-ikot, margin, at laki ng pahina na pagpipilian ay nakakaapekto sa kung gaano nababasa ang pakiramdam ng huling dokumento. + I-crop o i-rotate muna ang mga larawan kapag mali ang mga gilid ng page, anino, o oryentasyon. + Pumili ng mga larawan sa inilaan na pagkakasunud-sunod ng pahina at ayusin ang laki o mga margin ng pahina kung inaalok ng tool ang mga ito. + I-export ang PDF, pagkatapos ay suriin na ang lahat ng mga pahina ay nababasa bago ito ipadala. + I-scan ang mga dokumento + Kunin ang mga pahina ng papel at ihanda ang mga ito para sa PDF, OCR, o pagbabahagi + Kunin ang mga nababasang pahina + Ang pag-scan ng dokumento ay pinakamahusay na gumagana sa mga patag na pahina, mahusay na pag-iilaw, at naitama na pananaw. Ang isang malinis na pag-scan bago ang OCR o pag-export ng PDF ay nakakatipid ng oras dahil ang pagkilala sa teksto at pag-compress ay parehong nakadepende sa kalidad ng pahina. + Ilagay ang dokumento sa isang contrasting surface na may sapat na liwanag at minimal na anino. + I-adjust ang mga nakitang sulok o i-crop nang manu-mano para malinis ng page ang frame. + I-export bilang mga larawan o PDF, pagkatapos ay patakbuhin ang OCR kung kailangan mo ng mapipiling text. + Protektahan o i-unlock ang mga PDF file + Gumamit ng mga password nang maingat at panatilihin ang isang nae-edit na pinagmulang kopya kapag posible + Pangasiwaan ang PDF access nang ligtas + Ang mga tool sa proteksyon ay kapaki-pakinabang para sa kinokontrol na pagbabahagi, ngunit ang mga password ay madaling mawala at mahirap mabawi. Protektahan ang mga kopya para sa pamamahagi, panatilihing hiwalay ang mga hindi protektadong source file, at i-verify ang resulta bago magtanggal ng anuman. + Protektahan ang isang kopya ng PDF, hindi ang iyong pinagmumulan lamang na dokumento. + Itabi ang password sa isang lugar na ligtas bago ibahagi ang protektadong file. + Gamitin lang ang pag-unlock para sa mga file na pinapayagan kang i-edit, pagkatapos ay i-verify na bubukas nang tama ang naka-unlock na kopya. + Ayusin ang mga pahinang PDF + Pagsamahin, hatiin, muling ayusin, paikutin, i-crop, alisin ang mga pahina, at kusang magdagdag ng mga numero ng pahina + Ayusin ang istraktura ng dokumento bago palamuti + Ang mga tool sa pahina ng PDF ay pinakamadaling gamitin sa parehong pagkakasunud-sunod na ihahanda mo ang isang papel na dokumento: mangolekta ng mga pahina, alisin ang mga mali, ilagay ang mga ito sa pagkakasunud-sunod, tamang oryentasyon, pagkatapos ay magdagdag ng mga huling detalye tulad ng mga numero ng pahina o mga watermark. + Gamitin muna ang merge o images-to-PDF kapag nahahati pa rin ang dokumento sa ilang file. + Muling ayusin, paikutin, i-crop, o alisin ang mga pahina bago magdagdag ng mga numero ng pahina, lagda, o watermark. + I-preview ang huling PDF pagkatapos ng mga istrukturang pag-edit dahil ang isang nawawala o iniikot na pahina ay maaaring mahirap mapansin sa ibang pagkakataon. + Mga anotasyong PDF + Alisin ang mga anotasyon o i-flat ang mga ito kapag ang mga manonood ay nagpapakita ng mga komento at mga marka nang iba + Kontrolin kung ano ang nananatiling nakikita + Ang mga anotasyon ay maaaring mga komento, mga highlight, mga guhit, mga marka ng form, o iba pang mga karagdagang PDF na bagay. Ang ilang mga manonood ay nagpapakita ng mga ito nang iba, kaya ang pag-alis o pag-flatte sa mga ito ay maaaring gawing mas predictable ang mga nakabahaging dokumento. + Alisin ang mga anotasyon kapag ang mga komento, highlight, o marka ng pagsusuri ay hindi dapat isama sa nakabahaging kopya. + I-flatte kapag ang mga nakikitang marka ay dapat manatili sa page ngunit hindi na kumikilos tulad ng mga nae-edit na anotasyon. + Panatilihin ang isang orihinal na kopya dahil ang pag-alis o pag-flatte ng mga anotasyon ay maaaring mahirap ibalik. + Kilalanin ang teksto + I-extract ang text mula sa mga larawang may mga napiling OCR engine at wika + Makakuha ng mas magagandang resulta ng OCR + Ang katumpakan ng OCR ay nakasalalay sa katalinuhan ng imahe, data ng wika, kaibahan, at oryentasyon ng page. Ang pinakamahusay na mga resulta ay karaniwang nagmumula sa paghahanda ng larawan muna: i-crop ang ingay, paikutin patayo, pataasin ang pagiging madaling mabasa, pagkatapos ay kilalanin ang teksto. + I-crop ang larawan sa lugar ng teksto at i-rotate ito patayo bago makilala. + Piliin ang tamang wika at i-download ang data ng wika kung hihilingin ito ng app. + Kopyahin o ibahagi ang kinikilalang teksto, pagkatapos ay manu-manong suriin ang mga pangalan, numero, at bantas. + Pumili ng data ng wika ng OCR + Pahusayin ang pagkilala sa pamamagitan ng pagtutugma ng mga script, wika, at na-download na modelo ng OCR + Itugma ang OCR sa text + Ang maling wika ng OCR ay maaaring magmukhang masamang pagkilala ang magagandang larawan. Mahalaga ang data ng wika para sa mga script, bantas, numero, at magkahalong dokumento, kaya piliin ang wikang lalabas sa larawan sa halip na ang wika ng UI ng telepono. + Piliin ang wika o script na lalabas sa larawan, hindi lang ang wika ng telepono. + Mag-download ng nawawalang data bago magtrabaho offline o magproseso ng isang batch. + Para sa mga dokumentong may halong wika, patakbuhin muna ang pinakamahalagang wika at manu-manong suriin ang mga pangalan at numero. + Mga mahahanap na PDF + Isulat muli ang OCR text sa mga file upang mahanap at makopya ang mga na-scan na pahina + Gawing mga mahahanap na dokumento ang mga pag-scan + Ang OCR ay maaaring gumawa ng higit pa kaysa sa pagkopya ng teksto sa clipboard. Kapag nagsulat ka ng kinikilalang teksto sa isang file o nahahanap na PDF, ang larawan ng pahina ay mananatiling nakikita habang ang teksto ay nagiging mas madaling hanapin, piliin, i-index, o i-archive. + Gumamit ng nahahanap na PDF na output para sa mga na-scan na dokumento na dapat pa ring magmukhang orihinal na mga pahina. + Gumamit ng write-to-file kapag kailangan mo lang ng extracted na text at walang pakialam sa larawan ng page. + Manu-manong suriin ang mahahalagang numero, pangalan, at talahanayan dahil ang OCR na teksto ay maaaring mahanap ngunit hindi pa rin perpekto. + I-scan ang mga QR code + Basahin ang QR content mula sa camera input o mga naka-save na larawan kapag available + Basahin ang mga code nang ligtas + Ang mga QR code ay maaaring maglaman ng mga link, data ng Wi-Fi, mga contact, text, o iba pang mga payload. + Buksan ang Scan QR Code at panatilihing matalas, patag, at ganap na nasa loob ng frame ang code. + Suriin ang na-decode na nilalaman bago magbukas ng mga link o kopyahin ang sensitibong data. + Kung nabigo ang pag-scan, pagbutihin ang pag-iilaw, i-crop ang code, o subukan ang isang source na larawan na may mas mataas na resolution. + Gumamit ng Base64 at mga checksum + I-encode ang mga larawan bilang text, i-decode ang text pabalik sa mga larawan, at i-verify ang integridad ng file + Lumipat sa pagitan ng mga file at teksto + Ang Base64 ay kapaki-pakinabang para sa maliliit na payload ng imahe, habang ang mga checksum ay nakakatulong na kumpirmahin na ang mga file ay hindi nagbago. Ang mga tool na ito ay pinaka-kapaki-pakinabang sa mga teknikal na daloy ng trabaho, mga ulat ng suporta, paglilipat ng file, at mga lugar kung saan kailangang pansamantalang maging text ang mga binary file. + Gumamit ng mga tool ng Base64 upang mag-encode ng isang maliit na larawan kapag ang isang daloy ng trabaho ay nangangailangan ng teksto sa halip na isang binary file. + I-decode lang ang Base64 mula sa mga pinagkakatiwalaang source at i-verify ang preview bago i-save. + Gumamit ng mga checksum tool kapag kailangan mong ihambing ang mga na-export na file o kumpirmahin ang pag-upload/pag-download. + I-package o protektahan ang data + Gumamit ng ZIP at cipher tool para sa pag-bundle ng mga file o pagbabago ng data ng text + Panatilihing magkakaugnay ang mga file + Nakakatulong ang mga tool sa pag-package kapag maraming mga output ang dapat maglakbay bilang isang file. Mahusay din ang mga ito para sa pagpapanatiling magkasama ng mga nabuong larawan, PDF, log, at metadata kapag kailangan mong magpadala ng kumpletong set ng resulta. + Gamitin ang ZIP kapag kailangan mong magpadala ng maraming na-export na larawan o dokumento nang magkasama. + Gumamit lang ng mga cipher tool kapag nauunawaan mo ang napiling paraan at ligtas mong maiimbak ang mga kinakailangang key. + Subukan ang ginawang archive o binagong teksto bago tanggalin ang mga source file. + Mga checksum sa mga pangalan + Gumamit ng mga checksum-based na filename kapag ang pagiging natatangi at integridad ay higit na mahalaga kaysa sa pagiging madaling mabasa + Pangalanan ang mga file ayon sa nilalaman + Ang mga checksum filename ay kapaki-pakinabang kapag ang mga pag-export ay maaaring may mga duplicate na nakikitang pangalan o kapag kailangan mong patunayan na ang dalawang file ay magkapareho. Hindi gaanong friendly ang mga ito para sa manu-manong pagba-browse, kaya gamitin ang mga ito para sa mga teknikal na archive at mga daloy ng trabaho sa pag-verify. + Paganahin ang checksum bilang filename kapag ang pagkakakilanlan ng nilalaman ay mas mahalaga kaysa sa isang pamagat na nababasa ng tao. + Gamitin ito para sa mga archive, deduplication, paulit-ulit na pag-export, o mga file na maaaring i-upload at i-download muli. + Ipares ang mga pangalan ng checksum sa mga folder o metadata kapag kailangan pa ring maunawaan ng mga tao kung ano ang kinakatawan ng file. + Pumili ng mga kulay mula sa mga larawan + Mga sample na pixel, kopyahin ang mga code ng kulay, at muling gamitin ang mga kulay sa mga palette o disenyo + Sample ang eksaktong kulay + Ang pagpili ng kulay ay kapaki-pakinabang para sa pagtutugma ng isang disenyo, pagkuha ng mga kulay ng brand, o pagsuri ng mga halaga ng larawan. Mahalaga ang pag-zoom dahil ang antialiasing, mga anino, at compression ay maaaring gawing bahagyang naiiba ang mga kalapit na pixel sa kulay na iyong inaasahan. + Buksan ang Pick Color From Image at pumili ng source image na may kulay na kailangan mo. + Mag-zoom in bago mag-sample ng maliliit na detalye para hindi maapektuhan ng mga kalapit na pixel ang resulta. + Kopyahin ang kulay sa format na kinakailangan ng iyong patutunguhan, gaya ng HEX, RGB, o HSV. + Gumawa ng mga palette at gamitin ang color library + I-extract ang mga palette, i-browse ang mga naka-save na kulay, at panatilihing pare-pareho ang mga visual system + Bumuo ng isang magagamit muli na palette + Tumutulong ang mga palette na panatilihing pare-pareho ang mga na-export na graphics, watermark, at drawing. Lalo na kapaki-pakinabang ang mga ito kapag paulit-ulit kang nag-annotate ng mga screenshot, naghahanda ng mga asset ng brand, o nangangailangan ng parehong mga kulay sa ilang tool. + Bumuo ng palette mula sa isang imahe o magdagdag ng mga kulay nang manu-mano kapag alam mo na ang mga halaga. + Pangalanan o ayusin ang mahahalagang kulay upang mahanap mo silang muli sa ibang pagkakataon. + Gumamit ng mga kinopyang value sa Draw, watermark, gradient, SVG, o mga external na tool sa disenyo. + Lumikha ng mga gradient + Bumuo ng mga gradient na larawan para sa mga background, placeholder, at visual na mga eksperimento + Kontrolin ang mga paglipat ng kulay + Kapaki-pakinabang ang mga tool ng gradient kapag kailangan mo ng nabuong larawan sa halip na i-edit ang isang umiiral na. Maaari silang maging malinis na background, placeholder, wallpaper, mask, o simpleng asset para sa mga panlabas na tool sa disenyo. + Piliin ang uri ng gradient at itakda muna ang mahahalagang kulay. + Ayusin ang direksyon, paghinto, kinis, at laki ng output para sa target na use case. + I-export sa isang format na tumutugma sa destinasyon, karaniwang PNG para sa malinis na nabuong mga graphics. + Accessibility ng kulay + Gumamit ng mga dynamic na kulay, contrast, at color-blind na opsyon kapag mahirap basahin ang UI + Gawing mas madaling gamitin ang mga kulay + Naaapektuhan ng mga setting ng kulay ang kaginhawahan, pagiging madaling mabasa, at kung gaano kabilis mong makakapag-scan ng mga kontrol. Kung ang mga tool chips, slider, o mga napiling estado ay parang mahirap makilala, ang mga opsyon sa tema at accessibility ay maaaring maging mas kapaki-pakinabang kaysa sa simpleng pagbabago ng dark mode. + Subukan ang mga dynamic na kulay kapag gusto mong sundin ng app ang kasalukuyang wallpaper palette. + Dagdagan ang contrast o baguhin ang scheme kapag ang mga button at surface ay hindi masyadong namumukod-tangi. + Gumamit ng mga opsyon sa color-blind scheme kung ang ilang estado o accent ay mahirap makilala. + Alpha background + Kontrolin ang preview o fill color na ginagamit sa paligid ng transparent na PNG, WebP, at mga katulad na output + Unawain ang mga transparent na preview + Ang kulay ng background para sa mga alpha format ay maaaring gawing mas madaling suriin ang mga transparent na larawan, ngunit mahalagang malaman kung babaguhin mo ang isang pag-uugali ng preview/fill o pag-flatte sa huling resulta. Mahalaga ito para sa mga sticker, logo, at mga larawang inalis sa background. + Gumamit muna ng alpha-supporting format, gaya ng PNG o WebP, kapag ang mga transparent na pixel ay dapat mabuhay sa pag-export. + Isaayos ang mga setting ng kulay ng background kapag mahirap makita ang mga transparent na gilid sa ibabaw ng app. + Mag-export ng maliit na pagsubok at muling buksan ito sa ibang app para kumpirmahin kung na-save ang transparency o ang napiling fill. + Pagpili ng modelo ng AI + Pumili ng modelo ayon sa uri ng larawan at depekto sa halip na piliin muna ang pinakamalaki + Itugma ang modelo sa pinsala + Maaaring mag-download o mag-import ang AI Tools ng iba\'t ibang modelo ng ONNX/ORT, at ang bawat modelo ay sinanay para sa isang partikular na uri ng problema. Ang isang modelo para sa mga JPEG block, anime line cleanup, denoising, background removal, colorization, o upscaling ay maaaring magdulot ng mas masahol na resulta kapag ginamit sa maling uri ng larawan. + Basahin ang paglalarawan ng modelo bago mag-download; piliin ang modelong nagpapangalan sa iyong aktwal na problema, gaya ng compression, ingay, halftone, blur, o pag-alis ng background. + Magsimula sa mas maliit o mas partikular na modelo kapag sumusubok ng bagong uri ng larawan, pagkatapos ay ihambing lamang sa mas mabibigat na modelo kung hindi sapat ang resulta. + Panatilihin lamang ang mga modelong talagang ginagamit mo, dahil ang mga na-download at na-import na modelo ay maaaring tumagal ng kapansin-pansing espasyo sa imbakan. + Unawain ang mga modelong pamilya + Ang mga pangalan ng modelo ay madalas na nagpapahiwatig ng kanilang target: Ang mga modelo ng istilong RealESRGAN na upscale, ang mga modelo ng SCUNet at FBCNN ay malinis ang ingay o compression, ang mga modelo sa background ay gumagawa ng mga maskara, at ang mga colorizer ay nagdaragdag ng kulay sa grayscale na input. Maaaring maging makapangyarihan ang mga na-import na custom na modelo, ngunit dapat tumugma ang mga ito sa inaasahang hugis ng input/output at gumamit ng suportadong ONNX o ORT na format. + Gumamit ng mga upscaler kapag kailangan mo ng mas maraming pixel, denoisers kapag grainy ang imahe, at mga artifact remover kapag compression block o banding ang pangunahing isyu. + Gumamit lamang ng mga modelo ng background para sa paghihiwalay ng paksa; hindi sila katulad ng pangkalahatang pag-alis ng bagay o pagpapahusay ng imahe. + Mag-import lang ng mga custom na modelo mula sa mga pinagmumulan na pinagkakatiwalaan mo at subukan ang mga ito sa isang maliit na larawan bago gumamit ng mahahalagang file. + Mga parameter ng AI + Ibagay ang laki ng chunk, overlap, at parallel na mga manggagawa upang balansehin ang kalidad, bilis, at memorya + Balansehin ang bilis na may katatagan + Kinokontrol ng laki ng tipak kung gaano kalaki ang mga piraso ng imahe bago sila iproseso ng modelo. Pinagsasama ng overlap ang mga kalapit na chunks upang itago ang mga hangganan. Maaaring pabilisin ng mga parallel na manggagawa ang pagproseso, ngunit ang bawat manggagawa ay nangangailangan ng memorya, kaya ang mataas na halaga ay maaaring gawing hindi matatag ang malalaking larawan sa mga teleponong may limitadong RAM. + Gumamit ng mas malalaking tipak para sa mas malinaw na konteksto kapag ang iyong device ay humahawak sa modelo nang mapagkakatiwalaan at ang larawan ay hindi malaki. + Dagdagan ang overlap kapag nakikita ang mga chunk border, ngunit tandaan na ang mas maraming overlap ay nangangahulugan ng mas paulit-ulit na trabaho. + Itaas ang mga parallel na manggagawa pagkatapos lamang ng isang matatag na pagsubok; kung bumagal ang pagproseso, pinainit ang device, o nag-crash, bawasan muna ang mga manggagawa. + Iwasan ang mga tipak na artifact + Ang pag-chunking ay nagbibigay-daan sa app na magproseso ng mga larawan na mas malaki kaysa sa kumportableng mahawakan ng modelo nang sabay-sabay. Ang tradeoff ay ang bawat tile ay may mas kaunting nakapaligid na konteksto, kaya ang mababang overlap o masyadong maliliit na chunks ay maaaring lumikha ng mga nakikitang hangganan, mga pagbabago sa texture, o hindi pare-parehong detalye sa pagitan ng mga kalapit na lugar. + Dagdagan ang overlap kung makakita ka ng mga parihabang hangganan, paulit-ulit na pagbabago sa texture, o paglukso ng detalye sa pagitan ng mga naprosesong rehiyon. + Bawasan ang laki ng chunk kung nabigo ang proseso bago gumawa ng output, lalo na sa malalaking larawan o mabibigat na modelo. + Mag-save ng maliit na pagsubok sa pag-crop bago iproseso ang buong larawan upang mabilis mong maihambing ang mga parameter. + katatagan ng AI + Mas mababang chunk size, manggagawa, at source resolution kapag nag-crash o nag-freeze ang pagproseso ng AI + Makabawi mula sa mabibigat na trabaho sa AI + Ang pagproseso ng AI ay isa sa pinakamabigat na daloy ng trabaho sa app. Ang mga pag-crash, mga nabigong session, pag-freeze, o biglaang pag-restart ng app ay karaniwang nangangahulugan na ang modelo, resolution ng pinagmulan, laki ng chunk, o parallel na bilang ng manggagawa ay masyadong hinihingi para sa kasalukuyang estado ng device. + Kung nag-crash o nabigo ang app na magbukas ng session ng modelo, babaan ang mga parallel na manggagawa at laki ng chunk, pagkatapos ay subukang muli ang parehong larawan. + Baguhin ang laki ng napakalaking larawan bago ang pagproseso ng AI kapag ang layunin ay hindi nangangailangan ng orihinal na resolusyon. + Isara ang iba pang mabibigat na app, gumamit ng mas maliit na modelo, o magproseso ng mas kaunting mga file nang sabay-sabay kapag patuloy na bumabalik ang presyon ng memorya. + I-diagnose ang mga pagkabigo ng modelo + Ang isang nabigong AI run ay hindi palaging nangangahulugan na ang imahe ay masama. Ang file ng modelo ay maaaring hindi kumpleto, hindi suportado, masyadong malaki para sa memorya, o hindi tumutugma sa operasyon. Kapag nag-ulat ang app ng isang nabigong session, ituring ito bilang isang senyales upang pasimplehin muna ang modelo at mga parameter. + Tanggalin at muling i-download ang isang modelo kung nabigo ito kaagad pagkatapos ng pag-download o kumilos na parang sira ang file. + Subukan ang isang built-in na nada-download na modelo bago sisihin ang isang na-import na custom na modelo, dahil ang mga na-import na modelo ay maaaring magkaroon ng mga hindi tugmang input. + Gumamit ng Mga Log ng App pagkatapos kopyahin ang pagkabigo kung kailangan mong mag-ulat ng pag-crash o error sa session. + Eksperimento sa Shader Studio + Gumamit ng mga epekto ng GPU shader para sa advanced na pagpoproseso ng imahe at custom na hitsura + Magtrabaho nang mabuti sa mga shader + Makapangyarihan ang Shader Studio, ngunit kailangan ng shader code at mga parameter ng maliliit at masusubok na pagbabago. Tratuhin ito bilang isang pang-eksperimentong tool: panatilihin ang isang gumaganang baseline, baguhin ang isang bagay sa isang pagkakataon, at subukan gamit ang iba\'t ibang laki ng larawan bago magtiwala sa isang preset. + Magsimula sa isang kilalang gumaganang shader o isang simpleng epekto bago magdagdag ng mga parameter. + Baguhin ang isang parameter o block ng code sa isang pagkakataon at tingnan ang preview para sa mga error. + I-export ang mga preset pagkatapos lamang subukan ang mga ito sa ilang iba\'t ibang laki ng larawan. + Gumawa ng SVG o ASCII na sining + Bumuo ng mga asset na tulad ng vector o mga imaheng nakabatay sa text para sa malikhaing output + Piliin ang uri ng creative na output + Ang mga tool ng SVG at ASCII ay naghahatid ng iba\'t ibang target: scalable graphics o text-style rendering. Parehong malikhaing mga conversion, kaya ang pag-preview sa huling sukat ay mas mahalaga kaysa sa paghatol sa resulta mula sa isang maliit na thumbnail. + Gumamit ng SVG Maker kapag ang resulta ay dapat na malinis o magagamit muli sa mga daloy ng trabaho sa disenyo. + Gumamit ng ASCII Art kapag gusto mo ng naka-istilong representasyon ng teksto ng isang imahe. + I-preview sa huling sukat dahil maaaring mawala ang maliliit na detalye sa nabuong output. + Bumuo ng mga texture ng ingay + Gumawa ng mga texture na pamamaraan para sa mga background, mask, overlay, o mga eksperimento + Tune procedural output + Ang pagbuo ng ingay ay lumilikha ng mga bagong larawan mula sa mga parameter, kaya ang maliliit na pagbabago ay maaaring makagawa ng ibang mga resulta. Ito ay kapaki-pakinabang para sa mga texture, mask, overlay, placeholder, o pagsubok sa compression na may mga detalyadong pattern. + Pumili ng uri ng ingay at laki ng output bago i-tune ang mga pinong detalye. + Ayusin ang sukat, intensity, mga kulay, o buto hanggang sa magkasya ang pattern sa target na paggamit. + I-save ang mga kapaki-pakinabang na resulta at isulat ang mga parameter kung kailangan mong muling likhain ang texture sa ibang pagkakataon. + Mga proyekto ng markup + Gumamit ng mga file ng proyekto kapag kailangang manatiling nae-edit ang mga anotasyon pagkatapos isara ang app + Panatilihing magagamit muli ang mga layered na pag-edit + Kapaki-pakinabang ang mga markup project file kapag ang isang screenshot, diagram, o larawan ng pagtuturo ay maaaring mangailangan ng mga pagbabago sa ibang pagkakataon. Ang mga na-export na PNG/JPEG file ay mga huling larawan, habang ang mga file ng proyekto ay nagpapanatili ng mga nae-edit na layer at estado ng pag-edit para sa mga pagsasaayos sa hinaharap. + Gumamit ng Mga Markup Layers kapag ang mga anotasyon, callout, o mga elemento ng layout ay dapat manatiling nae-edit. + I-save o muling buksan ang file ng proyekto bago mag-export ng panghuling larawan kung inaasahan mo ang higit pang mga pagbabago. + I-export lamang ang isang normal na larawan kapag handa ka nang magbahagi ng pinatag na huling bersyon. + I-encrypt ang mga file + Protektahan ang mga file gamit ang isang password at subukan ang decryption bago tanggalin ang anumang pinagmulang kopya + Maingat na pangasiwaan ang mga naka-encrypt na output + Maaaring protektahan ng mga tool ng cipher ang mga file gamit ang pag-encrypt na nakabatay sa password, ngunit hindi ito kapalit para sa pag-alala sa susi o pagpapanatili ng mga backup. Ang pag-encrypt ay kapaki-pakinabang para sa transportasyon at imbakan, habang ang ZIP ay mas mahusay kapag ang pangunahing layunin ay pag-bundle ng ilang mga output. + I-encrypt muna ang isang kopya at panatilihin ang orihinal hanggang sa masubukan mo na gumagana ang decryption sa password na iyong inimbak. + Gumamit ng tagapamahala ng password o ibang ligtas na lugar para sa susi; hindi mahulaan ng app ang isang nawalang password para sa iyo. + Ibahagi lamang ang mga naka-encrypt na file sa mga taong mayroon ding password at alam kung aling tool o paraan ang dapat mag-decrypt sa kanila. + Ayusin ang mga gamit + Magpangkat, mag-order, maghanap, at mag-pin ng mga tool para tumugma ang pangunahing screen sa iyong tunay na daloy ng trabaho + Gawing mas mabilis ang pangunahing screen + Ang mga setting ng pag-aayos ng tool ay nagkakahalaga ng pag-tune kapag nalaman mo kung aling mga tool ang pinakamadalas mong ginagamit. Ang pagpapangkat ay nakakatulong sa paggalugad, nakakatulong ang custom na pagkakasunud-sunod sa memorya ng kalamnan, at ang mga paborito ay panatilihing naaabot ang mga pang-araw-araw na tool kahit na ang buong listahan ay nagiging malaki. + Gumamit ng grouped mode habang pinag-aaralan ang app o kapag mas gusto mo ang mga tool na nakaayos ayon sa uri ng gawain. + Lumipat sa custom na order kapag alam mo na ang iyong mga madalas na tool at gusto mo ng mas kaunting mga scroll. + Gamitin ang mga setting ng paghahanap at mga paborito nang magkasama para sa mga bihirang tool na naaalala mo sa pangalan ngunit ayaw mong makita sa lahat ng oras. + Pangasiwaan ang malalaking file + Iwasan ang mabagal na pag-export, presyon ng memorya, at hindi inaasahang malaking output + Bawasan ang trabaho bago i-export + Ang napakalaking larawan, mahabang batch, at mataas na kalidad na mga format ay maaaring tumagal ng mas maraming memorya at oras. Ang pinakamabilis na pag-aayos ay kadalasang binabawasan ang dami ng trabaho bago ang pinakamabigat na operasyon, hindi naghihintay na matapos ang parehong napakalaking input. + Baguhin ang laki ng malalaking larawan bago maglapat ng mabibigat na filter, OCR, o PDF conversion. + Gumamit muna ng mas maliit na batch para kumpirmahin ang mga setting, pagkatapos ay iproseso ang iba gamit ang parehong preset. + Ibaba ang kalidad o baguhin ang format kapag ang output file ay mas malaki kaysa sa inaasahan. + Cache at mga preview + Unawain ang mga nabuong preview, paglilinis ng cache, at paggamit ng storage pagkatapos ng mabibigat na session + Panatilihing balanse ang storage at bilis + Ang pagbuo ng preview at cache ay maaaring gawing mas mabilis ang paulit-ulit na pag-browse, ngunit maaaring mag-iwan ng pansamantalang data ang mga mabibigat na session sa pag-edit. Nakakatulong ang mga setting ng cache kapag mabagal ang app, masikip ang storage, o mukhang lipas ang mga preview pagkatapos ng maraming eksperimento. + Panatilihing naka-enable ang mga preview kapag nagba-browse ng maraming larawan ay mas mahalaga kaysa sa pagliit ng pansamantalang storage. + I-clear ang cache pagkatapos ng malalaking batch, mga nabigong eksperimento, o mahabang session na may maraming high-resolution na file. + Gumamit ng awtomatikong pag-clear ng cache kung mas gusto mo ang paglilinis ng storage kaysa panatilihing mainit ang mga preview sa pagitan ng mga paglulunsad. + Ayusin ang gawi ng screen + Gumamit ng fullscreen, secure mode, brightness, at mga opsyon sa system bar para sa nakatutok na trabaho + Gawing akma ang interface sa sitwasyon + Nakakatulong ang mga setting ng screen kapag nag-edit ka sa landscape, nagpapakita ng mga pribadong larawan, o nangangailangan ng pare-parehong liwanag. Hindi kinakailangan ang mga ito para sa normal na paggamit, ngunit nilulutas nila ang mga awkward na sitwasyon gaya ng pag-inspeksyon ng QR, mga pribadong preview, o masikip na pag-edit ng landscape. + Gumamit ng mga setting ng fullscreen o system bar kapag mas mahalaga ang pag-edit ng espasyo kaysa sa navigation chrome. + Gumamit ng secure mode kapag hindi dapat lumabas ang mga pribadong larawan sa mga screenshot o mga preview ng app. + Gumamit ng brightness enforcement para sa mga tool kung saan mahirap suriin ang mga madilim na larawan o QR code. + Panatilihin ang transparency + Pigilan ang mga transparent na background na maging puti, itim, o patag + Gumamit ng alpha-aware na output + Mananatili lamang ang transparency kapag sinusuportahan ng piniling tool at format ng output ang alpha. Kung ang isang na-export na background ay nagiging puti o itim, ang problema ay karaniwang ang output format, isang fill/background na setting, o isang patutunguhang app na nag-flatten sa larawan. + Pumili ng PNG o WebP kapag nag-e-export ng mga larawan, sticker, logo, o overlay na inalis sa background. + Iwasan ang JPEG para sa transparent na output dahil hindi ito nag-iimbak ng alpha channel. + Suriin ang mga setting na nauugnay sa kulay ng background para sa mga alpha format kung ang preview ay nagpapakita ng punong background. + Ayusin ang mga isyu sa pagbabahagi at pag-import + Gumamit ng mga setting ng picker at mga sinusuportahang format kapag ang mga file ay hindi lumalabas o nakabukas nang tama + Kunin ang file sa app + Ang mga problema sa pag-import ay kadalasang sanhi ng mga pahintulot ng provider, hindi sinusuportahang mga format, o gawi ng picker. Ang mga file na ibinahagi mula sa mga cloud app at messenger ay maaaring pansamantala, kaya ang pagpili ng patuloy na pinagmulan ay maaaring maging mas maaasahan para sa mas mahabang pag-edit. + Subukang buksan ang file sa pamamagitan ng tagapili ng system sa halip na isang shortcut ng kamakailang mga file. + Kung ang isang format ay hindi sinusuportahan ng isang tool, i-convert muna ito o magbukas ng mas partikular na tool. + Suriin ang mga setting ng picker at launcher ng larawan kung ang mga daloy ng pagbabahagi o direktang pagbubukas ng tool ay kumikilos nang iba kaysa sa inaasahan. + Lumabas sa kumpirmasyon + Iwasang mawala ang mga hindi na-save na pag-edit kapag aksidenteng nangyari ang mga galaw, pagpindot sa likod, o pagpapalit ng tool + Protektahan ang hindi natapos na gawain + Nakakatulong ang pagkumpirma sa paglabas ng tool kapag gumagamit ka ng gesture navigation, nag-edit ng malalaking file, o madalas na lumipat sa pagitan ng mga app. Nagdaragdag ito ng maliit na pag-pause bago umalis sa isang tool upang hindi aksidenteng mawala ang mga hindi natapos na setting at preview. + Paganahin ang kumpirmasyon kung ang hindi sinasadyang mga galaw sa likod ay nagsara ng isang tool bago ka nag-export. + Panatilihing naka-disable ito kung kadalasan ay gumagawa ka ng mabilis na one-step na conversion at mas gusto ang mas mabilis na nabigasyon. + Gamitin ito kasama ng mga preset para sa mas mahabang daloy ng trabaho kung saan nakakainis ang muling pagtatayo ng mga setting. + Gumamit ng mga log kapag nag-uulat ng mga problema + Mangolekta ng mga kapaki-pakinabang na detalye bago magbukas ng isyu o makipag-ugnayan sa developer + Mag-ulat ng mga isyu sa konteksto + Ang isang malinaw na ulat na may mga hakbang, bersyon ng app, uri ng input, at mga log ay mas madaling masuri. Ang mga log ay pinakakapaki-pakinabang pagkatapos na muling gumawa ng isang problema dahil pinapanatili nilang sariwa ang nakapaligid na konteksto. + Tandaan ang pangalan ng tool, ang eksaktong aksyon, at kung mangyayari ito sa isang file o bawat file. + Buksan ang Mga Log ng App pagkatapos kopyahin ang isyu at ibahagi ang nauugnay na output ng log kapag posible. + Maglakip lamang ng mga screenshot o sample na file kapag hindi naglalaman ang mga ito ng pribadong impormasyon. + Nahinto ang pagproseso sa background + Hindi hinayaan ng Android na magsimula ang notification sa pagpoproseso sa background sa oras. Isa itong limitasyon sa timing ng system na hindi mapagkakatiwalaang ayusin. I-restart ang app at subukang muli; Maaaring makatulong ang pagpapanatiling bukas ng app at pagpayag sa mga notification o background work. + Laktawan ang pagpili ng file + Buksan ang mga tool nang mas mabilis kapag ang input ay karaniwang nagmumula sa share, clipboard, o isang nakaraang screen + Gawing mas mabilis ang paulit-ulit na paglulunsad ng tool + Binago ng Laktawan ang Pagpili ng File ang unang hakbang ng mga sinusuportahang tool. Ito ay kapaki-pakinabang kapag karaniwan kang naglalagay ng tool na may napili nang larawan o mula sa Android share actions, ngunit maaari itong maging nakalilito kung inaasahan mong ang bawat tool ay humingi kaagad ng file. + Paganahin lamang ito kapag ang iyong karaniwang daloy ay nagbibigay na ng pinagmulang larawan bago magbukas ang tool. + Panatilihing naka-disable ito habang pinag-aaralan ang app, dahil ang tahasang pagpili ay ginagawang mas madaling maunawaan ang daloy ng bawat tool. + Kung bubukas ang isang tool nang walang malinaw na input, huwag paganahin ang setting na ito o magsimula sa Share/Open Kung kaya\'t naipasa muna ng Android ang file. + Buksan muna ang editor + Laktawan ang preview kapag ang isang nakabahaging larawan ay dapat na dumiretso sa pag-edit + Piliin ang preview o i-edit bilang unang stop + Ang Open Edit Instead Of Preview ay para sa mga user na alam na nilang gusto nilang baguhin ang larawan. Mas ligtas ang pag-preview kapag nag-inspeksyon ka ng mga file mula sa chat o cloud storage; Ang direktang pag-edit ay mas mabilis para sa mga screenshot, mabilisang pag-crop, anotasyon, at isahang pagwawasto. + I-enable ang direktang pag-edit kung karamihan sa mga nakabahaging larawan ay agad na nangangailangan ng mga pagbabago sa pag-crop, markup, mga filter, o pag-export. + Mag-iwan muna ng preview kapag madalas mong sinisiyasat ang metadata, mga dimensyon, transparency, o kalidad ng larawan bago magpasya. + Gamitin ito kasama ng mga paborito para makarating ang mga nakabahaging larawan malapit sa mga tool na aktwal mong ginagamit. + Preset na text entry + Maglagay ng eksaktong mga preset na halaga sa halip na paulit-ulit na ayusin ang mga kontrol + Gumamit ng mga tumpak na halaga kapag mabagal ang mga slider + Nakakatulong ang preset na text entry kapag kailangan mo ng mga eksaktong numero para sa paulit-ulit na trabaho: mga laki ng social na imahe, mga margin ng dokumento, mga target ng compression, lapad ng linya, o iba pang mga value na nakakainis na abutin gamit ang mga galaw. Kapaki-pakinabang din ito sa maliliit na screen kung saan mas mahirap ang paggalaw ng pinong slider. + I-enable ang text entry kung madalas mong ginagamit muli ang mga eksaktong laki, porsyento, mga value ng kalidad, o mga parameter ng tool. + I-save ang value bilang preset pagkatapos itong i-type nang isang beses, pagkatapos ay gamitin itong muli sa halip na muling buuin ang parehong setup. + Panatilihin ang mga kontrol sa kilos para sa magaspang na visual na pag-tune at mga nai-type na halaga para sa mahigpit na mga kinakailangan sa output. + I-save malapit sa orihinal + Ilagay ang mga nabuong file sa tabi ng mga pinagmulang larawan kapag mahalaga ang konteksto ng folder + Panatilihin ang mga output sa kanilang mga mapagkukunan + Ang Save To Original Folder ay madaling gamitin para sa mga folder ng camera, mga folder ng proyekto, mga pag-scan ng dokumento, at mga batch ng kliyente kung saan dapat manatili ang na-edit na kopya sa tabi ng pinagmulan. Ito ay maaaring hindi gaanong malinis kaysa sa isang nakalaang output folder, kaya pagsamahin ito sa malinaw na mga suffix o pattern ng filename. + Paganahin ito kapag ang bawat source folder ay makabuluhan na at ang mga output ay dapat manatiling nakapangkat doon. + Gumamit ng mga suffix, prefix, timestamp, o pattern ng filename upang madaling ihiwalay ang mga na-edit na kopya sa mga orihinal. + Huwag paganahin ito para sa magkahalong mga batch kapag gusto mong makolekta ang lahat ng nabuong file sa isang folder ng pag-export. + Laktawan ang mas malaking output + Iwasang mag-save ng mga conversion na hindi inaasahang nagiging mas mabigat kaysa sa pinagmulan + Protektahan ang mga batch mula sa hindi magandang laki ng mga sorpresa + Pinapalaki ng ilang conversion ang mga file, lalo na ang mga screenshot ng PNG, mga naka-compress na larawan, WebP na may mataas na kalidad, o mga larawang may napreserbang metadata. Payagan ang Laktawan Kung Mas Malaki ang humahadlang sa mga output na iyon na palitan ang isang mas maliit na pinagmulan sa mga daloy ng trabaho kung saan ang pagbabawas ng laki ay ang pangunahing layunin. + I-enable ito kapag ang pag-export ay nilalayong i-compress, baguhin ang laki, o ihanda ang mga file para sa mga limitasyon sa pag-upload. + Suriin ang mga nilaktawan na file pagkatapos ng isang batch; maaaring na-optimize na ang mga ito o maaaring mangailangan ng ibang format. + I-disable ito kapag mahalaga ang kalidad, transparency, o compatibility ng format kaysa sa panghuling laki ng file. + Clipboard at mga link + Kontrolin ang awtomatikong clipboard input at mga preview ng link para sa mas mabilis na mga tool na nakabatay sa text + Gawing predictable ang awtomatikong pag-input + Ang mga setting ng clipboard at link ay maginhawa para sa QR, Base64, URL, at mga workflow na mabigat sa text, ngunit ang awtomatikong pag-input ay dapat manatiling sinadya. Kung tila pinupunan ng app ang mga field nang mag-isa, tingnan ang gawi ng pag-paste ng clipboard; kung maingay o mabagal ang mga link card, ayusin ang gawi ng preview ng link. + Payagan ang awtomatikong pag-paste ng clipboard kapag madalas kang kumopya ng teksto at agad na nagbukas ng isang tool sa pagtutugma. + I-disable ang awtomatikong pag-paste kung lumalabas ang pribadong clipboard na content sa mga tool kung saan hindi mo ito inaasahan. + I-off ang mga preview ng link kapag ang pagkuha ng preview ay mabagal, nakakagambala, o hindi kailangan para sa iyong workflow. + Mga compact na kontrol + Gumamit ng mas siksik na mga tagapili at mabilis na paglalagay ng mga setting kapag masikip ang espasyo sa screen + Magkasya ng higit pang mga kontrol sa maliliit na screen + Nakakatulong ang mga compact selector at mabilis na paglalagay ng mga setting sa maliliit na telepono, pag-edit ng landscape, at mga tool na may maraming parameter. Ang tradeoff ay ang mga kontrol ay maaaring maging mas siksik, kaya ito ay pinakamahusay pagkatapos mong makilala ang mga karaniwang opsyon. + Paganahin ang mga compact na tagapili kapag ang mga dialog o mga listahan ng opsyon ay masyadong mataas para sa screen. + Ayusin ang gilid ng mabilis na mga setting kung mas madaling maabot ang mga kontrol ng tool mula sa isang gilid ng display. + Bumalik sa mga mas malawak na kontrol kung pinag-aaralan mo pa rin ang app o madalas na hindi na-tap ang mga siksik na opsyon. + Mag-load ng mga larawan mula sa web + Mag-paste ng page o URL ng larawan, i-preview ang mga na-parse na larawan, pagkatapos ay i-save o ibahagi ang mga napiling resulta + Gumamit ng mga larawan sa web nang sinasadya + Ang Web Image Loading ay nagpi-parse ng mga source ng larawan mula sa ibinigay na URL, nagpi-preview sa unang available na larawan, at hinahayaan kang i-save o ibahagi ang mga napiling na-parse na larawan. Gumagamit ang ilang site ng pansamantala, protektado, o mga link na binuo ng script, kaya ang isang page na gumagana sa isang browser ay maaari pa ring magbalik ng walang magagamit na mga larawan. + Mag-paste ng direktang URL ng larawan o URL ng pahina at hintaying lumabas ang listahan ng na-parse na larawan. + Gamitin ang frame o mga kontrol sa pagpili kapag naglalaman ang page ng ilang larawan at kailangan mo lang ng ilan sa mga ito. + Kung walang naglo-load, subukan ang direktang link ng imahe o mag-download muna sa pamamagitan ng browser; maaaring harangan ng site ang pag-parse o gumamit ng mga pribadong link. + Pumili ng uri ng pagbabago ng laki + Unawain ang Explicit, Flexible, I-crop, at Fit bago pilitin ang isang larawan sa mga bagong dimensyon + Kontrolin ang hugis, canvas, at aspect ratio + Ang uri ng pagbabago ng laki ay nagpapasya kung ano ang mangyayari kapag ang hiniling na lapad at taas ay hindi tumugma sa orihinal na aspect ratio. Ito ang pagkakaiba sa pagitan ng mga stretching pixel, pag-iingat ng mga proporsyon, pag-crop ng dagdag na lugar, o pagdaragdag ng background canvas. + Gamitin lang ang Explicit kapag katanggap-tanggap ang pagbaluktot o ang pinagmulan ay mayroon nang parehong aspect ratio sa target. + Gumamit ng Flexible upang mapanatili ang mga proporsyon sa pamamagitan ng pagbabago ng laki mula sa mahalagang bahagi, lalo na para sa mga larawan at halo-halong batch. + Gamitin ang I-crop para sa mahigpit na mga frame na walang hangganan, o Fit kapag ang buong larawan ay dapat na manatiling nakikita sa loob ng isang nakapirming canvas. + Pumili ng image scale mode + Piliin ang resampling filter na kumokontrol sa sharpness, smoothness, speed, at edge artifacts + Baguhin ang laki nang hindi sinasadyang lambot + Kinokontrol ng image scale mode kung paano kinakalkula ang mga bagong pixel sa panahon ng pagbabago ng laki. Ang mga simpleng mode ay mabilis at mahuhulaan, habang ang mga advanced na filter ay maaaring mapanatili ang detalye nang mas mahusay ngunit maaaring patalasin ang mga gilid, lumikha ng halos, o mas matagal sa malalaking larawan. + Gumamit ng Basic o Bilinear para sa mabilis na pagbabago ng laki araw-araw kapag kailangan lang na mas maliit at malinis ang resulta. + Gumamit ng mga mode na Lanczos, Robidoux, Spline, o EWA-style kapag ang mga pinong detalye, text, o de-kalidad na downscaling ay mahalaga. + Gamitin ang Pinakamalapit para sa pixel art, mga icon, at hard-edged na graphics kung saan ang mga blur na pixel ay makakasira sa hitsura. + I-scale ang espasyo ng kulay + Magpasya kung paano pinaghalo ang mga kulay habang ang mga pixel ay nire-resemple sa panahon ng pagbabago ng laki + Panatilihing natural ang mga gradient at gilid + Binabago ng espasyo ng kulay ng scale ang matematika na ginamit habang binabago ang laki. Karamihan sa mga larawan ay maayos sa default, ngunit ang mga linear o perceptual na espasyo ay maaaring gawing mas malinis ang mga gradient, madilim na gilid, at mga saturated na kulay pagkatapos ng malakas na pag-scale. + Iwanan ang default kapag mahalaga ang bilis at pagiging tugma kaysa sa maliliit na pagkakaiba ng kulay. + Subukan ang mga Linear o perceptual mode kapag ang mga madilim na outline, UI screenshot, gradient, o color band ay mukhang mali pagkatapos baguhin ang laki. + Ihambing ang bago at pagkatapos sa totoong target na screen, dahil ang mga pagbabago sa kulay-space ay maaaring maging banayad at nakadepende sa nilalaman. + Limitahan ang mga mode ng Pag-resize + Alamin kung kailan dapat laktawan, i-recode, o bawasan ang mga larawang sobra sa limitasyon para magkasya + Piliin kung ano ang mangyayari sa limitasyon + Ang Limit Resize ay hindi lamang isang tool na mas maliit na larawan. Ang mode nito ang nagpapasya kung ano ang gagawin ng app kapag ang isang source ay nasa loob na ng iyong mga limitasyon o kapag isang panig lang ang lumalabag sa panuntunan, na mahalaga para sa pinaghalong mga folder at paghahanda sa pag-upload. + Gamitin ang Laktawan kapag ang mga larawan sa loob ng mga limitasyon ay dapat iwanang hindi nagalaw at hindi na i-export muli. + Gamitin ang Recode kapag katanggap-tanggap ang mga dimensyon ngunit kailangan mo pa rin ng bagong format, gawi ng metadata, kalidad, o filename. + Gamitin ang Zoom kapag ang mga larawang lumalabag sa mga limitasyon ay dapat bawasan nang proporsyonal hanggang sa magkasya ang mga ito sa pinapayagang kahon. + Mga target na aspect ratio + Iwasan ang mga nakaunat na mukha, cut-off na mga gilid, at hindi inaasahang mga hangganan sa mahigpit na laki ng output + Itugma ang hugis bago baguhin ang laki + Ang lapad at taas ay kalahati lamang ng target na baguhin ang laki. Ang aspect ratio ay nagpapasya kung ang larawan ay maaaring magkasya nang natural, nangangailangan ng pag-crop, o nangangailangan ng isang canvas sa paligid nito. Ang pagsuri muna sa hugis ay pinipigilan ang karamihan sa mga nakakagulat na resulta ng pagbabago ng laki. + Ihambing ang pinagmulang hugis sa target na hugis bago piliin ang Explicit, I-crop, o Fit. + I-crop muna kapag ang paksa ay maaaring mawalan ng karagdagang background at ang patutunguhan ay nangangailangan ng isang mahigpit na frame. + Gamitin ang Fit o Flexible kapag dapat manatiling nakikita ang bawat bahagi ng orihinal na larawan. + Upscale o normal na resize + Alamin kung kailan nangangailangan ng AI ang pagdaragdag ng mga pixel at kung sapat na ang simpleng pag-scale + Huwag malito ang laki sa detalye + Ang normal na resize ay nagbabago ng mga sukat sa pamamagitan ng interpolating pixels; hindi ito makakaimbento ng totoong detalye. Ang AI upscale ay maaaring lumikha ng mas matalas na detalye, ngunit ito ay mas mabagal at maaaring mag-hallucinate ng texture, kaya ang tamang pagpipilian ay depende sa kung kailangan mo ng compatibility, bilis, o visual na pagpapanumbalik. + Gumamit ng normal na pagbabago para sa mga thumbnail, mga limitasyon sa pag-upload, mga screenshot, at mga simpleng pagbabago sa dimensyon. + Gumamit ng AI upscale kapag ang isang maliit o malambot na imahe ay kailangang magmukhang mas mahusay sa mas malaking sukat. + Ihambing ang mga mukha, text, at pattern pagkatapos ng AI upscale dahil ang nabuong detalye ay maaaring magmukhang kapani-paniwala ngunit mali. + Mahalaga ang pagkakasunud-sunod ng filter + Ilapat ang paglilinis, kulay, sharpness, at final compression sa isang sinadyang pagkakasunud-sunod + Bumuo ng mas malinis na stack ng filter + Ang mga filter ay hindi palaging mapapalitan. Ang isang blur bago ang hasa, isang contrast boost bago ang OCR, o isang pagbabago ng kulay bago ang compression ay maaaring makagawa ng ibang-iba na output mula sa parehong mga kontrol. + I-crop at i-rotate muna para gumana ang mga filter sa ibang pagkakataon sa mga pixel na mananatili. + Ilapat ang exposure, white balance, at contrast bago patalasin o stylized effect. + Panatilihin ang panghuling resize at compression malapit sa dulo upang ang mga na-preview na detalye ay makaligtas sa pag-export nang predictably. + Ayusin ang mga gilid ng background + Malinis na halos, mga natitirang anino, buhok, mga butas, at malambot na mga balangkas pagkatapos ng awtomatikong pag-alis + Gawing sinadya ang mga ginupit + Ang mga awtomatikong maskara ay kadalasang maganda sa isang sulyap ngunit nagpapakita ng mga problema sa maliwanag, madilim, o may pattern na background. Ang paglilinis sa gilid ay ang pagkakaiba sa pagitan ng isang mabilisang cutout at isang magagamit muli na sticker, logo, o larawan ng produkto. + I-preview ang resulta laban sa magkakaibang mga background upang ipakita ang halos at nawawalang detalye sa gilid. + Gumamit ng restore para sa buhok, mga sulok, at mga transparent na bagay bago burahin ang mga natira sa paligid. + I-export ang isang maliit na pagsubok sa huling kulay ng background kapag ang cutout ay ilalagay sa isang disenyo. + Mga nababasang watermark + Gawing nakikita ang mga marka sa maliwanag, madilim, abala, at mga crop na larawan nang hindi nasisira ang larawan + Protektahan ang imahe nang hindi itinatago + Ang isang watermark na mukhang maganda sa isang larawan ay maaaring mawala sa isa pa. Dapat piliin ang laki, opacity, contrast, placement, at pag-uulit para sa buong batch, hindi lang sa unang preview. + Subukan ang marka sa pinakamaliwanag at pinakamadilim na larawan sa batch bago i-export ang lahat ng file. + Gumamit ng mas mababang opacity para sa malalaking paulit-ulit na marka at mas malakas na contrast para sa maliliit na marka sa sulok. + Panatilihing malinaw ang mahahalagang mukha, text, at mga detalye ng produkto maliban kung ang marka ay sinadya upang maiwasan ang muling paggamit. + Hatiin ang isang larawan sa mga tile + Gupitin ang isang larawan ayon sa mga row, column, custom na porsyento, format, at kalidad + Maghanda ng mga grids at nag-order ng mga piraso + Ang Image Splitting ay lumilikha ng maraming output mula sa isang source na larawan. Maaari itong hatiin ayon sa mga bilang ng row at column, ayusin ang mga porsyento ng row o column, at mag-save ng mga piraso gamit ang napiling format at kalidad ng output, na kapaki-pakinabang para sa mga grids, hiwa ng page, panorama, at order na mga social post. + Piliin ang pinagmulang larawan, pagkatapos ay itakda ang bilang ng mga row at column na kailangan mo. + Ayusin ang mga porsyento ng row o column kapag hindi dapat magkapantay ang laki ng mga piraso. + Pumili ng format at kalidad ng output bago i-save upang ang bawat nabuong piraso ay gumagamit ng parehong mga panuntunan sa pag-export. + Gupitin ang mga piraso ng larawan + Alisin ang patayo o pahalang na mga rehiyon at pagsamahin muli ang natitirang mga bahagi + Alisin ang isang rehiyon nang hindi nag-iiwan ng puwang + Ang Pagputol ng Imahe ay iba sa karaniwang pag-crop. Maaari itong mag-alis ng napiling patayo o pahalang na strip, pagkatapos ay pagsamahin ang mga natitirang bahagi ng larawan. Sa halip, pinapanatili ng inverse mode ang napiling rehiyon, at ang paggamit ng parehong inverse na direksyon ay nagpapanatili sa napiling parihaba. + Gumamit ng vertical cutting para sa mga gilid na rehiyon, column, o gitnang strips; gumamit ng pahalang na pagputol para sa mga banner, gaps, o row. + I-enable ang inverse sa isang axis kapag gusto mong panatilihin ang napiling bahagi sa halip na alisin ito. + I-preview ang mga gilid pagkatapos i-cut dahil maaaring magmukhang biglaan ang pinagsamang content kapag tumawid ang inalis na bahagi sa mahahalagang detalye. + Piliin ang format ng output + Pumili ng JPEG, PNG, WebP, AVIF, JXL, o PDF batay sa nilalaman at destinasyon + Hayaan ang destinasyon na magpasya sa format + Ang pinakamahusay na format ay depende sa kung ano ang nilalaman ng larawan at kung saan ito gagamitin. Ang mga larawan, screenshot, transparent na asset, dokumento, at animated na file ay nabigo sa iba\'t ibang paraan kapag pinilit sa maling format. + Gumamit ng JPEG para sa malawak na compatibility ng larawan kapag hindi kinakailangan ang transparency at eksaktong pixel. + Gumamit ng PNG para sa mga matalim na screenshot, UI capture, transparent na graphics, at walang pagkawalang pag-edit. + Gamitin ang WebP, AVIF, o JXL kapag mahalaga ang compact na modernong output at sinusuportahan ito ng tumatanggap na app. + I-extract ang mga audio cover + I-save ang naka-embed na album art o mga available na media frame mula sa mga audio file bilang mga PNG na larawan + Hilahin ang mga likhang sining mula sa mga audio file + Gumagamit ang Mga Audio Cover ng Android media metadata para basahin ang naka-embed na artwork mula sa mga audio file. Kapag hindi available ang naka-embed na artwork, maaaring bumalik ang retriever sa isang media frame kung makakapagbigay ang Android ng isa; kung wala ang alinman, ang tool ay nag-uulat na walang larawang kukunin. + Buksan ang Mga Audio Cover at pumili ng isa o higit pang mga audio file na may inaasahang cover art. + Suriin ang na-extract na pabalat bago i-save o ibahagi, lalo na para sa mga file mula sa streaming o pag-record ng mga app. + Kung walang makitang larawan, malamang na walang naka-embed na takip ang audio file o hindi mailantad ng Android ang isang magagamit na frame. + Mga petsa at metadata ng lokasyon + Magpasya kung ano ang iingatan kapag mahalaga ang oras ng pagkuha ngunit hindi dapat tumagas ang data ng GPS o device + Paghiwalayin ang kapaki-pakinabang na metadata sa pribadong metadata + Ang metadata ay hindi lahat ay pantay na peligroso. Maaaring maging kapaki-pakinabang ang mga petsa ng pagkuha para sa mga archive at pag-uuri, habang ang GPS, mga serial-like na field ng device, mga tag ng software, o mga field ng may-ari ay maaaring magbunyag ng higit sa nilalayon. + Panatilihin ang mga tag ng petsa at oras kapag mahalaga ang pagkakasunod-sunod ng mga album, pag-scan, o kasaysayan ng proyekto. + Alisin ang GPS at mga tag na nagpapakilala sa device bago ang pampublikong pagbabahagi o pagpapadala ng mga larawan sa mga estranghero. + Mag-export ng sample at suriin muli ang EXIF ​​kapag mahigpit ang mga kinakailangan sa privacy. + Iwasan ang mga banggaan ng filename + Protektahan ang mga batch na output kapag maraming file ang nagbabahagi ng mga pangalan tulad ng image.jpg o screenshot.png + Gawing kakaiba ang bawat pangalan ng output + Karaniwan ang mga duplicate na filename kapag nagmula ang mga larawan sa mga messenger, screenshot, cloud folder, o maraming camera. Pinipigilan ng isang magandang pattern ang hindi sinasadyang pagpapalit at pinapanatili ang mga output na masusubaybayan pabalik sa kanilang mga pinagmulan. + Isama ang orihinal na pangalan at isang suffix kapag ang na-edit na kopya ay dapat na makilala. + Magdagdag ng sequence, timestamp, preset, o mga dimensyon kapag nagpoproseso ng malalaking halo-halong batch. + Iwasan ang pag-overwrite ng mga daloy ng trabaho hanggang sa ma-verify mo na ang pattern ng pagbibigay ng pangalan ay hindi maaaring magkabanggaan. + I-save o ibahagi + Pumili sa pagitan ng isang paulit-ulit na file at isang pansamantalang handoff sa isa pang app + I-export nang may tamang layunin + Maaaring magkapareho ang pakiramdam ng Save and Share, ngunit malulutas nila ang iba\'t ibang problema. Ang pag-save ay lumilikha ng isang file na mahahanap mo sa ibang pagkakataon; ang pagbabahagi ay nagpapadala ng nabuong resulta sa pamamagitan ng Android sa isa pang app at maaaring hindi mag-iwan ng matibay na lokal na kopya. + Gamitin ang I-save kapag ang output ay dapat manatili sa isang kilalang folder, ma-back up, o magamit muli sa ibang pagkakataon. + Gamitin ang Ibahagi para sa mga mabilisang mensahe, email attachment, social posting, o pagpapadala ng resulta sa ibang editor. + I-save muna kapag hindi maaasahan ang pagtanggap ng app o kapag ang isang nabigong bahagi ay nakakainis na muling likhain. + Laki at margin ng pahina ng PDF + Pumili ng laki ng papel, akma sa pag-uugali, at silid sa paghinga bago gumawa ng dokumento + Gawing napi-print at nababasa ang mga pahina ng larawan + Ang mga imahe ay maaaring maging awkward na mga pahina ng PDF kapag ang kanilang hugis ay hindi tumutugma sa napiling laki ng papel. Ang mga margin, fit mode, at oryentasyon ng page ay nagpapasya kung ang nilalaman ay na-crop, maliit, nakaunat, o komportableng basahin. + Gumamit ng totoong sukat ng papel kapag ang PDF ay maaaring i-print o isumite sa isang form. + Gumamit ng mga margin para sa mga pag-scan at mga screenshot upang ang teksto ay hindi umupo sa gilid ng pahina. + I-preview ang portrait at landscape na mga page nang magkasama kapag ang mga pinagmulang larawan ay may magkahalong oryentasyon. + Linisin ang mga pag-scan + Pahusayin ang mga gilid ng papel, anino, kaibahan, pag-ikot, at pagiging madaling mabasa bago ang PDF o OCR + Maghanda ng mga pahina bago i-archive + Ang isang pag-scan ay maaaring teknikal na makuha ngunit mahirap pa ring basahin. Ang mga maliliit na hakbang sa paglilinis bago ang pag-export ng PDF ay kadalasang mas mahalaga kaysa sa mga setting ng compression, lalo na para sa mga resibo, sulat-kamay na tala, at mahinang mga pahina. + Iwasto ang pananaw at i-crop ang mga gilid ng mesa, daliri, anino, at kalat sa background. + Maingat na dagdagan ang contrast upang maging mas malinaw ang text nang hindi sinisira ang mga selyo, lagda, o mga marka ng lapis. + Patakbuhin lamang ang OCR pagkatapos na patayo at nababasa ang page, pagkatapos ay manu-manong i-verify ang mahahalagang numero. + I-compress, grayscale, o ayusin ang mga PDF + Gumamit ng mga one-file na PDF operation para sa mas magaan, napi-print, o muling itinayong mga kopya ng dokumento + Piliin ang PDF cleanup operation + Kasama sa PDF Tools ang magkakahiwalay na operasyon para sa compression, grayscale na conversion, at repair. Ang compression at grayscale ay pangunahing gumagana sa pamamagitan ng mga naka-embed na larawan, habang ang pag-aayos ay muling itinatayo ang PDF structure; wala sa mga ito ang dapat ituring bilang isang garantisadong pag-aayos para sa bawat dokumento. + Gamitin ang Compress PDF kapag ang dokumento ay naglalaman ng mga larawan at ang laki ng file ay mahalaga para sa pagbabahagi. + Gumamit ng Grayscale kapag ang dokumento ay dapat na mas madaling i-print o hindi nangangailangan ng mga larawang may kulay. + Gamitin ang Repair PDF sa isang kopya kapag ang isang dokumento ay nabigong magbukas o nasira ang panloob na istraktura, pagkatapos ay i-verify ang muling itinayong file. + I-extract ang mga larawan mula sa mga PDF + I-recover ang mga naka-embed na PDF na larawan at i-pack ang mga nakuhang resulta sa isang archive + I-save ang mga pinagmulang larawan mula sa mga dokumento + Ini-scan ng Extract Images ang PDF para sa mga naka-embed na object ng imahe, nilalaktawan ang mga duplicate kung posible, at iniimbak ang mga na-recover na larawan sa isang nabuong ZIP archive. Kinukuha lang nito ang mga larawang aktwal na naka-embed sa PDF, hindi text, vector drawing, o bawat nakikitang page bilang isang screenshot. + Gumamit ng Extract Images kapag kailangan mo ng mga larawang nakaimbak sa loob ng PDF, gaya ng mga larawan mula sa isang catalog o mga na-scan na asset. + Gumamit ng PDF to Images o page extraction sa halip kapag kailangan mo ng buong page, kasama ang text at layout. + Kung ang tool ay hindi nag-uulat ng mga naka-embed na larawan, ang pahina ay maaaring text/vector na nilalaman o ang mga larawan ay maaaring hindi naka-imbak bilang mga na-extract na bagay. + Metadata, pag-flatte, at output ng pag-print + I-edit ang mga katangian ng dokumento, i-rasterize ang mga page, o ihanda ang mga custom na layout ng pag-print nang sinasadya + Piliin ang aksyong panghuling dokumento + Ang PDF metadata, pag-flatte, at paghahanda sa pag-print ay malulutas ang iba\'t ibang mga problema sa huling yugto. Kinokontrol ng metadata ang mga katangian ng dokumento, ang Flatten PDF ay nagra-raster ng mga pahina sa hindi gaanong nae-edit na output, at ang Print PDF ay naghahanda ng bagong layout na may mga kontrol sa laki, oryentasyon, margin, at page-per-sheet. + Gumamit ng Metadata kapag ang may-akda, pamagat, mga keyword, producer, o paglilinis ng privacy ay mahalaga. + Gumamit ng Flatten PDF kapag ang nakikitang nilalaman ng pahina ay dapat na maging mas mahirap i-edit bilang hiwalay na mga PDF object. + Gamitin ang Print PDF kapag kailangan mo ng custom na laki ng page, oryentasyon, margin, o maramihang page sa bawat sheet. + Maghanda ng mga larawan para sa OCR + Gumamit ng crop, rotation, contrast, denoise, at scale bago hilingin sa OCR na basahin ang text + Magbigay ng OCR cleaner pixels + Ang mga pagkakamali sa OCR ay kadalasang nagmumula sa input image kaysa sa OCR engine. Ang mga baluktot na page, mababang contrast, blur, shadow, maliliit na text, at halo-halong background ay lahat ay nagpapababa ng kalidad ng pagkilala. + I-crop sa lugar ng teksto at i-rotate ang imahe upang ang mga linya ay pahalang bago makilala. + Dagdagan ang contrast o i-convert sa mas malinis na black-and-white kapag ginagawang mas madaling basahin ang mga titik. + Baguhin nang katamtaman ang laki ng maliit na teksto pataas, ngunit iwasan ang agresibong paghasa na lumilikha ng mga pekeng gilid. + Suriin ang nilalaman ng QR + Suriin ang mga link, mga kredensyal sa Wi-Fi, mga contact, at mga aksyon bago magtiwala sa isang code + Ituring ang na-decode na text bilang hindi pinagkakatiwalaang input + Maaaring itago ng QR code ang isang URL, contact card, password ng Wi-Fi, kaganapan sa kalendaryo, numero ng telepono, o plain text. Ang nakikitang label na malapit sa code ay maaaring hindi tumugma sa aktwal na payload, kaya suriin ang na-decode na nilalaman bago kumilos dito. + Siyasatin ang buong na-decode na URL bago ito buksan, lalo na ang mga pinaikling o hindi pamilyar na mga domain. + Mag-ingat sa mga Wi-Fi, contact, kalendaryo, telepono, at mga SMS code dahil maaari silang mag-trigger ng mga sensitibong pagkilos. + Kopyahin muna ang content kapag kailangan mo itong i-verify sa ibang lugar sa halip na buksan ito kaagad. + Bumuo ng mga QR code + Gumawa ng mga code mula sa plain text, mga URL, Wi-Fi, mga contact, email, telepono, SMS, at data ng lokasyon + Buuin ang tamang QR payload + Ang QR screen ay maaaring bumuo ng mga code mula sa ipinasok na nilalaman pati na rin ang pag-scan ng mga umiiral na. Nakakatulong ang mga structured na uri ng QR na mag-encode ng data sa isang format na nauunawaan ng iba pang app, gaya ng mga detalye sa pag-log in sa Wi-Fi, contact card, email field, numero ng telepono, nilalamang SMS, URL, o geographic na coordinate. + Pumili ng plain text para sa mga simpleng tala, o gumamit ng structured na uri kapag dapat bumukas ang code bilang Wi-Fi, contact, URL, email, telepono, SMS, o data ng lokasyon. + Suriin ang bawat field bago ibahagi ang nabuong code dahil ang nakikitang preview ay sumasalamin lamang sa payload na iyong inilagay. + Subukan ang naka-save na QR code gamit ang isang scanner kapag ito ay ipi-print, ipo-post sa publiko, o gagamitin ng ibang tao. + Pumili ng checksum mode + Kalkulahin ang mga hash mula sa mga file o text, ihambing ang isang file, o batch-check ang maraming mga file + I-verify ang nilalaman, hindi ang hitsura + Ang Checksum Tools ay may hiwalay na mga tab para sa pag-hash ng file, pag-hash ng text, paghahambing ng isang file laban sa isang kilalang hash, at paghahambing ng batch. Ang isang checksum ay nagpapatunay ng byte-for-byte na pagkakakilanlan para sa napiling algorithm; hindi nito pinatutunayan na magkamukha lang ang dalawang larawan. + Gamitin ang Calculate para sa mga file at Text Hash para sa na-type o na-paste na data ng text. + Gamitin ang Ihambing kapag may nagbigay sa iyo ng inaasahang checksum para sa isang file. + Gamitin ang Batch Compare kapag maraming file ang nangangailangan ng mga hash o pag-verify sa isang pass, pagkatapos ay panatilihing pare-pareho ang napiling algorithm. + Privacy ng clipboard + Gumamit ng mga feature ng awtomatikong clipboard nang hindi sinasadyang ilantad ang pribadong nakopyang data + Panatilihing sinadya ang kinopyang nilalaman + Maginhawa ang pag-automate ng clipboard, ngunit ang kinopyang teksto ay maaaring maglaman ng mga password, link, address, token, o pribadong tala. Tratuhin ang input na nakabatay sa clipboard bilang isang feature ng bilis na dapat tumugma sa iyong mga gawi at inaasahan sa privacy. + I-disable ang awtomatikong paggamit ng clipboard kung ang pribadong text ay lilitaw sa mga tool nang hindi inaasahan. + Gumamit ng clipboard-lamang na nagse-save lamang kapag sinasadya mong maiwasan ang pag-iimbak ng resulta. + I-clear o palitan ang clipboard pagkatapos humawak ng sensitibong text, QR data, o Base64 payloads. + Mga format ng kulay + Unawain ang mga halaga ng HEX, RGB, HSV, alpha, at mga kinopyang kulay bago i-paste sa ibang lugar + Kopyahin ang format na inaasahan ng target + Ang parehong nakikitang kulay ay maaaring isulat sa maraming paraan. Ang isang tool sa disenyo, CSS field, Android color value, o image editor ay maaaring umasa ng ibang pagkakasunod-sunod, alpha handling, o color model. + Gamitin ang HEX kapag nagpe-paste sa mga field ng web, disenyo, o tema na umaasa sa mga compact na color code. + Gumamit ng RGB o HSV kapag kailangan mong manu-manong ayusin ang mga channel, liwanag, saturation, o hue. + Suriin nang hiwalay ang alpha kapag mahalaga ang transparency dahil binabalewala ito ng ilang destinasyon o gumagamit ng ibang order. + I-browse ang library ng kulay + Maghanap ng mga pinangalanang kulay ayon sa pangalan o HEX at panatilihing malapit sa itaas ang mga paborito + Maghanap ng mga reusable na pinangalanang mga kulay + Naglo-load ang Color Library ng pinangalanang koleksyon ng kulay, pinag-uuri-uriin ito ayon sa pangalan, sinasala ayon sa pangalan ng kulay o halaga ng HEX, at nag-iimbak ng mga paboritong kulay upang manatiling mas madaling maabot ang mahahalagang entry. Ito ay kapaki-pakinabang kapag kailangan mo ng isang tunay na pinangalanang kulay sa halip na sampling mula sa isang imahe. + Maghanap ayon sa pangalan ng kulay kapag kilala mo ang pamilya, gaya ng pula, asul, slate, o mint. + Maghanap sa pamamagitan ng HEX kapag mayroon ka nang numeric na kulay at gusto mong makahanap ng katugma o malapit na pinangalanang mga entry. + Markahan ang mga madalas na kulay bilang mga paborito upang mauna ang mga ito sa pangkalahatang aklatan habang nagba-browse. + Gumamit ng mga koleksyon ng mesh gradient + Mag-download ng mga magagamit na mapagkukunan ng mesh gradient at ibahagi ang mga napiling larawan + Gumamit ng mga asset ng malayuang mesh gradient + Maaaring mag-load ang Mesh Gradients ng malayuang koleksyon ng mapagkukunan, mag-download ng mga nawawalang gradient na larawan, magpakita ng progreso sa pag-download, at magbahagi ng mga napiling gradient. Ang kakayahang magamit ay depende sa pag-access sa network at sa malayong tindahan ng mapagkukunan, kaya ang isang walang laman na listahan ay karaniwang nangangahulugan na ang mga mapagkukunan ay hindi pa magagamit. + Buksan ang Mesh Gradients mula sa mga gradient tool kapag gusto mo ng mga yari na mesh gradient na larawan. + Maghintay para sa pag-load ng mapagkukunan o pag-unlad ng pag-download bago ipagpalagay na walang laman ang koleksyon. + Piliin at ibahagi ang mga gradient na kailangan mo, o bumalik sa Gradient Maker kapag gusto mong manu-manong bumuo ng custom na gradient. + Nakabuo ng resolusyon ng asset + Pumili ng laki ng output bago bumuo ng mga gradient, ingay, mga wallpaper, parang SVG na sining, o mga texture + Bumuo sa laki na kailangan mo + Ang mga nabuong larawan ay walang orihinal na camera na babalikan. Kung masyadong maliit ang output, ang pag-scale sa ibang pagkakataon ay maaaring magpapalambot ng mga pattern, maglipat ng mga detalye, o magbago kung paano mag-tile at mag-compress ang asset. + Magtakda muna ng mga dimensyon para sa huling use case, gaya ng wallpaper, icon, background, print, o overlay. + Gumamit ng mas malalaking sukat para sa mga texture na maaaring i-crop, i-zoom, o muling gamitin sa ilang mga layout. + I-export ang mga bersyon ng pagsubok bago ang malalaking output dahil maaaring baguhin ng detalye ng pamamaraan kung paano kumikilos ang compression. + I-export ang mga kasalukuyang wallpaper + Kunin ang mga available na Home, Lock, at mga built-in na wallpaper mula sa device + I-save o ibahagi ang mga naka-install na wallpaper + Binabasa ng Wallpapers Export ang mga wallpaper na inilalantad ng Android sa pamamagitan ng mga system wallpaper API. Depende sa device, bersyon ng Android, mga pahintulot, at variant ng build, ang Home, Lock, o mga built-in na wallpaper ay maaaring available nang hiwalay o maaaring nawawala. + Buksan ang Mga Wallpaper Export upang i-load ang mga wallpaper na pinapayagan ng system na basahin ng app. + Piliin ang available na Home, Lock, o built-in na mga entry sa wallpaper na gusto mong i-save, kopyahin, o ibahagi. + Kung ang isang wallpaper ay nawawala o ang tampok ay hindi magagamit, ito ay karaniwang isang sistema, pahintulot, o build-variant na limitasyon sa halip na isang setting ng pag-edit. + Corners Animation Throttle + Kopyahin ang kulay bilang + HEX + pangalan + Portrait matting model para sa mabilis na mga cutout ng tao na may mas malambot na buhok at paghawak sa gilid. + Mabilis na low-light enhancement gamit ang Zero-DCE++ curve estimation. + Modelo ng pagpapanumbalik ng imahe ng restormer para sa pagbabawas ng mga bahid ng ulan at mga artifact sa wet-weather. + Idokumento ang unwarping na modelo na hinuhulaan ang isang coordinate grid at i-remap ang mga curved page sa isang flatter scan. + Scale ng Tagal ng Paggalaw + Kinokontrol ang bilis ng animation ng app: Hindi pinapagana ng 0 ang paggalaw, ang 1 ay normal, ang mas mataas na mga halaga ay nagpapabagal sa mga animation + Ipakita ang Mga Kamakailang Tool + Ipakita ang mabilis na pag-access sa mga kamakailang ginamit na tool sa pangunahing screen + Mga Istatistika ng Paggamit + I-explore ang mga pagbukas ng app, paglulunsad ng tool, at ang iyong mga pinakaginagamit na tool + Bubukas ang app + Bumukas ang tool + Huling kasangkapan + Ang matagumpay na pag-save + Activity streak + Na-save ang data + Nangungunang format + Mga gamit na ginamit + %1$d ay bubukas • %2$s + Wala pang istatistika ng paggamit + Magbukas ng ilang tool at awtomatikong lilitaw ang mga ito dito + Ang iyong mga istatistika sa paggamit ay lokal lamang na naka-imbak sa iyong device. Ginagamit lang sila ng ImageToolbox para ipakita ang iyong personal na aktibidad, mga paboritong tool, at mga naka-save na insight sa data + editor edit photo picture retouch adjust + resize convert scale resolution dimensyon lapad taas jpg jpeg png webp compress + compress size weight bytes mb kb bawasan ang kalidad optimize + crop trim cut aspect ratio + filter effect color correction ayusin ang lut mask + gumuhit ng paint brush pencil annotate markup + cipher encrypt decrypt password secret + background remover burahin alisin cutout transparent alpha + i-preview ang viewer gallery siyasatin bukas + stitch merge pagsamahin ang panorama mahabang screenshot + url web download larawan ng link sa internet + picker eyedropper sample hex rgb color + Mga kulay ng palette swatch extract nangingibabaw + exif metadata privacy alisin ang strip malinis na lokasyon ng gps + ihambing ang pagkakaiba ng pagkakaiba bago pagkatapos + limitahan ang resize max min megapixels dimensyon clamp + Mga tool sa pahina ng dokumento ng pdf + Kinikilala ng ocr text ang extract scan + mesh ng kulay ng gradient ng background + watermark logo text stamp overlay + gif animation animated convert frames + apng animation animated png convert ng mga frame + zip archive compress pack i-unpack ang mga file + jxl jpeg xl convert jpg image format + svg vector trace convert xml + format convert jpg jpeg png webp heic avif jxl bmp + document scanner scan paper perspective crop + qr barcode scanner scan + stacking overlay average median astrophotography + hating tile grid hiwa bahagi + color convert hex rgb hsl hsv cmyk lab + webp convert animated na format ng imahe + ingay generator random texture grain + pinagsama ang layout ng collage grid + markup layers annotate overlay edit + base64 encode decode data uri + checksum hash md5 sha sha1 sha256 verify + mesh gradient na background + exif metadata edit gps date camera + gupitin ang split grid na mga piraso ng tile + audio cover album art music metadata + background sa pag-export ng wallpaper + ascii text art character + ai ml neural mapahusay upscale segment + color library material palette na pinangalanang mga kulay + shader glsl fragment effect studio + pdf merge combine join + pdf hatiin hiwalay na mga pahina + pdf rotate pages oryentasyon + pdf ayusin muli ang pag-uuri ng mga pahina + pdf page number pagination + Kinikilala ng pdf ocr text ang mahahanap na extract + pdf watermark stamp logo text + pdf signature sign draw + pdf protektahan ang lock ng pag-encrypt ng password + pdf unlock password decrypt alisin ang proteksyon + pdf compress bawasan ang laki optimize + pdf grayscale black white monochrome + pdf repair ayusin mabawi nasira + pdf metadata properties exif pamagat ng may-akda + pdf alisin ang tanggalin ang mga pahina + pdf crop trim margin + Ang pdf flatten annotation ay bumubuo ng mga layer + pdf extract images mga larawan + pdf zip archive compress + pdf print printer + pdf preview viewer read + mga imahe sa pdf convert jpg jpeg png dokumento + pdf sa mga pahina ng larawan i-export ang jpg png + pdf annotation comments alisin malinis + Neutral na Variant + Error + Drop Shadow + Taas ng ngipin + Pahalang na Saklaw ng Ngipin + Vertical na Saklaw ng Ngipin + Top Edge + Kanang Gilid + Ibabang Gilid + Kaliwang Gilid + Napunit na Gilid + I-reset ang mga istatistika ng paggamit + Magbubukas ang tool, i-save ang mga istatistika, streak, naka-save na data, at ire-reset ang nangungunang format. Ang pagbukas ng app ay mananatiling hindi magbabago. + Audio + file + I-export ang mga profile + I-save ang kasalukuyang profile + Tanggalin ang profile sa pag-export + Gusto mo bang tanggalin ang export na profile \\"%1$s\\"? + Pagguhit ng hangganan + Magpakita ng manipis na hangganan sa paligid ng drawing at burahin ang canvas + Wavy + Mga bilugan na elemento ng UI na may malambot na kulot na gilid + Scoop + bingaw + Ang mga bilog na sulok ay inukit sa loob + Mga stepped corner na may double cut + Placeholder + Gamitin ang {filename} upang ipasok ang orihinal na filename nang walang extension + Sumiklab + Base na halaga + halaga ng singsing + Ang dami ni Ray + Lapad ng singsing + Baluktutin ang Pananaw + Java Look at Feel + Gupitin + X anggulo + at anggulo + Baguhin ang laki ng output + Patak ng Tubig + Haba ng daluyong + Phase + Mataas na Pass + Kulay Mask + Chrome + Matunaw + Kalambutan + Feedback + Malabo ang Lens + Mababang input + Mataas na input + Mababang output + Mataas na output + Mga Light Effect + Taas ng bump + Bump lambot + Ripple + X amplitude + Y amplitude + X wavelength + Y wavelength + Adaptive Blur + Pagbuo ng Texture + Bumuo ng iba\'t ibang mga texture ng pamamaraan at i-save ang mga ito bilang mga imahe + Uri ng Texture + Bias + Oras + Shine + Pagpapakalat + Mga sample + Koepisyent ng anggulo + Koepisyent ng gradient + Uri ng grid + Kapangyarihan ng distansya + Scale Y + Pagkalabo + Uri ng batayan + Salik ng kaguluhan + Pagsusukat + Mga pag-ulit + Mga singsing + Brushed Metal + Caustics + Cellular + Checkerboard + fBm + Marmol + Plasma + kubrekama + Kahoy + random + Square + Heksagonal + May walong sulok + tatsulok + Ridged + Ingay ng VL + SC Ingay + Kamakailan + Wala pang kamakailang ginamit na mga filter + texture generator brushed metal caustics cellular checkerboard marble plasma quilt wood brick camouflage cell cloud crack fabric foliage honeycomb ice lava nebula paper kalawang buhangin usok bato terrain topograpiya tubig ripple + Brick + pagbabalatkayo + Cell + Ulap + basag + Tela + Mga dahon + pulot-pukyutan + yelo + Lava + Nebula + Papel + kalawang + buhangin + Usok + Bato + Terrain + Topograpiya + Tubig Ripple + Advanced na Kahoy + Lapad ng mortar + Iregularidad + Bevel + Kagaspangan + Unang threshold + Pangalawang threshold + Pangatlong threshold + Ang lambot ng gilid + Lapad ng hangganan + Saklaw + Detalye + Lalim + Nagsasanga-sanga + Mga pahalang na thread + Mga patayong thread + Malabo + Mga ugat + Pag-iilaw + Lapad ng basag + Frost + Daloy + Crust + Densidad ng ulap + Mga bituin + Densidad ng hibla + Lakas ng hibla + Mga mantsa + Kaagnasan + Pitting + Mga natuklap + dalas ng dune + Anggulo ng hangin + Ripples + Wisps + Sukat ng ugat + Antas ng tubig + Antas ng bundok + Pagguho + Antas ng niyebe + Bilang ng linya + Kapal ng linya + Pagtatabing + Kulay ng butas ng butas + Kulay ng mortar + Madilim na kulay ng ladrilyo + Kulay ng brick + I-highlight ang kulay + Madilim na kulay + Kulay ng kagubatan + Kulay ng lupa + Kulay ng buhangin + Kulay ng background + Kulay ng cell + Kulay ng gilid + Kulay ng langit + Kulay ng anino + Banayad na kulay + Kulay ng ibabaw + Kulay ng pagkakaiba-iba + Kulay ng basag + Kulay ng warp + Kulay ng habi + Madilim na kulay ng dahon + Kulay ng dahon + Kulay ng hangganan + Kulay ng pulot + Malalim na kulay + Kulay ng yelo + Kulay ng yelo + Kulay ng crust + Hugasan ang kulay + Kulay ng glow + Kulay ng espasyo + Kulay violet + Kulay asul + Kulay ng base + Kulay ng hibla + Kulay ng mantsa + Kulay ng metal + Madilim na kulay ng kalawang + Kulay ng kalawang + Kulay kahel + Kulay ng usok + Kulay ng ugat + Kulay ng mababang lupain + Kulay ng tubig + Kulay ng bato + Kulay ng niyebe + Mababang kulay + Mataas na kulay + Kulay ng linya + Mababaw na kulay + damo + Ang dumi + Balat + kongkreto + Aspalto + Lumot + Sunog + Aurora + Oil Slick + Watercolor + Abstract na Daloy + Damascus Steel + Kidlat + Ink Marbling + Holographic Foil + Bioluminescence + Cosmic Vortex + Lava Lamp + Horizon ng Kaganapan + Fractal Bloom + Chromatic Tunnel + Eclipse Corona + Ferrofluid Crown + Supernova + Iris + Balahibo ng Peacock + Nautilus Shell + Ringed Planet + Densidad ng talim + Haba ng talim + Hangin + Pachiness + Mga kumpol + Pebbles + Mga wrinkles + Mga pores + Pinagsama-sama + Mga bitak + Tar + Magsuot + Speckles + Mga hibla + Dalas ng apoy + Usok + Intensity + Mga laso + Mga banda + Iridescence + Kadiliman + Namumulaklak + Pigment + Papel + Pagsasabog + Simetrya + Ang talas + Paglalaro ng kulay + Pagkagatas + Mga layer + Pagtitiklop + Polish + Mga sanga + Direksyon + Sheen + Tupi + Balanse ng tinta + Spectrum + Crinkles + Diffraction + Mga armas + I-twist + Mga patak + Ikiling ng disc + Lapad ng disk + Lensing + Mga talulot + Piligree + Facets + Curvature + Laki ng buwan + Laki ng Corona + Sinag + singsing na brilyante + Mga lobe + Densidad ng orbit + kapal + Mga spike + Haba ng spike + Laki ng katawan + Metallic + Shock radius + Laki ng mag-aaral + Laki ng iris + Pagkakaiba-iba ng kulay + Catchlight + Laki ng mata + Densidad ng barb + lumiliko + Mga Kamara + Mga tagaytay + Perlas + Laki ng planeta + Ikiling ang singsing + Atmospera + Kulay ng dumi + Kulay ng madilim na damo + Kulay ng damo + Kulay ng tip + Madilim na kulay ng lupa + Tuyong kulay + Kulay ng pebble + Kulay ng katad + Konkretong kulay + Kulay ng tar + Kulay ng aspalto + Kulay ng alikabok + Madilim na kulay ng lumot + Kulay ng lumot + Kulay pula + Kulay ng core + Kulay berde + Kulay cyan + Kulay ginto + Kulay ng papel + Pangalawang kulay + Unang kulay + Pangalawang kulay + Kulay pink + Madilim na kulay ng bakal + Banayad na kulay ng bakal + Kulay ng oxide + Kulay ng halo + Kulay ng bolt + Kulay ng pelus + Kulay ng ningning + Kulay ng asul na tinta + Kulay ng pulang tinta + Madilim na kulay ng tinta + Kulay pilak + Kulay ng tissue + Kulay ng disk + Mainit na kulay + Kulay ng lens + Panlabas na kulay + Kulay sa loob + Kulay ng Corona + Malamig na kulay + Mainit na kulay + Kulay ng ulap + Kulay ng apoy + Kulay ng balahibo + Kulay perlas + Kulay ng planeta + Kulay ng singsing + Opal + Velvet + Kakaibang Attractor + Halumigmig + Mga gilid + Pagpapabalahibo + Core glow + Laki ng abot-tanaw + Kulot + Lapad ng shell + Itinapon + Pagbubukas + Lapad ng singsing + Kulay ng bato + Kulay ng lupa + Kulay ng magenta + Kulay ng pigment + Kulay ng bakal + Kulay dilaw + Kulay ng shell + Tanggalin ang mga orihinal na file pagkatapos i-save + Tanging matagumpay na naproseso ang mga source file ang matatanggal + Ang mga orihinal na file ay tinanggal: %1$d + Nabigong tanggalin ang mga orihinal na file: %1$d + Ang mga orihinal na file ay tinanggal: %1$d, nabigo: %2$d + Mga galaw sa sheet + Payagan ang pag-drag ng pinalawak na mga sheet ng opsyon + Chroma subsampling + Bit depth + Lossless + %1$d-bit + Palitan ang Pangalan ng Batch + Palitan ang pangalan ng maraming file gamit ang mga pattern ng filename + batch rename file filename pattern sequence date extension + Pumili ng mga file na palitan ng pangalan + Pattern ng filename + Palitan ang pangalan + Pinagmulan ng petsa + Kinuha ang petsa ng EXIF + Petsa ng pagbabago ng file + Petsa ng pagkakagawa ng file + Kasalukuyang petsa + Manu-manong petsa + Manu-manong petsa + Manu-manong oras + Ang napiling petsa ay hindi magagamit para sa ilang mga file. Gagamitin ang kasalukuyang petsa para sa kanila. + Maglagay ng pattern ng filename + Ang pattern ay gumagawa ng di-wasto o sobrang haba ng filename + Ang pattern ay gumagawa ng mga duplicate na filename + Ang lahat ng mga filename ay tumutugma na sa pattern + Gumagamit ang pattern na ito ng mga token na hindi available para sa pagpapalit ng pangalan ng batch + Ang pangalan ay mananatiling pareho + Matagumpay na napalitan ng pangalan ang mga file + Hindi mapalitan ng pangalan ang ilang file + Hindi ibinigay ang pahintulot + Gumagamit ng orihinal na pangalan ng file nang walang extension, na tumutulong sa iyong panatilihing buo ang pagkakakilanlan ng pinagmulan. Sinusuportahan din ang paghiwa ng \\o{start:end}, transliteration na may \\o{t} at palitan ng \\o{s/old/new/}. + Auto-incrementing counter. Sinusuportahan ang custom na pag-format: \\c{padding} (hal. \\c{3} -&gt; 001), \\c{start:step} o \\c{start:step:padding}. + Ang pangalan ng parent folder kung saan matatagpuan ang orihinal na file. + Ang laki ng orihinal na file. Sinusuportahan ang mga unit: \\z{b}, \\z{kb}, \\z{mb} o \\z lang para sa format na nababasa ng tao. + Bumubuo ng random na Universally Unique Identifier (UUID) para matiyak ang pagiging natatangi ng filename. + Ang pagpapalit ng pangalan ng mga file ay hindi maaaring i-undo. Tiyaking tama ang mga bagong pangalan bago magpatuloy. + Kumpirmahin ang pagpapalit ng pangalan + Mga setting ng side menu + Sukat ng menu + Menu alpha + Auto White Balance + Clipping + Sinusuri ang mga nakatagong watermark + Imbakan + Limitasyon ng cache + Awtomatikong i-clear ang cache kapag lumampas ito sa laki + Agwat ng paglilinis + Gaano kadalas suriin kung dapat i-clear ang cache + Sa paglulunsad ng app + 1 araw + 1 linggo + 1 buwan + Awtomatikong i-clear ang cache ng app ayon sa napiling limitasyon at pagitan + Movable crop area + Nagbibigay-daan sa paglipat ng buong lugar ng pananim sa pamamagitan ng pag-drag sa loob nito + Pagsamahin ang GIF + Pagsamahin ang maraming GIF clip sa isang animation + Pagkakasunod-sunod ng mga GIF clip + Reverse + Maglaro bilang baligtad + Boomerang + Maglaro pasulong at pagkatapos ay paatras + I-normalize ang laki ng frame + I-scale ang mga frame upang magkasya sa pinakamalaking clip; patayin upang mapanatili ang kanilang orihinal na sukat + Pagkaantala sa pagitan ng mga clip (ms) + Folder + Piliin ang lahat ng larawan mula sa isang folder at mga subfolder nito + Duplicate Finder + Maghanap ng mga eksaktong kopya at mga visual na katulad na larawan + duplicate finder katulad na mga larawan eksaktong kopya ng mga larawan sa paglilinis ng storage dHash SHA-256 + Pumili ng mga larawan upang makahanap ng mga duplicate + Sensitivity ng pagkakatulad + Mga eksaktong kopya + Mga katulad na larawan + Piliin ang lahat ng eksaktong duplicate + Piliin ang lahat maliban sa inirerekomenda + Walang nakitang mga duplicate + Subukang magdagdag ng higit pang mga larawan o pataasin ang pagiging sensitibo ng pagkakatulad + Hindi mabasa ang %1$d (mga) larawan + %1$s • %2$s maaaring i-reclaim + %1$d mga grupo • %2$s na maaaring i-reclaim + %1$d pinili • %2$s + Hindi na ito maaaring bawiin. Maaaring hilingin sa iyo ng Android na kumpirmahin ang pagtanggal sa isang dialog ng system. + Hindi Nahanap + Ilipat upang magsimula + Ilipat sa dulo + \ No newline at end of file diff --git a/core/resources/src/main/res/values-fr/strings.xml b/core/resources/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000..a045778 --- /dev/null +++ b/core/resources/src/main/res/values-fr/strings.xml @@ -0,0 +1,3739 @@ + + + + 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 + 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 + 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. + 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. + 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 + Distorsion sensible au contenu + Pas assez de mémoire pour terminer cette action. Veuillez essayer d\'utiliser une image plus petite, de fermer d\'autres applications ou de redémarrer l\'application. + Travailleurs parallèles + Ces images n\'ont pas été enregistrées car les fichiers convertis seraient plus volumineux que les originaux. Vérifiez cette liste avant de supprimer les images originales. + Fichiers ignorés : %1$s + Journaux d\'applications + Afficher les journaux de l\'application pour repérer les problèmes + Favoris dans les outils groupés + Ajoute des favoris sous forme d\'onglet lorsque les outils sont regroupés par type + Afficher le favori en dernier + Déplace l\'onglet des outils favoris vers la fin + Espacement horizontal + Espacement vertical + Énergie en arrière + Utilisez une simple carte d\'énergie de magnitude de gradient au lieu d\'un algorithme d\'énergie directe + Supprimer la zone masquée + Les coutures traverseront la région masquée, supprimant uniquement la zone sélectionnée au lieu de la protéger + VHS NTSC + Paramètres NTSC avancés + Réglage analogique supplémentaire pour des artefacts VHS et de diffusion plus puissants + Fond perdu chromatique + Usure du ruban adhésif + Suivi + Frottis de Luma + Sonnerie + Neige + Utiliser le champ + Type de filtre + Filtre de luminance d\'entrée + Chroma d\'entrée passe-bas + Démodulation chromatique + Déphasage + Décalage de phase + Changement de tête + Hauteur de commutation de la tête + Décalage de commutation de tête + Changement de tête + Position médiane + Gigue médiane + Suivi de la hauteur du bruit + Vague de suivi + Suivi de la neige + Suivi de l\'anisotropie de la neige + Bruit de suivi + Suivi de l\'intensité du bruit + Bruit composite + Fréquence du bruit composite + Intensité du bruit composite + Détail du bruit composite + Fréquence de sonnerie + Puissance de sonnerie + Bruit de lumière + Fréquence du bruit de luminance + Intensité du bruit Luma + Détail du bruit Luma + Bruit chromatique + Fréquence du bruit chromatique + Intensité du bruit chromatique + Détail du bruit chroma + Intensité de la neige + Anisotropie de la neige + Bruit de phase chroma + Erreur de phase chroma + Retard chroma horizontal + Retard chroma vertical + Vitesse de la bande VHS + Perte de chrominance VHS + Intensité de netteté VHS + Fréquence de netteté VHS + Intensité de l\'onde de bord VHS + Vitesse des ondes de bord VHS + Fréquence de l\'onde de bord VHS + Détail de l\'onde de bord VHS + Chroma de sortie passe-bas + Échelle horizontale + Échelle verticale + Facteur d\'échelle X + Facteur d\'échelle Y + Détecte les filigranes d’image courants et les peint avec LaMa. Télécharge automatiquement les modèles de détection et d’inpainting + Deutéranopie + Agrandir l\'image + Suppresseur d\'arrière-plan basé sur la segmentation d\'objets à l\'aide de la segmentation YOLO v11 + Afficher l\'angle de la ligne + Affiche la rotation actuelle de la ligne en degrés pendant le dessin + Émojis animés + Afficher les emoji disponibles sous forme d\'animations + Enregistrer dans le dossier d\'origine + Enregistrez les nouveaux fichiers à côté du fichier d\'origine au lieu du dossier sélectionné + Filigrane PDF + Pas assez de mémoire + L\'image est probablement trop volumineuse pour être traitée sur cet appareil, ou le système n\'a plus de mémoire disponible. Essayez de réduire la résolution de l\'image, de fermer d\'autres applications ou de choisir un fichier plus petit. + Menu de débogage + Menu permettant de tester les fonctions de l\'application. Il n\'est pas destiné à apparaître dans la version de production. + Fournisseur + PaddleOCR nécessite des modèles ONNX supplémentaires sur votre appareil. Voulez-vous télécharger les données %1$s ? + Universel + coréen + latin + Slave oriental + thaïlandais + grec + Anglais + cyrillique + arabe + Dévanagari + Tamoul + Télougou + Ombreur + Préréglage du shader + Aucun shader sélectionné + Studio de shaders + Créer, modifier, valider, importer et exporter des shaders de fragments personnalisés + Shaders enregistrés + Ouvrez les shaders enregistrés, dupliquez, exportez, partagez ou supprimez + Nouveau shader + Fichier de shader + .itshaderJSON + Paramètres %1$d + Source du shader + Écrivez uniquement le corps de void main(). Utilisez textureCoordonnée pour les coordonnées UV et inputImageTexture comme échantillonneur source. + Fonctions + Fonctions d\'assistance, constantes et structures facultatives insérées avant void main(). Les uniformes sont générés à partir des paramètres. + Ajoutez des uniformes ici lorsque le shader a besoin de valeurs modifiables. + Réinitialiser le shader + Le brouillon actuel du shader sera effacé + Shader invalide + Version de shader non prise en charge %1$d. Version prise en charge : %2$d. + Le nom du shader ne doit pas être vide. + La source du shader ne doit pas être vide. + La source du shader contient des caractères non pris en charge : %1$s. Utilisez uniquement la source ASCII GLSL. + Le paramètre \\\"%1$s\\\" est déclaré plusieurs fois. + Les noms de paramètres ne doivent pas être vides. + Le paramètre \\\"%1$s\\\" utilise un nom GPUImage réservé. + Le paramètre \\\"%1$s\\\" doit être un identifiant GLSL valide. + Le paramètre \\\"%1$s\\\" a le type de valeur %2$s \\\"%3$s\\\", attendu \\\"%4$s\\\". + Le paramètre \\\"%1$s\\\" ne peut pas définir min ou max pour les valeurs booléennes. + Le paramètre \\\"%1$s\\\" a un minimum supérieur au maximum. + Le paramètre \\\"%1$s\\\" par défaut est inférieur au min. + Le paramètre \\\"%1$s\\\" par défaut est supérieur au maximum. + Le shader doit déclarer \\\"uniform sampler2D %1$s;\\\". + Le shader doit déclarer \\\"varying vec2 %1$s;\\\". + Le shader doit définir \\\"void main()\\\". + Le shader doit écrire une couleur dans gl_FragColor. + Les shaders ShaderToy mainImage ne sont pas pris en charge. Utilisez le contrat void main() de GPUImage. + L\'uniforme ShaderToy animé « iTime » n\'est pas pris en charge. + L\'uniforme ShaderToy animé « iFrame » n\'est pas pris en charge. + L\'uniforme ShaderToy « iResolution » n\'est pas pris en charge. + Les textures ShaderToy iChannel ne sont pas prises en charge. + Les textures externes ne sont pas prises en charge. + Les extensions de texture externes ne sont pas prises en charge. + Les paramètres du shader Libretro ne sont pas pris en charge. + Une seule texture d’entrée est prise en charge. Supprimez \\\"uniforme %1$s %2$s;\\\". + Le paramètre \\\"%1$s\\\" doit être déclaré comme \\\"uniform %2$s %1$s;\\\". + Le paramètre \\\"%1$s\\\" est déclaré comme \\\"uniform %2$s %1$s;\\\", attendu \\\"uniform %3$s %1$s;\\\". + Le fichier shader est vide. + Le fichier shader doit être un objet JSON .itshader avec des champs de version, de nom et de shader. + Le fichier Shader n\'est pas un JSON valide ou ne correspond pas au format .itshader. + Le champ \\\"%1$s\\\" est obligatoire. + %1$s est requis. + %1$s \\\"%2$s\\\" n\'est pas pris en charge. Types pris en charge : %3$s. + %1$s est requis pour les paramètres \\\"%2$s\\\". + %1$s doit être un nombre fini. + %1$s doit être un entier. + %1$s doit être vrai ou faux. + %1$s doit être une chaîne de couleur, un tableau RVB/RGBA ou un objet couleur. + %1$s doit utiliser le format #RRGGBB ou #RRGGBBAA. + %1$s doit contenir des canaux de couleur hexadécimaux valides. + %1$s doit contenir 3 ou 4 canaux de couleur. + %1$s doit être compris entre 0 et 255. + %1$s doit être un tableau à deux nombres ou un objet avec x et y. + %1$s doit contenir exactement 2 chiffres. + Double + Toujours effacer EXIF + Supprimez les données EXIF ​​​​de l\'image lors de l\'enregistrement, même lorsqu\'un outil demande de conserver les métadonnées + Aide et conseils + %1$d tutoriels + Ouvrir l\'outil + Découvrez les principaux outils, options d\'exportation, flux PDF, utilitaires de couleur et correctifs pour les problèmes courants. + Commencer + Choisissez des outils, importez des fichiers, enregistrez les résultats et réutilisez les paramètres plus rapidement + Édition d\'images + Redimensionner, recadrer, filtrer, effacer les arrière-plans, dessiner et filigraner des images + Fichiers et métadonnées + Convertissez les formats, comparez les résultats, protégez la confidentialité et contrôlez les noms de fichiers + PDF et documents + Créez des PDF, numérisez des documents, des pages OCR et préparez des fichiers pour le partage + Texte, QR et données + Reconnaître le texte, scanner les codes, encoder en Base64, vérifier les hachages et regrouper les fichiers + Outils de couleur + Choisissez des couleurs, créez des palettes, explorez des bibliothèques de couleurs et créez des dégradés + Outils créatifs + Générez des illustrations SVG, ASCII, des textures sonores, des shaders et des visuels expérimentaux + Dépannage + Résoudre les problèmes de fichiers lourds, de transparence, de partage, d\'importation et de reporting + Choisissez le bon outil + Utilisez les catégories, la recherche, les favoris et les actions de partage pour commencer au bon endroit + Commencez par le meilleur point d’entrée + Image Toolbox propose de nombreux outils ciblés, et nombre d\'entre eux se chevauchent à première vue. Commencez par la tâche que vous souhaitez terminer, puis choisissez l\'outil qui contrôle la partie la plus importante du résultat : taille, format, métadonnées, texte, pages PDF, couleurs ou mise en page. + Utilisez la recherche lorsque vous connaissez le nom de l\'action, tel que redimensionner, PDF, EXIF, OCR, QR ou couleur. + Ouvrez une catégorie lorsque vous ne connaissez que le type de tâche, puis épinglez les outils fréquents comme favoris. + Lorsqu\'une autre application partage une image dans Image Toolbox, sélectionnez l\'outil dans le flux de la feuille de partage. + Importez, enregistrez et partagez les résultats + Comprendre le flux habituel depuis la sélection des entrées jusqu\'à l\'exportation du fichier modifié + Suivez le flux d\'édition commun + La plupart des outils suivent le même rythme : choisissez la saisie, ajustez les options, prévisualisez, puis enregistrez ou partagez. Le détail important est que l\'enregistrement crée un fichier de sortie, tandis que le partage est généralement préférable pour un transfert rapide vers une autre application. + Choisissez un fichier pour les outils à image unique ou plusieurs fichiers pour les outils par lots lorsque le sélecteur le permet. + Vérifiez les contrôles d\'aperçu et de sortie avant d\'enregistrer, en particulier les options de format, de qualité et de nom de fichier. + Utilisez le partage pour un transfert rapide ou enregistrez lorsque vous avez besoin d\'une copie persistante dans le dossier sélectionné. + Travaillez avec plusieurs images à la fois + Les outils par lots sont les meilleurs pour les tâches répétées de redimensionnement, de conversion, de compression et de dénomination + Préparez un lot prévisible + Le traitement par lots est plus rapide lorsque chaque sortie doit suivre les mêmes règles. C\'est également l\'endroit le plus simple pour commettre une erreur grave, alors choisissez le nom, la qualité, les métadonnées et le comportement du dossier avant de lancer une longue exportation. + Sélectionnez toutes les images associées ensemble et conservez l\'ordre si la sortie dépend de la séquence. + Définissez les règles de redimensionnement, de format, de qualité, de métadonnées et de nom de fichier une fois avant de lancer l\'exportation. + Exécutez d\'abord un petit lot de test lorsque les fichiers source sont volumineux ou lorsque le format cible est nouveau pour vous. + Modification rapide et unique + Utilisez l\'éditeur tout-en-un lorsque vous avez besoin de plusieurs modifications simples sur une image + Terminez une image sans sauter d\'outil + L\'édition unique est utile lorsque l\'image nécessite une petite chaîne de modifications plutôt qu\'une opération spécialisée. Il est généralement plus rapide pour un nettoyage, une prévisualisation et une exportation rapides d\'une copie finale sans créer de flux de travail par lots. + Ouvrez Single Edit pour une image qui nécessite des ajustements courants, un balisage, un recadrage, une rotation ou des modifications d\'exportation. + Appliquez les modifications dans l\'ordre qui modifie le moins de pixels en premier, comme recadrer avant les filtres et filtrer avant la compression finale. + Utilisez plutôt un outil spécialisé lorsque vous avez besoin d\'un traitement par lots, de cibles de taille de fichier exactes, d\'une sortie PDF, d\'une OCR ou d\'un nettoyage des métadonnées. + Utiliser les préréglages et les paramètres + Enregistrez les choix répétés et ajustez l\'application pour votre flux de travail préféré + Évitez de répéter la même configuration + Les préréglages et paramètres sont utiles lorsque vous exportez souvent le même type de fichier. Un bon préréglage transforme une liste de contrôle manuelle répétée en un seul clic, tandis que les paramètres globaux définissent les valeurs par défaut avec lesquelles les nouveaux outils démarrent. + Créez des préréglages pour les tailles de sortie, formats, valeurs de qualité ou modèles de dénomination courants. + Consultez les paramètres pour le format par défaut, la qualité, le dossier de sauvegarde, le nom de fichier, le sélecteur et le comportement de l\'interface. + Sauvegardez les paramètres avant de réinstaller l\'application ou de passer à un autre appareil. + Définir des paramètres par défaut utiles + Choisissez le format par défaut, la qualité, le mode d\'échelle, le type de redimensionnement et l\'espace colorimétrique une fois + Faites démarrer de nouveaux outils plus près de votre objectif + Les valeurs par défaut ne sont pas de simples préférences esthétiques. Ils décident du format, de la qualité, du comportement de redimensionnement, du mode d\'échelle et de l\'espace colorimétrique qui apparaissent en premier dans de nombreux outils, ce qui permet d\'éviter de resélectionner les mêmes choix à chaque exportation. + Définissez le format et la qualité de l\'image par défaut pour qu\'ils correspondent à votre destination habituelle, comme JPEG pour les photos ou PNG/WebP pour l\'alpha. + Ajustez le type de redimensionnement et le mode d\'échelle par défaut si vous exportez souvent pour des dimensions strictes, des plateformes sociales ou des pages de document. + Modifiez les valeurs par défaut de l\'espace colorimétrique uniquement lorsque votre flux de travail nécessite une gestion cohérente des couleurs sur tous les écrans, imprimantes ou éditeurs externes. + Sélecteur et lanceur + Utilisez les paramètres du sélecteur lorsque l\'application ouvre trop de boîtes de dialogue ou démarre au mauvais endroit + Adaptez l\'ouverture des fichiers à vos habitudes + Les paramètres du sélecteur et du lanceur contrôlent si Image Toolbox démarre à partir de l\'écran principal, d\'un outil ou d\'un flux de sélection de fichiers. Ces options sont particulièrement utiles lorsque vous entrez habituellement à partir de la feuille de partage Android ou lorsque vous souhaitez que l\'application ressemble davantage à un lanceur d\'outils. + Utilisez les paramètres du sélecteur de photos si le sélecteur système masque des fichiers, affiche trop d\'éléments récents ou gère mal les fichiers cloud. + Activez la sélection sautée uniquement pour les outils dans lesquels vous entrez habituellement à partir du partage ou connaissez déjà le fichier source. + Vérifiez le mode de lancement si vous souhaitez qu\'Image Toolbox se comporte davantage comme un sélecteur d\'outils que comme un écran d\'accueil normal. + Source des images + Choisissez le mode de sélection qui fonctionne le mieux avec les galeries, les gestionnaires de fichiers et les fichiers cloud + Choisissez des fichiers à partir de la bonne source + Les paramètres de source d’image affectent la manière dont l’application demande des images à Android. Le meilleur choix dépend du fait que vos fichiers se trouvent dans la galerie système, dans un dossier de gestionnaire de fichiers, chez un fournisseur de cloud ou dans une autre application partageant du contenu temporaire. + Utilisez le sélecteur par défaut lorsque vous souhaitez bénéficier de l\'expérience la plus intégrée au système pour les images de galerie courantes. + Changez de mode de sélection si les albums, dossiers, fichiers cloud ou formats non standard n\'apparaissent pas là où vous le souhaitez. + Si un fichier partagé disparaît après avoir quitté l\'application source, rouvrez-le via un sélecteur persistant ou enregistrez d\'abord une copie locale. + Favoris et outils de partage + Gardez l\'écran principal concentré et masquez les outils qui n\'ont pas de sens dans les flux de partage + Réduire le bruit de la liste d\'outils + Les favoris et les paramètres masqués pour le partage sont utiles lorsque vous n\'utilisez que quelques outils chaque jour. Ils maintiennent également le flux de partage pratique en masquant les outils qui ne peuvent pas utiliser le type de fichier envoyé par une autre application. + Épinglez les outils fréquents afin qu’ils restent faciles d’accès même lorsque les outils sont regroupés par catégorie. + Masquez les outils des flux de partage lorsqu\'ils ne sont pas pertinents pour les fichiers provenant d\'une autre application. + Utilisez les paramètres de classement des favoris si vous préférez les favoris à la fin ou regroupés avec des outils associés. + Sauvegarder les paramètres + Déplacez les préréglages, les préférences et le comportement de l\'application vers une autre installation en toute sécurité + Gardez votre configuration portable + La sauvegarde et la restauration sont plus utiles après avoir réglé les préréglages, les dossiers, les règles de nom de fichier, l\'ordre des outils et les préférences visuelles. Une sauvegarde vous permet de tester les paramètres ou de réinstaller l\'application sans reconstruire votre flux de travail à partir de la mémoire. + Créez une sauvegarde après avoir modifié les préréglages, le comportement du nom de fichier, les formats par défaut ou la disposition des outils. + Stockez la sauvegarde quelque part en dehors du dossier de l\'application si vous envisagez d\'effacer des données, de réinstaller ou de déplacer des appareils. + Restaurez les paramètres avant de traiter des lots importants afin que les valeurs par défaut et le comportement d\'enregistrement correspondent à votre ancienne configuration. + Redimensionner et convertir des images + Modifier la taille, le format, la qualité et les métadonnées d\'une ou plusieurs images + Créer des copies redimensionnées + Redimensionner et convertir est l\'outil principal pour les dimensions prévisibles et les fichiers de sortie plus légers. Utilisez-le lorsque la taille de l’image, le format de sortie et le comportement des métadonnées comptent tous en même temps. + Choisissez une ou plusieurs images, puis choisissez si les dimensions, l\'échelle ou la taille du fichier sont les plus importantes. + Sélectionnez le format et la qualité de sortie ; utilisez PNG ou WebP lorsque la transparence doit être préservée. + Prévisualisez le résultat, puis enregistrez ou partagez les copies générées sans modifier les originaux. + Redimensionner par taille de fichier + Ciblez une limite de taille lorsqu\'un site Web, une messagerie ou un formulaire rejette des images volumineuses + Respecter des limites de téléchargement strictes + Il est préférable de redimensionner en fonction de la taille du fichier plutôt que de deviner les valeurs de qualité lorsque la destination n\'accepte que les fichiers inférieurs à une limite spécifique. L\'outil peut ajuster la compression et les dimensions pour atteindre la cible tout en essayant de garder le résultat utilisable. + Entrez la taille cible légèrement en dessous de la limite de téléchargement réelle pour laisser de la place aux changements de métadonnées et de fournisseur. + Préférez les formats avec perte pour les photos lorsque la cible est petite ; PNG peut rester volumineux même après le redimensionnement. + Inspectez les faces, le texte et les bords après l\'exportation, car des cibles de taille agressive peuvent introduire des artefacts visibles. + Limiter le redimensionnement + Réduisez uniquement les images qui dépassent vos contraintes maximales de largeur, de hauteur ou de fichier. + Évitez de redimensionner des images déjà correctes + Limit Resize est utile pour les dossiers mixtes où certaines images sont énormes et d\'autres sont déjà préparées. Au lieu de forcer chaque fichier à subir le même redimensionnement, les images acceptables sont plus proches de leur qualité d\'origine. + Définissez des dimensions ou des limites maximales en fonction de la destination plutôt que de redimensionner aveuglément chaque fichier. + Activez le comportement de style ignorer si plus grand lorsque vous souhaitez éviter les sorties qui deviennent plus lourdes que les sources. + Utilisez-le pour les lots provenant de différentes caméras, captures d\'écran ou téléchargements où la taille des sources varie considérablement. + Recadrer, faire pivoter et redresser + Encadrez la zone importante avant d\'exporter ou d\'utiliser l\'image dans d\'autres outils + Nettoyer la composition + Le recadrage rend d\'abord les filtres ultérieurs, l\'OCR, les pages PDF et le partage des résultats plus propres. + Ouvrez Recadrer et choisissez l\'image que vous souhaitez ajuster. + Utilisez un recadrage libre ou un rapport hauteur/largeur lorsque la sortie doit correspondre à une publication, un document ou un avatar. + Faites pivoter ou retournez avant d\'enregistrer afin que les outils ultérieurs reçoivent l\'image corrigée. + Appliquer des filtres et des ajustements + Créez une pile de filtres modifiable et prévisualisez le résultat avant l\'exportation + Ajuster l\'apparence de l\'image + Les filtres sont utiles pour des corrections rapides, une sortie stylisée et la préparation des images pour l\'OCR ou l\'impression. De petites modifications du contraste, de la netteté et de la luminosité peuvent avoir plus d\'importance que des effets importants lorsque l\'image contient du texte ou des détails fins. + Ajoutez des filtres un par un et gardez un œil sur l\'aperçu après chaque modification. + Ajustez la luminosité, le contraste, la netteté, le flou, la couleur ou les masques en fonction de la tâche. + Exportez uniquement lorsque l\'aperçu correspond à votre appareil cible, votre document ou votre plateforme sociale. + Aperçu d\'abord + Inspectez les images avant de choisir un outil d\'édition, de conversion ou de nettoyage plus lourd + Vérifiez ce que vous avez réellement reçu + L\'aperçu de l\'image est utile lorsqu\'un fichier provient d\'un chat, d\'un stockage cloud ou d\'une autre application et que vous n\'êtes pas sûr de ce qu\'il contient. Vérifier d\'abord les dimensions, la transparence, l\'orientation et la qualité visuelle permet de choisir le bon outil de suivi. + Ouvrez Aperçu de l\'image lorsque vous devez inspecter plusieurs images avant de décider quoi en faire. + Recherchez les problèmes de rotation, les zones transparentes, les artefacts de compression et les dimensions étonnamment grandes. + Passez à Redimensionner, Recadrer, Comparer, EXIF ​​ou Supprimer l\'arrière-plan uniquement après avoir déterminé ce qui doit être corrigé. + Supprimer ou restaurer un arrière-plan + Effacez automatiquement les arrière-plans ou affinez les bords manuellement avec les outils de restauration + Gardez le sujet, supprimez le reste + La suppression de l’arrière-plan fonctionne mieux lorsque vous combinez la sélection automatique avec un nettoyage manuel minutieux. Le format d\'exportation final est aussi important que le masque, car JPEG aplatira les zones transparentes même si l\'aperçu semble correct. + Commencez par la suppression automatique si le sujet est clairement séparé de l’arrière-plan. + Effectuez un zoom avant et basculez entre l\'effacement et la restauration pour corriger les cheveux, les coins, les ombres et les petits détails. + Enregistrez au format PNG ou WebP lorsque vous avez besoin de transparence dans le fichier final. + Dessiner, annoter et filigraner + Ajoutez des flèches, des notes, des surlignages, des signatures ou des superpositions de filigranes répétées + Ajouter des informations visibles + Les outils de balisage aident à expliquer une image, tandis que le filigrane permet de marquer ou de protéger les fichiers exportés. + Utilisez Draw lorsque vous avez besoin de notes à main levée, de formes, de surlignages ou d\'annotations rapides. + Utilisez le filigrane lorsque la même marque de texte ou d’image doit apparaître de manière cohérente sur les sorties. + Vérifiez l\'opacité, la position et l\'échelle avant d\'exporter afin que la marque soit visible mais ne gêne pas. + Dessiner les valeurs par défaut + Définissez la largeur de ligne, la couleur, le mode de tracé, le verrouillage de l\'orientation et la loupe pour un balisage plus rapide + Rendre le dessin prévisible + Les paramètres de dessin sont utiles lorsque vous annotez souvent des captures d’écran, marquez des documents ou signez des images. Les valeurs par défaut réduisent le temps de configuration, tandis que la loupe et le verrouillage de l\'orientation facilitent les modifications précises sur les petits écrans. + Définissez la largeur de ligne par défaut et dessinez la couleur selon les valeurs que vous utilisez le plus pour les flèches, les surbrillances ou les signatures. + Utilisez le mode tracé par défaut lorsque vous dessinez habituellement le même type de traits, de formes ou de balisage. + Activez la loupe ou le verrouillage de l\'orientation lorsque la saisie avec le doigt rend les petits détails difficiles à placer avec précision. + Mises en page multi-images + Choisissez le bon outil multi-images avant d\'organiser des captures d\'écran, des numérisations ou des bandes de comparaison + Utilisez le bon outil de mise en page + Ces outils semblent similaires, mais chacun est conçu pour un type différent de sortie multi-images. Choisir le bon en premier évite de combattre les contrôles de mise en page conçus pour un résultat différent. + Utilisez Collage Maker pour des mises en page conçues avec plusieurs images dans une seule composition. + Utilisez l\'assemblage ou l\'empilement d\'images lorsque les images doivent former une longue bande verticale ou horizontale. + Utilisez le fractionnement d\'image lorsqu\'une grande image doit devenir plusieurs morceaux plus petits. + Convertir les formats d\'images + Créez des copies JPEG, PNG, WebP, AVIF, JXL et autres sans modifier manuellement les pixels + Choisissez le format du travail + Différents formats sont meilleurs pour les photos, les captures d\'écran, la transparence, la compression ou la compatibilité. La conversion est plus sûre lorsque vous comprenez ce que l\'application de destination prend en charge et si la source contient des données alpha, des animations ou des métadonnées que vous souhaitez conserver. + Utilisez JPEG pour les photos compatibles, PNG pour les images sans perte et alpha, et WebP pour le partage compact. + Ajustez la qualité lorsque le format le prend en charge ; des valeurs inférieures réduisent la taille mais peuvent ajouter des artefacts. + Conservez les originaux jusqu\'à ce que vous vérifiiez que les fichiers convertis s\'ouvrent correctement dans l\'application cible. + Vérifiez EXIF ​​et confidentialité + Afficher, modifier ou supprimer des métadonnées avant de partager des photos sensibles + Contrôler les données d\'image cachées + EXIF peut contenir des données de caméra, des dates, un emplacement, une orientation et d\'autres détails non visibles dans l\'image. Cela vaut la peine de vérifier avant le partage public, les rapports d\'assistance, les listes de marchés ou toute photo susceptible de révéler un lieu privé. + Ouvrez les outils EXIF ​​avant de partager des photos pouvant contenir des informations de localisation ou d\'appareil privé. + Supprimez les champs sensibles ou modifiez les balises incorrectes telles que la date, l\'orientation, l\'auteur ou les données de l\'appareil photo. + Enregistrez une nouvelle copie et partagez cette copie au lieu de l\'original lorsque la confidentialité est importante. + Nettoyage automatique des EXIF + Supprimez les métadonnées par défaut tout en décidant si les dates doivent être conservées + Faire de la confidentialité la valeur par défaut + Le groupe de paramètres EXIF ​​est utile lorsque vous souhaitez que le nettoyage de la confidentialité se fasse automatiquement au lieu de le mémoriser à chaque exportation. Conserver la date et l\'heure est une décision distincte car les dates peuvent être utiles pour le tri mais sensibles pour le partage. + Activez EXIF ​​toujours clair lorsque la plupart de vos exportations sont destinées à un partage public ou semi-public. + Activez Conserver la date et l\'heure uniquement lorsque la préservation de l\'heure de capture est plus importante que la suppression de chaque horodatage. + Utilisez l\'édition EXIF ​​manuelle pour les fichiers uniques dans lesquels vous devez conserver certains champs et en supprimer d\'autres. + Contrôler les noms de fichiers + Utiliser des préfixes, des suffixes, des horodatages, des préréglages, des sommes de contrôle et des numéros de séquence + Rendre les exportations faciles à trouver + Un bon modèle de nom de fichier permet de trier les lots et d\'éviter les écrasements accidentels. Les paramètres de nom de fichier sont particulièrement utiles lorsque les exportations retournent vers le dossier source, où des noms similaires peuvent rapidement prêter à confusion. + Définissez le préfixe, le suffixe, l\'horodatage, le nom d\'origine, les informations prédéfinies ou les options de séquence du nom de fichier dans Paramètres. + Utilisez des numéros de séquence pour les lots où l\'ordre est important, comme les pages, les cadres ou les collages. + Activez l\'écrasement uniquement lorsque vous êtes sûr que le remplacement des fichiers existants est sans danger pour le dossier actuel. + Écraser les fichiers + Remplacez les sorties par des noms correspondants uniquement lorsque votre flux de travail est intentionnellement destructeur + Utilisez le mode écrasement avec précaution + L\'option Écraser les fichiers modifie la manière dont les conflits de noms sont gérés. Il est utile pour répéter les exportations dans le même dossier, mais il peut également remplacer les résultats antérieurs si votre modèle de nom de fichier produit à nouveau le même nom. + Activez l\'écrasement uniquement pour les dossiers dans lesquels le remplacement des anciens fichiers générés est attendu et récupérable. + Gardez l\'écrasement désactivé lors de l\'exportation d\'originaux, de fichiers clients, d\'analyses ponctuelles ou de tout autre élément sans sauvegarde. + Combinez l\'écrasement avec un modèle de nom de fichier délibéré afin que seules les sorties prévues soient remplacées. + Modèles de nom de fichier + Combinez les noms d\'origine, les préfixes, les suffixes, les horodatages, les préréglages, le mode d\'échelle et les sommes de contrôle + Créer des noms qui expliquent le résultat + Les paramètres de modèle de nom de fichier peuvent transformer les fichiers exportés en un historique utile de ce qui leur est arrivé. Cela est important par lots, car la vue des dossiers peut être le seul endroit où vous pouvez distinguer la taille d\'origine, le préréglage, le format et l\'ordre de traitement. + Utilisez le nom de fichier, le préfixe, le suffixe et l\'horodatage d\'origine lorsque vous avez besoin de noms lisibles pour le tri manuel. + Ajoutez des informations sur les préréglages, le mode d\'échelle, la taille du fichier ou la somme de contrôle lorsque les sorties doivent documenter la manière dont elles ont été produites. + Évitez les noms aléatoires pour les flux de travail dans lesquels les fichiers doivent rester associés aux originaux ou à l\'ordre des pages. + Choisissez où les fichiers sont enregistrés + Évitez de perdre les exportations en définissant des dossiers, en enregistrant dans le dossier d\'origine et en plaçant des emplacements de sauvegarde uniques. + Gardez les résultats prévisibles + Le comportement d\'enregistrement est particulièrement important lorsque vous traitez de nombreux fichiers ou partagez des fichiers provenant de fournisseurs de cloud. Les paramètres du dossier déterminent si les résultats sont collectés dans un emplacement connu, s\'affichent à côté des originaux ou sont temporairement transférés ailleurs pour une tâche spécifique. + Définissez un dossier de sauvegarde par défaut si vous souhaitez toujours exporter au même endroit. + Utilisez l\'enregistrement dans le dossier d\'origine pour les flux de travail de nettoyage où la sortie doit rester proche du fichier source. + Utilisez un emplacement de sauvegarde unique lorsqu\'une seule tâche nécessite une destination différente sans modifier votre valeur par défaut. + Dossiers de sauvegarde uniques + Envoyez les prochains exports vers un dossier temporaire sans changer votre destination habituelle + Séparez les tâches spéciales + L\'emplacement de sauvegarde unique est utile pour les projets temporaires, les dossiers clients, les sessions de nettoyage ou les exportations qui ne doivent pas se mélanger avec votre dossier Image Toolbox habituel. Il donne une destination de courte durée sans réécrire votre configuration par défaut. + Choisissez un dossier à usage unique avant de démarrer une tâche qui appartient à l\'extérieur de votre emplacement d\'exportation habituel. + Utilisez-le pour des projets courts, des dossiers partagés ou des lots où les sorties doivent rester regroupées. + Revenez au dossier par défaut après la tâche afin que les exportations ultérieures n\'aboutissent pas de manière inattendue à l\'emplacement temporaire. + Équilibrer la qualité et la taille du fichier + Comprendre quand modifier les dimensions, le format ou la qualité au lieu de deviner + Réduire intentionnellement les fichiers + La taille du fichier dépend des dimensions, du format, de la qualité, de la transparence et de la complexité du contenu de l\'image. Si un fichier est encore trop lourd, la modification des dimensions contribue généralement à bien plus qu\'une baisse répétée de la qualité. + Redimensionnez d\'abord les dimensions lorsque l\'image est plus grande que ce que la destination peut afficher. + Qualité inférieure uniquement pour les formats avec perte et vérification du texte, des bords, des dégradés et des faces après l\'exportation. + Changez de format lorsque les changements de qualité ne suffisent pas, par exemple WebP pour le partage ou PNG pour des ressources transparentes et nettes. + Travailler avec des formats animés + Utilisez les outils GIF, APNG, WebP et JXL lorsqu\'un fichier contient des images au lieu d\'un bitmap + Ne traitez pas chaque image comme étant fixe + Les formats animés nécessitent des outils qui comprennent les images, la durée et la compatibilité. + Utilisez les outils GIF ou APNG lorsque vous avez besoin d\'opérations au niveau de l\'image pour ces formats. + Utilisez les outils WebP lorsque vous avez besoin d\'une compression moderne ou d\'une sortie WebP animée. + Prévisualisez le timing de l\'animation avant de le partager, car certaines plates-formes convertissent ou aplatissent les images animées. + Comparer et prévisualiser la sortie + Inspectez les modifications, la taille du fichier et les différences visuelles avant de conserver une exportation + Vérifiez avant de partager + Les outils de comparaison permettent de détecter rapidement les artefacts de compression, les mauvaises couleurs ou les modifications indésirables. + Ouvrez Compare lorsque vous souhaitez inspecter deux versions côte à côte. + Zoomez sur les bords, le texte, les dégradés et les zones transparentes où les problèmes sont les plus faciles à ignorer. + Si la sortie est trop lourde ou floue, revenez à l’outil précédent et ajustez la qualité ou le format. + Utiliser le hub d\'outils PDF + Fusionner, diviser, faire pivoter, supprimer des pages, compresser, protéger, OCR et filigraner des fichiers PDF + Choisissez une opération PDF + Le hub PDF regroupe les actions du document derrière un point d\'entrée afin que vous puissiez choisir l\'opération exacte. Commencez par là lorsque le fichier est déjà un PDF et utilisez Images vers PDF ou Scanner de documents lorsque votre source est toujours un ensemble d\'images. + Ouvrez PDF Tools et choisissez l\'opération qui correspond à votre objectif. + Sélectionnez le PDF ou les images sources, puis examinez les options d\'ordre des pages, de rotation, de protection ou d\'OCR. + Enregistrez le PDF généré et ouvrez-le une fois pour confirmer le nombre de pages, l\'ordre et la lisibilité. + Transformez des images en PDF + Combinez des numérisations, des captures d\'écran, des reçus ou des photos en un seul document partageable + Construire un document propre + Les images deviennent de meilleures pages PDF lorsqu\'elles sont recadrées, ordonnées et dimensionnées de manière cohérente. Traitez la liste d\'images comme une pile de pages : chaque choix de rotation, de marge et de taille de page affecte la lisibilité du document final. + Recadrez ou faites pivoter les images en premier lorsque les bords de la page, les ombres ou l\'orientation sont incorrects. + Sélectionnez les images dans l’ordre des pages prévu et ajustez la taille de la page ou les marges si l’outil les propose. + Exportez le PDF, puis vérifiez que toutes les pages sont lisibles avant de l\'envoyer. + Numériser des documents + Capturez des pages papier et préparez-les pour le PDF, l\'OCR ou le partage + Capturez des pages lisibles + La numérisation de documents fonctionne mieux avec des pages plates, un bon éclairage et une perspective corrigée. Une analyse nette avant l\'exportation OCR ou PDF permet de gagner du temps, car la reconnaissance et la compression du texte dépendent toutes deux de la qualité de la page. + Placez le document sur une surface contrastée avec suffisamment de lumière et un minimum d\'ombres. + Ajustez les coins détectés ou recadrez manuellement pour que la page remplisse proprement le cadre. + Exportez sous forme d\'images ou de PDF, puis exécutez OCR si vous avez besoin de texte sélectionnable. + Protéger ou déverrouiller des fichiers PDF + Utilisez les mots de passe avec précaution et conservez une copie source modifiable lorsque cela est possible + Gérez l\'accès aux PDF en toute sécurité + Les outils de protection sont utiles pour un partage contrôlé, mais les mots de passe sont faciles à perdre et difficiles à récupérer. Protégez les copies pour la distribution, conservez les fichiers sources non protégés séparément et vérifiez le résultat avant de supprimer quoi que ce soit. + Protégez une copie du PDF, pas votre seul document source. + Conservez le mot de passe dans un endroit sûr avant de partager le fichier protégé. + Utilisez le déverrouillage uniquement pour les fichiers que vous êtes autorisé à modifier, puis vérifiez que la copie déverrouillée s\'ouvre correctement. + Organiser les pages PDF + Fusionner, diviser, réorganiser, faire pivoter, recadrer, supprimer des pages et ajouter délibérément des numéros de page + Corriger la structure du document avant la décoration + Les outils de page PDF sont plus faciles à utiliser dans le même ordre que celui dans lequel vous prépareriez un document papier : collecter les pages, supprimer les mauvaises, les mettre en ordre, corriger l\'orientation, puis ajouter les derniers détails tels que les numéros de page ou les filigranes. + Utilisez d\'abord la fusion ou la conversion d\'images en PDF lorsque le document est toujours divisé en plusieurs fichiers. + Réorganisez, faites pivoter, recadrez ou supprimez des pages avant d\'ajouter des numéros de page, des signatures ou des filigranes. + Prévisualisez le PDF final après les modifications structurelles, car une page manquante ou pivotée peut être difficile à remarquer plus tard. + Annotations PDF + Supprimez les annotations ou aplatissez-les lorsque les spectateurs affichent les commentaires et les marques différemment + Contrôlez ce qui reste visible + Les annotations peuvent être des commentaires, des surlignages, des dessins, des marques de forme ou d\'autres objets PDF supplémentaires. Certains visualiseurs les affichent différemment, donc les supprimer ou les aplatir peut rendre les documents partagés plus prévisibles. + Supprimez les annotations lorsque les commentaires, les surlignages ou les marques de révision ne doivent pas être inclus dans la copie partagée. + Aplatir lorsque les marques visibles doivent rester sur la page mais ne plus se comporter comme des annotations modifiables. + Conservez une copie originale, car la suppression ou l\'aplatissement des annotations peut être difficile à inverser. + Reconnaître le texte + Extrayez le texte des images avec des moteurs et des langues OCR sélectionnables + Obtenez de meilleurs résultats OCR + La précision de l\'OCR dépend de la netteté de l\'image, des données linguistiques, du contraste et de l\'orientation de la page. Les meilleurs résultats proviennent généralement de la préparation préalable de l\'image : recadrez l\'image, éliminez le bruit, faites-la pivoter verticalement, augmentez la lisibilité, puis reconnaissez le texte. + Recadrez l\'image dans la zone de texte et faites-la pivoter verticalement avant la reconnaissance. + Choisissez la bonne langue et téléchargez les données linguistiques si l\'application le demande. + Copiez ou partagez le texte reconnu, puis vérifiez manuellement les noms, les chiffres et la ponctuation. + Choisissez les données de langue OCR + Améliorez la reconnaissance en faisant correspondre les scripts, les langues et les modèles OCR téléchargés + Faites correspondre l\'OCR au texte + Une mauvaise langue OCR peut faire passer de bonnes photos pour une mauvaise reconnaissance. Les données linguistiques sont importantes pour les scripts, la ponctuation, les chiffres et les documents mixtes. Choisissez donc la langue qui apparaît dans l\'image plutôt que la langue de l\'interface utilisateur du téléphone. + Choisissez la langue ou l\'écriture qui apparaît dans l\'image, pas seulement la langue du téléphone. + Téléchargez les données manquantes avant de travailler hors ligne ou de traiter un lot. + Pour les documents multilingues, exécutez d\'abord la langue la plus importante et vérifiez manuellement les noms et les numéros. + PDF consultables + Réécrivez le texte OCR dans des fichiers afin que les pages numérisées puissent être recherchées et copiées + Transformez les numérisations en documents consultables + L\'OCR peut faire plus que copier du texte dans le presse-papiers. Lorsque vous écrivez du texte reconnu dans un fichier ou un PDF consultable, l\'image de la page reste visible tandis que le texte devient plus facile à rechercher, sélectionner, indexer ou archiver. + Utilisez une sortie PDF consultable pour les documents numérisés qui doivent toujours ressembler aux pages originales. + Utilisez l\'écriture dans un fichier lorsque vous n\'avez besoin que du texte extrait et que vous ne vous souciez pas de l\'image de la page. + Vérifiez manuellement les numéros, noms et tableaux importants, car le texte OCR peut être recherché mais reste imparfait. + Scanner les codes QR + Lisez le contenu QR à partir de l\'entrée de la caméra ou des images enregistrées lorsqu\'elles sont disponibles + Lisez les codes en toute sécurité + Les codes QR peuvent contenir des liens, des données Wi-Fi, des contacts, du texte ou d\'autres charges utiles. + Ouvrez Scan QR Code et gardez le code net, plat et entièrement à l\'intérieur du cadre. + Examinez le contenu décodé avant d’ouvrir des liens ou de copier des données sensibles. + Si la numérisation échoue, améliorez l’éclairage, recadrez le code ou essayez une image source de plus haute résolution. + Utilisez Base64 et les sommes de contrôle + Encodez les images sous forme de texte, décodez le texte en images et vérifiez l\'intégrité des fichiers. + Se déplacer entre les fichiers et le texte + Base64 est utile pour les petites charges utiles d\'images, tandis que les sommes de contrôle permettent de confirmer que les fichiers n\'ont pas changé. Ces outils sont particulièrement utiles dans les flux de travail techniques, les rapports d\'assistance, les transferts de fichiers et les endroits où les fichiers binaires doivent temporairement devenir du texte. + Utilisez les outils Base64 pour encoder une petite image lorsqu\'un flux de travail a besoin de texte au lieu d\'un fichier binaire. + Décodez Base64 uniquement à partir de sources fiables et vérifiez l’aperçu avant de l’enregistrer. + Utilisez les outils de somme de contrôle lorsque vous devez comparer des fichiers exportés ou confirmer un téléchargement/téléchargement. + Regrouper ou protéger les données + Utilisez des outils ZIP et de chiffrement pour regrouper des fichiers ou transformer des données texte + Conserver les fichiers associés ensemble + Les outils de packaging sont utiles lorsque plusieurs sorties doivent voyager dans un seul fichier. Ils sont également utiles pour conserver ensemble les images, les PDF, les journaux et les métadonnées générés lorsque vous devez envoyer un ensemble de résultats complet. + Utilisez ZIP lorsque vous devez envoyer plusieurs images ou documents exportés ensemble. + Utilisez les outils de chiffrement uniquement lorsque vous comprenez la méthode sélectionnée et pouvez stocker les clés requises en toute sécurité. + Testez l\'archive créée ou le texte transformé avant de supprimer les fichiers sources. + Sommes de contrôle dans les noms + Utilisez des noms de fichiers basés sur une somme de contrôle lorsque l\'unicité et l\'intégrité comptent plus que la lisibilité + Nommer les fichiers par contenu + Les noms de fichiers de somme de contrôle sont utiles lorsque les exportations peuvent avoir des noms visibles en double ou lorsque vous devez prouver que deux fichiers sont identiques. Ils sont moins conviviaux pour la navigation manuelle, utilisez-les donc pour les archives techniques et les flux de travail de vérification. + Activez la somme de contrôle comme nom de fichier lorsque l\'identité du contenu est plus importante qu\'un titre lisible par l\'homme. + Utilisez-le pour les archives, la déduplication, les exportations répétables ou les fichiers pouvant être téléchargés et téléchargés à nouveau. + Associez les noms de somme de contrôle à des dossiers ou des métadonnées lorsque les utilisateurs ont encore besoin de comprendre ce que représente le fichier. + Choisissez des couleurs à partir d\'images + Échantillonnez des pixels, copiez les codes de couleur et réutilisez les couleurs dans des palettes ou des conceptions + Échantillonnez la couleur exacte + La sélection des couleurs est utile pour faire correspondre un design, extraire les couleurs de la marque ou vérifier les valeurs de l\'image. Le zoom est important car l\'anticrénelage, les ombres et la compression peuvent rendre les pixels voisins légèrement différents de la couleur attendue. + Ouvrez Choisir la couleur de l\'image et choisissez une image source avec la couleur dont vous avez besoin. + Effectuez un zoom avant avant d\'échantillonner les petits détails afin que les pixels proches n\'affectent pas le résultat. + Copiez la couleur dans le format requis par votre destination, tel que HEX, RVB ou HSV. + Créez des palettes et utilisez la bibliothèque de couleurs + Extrayez les palettes, parcourez les couleurs enregistrées et conservez des systèmes visuels cohérents + Construire une palette réutilisable + Les palettes permettent de conserver la cohérence visuelle des graphiques, des filigranes et des dessins exportés. Ils sont particulièrement utiles lorsque vous annotez à plusieurs reprises des captures d’écran, préparez des éléments de marque ou avez besoin des mêmes couleurs sur plusieurs outils. + Générez une palette à partir d\'une image ou ajoutez des couleurs manuellement lorsque vous connaissez déjà les valeurs. + Nommez ou organisez les couleurs importantes afin de pouvoir les retrouver plus tard. + Utilisez les valeurs copiées dans des outils de dessin, de filigrane, de dégradé, SVG ou de conception externe. + Créer des dégradés + Créez des images dégradées pour les arrière-plans, les espaces réservés et les expériences visuelles + Contrôler les transitions de couleurs + Les outils de dégradé sont utiles lorsque vous avez besoin d\'une image générée au lieu de modifier une image existante. Ils peuvent devenir des arrière-plans épurés, des espaces réservés, des fonds d’écran, des masques ou de simples ressources pour des outils de conception externes. + Choisissez le type de dégradé et définissez d\'abord les couleurs importantes. + Ajustez la direction, les arrêts, la fluidité et la taille de sortie en fonction du cas d\'utilisation cible. + Exportez dans un format qui correspond à la destination, généralement PNG pour des graphiques générés proprement. + Accessibilité des couleurs + Utilisez des options de couleurs dynamiques, de contraste et de daltonisme lorsque l\'interface utilisateur est difficile à lire + Rendre les couleurs plus faciles à utiliser + Les paramètres de couleur affectent le confort, la lisibilité et la rapidité avec laquelle vous pouvez numériser les commandes. Si les puces d\'outils, les curseurs ou les états sélectionnés semblent difficiles à distinguer, les options de thème et d\'accessibilité peuvent être plus utiles que le simple changement du mode sombre. + Essayez des couleurs dynamiques lorsque vous souhaitez que l\'application suive la palette de papier peint actuelle. + Augmentez le contraste ou modifiez le schéma lorsque les boutons et les surfaces ne ressortent pas suffisamment. + Utilisez les options de schéma de daltonisme si certains états ou accents sont difficiles à distinguer. + Fond alpha + Contrôlez l\'aperçu ou la couleur de remplissage utilisée autour des fichiers PNG transparents, WebP et autres sorties similaires. + Comprendre les aperçus transparents + La couleur d\'arrière-plan des formats alpha peut faciliter l\'inspection des images transparentes, mais il est important de savoir si vous modifiez un comportement d\'aperçu/remplissage ou si vous aplatissez le résultat final. Cela est important pour les autocollants, les logos et les images supprimées en arrière-plan. + Utilisez d\'abord un format prenant en charge l\'alpha, tel que PNG ou WebP, lorsque les pixels transparents doivent survivre à l\'exportation. + Ajustez les paramètres de couleur d’arrière-plan lorsque les bords transparents sont difficiles à voir sur la surface de l’application. + Exportez un petit test et rouvrez-le dans une autre application pour confirmer si la transparence ou le remplissage choisi a été enregistré. + Choix du modèle d\'IA + Choisissez un modèle par type d\'image et défaut au lieu de choisir d\'abord le plus grand + Faites correspondre le modèle aux dégâts + AI Tools peut télécharger ou importer différents modèles ONNX/ORT, et chaque modèle est formé pour un type particulier de problème. Un modèle pour les blocs JPEG, le nettoyage des lignes animées, le débruitage, la suppression de l\'arrière-plan, la colorisation ou la mise à l\'échelle peut produire de moins bons résultats s\'il est utilisé sur le mauvais type d\'image. + Lisez la description du modèle avant de télécharger ; choisissez le modèle qui nomme votre problème réel, tel que la compression, le bruit, les demi-teintes, le flou ou la suppression de l\'arrière-plan. + Commencez avec un modèle plus petit ou plus spécifique lorsque vous testez un nouveau type d\'image, puis comparez avec des modèles plus lourds uniquement si le résultat n\'est pas assez bon. + Conservez uniquement les modèles que vous utilisez réellement, car les modèles téléchargés et importés peuvent occuper un espace de stockage considérable. + Comprendre les familles de modèles + Les noms de modèles font souvent allusion à leur cible : les modèles de style RealESRGAN haut de gamme, les modèles SCUNet et FBCNN nettoient le bruit ou la compression, les modèles d\'arrière-plan créent des masques et les coloriseurs ajoutent de la couleur à l\'entrée en niveaux de gris. Les modèles personnalisés importés peuvent être puissants, mais ils doivent correspondre à la forme d\'entrée/sortie attendue et utiliser le format ONNX ou ORT pris en charge. + Utilisez des upscalers lorsque vous avez besoin de plus de pixels, des débruiteurs lorsque l\'image est granuleuse et des suppresseurs d\'artefacts lorsque les blocs de compression ou les bandes sont le principal problème. + Utilisez des modèles d\'arrière-plan uniquement pour la séparation des sujets ; ce ne sont pas les mêmes que la suppression générale d’objets ou l’amélioration d’image. + Importez des modèles personnalisés uniquement à partir de sources fiables et testez-les sur une petite image avant d\'utiliser des fichiers importants. + Paramètres de l\'IA + Ajustez la taille des blocs, le chevauchement et les travailleurs parallèles pour équilibrer la qualité, la vitesse et la mémoire. + Équilibrer vitesse et stabilité + La taille des morceaux contrôle la taille des morceaux d’image avant qu’ils ne soient traités par le modèle. Overlap mélange les morceaux voisins pour masquer les bordures. Les travailleurs parallèles peuvent accélérer le traitement, mais chaque travailleur a besoin de mémoire, de sorte que des valeurs élevées peuvent rendre les images volumineuses instables sur les téléphones dotés de RAM limitée. + Utilisez des morceaux plus gros pour un contexte plus fluide lorsque votre appareil gère le modèle de manière fiable et que l\'image n\'est pas énorme. + Augmentez le chevauchement lorsque les bordures des morceaux deviennent visibles, mais n\'oubliez pas qu\'un chevauchement plus important signifie un travail plus répété. + Élever des travailleurs parallèles uniquement après un test stable ; si le traitement ralentit, chauffe l\'appareil ou tombe en panne, réduisez d\'abord le nombre de travailleurs. + Évitez les artefacts de fragments + Le chunking permet à l\'application de traiter des images plus grandes que ce que le modèle peut gérer confortablement à la fois. Le compromis est que chaque tuile a moins de contexte environnant, donc un faible chevauchement ou des morceaux trop petits peuvent créer des bordures visibles, des changements de texture ou des détails incohérents entre les zones voisines. + Augmentez le chevauchement si vous voyez des bordures rectangulaires, des changements de texture répétés ou des sauts de détails entre les régions traitées. + Réduisez la taille des fragments si le processus échoue avant de produire le résultat, en particulier sur les images volumineuses ou les modèles lourds. + Enregistrez un petit test de recadrage avant de traiter l\'image complète afin de pouvoir comparer rapidement les paramètres. + Stabilité de l\'IA + Réduisez la taille des blocs, les nœuds de calcul et la résolution de la source lorsque le traitement de l\'IA plante ou se bloque + Récupération après des tâches d\'IA lourdes + Le traitement de l\'IA est l\'un des flux de travail les plus lourds de l\'application. Les plantages, les échecs de sessions, les blocages ou les redémarrages soudains des applications signifient généralement que le modèle, la résolution source, la taille des blocs ou le nombre de travailleurs parallèles sont trop exigeants pour l\'état actuel de l\'appareil. + Si l\'application plante ou ne parvient pas à ouvrir une session de modèle, réduisez les travailleurs parallèles et la taille des fragments, puis réessayez la même image. + Redimensionnez les images très volumineuses avant le traitement par l\'IA lorsque l\'objectif ne nécessite pas la résolution d\'origine. + Fermez les autres applications lourdes, utilisez un modèle plus petit ou traitez moins de fichiers à la fois lorsque la pression de la mémoire revient sans cesse. + Diagnostiquer les échecs du modèle + Un échec d’exécution de l’IA ne signifie pas toujours que l’image est mauvaise. Le fichier modèle est peut-être incomplet, non pris en charge, trop volumineux pour la mémoire ou ne correspond pas à l\'opération. Lorsque l\'application signale un échec de session, traitez-le comme un signal pour simplifier d\'abord le modèle et les paramètres. + Supprimez et retéléchargez un modèle s\'il échoue immédiatement après le téléchargement ou s\'il se comporte comme si le fichier était corrompu. + Essayez un modèle téléchargeable intégré avant de blâmer un modèle personnalisé importé, car les modèles importés peuvent avoir des entrées incompatibles. + Utilisez les journaux d\'application après avoir reproduit l\'échec si vous devez signaler un crash ou une erreur de session. + Expérimentez dans Shader Studio + Utilisez les effets de shader GPU pour un traitement d\'image avancé et des looks personnalisés + Travaillez soigneusement avec les shaders + Shader Studio est puissant, mais le code et les paramètres du shader nécessitent de petites modifications testables. Traitez-le comme un outil expérimental : conservez une base de référence fonctionnelle, modifiez une chose à la fois et testez avec différentes tailles d\'image avant de faire confiance à un préréglage. + Commencez par un shader fonctionnel connu ou un effet simple avant d\'ajouter des paramètres. + Modifiez un paramètre ou un bloc de code à la fois et vérifiez l\'aperçu pour détecter les erreurs. + Exportez les préréglages uniquement après les avoir testés sur quelques tailles d\'image différentes. + Créez des illustrations SVG ou ASCII + Générez des ressources de type vectoriel ou des images basées sur du texte pour une sortie créative + Choisissez le type de sortie de la création + Les outils SVG et ASCII servent différentes cibles : graphiques évolutifs ou rendu de style texte. Les deux sont des conversions créatives, il est donc plus important de prévisualiser à la taille finale que de juger le résultat à partir d’une petite vignette. + Utilisez SVG Maker lorsque le résultat doit être mis à l\'échelle proprement ou être réutilisé dans les flux de travail de conception. + Utilisez ASCII Art lorsque vous souhaitez une représentation textuelle stylisée d\'une image. + Prévisualisez à la taille finale car de petits détails peuvent disparaître dans la sortie générée. + Générer des textures de bruit + Créez des textures procédurales pour les arrière-plans, les masques, les superpositions ou les expériences + Ajuster le résultat procédural + La génération de bruit crée de nouvelles images à partir de paramètres, de sorte que de petits changements peuvent produire des résultats très différents. Il est utile pour les textures, les masques, les superpositions, les espaces réservés ou pour tester la compression avec des modèles détaillés. + Choisissez un type de bruit et une taille de sortie avant de régler les détails. + Ajustez l\'échelle, l\'intensité, les couleurs ou la graine jusqu\'à ce que le motif corresponde à l\'utilisation cible. + Enregistrez les résultats utiles et notez les paramètres si vous devez recréer la texture ultérieurement. + Projets de balisage + Utilisez les fichiers de projet lorsque les annotations doivent rester modifiables après la fermeture de l\'application + Gardez les modifications en couches réutilisables + Les fichiers de projet de balisage sont utiles lorsqu\'une capture d\'écran, un diagramme ou une image d\'instruction peut nécessiter des modifications ultérieurement. Les fichiers PNG/JPEG exportés sont des images finales, tandis que les fichiers de projet conservent les calques modifiables et l\'état d\'édition pour les ajustements futurs. + Utilisez les calques de balisage lorsque les annotations, les légendes ou les éléments de mise en page doivent rester modifiables. + Enregistrez ou rouvrez le fichier de projet avant d\'exporter une image finale si vous attendez d\'autres révisions. + Exportez une image normale uniquement lorsque vous êtes prêt à partager une version finale aplatie. + Chiffrer les fichiers + Protégez les fichiers avec un mot de passe et testez le décryptage avant de supprimer toute copie source + Gérez les sorties chiffrées avec soin + Les outils de chiffrement peuvent protéger les fichiers avec un cryptage par mot de passe, mais ils ne remplacent pas la mémorisation de la clé ou la conservation de sauvegardes. Le cryptage est utile pour le transport et le stockage, tandis que ZIP est préférable lorsque l\'objectif principal est de regrouper plusieurs sorties. + Chiffrez d’abord une copie et conservez l’original jusqu’à ce que vous ayez vérifié que le décryptage fonctionne avec le mot de passe que vous avez stocké. + Utilisez un gestionnaire de mots de passe ou un autre endroit sûr pour la clé ; l\'application ne peut pas deviner un mot de passe perdu pour vous. + Partagez des fichiers cryptés uniquement avec des personnes disposant également du mot de passe et sachant quel outil ou méthode doit les déchiffrer. + Organiser les outils + Outils de regroupement, de commande, de recherche et d\'épinglage pour que l\'écran principal corresponde à votre flux de travail réel + Rendre l\'écran principal plus rapide + Les paramètres de disposition des outils valent la peine d\'être ajustés une fois que vous savez quels outils vous utilisez le plus. Le regroupement facilite l\'exploration, l\'ordre personnalisé favorise la mémoire musculaire et les favoris permettent de garder les outils quotidiens accessibles même lorsque la liste complète devient longue. + Utilisez le mode groupé lors de l\'apprentissage de l\'application ou lorsque vous préférez des outils organisés par type de tâche. + Passez à l\'ordre personnalisé lorsque vous connaissez déjà vos outils fréquents et que vous souhaitez moins de parchemins. + Utilisez ensemble les paramètres de recherche et les favoris pour les outils rares dont vous vous souvenez par leur nom mais que vous ne souhaitez pas voir à tout moment. + Gérer des fichiers volumineux + Évitez les exportations lentes, la pression sur la mémoire et les résultats inattendus + Réduire le travail avant l’exportation + Les images très volumineuses, les lots longs et les formats de haute qualité peuvent nécessiter plus de mémoire et de temps. La solution la plus rapide consiste généralement à réduire la quantité de travail avant l’opération la plus lourde, sans attendre la fin de la même entrée surdimensionnée. + Redimensionnez des images volumineuses avant d\'appliquer des filtres lourds, une OCR ou une conversion PDF. + Utilisez d’abord un lot plus petit pour confirmer les paramètres, puis traitez le reste avec le même préréglage. + Qualité inférieure ou changement de format lorsque le fichier de sortie est plus volumineux que prévu. + Cache et aperçus + Comprendre les aperçus générés, le nettoyage du cache et l\'utilisation du stockage après des sessions intensives + Gardez le stockage et la vitesse équilibrés + La génération d\'aperçus et le cache peuvent accélérer la navigation répétée, mais des sessions d\'édition lourdes peuvent laisser des données temporaires derrière elles. Les paramètres de cache sont utiles lorsque l\'application semble lente, que le stockage est limité ou que les aperçus semblent obsolètes après de nombreuses expériences. + Il est plus important de garder les aperçus activés lorsque vous parcourez de nombreuses images que de minimiser le stockage temporaire. + Videz le cache après des lots volumineux, des expériences infructueuses ou de longues sessions avec de nombreux fichiers haute résolution. + Utilisez la suppression automatique du cache si vous préférez nettoyer le stockage plutôt que de garder les aperçus au chaud entre les lancements. + Ajuster le comportement de l\'écran + Utilisez les options plein écran, mode sécurisé, luminosité et barre système pour un travail ciblé + Adaptez l\'interface à la situation + Les paramètres de l\'écran sont utiles lorsque vous modifiez en mode paysage, présentez des images privées ou avez besoin d\'une luminosité constante. Ils ne sont pas nécessaires pour une utilisation normale, mais ils résolvent des situations délicates telles que l\'inspection QR, les aperçus privés ou l\'édition de paysages exigus. + Utilisez les paramètres du plein écran ou de la barre système lorsque l\'édition de l\'espace est plus importante que le chrome de navigation. + Utilisez le mode sécurisé lorsque les images privées ne doivent pas apparaître dans les captures d\'écran ou les aperçus d\'applications. + Utilisez l’application de la luminosité pour les outils où les images sombres ou les codes QR sont difficiles à inspecter. + Gardez la transparence + Empêcher les arrière-plans transparents de devenir blancs, noirs ou aplatis + Utiliser une sortie compatible alpha + La transparence ne survit que lorsque l\'outil sélectionné et le format de sortie prennent en charge l\'alpha. Si un arrière-plan exporté devient blanc ou noir, le problème vient généralement du format de sortie, d\'un paramètre de remplissage/arrière-plan ou d\'une application de destination qui a aplati l\'image. + Choisissez PNG ou WebP lors de l’exportation d’images, d’autocollants, de logos ou de superpositions supprimés en arrière-plan. + Évitez JPEG pour une sortie transparente car il ne stocke pas de canal alpha. + Vérifiez les paramètres liés à la couleur d\'arrière-plan pour les formats alpha si l\'aperçu affiche un arrière-plan rempli. + Résoudre les problèmes de partage et d\'importation + Utiliser les paramètres du sélecteur et les formats pris en charge lorsque les fichiers n\'apparaissent pas ou ne s\'ouvrent pas correctement + Obtenez le fichier dans l\'application + Les problèmes d\'importation sont souvent causés par les autorisations du fournisseur, des formats non pris en charge ou le comportement du sélecteur. Les fichiers partagés à partir d\'applications cloud et de messageries peuvent être temporaires, donc le choix d\'une source persistante peut être plus fiable pour des modifications plus longues. + Essayez d\'ouvrir le fichier via le sélecteur système au lieu d\'un raccourci de fichiers récents. + Si un format n\'est pas pris en charge par un outil, convertissez-le d\'abord ou ouvrez un outil plus spécifique. + Examinez les paramètres du sélecteur d’images et du lanceur si les flux de partage ou l’ouverture directe de l’outil se comportent différemment que prévu. + Confirmation de sortie + Évitez de perdre les modifications non enregistrées lorsque des gestes, des pressions arrière ou des changements d\'outil se produisent accidentellement + Protéger le travail inachevé + La confirmation de sortie de l\'outil est utile lorsque vous utilisez la navigation gestuelle, modifiez des fichiers volumineux ou passez souvent d\'une application à l\'autre. Il ajoute une petite pause avant de quitter un outil afin que les paramètres et aperçus inachevés ne soient pas perdus par accident. + Activez la confirmation si des gestes de retour accidentels ont déjà fermé un outil avant l\'exportation. + Gardez-le désactivé si vous effectuez principalement des conversions rapides en une étape et préférez une navigation plus rapide. + Utilisez-le avec des préréglages pour des flux de travail plus longs où la reconstruction des paramètres serait ennuyeuse. + Utiliser les journaux pour signaler des problèmes + Collectez des détails utiles avant d\'ouvrir un ticket ou de contacter le développeur + Signaler des problèmes avec le contexte + Un rapport clair avec les étapes, la version de l\'application, le type d\'entrée et les journaux est beaucoup plus facile à diagnostiquer. Les journaux sont plus utiles juste après avoir reproduit un problème, car ils maintiennent le contexte environnant à jour. + Notez le nom de l\'outil, l\'action exacte et si cela se produit avec un fichier ou avec chaque fichier. + Ouvrez les journaux d\'application après avoir reproduit le problème et partagez la sortie du journal pertinente lorsque cela est possible. + Joignez des captures d\'écran ou des exemples de fichiers uniquement lorsqu\'ils ne contiennent pas d\'informations privées. + Le traitement en arrière-plan a été arrêté + Android n\'a pas laissé la notification de traitement en arrière-plan démarrer à temps. Il s’agit d’une limitation de synchronisation du système qui ne peut pas être corrigée de manière fiable. Redémarrez l\'application et réessayez ; garder l\'application ouverte et autoriser les notifications ou le travail en arrière-plan peut être utile. + Ignorer la sélection de fichiers + Ouvrez les outils plus rapidement lorsque la saisie provient généralement d\'un partage, du presse-papiers ou d\'un écran précédent. + Rendre les lancements répétés d\'outils plus rapides + Ignorer la sélection de fichiers modifie la première étape des outils pris en charge. C\'est utile lorsque vous entrez habituellement un outil avec une image déjà sélectionnée ou à partir d\'actions de partage Android, mais cela peut sembler déroutant si vous vous attendez à ce que chaque outil demande un fichier immédiatement. + Activez-le uniquement lorsque votre flux habituel fournit déjà l\'image source avant l\'ouverture de l\'outil. + Gardez-le désactivé pendant l\'apprentissage de l\'application, car la sélection explicite facilite la compréhension de chaque flux d\'outils. + Si un outil s\'ouvre sans entrée évidente, désactivez ce paramètre ou démarrez à partir de Partager/Ouvrir avec pour qu\'Android transmette le fichier en premier. + Ouvrir d\'abord l\'éditeur + Ignorer l\'aperçu lorsqu\'une image partagée doit être directement modifiée + Choisissez Aperçu ou Modifier comme premier arrêt + Ouvrir la modification au lieu de l\'aperçu est destiné aux utilisateurs qui savent déjà qu\'ils souhaitent modifier l\'image. L\'aperçu est plus sûr lorsque vous inspectez des fichiers à partir d\'un chat ou d\'un stockage cloud ; la modification directe est plus rapide pour les captures d\'écran, les recadrages rapides, les annotations et les corrections ponctuelles. + Activez la modification directe si la plupart des images partagées nécessitent immédiatement des modifications de recadrage, de balisage, de filtres ou d\'exportation. + Laissez d\'abord l\'aperçu lorsque vous inspectez souvent les métadonnées, les dimensions, la transparence ou la qualité de l\'image avant de prendre une décision. + Utilisez-le avec vos favoris pour que les images partagées se rapprochent des outils que vous utilisez réellement. + Saisie de texte prédéfinie + Entrez des valeurs prédéfinies exactes au lieu d\'ajuster les commandes à plusieurs reprises + Utilisez des valeurs précises lorsque les curseurs sont lents + La saisie de texte prédéfinie est utile lorsque vous avez besoin de chiffres exacts pour un travail répété : tailles d\'images sociales, marges de documents, cibles de compression, largeurs de ligne ou autres valeurs difficiles à atteindre avec des gestes. Il est également utile sur les petits écrans où les mouvements fins du curseur sont plus difficiles. + Activez la saisie de texte si vous réutilisez souvent des tailles, des pourcentages, des valeurs de qualité ou des paramètres d\'outils exacts. + Enregistrez la valeur en tant que préréglage après l\'avoir saisie une fois, puis réutilisez-la au lieu de reconstruire la même configuration. + Conservez les commandes gestuelles pour un réglage visuel approximatif et les valeurs saisies pour des exigences de sortie strictes. + Enregistrer près de l\'original + Placez les fichiers générés à côté des images sources lorsque le contexte du dossier est important + Conserver les sorties avec leurs sources + Enregistrer dans le dossier d\'origine est pratique pour les dossiers de caméra, les dossiers de projets, les numérisations de documents et les lots de clients où la copie modifiée doit rester à côté de la source. Il peut être moins ordonné qu\'un dossier de sortie dédié, alors combinez-le avec des suffixes ou des modèles de nom de fichier clairs. + Activez-le lorsque chaque dossier source est déjà significatif et que les sorties doivent y rester regroupées. + Utilisez des suffixes de nom de fichier, des préfixes, des horodatages ou des modèles afin que les copies modifiées soient faciles à séparer des originaux. + Désactivez-le pour les lots mixtes lorsque vous souhaitez que tous les fichiers générés soient collectés dans un seul dossier d\'exportation. + Ignorer une production plus importante + Évitez d\'enregistrer les conversions qui deviennent inopinément plus lourdes que la source + Protégez les lots des surprises de mauvaise taille + Certaines conversions agrandissent les fichiers, en particulier les captures d\'écran PNG, les photos déjà compressées, les WebP de haute qualité ou les images dont les métadonnées sont préservées. Autoriser sauter si plus grand empêche ces sorties de remplacer une source plus petite dans les flux de travail où la réduction de la taille est l’objectif principal. + Activez-le lorsque l\'exportation est destinée à compresser, redimensionner ou préparer des fichiers aux limites de téléchargement. + Examinez les fichiers ignorés après un lot ; ils peuvent déjà être optimisés ou nécessiter un format différent. + Désactivez-le lorsque la qualité, la transparence ou la compatibilité des formats comptent plus que la taille finale du fichier. + Presse-papiers et liens + Contrôlez la saisie automatique du presse-papiers et les aperçus des liens pour des outils textuels plus rapides + Rendre la saisie automatique prévisible + Les paramètres du presse-papiers et des liens sont pratiques pour les flux de travail QR, Base64, URL et contenant beaucoup de texte, mais la saisie automatique doit rester intentionnelle. Si l\'application semble remplir les champs toute seule, vérifiez le comportement de collage du presse-papiers ; si les cartes de lien semblent bruyantes ou lentes, ajustez le comportement de l\'aperçu des liens. + Autorisez le collage automatique dans le presse-papiers lorsque vous copiez souvent du texte et ouvrez immédiatement un outil de correspondance. + Désactivez le collage automatique si le contenu privé du presse-papiers apparaît dans les outils là où vous ne l\'attendiez pas. + Désactivez les aperçus des liens lorsque la récupération des aperçus est lente, gênante ou inutile pour votre flux de travail. + Commandes compactes + Utilisez des sélecteurs plus denses et un placement rapide des paramètres lorsque l\'espace sur l\'écran est restreint + Installez plus de commandes sur les petits écrans + Des sélecteurs compacts et un placement rapide des paramètres aident sur les petits téléphones, l\'édition de paysage et les outils avec de nombreux paramètres. Le compromis est que les contrôles peuvent sembler plus denses, c\'est donc mieux après avoir déjà reconnu les options courantes. + Activez les sélecteurs compacts lorsque les boîtes de dialogue ou les listes d\'options semblent trop hautes pour l\'écran. + Ajustez le côté des paramètres rapides si les commandes de l\'outil sont plus faciles à atteindre d\'un côté de l\'écran. + Revenez à des commandes plus spacieuses si vous êtes encore en train d\'apprendre l\'application ou si vous manquez fréquemment des options denses. + Charger des images depuis le Web + Collez l\'URL d\'une page ou d\'une image, prévisualisez les images analysées, puis enregistrez ou partagez les résultats sélectionnés + Utiliser délibérément des images Web + Web Image Loading analyse les sources d\'images à partir de l\'URL fournie, prévisualise la première image disponible et vous permet d\'enregistrer ou de partager les images analysées sélectionnées. Certains sites utilisent des liens temporaires, protégés ou générés par un script, de sorte qu\'une page qui fonctionne dans un navigateur peut toujours ne renvoyer aucune image utilisable. + Collez une URL d\'image directe ou une URL de page et attendez que la liste d\'images analysées apparaisse. + Utilisez les contrôles de cadre ou de sélection lorsque la page contient plusieurs images et que vous n’en avez besoin que de certaines. + Si rien ne se charge, essayez d\'abord un lien d\'image direct ou téléchargez-le via le navigateur ; le site peut bloquer l\'analyse ou utiliser des liens privés. + Choisissez le type de redimensionnement + Comprendre Explicite, Flexible, Recadrer et Ajuster avant de forcer une image dans de nouvelles dimensions + Contrôler la forme, le canevas et les proportions + Le type de redimensionnement décide de ce qui se passe lorsque la largeur et la hauteur demandées ne correspondent pas au rapport hauteur/largeur d\'origine. C\'est la différence entre étirer les pixels, préserver les proportions, recadrer une zone supplémentaire ou ajouter un canevas d\'arrière-plan. + Utilisez Explicite uniquement lorsque la distorsion est acceptable ou que la source a déjà le même rapport hauteur/largeur que la cible. + Utilisez Flexible pour conserver les proportions en redimensionnant du côté important, en particulier pour les photos et les lots mixtes. + Utilisez Recadrer pour des cadres stricts sans bordures, ou Ajuster lorsque l\'image entière doit rester visible à l\'intérieur d\'un canevas fixe. + Choisir le mode d\'échelle de l\'image + Choisissez le filtre de rééchantillonnage qui contrôle la netteté, la douceur, la vitesse et les artefacts de contour + Redimensionner sans douceur accidentelle + Le mode échelle d\'image contrôle la manière dont les nouveaux pixels sont calculés lors du redimensionnement. Les modes simples sont rapides et prévisibles, tandis que les filtres avancés peuvent mieux préserver les détails mais peuvent accentuer les contours, créer des halos ou prendre plus de temps sur les grandes images. + Utilisez Basic ou Bilinéaire pour un redimensionnement quotidien rapide lorsque le résultat doit seulement être plus petit et propre. + Utilisez les modes de style Lanczos, Robidoux, Spline ou EWA lorsque des détails fins, du texte ou une réduction d\'échelle de haute qualité sont importants. + Utilisez le plus proche pour le pixel art, les icônes et les graphiques aux contours nets où des pixels flous gâcheraient l\'apparence. + Espace colorimétrique d’échelle + Décidez comment les couleurs sont mélangées pendant que les pixels sont rééchantillonnés lors du redimensionnement + Gardez les dégradés et les bords naturels + L’espace colorimétrique d’échelle modifie les calculs utilisés lors du redimensionnement. La plupart des images conviennent aux valeurs par défaut, mais les espaces linéaires ou perceptuels peuvent rendre les dégradés, les bords sombres et les couleurs saturées plus propres après une forte mise à l\'échelle. + Laissez la valeur par défaut lorsque la vitesse et la compatibilité comptent plus que de minuscules différences de couleur. + Essayez les modes linéaire ou perceptuel lorsque les contours sombres, les captures d\'écran de l\'interface utilisateur, les dégradés ou les bandes de couleurs ne semblent pas corrects après le redimensionnement. + Comparez avant et après sur l\'écran cible réel, car les changements d\'espace colorimétrique peuvent être subtils et dépendants du contenu. + Limiter les modes de redimensionnement + Sachez quand les images dépassant les limites doivent être ignorées, recodées ou réduites pour s\'adapter + Choisissez ce qui se passe à la limite + Limit Resize n\'est pas seulement un outil pour des images plus petites. Son mode décide de ce que fait l\'application lorsqu\'une source est déjà dans vos limites ou lorsqu\'un seul côté enfreint la règle, ce qui est important pour les dossiers mixtes et la préparation du téléchargement. + Utilisez Ignorer lorsque les images situées à l’intérieur des limites doivent rester intactes et ne plus être exportées. + Utilisez Recode lorsque les dimensions sont acceptables mais que vous avez toujours besoin d\'un nouveau format, d\'un nouveau comportement de métadonnées, d\'une qualité ou d\'un nouveau nom de fichier. + Utilisez Zoom lorsque les images qui dépassent les limites doivent être réduites proportionnellement jusqu\'à ce qu\'elles correspondent à la zone autorisée. + Objectifs de rapport hauteur/largeur + Évitez les faces étirées, les bords coupés et les bordures inattendues dans des tailles de sortie strictes + Faites correspondre la forme avant de redimensionner + La largeur et la hauteur ne représentent que la moitié d\'un objectif de redimensionnement. Le rapport hauteur/largeur décide si l\'image peut s\'adapter naturellement, doit être recadrée ou a besoin d\'une toile autour d\'elle. Vérifier d\'abord la forme évite les résultats de redimensionnement les plus surprenants. + Comparez la forme source avec la forme cible avant de choisir Explicite, Recadrer ou Ajuster. + Recadrez d\'abord lorsque le sujet peut perdre un arrière-plan supplémentaire et que la destination a besoin d\'un cadre strict. + Utilisez Ajuster ou Flexible lorsque chaque partie de l\'image originale doit rester visible. + Redimensionnement haut de gamme ou normal + Savoir quand l\'ajout de pixels nécessite l\'IA et quand une simple mise à l\'échelle suffit + Ne confondez pas taille et détail + Le redimensionnement normal modifie les dimensions en interpolant les pixels ; il ne peut pas inventer de vrais détails. L\'IA haut de gamme peut créer des détails plus nets, mais elle est plus lente et peut halluciner la texture, le bon choix dépend donc de si vous avez besoin de compatibilité, de vitesse ou de restauration visuelle. + Utilisez le redimensionnement normal pour les miniatures, les limites de téléchargement, les captures d\'écran et les modifications simples des dimensions. + Utilisez l\'IA haut de gamme lorsqu\'une image petite ou douce doit être plus belle dans une taille plus grande. + Comparez les visages, le texte et les motifs après la mise à niveau de l\'IA, car les détails générés peuvent sembler convaincants mais incorrects. + L’ordre des filtres est important + Appliquez le nettoyage, la couleur, la netteté et la compression finale dans une séquence intentionnelle + Construire une pile de filtres plus propre + Les filtres ne sont pas toujours interchangeables. Un flou avant la netteté, une augmentation du contraste avant l\'OCR ou un changement de couleur avant la compression peuvent produire un résultat très différent à partir des mêmes commandes. + Recadrez et faites d\'abord pivoter afin que les filtres ultérieurs ne fonctionnent que sur les pixels qui resteront. + Appliquez l’exposition, la balance des blancs et le contraste avant la netteté ou les effets stylisés. + Conservez le redimensionnement et la compression finaux vers la fin afin que les détails prévisualisés survivent à l\'exportation de manière prévisible. + Corriger les bords de l\'arrière-plan + Nettoyez les halos, les ombres restantes, les cheveux, les trous et les contours adoucis après la suppression automatique + Donner aux découpes un aspect intentionnel + Les masques automatiques semblent souvent esthétiques au premier coup d\'œil, mais révèlent des problèmes sur des arrière-plans clairs, sombres ou à motifs. Le nettoyage des bords fait la différence entre une découpe rapide et un autocollant, un logo ou une image de produit réutilisable. + Prévisualisez le résultat sur des arrière-plans contrastés pour révéler les halos et les détails des bords manquants. + Utilisez la restauration pour les cheveux, les coins et les objets transparents avant d\'effacer les restes environnants. + Exportez un petit test sur la couleur de fond finale lorsque la découpe sera placée dans un dessin. + Filigranes lisibles + Rendre les marques visibles sur les images claires, sombres, chargées et recadrées sans gâcher la photo + Protéger l\'image sans la cacher + Un filigrane qui semble bon sur une image peut disparaître sur une autre. La taille, l\'opacité, le contraste, le placement et la répétition doivent être choisis pour l\'ensemble du lot, et pas seulement pour le premier aperçu. + Testez la marque sur les images les plus claires et les plus sombres du lot avant d\'exporter tous les fichiers. + Utilisez une opacité plus faible pour les grandes marques répétées et un contraste plus fort pour les petites marques de coin. + Gardez les visages, le texte et les détails importants du produit clairs, à moins que la marque ne soit destinée à empêcher la réutilisation. + Diviser une image en tuiles + Coupez une seule image par lignes, colonnes, pourcentages personnalisés, format et qualité + Préparer les grilles et les pièces commandées + Image Splitting crée plusieurs sorties à partir d’une image source. Il peut diviser par nombre de lignes et de colonnes, ajuster les pourcentages de lignes ou de colonnes et enregistrer des éléments avec le format et la qualité de sortie sélectionnés, ce qui est utile pour les grilles, les tranches de page, les panoramas et les publications sociales ordonnées. + Choisissez l\'image source, puis définissez le nombre de lignes et de colonnes dont vous avez besoin. + Ajustez les pourcentages de lignes ou de colonnes lorsque les pièces ne doivent pas toutes être de même taille. + Choisissez le format et la qualité de sortie avant de sauvegarder afin que chaque pièce générée utilise les mêmes règles d\'exportation. + Découper des bandes d\'images + Supprimez les régions verticales ou horizontales et fusionnez les parties restantes ensemble + Supprimer une région sans laisser d\'espace + La découpe d\'image est différente du recadrage normal. Il peut supprimer une bande verticale ou horizontale sélectionnée, puis fusionner les parties d\'image restantes. Le mode inverse conserve la région sélectionnée à la place, et l\'utilisation des deux directions inverses conserve le rectangle sélectionné. + Utilisez la coupe verticale pour les régions latérales, les colonnes ou les bandes médianes ; utilisez la coupe horizontale pour les bannières, les espaces ou les rangées. + Activez l\'inverse sur un axe lorsque vous souhaitez conserver la pièce sélectionnée au lieu de la supprimer. + Prévisualisez les bords après la découpe, car le contenu fusionné peut paraître abrupt lorsque la zone supprimée traverse des détails importants. + Choisissez le format de sortie + Choisissez JPEG, PNG, WebP, AVIF, JXL ou PDF en fonction du contenu et de la destination. + Laissez la destination décider du format + Le meilleur format dépend de ce que contient l’image et de l’endroit où elle sera utilisée. Les photos, captures d\'écran, ressources transparentes, documents et fichiers animés échouent tous de différentes manières lorsqu\'ils sont forcés dans le mauvais format. + Utilisez JPEG pour une large compatibilité photo lorsque la transparence et les pixels exacts ne sont pas requis. + Utilisez PNG pour des captures d’écran nettes, des captures d’interface utilisateur, des graphiques transparents et des modifications sans perte. + Utilisez WebP, AVIF ou JXL lorsque la sortie moderne et compacte est importante et que l\'application de réception la prend en charge. + Extraire les couvertures audio + Enregistrez les pochettes d\'album intégrées ou les images multimédias disponibles à partir de fichiers audio sous forme d\'images PNG + Extraire des illustrations de fichiers audio + Audio Covers utilise les métadonnées multimédias Android pour lire les illustrations intégrées à partir de fichiers audio. Lorsque les illustrations intégrées ne sont pas disponibles, le récupérateur peut revenir à un cadre multimédia si Android peut en fournir un ; si ni l’un ni l’autre n’existe, l’outil signale qu’il n’y a aucune image à extraire. + Ouvrez les couvertures audio et sélectionnez un ou plusieurs fichiers audio avec la pochette attendue. + Examinez la couverture extraite avant de l\'enregistrer ou de la partager, en particulier pour les fichiers provenant d\'applications de streaming ou d\'enregistrement. + Si aucune image n\'est trouvée, le fichier audio n\'a probablement pas de couverture intégrée ou Android n\'a pas pu exposer un cadre utilisable. + Métadonnées de dates et de localisation + Décidez ce que vous souhaitez conserver lorsque le temps de capture est important mais que les données du GPS ou de l\'appareil ne doivent pas fuir. + Séparez les métadonnées utiles des métadonnées privées + Les métadonnées ne sont pas toutes également risquées. Les dates de capture peuvent être utiles pour les archives et le tri, tandis que le GPS, les champs de type série d\'appareil, les balises logicielles ou les champs de propriétaire peuvent révéler plus que prévu. + Conservez les balises de date et d\'heure lorsque l\'ordre chronologique est important pour les albums, les numérisations ou l\'historique du projet. + Supprimez le GPS et les balises d\'identification de l\'appareil avant de partager publiquement ou d\'envoyer des images à des inconnus. + Exportez un échantillon et inspectez à nouveau EXIF ​​lorsque les exigences de confidentialité sont strictes. + Évitez les collisions de noms de fichiers + Protégez les sorties par lots lorsque de nombreux fichiers partagent des noms tels que image.jpg ou capture d\'écran.png + Rendre chaque nom de sortie unique + Les noms de fichiers en double sont courants lorsque les images proviennent de messageries, de captures d\'écran, de dossiers cloud ou de plusieurs caméras. Un bon modèle évite tout remplacement accidentel et permet de retracer les sorties jusqu\'à leurs sources. + Incluez le nom original plus un suffixe lorsque la copie modifiée doit être reconnaissable. + Ajoutez une séquence, un horodatage, un préréglage ou des dimensions lors du traitement de grands lots mixtes. + Évitez les workflows d\'écrasement jusqu\'à ce que vous ayez vérifié que le modèle de dénomination ne peut pas entrer en collision. + Enregistrer ou partager + Choisissez entre un fichier persistant et un transfert temporaire vers une autre application + Exporter avec la bonne intention + Enregistrer et partager peuvent sembler similaires, mais ils résolvent des problèmes différents. L\'enregistrement crée un fichier que vous pourrez retrouver plus tard ; le partage envoie un résultat généré via Android vers une autre application et peut ne pas laisser de copie locale durable. + Utilisez Enregistrer lorsque la sortie doit rester dans un dossier connu, être sauvegardée ou être réutilisée ultérieurement. + Utilisez Partager pour des messages rapides, des pièces jointes à des e-mails, des publications sur les réseaux sociaux ou pour envoyer le résultat à un autre éditeur. + Enregistrez d\'abord lorsque l\'application de réception n\'est pas fiable ou lorsqu\'un partage ayant échoué serait ennuyeux à recréer. + Taille et marges de la page PDF + Choisissez le format du papier, le comportement d\'ajustement et la marge de manœuvre avant de créer un document + Rendre les pages d\'images imprimables et lisibles + Les images peuvent devenir des pages PDF peu pratiques lorsque leur forme ne correspond pas au format de papier choisi. Les marges, le mode d\'ajustement et l\'orientation de la page déterminent si le contenu est recadré, minuscule, étiré ou confortable à lire. + Utilisez un format de papier réel lorsque le PDF peut être imprimé ou soumis à un formulaire. + Utilisez des marges pour les numérisations et les captures d\'écran afin que le texte ne repose pas contre le bord de la page. + Prévisualisez les pages portrait et paysage ensemble lorsque les images source ont une orientation mixte. + Nettoyer les analyses + Améliorez les bords du papier, les ombres, le contraste, la rotation et la lisibilité avant le format PDF ou OCR. + Préparer les pages avant de les archiver + Un scan peut être techniquement capturé mais reste difficile à lire. De petites étapes de nettoyage avant l\'exportation au format PDF comptent souvent plus que les paramètres de compression, en particulier pour les reçus, les notes manuscrites et les pages peu éclairées. + Corrigez la perspective et supprimez les bords du tableau, les doigts, les ombres et l\'encombrement de l\'arrière-plan. + Augmentez soigneusement le contraste pour que le texte devienne plus clair sans détruire les tampons, les signatures ou les marques de crayon. + Exécutez l\'OCR uniquement une fois que la page est droite et lisible, puis vérifiez manuellement les numéros importants. + Compresser, mettre en niveaux de gris ou réparer des PDF + Utilisez les opérations PDF sur un seul fichier pour des copies de documents plus légères, imprimables ou reconstruites + Choisissez l\'opération de nettoyage PDF + PDF Tools comprend des opérations distinctes pour la compression, la conversion en niveaux de gris et la réparation. La compression et les niveaux de gris fonctionnent principalement via les images intégrées, tandis que la réparation reconstruit la structure du PDF ; aucun de ces éléments ne doit être traité comme une solution garantie pour chaque document. + Utilisez Compress PDF lorsque le document contient des images et que la taille du fichier est importante pour le partage. + Utilisez Niveaux de gris lorsque le document doit être plus facile à imprimer ou ne nécessite pas d\'images couleur. + Utilisez Réparer le PDF sur une copie lorsqu\'un document ne s\'ouvre pas ou a une structure interne endommagée, puis vérifiez le fichier reconstruit. + Extraire des images à partir de PDF + Récupérez les images PDF intégrées et regroupez les résultats extraits dans une archive + Enregistrer les images sources des documents + Extract Images analyse le PDF à la recherche d\'objets image intégrés, ignore les doublons lorsque cela est possible et stocke les images récupérées dans une archive ZIP générée. Il extrait uniquement les images réellement intégrées dans le PDF, pas le texte, les dessins vectoriels ou chaque page visible sous forme de capture d\'écran. + Utilisez Extraire des images lorsque vous avez besoin d\'images stockées dans un PDF, telles que des photos d\'un catalogue ou des ressources numérisées. + Utilisez plutôt PDF vers images ou extraction de pages lorsque vous avez besoin de pages complètes, y compris le texte et la mise en page. + Si l\'outil ne signale aucune image intégrée, la page peut contenir du texte/vecteur ou les images peuvent ne pas être stockées en tant qu\'objets extractibles. + Métadonnées, aplatissement et sortie d\'impression + Modifiez les propriétés du document, rastérisez les pages ou préparez intentionnellement des mises en page d\'impression personnalisées. + Choisissez l\'action du document final + Les métadonnées PDF, l\'aplatissement et la préparation à l\'impression résolvent différents problèmes de la phase finale. Les métadonnées contrôlent les propriétés du document, Flatten PDF rastérise les pages en une sortie moins modifiable et Print PDF prépare une nouvelle mise en page avec des contrôles de taille de page, d\'orientation, de marge et de pages par feuille. + Utilisez les métadonnées lorsque l\'auteur, le titre, les mots-clés, le producteur ou le nettoyage de la confidentialité sont importants. + Utilisez Flatten PDF lorsque le contenu de la page visible devrait devenir plus difficile à modifier en tant qu\'objets PDF distincts. + Utilisez Imprimer PDF lorsque vous avez besoin d\'un format de page, d\'une orientation, de marges ou de plusieurs pages par feuille personnalisées. + Préparer les images pour l\'OCR + Utilisez le recadrage, la rotation, le contraste, le débruitage et l\'échelle avant de demander à l\'OCR de lire le texte + Donner des pixels plus propres à l\'OCR + Les erreurs OCR proviennent souvent de l’image d’entrée plutôt que du moteur OCR. Les pages tordues, le faible contraste, le flou, les ombres, le texte minuscule et les arrière-plans mixtes réduisent tous la qualité de la reconnaissance. + Recadrez la zone de texte et faites pivoter l\'image pour que les lignes soient horizontales avant la reconnaissance. + Augmentez le contraste ou convertissez en noir et blanc plus propre uniquement lorsque cela facilite la lecture des lettres. + Redimensionnez modérément le petit texte vers le haut, mais évitez une netteté agressive qui crée de faux bords. + Vérifier le contenu QR + Examinez les liens, les informations d\'identification Wi-Fi, les contacts et les actions avant de faire confiance à un code + Traitez le texte décodé comme une entrée non fiable + Un code QR peut masquer une URL, une fiche de contact, un mot de passe Wi-Fi, un événement de calendrier, un numéro de téléphone ou du texte brut. L\'étiquette visible à côté du code peut ne pas correspondre à la charge utile réelle, alors examinez le contenu décodé avant d\'agir dessus. + Inspectez l’URL entièrement décodée avant de l’ouvrir, en particulier les domaines raccourcis ou inconnus. + Soyez prudent avec les codes Wi-Fi, de contact, d\'agenda, de téléphone et SMS car ils peuvent déclencher des actions sensibles. + Copiez d\'abord le contenu lorsque vous devez le vérifier ailleurs au lieu de l\'ouvrir immédiatement. + Générer des codes QR + Créez des codes à partir de texte brut, d\'URL, de Wi-Fi, de contacts, d\'e-mails, de téléphone, de SMS et de données de localisation + Créez la bonne charge utile QR + L\'écran QR peut générer des codes à partir du contenu saisi ainsi que scanner ceux existants. Les types de QR structurés permettent d\'encoder les données dans un format compris par d\'autres applications, comme les informations de connexion Wi-Fi, les cartes de contact, les champs de courrier électronique, les numéros de téléphone, le contenu SMS, les URL ou les coordonnées géographiques. + Choisissez du texte brut pour des notes simples ou utilisez un type structuré lorsque le code doit s\'ouvrir sous forme de données Wi-Fi, contact, URL, e-mail, téléphone, SMS ou localisation. + Vérifiez chaque champ avant de partager le code généré, car l\'aperçu visible ne reflète que la charge utile que vous avez saisie. + Testez le code QR enregistré avec un scanner lorsqu\'il sera imprimé, publié publiquement ou utilisé par d\'autres personnes. + Choisissez un mode de somme de contrôle + Calculez les hachages de fichiers ou de texte, comparez un fichier ou vérifiez par lots plusieurs fichiers + Vérifier le contenu, pas l\'apparence + Checksum Tools comporte des onglets séparés pour le hachage de fichiers, le hachage de texte, la comparaison d\'un fichier avec un hachage connu et la comparaison par lots. Une somme de contrôle prouve l\'identité octet par octet pour l\'algorithme sélectionné ; cela ne prouve pas que deux images se ressemblent simplement. + Utilisez Calculer pour les fichiers et Text Hash pour les données texte saisies ou collées. + Utilisez Compare lorsque quelqu\'un vous donne une somme de contrôle attendue pour un fichier. + Utilisez Batch Compare lorsque de nombreux fichiers nécessitent des hachages ou une vérification en un seul passage, puis gardez l\'algorithme sélectionné cohérent. + Confidentialité du presse-papiers + Utilisez les fonctionnalités automatiques du presse-papiers sans exposer accidentellement les données privées copiées + Conserver le contenu copié intentionnellement + L\'automatisation du Presse-papiers est pratique, mais le texte copié peut contenir des mots de passe, des liens, des adresses, des jetons ou des notes privées. Considérez la saisie basée sur le presse-papiers comme une fonctionnalité rapide qui doit correspondre à vos habitudes et à vos attentes en matière de confidentialité. + Désactivez l\'utilisation automatique du presse-papiers si du texte privé apparaît de manière inattendue dans les outils. + Utilisez la sauvegarde dans le Presse-papiers uniquement lorsque vous souhaitez délibérément que le résultat évite le stockage. + Effacez ou remplacez le presse-papiers après avoir traité du texte sensible, des données QR ou des charges utiles Base64. + Formats de couleur + Comprendre les valeurs de couleur HEX, RVB, HSV, alpha et copiées avant de les coller ailleurs + Copiez le format attendu par la cible + La même couleur visible peut être écrite de plusieurs manières. Un outil de conception, un champ CSS, une valeur de couleur Android ou un éditeur d\'images peuvent s\'attendre à un ordre, une gestion alpha ou un modèle de couleur différent. + Utilisez HEX lors du collage dans des champs Web, de conception ou de thème qui attendent des codes de couleur compacts. + Utilisez RVB ou HSV lorsque vous devez régler manuellement les canaux, la luminosité, la saturation ou la teinte. + Vérifiez alpha séparément lorsque la transparence est importante, car certaines destinations l\'ignorent ou utilisent un ordre différent. + Parcourez la bibliothèque de couleurs + Recherchez les couleurs nommées par nom ou HEX et conservez les favoris en haut + Trouver des couleurs nommées réutilisables + La bibliothèque de couleurs charge une collection de couleurs nommée, la trie par nom, filtre par nom de couleur ou valeur HEX et stocke les couleurs préférées afin que les entrées importantes restent plus faciles à atteindre. C\'est utile lorsque vous avez besoin d\'une vraie couleur nommée au lieu d\'échantillonner à partir d\'une image. + Recherchez par nom de couleur lorsque vous connaissez la famille, comme le rouge, le bleu, l\'ardoise ou la menthe. + Recherchez par HEX lorsque vous avez déjà une couleur numérique et que vous souhaitez trouver des entrées nommées correspondantes ou à proximité. + Marquez les couleurs fréquentes comme favorites afin qu’elles passent devant la bibliothèque générale lors de la navigation. + Utiliser des collections de dégradés de maillage + Téléchargez les ressources de dégradé de maillage disponibles et partagez les images sélectionnées + Utiliser des actifs de dégradé de maillage distants + Mesh Gradients peut charger une collection de ressources distantes, télécharger des images de dégradés manquantes, afficher la progression du téléchargement et partager les dégradés sélectionnés. La disponibilité dépend de l\'accès au réseau et du magasin de ressources distant, donc une liste vide signifie généralement que les ressources n\'étaient pas encore disponibles. + Ouvrez Dégradés de maillage à partir des outils de dégradé lorsque vous souhaitez des images de dégradé de maillage prêtes à l\'emploi. + Attendez le chargement des ressources ou la progression du téléchargement avant de supposer que la collection est vide. + Sélectionnez et partagez les dégradés dont vous avez besoin, ou revenez à Gradient Maker lorsque vous souhaitez créer manuellement un dégradé personnalisé. + Résolution des actifs générés + Choisissez la taille de sortie avant de générer des dégradés, du bruit, des fonds d\'écran, des illustrations de type SVG ou des textures + Générez à la taille dont vous avez besoin + Les images générées ne disposent pas d\'un original d\'appareil photo sur lequel s\'appuyer. Si la sortie est trop petite, une mise à l’échelle ultérieure peut adoucir les modèles, décaler les détails ou modifier la manière dont l’élément est mis en mosaïque et compressé. + Définissez d\'abord les dimensions du cas d\'utilisation final, telles que le fond d\'écran, l\'icône, l\'arrière-plan, l\'impression ou la superposition. + Utilisez des tailles plus grandes pour les textures qui peuvent être recadrées, agrandies ou réutilisées dans plusieurs mises en page. + Exportez les versions de test avant des résultats énormes, car les détails procéduraux peuvent modifier le comportement de la compression. + Exporter les fonds d\'écran actuels + Récupérer les fonds d\'écran d\'accueil, de verrouillage et intégrés disponibles à partir de l\'appareil + Enregistrez ou partagez les fonds d\'écran installés + Wallpapers Export lit les fonds d’écran qu’Android expose via les API de fonds d’écran du système. Selon l\'appareil, la version d\'Android, les autorisations et la variante de construction, les fonds d\'écran Accueil, Verrouillage ou intégrés peuvent être disponibles séparément ou peuvent être manquants. + Ouvrez Wallpapers Export pour charger les fonds d’écran que le système permet à l’application de lire. + Sélectionnez les entrées disponibles Accueil, Verrouillage ou fond d\'écran intégré que vous souhaitez enregistrer, copier ou partager. + Si un fond d\'écran est manquant ou si la fonctionnalité n\'est pas disponible, il s\'agit généralement d\'une limitation du système, d\'une autorisation ou d\'une variante de construction plutôt que d\'un paramètre d\'édition. + Accélérateur d\'animation dans les coins + Copier la couleur comme + HEX + nom + Modèle de tapis de portrait pour des découpes rapides de personnes avec des cheveux et une manipulation des bords plus doux. + Amélioration rapide des conditions de faible luminosité grâce à l\'estimation de la courbe Zero-DCE++. + Modèle de restauration d\'image Restormer pour réduire les traînées de pluie et les artefacts de temps pluvieux. + Modèle de déformation de documents qui prédit une grille de coordonnées et remappe les pages courbes en une numérisation plus plate. + Échelle de durée de mouvement + Contrôle la vitesse d\'animation de l\'application : 0 désactive le mouvement, 1 est normal, des valeurs plus élevées ralentissent les animations + Afficher les outils récents + Afficher un accès rapide aux outils récemment utilisés sur l\'écran principal + Statistiques d\'utilisation + Explorez les ouvertures d\'applications, les lancements d\'outils et vos outils les plus utilisés + L\'application s\'ouvre + L\'outil s\'ouvre + Dernier outil + Sauvegardes réussies + Séquence d\'activité + Données enregistrées + Format supérieur + Outils utilisés + %1$d ouvre • %2$s + Aucune statistique d\'utilisation pour l\'instant + Ouvrez quelques outils et ils apparaîtront ici automatiquement + Vos statistiques d\'utilisation sont stockées uniquement localement sur votre appareil. ImageToolbox les utilise uniquement pour afficher votre activité personnelle, vos outils favoris et les informations sur les données enregistrées + éditeur éditer photo retouche d\'image ajuster + redimensionner convertir échelle résolution dimensions largeur hauteur jpg jpeg png webp compresser + compresser taille poids octets mo ko réduire la qualité optimiser + rapport d\'aspect de coupe de coupe de recadrage + filtre effet correction des couleurs ajuster masque de lut + dessiner pinceau crayon annoter balisage + chiffrer chiffrer déchiffrer mot de passe secret + dissolvant d\'arrière-plan effacer supprimer découpe transparent alpha + aperçu de la galerie de la visionneuse inspecter ouverte + point fusionner combiner panorama longue capture d\'écran + url web télécharger internet lien image + sélecteur pipette échantillon hex rgb couleur + palette couleurs échantillons extraire dominante + confidentialité des métadonnées exif supprimer la suppression de l\'emplacement GPS propre + comparer la différence avant après + limite redimensionner max min mégapixels dimensions pince + outils de pages de documents pdf + texte ocr reconnaître l\'analyse d\'extrait + maille de couleur de fond dégradé + superposition de tampon de texte de logo en filigrane + animation gif images de conversion animées + apng animation png animé convertir des images + archive zip compresser le pack décompresser les fichiers + jxl jpeg xl convertir le format d\'image jpg + trace vectorielle svg conversion xml + format convertir jpg jpeg png webp heic avif jxl bmp + scanner de documents numériser papier point de vue + analyse du scanner de codes à barres qr + superposition d\'empilement astrophotographie médiane moyenne + pièces de tranche de grille de tuiles divisées + conversion de couleur hex rgb hsl hsv cmjn laboratoire + webp convertir le format d\'image animée + générateur de bruit grain de texture aléatoire + combinaison de mise en page de grille de collage + les calques de balisage annotent la superposition modifier + base64 encoder décoder les données uri + somme de contrôle hachage md5 sha sha1 sha256 vérifier + fond dégradé de maille + métadonnées exif modifier caméra de date gps + couper des carreaux en morceaux de grille divisée + métadonnées de la musique de la couverture audio de l\'album + fond d\'exportation de papier peint + caractères d\'art de texte ascii + ai ml neural améliore le segment haut de gamme + palette de matériaux de la bibliothèque de couleurs nommée couleurs + studio d\'effet de fragment shader glsl + pdf fusionner combiner rejoindre + pdf divisé en pages séparées + pdf faire pivoter l\'orientation des pages + pdf réorganiser réorganiser les pages trier + pagination des numéros de page pdf + pdf ocr texte reconnaître extrait consultable + texte du logo du timbre en filigrane pdf + tirage au sort du signe de signature pdf + pdf protéger le mot de passe chiffrer le verrouillage + pdf déverrouiller le mot de passe décrypter supprimer la protection + pdf compresser réduire la taille optimiser + pdf niveaux de gris noir blanc monochrome + pdf réparation correctif récupérer cassé + propriétés des métadonnées pdf titre de l\'auteur exif + pdf supprimer supprimer des pages + marges de coupe du recadrage pdf + pdf aplatir les annotations des formulaires + pdf extraire des images images + compresser l\'archive zip pdf + imprimante d\'impression pdf + visionneuse d\'aperçu pdf lire + images en pdf convertir un document jpg jpeg png + pdf vers images pages exporter jpg png + pdf annotations commentaires supprimer nettoyer + Variante neutre + Erreur + Ombre portée + Hauteur des dents + Plage de dents horizontale + Gamme de dents verticales + Bord supérieur + Bord droit + Bord inférieur + Bord gauche + Bord déchiré + Réinitialiser les statistiques d\'utilisation + L\'outil s\'ouvre, les statistiques de sauvegarde, la séquence, les données enregistrées et le format supérieur seront réinitialisés. L\'ouverture de l\'application restera inchangée. + Audio + Déposer + Exporter des profils + Enregistrer le profil actuel + Supprimer le profil d\'exportation + Voulez-vous supprimer le profil d\'exportation \\"%1$s\\" ? + Bordure de dessin + Afficher une fine bordure autour de la toile de dessin et d\'effacement + Ondulé + Éléments d\'interface utilisateur arrondis avec un bord légèrement ondulé + Scoop + Entailler + Coins arrondis sculptés vers l\'intérieur + Coins étagés avec une double coupe + Espace réservé + Utilisez {filename} pour insérer le nom de fichier d\'origine sans extension + Éclater + Montant de base + Montant de la bague + Quantité de rayons + Largeur de l\'anneau + Perspective de distorsion + Apparence et convivialité Java + Tondre + Angle X + et angle + Redimensionner la sortie + Goutte d\'eau + Longueur d\'onde + Phase + Passe-haut + Masque de couleur + Chrome + Dissoudre + Douceur + Retour + Flou de l\'objectif + Faible entrée + Entrée élevée + Faible rendement + Haut rendement + Effets de lumière + Hauteur de bosse + Douceur des chocs + Ondulation + Amplitude X + Amplitude Y + Longueur d\'onde X + Longueur d\'onde Y + Flou adaptatif + Génération de textures + Générez diverses textures procédurales et enregistrez-les sous forme d\'images + Type de texture + Biais + Temps + Briller + Dispersion + Échantillons + Coefficient d\'angle + Coefficient de gradient + Type de grille + Puissance à distance + Échelle Y + Flou + Type de base + Facteur de turbulence + Mise à l\'échelle + Itérations + Anneaux + Métal brossé + Caustiques + Cellulaire + Damier + fBM + Marbre + Plasma + Édredon + Bois + aléatoire + Carré + Hexagonal + Octogonal + Triangulaire + Strié + Bruit VL + Bruit SC + Récent + Aucun filtre récemment utilisé pour l\'instant + texture générateur métal brossé caustiques cellulaire damier marbre plasma courtepointe bois brique camouflage cellule nuage fissure tissu feuillage nid d\'abeille glace lave nébuleuse papier rouille sable fumée pierre terrain topographie eau ondulation + Brique + Camouflage + Cellule + Nuage + Fissure + Tissu + Feuillage + Rayon de miel + Glace + Lave + Nébuleuse + Papier + Rouiller + Sable + Fumée + Pierre + Terrain + Topographie + Ondulation de l\'eau + Bois avancé + Largeur du mortier + Irrégularité + Biseau + Rugosité + Premier seuil + Deuxième seuil + Troisième seuil + Douceur des bords + Largeur de bordure + Couverture + Détail + Profondeur + Ramification + Fils horizontaux + Fils verticaux + Duvet + Veines + Éclairage + Largeur de fissure + Gel + Couler + Croûte + Densité des nuages + Étoiles + Densité des fibres + Résistance des fibres + Taches + Corrosion + Piqûres + Flocons + Fréquence des dunes + Angle du vent + Ondulations + Feux follets + Échelle veineuse + Niveau d\'eau + Niveau montagne + Érosion + Niveau de neige + Nombre de lignes + Épaisseur du trait + Ombres + Couleur des pores + Couleur du mortier + Couleur brique foncée + Couleur brique + Couleur de surbrillance + Couleur foncée + Couleur forêt + Couleur de la terre + Couleur sable + Couleur de fond + Couleur des cellules + Couleur du bord + Couleur du ciel + Couleur de l\'ombre + Couleur claire + Couleur de la surface + Couleur variable + Couleur des fissures + Couleur de chaîne + Couleur de trame + Couleur des feuilles foncées + Couleur des feuilles + Couleur de la bordure + Couleur miel + Couleur profonde + Couleur glace + Couleur givrée + Couleur de la croûte + Laver la couleur + Couleur lumineuse + Couleur de l\'espace + Couleur violette + Couleur bleue + Couleur de base + Couleur des fibres + Couleur de la tache + Couleur métal + Couleur rouille foncée + Couleur rouille + Couleur orange + Couleur de fumée + Couleur des veines + Couleur des basses terres + Aquarelle + Couleur roche + Couleur de la neige + Couleur faible + Haute couleur + Couleur de ligne + Couleur peu profonde + Herbe + Saleté + Cuir + Béton + Asphalte + Mousse + Feu + Aurore + Nappe de pétrole + Aquarelle + Flux abstrait + Opale + Acier Damas + Foudre + Velours + Marbrure à l\'encre + Feuille holographique + Bioluminescence + Vortex cosmique + Lampe à lave + Horizon des événements + Floraison fractale + Tunnel Chromatique + Éclipse Couronne + Attracteur étrange + Couronne ferrofluide + Supernova + Iris + Plume de Paon + Coquille de Nautilus + Planète aux anneaux + Densité de la lame + Longueur de la lame + Vent + Inégalité + Touffes + Humidité + Cailloux + Rides + Pores + Agrégat + Fissures + Goudron + Porter + Taches + Fibres + Fréquence de flamme + Fumée + Intensité + Rubans + Bandes + Irisation + Obscurité + Fleurs + Pigment + Bords + Papier + Diffusion + Symétrie + Acuité + Jeu de couleurs + Lait + Calques + Pliant + polonais + Succursales + Direction + Éclat + Plis + Plumage + Balance d\'encre + Spectre + Froissés + Diffraction + Bras + Torsion + Lueur de base + Objets blobs + Inclinaison du disque + Taille de l\'horizon + Largeur du disque + Objectif + Pétales + Boucle + Filigrane + Facettes + Courbure + Taille de la lune + Taille de la couronne + Rayons + Bague diamant + Lobes + Densité de l\'orbite + Épaisseur + Pointes + Longueur des pointes + Taille du corps + Métallique + Rayon de choc + Largeur de la coque + Jeté + Taille de la pupille + Taille de l\'iris + Variation de couleur + Projecteur + Taille des yeux + Densité des barbes + Virages + Chambres + Ouverture + Crêtes + Nacré + Taille de la planète + Inclinaison de l\'anneau + Largeur de l\'anneau + Atmosphère + Couleur de la saleté + Couleur herbe foncée + Couleur de l\'herbe + Couleur de la pointe + Couleur terre foncée + Couleur sèche + Couleur galet + Couleur du cuir + Couleur béton + Couleur du goudron + Couleur asphalte + Couleur de la pierre + Couleur de la poussière + Couleur du sol + Couleur mousse foncée + Couleur mousse + Couleur rouge + Couleur de base + Couleur verte + Couleur bleue + Couleur magenta + Couleur or + Couleur du papier + Couleur des pigments + Couleur secondaire + Première couleur + Deuxième couleur + Couleur rose + Couleur acier foncé + Couleur acier + Couleur acier clair + Couleur oxyde + Couleur du halo + Couleur du boulon + Couleur velours + Couleur brillante + Couleur de l\'encre bleue + Couleur de l\'encre rouge + Couleur d\'encre foncée + Couleur argent + Couleur jaune + Couleur du tissu + Couleur du disque + Couleur chaude + Couleur des verres + Couleur extérieure + Couleur intérieure + Couleur couronne + Couleur froide + Couleur chaude + Couleur du nuage + Couleur de la flamme + Couleur des plumes + Couleur de la coque + Couleur perle + Couleur de la planète + Couleur de la bague + Supprimer les fichiers originaux après l\'enregistrement + Seuls les fichiers sources traités avec succès seront supprimés + Fichiers originaux supprimés : %1$d + Échec de la suppression des fichiers d\'origine : %1$d + Fichiers d\'origine supprimés : %1$d, échec : %2$d + Gestes de feuille + Autoriser le glissement des feuilles d\'options développées + Sous-échantillonnage de chrominance + Profondeur de bits + Sans perte + %1$d-bit + Renommer par lots + Renommer plusieurs fichiers à l\'aide de modèles de nom de fichier + renommer les fichiers par lots nom de fichier modèle séquence extension de date + Choisissez les fichiers à renommer + Modèle de nom de fichier + Rebaptiser + Source des dates + Date EXIF ​​prise + Date de modification du fichier + Date de création du fichier + Date actuelle + Date manuelle + Date manuelle + Heure manuelle + La date sélectionnée n\'est pas disponible pour certains fichiers. La date du jour sera utilisée pour eux. + Entrez un modèle de nom de fichier + Le modèle produit un nom de fichier invalide ou trop long + Le modèle produit des noms de fichiers en double + Tous les noms de fichiers correspondent déjà au modèle + Ce modèle utilise des jetons qui ne sont pas disponibles pour le renommage par lots + Le nom restera le même + Fichiers renommés avec succès + Certains fichiers n\'ont pas pu être renommés + L\'autorisation n\'a pas été accordée + Utilise le nom de fichier d\'origine sans extension, vous aidant ainsi à conserver l\'identification de la source intacte. Prend également en charge le découpage avec \\o{start:end}, la translittération avec \\o{t} et le remplacement par \\o{s/old/new/}. + Compteur à incrémentation automatique. Prend en charge le formatage personnalisé : \\c{padding} (par exemple \\c{3} -&gt; 001), \\c{start:step} ou \\c{start:step:padding}. + Le nom du dossier parent où se trouvait le fichier d\'origine. + La taille du fichier d\'origine. Prend en charge les unités : \\z{b}, \\z{kb}, \\z{mb} ou simplement \\z pour un format lisible par l\'homme. + Génère un identifiant unique universel (UUID) aléatoire pour garantir l\'unicité du nom de fichier. + Le renommage des fichiers ne peut pas être annulé. Assurez-vous que les nouveaux noms sont corrects avant de continuer. + Confirmer le changement de nom + Paramètres du menu latéral + Échelle des menus + Menu alpha + Balance des blancs automatique + Coupure + Vérification des filigranes cachés + Stockage + Limite du cache + Vider automatiquement le cache lorsqu\'il dépasse cette taille + Intervalle de nettoyage + À quelle fréquence vérifier si le cache doit être vidé + Au lancement de l\'application + 1 jour + 1 semaine + 1 mois + Effacez automatiquement le cache de l\'application en fonction de la limite et de l\'intervalle sélectionnés + Surface de culture mobile + Permet de déplacer toute la zone de recadrage en la faisant glisser à l\'intérieur + Fusionner les GIF + Combinez plusieurs clips GIF en une seule animation + Ordre des clips GIF + Inverse + Jouer à l\'envers + Boomerang + Jouer en avant puis en arrière + Normaliser la taille du cadre + Redimensionnez les images pour qu\'elles s\'adaptent au plus grand clip ; éteindre pour conserver leur échelle d\'origine + Délai entre les clips (ms) + Dossier + Sélectionnez toutes les images d\'un dossier et de ses sous-dossiers + Recherche de doublons + Trouvez des copies exactes et des images visuellement similaires + recherche de doublons images similaires copies exactes photos nettoyage stockage dHash SHA-256 + Choisissez des images pour trouver les doublons + Sensibilité à la similarité + Copies exactes + Images similaires + Sélectionnez tous les doublons exacts + Sélectionner tout sauf recommandé + Aucun doublon trouvé + Essayez d\'ajouter plus d\'images ou d\'augmenter la sensibilité de similarité + Impossible de lire %1$d image(s) + %1$s • %2$s récupérable + %1$d groupes • %2$s récupérables + %1$d sélectionné • %2$s + Cela ne peut pas être annulé. Android peut vous demander de confirmer la suppression dans une boîte de dialogue système. + Introuvable + Déplacer pour commencer + Passer à la fin + \ No newline at end of file diff --git a/core/resources/src/main/res/values-hi/strings.xml b/core/resources/src/main/res/values-hi/strings.xml new file mode 100644 index 0000000..1b27c45 --- /dev/null +++ b/core/resources/src/main/res/values-hi/strings.xml @@ -0,0 +1,3739 @@ + + + + ठहरें + बंद करें + छवि रीसेट करें + छवि परिवर्तन प्रारंभिक मानों पर वापस आ जाएंगे + मान ठीक से रीसेट हो गए + रीसेट + दी गई दो छवियों की तुलना करें + आरंभ करने के लिए दो छवियां चुनें + गतिशील रंग चालू होने पर ऐप रंग योजना नहीं बदली जा सकती + ऐप थीम चयनित रंग पर आधारित होगी + ऐप के बारे में + कुछ ग़लत हुआ: %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 + प्रोजेक्ट सहेजें + एक संपादन योग्य प्रोजेक्ट फ़ाइल में परतें, पृष्ठभूमि और संपादन इतिहास संग्रहीत करें + खोलने में विफल + खोजने योग्य पीडीएफ में लिखें + छवि बैच से पाठ को पहचानें और छवि और चयन योग्य पाठ परत के साथ खोजने योग्य पीडीएफ को सहेजें + परत अल्फा + क्षैतिज फ़्लिप + लंबवत फ्लिप + ताला + छाया जोड़ें + छाया रंग + पाठ ज्यामिति + अधिक स्पष्ट शैलीकरण के लिए पाठ को फैलाएँ या तिरछा करें + स्केल एक्स + तिरछा एक्स + एनोटेशन हटाएँ + पीडीएफ पृष्ठों से चयनित एनोटेशन प्रकार जैसे लिंक, टिप्पणियाँ, हाइलाइट्स, आकार या फॉर्म फ़ील्ड हटा दें + हाइपरलिंक + फ़ाइल अनुलग्नक + पंक्तियां + पॉप अप + टिकटों + आकार + पाठ नोट्स + टेक्स्ट मार्कअप + प्रपत्र फ़ील्ड + मार्कअप + अज्ञात + एनोटेशन + असमूहीकृत + कॉन्फ़िगर करने योग्य रंग और ऑफसेट के साथ परत के पीछे धुंधली छाया जोड़ें + सामग्री जागरूक विरूपण + इस क्रिया को पूरा करने के लिए पर्याप्त मेमोरी नहीं है. कृपया छोटी छवि का उपयोग करने, अन्य ऐप्स बंद करने या ऐप को पुनः प्रारंभ करने का प्रयास करें। + समानांतर कार्यकर्ता + ये छवियाँ सहेजी नहीं गईं क्योंकि परिवर्तित फ़ाइलें मूल से बड़ी होंगी। मूल छवियाँ हटाने से पहले इस सूची की जाँच करें। + फ़ाइलें छोड़ दी गईं: %1$s + ऐप लॉग + समस्याओं का पता लगाने के लिए ऐप के लॉग देखें + समूहीकृत टूल में पसंदीदा + जब टूल को प्रकार के आधार पर समूहीकृत किया जाता है तो पसंदीदा को एक टैब के रूप में जोड़ता है + पसंदीदा को अंतिम के रूप में दिखाएँ + पसंदीदा टूल टैब को अंत तक ले जाता है + क्षैतिज दूरी + लंबवत रिक्ति + पिछड़ी ऊर्जा + फॉरवर्ड एनर्जी एल्गोरिदम के बजाय सरल ग्रेडिएंट परिमाण ऊर्जा मानचित्र का उपयोग करें + नकाबपोश क्षेत्र को हटा दें + सीम नकाबपोश क्षेत्र से होकर गुजरेंगी, और इसे सुरक्षित करने के बजाय केवल चयनित क्षेत्र को हटा देंगी + वीएचएस एनटीएससी + उन्नत एनटीएससी सेटिंग्स + मजबूत वीएचएस और प्रसारण कलाकृतियों के लिए अतिरिक्त एनालॉग ट्यूनिंग + क्रोमा ब्लीड + टेप पहनना + ट्रैकिंग + लूमा धब्बा + बज + बर्फ + फ़ील्ड का उपयोग करें + फ़िल्टर प्रकार + इनपुट लूमा फ़िल्टर + इनपुट क्रोमा लोपास + क्रोमा डिमोड्यूलेशन + चरण में बदलाव + चरण ऑफसेट + सिर बदलना + सिर बदलने की ऊंचाई + हेड स्विचिंग ऑफसेट + हेड स्विचिंग शिफ्ट + मध्य रेखा की स्थिति + मध्य-रेखा घबराना + शोर की ऊँचाई पर नज़र रखना + ट्रैकिंग तरंग + बर्फ़ पर नज़र रखना + हिम अनिसोट्रॉपी पर नज़र रखना + ट्रैकिंग शोर + शोर की तीव्रता पर नज़र रखना + समग्र शोर + समग्र शोर आवृत्ति + समग्र शोर तीव्रता + समग्र शोर विवरण + बजने की आवृत्ति + बजने की शक्ति + लूमा शोर + लूमा शोर आवृत्ति + लूमा शोर की तीव्रता + लूमा शोर विवरण + क्रोमा शोर + क्रोमा शोर आवृत्ति + क्रोमा शोर तीव्रता + क्रोमा शोर विवरण + हिमपात की तीव्रता + हिम अनिसोट्रॉपी + क्रोमा चरण शोर + क्रोमा चरण त्रुटि + क्रोमा विलंब क्षैतिज + क्रोमा विलंब लंबवत + वीएचएस टेप की गति + वीएचएस क्रोमा हानि + वीएचएस तीव्रता को तेज करता है + वीएचएस पैनापन आवृत्ति + वीएचएस एज वेव तीव्रता + वीएचएस एज तरंग गति + वीएचएस एज तरंग आवृत्ति + वीएचएस एज वेव विवरण + आउटपुट क्रोमा लोपास + क्षैतिज स्केल + ऊर्ध्वाधर स्केल + स्केल फ़ैक्टर X + स्केल फ़ैक्टर Y + सामान्य छवि वॉटरमार्क का पता लगाता है और उन्हें लामा से रंग देता है। स्वचालित रूप से डिटेक्शन और इनपेंटिंग मॉडल डाउनलोड करता है + deuteranopia + छवि का विस्तार करें + YOLO v11 सेगमेंटेशन का उपयोग करके ऑब्जेक्ट सेगमेंटेशन आधारित बैकग्राउंड रिमूवर + रेखा कोण दिखाएँ + ड्राइंग करते समय वर्तमान रेखा का घुमाव डिग्री में दिखाता है + एनिमेटेड इमोजी + उपलब्ध इमोजी को एनिमेशन के रूप में दिखाएं + मूल फ़ोल्डर में सहेजें + चयनित फ़ोल्डर के बजाय मूल फ़ाइल के बगल में नई फ़ाइलें सहेजें + वॉटरमार्क पीडीएफ + पर्याप्त स्मृति नहीं + इस उपकरण पर संसाधित करने के लिए छवि संभवतः बहुत बड़ी है, या सिस्टम में उपलब्ध मेमोरी समाप्त हो गई है। छवि रिज़ॉल्यूशन को कम करने, अन्य ऐप्स बंद करने या छोटी फ़ाइल चुनने का प्रयास करें। + डिबग मेनू + ऐप फ़ंक्शंस का परीक्षण करने के लिए मेनू, इसका उद्देश्य उत्पादन रिलीज़ में दिखाना नहीं है + प्रदाता + पैडलओसीआर को आपके डिवाइस पर अतिरिक्त ONNX मॉडल की आवश्यकता है। क्या आप %1$s डेटा डाउनलोड करना चाहते हैं? + सार्वभौमिक + कोरियाई + लैटिन + पूर्वी स्लाव + थाई + यूनानी + अंग्रेज़ी + सिरिलिक + अरबी + देवनागरी + तामिल + तेलुगू + शेडर + शेडर प्रीसेट + कोई शेडर चयनित नहीं + शेडर स्टूडियो + कस्टम फ्रैगमेंट शेडर्स बनाएं, संपादित करें, मान्य करें, आयात करें और निर्यात करें + शेडर्स सहेजे गए + सहेजे गए शेडर खोलें, डुप्लिकेट बनाएं, निर्यात करें, साझा करें या हटाएं + नया शेडर + शेडर फ़ाइल + .itshader JSON + %1$d पैरामीटर + शेडर स्रोत + केवल शून्य मुख्य() का मुख्य भाग लिखें। स्रोत नमूने के रूप में यूवी निर्देशांक और इनपुटइमेजटेक्स्चर के लिए टेक्सचरकोऑर्डिनेट का उपयोग करें। + कार्य + शून्य मुख्य() से पहले वैकल्पिक सहायक फ़ंक्शन, स्थिरांक और संरचनाएं डाली गईं। वर्दी पैराम से उत्पन्न होती है। + जब शेडर को संपादन योग्य मानों की आवश्यकता हो तो यहां वर्दी जोड़ें। + शेडर रीसेट करें + वर्तमान शेडर ड्राफ्ट साफ़ कर दिया जाएगा + अमान्य शेडर + असमर्थित शेडर संस्करण %1$d। समर्थित संस्करण: %2$d. + शेडर नाम रिक्त नहीं होना चाहिए. + शेडर स्रोत रिक्त नहीं होना चाहिए. + शेडर स्रोत में असमर्थित वर्ण हैं: %1$s। केवल ASCII GLSL स्रोत का उपयोग करें। + पैरामीटर \\\"%1$s\\\" एक से अधिक बार घोषित किया गया है। + पैरामीटर नाम रिक्त नहीं होने चाहिए. + पैरामीटर \\\"%1$s\\\" एक आरक्षित GPUImage नाम का उपयोग करता है। + पैरामीटर \\\"%1$s\\\" एक वैध GLSL पहचानकर्ता होना चाहिए. + पैरामीटर \"%1$s\\\" में %2$s मान प्रकार \"%3$s\\\" है, अपेक्षित \"%4$s\\\"। + पैरामीटर \\\"%1$s\\\" बूल मानों के लिए न्यूनतम या अधिकतम परिभाषित नहीं कर सकता। + पैरामीटर \\\"%1$s\\\" में न्यूनतम अधिकतम से अधिक है। + पैरामीटर \\\"%1$s\\\" डिफ़ॉल्ट न्यूनतम से कम है। + पैरामीटर \\\"%1$s\\\" डिफ़ॉल्ट अधिकतम से अधिक है। + शेडर को \\\"यूनिफ़ॉर्म सैम्पलर2D %1$s;\\\" घोषित करना होगा। + शेडर को \\\"विभिन्न vec2 %1$s;\\\" घोषित करना होगा। + शेडर को \\\"void main()\\\" परिभाषित करना होगा। + शेडर को gl_FragColor पर एक रंग लिखना होगा। + शेडरटॉय मेनइमेज शेडर्स समर्थित नहीं हैं। GPUImage के शून्य मुख्य() अनुबंध का उपयोग करें। + एनिमेटेड शेडरटॉय वर्दी \\\"iTime\\\" समर्थित नहीं है। + एनिमेटेड शेडरटॉय वर्दी \\\"iFrame\\\" समर्थित नहीं है। + शेडरटॉय वर्दी \\\"iResolution\\\" समर्थित नहीं है। + शेडरटॉय आईचैनल बनावट समर्थित नहीं हैं। + बाहरी बनावट समर्थित नहीं हैं. + बाहरी बनावट एक्सटेंशन समर्थित नहीं हैं. + लिब्रेट्रो शेडर पैरामीटर समर्थित नहीं हैं। + केवल एक इनपुट बनावट समर्थित है. \\\"वर्दी %1$s %2$s;\\\" हटाएं। + पैरामीटर \\\"%1$s\\\" को \\\"यूनिफ़ॉर्म %2$s %1$s;\\\" घोषित किया जाना चाहिए। + पैरामीटर \"%1$s\\\" को \"यूनिफ़ॉर्म %2$s %1$s;\\\" घोषित किया गया है, अपेक्षित \"यूनिफ़ॉर्म %3$s %1$s;\\\"। + शेडर फ़ाइल खाली है. + शेडर फ़ाइल संस्करण, नाम और शेडर फ़ील्ड के साथ एक .itshader JSON ऑब्जेक्ट होनी चाहिए। + शेडर फ़ाइल वैध JSON नहीं है या .itshader प्रारूप से मेल नहीं खाती है। + फ़ील्ड \\\"%1$s\\\" आवश्यक है. + %1$s आवश्यक है. + %1$s \\\"%2$s\\\" समर्थित नहीं है. समर्थित प्रकार: %3$s। + \\\"%2$s\\\" पैरामीटर के लिए %1$s आवश्यक है। + %1$s एक सीमित संख्या होनी चाहिए. + %1$s एक पूर्णांक होना चाहिए. + %1$s सत्य या असत्य होना चाहिए। + %1$s एक रंग स्ट्रिंग, RGB/RGBA सरणी, या रंग ऑब्जेक्ट होना चाहिए। + %1$s को #RRGGBB या #RRGGBBAA प्रारूप का उपयोग करना चाहिए। + %1$s में वैध हेक्साडेसिमल रंग चैनल होने चाहिए। + %1$s में 3 या 4 रंगीन चैनल होने चाहिए। + %1$s 0 और 255 के बीच होना चाहिए. + %1$s दो-संख्या वाली सरणी या x और y वाला ऑब्जेक्ट होना चाहिए। + %1$s में बिल्कुल 2 संख्याएँ होनी चाहिए। + डुप्लिकेट + EXIF को हमेशा साफ़ करें + सहेजने पर छवि EXIF ​​डेटा हटाएं, तब भी जब कोई टूल मेटाडेटा रखने का अनुरोध करता है + सहायता एवं सुझाव + %1$d ट्यूटोरियल + उपकरण खोलें + मुख्य उपकरण, निर्यात विकल्प, पीडीएफ प्रवाह, रंग उपयोगिताएँ और सामान्य समस्याओं के समाधान जानें + शुरू करना + उपकरण चुनें, फ़ाइलें आयात करें, परिणाम सहेजें, और सेटिंग्स का तेजी से पुन: उपयोग करें + छवि संपादन + छवियों का आकार बदलें, काटें, फ़िल्टर करें, पृष्ठभूमि मिटाएँ, चित्र बनाएँ और वॉटरमार्क करें + फ़ाइलें और मेटाडेटा + प्रारूप परिवर्तित करें, आउटपुट की तुलना करें, गोपनीयता की रक्षा करें और फ़ाइल नामों को नियंत्रित करें + पीडीएफ और दस्तावेज़ + पीडीएफ बनाएं, दस्तावेज़ स्कैन करें, ओसीआर पेज, और साझा करने के लिए फ़ाइलें तैयार करें + टेक्स्ट, क्यूआर और डेटा + टेक्स्ट को पहचानें, कोड स्कैन करें, बेस64 को एन्कोड करें, हैश और पैकेज फ़ाइलों की जाँच करें + रंग उपकरण + रंग चुनें, पैलेट बनाएं, रंग लाइब्रेरी खोजें और ग्रेडिएंट बनाएं + रचनात्मक उपकरण + एसवीजी, एएससीआईआई कला, शोर बनावट, शेडर्स और प्रयोगात्मक दृश्य उत्पन्न करें + समस्या निवारण + भारी फ़ाइलें, पारदर्शिता, साझाकरण, आयात और रिपोर्टिंग संबंधी समस्याओं को ठीक करें + सही उपकरण चुनें + सही जगह से शुरुआत करने के लिए श्रेणियों, खोज, पसंदीदा और साझा क्रियाओं का उपयोग करें + सर्वोत्तम प्रवेश बिंदु से प्रारंभ करें + इमेज टूलबॉक्स में कई केंद्रित टूल हैं, और उनमें से कई पहली नज़र में ओवरलैप हो जाते हैं। उस कार्य से प्रारंभ करें जिसे आप समाप्त करना चाहते हैं, फिर वह टूल चुनें जो परिणाम के सबसे महत्वपूर्ण भाग को नियंत्रित करता है: आकार, प्रारूप, मेटाडेटा, टेक्स्ट, पीडीएफ पेज, रंग, या लेआउट। + जब आप क्रिया का नाम जानते हों, जैसे कि आकार बदलना, पीडीएफ, EXIF, OCR, QR, या रंग, तो खोज का उपयोग करें। + जब आप केवल कार्य प्रकार जानते हों तो एक श्रेणी खोलें, फिर पसंदीदा के रूप में बार-बार आने वाले टूल को पिन करें। + जब कोई अन्य ऐप इमेज टूलबॉक्स में एक छवि साझा करता है, तो शेयर शीट फ़्लो से टूल चुनें। + परिणाम आयात करें, सहेजें और साझा करें + इनपुट चुनने से लेकर संपादित फ़ाइल को निर्यात करने तक के सामान्य प्रवाह को समझें + सामान्य संपादन प्रवाह का पालन करें + अधिकांश उपकरण समान लय का पालन करते हैं: इनपुट चुनें, विकल्प समायोजित करें, पूर्वावलोकन करें, फिर सहेजें या साझा करें। महत्वपूर्ण विवरण यह है कि सहेजने से एक आउटपुट फ़ाइल बनती है, जबकि साझा करना आमतौर पर किसी अन्य ऐप को त्वरित हैंडऑफ़ के लिए सर्वोत्तम होता है। + जब पिकर इसकी अनुमति दे तो एकल-छवि टूल के लिए एक फ़ाइल या बैच टूल के लिए कई फ़ाइलें चुनें। + सहेजने से पहले पूर्वावलोकन और आउटपुट नियंत्रणों की जाँच करें, विशेष रूप से प्रारूप, गुणवत्ता और फ़ाइल नाम विकल्प। + त्वरित हैंडऑफ़ के लिए शेयर का उपयोग करें, या जब आपको चयनित फ़ोल्डर में लगातार प्रतिलिपि की आवश्यकता हो तो सहेजें। + एक साथ कई छवियों के साथ काम करें + बार-बार आकार बदलने, रूपांतरण, संपीड़न और नामकरण कार्यों के लिए बैच उपकरण सर्वोत्तम हैं + एक पूर्वानुमानित बैच तैयार करें + बैच प्रोसेसिंग सबसे तेज़ होती है जब प्रत्येक आउटपुट को समान नियमों का पालन करना चाहिए। यह बड़ी गलती करने का सबसे आसान स्थान भी है, इसलिए लंबा निर्यात शुरू करने से पहले नामकरण, गुणवत्ता, मेटाडेटा और फ़ोल्डर व्यवहार चुनें। + सभी संबंधित छवियों को एक साथ चुनें और यदि आउटपुट अनुक्रम पर निर्भर करता है तो क्रम रखें। + निर्यात शुरू करने से पहले एक बार आकार, प्रारूप, गुणवत्ता, मेटाडेटा और फ़ाइल नाम नियम सेट करें। + जब स्रोत फ़ाइलें बड़ी हों या जब लक्ष्य प्रारूप आपके लिए नया हो, तो पहले एक छोटा परीक्षण बैच चलाएँ। + त्वरित एकल संपादन + जब आपको एक छवि में कई सरल बदलावों की आवश्यकता हो तो ऑल-इन-वन संपादक का उपयोग करें + टूल हॉपिंग के बिना एक छवि समाप्त करें + एकल संपादन तब उपयोगी होता है जब छवि को एक विशेष ऑपरेशन के बजाय परिवर्तनों की एक छोटी श्रृंखला की आवश्यकता होती है। यह आमतौर पर बैच वर्कफ़्लो बनाए बिना त्वरित सफाई, पूर्वावलोकन और एक अंतिम प्रतिलिपि निर्यात करने के लिए तेज़ होता है। + एक छवि के लिए सिंगल एडिट खोलें, जिसमें सामान्य समायोजन, मार्कअप, क्रॉप, रोटेशन या निर्यात परिवर्तन की आवश्यकता है। + संपादन उस क्रम में लागू करें जो सबसे पहले सबसे कम पिक्सेल बदलता है, जैसे फ़िल्टर से पहले क्रॉप करना और अंतिम संपीड़न से पहले फ़िल्टर करना। + जब आपको बैच प्रोसेसिंग, सटीक फ़ाइल आकार लक्ष्य, पीडीएफ आउटपुट, ओसीआर, या मेटाडेटा क्लीनअप की आवश्यकता हो तो इसके बजाय एक विशेष उपकरण का उपयोग करें। + प्रीसेट और सेटिंग्स का उपयोग करें + बार-बार चुने गए विकल्पों को सहेजें और अपने पसंदीदा वर्कफ़्लो के लिए ऐप को ट्यून करें + एक ही सेटअप दोहराने से बचें + जब आप अक्सर एक ही प्रकार की फ़ाइल निर्यात करते हैं तो प्रीसेट और सेटिंग्स उपयोगी होते हैं। एक अच्छा प्रीसेट बार-बार दोहराई जाने वाली मैन्युअल चेकलिस्ट को एक टैप में बदल देता है, जबकि वैश्विक सेटिंग्स उन डिफ़ॉल्ट को परिभाषित करती हैं जिनसे नए टूल शुरू होते हैं। + सामान्य आउटपुट आकार, प्रारूप, गुणवत्ता मान या नामकरण पैटर्न के लिए प्रीसेट बनाएं। + डिफ़ॉल्ट प्रारूप, गुणवत्ता, सेव फ़ोल्डर, फ़ाइल नाम, पिकर और इंटरफ़ेस व्यवहार के लिए सेटिंग्स की समीक्षा करें। + ऐप को दोबारा इंस्टॉल करने या किसी अन्य डिवाइस पर जाने से पहले सेटिंग्स का बैकअप ले लें। + उपयोगी डिफ़ॉल्ट सेट करें + एक बार डिफ़ॉल्ट प्रारूप, गुणवत्ता, स्केल मोड, आकार प्रकार और रंग स्थान चुनें + नए टूल को अपने लक्ष्य के करीब शुरू करें + डिफ़ॉल्ट मान केवल दिखावटी प्राथमिकताएँ नहीं हैं। वे तय करते हैं कि कई टूल में कौन सा प्रारूप, गुणवत्ता, आकार बदलने का व्यवहार, स्केल मोड और रंग स्थान पहले दिखाई देते हैं, जो प्रत्येक निर्यात पर समान विकल्पों को फिर से चुनने से बचने में मदद करता है। + अपने सामान्य गंतव्य से मेल खाने के लिए डिफ़ॉल्ट छवि प्रारूप और गुणवत्ता सेट करें, जैसे फ़ोटो के लिए JPEG या अल्फ़ा के लिए PNG/WebP। + यदि आप अक्सर सख्त आयामों, सामाजिक प्लेटफ़ॉर्म या दस्तावेज़ पृष्ठों के लिए निर्यात करते हैं तो डिफ़ॉल्ट आकार प्रकार और स्केल मोड को ट्यून करें। + कलर स्पेस डिफ़ॉल्ट को केवल तभी बदलें जब आपके वर्कफ़्लो को डिस्प्ले, प्रिंट, या बाहरी संपादकों में लगातार रंग प्रबंधन की आवश्यकता हो। + पिकर और लांचर + जब ऐप बहुत अधिक संवाद खोलता है या गलत स्थान पर प्रारंभ होता है तो पिकर सेटिंग्स का उपयोग करें + फ़ाइलें खोलने को अपनी आदत के अनुरूप बनाएं + पिकर और लॉन्चर सेटिंग्स नियंत्रित करती हैं कि इमेज टूलबॉक्स मुख्य स्क्रीन, टूल या फ़ाइल चयन प्रवाह से शुरू होता है या नहीं। ये विकल्प विशेष रूप से तब उपयोगी होते हैं जब आप आमतौर पर एंड्रॉइड शेयर शीट से प्रवेश करते हैं या जब आप चाहते हैं कि ऐप एक टूल लॉन्चर जैसा लगे। + यदि सिस्टम पिकर फ़ाइलों को छुपाता है, बहुत सारे हालिया आइटम दिखाता है, या क्लाउड फ़ाइलों को खराब तरीके से संभालता है, तो फोटो पिकर सेटिंग्स का उपयोग करें। + केवल उन टूल के लिए स्किप पिकिंग सक्षम करें जहां आप आमतौर पर शेयर से प्रवेश करते हैं या पहले से ही स्रोत फ़ाइल जानते हैं। + यदि आप चाहते हैं कि इमेज टूलबॉक्स सामान्य होम स्क्रीन की तुलना में टूल पिकर की तरह अधिक व्यवहार करे तो लॉन्चर मोड की समीक्षा करें। + छवि स्रोत + वह पिकर मोड चुनें जो गैलरी, फ़ाइल प्रबंधक और क्लाउड फ़ाइलों के साथ सबसे अच्छा काम करता है + सही स्रोत से फ़ाइलें चुनें + छवि स्रोत सेटिंग्स इस बात को प्रभावित करती हैं कि ऐप किस प्रकार एंड्रॉइड से छवियां मांगता है। सबसे अच्छा विकल्प इस बात पर निर्भर करता है कि आपकी फ़ाइलें सिस्टम गैलरी, फ़ाइल प्रबंधक फ़ोल्डर, क्लाउड प्रदाता, या किसी अन्य ऐप में रहती हैं जो अस्थायी सामग्री साझा करती है। + जब आप सामान्य गैलरी छवियों के लिए सबसे अधिक सिस्टम-एकीकृत अनुभव चाहते हैं तो डिफ़ॉल्ट पिकर का उपयोग करें। + यदि एल्बम, फ़ोल्डर, क्लाउड फ़ाइलें, या गैर-मानक प्रारूप आपकी अपेक्षा के अनुरूप दिखाई नहीं देते हैं तो पिकर मोड स्विच करें। + यदि कोई साझा फ़ाइल स्रोत ऐप छोड़ने के बाद गायब हो जाती है, तो उसे लगातार पिकर के माध्यम से फिर से खोलें या पहले एक स्थानीय प्रतिलिपि सहेजें। + पसंदीदा और साझा उपकरण + मुख्य स्क्रीन को फ़ोकस रखें और उन टूल को छिपाएँ जिनका शेयर प्रवाह में कोई मतलब नहीं है + टूल सूची शोर कम करें + पसंदीदा और हिडन-फॉर-शेयर सेटिंग्स तब उपयोगी होती हैं जब आप हर दिन केवल कुछ टूल का उपयोग करते हैं। वे ऐसे टूल छिपाकर शेयर प्रवाह को व्यावहारिक बनाए रखते हैं जो किसी अन्य ऐप द्वारा भेजी गई फ़ाइल के प्रकार का उपयोग नहीं कर सकते हैं। + टूल को बार-बार पिन करें ताकि टूल को श्रेणी के आधार पर समूहीकृत किए जाने पर भी उन तक पहुंचना आसान रहे। + जब टूल किसी अन्य ऐप से आने वाली फ़ाइलों के लिए अप्रासंगिक हों तो उन्हें शेयर फ़्लो से छिपाएँ। + यदि आप अंत में पसंदीदा पसंद करते हैं या संबंधित टूल के साथ समूहीकृत हैं तो पसंदीदा ऑर्डरिंग सेटिंग्स का उपयोग करें। + सेटिंग्स का बैकअप लें + प्रीसेट, प्राथमिकताएं और ऐप व्यवहार को किसी अन्य इंस्टॉल पर सुरक्षित रूप से ले जाएं + अपना सेटअप पोर्टेबल रखें + प्रीसेट, फ़ोल्डर्स, फ़ाइल नाम नियम, टूल ऑर्डर और विज़ुअल प्राथमिकताओं को ट्यून करने के बाद बैकअप और रीस्टोर सबसे अधिक सहायक होता है। बैकअप आपको मेमोरी से अपने वर्कफ़्लो को पुनर्निर्माण किए बिना सेटिंग्स के साथ प्रयोग करने या ऐप को फिर से इंस्टॉल करने की सुविधा देता है। + प्रीसेट, फ़ाइल नाम व्यवहार, डिफ़ॉल्ट प्रारूप या टूल व्यवस्था बदलने के बाद बैकअप बनाएं। + यदि आप डेटा साफ़ करने, पुनः इंस्टॉल करने या डिवाइस को स्थानांतरित करने की योजना बना रहे हैं तो बैकअप को ऐप फ़ोल्डर के बाहर कहीं संग्रहीत करें। + महत्वपूर्ण बैचों को संसाधित करने से पहले सेटिंग्स को पुनर्स्थापित करें ताकि डिफ़ॉल्ट और सेव व्यवहार आपके पुराने सेटअप से मेल खाए। + छवियों का आकार बदलें और परिवर्तित करें + एक या अनेक छवियों का आकार, प्रारूप, गुणवत्ता और मेटाडेटा बदलें + संशोधित प्रतिलिपियाँ बनाएँ + पूर्वानुमानित आयामों और हल्के आउटपुट फ़ाइलों के लिए आकार बदलें और परिवर्तित करें मुख्य उपकरण है। इसका उपयोग तब करें जब छवि का आकार, आउटपुट स्वरूप और मेटाडेटा व्यवहार सभी एक ही समय में मायने रखते हों। + एक या अधिक छवियाँ चुनें, फिर चुनें कि आयाम, स्केल या फ़ाइल आकार सबसे अधिक मायने रखता है या नहीं। + आउटपुट स्वरूप और गुणवत्ता का चयन करें; जब पारदर्शिता बनाए रखनी हो तो पीएनजी या वेबपी का उपयोग करें। + परिणाम का पूर्वावलोकन करें, फिर मूल प्रतियों को बदले बिना उत्पन्न प्रतियों को सहेजें या साझा करें। + फ़ाइल आकार के अनुसार आकार बदलें + जब कोई वेबसाइट, मैसेंजर या फॉर्म भारी छवियों को अस्वीकार करता है तो आकार सीमा को लक्षित करें + सख्त अपलोड सीमाएँ फ़िट करें + फ़ाइल आकार के आधार पर आकार बदलना गुणवत्ता मानों का अनुमान लगाने से बेहतर है जब गंतव्य केवल एक विशिष्ट सीमा से नीचे की फ़ाइलों को स्वीकार करता है। परिणाम को प्रयोग योग्य बनाए रखने की कोशिश करते हुए उपकरण लक्ष्य तक पहुंचने के लिए संपीड़न और आयामों को समायोजित कर सकता है। + मेटाडेटा और प्रदाता परिवर्तनों के लिए जगह छोड़ने के लिए वास्तविक अपलोड सीमा से थोड़ा नीचे लक्ष्य आकार दर्ज करें। + जब लक्ष्य छोटा हो तो फ़ोटो के लिए हानिपूर्ण प्रारूपों को प्राथमिकता दें; आकार बदलने के बाद भी पीएनजी बड़ी रह सकती है। + निर्यात के बाद चेहरों, पाठ और किनारों का निरीक्षण करें क्योंकि आक्रामक आकार के लक्ष्य दृश्यमान कलाकृतियों का परिचय दे सकते हैं। + सीमा का आकार बदलें + केवल उन छवियों को सिकोड़ें जो आपकी अधिकतम चौड़ाई, ऊँचाई या फ़ाइल बाधाओं से अधिक हों + उन छवियों का आकार बदलने से बचें जो पहले से ही ठीक हैं + लिमिट रिसाइज़ मिश्रित फ़ोल्डरों के लिए उपयोगी है जहां कुछ छवियां बड़ी हैं और अन्य पहले से ही तैयार हैं। प्रत्येक फ़ाइल को समान आकार बदलने के लिए मजबूर करने के बजाय, यह स्वीकार्य छवियों को उनकी मूल गुणवत्ता के करीब रखता है। + प्रत्येक फ़ाइल का आँख मूंदकर आकार बदलने के बजाय गंतव्य के आधार पर अधिकतम आयाम या सीमाएँ निर्धारित करें। + जब आप ऐसे आउटपुट से बचना चाहते हैं जो स्रोतों से भारी हो जाते हैं तो स्किप-इफ-लार्ज स्टाइल व्यवहार सक्षम करें। + इसका उपयोग विभिन्न कैमरों, स्क्रीनशॉट या डाउनलोड के बैचों के लिए करें जहां स्रोत का आकार बहुत भिन्न होता है। + काटें, घुमाएँ और सीधा करें + छवि को निर्यात करने या अन्य टूल में उपयोग करने से पहले महत्वपूर्ण क्षेत्र को फ़्रेम करें + रचना साफ़ करें + पहले क्रॉप करने से बाद के फ़िल्टर, ओसीआर, पीडीएफ पेज और साझाकरण परिणाम साफ-सुथरे हो जाते हैं। + क्रॉप खोलें और वह छवि चुनें जिसे आप समायोजित करना चाहते हैं। + जब आउटपुट किसी पोस्ट, दस्तावेज़ या अवतार में फिट होना चाहिए तो फ्री क्रॉप या पहलू अनुपात का उपयोग करें। + सहेजने से पहले घुमाएँ या फ़्लिप करें ताकि बाद के टूल को सही छवि प्राप्त हो सके। + फ़िल्टर और समायोजन लागू करें + एक संपादन योग्य फ़िल्टर स्टैक बनाएं और निर्यात से पहले परिणाम का पूर्वावलोकन करें + छवि उपस्थिति को ट्यून करें + फ़िल्टर त्वरित सुधार, शैलीबद्ध आउटपुट और ओसीआर या प्रिंटिंग के लिए छवियां तैयार करने के लिए उपयोगी होते हैं। जब छवि में टेक्स्ट या बारीक विवरण हों तो कंट्रास्ट, तीक्ष्णता और चमक में छोटे बदलाव भारी प्रभावों से अधिक मायने रख सकते हैं। + एक-एक करके फ़िल्टर जोड़ें और प्रत्येक परिवर्तन के बाद पूर्वावलोकन पर नज़र रखें। + कार्य के आधार पर चमक, कंट्रास्ट, तीक्ष्णता, धुंधलापन, रंग या मास्क को समायोजित करें। + केवल तभी निर्यात करें जब पूर्वावलोकन आपके लक्षित डिवाइस, दस्तावेज़ या सोशल प्लेटफ़ॉर्म से मेल खाता हो। + पहले पूर्वावलोकन करें + भारी संपादन, रूपांतरण, या सफ़ाई उपकरण चुनने से पहले छवियों का निरीक्षण करें + जाँचें कि आपको वास्तव में क्या प्राप्त हुआ + छवि पूर्वावलोकन तब उपयोगी होता है जब कोई फ़ाइल चैट, क्लाउड स्टोरेज या किसी अन्य ऐप से आती है और आप निश्चित नहीं हैं कि इसमें क्या है। सबसे पहले आयाम, पारदर्शिता, अभिविन्यास और दृश्य गुणवत्ता की जाँच करने से सही अनुवर्ती टूल चुनने में मदद मिलती है। + जब आपको कई छवियों के साथ क्या करना है यह तय करने से पहले उनका निरीक्षण करने की आवश्यकता हो तो छवि पूर्वावलोकन खोलें। + रोटेशन समस्याओं, पारदर्शी क्षेत्रों, संपीड़न कलाकृतियों और अप्रत्याशित रूप से बड़े आयामों को देखें। + आकार बदलने, क्रॉप करने, तुलना करने, EXIF ​​या बैकग्राउंड रिमूवर पर तभी आगे बढ़ें जब आपको पता चल जाए कि क्या ठीक करने की जरूरत है। + पृष्ठभूमि हटाएँ या पुनर्स्थापित करें + पृष्ठभूमि को स्वचालित रूप से मिटाएं या पुनर्स्थापना टूल के साथ किनारों को मैन्युअल रूप से परिष्कृत करें + विषय को रखें, बाकी को हटा दें + जब आप स्वचालित चयन को सावधानीपूर्वक मैन्युअल सफ़ाई के साथ जोड़ते हैं तो पृष्ठभूमि हटाना सबसे अच्छा काम करता है। अंतिम निर्यात प्रारूप मास्क जितना ही मायने रखता है, क्योंकि पूर्वावलोकन सही दिखने पर भी जेपीईजी पारदर्शी क्षेत्रों को समतल कर देगा। + यदि विषय पृष्ठभूमि से स्पष्ट रूप से अलग हो गया है तो स्वचालित निष्कासन से प्रारंभ करें। + बालों, कोनों, छायाओं और छोटे विवरणों को ठीक करने के लिए ज़ूम इन करें और मिटाने और पुनर्स्थापित करने के बीच स्विच करें। + जब आपको अंतिम फ़ाइल में पारदर्शिता की आवश्यकता हो तो PNG या WebP के रूप में सहेजें। + ड्रा करें, चिह्नित करें और वॉटरमार्क करें + तीर, नोट्स, हाइलाइट्स, हस्ताक्षर या बार-बार वॉटरमार्क ओवरले जोड़ें + दृश्यमान जानकारी जोड़ें + मार्कअप टूल एक छवि को समझाने में मदद करते हैं, जबकि वॉटरमार्किंग निर्यात की गई फ़ाइलों को ब्रांड या संरक्षित करने में मदद करता है। + जब आपको फ्रीहैंड नोट्स, आकार, हाइलाइटिंग या त्वरित एनोटेशन की आवश्यकता हो तो ड्रा का उपयोग करें। + वॉटरमार्किंग का उपयोग तब करें जब आउटपुट पर एक ही टेक्स्ट या छवि चिह्न लगातार दिखाई दे। + निर्यात करने से पहले अपारदर्शिता, स्थिति और पैमाने की जाँच करें ताकि निशान दिखाई दे लेकिन ध्यान भटकाने वाला न हो। + डिफ़ॉल्ट ड्रा करें + तेज़ मार्कअप के लिए लाइन की चौड़ाई, रंग, पथ मोड, ओरिएंटेशन लॉक और आवर्धक सेट करें + ड्राइंग को पूर्वानुमेय बनाएं + जब आप स्क्रीनशॉट पर टिप्पणी करते हैं, दस्तावेज़ों को चिह्नित करते हैं, या छवियों पर अक्सर हस्ताक्षर करते हैं तो ड्रॉ सेटिंग्स सहायक होती हैं। डिफ़ॉल्ट सेटअप समय को कम करते हैं, जबकि आवर्धक और ओरिएंटेशन लॉक छोटे स्क्रीन पर सटीक संपादन को आसान बनाते हैं। + डिफ़ॉल्ट लाइन चौड़ाई सेट करें और उन मानों के अनुसार रंग बनाएं जिनका उपयोग आप तीर, हाइलाइट्स या हस्ताक्षर के लिए सबसे अधिक करते हैं। + जब आप आमतौर पर एक ही प्रकार के स्ट्रोक, आकार या मार्कअप बनाते हैं तो डिफ़ॉल्ट पथ मोड का उपयोग करें। + जब उंगली इनपुट से छोटे विवरणों को सटीक रूप से रखना मुश्किल हो जाता है तो मैग्निफायर या ओरिएंटेशन लॉक सक्षम करें। + बहु-छवि लेआउट + स्क्रीनशॉट, स्कैन या तुलना स्ट्रिप्स को व्यवस्थित करने से पहले सही मल्टी-इमेज टूल चुनें + सही लेआउट टूल का उपयोग करें + ये उपकरण समान लगते हैं, लेकिन प्रत्येक को एक अलग प्रकार के मल्टी-इमेज आउटपुट के लिए ट्यून किया गया है। पहले सही का चयन करने से उन लेआउट नियंत्रणों से लड़ने से बचा जा सकता है जो एक अलग परिणाम के लिए डिज़ाइन किए गए थे। + एक रचना में कई छवियों के साथ डिज़ाइन किए गए लेआउट के लिए कोलाज मेकर का उपयोग करें। + जब छवियां एक लंबी ऊर्ध्वाधर या क्षैतिज पट्टी बन जाएं तो इमेज स्टिचिंग या स्टैकिंग का उपयोग करें। + जब एक बड़ी छवि को कई छोटे टुकड़ों में बदलने की आवश्यकता हो तो छवि विभाजन का उपयोग करें। + छवि प्रारूप परिवर्तित करें + पिक्सेल को मैन्युअल रूप से संपादित किए बिना JPEG, PNG, WebP, AVIF, JXL और अन्य प्रतियां बनाएं + कार्य के लिए प्रारूप चुनें + फ़ोटो, स्क्रीनशॉट, पारदर्शिता, संपीड़न या अनुकूलता के लिए अलग-अलग प्रारूप बेहतर हैं। रूपांतरण सबसे सुरक्षित है जब आप समझते हैं कि गंतव्य ऐप क्या समर्थन करता है और क्या स्रोत में अल्फा, एनीमेशन या मेटाडेटा है जिसे आप रखना चाहते हैं। + संगत फ़ोटो के लिए JPEG, दोषरहित छवियों और अल्फ़ा के लिए PNG और कॉम्पैक्ट साझाकरण के लिए WebP का उपयोग करें। + जब प्रारूप इसका समर्थन करता है तो गुणवत्ता समायोजित करें; निम्न मान आकार को कम करते हैं लेकिन कलाकृतियाँ जोड़ सकते हैं। + जब तक आप सत्यापित नहीं कर लेते कि परिवर्तित फ़ाइलें लक्ष्य ऐप में सही ढंग से खुली हैं, तब तक मूल फ़ाइलें अपने पास रखें। + EXIF और गोपनीयता की जाँच करें + संवेदनशील फ़ोटो साझा करने से पहले मेटाडेटा देखें, संपादित करें या हटाएँ + छुपे हुए छवि डेटा को नियंत्रित करें + EXIF में कैमरा डेटा, दिनांक, स्थान, ओरिएंटेशन और अन्य विवरण शामिल हो सकते हैं जो छवि में दिखाई नहीं देते हैं। सार्वजनिक साझाकरण, समर्थन रिपोर्ट, मार्केटप्लेस लिस्टिंग, या किसी निजी स्थान को प्रकट करने वाली किसी भी तस्वीर से पहले इसकी जांच करना उचित है। + फ़ोटो साझा करने से पहले EXIF ​​टूल खोलें जिनमें स्थान या निजी डिवाइस की जानकारी हो सकती है। + संवेदनशील फ़ील्ड हटाएं या दिनांक, ओरिएंटेशन, लेखक या कैमरा डेटा जैसे ग़लत टैग संपादित करें। + एक नई प्रति सहेजें और जब गोपनीयता मायने रखती है तो मूल प्रति के बजाय उस प्रति को साझा करें। + ऑटो EXIF ​​सफ़ाई + तारीखों को संरक्षित किया जाना चाहिए या नहीं, यह तय करते समय मेटाडेटा को डिफ़ॉल्ट रूप से हटा दें + गोपनीयता को डिफ़ॉल्ट बनाएं + EXIF सेटिंग्स समूह तब उपयोगी होता है जब आप चाहते हैं कि गोपनीयता सफाई प्रत्येक निर्यात के लिए याद रखने के बजाय स्वचालित रूप से हो। दिनांक समय रखना एक अलग निर्णय है क्योंकि दिनांक क्रमबद्ध करने के लिए उपयोगी हो सकते हैं लेकिन साझा करने के लिए संवेदनशील हो सकते हैं। + जब आपके अधिकांश निर्यात सार्वजनिक या अर्ध-सार्वजनिक साझाकरण के लिए हों तो हमेशा-स्पष्ट EXIF ​​सक्षम करें। + दिनांक समय रखें तभी सक्षम करें जब प्रत्येक टाइमस्टैम्प को हटाने की तुलना में कैप्चर समय को संरक्षित करना अधिक महत्वपूर्ण हो। + उन वन-ऑफ़ फ़ाइलों के लिए मैन्युअल EXIF ​​संपादन का उपयोग करें जहाँ आपको कुछ फ़ील्ड रखने और अन्य को हटाने की आवश्यकता होती है। + फ़ाइल नाम नियंत्रित करें + उपसर्ग, प्रत्यय, टाइमस्टैम्प, प्रीसेट, चेकसम और अनुक्रम संख्याओं का उपयोग करें + निर्यात को ढूंढना आसान बनाएं + एक अच्छा फ़ाइल नाम पैटर्न बैचों को क्रमबद्ध करने में मदद करता है और आकस्मिक ओवरराइट को रोकता है। फ़ाइल नाम सेटिंग्स विशेष रूप से उपयोगी होती हैं जब निर्यात स्रोत फ़ोल्डर में वापस जाते हैं, जहां समान नाम जल्दी से भ्रमित हो सकते हैं। + सेटिंग्स में फ़ाइल नाम उपसर्ग, प्रत्यय, टाइमस्टैम्प, मूल नाम, पूर्व निर्धारित जानकारी या अनुक्रम विकल्प सेट करें। + उन बैचों के लिए अनुक्रम संख्याओं का उपयोग करें जहां क्रम मायने रखता है, जैसे पेज, फ़्रेम या कोलाज। + ओवरराइट तभी सक्षम करें जब आप सुनिश्चित हों कि मौजूदा फ़ाइलों को बदलना वर्तमान फ़ोल्डर के लिए सुरक्षित है। + फ़ाइलें अधिलेखित करें + आउटपुट को मिलते-जुलते नामों से तभी बदलें जब आपका वर्कफ़्लो जानबूझकर विनाशकारी हो + ओवरराइट मोड का उपयोग सावधानीपूर्वक करें + फ़ाइलों को अधिलेखित करने से नामकरण विवादों को संभालने का तरीका बदल जाता है। यह एक ही फ़ोल्डर में बार-बार निर्यात के लिए उपयोगी है, लेकिन यदि आपका फ़ाइल नाम पैटर्न फिर से वही नाम उत्पन्न करता है तो यह पहले के परिणामों को भी बदल सकता है। + केवल उन फ़ोल्डरों के लिए ओवरराइट सक्षम करें जहां पुरानी जेनरेट की गई फ़ाइलों को बदलना अपेक्षित और पुनर्प्राप्ति योग्य है। + मूल, क्लाइंट फ़ाइलें, वन-टाइम स्कैन, या बैकअप के बिना कुछ भी निर्यात करते समय ओवरराइट अक्षम रखें। + एक जानबूझकर फ़ाइल नाम पैटर्न के साथ ओवरराइट को संयोजित करें ताकि केवल इच्छित आउटपुट को प्रतिस्थापित किया जा सके। + फ़ाइल नाम पैटर्न + मूल नाम, उपसर्ग, प्रत्यय, टाइमस्टैम्प, प्रीसेट, स्केल मोड और चेकसम को मिलाएं + ऐसे नाम बनाएं जो आउटपुट की व्याख्या करें + फ़ाइल नाम पैटर्न सेटिंग्स निर्यात की गई फ़ाइलों को उनके साथ क्या हुआ इसके उपयोगी इतिहास में बदल सकती हैं। यह बैचों में मायने रखता है क्योंकि फ़ोल्डर दृश्य ही एकमात्र स्थान हो सकता है जहां आप मूल आकार, पूर्व निर्धारित, प्रारूप और प्रसंस्करण क्रम को अलग कर सकते हैं। + जब आपको मैन्युअल सॉर्टिंग के लिए पढ़ने योग्य नामों की आवश्यकता हो तो मूल फ़ाइल नाम, उपसर्ग, प्रत्यय और टाइमस्टैम्प का उपयोग करें। + जब आउटपुट को यह दस्तावेज करना होगा कि वे कैसे उत्पादित किए गए हैं, तो प्रीसेट, स्केल मोड, फ़ाइल आकार या चेकसम जानकारी जोड़ें। + वर्कफ़्लो के लिए यादृच्छिक नामों से बचें जहां फ़ाइलों को मूल या पृष्ठ क्रम के साथ जोड़े रखने की आवश्यकता होती है। + चुनें कि फ़ाइलें कहाँ सहेजी गई हैं + फ़ोल्डर, मूल-फ़ोल्डर सहेजना और एक बार सहेजने वाले स्थान सेट करके निर्यात खोने से बचें + आउटपुट को पूर्वानुमानित रखें + जब आप कई फ़ाइलें संसाधित करते हैं या क्लाउड प्रदाताओं से फ़ाइलें साझा करते हैं तो सेव व्यवहार सबसे अधिक मायने रखता है। फ़ोल्डर सेटिंग्स तय करती हैं कि आउटपुट एक ज्ञात स्थान पर एकत्रित होंगे, मूल के बगल में दिखाई देंगे, या किसी विशिष्ट कार्य के लिए अस्थायी रूप से कहीं और जाएंगे। + यदि आप हमेशा एक ही स्थान पर निर्यात चाहते हैं तो एक डिफ़ॉल्ट बचत फ़ोल्डर सेट करें। + क्लीनअप वर्कफ़्लो के लिए सेव-टू-ओरिजिनल-फ़ोल्डर का उपयोग करें जहां आउटपुट स्रोत फ़ाइल के पास रहना चाहिए। + जब किसी एकल कार्य को अपना डिफ़ॉल्ट बदले बिना एक अलग गंतव्य की आवश्यकता हो तो एक बार सेव लोकेशन का उपयोग करें। + वन-टाइम सेव फोल्डर + अपना सामान्य गंतव्य बदले बिना अगले निर्यात को एक अस्थायी फ़ोल्डर में भेजें + विशेष कार्यों को अलग रखें + वन-टाइम सेव लोकेशन अस्थायी प्रोजेक्ट्स, क्लाइंट फ़ोल्डर्स, क्लीनअप सेशन या एक्सपोर्ट के लिए उपयोगी है, जिन्हें आपके नियमित इमेज टूलबॉक्स फ़ोल्डर के साथ मिश्रित नहीं होना चाहिए। यह आपके डिफ़ॉल्ट सेटअप को दोबारा लिखे बिना एक अल्पकालिक गंतव्य देता है। + कोई कार्य शुरू करने से पहले एक वन-टाइम फ़ोल्डर चुनें जो आपके सामान्य निर्यात स्थान से बाहर हो। + इसे छोटी परियोजनाओं, साझा फ़ोल्डरों या बैचों के लिए उपयोग करें जहां आउटपुट को एक साथ समूहीकृत रहना चाहिए। + कार्य के बाद डिफ़ॉल्ट फ़ोल्डर पर वापस लौटें ताकि बाद में निर्यात अप्रत्याशित रूप से अस्थायी स्थान पर न पहुँचे। + गुणवत्ता और फ़ाइल आकार को संतुलित करें + अनुमान लगाने के बजाय समझें कि आयाम, प्रारूप या गुणवत्ता कब बदलनी है + जानबूझकर फ़ाइलें सिकोड़ें + फ़ाइल का आकार छवि आयाम, प्रारूप, गुणवत्ता, पारदर्शिता और सामग्री जटिलता पर निर्भर करता है। यदि कोई फ़ाइल अभी भी बहुत भारी है, तो आयाम बदलने से आमतौर पर गुणवत्ता को बार-बार कम करने से अधिक मदद मिलती है। + जब छवि गंतव्य द्वारा प्रदर्शित की जा सकने वाली क्षमता से बड़ी हो तो पहले आयामों का आकार बदलें। + केवल हानिपूर्ण प्रारूपों के लिए निम्न गुणवत्ता और निर्यात के बाद टेक्स्ट, किनारों, ग्रेडिएंट्स और चेहरों की जाँच करें। + जब गुणवत्ता परिवर्तन पर्याप्त न हो तो प्रारूप बदलें, उदाहरण के लिए साझा करने के लिए वेबपी या स्पष्ट पारदर्शी परिसंपत्तियों के लिए पीएनजी। + एनिमेटेड प्रारूपों के साथ काम करें + जब किसी फ़ाइल में एक बिटमैप के बजाय फ़्रेम हों तो GIF, APNG, WebP और JXL टूल का उपयोग करें + प्रत्येक छवि को स्थिर न मानें + एनिमेटेड प्रारूपों के लिए ऐसे टूल की आवश्यकता होती है जो फ़्रेम, अवधि और अनुकूलता को समझते हों। + जब आपको उन प्रारूपों के लिए फ़्रेम-स्तरीय संचालन की आवश्यकता हो तो GIF या APNG टूल का उपयोग करें। + जब आपको आधुनिक संपीड़न या एनिमेटेड वेबपी आउटपुट की आवश्यकता हो तो वेबपी टूल का उपयोग करें। + साझा करने से पहले एनीमेशन समय का पूर्वावलोकन करें क्योंकि कुछ प्लेटफ़ॉर्म एनिमेटेड छवियों को परिवर्तित या फ़्लैट करते हैं। + आउटपुट की तुलना करें और पूर्वावलोकन करें + निर्यात रखने से पहले परिवर्तन, फ़ाइल आकार और दृश्य अंतर का निरीक्षण करें + साझा करने से पहले सत्यापित करें + तुलना उपकरण संपीड़न कलाकृतियों, गलत रंगों या अवांछित संपादनों को जल्दी पकड़ने में मदद करते हैं। + जब आप दो संस्करणों का एक साथ निरीक्षण करना चाहें तो तुलना खोलें। + किनारों, टेक्स्ट, ग्रेडिएंट्स और पारदर्शी क्षेत्रों में ज़ूम करें जहां समस्याओं से बचना सबसे आसान है। + यदि आउटपुट बहुत भारी या धुंधला है, तो पिछले टूल पर वापस लौटें और गुणवत्ता या प्रारूप समायोजित करें। + पीडीएफ टूल्स हब का उपयोग करें + पीडीएफ फाइलों को मर्ज करें, विभाजित करें, घुमाएं, पेज हटाएं, संपीड़ित करें, संरक्षित करें, ओसीआर और वॉटरमार्क करें + एक पीडीएफ ऑपरेशन चुनें + पीडीएफ हब दस्तावेज़ क्रियाओं को एक प्रवेश बिंदु के पीछे समूहित करता है ताकि आप सटीक ऑपरेशन चुन सकें। जब फ़ाइल पहले से ही एक पीडीएफ हो तो वहां से प्रारंभ करें, और जब आपका स्रोत अभी भी छवियों का एक सेट हो तो इमेजेज टू पीडीएफ या दस्तावेज़ स्कैनर का उपयोग करें। + पीडीएफ टूल्स खोलें और वह ऑपरेशन चुनें जो आपके लक्ष्य से मेल खाता हो। + स्रोत पीडीएफ या छवियों का चयन करें, फिर पृष्ठ क्रम, रोटेशन, सुरक्षा, या ओसीआर विकल्पों की समीक्षा करें। + जेनरेट की गई पीडीएफ को सेव करें और पृष्ठ संख्या, क्रम और पठनीयता की पुष्टि करने के लिए इसे एक बार खोलें। + छवियों को पीडीएफ में बदलें + स्कैन, स्क्रीनशॉट, रसीदें या फ़ोटो को एक साझा करने योग्य दस्तावेज़ में संयोजित करें + एक साफ़ दस्तावेज़ बनाएँ + जब छवियों को लगातार काटा, क्रमबद्ध और आकार दिया जाता है तो वे बेहतर पीडीएफ पेज बन जाते हैं। छवि सूची को पेज स्टैक की तरह समझें: प्रत्येक रोटेशन, मार्जिन और पेज-आकार का चयन इस बात को प्रभावित करता है कि अंतिम दस्तावेज़ कितना पठनीय लगता है। + पृष्ठ के किनारे, छाया या ओरिएंटेशन गलत होने पर सबसे पहले छवियों को काटें या घुमाएँ। + इच्छित पृष्ठ क्रम में छवियों का चयन करें और यदि उपकरण उन्हें प्रदान करता है तो पृष्ठ आकार या मार्जिन समायोजित करें। + पीडीएफ निर्यात करें, फिर भेजने से पहले जांच लें कि सभी पृष्ठ पढ़ने योग्य हैं या नहीं। + दस्तावेज़ स्कैन करें + कागज़ के पन्नों को कैप्चर करें और उन्हें पीडीएफ, ओसीआर या साझा करने के लिए तैयार करें + पढ़ने योग्य पेज कैप्चर करें + दस्तावेज़ स्कैनिंग सपाट पृष्ठों, अच्छी रोशनी और सही परिप्रेक्ष्य के साथ सबसे अच्छा काम करती है। ओसीआर या पीडीएफ निर्यात से पहले एक साफ स्कैन से समय की बचत होती है क्योंकि पाठ की पहचान और संपीड़न दोनों पृष्ठ की गुणवत्ता पर निर्भर करते हैं। + दस्तावेज़ को पर्याप्त रोशनी और न्यूनतम छाया वाली विपरीत सतह पर रखें। + पहचाने गए कोनों को समायोजित करें या मैन्युअल रूप से क्रॉप करें ताकि पृष्ठ फ़्रेम में साफ़-साफ़ भर जाए। + छवियों या पीडीएफ के रूप में निर्यात करें, फिर यदि आपको चयन योग्य टेक्स्ट की आवश्यकता है तो ओसीआर चलाएं। + पीडीएफ फाइलों को सुरक्षित रखें या अनलॉक करें + पासवर्ड का सावधानीपूर्वक उपयोग करें और जब संभव हो तो एक संपादन योग्य स्रोत प्रति अपने पास रखें + पीडीएफ एक्सेस को सुरक्षित रूप से संभालें + सुरक्षा उपकरण नियंत्रित साझाकरण के लिए उपयोगी हैं, लेकिन पासवर्ड खोना आसान है और पुनर्प्राप्त करना कठिन है। वितरण के लिए प्रतियों को सुरक्षित रखें, असुरक्षित स्रोत फ़ाइलों को अलग रखें, और कुछ भी हटाने से पहले परिणाम सत्यापित करें। + अपने एकमात्र स्रोत दस्तावेज़ को नहीं, बल्कि पीडीएफ की एक प्रति को सुरक्षित रखें। + संरक्षित फ़ाइल साझा करने से पहले पासवर्ड को किसी सुरक्षित स्थान पर संग्रहीत करें। + केवल उन फ़ाइलों के लिए अनलॉक का उपयोग करें जिन्हें आपको संपादित करने की अनुमति है, फिर सत्यापित करें कि अनलॉक की गई कॉपी सही ढंग से खुलती है। + पीडीएफ पेज व्यवस्थित करें + मर्ज करें, विभाजित करें, पुनर्व्यवस्थित करें, घुमाएं, क्रॉप करें, पेज हटाएं और जानबूझकर पेज नंबर जोड़ें + सजावट से पहले दस्तावेज़ संरचना ठीक करें + पीडीएफ पेज टूल का उपयोग उसी क्रम में करना सबसे आसान है, जिस क्रम में आप एक पेपर दस्तावेज़ तैयार करते हैं: पेज इकट्ठा करें, गलत पेज हटाएं, उन्हें क्रम में रखें, सही ओरिएंटेशन, फिर पेज नंबर या वॉटरमार्क जैसे अंतिम विवरण जोड़ें। + जब दस्तावेज़ अभी भी कई फ़ाइलों में विभाजित हो तो पहले मर्ज या इमेज-टू-पीडीएफ का उपयोग करें। + पृष्ठ संख्या, हस्ताक्षर या वॉटरमार्क जोड़ने से पहले पृष्ठों को पुनर्व्यवस्थित करें, घुमाएँ, काटें या हटाएँ। + संरचनात्मक संपादन के बाद अंतिम पीडीएफ का पूर्वावलोकन करें क्योंकि एक गायब या घुमाए गए पृष्ठ को बाद में नोटिस करना मुश्किल हो सकता है। + पीडीएफ एनोटेशन + जब दर्शक अलग-अलग टिप्पणियाँ और चिह्न दिखाते हैं तो एनोटेशन हटा दें या उन्हें समतल कर दें + जो दृश्यमान रहता है उसे नियंत्रित करें + एनोटेशन टिप्पणियाँ, हाइलाइट्स, चित्र, फॉर्म चिह्न या अन्य अतिरिक्त पीडीएफ ऑब्जेक्ट हो सकते हैं। कुछ दर्शक उन्हें अलग तरह से प्रदर्शित करते हैं, इसलिए उन्हें हटाने या समतल करने से साझा किए गए दस्तावेज़ अधिक पूर्वानुमानित हो सकते हैं। + जब साझा कॉपी में टिप्पणियाँ, हाइलाइट्स या समीक्षा चिह्न शामिल नहीं किए जाने चाहिए तो एनोटेशन हटा दें। + जब दृश्य चिह्न पृष्ठ पर बने रहें तो फ़्लैट करें लेकिन अब संपादन योग्य एनोटेशन की तरह व्यवहार न करें। + एक मूल प्रति रखें क्योंकि एनोटेशन को हटाने या समतल करने से इसे उलटना मुश्किल हो सकता है। + पाठ को पहचानें + चयन योग्य ओसीआर इंजन और भाषाओं के साथ छवियों से टेक्स्ट निकालें + बेहतर OCR परिणाम प्राप्त करें + ओसीआर सटीकता छवि तीक्ष्णता, भाषा डेटा, कंट्रास्ट और पेज ओरिएंटेशन पर निर्भर करती है। सबसे अच्छे परिणाम आम तौर पर पहले छवि तैयार करने से आते हैं: शोर को दूर करना, सीधा घुमाना, पठनीयता बढ़ाना, फिर पाठ को पहचानना। + छवि को टेक्स्ट क्षेत्र में काटें और पहचानने से पहले उसे सीधा घुमाएँ। + यदि ऐप अनुरोध करता है तो सही भाषा चुनें और भाषा डेटा डाउनलोड करें। + मान्यता प्राप्त पाठ को कॉपी या साझा करें, फिर मैन्युअल रूप से नाम, संख्या और विराम चिह्न जांचें। + OCR भाषा डेटा चुनें + स्क्रिप्ट, भाषाओं और डाउनलोड किए गए ओसीआर मॉडल का मिलान करके पहचान में सुधार करें + OCR को टेक्स्ट से मिलाएँ + गलत ओसीआर भाषा अच्छी तस्वीरों को खराब पहचान की तरह बना सकती है। भाषा डेटा स्क्रिप्ट, विराम चिह्न, संख्याओं और मिश्रित दस्तावेज़ों के लिए मायने रखता है, इसलिए फ़ोन यूआई की भाषा के बजाय छवि में दिखाई देने वाली भाषा चुनें। + वह भाषा या स्क्रिप्ट चुनें जो छवि में दिखाई देती है, न कि केवल फ़ोन भाषा। + ऑफ़लाइन काम करने या किसी बैच को संसाधित करने से पहले लापता डेटा डाउनलोड करें। + मिश्रित भाषा वाले दस्तावेज़ों के लिए, पहले सबसे महत्वपूर्ण भाषा चलाएँ और मैन्युअल रूप से नाम और संख्याएँ जाँचें। + खोजने योग्य पीडीएफ़ + ओसीआर टेक्स्ट को वापस फाइलों में लिखें ताकि स्कैन किए गए पेजों को खोजा और कॉपी किया जा सके + स्कैन को खोजने योग्य दस्तावेज़ों में बदलें + OCR टेक्स्ट को क्लिपबोर्ड पर कॉपी करने के अलावा और भी बहुत कुछ कर सकता है। जब आप किसी फ़ाइल या खोजने योग्य पीडीएफ में मान्यता प्राप्त पाठ लिखते हैं, तो पृष्ठ की छवि दृश्यमान रहती है जबकि पाठ को खोजना, चयन करना, अनुक्रमित करना या संग्रहीत करना आसान हो जाता है। + स्कैन किए गए दस्तावेज़ों के लिए खोजने योग्य पीडीएफ आउटपुट का उपयोग करें जो अभी भी मूल पृष्ठों की तरह दिखना चाहिए। + जब आपको केवल निकाले गए टेक्स्ट की आवश्यकता हो और पृष्ठ छवि की परवाह न हो तो राइट-टू-फ़ाइल का उपयोग करें। + महत्वपूर्ण संख्याओं, नामों और तालिकाओं को मैन्युअल रूप से जांचें क्योंकि OCR टेक्स्ट खोजा जा सकता है लेकिन फिर भी अपूर्ण है। + QR कोड स्कैन करें + उपलब्ध होने पर कैमरा इनपुट या सहेजी गई छवियों से क्यूआर सामग्री पढ़ें + कोड सुरक्षित रूप से पढ़ें + क्यूआर कोड में लिंक, वाई-फाई डेटा, संपर्क, टेक्स्ट या अन्य पेलोड हो सकते हैं। + स्कैन क्यूआर कोड खोलें और कोड को तेज, सपाट और पूरी तरह से फ्रेम के अंदर रखें। + लिंक खोलने या संवेदनशील डेटा की प्रतिलिपि बनाने से पहले डिकोड की गई सामग्री की समीक्षा करें। + यदि स्कैनिंग विफल हो जाती है, तो प्रकाश व्यवस्था में सुधार करें, कोड को क्रॉप करें, या उच्च-रिज़ॉल्यूशन स्रोत छवि का प्रयास करें। + बेस64 और चेकसम का उपयोग करें + छवियों को टेक्स्ट के रूप में एन्कोड करें, टेक्स्ट को वापस छवियों में डिकोड करें और फ़ाइल की अखंडता को सत्यापित करें + फ़ाइलों और टेक्स्ट के बीच जाएँ + बेस64 छोटी छवि पेलोड के लिए उपयोगी है, जबकि चेकसम यह पुष्टि करने में मदद करते हैं कि फ़ाइलें नहीं बदलीं। ये उपकरण तकनीकी वर्कफ़्लोज़, समर्थन रिपोर्ट, फ़ाइल स्थानांतरण और उन स्थानों पर सबसे अधिक सहायक होते हैं जहाँ बाइनरी फ़ाइलों को अस्थायी रूप से टेक्स्ट बनने की आवश्यकता होती है। + जब वर्कफ़्लो को बाइनरी फ़ाइल के बजाय टेक्स्ट की आवश्यकता होती है तो एक छोटी छवि को एन्कोड करने के लिए बेस 64 टूल का उपयोग करें। + बेस64 को केवल विश्वसनीय स्रोतों से डिकोड करें और सहेजने से पहले पूर्वावलोकन सत्यापित करें। + जब आपको निर्यात की गई फ़ाइलों की तुलना करने या अपलोड/डाउनलोड की पुष्टि करने की आवश्यकता हो तो चेकसम टूल का उपयोग करें। + डेटा को पैकेज करना या सुरक्षित करना + फ़ाइलों को बंडल करने या टेक्स्ट डेटा को बदलने के लिए ज़िप और सिफर टूल का उपयोग करें + संबंधित फाइलों को एक साथ रखें + पैकेजिंग उपकरण तब सहायक होते हैं जब कई आउटपुट एक फ़ाइल के रूप में यात्रा करने चाहिए। जब आपको पूरा परिणाम सेट भेजने की आवश्यकता होती है तो वे जेनरेट की गई छवियों, पीडीएफ, लॉग और मेटाडेटा को एक साथ रखने के लिए भी अच्छे होते हैं। + जब आपको कई निर्यातित छवियाँ या दस्तावेज़ एक साथ भेजने की आवश्यकता हो तो ज़िप का उपयोग करें। + सिफर टूल का उपयोग केवल तभी करें जब आप चयनित विधि को समझते हैं और आवश्यक कुंजियों को सुरक्षित रूप से संग्रहीत कर सकते हैं। + स्रोत फ़ाइलों को हटाने से पहले बनाए गए संग्रह या रूपांतरित पाठ का परीक्षण करें। + नामों में चेकसम + जब विशिष्टता और अखंडता पठनीयता से अधिक मायने रखती है तो चेकसम-आधारित फ़ाइल नामों का उपयोग करें + फ़ाइलों को सामग्री के आधार पर नाम दें + चेकसम फ़ाइल नाम तब उपयोगी होते हैं जब निर्यात में डुप्लिकेट दृश्यमान नाम हो सकते हैं या जब आपको यह साबित करने की आवश्यकता होती है कि दो फ़ाइलें समान हैं। वे मैन्युअल ब्राउज़िंग के लिए कम अनुकूल हैं, इसलिए तकनीकी अभिलेखागार और सत्यापन वर्कफ़्लो के लिए उनका उपयोग करें। + जब सामग्री की पहचान मानव-पठनीय शीर्षक से अधिक महत्वपूर्ण हो तो चेकसम को फ़ाइल नाम के रूप में सक्षम करें। + इसका उपयोग अभिलेखागार, डिडुप्लीकेशन, दोहराए जाने योग्य निर्यात, या फ़ाइलों के लिए करें जिन्हें अपलोड और फिर से डाउनलोड किया जा सकता है। + चेकसम नामों को फ़ोल्डरों या मेटाडेटा के साथ जोड़ें जब लोगों को अभी भी यह समझने की आवश्यकता हो कि फ़ाइल क्या दर्शाती है। + छवियों से रंग चुनें + पिक्सेल का नमूना लें, रंग कोड कॉपी करें, और पैलेट या डिज़ाइन में रंगों का पुन: उपयोग करें + सटीक रंग का नमूना लें + रंग चुनना किसी डिज़ाइन से मिलान करने, ब्रांड रंग निकालने या छवि मानों की जांच करने के लिए उपयोगी है। ज़ूम करना मायने रखता है क्योंकि एंटीएलियासिंग, छाया और संपीड़न पड़ोसी पिक्सेल को आपके अपेक्षित रंग से थोड़ा अलग बना सकते हैं। + छवि से रंग चुनें खोलें और अपने इच्छित रंग के साथ एक स्रोत छवि चुनें। + छोटे विवरणों का नमूना लेने से पहले ज़ूम इन करें ताकि आस-पास के पिक्सेल परिणाम को प्रभावित न करें। + रंग को अपने गंतव्य के लिए आवश्यक प्रारूप में कॉपी करें, जैसे HEX, RGB, या HSV। + पैलेट बनाएं और रंग लाइब्रेरी का उपयोग करें + पैलेट निकालें, सहेजे गए रंग ब्राउज़ करें, और सुसंगत विज़ुअल सिस्टम रखें + एक पुन: प्रयोज्य पैलेट बनाएं + पैलेट निर्यातित ग्राफ़िक्स, वॉटरमार्क और रेखाचित्रों को दृष्टिगत रूप से सुसंगत बनाए रखने में मदद करते हैं। वे विशेष रूप से तब उपयोगी होते हैं जब आप स्क्रीनशॉट को बार-बार एनोटेट करते हैं, ब्रांड संपत्ति तैयार करते हैं, या कई टूल में समान रंगों की आवश्यकता होती है। + जब आप पहले से ही मान जानते हों तो किसी छवि से पैलेट बनाएं या मैन्युअल रूप से रंग जोड़ें। + महत्वपूर्ण रंगों को नाम दें या व्यवस्थित करें ताकि आप उन्हें बाद में फिर से पा सकें। + ड्रा, वॉटरमार्क, ग्रेडिएंट, एसवीजी, या बाहरी डिज़ाइन टूल में कॉपी किए गए मानों का उपयोग करें। + ग्रेडिएंट बनाएं + पृष्ठभूमि, प्लेसहोल्डर और दृश्य प्रयोगों के लिए ग्रेडिएंट छवियां बनाएं + रंग परिवर्तन को नियंत्रित करें + ग्रेडिएंट टूल तब उपयोगी होते हैं जब आपको किसी मौजूदा छवि को संपादित करने के बजाय जेनरेट की गई छवि की आवश्यकता होती है। वे बाहरी डिज़ाइन टूल के लिए साफ़ पृष्ठभूमि, प्लेसहोल्डर, वॉलपेपर, मास्क या सरल संपत्ति बन सकते हैं। + ग्रेडिएंट प्रकार चुनें और पहले महत्वपूर्ण रंग सेट करें। + लक्ष्य उपयोग के मामले के लिए दिशा, स्टॉप, चिकनाई और आउटपुट आकार समायोजित करें। + ऐसे प्रारूप में निर्यात करें जो गंतव्य से मेल खाता हो, आमतौर पर स्वच्छ उत्पन्न ग्राफिक्स के लिए पीएनजी। + रंग अभिगम्यता + जब यूआई को पढ़ना मुश्किल हो तो डायनामिक रंग, कंट्रास्ट और कलर-ब्लाइंड विकल्पों का उपयोग करें + रंगों का उपयोग आसान बनाएं + रंग सेटिंग्स आराम, पठनीयता और आप नियंत्रणों को कितनी जल्दी स्कैन कर सकते हैं, प्रभावित करती हैं। यदि टूल चिप्स, स्लाइडर्स, या चयनित राज्यों को अलग करना कठिन लगता है, तो थीम और एक्सेसिबिलिटी विकल्प केवल डार्क मोड को बदलने की तुलना में अधिक उपयोगी हो सकते हैं। + जब आप चाहते हैं कि ऐप वर्तमान वॉलपेपर पैलेट का अनुसरण करे तो गतिशील रंग आज़माएँ। + जब बटन और सतहें पर्याप्त रूप से अलग न दिखें तो कंट्रास्ट बढ़ाएँ या योजना बदलें। + यदि कुछ अवस्थाओं या उच्चारणों में अंतर करना कठिन हो तो रंग-अंध योजना विकल्पों का उपयोग करें। + अल्फ़ा पृष्ठभूमि + पारदर्शी पीएनजी, वेबपी और समान आउटपुट के आसपास उपयोग किए गए पूर्वावलोकन या रंग को नियंत्रित करें + पारदर्शी पूर्वावलोकन को समझें + अल्फ़ा प्रारूपों के लिए पृष्ठभूमि का रंग पारदर्शी छवियों का निरीक्षण करना आसान बना सकता है, लेकिन यह जानना महत्वपूर्ण है कि क्या आप पूर्वावलोकन/भरण व्यवहार बदल रहे हैं या अंतिम परिणाम को समतल कर रहे हैं। यह स्टिकर, लोगो और पृष्ठभूमि से हटाई गई छवियों के लिए मायने रखता है। + जब पारदर्शी पिक्सल को निर्यात से बचना होगा, तो पहले पीएनजी या वेबपी जैसे अल्फा-सपोर्टिंग प्रारूप का उपयोग करें। + जब ऐप की सतह पर पारदर्शी किनारों को देखना मुश्किल हो तो पृष्ठभूमि रंग सेटिंग्स समायोजित करें। + एक छोटा परीक्षण निर्यात करें और इसे किसी अन्य ऐप में दोबारा खोलें ताकि यह पुष्टि हो सके कि पारदर्शिता या चुनी गई भरण सामग्री सहेजी गई थी या नहीं। + एआई मॉडल का चयन + पहले सबसे बड़े मॉडल को चुनने के बजाय छवि प्रकार और दोष के आधार पर एक मॉडल चुनें + क्षति के साथ मॉडल का मिलान करें + एआई उपकरण विभिन्न ओएनएनएक्स/ओआरटी मॉडल को डाउनलोड या आयात कर सकते हैं, और प्रत्येक मॉडल को एक विशेष प्रकार की समस्या के लिए प्रशिक्षित किया जाता है। जेपीईजी ब्लॉक, एनीमे लाइन क्लीनअप, डीनोइजिंग, बैकग्राउंड रिमूवल, कलराइजेशन या अपस्केलिंग के लिए एक मॉडल गलत छवि प्रकार पर उपयोग किए जाने पर खराब परिणाम दे सकता है। + डाउनलोड करने से पहले मॉडल विवरण पढ़ें; वह मॉडल चुनें जो आपकी वास्तविक समस्या का नाम देता है, जैसे संपीड़न, शोर, हाफ़टोन, धुंधलापन, या पृष्ठभूमि हटाना। + किसी नए छवि प्रकार का परीक्षण करते समय छोटे या अधिक विशिष्ट मॉडल से शुरुआत करें, फिर भारी मॉडल से तुलना तभी करें जब परिणाम पर्याप्त अच्छा न हो। + केवल वही मॉडल रखें जिनका आप वास्तव में उपयोग करते हैं, क्योंकि डाउनलोड किए गए और आयातित मॉडल उल्लेखनीय भंडारण स्थान ले सकते हैं। + आदर्श परिवारों को समझें + मॉडल नाम अक्सर उनके लक्ष्य पर संकेत देते हैं: RealESRGAN-शैली मॉडल अपस्केल, SCUNet और FBCNN मॉडल शोर या संपीड़न को साफ करते हैं, पृष्ठभूमि मॉडल मास्क बनाते हैं, और कलराइज़र ग्रेस्केल इनपुट में रंग जोड़ते हैं। आयातित कस्टम मॉडल शक्तिशाली हो सकते हैं, लेकिन उन्हें अपेक्षित इनपुट/आउटपुट आकार से मेल खाना चाहिए और समर्थित ONNX या ORT प्रारूप का उपयोग करना चाहिए। + जब आपको अधिक पिक्सेल की आवश्यकता हो तो अपस्केलर का उपयोग करें, जब छवि दानेदार हो तो डीनोइज़र का उपयोग करें, और जब संपीड़न ब्लॉक या बैंडिंग मुख्य मुद्दा हो तो आर्टिफैक्ट रिमूवर का उपयोग करें। + केवल विषय पृथक्करण के लिए पृष्ठभूमि मॉडल का उपयोग करें; वे सामान्य वस्तु हटाने या छवि वृद्धि के समान नहीं हैं। + कस्टम मॉडल केवल उन स्रोतों से आयात करें जिन पर आप भरोसा करते हैं और महत्वपूर्ण फ़ाइलों का उपयोग करने से पहले एक छोटी छवि पर उनका परीक्षण करें। + एआई पैरामीटर + गुणवत्ता, गति और मेमोरी को संतुलित करने के लिए चंक आकार, ओवरलैप और समानांतर श्रमिकों को ट्यून करें + स्थिरता के साथ गति को संतुलित करें + चंक आकार नियंत्रित करता है कि मॉडल द्वारा संसाधित किए जाने से पहले छवि के टुकड़े कितने बड़े हैं। ओवरलैप सीमाओं को छिपाने के लिए पड़ोसी हिस्सों को मिश्रित करता है। समानांतर कार्यकर्ता प्रसंस्करण की गति बढ़ा सकते हैं, लेकिन प्रत्येक कार्यकर्ता को मेमोरी की आवश्यकता होती है, इसलिए उच्च मान सीमित रैम वाले फोन पर बड़ी छवियों को अस्थिर बना सकते हैं। + जब आपका उपकरण मॉडल को विश्वसनीय रूप से संभालता है और छवि बड़ी नहीं है तो सहज संदर्भ के लिए बड़े हिस्सों का उपयोग करें। + जब टुकड़ों की सीमाएँ दिखाई देने लगें तो ओवरलैप बढ़ाएँ, लेकिन याद रखें कि अधिक ओवरलैप का मतलब है अधिक दोहराया गया कार्य। + स्थिर परीक्षण के बाद ही समानांतर श्रमिकों को बढ़ाएं; यदि प्रसंस्करण धीमा हो जाता है, उपकरण गर्म हो जाता है, या क्रैश हो जाता है, तो पहले श्रमिकों को कम करें। + खंडित कलाकृतियों से बचें + चंकिंग ऐप को उन छवियों को संसाधित करने देता है जो मॉडल द्वारा एक बार में आराम से संभाले जा सकने वाली छवियों से बड़ी होती हैं। ट्रेडऑफ़ यह है कि प्रत्येक टाइल में आसपास का संदर्भ कम होता है, इसलिए कम ओवरलैप या बहुत छोटे हिस्से दृश्यमान सीमाएँ, बनावट में बदलाव, या पड़ोसी क्षेत्रों के बीच असंगत विवरण बना सकते हैं। + यदि आप आयताकार सीमाएँ, बार-बार बनावट में परिवर्तन, या संसाधित क्षेत्रों के बीच विस्तार में बदलाव देखते हैं, तो ओवरलैप बढ़ाएँ। + यदि आउटपुट उत्पन्न करने से पहले प्रक्रिया विफल हो जाती है, तो चंक आकार कम करें, विशेष रूप से बड़ी छवियों या भारी मॉडल पर। + पूर्ण छवि को संसाधित करने से पहले एक छोटा सा क्रॉप परीक्षण सहेजें ताकि आप मापदंडों की शीघ्रता से तुलना कर सकें। + एआई स्थिरता + जब AI प्रोसेसिंग क्रैश या फ़्रीज़ हो जाती है, तो चंक का आकार, श्रमिक और स्रोत रिज़ॉल्यूशन कम हो जाता है + भारी एआई नौकरियों से उबरें + एआई प्रोसेसिंग ऐप में सबसे भारी वर्कफ़्लो में से एक है। क्रैश, असफल सत्र, फ़्रीज़, या अचानक ऐप पुनरारंभ होने का मतलब आमतौर पर मॉडल, स्रोत रिज़ॉल्यूशन, चंक आकार, या समानांतर कार्यकर्ता गणना वर्तमान डिवाइस स्थिति के लिए बहुत अधिक मांग है। + यदि ऐप क्रैश हो जाता है या मॉडल सत्र खोलने में विफल रहता है, तो समानांतर कार्यकर्ताओं और चंक आकार को कम करें, फिर उसी छवि को दोबारा आज़माएं। + एआई प्रसंस्करण से पहले बहुत बड़ी छवियों का आकार बदलें जब लक्ष्य को मूल रिज़ॉल्यूशन की आवश्यकता नहीं होती है। + अन्य भारी ऐप्स बंद करें, छोटे मॉडल का उपयोग करें, या मेमोरी दबाव वापस आने पर एक बार में कम फ़ाइलें संसाधित करें। + मॉडल विफलताओं का निदान करें + एक असफल एआई रन का मतलब हमेशा यह नहीं होता कि छवि खराब है। मॉडल फ़ाइल अधूरी, असमर्थित, मेमोरी के लिए बहुत बड़ी या ऑपरेशन से मेल नहीं खाने वाली हो सकती है। जब ऐप किसी विफल सत्र की रिपोर्ट करता है, तो इसे पहले मॉडल और मापदंडों को सरल बनाने के लिए एक संकेत के रूप में मानें। + यदि कोई मॉडल डाउनलोड के तुरंत बाद विफल हो जाता है या ऐसा व्यवहार करता है जैसे कि फ़ाइल दूषित हो गई है तो उसे हटाएं और पुनः डाउनलोड करें। + किसी आयातित कस्टम मॉडल को दोष देने से पहले एक अंतर्निहित डाउनलोड करने योग्य मॉडल आज़माएं, क्योंकि आयातित मॉडल में असंगत इनपुट हो सकते हैं। + यदि आपको क्रैश या सत्र त्रुटि की रिपोर्ट करने की आवश्यकता है तो विफलता को पुन: प्रस्तुत करने के बाद ऐप लॉग का उपयोग करें। + शेडर स्टूडियो में प्रयोग + उन्नत छवि प्रसंस्करण और कस्टम लुक के लिए GPU शेडर प्रभावों का उपयोग करें + शेडर्स के साथ सावधानी से काम करें + शेडर स्टूडियो शक्तिशाली है, लेकिन शेडर कोड और पैरामीटर्स को छोटे, परीक्षण योग्य परिवर्तनों की आवश्यकता है। इसे एक प्रायोगिक उपकरण की तरह मानें: एक कार्यशील आधार रेखा रखें, एक समय में एक चीज़ बदलें, और प्रीसेट पर भरोसा करने से पहले विभिन्न छवि आकारों के साथ परीक्षण करें। + पैरामीटर जोड़ने से पहले किसी ज्ञात कार्यशील शेडर या साधारण प्रभाव से प्रारंभ करें। + एक समय में एक पैरामीटर या कोड ब्लॉक बदलें और त्रुटियों के लिए पूर्वावलोकन की जाँच करें। + प्रीसेट को कुछ भिन्न छवि आकारों पर परीक्षण करने के बाद ही निर्यात करें। + एसवीजी या एएससीआईआई कला बनाएं + रचनात्मक आउटपुट के लिए वेक्टर-जैसी संपत्तियां या टेक्स्ट-आधारित छवियां उत्पन्न करें + रचनात्मक आउटपुट प्रकार चुनें + एसवीजी और एएससीआईआई उपकरण अलग-अलग लक्ष्य पूरा करते हैं: स्केलेबल ग्राफिक्स या टेक्स्ट-स्टाइल रेंडरिंग। दोनों रचनात्मक रूपांतरण हैं, इसलिए एक छोटे थंबनेल से परिणाम का आकलन करने की तुलना में अंतिम आकार में पूर्वावलोकन करना अधिक महत्वपूर्ण है। + एसवीजी मेकर का उपयोग तब करें जब परिणाम को साफ-सुथरा स्केल किया जाना चाहिए या डिज़ाइन वर्कफ़्लो में पुन: उपयोग किया जाना चाहिए। + जब आप किसी छवि का शैलीबद्ध पाठ प्रतिनिधित्व चाहते हैं तो ASCII कला का उपयोग करें। + अंतिम आकार में पूर्वावलोकन करें क्योंकि उत्पन्न आउटपुट में छोटे विवरण गायब हो सकते हैं। + शोर बनावट उत्पन्न करें + पृष्ठभूमि, मास्क, ओवरले या प्रयोगों के लिए प्रक्रियात्मक बनावट बनाएं + प्रक्रियात्मक आउटपुट को ट्यून करें + शोर उत्पन्न करने से मापदंडों से नई छवियां बनती हैं, इसलिए छोटे परिवर्तन बहुत अलग परिणाम उत्पन्न कर सकते हैं। यह बनावट, मास्क, ओवरले, प्लेसहोल्डर या विस्तृत पैटर्न के साथ संपीड़न का परीक्षण करने के लिए उपयोगी है। + बारीक विवरण ट्यून करने से पहले शोर का प्रकार और आउटपुट आकार चुनें। + पैमाने, तीव्रता, रंग या बीज को तब तक समायोजित करें जब तक कि पैटर्न लक्ष्य उपयोग में फिट न हो जाए। + उपयोगी परिणाम सहेजें और यदि आपको बाद में बनावट को फिर से बनाने की आवश्यकता हो तो पैरामीटर लिख लें। + मार्कअप परियोजनाएं + जब ऐप बंद करने के बाद एनोटेशन को संपादन योग्य बनाए रखने की आवश्यकता हो तो प्रोजेक्ट फ़ाइलों का उपयोग करें + स्तरित संपादनों को पुन: प्रयोज्य रखें + मार्कअप प्रोजेक्ट फ़ाइलें तब उपयोगी होती हैं जब स्क्रीनशॉट, आरेख या निर्देश छवि में बाद में बदलाव की आवश्यकता हो सकती है। निर्यात की गई पीएनजी/जेपीईजी फाइलें अंतिम छवियां हैं, जबकि प्रोजेक्ट फाइलें भविष्य के समायोजन के लिए संपादन योग्य परतों और संपादन स्थिति को संरक्षित करती हैं। + जब एनोटेशन, कॉलआउट या लेआउट तत्व संपादन योग्य बने रहें तो मार्कअप लेयर्स का उपयोग करें। + यदि आप अधिक संशोधन की उम्मीद करते हैं तो अंतिम छवि निर्यात करने से पहले प्रोजेक्ट फ़ाइल को सहेजें या दोबारा खोलें। + सामान्य छवि तभी निर्यात करें जब आप एक चपटा अंतिम संस्करण साझा करने के लिए तैयार हों। + फ़ाइलें एन्क्रिप्ट करें + किसी भी स्रोत प्रति को हटाने से पहले फ़ाइलों को पासवर्ड से सुरक्षित रखें और डिक्रिप्शन का परीक्षण करें + एन्क्रिप्टेड आउटपुट को सावधानी से संभालें + सिफर उपकरण पासवर्ड-आधारित एन्क्रिप्शन के साथ फ़ाइलों की सुरक्षा कर सकते हैं, लेकिन वे कुंजी को याद रखने या बैकअप रखने के लिए प्रतिस्थापन नहीं हैं। एन्क्रिप्शन परिवहन और भंडारण के लिए उपयोगी है, जबकि ज़िप तब बेहतर होता है जब मुख्य लक्ष्य कई आउटपुट को बंडल करना हो। + पहले एक प्रति एन्क्रिप्ट करें और मूल प्रति तब तक अपने पास रखें जब तक आप यह परीक्षण न कर लें कि डिक्रिप्शन आपके द्वारा संग्रहीत पासवर्ड के साथ काम करता है। + कुंजी के लिए पासवर्ड मैनेजर या किसी अन्य सुरक्षित स्थान का उपयोग करें; ऐप आपके खोए हुए पासवर्ड का अनुमान नहीं लगा सकता। + एन्क्रिप्टेड फ़ाइलें केवल उन लोगों के साथ साझा करें जिनके पास पासवर्ड भी है और जानते हैं कि किस टूल या विधि से उन्हें डिक्रिप्ट करना चाहिए। + उपकरण व्यवस्थित करें + टूल को समूहित करें, ऑर्डर करें, खोजें और पिन करें ताकि मुख्य स्क्रीन आपके वास्तविक वर्कफ़्लो से मेल खाए + मुख्य स्क्रीन को तेज़ बनाएं + एक बार जब आपको पता चल जाए कि आप कौन से टूल का सबसे अधिक उपयोग करते हैं तो टूल व्यवस्था सेटिंग्स ट्यूनिंग के लायक हैं। समूहीकरण अन्वेषण में मदद करता है, कस्टम ऑर्डर मांसपेशियों की स्मृति में मदद करता है, और पसंदीदा पूरी सूची बड़ी होने पर भी दैनिक टूल को पहुंच योग्य रखता है। + ऐप सीखते समय या जब आप कार्य प्रकार के अनुसार व्यवस्थित टूल पसंद करते हैं तो समूहीकृत मोड का उपयोग करें। + जब आप पहले से ही अपने नियमित टूल को जानते हों और कम स्क्रॉल चाहते हों तो कस्टम ऑर्डर पर स्विच करें। + उन दुर्लभ टूल के लिए खोज सेटिंग्स और पसंदीदा का एक साथ उपयोग करें जिन्हें आप नाम से याद रखते हैं लेकिन हर समय दिखाई देना नहीं चाहते हैं। + बड़ी फ़ाइलें संभालें + धीमे निर्यात, मेमोरी दबाव और अप्रत्याशित रूप से बड़े आउटपुट से बचें + निर्यात से पहले काम कम करें + बहुत बड़ी छवियां, लंबे बैच और उच्च-गुणवत्ता वाले प्रारूप अधिक मेमोरी और समय ले सकते हैं। सबसे तेज़ समाधान आमतौर पर सबसे भारी ऑपरेशन से पहले काम की मात्रा को कम करना है, न कि उसी बड़े आकार के इनपुट के ख़त्म होने का इंतज़ार करना। + भारी फ़िल्टर, ओसीआर, या पीडीएफ रूपांतरण लागू करने से पहले बड़ी छवियों का आकार बदलें। + सेटिंग्स की पुष्टि करने के लिए पहले एक छोटे बैच का उपयोग करें, फिर बाकी को उसी प्रीसेट के साथ संसाधित करें। + जब आउटपुट फ़ाइल अपेक्षा से बड़ी हो तो कम गुणवत्ता या प्रारूप बदलें। + कैश और पूर्वावलोकन + भारी सत्रों के बाद जेनरेट किए गए पूर्वावलोकन, कैशे क्लीनअप और स्टोरेज उपयोग को समझें + भंडारण एवं गति संतुलित रखें + पूर्वावलोकन पीढ़ी और कैश बार-बार ब्राउज़िंग को तेज़ बना सकते हैं, लेकिन भारी संपादन सत्र अस्थायी डेटा को पीछे छोड़ सकते हैं। जब ऐप धीमा लगता है, स्टोरेज तंग होता है, या कई प्रयोगों के बाद पूर्वावलोकन पुराने लगते हैं तो कैश सेटिंग्स मदद करती हैं। + अस्थायी भंडारण को कम करने की तुलना में कई छवियों को ब्राउज़ करते समय पूर्वावलोकन सक्षम रखना अधिक महत्वपूर्ण है। + बड़े बैचों, असफल प्रयोगों या कई उच्च-रिज़ॉल्यूशन फ़ाइलों के साथ लंबे सत्रों के बाद कैश साफ़ करें। + यदि आप लॉन्च के बीच पूर्वावलोकन को गर्म रखने के बजाय स्टोरेज क्लीनअप को प्राथमिकता देते हैं तो स्वचालित कैश क्लियरिंग का उपयोग करें। + स्क्रीन व्यवहार समायोजित करें + केंद्रित कार्य के लिए फ़ुलस्क्रीन, सुरक्षित मोड, चमक और सिस्टम बार विकल्पों का उपयोग करें + इंटरफ़ेस को स्थिति के अनुकूल बनाएं + जब आप लैंडस्केप में संपादन करते हैं, निजी चित्र प्रस्तुत करते हैं, या लगातार चमक की आवश्यकता होती है, तो स्क्रीन सेटिंग्स मदद करती हैं। सामान्य उपयोग के लिए उनकी आवश्यकता नहीं होती है, लेकिन वे क्यूआर निरीक्षण, निजी पूर्वावलोकन या तंग परिदृश्य संपादन जैसी अजीब स्थितियों को हल करते हैं। + जब नेविगेशन क्रोम की तुलना में स्थान संपादित करना अधिक महत्वपूर्ण हो तो फ़ुलस्क्रीन या सिस्टम बार सेटिंग्स का उपयोग करें। + जब निजी छवियां स्क्रीनशॉट या ऐप पूर्वावलोकन में दिखाई नहीं देनी चाहिए तो सुरक्षित मोड का उपयोग करें। + उन उपकरणों के लिए चमक प्रवर्तन का उपयोग करें जहां गहरे रंग की छवियों या क्यूआर कोड का निरीक्षण करना कठिन है। + पारदर्शिता रखें + पारदर्शी पृष्ठभूमि को सफ़ेद, काला या चपटा होने से रोकें + अल्फ़ा-अवेयर आउटपुट का उपयोग करें + पारदर्शिता तभी कायम रहती है जब चयनित टूल और आउटपुट फॉर्मेट अल्फा का समर्थन करते हैं। यदि निर्यात की गई पृष्ठभूमि सफेद या काली हो जाती है, तो समस्या आमतौर पर आउटपुट प्रारूप, भरण/पृष्ठभूमि सेटिंग, या एक गंतव्य ऐप है जो छवि को समतल करती है। + पृष्ठभूमि से हटाई गई छवियां, स्टिकर, लोगो या ओवरले निर्यात करते समय पीएनजी या वेबपी चुनें। + पारदर्शी आउटपुट के लिए JPEG से बचें क्योंकि यह अल्फ़ा चैनल संग्रहीत नहीं करता है। + यदि पूर्वावलोकन भरा हुआ पृष्ठभूमि दिखाता है तो अल्फा प्रारूपों के लिए पृष्ठभूमि रंग से संबंधित सेटिंग्स की जाँच करें। + साझाकरण और आयात संबंधी समस्याएं ठीक करें + जब फ़ाइलें दिखाई न दें या ठीक से न खुलें तो पिकर सेटिंग्स और समर्थित प्रारूपों का उपयोग करें + फ़ाइल को ऐप में प्राप्त करें + आयात समस्याएँ अक्सर प्रदाता अनुमतियों, असमर्थित प्रारूपों या पिकर व्यवहार के कारण होती हैं। क्लाउड ऐप्स और मैसेंजर से साझा की गई फ़ाइलें अस्थायी हो सकती हैं, इसलिए लंबे संपादन के लिए स्थायी स्रोत चुनना अधिक विश्वसनीय हो सकता है। + फ़ाइल को रीसेंट-फ़ाइल शॉर्टकट के बजाय सिस्टम पिकर के माध्यम से खोलने का प्रयास करें। + यदि कोई प्रारूप किसी एक टूल द्वारा समर्थित नहीं है, तो पहले उसे परिवर्तित करें या कोई अधिक विशिष्ट टूल खोलें। + यदि शेयर प्रवाह या प्रत्यक्ष टूल ओपनिंग अपेक्षा से भिन्न व्यवहार करती है तो छवि पिकर और लॉन्चर सेटिंग्स की समीक्षा करें। + बाहर निकलने की पुष्टि + जेस्चर, बैक प्रेस, या टूल स्विच गलती से होने पर बिना सहेजे गए संपादन खोने से बचें + अधूरे काम को सुरक्षित रखें + जब आप जेस्चर नेविगेशन का उपयोग करते हैं, बड़ी फ़ाइलों को संपादित करते हैं, या अक्सर ऐप्स के बीच स्विच करते हैं तो टूल निकास पुष्टिकरण सहायक होता है। यह किसी टूल को छोड़ने से पहले एक छोटा सा विराम जोड़ता है ताकि अधूरी सेटिंग्स और पूर्वावलोकन दुर्घटना से नष्ट न हों। + यदि आपके निर्यात करने से पहले आकस्मिक बैक जेस्चर ने कभी किसी टूल को बंद कर दिया हो तो पुष्टि सक्षम करें। + यदि आप अधिकतर त्वरित एक-चरण रूपांतरण करते हैं और तेज़ नेविगेशन पसंद करते हैं तो इसे अक्षम रखें। + लंबे वर्कफ़्लो के लिए प्रीसेट के साथ इसका उपयोग करें जहां सेटिंग्स का पुनर्निर्माण कष्टप्रद होगा। + समस्याओं की रिपोर्ट करते समय लॉग का उपयोग करें + किसी मुद्दे को खोलने या डेवलपर से संपर्क करने से पहले उपयोगी विवरण एकत्र करें + संदर्भ सहित मुद्दों की रिपोर्ट करें + चरणों, ऐप संस्करण, इनपुट प्रकार और लॉग के साथ एक स्पष्ट रिपोर्ट का निदान करना बहुत आसान है। किसी समस्या को पुन: प्रस्तुत करने के तुरंत बाद लॉग सबसे उपयोगी होते हैं क्योंकि वे आसपास के संदर्भ को ताज़ा रखते हैं। + टूल का नाम, सटीक क्रिया और क्या यह एक फ़ाइल या प्रत्येक फ़ाइल के साथ होता है, इस पर ध्यान दें। + समस्या को पुन: प्रस्तुत करने के बाद ऐप लॉग खोलें और जब संभव हो तो प्रासंगिक लॉग आउटपुट साझा करें। + स्क्रीनशॉट या नमूना फ़ाइलें केवल तभी संलग्न करें जब उनमें निजी जानकारी न हो। + पृष्ठभूमि प्रसंस्करण रोक दिया गया था + एंड्रॉइड ने बैकग्राउंड प्रोसेसिंग नोटिफिकेशन को समय पर शुरू नहीं होने दिया। यह एक सिस्टम समय सीमा है जिसे विश्वसनीय रूप से ठीक नहीं किया जा सकता है। ऐप को पुनरारंभ करें और पुनः प्रयास करें; ऐप को खुला रखने और नोटिफिकेशन या पृष्ठभूमि कार्य की अनुमति देने से मदद मिल सकती है। + फ़ाइल चुनना छोड़ें + जब इनपुट आमतौर पर शेयर, क्लिपबोर्ड या पिछली स्क्रीन से आता है तो टूल तेज़ी से खोलें + बार-बार टूल लॉन्च को तेज़ बनाएं + स्किप फ़ाइल पिकिंग समर्थित टूल के पहले चरण को बदल देती है। यह तब उपयोगी होता है जब आप आमतौर पर पहले से चयनित छवि या एंड्रॉइड शेयर क्रियाओं के साथ एक टूल दर्ज करते हैं, लेकिन यह भ्रमित करने वाला लग सकता है यदि आप उम्मीद करते हैं कि हर टूल तुरंत एक फ़ाइल मांगेगा। + इसे तभी सक्षम करें जब आपका सामान्य प्रवाह टूल खुलने से पहले ही स्रोत छवि प्रदान कर दे। + ऐप सीखते समय इसे अक्षम रखें, क्योंकि स्पष्ट चयन से प्रत्येक टूल प्रवाह को समझना आसान हो जाता है। + यदि कोई टूल बिना किसी स्पष्ट इनपुट के खुलता है, तो इस सेटिंग को अक्षम करें या शेयर/ओपन विथ से प्रारंभ करें ताकि एंड्रॉइड पहले फ़ाइल को पास कर दे। + सबसे पहले संपादक खोलें + जब कोई साझा छवि सीधे संपादन में चली जाए तो पूर्वावलोकन छोड़ें + पहले पड़ाव के रूप में पूर्वावलोकन या संपादन चुनें + पूर्वावलोकन के बजाय ओपन एडिट उन उपयोगकर्ताओं के लिए है जो पहले से ही जानते हैं कि वे छवि को संशोधित करना चाहते हैं। जब आप चैट या क्लाउड स्टोरेज से फ़ाइलों का निरीक्षण करते हैं तो पूर्वावलोकन अधिक सुरक्षित होता है; स्क्रीनशॉट, त्वरित क्रॉप, एनोटेशन और एकमुश्त सुधार के लिए प्रत्यक्ष संपादन तेज़ है। + यदि अधिकांश साझा की गई छवियों को तुरंत क्रॉप, मार्कअप, फ़िल्टर या निर्यात परिवर्तन की आवश्यकता हो तो सीधे संपादन सक्षम करें। + जब आप अक्सर निर्णय लेने से पहले मेटाडेटा, आयाम, पारदर्शिता, या छवि गुणवत्ता का निरीक्षण करते हैं तो पहले पूर्वावलोकन छोड़ दें। + पसंदीदा के साथ इसका उपयोग करें ताकि साझा की गई छवियां आपके द्वारा वास्तव में उपयोग किए जाने वाले टूल के करीब आ जाएं। + पूर्व निर्धारित पाठ प्रविष्टि + नियंत्रणों को बार-बार समायोजित करने के बजाय सटीक पूर्व निर्धारित मान दर्ज करें + जब स्लाइडर धीमे हों तो सटीक मानों का उपयोग करें + प्रीसेट टेक्स्ट प्रविष्टि तब मदद करती है जब आपको बार-बार काम के लिए सटीक संख्याओं की आवश्यकता होती है: सामाजिक छवि आकार, दस्तावेज़ मार्जिन, संपीड़न लक्ष्य, लाइन चौड़ाई, या अन्य मान जो इशारों से पहुंचने में परेशान होते हैं। यह छोटे स्क्रीन पर भी उपयोगी है जहां बारीक स्लाइडर मूवमेंट कठिन होता है। + यदि आप अक्सर सटीक आकार, प्रतिशत, गुणवत्ता मान या टूल पैरामीटर का पुन: उपयोग करते हैं तो टेक्स्ट प्रविष्टि सक्षम करें। + मान को एक बार टाइप करने के बाद प्रीसेट के रूप में सहेजें, फिर उसी सेटअप को दोबारा बनाने के बजाय उसका पुन: उपयोग करें। + रफ विज़ुअल ट्यूनिंग के लिए जेस्चर नियंत्रण रखें और सख्त आउटपुट आवश्यकताओं के लिए टाइप किए गए मान रखें। + मूल के पास सहेजें + जब फ़ोल्डर संदर्भ मायने रखता है तो जेनरेट की गई फ़ाइलों को स्रोत छवियों के बगल में रखें + आउटपुट को उनके स्रोतों के साथ रखें + सेव टू ओरिजिनल फोल्डर कैमरा फोल्डर, प्रोजेक्ट फोल्डर, डॉक्यूमेंट स्कैन और क्लाइंट बैच के लिए उपयोगी है, जहां संपादित कॉपी स्रोत के बगल में रहनी चाहिए। यह एक समर्पित आउटपुट फ़ोल्डर की तुलना में कम सुव्यवस्थित हो सकता है, इसलिए इसे स्पष्ट फ़ाइल नाम प्रत्यय या पैटर्न के साथ संयोजित करें। + इसे तब सक्षम करें जब प्रत्येक स्रोत फ़ोल्डर पहले से ही सार्थक हो और आउटपुट वहां समूहीकृत रहना चाहिए। + फ़ाइल नाम प्रत्यय, उपसर्ग, टाइमस्टैम्प या पैटर्न का उपयोग करें ताकि संपादित प्रतियों को मूल से अलग करना आसान हो। + जब आप चाहते हैं कि सभी जेनरेट की गई फ़ाइलें एक निर्यात फ़ोल्डर में एकत्रित हों तो मिश्रित बैचों के लिए इसे अक्षम करें। + बड़े आउटपुट को छोड़ें + उन रूपांतरणों को सहेजने से बचें जो अप्रत्याशित रूप से स्रोत से भारी हो जाते हैं + बैचों को खराब आकार के आश्चर्यों से बचाएं + कुछ रूपांतरण फ़ाइलों को बड़ा बनाते हैं, विशेष रूप से पीएनजी स्क्रीनशॉट, पहले से संपीड़ित फ़ोटो, उच्च गुणवत्ता वाले वेबपी, या संरक्षित मेटाडेटा वाली छवियां। यदि बड़ा है तो छोड़ें की अनुमति उन आउटपुट को वर्कफ़्लो में छोटे स्रोत को बदलने से रोकती है जहां आकार कम करना मुख्य लक्ष्य है। + इसे तब सक्षम करें जब निर्यात का उद्देश्य फ़ाइलों को संपीड़ित करना, आकार बदलना या अपलोड सीमा के लिए तैयार करना हो। + एक बैच के बाद छोड़ी गई फ़ाइलों की समीक्षा करें; वे पहले से ही अनुकूलित हो सकते हैं या उन्हें एक अलग प्रारूप की आवश्यकता हो सकती है। + जब गुणवत्ता, पारदर्शिता, या प्रारूप अनुकूलता अंतिम फ़ाइल आकार से अधिक मायने रखती है तो इसे अक्षम करें। + क्लिपबोर्ड और लिंक + तेज़ टेक्स्ट-आधारित टूल के लिए स्वचालित क्लिपबोर्ड इनपुट और लिंक पूर्वावलोकन को नियंत्रित करें + स्वचालित इनपुट को पूर्वानुमानित बनाएं + क्यूआर, बेस64, यूआरएल और टेक्स्ट-हेवी वर्कफ़्लो के लिए क्लिपबोर्ड और लिंक सेटिंग्स सुविधाजनक हैं, लेकिन स्वचालित इनपुट जानबूझकर रहना चाहिए। यदि ऐप स्वयं फ़ील्ड भरता प्रतीत होता है, तो क्लिपबोर्ड पेस्ट व्यवहार की जाँच करें; यदि लिंक कार्ड में शोर या धीमापन महसूस होता है, तो लिंक पूर्वावलोकन व्यवहार को समायोजित करें। + जब आप अक्सर टेक्स्ट कॉपी करते हैं तो स्वचालित क्लिपबोर्ड पेस्ट की अनुमति दें और तुरंत एक मिलान टूल खोलें। + यदि निजी क्लिपबोर्ड सामग्री उन टूल में दिखाई देती है जहां आपने इसकी अपेक्षा नहीं की थी तो स्वचालित पेस्ट अक्षम करें। + जब पूर्वावलोकन प्राप्त करना धीमा, ध्यान भटकाने वाला या आपके वर्कफ़्लो के लिए अनावश्यक हो तो लिंक पूर्वावलोकन बंद कर दें। + कॉम्पैक्ट नियंत्रण + स्क्रीन पर जगह कम होने पर सघन चयनकर्ताओं और तेज़ सेटिंग प्लेसमेंट का उपयोग करें + छोटी स्क्रीन पर अधिक नियंत्रण फ़िट करें + कॉम्पैक्ट चयनकर्ता और तेज़ सेटिंग्स प्लेसमेंट छोटे फोन, लैंडस्केप संपादन और कई मापदंडों वाले टूल पर मदद करते हैं। ट्रेडऑफ़ यह है कि नियंत्रण सघन महसूस हो सकता है, इसलिए सामान्य विकल्पों को पहले से ही पहचानने के बाद यह सबसे अच्छा है। + जब संवाद या विकल्प सूचियाँ स्क्रीन के लिए बहुत लंबी लगें तो कॉम्पैक्ट चयनकर्ताओं को सक्षम करें। + यदि डिस्प्ले के एक तरफ से उपकरण नियंत्रण तक पहुंचना आसान हो तो तेज सेटिंग्स को समायोजित करें। + यदि आप अभी भी ऐप सीख रहे हैं या बार-बार घने विकल्पों को मिस-टैप करते हैं, तो रूमियर नियंत्रण पर वापस लौटें। + वेब से छवियाँ लोड करें + एक पेज या छवि यूआरएल चिपकाएँ, पार्स की गई छवियों का पूर्वावलोकन करें, फिर चयनित परिणामों को सहेजें या साझा करें + वेब छवियों का जानबूझकर उपयोग करें + वेब इमेज लोडिंग दिए गए यूआरएल से छवि स्रोतों को पार्स करती है, पहली उपलब्ध छवि का पूर्वावलोकन करती है, और आपको चयनित पार्स की गई छवियों को सहेजने या साझा करने देती है। कुछ साइटें अस्थायी, संरक्षित, या स्क्रिप्ट-जनरेटेड लिंक का उपयोग करती हैं, इसलिए ब्राउज़र में काम करने वाला पेज अभी भी कोई उपयोगी छवि नहीं लौटा सकता है। + एक सीधा छवि यूआरएल या एक पेज यूआरएल चिपकाएँ और पार्स की गई छवि सूची के प्रकट होने की प्रतीक्षा करें। + जब पृष्ठ में कई छवियां हों और आपको उनमें से केवल कुछ की आवश्यकता हो तो फ़्रेम या चयन नियंत्रण का उपयोग करें। + यदि कुछ भी लोड नहीं होता है, तो सीधे छवि लिंक आज़माएं या पहले ब्राउज़र के माध्यम से डाउनलोड करें; साइट पार्सिंग को अवरुद्ध कर सकती है या निजी लिंक का उपयोग कर सकती है। + आकार बदलने का प्रकार चुनें + किसी छवि को नए आयामों में बदलने से पहले स्पष्ट, लचीले, क्रॉप और फ़िट को समझें + आकार, कैनवास और पहलू अनुपात को नियंत्रित करें + आकार बदलने का प्रकार यह तय करता है कि क्या होगा जब अनुरोधित चौड़ाई और ऊंचाई मूल पहलू अनुपात से मेल नहीं खाती। यह पिक्सेल को खींचने, अनुपात को संरक्षित करने, अतिरिक्त क्षेत्र को काटने या पृष्ठभूमि कैनवास जोड़ने के बीच का अंतर है। + एक्सप्लिसिट का उपयोग केवल तभी करें जब विकृति स्वीकार्य हो या स्रोत का पहलू अनुपात पहले से ही लक्ष्य के समान हो। + महत्वपूर्ण पक्ष से आकार बदलकर अनुपात बनाए रखने के लिए फ्लेक्सिबल का उपयोग करें, विशेष रूप से फ़ोटो और मिश्रित बैचों के लिए। + सीमाओं के बिना सख्त फ़्रेमों के लिए क्रॉप का उपयोग करें, या फ़िट का उपयोग करें जब पूरी छवि एक निश्चित कैनवास के अंदर दृश्यमान रहनी चाहिए। + छवि स्केल मोड चुनें + पुन: नमूनाकरण फ़िल्टर चुनें जो तीक्ष्णता, चिकनाई, गति और किनारे की कलाकृतियों को नियंत्रित करता है + आकस्मिक कोमलता के बिना आकार बदलें + छवि स्केल मोड नियंत्रित करता है कि आकार बदलने के दौरान नए पिक्सेल की गणना कैसे की जाती है। सरल मोड तेज़ और पूर्वानुमानित होते हैं, जबकि उन्नत फ़िल्टर विवरण को बेहतर ढंग से संरक्षित कर सकते हैं, लेकिन किनारों को तेज़ कर सकते हैं, प्रभामंडल बना सकते हैं, या बड़ी छवियों पर अधिक समय ले सकते हैं। + प्रतिदिन तेजी से आकार बदलने के लिए बेसिक या बिलिनियर का उपयोग करें जब परिणाम केवल छोटा और साफ होना चाहिए। + बारीक विवरण, टेक्स्ट, या उच्च-गुणवत्ता वाले डाउनस्केलिंग मामले में लैंज़ोस, रोबिडौक्स, स्प्लाइन या ईडब्ल्यूए-शैली मोड का उपयोग करें। + पिक्सेल कला, आइकन और कठोर ग्राफिक्स के लिए निकटतम का उपयोग करें जहां धुंधले पिक्सेल लुक को खराब कर देंगे। + स्केल रंग स्थान + तय करें कि आकार बदलने के दौरान पिक्सेल का पुनः नमूना लेते समय रंगों को कैसे मिश्रित किया जाए + ग्रेडियेंट और किनारों को प्राकृतिक रखें + स्केल कलर स्पेस आकार बदलने के दौरान उपयोग किए जाने वाले गणित को बदल देता है। अधिकांश छवियां डिफ़ॉल्ट के साथ ठीक हैं, लेकिन रैखिक या अवधारणात्मक स्थान मजबूत स्केलिंग के बाद ग्रेडिएंट, गहरे किनारों और संतृप्त रंगों को साफ दिखा सकते हैं। + जब गति और अनुकूलता छोटे रंग के अंतर से अधिक मायने रखती है तो डिफ़ॉल्ट को छोड़ दें। + जब आकार बदलने के बाद डार्क आउटलाइन, यूआई स्क्रीनशॉट, ग्रेडिएंट या कलर बैंड गलत दिखते हैं तो रैखिक या अवधारणात्मक मोड आज़माएं। + वास्तविक लक्ष्य स्क्रीन पर पहले और बाद की तुलना करें, क्योंकि रंग-स्थान परिवर्तन सूक्ष्म और सामग्री पर निर्भर हो सकते हैं। + आकार बदलने के मोड सीमित करें + जानें कि कब ओवर-लिमिट छवियों को छोड़ दिया जाना चाहिए, रीकोड किया जाना चाहिए, या फिट होने के लिए छोटा किया जाना चाहिए + चुनें कि सीमा पर क्या होता है + लिमिट रिसाइज़ केवल एक छोटी छवि वाला टूल नहीं है। इसका मोड यह तय करता है कि ऐप क्या करता है जब कोई स्रोत पहले से ही आपकी सीमा के अंदर है या जब केवल एक पक्ष नियम तोड़ता है, जो मिश्रित फ़ोल्डर्स और अपलोड तैयारी के लिए महत्वपूर्ण है। + जब सीमा के अंदर की छवियों को अछूता छोड़ दिया जाए और दोबारा निर्यात न किया जाए तो स्किप का उपयोग करें। + जब आयाम स्वीकार्य हों तो रिकोड का उपयोग करें लेकिन आपको अभी भी एक नए प्रारूप, मेटाडेटा व्यवहार, गुणवत्ता या फ़ाइल नाम की आवश्यकता है। + ज़ूम का उपयोग तब करें जब सीमाएँ तोड़ने वाली छवियों को आनुपातिक रूप से तब तक छोटा किया जाना चाहिए जब तक कि वे अनुमत बॉक्स में फिट न हो जाएँ। + पहलू अनुपात लक्ष्य + सख्त आउटपुट आकारों में फैले हुए चेहरों, कट-ऑफ किनारों और अप्रत्याशित सीमाओं से बचें + आकार बदलने से पहले आकार का मिलान करें + चौड़ाई और ऊंचाई आकार बदलने के लक्ष्य का केवल आधा हिस्सा है। पक्षानुपात यह तय करता है कि क्या छवि स्वाभाविक रूप से फिट हो सकती है, उसे क्रॉप करने की आवश्यकता है, या उसके चारों ओर एक कैनवास की आवश्यकता है। पहले आकार की जाँच करने से अधिकांश आश्चर्यजनक आकार परिवर्तन परिणाम नहीं आते। + स्पष्ट, क्रॉप या फ़िट चुनने से पहले स्रोत आकार की तुलना लक्ष्य आकार से करें। + जब विषय अतिरिक्त पृष्ठभूमि खो सकता है और गंतव्य को एक सख्त फ्रेम की आवश्यकता हो तो पहले क्रॉप करें। + जब मूल छवि का प्रत्येक भाग दृश्यमान रहना चाहिए तो फिट या फ्लेक्सिबल का उपयोग करें। + अपस्केल या सामान्य आकार + जानें कि कब पिक्सेल जोड़ने के लिए AI की आवश्यकता होती है और कब साधारण स्केलिंग पर्याप्त होती है + विवरण के साथ आकार को भ्रमित न करें + सामान्य आकार पिक्सेल को प्रक्षेपित करके आयाम बदलता है; यह वास्तविक विवरण का आविष्कार नहीं कर सकता। एआई अपस्केल स्पष्ट दिखने वाला विवरण बना सकता है, लेकिन यह धीमा है और बनावट को भ्रमित कर सकता है, इसलिए सही विकल्प इस पर निर्भर करता है कि आपको अनुकूलता, गति या दृश्य बहाली की आवश्यकता है या नहीं। + थंबनेल, अपलोड सीमाएं, स्क्रीनशॉट और सरल आयाम परिवर्तनों के लिए सामान्य आकार का उपयोग करें। + जब किसी छोटी या नरम छवि को बड़े आकार में बेहतर दिखने की आवश्यकता हो तो एआई अपस्केल का उपयोग करें। + एआई अपस्केल के बाद चेहरे, पाठ और पैटर्न की तुलना करें क्योंकि उत्पन्न विवरण विश्वसनीय लेकिन गलत लग सकता है। + फ़िल्टर क्रम मायने रखता है + जानबूझकर क्रम में सफाई, रंग, तीक्ष्णता और अंतिम संपीड़न लागू करें + एक क्लीनर फ़िल्टर स्टैक बनाएँ + फ़िल्टर हमेशा विनिमेय नहीं होते हैं. शार्पनिंग से पहले धुंधलापन, ओसीआर से पहले कंट्रास्ट बूस्ट, या संपीड़न से पहले रंग परिवर्तन एक ही नियंत्रण से बहुत अलग आउटपुट उत्पन्न कर सकता है। + पहले काटें और घुमाएँ ताकि बाद में फ़िल्टर केवल बचे हुए पिक्सेल पर ही काम करें। + प्रभावों को तेज़ या शैलीबद्ध करने से पहले एक्सपोज़र, श्वेत संतुलन और कंट्रास्ट लागू करें। + अंतिम आकार और संपीड़न को अंत के पास रखें ताकि पूर्वावलोकन किए गए विवरण अनुमानित रूप से निर्यात से बचे रहें। + पृष्ठभूमि किनारों को ठीक करें + स्वचालित निष्कासन के बाद आभामंडल, बची हुई परछाइयाँ, बाल, छिद्र और मुलायम रूपरेखा साफ़ करें + कटआउट को जानबूझकर बनाएं + स्वचालित मास्क अक्सर एक नज़र में अच्छे लगते हैं लेकिन चमकीले, गहरे या पैटर्न वाले पृष्ठभूमि पर समस्याएँ प्रकट करते हैं। एज क्लीनअप एक त्वरित कटआउट और पुन: प्रयोज्य स्टिकर, लोगो या उत्पाद छवि के बीच का अंतर है। + प्रभामंडल और गायब किनारे के विवरण को प्रकट करने के लिए विपरीत पृष्ठभूमि के विरुद्ध परिणाम का पूर्वावलोकन करें। + आसपास के बचे हुए हिस्से को मिटाने से पहले बालों, कोनों और पारदर्शी वस्तुओं के लिए रिस्टोर का उपयोग करें। + जब कटआउट को डिज़ाइन में रखा जाएगा तो अंतिम पृष्ठभूमि रंग पर एक छोटा सा परीक्षण निर्यात करें। + पढ़ने योग्य वॉटरमार्क + फ़ोटो को ख़राब किए बिना उज्ज्वल, गहरे, व्यस्त और क्रॉप की गई छवियों पर निशान दृश्यमान बनाएं + छवि को छुपाए बिना सुरक्षित रखें + एक वॉटरमार्क जो एक छवि पर अच्छा दिखता है वह दूसरी छवि पर गायब हो सकता है। आकार, अस्पष्टता, कंट्रास्ट, प्लेसमेंट और दोहराव को केवल पहले पूर्वावलोकन के लिए नहीं, बल्कि पूरे बैच के लिए चुना जाना चाहिए। + सभी फ़ाइलों को निर्यात करने से पहले बैच में सबसे चमकीली और सबसे गहरी छवियों पर निशान का परीक्षण करें। + बड़े दोहराए गए निशानों के लिए कम अपारदर्शिता और छोटे कोने के निशानों के लिए मजबूत कंट्रास्ट का उपयोग करें। + महत्वपूर्ण चेहरों, पाठ और उत्पाद विवरणों को तब तक स्पष्ट रखें जब तक कि चिह्न पुन: उपयोग को रोकने के लिए न हो। + एक छवि को टाइल्स में विभाजित करें + एकल छवि को पंक्तियों, स्तंभों, कस्टम प्रतिशत, प्रारूप और गुणवत्ता के आधार पर काटें + ग्रिड और ऑर्डर किए गए टुकड़े तैयार करें + इमेज स्प्लिटिंग एक स्रोत छवि से कई आउटपुट बनाता है। यह पंक्ति और स्तंभ की संख्या के आधार पर विभाजित हो सकता है, पंक्ति या स्तंभ प्रतिशत को समायोजित कर सकता है, और चयनित आउटपुट प्रारूप और गुणवत्ता के साथ टुकड़ों को सहेज सकता है, जो ग्रिड, पेज स्लाइस, पैनोरमा और ऑर्डर किए गए सामाजिक पोस्ट के लिए उपयोगी है। + स्रोत छवि चुनें, फिर आपके लिए आवश्यक पंक्तियों और स्तंभों की संख्या निर्धारित करें। + जब सभी टुकड़े समान आकार के न हों तो पंक्ति या स्तंभ प्रतिशत समायोजित करें। + सहेजने से पहले आउटपुट स्वरूप और गुणवत्ता चुनें ताकि प्रत्येक उत्पन्न टुकड़ा समान निर्यात नियमों का उपयोग करे। + छवि पट्टियाँ काटें + ऊर्ध्वाधर या क्षैतिज क्षेत्रों को हटा दें और शेष हिस्सों को वापस एक साथ मिला दें + कोई अंतराल छोड़े बिना एक क्षेत्र हटाएँ + इमेज कटिंग सामान्य फसल से अलग है। यह चयनित ऊर्ध्वाधर या क्षैतिज पट्टी को हटा सकता है, फिर शेष छवि भागों को एक साथ मिला सकता है। व्युत्क्रम मोड चयनित क्षेत्र को रखता है, और दोनों व्युत्क्रम दिशाओं का उपयोग करके चयनित आयत को रखता है। + पार्श्व क्षेत्रों, स्तंभों या मध्य पट्टियों के लिए ऊर्ध्वाधर कटिंग का उपयोग करें; बैनर, अंतराल या पंक्तियों के लिए क्षैतिज कटिंग का उपयोग करें। + जब आप चयनित भाग को हटाने के बजाय उसे रखना चाहते हैं तो अक्ष पर व्युत्क्रम सक्षम करें। + काटने के बाद किनारों का पूर्वावलोकन करें क्योंकि हटाए गए क्षेत्र के महत्वपूर्ण विवरणों को पार करने पर मर्ज की गई सामग्री अचानक दिख सकती है। + आउटपुट स्वरूप चुनें + सामग्री और गंतव्य के आधार पर JPEG, PNG, WebP, AVIF, JXL, या PDF चुनें + गंतव्य को प्रारूप तय करने दें + सबसे अच्छा प्रारूप इस बात पर निर्भर करता है कि छवि में क्या है और इसका उपयोग कहाँ किया जाएगा। फ़ोटो, स्क्रीनशॉट, पारदर्शी परिसंपत्तियाँ, दस्तावेज़ और एनिमेटेड फ़ाइलें गलत प्रारूप में डाले जाने पर अलग-अलग तरीकों से विफल हो जाती हैं। + जब पारदर्शिता और सटीक पिक्सेल की आवश्यकता न हो तो व्यापक फोटो अनुकूलता के लिए JPEG का उपयोग करें। + शार्प स्क्रीनशॉट, यूआई कैप्चर, पारदर्शी ग्राफिक्स और दोषरहित संपादन के लिए पीएनजी का उपयोग करें। + जब कॉम्पैक्ट आधुनिक आउटपुट मायने रखता है और प्राप्त करने वाला ऐप इसका समर्थन करता है तो WebP, AVIF, या JXL का उपयोग करें। + ऑडियो कवर निकालें + ऑडियो फ़ाइलों से एम्बेडेड एल्बम कला या उपलब्ध मीडिया फ़्रेम को पीएनजी छवियों के रूप में सहेजें + ऑडियो फ़ाइलों से कलाकृति निकालें + ऑडियो कवर ऑडियो फ़ाइलों से एम्बेडेड कलाकृति को पढ़ने के लिए एंड्रॉइड मीडिया मेटाडेटा का उपयोग करता है। जब एम्बेडेड कलाकृति अनुपलब्ध होती है, तो यदि एंड्रॉइड एक प्रदान कर सकता है तो रिट्रीवर मीडिया फ़्रेम पर वापस आ सकता है; यदि कोई मौजूद नहीं है, तो टूल रिपोर्ट करता है कि निकालने के लिए कोई छवि नहीं है। + ऑडियो कवर खोलें और अपेक्षित कवर आर्ट के साथ एक या अधिक ऑडियो फ़ाइलें चुनें। + सहेजने या साझा करने से पहले निकाले गए कवर की समीक्षा करें, विशेष रूप से स्ट्रीमिंग या रिकॉर्डिंग ऐप्स की फ़ाइलों के लिए। + यदि कोई छवि नहीं मिलती है, तो संभवतः ऑडियो फ़ाइल में कोई एम्बेडेड कवर नहीं है या एंड्रॉइड उपयोग करने योग्य फ़्रेम को प्रदर्शित नहीं कर सका है। + दिनांक और स्थान मेटाडेटा + तय करें कि जब कैप्चर करने का समय मायने रखता है तो क्या संरक्षित करना है लेकिन जीपीएस या डिवाइस डेटा लीक नहीं होना चाहिए + उपयोगी मेटाडेटा को निजी मेटाडेटा से अलग करें + मेटाडेटा सभी समान रूप से जोखिम भरा नहीं है। कैप्चर तिथियां संग्रह और सॉर्टिंग के लिए उपयोगी हो सकती हैं, जबकि जीपीएस, डिवाइस सीरियल-जैसे फ़ील्ड, सॉफ़्टवेयर टैग, या स्वामी फ़ील्ड इच्छित से अधिक प्रकट कर सकते हैं। + जब एल्बम, स्कैन या प्रोजेक्ट इतिहास के लिए कालानुक्रमिक क्रम मायने रखता है तो दिनांक और समय टैग रखें। + सार्वजनिक रूप से साझा करने या अजनबियों को चित्र भेजने से पहले जीपीएस और डिवाइस-पहचान टैग हटा दें। + एक नमूना निर्यात करें और गोपनीयता आवश्यकताएँ सख्त होने पर EXIF ​​का दोबारा निरीक्षण करें। + फ़ाइल नाम टकराव से बचें + जब कई फ़ाइलें image.jpg या स्क्रीनशॉट.png जैसे नाम साझा करती हैं तो बैच आउटपुट को सुरक्षित रखें + प्रत्येक आउटपुट नाम को अद्वितीय बनाएं + जब छवियाँ मैसेंजर, स्क्रीनशॉट, क्लाउड फ़ोल्डर या एकाधिक कैमरों से आती हैं तो डुप्लिकेट फ़ाइल नाम आम हैं। एक अच्छा पैटर्न आकस्मिक प्रतिस्थापन को रोकता है और आउटपुट को उनके स्रोतों पर वापस ट्रेस करने योग्य रखता है। + जब संपादित प्रति पहचानने योग्य होनी चाहिए तो मूल नाम और एक प्रत्यय शामिल करें। + बड़े मिश्रित बैचों को संसाधित करते समय अनुक्रम, टाइमस्टैम्प, प्रीसेट या आयाम जोड़ें। + जब तक आप यह सत्यापित नहीं कर लेते कि नामकरण पैटर्न टकरा नहीं सकता, तब तक वर्कफ़्लो को ओवरराइट करने से बचें। + सहेजें या साझा करें + किसी स्थायी फ़ाइल और किसी अन्य ऐप के लिए अस्थायी हैंडऑफ़ के बीच चयन करें + सही इरादे से निर्यात करें + सहेजें और साझा करें समान महसूस हो सकते हैं, लेकिन वे अलग-अलग समस्याओं का समाधान करते हैं। सहेजने से एक फ़ाइल बनती है जिसे आप बाद में पा सकते हैं; साझाकरण एंड्रॉइड के माध्यम से उत्पन्न परिणाम को दूसरे ऐप पर भेजता है और एक टिकाऊ स्थानीय प्रतिलिपि नहीं छोड़ सकता है। + जब आउटपुट किसी ज्ञात फ़ोल्डर में रहना चाहिए, बैकअप लिया जाना चाहिए, या बाद में पुन: उपयोग किया जाना चाहिए तो सेव का उपयोग करें। + त्वरित संदेश, ईमेल अनुलग्नक, सामाजिक पोस्टिंग, या किसी अन्य संपादक को परिणाम भेजने के लिए शेयर का उपयोग करें। + पहले सेव करें जब प्राप्त करने वाला ऐप अविश्वसनीय हो या जब किसी असफल शेयर को दोबारा बनाना कष्टप्रद हो। + पीडीएफ पेज का आकार और मार्जिन + दस्तावेज़ बनाने से पहले कागज़ का आकार, उपयुक्त व्यवहार और सांस लेने की जगह चुनें + छवि पृष्ठों को मुद्रण योग्य और पठनीय बनाएं + छवियाँ अजीब पीडीएफ पेज बन सकती हैं जब उनका आकार चुने हुए कागज़ के आकार से मेल नहीं खाता। मार्जिन, फिट मोड और पेज ओरिएंटेशन तय करते हैं कि सामग्री क्रॉप की गई है, छोटी है, फैली हुई है या पढ़ने में आरामदायक है। + जब पीडीएफ मुद्रित किया जाए या किसी फॉर्म में जमा किया जाए तो वास्तविक कागज़ के आकार का उपयोग करें। + स्कैन और स्क्रीनशॉट के लिए मार्जिन का उपयोग करें ताकि टेक्स्ट पृष्ठ किनारे के सामने न बैठे। + जब स्रोत छवियों में मिश्रित अभिविन्यास हो तो पोर्ट्रेट और लैंडस्केप पृष्ठों का एक साथ पूर्वावलोकन करें। + स्कैन साफ़ करें + पीडीएफ या ओसीआर से पहले कागज के किनारों, छाया, कंट्रास्ट, रोटेशन और पठनीयता में सुधार करें + संग्रहित करने से पहले पृष्ठ तैयार करें + एक स्कैन को तकनीकी रूप से कैप्चर किया जा सकता है लेकिन फिर भी पढ़ना कठिन है। पीडीएफ निर्यात से पहले छोटे सफाई चरण अक्सर संपीड़न सेटिंग्स से अधिक मायने रखते हैं, खासकर रसीदों, हस्तलिखित नोट्स और कम रोशनी वाले पृष्ठों के लिए। + परिप्रेक्ष्य को सही करें और टेबल के किनारों, उंगलियों, छायाओं और पृष्ठभूमि की अव्यवस्था को दूर करें। + कंट्रास्ट को सावधानी से बढ़ाएं ताकि स्टांप, हस्ताक्षर या पेंसिल के निशान को नष्ट किए बिना पाठ स्पष्ट हो जाए। + पृष्ठ सीधा और पढ़ने योग्य होने के बाद ही ओसीआर चलाएं, फिर महत्वपूर्ण संख्याओं को मैन्युअल रूप से सत्यापित करें। + पीडीएफ़ को संपीड़ित करें, ग्रेस्केल करें या मरम्मत करें + हल्के, प्रिंट करने योग्य, या पुनर्निर्मित दस्तावेज़ प्रतियों के लिए एक-फ़ाइल पीडीएफ संचालन का उपयोग करें + पीडीएफ क्लीनअप ऑपरेशन चुनें + पीडीएफ टूल्स में संपीड़न, ग्रेस्केल रूपांतरण और मरम्मत के लिए अलग-अलग ऑपरेशन शामिल हैं। संपीड़न और ग्रेस्केल मुख्य रूप से एम्बेडेड छवियों के माध्यम से काम करते हैं, जबकि मरम्मत पीडीएफ संरचना का पुनर्निर्माण करती है; इनमें से किसी को भी प्रत्येक दस्तावेज़ के लिए गारंटीशुदा समाधान के रूप में नहीं माना जाना चाहिए। + जब दस्तावेज़ में छवियाँ हों और फ़ाइल का आकार साझा करने के लिए मायने रखता हो तो कंप्रेस पीडीएफ का उपयोग करें। + ग्रेस्केल का उपयोग तब करें जब दस्तावेज़ को प्रिंट करना आसान हो या उसे रंगीन छवियों की आवश्यकता न हो। + जब कोई दस्तावेज़ खुलने में विफल रहता है या आंतरिक संरचना क्षतिग्रस्त हो जाती है, तो प्रतिलिपि पर रिपेयर पीडीएफ का उपयोग करें, फिर पुनर्निर्मित फ़ाइल को सत्यापित करें। + पीडीएफ़ से छवियाँ निकालें + एम्बेडेड पीडीएफ छवियों को पुनर्प्राप्त करें और निकाले गए परिणामों को एक संग्रह में पैक करें + दस्तावेज़ों से स्रोत छवियाँ सहेजें + एक्सट्रेक्ट इमेजेज एम्बेडेड इमेज ऑब्जेक्ट के लिए पीडीएफ को स्कैन करता है, जहां संभव हो डुप्लिकेट को छोड़ देता है, और पुनर्प्राप्त छवियों को जेनरेट किए गए ज़िप संग्रह में संग्रहीत करता है। यह केवल उन छवियों को निकालता है जो वास्तव में पीडीएफ में एम्बेडेड हैं, टेक्स्ट, वेक्टर चित्र या स्क्रीनशॉट के रूप में प्रत्येक दृश्यमान पृष्ठ को नहीं। + जब आपको पीडीएफ के अंदर संग्रहीत चित्रों की आवश्यकता हो, जैसे कैटलॉग या स्कैन की गई संपत्तियों से तस्वीरें, तो एक्सट्रेक्ट इमेज का उपयोग करें। + जब आपको टेक्स्ट और लेआउट सहित पूरे पृष्ठों की आवश्यकता हो तो छवियों या पृष्ठ निष्कर्षण के लिए पीडीएफ का उपयोग करें। + यदि टूल कोई एम्बेडेड छवि नहीं होने की रिपोर्ट करता है, तो पृष्ठ टेक्स्ट/वेक्टर सामग्री हो सकता है या छवियों को निकालने योग्य ऑब्जेक्ट के रूप में संग्रहीत नहीं किया जा सकता है। + मेटाडेटा, फ़्लैटनिंग और प्रिंट आउटपुट + दस्तावेज़ गुणों को संपादित करें, पृष्ठों को व्यवस्थित करें, या जानबूझकर कस्टम प्रिंट लेआउट तैयार करें + अंतिम दस्तावेज़ कार्रवाई चुनें + पीडीएफ मेटाडेटा, फ़्लैटनिंग और प्रिंट तैयारी अंतिम चरण की विभिन्न समस्याओं का समाधान करती है। मेटाडेटा दस्तावेज़ गुणों को नियंत्रित करता है, फ़्लैटन पीडीएफ पृष्ठों को कम संपादन योग्य आउटपुट में व्यवस्थित करता है, और प्रिंट पीडीएफ पृष्ठ आकार, अभिविन्यास, मार्जिन और पेज-प्रति-शीट नियंत्रण के साथ एक नया लेआउट तैयार करता है। + जब लेखक, शीर्षक, कीवर्ड, निर्माता, या गोपनीयता सफ़ाई मायने रखती है तो मेटाडेटा का उपयोग करें। + जब दृश्यमान पृष्ठ सामग्री को अलग-अलग पीडीएफ ऑब्जेक्ट के रूप में संपादित करना कठिन हो जाए तो फ़्लैटन पीडीएफ का उपयोग करें। + जब आपको कस्टम पेज आकार, ओरिएंटेशन, मार्जिन या प्रति शीट एकाधिक पृष्ठों की आवश्यकता हो तो प्रिंट पीडीएफ का उपयोग करें। + OCR के लिए चित्र तैयार करें + ओसीआर से पाठ पढ़ने के लिए कहने से पहले क्रॉप, रोटेशन, कंट्रास्ट, डीनोइस और स्केल का उपयोग करें + OCR क्लीनर पिक्सेल दें + ओसीआर गलतियाँ अक्सर ओसीआर इंजन के बजाय इनपुट छवि से आती हैं। टेढ़े-मेढ़े पृष्ठ, कम कंट्रास्ट, धुंधलापन, छाया, छोटा पाठ और मिश्रित पृष्ठभूमि सभी पहचान गुणवत्ता को कम कर देते हैं। + पाठ क्षेत्र में काटें और छवि को घुमाएँ ताकि पहचान से पहले रेखाएँ क्षैतिज हों। + कंट्रास्ट बढ़ाएं या साफ़ काले-सफ़ेद में तभी बदलें जब इससे अक्षरों को पढ़ना आसान हो जाए। + छोटे टेक्स्ट का आकार ऊपर की ओर मध्यम रूप से बदलें, लेकिन आक्रामक शार्पनिंग से बचें जो नकली किनारे बनाता है। + QR सामग्री की जाँच करें + किसी कोड पर भरोसा करने से पहले लिंक, वाई-फाई क्रेडेंशियल, संपर्क और कार्यों की समीक्षा करें + डिकोड किए गए टेक्स्ट को अविश्वसनीय इनपुट मानें + एक क्यूआर कोड एक यूआरएल, संपर्क कार्ड, वाई-फाई पासवर्ड, कैलेंडर ईवेंट, फोन नंबर या सादा पाठ छिपा सकता है। कोड के पास दिखाई देने वाला लेबल वास्तविक पेलोड से मेल नहीं खा सकता है, इसलिए इस पर कार्रवाई करने से पहले डिकोड की गई सामग्री की समीक्षा करें। + इसे खोलने से पहले पूर्ण डिकोड किए गए यूआरएल का निरीक्षण करें, विशेष रूप से छोटे या अपरिचित डोमेन। + वाई-फ़ाई, संपर्क, कैलेंडर, फ़ोन और एसएमएस कोड से सावधान रहें क्योंकि वे संवेदनशील कार्रवाइयों को ट्रिगर कर सकते हैं। + जब आपको सामग्री को कहीं और सत्यापित करने की आवश्यकता हो तो उसे तुरंत खोलने के बजाय पहले उसकी प्रतिलिपि बनाएँ। + QR कोड जनरेट करें + सादे पाठ, यूआरएल, वाई-फाई, संपर्क, ईमेल, फोन, एसएमएस और स्थान डेटा से कोड बनाएं + सही क्यूआर पेलोड बनाएं + क्यूआर स्क्रीन दर्ज की गई सामग्री से कोड उत्पन्न कर सकती है और साथ ही मौजूदा सामग्री को स्कैन भी कर सकती है। संरचित क्यूआर प्रकार डेटा को उस प्रारूप में एनकोड करने में मदद करते हैं जिसे अन्य ऐप्स समझते हैं, जैसे कि वाई-फाई लॉगिन विवरण, संपर्क कार्ड, ईमेल फ़ील्ड, फोन नंबर, एसएमएस सामग्री, यूआरएल, या भौगोलिक निर्देशांक। + सरल नोट्स के लिए सादा पाठ चुनें, या जब कोड वाई-फाई, संपर्क, यूआरएल, ईमेल, फोन, एसएमएस या स्थान डेटा के रूप में खुलना चाहिए तो एक संरचित प्रकार का उपयोग करें। + जनरेट किए गए कोड को साझा करने से पहले प्रत्येक फ़ील्ड की जांच करें क्योंकि दृश्यमान पूर्वावलोकन केवल आपके द्वारा दर्ज किए गए पेलोड को दर्शाता है। + सहेजे गए क्यूआर कोड का स्कैनर से परीक्षण करें जब इसे मुद्रित किया जाएगा, सार्वजनिक रूप से पोस्ट किया जाएगा, या अन्य लोगों द्वारा उपयोग किया जाएगा। + चेकसम मोड चुनें + फ़ाइलों या टेक्स्ट से हैश की गणना करें, एक फ़ाइल की तुलना करें, या कई फ़ाइलों की बैच-जाँच करें + सामग्री सत्यापित करें, उपस्थिति नहीं + चेकसम टूल्स में फ़ाइल हैशिंग, टेक्स्ट हैशिंग, ज्ञात हैश के विरुद्ध फ़ाइल की तुलना और बैच तुलना के लिए अलग-अलग टैब हैं। एक चेकसम चयनित एल्गोरिदम के लिए बाइट-दर-बाइट पहचान साबित करता है; इससे यह साबित नहीं होता कि दो तस्वीरें महज एक जैसी दिखती हैं। + फ़ाइलों के लिए कैलकुलेट और टाइप किए गए या चिपकाए गए टेक्स्ट डेटा के लिए टेक्स्ट हैश का उपयोग करें। + जब कोई आपको एक फ़ाइल के लिए अपेक्षित चेकसम देता है तो तुलना का उपयोग करें। + बैच तुलना का उपयोग करें जब कई फ़ाइलों को एक पास में हैश या सत्यापन की आवश्यकता होती है, तो चयनित एल्गोरिदम को सुसंगत रखें। + क्लिपबोर्ड गोपनीयता + निजी कॉपी किए गए डेटा को गलती से उजागर किए बिना स्वचालित क्लिपबोर्ड सुविधाओं का उपयोग करें + कॉपी की गई सामग्री को जानबूझकर रखें + क्लिपबोर्ड स्वचालन सुविधाजनक है, लेकिन कॉपी किए गए टेक्स्ट में पासवर्ड, लिंक, पते, टोकन या निजी नोट्स हो सकते हैं। क्लिपबोर्ड-आधारित इनपुट को एक गति सुविधा के रूप में मानें जो आपकी आदतों और गोपनीयता अपेक्षाओं से मेल खाना चाहिए। + यदि उपकरण में निजी पाठ अप्रत्याशित रूप से प्रकट होता है तो स्वचालित क्लिपबोर्ड उपयोग अक्षम करें। + क्लिपबोर्ड-ओनली सेविंग का उपयोग केवल तभी करें जब आप जानबूझकर परिणाम को स्टोरेज से बचाना चाहते हों। + संवेदनशील टेक्स्ट, क्यूआर डेटा, या बेस64 पेलोड को संभालने के बाद क्लिपबोर्ड को साफ़ करें या बदलें। + रंग प्रारूप + कहीं और चिपकाने से पहले HEX, RGB, HSV, अल्फ़ा और कॉपी किए गए रंग मानों को समझें + लक्ष्य द्वारा अपेक्षित प्रारूप की प्रतिलिपि बनाएँ + एक ही दृश्यमान रंग को कई प्रकार से लिखा जा सकता है। एक डिज़ाइन टूल, सीएसएस फ़ील्ड, एंड्रॉइड रंग मान, या छवि संपादक एक अलग ऑर्डर, अल्फा हैंडलिंग या रंग मॉडल की अपेक्षा कर सकता है। + वेब, डिज़ाइन, या थीम फ़ील्ड में पेस्ट करते समय HEX का उपयोग करें जो कॉम्पैक्ट रंग कोड की अपेक्षा करता है। + जब आपको चैनल, चमक, संतृप्ति, या रंग को मैन्युअल रूप से समायोजित करने की आवश्यकता हो तो आरजीबी या एचएसवी का उपयोग करें। + जब पारदर्शिता मायने रखती है तो अल्फ़ा को अलग से जांचें क्योंकि कुछ गंतव्य इसे अनदेखा करते हैं या किसी भिन्न क्रम का उपयोग करते हैं। + रंग लाइब्रेरी ब्राउज़ करें + नामित रंगों को नाम या HEX के आधार पर खोजें और पसंदीदा को शीर्ष के पास रखें + पुन: प्रयोज्य नामित रंग खोजें + कलर लाइब्रेरी एक नामित रंग संग्रह को लोड करती है, इसे नाम के आधार पर क्रमबद्ध करती है, रंग नाम या HEX मान के आधार पर फ़िल्टर करती है, और पसंदीदा रंगों को संग्रहीत करती है ताकि महत्वपूर्ण प्रविष्टियों तक पहुंचना आसान रहे। यह तब उपयोगी होता है जब आपको किसी छवि से नमूना लेने के बजाय वास्तविक नामित रंग की आवश्यकता होती है। + जब आप परिवार को जानते हों तो रंग के नाम से खोजें, जैसे लाल, नीला, स्लेट, या मिंट। + जब आपके पास पहले से ही एक संख्यात्मक रंग हो और आप मिलान या निकटवर्ती नामित प्रविष्टियाँ ढूंढना चाहते हों तो HEX द्वारा खोजें। + लगातार रंगों को पसंदीदा के रूप में चिह्नित करें ताकि ब्राउज़ करते समय वे सामान्य लाइब्रेरी से आगे बढ़ें। + जाल ढाल संग्रह का प्रयोग करें + उपलब्ध मेश ग्रेडिएंट संसाधन डाउनलोड करें और चयनित छवियां साझा करें + रिमोट मेश ग्रेडिएंट एसेट्स का उपयोग करें + मेश ग्रेडिएंट्स एक दूरस्थ संसाधन संग्रह को लोड कर सकते हैं, लापता ग्रेडिएंट छवियों को डाउनलोड कर सकते हैं, डाउनलोड प्रगति दिखा सकते हैं और चयनित ग्रेडिएंट्स को साझा कर सकते हैं। उपलब्धता नेटवर्क एक्सेस और रिमोट रिसोर्स स्टोर पर निर्भर करती है, इसलिए खाली सूची का आमतौर पर मतलब होता है कि संसाधन अभी तक उपलब्ध नहीं थे। + जब आप रेडीमेड मेश ग्रेडिएंट इमेज चाहते हैं तो ग्रेडिएंट टूल से मेश ग्रेडिएंट खोलें। + संग्रह खाली है यह मानने से पहले संसाधन लोड होने या डाउनलोड प्रगति की प्रतीक्षा करें। + जिन ग्रेडिएंट्स की आपको आवश्यकता है उन्हें चुनें और साझा करें, या जब आप मैन्युअल रूप से कस्टम ग्रेडिएंट बनाना चाहते हैं तो ग्रेडिएंट मेकर पर वापस लौटें। + उत्पन्न परिसंपत्ति समाधान + ग्रेडिएंट, शोर, वॉलपेपर, एसवीजी जैसी कला या बनावट उत्पन्न करने से पहले आउटपुट आकार चुनें + अपनी आवश्यकता के अनुसार आकार उत्पन्न करें + जेनरेट की गई छवियों में वापस देखने के लिए मूल कैमरा नहीं होता है। यदि आउटपुट बहुत छोटा है, तो बाद में स्केलिंग पैटर्न को नरम कर सकती है, विवरण बदल सकती है, या परिसंपत्ति टाइल और संपीड़ित करने के तरीके को बदल सकती है। + पहले अंतिम उपयोग के मामले के लिए आयाम सेट करें, जैसे वॉलपेपर, आइकन, पृष्ठभूमि, प्रिंट, या ओवरले। + बनावट के लिए बड़े आकार का उपयोग करें जिन्हें क्रॉप किया जा सकता है, ज़ूम किया जा सकता है, या कई लेआउट में पुन: उपयोग किया जा सकता है। + विशाल आउटपुट से पहले परीक्षण संस्करण निर्यात करें क्योंकि प्रक्रियात्मक विवरण संपीड़न के व्यवहार को बदल सकता है। + वर्तमान वॉलपेपर निर्यात करें + डिवाइस से उपलब्ध होम, लॉक और अंतर्निर्मित वॉलपेपर पुनर्प्राप्त करें + स्थापित वॉलपेपर सहेजें या साझा करें + वॉलपेपर एक्सपोर्ट उन वॉलपेपर को पढ़ता है जो एंड्रॉइड सिस्टम वॉलपेपर एपीआई के माध्यम से प्रदर्शित करता है। डिवाइस, एंड्रॉइड वर्जन, अनुमतियों और बिल्ड वेरिएंट के आधार पर, होम, लॉक या बिल्ट-इन वॉलपेपर अलग से उपलब्ध हो सकते हैं या गायब हो सकते हैं। + वॉलपेपर लोड करने के लिए वॉलपेपर एक्सपोर्ट खोलें, सिस्टम ऐप को पढ़ने की अनुमति देता है। + उपलब्ध होम, लॉक, या अंतर्निहित वॉलपेपर प्रविष्टियों का चयन करें जिन्हें आप सहेजना, कॉपी करना या साझा करना चाहते हैं। + यदि कोई वॉलपेपर गायब है या सुविधा अनुपलब्ध है, तो यह आमतौर पर संपादन सेटिंग के बजाय एक सिस्टम, अनुमति या बिल्ड-वेरिएंट सीमा है। + कॉर्नर एनिमेशन थ्रॉटल + रंग को इस रूप में कॉपी करें + हेक्स + नाम + मुलायम बालों और किनारे की हैंडलिंग के साथ तेज़ व्यक्ति कटआउट के लिए पोर्ट्रेट मैटिंग मॉडल। + ज़ीरो-डीसीई++ वक्र अनुमान का उपयोग करके तेज़ कम रोशनी में वृद्धि। + बारिश की लकीरों और गीले मौसम की कलाकृतियों को कम करने के लिए रिस्टॉर्मर इमेज रेस्टोरेशन मॉडल। + दस्तावेज़ अनवारपिंग मॉडल जो एक समन्वय ग्रिड की भविष्यवाणी करता है और घुमावदार पृष्ठों को एक फ्लैटर स्कैन में रीमैप करता है। + गति अवधि स्केल + ऐप एनीमेशन गति को नियंत्रित करता है: 0 गति को अक्षम करता है, 1 सामान्य है, उच्च मान एनिमेशन को धीमा कर देते हैं + नवीनतम उपकरण दिखाएँ + मुख्य स्क्रीन पर हाल ही में उपयोग किए गए टूल तक त्वरित पहुंच प्रदर्शित करें + उपयोग सांख्यिकी + ऐप खुलने, टूल लॉन्च होने और आपके सबसे अधिक उपयोग किए जाने वाले टूल का अन्वेषण करें + ऐप खुलता है + टूल खुलता है + अंतिम उपकरण + सफल बचत + गतिविधि की लकीर + डेटा सहेजा गया + शीर्ष प्रारूप + उपकरणों का इस्तेमाल + %1$d खुलता है • %2$s + अभी तक कोई उपयोग आँकड़े नहीं + कुछ उपकरण खोलें और वे स्वचालित रूप से यहां दिखाई देंगे + आपके उपयोग के आँकड़े केवल आपके डिवाइस पर स्थानीय रूप से संग्रहीत किए जाते हैं। ImageToolbox उनका उपयोग केवल आपकी व्यक्तिगत गतिविधि, पसंदीदा टूल और सहेजे गए डेटा अंतर्दृष्टि को दिखाने के लिए करता है + संपादक संपादन फोटो चित्र सुधार समायोजन + आकार बदलें, स्केल रिज़ॉल्यूशन आयाम, चौड़ाई, ऊंचाई, जेपीजी, पीएनजी, वेबपी, कंप्रेस करें + संपीड़ित आकार वजन बाइट्स एमबी केबी गुणवत्ता को कम करें अनुकूलन करें + क्रॉप ट्रिम कट पहलू अनुपात + फिल्टर प्रभाव रंग सुधार लुट मास्क समायोजित करें + पेंट ब्रश पेंसिल एनोटेट मार्कअप बनाएं + सिफर एन्क्रिप्ट डिक्रिप्ट पासवर्ड रहस्य + बैकग्राउंड रिमूवर मिटाएं, कटआउट पारदर्शी अल्फा हटाएं + पूर्वावलोकन दर्शक गैलरी का निरीक्षण खुला + स्टिच मर्ज कंबाइन पैनोरमा लंबा स्क्रीनशॉट + यूआरएल वेब डाउनलोड इंटरनेट लिंक छवि + पिकर आईड्रॉपर नमूना हेक्स आरजीबी रंग + पैलेट रंग नमूने प्रमुख निकालते हैं + एक्सिफ़ मेटाडेटा गोपनीयता स्ट्रिप क्लीन जीपीएस स्थान को हटा दें + पहले के बाद के अंतर की तुलना करें + सीमा आकार अधिकतम न्यूनतम मेगापिक्सेल आयाम क्लैंप + पीडीएफ दस्तावेज़ पृष्ठ उपकरण + ओसीआर टेक्स्ट एक्सट्रेक्ट स्कैन को पहचानें + ढाल पृष्ठभूमि रंग जाल + वॉटरमार्क लोगो टेक्स्ट स्टैम्प ओवरले + जीआईएफ एनीमेशन एनिमेटेड कन्वर्ट फ्रेम + एपीएनजी एनीमेशन एनिमेटेड पीएनजी कन्वर्ट फ्रेम + ज़िप संग्रह संपीड़ित पैक फ़ाइलों को अनपैक करें + jxl jpeg xl jpg छवि प्रारूप में कनवर्ट करें + एसवीजी वेक्टर ट्रेस एक्सएमएल कनवर्ट करें + फॉर्मेट कन्वर्ट जेपीजी जेपीईजी पीएनजी वेबपी हेइक एवीआईएफ जेएक्सएल बीएमपी + दस्तावेज़ स्कैनर स्कैन पेपर परिप्रेक्ष्य फसल + क्यूआर बारकोड कोड स्कैनर स्कैन + स्टैकिंग ओवरले औसत माध्यिका एस्ट्रोफोटोग्राफ़ी + स्प्लिट टाइल्स ग्रिड स्लाइस पार्ट्स + कलर कन्वर्ट हेक्स आरजीबी एचएसएल एचएसवी सीएमवाईके लैब + वेबपी एनिमेटेड छवि प्रारूप को परिवर्तित करता है + शोर जनरेटर यादृच्छिक बनावट अनाज + कोलाज ग्रिड लेआउट संयोजन + मार्कअप परतें ओवरले संपादन को एनोटेट करती हैं + बेस64 एन्कोड डिकोड डेटा यूरी + चेकसम हैश एमडी5 शा शा1 शा256 सत्यापित करें + जाल ढाल पृष्ठभूमि + एक्सिफ़ मेटाडेटा संपादित करें जीपीएस दिनांक कैमरा + स्प्लिट ग्रिड टुकड़े टाइल्स काटें + ऑडियो कवर एल्बम कला संगीत मेटाडेटा + वॉलपेपर निर्यात पृष्ठभूमि + एएससीआईआई पाठ कला पात्र + एआई एमएल न्यूरल एन्हांस अपस्केल सेगमेंट + रंग पुस्तकालय सामग्री पैलेट नामित रंग + शेडर जीएलएसएल फ़्रैगमेंट इफ़ेक्ट स्टूडियो + पीडीएफ मर्ज कंबाइन जॉइन + पीडीएफ अलग पेजों को विभाजित करता है + पीडीएफ रोटेट पेज ओरिएंटेशन + पीडीएफ पुनर्व्यवस्थित पृष्ठों को क्रमबद्ध करें + पीडीएफ पेज नंबर पेजिनेशन + पीडीएफ ओसीआर टेक्स्ट खोजने योग्य अर्क को पहचानता है + पीडीएफ वॉटरमार्क स्टैम्प लोगो टेक्स्ट + पीडीएफ हस्ताक्षर चिह्न ड्रा + पीडीएफ सुरक्षा पासवर्ड एन्क्रिप्ट लॉक + पीडीएफ अनलॉक पासवर्ड डिक्रिप्ट सुरक्षा हटाएं + पीडीएफ कंप्रेस आकार को कम करें अनुकूलित करें + पीडीएफ ग्रेस्केल काला सफेद मोनोक्रोम + पीडीएफ रिपेयर फिक्स टूटे हुए को ठीक करें + पीडीएफ मेटाडेटा गुण exif लेखक शीर्षक + पीडीएफ डिलीट पेज हटाएं + पीडीएफ फसल ट्रिम मार्जिन + पीडीएफ फ़्लैटन एनोटेशन परतें बनाता है + पीडीएफ छवियाँ चित्र निकालें + पीडीएफ ज़िप संग्रह संपीड़ित करें + पीडीएफ प्रिंट प्रिंटर + पीडीएफ पूर्वावलोकन दर्शक पढ़ें + छवियों को पीडीएफ में परिवर्तित करें जेपीजी जेपीईजी पीएनजी दस्तावेज़ में + छवि पृष्ठों के लिए पीडीएफ जेपीजी पीएनजी निर्यात करें + पीडीएफ एनोटेशन टिप्पणियाँ साफ हटा दें + तटस्थ संस्करण + गलती + परछाई डालना + दांत की ऊंचाई + क्षैतिज दाँत रेंज + वर्टिकल टूथ रेंज + शीर्ष बढ़त + दाहिना किनारा + निचला किनारा + बायां किनारा + फटा हुआ किनारा + उपयोग आँकड़े रीसेट करें + टूल खुलता है, आँकड़े सहेजें, स्ट्रीक, सहेजा गया डेटा, और शीर्ष प्रारूप रीसेट हो जाएगा। ऐप खुलने का समय अपरिवर्तित रहेगा. + ऑडियो + फ़ाइल + प्रोफ़ाइल निर्यात करें + वर्तमान प्रोफ़ाइल सहेजें + निर्यात प्रोफ़ाइल हटाएँ + क्या आप निर्यात प्रोफ़ाइल \\"%1$s\\" को हटाना चाहते हैं? + सीमा रेखा खींचना + ड्राइंग और मिटाए गए कैनवास के चारों ओर एक पतली सीमा दिखाएं + लहरदार + नरम लहरदार किनारे के साथ गोल यूआई तत्व + स्कूप + निशान + अंदर की ओर गोल कोने खुदे हुए + डबल कट के साथ स्टेप्ड कोने + प्लेसहोल्डर + बिना एक्सटेंशन के मूल फ़ाइल नाम सम्मिलित करने के लिए {फ़ाइल नाम} का उपयोग करें + चमक + आधार राशि + अंगूठी राशि + रे राशि + रिंग की चौड़ाई + विकृत परिप्रेक्ष्य + जावा लुक और फील + कतरनी + एक्स कोण + और कोण + आउटपुट का आकार बदलें + पानी के बिंदु + वेवलेंथ + चरण + हाई पास + रंग मुखौटा + क्रोम + भंग करना + मृदुता + प्रतिक्रिया + लेंस धुंधला + कम इनपुट + उच्च इनपुट + कम उत्पादन + उच्च आउटपुट + प्रकाश प्रभाव + उभार की ऊंचाई + धक्कों की कोमलता + लहर + एक्स आयाम + Y आयाम + एक्स तरंग दैर्ध्य + वाई तरंग दैर्ध्य + अनुकूली धुंधलापन + बनावट निर्माण + विभिन्न प्रक्रियात्मक बनावट बनाएं और उन्हें छवियों के रूप में सहेजें + बनावट प्रकार + पक्षपात + समय + चमक + फैलाव + नमूने + कोण गुणांक + ग्रेडियेंट गुणांक + ग्रिड प्रकार + दूरी की शक्ति + स्केल वाई + फजीपन + आधार प्रकार + अशांति कारक + स्केलिंग + पुनरावृत्तियों + रिंगों + ब्रश की हुई धातु + कास्टिक्स + सेलुलर + बिसात + एफबीएम + संगमरमर + प्लाज्मा + रज़ाई + लकड़ी + Random + वर्ग + षटकोणीय + अष्टकोन + त्रिकोणीय + चोटी वाला + वीएल शोर + एससी शोर + हाल ही का + अभी तक कोई हाल ही में उपयोग किया गया फ़िल्टर नहीं + बनावट जनरेटर ब्रश धातु कास्टिक सेलुलर बिसात संगमरमर प्लाज्मा रजाई लकड़ी ईंट छलावरण कोशिका बादल दरार कपड़े पत्ते मधुकोश बर्फ लावा निहारिका कागज जंग रेत धुआं पत्थर भूभाग स्थलाकृति पानी की लहर + ईंट + छलावरण + कक्ष + बादल + दरार + कपड़ा + पत्ते + मधुकोश का + बर्फ़ + लावा + नाब्युला + कागज़ + जंग + रेत + धुआँ + पत्थर + इलाके + तलरूप + पानी की लहर + उन्नत लकड़ी + मोर्टार की चौड़ाई + अनियमितता + झुकना + बेअदबी + पहली दहलीज + दूसरी दहलीज + तीसरी दहलीज + किनारे की कोमलता + सीमा चौड़ाई + कवरेज + विवरण + गहराई + शाखाओं में + क्षैतिज धागे + लंबवत धागे + परमाणु रूप में पृथक होना + नसों + प्रकाश + दरार की चौड़ाई + पाला + प्रवाह + पपड़ी + बादल का घनत्व + सितारे + फाइबर घनत्व + फाइबर ताकत + दाग + जंग + खड़ा + गुच्छे + टिब्बा आवृत्ति + पवन कोण + लहरें + कणों का + शिरा पैमाना + पानी की सतह + पर्वतीय स्तर + कटाव + हिमपात का स्तर + पंक्ति गिनती + रेखा मोटाई + लकीर खींचने की क्रिया + रोमछिद्रों का रंग + मोर्टार का रंग + गहरे ईंट का रंग + ईंट का रंग + रंग हाइलाइट करें + गहरा रंग + जंगल का रंग + पृथ्वी का रंग + रेत का रंग + पृष्ठभूमि का रंग + कोशिका का रंग + किनारे का रंग + आसमानी रंग + छाया रंग + हल्के रंग + सतह का रंग + भिन्न-भिन्न रंग + दरार का रंग + ताना-बाना रंग + बाने का रंग + गहरे पत्ते का रंग + पत्ती का रंग + सीमा रंग + शहद का रंग + गहरा रंग + बर्फ का रंग + ठंढा रंग + पपड़ी का रंग + रंग धो लें + चमकीला रंग + अंतरिक्ष का रंग + बैंगनी रंग + नीला रंग + आधारभूत रंग + रेशे का रंग + दाग का रंग + धातु का रंग + गहरे जंग का रंग + जंग का रंग + नारंगी रंग + धुएँ का रंग + नस का रंग + तराई का रंग + पानी का रंग + चट्टानी रंग + बर्फ का रंग + कम रंग + उच्च रंग + रेखा रंग + उथला रंग + घास + गंध + चमड़ा + ठोस + डामर + काई + आग + अरोड़ा + तेल निकला + आबरंग + सार प्रवाह + दूधिया पत्थर + दमिश्क स्टील + बिजली चमकना + मख़मली + स्याही मार्बलिंग + होलोग्राफिक फ़ॉइल + बायोलुमिनसेंस + ब्रह्मांडीय भंवर + लावा लैंप + घटना क्षितिज + फ्रैक्टल ब्लूम + रंगीन सुरंग + ग्रहण कोरोना + अजीब आकर्षण + फेरोफ्लुइड क्राउन + सुपरनोवा + आईरिस + मोर के पंख + नॉटिलस शैल + चक्राकार ग्रह + ब्लेड का घनत्व + ब्लेड की लंबाई + हवा + चिथड़ेपन + गुच्छों + नमी + कंकड़ + झुर्रियाँ + छिद्र + सकल + दरारें + टार + घिसाव + धब्बेदार + रेशे + ज्वाला आवृत्ति + धुआँ + तीव्रता + रिबन + बैंड + आनंददायकता + अंधेरा + खिलता + रंग + किनारों + कागज़ + प्रसार + समरूपता + तीखेपन + रंग खेलना + दूधियापन + परतें + तह + पोलिश + शाखाओं + दिशा + चमक + परतों + तीर के सिरेपर पर लगाना + स्याही संतुलन + स्पेक्ट्रम + चॉकलेट + विवर्तन + हथियारों + मोड़ + कोर चमक + धब्बे + डिस्क झुकाव + क्षितिज का आकार + डिस्क की चौड़ाई + लेंसिंग + पंखुड़ियों + कर्ल + चांदी के महीन + पहलुओं + वक्रता + चंद्रमा का आकार + कोरोना का आकार + किरणों + हीरे की अंगूठी + पालियों + कक्षा घनत्व + मोटाई + स्पाइक + स्पाइक की लंबाई + शरीर का नाप + धातु का + शॉक त्रिज्या + खोल की चौड़ाई + बाहर फेंको + पुतली का आकार + आईरिस का आकार + रंग भिन्नता + कैचलाइट + आँख का आकार + कंटिया घनत्व + मोड़ों + मंडलों + प्रारंभिक + लकीरें + मोतियों की चमक + ग्रह का आकार + रिंग झुकाव + रिंग की चौड़ाई + वायुमंडल + गंदा रंग + गहरे घास का रंग + घास का रंग + टिप का रंग + गहरा पृथ्वी रंग + सूखा रंग + कंकड़ रंग + चमड़े का रंग + ठोस रंग + टार रंग + डामर का रंग + पत्थर का रंग + धूल का रंग + मिट्टी का रंग + गहरे काई का रंग + काई का रंग + लाल रंग + कोर रंग + हरा रंग + स्यान रंग + मैजेंटा रंग + सुनहरा रंग + कागज का रंग + वर्णक रंग + द्वितीयक रंग + पहला रंग + दूसरा रंग + गुलाबी रंग + गहरा स्टील रंग + स्टील का रंग + हल्के स्टील का रंग + ऑक्साइड रंग + प्रभामंडल रंग + बोल्ट का रंग + मखमली रंग + चमकीला रंग + नीली स्याही का रंग + लाल स्याही का रंग + गहरा स्याही रंग + चांदी के रंग + पीला रंग + ऊतक का रंग + डिस्क का रंग + गरम रंग + लेंस का रंग + बाहरी रंग + भीतरी रंग + कोरोना रंग + ठंडा रंग + गरम रंग + बादल का रंग + लौ का रंग + पंख का रंग + शैल रंग + मोती का रंग + ग्रह का रंग + अंगूठी का रंग + सहेजने के बाद मूल फ़ाइलें हटा दें + केवल सफलतापूर्वक संसाधित स्रोत फ़ाइलें ही हटाई जाएंगी + मूल फ़ाइलें हटा दी गईं: %1$d + मूल फ़ाइलें हटाने में विफल: %1$d + मूल फ़ाइलें हटाई गईं: %1$d, विफल: %2$d + शीट इशारे + विस्तारित विकल्प पत्रक को खींचने की अनुमति दें + क्रोमा सबसैंपलिंग + थोड़ी गहराई + दोषरहित + %1$d-बिट + बैच का नाम बदलें + फ़ाइल नाम पैटर्न का उपयोग करके एकाधिक फ़ाइलों का नाम बदलें + बैच फ़ाइलों का नाम बदलें फ़ाइल नाम पैटर्न अनुक्रम दिनांक विस्तार + नाम बदलने के लिए फ़ाइलें चुनें + फ़ाइलनाम पैटर्न + नाम बदलें + दिनांक स्रोत + EXIF दिनांक लिया गया + फ़ाइल संशोधित दिनांक + फ़ाइल निर्माण तिथि + वर्तमान तिथि + मैनुअल तिथि + मैनुअल तिथि + मैनुअल समय + कुछ फ़ाइलों के लिए चयनित तिथि अनुपलब्ध है. उनके लिए वर्तमान तिथि का उपयोग किया जाएगा. + फ़ाइल नाम पैटर्न दर्ज करें + पैटर्न एक अमान्य या अत्यधिक लंबा फ़ाइल नाम उत्पन्न करता है + पैटर्न डुप्लिकेट फ़ाइल नाम उत्पन्न करता है + सभी फ़ाइल नाम पहले से ही पैटर्न से मेल खाते हैं + यह पैटर्न उन टोकन का उपयोग करता है जो बैच नाम बदलने के लिए उपलब्ध नहीं हैं + नाम वही रहेगा + फ़ाइलों का नाम सफलतापूर्वक बदला गया + कुछ फ़ाइलों का नाम नहीं बदला जा सका + अनुमति नहीं दी गई + बिना एक्सटेंशन के मूल फ़ाइल नाम का उपयोग करता है, जिससे आपको स्रोत पहचान बरकरार रखने में मदद मिलती है। \\o{start:end} के साथ स्लाइसिंग, \\o{t} के साथ लिप्यंतरण और \\o{s/old/new/} के साथ बदलने का भी समर्थन करता है। + स्वत: वृद्धिशील काउंटर. कस्टम फ़ॉर्मेटिंग का समर्थन करता है: \\c{padding} (उदा. \\c{3} -&gt; 001), \\c{start:step} या \\c{start:step:padding}. + मूल फ़ोल्डर का नाम जहां मूल फ़ाइल स्थित थी. + मूल फ़ाइल का आकार. इकाइयों का समर्थन करता है: \\z{b}, \\z{kb}, \\z{mb} या मानव पठनीय प्रारूप के लिए बस \\z। + फ़ाइल नाम की विशिष्टता सुनिश्चित करने के लिए एक यादृच्छिक सार्वभौमिक विशिष्ट पहचानकर्ता (यूयूआईडी) उत्पन्न करता है। + फ़ाइलों का नाम बदलना पूर्ववत नहीं किया जा सकता. जारी रखने से पहले सुनिश्चित करें कि नए नाम सही हैं। + नाम बदलने की पुष्टि करें + साइड मेनू सेटिंग्स + मेनू स्केल + मेनू अल्फा + ऑटो व्हाइट बैलेंस + कतरन + छिपे हुए वॉटरमार्क की जाँच की जा रही है + भंडारण + कैश सीमा + जब कैश इस आकार से अधिक बढ़ जाए तो स्वचालित रूप से साफ़ करें + सफ़ाई अंतराल + कितनी बार जांचें कि कैश साफ़ होना चाहिए या नहीं + ऐप लॉन्च पर + 1 दिन + 1 सप्ताह + 1 महीना + चयनित सीमा और अंतराल के अनुसार ऐप कैश को स्वचालित रूप से साफ़ करें + चल फसल क्षेत्र + संपूर्ण फसल क्षेत्र को अंदर खींचकर ले जाने की अनुमति देता है + GIF मर्ज करें + एकाधिक GIF क्लिप को एक एनीमेशन में संयोजित करें + जीआईएफ क्लिप ऑर्डर + रिवर्स + उलटे की तरह खेलें + बुमेरांग + आगे खेलें और फिर पीछे की ओर + फ़्रेम का आकार सामान्य करें + सबसे बड़ी क्लिप को फिट करने के लिए स्केल फ़्रेम; उनके मूल पैमाने को संरक्षित करने के लिए बंद करें + क्लिप के बीच विलंब (एमएस) + फ़ोल्डर + किसी फ़ोल्डर और उसके सबफ़ोल्डर्स से सभी छवियों का चयन करें + डुप्लिकेट खोजक + सटीक प्रतियाँ और दृष्टिगत रूप से समान छवियाँ ढूँढ़ें + डुप्लिकेट खोजक समान छवियाँ सटीक प्रतिलिपियाँ फ़ोटो क्लीनअप स्टोरेज dHash SHA-256 + डुप्लिकेट ढूंढने के लिए छवियां चुनें + समानता संवेदनशीलता + सटीक प्रतिलिपियाँ + समान छवियाँ + सभी सटीक डुप्लिकेट का चयन करें + अनुशंसित को छोड़कर सभी का चयन करें + कोई डुप्लिकेट नहीं मिला + अधिक छवियाँ जोड़ने या समानता संवेदनशीलता बढ़ाने का प्रयास करें + %1$d छवि(छवियों) को पढ़ा नहीं जा सका + %1$s • %2$s पुनः प्राप्त करने योग्य + %1$d समूह • %2$s पुनः प्राप्त करने योग्य + %1$d चयनित • %2$s + इसे असंपादित नहीं किया जा सकता है। एंड्रॉइड आपसे सिस्टम डायलॉग में डिलीट की पुष्टि करने के लिए कह सकता है। + नहीं मिला + आरंभ करने के लिए आगे बढ़ें + समाप्त करने के लिए ले जाएँ + \ No newline at end of file diff --git a/core/resources/src/main/res/values-hu/strings.xml b/core/resources/src/main/res/values-hu/strings.xml new file mode 100644 index 0000000..ee17b87 --- /dev/null +++ b/core/resources/src/main/res/values-hu/strings.xml @@ -0,0 +1,3740 @@ + + + + + É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 + 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 + 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. + 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. + 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 + Tartalomtudatos torzítás + Nincs elég memória a művelet végrehajtásához. Kérjük, próbáljon meg egy kisebb képet használni, zárjon be más alkalmazásokat, vagy indítsa újra az alkalmazást. + Párhuzamos munkások + Ezeket a képeket nem mentettük, mert a konvertált fájlok nagyobbak lennének, mint az eredetiek. Az eredeti képek törlése előtt ellenőrizze ezt a listát. + Kihagyott fájlok: %1$s + Alkalmazásnaplók + Tekintse meg az alkalmazás naplóit a problémák észleléséhez + Kedvencek csoportosított eszközökben + Hozzáadja a kedvenceket lapként, amikor az eszközök típus szerint vannak csoportosítva + Kedvenc megjelenítése utolsóként + A kedvenc eszközök lapját a végére helyezi + Vízszintes távolság + Függőleges távolság + Visszafelé irányuló energia + Használjon egyszerű gradiens nagyságrendű energiatérképet az előremenő energia algoritmus helyett + Távolítsa el a maszkolt területet + A varratok áthaladnak a maszkolt területen, és csak a kiválasztott területet távolítják el, ahelyett, hogy megvédenék + VHS NTSC + Speciális NTSC beállítások + Extra analóg hangolás az erősebb VHS és sugárzott műtermékek érdekében + Chroma vérzés + Szalagkopás + Követés + Luma kenet + Csengetés + + Mező használata + Szűrő típusa + Bemeneti luma szűrő + Bemeneti chroma aluláteresztő + Króma demoduláció + Fázisváltás + Fáziseltolás + Fejváltás + Fej kapcsolási magassága + Fej kapcsolási eltolás + Fejváltó váltás + Középvonali pozíció + Középvonali jitter + Zajmagasság követése + Követési hullám + A hókövetés + A hó anizotrópia követése + Követési zaj + A zaj intenzitásának követése + Kompozit zaj + Kompozit zajfrekvencia + Kompozit zajintenzitás + Kompozit zajrészlet + Csengetési gyakoriság + Csengetési erő + Luma zaj + Luma zajfrekvencia + Luma zaj intenzitása + Luma zaj részlet + Chroma zaj + Chroma zaj frekvencia + Króma zaj intenzitása + Króma zajrészlet + A hó intenzitása + Havas anizotrópia + Krómfázis zaj + Krómfázis hiba + Vízszintes színkésleltetés + Chroma késleltetés függőleges + VHS szalag sebesség + VHS színvesztés + VHS élesítés intenzitása + VHS élesítési frekvencia + VHS élhullám intenzitása + VHS élhullám sebesség + VHS élhullám frekvencia + VHS élhullám részlet + Kimeneti chroma aluláteresztő + Vízszintes lépték + Függőleges lépték + X léptéktényező + Y léptéktényező + Érzékeli a gyakori képi vízjeleket, és LaMa-val befesti őket. Automatikusan letölti az észlelési és festési modelleket + Deuteranópia + Kép kibontása + Objektumszegmentáción alapuló háttéreltávolító a YOLO v11 szegmentáció segítségével + Vonalszög megjelenítése + Rajzolás közben az aktuális vonalelforgatást mutatja fokban + Animált hangulatjelek + Az elérhető hangulatjelek megjelenítése animációként + Mentés az eredeti mappába + Mentse az új fájlokat az eredeti fájl mellé a kiválasztott mappa helyett + Vízjel PDF + Nincs elég memória + A kép valószínűleg túl nagy ahhoz, hogy feldolgozzuk ezen az eszközön, vagy a rendszerben elfogyott a szabad memória. Csökkentse a képfelbontást, zárjon be más alkalmazásokat, vagy válasszon kisebb fájlt. + Hibakeresés menü + Az alkalmazás funkcióinak tesztelésére szolgáló menü, ennek nem célja, hogy megjelenjen az éles kiadásban + Szolgáltató + A PaddleOCR további ONNX modelleket igényel az eszközön. Szeretné letölteni a %1$s adatot? + Egyetemes + koreai + latin + keleti szláv + thai + görög + angol + cirill betűs + arab + Devanagari + tamil + telugu + Shader + Shader előre beállított + Nincs kijelölve árnyékoló + Shader Stúdió + Egyéni töredék árnyékolók létrehozása, szerkesztése, érvényesítése, importálása és exportálása + Mentett árnyékolók + Mentett árnyékolók megnyitása, másolás, exportálás, megosztás vagy törlés + Új shader + Shader fájl + .itshader JSON + %1$d paraméterek + Shader forrás + Csak a void main() törzsét írja be. Használja a textureCoordinate-ot az UV-koordinátákhoz és az inputImageTexture-t forrásmintavevőként. + Funkciók + Opcionális segédfüggvények, konstansok és struktúrák a void main() elé beszúrva. Az egyenruha paramokból jön létre. + Adjon hozzá egyenruhákat, ha a shadernek szerkeszthető értékekre van szüksége. + Shader alaphelyzetbe állítása + Az aktuális árnyékoló piszkozat törlődik + Érvénytelen shader + Nem támogatott shader verzió: %1$d. Támogatott verzió: %2$d. + A Shader neve nem lehet üres. + A Shader forrása nem lehet üres. + A Shader forrás nem támogatott karaktereket tartalmaz: %1$s. Csak ASCII GLSL forrást használjon. + A \\\"%1$s\\\" paraméter többször deklarálva van. + A paraméternevek nem lehetnek üresek. + A \\\"%1$s\\\" paraméter fenntartott GPUI-képnevet használ. + A \\\"%1$s\\\" paraméternek érvényes GLSL-azonosítónak kell lennie. + A \\\"%1$s\\\" paraméter %2$s értéktípusa \\\"%3$s\\\", várható érték: \\\"%4$s\\\". + A \\\"%1$s\\\" paraméter nem határozhat meg minimum vagy max értéket a logikai értékekhez. + A \\\"%1$s\\\" paraméter min nagyobb, mint max. + A \\\"%1$s\\\" paraméter alapértelmezett értéke kisebb, mint min. + A \\\"%1$s\\\" paraméter alapértelmezett értéke nagyobb, mint a max. + A Shadernek deklarálnia kell, hogy \\\"uniform sampler2D %1$s;\\\". + A Shadernek deklarálnia kell, hogy \\\"változó vec2 %1$s;\\\". + A Shadernek meg kell határoznia a \\\"void main()\\\" paramétert. + A Shadernek színt kell írnia a gl_FragColor-ba. + A ShaderToy mainImage shaderek nem támogatottak. Használja a GPUImage érvénytelen main() szerződését. + Az \\\"iTime\\\" animált ShaderToy egyenruha nem támogatott. + Az animált ShaderToy egyenruha \\\"iFrame\\\" nem támogatott. + A ShaderToy egységes \\\"iResolution\\\" nem támogatott. + A ShaderToy iChannel textúrák nem támogatottak. + A külső textúrák nem támogatottak. + A külső textúra kiterjesztések nem támogatottak. + A Libretro shader paraméterei nem támogatottak. + Csak egy bemeneti textúra támogatott. Távolítsa el az \\\"uniform %1$s %2$s;\\\". + A \\\"%1$s\\\" paramétert \\\"uniform %2$s %1$s;\\\"-ként kell deklarálni. + A \\\"%1$s\\\" paraméter a következőként van deklarálva: \\\"uniform %2$s %1$s;\\\", várhatóan \\\"uniform %3$s %1$s;\\\". + A Shader fájl üres. + A Shader fájlnak .itshader JSON-objektumnak kell lennie verzió-, név- és shader-mezőkkel. + A Shader fájl nem érvényes JSON, vagy nem egyezik az .itshader formátummal. + A \\\"%1$s\\\" mező kitöltése kötelező. + %1$s szükséges. + %1$s \\\"%2$s\\\" nem támogatott. Támogatott típusok: %3$s. + %1$s szükséges a \\\"%2$s\\\" paraméterekhez. + A %1$s véges szám kell, hogy legyen. + %1$s egész számnak kell lennie. + %1$s igaznak vagy hamisnak kell lennie. + A %1$s színes karakterláncnak, RGB/RGBA tömbnek vagy színes objektumnak kell lennie. + %1$s #RRGGBAB vagy #RRGGBBAA formátumot kell használnia. + A %1$s érvényes hexadecimális színcsatornákat kell, hogy tartalmazzon. + A %1$s 3 vagy 4 színcsatornát kell, hogy tartalmazzon. + A %1$s értékének 0 és 255 között kell lennie. + A %1$s egy kétszámú tömbnek vagy egy x-et és y-t tartalmazó objektumnak kell lennie. + A %1$s pontnak pontosan 2 számot kell tartalmaznia. + Másolat + Mindig törölje az EXIF-et + Mentéskor távolítsa el a kép EXIF-adatait, még akkor is, ha egy eszköz a metaadatok megtartását kéri + Segítség és tippek + %1$d oktatóanyagok + Nyissa meg az eszközt + Ismerje meg a főbb eszközöket, az exportálási lehetőségeket, a PDF-folyamatokat, a színes segédprogramokat és a gyakori problémák javításait + Kezdő lépések + Válasszon eszközöket, importáljon fájlokat, mentse el az eredményeket, és használja fel gyorsabban a beállításokat + Képszerkesztés + Átméretezheti, kivághatja, szűrheti, törölheti a háttereket, rajzolhat és vízjelezhet képeket + Fájlok és metaadatok + A formátumok konvertálása, a kimenetek összehasonlítása, a magánélet védelme és a fájlnevek szabályozása + PDF és dokumentumok + Hozzon létre PDF-fájlokat, szkenneljen be dokumentumokat, OCR-oldalakat, és készítsen elő fájlokat megosztásra + Szöveg, QR és adatok + Szöveg felismerése, kódok beolvasása, Base64 kódolása, kivonatok ellenőrzése és csomagfájlok + Színes eszközök + Válasszon színeket, építsen palettákat, fedezze fel a színkönyvtárakat és hozzon létre színátmeneteket + Kreatív eszközök + SVG, ASCII művészet, zajtextúrák, árnyékolók és kísérleti látványelemek létrehozása + Hibaelhárítás + Javítsa ki a nehéz fájlok, az átláthatóság, a megosztás, az importálás és a jelentéskészítési problémákat + Válassza ki a megfelelő eszközt + Használjon kategóriákat, keresést, kedvenceket, és ossza meg a műveleteket, hogy a megfelelő helyen kezdje + Kezdje a legjobb belépési ponttól + Az Image Toolbox számos fókuszált eszközzel rendelkezik, és sok közülük első pillantásra átfedi egymást. Kezdje a befejezni kívánt feladattól, majd válassza ki az eredmény legfontosabb részét vezérlő eszközt: méretet, formátumot, metaadatokat, szöveget, PDF-oldalakat, színeket vagy elrendezést. + Használja a keresést, ha ismeri a művelet nevét, például átméretezés, PDF, EXIF, OCR, QR vagy szín. + Nyisson meg egy kategóriát, ha csak a feladat típusát ismeri, majd rögzítse a gyakori eszközöket kedvencként. + Ha egy másik alkalmazás megoszt egy képet az Image Toolboxban, válassza ki az eszközt a megosztási munkafolyamatból. + Az eredmények importálása, mentése és megosztása + Ismerje meg a szokásos folyamatot a bemenet kiválasztásától a szerkesztett fájl exportálásáig + Kövesse a közös szerkesztési folyamatot + A legtöbb eszköz ugyanazt a ritmust követi: bevitel kiválasztása, beállítások módosítása, előnézet, majd mentés vagy megosztás. A fontos részlet az, hogy a mentés kimeneti fájlt hoz létre, míg a megosztás általában a legjobb egy másik alkalmazásba való gyors átadáshoz. + Válasszon egy fájlt az egyképes eszközökhöz, vagy több fájlt a kötegelt eszközökhöz, ha a kiválasztó lehetővé teszi. + Mentés előtt ellenőrizze az előnézeti és kimeneti vezérlőket, különösen a formátum, a minőség és a fájlnév beállításokat. + Használja a megosztást a gyors átadáshoz, vagy mentse el, ha tartós másolatra van szüksége a kiválasztott mappában. + Dolgozzon több képpel egyszerre + A kötegelt eszközök a legjobbak ismételt átméretezési, átalakítási, tömörítési és elnevezési feladatokhoz + Készítsen előre kiszámítható adagot + A kötegelt feldolgozás akkor a leggyorsabb, ha minden kimenetnek ugyanazokat a szabályokat kell követnie. Ezen túlmenően ez a legegyszerűbb hely a nagy hiba elkövetésére, ezért válassza ki az elnevezést, a minőséget, a metaadatokat és a mappa viselkedését, mielőtt elkezdené a hosszú exportálást. + Jelölje ki az összes kapcsolódó képet együtt, és tartsa meg a sorrendet, ha a kimenet a sorrendtől függ. + Az exportálás megkezdése előtt állítsa be egyszer az átméretezési, formázási, minőségi, metaadatokra és fájlnévre vonatkozó szabályokat. + Először futtasson egy kis tesztköteget, ha a forrásfájlok nagyok, vagy ha a célformátum új az Ön számára. + Gyors egyszeri szerkesztés + Használja a többfunkciós szerkesztőt, ha több egyszerű módosításra van szüksége egy képen + Készítsen egy képet szerszámugrás nélkül + Az Egyszeri szerkesztés akkor hasznos, ha a képen egy speciális művelet helyett kis változtatási láncra van szükség. Általában gyorsabb a gyors tisztításhoz, előnézethez és egy végső másolat exportálásához kötegelt munkafolyamat létrehozása nélkül. + Nyissa meg az Egyszeri szerkesztést egy olyan képhez, amelyen általános beállításokra, jelölésekre, körbevágásra, elforgatásra vagy exportálásra van szükség. + Alkalmazza a szerkesztéseket abban a sorrendben, hogy a legkevesebb képpontot módosítsa először, például a szűrők előtti levágást és a végső tömörítés előtti szűrőket. + Ehelyett használjon speciális eszközt, ha kötegelt feldolgozásra, pontos fájlméret-célokra, PDF-kimenetre, OCR-re vagy metaadat-tisztításra van szüksége. + Előbeállítások és beállítások használata + Mentse el az ismételt választásokat, és hangolja be az alkalmazást a kívánt munkafolyamathoz + Ne ismételje meg ugyanazt a beállítást + Az előbeállítások és beállítások hasznosak, ha gyakran exportál ugyanazt a fájltípust. Egy jó előre beállított érték az ismétlődő kézi ellenőrzőlistát egyetlen érintéssel változtatja meg, míg a globális beállítások határozzák meg az új eszközök alapértékeit. + Hozzon létre előre beállított értékeket a gyakori kimeneti méretekhez, formátumokhoz, minőségi értékekhez vagy elnevezési mintákhoz. + Tekintse át az alapértelmezett formátum, minőség, mentési mappa, fájlnév, választó és interfész viselkedésének beállításait. + Az alkalmazás újratelepítése vagy másik eszközre való áthelyezése előtt készítsen biztonsági másolatot a beállításokról. + Hasznos alapértelmezések beállítása + Válassza ki egyszer az alapértelmezett formátumot, minőséget, méretezési módot, átméretezési típust és színteret + Tegye az új eszközöket közelebb a célhoz + Az alapértelmezett értékek nem csak kozmetikai beállítások. Ők döntik el, hogy sok eszközben milyen formátum, minőség, átméretezési viselkedés, méretezési mód és színtér jelenjen meg először, ami segít elkerülni, hogy minden exportáláskor ugyanazokat a lehetőségeket kelljen újra kiválasztani. + Állítsa be az alapértelmezett képformátumot és -minőséget, hogy megfeleljen a szokásos célhelyének, például JPEG fényképekhez vagy PNG/WebP alfa esetén. + Hangolja be az alapértelmezett átméretezési típust és méretezési módot, ha gyakran exportál szigorú méretekre, közösségi platformokra vagy dokumentumoldalakra. + Csak akkor változtassa meg a színtér alapértelmezett beállításait, ha a munkafolyamat következetes színkezelést igényel a kijelzők, a nyomtatási vagy a külső szerkesztők között. + Picker és launcher + Használja a kiválasztási beállításokat, ha az alkalmazás túl sok párbeszédablakot nyit meg, vagy rossz helyen indul + A fájlok megnyitása megfeleljen a szokásainak + A kiválasztó és indító beállításai szabályozzák, hogy az Image Toolbox a főképernyőről, egy eszközről vagy egy fájlkiválasztási folyamatról induljon-e el. Ezek a beállítások különösen akkor hasznosak, ha általában az Android megosztási lapjáról lép be, vagy ha azt szeretné, hogy az alkalmazás inkább eszközindítónak tűnjön. + Használja a fotóválasztó beállításait, ha a rendszerválasztó elrejti a fájlokat, túl sok friss elemet jelenít meg, vagy rosszul kezeli a felhőfájlokat. + Csak azoknál az eszközöknél engedélyezze a kihagyást, amelyekhez általában megosztásból lép be, vagy amelyekhez már ismeri a forrásfájlt. + Tekintse át az Indító módot, ha azt szeretné, hogy az Image Toolbox inkább eszközválasztóként működjön, mint egy normál kezdőképernyőn. + Kép forrása + Válassza ki azt a kiválasztó módot, amely a legjobban működik a galériákkal, fájlkezelőkkel és felhőfájlokkal + Válasszon fájlokat a megfelelő forrásból + A képforrás beállításai befolyásolják, hogy az alkalmazás hogyan kér képeket az Androidtól. A legjobb választás attól függ, hogy a fájljai a rendszergalériában, egy fájlkezelő mappában, egy felhőszolgáltatóban vagy más ideiglenes tartalmat megosztó alkalmazásban találhatók. + Használja az alapértelmezett választót, ha a legtöbb rendszerbe integrált élményt szeretné elérni a gyakori galériaképekhez. + Váltson kiválasztó módra, ha az albumok, mappák, felhőfájlok vagy nem szabványos formátumok nem ott jelennek meg, ahol elvárná. + Ha egy megosztott fájl eltűnik a forrásalkalmazás elhagyása után, nyissa meg újra egy állandó kiválasztó segítségével, vagy először mentse el a helyi másolatot. + Kedvencek és megosztási eszközök + Tartsa fókuszban a főképernyőt, és rejtse el azokat az eszközöket, amelyeknek nincs értelme a megosztási folyamatokban + Csökkentse a szerszámlista zaját + A kedvencek és a rejtett megosztási beállítások akkor hasznosak, ha naponta csak néhány eszközt használ. Praktikussá teszik a megosztási folyamatot azáltal, hogy elrejtik azokat az eszközöket, amelyek nem tudják használni a másik alkalmazás által küldött fájltípust. + Gyakran rögzítse az eszközöket, így könnyen elérhetőek maradnak még akkor is, ha az eszközök kategóriák szerint vannak csoportosítva. + Eszközök elrejtése a megosztási folyamatokból, ha azok nem relevánsak egy másik alkalmazásból származó fájlok szempontjából. + Használja a kedvencek rendezési beállításait, ha a kedvenceket részesíti előnyben a végén, vagy a kapcsolódó eszközökkel csoportosítva. + Beállítások biztonsági mentése + Helyezze át biztonságosan az előre beállított értékeket, beállításokat és alkalmazások viselkedését egy másik telepítésre + Tartsa hordozható beállítását + A Biztonsági mentés és visszaállítás akkor a leghasznosabb, ha beállította az előre beállított értékeket, mappákat, fájlnév-szabályokat, az eszközök sorrendjét és a vizuális beállításokat. A biztonsági másolat segítségével kísérletezhet a beállításokkal, vagy újratelepítheti az alkalmazást anélkül, hogy a munkafolyamatot a memóriából újra kellene építeni. + Készítsen biztonsági másolatot az előre beállított értékek, a fájlnevek viselkedése, az alapértelmezett formátumok vagy az eszközelrendezés módosítása után. + Tárolja a biztonsági másolatot valahol az alkalmazásmappán kívül, ha adatok törlését, újratelepítését vagy eszközök áthelyezését tervezi. + A fontos kötegek feldolgozása előtt állítsa vissza a beállításokat, hogy az alapértelmezett értékek és a mentési viselkedés megegyezzen a régi beállítással. + Képek átméretezése és konvertálása + Módosítsa egy vagy több kép méretét, formátumát, minőségét és metaadatait + Hozzon létre átméretezett másolatokat + A Resize and Convert a fő eszköz a kiszámítható méretekhez és a könnyebb kimeneti fájlok eléréséhez. Akkor használja, ha a kép mérete, a kimeneti formátum és a metaadatok viselkedése egyszerre számít. + Válasszon ki egy vagy több képet, majd válassza ki, hogy a méret, a méretarány vagy a fájlméret számít-e leginkább. + Válassza ki a kimeneti formátumot és minőséget; használjon PNG-t vagy WebP-t, ha meg kell őrizni az átláthatóságot. + Tekintse meg az eredmény előnézetét, majd mentse vagy ossza meg a létrehozott másolatokat anélkül, hogy megváltoztatná az eredetiket. + Átméretezés fájlméret szerint + Célozzon meg egy méretkorlátot, amikor egy webhely, üzenetküldő vagy űrlap elutasítja a nehéz képeket + Illessze be a szigorú feltöltési korlátokat + A fájlméret szerinti átméretezés jobb, mint a minőségi értékek kitalálása, amikor a cél csak egy adott határ alatti fájlokat fogad el. Az eszköz módosíthatja a tömörítést és a méreteket a cél elérése érdekében, miközben megpróbálja az eredményt használható maradni. + Adja meg a célméretet valamivel a valós feltöltési korlát alatt, hogy teret hagyjon a metaadatok és a szolgáltató módosításainak. + Előnyben részesítse a veszteséges formátumokat a fényképekhez, ha a cél kicsi; A PNG még átméretezés után is nagy maradhat. + Exportálás után ellenőrizze az arcokat, a szöveget és az éleket, mert az agresszív méretű céltárgyak látható műtermékeket okozhatnak. + Korlátozza az átméretezést + Csak azokat a képeket zsugorítsa össze, amelyek túllépik a maximális szélességet, magasságot vagy fájlkorlátozást + Kerülje a már megfelelő képek átméretezését + A Limit Resize hasznos vegyes mappák esetén, ahol egyes képek hatalmasak, mások pedig már készen vannak. Ahelyett, hogy minden fájlt ugyanarra az átméretezésre kényszerítene, az elfogadható képeket közelebb tartja az eredeti minőségükhöz. + Állítsa be a maximális méreteket vagy korlátokat a cél alapján, ahelyett, hogy vakon átméretezné az összes fájlt. + Ha el szeretné kerülni a forrásoknál nehezebb kimeneteket, engedélyezze a „kihagyás, ha nagyobb” stílust. + Használja ezt a különböző kamerákból származó kötegekhez, képernyőképekhez vagy olyan letöltésekhez, ahol a forrásméretek nagyon eltérőek. + Vágja, forgassa és egyenesítse ki + A kép exportálása vagy más eszközökben való felhasználása előtt keretezze be a fontos területet + Tisztítsa meg a kompozíciót + A körbevágással a későbbiekben tisztábbak lesznek a szűrők, az OCR, a PDF-oldalak és az eredmények megosztása. + Nyissa meg a Vágás lehetőséget, és válassza ki a módosítani kívánt képet. + Használjon szabad kivágást vagy képarányt, ha a kimenetnek illeszkednie kell egy bejegyzéshez, dokumentumhoz vagy avatarhoz. + Mentés előtt forgassa el vagy fordítsa meg, hogy a későbbi eszközök megkapják a javított képet. + Szűrők és beállítások alkalmazása + Szerkeszthető szűrőverem létrehozása és az eredmény előnézetének megtekintése az exportálás előtt + Hangolja be a kép megjelenését + A szűrők hasznosak a gyors javításokhoz, a stilizált kimenetekhez, valamint a képek OCR-hez vagy nyomtatáshoz való előkészítéséhez. Ha a kép szöveget vagy finom részleteket tartalmaz, a kontraszt, az élesség és a fényerő kis változtatásai többet jelentenek, mint az erős hatások. + Adjon hozzá egyenként szűrőket, és minden változtatás után figyelje az előnézetet. + A feladattól függően állítsa be a fényerőt, a kontrasztot, az élességet, az elmosódást, a színt vagy a maszkokat. + Csak akkor exportáljon, ha az előnézet megfelel a céleszköznek, dokumentumnak vagy közösségi platformnak. + Előbb az előnézet + Vizsgálja meg a képeket, mielőtt súlyosabb szerkesztő-, konvertáló- vagy tisztítóeszközt választana + Ellenőrizze, hogy mit kapott valójában + A kép előnézete akkor hasznos, ha egy fájl csevegésből, felhőtárhelyről vagy más alkalmazásból származik, és nem biztos benne, hogy mit tartalmaz. A méretek, az átlátszóság, a tájolás és a vizuális minőség ellenőrzése először segít kiválasztani a megfelelő nyomkövető eszközt. + Nyissa meg a Kép-előnézetet, ha több képet is meg kell vizsgálnia, mielőtt eldönti, mit tegyen velük. + Keresse a forgatási problémákat, az átlátszó területeket, a tömörítési műtermékeket és a váratlanul nagy méreteket. + Csak akkor lépjen át az Átméretezés, Vágás, Összehasonlítás, EXIF ​​vagy Háttéreltávolító elemre, ha tudja, hogy mit kell javítani. + Háttér eltávolítása vagy visszaállítása + Törölje automatikusan a háttereket, vagy finomítsa manuálisan az éleket a visszaállító eszközökkel + Tartsa meg a témát, távolítsa el a többit + A háttér eltávolítása akkor működik a legjobban, ha az automatikus kiválasztást a gondos kézi tisztítással kombinálja. A végső exportformátum ugyanolyan fontos, mint a maszk, mert a JPEG még akkor is lesimítja az átlátszó területeket, ha az előnézet megfelelőnek tűnik. + Kezdje az automatikus eltávolítással, ha a téma egyértelműen elválik a háttértől. + Nagyítson, és váltson a törlés és visszaállítás között a haj, a sarkok, az árnyékok és az apró részletek rögzítéséhez. + Mentse el PNG vagy WebP formátumban, ha átláthatóságra van szüksége a végső fájlban. + Rajzolj, jelölj meg és vízjellel + Nyilakat, jegyzeteket, kiemeléseket, aláírásokat vagy ismétlődő vízjel-fedvényeket adhat hozzá + Adjon hozzá látható információkat + A jelölőeszközök segítenek a képek magyarázatában, míg a vízjel segít az exportált fájlok megjelölésében vagy védelmében. + Használja a Draw alkalmazást, ha szabadkézi jegyzetekre, alakzatokra, kiemelésre vagy gyors megjegyzésekre van szüksége. + Használja a vízjelet, ha ugyanaz a szöveg vagy képjel konzisztensen jelenik meg a kimeneteken. + Exportálás előtt ellenőrizze az átlátszatlanságot, a pozíciót és a léptéket, hogy a jel látható legyen, de ne vonja el a figyelmet. + Rajzolj alapértelmezett értékeket + Állítsa be a vonalszélességet, a színt, az útvonal módot, a tájolás rögzítését és a nagyítót a gyorsabb jelölés érdekében + Tedd kiszámíthatóvá a rajzolást + A Rajzbeállítások akkor hasznosak, ha képernyőképeket kommentál, dokumentumokat jelöl meg vagy képeket ír alá gyakran. Az alapértelmezések csökkentik a beállítási időt, míg a nagyító és a tájolásrögzítés megkönnyíti a pontos szerkesztést kis képernyőkön. + Állítsa be az alapértelmezett vonalszélességet, és rajzoljon színt a nyilakhoz, kiemelésekhez vagy aláírásokhoz leginkább használt értékekhez. + Használja az alapértelmezett görbe módot, ha általában ugyanazokat a körvonalakat, alakzatokat vagy jelöléseket rajzolja. + Engedélyezze a nagyítót vagy a tájolásrögzítést, ha az ujjal történő bevitel megnehezíti a kis részletek pontos elhelyezését. + Többképes elrendezések + Válassza ki a megfelelő többképes eszközt, mielőtt képernyőképeket, szkenneléseket vagy összehasonlító csíkokat rendezne + Használja a megfelelő elrendezési eszközt + Ezek az eszközök hasonlóan hangzanak, de mindegyik más típusú többképes kimenetre van hangolva. Ha először a megfelelőt választja, akkor elkerülhető a más eredményre tervezett elrendezésvezérlők elleni küzdelem. + A Collage Maker segítségével több képet tartalmazó elrendezéseket készíthet egy kompozícióban. + Ha a képek egyetlen hosszú függőleges vagy vízszintes csíkká válnak, használja a Képösszefűzést vagy a halmozást. + Használja a Képfelosztást, ha egy nagy képből több kisebb darabból kell állnia. + Képformátumok konvertálása + JPEG, PNG, WebP, AVIF, JXL és más másolatok létrehozása pixelek kézi szerkesztése nélkül + Válassza ki a munka formátumát + A különböző formátumok jobbak a fényképek, képernyőképek, átlátszóság, tömörítés vagy kompatibilitás szempontjából. A konverzió akkor a legbiztonságosabb, ha tisztában van azzal, hogy mit támogat a célalkalmazás, és hogy a forrás tartalmaz-e alfa-, animáció- vagy metaadatokat, amelyeket meg akar tartani. + Használja a JPEG fájlt a kompatibilis fényképekhez, a PNG-t a veszteségmentes képekhez és az alfa, a WebP-t pedig a kompakt megosztáshoz. + Állítsa be a minőséget, ha a formátum támogatja; Az alacsonyabb értékek csökkentik a méretet, de hozzáadhatnak műtermékeket. + Tartsa meg az eredeti dokumentumokat, amíg meg nem ellenőrzi, hogy a konvertált fájlok megfelelően megnyíltak a célalkalmazásban. + Ellenőrizze az EXIF-et és az adatvédelmet + Az érzékeny fotók megosztása előtt megtekintheti, szerkesztheti vagy eltávolíthatja a metaadatokat + A rejtett képadatok szabályozása + Az EXIF ​​tartalmazhat kameraadatokat, dátumokat, helyet, tájolást és egyéb, a képen nem látható részleteket. Érdemes megnézni a nyilvános megosztás, a támogatási jelentések, a piactéri listák vagy minden olyan fotó előtt, amely privát helyet fedhet fel. + Nyissa meg az EXIF-eszközöket, mielőtt olyan fényképeket osztana meg, amelyek helyadatokat vagy privát eszközadatokat tartalmazhatnak. + Távolítsa el az érzékeny mezőket, vagy szerkessze a helytelen címkéket, például dátumot, tájolást, szerzőt vagy kameraadatokat. + Mentse el az új másolatot, és ossza meg azt az eredeti helyett, ha az adatvédelem számít. + Automatikus EXIF ​​tisztítás + Alapértelmezés szerint távolítsa el a metaadatokat, miközben eldönti, hogy a dátumokat meg kell-e őrizni + Legyen az adatvédelem az alapértelmezett + Az EXIF-beállítások csoportja akkor hasznos, ha azt szeretné, hogy az adatvédelmi tisztítás automatikusan megtörténjen, ahelyett, hogy minden exportnál emlékezne rá. A dátum megtartása Az idő külön döntés, mert a dátumok hasznosak lehetnek a rendezéshez, de érzékenyek a megosztáshoz. + Engedélyezze a mindig tiszta EXIF-et, ha az exportált anyagok nagy részét nyilvános vagy félig nyilvános megosztásra szánják. + Csak akkor engedélyezze a Dátum megőrzési idejét, ha a rögzítési idő megőrzése fontosabb, mint minden időbélyeg eltávolítása. + Használjon kézi EXIF-szerkesztést az egyszeri fájlokhoz, ahol meg kell tartania néhány mezőt, míg másokat el kell távolítania. + Fájlnevek szabályozása + Használjon előtagokat, utótagokat, időbélyegeket, előre beállított értékeket, ellenőrző összegeket és sorszámokat + Könnyen megtalálhatja az exportált termékeket + A jó fájlnév-minta segít a kötegek rendezésében és megakadályozza a véletlen felülírásokat. A fájlnév-beállítások különösen akkor hasznosak, ha az exportálás visszakerül a forrásmappába, ahol a hasonló nevek gyorsan zavaróvá válhatnak. + Állítsa be a fájlnév előtagját, utótagját, időbélyegzőjét, eredeti nevét, előre beállított adatait vagy sorrendbeállításait a Beállításokban. + Használjon sorszámokat azokhoz a kötegekhez, ahol a sorrend számít, például oldalak, keretek vagy kollázsok. + Csak akkor engedélyezze a felülírást, ha biztos abban, hogy a meglévő fájlok cseréje biztonságos az aktuális mappában. + Fájlok felülírása + Csak akkor cserélje le a kimeneteket egyező nevekre, ha a munkafolyamat szándékosan romboló + Óvatosan használja a felülírási módot + A Fájlok felülírása megváltoztatja az elnevezési ütközések kezelését. Hasznos az ugyanabba a mappába történő ismételt exportáláshoz, de helyettesítheti a korábbi eredményeket is, ha a fájlnév-minta ismét ugyanazt a nevet adja. + Csak azoknál a mappáknál engedélyezze a felülírást, ahol a régebbi generált fájlok cseréje várható és helyreállítható. + Tartsa letiltva a felülírást, ha eredeti dokumentumokat, ügyfélfájlokat, egyszeri szkennelést vagy bármi mást exportál biztonsági mentés nélkül. + Kombinálja a felülírást egy szándékos fájlnév-mintával, így csak a tervezett kimenetek kerülnek lecserélésre. + Fájlnévminták + Kombinálja az eredeti neveket, előtagokat, utótagokat, időbélyegeket, előre beállított értékeket, méretezési módot és ellenőrző összegeket + Építsen neveket, amelyek megmagyarázzák a kimenetet + A fájlnév-minta-beállítások segítségével az exportált fájlokat a velük történt események hasznos történetévé alakíthatják. Ez kötegekben számít, mert a mappanézet lehet az egyetlen hely, ahol megkülönböztetheti az eredeti méretet, előre beállított értéket, formátumot és feldolgozási sorrendet. + Használja az eredeti fájlnevet, előtagot, utótagot és időbélyeget, ha olvasható nevekre van szüksége a kézi rendezéshez. + Adjon hozzá előre beállított értéket, méretezési módot, fájlméretet vagy ellenőrzőösszeg-információkat, ha a kimeneteknek dokumentálniuk kell, hogyan készültek. + Kerülje el a véletlenszerű elnevezéseket azoknál a munkafolyamatoknál, ahol a fájloknak az eredetivel vagy az oldalsorrenddel kell párosulniuk. + Válassza ki a fájlok mentési helyét + Kerülje el az exportálás elvesztését a mappák beállításával, az eredeti mappába való mentéssel és az egyszeri mentési helyek beállításával + A kimenetek kiszámíthatóak legyenek + A mentési viselkedés akkor számít leginkább, ha sok fájlt dolgoz fel, vagy fájlokat oszt meg a felhőszolgáltatóktól. A mappabeállítások határozzák meg, hogy a kimenetek egy ismert helyen gyűljenek össze, megjelenjenek-e az eredetik mellett, vagy ideiglenesen máshová kerüljenek egy adott feladathoz. + Állítson be egy alapértelmezett mentési mappát, ha mindig egy helyre szeretné exportálni. + Használja az eredeti mappába mentést a munkafolyamatok tisztításához, ahol a kimenetnek a forrásfájl közelében kell maradnia. + Használjon egyszeri mentési helyet, ha egyetlen feladathoz más célállomásra van szükség anélkül, hogy megváltoztatná az alapértelmezett értéket. + Egyszeri mentési mappák + Küldje el a következő exportokat egy ideiglenes mappába anélkül, hogy megváltoztatná a normál célhelyet + Tartsa külön a speciális munkákat + Az egyszeri mentési hely hasznos ideiglenes projektekhez, ügyfélmappákhoz, tisztítási munkamenetekhez vagy exportáláshoz, amelyek nem keveredhetnek a szokásos Image Toolbox mappával. Rövid életű úticélt biztosít az alapértelmezett beállítás átírása nélkül. + Válasszon egy egyszeri mappát, mielőtt olyan feladatot kezdene el, amely a szokásos exportálási helyén kívül esik. + Használja rövid projektekhez, megosztott mappákhoz vagy kötegekhez, ahol a kimeneteknek együtt kell maradniuk. + A feladat elvégzése után térjen vissza az alapértelmezett mappába, hogy a későbbi exportálások ne kerüljenek váratlanul az ideiglenes helyre. + Egyensúlyozza a minőséget és a fájlméretet + Értse meg, mikor kell módosítani a méreteket, a formátumot vagy a minőséget a találgatások helyett + Szándékosan zsugorítsa össze a fájlokat + A fájl mérete a kép méretétől, formátumától, minőségétől, átlátszóságától és a tartalom összetettségétől függ. Ha egy fájl még mindig túl nehéz, a méretek megváltoztatása általában többet segít, mint a minőség ismételt csökkentése. + Először módosítsa a méreteket, ha a kép nagyobb, mint amit a cél megjeleníthet. + Gyengébb minőség csak veszteséges formátumok esetén, és exportálás után ellenőrizze a szöveget, az éleket, a színátmeneteket és az oldalakat. + Váltson formátumot, ha a minőségi változtatások nem elegendőek, például WebP a megosztáshoz vagy PNG az éles átlátszó elemekhez. + Dolgozzon animált formátumokkal + Használjon GIF, APNG, WebP és JXL eszközöket, ha egy fájl egy bittérkép helyett kerettel rendelkezik + Ne kezeljen minden képet állóként + Az animált formátumokhoz olyan eszközökre van szükség, amelyek megértik a kereteket, az időtartamot és a kompatibilitást. + Használjon GIF vagy APNG eszközöket, ha keretszintű műveletekre van szüksége ezekhez a formátumokhoz. + Használja a WebP-eszközöket, ha modern tömörítésre vagy animált WebP-kimenetre van szüksége. + Tekintse meg az animáció időzítésének előnézetét a megosztás előtt, mert egyes platformok konvertálják vagy simítják az animált képeket. + Hasonlítsa össze és tekintse meg a kimenetet + Az exportálás megőrzése előtt ellenőrizze a változtatásokat, a fájlméretet és a vizuális eltéréseket + Megosztás előtt ellenőrizze + Az összehasonlító eszközök segítenek a tömörítési műtermékek, a rossz színek vagy a nem kívánt szerkesztések korai észlelésében. + Nyissa meg az Összehasonlítást, ha két verziót szeretne megvizsgálni egymás mellett. + Nagyítson az élekre, szövegekre, színátmenetekre és átlátszó területekre, ahol a problémákat a legkönnyebb kihagyni. + Ha a kimenet túl nehéz vagy homályos, térjen vissza az előző eszközhöz, és állítsa be a minőséget vagy a formátumot. + Használja a PDF-eszközök központját + PDF-fájlok egyesítése, felosztása, elforgatása, oldalak eltávolítása, tömörítése, védelme, OCR és vízjellel + Válasszon egy PDF-műveletet + A PDF hub egy belépési pont mögé csoportosítja a dokumentumműveleteket, így kiválaszthatja a pontos műveletet. Kezdje ott, amikor a fájl már PDF, és használja a Képek PDF-be vagy a Dokumentumszkennert, amikor a forrás még mindig képkészlet. + Nyissa meg a PDF-eszközöket, és válassza ki a célnak megfelelő műveletet. + Válassza ki a forrás-PDF-et vagy képeket, majd tekintse át az oldalsorrendet, az elforgatást, a védelmet vagy az OCR-beállításokat. + Mentse el a létrehozott PDF-fájlt, és nyissa meg egyszer az oldalszám, a sorrend és az olvashatóság megerősítéséhez. + A képeket PDF formátumba alakíthatja + Egyesítse a beszkennelt képeket, képernyőképeket, nyugtákat vagy fényképeket egyetlen megosztható dokumentumban + Készítsen tiszta dokumentumot + A képek jobb PDF-oldalakká válnak, ha következetesen vágják, rendezik és méretezik őket. Kezelje a képlistát úgy, mint egy oldalköteget: minden elforgatás, margó és oldalméret befolyásolja a végleges dokumentum olvashatóságát. + Először vágja le vagy forgassa el a képeket, ha az oldal szélei, árnyékai vagy tájolása nem megfelelő. + Válassza ki a képeket a kívánt oldalsorrendben, és módosítsa az oldalméretet vagy a margókat, ha az eszköz kínálja ezeket. + Exportálja a PDF-fájlt, majd elküldése előtt ellenőrizze, hogy minden oldal olvasható-e. + Dokumentumok szkennelése + Rögzítsen papírlapokat, és készítse elő őket PDF-re, OCR-re vagy megosztásra + Rögzítsen olvasható oldalakat + A dokumentumbeolvasás lapos oldalak, jó megvilágítás és korrigált perspektíva esetén működik a legjobban. Az OCR vagy PDF-exportálás előtti tiszta beolvasás időt takarít meg, mivel a szövegfelismerés és a tömörítés egyaránt az oldal minőségétől függ. + Helyezze a dokumentumot kontrasztos felületre, ahol elegendő fény és minimális árnyékok vannak. + Állítsa be az észlelt sarkokat vagy vágja körbe kézzel, hogy az oldal tisztán kitöltse a keretet. + Exportálja képként vagy PDF-ként, majd futtassa az OCR-t, ha kiválasztható szövegre van szüksége. + Védje vagy oldja fel a PDF-fájlokat + Használjon körültekintően jelszavakat, és lehetőség szerint őrizzen meg egy szerkeszthető forráspéldányt + Kezelje biztonságosan a PDF-hozzáférést + A védelmi eszközök hasznosak az ellenőrzött megosztáshoz, de a jelszavakat könnyű elveszíteni és nehéz visszaállítani. Védje meg a másolatokat a terjesztéshez, tartsa külön a nem védett forrásfájlokat, és ellenőrizze az eredményt, mielőtt bármit is törölne. + Védje meg a PDF másolatát, ne az egyetlen forrásdokumentumot. + A védett fájl megosztása előtt tárolja biztonságos helyen a jelszót. + Csak azokhoz a fájlokhoz használja a feloldást, amelyeket szerkeszthet, majd ellenőrizze, hogy a feloldott példány megfelelően nyílik-e meg. + PDF oldalak rendezése + Egyesítés, felosztás, átrendezés, forgatás, körbevágás, oldalak eltávolítása és oldalszámok szándékos hozzáadása + Díszítés előtt javítsa ki a dokumentum szerkezetét + A PDF-oldal eszközöket abban a sorrendben lehet a legegyszerűbben használni, mint a papírdokumentumot: gyűjtsd össze az oldalakat, távolítsd el a rossz oldalakat, tedd őket sorrendbe, javítsd a tájolást, majd add hozzá a végső részleteket, például oldalszámokat vagy vízjeleket. + Először használja az egyesítést vagy a képeket PDF-be, ha a dokumentum még mindig több fájlra van felosztva. + Az oldalszámok, aláírások vagy vízjelek hozzáadása előtt rendezze át, forgassa, vágja ki vagy távolítsa el az oldalakat. + Tekintse meg a végleges PDF előnézetét a szerkezeti szerkesztések után, mert egy hiányzó vagy elforgatott oldalt később nehéz észrevenni. + PDF megjegyzések + Távolítsa el a kommentárokat vagy simítsa ki őket, ha a nézők eltérően jelenítik meg a megjegyzéseket és a jelöléseket + Irányítsd, mi marad látható + A megjegyzések lehetnek megjegyzések, kiemelések, rajzok, űrlapjelek vagy egyéb extra PDF-objektumok. Egyes nézők eltérően jelenítik meg őket, így eltávolításuk vagy lapításuk kiszámíthatóbbá teheti a megosztott dokumentumokat. + Távolítsa el a megjegyzéseket, kiemeléseket vagy felülvizsgálati jeleket, ha a megosztott példányban nem szerepelhet. + Egyenítse ki, ha a látható jelek az oldalon maradnak, de már nem viselkednek szerkeszthető kommentárként. + Őrizze meg az eredeti másolatot, mert a megjegyzések eltávolítása vagy kisimítása nehézkes lehet. + Szöveg felismerése + Vágjon ki szöveget a képekből a választható OCR motorokkal és nyelvekkel + Jobb OCR-eredményeket érhet el + Az OCR pontossága a kép élességétől, a nyelvi adatoktól, a kontraszttól és az oldal tájolásától függ. A legjobb eredményt általában akkor éri el, ha először előkészíti a képet: vágja le a zajt, forgassa függőlegesen, javítja az olvashatóságot, majd ismeri fel a szöveget. + Vágja le a képet a szövegterületre, és forgassa el függőlegesen a felismerés előtt. + Válassza ki a megfelelő nyelvet, és töltse le a nyelvi adatokat, ha az alkalmazás kéri. + Másolja vagy ossza meg a felismert szöveget, majd ellenőrizze manuálisan a neveket, számokat és írásjeleket. + Válassza ki az OCR nyelvi adatokat + Javítsa a felismerést a szkriptek, nyelvek és letöltött OCR-modellek egyeztetésével + Illessze az OCR-t a szöveghez + A rossz OCR-nyelv miatt a jó fényképek rossz felismerésnek tűnhetnek. A nyelvi adatok számítanak a szkripteknél, az írásjeleknél, a számoknál és a vegyes dokumentumoknál, ezért a telefon felhasználói felületének nyelve helyett a képen megjelenő nyelvet válassza. + Válassza ki a képen megjelenő nyelvet vagy szkriptet, ne csak a telefon nyelvét. + Töltse le a hiányzó adatokat az offline munkavégzés vagy a köteg feldolgozása előtt. + Vegyes nyelvű dokumentumok esetén először futtassa a legfontosabb nyelvet, és manuálisan ellenőrizze a neveket és a számokat. + Kereshető PDF-ek + Írja vissza az OCR szöveget a fájlokba, így a beolvasott oldalak kereshetők és másolhatók + A beolvasott dokumentumokat kereshető dokumentumokká alakíthatja + Az OCR többet tud, mint a szöveget a vágólapra másolni. Amikor felismert szöveget ír egy fájlba vagy kereshető PDF-fájlba, az oldal képe látható marad, miközben a szöveg könnyebben kereshető, kijelölhető, indexelhető vagy archiválható. + Használjon kereshető PDF-kimenetet a beolvasott dokumentumokhoz, amelyeknek továbbra is úgy kell kinézniük, mint az eredeti oldalak. + Használja a fájlba írást, ha csak kivonatolt szövegre van szüksége, és nem törődik az oldal képével. + Kézzel ellenőrizze a fontos számokat, neveket és táblázatokat, mert az OCR-szöveg kereshető, de még mindig tökéletlen. + QR-kódok beolvasása + Olvassa el a QR-tartalmat a kamera bemenetéről vagy a mentett képekről, ha elérhető + Olvassa el biztonságosan a kódokat + A QR-kódok tartalmazhatnak linkeket, Wi-Fi-adatokat, névjegyeket, szöveget vagy egyéb hasznos adatokat. + Nyissa meg a QR-kód beolvasását, és tartsa a kódot élesen, laposan és teljesen a kereten belül. + A linkek megnyitása vagy az érzékeny adatok másolása előtt tekintse át a dekódolt tartalmat. + Ha a beolvasás sikertelen, javítsa a megvilágítást, vágja le a kódot, vagy próbáljon ki egy nagyobb felbontású forrásképet. + Használja a Base64-et és az ellenőrző összegeket + Kódolja a képeket szövegként, dekódolja vissza a szöveget képekké, és ellenőrizze a fájl integritását + Mozgás a fájlok és a szöveg között + A Base64 hasznos kisméretű képfájlok esetén, míg az ellenőrző összegek segítenek megerősíteni, hogy a fájlok nem változtak. Ezek az eszközök a leghasznosabbak a technikai munkafolyamatoknál, a támogatási jelentéseknél, a fájlátvitelnél, és olyan helyeken, ahol a bináris fájlokat átmenetileg szöveggé kell alakítani. + Használja a Base64 eszközöket egy kis kép kódolására, ha a munkafolyamathoz szövegre van szüksége bináris fájl helyett. + A Base64 dekódolása csak megbízható forrásból származik, és mentés előtt ellenőrizze az előnézetet. + Használja az ellenőrzőösszeg eszközöket, ha össze kell hasonlítania az exportált fájlokat, vagy meg kell erősítenie a feltöltést/letöltést. + Csomagolja vagy védi az adatokat + Használjon ZIP és titkosító eszközöket a fájlok kötegeléséhez vagy szöveges adatok átalakításához + Tartsa együtt a kapcsolódó fájlokat + A csomagolóeszközök akkor hasznosak, ha több kimenetnek egy fájlként kell utaznia. Arra is jók, hogy a generált képeket, PDF-eket, naplókat és metaadatokat együtt tárolják, amikor teljes eredménykészletet kell elküldeni. + Használja a ZIP-fájlt, ha sok exportált képet vagy dokumentumot kell együtt küldenie. + Csak akkor használja a titkosító eszközöket, ha megérti a kiválasztott módszert, és biztonságosan tudja tárolni a szükséges kulcsokat. + A forrásfájlok törlése előtt tesztelje a létrehozott archívumot vagy az átalakított szöveget. + Ellenőrző összegek a nevekben + Használjon ellenőrzőösszeg-alapú fájlneveket, ha az egyediség és az integritás fontosabb, mint az olvashatóság + Nevezd el a fájlokat tartalom szerint + Az ellenőrzőösszegű fájlnevek akkor hasznosak, ha az exportált termékeknek duplikált látható nevei lehetnek, vagy ha bizonyítania kell, hogy két fájl azonos. Kevésbé barátságosak a kézi böngészéshez, ezért használja őket műszaki archívumokhoz és ellenőrzési munkafolyamatokhoz. + Engedélyezze az ellenőrző összeget fájlnévként, ha a tartalom azonossága fontosabb, mint egy ember által olvasható cím. + Használja archívumokhoz, duplikáció megszüntetéséhez, megismételhető exportáláshoz vagy olyan fájlokhoz, amelyek feltölthetők és újra letölthetők. + Párosítsa az ellenőrzőösszeg-neveket mappákkal vagy metaadatokkal, amikor az embereknek még meg kell érteniük, hogy mit jelent a fájl. + Válasszon színeket a képekből + Minta pixelek, színkódok másolása, és színek újrafelhasználása palettákon vagy terveken + Minta a pontos színből + A színválasztás hasznos a tervezéshez, a márkaszínek kinyeréséhez vagy a képértékek ellenőrzéséhez. A nagyítás azért fontos, mert az élsimítás, az árnyékolás és a tömörítés hatására a szomszédos képpontok kissé eltérhetnek a várt színtől. + Nyissa meg a Szín kiválasztása képből lehetőséget, és válasszon egy forrásképet a kívánt színnel. + A kis részletek mintavétele előtt nagyítson rá, hogy a közeli képpontok ne befolyásolják az eredményt. + Másolja a színt a cél által megkívánt formátumba, például HEX, RGB vagy HSV. + Hozzon létre palettákat, és használja a színtárat + Bontsa ki a palettákat, böngésszen a mentett színekben, és tartsa konzisztens vizuális rendszereket + Készítsen újrafelhasználható palettát + A paletták segítenek megőrizni az exportált grafikák, vízjelek és rajzok vizuális egységességét. Különösen hasznosak, ha ismételten megjegyzéseket fűz a képernyőképekhez, készít márkaelemeket, vagy ha több eszközben ugyanazokra a színekre van szüksége. + Hozzon létre egy palettát egy képből, vagy adjon hozzá kézzel színeket, ha már ismeri az értékeket. + Nevezze el vagy rendezze el a fontos színeket, hogy később újra megtalálhassa őket. + Használja a másolt értékeket Draw-ban, vízjelben, színátmenetben, SVG-ben vagy külső tervezőeszközökben. + Gradiensek létrehozása + Készítsen színátmenetes képeket hátterekhez, helyőrzőkhöz és vizuális kísérletekhez + Szabályozza a színátmeneteket + A színátmenet eszközök akkor hasznosak, ha egy meglévő kép szerkesztése helyett generált képre van szüksége. Letisztult hátterek, helyőrzők, háttérképek, maszkok vagy külső tervezési eszközök egyszerű eszközei lehetnek. + Válassza ki a színátmenet típusát, és először állítsa be a fontos színeket. + Állítsa be az irányt, az ütközőket, a simaságot és a kimeneti méretet a célhasználati esetnek megfelelően. + Exportáljon a célnak megfelelő formátumban, általában PNG-ben a tiszta grafika érdekében. + Színek hozzáférhetősége + Használjon dinamikus színeket, kontrasztot és színvak beállításokat, ha a felhasználói felület nehezen olvasható + Tegye könnyebbé a színek használatát + A színbeállítások hatással vannak a kényelemre, az olvashatóságra és a vezérlőelemek beolvasásának sebességére. Ha a szerszámforgácsokat, csúszkákat vagy kiválasztott állapotokat nehéz megkülönböztetni, a téma és a kisegítő lehetőségek hasznosabbak lehetnek, mint a sötét mód egyszerű megváltoztatása. + Próbálja ki a dinamikus színeket, ha azt szeretné, hogy az alkalmazás kövesse az aktuális háttérképpalettát. + Növelje a kontrasztot vagy módosítsa a sémát, ha a gombok és felületek nem tűnnek ki eléggé. + Használja a színvak-séma opciókat, ha egyes állapotok vagy akcentusok nehezen megkülönböztethetők. + Alfa háttér + Szabályozza az átlátszó PNG, WebP és hasonló kimenetek körül használt előnézeti vagy kitöltési színt + Az átlátszó előnézetek megértése + Az alfa formátumok háttérszíne megkönnyítheti az átlátszó képek ellenőrzését, de fontos tudni, hogy az előnézeti/kitöltési viselkedést módosítja, vagy a végeredményt simítja. Ez a matricák, logók és háttérrel eltávolított képek esetében számít. + Először használjon alfa-támogató formátumot, például PNG-t vagy WebP-t, amikor az átlátszó képpontoknak túl kell élniük az exportálást. + Módosítsa a háttérszín beállításait, ha az átlátszó élek nehezen láthatók az alkalmazás felületén. + Exportáljon egy kis tesztet, majd nyissa meg újra egy másik alkalmazásban, hogy megbizonyosodjon arról, hogy az átlátszóságot vagy a kiválasztott kitöltést mentette-e. + AI modellválasztás + Válasszon modellt képtípus és hiba alapján, ahelyett, hogy először a legnagyobbat választaná + Párosítsa a modellt a sérüléshez + Az AI Tools különböző ONNX/ORT-modelleket tud letölteni vagy importálni, és mindegyik modell egy adott típusú problémára van kiképezve. A JPEG-blokkok, az anime sortisztítás, a zajtalanítás, a háttér eltávolítása, a színezés vagy a felskálázás modellje rosszabb eredményt adhat, ha nem megfelelő képtípuson használják. + A letöltés előtt olvassa el a modell leírását; válassza ki azt a modellt, amely megnevezi a tényleges problémát, például tömörítés, zaj, féltónus, elmosódás vagy háttér eltávolítása. + Egy új képtípus tesztelésekor kezdjen egy kisebb vagy specifikusabb modellel, majd csak akkor hasonlítsa össze nehezebb modellekkel, ha az eredmény nem elég jó. + Csak a valóban használt modelleket tartsa meg, mert a letöltött és importált modellek jelentős tárhelyet foglalhatnak. + Ismerje meg a mintacsaládokat + A modellnevek gyakran utalnak céljukra: a RealESRGAN-stílusú modellek előkelő, a SCUNet és az FBCNN modellek a zajt vagy a tömörítést tisztítják, a háttérmodellek maszkokat hoznak létre, a színezők pedig színesítik a szürkeárnyalatos bemenetet. Az importált egyéni modellek erősek lehetnek, de meg kell felelniük a várt bemeneti/kimeneti alaknak, és támogatott ONNX vagy ORT formátumot kell használniuk. + Használjon felskálázót, ha több képpontra van szüksége, zajtalanítókat, ha a kép szemcsés, és műtermékeltávolítókat, ha a tömörítési blokkok vagy a sávok jelentik a fő problémát. + Csak a téma elválasztásához használjon háttérmodellt; nem azonosak az általános tárgyeltávolítással vagy képjavítással. + Csak megbízható forrásokból importáljon egyéni modelleket, és tesztelje őket egy kis képen, mielőtt fontos fájlokat használna. + AI paraméterek + Hangolja be a darabméretet, az átfedést és a párhuzamos dolgozókat a minőség, a sebesség és a memória egyensúlya érdekében + Egyensúlyozza a sebességet a stabilitással + A darabméret szabályozza, hogy mekkora képdarabok legyenek, mielőtt a modell feldolgozná őket. Az átfedés összekeveri a szomszédos darabokat a szegélyek elrejtéséhez. A párhuzamos dolgozók felgyorsíthatják a feldolgozást, de minden dolgozónak memóriára van szüksége, így a magas értékek instabillá tehetik a nagy képeket a korlátozott RAM-mal rendelkező telefonokon. + Használjon nagyobb darabokat a simább kontextus érdekében, amikor az eszköz megbízhatóan kezeli a modellt, és a kép nem hatalmas. + Növelje az átfedést, amikor láthatóvá válnak a darabszegélyek, de ne feledje, hogy a nagyobb átfedés több ismételt munkát jelent. + Párhuzamos munkásokat csak stabil teszt után nevelj fel; ha a feldolgozás lelassul, az eszköz felmelegszik vagy összeomlik, először csökkentse a dolgozókat. + Kerülje a darabos műtermékeket + A darabolás lehetővé teszi az alkalmazás számára, hogy egyszerre nagyobb képeket dolgozzon fel, mint amennyit a modell kényelmesen kezelni tud. A kompromisszum az, hogy minden csempe kevesebb körülvevő kontextust tartalmaz, így az alacsony átfedés vagy a túl kicsi darabok látható szegélyeket, textúraváltozásokat vagy következetlen részleteket hozhatnak létre a szomszédos területek között. + Növelje az átfedést, ha téglalap alakú szegélyeket, ismétlődő textúraváltozásokat vagy részletugrásokat lát a feldolgozott régiók között. + Csökkentse a darabméretet, ha a folyamat sikertelen a kimenet létrehozása előtt, különösen nagy képek vagy nehéz modellek esetén. + A teljes kép feldolgozása előtt mentsen el egy kis kivágási tesztet, hogy gyorsan összehasonlíthassa a paramétereket. + AI stabilitás + Alacsonyabb darabméret, dolgozók és forrásfelbontás, ha az AI-feldolgozás összeomlik vagy lefagy + Helyreállítás a nehéz mesterségesintelligencia-munkákból + Az AI-feldolgozás az egyik legnehezebb munkafolyamat az alkalmazásban. Az összeomlások, sikertelen munkamenetek, lefagyások vagy hirtelen alkalmazás-újraindítások általában azt jelentik, hogy a modell, a forrásfelbontás, a darabméret vagy a párhuzamos dolgozók száma túlságosan megerőltető az eszköz aktuális állapotához. + Ha az alkalmazás összeomlik vagy nem nyit meg egy modellmunkamenetet, csökkentse a párhuzamos dolgozókat és a darabméretet, majd próbálja meg újra ugyanazt a képet. + Méretezze át a nagyon nagy képeket a mesterséges intelligencia feldolgozása előtt, ha a cél nem igényli az eredeti felbontást. + Zárjon be más nehéz alkalmazásokat, használjon kisebb modellt, vagy dolgozzon fel egyszerre kevesebb fájlt, amikor a memória nyomása folyamatosan visszatér. + A modell hibáinak diagnosztizálása + A sikertelen AI-futás nem mindig jelenti azt, hogy a kép rossz. Lehet, hogy a modellfájl hiányos, nem támogatott, túl nagy a memória számára, vagy nem egyezik a művelettel. Amikor az alkalmazás sikertelen munkamenetet jelent, kezelje azt jelzésként, hogy először egyszerűsítse a modellt és a paramétereket. + Töröljön és töltse le újra a modellt, ha az közvetlenül a letöltés után meghiúsul, vagy úgy viselkedik, mintha a fájl sérült volna. + Próbáljon ki egy beépített letölthető modellt, mielőtt egy importált egyéni modellt hibáztatna, mert az importált modellek bemenetei nem kompatibilisek. + Ha összeomlást vagy munkamenet-hibát kell jelentenie, használja az alkalmazásnaplókat a hiba reprodukálása után. + Kísérletezzen a Shader Stúdióban + Használjon GPU shader effektusokat a fejlett képfeldolgozáshoz és egyedi megjelenéshez + Óvatosan dolgozzon a shaderekkel + A Shader Studio erőteljes, de a shader kódon és paramétereken kis, tesztelhető változtatásokra van szükség. Kezelje úgy, mint egy kísérleti eszközt: tartsa meg a működő alapvonalat, változtassa meg egyszerre egy dolgot, és tesztelje a különböző képméreteket, mielőtt megbízna egy előre beállított értékben. + Paraméterek hozzáadása előtt kezdjen el egy ismert működő shaderrel vagy egy egyszerű effektussal. + Egyszerre módosítson egy paramétert vagy kódblokkot, és ellenőrizze az előnézetet, hogy vannak-e hibák. + Az előre beállított értékeket csak néhány különböző képméreten való tesztelés után exportálja. + Készítsen SVG vagy ASCII art + A kreatív kimenethez vektorszerű elemeket vagy szöveges képeket hozhat létre + Válassza ki a kreatív kimeneti típusát + Az SVG és ASCII eszközök különböző célokat szolgálnak ki: skálázható grafika vagy szöveges megjelenítés. Mindkettő kreatív konverzió, így a végső méretben való előnézet fontosabb, mint az eredmény megítélése egy apró miniatűrből. + Használja az SVG Maker-t, ha az eredmény tisztán méretezhető, vagy újra fel kell használni a tervezési munkafolyamatokban. + Használja az ASCII Art elemet, ha stilizált szöveges ábrázolást szeretne egy képről. + Előnézet a végső méretben, mert az apró részletek eltűnhetnek a generált kimenetben. + Zajtextúrák létrehozása + Hozzon létre eljárási textúrákat hátterekhez, maszkokhoz, lefedésekhez vagy kísérletekhez + Hangolja be az eljárási kimenetet + A zajkeltés a paraméterekből új képeket hoz létre, így a kis változtatások nagyon eltérő eredményeket hozhatnak. Hasznos textúrákhoz, maszkokhoz, átfedésekhez, helyőrzőkhöz vagy a tömörítés teszteléséhez részletes mintákkal. + A finom részletek hangolása előtt válassza ki a zajtípust és a kimeneti méretet. + Állítsa be a léptéket, az intenzitást, a színeket vagy a magot, amíg a minta nem illeszkedik a célhoz. + Mentse el a hasznos eredményeket, és írja le a paramétereket, ha később újra kell létrehoznia a textúrát. + Markup projektek + Használjon projektfájlokat, ha a megjegyzéseknek az alkalmazás bezárása után is szerkeszthetőnek kell maradniuk + Legyen a réteges szerkesztések újrafelhasználhatóak + A jelölőprojektfájlok akkor hasznosak, ha egy képernyőképet, diagramot vagy utasításképet később módosítani kell. Az exportált PNG/JPEG fájlok végleges képek, míg a projektfájlok megőrzik a szerkeszthető rétegeket és a szerkesztési állapotot a későbbi módosításokhoz. + Használja a jelölőrétegeket, ha a megjegyzések, kiemelések vagy elrendezési elemek szerkeszthetők maradnak. + Mentse vagy nyissa meg újra a projektfájlt a végleges kép exportálása előtt, ha további módosításokra számít. + Csak akkor exportáljon normál képet, ha készen áll a lapított végleges verzió megosztására. + Fájlok titkosítása + Védje a fájlokat jelszóval, és tesztelje a visszafejtést, mielőtt bármilyen forráspéldányt törölne + Óvatosan kezelje a titkosított kimeneteket + A titkosítási eszközök jelszó alapú titkosítással védhetik a fájlokat, de nem helyettesítik a kulcs emlékezését vagy a biztonsági másolatok készítését. A titkosítás hasznos a szállításhoz és tároláshoz, míg a ZIP jobb, ha a fő cél több kimenet kötegelése. + Először titkosítsa a másolatot, és őrizze meg az eredetit, amíg nem teszteli, hogy a visszafejtés működik-e az Ön által tárolt jelszóval. + Használjon jelszókezelőt vagy más biztonságos helyet a kulcshoz; az alkalmazás nem tudja kitalálni az elveszett jelszót. + A titkosított fájlokat csak olyan személyekkel ossza meg, akik rendelkeznek a jelszóval, és tudják, melyik eszközzel vagy módszerrel kell visszafejteni őket. + Rendezze el az eszközöket + Csoportosítsa, rendelje meg, keressen és rögzítsen eszközöket, hogy a főképernyő megfeleljen a valódi munkafolyamatnak + Gyorsítsa fel a főképernyőt + A szerszámelrendezés beállításait érdemes hangolni, ha már tudod, hogy mely eszközöket használod a legtöbbet. A csoportosítás segíti a felfedezést, az egyéni sorrend segíti az izommemóriát, a kedvencek pedig akkor is elérhetővé teszik a napi eszközöket, ha a teljes lista nagy lesz. + Használja a csoportosított módot az alkalmazás tanulása közben, vagy ha a feladattípus szerint rendezett eszközöket részesíti előnyben. + Váltson egyéni sorrendre, ha már ismeri a gyakran használt eszközeit, és kevesebb tekercset szeretne. + Használja együtt a keresési beállításokat és a kedvenceket olyan ritka eszközökhöz, amelyekre név szerint emlékszik, de nem szeretné, hogy mindig megjelenjenek. + Kezelje a nagy fájlokat + Kerülje a lassú exportálást, a memórianyomást és a váratlanul nagy kimenetet + Csökkentse a munkát az exportálás előtt + A nagyon nagy képek, a hosszú kötegek és a jó minőségű formátumok több memóriát és időt igényelhetnek. A leggyorsabb megoldás általában a munka mennyiségének csökkentése a legnehezebb művelet előtt, nem pedig a túlméretezett bemenet befejezését várva. + Méretezze át a hatalmas képeket, mielőtt erős szűrőket, OCR-t vagy PDF-konverziót alkalmazna. + Először használjon kisebb tételt a beállítások megerősítéséhez, majd dolgozza fel a többit ugyanazzal az előbeállítással. + Gyengébb minőség vagy formátum módosítása, ha a kimeneti fájl nagyobb a vártnál. + Gyorsítótár és előnézetek + Ismerje meg a generált előnézeteket, a gyorsítótár-tisztítást és a tárhelyhasználatot nehéz munkamenetek után + Tartsa egyensúlyban a tárolást és a sebességet + Az előnézet létrehozása és a gyorsítótár gyorsíthatja az ismételt böngészést, de a nehéz szerkesztési munkamenetek ideiglenes adatokat hagyhatnak hátra. A gyorsítótár beállításai segítenek, ha az alkalmazás lassúnak tűnik, szűkös a tárhely, vagy az előnézetek sok kísérlet után elavultnak tűnnek. + Az előnézetek bekapcsolva tartása sok kép böngészésekor fontosabb, mint az ideiglenes tárolás minimalizálása. + Gyorsítótár törlése nagy tételek, sikertelen kísérletek vagy hosszú munkamenetek után sok nagy felbontású fájllal. + Használjon automatikus gyorsítótár-ürítést, ha a tárhelytisztítást részesíti előnyben az előnézetek melegen tartása helyett az indítások között. + Állítsa be a képernyő viselkedését + Használja a teljes képernyős, a biztonságos módot, a fényerőt és a rendszersáv beállításait a koncentrált munkához + Tegye a felületet a helyzethez illővé + A képernyőbeállítások segítenek, ha fekvő tájolásban szerkeszt, privát képeket jelenít meg, vagy állandó fényerőre van szüksége. Normál használathoz nem szükségesek, de megoldják az olyan kínos helyzeteket, mint a QR-ellenőrzés, a privát előnézetek vagy a szűkös tájszerkesztés. + Használja a teljes képernyős vagy a rendszersáv beállításait, ha a szerkesztési terület fontosabb, mint a navigációs króm. + Használja a biztonságos módot, ha a privát képek nem jelennek meg a képernyőképeken vagy az alkalmazás előnézetein. + Használjon fényerő-szabályozást olyan eszközökhöz, ahol a sötét képeket vagy QR-kódokat nehéz ellenőrizni. + Tartsa meg az átláthatóságot + Megakadályozza, hogy az átlátszó hátterek fehérré, feketévé vagy ellaposodjanak + Alfa-tudatos kimenet használata + Az átlátszóság csak akkor marad fenn, ha a kiválasztott eszköz és kimeneti formátum támogatja az alfa-t. Ha az exportált háttér fehér vagy feketévé válik, a probléma általában a kimeneti formátumban, a kitöltési/háttér-beállításban vagy a képet kisimító célalkalmazásban van. + Válassza a PNG vagy a WebP lehetőséget a háttérből eltávolított képek, matricák, logók vagy lefedések exportálásakor. + Kerülje a JPEG-et az átlátszó kimenethez, mert az nem tárol alfa-csatornát. + Ellenőrizze az alfa formátumok háttérszínére vonatkozó beállításokat, ha az előnézet kitöltött hátteret mutat. + Megosztási és importálási problémák megoldása + Használja a kiválasztási beállításokat és a támogatott formátumokat, ha a fájlok nem jelennek meg vagy nem nyílnak meg megfelelően + Töltse be a fájlt az alkalmazásba + Az importálási problémákat gyakran a szolgáltatói engedélyek, a nem támogatott formátumok vagy a kiválasztási viselkedés okozzák. A felhőalkalmazásokból és üzenetküldőkből megosztott fájlok ideiglenesek lehetnek, így a tartós forrás kiválasztása megbízhatóbb lehet a hosszabb szerkesztések során. + Próbálja meg megnyitni a fájlt a rendszerválasztón keresztül a legutóbbi fájlok parancsikonja helyett. + Ha egy formátumot egy eszköz nem támogat, először konvertálja át, vagy nyisson meg egy konkrétabb eszközt. + Tekintse át a képválasztó és az indító beállításait, ha a megosztási folyamatok vagy az eszköz közvetlen megnyitása a várttól eltérően működik. + Kilépés megerősítése + Kerülje el a nem mentett szerkesztések elvesztését, ha véletlenül gesztusok, visszanyomások vagy szerszámváltások történnek + Védje a befejezetlen munkát + Az eszközből való kilépés megerősítése akkor hasznos, ha kézmozdulatokkal történő navigációt használ, nagy fájlokat szerkeszt, vagy gyakran vált az alkalmazások között. Kis szünetet ad az eszköz elhagyása előtt, így a befejezetlen beállítások és előnézetek nem vesznek el véletlenül. + Engedélyezze a megerősítést, ha a véletlen visszamozdulások valaha is bezártak egy eszközt az exportálás előtt. + Tartsa letiltva, ha többnyire gyors, egylépéses konverziót végez, és a gyorsabb navigációt részesíti előnyben. + Használja előre beállított értékekkel együtt hosszabb munkafolyamatokhoz, ahol a beállítások újraépítése bosszantó lenne. + Használjon naplókat a problémák bejelentéséhez + Gyűjtsön össze hasznos részleteket, mielőtt megnyit egy problémát vagy felveszi a kapcsolatot a fejlesztővel + Jelentse a kontextussal kapcsolatos problémákat + A lépéseket, az alkalmazás verzióját, a beviteli típust és a naplókat tartalmazó egyértelmű jelentést sokkal könnyebb diagnosztizálni. A naplók a leghasznosabbak közvetlenül a probléma reprodukálása után, mivel frissen tartják a környező kontextust. + Jegyezze fel az eszköz nevét, a pontos műveletet, és azt, hogy egy fájllal vagy minden fájllal történik-e. + A probléma reprodukálása után nyissa meg az Alkalmazásnaplókat, és lehetőség szerint ossza meg a megfelelő naplókimenetet. + Csak akkor csatoljon képernyőképeket vagy mintafájlokat, ha azok nem tartalmaznak személyes adatokat. + A háttérfeldolgozás leállt + Az Android nem engedte, hogy időben elinduljon a háttérfeldolgozási értesítés. Ez egy rendszeridőzítési korlátozás, amelyet nem lehet megbízhatóan kijavítani. Indítsa újra az alkalmazást, és próbálja újra; az alkalmazás nyitva tartása és az értesítések vagy a háttérben végzett munka engedélyezése segíthet. + Fájlkiválasztás kihagyása + Az eszközök gyorsabban nyithatók meg, ha a bevitel általában megosztásról, vágólapról vagy előző képernyőről érkezik + Gyorsítsa fel az ismételt eszközindításokat + A Fájlkiválasztás kihagyása megváltoztatja a támogatott eszközök első lépését. Hasznos, ha általában már kiválasztott képpel vagy Android megosztási műveletekből ad meg egy eszközt, de zavaró lehet, ha arra számít, hogy minden eszköz azonnal kér egy fájlt. + Csak akkor engedélyezze, ha a szokásos folyamat már az eszköz megnyitása előtt biztosítja a forrásképet. + Tartsa letiltva az alkalmazás tanulása közben, mert az explicit kiválasztás megkönnyíti az összes eszközfolyam megértését. + Ha egy eszköz nyilvánvaló bevitel nélkül nyílik meg, tiltsa le ezt a beállítást, vagy indítsa el a Megosztás/Megnyitás funkcióval, hogy az Android először adja át a fájlt. + Először nyissa meg a szerkesztőt + Hagyja ki az előnézetet, ha egy megosztott képet azonnal szerkeszteni kell + Válassza az előnézetet vagy a szerkesztést első állomásként + Az Előnézet helyett a Szerkesztés megnyitása azoknak a felhasználóknak szól, akik már tudják, hogy módosítani szeretnék a képet. Az előnézet biztonságosabb, ha csevegésből vagy felhőalapú tárhelyről vizsgálja meg a fájlokat; A közvetlen szerkesztés gyorsabb a képernyőképeknél, a gyorskivágásoknál, a megjegyzéseknél és az egyszeri javításoknál. + Engedélyezze a közvetlen szerkesztést, ha a legtöbb megosztott képnél azonnal kivágásra, jelölésre, szűrőkre vagy exportálásra van szükség. + Először hagyja el az előnézetet, ha gyakran ellenőrzi a metaadatokat, a méreteket, az átlátszóságot vagy a képminőséget, mielőtt döntést hozna. + Használja ezt a kedvencekkel együtt, így a megosztott képek közel kerülnek a ténylegesen használt eszközökhöz. + Előre beállított szövegbevitel + Adja meg a pontos előre beállított értékeket a vezérlők ismételt módosítása helyett + Használjon pontos értékeket, ha a csúszkák lassúak + Az előre beállított szövegbevitel segít, ha pontos számokra van szüksége az ismételt munkához: közösségi képméretek, dokumentummargók, tömörítési célok, vonalszélesség vagy egyéb olyan értékek, amelyeket bosszantó kézmozdulatokkal elérni. Kis képernyőkön is hasznos, ahol a finom csúszka mozgatása nehezebb. + Engedélyezze a szövegbevitelt, ha gyakran használ újra pontos méreteket, százalékokat, minőségi értékeket vagy eszközparamétereket. + Mentse el az értéket előre beállított értékként, miután egyszer begépelte, majd használja újra, ahelyett, hogy újra összeállítaná ugyanazt a beállítást. + Tartsa meg a gesztusvezérlőket a durva vizuális hangoláshoz és a begépelt értékeket a szigorú kimeneti követelményekhez. + Mentse az eredetihez közel + Tegye a generált fájlokat a forrásképek mellé, ha a mappakontextus számít + Tartsa a kimeneteket a forrásaikkal együtt + A Mentés Eredeti mappába funkció hasznos a fényképezőgép mappáihoz, projektmappákhoz, dokumentumok szkenneléséhez és ügyfélkötegekhez, ahol a szerkesztett másolatnak a forrás mellett kell maradnia. Lehet, hogy kevésbé rendezett, mint egy dedikált kimeneti mappa, ezért kombinálja egyértelmű fájlnévutótagokkal vagy mintákkal. + Engedélyezze, ha az egyes forrásmappák már értelmesek, és a kimeneteknek ott kell maradniuk. + Használjon fájlnév-utótagokat, előtagokat, időbélyegeket vagy mintákat, hogy a szerkesztett másolatokat könnyen el lehessen választani az eredetitől. + Tiltsa le vegyes kötegeknél, ha az összes generált fájlt egyetlen export mappába szeretné gyűjteni. + Nagyobb kimenet kihagyása + Kerülje a forrásnál váratlanul nehezebb konverziók mentését + Védje a tételeket a rossz méretű meglepetésektől + Egyes átalakítások megnövelik a fájlokat, különösen a PNG képernyőképeket, a már tömörített fényképeket, a kiváló minőségű WebP-t vagy a metaadatokat megőrzött képeket. A nagyobb átugrás engedélyezése megakadályozza, hogy ezek a kimenetek egy kisebb forrást helyettesítsenek azokban a munkafolyamatokban, ahol a méret csökkentése a fő cél. + Engedélyezze, ha az exportálás célja a fájlok tömörítése, átméretezése vagy feltöltési korlátokhoz való előkészítése. + Az átugrott fájlok áttekintése köteg után; lehet, hogy már optimalizálva vannak, vagy más formátumra van szükségük. + Tiltsa le, ha a minőség, az átlátszóság vagy a formátumkompatibilitás fontosabb, mint a végleges fájlméret. + Vágólap és hivatkozások + Vezérelje az automatikus vágólap-bevitelt és a linkelőnézeteket a gyorsabb szövegalapú eszközök érdekében + Az automatikus bevitel kiszámíthatóvá tétele + A vágólap- és hivatkozásbeállítások kényelmesek QR-, Base64-, URL- és szöveges munkafolyamatokhoz, de az automatikus bevitelnek szándékosnak kell lennie. Ha úgy tűnik, hogy az alkalmazás magától kitölti a mezőket, ellenőrizze a vágólap beillesztési viselkedését; ha a linkkártyák zajosak vagy lassúak, állítsa be a hivatkozás előnézeti viselkedését. + Engedélyezze az automatikus vágólapra beillesztést, ha gyakran másol szöveget, és azonnal megnyit egy megfelelő eszközt. + Tiltsa le az automatikus beillesztést, ha privát vágólaptartalom jelenik meg olyan eszközökben, ahol nem számított rá. + Kapcsolja ki a linkelőnézeteket, ha az előnézeti letöltés lassú, elvonja a figyelmet vagy szükségtelen a munkafolyamathoz. + Kompakt kezelőszervek + Használjon sűrűbb választógombokat és gyors beállításokat, ha szűk a képernyő + Több vezérlőelem elhelyezése kis képernyőkön + A kompakt szelektorok és a gyors beállítások elhelyezése segít a kis telefonokon, a fekvő szerkesztésben és a sok paraméterrel rendelkező eszközökön. A kompromisszum az, hogy a kezelőszervek sűrűbbnek tűnhetnek, így ez a legjobb, miután már felismerte a gyakori lehetőségeket. + Engedélyezze a kompakt kiválasztókat, ha a párbeszédpanelek vagy az opciólisták túl magasnak tűnnek a képernyőhöz. + Állítsa be a gyors beállítási oldalt, ha a szerszámvezérlők könnyebben elérhetők a kijelző egyik oldaláról. + Térjen vissza a tágasabb vezérlőelemekhez, ha még tanulja az alkalmazást, vagy ha gyakran kihagyja a sűrű beállításokat. + Töltsön be képeket az internetről + Illesszen be egy oldal vagy kép URL-címét, tekintse meg az elemzett képek előnézetét, majd mentse vagy ossza meg a kiválasztott eredményeket + Szándékosan használjon webes képeket + A Web Image Loading elemzi a képforrásokat a megadott URL-ről, megtekinti az első elérhető kép előnézetét, és lehetővé teszi a kiválasztott elemzett képek mentését vagy megosztását. Egyes webhelyek ideiglenes, védett vagy szkript által generált hivatkozásokat használnak, így előfordulhat, hogy a böngészőben működő oldal továbbra sem ad vissza használható képeket. + Illesszen be egy közvetlen kép URL-t vagy egy oldal URL-jét, és várja meg, amíg megjelenik az elemzett képek listája. + Használja a keret- vagy kijelölésvezérlőket, ha az oldal több képet tartalmaz, és csak néhányra van szüksége. + Ha semmi sem töltődik be, próbálkozzon közvetlen képhivatkozással, vagy először töltse le a böngészőn keresztül; a webhely blokkolhatja az elemzést, vagy privát hivatkozásokat használhat. + Válassza ki az átméretezés típusát + Az explicit, rugalmas, kivágás és illeszkedés megértése, mielőtt új dimenziókra kényszerítené a képet + Az alak, a vászon és a képarány szabályozása + Az átméretezés típusa határozza meg, hogy mi történik, ha a kért szélesség és magasság nem egyezik az eredeti képaránnyal. Ez a különbség a képpontok nyújtása, az arányok megőrzése, az extra terület kivágása vagy a háttérvászon hozzáadása között. + Csak akkor használja az Explicit beállítást, ha a torzítás elfogadható, vagy a forrás már azonos képarányú, mint a cél. + A Flexible használatával megtarthatja az arányokat a fontos oldalról történő átméretezéssel, különösen fényképek és vegyes tételek esetén. + Használja a Körbevágást a szigorú keretekhez, szegélyek nélkül, vagy az Illesztést, ha a teljes képnek láthatónak kell maradnia egy rögzített vásznon belül. + Válassza ki a képméretezési módot + Válassza ki az újramintavételezési szűrőt, amely szabályozza az élességet, simaságot, sebességet és az éles műtermékeket + Méretezés véletlenszerű lágyság nélkül + A képméretezési mód szabályozza az új képpontok számítási módját az átméretezés során. Az egyszerű módok gyorsak és kiszámíthatók, míg a fejlett szűrők jobban megőrzik a részleteket, de élesíthetik a széleket, fényudvarokat hozhatnak létre, vagy hosszabb ideig tartanak a nagy képeken. + Használja a Basic vagy a Bilinear gyors mindennapi átméretezést, amikor az eredménynek csak kisebbnek és tisztának kell lennie. + Használja a Lanczos, Robidoux, Spline vagy EWA-stílusú módokat, ha a finom részletek, szöveg vagy jó minőségű kicsinyítés számít. + Használja a Legközelebbet pixelgrafikákhoz, ikonokhoz és éles grafikákhoz, ahol az elmosódott képpontok tönkretennék a megjelenést. + Skála színtér + Döntse el a színek keverésének módját, miközben a képpontok újramintavételezése az átméretezés során történik + A színátmenetek és az élek maradjanak természetesek + A skála színtér megváltoztatja az átméretezés során használt matematikát. A legtöbb kép rendben van az alapértelmezett értékkel, de a lineáris vagy érzékelési terek miatt a színátmenetek, a sötét élek és a telített színek tisztábbnak tűnhetnek erős skálázás után. + Hagyja az alapértelmezettet, ha a sebesség és a kompatibilitás többet jelent, mint az apró színeltérések. + Próbálja ki a Lineáris vagy észlelési módokat, amikor a sötét körvonalak, a felhasználói felület képernyőképei, a színátmenetek vagy a színsávok rossznak tűnnek az átméretezés után. + Hasonlítsa össze előtte és utána a valódi célképernyőn, mert a színtér változásai finomak és tartalomfüggőek lehetnek. + Limit Resize módok + Tudja, mikor kell a korlátot meghaladó képeket kihagyni, átkódolni vagy kicsinyíteni, hogy illeszkedjenek + Válassza ki, mi történjen a korláton + A Limit Resize nem csak egy kisebb képeket tartalmazó eszköz. Módja dönti el, hogy mit csináljon az alkalmazás, ha egy forrás már az Ön korlátain belül van, vagy ha csak az egyik oldal szegi meg a szabályt, ami fontos a vegyes mappák és a feltöltés előkészítése esetén. + Használja a Kihagyás lehetőséget, ha a határokon belüli képeket érintetlenül kell hagyni, és nem kell újra exportálni. + Használja az Újrakódolást, ha a méretek elfogadhatók, de továbbra is új formátumra, metaadat-viselkedésre, minőségre vagy fájlnévre van szüksége. + Használja a Nagyítást, ha a határokat átlépő képeket arányosan le kell kicsinyíteni, amíg el nem érik a megengedett keretet. + Képarány-célok + Kerülje a megnyúlt felületeket, a levágott éleket és a váratlan szegélyeket szigorú kimeneti méreteknél + Méretezés előtt párosítsa az alakot + A szélesség és a magasság csak a fele az átméretezési célnak. A képarány dönti el, hogy a kép természetesen illeszkedik-e, ki kell-e vágni, vagy kell-e vászon körülötte. Az alakzat első ellenőrzése megakadályozza a legmeglepőbb átméretezési eredményeket. + Hasonlítsa össze a forrás alakzatot a cél alakzattal, mielőtt az Explicit, Crop vagy Fit opciót választja. + Vágja le először, ha a téma elveszíthet extra hátteret, és a célnak szigorú keretre van szüksége. + Használja a Fit vagy Flexible beállítást, ha az eredeti kép minden részének láthatónak kell maradnia. + Előkelő vagy normál átméretezés + Tudja, hogy a képpontok hozzáadásához mikor kell mesterséges intelligencia, és mikor elegendő az egyszerű méretezés + Ne keverje össze a méretet a részletekkel + A normál átméretezés pixelek interpolálásával módosítja a méreteket; nem tud valódi részleteket kitalálni. Az AI felskálázása élesebben kinéző részleteket hozhat létre, de lassabb és hallucinálhatja a textúrát, így a megfelelő választás attól függ, hogy kompatibilitásra, sebességre vagy vizuális helyreállításra van szüksége. + Használjon normál átméretezést bélyegképekhez, feltöltési korlátokhoz, képernyőképekhez és egyszerű méretmódosításokhoz. + Használja a mesterséges intelligencia felskálázását, ha egy kicsi vagy lágy képnek jobban kell kinéznie nagyobb méretben. + Hasonlítsa össze az arcokat, a szöveget és a mintákat a mesterséges intelligencia feljavítása után, mert a generált részletek meggyőzőnek, de helytelennek tűnhetnek. + A szűrők sorrendje számít + Szándékos sorrendben alkalmazza a tisztítást, a színezést, az élességet és a végső tömörítést + Építsen tisztább szűrőköteget + A szűrők nem mindig cserélhetők. Az élesítés előtti elmosódás, az OCR előtti kontrasztnövelés vagy a tömörítés előtti színváltozás nagyon eltérő kimenetet eredményezhet ugyanazon vezérlők használatával. + Először vágja le és forgassa el, így a szűrők csak a megmaradt képpontokon működnek. + Élesítés vagy stilizált effektusok előtt alkalmazzon expozíciót, fehéregyensúlyt és kontrasztot. + Tartsa a végső átméretezést és tömörítést a vég közelében, hogy az előnézeti részletek kiszámíthatóan túléljék az exportálást. + Rögzítse a háttér széleit + Tisztítsa meg a fényudvarokat, a megmaradt árnyékokat, a hajszálakat, a lyukakat és a lágy körvonalakat az automatikus eltávolítás után + A kivágások szándékosnak tűnjenek + Az automatikus maszkok gyakran jól néznek ki ránézésre, de fényes, sötét vagy mintás háttérrel felfedik a problémákat. Az éltisztítás a különbség a gyors kivágás és az újrafelhasználható matrica, logó vagy termékkép között. + Tekintse meg az eredményt kontrasztos háttérrel, hogy felfedje a fényudvarokat és a hiányzó élrészleteket. + Használja a helyreállítást a hajhoz, a sarkokhoz és az átlátszó tárgyakhoz, mielőtt törölné a környező maradékokat. + Exportáljon egy kis tesztet a végső háttérszínről, amikor a kivágást a tervbe helyezi. + Olvasható vízjelek + Tegye láthatóvá a jeleket a világos, sötét, elfoglalt és levágott képeken anélkül, hogy tönkretenné a fényképet + Védje a képet anélkül, hogy elrejtené + Az egyik képen jól kinéző vízjel a másikon eltűnhet. A méretet, az átlátszatlanságot, a kontrasztot, az elhelyezést és az ismétlést a teljes köteghez kell kiválasztani, nem csak az első előnézethez. + Az összes fájl exportálása előtt tesztelje a jelet a köteg legvilágosabb és legsötétebb képén. + Használjon kisebb átlátszatlanságot a nagy ismétlődő jelekhez, és erősebb kontrasztot a kis saroknyomokhoz. + Tartsa tisztán a fontos arcokat, szöveget és termékadatokat, kivéve, ha a jelölés az újrahasználat megakadályozását szolgálja. + Osszon fel egy képet csempékre + Vágjon ki egyetlen képet sorok, oszlopok, egyéni százalékok, formátum és minőség szerint + Rácsok és megrendelt darabok elkészítése + A képfelosztás több kimenetet hoz létre egy forrásképből. Feloszthat sorok és oszlopok száma szerint, beállíthatja a sorok vagy oszlopok százalékos arányát, és a kiválasztott kimeneti formátummal és minőséggel mentheti a darabokat, ami hasznos lehet rácsokhoz, oldalszeletekhez, panorámákhoz és rendezett közösségi bejegyzésekhez. + Válassza ki a forrásképet, majd állítsa be a szükséges sorok és oszlopok számát. + Állítsa be a sorok vagy oszlopok százalékos arányát, ha a darabok nem lehetnek egyenlő méretűek. + Mentés előtt válassza ki a kimeneti formátumot és minőséget, hogy minden generált darab ugyanazokat az exportálási szabályokat használja. + Vágjon ki képcsíkokat + Távolítsa el a függőleges vagy vízszintes részeket, és illessze vissza a többi részeket + Távolítson el egy régiót anélkül, hogy rést hagyna + A kép vágása eltér a normál kivágástól. Eltávolíthat egy kiválasztott függőleges vagy vízszintes csíkot, majd összevonhatja a többi képrészletet. Az inverz mód megtartja a kiválasztott régiót, és mindkét inverz irány használata megtartja a kiválasztott téglalapot. + Használjon függőleges vágást az oldalsó régiókhoz, oszlopokhoz vagy középső csíkokhoz; Használjon vízszintes vágást transzparensekhez, résekhez vagy sorokhoz. + Engedélyezze az inverzt egy tengelyen, ha szeretné megtartani a kiválasztott részt ahelyett, hogy eltávolítaná. + Tekintse meg az élek előnézetét vágás után, mert az egyesített tartalom hirtelen kinézetű lehet, ha az eltávolított terület fontos részleteket keresztez. + Válassza ki a kimeneti formátumot + Válasszon JPEG, PNG, WebP, AVIF, JXL vagy PDF formátumot a tartalom és a cél alapján + Hagyja, hogy a cél határozza meg a formátumot + A legjobb formátum attól függ, hogy mit tartalmaz a kép és hol fogják használni. A fotók, a képernyőképek, az átlátszó elemek, a dokumentumok és az animált fájlok különböző módon meghibásodnak, ha rossz formátumba kényszerítik őket. + Használja a JPEG formátumot a széles körű fényképkompatibilitás érdekében, ha nincs szükség átlátszóságra és pontos képpontokra. + Használja a PNG-t éles képernyőképekhez, felhasználói felület rögzítéséhez, átlátszó grafikához és veszteségmentes szerkesztésekhez. + Használja a WebP-t, AVIF-et vagy JXL-t, ha a kompakt modern kimenet számít, és a fogadó alkalmazás támogatja azt. + Hangos borítók kibontása + Mentse el a beágyazott albumborítókat vagy a rendelkezésre álló médiakereteket hangfájlokból PNG-képként + Húzza ki az alkotásokat az audiofájlokból + Az Audio Covers Android média metaadatokat használ a beágyazott grafikák hangfájlokból történő olvasásához. Ha a beágyazott grafika nem érhető el, a retriever visszakerülhet egy médiakerethez, ha az Android képes ilyet biztosítani; ha egyik sem létezik, az eszköz azt jelenti, hogy nincs kibontható kép. + Nyissa meg a Hangborítókat, és válasszon ki egy vagy több hangfájlt a várható borítóképekkel. + Mentés vagy megosztás előtt tekintse át a kibontott borítót, különösen az adatfolyam- vagy rögzítőalkalmazásokból származó fájlok esetében. + Ha nem található kép, akkor valószínűleg nincs beágyazott borítója a hangfájlnak, vagy az Android nem tudott használható keretet megjeleníteni. + Dátumok és hely metaadatok + Döntse el, mit őriz meg, ha a rögzítési idő számít, de a GPS- vagy az eszközadatok nem szivároghatnak ki + Különítse el a hasznos metaadatokat a privát metaadatoktól + A metaadatok nem egyformán kockázatosak. A rögzítési dátumok hasznosak lehetnek az archiváláshoz és a rendezéshez, míg a GPS, az eszköz sorozatszerű mezői, a szoftvercímkék vagy a tulajdonosi mezők a tervezettnél többet árulnak el. + Tartsa meg a dátum- és időcímkéket, ha az időrendi sorrend számít az albumok, a szkennelések vagy a projektelőzmények szempontjából. + Távolítsa el a GPS- és az eszközazonosító címkéket, mielőtt nyilvánosan megosztaná vagy elküldené a képeket idegeneknek. + Exportáljon egy mintát, és ellenőrizze újra az EXIF-et, ha szigorúak az adatvédelmi követelmények. + Kerülje el a fájlnevek ütközését + Védje a kötegelt kimeneteket, ha sok fájl megosztja a nevét, például image.jpg vagy screenshot.png + Minden kimenet nevét egyedivé tegye + A duplikált fájlnevek gyakoriak, ha a képek üzenetküldőkből, képernyőképekből, felhőmappákból vagy több kamerából származnak. A jó minta megakadályozza a véletlen cserét, és visszavezethetővé teszi a kimeneteket a forrásukig. + Ha a szerkesztett példánynak felismerhetőnek kell lennie, tüntesse fel az eredeti nevet és egy utótagot. + Sorozat, időbélyeg, előre beállított vagy méret hozzáadása nagy vegyes kötegek feldolgozásakor. + Addig kerülje a felülírási munkafolyamatokat, amíg meg nem győződött arról, hogy az elnevezési minta nem ütközhet. + Mentés vagy megosztás + Válasszon egy állandó fájl és egy másik alkalmazásnak való ideiglenes átadás között + Exportáljon megfelelő szándékkal + A Mentés és megosztás hasonló érzések, de más-más problémákat oldanak meg. A mentés egy fájlt hoz létre, amelyet később megtalálhat; A megosztás elküldi az Androidon keresztül generált eredményt egy másik alkalmazásnak, és előfordulhat, hogy nem hagy tartós helyi másolatot. + Használja a Mentés lehetőséget, ha a kimenetnek egy ismert mappában kell maradnia, biztonsági másolatot kell készítenie, vagy később újra fel kell használni. + Használja a Megosztás funkciót gyorsüzenetekhez, e-mail mellékletekhez, közösségi bejegyzésekhez vagy az eredmények másik szerkesztőnek való elküldéséhez. + Először mentse el, ha a fogadó alkalmazás megbízhatatlan, vagy ha egy sikertelen megosztást bosszantó lenne újra létrehozni. + PDF oldalméret és margók + A dokumentum létrehozása előtt válassza ki a papírméretet, az illeszkedési viselkedést és a légzőteret + Tegye nyomtathatóvá és olvashatóvá a képoldalakat + A képek kínos PDF-oldalakká válhatnak, ha alakjuk nem egyezik a kiválasztott papírmérettel. A margók, az illeszkedési mód és az oldal tájolása dönti el, hogy a tartalom levágott, apró, nyújtott vagy kényelmesen olvasható. + Használjon valódi papírméretet, ha a PDF-t kinyomtathatja vagy űrlapra küldheti be. + Használjon margót a beolvasásokhoz és a képernyőképekhez, hogy a szöveg ne üljön az oldal széléhez. + Tekintse meg az álló és fekvő oldalak előnézetét együtt, ha a forrásképek tájolása vegyes. + Tisztítsa meg a beolvasásokat + Javítsa a papír széleit, az árnyékokat, a kontrasztot, az elforgatást és az olvashatóságot a PDF vagy az OCR előtt + Az archiválás előtt készítse elő az oldalakat + A beolvasás technikailag rögzíthető, de még mindig nehezen olvasható. A PDF-exportálás előtti kis tisztítási lépések gyakran többet jelentenek, mint a tömörítési beállítások, különösen a nyugták, kézzel írt feljegyzések és gyenge megvilágítású oldalak esetében. + Korrigálja a perspektívát, és vágja le az asztal széleit, az ujjakat, az árnyékokat és a háttér rendetlenségét. + Óvatosan növelje a kontrasztot, hogy a szöveg világosabbá váljon anélkül, hogy tönkretenné a bélyegeket, aláírásokat vagy ceruzanyomokat. + Csak akkor futtassa az OCR-t, ha az oldal függőlegesen áll és olvasható, majd ellenőrizze manuálisan a fontos számokat. + PDF-fájlok tömörítése, szürkeárnyalatos vagy javítása + Használjon egyfájlos PDF-műveleteket könnyebb, nyomtatható vagy átépített dokumentummásolatok készítéséhez + Válassza ki a PDF-tisztítási műveletet + A PDF Tools különálló műveleteket tartalmaz a tömörítéshez, a szürkeárnyalatos konvertáláshoz és a javításhoz. A tömörítés és a szürkeárnyalatos főként beágyazott képeken keresztül működik, míg a javítás újraépíti a PDF szerkezetét; ezek egyike sem tekinthető minden dokumentum garantált javításának. + Használja a PDF tömörítést, ha a dokumentum képeket tartalmaz, és a fájlméret számít a megosztáshoz. + Használja a Szürkeárnyalatos árnyalatokat, ha a dokumentumot könnyebben kell nyomtatni, vagy ha nincs szükség színes képekre. + Használja a PDF javítását egy másolaton, ha a dokumentum nem nyílik meg, vagy megsérült a belső szerkezete, majd ellenőrizze az újraépített fájlt. + Képek kibontása PDF-ekből + Helyezze vissza a beágyazott PDF-képeket, és csomagolja a kibontott eredményeket egy archívumba + Mentse a forrásképeket a dokumentumokból + Az Extract Images beágyazott képobjektumokat keres a PDF-ben, lehetőség szerint kihagyja a duplikációkat, és a visszaállított képeket egy generált ZIP-archívumban tárolja. Csak azokat a képeket bontja ki, amelyek valóban be vannak ágyazva a PDF-be, nem szöveget, vektoros rajzokat vagy minden látható oldalt képernyőképként. + Használja a Képek kibontását, ha PDF-ben tárolt képekre van szüksége, például egy katalógusból vagy beszkennelt tartalomból. + Ha teljes oldalakra van szüksége, beleértve a szöveget és az elrendezést is, használja a PDF to Images vagy az oldalak kibontását. + Ha az eszköz nem jelez beágyazott képeket, akkor előfordulhat, hogy az oldal szöveges/vektoros tartalom, vagy a képek nem tárolhatók kibontható objektumként. + Metaadatok, lapítás és nyomtatási kimenet + Szándékosan szerkessze a dokumentum tulajdonságait, raszterizálja az oldalakat, vagy készítsen egyedi nyomtatási elrendezéseket + Válassza ki a végső dokumentum műveletét + A PDF-metaadatok, a lapítás és a nyomtatás előkészítése különböző végső szakaszbeli problémákat old meg. A metaadatok szabályozzák a dokumentum tulajdonságait, a Flatten PDF raszterizálja az oldalakat kevésbé szerkeszthető kimenetekké, a Print PDF pedig új elrendezést készít oldalméret, tájolás, margó és laponkénti vezérlőkkel. + Használja a metaadatokat, ha a szerző, a cím, a kulcsszavak, a producer vagy az adatvédelmi törlés számít. + Használja a PDF egyesítését, ha a látható oldaltartalom különálló PDF-objektumként nehezebbé válik. + Használja a PDF nyomtatása funkciót, ha egyéni oldalméretre, tájolásra, margókra vagy laponként több oldalra van szüksége. + Készítse elő a képeket az OCR-hez + Használjon körbevágást, forgatást, kontrasztot, zajtalanítást és skálázást, mielőtt szövegfelolvasásra kérné az OCR-t + Adjon tisztább OCR-képpontokat + Az OCR hibák gyakran a bemeneti képből származnak, nem pedig az OCR motorból. A görbe oldalak, az alacsony kontraszt, az elmosódás, az árnyékok, az apró szövegek és a vegyes hátterek mind rontják a felismerés minőségét. + Vágja le a szövegterületre, és forgassa el a képet úgy, hogy a vonalak vízszintesek legyenek a felismerés előtt. + Csak akkor növelje a kontrasztot vagy alakítsa át tisztább fekete-fehérre, ha ez megkönnyíti a betűk olvashatóságát. + Mérsékelten méretezze át az apró szöveget felfelé, de kerülje az agresszív élesítést, amely hamis éleket hoz létre. + Ellenőrizze a QR-tartalmat + Mielőtt megbízna egy kódban, tekintse át a linkeket, a Wi-Fi hitelesítő adatokat, a névjegyeket és a műveleteket + A dekódolt szöveget nem megbízható bemenetként kezeli + A QR-kód elrejthet egy URL-t, névjegykártyát, Wi-Fi-jelszót, naptáreseményt, telefonszámot vagy egyszerű szöveget. Előfordulhat, hogy a kód mellett látható címke nem egyezik a tényleges hasznos adattal, ezért tekintse át a dekódolt tartalmat, mielőtt hozzáfogna. + Megnyitás előtt ellenőrizze a teljes dekódolt URL-t, különösen a rövidített vagy ismeretlen domaineket. + Legyen óvatos a Wi-Fi-, névjegy-, naptár-, telefon- és SMS-kódokkal, mert érzékeny műveleteket indíthatnak el. + Először másolja ki a tartalmat, amikor máshol kell ellenőriznie, ahelyett, hogy azonnal megnyitná. + QR-kódok generálása + Kódok létrehozása egyszerű szövegből, URL-ekből, Wi-Fi-ből, névjegyekből, e-mailekből, telefonból, SMS-ből és helyadatokból + Építsd meg a megfelelő QR-terhelést + A QR képernyő képes kódokat generálni a bevitt tartalomból, valamint beolvasni a meglévőket. A strukturált QR-típusok segítenek az adatok olyan formátumban kódolásában, amelyet más alkalmazások is megértenek, például Wi-Fi bejelentkezési adatokat, névjegykártyákat, e-mail-mezőket, telefonszámokat, SMS-tartalmat, URL-eket vagy földrajzi koordinátákat. + Válasszon egyszerű szöveget az egyszerű jegyzetekhez, vagy használjon strukturált típust, amikor a kódnak Wi-Fi-ként, névjegyként, URL-ként, e-mailként, telefonként, SMS-ként vagy helyadatként kell megnyílnia. + Ellenőrizze minden mezőt a generált kód megosztása előtt, mert a látható előnézet csak a megadott hasznos terhet tükrözi. + Tesztelje a mentett QR-kódot egy szkennerrel, amikor kinyomtatják, nyilvánosan közzéteszik vagy mások használják. + Válasszon egy ellenőrző összeg módot + Számítsa ki a kivonatokat fájlokból vagy szövegből, hasonlítson össze egy fájlt, vagy kötegelt módon ellenőrizzen sok fájlt + A tartalmat ellenőrizd, ne a megjelenést + A Checksum Tools külön lapokkal rendelkezik a fájlkivonatoláshoz, a szövegkivonatoláshoz, a fájl összehasonlításához egy ismert hash-el, valamint a kötegelt összehasonlításhoz. Az ellenőrző összeg bizonyítja a kiválasztott algoritmus bájtonkénti azonosságát; ez nem bizonyítja, hogy két kép pusztán hasonlónak tűnik. + Fájlokhoz használja a Számítás funkciót, begépelt vagy beillesztett szöveges adatokhoz pedig a Szövegkivonatot. + Használja az Összehasonlítást, ha valaki megadja a várt ellenőrző összeget egy fájlra vonatkozóan. + Használja a Kötegelt összehasonlítást, ha sok fájlnak egy menetben kell kivonatolnia vagy ellenőriznie kell, majd tartsa konzisztens a kiválasztott algoritmust. + A vágólap adatvédelme + Használja az automatikus vágólap funkcióit anélkül, hogy véletlenül felfedné a magánmásolt adatokat + A másolt tartalmat szándékosan őrizze meg + A vágólap automatizálása kényelmes, de a másolt szöveg jelszavakat, hivatkozásokat, címeket, tokeneket vagy privát megjegyzéseket tartalmazhat. Kezelje a vágólap alapú bevitelt olyan sebességfunkcióként, amelynek meg kell felelnie szokásainak és adatvédelmi elvárásainak. + Tiltsa le az automatikus vágólap használatát, ha váratlanul privát szöveg jelenik meg az eszközökben. + Csak akkor használja a vágólapra mentést, ha szándékosan szeretné elkerülni a tárolást. + Törölje vagy cserélje ki a vágólapot az érzékeny szövegek, QR-adatok vagy Base64 rakományok kezelése után. + Színes formátumok + Ismerje meg a HEX, RGB, HSV, alfa és másolt színértékeket, mielőtt máshová illesztené + Másolja ki a cél által elvárt formátumot + Ugyanaz a látható szín többféleképpen is felírható. Egy tervezőeszköz, CSS-mező, Android-színérték vagy képszerkesztő eltérő sorrendre, alfa-kezelésre vagy színmodellre számíthat. + Használja a HEX-et, amikor olyan web-, design- vagy témamezőkbe illeszt be, amelyek kompakt színkódokat várnak el. + Használja az RGB-t vagy a HSV-t, ha manuálisan kell beállítania a csatornákat, a fényerőt, a telítettséget vagy a színárnyalatot. + Ha az átlátszóság fontos, ellenőrizze az alfat külön, mert egyes célhelyek figyelmen kívül hagyják, vagy más sorrendet használnak. + Böngésszen a színtárban + Keressen a megnevezett színekben név vagy HEX alapján, és tartsa kedvenceit a tetején + Keressen újrafelhasználható elnevezett színeket + A Color Library betölt egy elnevezett színgyűjteményt, név szerint rendezi, színnév vagy HEX-érték alapján szűr, és eltárolja a kedvenc színeket, így a fontos bejegyzések könnyebben elérhetők maradnak. Ez akkor hasznos, ha valódi elnevezésű színre van szüksége, ahelyett, hogy képből vett volna mintát. + Keressen színnév alapján, ha ismeri a családot, például piros, kék, pala vagy menta. + Keresés HEX szerint, ha már rendelkezik numerikus színnel, és szeretne egyező vagy közeli elnevezett bejegyzéseket találni. + Jelölje meg a gyakori színeket kedvencként, hogy böngészés közben megelőzze az általános könyvtárat. + Hálós színátmenet-gyűjtemények használata + Töltse le az elérhető mesh gradiens erőforrásokat, és ossza meg a kiválasztott képeket + Távoli mesh gradiens eszközök használata + A Mesh Gradients betöltheti a távoli erőforrásgyűjteményt, letöltheti a hiányzó színátmeneteket, megmutathatja a letöltés folyamatát, és megoszthatja a kiválasztott színátmeneteket. Az elérhetőség a hálózati hozzáféréstől és a távoli erőforrástárolótól függ, így az üres lista általában azt jelenti, hogy az erőforrások még nem voltak elérhetők. + Nyissa meg a Háló színátmeneteket a színátmenet eszközökből, ha kész háló gradiens képeket szeretne. + Várja meg az erőforrás betöltését vagy a letöltés folyamatát, mielőtt feltételezi, hogy a gyűjtemény üres. + Válassza ki és ossza meg a szükséges színátmeneteket, vagy térjen vissza a Gradient Maker alkalmazáshoz, ha kézzel szeretne egyéni színátmenetet létrehozni. + Létrehozott eszközfelbontás + A színátmenetek, zaj, háttérképek, SVG-szerű művészet vagy textúrák létrehozása előtt válassza ki a kimeneti méretet + Generáljon a szükséges méretben + A generált képeknek nincs eredeti fényképezőgépük, amelyre visszamennének. Ha a kimenet túl kicsi, a későbbi méretezés lágyíthatja a mintákat, eltolhatja a részleteket, vagy megváltoztathatja az eszköz csempézési és tömörítési módját. + Először állítsa be a végső felhasználási eset méreteit, például háttérkép, ikon, háttér, nyomtatás vagy fedvény. + Használjon nagyobb méreteket olyan textúrákhoz, amelyek körbevághatók, nagyíthatók vagy újra felhasználhatók többféle elrendezésben. + Exportálja a tesztverziókat a hatalmas kimenetek előtt, mert az eljárási részletek megváltoztathatják a tömörítés viselkedését. + Aktuális háttérképek exportálása + Letöltheti az elérhető Home, Lock és beépített háttérképeket az eszközről + Mentse vagy ossza meg a telepített háttérképeket + A Wallpapers Export beolvassa az Android által a rendszerháttérkép API-kon keresztül megjelenített háttérképeket. Eszköztől, Android-verziótól, engedélyektől és összeállítási változattól függően a Home, a Lock vagy a beépített háttérképek külön kaphatók, vagy hiányozhatnak. + Nyissa meg a Háttérképek exportálását, hogy betöltse azokat a háttérképeket, amelyeket a rendszer lehetővé tesz az alkalmazás számára. + Válassza ki a menteni, másolni vagy megosztani kívánt elérhető kezdőlap, zárolás vagy beépített háttérkép bejegyzéseket. + Ha egy háttérkép hiányzik, vagy a funkció nem érhető el, az általában rendszer-, engedély- vagy build-változat-korlátozás, nem pedig szerkesztési beállítás. + Corners Animációs Fojtószelep + Szín másolása mint + HEX + név + Portré szőnyeg modell a gyors személykivágásokhoz puhább hajjal és élkezeléssel. + Gyors javítás gyenge fényviszonyok mellett a Zero-DCE++ görbebecsléssel. + Restormer kép-helyreállítási modell az esőcsíkok és a nedves időjárási műtermékek csökkentésére. + Dokumentáljon görbületmentesítő modellt, amely előrejelzi a koordinátarácsot, és az ívelt oldalakat laposabb szkenneléssé alakítja át. + Mozgás időtartama skála + Az alkalmazás animációjának sebességét szabályozza: 0 letiltja a mozgást, az 1 a normál, a magasabb értékek lassítják az animációt + Legutóbbi eszközök megjelenítése + Gyors hozzáférést biztosít a legutóbb használt eszközökhöz a főképernyőn + Használati statisztika + Fedezze fel az alkalmazásmegnyitásokat, az eszközindításokat és a leggyakrabban használt eszközöket + Megnyílik az alkalmazás + Megnyílik az eszköz + Utolsó eszköz + Sikeres mentések + Tevékenységi sorozat + Adatok mentve + Top formátum + Használt eszközök + %1$d nyílik • %2$s + Még nincs használati statisztika + Nyisson meg néhány eszközt, és azok automatikusan megjelennek itt + A használati statisztikát csak helyileg tároljuk az eszközön. Az ImageToolbox csak az Ön személyes tevékenységének, kedvenc eszközeinek és mentett adatok betekintésének megjelenítésére használja őket + szerkesztő fotó szerkesztése kép retusálás beállítása + átméretezés konvertálás skála felbontás méretek szélesség magasság jpg jpeg png webp tömörítés + tömörítés méret súly bájt mb kb minőség csökkentése optimalizál + kivágás trim vágási méretarány + szűrő hatás színkorrekció beállítása lut maszk + rajzolni ecset ceruza annotate markup + cipher encrypt decrypt password secret + háttér eltávolító törlés eltávolítása kivágás átlátszó alfa + előnézet megtekintő galéria ellenőrzés megnyitva + öltés egyesítése kombinálja panoráma hosszú képernyőképet + url web letöltés internetes link kép + picker szemcsepp minta hex rgb szín + paletta színek színminták kivonat domináns + exif metaadat adatvédelem távolítsa el a csík tiszta gps helyét + hasonlítsa össze a különbség különbséget előtte utána + limit átméretezés max min megapixel méretek bilincs + pdf dokumentumoldalak eszközei + ocr szöveg felismerése kivonat szkennelés + gradiens háttérszín háló + vízjel logó szöveg bélyegző overlay + gif animáció animált képkockák konvertálása + apng animáció animált png képkockák konvertálása + zip archívum tömörítési csomag fájlok kicsomagolása + jxl jpeg xl jpg képformátum konvertálása + svg vector trace convert xml + formátum konvertálása jpg jpeg png webp heic avif jxl bmp + dokumentumszkenner beolvasó papír perspektivikus kivágás + qr vonalkód-leolvasó + halmozási átfedés átlagos medián asztrofotózás + osztott csempe rács szelet részek + color convert hex rgb hsl hsv cmyk lab + webp animált képformátum konvertálása + zajgenerátor véletlenszerű textúra szemcsés + kollázs rács elrendezés kombinálni + a jelölőrétegek a fedvény szerkesztését jegyzik + base64 kódolja az adatok uri dekódolását + checksum hash md5 sha1 sha256 ellenőrizni + háló gradiens háttér + exif metaadatok szerkesztés gps dátum kamera + vágott osztott rács darab csempe + hanglemezborító lemezborító zenei metaadatok + háttérkép export háttér + ascii text art karakterek + ai ml idegi fokozás előkelő szegmens + színek elnevezésű színkönyvtár anyagpaletta + shader glsl fragment hatás stúdió + pdf egyesítése egyesíteni csatlakozni + pdf külön oldalra osztott + pdf oldalforgatás tájolása + pdf átrendezés oldalak rendezése + pdf oldalszámok oldalszámozása + pdf ocr szöveg felismerhető kereshető kivonat + pdf vízjel bélyegző logó szövege + pdf aláírás húzás + pdf védelme jelszóval titkosítási zár + pdf feloldó jelszó visszafejtése védelem eltávolítása + pdf tömörítés méret csökkentése optimalizálni + pdf szürkeárnyalatos fekete-fehér monokróm + pdf javítási javítás helyreállítása törött + pdf metaadatok tulajdonságai exif szerző címe + pdf távolítsa el az oldalakat + pdf kivágási margók + A pdf lapos annotációk rétegeket képeznek + pdf kivonat képek + pdf zip archívum tömörítés + pdf nyomtató + pdf preview viewer olvasni + képeket pdf-be konvertálhat jpg jpeg png dokumentumot + pdf képekbe oldalak exportálása jpg png + pdf megjegyzések eltávolítása tiszta + Semleges változat + Hiba + Dobó árnyék + Fogmagasság + Vízszintes fogtartomány + Függőleges fogtartomány + Top Edge + Jobb Edge + Alsó él + Left Edge + Szakadt él + Használati statisztikák visszaállítása + Megnyílik az eszköz, elmenti a statisztikákat, a csíkokat, a mentett adatokat és a felső formátumot. Az alkalmazásmegnyitások változatlanok maradnak. + Hang + Fájl + Profilok exportálása + Az aktuális profil mentése + Exportálási profil törlése + Törli a \\"%1$s\\" exportprofilt? + Rajz szegély + Mutasson vékony szegélyt a rajz- és törlésvászon körül + Hullámos + Lekerekített felhasználói felület elemek lágy hullámos éllel + Gombóc + Bemetszés + Lekerekített sarkok befelé faragva + Lépcsős sarkok dupla vágással + Helyőrző + A(z) {filename} használatával illessze be az eredeti fájlnevet kiterjesztés nélkül + Fellobbanás + Alapösszeg + A gyűrű mennyisége + Sugármennyiség + Gyűrű szélessége + Perspektíva torzítása + Java megjelenés és érzés + Nyírás + X szög + és szög + Kimenet átméretezése + Vízi vadászatra betanított kutya + Hullámhossz + Fázis + High Pass + Színes maszk + Króm + Feloldódik + Lágyság + Visszacsatolás + Lens Blur + Alacsony bemenet + Magas bemenet + Alacsony teljesítmény + Magas teljesítmény + Fényeffektusok + Ütés magassága + Bump lágyság + Fodrozódás + X amplitúdó + Y amplitúdó + X hullámhossz + Y hullámhossz + Adaptív elmosódás + Textúra generálása + Különféle eljárási textúrák létrehozása és képként való mentése + Textúra típusa + Idő + Ragyog + Diszperzió + Minták + Szögegyüttható + Távolsági teljesítmény + Y skála + Elmosódottság + Turbulencia tényező + Méretezés + Iterációk + Gyűrűk + Marószerek + Sejtes + fBm + Márvány + Vérplazma + Paplan + Faipari + véletlen + Négyzet + Hatszögletű + Nyolcszögű + Háromszögű + Barázdás + Elfogultság + Gradiens együttható + Rács típusa + Alaptípus + Szálcsiszolt fém + Sakktábla + VL Zaj + SC Zaj + Legutóbbi + Még nincsenek nemrégiben használt szűrők + szálcsiszolt fém maró tábla márvány plazma paplan fa tégla álcázás sejtfelhő lombozat méhsejt jég láva köd papír rozsda szövet domborzat víz hullámzás + Tégla + Álcázás + Sejt + Felhő + Repedés + Szövet + Lombozat + Méhsejt + Jég + Láva + Ködfolt + Papír + Rozsda + Homok + Füst + + Terep + Topográfia + Víz Ripple + Advanced Wood + Habarcs szélessége + Szabálytalanság + Ferde + Érdesség + Első küszöb + Második küszöb + Harmadik küszöb + Szélpuhaság + Szegély szélessége + Lefedettség + Részlet + Mélység + Elágazó + Vízszintes szálak + Függőleges szálak + Bodrosít + Vénák + Világítás + Repedés szélessége + Fagy + Folyik + Kéreg + Felhősűrűség + Csillagok + A rost sűrűsége + Szálszilárdság + Foltok + Korrózió + Pitting + Pehely + Dűne frekvencia + Szélszög + Hullámok + Wisps + Vénás pikkely + Vízállás + Hegyi szint + Erózió + Hószint + Sorszám + Vonalvastagság + Árnyékolás + Pórus színe + Habarcs színe + Sötét tégla színű + Tégla színe + Szín kiemelése + Sötét szín + Erdő színe + Föld színe + Homok színe + Háttérszín + Sejt színe + Élszín + Égszín + Árnyék színe + Világos szín + Felület színe + Változatos szín + Repedés színe + Warp szín + Vetülék színe + Sötét levél szín + Levél színe + Szegély színe + Méz színű + Mély szín + Jég színe + Fagy színe + Kéreg színe + Mosás színe + Ragyogó szín + Tér színe + Lila színű + Kék szín + Alapszín + Szálszín + Folt színe + Fém szín + Sötét rozsda színű + Rozsda szín + Narancs színű + Füst színe + A vénák színe + Alföld színe + Víz színe + Szikla színe + Hó színe + Alacsony szín + Élénk szín + Vonal színe + Sekély színű + + Piszok + Bőr + Konkrét + Aszfalt + Moha + Tűz + Hajnal + Olajfolt + Vízfestmény + Absztrakt Flow + Opál + Damaszkuszi acél + Villám + Bársony + Tinta márványozás + Holografikus fólia + Biolumineszcencia + Kozmikus Örvény + Láva lámpa + Eseményhorizont + Fractal Bloom + Kromatikus alagút + Eclipse Corona + Furcsa attraktor + Ferrofluid korona + Szupernóva + Írisz + Pávatoll + Nautilus Shell + Gyűrűs bolygó + A penge sűrűsége + Penge hossza + Szél + foltosság + Rögök + Nedvesség + Kavicsok + Ráncok + Pórusok + Összesített + Repedések + Kátrány + Viselet + Foltok + Rostok + A láng frekvenciája + Füst + Intenzitás + Szalagok + Zenekarok + Irizálás + Sötétség + Virágzik + Pigment + Élek + Papír + Diffúzió + Szimmetria + Élesség + Színjáték + tejszerűség + Rétegek + Összecsukható + lengyel + Ágak + Irány + Ragyogás + Összehajt + Tollazat + Tintaegyensúly + Spectrum + Ráncok + Diffrakció + Fegyver + Csavar + Core ragyogás + Blobs + Lemezdőlés + Horizont mérete + Lemez szélessége + Lencsézés + Szirmok + Becsavar + Finom + Szempontok + Görbület + Hold mérete + Korona méret + Sugarak + Gyémánt gyűrű + Lebenyek + Pálya sűrűsége + Vastagság + Tüskék + Tüske hossza + Testméret + Fémes + Lökés sugár + Kagyló szélessége + Kidobva + Pupilla mérete + Írisz mérete + Színvariáció + Catchlight + Szemméret + Barb sűrűsége + Fordul + Chambers + Nyílás + Ridges + Gyöngyházhatás + Bolygó mérete + Gyűrűdöntés + Gyűrű szélessége + Légkör + Kosz színe + Sötét fű szín + Fű színe + Tipp színe + Sötét föld szín + Száraz szín + Kavicsos szín + Bőr szín + Beton szín + Kátrány szín + Aszfalt színű + Kő színű + Hamuszínű + Talaj színe + Sötét moha színű + Moha színe + Piros szín + Mag színe + Zöld szín + Cián színű + Magenta színű + Arany színű + Papír színe + Pigment szín + Másodlagos szín + Első szín + Második szín + Rózsaszín színű + Sötét acél szín + Acél szín + Világos acél szín + Oxid szín + Halo szín + Csavar színe + Bársony színű + Fényes szín + Kék tinta színe + Piros tinta színe + Sötét tinta színű + Ezüst színű + Sárga szín + Szövet színe + Lemez színe + Forró szín + Lencse színe + Külső szín + Belső szín + Korona szín + Hideg szín + Meleg szín + Felhő színe + Láng színe + Toll színe + Kagyló színe + Gyöngy színű + Bolygó színe + Gyűrű színe + Mentés után törölje az eredeti fájlokat + Csak a sikeresen feldolgozott forrásfájlok törlődnek + Eredeti fájlok törölve: %1$d + Nem sikerült törölni az eredeti fájlokat: %1$d + Eredeti fájlok törölve: %1$d, sikertelen: %2$d + Lapmozdulatok + A kibontott beállítási lapok húzásának engedélyezése + Chroma részmintavétel + Bitmélység + Veszteségmentes + %1$d-bit + Kötegelt átnevezés + Több fájl átnevezése fájlnévminták használatával + fájlok kötegelt átnevezése fájlnév minta sorozat dátumkiterjesztés + Válassza ki az átnevezni kívánt fájlokat + Fájlnév minta + Átnevezés + Dátumforrás + Az EXIF ​​dátum felvétele + A fájl módosításának dátuma + A fájl létrehozásának dátuma + Aktuális dátum + Manuális dátum + Manuális dátum + Manuális idő + A kiválasztott dátum bizonyos fájloknál nem érhető el. Számukra az aktuális dátum kerül felhasználásra. + Adjon meg egy fájlnév-mintát + A minta érvénytelen vagy túl hosszú fájlnevet eredményez + A minta duplikált fájlneveket hoz létre + Az összes fájlnév már megegyezik a mintával + Ez a minta olyan tokeneket használ, amelyek nem állnak rendelkezésre kötegelt átnevezéshez + A név változatlan marad + A fájlok átnevezése sikeres volt + Egyes fájlokat nem lehetett átnevezni + Az engedélyt nem adták meg + Az eredeti fájlnevet használja kiterjesztés nélkül, így segít megőrizni a forrás azonosítását. Támogatja a \\o{start:end} karakterrel történő szeletelést, \\o{t} átírást és \\o{s/old/new/} cserét. + Automatikusan növekvő számláló. Támogatja az egyéni formázást: \\c{padding} (pl. \\c{3} -&gt; 001), \\c{start:step} vagy \\c{start:step:padding}. + A szülőmappa neve, ahol az eredeti fájl található. + Az eredeti fájl mérete. Támogatja a következő egységeket: \\z{b}, \\z{kb}, \\z{mb} vagy csak \\z az ember által olvasható formátumhoz. + Véletlenszerű, univerzálisan egyedi azonosítót (UUID) generál a fájlnév egyediségének biztosítása érdekében. + A fájlok átnevezése nem vonható vissza. Mielőtt folytatná, győződjön meg arról, hogy az új nevek helyesek. + Átnevezés megerősítése + Oldalmenü beállításai + Menü skála + Alfa menü + Automatikus fehéregyensúly + Darabka + Rejtett vízjelek keresése + Tárolás + Gyorsítótár korlát + Automatikusan törölje a gyorsítótárat, ha az meghaladja ezt a méretet + Tisztítási intervallum + Milyen gyakran ellenőrizze, hogy a gyorsítótárat törölni kell-e + Az alkalmazás indításakor + 1 nap + 1 hét + 1 hónap + Az alkalmazás gyorsítótárának automatikus törlése a kiválasztott korlátnak és intervallumnak megfelelően + Mozgatható termőterület + Lehetővé teszi a teljes kivágási terület mozgatását a benne lévő húzással + GIF egyesítése + Egyesítsen több GIF klipet egyetlen animációba + GIF klipek rendelése + Fordított + Játssz fordítva + Bumeráng + Játssz előre, majd vissza + A keret méretének normalizálása + Méretezze a kereteket a legnagyobb kliphez; kapcsolja ki, hogy megőrizze eredeti méretét + A klipek közötti késleltetés (ms) + Mappa + Válassza ki az összes képet egy mappából és annak almappáiból + Duplicate Finder + Keressen pontos másolatokat és vizuálisan hasonló képeket + másodpéldány kereső hasonló képek pontos másolatok fotók tisztítás tárolás dHash SHA-256 + Válasszon képeket a másolatok kereséséhez + Hasonlóság érzékenység + Pontos másolatok + Hasonló képek + Válassza ki az összes pontos másolatot + Válassza ki az összeset, kivéve az ajánlottakat + Nem található ismétlődés + Próbáljon meg több képet hozzáadni, vagy növelje a hasonlóság érzékenységét + %1$d kép nem olvasható + %1$s • %2$s visszaigényelhető + %1$d csoport • %2$s visszaigényelhető + %1$d kiválasztva • %2$s + Ezt nem lehet visszavonni. Az Android rendszer párbeszédpanelen kérheti a törlés megerősítését. + Nem található + Mozgás az induláshoz + Mozgás a végére + \ No newline at end of file diff --git a/core/resources/src/main/res/values-in/strings.xml b/core/resources/src/main/res/values-in/strings.xml new file mode 100644 index 0000000..e196a6c --- /dev/null +++ b/core/resources/src/main/res/values-in/strings.xml @@ -0,0 +1,3738 @@ + + + + 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 + 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 + 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 & 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 & 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. + 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. + 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 & 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 + Distorsi Sadar Konten + Memori tidak cukup untuk menyelesaikan tindakan ini. Silakan coba gunakan gambar yang lebih kecil, tutup aplikasi lain, atau mulai ulang aplikasi. + Pekerja Paralel + Gambar-gambar ini tidak disimpan karena file yang dikonversi akan lebih besar dari aslinya. Periksa daftar ini sebelum menghapus gambar asli. + File dilewati: %1$s + Log Aplikasi + Lihat log aplikasi untuk menemukan masalahnya + Favorit dalam alat yang dikelompokkan + Menambahkan favorit sebagai tab ketika alat dikelompokkan berdasarkan jenis + Tampilkan favorit sebagai yang terakhir + Memindahkan tab alat favorit ke bagian akhir + Jarak horizontal + Jarak vertikal + Energi mundur + Gunakan peta energi besaran gradien sederhana alih-alih algoritma energi maju + Hapus area bertopeng + Jahitan akan melewati area yang ditutupi, hanya menghilangkan area yang dipilih dan bukan melindunginya + VHS NTSC + Pengaturan NTSC tingkat lanjut + Penyetelan analog ekstra untuk VHS dan artefak siaran yang lebih kuat + Pendarahan kroma + Keausan pita + Pelacakan + Luma mengoles + Berdering + Salju + Gunakan bidang + Jenis penyaring + Masukkan filter luma + Masukkan lowpass kroma + Demodulasi kroma + Pergeseran fase + Pergeseran fase + Pergantian kepala + Ketinggian peralihan kepala + Offset peralihan kepala + Pergeseran pergantian kepala + Posisi garis tengah + Kegelisahan garis tengah + Melacak ketinggian kebisingan + Gelombang pelacakan + Melacak salju + Melacak anisotropi salju + Melacak kebisingan + Melacak intensitas kebisingan + Kebisingan komposit + Frekuensi kebisingan komposit + Intensitas kebisingan komposit + Detail kebisingan komposit + Frekuensi dering + Kekuatan dering + Kebisingan Luma + Frekuensi kebisingan Luma + Intensitas kebisingan Luma + Detail kebisingan Luma + Kebisingan kroma + Frekuensi kebisingan kroma + Intensitas kebisingan kroma + Detail kebisingan kroma + Intensitas salju + Anisotropi salju + Kebisingan fase kroma + Kesalahan fase kroma + Penundaan kroma horizontal + Penundaan kroma vertikal + Kecepatan kaset VHS + Hilangnya kroma VHS + VHS mempertajam intensitas + VHS mempertajam frekuensi + Intensitas gelombang tepi VHS + Kecepatan gelombang tepi VHS + Frekuensi gelombang tepi VHS + Detail gelombang tepi VHS + Lowpass kroma keluaran + Skala horizontal + Skala vertikal + Faktor skala X + Faktor skala Y + Mendeteksi tanda air gambar umum dan mengecatnya dengan LaMa. Mengunduh model deteksi dan pengecatan secara otomatis + Deuteranopia + Perluas Gambar + Penghilang latar belakang berbasis segmentasi objek menggunakan YOLO v11 Segmentation + Tampilkan sudut garis + Menunjukkan rotasi garis saat ini dalam derajat saat menggambar + Emoji Animasi + Tampilkan emoji yang tersedia sebagai animasi + Simpan ke Folder Asli + Simpan file baru di sebelah file asli, bukan di folder yang dipilih + PDF tanda air + Memori tidak cukup + Gambar mungkin terlalu besar untuk diproses pada perangkat ini, atau sistem kehabisan memori yang tersedia. Coba kurangi resolusi gambar, tutup aplikasi lain, atau pilih file yang lebih kecil. + Menu Debug + Menu untuk menguji fungsi aplikasi, ini tidak dimaksudkan untuk muncul dalam rilis produksi + Penyedia + PaddleOCR memerlukan model ONNX tambahan di perangkat Anda. Apakah Anda ingin mengunduh data %1$s? + Universal + Korea + Latin + Slavia Timur + Thai + Orang yunani + Bahasa inggris + Sirilik + Arab + Dewanagari + Tamil + Telugu + peneduh + Prasetel shader + Tidak ada shader yang dipilih + Studio Bayangan + Membuat, mengedit, memvalidasi, mengimpor, dan mengekspor shader fragmen khusus + Shader yang disimpan + Buka shader yang disimpan, duplikat, ekspor, bagikan, atau hapus + shader baru + File bayangan + .itshader JSON + %1$d param + Sumber bayangan + Tulis hanya isi void main(). Gunakan textureCoordinate untuk koordinat UV dan inputImageTexture sebagai sumber sampler. + Fungsi + Fungsi pembantu opsional, konstanta, dan struct disisipkan sebelum void main(). Seragam dihasilkan dari params. + Tambahkan seragam di sini ketika shader memerlukan nilai yang dapat diedit. + Setel ulang shader + Draf shader saat ini akan dihapus + Bayangan tidak valid + Versi shader %1$d tidak didukung. Versi yang didukung: %2$d. + Nama shader wajib diisi. + Sumber shader wajib diisi. + Sumber shader berisi karakter yang tidak didukung: %1$s. Gunakan sumber ASCII GLSL saja. + Parameter \\\"%1$s\\\" dideklarasikan lebih dari satu kali. + Nama parameter wajib diisi. + Parameter \\\"%1$s\\\" menggunakan nama GPUImage yang dicadangkan. + Parameter \\\"%1$s\\\" harus berupa pengidentifikasi GLSL yang valid. + Parameter \\\"%1$s\\\" memiliki tipe nilai %2$s \\\"%3$s\\\", yang diharapkan \\\"%4$s\\\". + Parameter \\\"%1$s\\\" tidak dapat menentukan min atau max untuk nilai bool. + Parameter \\\"%1$s\\\" memiliki nilai minimum lebih besar dari nilai maksimum. + Parameter \\\"%1$s\\\" default lebih rendah dari min. + Parameter \\\"%1$s\\\" default lebih besar dari maks. + Shader harus mendeklarasikan \\\"uniform sampler2D %1$s;\\\". + Shader harus mendeklarasikan \\\"bervariasi vec2 %1$s;\\\". + Shader harus mendefinisikan \\\"void main()\\\". + Shader harus menulis warna ke gl_FragColor. + ShaderToy mainImage shader tidak didukung. Gunakan kontrak void main() GPUImage. + Seragam ShaderToy animasi \\\"iTime\\\" tidak didukung. + Seragam animasi ShaderToy \\\"iFrame\\\" tidak didukung. + Seragam ShaderToy \\\"iResolution\\\" tidak didukung. + Tekstur ShaderToy iChannel tidak didukung. + Tekstur eksternal tidak didukung. + Ekstensi tekstur eksternal tidak didukung. + Parameter libretro shader tidak didukung. + Hanya satu tekstur masukan yang didukung. Hapus \\\"seragam %1$s %2$s;\\\". + Parameter \\\"%1$s\\\" harus dideklarasikan sebagai \\\"seragam %2$s %1$s;\\\". + Parameter \\\"%1$s\\\" dideklarasikan sebagai \\\"seragam %2$s %1$s;\\\", yang diharapkan \\\"seragam %3$s %1$s;\\\". + File shader kosong. + File shader harus berupa objek .itshader JSON dengan kolom versi, nama, dan shader. + File shader bukan JSON yang valid atau tidak cocok dengan format .itshader. + Bidang \\\"%1$s\\\" wajib diisi. + %1$s diperlukan. + %1$s \\\"%2$s\\\" tidak didukung. Jenis yang didukung: %3$s. + %1$s diperlukan untuk parameter \\\"%2$s\\\". + %1$s harus berupa bilangan berhingga. + %1$s harus berupa bilangan bulat. + %1$s harus benar atau salah. + %1$s harus berupa string warna, larik RGB/RGBA, atau objek warna. + %1$s harus menggunakan format #RRGGBB atau #RRGGBBAA. + %1$s harus berisi saluran warna heksadesimal yang valid. + %1$s harus berisi 3 atau 4 saluran warna. + %1$s harus antara 0 dan 255. + %1$s harus berupa array dua angka atau objek dengan x dan y. + %1$s harus berisi tepat 2 angka. + Duplikat + Selalu hapus EXIF + Hapus data EXIF ​​gambar saat disimpan, bahkan ketika alat meminta penyimpanan metadata + Bantuan & Tip + %1$d tutorial + Buka alat + Pelajari alat utama, opsi ekspor, alur PDF, utilitas warna, dan perbaikan untuk masalah umum + Memulai + Pilih alat, impor file, simpan hasil, dan gunakan kembali pengaturan dengan lebih cepat + Pengeditan gambar + Ubah ukuran, potong, filter, hapus latar belakang, gambar, dan tanda air pada gambar + File dan metadata + Konversi format, bandingkan keluaran, lindungi privasi, dan kontrol nama file + PDF dan dokumen + Buat PDF, pindai dokumen, halaman OCR, dan siapkan file untuk dibagikan + Teks, QR, dan data + Kenali teks, pindai kode, enkode Base64, periksa hash, dan paket file + Alat warna + Pilih warna, buat palet, jelajahi perpustakaan warna, dan buat gradien + Alat kreatif + Hasilkan SVG, seni ASCII, tekstur kebisingan, shader, dan visual eksperimental + Pemecahan masalah + Perbaiki masalah file berat, transparansi, berbagi, impor, dan pelaporan + Pilih alat yang tepat + Gunakan kategori, pencarian, favorit, dan berbagi tindakan untuk memulai di tempat yang tepat + Mulailah dari titik masuk terbaik + Image Toolbox memiliki banyak alat yang terfokus, dan banyak di antaranya yang terlihat tumpang tindih. Mulailah dari tugas yang ingin Anda selesaikan, lalu pilih alat yang mengontrol bagian terpenting dari hasil: ukuran, format, metadata, teks, halaman PDF, warna, atau tata letak. + Gunakan pencarian ketika Anda mengetahui nama tindakan, seperti mengubah ukuran, PDF, EXIF, OCR, QR, atau warna. + Buka kategori jika Anda hanya mengetahui jenis tugasnya, lalu sematkan alat yang sering digunakan sebagai favorit. + Saat aplikasi lain membagikan gambar ke Kotak Alat Gambar, pilih alat dari alur lembar berbagi. + Impor, simpan, dan bagikan hasil + Pahami alur umum mulai dari memilih masukan hingga mengekspor file yang diedit + Ikuti alur pengeditan umum + Sebagian besar alat mengikuti ritme yang sama: pilih masukan, sesuaikan opsi, pratinjau, lalu simpan atau bagikan. Detail pentingnya adalah menyimpan akan menghasilkan file keluaran, sementara berbagi biasanya paling baik untuk peralihan cepat ke aplikasi lain. + Pilih satu file untuk alat gambar tunggal atau beberapa file untuk alat batch jika pemilih mengizinkannya. + Periksa pratinjau dan kontrol keluaran sebelum menyimpan, terutama opsi format, kualitas, dan nama file. + Gunakan berbagi untuk penyerahan cepat, atau simpan saat Anda memerlukan salinan tetap di folder yang dipilih. + Bekerja dengan banyak gambar sekaligus + Alat batch paling baik untuk tugas pengubahan ukuran, konversi, kompresi, dan penamaan berulang + Siapkan batch yang dapat diprediksi + Pemrosesan batch paling cepat terjadi ketika setiap keluaran harus mengikuti aturan yang sama. Ini juga merupakan tempat termudah untuk membuat kesalahan besar, jadi pilihlah penamaan, kualitas, metadata, dan perilaku folder sebelum memulai ekspor yang panjang. + Pilih semua gambar terkait bersama-sama dan pertahankan urutannya jika keluaran bergantung pada urutan. + Tetapkan aturan pengubahan ukuran, format, kualitas, metadata, dan nama file satu kali sebelum memulai ekspor. + Jalankan batch pengujian kecil terlebih dahulu ketika file sumber berukuran besar atau ketika format target baru untuk Anda. + Pengeditan tunggal yang cepat + Gunakan editor lengkap saat Anda memerlukan beberapa perubahan sederhana pada satu gambar + Selesaikan satu gambar tanpa melompati alat + Pengeditan Tunggal berguna ketika gambar memerlukan serangkaian perubahan kecil, bukan satu operasi khusus. Biasanya lebih cepat untuk pembersihan cepat, pratinjau, dan mengekspor satu salinan akhir tanpa membuat alur kerja batch. + Buka Edit Tunggal untuk satu gambar yang memerlukan penyesuaian umum, markup, pemotongan, rotasi, atau perubahan ekspor. + Terapkan pengeditan dalam urutan yang mengubah piksel paling sedikit terlebih dahulu, misalnya potong sebelum filter dan filter sebelum kompresi akhir. + Gunakan alat khusus saat Anda memerlukan pemrosesan batch, target ukuran file yang tepat, keluaran PDF, OCR, atau pembersihan metadata. + Gunakan preset dan pengaturan + Simpan pilihan berulang dan sesuaikan aplikasi untuk alur kerja pilihan Anda + Hindari mengulangi pengaturan yang sama + Preset dan pengaturan berguna ketika Anda sering mengekspor jenis file yang sama. Prasetel yang baik mengubah daftar periksa manual yang berulang menjadi satu ketukan, sementara pengaturan global menentukan default yang digunakan alat baru. + Buat preset untuk ukuran output umum, format, nilai kualitas, atau pola penamaan. + Tinjau Pengaturan untuk format default, kualitas, folder penyimpanan, nama file, pemilih, dan perilaku antarmuka. + Cadangkan pengaturan sebelum menginstal ulang aplikasi atau berpindah ke perangkat lain. + Tetapkan default yang berguna + Pilih format default, kualitas, mode skala, jenis pengubahan ukuran, dan ruang warna satu kali + Jadikan alat baru semakin dekat dengan tujuan Anda + Nilai default bukan hanya preferensi kosmetik. Mereka memutuskan format, kualitas, perilaku pengubahan ukuran, mode skala, dan ruang warna apa yang muncul pertama kali di banyak alat, sehingga membantu menghindari pemilihan ulang pilihan yang sama pada setiap ekspor. + Atur format dan kualitas gambar default agar sesuai dengan tujuan biasanya, seperti JPEG untuk foto atau PNG/WebP untuk alfa. + Sesuaikan jenis pengubahan ukuran dan mode skala default jika Anda sering mengekspor untuk dimensi ketat, platform sosial, atau halaman dokumen. + Ubah default ruang warna hanya ketika alur kerja Anda memerlukan penanganan warna yang konsisten di seluruh tampilan, pencetakan, atau editor eksternal. + Pemetik dan peluncur + Gunakan pengaturan pemilih ketika aplikasi membuka terlalu banyak dialog atau dimulai di tempat yang salah + Jadikan pembukaan file sesuai dengan kebiasaan Anda + Pengaturan pemilih dan peluncur mengontrol apakah Image Toolbox dimulai dari layar utama, alat, atau alur pemilihan file. Opsi ini sangat berguna ketika Anda biasanya masuk dari lembar berbagi Android atau ketika Anda ingin aplikasi terasa lebih seperti peluncur alat. + Gunakan pengaturan pemilih foto jika pemilih sistem menyembunyikan file, menampilkan terlalu banyak item terbaru, atau menangani file cloud dengan buruk. + Aktifkan lewati pengambilan hanya untuk alat yang biasa Anda masuki dari share atau sudah mengetahui sumber filenya. + Tinjau mode peluncur jika Anda ingin Image Toolbox berperilaku lebih seperti pemilih alat daripada layar beranda biasa. + Sumber gambar + Pilih mode pemilih yang paling sesuai dengan galeri, pengelola file, dan file cloud + Pilih file dari sumber yang tepat + Setelan Sumber Gambar memengaruhi cara aplikasi meminta gambar pada Android. Pilihan terbaik bergantung pada apakah file Anda berada di galeri sistem, folder pengelola file, penyedia cloud, atau aplikasi lain yang membagikan konten sementara. + Gunakan pemilih default bila Anda menginginkan pengalaman paling terintegrasi sistem untuk gambar galeri umum. + Ganti mode pemilih jika album, folder, file cloud, atau format tidak standar tidak muncul sesuai harapan. + Jika file bersama hilang setelah keluar dari aplikasi sumber, buka kembali file tersebut melalui pemilih tetap atau simpan salinan lokal terlebih dahulu. + Favorit dan alat berbagi + Jaga agar layar utama tetap fokus dan sembunyikan alat yang tidak masuk akal dalam alur berbagi + Mengurangi kebisingan daftar alat + Pengaturan favorit dan tersembunyi untuk dibagikan berguna ketika Anda hanya menggunakan beberapa alat setiap hari. Mereka juga menjaga alur berbagi tetap praktis dengan menyembunyikan alat yang tidak dapat menggunakan jenis file yang dikirimkan aplikasi lain. + Pasang pin pada alat secara sering agar tetap mudah dijangkau meskipun alat dikelompokkan berdasarkan kategori. + Sembunyikan alat dari alur berbagi jika tidak relevan untuk file yang berasal dari aplikasi lain. + Gunakan pengaturan pengurutan favorit jika Anda lebih suka favorit di bagian akhir atau dikelompokkan dengan alat terkait. + Cadangkan pengaturan + Pindahkan preset, preferensi, dan perilaku aplikasi ke instalasi lain dengan aman + Jaga agar pengaturan Anda tetap portabel + Pencadangan dan Pemulihan paling berguna setelah Anda menyetel preset, folder, aturan nama file, urutan alat, dan preferensi visual. Cadangan memungkinkan Anda bereksperimen dengan pengaturan atau menginstal ulang aplikasi tanpa membangun kembali alur kerja dari memori. + Buat cadangan setelah mengubah preset, perilaku nama file, format default, atau pengaturan alat. + Simpan cadangan di suatu tempat di luar folder aplikasi jika Anda berencana menghapus data, menginstal ulang, atau memindahkan perangkat. + Pulihkan pengaturan sebelum memproses kumpulan penting sehingga perilaku default dan penyimpanan sesuai dengan pengaturan lama Anda. + Ubah ukuran dan konversi gambar + Ubah ukuran, format, kualitas, dan metadata untuk satu atau banyak gambar + Buat salinan yang diubah ukurannya + Ubah ukuran dan Konversi adalah alat utama untuk dimensi yang dapat diprediksi dan file keluaran yang lebih ringan. Gunakan ketika ukuran gambar, format keluaran, dan perilaku metadata semuanya penting pada saat yang bersamaan. + Pilih satu atau beberapa gambar, lalu pilih apakah dimensi, skala, atau ukuran file paling penting. + Pilih format dan kualitas keluaran; gunakan PNG atau WebP ketika transparansi harus dijaga. + Pratinjau hasilnya, lalu simpan atau bagikan salinan yang dihasilkan tanpa mengubah aslinya. + Ubah ukuran berdasarkan ukuran file + Targetkan batas ukuran ketika situs web, messenger, atau formulir menolak gambar yang berat + Sesuaikan batas unggahan yang ketat + Mengubah ukuran berdasarkan ukuran file lebih baik daripada menebak nilai kualitas ketika tujuan hanya menerima file di bawah batas tertentu. Alat ini dapat menyesuaikan kompresi dan dimensi untuk mencapai target sambil mencoba menjaga hasilnya tetap dapat digunakan. + Masukkan ukuran target sedikit di bawah batas unggahan sebenarnya untuk memberikan ruang bagi perubahan metadata dan penyedia. + Lebih memilih format foto lossy ketika targetnya kecil; PNG bisa tetap besar bahkan setelah diubah ukurannya. + Periksa wajah, teks, dan tepian setelah ekspor karena target ukuran yang agresif dapat menimbulkan artefak yang terlihat. + Batasi pengubahan ukuran + Kecilkan hanya gambar yang melebihi batasan lebar, tinggi, atau file maksimum Anda + Hindari mengubah ukuran gambar yang sudah baik-baik saja + Batasi Pengubahan Ukuran berguna untuk folder campuran yang beberapa gambarnya berukuran besar dan gambar lainnya sudah disiapkan. Alih-alih memaksa setiap file melalui perubahan ukuran yang sama, ini membuat gambar yang dapat diterima mendekati kualitas aslinya. + Tetapkan dimensi atau batasan maksimum berdasarkan tujuan daripada mengubah ukuran setiap file secara membabi buta. + Aktifkan perilaku gaya lewati-jika-lebih besar ketika Anda ingin menghindari keluaran yang menjadi lebih berat daripada sumber. + Gunakan ini untuk kumpulan dari kamera, tangkapan layar, atau unduhan berbeda yang ukuran sumbernya sangat bervariasi. + Pangkas, putar, dan luruskan + Bingkai area penting sebelum mengekspor atau menggunakan gambar di alat lain + Bersihkan komposisinya + Memotong terlebih dahulu membuat filter, OCR, halaman PDF, dan hasil berbagi selanjutnya menjadi lebih bersih. + Buka Pangkas dan pilih gambar yang ingin Anda sesuaikan. + Gunakan pemangkasan gratis atau rasio aspek jika keluarannya harus sesuai dengan postingan, dokumen, atau avatar. + Putar atau balik sebelum menyimpan sehingga alat selanjutnya menerima gambar yang dikoreksi. + Terapkan filter dan penyesuaian + Buat tumpukan filter yang dapat diedit dan pratinjau hasilnya sebelum mengekspor + Sesuaikan tampilan gambar + Filter berguna untuk koreksi cepat, keluaran bergaya, dan menyiapkan gambar untuk OCR atau pencetakan. Perubahan kecil pada kontras, ketajaman, dan kecerahan dapat menjadi lebih penting daripada efek besar jika gambar berisi teks atau detail halus. + Tambahkan filter satu per satu dan pantau pratinjau setelah setiap perubahan. + Sesuaikan kecerahan, kontras, ketajaman, keburaman, warna, atau topeng tergantung pada tugasnya. + Ekspor hanya jika pratinjau cocok dengan perangkat target, dokumen, atau platform sosial Anda. + Pratinjau terlebih dahulu + Periksa gambar sebelum memilih alat pengeditan, konversi, atau pembersihan yang lebih berat + Periksa apa yang sebenarnya Anda terima + Pratinjau Gambar berguna ketika file berasal dari obrolan, penyimpanan cloud, atau aplikasi lain dan Anda tidak yakin apa isinya. Memeriksa dimensi, transparansi, orientasi, dan kualitas visual terlebih dahulu membantu memilih alat tindak lanjut yang tepat. + Buka Pratinjau Gambar ketika Anda perlu memeriksa beberapa gambar sebelum memutuskan apa yang harus dilakukan dengannya. + Carilah masalah rotasi, area transparan, artefak kompresi, dan dimensi besar yang tidak terduga. + Pindah ke Ubah Ukuran, Pangkas, Bandingkan, EXIF, atau Penghapus Latar Belakang hanya setelah Anda mengetahui apa yang perlu diperbaiki. + Hapus atau pulihkan latar belakang + Hapus latar belakang secara otomatis atau sempurnakan tepian secara manual dengan alat pemulihan + Pertahankan subjeknya, hapus sisanya + Penghapusan latar belakang berfungsi paling baik bila Anda menggabungkan pemilihan otomatis dengan pembersihan manual yang cermat. Format ekspor akhir sama pentingnya dengan mask, karena JPEG akan meratakan area transparan meskipun pratinjau terlihat benar. + Mulailah dengan penghapusan otomatis jika subjek terpisah dengan jelas dari latar belakang. + Perbesar dan beralih antara hapus dan pulihkan untuk memperbaiki rambut, sudut, bayangan, dan detail kecil. + Simpan sebagai PNG atau WebP saat Anda memerlukan transparansi dalam file akhir. + Gambar, tandai, dan beri tanda air + Tambahkan panah, catatan, sorotan, tanda tangan, atau hamparan tanda air berulang + Tambahkan informasi yang terlihat + Alat markup membantu menjelaskan gambar, sedangkan watermark membantu memberi merek atau melindungi file yang diekspor. + Gunakan Gambar saat Anda memerlukan catatan tangan, bentuk, sorotan, atau anotasi cepat. + Gunakan Tanda Air ketika tanda teks atau gambar yang sama muncul secara konsisten pada keluaran. + Periksa opacity, posisi, dan skala sebelum mengekspor sehingga tandanya terlihat namun tidak mengganggu. + Gambar default + Atur lebar garis, warna, mode jalur, kunci orientasi, dan kaca pembesar untuk markup yang lebih cepat + Jadikan gambar terasa mudah ditebak + Pengaturan gambar berguna saat Anda sering membuat anotasi pada tangkapan layar, menandai dokumen, atau menandatangani gambar. Setelan default mengurangi waktu penyiapan, sementara kaca pembesar dan kunci orientasi memudahkan pengeditan presisi di layar kecil. + Tetapkan lebar garis default dan gambar warna sesuai nilai yang paling sering Anda gunakan untuk panah, sorotan, atau tanda tangan. + Gunakan mode jalur default ketika Anda biasanya menggambar jenis guratan, bentuk, atau markup yang sama. + Aktifkan kaca pembesar atau kunci orientasi ketika input jari membuat detail kecil sulit ditempatkan secara akurat. + Tata letak multi-gambar + Pilih alat multi-gambar yang tepat sebelum mengatur tangkapan layar, pindaian, atau strip perbandingan + Gunakan alat tata letak yang tepat + Alat-alat ini terdengar serupa, namun masing-masing disetel untuk jenis keluaran multi-gambar yang berbeda. Memilih yang tepat terlebih dahulu menghindari pertarungan kontrol tata letak yang dirancang untuk hasil yang berbeda. + Gunakan Pembuat Kolase untuk mendesain tata letak dengan beberapa gambar dalam satu komposisi. + Gunakan Penggabungan atau Penumpukan Gambar ketika gambar harus menjadi satu strip panjang vertikal atau horizontal. + Gunakan Pemisahan Gambar ketika satu gambar besar perlu menjadi beberapa bagian yang lebih kecil. + Konversi format gambar + Buat salinan JPEG, PNG, WebP, AVIF, JXL, dan lainnya tanpa mengedit piksel secara manual + Pilih format untuk pekerjaan itu + Format berbeda lebih baik untuk foto, tangkapan layar, transparansi, kompresi, atau kompatibilitas. Konversi paling aman bila Anda memahami apa yang didukung aplikasi tujuan dan apakah sumbernya berisi alfa, animasi, atau metadata yang ingin Anda pertahankan. + Gunakan JPEG untuk foto yang kompatibel, PNG untuk gambar lossless dan alfa, dan WebP untuk berbagi secara ringkas. + Sesuaikan kualitas jika formatnya mendukung; nilai yang lebih rendah mengurangi ukuran tetapi dapat menambah artefak. + Simpan yang asli sampai Anda memverifikasi file yang dikonversi terbuka dengan benar di aplikasi target. + Periksa EXIF ​​dan privasi + Lihat, edit, atau hapus metadata sebelum membagikan foto sensitif + Kontrol data gambar tersembunyi + EXIF dapat berisi data kamera, tanggal, lokasi, orientasi, dan detail lainnya yang tidak terlihat pada gambar. Sebaiknya periksa sebelum membagikannya kepada publik, laporan dukungan, daftar pasar, atau foto apa pun yang mungkin mengungkapkan tempat pribadi. + Buka alat EXIF ​​sebelum berbagi foto yang mungkin berisi informasi lokasi atau perangkat pribadi. + Hapus bidang sensitif atau edit tag yang salah seperti tanggal, orientasi, penulis, atau data kamera. + Simpan salinan baru dan bagikan salinan tersebut, bukan salinan asli, jika privasi penting. + Pembersihan EXIF ​​​​otomatis + Hapus metadata secara default saat memutuskan apakah tanggal harus dipertahankan + Jadikan privasi sebagai default + Grup pengaturan EXIF ​​​​berguna ketika Anda ingin pembersihan privasi terjadi secara otomatis alih-alih mengingatnya untuk setiap ekspor. Pertahankan Tanggal Waktu adalah keputusan terpisah karena tanggal dapat berguna untuk menyortir namun sensitif untuk dibagikan. + Aktifkan EXIF ​​yang selalu jelas ketika sebagian besar ekspor Anda ditujukan untuk berbagi publik atau semi-publik. + Aktifkan Pertahankan Waktu Tanggal hanya ketika mempertahankan waktu pengambilan lebih penting daripada menghapus setiap stempel waktu. + Gunakan pengeditan EXIF ​​manual untuk file satu kali di mana Anda perlu menyimpan beberapa bidang dan menghapus yang lain. + Kontrol nama file + Gunakan prefiks, sufiks, stempel waktu, preset, checksum, dan nomor urut + Jadikan ekspor mudah ditemukan + Pola nama file yang baik membantu mengurutkan kumpulan dan mencegah penimpaan yang tidak disengaja. Pengaturan nama file sangat berguna ketika ekspor kembali ke folder sumber, karena nama yang mirip dapat dengan cepat membingungkan. + Atur awalan nama file, akhiran, stempel waktu, nama asli, info preset, atau opsi urutan di Pengaturan. + Gunakan nomor urut untuk kumpulan yang urutannya penting, seperti halaman, bingkai, atau kolase. + Aktifkan penimpaan hanya ketika Anda yakin mengganti file yang ada aman untuk folder saat ini. + Timpa file + Ganti keluaran dengan nama yang cocok hanya jika alur kerja Anda sengaja merusak + Gunakan mode timpa dengan hati-hati + Timpa File mengubah cara penanganan konflik penamaan. Hal ini berguna untuk mengekspor ulang ke dalam folder yang sama, namun juga dapat menggantikan hasil sebelumnya jika pola nama file Anda menghasilkan nama yang sama lagi. + Aktifkan penimpaan hanya untuk folder yang diharapkan dapat mengganti file lama yang dihasilkan dan dapat dipulihkan. + Tetap nonaktifkan penimpaan saat mengekspor dokumen asli, file klien, pemindaian satu kali, atau apa pun tanpa cadangan. + Gabungkan penimpaan dengan pola nama file yang disengaja sehingga hanya keluaran yang diinginkan yang diganti. + Pola nama file + Gabungkan nama asli, awalan, akhiran, cap waktu, preset, mode skala, dan checksum + Buat nama yang menjelaskan hasilnya + Pengaturan pola nama file dapat mengubah file yang diekspor menjadi riwayat berguna tentang apa yang terjadi pada file tersebut. Hal ini penting dalam batch karena tampilan folder mungkin satu-satunya tempat di mana Anda dapat membedakan ukuran asli, preset, format, dan urutan pemrosesan. + Gunakan nama file asli, awalan, akhiran, dan stempel waktu saat Anda memerlukan nama yang dapat dibaca untuk penyortiran manual. + Tambahkan informasi preset, mode skala, ukuran file, atau checksum ketika output harus mendokumentasikan cara produksinya. + Hindari nama acak untuk alur kerja yang mengharuskan file tetap dipasangkan dengan dokumen asli atau urutan halaman. + Pilih di mana file disimpan + Hindari kehilangan ekspor dengan mengatur folder, penyimpanan folder asli, dan lokasi penyimpanan satu kali + Jaga agar keluaran dapat diprediksi + Perilaku penyimpanan paling penting saat Anda memproses banyak file atau berbagi file dari penyedia cloud. Pengaturan folder menentukan apakah keluaran dikumpulkan di satu tempat yang diketahui, muncul di samping dokumen asli, atau dipindahkan sementara ke tempat lain untuk tugas tertentu. + Tetapkan folder penyimpanan default jika Anda selalu ingin mengekspor di satu tempat. + Gunakan folder simpan-ke-asli untuk alur kerja pembersihan yang outputnya harus berada di dekat file sumber. + Gunakan penyimpanan lokasi satu kali ketika satu tugas memerlukan tujuan berbeda tanpa mengubah default Anda. + Simpan folder satu kali + Kirim ekspor berikutnya ke folder sementara tanpa mengubah tujuan normal Anda + Pisahkan pekerjaan khusus + Lokasi penyimpanan satu kali berguna untuk proyek sementara, folder klien, sesi pembersihan, atau ekspor yang tidak boleh tercampur dengan folder Image Toolbox biasa Anda. Ini memberikan tujuan jangka pendek tanpa menulis ulang pengaturan default Anda. + Pilih folder satu kali sebelum memulai tugas yang berada di luar lokasi ekspor normal Anda. + Gunakan untuk proyek pendek, folder bersama, atau batch yang keluarannya harus tetap dikelompokkan bersama. + Kembali ke folder default setelah pekerjaan selesai sehingga ekspor selanjutnya tidak tiba-tiba mendarat di lokasi sementara. + Seimbangkan kualitas dan ukuran file + Pahami kapan harus mengubah dimensi, format, atau kualitas alih-alih hanya menebak-nebak + Kecilkan file dengan sengaja + Ukuran file bergantung pada dimensi gambar, format, kualitas, transparansi, dan kompleksitas konten. Jika file masih terlalu berat, mengubah dimensi biasanya membantu lebih dari sekadar menurunkan kualitas berulang kali. + Ubah ukuran dimensi terlebih dahulu ketika gambar lebih besar dari yang dapat ditampilkan tujuan. + Kualitas lebih rendah hanya untuk format lossy dan periksa teks, tepi, gradien, dan wajah setelah ekspor. + Ganti format ketika perubahan kualitas tidak cukup, misalnya WebP untuk berbagi atau PNG untuk aset transparan yang tajam. + Bekerja dengan format animasi + Gunakan alat GIF, APNG, WebP, dan JXL jika file memiliki bingkai, bukan satu bitmap + Jangan perlakukan setiap gambar sebagai gambar diam + Format animasi memerlukan alat yang memahami bingkai, durasi, dan kompatibilitas. + Gunakan alat GIF atau APNG saat Anda memerlukan operasi tingkat bingkai untuk format tersebut. + Gunakan alat WebP saat Anda memerlukan kompresi modern atau keluaran WebP animasi. + Pratinjau waktu animasi sebelum dibagikan karena beberapa platform mengonversi atau meratakan gambar animasi. + Bandingkan dan pratinjau keluaran + Periksa perubahan, ukuran file, dan perbedaan visual sebelum mempertahankan ekspor + Verifikasi sebelum berbagi + Alat perbandingan membantu menangkap artefak kompresi, warna yang salah, atau pengeditan yang tidak diinginkan sejak dini. + Buka Bandingkan ketika Anda ingin memeriksa dua versi secara berdampingan. + Memperbesar bagian tepi, teks, gradien, dan area transparan yang paling mudah untuk dilewatkan. + Jika keluarannya terlalu berat atau buram, kembali ke alat sebelumnya dan sesuaikan kualitas atau format. + Gunakan hub alat PDF + Gabungkan, pisahkan, putar, hapus halaman, kompres, lindungi, OCR, dan beri tanda air pada file PDF + Pilih operasi PDF + Hub PDF mengelompokkan tindakan dokumen di belakang satu titik masuk sehingga Anda dapat memilih operasi yang tepat. Mulai dari sana saat file sudah berbentuk PDF, dan gunakan Gambar ke PDF atau Pemindai Dokumen saat sumber Anda masih berupa kumpulan gambar. + Buka Alat PDF dan pilih operasi yang sesuai dengan tujuan Anda. + Pilih sumber PDF atau gambar, lalu tinjau urutan halaman, rotasi, perlindungan, atau opsi OCR. + Simpan PDF yang dihasilkan dan buka sekali untuk mengonfirmasi jumlah halaman, urutan, dan keterbacaan. + Ubah gambar menjadi PDF + Gabungkan pindaian, tangkapan layar, tanda terima, atau foto ke dalam satu dokumen yang dapat dibagikan + Buat dokumen yang bersih + Gambar menjadi halaman PDF yang lebih baik ketika dipotong, diurutkan, dan diukur secara konsisten. Perlakukan daftar gambar seperti tumpukan halaman: setiap rotasi, margin, dan pilihan ukuran halaman memengaruhi seberapa mudah dibacanya dokumen akhir. + Pangkas atau putar gambar terlebih dahulu ketika tepi halaman, bayangan, atau orientasi salah. + Pilih gambar dalam urutan halaman yang diinginkan dan sesuaikan ukuran halaman atau margin jika alat menawarkannya. + Ekspor PDF, lalu periksa apakah semua halaman dapat dibaca sebelum mengirimkannya. + Pindai dokumen + Ambil halaman kertas dan persiapkan untuk PDF, OCR, atau berbagi + Tangkap halaman yang dapat dibaca + Pemindaian dokumen berfungsi paling baik pada halaman datar, pencahayaan bagus, dan perspektif terkoreksi. Pemindaian bersih sebelum ekspor OCR atau PDF menghemat waktu karena pengenalan dan kompresi teks bergantung pada kualitas halaman. + Tempatkan dokumen pada permukaan yang kontras dengan cahaya yang cukup dan bayangan minimal. + Sesuaikan sudut yang terdeteksi atau potong secara manual sehingga halaman memenuhi bingkai dengan rapi. + Ekspor sebagai gambar atau PDF, lalu jalankan OCR jika Anda memerlukan teks yang dapat dipilih. + Lindungi atau buka kunci file PDF + Gunakan kata sandi dengan hati-hati dan simpan salinan sumber yang dapat diedit jika memungkinkan + Tangani akses PDF dengan aman + Alat perlindungan berguna untuk berbagi secara terkendali, namun kata sandi mudah hilang dan sulit dipulihkan. Lindungi salinan untuk distribusi, simpan file sumber yang tidak dilindungi secara terpisah, dan verifikasi hasilnya sebelum menghapus apa pun. + Lindungi salinan PDF, bukan satu-satunya dokumen sumber Anda. + Simpan kata sandi di tempat yang aman sebelum membagikan file yang dilindungi. + Gunakan buka kunci hanya untuk file yang boleh Anda edit, lalu verifikasi salinan yang tidak terkunci dibuka dengan benar. + Atur halaman PDF + Menggabungkan, membagi, menyusun ulang, memutar, memotong, menghapus halaman, dan menambahkan nomor halaman dengan sengaja + Perbaiki struktur dokumen sebelum dekorasi + Alat halaman PDF paling mudah digunakan dengan urutan yang sama seperti Anda menyiapkan dokumen kertas: kumpulkan halaman, hapus halaman yang salah, urutkan, perbaiki orientasi, lalu tambahkan detail akhir seperti nomor halaman atau tanda air. + Gunakan penggabungan atau gambar-ke-PDF terlebih dahulu ketika dokumen masih terbagi menjadi beberapa file. + Menyusun ulang, memutar, memotong, atau menghapus halaman sebelum menambahkan nomor halaman, tanda tangan, atau tanda air. + Pratinjau PDF akhir setelah pengeditan struktural karena satu halaman yang hilang atau diputar mungkin sulit diketahui nantinya. + Anotasi PDF + Hapus anotasi atau ratakan ketika pemirsa menampilkan komentar dan tanda berbeda + Kendalikan apa yang masih terlihat + Anotasi dapat berupa komentar, sorotan, gambar, tanda formulir, atau objek PDF tambahan lainnya. Beberapa penampil menampilkannya secara berbeda, jadi menghapus atau meratakannya dapat membuat dokumen bersama lebih mudah diprediksi. + Hapus anotasi ketika komentar, sorotan, atau tanda ulasan tidak boleh disertakan dalam salinan yang dibagikan. + Ratakan ketika tanda yang terlihat tetap berada di halaman tetapi tidak lagi berfungsi seperti anotasi yang dapat diedit. + Simpan salinan asli karena menghapus atau meratakan anotasi mungkin sulit untuk dibatalkan. + Kenali teks + Ekstrak teks dari gambar dengan mesin dan bahasa OCR yang dapat dipilih + Dapatkan hasil OCR yang lebih baik + Akurasi OCR bergantung pada ketajaman gambar, data bahasa, kontras, dan orientasi halaman. Hasil terbaik biasanya diperoleh dengan menyiapkan gambar terlebih dahulu: menghilangkan noise, memutar tegak, meningkatkan keterbacaan, lalu mengenali teks. + Pangkas gambar ke area teks dan putar tegak sebelum dikenali. + Pilih bahasa yang benar dan unduh data bahasa jika aplikasi memintanya. + Salin atau bagikan teks yang dikenali, lalu periksa nama, nomor, dan tanda baca secara manual. + Pilih data bahasa OCR + Tingkatkan pengenalan dengan mencocokkan skrip, bahasa, dan model OCR yang diunduh + Cocokkan OCR dengan teks + Bahasa OCR yang salah dapat membuat foto bagus tampak seperti pengenalan yang buruk. Data bahasa penting untuk skrip, tanda baca, angka, dan dokumen campuran, jadi pilihlah bahasa yang muncul di gambar daripada bahasa UI ponsel. + Pilih bahasa atau skrip yang muncul pada gambar, bukan hanya bahasa ponsel. + Unduh data yang hilang sebelum bekerja offline atau memproses batch. + Untuk dokumen dengan bahasa campuran, jalankan bahasa yang paling penting terlebih dahulu dan periksa nama dan nomor secara manual. + PDF yang dapat dicari + Tulis kembali teks OCR ke dalam file sehingga halaman yang dipindai dapat dicari dan disalin + Ubah pindaian menjadi dokumen yang dapat dicari + OCR dapat melakukan lebih dari sekedar menyalin teks ke clipboard. Saat Anda menulis teks yang dikenali ke file atau PDF yang dapat dicari, gambar halaman tetap terlihat sementara teks menjadi lebih mudah untuk dicari, dipilih, diindeks, atau diarsipkan. + Gunakan keluaran PDF yang dapat dicari untuk dokumen pindaian yang masih terlihat seperti halaman aslinya. + Gunakan write-to-file ketika Anda hanya perlu mengekstrak teks dan tidak peduli dengan gambar halaman. + Periksa nomor, nama, dan tabel penting secara manual karena teks OCR dapat dicari namun masih belum sempurna. + Pindai kode QR + Baca konten QR dari input kamera atau gambar yang disimpan bila tersedia + Baca kode dengan aman + Kode QR dapat berisi tautan, data Wi-Fi, kontak, teks, atau muatan lainnya. + Buka Pindai Kode QR dan jaga agar kode tetap tajam, rata, dan sepenuhnya berada di dalam bingkai. + Tinjau konten yang didekodekan sebelum membuka tautan atau menyalin data sensitif. + Jika pemindaian gagal, tingkatkan pencahayaan, potong kode, atau coba gambar sumber dengan resolusi lebih tinggi. + Gunakan Base64 dan checksum + Enkode gambar sebagai teks, dekode teks kembali menjadi gambar, dan verifikasi integritas file + Berpindah antara file dan teks + Base64 berguna untuk muatan gambar kecil, sementara checksum membantu mengonfirmasi bahwa file tidak berubah. Alat-alat ini sangat membantu dalam alur kerja teknis, laporan dukungan, transfer file, dan tempat-tempat di mana file biner perlu diubah menjadi teks untuk sementara. + Gunakan alat Base64 untuk menyandikan gambar kecil ketika alur kerja memerlukan teks, bukan file biner. + Dekode Base64 hanya dari sumber tepercaya dan verifikasi pratinjau sebelum menyimpan. + Gunakan alat checksum ketika Anda perlu membandingkan file yang diekspor atau mengonfirmasi unggahan/unduhan. + Kemas atau lindungi data + Gunakan ZIP dan alat sandi untuk menggabungkan file atau mengubah data teks + Simpan file terkait bersama-sama + Alat pengemasan sangat membantu ketika beberapa keluaran harus dikirim sebagai satu file. Mereka juga bagus untuk menyimpan gambar, PDF, log, dan metadata yang dihasilkan bersama-sama saat Anda perlu mengirim kumpulan hasil yang lengkap. + Gunakan ZIP ketika Anda perlu mengirim banyak gambar atau dokumen yang diekspor secara bersamaan. + Gunakan alat sandi hanya jika Anda memahami metode yang dipilih dan dapat menyimpan kunci yang diperlukan dengan aman. + Uji arsip yang dibuat atau teks yang diubah sebelum menghapus file sumber. + Checksum dalam nama + Gunakan nama file berbasis checksum ketika keunikan dan integritas lebih penting daripada keterbacaan + Beri nama file berdasarkan konten + Nama file checksum berguna ketika ekspor mungkin memiliki nama duplikat yang terlihat atau ketika Anda perlu membuktikan bahwa dua file identik. Mereka kurang ramah untuk penelusuran manual, jadi gunakanlah untuk arsip teknis dan alur kerja verifikasi. + Aktifkan checksum sebagai nama file ketika identitas konten lebih penting daripada judul yang dapat dibaca manusia. + Gunakan untuk arsip, deduplikasi, ekspor berulang, atau file yang dapat diunggah dan diunduh lagi. + Pasangkan nama checksum dengan folder atau metadata ketika orang masih perlu memahami apa yang diwakili oleh file tersebut. + Pilih warna dari gambar + Contoh piksel, salin kode warna, dan gunakan kembali warna dalam palet atau desain + Cicipi warna yang tepat + Pemilihan warna berguna untuk mencocokkan desain, mengekstraksi warna merek, atau memeriksa nilai gambar. Pembesaran penting karena antialiasing, bayangan, dan kompresi dapat membuat piksel di sekitarnya sedikit berbeda dari warna yang Anda harapkan. + Buka Pilih Warna Dari Gambar dan pilih gambar sumber dengan warna yang Anda butuhkan. + Perbesar sebelum mengambil sampel detail kecil sehingga piksel di dekatnya tidak memengaruhi hasilnya. + Salin warna dalam format yang dibutuhkan tujuan Anda, seperti HEX, RGB, atau HSV. + Buat palet dan gunakan perpustakaan warna + Ekstrak palet, jelajahi warna yang disimpan, dan pertahankan sistem visual yang konsisten + Buat palet yang dapat digunakan kembali + Palet membantu menjaga grafik, tanda air, dan gambar yang diekspor tetap konsisten secara visual. Mereka sangat berguna ketika Anda berulang kali membuat anotasi pada tangkapan layar, menyiapkan aset merek, atau memerlukan warna yang sama di beberapa alat. + Hasilkan palet dari gambar atau tambahkan warna secara manual ketika Anda sudah mengetahui nilainya. + Beri nama atau atur warna-warna penting agar Anda dapat menemukannya lagi nanti. + Gunakan nilai yang disalin di Gambar, tanda air, gradien, SVG, atau alat desain eksternal. + Buat gradien + Buat gambar gradien untuk latar belakang, placeholder, dan eksperimen visual + Kontrol transisi warna + Alat gradien berguna saat Anda memerlukan gambar yang dihasilkan daripada mengedit gambar yang sudah ada. Mereka bisa menjadi latar belakang yang bersih, placeholder, wallpaper, masker, atau aset sederhana untuk alat desain eksternal. + Pilih jenis gradien dan atur warna penting terlebih dahulu. + Sesuaikan arah, penghentian, kelancaran, dan ukuran keluaran untuk kasus penggunaan target. + Ekspor dalam format yang sesuai dengan tujuan, biasanya PNG untuk menghasilkan grafik yang bersih. + Aksesibilitas warna + Gunakan warna dinamis, kontras, dan opsi buta warna saat UI sulit dibaca + Membuat warna lebih mudah digunakan + Pengaturan warna memengaruhi kenyamanan, keterbacaan, dan seberapa cepat Anda dapat memindai kontrol. Jika chip alat, penggeser, atau status tertentu sulit dibedakan, opsi tema dan aksesibilitas bisa lebih berguna daripada sekadar mengubah mode gelap. + Cobalah warna dinamis bila Anda ingin aplikasi mengikuti palet wallpaper saat ini. + Tingkatkan kontras atau ubah skema saat tombol dan permukaan tidak cukup menonjol. + Gunakan opsi skema buta warna jika beberapa keadaan atau aksen sulit dibedakan. + Latar belakang alfa + Kontrol pratinjau atau warna isian yang digunakan di sekitar PNG transparan, WebP, dan keluaran serupa + Pahami pratinjau transparan + Warna latar belakang untuk format alfa dapat membuat gambar transparan lebih mudah diperiksa, namun penting untuk mengetahui apakah Anda mengubah perilaku pratinjau/pengisian atau meratakan hasil akhirnya. Ini penting untuk stiker, logo, dan gambar yang dihapus latar belakangnya. + Gunakan format pendukung alfa terlebih dahulu, seperti PNG atau WebP, jika piksel transparan harus bertahan saat diekspor. + Sesuaikan pengaturan warna latar belakang ketika tepi transparan sulit dilihat pada permukaan aplikasi. + Ekspor tes kecil dan buka kembali di aplikasi lain untuk mengonfirmasi apakah transparansi atau isian yang dipilih telah disimpan. + Pilihan model AI + Pilih model berdasarkan jenis gambar dan cacatnya daripada memilih yang terbesar terlebih dahulu + Cocokkan model dengan kerusakannya + AI Tools dapat mengunduh atau mengimpor model ONNX/ORT yang berbeda, dan setiap model dilatih untuk jenis masalah tertentu. Model untuk blok JPEG, pembersihan garis anime, denoising, penghapusan latar belakang, pewarnaan, atau peningkatan skala dapat memberikan hasil yang lebih buruk bila digunakan pada jenis gambar yang salah. + Baca deskripsi model sebelum mengunduh; pilih model yang sesuai dengan masalah Anda yang sebenarnya, seperti kompresi, noise, halftone, blur, atau penghapusan latar belakang. + Mulailah dengan model yang lebih kecil atau lebih spesifik saat menguji jenis gambar baru, lalu bandingkan dengan model yang lebih berat hanya jika hasilnya kurang bagus. + Simpan hanya model yang benar-benar Anda gunakan, karena model yang diunduh dan diimpor dapat menghabiskan banyak ruang penyimpanan. + Pahami keluarga teladan + Nama model sering kali menunjukkan targetnya: model gaya RealESRGAN kelas atas, model SCUNet dan FBCNN menghilangkan noise atau kompresi, model latar belakang membuat topeng, dan pewarna menambahkan warna pada masukan skala abu-abu. Model khusus yang diimpor bisa jadi sangat canggih, namun model tersebut harus sesuai dengan bentuk input/output yang diharapkan dan menggunakan format ONNX atau ORT yang didukung. + Gunakan upscaler saat Anda membutuhkan lebih banyak piksel, denoiser saat gambar berbintik, dan penghapus artefak saat blok kompresi atau garis melintang menjadi masalah utama. + Gunakan model latar belakang hanya untuk pemisahan subjek; ini tidak sama dengan penghapusan objek secara umum atau peningkatan gambar. + Impor model khusus hanya dari sumber yang Anda percayai dan uji model tersebut pada gambar kecil sebelum menggunakan file penting. + Parameter AI + Sesuaikan ukuran potongan, tumpang tindih, dan pekerja paralel untuk menyeimbangkan kualitas, kecepatan, dan memori + Seimbangkan kecepatan dengan stabilitas + Ukuran potongan mengontrol seberapa besar potongan gambar sebelum diproses oleh model. Tumpang tindih memadukan potongan-potongan yang berdekatan untuk menyembunyikan batas. Pekerja paralel dapat mempercepat pemrosesan, namun setiap pekerja membutuhkan memori, sehingga nilai yang tinggi dapat membuat gambar berukuran besar tidak stabil pada ponsel dengan RAM terbatas. + Gunakan potongan yang lebih besar untuk konteks yang lebih halus ketika perangkat Anda menangani model dengan andal dan gambarnya tidak besar. + Tingkatkan tumpang tindih ketika batas potongan terlihat, namun ingat bahwa semakin banyak tumpang tindih berarti semakin banyak pekerjaan yang berulang. + Angkat pekerja paralel hanya setelah pengujian stabil; jika pemrosesan melambat, perangkat menjadi panas, atau crash, kurangi pekerja terlebih dahulu. + Hindari artefak potongan + Chunking memungkinkan aplikasi memproses gambar yang lebih besar dari yang dapat ditangani dengan nyaman oleh model sekaligus. Kerugiannya adalah setiap ubin memiliki konteks di sekitarnya yang lebih sedikit, sehingga sedikit tumpang tindih atau potongan yang terlalu kecil dapat membuat batas terlihat, perubahan tekstur, atau detail yang tidak konsisten antar area di sekitarnya. + Tingkatkan tumpang tindih jika Anda melihat batas persegi panjang, perubahan tekstur berulang, atau lompatan detail antar wilayah yang diproses. + Kurangi ukuran potongan jika proses gagal sebelum menghasilkan keluaran, terutama pada gambar besar atau model berat. + Simpan tes pemangkasan kecil sebelum memproses gambar penuh sehingga Anda dapat membandingkan parameter dengan cepat. + Stabilitas AI + Mengurangi ukuran potongan, pekerja, dan resolusi sumber saat pemrosesan AI mengalami crash atau terhenti + Pulihkan dari pekerjaan AI yang berat + Pemrosesan AI adalah salah satu alur kerja terberat di aplikasi. Error, sesi gagal, macet, atau aplikasi dimulai ulang secara tiba-tiba biasanya berarti model, resolusi sumber, ukuran potongan, atau jumlah pekerja paralel terlalu menuntut untuk kondisi perangkat saat ini. + Jika aplikasi mogok atau gagal membuka sesi model, turunkan pekerja paralel dan ukuran potongan, lalu coba gambar yang sama lagi. + Ubah ukuran gambar yang sangat besar sebelum pemrosesan AI ketika tujuannya tidak memerlukan resolusi asli. + Tutup aplikasi berat lainnya, gunakan model yang lebih kecil, atau proses lebih sedikit file sekaligus ketika tekanan memori terus kembali. + Diagnosis kegagalan model + Kegagalan menjalankan AI tidak selalu berarti gambarnya buruk. File model mungkin tidak lengkap, tidak didukung, terlalu besar untuk memori, atau tidak cocok dengan pengoperasian. Saat aplikasi melaporkan sesi yang gagal, perlakukan itu sebagai sinyal untuk menyederhanakan model dan parameter terlebih dahulu. + Hapus dan unduh ulang model jika gagal segera setelah diunduh atau berperilaku seolah-olah file rusak. + Cobalah model bawaan yang dapat diunduh sebelum menyalahkan model khusus yang diimpor, karena model yang diimpor mungkin memiliki masukan yang tidak kompatibel. + Gunakan Log Aplikasi setelah mereproduksi kegagalan jika Anda perlu melaporkan kerusakan atau kesalahan sesi. + Eksperimen di Shader Studio + Gunakan efek shader GPU untuk pemrosesan gambar tingkat lanjut dan tampilan khusus + Bekerjalah dengan hati-hati dengan shader + Shader Studio sangat kuat, tetapi kode dan parameter shader memerlukan perubahan kecil dan dapat diuji. Perlakukan ini seperti alat eksperimental: pertahankan garis dasar yang berfungsi, ubah satu per satu, dan uji dengan ukuran gambar berbeda sebelum mempercayai preset. + Mulai dari shader yang berfungsi atau efek sederhana sebelum menambahkan parameter. + Ubah satu parameter atau blok kode dalam satu waktu dan periksa pratinjau apakah ada kesalahan. + Ekspor preset hanya setelah mengujinya pada beberapa ukuran gambar yang berbeda. + Buat karya seni SVG atau ASCII + Hasilkan aset seperti vektor atau gambar berbasis teks untuk keluaran materi iklan + Pilih jenis keluaran materi iklan + Alat SVG dan ASCII melayani target yang berbeda: grafik yang dapat diskalakan atau rendering gaya teks. Keduanya merupakan konversi kreatif, jadi melihat pratinjau pada ukuran akhir lebih penting daripada menilai hasil dari thumbnail kecil. + Gunakan SVG Maker ketika hasilnya harus diskalakan dengan rapi atau digunakan kembali dalam alur kerja desain. + Gunakan Seni ASCII bila Anda menginginkan representasi teks bergaya dari suatu gambar. + Pratinjau dalam ukuran akhir karena detail kecil dapat hilang dalam keluaran yang dihasilkan. + Menghasilkan tekstur kebisingan + Buat tekstur prosedural untuk latar belakang, topeng, overlay, atau eksperimen + Sesuaikan keluaran prosedural + Pembangkitan noise menghasilkan gambar baru dari parameter, sehingga perubahan kecil dapat menghasilkan hasil yang sangat berbeda. Ini berguna untuk tekstur, masker, overlay, placeholder, atau pengujian kompresi dengan pola detail. + Pilih jenis noise dan ukuran output sebelum menyetel detail halus. + Sesuaikan skala, intensitas, warna, atau benih hingga polanya sesuai dengan target penggunaan. + Simpan hasil yang berguna dan tuliskan parameternya jika Anda perlu membuat ulang teksturnya nanti. + Proyek penandaan + Gunakan file proyek ketika anotasi harus tetap dapat diedit setelah menutup aplikasi + Jaga agar hasil edit berlapis dapat digunakan kembali + File proyek markup berguna ketika tangkapan layar, diagram, atau gambar instruksi mungkin perlu diubah nanti. File PNG/JPEG yang diekspor adalah gambar akhir, sedangkan file proyek mempertahankan lapisan yang dapat diedit dan status pengeditan untuk penyesuaian di masa mendatang. + Gunakan Lapisan Markup ketika anotasi, keterangan, atau elemen tata letak tetap dapat diedit. + Simpan atau buka kembali file proyek sebelum mengekspor gambar akhir jika Anda mengharapkan revisi lebih lanjut. + Ekspor gambar normal hanya jika Anda siap membagikan versi final yang diratakan. + Enkripsi file + Lindungi file dengan kata sandi dan uji dekripsi sebelum menghapus salinan sumber apa pun + Tangani keluaran terenkripsi dengan hati-hati + Alat sandi dapat melindungi file dengan enkripsi berbasis kata sandi, namun alat tersebut bukan pengganti untuk mengingat kunci atau menyimpan cadangan. Enkripsi berguna untuk transportasi dan penyimpanan, sedangkan ZIP lebih baik jika tujuan utamanya adalah menggabungkan beberapa keluaran. + Enkripsikan salinannya terlebih dahulu dan simpan yang asli hingga Anda menguji bahwa dekripsi berfungsi dengan kata sandi yang Anda simpan. + Gunakan pengelola kata sandi atau tempat aman lainnya untuk kuncinya; aplikasi tidak dapat menebak kata sandi Anda yang hilang. + Bagikan file terenkripsi hanya dengan orang yang juga memiliki kata sandi dan mengetahui alat atau metode mana yang harus mendekripsi file tersebut. + Atur alat + Kelompokkan, pesan, cari, dan sematkan alat sehingga layar utama sesuai dengan alur kerja Anda yang sebenarnya + Jadikan layar utama lebih cepat + Pengaturan pengaturan alat layak untuk disesuaikan setelah Anda mengetahui alat mana yang paling sering Anda gunakan. Pengelompokan membantu eksplorasi, urutan khusus membantu memori otot, dan favorit membuat alat harian tetap dapat dijangkau bahkan ketika daftar lengkap menjadi lebih banyak. + Gunakan mode berkelompok saat mempelajari aplikasi atau saat Anda lebih memilih alat yang diatur berdasarkan jenis tugas. + Beralih ke pesanan khusus ketika Anda sudah mengetahui alat yang sering Anda gunakan dan menginginkan lebih sedikit gulungan. + Gunakan pengaturan pencarian dan favorit secara bersamaan untuk alat langka yang Anda ingat namanya tetapi tidak ingin selalu terlihat. + Menangani file besar + Hindari ekspor yang lambat, tekanan memori, dan keluaran besar yang tidak terduga + Kurangi pekerjaan sebelum mengekspor + Gambar yang sangat besar, kumpulan yang panjang, dan format berkualitas tinggi dapat memakan lebih banyak memori dan waktu. Perbaikan tercepat biasanya adalah mengurangi jumlah pekerjaan sebelum operasi terberat, tidak menunggu input berukuran besar yang sama selesai. + Ubah ukuran gambar besar sebelum menerapkan filter berat, OCR, atau konversi PDF. + Gunakan batch yang lebih kecil terlebih dahulu untuk mengonfirmasi pengaturan, lalu proses sisanya dengan preset yang sama. + Turunkan kualitas atau ubah format ketika file keluaran lebih besar dari yang diharapkan. + Cache dan pratinjau + Pahami pratinjau yang dihasilkan, pembersihan cache, dan penggunaan penyimpanan setelah sesi yang berat + Jaga keseimbangan penyimpanan dan kecepatan + Pembuatan pratinjau dan cache dapat membuat penjelajahan berulang menjadi lebih cepat, namun sesi pengeditan yang berat mungkin meninggalkan data sementara. Pengaturan cache membantu ketika aplikasi terasa lambat, penyimpanan terbatas, atau pratinjau terlihat basi setelah banyak percobaan. + Tetap mengaktifkan pratinjau saat menelusuri banyak gambar lebih penting daripada meminimalkan penyimpanan sementara. + Hapus cache setelah batch besar, eksperimen gagal, atau sesi panjang dengan banyak file resolusi tinggi. + Gunakan pembersihan cache otomatis jika Anda lebih memilih pembersihan penyimpanan daripada menjaga pratinjau tetap hangat di antara peluncuran. + Sesuaikan perilaku layar + Gunakan opsi layar penuh, mode aman, kecerahan, dan bilah sistem untuk pekerjaan terfokus + Buat antarmuka sesuai dengan situasi + Pengaturan layar membantu saat Anda mengedit dalam lanskap, menyajikan gambar pribadi, atau memerlukan kecerahan yang konsisten. Mereka tidak diperlukan untuk penggunaan normal, tetapi mereka memecahkan situasi canggung seperti inspeksi QR, pratinjau pribadi, atau pengeditan lanskap yang sempit. + Gunakan pengaturan layar penuh atau bilah sistem ketika ruang pengeditan lebih penting daripada navigasi chrome. + Gunakan mode aman ketika gambar pribadi tidak muncul di tangkapan layar atau pratinjau aplikasi. + Gunakan penerapan kecerahan untuk alat yang gambar gelap atau kode QR sulit diperiksa. + Jaga transparansi + Cegah latar belakang transparan berubah menjadi putih, hitam, atau rata + Gunakan keluaran sadar alfa + Transparansi hanya bertahan bila alat yang dipilih dan format keluaran mendukung alfa. Jika latar belakang yang diekspor berubah menjadi putih atau hitam, masalahnya biasanya terletak pada format keluaran, pengaturan isian/latar belakang, atau aplikasi tujuan yang meratakan gambar. + Pilih PNG atau WebP saat mengekspor gambar, stiker, logo, atau overlay yang dihilangkan latar belakangnya. + Hindari JPEG untuk keluaran transparan karena tidak menyimpan saluran alfa. + Periksa pengaturan terkait warna latar belakang untuk format alfa jika pratinjau menunjukkan latar belakang penuh. + Perbaiki masalah berbagi dan impor + Gunakan pengaturan pemilih dan format yang didukung ketika file tidak muncul atau dibuka dengan benar + Dapatkan file ke dalam aplikasi + Masalah impor sering kali disebabkan oleh izin penyedia, format yang tidak didukung, atau perilaku pemilih. File yang dibagikan dari aplikasi cloud dan messenger mungkin bersifat sementara, jadi memilih sumber yang tetap bisa lebih andal untuk pengeditan yang lebih lama. + Coba buka file melalui pemilih sistem, bukan melalui pintasan file terbaru. + Jika suatu format tidak didukung oleh satu alat, konversikan terlebih dahulu atau buka alat yang lebih spesifik. + Tinjau pengaturan pemilih gambar dan peluncur jika alur berbagi atau pembukaan alat langsung berperilaku berbeda dari yang diharapkan. + Keluar dari konfirmasi + Hindari kehilangan hasil edit yang belum disimpan ketika gerakan, penekanan kembali, atau peralihan alat terjadi secara tidak sengaja + Lindungi pekerjaan yang belum selesai + Alat konfirmasi keluar berguna saat Anda menggunakan navigasi gerakan, mengedit file besar, atau sering berpindah antar aplikasi. Ini menambahkan jeda kecil sebelum meninggalkan alat sehingga pengaturan dan pratinjau yang belum selesai tidak hilang secara tidak sengaja. + Aktifkan konfirmasi jika gerakan mundur yang tidak disengaja pernah menutup alat sebelum Anda mengekspor. + Tetap nonaktifkan jika Anda kebanyakan melakukan konversi satu langkah cepat dan lebih memilih navigasi yang lebih cepat. + Gunakan bersama dengan preset untuk alur kerja yang lebih panjang di mana pengaturan ulang akan mengganggu. + Gunakan log saat melaporkan masalah + Kumpulkan detail berguna sebelum membuka terbitan atau menghubungi pengembang + Laporkan masalah sesuai konteks + Laporan yang jelas dengan langkah-langkah, versi aplikasi, jenis input, dan log jauh lebih mudah untuk didiagnosis. Log paling berguna setelah mereproduksi masalah karena log menjaga konteks di sekitarnya tetap segar. + Catat nama alat, tindakan sebenarnya, dan apakah ini terjadi pada satu file atau setiap file. + Buka Log Aplikasi setelah mereproduksi masalah dan bagikan keluaran log yang relevan jika memungkinkan. + Lampirkan tangkapan layar atau file contoh hanya jika tidak berisi informasi pribadi. + Pemrosesan latar belakang dihentikan + Android tidak membiarkan pemberitahuan pemrosesan latar belakang dimulai tepat waktu. Ini adalah batasan waktu sistem yang tidak dapat diperbaiki dengan andal. Mulai ulang aplikasi dan coba lagi; menjaga aplikasi tetap terbuka dan mengizinkan notifikasi atau pekerjaan latar belakang dapat membantu. + Lewati pengambilan file + Membuka alat lebih cepat ketika masukan biasanya berasal dari berbagi, papan klip, atau layar sebelumnya + Jadikan peluncuran alat berulang lebih cepat + Lewati Pengambilan File mengubah langkah pertama alat yang didukung. Ini berguna ketika Anda biasanya memasukkan alat dengan gambar yang sudah dipilih atau dari tindakan berbagi Android, namun bisa terasa membingungkan jika Anda mengharapkan setiap alat meminta file dengan segera. + Aktifkan hanya ketika aliran biasa Anda sudah menyediakan gambar sumber sebelum alat terbuka. + Tetap nonaktifkan saat mempelajari aplikasi, karena pemilihan eksplisit membuat setiap alur alat lebih mudah dipahami. + Jika alat terbuka tanpa masukan yang jelas, nonaktifkan pengaturan ini atau mulai dari Bagikan/Buka Dengan agar Android meneruskan file terlebih dahulu. + Buka editornya terlebih dahulu + Lewati pratinjau ketika gambar yang dibagikan harus langsung diedit + Pilih pratinjau atau edit sebagai perhentian pertama + Buka Edit Daripada Pratinjau diperuntukkan bagi pengguna yang sudah mengetahui bahwa mereka ingin memodifikasi gambar. Pratinjau lebih aman saat Anda memeriksa file dari obrolan atau penyimpanan cloud; pengeditan langsung lebih cepat untuk tangkapan layar, pemotongan cepat, anotasi, dan koreksi satu kali. + Aktifkan pengeditan langsung jika sebagian besar gambar yang dibagikan segera memerlukan pemotongan, markup, filter, atau perubahan ekspor. + Tinggalkan pratinjau terlebih dahulu ketika Anda sering memeriksa metadata, dimensi, transparansi, atau kualitas gambar sebelum memutuskan. + Gunakan ini bersama dengan favorit sehingga gambar yang dibagikan dekat dengan alat yang sebenarnya Anda gunakan. + Entri teks preset + Masukkan nilai prasetel yang tepat alih-alih menyesuaikan kontrol berulang kali + Gunakan nilai yang tepat saat penggeser lambat + Entri teks preset membantu ketika Anda memerlukan angka pasti untuk pekerjaan berulang: ukuran gambar sosial, margin dokumen, target kompresi, lebar garis, atau nilai lain yang sulit dijangkau dengan gerakan. Ini juga berguna pada layar kecil di mana gerakan penggeser halus lebih sulit. + Aktifkan entri teks jika Anda sering menggunakan kembali ukuran, persentase, nilai kualitas, atau parameter alat yang tepat. + Simpan nilainya sebagai preset setelah mengetiknya sekali, lalu gunakan kembali alih-alih membuat ulang pengaturan yang sama. + Pertahankan kontrol gerakan untuk penyesuaian visual kasar dan nilai yang diketik untuk persyaratan keluaran yang ketat. + Simpan mendekati aslinya + Letakkan file yang dihasilkan di samping gambar sumber ketika konteks folder penting + Simpan keluaran dengan sumbernya + Simpan Ke Folder Asli berguna untuk folder kamera, folder proyek, pemindaian dokumen, dan kumpulan klien di mana salinan yang diedit harus tetap berada di sebelah sumbernya. Ini mungkin kurang rapi dibandingkan folder keluaran khusus, jadi gabungkan dengan akhiran atau pola nama file yang jelas. + Aktifkan ketika setiap folder sumber sudah bermakna dan keluaran harus tetap dikelompokkan di sana. + Gunakan akhiran nama file, awalan, stempel waktu, atau pola sehingga salinan yang diedit mudah dipisahkan dari aslinya. + Nonaktifkan untuk kumpulan campuran bila Anda ingin semua file yang dihasilkan dikumpulkan dalam satu folder ekspor. + Lewati keluaran yang lebih besar + Hindari menyimpan konversi yang tiba-tiba menjadi lebih berat daripada sumbernya + Lindungi batch dari kejutan ukuran yang buruk + Beberapa konversi membuat file lebih besar, terutama tangkapan layar PNG, foto yang sudah dikompresi, WebP berkualitas tinggi, atau gambar dengan metadata yang dipertahankan. Izinkan Lewati Jika Lebih Besar mencegah output tersebut menggantikan sumber yang lebih kecil dalam alur kerja yang tujuan utamanya adalah mengurangi ukuran. + Aktifkan ketika ekspor dimaksudkan untuk mengompresi, mengubah ukuran, atau menyiapkan file untuk batas unggahan. + Tinjau file yang dilewati setelah batch; mereka mungkin sudah dioptimalkan atau mungkin memerlukan format yang berbeda. + Nonaktifkan ketika kualitas, transparansi, atau kompatibilitas format lebih penting daripada ukuran file akhir. + Papan klip dan tautan + Kontrol input clipboard otomatis dan pratinjau tautan untuk alat berbasis teks yang lebih cepat + Jadikan masukan otomatis dapat diprediksi + Pengaturan papan klip dan tautan cocok untuk alur kerja QR, Base64, URL, dan banyak teks, namun masukan otomatis harus tetap disengaja. Jika aplikasi tampaknya mengisi kolom dengan sendirinya, periksa perilaku tempel clipboard; jika kartu tautan terasa berisik atau lambat, sesuaikan perilaku pratinjau tautan. + Izinkan tempel clipboard otomatis ketika Anda sering menyalin teks dan segera membuka alat yang cocok. + Nonaktifkan tempel otomatis jika konten papan klip pribadi muncul di alat yang tidak Anda duga. + Nonaktifkan pratinjau tautan saat pengambilan pratinjau lambat, mengganggu, atau tidak diperlukan untuk alur kerja Anda. + Kontrol yang ringkas + Gunakan pemilih yang lebih padat dan penempatan pengaturan yang cepat ketika ruang layar sempit + Pasang lebih banyak kontrol di layar kecil + Penyeleksi ringkas dan penempatan pengaturan cepat membantu pada ponsel kecil, pengeditan lanskap, dan alat dengan banyak parameter. Imbalannya adalah kontrolnya bisa terasa lebih padat, jadi ini paling baik dilakukan setelah Anda mengenali opsi umum. + Aktifkan pemilih ringkas ketika dialog atau daftar opsi terasa terlalu besar untuk layar. + Sesuaikan sisi pengaturan cepat jika kontrol alat lebih mudah dijangkau dari satu sisi tampilan. + Kembali ke kontrol yang lebih luas jika Anda masih mempelajari aplikasinya atau sering salah mengetuk opsi padat. + Muat gambar dari web + Tempelkan URL halaman atau gambar, pratinjau gambar yang diurai, lalu simpan atau bagikan hasil yang dipilih + Gunakan gambar web dengan sengaja + Pemuatan Gambar Web mem-parsing sumber gambar dari URL yang disediakan, mempratinjau gambar pertama yang tersedia, dan memungkinkan Anda menyimpan atau berbagi gambar parsing yang dipilih. Beberapa situs menggunakan tautan sementara, dilindungi, atau dibuat dengan skrip, sehingga laman yang berfungsi di browser mungkin masih tidak menghasilkan gambar yang dapat digunakan. + Tempelkan URL gambar langsung atau URL halaman dan tunggu hingga daftar gambar yang diurai muncul. + Gunakan kontrol bingkai atau pemilihan ketika halaman berisi beberapa gambar dan Anda hanya memerlukan beberapa gambar saja. + Jika tidak ada yang dimuat, coba tautan gambar langsung atau unduh melalui browser terlebih dahulu; situs mungkin memblokir penguraian atau menggunakan tautan pribadi. + Pilih jenis pengubahan ukuran + Pahami Eksplisit, Fleksibel, Pangkas, dan Sesuaikan sebelum memaksakan gambar ke dimensi baru + Kontrol bentuk, kanvas, dan rasio aspek + Jenis pengubahan ukuran memutuskan apa yang terjadi jika lebar dan tinggi yang diminta tidak sesuai dengan rasio aspek asli. Ini adalah perbedaan antara merenggangkan piksel, mempertahankan proporsi, memotong area ekstra, atau menambahkan kanvas latar belakang. + Gunakan Eksplisit hanya jika distorsi dapat diterima atau sumber sudah memiliki rasio aspek yang sama dengan target. + Gunakan Fleksibel untuk menjaga proporsi dengan mengubah ukuran dari sisi penting, terutama untuk foto dan kumpulan campuran. + Gunakan Pangkas untuk bingkai ketat tanpa batas, atau Pas ketika seluruh gambar harus tetap terlihat di dalam kanvas tetap. + Pilih mode skala gambar + Pilih filter pengambilan sampel ulang yang mengontrol ketajaman, kehalusan, kecepatan, dan artefak tepi + Ubah ukuran tanpa kelembutan yang tidak disengaja + Mode skala gambar mengontrol bagaimana piksel baru dihitung selama pengubahan ukuran. Mode sederhana cepat dan dapat diprediksi, sedangkan filter lanjutan dapat mempertahankan detail dengan lebih baik tetapi dapat mempertajam tepian, menciptakan lingkaran cahaya, atau membutuhkan waktu lebih lama pada gambar besar. + Gunakan Basic atau Bilinear untuk mengubah ukuran dengan cepat setiap hari ketika hasilnya hanya perlu lebih kecil dan bersih. + Gunakan mode gaya Lanczos, Robidoux, Spline, atau EWA ketika detail halus, teks, atau penurunan skala berkualitas tinggi penting. + Gunakan Nearest untuk seni piksel, ikon, dan grafis bertepi keras yang pikselnya buram akan merusak tampilan. + Ruang warna skala + Putuskan bagaimana warna dicampur saat piksel diambil sampelnya ulang selama pengubahan ukuran + Pertahankan gradien dan tepian tetap alami + Ruang warna skala mengubah matematika yang digunakan saat mengubah ukuran. Sebagian besar gambar baik-baik saja dengan default, tetapi ruang linier atau persepsi dapat membuat gradien, tepi gelap, dan warna jenuh terlihat lebih bersih setelah penskalaan yang kuat. + Biarkan default ketika kecepatan dan kompatibilitas lebih penting daripada perbedaan warna kecil. + Coba mode Linier atau persepsi ketika garis gelap, tangkapan layar UI, gradien, atau pita warna terlihat salah setelah diubah ukurannya. + Bandingkan sebelum dan sesudah pada layar target sebenarnya, karena perubahan ruang warna bisa jadi tidak kentara dan bergantung pada konten. + Batasi mode Pengubahan Ukuran + Ketahui kapan gambar yang melebihi batas harus dilewati, dikodekan ulang, atau diperkecil agar pas + Pilih apa yang terjadi pada batasnya + Batasi Pengubahan Ukuran bukan hanya alat untuk memperkecil gambar. Modenya menentukan apa yang dilakukan aplikasi ketika sumber sudah berada di dalam batas Anda atau ketika hanya satu pihak yang melanggar aturan, yang penting untuk folder campuran dan persiapan pengunggahan. + Gunakan Lewati ketika gambar di dalam batas tidak boleh disentuh dan tidak diekspor lagi. + Gunakan Kode Ulang ketika dimensi dapat diterima tetapi Anda masih memerlukan format, perilaku metadata, kualitas, atau nama file baru. + Gunakan Zoom ketika gambar yang melanggar batas harus diperkecil secara proporsional hingga sesuai dengan kotak yang diizinkan. + Target rasio aspek + Hindari permukaan yang melebar, tepi yang terpotong, dan tepian yang tidak terduga dalam ukuran keluaran yang ketat + Cocokkan bentuknya sebelum mengubah ukurannya + Lebar dan tinggi hanya setengah dari target pengubahan ukuran. Rasio aspek menentukan apakah gambar bisa pas secara alami, perlu dipotong, atau perlu kanvas di sekelilingnya. Memeriksa bentuknya terlebih dahulu akan mencegah hasil pengubahan ukuran yang paling mengejutkan. + Bandingkan bentuk sumber dengan bentuk target sebelum memilih Eksplisit, Pangkas, atau Sesuaikan. + Pangkas terlebih dahulu ketika subjek dapat kehilangan latar belakang tambahan dan tujuannya memerlukan bingkai yang ketat. + Gunakan Fit atau Fleksibel ketika setiap bagian gambar asli harus tetap terlihat. + Pengubahan ukuran kelas atas atau normal + Ketahui kapan penambahan piksel memerlukan AI dan kapan penskalaan sederhana sudah cukup + Jangan bingung membedakan ukuran dengan detail + Pengubahan ukuran normal mengubah dimensi dengan menginterpolasi piksel; ia tidak dapat menciptakan detail nyata. AI kelas atas dapat menciptakan detail yang tampak lebih tajam, namun lebih lambat dan mungkin membuat halusinasi tekstur, jadi pilihan yang tepat bergantung pada apakah Anda memerlukan kompatibilitas, kecepatan, atau restorasi visual. + Gunakan pengubahan ukuran normal untuk gambar mini, batas unggahan, tangkapan layar, dan perubahan dimensi sederhana. + Gunakan AI kelas atas ketika gambar kecil atau lembut perlu terlihat lebih baik pada ukuran lebih besar. + Bandingkan wajah, teks, dan pola setelah AI ditingkatkan karena detail yang dihasilkan mungkin terlihat meyakinkan tetapi salah. + Urutan filter itu penting + Terapkan pembersihan, warna, ketajaman, dan kompresi akhir dalam urutan yang disengaja + Bangun tumpukan filter yang lebih bersih + Filter tidak selalu dapat dipertukarkan. Keburaman sebelum dipertajam, peningkatan kontras sebelum OCR, atau perubahan warna sebelum kompresi dapat menghasilkan keluaran yang sangat berbeda dari kontrol yang sama. + Pangkas dan putar terlebih dahulu sehingga filter selanjutnya hanya berfungsi pada piksel yang tersisa. + Terapkan eksposur, keseimbangan putih, dan kontras sebelum mempertajam atau memberi gaya pada efek. + Pertahankan pengubahan ukuran dan kompresi akhir di dekat akhir sehingga detail yang dipratinjau dapat bertahan saat diekspor. + Perbaiki tepi latar belakang + Bersihkan lingkaran cahaya, sisa bayangan, rambut, lubang, dan garis halus setelah penghapusan otomatis + Buat potongan terlihat disengaja + Masker otomatis sering kali terlihat bagus secara sekilas, tetapi menunjukkan masalah pada latar belakang terang, gelap, atau berpola. Pembersihan tepi adalah perbedaan antara potongan cepat dan stiker, logo, atau gambar produk yang dapat digunakan kembali. + Pratinjau hasilnya dengan latar belakang kontras untuk memperlihatkan lingkaran cahaya dan detail tepi yang hilang. + Gunakan restorasi pada rambut, sudut, dan objek transparan sebelum menghapus sisa makanan di sekitarnya. + Ekspor tes kecil pada warna latar belakang akhir saat potongan akan ditempatkan ke dalam desain. + Tanda air yang dapat dibaca + Membuat tanda terlihat pada gambar terang, gelap, sibuk, dan terpotong tanpa merusak foto + Lindungi gambar tanpa menyembunyikannya + Tanda air yang terlihat bagus di satu gambar mungkin hilang di gambar lain. Ukuran, opasitas, kontras, penempatan, dan pengulangan harus dipilih untuk keseluruhan kumpulan, bukan hanya pratinjau pertama. + Uji tanda pada gambar paling terang dan paling gelap dalam kumpulan sebelum mengekspor semua file. + Gunakan opacity yang lebih rendah untuk tanda berulang yang besar dan kontras yang lebih kuat untuk tanda sudut kecil. + Jaga agar wajah, teks, dan detail produk yang penting tetap jelas kecuali tanda tersebut dimaksudkan untuk mencegah penggunaan kembali. + Pisahkan satu gambar menjadi ubin + Potong satu gambar berdasarkan baris, kolom, persentase khusus, format, dan kualitas + Siapkan kisi-kisi dan potongan yang dipesan + Pemisahan Gambar menghasilkan banyak keluaran dari satu gambar sumber. Itu dapat dibagi berdasarkan jumlah baris dan kolom, menyesuaikan persentase baris atau kolom, dan menyimpan potongan dengan format dan kualitas keluaran yang dipilih, yang berguna untuk kisi, irisan halaman, panorama, dan kiriman sosial yang diurutkan. + Pilih gambar sumber, lalu atur jumlah baris dan kolom yang Anda perlukan. + Sesuaikan persentase baris atau kolom ketika potongan-potongannya tidak semuanya berukuran sama. + Pilih format dan kualitas keluaran sebelum disimpan sehingga setiap karya yang dihasilkan menggunakan aturan ekspor yang sama. + Gunting strip gambar + Hapus wilayah vertikal atau horizontal dan gabungkan kembali bagian yang tersisa + Hapus suatu wilayah tanpa meninggalkan celah + Pemotongan Gambar berbeda dengan pemotongan biasa. Itu dapat menghapus strip vertikal atau horizontal yang dipilih, lalu menggabungkan bagian gambar yang tersisa menjadi satu. Mode invers mempertahankan wilayah yang dipilih, dan menggunakan kedua arah invers mempertahankan persegi panjang yang dipilih. + Gunakan pemotongan vertikal untuk bagian samping, kolom, atau strip tengah; gunakan pemotongan horizontal untuk spanduk, celah, atau baris. + Aktifkan invers pada sumbu ketika Anda ingin mempertahankan bagian yang dipilih alih-alih menghapusnya. + Pratinjau tepian setelah pemotongan karena konten yang digabungkan dapat terlihat tiba-tiba ketika area yang dihapus melintasi detail penting. + Pilih format keluaran + Pilih JPEG, PNG, WebP, AVIF, JXL, atau PDF berdasarkan konten dan tujuan + Biarkan tujuan menentukan formatnya + Format terbaik bergantung pada isi gambar dan di mana gambar itu akan digunakan. Foto, tangkapan layar, aset transparan, dokumen, dan file animasi semuanya gagal dengan cara yang berbeda-beda jika dipaksa ke dalam format yang salah. + Gunakan JPEG untuk kompatibilitas foto yang luas ketika transparansi dan piksel yang tepat tidak diperlukan. + Gunakan PNG untuk tangkapan layar yang tajam, pengambilan UI, grafik transparan, dan pengeditan tanpa kehilangan. + Gunakan WebP, AVIF, atau JXL ketika keluaran modern yang ringkas penting dan aplikasi penerima mendukungnya. + Ekstrak sampul audio + Simpan sampul album tersemat atau bingkai media yang tersedia dari file audio sebagai gambar PNG + Tarik karya seni dari file audio + Sampul Audio menggunakan metadata media Android untuk membaca karya seni yang tersemat dari file audio. Jika karya seni yang disematkan tidak tersedia, pengambilan dapat kembali ke bingkai media jika Android dapat menyediakannya; jika tidak ada, alat melaporkan bahwa tidak ada gambar untuk diekstraksi. + Buka Audio Covers dan pilih satu atau lebih file audio dengan cover art yang diharapkan. + Tinjau sampul yang diekstrak sebelum menyimpan atau berbagi, terutama untuk file dari aplikasi streaming atau rekaman. + Jika tidak ada gambar yang ditemukan, kemungkinan besar file audio tidak memiliki sampul yang tertanam atau Android tidak dapat menampilkan bingkai yang dapat digunakan. + Metadata tanggal dan lokasi + Putuskan apa yang ingin disimpan ketika waktu pengambilan gambar penting, namun GPS atau data perangkat tidak boleh bocor + Pisahkan metadata yang berguna dari metadata pribadi + Metadata tidak semuanya memiliki risiko yang sama. Tanggal pengambilan dapat berguna untuk pengarsipan dan penyortiran, sementara GPS, bidang seperti serial perangkat, tag perangkat lunak, atau bidang pemilik dapat mengungkapkan lebih dari yang diharapkan. + Simpan tag tanggal dan waktu ketika urutan kronologis penting untuk album, pindaian, atau riwayat proyek. + Hapus GPS dan tag pengenal perangkat sebelum membagikannya secara publik atau mengirim gambar kepada orang asing. + Ekspor sampel dan periksa kembali EXIF ​​ketika persyaratan privasi ketat. + Hindari tabrakan nama file + Lindungi keluaran batch ketika banyak file memiliki nama yang sama seperti image.jpg atau screenshot.png + Jadikan setiap nama keluaran unik + Nama file duplikat sering terjadi jika gambar berasal dari messenger, tangkapan layar, folder cloud, atau beberapa kamera. Pola yang baik mencegah penggantian yang tidak disengaja dan menjaga keluaran tetap dapat dilacak kembali ke sumbernya. + Cantumkan nama asli ditambah akhiran agar salinan yang diedit dapat dikenali. + Tambahkan urutan, stempel waktu, preset, atau dimensi saat memproses batch campuran dalam jumlah besar. + Hindari menimpa alur kerja sampai Anda memverifikasi bahwa pola penamaan tidak dapat bertabrakan. + Simpan atau bagikan + Pilih antara file persisten dan penyerahan sementara ke aplikasi lain + Ekspor dengan niat yang benar + Simpan dan Bagikan mungkin terasa serupa, tetapi keduanya memecahkan masalah yang berbeda. Menyimpan akan membuat file yang dapat Anda temukan nanti; berbagi mengirimkan hasil yang dihasilkan melalui Android ke aplikasi lain dan mungkin tidak meninggalkan salinan lokal yang tahan lama. + Gunakan Simpan ketika keluaran harus tetap berada di folder yang dikenal, dicadangkan, atau digunakan kembali nanti. + Gunakan Bagikan untuk pesan cepat, lampiran email, postingan sosial, atau mengirimkan hasilnya ke editor lain. + Simpan terlebih dahulu ketika aplikasi penerima tidak dapat diandalkan atau ketika pembagian yang gagal akan mengganggu untuk dibuat ulang. + Ukuran dan margin halaman PDF + Pilih ukuran kertas, kesesuaian, dan ruang bernapas sebelum membuat dokumen + Jadikan halaman gambar dapat dicetak dan dibaca + Gambar bisa menjadi halaman PDF yang aneh jika bentuknya tidak sesuai dengan ukuran kertas yang dipilih. Margin, mode pas, dan orientasi halaman menentukan apakah konten dipotong, kecil, diregangkan, atau nyaman dibaca. + Gunakan ukuran kertas asli ketika PDF dapat dicetak atau diserahkan ke formulir. + Gunakan margin untuk pemindaian dan tangkapan layar agar teks tidak menempel di tepi halaman. + Pratinjau halaman potret dan lanskap secara bersamaan ketika gambar sumber memiliki orientasi campuran. + Bersihkan pindaian + Tingkatkan tepi kertas, bayangan, kontras, rotasi, dan keterbacaan sebelum PDF atau OCR + Siapkan halaman sebelum pengarsipan + Pemindaian secara teknis dapat ditangkap tetapi masih sulit dibaca. Langkah-langkah pembersihan kecil sebelum mengekspor PDF seringkali lebih penting daripada pengaturan kompresi, terutama untuk tanda terima, catatan tulisan tangan, dan halaman dengan cahaya redup. + Perbaiki perspektif dan pangkas tepi meja, jari, bayangan, dan latar belakang yang berantakan. + Tingkatkan kontras dengan hati-hati agar teks menjadi lebih jelas tanpa merusak prangko, tanda tangan, atau tanda pensil. + Jalankan OCR hanya setelah halaman tegak dan dapat dibaca, lalu verifikasi nomor-nomor penting secara manual. + Kompres, skala abu-abu, atau perbaiki PDF + Gunakan operasi PDF satu file untuk salinan dokumen yang lebih ringan, dapat dicetak, atau dibuat ulang + Pilih operasi pembersihan PDF + Alat PDF mencakup operasi terpisah untuk kompresi, konversi skala abu-abu, dan perbaikan. Kompresi dan skala abu-abu berfungsi terutama melalui gambar yang disematkan, sementara perbaikan membangun kembali struktur PDF; tidak satu pun dari hal ini yang harus diperlakukan sebagai jaminan perbaikan untuk setiap dokumen. + Gunakan Kompres PDF ketika dokumen berisi gambar dan ukuran file penting untuk dibagikan. + Gunakan Grayscale ketika dokumen seharusnya lebih mudah dicetak atau tidak memerlukan gambar berwarna. + Gunakan Perbaiki PDF pada salinan ketika dokumen gagal dibuka atau struktur internalnya rusak, lalu verifikasi file yang dibuat ulang. + Ekstrak gambar dari PDF + Pulihkan gambar PDF yang tertanam dan kemas hasil yang diekstraksi ke dalam arsip + Simpan gambar sumber dari dokumen + Ekstrak Gambar memindai PDF untuk mencari objek gambar yang disematkan, melewatkan duplikat jika memungkinkan, dan menyimpan gambar yang dipulihkan dalam arsip ZIP yang dihasilkan. Itu hanya mengekstrak gambar yang sebenarnya tertanam dalam PDF, bukan teks, gambar vektor, atau setiap halaman yang terlihat sebagai tangkapan layar. + Gunakan Ekstrak Gambar ketika Anda memerlukan gambar yang disimpan di dalam PDF, seperti foto dari katalog atau aset yang dipindai. + Gunakan PDF ke Gambar atau ekstraksi halaman saat Anda membutuhkan halaman penuh, termasuk teks dan tata letak. + Jika alat tersebut melaporkan tidak ada gambar yang disematkan, halaman tersebut mungkin berisi teks/vektor atau gambar tersebut mungkin tidak disimpan sebagai objek yang dapat diekstraksi. + Metadata, perataan, dan hasil cetak + Edit properti dokumen, rasterisasi halaman, atau persiapkan tata letak cetak khusus dengan sengaja + Pilih tindakan dokumen terakhir + Metadata PDF, perataan, dan persiapan pencetakan memecahkan berbagai masalah tahap akhir. Metadata mengontrol properti dokumen, Ratakan PDF meraster halaman menjadi keluaran yang lebih sedikit dapat diedit, dan Cetak PDF menyiapkan tata letak baru dengan ukuran halaman, orientasi, margin, dan kontrol halaman per lembar. + Gunakan Metadata ketika penulis, judul, kata kunci, produser, atau pembersihan privasi penting. + Gunakan Ratakan PDF ketika konten halaman yang terlihat menjadi lebih sulit untuk diedit sebagai objek PDF terpisah. + Gunakan Cetak PDF saat Anda memerlukan ukuran halaman khusus, orientasi, margin, atau beberapa halaman per lembar. + Siapkan gambar untuk OCR + Gunakan krop, rotasi, kontras, denoise, dan skala sebelum meminta OCR membaca teks + Berikan piksel pembersih OCR + Kesalahan OCR sering kali berasal dari gambar masukan, bukan mesin OCR. Halaman bengkok, kontras rendah, buram, bayangan, teks kecil, dan latar belakang tercampur semuanya mengurangi kualitas pengenalan. + Pangkas ke area teks dan putar gambar sehingga garis menjadi horizontal sebelum dikenali. + Tingkatkan kontras atau ubah ke hitam-putih yang lebih bersih hanya jika hal itu membuat huruf lebih mudah dibaca. + Ubah ukuran teks kecil ke atas secukupnya, namun hindari penajaman agresif yang menghasilkan tepi palsu. + Periksa konten QR + Tinjau tautan, kredensial Wi-Fi, kontak, dan tindakan sebelum memercayai kode + Perlakukan teks yang didekodekan sebagai masukan yang tidak tepercaya + Kode QR dapat menyembunyikan URL, kartu kontak, kata sandi Wi-Fi, acara kalender, nomor telepon, atau teks biasa. Label yang terlihat di dekat kode mungkin tidak cocok dengan muatan sebenarnya, jadi tinjau konten yang didekodekan sebelum mengambil tindakan. + Periksa URL lengkap yang didekodekan sebelum membukanya, terutama domain yang diperpendek atau tidak dikenal. + Hati-hati dengan kode Wi-Fi, kontak, kalender, telepon, dan SMS karena dapat memicu tindakan sensitif. + Salin kontennya terlebih dahulu ketika Anda perlu memverifikasinya di tempat lain daripada langsung membukanya. + Hasilkan kode QR + Buat kode dari teks biasa, URL, Wi-Fi, kontak, email, telepon, SMS, dan data lokasi + Bangun muatan QR yang tepat + Layar QR dapat menghasilkan kode dari konten yang dimasukkan serta memindai konten yang sudah ada. Jenis QR terstruktur membantu menyandikan data dalam format yang dipahami aplikasi lain, seperti detail login Wi-Fi, kartu kontak, kolom email, nomor telepon, konten SMS, URL, atau koordinat geografis. + Pilih teks biasa untuk catatan sederhana, atau gunakan tipe terstruktur ketika kode harus dibuka sebagai Wi-Fi, kontak, URL, email, telepon, SMS, atau data lokasi. + Periksa setiap kolom sebelum membagikan kode yang dihasilkan karena pratinjau yang terlihat hanya mencerminkan payload yang Anda masukkan. + Uji kode QR yang disimpan dengan pemindai ketika akan dicetak, diposting secara publik, atau digunakan oleh orang lain. + Pilih mode checksum + Hitung hash dari file atau teks, bandingkan satu file, atau periksa banyak file secara batch + Verifikasi konten, bukan penampilan + Alat Checksum memiliki tab terpisah untuk hashing file, hashing teks, membandingkan file dengan hash yang diketahui, dan perbandingan batch. Sebuah checksum membuktikan identitas byte demi byte untuk algoritma yang dipilih; ini tidak membuktikan bahwa dua gambar terlihat serupa. + Gunakan Hitung untuk file dan Hash Teks untuk data teks yang diketik atau ditempel. + Gunakan Bandingkan ketika seseorang memberi Anda checksum yang diharapkan untuk satu file. + Gunakan Bandingkan Batch ketika banyak file memerlukan hash atau verifikasi sekaligus, lalu pertahankan konsistensi algoritma yang dipilih. + Privasi papan klip + Gunakan fitur papan klip otomatis tanpa sengaja mengekspos data pribadi yang disalin + Jaga agar konten yang disalin tetap disengaja + Otomatisasi papan klip memang mudah dilakukan, tetapi teks yang disalin mungkin berisi kata sandi, tautan, alamat, token, atau catatan pribadi. Perlakukan masukan berbasis papan klip sebagai fitur kecepatan yang harus sesuai dengan kebiasaan dan ekspektasi privasi Anda. + Nonaktifkan penggunaan papan klip otomatis jika teks pribadi muncul di alat secara tidak terduga. + Gunakan penyimpanan hanya clipboard hanya jika Anda sengaja menginginkan hasilnya untuk menghindari penyimpanan. + Hapus atau ganti clipboard setelah menangani teks sensitif, data QR, atau muatan Base64. + Format warna + Pahami nilai warna HEX, RGB, HSV, alfa, dan warna yang disalin sebelum menempelkannya di tempat lain + Salin format yang diharapkan target + Warna tampak yang sama dapat ditulis dengan beberapa cara. Alat desain, bidang CSS, nilai warna Android, atau editor gambar mungkin memerlukan urutan, penanganan alfa, atau model warna yang berbeda. + Gunakan HEX saat menempelkan ke bidang web, desain, atau tema yang mengharapkan kode warna ringkas. + Gunakan RGB atau HSV saat Anda perlu menyesuaikan saluran, kecerahan, saturasi, atau rona secara manual. + Periksa alfa secara terpisah ketika transparansi penting karena beberapa tujuan mengabaikannya atau menggunakan urutan yang berbeda. + Jelajahi perpustakaan warna + Cari warna bernama berdasarkan nama atau HEX dan simpan favorit di dekat bagian atas + Temukan warna bernama yang dapat digunakan kembali + Perpustakaan Warna memuat koleksi warna yang diberi nama, mengurutkannya berdasarkan nama, memfilter berdasarkan nama warna atau nilai HEX, dan menyimpan warna favorit sehingga entri penting tetap mudah dijangkau. Hal ini berguna ketika Anda membutuhkan warna bernama asli alih-alih mengambil sampel dari sebuah gambar. + Telusuri berdasarkan nama warna jika Anda mengetahui keluarganya, seperti merah, biru, batu tulis, atau mint. + Cari berdasarkan HEX ketika Anda sudah memiliki warna numerik dan ingin menemukan entri bernama yang cocok atau terdekat. + Tandai warna yang sering digunakan sebagai favorit sehingga warna tersebut lebih unggul dari perpustakaan umum saat menjelajah. + Gunakan koleksi gradien mesh + Unduh sumber daya gradien mesh yang tersedia dan bagikan gambar yang dipilih + Gunakan aset gradien mesh jarak jauh + Mesh Gradients dapat memuat koleksi sumber daya jarak jauh, mengunduh gambar gradien yang hilang, menampilkan kemajuan unduhan, dan membagikan gradien yang dipilih. Ketersediaan bergantung pada akses jaringan dan penyimpanan sumber daya jarak jauh, jadi daftar kosong biasanya berarti sumber daya belum tersedia. + Buka Mesh Gradients dari alat gradien bila Anda menginginkan gambar gradien mesh yang sudah jadi. + Tunggu pemuatan sumber daya atau kemajuan pengunduhan sebelum menganggap koleksinya kosong. + Pilih dan bagikan gradien yang Anda perlukan, atau kembali ke Gradient Maker saat Anda ingin membuat gradien khusus secara manual. + Resolusi aset yang dihasilkan + Pilih ukuran keluaran sebelum membuat gradien, noise, wallpaper, seni mirip SVG, atau tekstur + Hasilkan sesuai ukuran yang Anda butuhkan + Gambar yang dihasilkan tidak memiliki kamera asli sebagai sandarannya. Jika outputnya terlalu kecil, penskalaan selanjutnya dapat memperhalus pola, menggeser detail, atau mengubah cara aset disusun dan dikompres. + Tetapkan dimensi untuk kasus penggunaan akhir terlebih dahulu, seperti wallpaper, ikon, latar belakang, cetakan, atau overlay. + Gunakan ukuran yang lebih besar untuk tekstur yang dapat dipotong, diperbesar, atau digunakan kembali dalam beberapa tata letak. + Ekspor versi pengujian sebelum keluaran besar karena detail prosedural dapat mengubah perilaku kompresi. + Ekspor wallpaper saat ini + Ambil Wallpaper Rumah, Kunci, dan bawaan yang tersedia dari perangkat + Simpan atau bagikan wallpaper yang terpasang + Ekspor Wallpaper membaca wallpaper yang diekspos Android melalui API wallpaper sistem. Tergantung pada perangkat, versi Android, izin, dan varian build, Wallpaper Beranda, Kunci, atau bawaan mungkin tersedia secara terpisah atau mungkin hilang. + Buka Ekspor Wallpaper untuk memuat wallpaper yang dapat dibaca oleh aplikasi oleh sistem. + Pilih entri Rumah, Kunci, atau wallpaper bawaan yang tersedia yang ingin Anda simpan, salin, atau bagikan. + Jika wallpaper tidak ada atau fitur tidak tersedia, biasanya hal tersebut disebabkan oleh sistem, izin, atau batasan varian build, bukan pengaturan pengeditan. + Throttle Animasi Sudut + Salin warna sebagai + HEX + nama + Model anyaman potret untuk potongan orang cepat dengan rambut lebih lembut dan penanganan tepi. + Peningkatan cahaya rendah yang cepat menggunakan estimasi kurva Zero-DCE++. + Model restorasi gambar Restormer untuk mengurangi garis-garis hujan dan artefak cuaca basah. + Model dokumentasi yang tidak melengkung yang memprediksi kisi koordinat dan memetakan ulang halaman melengkung menjadi pemindaian yang lebih datar. + Skala Durasi Gerak + Mengontrol kecepatan animasi aplikasi: 0 menonaktifkan gerakan, 1 normal, nilai yang lebih tinggi memperlambat animasi + Tampilkan Alat Terbaru + Tampilkan akses cepat ke alat yang baru-baru ini digunakan di layar utama + Statistik Penggunaan + Jelajahi pembukaan aplikasi, peluncuran alat, dan alat yang paling sering Anda gunakan + Aplikasi terbuka + Alat terbuka + Alat terakhir + Penyelamatan berhasil + Rentetan aktivitas + Data disimpan + Format teratas + Alat yang digunakan + %1$d terbuka • %2$s + Belum ada statistik penggunaan + Buka beberapa alat dan alat tersebut akan muncul di sini secara otomatis + Statistik penggunaan Anda hanya disimpan secara lokal di perangkat Anda. ImageToolbox menggunakannya hanya untuk menampilkan aktivitas pribadi Anda, alat favorit, dan wawasan data yang disimpan + editor edit foto, retouch gambar, sesuaikan + ubah ukuran konversi skala resolusi dimensi lebar tinggi jpg jpeg png kompres webp + ukuran kompres berat byte mb kb mengurangi optimalisasi kualitas + rasio aspek pemotongan trim tanaman + filter efek koreksi warna sesuaikan masker lut + menggambar markup anotasi pensil kuas cat + enkripsi sandi mendekripsi rahasia kata sandi + penghapus latar belakang hapus hapus potongan alfa transparan + pratinjau galeri penampil periksa terbuka + stitch merge menggabungkan tangkapan layar panorama panjang + url web unduh gambar tautan internet + pemilih pipet sampel warna hex rgb + contoh warna palet ekstrak dominan + privasi metadata Exic hapus hapus lokasi gps yang bersih + bandingkan perbedaan perbedaan sebelum sesudahnya + batas ubah ukuran penjepit dimensi maks min megapiksel + alat halaman dokumen pdf + teks ocr mengenali pemindaian ekstrak + jaring warna latar belakang gradien + hamparan stempel teks logo tanda air + bingkai konversi animasi animasi gif + animasi apng animasi png mengkonversi bingkai + arsip zip paket kompres membongkar file + jxl jpeg xl mengkonversi format gambar jpg + svg jejak vektor mengonversi xml + format konversi jpg jpeg png webp heic avif jxl bmp + pemindai dokumen memindai tanaman perspektif kertas + pemindaian pemindai kode batang qr + menumpuk overlay astrofotografi median rata-rata + bagian irisan kotak ubin terpisah + konversi warna hex rgb hsl hsv cmyk lab + webp mengonversi format gambar animasi + butiran tekstur acak penghasil kebisingan + gabungan tata letak kotak kolase + lapisan markup membubuhi keterangan edit overlay + base64 menyandikan mendekode data uri + checksum hash md5 sha sha1 sha256 verifikasi + latar belakang gradien jala + exemp metadata edit kamera tanggal gps + potong ubin potongan kotak terpisah + metadata musik seni album sampul audio + latar belakang ekspor wallpaper + karakter seni teks ascii + ai ml saraf meningkatkan segmen kelas atas + palet bahan perpustakaan warna bernama warna + studio efek fragmen shader glsl + pdf menggabungkan menggabungkan bergabung + pdf membagi halaman terpisah + pdf memutar orientasi halaman + pdf mengatur ulang menyusun ulang halaman-halaman + paginasi nomor halaman pdf + teks pdf ocr mengenali ekstrak yang dapat dicari + teks logo stempel tanda air pdf + gambar tanda tangan pdf + pdf melindungi kunci enkripsi kata sandi + pdf buka kunci kata sandi dekripsi hapus perlindungan + kompres pdf mengurangi ukuran mengoptimalkan + pdf skala abu-abu hitam putih monokrom + perbaikan perbaikan pdf memulihkan rusak + properti metadata pdf menentukan judul penulis + pdf hapus hapus halaman + margin pemangkasan pangkas pdf + pdf meratakan anotasi membentuk lapisan + pdf ekstrak gambar gambar + kompres arsip zip pdf + pencetak cetak pdf + penampil pratinjau pdf membaca + gambar ke pdf mengkonversi dokumen jpg jpeg png + pdf ke halaman gambar ekspor jpg png + komentar anotasi pdf hapus bersih + Varian Netral + Kesalahan + Jatuhkan Bayangan + Tinggi Gigi + Rentang Gigi Horisontal + Rentang Gigi Vertikal + Tepi Atas + Tepi Kanan + Tepi Bawah + Tepi Kiri + Tepi Robek + Setel ulang statistik penggunaan + Alat terbuka, menyimpan statistik, coretan, data yang disimpan, dan format teratas akan diatur ulang. Pembukaan aplikasi tidak akan berubah. + Audio + Mengajukan + Ekspor profil + Simpan profil saat ini + Hapus profil ekspor + Apakah Anda ingin menghapus profil ekspor \\"%1$s\\"? + Menggambar garis tepi + Tampilkan garis tepi tipis di sekeliling kanvas menggambar dan menghapus + Bergelombang + Elemen UI bulat dengan tepi bergelombang lembut + Sendok + Takik + Sudut membulat diukir ke dalam + Sudut berundak dengan potongan ganda + Penampung + Gunakan {filename} untuk memasukkan nama file asli tanpa ekstensi + Suar + Jumlah dasar + Jumlah dering + jumlah sinar + Lebar cincin + Mendistorsi Perspektif + Tampilan dan Nuansa Jawa + Mencukur + sudut X + dan sudut + Ubah ukuran keluaran + Tetesan Air + Panjang gelombang + Fase + Lulus Tinggi + Masker Warna + krom + Larut + Kelembutan + Masukan + Lensa Buram + Masukan rendah + Masukan tinggi + Keluaran rendah + Keluaran tinggi + Efek Cahaya + Tinggi benjolan + Kelembutan benjolan + Riak + amplitudo X + amplitudo Y + X panjang gelombang + panjang gelombang Y + Buram Adaptif + Generasi Tekstur + Hasilkan berbagai tekstur prosedural dan simpan sebagai gambar + Tipe Tekstur + Bias + Waktu + Bersinar + Penyebaran + Sampel + Koefisien sudut + Koefisien gradien + Tipe kisi + Kekuatan jarak + Skala Y + Ketidakjelasan + Tipe dasar + Faktor turbulensi + Penskalaan + Iterasi + Cincin + Logam yang Disikat + kaustik + Seluler + Papan main dam + fBm + Marmer + Plasma + Selimut + Kayu + acak + Persegi + heksagonal + Bersegi delapan + Segitiga + Berpuncak runcing + Kebisingan VL + Kebisingan SC + Terkini + Belum ada filter yang digunakan baru-baru ini + generator tekstur logam yang disikat caustics seluler kotak-kotak marmer plasma selimut kayu bata kamuflase sel awan retak kain dedaunan sarang lebah es lava nebula kertas karat pasir asap batu medan topografi riak air + Bata + Kamuflase + Sel + Awan + Retakan + Kain + Dedaunan + Sarang madu + Es + Lahar + Nebula + Kertas + Karat + Pasir + Merokok + Batu + Medan + Topografi + Riak Air + Kayu Tingkat Lanjut + Lebar mortar + Ketidakteraturan + Memiringkan + Kekasaran + Ambang batas pertama + Ambang batas kedua + Ambang batas ketiga + Kelembutan tepi + Lebar perbatasan + Cakupan + Detil + Kedalaman + Percabangan + Benang horisontal + Benang vertikal + Bulu halus + Pembuluh darah + Penerangan + Lebar retak + Embun beku + Mengalir + Kerak + Kepadatan awan + Bintang + Kepadatan serat + Kekuatan serat + Noda + Korosi + Mengadu + Serpih + Frekuensi bukit pasir + Sudut angin + riak + Gumpalan + Skala vena + Ketinggian air + Tingkat gunung + Erosi + tingkat salju + Jumlah garis + Ketebalan garis + bayangan + Warna pori-pori + Warna mortir + Warna bata gelap + Warna bata + Sorot warna + Warna gelap + Warna hutan + Warna bumi + Warna pasir + Warna latar belakang + Warna sel + Warna tepi + Warna langit + Warna bayangan + Warna terang + Warna permukaan + Warna variasi + Warna retak + Warna melengkung + Warna pakan + Warna daun gelap + Warna daun + Warna perbatasan + Warna madu + Warna yang dalam + Warna es + Warna beku + Warna kerak + Cuci warna + Warna bersinar + Warna luar angkasa + Warna ungu + Warna biru + Warna dasar + Warna serat + Warna noda + Warna logam + Warna karat gelap + Warna karat + Warna oranye + Warna asap + Warna vena + Warna dataran rendah + Warna air + Warna batu + Warna salju + Warna rendah + Warna tinggi + Warna garis + Warna dangkal + Rumput + Kotoran + Kulit + Konkret + Aspal + Lumut + Api + Aurora + Minyak Licin + Cat air + Aliran Abstrak + Opal + Baja Damaskus + Petir + Beludru + Marmer Tinta + Foil Holografik + Bioluminesensi + Pusaran Kosmik + Lampu Lava + Cakrawala Peristiwa + Mekar Fraktal + Terowongan Berwarna + Gerhana Corona + Penarik Aneh + Mahkota Ferrofluida + Supernova + Iris + Bulu Merak + Cangkang Nautilus + Planet Bercincin + Kepadatan bilah + Panjang bilah + Angin + tambal sulam + gumpalan + kelembaban + kerikil + Kerutan + pori-pori + Agregat + Retak + Ter + Memakai + bintik-bintik + serat + Frekuensi api + Merokok + Intensitas + Pita + Band + Warna-warni + Kegelapan + Mekar + Pigmen + Tepian + Kertas + Difusi + Simetri + Ketajaman + Permainan warna + rasa susu + Lapisan + Lipat + Polandia + Cabang + Arah + Kemilau + Lipatan + Berbulu + Keseimbangan tinta + Spektrum + Kerutan + Difraksi + Lengan + Memutar + Cahaya inti + Gumpalan + Kemiringan cakram + Ukuran cakrawala + Lebar disk + Pelensaan + Kelopak + Keriting + Kerawang + Aspek + Lengkungan + Ukuran bulan + Ukuran Korona + sinar + Cincin berlian + Lobus + Kepadatan orbit + Ketebalan + Sepatu berduri + Panjang paku + Ukuran tubuh + Metalik + Radius guncangan + Lebar cangkang + Dibuang + Ukuran murid + Ukuran iris + Variasi warna + Cahaya tangkapan + Ukuran mata + Kepadatan duri + Ternyata + Kamar + Pembukaan + punggung bukit + Mutiara + Ukuran planet + Kemiringan cincin + Lebar cincin + Suasana + Warna kotoran + Warna rumput gelap + Warna rumput + Warna ujung + Warna tanah gelap + Warna kering + Warna kerikil + Warna kulit + Warna beton + Warna ter + Warna aspal + Warna batu + Warna debu + Warna tanah + Warna lumut gelap + Warna lumut + Warna merah + Warna inti + Warna hijau + Warna cyan + Warna magenta + Warna emas + Warna kertas + Warna pigmen + Warna sekunder + Warna pertama + Warna kedua + Warna merah jambu + Warna baja gelap + Warna baja + Warna baja ringan + Warna oksida + Warna halo + Warna baut + Warna beludru + Warna kemilau + Warna tinta biru + Warna tinta merah + Warna tinta gelap + Warna perak + Warna kuning + Warna tisu + Warna disk + Warna panas + Warna lensa + Warna luar + Warna batin + Warna Korona + Warna dingin + Warna hangat + Warna awan + Warna api + Warna bulu + Warna cangkang + Warna mutiara + Warna planet + Warna cincin + Hapus file asli setelah disimpan + Hanya file sumber yang berhasil diproses yang akan dihapus + File asli dihapus: %1$d + Gagal menghapus file asli: %1$d + File asli dihapus: %1$d, gagal: %2$d + Gerakan lembar + Izinkan menyeret lembar opsi yang diperluas + Subsampling kroma + Kedalaman sedikit + Tanpa kerugian + %1$d-sedikit + Ganti Nama Batch + Ganti nama banyak file menggunakan pola nama file + batch mengganti nama file, pola nama file, urutan tanggal ekstensi + Pilih file untuk diganti namanya + Pola nama file + Ganti nama + Sumber tanggal + Tanggal EXIF ​​diambil + Tanggal modifikasi file + Tanggal pembuatan file + Tanggal saat ini + Tanggal petunjuk + Tanggal petunjuk + Waktu manual + Tanggal yang dipilih tidak tersedia untuk beberapa file. Tanggal saat ini akan digunakan untuk mereka. + Masukkan pola nama file + Pola ini menghasilkan nama file yang tidak valid atau terlalu panjang + Pola ini menghasilkan nama file duplikat + Semua nama file sudah sesuai dengan polanya + Pola ini menggunakan token yang tidak tersedia untuk penggantian nama batch + Nama akan tetap sama + File berhasil diganti namanya + Beberapa file tidak dapat diganti namanya + Izin tidak diberikan + Menggunakan nama file asli tanpa ekstensi, membantu Anda menjaga identifikasi sumber tetap utuh. Juga mendukung pemotongan dengan \\o{start:end}, transliterasi dengan \\o{t} dan mengganti dengan \\o{s/old/new/}. + Penghitung kenaikan otomatis. Mendukung pemformatan khusus: \\c{padding} (misalnya \\c{3} -&gt; 001), \\c{start:step} atau \\c{start:step:padding}. + Nama folder induk tempat file asli berada. + Ukuran file aslinya. Mendukung unit: \\z{b}, \\z{kb}, \\z{mb} atau hanya \\z untuk format yang dapat dibaca manusia. + Menghasilkan Pengidentifikasi Unik Universal (UUID) acak untuk memastikan keunikan nama file. + Penggantian nama file tidak dapat dibatalkan. Pastikan nama baru sudah benar sebelum melanjutkan. + Konfirmasikan penggantian nama + Pengaturan menu samping + Skala menu + Menu alfa + Keseimbangan Putih Otomatis + Guntingan + Memeriksa tanda air yang tersembunyi + Penyimpanan + Batas cache + Hapus cache secara otomatis ketika ukurannya melebihi ukuran ini + Interval pembersihan + Seberapa sering memeriksa apakah cache harus dibersihkan + Saat peluncuran aplikasi + 1 hari + 1 minggu + 1 bulan + Hapus cache aplikasi secara otomatis sesuai batas dan interval yang dipilih + Area tanaman yang dapat dipindahkan + Memungkinkan memindahkan seluruh area pemotongan dengan menyeret ke dalamnya + Gabungkan GIF + Gabungkan beberapa klip GIF menjadi satu animasi + Urutan klip GIF + Balik + Mainkan secara terbalik + Bumerang + Mainkan maju lalu mundur + Normalisasikan ukuran bingkai + Skala bingkai agar sesuai dengan klip terbesar; matikan untuk mempertahankan skala aslinya + Penundaan antar klip (ms) + Map + Pilih semua gambar dari folder dan subfoldernya + Pencari Duplikat + Temukan salinan persisnya dan gambar yang mirip secara visual + pencari duplikat gambar serupa salinan persis foto pembersihan penyimpanan dHash SHA-256 + Pilih gambar untuk menemukan duplikat + Sensitivitas kesamaan + Salinan persisnya + Gambar serupa + Pilih semua duplikat yang tepat + Pilih semua kecuali direkomendasikan + Tidak ada duplikat yang ditemukan + Coba tambahkan lebih banyak gambar atau tingkatkan sensitivitas kesamaan + Tidak dapat membaca %1$d gambar + %1$s • %2$s dapat diklaim kembali + %1$d kelompok • %2$s dapat diklaim kembali + %1$d dipilih • %2$s + Hal ini tidak dapat dibatalkan. Android mungkin meminta Anda untuk mengonfirmasi penghapusan dalam dialog sistem. + Tidak Ditemukan + Pindah untuk memulai + Pindah ke akhir + \ No newline at end of file diff --git a/core/resources/src/main/res/values-it/strings.xml b/core/resources/src/main/res/values-it/strings.xml new file mode 100644 index 0000000..1798084 --- /dev/null +++ b/core/resources/src/main/res/values-it/strings.xml @@ -0,0 +1,3739 @@ + + + + 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 + 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 + 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. + 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. + 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 + Distorsione consapevole del contenuto + Memoria insufficiente per completare questa azione. Prova a utilizzare un\'immagine più piccola, a chiudere altre app o a riavviare l\'app. + Lavoratori paralleli + Queste immagini non sono state salvate perché i file convertiti sarebbero più grandi degli originali. Controlla questo elenco prima di eliminare le immagini originali. + File saltati: %1$s + Registri dell\'app + Visualizza i log dell\'app per individuare i problemi + Preferiti negli strumenti raggruppati + Aggiunge i preferiti come scheda quando gli strumenti vengono raggruppati per tipo + Mostra preferito come ultimo + Sposta la scheda degli strumenti preferiti alla fine + Spaziatura orizzontale + Spaziatura verticale + Energia all\'indietro + Utilizzare una semplice mappa energetica della magnitudo del gradiente invece dell\'algoritmo dell\'energia diretta + Rimuovi l\'area mascherata + Le cuciture passeranno attraverso la regione mascherata, rimuovendo solo l\'area selezionata invece di proteggerla + VHSNTSC + Impostazioni NTSC avanzate + Sintonizzazione analogica extra per VHS più potenti e artefatti di trasmissione + Spurgo cromatico + Usura del nastro + Monitoraggio + Striscio di luminanza + Squillo + Nevicare + Usa il campo + Tipo di filtro + Filtro luminanza in ingresso + Ingresso passa-basso della crominanza + Demodulazione della crominanza + Sfasamento + Sfasamento + Cambio di testa + Altezza di cambio testa + Offset cambio testa + Cambio di testa + Posizione sulla linea mediana + Jitter a metà linea + Monitoraggio dell\'altezza del rumore + Monitoraggio dell\'onda + Monitoraggio della neve + Monitoraggio dell\'anisotropia della neve + Tracciamento del rumore + Monitoraggio dell\'intensità del rumore + Rumore composito + Frequenza del rumore composito + Intensità del rumore composito + Dettaglio del rumore composito + Frequenza di chiamata + Potenza di chiamata + Rumore luminoso + Frequenza del rumore luminanza + Intensità del rumore luminanza + Dettaglio del rumore luminanza + Rumore cromatico + Frequenza del rumore cromatico + Intensità del rumore cromatico + Dettaglio del rumore cromatico + Intensità della neve + Anisotropia della neve + Rumore di fase cromatica + Errore di fase cromatica + Ritardo cromatico orizzontale + Ritardo cromatico verticale + Velocità del nastro VHS + Perdita di crominanza VHS + VHS aumenta l\'intensità + Frequenza di affilatura VHS + Intensità dell\'onda del bordo VHS + Velocità dell\'onda del bordo VHS + Frequenza dell\'onda del bordo VHS + Dettaglio dell\'onda del bordo VHS + Passa-basso della crominanza in uscita + Scala orizzontale + Scala verticale + Fattore di scala X + Fattore di scala Y + Rileva filigrane di immagini comuni e le colora con LaMa. Scarica automaticamente i modelli di rilevamento e repainting + Deuteranopia + Espandi immagine + Dispositivo di rimozione dello sfondo basato sulla segmentazione degli oggetti utilizzando la segmentazione YOLO v11 + Mostra l\'angolo della linea + Mostra la rotazione corrente della linea in gradi durante il disegno + Emoji animate + Mostra le emoji disponibili come animazioni + Salva nella cartella originale + Salva i nuovi file accanto al file originale anziché nella cartella selezionata + PDF con filigrana + Memoria insufficiente + Probabilmente l\'immagine è troppo grande per essere elaborata su questo dispositivo oppure il sistema ha esaurito la memoria disponibile. Prova a ridurre la risoluzione dell\'immagine, a chiudere altre app o a scegliere un file più piccolo. + Menù Debug + Menu per testare le funzioni dell\'app, non è previsto che venga visualizzato nella versione di produzione + Fornitore + PaddleOCR richiede modelli ONNX aggiuntivi sul tuo dispositivo. Vuoi scaricare i dati %1$s? + Universale + coreano + latino + Slavo orientale + tailandese + greco + Inglese + cirillico + arabo + Devanagari + Tamil + Telugu + Shader + Preimpostazione dello shader + Nessuno shader selezionato + Studio Shader + Crea, modifica, convalida, importa ed esporta shader di frammenti personalizzati + Shader salvati + Apri shader salvati, duplica, esporta, condividi o elimina + Nuovo shader + File di ombreggiatura + .itshader JSON + %1$d param + Sorgente dello shader + Scrivi solo il corpo di void main(). Utilizza textureCoordinate per le coordinate UV e inputImageTexture come campionatore di origine. + Funzioni + Funzioni helper, costanti e strutture facoltative inserite prima di void main(). Le uniformi vengono generate da params. + Aggiungi qui le uniformi quando lo shader necessita di valori modificabili. + Reimposta lo shader + La bozza dello shader corrente verrà cancellata + Shader non valido + Versione shader non supportata %1$d. Versione supportata: %2$d. + Il nome dello shader non deve essere vuoto. + La sorgente dello shader non deve essere vuota. + L\'origine dello shader contiene caratteri non supportati: %1$s. Utilizzare solo l\'origine ASCII GLSL. + Il parametro \\\"%1$s\\\" è dichiarato più di una volta. + I nomi dei parametri non devono essere vuoti. + Il parametro \\\"%1$s\\\" utilizza un nome GPUImage riservato. + Il parametro \\\"%1$s\\\" deve essere un identificatore GLSL valido. + Il parametro \\\"%1$s\\\" ha %2$s tipo di valore \\\"%3$s\\\", previsto \\\"%4$s\\\". + Il parametro \\\"%1$s\\\" non può definire il minimo o il massimo per i valori bool. + Il parametro \\\"%1$s\\\" ha min maggiore di max. + Il valore predefinito del parametro \\\"%1$s\\\" è inferiore al valore minimo. + Il valore predefinito del parametro \\\"%1$s\\\" è maggiore del massimo. + Lo shader deve dichiarare \\\"uniform sampler2D %1$s;\\\". + Lo shader deve dichiarare \\\"varying vec2 %1$s;\\\". + Lo shader deve definire \\\"void main()\\\". + Lo shader deve scrivere un colore in gl_FragColor. + Gli shader ShaderToy mainImage non sono supportati. Utilizza il contratto void main() di GPUImage. + L\'uniforme animata ShaderToy \\\"iTime\\\" non è supportata. + L\'uniforme animata ShaderToy \\\"iFrame\\\" non è supportata. + L\'uniforme ShaderToy \\\"iResolution\\\" non è supportata. + Le texture ShaderToy iChannel non sono supportate. + Le texture esterne non sono supportate. + Le estensioni delle texture esterne non sono supportate. + I parametri dello shader Libretro non sono supportati. + È supportata solo una texture di input. Rimuovi \\\"uniforme %1$s %2$s;\\\". + Il parametro \\\"%1$s\\\" deve essere dichiarato come \\\"uniform %2$s %1$s;\\\". + Il parametro \\\"%1$s\\\" è dichiarato come \\\"uniform %2$s %1$s;\\\", previsto \\\"uniform %3$s %1$s;\\\". + Il file dello shader è vuoto. + Il file shader deve essere un oggetto JSON .itshader con campi versione, nome e shader. + Il file shader non è un JSON valido o non corrisponde al formato .itshader. + Il campo \\\"%1$s\\\" è obbligatorio. + %1$s è obbligatorio. + %1$s \\\"%2$s\\\" non è supportato. Tipi supportati: %3$s. + %1$s è obbligatorio per i parametri \\\"%2$s\\\". + %1$s deve essere un numero finito. + %1$s deve essere un numero intero. + %1$s deve essere vero o falso. + %1$s deve essere una stringa di colori, un array RGB/RGBA o un oggetto colore. + %1$s deve utilizzare il formato #RRGGBB o #RRGGBBAA. + %1$s deve contenere canali di colore esadecimali validi. + %1$s deve contenere 3 o 4 canali di colore. + %1$s deve essere compreso tra 0 e 255. + %1$s deve essere un array di due numeri o un oggetto con x e y. + %1$s deve contenere esattamente 2 numeri. + Duplicato + Cancella sempre EXIF + Rimuovi i dati EXIF ​​dell\'immagine al salvataggio, anche quando uno strumento richiede di conservare i metadati + Aiuto e suggerimenti + %1$d tutorial + Strumento aperto + Scopri gli strumenti principali, le opzioni di esportazione, i flussi PDF, le utilità del colore e le soluzioni per i problemi più comuni + Iniziare + Scegli strumenti, importa file, salva risultati e riutilizza le impostazioni più velocemente + Modifica delle immagini + Ridimensiona, ritaglia, filtra, cancella sfondi, disegna e filigrana le immagini + File e metadati + Converti formati, confronta l\'output, proteggi la privacy e controlla i nomi dei file + PDF e documenti + Crea PDF, scansiona documenti, pagine OCR e prepara file per la condivisione + Testo, QR e dati + Riconoscere testo, scansionare codici, codificare Base64, controllare hash e pacchettizzare file + Strumenti per il colore + Scegli i colori, crea tavolozze, esplora librerie di colori e crea sfumature + Strumenti creativi + Genera SVG, arte ASCII, texture di rumore, shader e immagini sperimentali + Risoluzione dei problemi + Risolvi problemi legati a file pesanti, trasparenza, condivisione, importazione e reporting + Scegli lo strumento giusto + Utilizza le categorie, la ricerca, i preferiti e le azioni di condivisione per iniziare nel posto giusto + Inizia dal miglior punto di ingresso + Image Toolbox ha molti strumenti mirati e molti di essi si sovrappongono a prima vista. Inizia dall\'attività che desideri completare, quindi scegli lo strumento che controlla la parte più importante del risultato: dimensioni, formato, metadati, testo, pagine PDF, colori o layout. + Utilizza la ricerca quando conosci il nome dell\'azione, ad esempio ridimensiona, PDF, EXIF, OCR, QR o colore. + Apri una categoria quando conosci solo il tipo di attività, quindi aggiungi gli strumenti frequenti come preferiti. + Quando un\'altra app condivide un\'immagine in Image Toolbox, seleziona lo strumento dal flusso del foglio di condivisione. + Importa, salva e condividi i risultati + Comprendere il normale flusso dalla raccolta dell\'input all\'esportazione del file modificato + Segui il flusso di modifica comune + La maggior parte degli strumenti segue lo stesso ritmo: scegli l\'input, regola le opzioni, visualizza l\'anteprima, quindi salva o condividi. Il dettaglio importante è che il salvataggio crea un file di output, mentre la condivisione è solitamente la soluzione migliore per un trasferimento rapido a un\'altra app. + Scegli un file per gli strumenti a immagine singola o più file per gli strumenti batch quando il selettore lo consente. + Controlla i controlli di anteprima e di output prima di salvare, in particolare le opzioni di formato, qualità e nome file. + Utilizza la condivisione per un trasferimento rapido o salva quando hai bisogno di una copia persistente nella cartella selezionata. + Lavora con più immagini contemporaneamente + Gli strumenti batch sono ideali per attività ripetute di ridimensionamento, conversione, compressione e denominazione + Preparare un lotto prevedibile + L\'elaborazione batch è più veloce quando ogni output deve seguire le stesse regole. È anche il luogo in cui è più facile commettere un grosso errore, quindi scegli la denominazione, la qualità, i metadati e il comportamento della cartella prima di avviare una lunga esportazione. + Seleziona insieme tutte le immagini correlate e mantieni l\'ordine se l\'output dipende dalla sequenza. + Imposta le regole di ridimensionamento, formato, qualità, metadati e nome file una volta prima di avviare l\'esportazione. + Esegui prima un piccolo batch di prova quando i file di origine sono di grandi dimensioni o quando il formato di destinazione è nuovo per te. + Modifica singola rapida + Utilizza l\'editor tutto in uno quando hai bisogno di diverse semplici modifiche su un\'immagine + Termina un\'immagine senza cambiare strumento + La modifica singola è utile quando l\'immagine necessita di una piccola catena di modifiche anziché di un\'operazione specializzata. Di solito è più veloce per una rapida pulizia, anteprima ed esportazione di una copia finale senza creare un flusso di lavoro batch. + Apri Modifica singola per un\'immagine che necessita di modifiche comuni, markup, ritaglio, rotazione o esportazione. + Applica le modifiche nell\'ordine in cui cambia prima il minor numero di pixel, ad esempio ritaglia prima dei filtri e filtri prima della compressione finale. + Utilizza invece uno strumento specializzato quando hai bisogno di elaborazione batch, target esatti per le dimensioni dei file, output PDF, OCR o pulizia dei metadati. + Utilizza preimpostazioni e impostazioni + Salva le scelte ripetute e ottimizza l\'app per il tuo flusso di lavoro preferito + Evitare di ripetere la stessa configurazione + Le preimpostazioni e le impostazioni sono utili quando si esporta spesso lo stesso tipo di file. Una buona preimpostazione trasforma una lista di controllo manuale ripetuta in un solo tocco, mentre le impostazioni globali definiscono le impostazioni predefinite con cui iniziano i nuovi strumenti. + Crea preimpostazioni per dimensioni di output, formati, valori di qualità o modelli di denominazione comuni. + Controlla le impostazioni per formato predefinito, qualità, cartella di salvataggio, nome file, selettore e comportamento dell\'interfaccia. + Esegui il backup delle impostazioni prima di reinstallare l\'app o passare a un altro dispositivo. + Imposta impostazioni predefinite utili + Scegli una volta il formato predefinito, la qualità, la modalità scala, il tipo di ridimensionamento e lo spazio colore + Fai in modo che i nuovi strumenti inizino più vicino al tuo obiettivo + I valori predefiniti non sono solo preferenze estetiche. Decidono quale formato, qualità, comportamento di ridimensionamento, modalità di scala e spazio colore vengono visualizzati per primi in molti strumenti, il che aiuta a evitare di riselezionare le stesse scelte a ogni esportazione. + Imposta il formato e la qualità dell\'immagine predefiniti in modo che corrispondano alla tua destinazione abituale, ad esempio JPEG per le foto o PNG/WebP per alpha. + Ottimizza il tipo di ridimensionamento e la modalità di scala predefiniti se esporti spesso per dimensioni rigide, piattaforme social o pagine di documenti. + Modifica le impostazioni predefinite dello spazio colore solo quando il tuo flusso di lavoro richiede una gestione del colore coerente su display, stampa o editor esterni. + Selettore e lanciatore + Utilizza le impostazioni del selettore quando l\'app apre troppe finestre di dialogo o si avvia nel punto sbagliato + Fai in modo che l\'apertura dei file corrisponda alle tue abitudini + Le impostazioni del selettore e del programma di avvio controllano se Image Toolbox si avvia dalla schermata principale, da uno strumento o da un flusso di selezione file. Queste opzioni sono particolarmente utili quando di solito accedi dal foglio di condivisione di Android o quando desideri che l\'app sembri più un launcher di strumenti. + Utilizza le impostazioni del selettore foto se il selettore di sistema nasconde file, mostra troppi elementi recenti o gestisce in modo inadeguato i file cloud. + Abilita la selezione salta solo per gli strumenti in cui di solito accedi dalla condivisione o conosci già il file di origine. + Controlla la modalità di avvio se desideri che Image Toolbox si comporti più come un selettore di strumenti che come una normale schermata iniziale. + Fonte dell\'immagine + Scegli la modalità di selezione che funziona meglio con gallerie, file manager e file cloud + Scegli i file dalla fonte giusta + Le impostazioni dell\'origine immagine influiscono sul modo in cui l\'app richiede le immagini ad Android. La scelta migliore dipende dal fatto che i tuoi file si trovino nella galleria di sistema, in una cartella di gestione file, in un provider cloud o in un\'altra app che condivide contenuti temporanei. + Utilizza il selettore predefinito quando desideri l\'esperienza più integrata nel sistema per le immagini della galleria comune. + Cambia la modalità di selezione se album, cartelle, file cloud o formati non standard non vengono visualizzati dove previsto. + Se un file condiviso scompare dopo aver lasciato l\'app di origine, riaprilo tramite un selettore permanente o salva prima una copia locale. + Preferiti e strumenti di condivisione + Mantieni focalizzata la schermata principale e nascondi gli strumenti che non hanno senso nei flussi di condivisione + Riduci il rumore dell\'elenco degli strumenti + I preferiti e le impostazioni nascoste per la condivisione sono utili quando utilizzi solo pochi strumenti ogni giorno. Inoltre, mantengono pratico il flusso di condivisione nascondendo strumenti che non possono utilizzare il tipo di file inviato da un\'altra app. + Appunta gli strumenti frequenti in modo che siano facili da raggiungere anche quando gli strumenti sono raggruppati per categoria. + Nascondi gli strumenti dai flussi di condivisione quando sono irrilevanti per i file provenienti da un\'altra app. + Utilizza le impostazioni di ordinamento dei preferiti se preferisci i preferiti alla fine o raggruppati con gli strumenti correlati. + Eseguire il backup delle impostazioni + Sposta le preimpostazioni, le preferenze e il comportamento dell\'app in un\'altra installazione in modo sicuro + Mantieni la tua configurazione portatile + Il backup e il ripristino sono molto utili dopo aver ottimizzato le preimpostazioni, le cartelle, le regole sui nomi dei file, l\'ordine degli strumenti e le preferenze visive. Un backup ti consente di sperimentare le impostazioni o reinstallare l\'app senza ricostruire il flusso di lavoro dalla memoria. + Crea un backup dopo aver modificato le preimpostazioni, il comportamento dei nomi dei file, i formati predefiniti o la disposizione degli strumenti. + Archivia il backup in un posto esterno alla cartella dell\'app se prevedi di cancellare dati, reinstallare o spostare dispositivi. + Ripristina le impostazioni prima di elaborare batch importanti in modo che le impostazioni predefinite e il comportamento di salvataggio corrispondano alla vecchia configurazione. + Ridimensiona e converti le immagini + Modifica dimensioni, formato, qualità e metadati per una o più immagini + Crea copie ridimensionate + Ridimensiona e converti è lo strumento principale per dimensioni prevedibili e file di output più leggeri. Usalo quando la dimensione dell\'immagine, il formato di output e il comportamento dei metadati sono tutti importanti allo stesso tempo. + Scegli una o più immagini, quindi scegli se sono più importanti le dimensioni, la scala o la dimensione del file. + Seleziona il formato e la qualità di output; utilizzare PNG o WebP quando è necessario preservare la trasparenza. + Visualizza l\'anteprima del risultato, quindi salva o condividi le copie generate senza modificare gli originali. + Ridimensiona in base alla dimensione del file + Scegli come target un limite di dimensione quando un sito web, un messenger o un modulo rifiutano immagini pesanti + Rispetta limiti di caricamento rigorosi + Ridimensionare in base alla dimensione del file è meglio che indovinare i valori di qualità quando la destinazione accetta solo file al di sotto di un limite specifico. Lo strumento può regolare la compressione e le dimensioni per raggiungere l\'obiettivo cercando di mantenere il risultato utilizzabile. + Inserisci la dimensione target leggermente al di sotto del limite di caricamento reale per lasciare spazio ai metadati e alle modifiche del provider. + Preferire formati con perdita di dati per le foto quando il target è piccolo; Il PNG può rimanere grande anche dopo il ridimensionamento. + Ispeziona facce, testo e bordi dopo l\'esportazione perché target di dimensioni aggressive possono introdurre artefatti visibili. + Limita ridimensionamento + Riduci solo le immagini che superano i limiti massimi di larghezza, altezza o file + Evita di ridimensionare le immagini che vanno già bene + Limit Resize è utile per cartelle miste in cui alcune immagini sono enormi e altre sono già preparate. Invece di forzare ogni file allo stesso ridimensionamento, mantiene le immagini accettabili più vicine alla loro qualità originale. + Imposta dimensioni o limiti massimi in base alla destinazione anziché ridimensionare ogni file alla cieca. + Abilita il comportamento dello stile salta se più grande quando vuoi evitare che gli output diventino più pesanti delle sorgenti. + Utilizzalo per batch di diverse fotocamere, screenshot o download in cui le dimensioni della sorgente variano molto. + Ritaglia, ruota e raddrizza + Inquadra l\'area importante prima di esportare o utilizzare l\'immagine in altri strumenti + Pulisci la composizione + Il primo ritaglio rende i filtri, l\'OCR, le pagine PDF e la condivisione dei risultati successivi più puliti. + Apri Ritaglia e scegli l\'immagine che desideri regolare. + Utilizza il ritaglio libero o le proporzioni quando l\'output deve adattarsi a un post, un documento o un avatar. + Ruota o capovolgi prima di salvare in modo che gli strumenti successivi ricevano l\'immagine corretta. + Applicare filtri e regolazioni + Crea uno stack di filtri modificabile e visualizza in anteprima il risultato prima dell\'esportazione + Ottimizza l\'aspetto dell\'immagine + I filtri sono utili per correzioni rapide, output stilizzato e preparazione di immagini per l\'OCR o la stampa. Piccole modifiche al contrasto, alla nitidezza e alla luminosità possono avere più importanza degli effetti pesanti quando l\'immagine contiene testo o dettagli fini. + Aggiungi i filtri uno per uno e tieni d\'occhio l\'anteprima dopo ogni modifica. + Regola luminosità, contrasto, nitidezza, sfocatura, colore o maschere a seconda dell\'attività. + Esporta solo quando l\'anteprima corrisponde al dispositivo, al documento o alla piattaforma social di destinazione. + Prima l\'anteprima + Esamina le immagini prima di scegliere uno strumento di modifica, conversione o pulizia più pesante + Controlla cosa hai effettivamente ricevuto + L\'anteprima immagine è utile quando un file proviene da chat, archivio cloud o un\'altra app e non sei sicuro di cosa contenga. Controllare innanzitutto le dimensioni, la trasparenza, l\'orientamento e la qualità visiva aiuta a scegliere lo strumento di follow-up corretto. + Apri Anteprima immagine quando devi ispezionare diverse immagini prima di decidere cosa farne. + Cerca problemi di rotazione, aree trasparenti, artefatti di compressione e dimensioni inaspettatamente grandi. + Passa a Ridimensiona, Ritaglia, Confronta, EXIF ​​o Rimozione sfondo solo dopo aver saputo cosa è necessario correggere. + Rimuovere o ripristinare uno sfondo + Cancella automaticamente gli sfondi o perfeziona i bordi manualmente con gli strumenti di ripristino + Mantieni l\'argomento, togli il resto + La rimozione dello sfondo funziona meglio quando si combina la selezione automatica con un\'attenta pulizia manuale. Il formato di esportazione finale è importante quanto la maschera, poiché JPEG appiattirà le aree trasparenti anche se l\'anteprima sembra corretta. + Inizia con la rimozione automatica se il soggetto è chiaramente separato dallo sfondo. + Ingrandisci e alterna tra Cancella e Ripristina per correggere capelli, angoli, ombre e piccoli dettagli. + Salva come PNG o WebP quando hai bisogno di trasparenza nel file finale. + Disegna, annota e filigrana + Aggiungi frecce, note, evidenziazioni, firme o sovrapposizioni di filigrane ripetute + Aggiungi informazioni visibili + Gli strumenti di marcatura aiutano a spiegare un\'immagine, mentre la filigrana aiuta a marchiare o proteggere i file esportati. + Usa Draw quando hai bisogno di note a mano libera, forme, evidenziazioni o annotazioni rapide. + Utilizzare la filigrana quando lo stesso testo o marchio immagine deve apparire in modo coerente sulle stampe. + Controlla l\'opacità, la posizione e la scala prima dell\'esportazione in modo che il segno sia visibile ma non distragga. + Disegna i valori predefiniti + Imposta la larghezza della linea, il colore, la modalità del percorso, il blocco dell\'orientamento e la lente d\'ingrandimento per una marcatura più rapida + Rendi il disegno prevedibile + Le impostazioni di disegno sono utili quando annoti screenshot, contrassegni documenti o firmi spesso immagini. Le impostazioni predefinite riducono i tempi di configurazione, mentre la lente d\'ingrandimento e il blocco dell\'orientamento semplificano le modifiche precise su schermi di piccole dimensioni. + Imposta la larghezza della linea predefinita e il colore del disegno sui valori che utilizzi maggiormente per frecce, evidenziazioni o firme. + Utilizza la modalità percorso predefinita quando di solito disegni lo stesso tipo di tratti, forme o markup. + Abilita la lente d\'ingrandimento o il blocco dell\'orientamento quando l\'input con il dito rende difficile posizionare con precisione i piccoli dettagli. + Layout multi-immagine + Scegli lo strumento multi-immagine giusto prima di organizzare screenshot, scansioni o strisce di confronto + Usa lo strumento di layout giusto + Questi strumenti sembrano simili, ma ognuno è ottimizzato per un diverso tipo di output multi-immagine. Scegliere prima quello giusto evita di combattere i controlli di layout progettati per un risultato diverso. + Utilizza Collage Maker per layout progettati con più immagini in un\'unica composizione. + Utilizzare l\'unione o l\'impilamento delle immagini quando le immagini devono diventare una lunga striscia verticale o orizzontale. + Utilizzare la divisione delle immagini quando è necessario dividere un\'immagine di grandi dimensioni in più parti più piccole. + Converti formati di immagine + Crea copie JPEG, PNG, WebP, AVIF, JXL e altre copie senza modificare manualmente i pixel + Scegli il formato per il lavoro + Formati diversi sono migliori per foto, screenshot, trasparenza, compressione o compatibilità. La conversione è più sicura quando capisci cosa supporta l\'app di destinazione e se la fonte contiene alfa, animazioni o metadati che desideri conservare. + Utilizza JPEG per foto compatibili, PNG per immagini senza perdita di dati e alpha e WebP per la condivisione compatta. + Regola la qualità quando il formato la supporta; valori più bassi riducono le dimensioni ma possono aggiungere artefatti. + Conserva gli originali finché non verifichi che i file convertiti si aprano correttamente nell\'app di destinazione. + Controlla EXIF ​​e privacy + Visualizza, modifica o rimuovi i metadati prima di condividere foto sensibili + Controlla i dati dell\'immagine nascosta + EXIF può contenere dati della fotocamera, date, posizione, orientamento e altri dettagli non visibili nell\'immagine. Vale la pena controllare prima di condividere pubblicamente, rapporti di supporto, elenchi di marketplace o qualsiasi foto che possa rivelare un luogo privato. + Apri gli strumenti EXIF ​​prima di condividere foto che potrebbero contenere informazioni sulla posizione o sul dispositivo privato. + Rimuovi i campi sensibili o modifica i tag errati come data, orientamento, autore o dati della fotocamera. + Salva una nuova copia e condividi quella copia al posto dell\'originale quando la privacy è importante. + Pulizia EXIF ​​automatica + Rimuovi i metadati per impostazione predefinita mentre decidi se conservare le date + Rendi la privacy l\'impostazione predefinita + Il gruppo di impostazioni EXIF ​​è utile quando desideri che la pulizia della privacy avvenga automaticamente invece di ricordarla per ogni esportazione. Mantenere la data e l\'ora è una decisione separata perché le date possono essere utili per l\'ordinamento ma sensibili per la condivisione. + Abilita EXIF ​​sempre chiaro quando la maggior parte delle tue esportazioni sono destinate alla condivisione pubblica o semi-pubblica. + Abilita Mantieni data e ora solo quando preservare l\'ora di acquisizione è più importante che rimuovere ogni timestamp. + Utilizza la modifica EXIF ​​manuale per file singoli in cui è necessario mantenere alcuni campi e rimuoverne altri. + Controlla i nomi dei file + Utilizza prefissi, suffissi, timestamp, preimpostazioni, checksum e numeri di sequenza + Rendi le esportazioni facili da trovare + Un buon modello di nome file aiuta a ordinare i batch e impedisce sovrascritture accidentali. Le impostazioni dei nomi file sono particolarmente utili quando le esportazioni tornano alla cartella di origine, dove nomi simili possono creare rapidamente confusione. + Imposta il prefisso del nome file, il suffisso, il timestamp, il nome originale, le informazioni preimpostate o le opzioni della sequenza in Impostazioni. + Utilizza i numeri di sequenza per i batch in cui l\'ordine è importante, come pagine, cornici o collage. + Abilita la sovrascrittura solo quando sei sicuro che la sostituzione dei file esistenti sia sicura per la cartella corrente. + Sovrascrivi i file + Sostituisci gli output con nomi corrispondenti solo quando il flusso di lavoro è intenzionalmente distruttivo + Utilizzare la modalità di sovrascrittura con attenzione + Sovrascrivi file modifica la modalità di gestione dei conflitti di denominazione. È utile per ripetere le esportazioni nella stessa cartella, ma può anche sostituire i risultati precedenti se il modello del nome file produce nuovamente lo stesso nome. + Abilita la sovrascrittura solo per le cartelle in cui è prevista e recuperabile la sostituzione dei file generati più vecchi. + Mantieni la sovrascrittura disabilitata durante l\'esportazione di originali, file client, scansioni singole o qualsiasi cosa senza un backup. + Combina la sovrascrittura con uno schema di nome file deliberato in modo che vengano sostituiti solo gli output previsti. + Modelli di nomi di file + Combina nomi originali, prefissi, suffissi, timestamp, preimpostazioni, modalità scala e checksum + Costruisci nomi che spieghino l\'output + Le impostazioni del modello di nome file possono trasformare i file esportati in un\'utile cronologia di ciò che è accaduto loro. Ciò è importante nei batch perché la visualizzazione della cartella potrebbe essere l\'unico posto in cui è possibile distinguere la dimensione originale, la preimpostazione, il formato e l\'ordine di elaborazione. + Utilizza il nome file, il prefisso, il suffisso e il timestamp originali quando hai bisogno di nomi leggibili per l\'ordinamento manuale. + Aggiungi informazioni su preimpostazione, modalità scala, dimensione file o checksum quando gli output devono documentare come sono stati prodotti. + Evita nomi casuali per flussi di lavoro in cui i file devono rimanere associati agli originali o all\'ordine delle pagine. + Scegli dove salvare i file + Evita di perdere le esportazioni impostando cartelle, salvataggio nella cartella originale e posizioni di salvataggio una tantum + Mantenere i risultati prevedibili + Il comportamento di salvataggio è più importante quando si elaborano molti file o si condividono file da provider cloud. Le impostazioni della cartella determinano se gli output vengono raccolti in un luogo noto, visualizzati accanto agli originali o vanno temporaneamente altrove per un\'attività specifica. + Imposta una cartella di salvataggio predefinita se desideri esportare sempre in un unico posto. + Utilizza il salvataggio nella cartella originale per i flussi di lavoro di pulizia in cui l\'output deve rimanere vicino al file di origine. + Utilizza la posizione di salvataggio una tantum quando una singola attività richiede una destinazione diversa senza modificare l\'impostazione predefinita. + Cartelle di salvataggio una tantum + Invia le prossime esportazioni a una cartella temporanea senza modificare la destinazione normale + Mantieni separati i lavori speciali + La posizione di salvataggio una tantum è utile per progetti temporanei, cartelle client, sessioni di pulizia o esportazioni che non devono mescolarsi con la normale cartella Image Toolbox. Fornisce una destinazione di breve durata senza riscrivere la configurazione predefinita. + Scegli una cartella monouso prima di avviare un\'attività che non appartiene alla normale posizione di esportazione. + Usalo per progetti brevi, cartelle condivise o batch in cui gli output devono rimanere raggruppati insieme. + Ritorna alla cartella predefinita dopo il lavoro in modo che le esportazioni successive non arrivino inaspettatamente nella posizione temporanea. + Bilancia qualità e dimensione del file + Comprendi quando modificare dimensioni, formato o qualità invece di tirare a indovinare + Riduci i file intenzionalmente + La dimensione del file dipende dalle dimensioni dell\'immagine, dal formato, dalla qualità, dalla trasparenza e dalla complessità del contenuto. Se un file è ancora troppo pesante, la modifica delle dimensioni di solito aiuta di più che abbassare ripetutamente la qualità. + Ridimensionare prima le dimensioni quando l\'immagine è più grande di quanto possa essere visualizzata dalla destinazione. + Abbassa la qualità solo per i formati con perdita e controlla testo, bordi, sfumature e facce dopo l\'esportazione. + Cambia formato quando le modifiche alla qualità non sono sufficienti, ad esempio WebP per la condivisione o PNG per risorse trasparenti e nitide. + Lavora con formati animati + Utilizza gli strumenti GIF, APNG, WebP e JXL quando un file contiene fotogrammi anziché una bitmap + Non considerare ogni immagine come ferma + I formati animati necessitano di strumenti in grado di comprendere fotogrammi, durata e compatibilità. + Utilizza gli strumenti GIF o APNG quando sono necessarie operazioni a livello di fotogramma per tali formati. + Utilizza gli strumenti WebP quando hai bisogno di una compressione moderna o di un output WebP animato. + Visualizza in anteprima i tempi dell\'animazione prima della condivisione perché alcune piattaforme convertono o appiattiscono le immagini animate. + Confronta e visualizza in anteprima l\'output + Controlla le modifiche, le dimensioni del file e le differenze visive prima di conservare un\'esportazione + Verifica prima di condividere + Gli strumenti di confronto aiutano a individuare tempestivamente artefatti di compressione, colori errati o modifiche indesiderate. + Apri Confronta quando desideri esaminare due versioni affiancate. + Ingrandisci bordi, testo, sfumature e aree trasparenti dove i problemi sono più facili da perdere. + Se l\'output è troppo pesante o sfocato, torna allo strumento precedente e regola la qualità o il formato. + Utilizza l\'hub degli strumenti PDF + Unisci, dividi, ruota, rimuovi pagine, comprimi, proteggi, esegui l\'OCR e filigrana i file PDF + Scegli un\'operazione PDF + L\'hub PDF raggruppa le azioni del documento dietro un punto di ingresso in modo da poter scegliere l\'operazione esatta. Inizia da lì quando il file è già un PDF e utilizza Immagini in PDF o Scanner di documenti quando la tua origine è ancora un insieme di immagini. + Apri Strumenti PDF e scegli l\'operazione che corrisponde al tuo obiettivo. + Seleziona il PDF o le immagini di origine, quindi rivedi l\'ordine delle pagine, la rotazione, la protezione o le opzioni OCR. + Salva il PDF generato e aprilo una volta per verificare il conteggio delle pagine, l\'ordine e la leggibilità. + Trasforma le immagini in un PDF + Combina scansioni, screenshot, ricevute o foto in un unico documento condivisibile + Costruisci un documento pulito + Le immagini diventano pagine PDF migliori quando vengono ritagliate, ordinate e dimensionate in modo coerente. Tratta l\'elenco delle immagini come una pila di pagine: ogni scelta di rotazione, margine e dimensione della pagina influisce sulla leggibilità del documento finale. + Ritaglia o ruota prima le immagini quando i bordi, le ombre o l\'orientamento della pagina non sono corretti. + Seleziona le immagini nell\'ordine delle pagine previsto e regola le dimensioni o i margini della pagina se lo strumento li offre. + Esporta il PDF, quindi controlla che tutte le pagine siano leggibili prima di inviarlo. + Scansiona documenti + Cattura pagine cartacee e preparale per PDF, OCR o condivisione + Cattura pagine leggibili + La scansione dei documenti funziona meglio con pagine piatte, buona illuminazione e prospettiva corretta. Una scansione pulita prima dell\'esportazione tramite OCR o PDF consente di risparmiare tempo poiché il riconoscimento e la compressione del testo dipendono entrambi dalla qualità della pagina. + Posiziona il documento su una superficie contrastante con abbastanza luce e ombre minime. + Regola gli angoli rilevati o ritaglia manualmente in modo che la pagina riempia la cornice in modo pulito. + Esporta come immagini o PDF, quindi esegui l\'OCR se hai bisogno di testo selezionabile. + Proteggi o sblocca i file PDF + Utilizzare le password con attenzione e conservare una copia sorgente modificabile quando possibile + Gestisci l\'accesso ai PDF in tutta sicurezza + Gli strumenti di protezione sono utili per la condivisione controllata, ma le password sono facili da perdere e difficili da recuperare. Proteggi le copie per la distribuzione, conserva separatamente i file sorgente non protetti e verifica il risultato prima di eliminare qualsiasi cosa. + Proteggi una copia del PDF, non il tuo unico documento di origine. + Conserva la password in un luogo sicuro prima di condividere il file protetto. + Utilizza lo sblocco solo per i file che puoi modificare, quindi verifica che la copia sbloccata si apra correttamente. + Organizza le pagine PDF + Unisci, dividi, riorganizza, ruota, ritaglia, rimuovi pagine e aggiungi deliberatamente numeri di pagina + Correggere la struttura del documento prima della decorazione + Gli strumenti per le pagine PDF sono più facili da usare nello stesso ordine in cui prepareresti un documento cartaceo: raccogli le pagine, rimuovi quelle sbagliate, mettile in ordine, correggi l\'orientamento, quindi aggiungi dettagli finali come numeri di pagina o filigrane. + Utilizzare prima l\'unione o la conversione delle immagini in PDF quando il documento è ancora suddiviso in più file. + Riorganizza, ruota, ritaglia o rimuovi le pagine prima di aggiungere numeri di pagina, firme o filigrane. + Visualizza l\'anteprima del PDF finale dopo le modifiche strutturali perché una pagina mancante o ruotata può essere difficile da notare in seguito. + Annotazioni PDF + Rimuovi le annotazioni o uniscile quando gli spettatori mostrano commenti e contrassegni in modo diverso + Controlla ciò che rimane visibile + Le annotazioni possono essere commenti, evidenziazioni, disegni, contrassegni di modulo o altri oggetti PDF aggiuntivi. Alcuni visualizzatori li visualizzano in modo diverso, quindi rimuoverli o unirli può rendere i documenti condivisi più prevedibili. + Rimuovi le annotazioni quando commenti, evidenziazioni o voti di revisione non devono essere inclusi nella copia condivisa. + Appiattisci quando i segni visibili devono rimanere sulla pagina ma non comportarsi più come annotazioni modificabili. + Conserva una copia originale perché la rimozione o l\'appiattimento delle annotazioni può essere difficile da annullare. + Riconoscere il testo + Estrai testo da immagini con motori OCR e lingue selezionabili + Ottieni risultati OCR migliori + La precisione dell\'OCR dipende dalla nitidezza dell\'immagine, dai dati della lingua, dal contrasto e dall\'orientamento della pagina. I risultati migliori di solito si ottengono preparando prima l\'immagine: ritagliare il rumore, ruotarla in posizione verticale, aumentare la leggibilità, quindi riconoscere il testo. + Ritaglia l\'immagine nell\'area di testo e ruotala in posizione verticale prima del riconoscimento. + Scegli la lingua corretta e scarica i dati della lingua se l\'app lo richiede. + Copia o condividi il testo riconosciuto, quindi controlla manualmente nomi, numeri e punteggiatura. + Scegli i dati della lingua OCR + Migliora il riconoscimento abbinando script, lingue e modelli OCR scaricati + Abbina l\'OCR al testo + Il linguaggio OCR sbagliato può far sì che le buone foto sembrino un cattivo riconoscimento. I dati linguistici sono importanti per script, punteggiatura, numeri e documenti misti, quindi scegli la lingua visualizzata nell\'immagine anziché la lingua dell\'interfaccia utente del telefono. + Scegli la lingua o la scrittura visualizzata nell\'immagine, non solo la lingua del telefono. + Scarica i dati mancanti prima di lavorare offline o elaborare un batch. + Per i documenti in lingue miste, esegui prima la lingua più importante e controlla manualmente nomi e numeri. + PDF ricercabili + Riscrivi il testo OCR nei file in modo che le pagine scansionate possano essere cercate e copiate + Trasforma le scansioni in documenti ricercabili + L\'OCR può fare molto di più che copiare il testo negli appunti. Quando si scrive testo riconosciuto in un file o in un PDF ricercabile, l\'immagine della pagina rimane visibile mentre il testo diventa più facile da cercare, selezionare, indicizzare o archiviare. + Utilizza l\'output PDF ricercabile per i documenti scansionati che dovrebbero avere ancora l\'aspetto delle pagine originali. + Utilizza la scrittura su file quando hai solo bisogno del testo estratto e non ti interessa l\'immagine della pagina. + Controlla manualmente numeri, nomi e tabelle importanti perché il testo OCR può essere ricercabile ma comunque imperfetto. + Scansiona i codici QR + Leggi il contenuto QR dall\'input della fotocamera o dalle immagini salvate quando disponibili + Leggi i codici in sicurezza + I codici QR possono contenere collegamenti, dati Wi-Fi, contatti, testo o altri payload. + Apri Scansione codice QR e mantieni il codice nitido, piatto e completamente all\'interno della cornice. + Esaminare il contenuto decodificato prima di aprire collegamenti o copiare dati sensibili. + Se la scansione fallisce, migliora l\'illuminazione, ritaglia il codice o prova un\'immagine sorgente con una risoluzione più elevata. + Utilizza Base64 e checksum + Codifica le immagini come testo, decodifica il testo in immagini e verifica l\'integrità del file + Spostarsi tra file e testo + Base64 è utile per piccoli payload di immagini, mentre i checksum aiutano a confermare che i file non sono cambiati. Questi strumenti sono molto utili nei flussi di lavoro tecnici, nei rapporti di supporto, nei trasferimenti di file e nei luoghi in cui i file binari devono diventare temporaneamente testo. + Utilizza gli strumenti Base64 per codificare una piccola immagine quando un flusso di lavoro necessita di testo anziché di un file binario. + Decodifica Base64 solo da fonti attendibili e verifica l\'anteprima prima di salvare. + Utilizza gli strumenti di checksum quando devi confrontare i file esportati o confermare un caricamento/download. + Comprimere o proteggere i dati + Utilizza ZIP e strumenti di crittografia per raggruppare file o trasformare dati di testo + Mantieni insieme i file correlati + Gli strumenti di confezionamento sono utili quando più output devono viaggiare come un unico file. Sono utili anche per tenere insieme immagini, PDF, registri e metadati generati quando è necessario inviare un set di risultati completo. + Utilizza ZIP quando devi inviare insieme molte immagini o documenti esportati. + Utilizza gli strumenti di crittografia solo quando comprendi il metodo selezionato e puoi archiviare le chiavi richieste in modo sicuro. + Testare l\'archivio creato o il testo trasformato prima di eliminare i file di origine. + Checksum nei nomi + Utilizza nomi di file basati su checksum quando l\'unicità e l\'integrità contano più della leggibilità + Assegna un nome ai file in base al contenuto + I nomi dei file del checksum sono utili quando le esportazioni potrebbero avere nomi visibili duplicati o quando è necessario dimostrare che due file sono identici. Sono meno intuitivi per la navigazione manuale, quindi usali per archivi tecnici e flussi di lavoro di verifica. + Abilita il checksum come nome file quando l\'identità del contenuto è più importante di un titolo leggibile dall\'uomo. + Usalo per archivi, deduplicazione, esportazioni ripetibili o file che possono essere caricati e scaricati nuovamente. + Associa i nomi dei checksum a cartelle o metadati quando le persone devono ancora capire cosa rappresenta il file. + Scegli i colori dalle immagini + Campiona pixel, copia codici colore e riutilizza i colori in tavolozze o disegni + Prova il colore esatto + La selezione del colore è utile per abbinare un disegno, estrarre i colori del marchio o controllare i valori dell\'immagine. Lo zoom è importante perché l\'antialiasing, le ombre e la compressione possono rendere i pixel vicini leggermente diversi dal colore previsto. + Apri Scegli colore dall\'immagine e scegli un\'immagine sorgente con il colore che ti serve. + Ingrandisci prima di campionare piccoli dettagli in modo che i pixel vicini non influenzino il risultato. + Copia il colore nel formato richiesto dalla tua destinazione, come HEX, RGB o HSV. + Crea tavolozze e utilizza la libreria dei colori + Estrai tavolozze, sfoglia i colori salvati e mantieni sistemi visivi coerenti + Costruisci una tavolozza riutilizzabile + Le tavolozze aiutano a mantenere visivamente coerenti la grafica, le filigrane e i disegni esportati. Sono particolarmente utili quando annoti ripetutamente screenshot, prepari risorse del marchio o hai bisogno degli stessi colori su diversi strumenti. + Genera una tavolozza da un\'immagine o aggiungi colori manualmente quando conosci già i valori. + Assegna un nome o organizza i colori importanti in modo da poterli ritrovare in seguito. + Utilizza i valori copiati in Draw, filigrana, gradiente, SVG o strumenti di progettazione esterni. + Crea gradienti + Crea immagini sfumate per sfondi, segnaposto ed esperimenti visivi + Controlla le transizioni di colore + Gli strumenti sfumatura sono utili quando hai bisogno di un\'immagine generata invece di modificarne una esistente. Possono diventare sfondi puliti, segnaposto, sfondi, maschere o semplici risorse per strumenti di progettazione esterni. + Scegli il tipo di gradiente e imposta prima i colori importanti. + Regola la direzione, le fermate, la fluidità e le dimensioni dell\'output per il caso d\'uso target. + Esporta in un formato che corrisponda alla destinazione, solitamente PNG per una grafica generata in modo pulito. + Accessibilità del colore + Utilizza colori dinamici, contrasto e opzioni per daltonici quando l\'interfaccia utente è difficile da leggere + Rendi i colori più facili da usare + Le impostazioni del colore influiscono sul comfort, sulla leggibilità e sulla velocità con cui è possibile eseguire la scansione dei controlli. Se i chip degli strumenti, i cursori o gli stati selezionati risultano difficili da distinguere, le opzioni di tema e accessibilità possono essere più utili della semplice modifica della modalità oscura. + Prova i colori dinamici quando desideri che l\'app segua la tavolozza degli sfondi corrente. + Aumenta il contrasto o cambia schema quando i pulsanti e le superfici non risaltano abbastanza. + Utilizza le opzioni dello schema per daltonici se alcuni stati o accenti sono difficili da distinguere. + Sfondo alfa + Controlla l\'anteprima o il colore di riempimento utilizzato attorno agli output PNG trasparenti, WebP e simili + Comprendere le anteprime trasparenti + Il colore di sfondo per i formati alfa può rendere le immagini trasparenti più facili da ispezionare, ma è importante sapere se stai modificando il comportamento di anteprima/riempimento o appiattindo il risultato finale. Questo è importante per adesivi, loghi e immagini rimosse dallo sfondo. + Utilizza prima un formato che supporti il ​​formato alfa, come PNG o WebP, quando i pixel trasparenti devono sopravvivere all\'esportazione. + Regola le impostazioni del colore di sfondo quando i bordi trasparenti sono difficili da vedere sulla superficie dell\'app. + Esporta un piccolo test e riaprilo in un\'altra app per verificare se è stata salvata la trasparenza o il riempimento scelto. + Scelta del modello di intelligenza artificiale + Scegli un modello in base al tipo di immagine e al difetto invece di scegliere prima quello più grande + Abbina il modello al danno + Gli strumenti AI possono scaricare o importare diversi modelli ONNX/ORT e ciascun modello è addestrato per un particolare tipo di problema. Un modello per blocchi JPEG, pulizia delle linee anime, rimozione del rumore, rimozione dello sfondo, colorazione o upscaling può produrre risultati peggiori se utilizzato sul tipo di immagine sbagliato. + Leggere la descrizione del modello prima del download; scegli il modello che identifica il tuo problema reale, ad esempio compressione, rumore, mezzitoni, sfocatura o rimozione dello sfondo. + Inizia con un modello più piccolo o più specifico quando provi un nuovo tipo di immagine, quindi confronta con modelli più pesanti solo se il risultato non è abbastanza buono. + Conserva solo i modelli che usi realmente, perché i modelli scaricati e importati possono occupare molto spazio di archiviazione. + Comprendere le famiglie di modelli + I nomi dei modelli spesso suggeriscono il loro obiettivo: modelli in stile RealESRGAN di alto livello, modelli SCUNet e FBCNN puliscono il rumore o la compressione, i modelli di sfondo creano maschere e i coloratori aggiungono colore all\'input in scala di grigi. I modelli personalizzati importati possono essere potenti, ma devono corrispondere alla forma di input/output prevista e utilizzare il formato ONNX o ORT supportato. + Utilizza gli upscaler quando hai bisogno di più pixel, i denoiser quando l\'immagine è sgranata e i dispositivi di rimozione degli artefatti quando i blocchi di compressione o le bande sono il problema principale. + Utilizzare i modelli di sfondo solo per la separazione dei soggetti; non sono la stessa cosa della rimozione generale di oggetti o del miglioramento delle immagini. + Importa modelli personalizzati solo da fonti di cui ti fidi e testali su una piccola immagine prima di utilizzare file importanti. + Parametri dell\'IA + Ottimizza la dimensione dei blocchi, la sovrapposizione e i lavoratori paralleli per bilanciare qualità, velocità e memoria + Bilancia la velocità con la stabilità + La dimensione del blocco controlla quanto sono grandi i pezzi dell\'immagine prima che vengano elaborati dal modello. La sovrapposizione unisce i blocchi vicini per nascondere i bordi. I lavoratori paralleli possono accelerare l\'elaborazione, ma ogni lavoratore necessita di memoria, quindi valori elevati possono rendere instabili immagini di grandi dimensioni su telefoni con RAM limitata. + Utilizza blocchi più grandi per un contesto più fluido quando il tuo dispositivo gestisce il modello in modo affidabile e l\'immagine non è enorme. + Aumenta la sovrapposizione quando i bordi dei blocchi diventano visibili, ma ricorda che una maggiore sovrapposizione significa un lavoro più ripetuto. + Alzare i lavoratori paralleli solo dopo un test stabile; se l\'elaborazione rallenta, surriscalda il dispositivo o si blocca, ridurre prima i lavoratori. + Evita gli artefatti dei blocchi + La suddivisione in blocchi consente all\'app di elaborare immagini più grandi di quelle che il modello può gestire comodamente in una sola volta. Il compromesso è che ogni tessera ha meno contesto circostante, quindi una sovrapposizione bassa o pezzi troppo piccoli possono creare bordi visibili, modifiche alla trama o dettagli incoerenti tra aree vicine. + Aumenta la sovrapposizione se vedi bordi rettangolari, modifiche ripetute della trama o salti di dettaglio tra le regioni elaborate. + Ridurre le dimensioni del blocco se il processo fallisce prima di produrre l\'output, soprattutto su immagini di grandi dimensioni o modelli pesanti. + Salva un piccolo test di ritaglio prima di elaborare l\'immagine completa in modo da poter confrontare rapidamente i parametri. + Stabilità dell\'IA + Riduci le dimensioni dei blocchi, i lavoratori e la risoluzione della sorgente quando l\'elaborazione dell\'IA si arresta in modo anomalo o si blocca + Recuperati dai pesanti lavori di intelligenza artificiale + L\'elaborazione dell\'intelligenza artificiale è uno dei flussi di lavoro più pesanti nell\'app. Arresti anomali, sessioni non riuscite, blocchi o riavvii improvvisi delle app in genere indicano che il modello, la risoluzione dell\'origine, la dimensione del blocco o il conteggio dei lavoratori paralleli sono troppo impegnativi per lo stato corrente del dispositivo. + Se l\'app si arresta in modo anomalo o non riesce ad aprire una sessione del modello, riduci i lavoratori paralleli e le dimensioni del blocco, quindi riprova con la stessa immagine. + Ridimensiona immagini molto grandi prima dell\'elaborazione AI quando l\'obiettivo non richiede la risoluzione originale. + Chiudi altre app pesanti, utilizza un modello più piccolo o elabora meno file contemporaneamente quando la pressione della memoria continua a tornare. + Diagnosticare i fallimenti del modello + Un\'esecuzione AI fallita non significa sempre che l\'immagine sia pessima. Il file del modello potrebbe essere incompleto, non supportato, troppo grande per la memoria o non corrispondente all\'operazione. Quando l\'app segnala una sessione non riuscita, trattala come un segnale per semplificare innanzitutto il modello e i parametri. + Elimina e scarica nuovamente un modello se fallisce immediatamente dopo il download o si comporta come se il file fosse danneggiato. + Prova un modello scaricabile integrato prima di incolpare un modello personalizzato importato, perché i modelli importati possono avere input incompatibili. + Utilizza i registri delle app dopo aver riprodotto l\'errore se devi segnalare un arresto anomalo o un errore di sessione. + Sperimenta in Shader Studio + Utilizza gli effetti shader GPU per l\'elaborazione avanzata delle immagini e aspetti personalizzati + Lavora attentamente con gli shader + Shader Studio è potente, ma il codice e i parametri dello shader richiedono piccole modifiche verificabili. Trattalo come uno strumento sperimentale: mantieni una linea di base funzionante, modifica una cosa alla volta e prova con dimensioni di immagine diverse prima di fidarti di un preset. + Inizia da uno shader funzionante noto o da un semplice effetto prima di aggiungere parametri. + Modifica un parametro o un blocco di codice alla volta e controlla l\'anteprima per eventuali errori. + Esporta le preimpostazioni solo dopo averle testate su alcune dimensioni di immagine diverse. + Crea arte SVG o ASCII + Genera risorse di tipo vettoriale o immagini basate su testo per risultati creativi + Scegli il tipo di output della creatività + Gli strumenti SVG e ASCII servono obiettivi diversi: grafica scalabile o rendering in stile testo. Entrambe sono conversioni creative, quindi visualizzare l\'anteprima nella dimensione finale è più importante che giudicare il risultato da una piccola miniatura. + Utilizza SVG Maker quando il risultato deve essere ridimensionato in modo pulito o essere riutilizzato nei flussi di lavoro di progettazione. + Utilizza ASCII Art quando desideri una rappresentazione testuale stilizzata di un\'immagine. + Anteprima nella dimensione finale perché piccoli dettagli possono scomparire nell\'output generato. + Genera texture di rumore + Crea texture procedurali per sfondi, maschere, sovrapposizioni o esperimenti + Ottimizza l\'output procedurale + La generazione del rumore crea nuove immagini dai parametri, quindi piccole modifiche possono produrre risultati molto diversi. È utile per texture, maschere, sovrapposizioni, segnaposto o per testare la compressione con modelli dettagliati. + Scegli un tipo di rumore e una dimensione di output prima di regolare i dettagli più fini. + Regola la scala, l\'intensità, i colori o il seme finché il modello non si adatta all\'uso target. + Salva i risultati utili e annota i parametri se hai bisogno di ricreare la texture in un secondo momento. + Progetti di markup + Utilizza i file di progetto quando le annotazioni devono rimanere modificabili dopo aver chiuso l\'app + Mantieni riutilizzabili le modifiche a più livelli + I file di progetto di markup sono utili quando uno screenshot, un diagramma o un\'immagine di istruzioni potrebbe richiedere modifiche in un secondo momento. I file PNG/JPEG esportati sono immagini finali, mentre i file di progetto conservano i livelli modificabili e lo stato di modifica per regolazioni future. + Utilizza i livelli di markup quando annotazioni, didascalie o elementi di layout devono rimanere modificabili. + Salva o riapri il file di progetto prima di esportare un\'immagine finale se prevedi più revisioni. + Esporta un\'immagine normale solo quando sei pronto per condividere una versione finale appiattita. + Crittografare i file + Proteggi i file con una password e verifica la decrittografia prima di eliminare qualsiasi copia sorgente + Gestisci con attenzione gli output crittografati + Gli strumenti di crittografia possono proteggere i file con crittografia basata su password, ma non sostituiscono la memorizzazione della chiave o la conservazione dei backup. La crittografia è utile per il trasporto e l\'archiviazione, mentre ZIP è migliore quando l\'obiettivo principale è raggruppare diversi output. + Crittografa prima una copia e conserva l\'originale finché non hai testato che la decrittografia funzioni con la password memorizzata. + Utilizza un gestore di password o un altro luogo sicuro per la chiave; l\'app non può indovinare una password persa per te. + Condividi i file crittografati solo con persone che dispongono anche della password e sanno quale strumento o metodo dovrebbe decrittografarli. + Disporre gli strumenti + Raggruppa, ordina, cerca e aggiungi strumenti in modo che la schermata principale corrisponda al tuo flusso di lavoro reale + Rendi la schermata principale più veloce + Vale la pena ottimizzare le impostazioni di disposizione degli strumenti una volta che sai quali strumenti usi di più. Il raggruppamento aiuta l\'esplorazione, l\'ordine personalizzato aiuta la memoria muscolare e i preferiti mantengono gli strumenti quotidiani raggiungibili anche quando l\'elenco completo diventa grande. + Utilizza la modalità raggruppata mentre apprendi l\'app o quando preferisci gli strumenti organizzati per tipo di attività. + Passa all\'ordine personalizzato quando conosci già i tuoi strumenti frequenti e desideri meno pergamene. + Utilizza insieme le impostazioni di ricerca e i preferiti per strumenti rari che ricordi per nome ma che non desideri siano sempre visibili. + Gestisci file di grandi dimensioni + Evita esportazioni lente, sovraccarico della memoria e output inaspettatamente grandi + Ridurre il lavoro prima dell\'esportazione + Immagini molto grandi, batch lunghi e formati di alta qualità possono richiedere più memoria e tempo. La soluzione più rapida consiste solitamente nel ridurre la quantità di lavoro prima dell\'operazione più pesante, senza attendere il completamento dello stesso input sovradimensionato. + Ridimensiona immagini enormi prima di applicare filtri pesanti, OCR o conversione PDF. + Utilizzare prima un lotto più piccolo per confermare le impostazioni, quindi elaborare il resto con la stessa preimpostazione. + Ridurre la qualità o modificare il formato quando il file di output è più grande del previsto. + Cache e anteprime + Comprendere le anteprime generate, la pulizia della cache e l\'utilizzo dello spazio di archiviazione dopo sessioni intense + Mantieni spazio di archiviazione e velocità bilanciati + La generazione di anteprime e la cache possono velocizzare la navigazione ripetuta, ma sessioni di modifica pesanti potrebbero lasciare dietro di sé dati temporanei. Le impostazioni della cache aiutano quando l\'app sembra lenta, lo spazio di archiviazione è limitato o le anteprime sembrano obsolete dopo molti esperimenti. + Mantenere le anteprime abilitate quando si sfogliano molte immagini è più importante che ridurre al minimo l\'archiviazione temporanea. + Svuota la cache dopo batch di grandi dimensioni, esperimenti falliti o lunghe sessioni con molti file ad alta risoluzione. + Utilizza la cancellazione automatica della cache se preferisci la pulizia dello spazio di archiviazione piuttosto che mantenere le anteprime calde tra i lanci. + Regola il comportamento dello schermo + Utilizza le opzioni a schermo intero, modalità protetta, luminosità e barra di sistema per lavorare in modo mirato + Adatta l\'interfaccia alla situazione + Le impostazioni dello schermo sono utili quando effettui modifiche in orizzontale, presenti immagini private o hai bisogno di una luminosità costante. Non sono necessari per l\'uso normale, ma risolvono situazioni scomode come l\'ispezione QR, le anteprime private o la modifica di paesaggi angusti. + Utilizza le impostazioni a schermo intero o nella barra di sistema quando lo spazio di modifica è più importante del Chrome di navigazione. + Utilizza la modalità protetta quando le immagini private non devono essere visualizzate negli screenshot o nelle anteprime delle app. + Utilizza l\'applicazione della luminosità per gli strumenti in cui le immagini scure o i codici QR sono difficili da ispezionare. + Mantieni la trasparenza + Evita che gli sfondi trasparenti diventino bianchi, neri o appiattiti + Utilizza output compatibile con alfa + La trasparenza sopravvive solo quando lo strumento selezionato e il formato di output supportano l\'alfa. Se uno sfondo esportato diventa bianco o nero, il problema in genere è dovuto al formato di output, a un\'impostazione di riempimento/sfondo o a un\'app di destinazione che ha appiattito l\'immagine. + Scegli PNG o WebP quando esporti immagini, adesivi, loghi o sovrapposizioni rimossi dallo sfondo. + Evita JPEG per output trasparente perché non memorizza un canale alfa. + Controlla le impostazioni relative al colore di sfondo per i formati alfa se l\'anteprima mostra uno sfondo pieno. + Risolvi i problemi di condivisione e importazione + Utilizza le impostazioni del selettore e i formati supportati quando i file non vengono visualizzati o non si aprono correttamente + Ottieni il file nell\'app + I problemi di importazione sono spesso causati da autorizzazioni del provider, formati non supportati o comportamento del selettore. I file condivisi da app cloud e messaggistica potrebbero essere temporanei, quindi scegliere un\'origine persistente può essere più affidabile per modifiche più lunghe. + Prova ad aprire il file tramite il selettore di sistema anziché un collegamento ai file recenti. + Se un formato non è supportato da uno strumento, convertilo prima o apri uno strumento più specifico. + Controlla le impostazioni del selettore immagini e dell\'utilità di avvio se i flussi di condivisione o l\'apertura diretta dello strumento si comportano diversamente dal previsto. + Conferma uscita + Evita di perdere le modifiche non salvate quando si verificano gesti, pressioni all\'indietro o cambi di strumento accidentalmente + Proteggi il lavoro incompiuto + La conferma dell\'uscita dallo strumento è utile quando utilizzi la navigazione gestuale, modifichi file di grandi dimensioni o passi spesso da un\'app all\'altra. Aggiunge una piccola pausa prima di uscire da uno strumento in modo che le impostazioni e le anteprime non completate non vadano perse accidentalmente. + Abilita la conferma se i gesti indietro accidentali hanno mai chiuso uno strumento prima dell\'esportazione. + Mantienilo disabilitato se esegui principalmente conversioni rapide in un solo passaggio e preferisci una navigazione più veloce. + Usalo insieme alle preimpostazioni per flussi di lavoro più lunghi in cui la ricostruzione delle impostazioni sarebbe fastidiosa. + Utilizza i log quando segnali problemi + Raccogli dettagli utili prima di aprire un problema o contattare lo sviluppatore + Segnala problemi con il contesto + Un report chiaro con passaggi, versione dell\'app, tipo di input e log è molto più semplice da diagnosticare. I log sono molto utili subito dopo aver riprodotto un problema perché mantengono aggiornato il contesto circostante. + Prendi nota del nome dello strumento, dell\'azione esatta e se si verifica con un file o con tutti i file. + Apri i registri delle app dopo aver riprodotto il problema e condividi l\'output del registro pertinente quando possibile. + Allega screenshot o file di esempio solo quando non contengono informazioni private. + L\'elaborazione in background è stata interrotta + Android non ha consentito l\'avvio in tempo della notifica di elaborazione in background. Si tratta di una limitazione temporale del sistema che non può essere risolta in modo affidabile. Riavvia l\'app e riprova; può essere utile mantenere l\'app aperta e consentire notifiche o lavoro in background. + Salta la selezione dei file + Apri gli strumenti più velocemente quando l\'input proviene solitamente dalla condivisione, dagli appunti o da una schermata precedente + Rendi più rapidi i lanci ripetuti degli strumenti + Ignora selezione file modifica il primo passaggio degli strumenti supportati. È utile quando di solito accedi a uno strumento con un\'immagine già selezionata o dalle azioni di condivisione di Android, ma può creare confusione se ti aspetti che ogni strumento chieda immediatamente un file. + Abilitalo solo quando il tuo flusso abituale fornisce già l\'immagine di origine prima dell\'apertura dello strumento. + Mantienilo disabilitato durante l\'apprendimento dell\'app, perché la selezione esplicita rende più facile comprendere il flusso di ogni strumento. + Se uno strumento si apre senza input evidente, disabilita questa impostazione o avvia da Condividi/Apri con in modo che Android passi prima il file. + Apri prima l\'editor + Salta l\'anteprima quando un\'immagine condivisa deve essere direttamente modificata + Scegli l\'anteprima o la modifica come prima tappa + Apri modifica invece di anteprima è per gli utenti che sanno già di voler modificare l\'immagine. L\'anteprima è più sicura quando controlli i file dalla chat o dall\'archivio cloud; la modifica diretta è più veloce per screenshot, ritagli rapidi, annotazioni e correzioni una tantum. + Abilita la modifica diretta se la maggior parte delle immagini condivise necessita immediatamente di modifiche di ritaglio, markup, filtri o esportazione. + Lascia prima l\'anteprima quando controlli spesso metadati, dimensioni, trasparenza o qualità dell\'immagine prima di decidere. + Usalo insieme ai preferiti in modo che le immagini condivise si avvicinino agli strumenti che usi effettivamente. + Immissione di testo preimpostata + Inserisci valori preimpostati esatti invece di regolare ripetutamente i controlli + Utilizza valori precisi quando i cursori sono lenti + L\'immissione di testo preimpostata aiuta quando hai bisogno di numeri esatti per lavori ripetuti: dimensioni di immagini social, margini di documenti, obiettivi di compressione, larghezze di linea o altri valori che è fastidioso raggiungere con i gesti. È utile anche su schermi piccoli dove il movimento preciso del cursore è più difficile. + Abilita l\'immissione di testo se riutilizzi spesso dimensioni, percentuali, valori di qualità o parametri dello strumento esatti. + Salva il valore come preimpostazione dopo averlo digitato una volta, quindi riutilizzalo invece di ricostruire la stessa configurazione. + Mantieni i controlli gestuali per una regolazione visiva approssimativa e i valori digitati per requisiti di output rigorosi. + Salva quasi originale + Metti i file generati accanto alle immagini di origine quando il contesto della cartella è importante + Mantieni gli output con le relative fonti + Salva nella cartella originale è utile per le cartelle della fotocamera, le cartelle di progetto, le scansioni di documenti e i batch di client in cui la copia modificata deve rimanere accanto all\'origine. Può essere meno ordinato di una cartella di output dedicata, quindi combinalo con suffissi o modelli di nome file chiari. + Abilitalo quando ciascuna cartella di origine è già significativa e gli output dovrebbero rimanere raggruppati lì. + Utilizza suffissi, prefissi, timestamp o modelli di nomi file in modo che le copie modificate siano facili da separare dagli originali. + Disabilitalo per batch misti quando desideri che tutti i file generati vengano raccolti in un\'unica cartella di esportazione. + Salta output più grande + Evita di salvare le conversioni che diventano inaspettatamente più pesanti dell\'origine + Proteggi i lotti da sorprese di pessime dimensioni + Alcune conversioni rendono i file più grandi, in particolare screenshot PNG, foto già compresse, WebP di alta qualità o immagini con metadati conservati. Consenti Salta se più grande impedisce a tali output di sostituire un\'origine più piccola nei flussi di lavoro in cui la riduzione delle dimensioni è l\'obiettivo principale. + Abilitalo quando l\'esportazione ha lo scopo di comprimere, ridimensionare o preparare i file per i limiti di caricamento. + Revisione dei file saltati dopo un batch; potrebbero essere già ottimizzati o potrebbero richiedere un formato diverso. + Disabilitalo quando la qualità, la trasparenza o la compatibilità del formato sono più importanti della dimensione del file finale. + Appunti e collegamenti + Controlla l\'input automatico degli appunti e le anteprime dei collegamenti per strumenti basati su testo più veloci + Rendi prevedibile l\'input automatico + Le impostazioni degli appunti e dei collegamenti sono utili per i flussi di lavoro QR, Base64, URL e con testo pesante, ma l\'input automatico dovrebbe rimanere intenzionale. Se l\'app sembra riempire i campi da sola, controlla il comportamento dell\'incolla negli appunti; se le schede di collegamento risultano rumorose o lente, regola il comportamento dell\'anteprima del collegamento. + Consenti l\'incollamento automatico degli appunti quando copi spesso il testo e apri immediatamente uno strumento di corrispondenza. + Disattiva l\'incolla automatica se il contenuto degli appunti privati ​​viene visualizzato in strumenti in cui non te lo aspettavi. + Disattiva le anteprime dei collegamenti quando il recupero delle anteprime è lento, distrae o non è necessario per il tuo flusso di lavoro. + Controlli compatti + Utilizza selettori più densi e un posizionamento rapido delle impostazioni quando lo spazio sullo schermo è limitato + Adatta più controlli agli schermi di piccole dimensioni + Selettori compatti e posizionamento rapido delle impostazioni aiutano su telefoni piccoli, editing orizzontale e strumenti con molti parametri. Il compromesso è che i controlli possono sembrare più densi, quindi è meglio dopo aver già riconosciuto le opzioni comuni. + Abilita i selettori compatti quando le finestre di dialogo o gli elenchi di opzioni sembrano troppo alti per lo schermo. + Regola il lato delle impostazioni rapide se i controlli dello strumento sono più facili da raggiungere da un lato del display. + Torna ai controlli più spaziosi se stai ancora imparando a usare l\'app o se tocchi spesso opzioni dense. + Carica immagini dal web + Incolla l\'URL di una pagina o di un\'immagine, visualizza in anteprima le immagini analizzate, quindi salva o condividi i risultati selezionati + Usa deliberatamente le immagini web + Il caricamento delle immagini sul Web analizza le origini delle immagini dall\'URL fornito, visualizza in anteprima la prima immagine disponibile e consente di salvare o condividere le immagini analizzate selezionate. Alcuni siti utilizzano collegamenti temporanei, protetti o generati da script, pertanto una pagina che funziona in un browser potrebbe comunque non restituire immagini utilizzabili. + Incolla l\'URL di un\'immagine diretta o l\'URL di una pagina e attendi che venga visualizzato l\'elenco delle immagini analizzate. + Utilizza la cornice o i controlli di selezione quando la pagina contiene più immagini e ne hai bisogno solo alcune. + Se non viene caricato nulla, prova prima un collegamento diretto all\'immagine o scaricalo tramite il browser; il sito potrebbe bloccare l\'analisi o utilizzare collegamenti privati. + Scegli il tipo di ridimensionamento + Comprendere Esplicito, Flessibile, Ritaglia e Adatta prima di forzare un\'immagine in nuove dimensioni + Controlla forma, tela e proporzioni + Il tipo di ridimensionamento decide cosa succede quando la larghezza e l\'altezza richieste non corrispondono alle proporzioni originali. È la differenza tra allungare i pixel, preservare le proporzioni, ritagliare un\'area extra o aggiungere una tela di sfondo. + Utilizza Esplicito solo quando la distorsione è accettabile o la sorgente ha già le stesse proporzioni della destinazione. + Utilizza Flessibile per mantenere le proporzioni ridimensionando dal lato importante, soprattutto per foto e lotti misti. + Utilizza Ritaglia per cornici rigorose senza bordi o Adatta quando l\'intera immagine deve rimanere visibile all\'interno di una tela fissa. + Scegli la modalità scala immagine + Scegli il filtro di ricampionamento che controlla nitidezza, uniformità, velocità e artefatti dei bordi + Ridimensiona senza morbidezza accidentale + La modalità scala immagine controlla il modo in cui vengono calcolati i nuovi pixel durante il ridimensionamento. Le modalità semplici sono veloci e prevedibili, mentre i filtri avanzati possono preservare meglio i dettagli ma possono rendere più nitidi i bordi, creare aloni o richiedere più tempo su immagini di grandi dimensioni. + Utilizza Base o Bilineare per un rapido ridimensionamento quotidiano quando il risultato deve solo essere più piccolo e pulito. + Utilizza le modalità Lanczos, Robidoux, Spline o in stile EWA quando sono importanti i dettagli fini, il testo o il downscaling di alta qualità. + Utilizza Più vicino per pixel art, icone e grafica dai bordi netti in cui i pixel sfocati rovinerebbero l\'aspetto. + Scala lo spazio colore + Decidi come i colori vengono miscelati mentre i pixel vengono ricampionati durante il ridimensionamento + Mantieni sfumature e bordi naturali + Scala spazio colore modifica i calcoli utilizzati durante il ridimensionamento. La maggior parte delle immagini va bene con l\'impostazione predefinita, ma gli spazi lineari o percettivi possono rendere le sfumature, i bordi scuri e i colori saturi più puliti dopo un forte ridimensionamento. + Lascia l\'impostazione predefinita quando la velocità e la compatibilità contano più delle piccole differenze di colore. + Prova le modalità lineare o percettiva quando i contorni scuri, gli screenshot dell\'interfaccia utente, i gradienti o le bande di colore appaiono errati dopo il ridimensionamento. + Confronta il prima e il dopo sullo schermo di destinazione reale, perché i cambiamenti dello spazio colore possono essere impercettibili e dipendenti dal contenuto. + Limita le modalità di ridimensionamento + Scopri quando le immagini che superano i limiti devono essere saltate, ricodificate o ridotte per adattarle + Scegli cosa succede al limite + Limit Resize non è solo uno strumento per immagini più piccole. La sua modalità decide cosa fa l\'app quando una fonte è già entro i tuoi limiti o quando solo una parte infrange la regola, il che è importante per le cartelle miste e la preparazione del caricamento. + Utilizzare Salta quando le immagini all\'interno dei limiti devono essere lasciate intatte e non esportate nuovamente. + Utilizza Ricodifica quando le dimensioni sono accettabili ma hai ancora bisogno di un nuovo formato, comportamento dei metadati, qualità o nome file. + Utilizzare Zoom quando le immagini che superano i limiti devono essere ridimensionate proporzionalmente fino a quando non rientrano nella casella consentita. + Obiettivi delle proporzioni + Evita facce allungate, bordi tagliati e bordi inattesi in formati di output rigorosi + Abbina la forma prima di ridimensionarla + La larghezza e l\'altezza sono solo la metà di un obiettivo di ridimensionamento. Le proporzioni determinano se l\'immagine può adattarsi in modo naturale, deve essere ritagliata o necessita di una tela attorno. Controllare prima la forma impedisce risultati di ridimensionamento più sorprendenti. + Confronta la forma di origine con la forma di destinazione prima di scegliere Esplicito, Ritaglia o Adatta. + Ritaglia prima quando il soggetto può perdere ulteriore sfondo e la destinazione necessita di una cornice rigorosa. + Utilizza Adatta o Flessibile quando ogni parte dell\'immagine originale deve rimanere visibile. + Ridimensionamento elegante o normale + Scopri quando l\'aggiunta di pixel richiede l\'intelligenza artificiale e quando è sufficiente il semplice ridimensionamento + Non confondere le dimensioni con i dettagli + Il ridimensionamento normale modifica le dimensioni interpolando i pixel; non può inventare dettagli reali. L\'upscaling dell\'intelligenza artificiale può creare dettagli dall\'aspetto più nitido, ma è più lento e può allucinare la trama, quindi la scelta giusta dipende da se hai bisogno di compatibilità, velocità o ripristino visivo. + Utilizza il ridimensionamento normale per miniature, limiti di caricamento, screenshot e semplici modifiche alle dimensioni. + Utilizza l\'upscale AI quando un\'immagine piccola o morbida deve avere un aspetto migliore in una dimensione più grande. + Confronta volti, testo e motivi dopo l\'upscaling dell\'intelligenza artificiale perché i dettagli generati possono sembrare convincenti ma errati. + L\'ordine dei filtri è importante + Applica pulizia, colore, nitidezza e compressione finale in una sequenza intenzionale + Costruisci uno stack di filtri più pulito + I filtri non sono sempre intercambiabili. Una sfocatura prima della nitidezza, un aumento del contrasto prima dell\'OCR o un cambiamento di colore prima della compressione possono produrre output molto diversi dagli stessi controlli. + Ritaglia e ruota prima, così in seguito i filtri funzioneranno solo sui pixel che rimarranno. + Applicare l\'esposizione, il bilanciamento del bianco e il contrasto prima della nitidezza o degli effetti stilizzati. + Mantieni il ridimensionamento e la compressione finali verso la fine in modo che i dettagli visualizzati in anteprima sopravvivano in modo prevedibile all\'esportazione. + Correggi i bordi dello sfondo + Pulisci aloni, ombre rimanenti, peli, buchi e contorni sfumati dopo la rimozione automatica + Fai in modo che i ritagli sembrino intenzionali + Le maschere automatiche spesso sembrano belle a prima vista, ma rivelano problemi su sfondi luminosi, scuri o con motivi. La pulizia dei bordi è la differenza tra un ritaglio rapido e un adesivo, un logo o un\'immagine del prodotto riutilizzabile. + Visualizza l\'anteprima del risultato su sfondi contrastanti per rivelare aloni e dettagli mancanti dei bordi. + Utilizza il ripristino per capelli, angoli e oggetti trasparenti prima di cancellare i residui circostanti. + Esporta un piccolo test sul colore di sfondo finale quando il ritaglio verrà inserito in un disegno. + Filigrane leggibili + Rendi visibili i segni sulle immagini luminose, scure, occupate e ritagliate senza rovinare la foto + Proteggi l\'immagine senza nasconderla + Una filigrana che sembra buona su un\'immagine potrebbe scomparire su un\'altra. Dimensioni, opacità, contrasto, posizionamento e ripetizione dovrebbero essere scelti per l\'intero batch, non solo per la prima anteprima. + Metti alla prova il segno sulle immagini più luminose e più scure del batch prima di esportare tutti i file. + Utilizzare un\'opacità inferiore per segni ripetuti di grandi dimensioni e un contrasto più forte per segni d\'angolo piccoli. + Mantieni chiari i volti, i testi e i dettagli del prodotto importanti, a meno che il marchio non abbia lo scopo di impedirne il riutilizzo. + Dividi un\'immagine in tessere + Taglia una singola immagine per righe, colonne, percentuali personalizzate, formato e qualità + Preparare griglie e pezzi ordinati + La divisione delle immagini crea più output da un\'immagine sorgente. Può dividere in base al conteggio di righe e colonne, regolare le percentuali di righe o colonne e salvare pezzi con il formato e la qualità di output selezionati, utile per griglie, sezioni di pagina, panorami e post social ordinati. + Scegli l\'immagine sorgente, quindi imposta il numero di righe e colonne necessarie. + Regola le percentuali di riga o colonna quando i pezzi non devono avere tutti la stessa dimensione. + Scegli il formato e la qualità di output prima di salvare, in modo che ogni pezzo generato utilizzi le stesse regole di esportazione. + Ritaglia le strisce di immagini + Rimuovi le regioni verticali o orizzontali e unisci nuovamente le parti rimanenti + Rimuovi una regione senza lasciare spazi vuoti + Il taglio dell\'immagine è diverso dal ritaglio normale. Può rimuovere una striscia verticale o orizzontale selezionata, quindi unire insieme le parti rimanenti dell\'immagine. La modalità inversa mantiene invece la regione selezionata e l\'utilizzo di entrambe le direzioni inverse mantiene il rettangolo selezionato. + Utilizzare il taglio verticale per le regioni laterali, le colonne o le strisce centrali; utilizzare il taglio orizzontale per banner, spazi vuoti o righe. + Abilita l\'inverso su un asse quando vuoi mantenere la parte selezionata invece di rimuoverla. + Visualizza l\'anteprima dei bordi dopo il taglio perché il contenuto unito può apparire brusco quando l\'area rimossa attraversa dettagli importanti. + Scegli il formato di output + Scegli JPEG, PNG, WebP, AVIF, JXL o PDF in base al contenuto e alla destinazione + Lascia che sia la destinazione a decidere il formato + Il formato migliore dipende da cosa contiene l\'immagine e dove verrà utilizzata. Foto, screenshot, risorse trasparenti, documenti e file animati falliscono tutti in modi diversi se forzati nel formato sbagliato. + Utilizza JPEG per un\'ampia compatibilità fotografica quando non sono richiesti trasparenza e pixel esatti. + Utilizza PNG per screenshot nitidi, acquisizioni dell\'interfaccia utente, grafica trasparente e modifiche senza perdite. + Utilizza WebP, AVIF o JXL quando l\'output moderno e compatto è importante e l\'app ricevente lo supporta. + Estrai copertine audio + Salva le copertine degli album incorporati o i fotogrammi multimediali disponibili dai file audio come immagini PNG + Estrai la grafica dai file audio + Audio Covers utilizza i metadati multimediali Android per leggere la grafica incorporata dai file audio. Quando la grafica incorporata non è disponibile, il retriever potrebbe ricorrere a un frame multimediale se Android può fornirne uno; se non esiste nessuno dei due, lo strumento segnala che non c\'è nessuna immagine da estrarre. + Apri Copertine audio e seleziona uno o più file audio con la copertina prevista. + Rivedi la copertina estratta prima di salvare o condividere, soprattutto per i file provenienti da app di streaming o registrazione. + Se non viene trovata alcuna immagine, è probabile che il file audio non abbia una copertina incorporata oppure Android non sia in grado di esporre un fotogramma utilizzabile. + Date e metadati sulla posizione + Decidi cosa conservare quando il tempo di acquisizione è importante ma i dati del GPS o del dispositivo non devono fuoriuscire + Separare i metadati utili dai metadati privati + I metadati non sono tutti ugualmente rischiosi. Le date di acquisizione possono essere utili per archivi e ordinamenti, mentre GPS, campi simili a seriali del dispositivo, tag software o campi proprietario possono rivelare più del previsto. + Conserva i tag di data e ora quando l\'ordine cronologico è importante per album, scansioni o cronologia del progetto. + Rimuovi i tag GPS e identificativi del dispositivo prima di condividere pubblicamente o inviare immagini a sconosciuti. + Esporta un campione e controlla nuovamente l\'EXIF quando i requisiti sulla privacy sono severi. + Evita collisioni tra i nomi dei file + Proteggi gli output batch quando molti file condividono nomi come image.jpg o screenshot.png + Rendi univoco ogni nome di output + I nomi di file duplicati sono comuni quando le immagini provengono da messenger, screenshot, cartelle cloud o più fotocamere. Un buon modello previene la sostituzione accidentale e mantiene gli output tracciabili fino alle loro fonti. + Includi il nome originale più un suffisso quando la copia modificata deve essere riconoscibile. + Aggiungi sequenza, timestamp, preimpostazione o dimensioni durante l\'elaborazione di grandi batch misti. + Evitare di sovrascrivere i flussi di lavoro finché non si è verificato che il modello di denominazione non possa entrare in conflitto. + Salva o condividi + Scegli tra un file persistente e un trasferimento temporaneo a un\'altra app + Esporta con il giusto intento + Salva e condividi può sembrare simile, ma risolvono problemi diversi. Il salvataggio crea un file che potrai ritrovare in seguito; la condivisione invia un risultato generato tramite Android a un\'altra app e potrebbe non lasciare una copia locale durevole. + Utilizzare Salva quando l\'output deve rimanere in una cartella conosciuta, essere sottoposto a backup o essere riutilizzato in seguito. + Utilizza Condividi per messaggi rapidi, allegati e-mail, post sui social o per inviare il risultato a un altro editor. + Salva prima quando l\'app ricevente è inaffidabile o quando sarebbe fastidioso ricreare una condivisione non riuscita. + Dimensioni e margini della pagina PDF + Scegli il formato della carta, il comportamento di adattamento e il margine di respiro prima di creare un documento + Rendi le pagine delle immagini stampabili e leggibili + Le immagini possono diventare pagine PDF scomode quando la loro forma non corrisponde al formato carta scelto. I margini, la modalità di adattamento e l\'orientamento della pagina determinano se il contenuto è ritagliato, minuscolo, allungato o comodo da leggere. + Utilizza un formato carta reale quando il PDF può essere stampato o inviato a un modulo. + Utilizza i margini per scansioni e screenshot in modo che il testo non si appoggi al bordo della pagina. + Visualizza l\'anteprima delle pagine verticale e orizzontale insieme quando le immagini di origine hanno un orientamento misto. + Pulisci le scansioni + Migliora i bordi della carta, le ombre, il contrasto, la rotazione e la leggibilità prima del PDF o dell\'OCR + Preparare le pagine prima dell\'archiviazione + Una scansione può essere tecnicamente catturata ma ancora difficile da leggere. Piccoli passaggi di pulizia prima dell\'esportazione in PDF spesso contano più delle impostazioni di compressione, soprattutto per ricevute, note scritte a mano e pagine poco illuminate. + Correggi la prospettiva e ritaglia i bordi del tavolo, le dita, le ombre e il disordine dello sfondo. + Aumenta attentamente il contrasto in modo che il testo diventi più chiaro senza distruggere timbri, firme o segni di matita. + Esegui l\'OCR solo dopo che la pagina è diritta e leggibile, quindi verifica manualmente i numeri importanti. + Comprimi, scala di grigi o ripara i PDF + Utilizza operazioni PDF a file singolo per copie di documenti più leggere, stampabili o ricostruite + Scegli l\'operazione di pulizia del PDF + PDF Tools include operazioni separate per la compressione, la conversione in scala di grigi e la riparazione. La compressione e la scala di grigi funzionano principalmente attraverso immagini incorporate, mentre la riparazione ricostruisce la struttura del PDF; nessuno di questi dovrebbe essere trattato come una soluzione garantita per ogni documento. + Utilizza Comprimi PDF quando il documento contiene immagini e le dimensioni del file sono importanti per la condivisione. + Utilizzare Scala di grigi quando il documento deve essere più facile da stampare o non necessita di immagini a colori. + Utilizza Ripara PDF su una copia quando un documento non si apre o presenta danni alla struttura interna, quindi verifica il file ricostruito. + Estrai immagini da PDF + Recupera le immagini PDF incorporate e inserisci i risultati estratti in un archivio + Salva immagini di origine dai documenti + Estrai immagini esegue la scansione del PDF alla ricerca di oggetti immagine incorporati, salta i duplicati ove possibile e memorizza le immagini recuperate in un archivio ZIP generato. Estrae solo le immagini effettivamente incorporate nel PDF, non testo, disegni vettoriali o ogni pagina visibile come screenshot. + Utilizza Estrai immagini quando hai bisogno di immagini archiviate in un PDF, come foto da un catalogo o risorse scansionate. + Utilizza invece PDF in immagini o l\'estrazione delle pagine quando hai bisogno di pagine intere, inclusi testo e layout. + Se lo strumento non segnala immagini incorporate, la pagina potrebbe contenere contenuto di testo/vettoriale oppure le immagini potrebbero non essere archiviate come oggetti estraibili. + Metadati, appiattimento e output di stampa + Modifica le proprietà del documento, rasterizza le pagine o prepara intenzionalmente layout di stampa personalizzati + Scegli l\'azione del documento finale + I metadati PDF, l\'appiattimento e la preparazione della stampa risolvono diversi problemi nella fase finale. I metadati controllano le proprietà del documento, Appiattisci PDF rasterizza le pagine in output meno modificabili e Stampa PDF prepara un nuovo layout con controlli di dimensioni della pagina, orientamento, margine e pagine per foglio. + Utilizza i metadati quando l\'autore, il titolo, le parole chiave, il produttore o la pulizia della privacy sono importanti. + Utilizzare Appiattisci PDF quando il contenuto della pagina visibile dovrebbe diventare più difficile da modificare come oggetti PDF separati. + Utilizza Stampa PDF quando hai bisogno di dimensioni della pagina, orientamento, margini personalizzati o più pagine per foglio. + Preparare le immagini per l\'OCR + Utilizza il ritaglio, la rotazione, il contrasto, la riduzione del rumore e la scala prima di chiedere all\'OCR di leggere il testo + Fornisci pixel più puliti all\'OCR + Gli errori OCR spesso provengono dall\'immagine di input piuttosto che dal motore OCR. Pagine storte, contrasto basso, sfocature, ombre, testo minuscolo e sfondi misti riducono la qualità del riconoscimento. + Ritaglia nell\'area del testo e ruota l\'immagine in modo che le linee siano orizzontali prima del riconoscimento. + Aumenta il contrasto o converti in un bianco e nero più pulito solo quando rende le lettere più facili da leggere. + Ridimensiona moderatamente il testo piccolo verso l\'alto, ma evita una nitidezza aggressiva che crea bordi falsi. + Controlla il contenuto QR + Esamina collegamenti, credenziali Wi-Fi, contatti e azioni prima di considerare attendibile un codice + Tratta il testo decodificato come input non attendibile + Un codice QR può nascondere un URL, una scheda contatto, una password Wi-Fi, un evento del calendario, un numero di telefono o un testo normale. L\'etichetta visibile vicino al codice potrebbe non corrispondere al carico utile effettivo, quindi esamina il contenuto decodificato prima di agire su di esso. + Ispeziona l\'URL completo decodificato prima di aprirlo, in particolare i domini abbreviati o sconosciuti. + Fai attenzione ai codici Wi-Fi, contatti, calendario, telefono e SMS perché possono attivare azioni sensibili. + Copia prima il contenuto quando devi verificarlo altrove invece di aprirlo immediatamente. + Genera codici QR + Crea codici da testo semplice, URL, Wi-Fi, contatti, e-mail, telefono, SMS e dati sulla posizione + Costruisci il giusto payload QR + Lo schermo QR può generare codici dal contenuto inserito e scansionare quelli esistenti. I tipi QR strutturati aiutano a codificare i dati in un formato comprensibile da altre app, come dettagli di accesso Wi-Fi, schede di contatto, campi e-mail, numeri di telefono, contenuto SMS, URL o coordinate geografiche. + Scegli testo normale per note semplici oppure utilizza un tipo strutturato quando il codice deve aprirsi come Wi-Fi, contatto, URL, e-mail, telefono, SMS o dati sulla posizione. + Controlla ogni campo prima di condividere il codice generato perché l\'anteprima visibile riflette solo il payload che hai inserito. + Prova il codice QR salvato con uno scanner quando verrà stampato, pubblicato pubblicamente o utilizzato da altre persone. + Scegli una modalità di checksum + Calcola gli hash da file o testo, confronta un file o controlla in batch più file + Verifica il contenuto, non l\'aspetto + Checksum Tools dispone di schede separate per l\'hashing dei file, l\'hashing del testo, il confronto di un file con un hash noto e il confronto batch. Un checksum dimostra l\'identità byte per byte dell\'algoritmo selezionato; ciò non prova che due immagini semplicemente sembrino simili. + Utilizzare Calcola per i file e Text Hash per i dati di testo digitati o incollati. + Utilizza Confronta quando qualcuno ti fornisce un checksum previsto per un file. + Utilizza il confronto batch quando molti file necessitano di hash o verifica in un unico passaggio, quindi mantieni coerente l\'algoritmo selezionato. + Privacy degli appunti + Utilizza le funzionalità automatiche degli appunti senza esporre accidentalmente i dati copiati privati + Mantieni il contenuto copiato intenzionale + L\'automazione degli appunti è comoda, ma il testo copiato può contenere password, collegamenti, indirizzi, token o note private. Tratta l\'input basato sugli appunti come una funzionalità di velocità che dovrebbe corrispondere alle tue abitudini e alle aspettative sulla privacy. + Disattiva l\'utilizzo automatico degli appunti se il testo privato viene visualizzato inaspettatamente negli strumenti. + Utilizza il salvataggio solo negli appunti solo quando desideri deliberatamente che il risultato eviti la memorizzazione. + Cancella o sostituisci gli appunti dopo aver gestito testo sensibile, dati QR o payload Base64. + Formati di colore + Comprendi i valori di colore HEX, RGB, HSV, alfa e copiati prima di incollarli altrove + Copia il formato previsto dalla destinazione + Lo stesso colore visibile può essere scritto in diversi modi. Uno strumento di progettazione, un campo CSS, un valore di colore Android o un editor di immagini potrebbe prevedere un ordine, una gestione alfa o un modello di colore diversi. + Utilizza HEX quando incolli in campi web, di design o di temi che prevedono codici colore compatti. + Utilizza RGB o HSV quando devi regolare manualmente canali, luminosità, saturazione o tonalità. + Controlla l\'alfa separatamente quando la trasparenza è importante perché alcune destinazioni la ignorano o utilizzano un ordine diverso. + Sfoglia la libreria dei colori + Cerca i colori con nome per nome o HEX e mantieni i preferiti in alto + Trova colori con nome riutilizzabili + La libreria colori carica una raccolta di colori con nome, la ordina per nome, filtra in base al nome del colore o al valore esadecimale e memorizza i colori preferiti in modo che le voci importanti siano più facili da raggiungere. È utile quando è necessario un colore con nome reale anziché campionare da un\'immagine. + Cerca in base al nome del colore quando conosci la famiglia, ad esempio rosso, blu, ardesia o menta. + Cerca per HEX quando hai già un colore numerico e desideri trovare voci con nome corrispondenti o vicine. + Contrassegna i colori frequenti come preferiti in modo che vadano avanti rispetto alla libreria generale durante la navigazione. + Utilizza raccolte di gradienti mesh + Scarica le risorse del gradiente mesh disponibili e condividi le immagini selezionate + Utilizza risorse gradiente mesh remote + I gradienti mesh possono caricare una raccolta di risorse remote, scaricare immagini di gradienti mancanti, mostrare l\'avanzamento del download e condividere gradienti selezionati. La disponibilità dipende dall\'accesso alla rete e dall\'archivio risorse remoto, quindi un elenco vuoto in genere significa che le risorse non erano ancora disponibili. + Apri Sfumature mesh dagli strumenti sfumatura quando desideri immagini con gradiente mesh già pronte. + Attendi il caricamento delle risorse o l\'avanzamento del download prima di presumere che la raccolta sia vuota. + Seleziona e condividi le sfumature di cui hai bisogno oppure torna a Gradient Maker quando desideri creare manualmente una sfumatura personalizzata. + Risoluzione degli asset generati + Scegli la dimensione di output prima di generare sfumature, rumore, sfondi, grafica in stile SVG o texture + Genera nella dimensione che ti serve + Le immagini generate non hanno una fotocamera originale su cui ricorrere. Se l\'output è troppo piccolo, il ridimensionamento successivo può ammorbidire i modelli, spostare i dettagli o modificare il modo in cui le risorse vengono affiancate e compresse. + Imposta prima le dimensioni per il caso d\'uso finale, ad esempio sfondo, icona, sfondo, stampa o sovrapposizione. + Utilizza dimensioni più grandi per texture che possono essere ritagliate, ingrandite o riutilizzate in diversi layout. + Esporta versioni di test prima di output enormi perché i dettagli procedurali possono modificare il comportamento della compressione. + Esporta gli sfondi attuali + Recupera gli sfondi Home, Blocco e integrati disponibili dal dispositivo + Salva o condividi gli sfondi installati + Wallpapers Export legge gli sfondi esposti da Android tramite le API degli sfondi di sistema. A seconda del dispositivo, della versione Android, delle autorizzazioni e della variante di build, gli sfondi Home, Blocco o integrati potrebbero essere disponibili separatamente o potrebbero mancare. + Apri Sfondi Esporta per caricare gli sfondi che il sistema consente all\'app di leggere. + Seleziona le voci Home, Blocco o sfondo integrato disponibili che desideri salvare, copiare o condividere. + Se manca uno sfondo o la funzionalità non è disponibile, in genere si tratta di una limitazione del sistema, di un\'autorizzazione o di una variante di build piuttosto che di un\'impostazione di modifica. + Acceleratore per l\'animazione degli angoli + Copia colore come + ESAGONALE + nome + Modello con stuoia verticale per ritagli rapidi di persone con peli più morbidi e gestione dei bordi. + Miglioramento rapido in condizioni di scarsa illuminazione utilizzando la stima della curva Zero-DCE++. + Modello di ripristino delle immagini Restormer per ridurre le strisce di pioggia e gli artefatti dovuti al tempo umido. + Modello di rimozione della deformazione del documento che prevede una griglia di coordinate e rimappa le pagine curve in una scansione più piatta. + Scala della durata del movimento + Controlla la velocità di animazione dell\'app: 0 disabilita il movimento, 1 è normale, valori più alti rallentano le animazioni + Mostra strumenti recenti + Visualizza l\'accesso rapido agli strumenti utilizzati di recente nella schermata principale + Statistiche sull\'utilizzo + Esplora le aperture delle app, i lanci degli strumenti e gli strumenti più utilizzati + Si apre l\'applicazione + Lo strumento si apre + Ultimo strumento + Salvataggi riusciti + Serie di attività + Dati salvati + Il miglior formato + Strumenti utilizzati + %1$d apre • %2$s + Ancora nessuna statistica sull\'utilizzo + Apri alcuni strumenti e appariranno qui automaticamente + Le tue statistiche di utilizzo vengono archiviate solo localmente sul tuo dispositivo. ImageToolbox li utilizza solo per mostrare la tua attività personale, gli strumenti preferiti e gli approfondimenti sui dati salvati + editor modifica foto ritocco immagine regola + ridimensiona converti scala risoluzione dimensioni larghezza altezza jpg jpeg png webp comprimi + comprimere dimensione peso byte mb kb ridurre qualità ottimizzare + proporzioni del taglio del ritaglio del ritaglio + effetto filtro correzione colore regola maschera lut + disegnare pennello matita annotare markup + cifrare crittografare decrittografare la password segreta + rimozione sfondo cancella rimuovi ritaglio trasparente alfa + anteprima visualizzatore galleria ispeziona aperto + punti unisci combina screenshot lungo panorama + url web download immagine collegamento internet + campione contagocce selettore colore esagonale rgb + i campioni di colori della tavolozza estraggono la dominante + Privacy dei metadati EXIF ​​rimuovi la posizione GPS pulita della striscia + confrontare la differenza di differenza prima e dopo + limite ridimensionamento max min megapixel dimensioni morsetto + Strumenti per le pagine dei documenti PDF + il testo ocr riconosce la scansione dell\'estrazione + maglia di colore di sfondo sfumato + sovrapposizione di timbro testo logo filigrana + gif animate converti fotogrammi animati + apng animazione png animato converti fotogrammi + archivio zip comprimi pack decomprimi i file + jxl jpeg xl converte il formato immagine jpg + svg traccia vettoriale converti xml + formato convertire jpg jpeg png webp heic avif jxl bmp + scansione dello scanner per documenti ritaglio prospettico della carta + scansione dello scanner di codici a barre qr + astrofotografia mediana media sovrapposta impilamento + parti di fette di griglia di piastrelle divise + conversione colore esadecimale rgb hsl hsv cmyk lab + webp converte il formato di immagine animata + grana della trama casuale del generatore di rumore + Combinazione del layout della griglia del collage + i livelli di markup annotano la modifica in sovrapposizione + base64 codifica decodifica dati uri + checksum hash md5 sha1 sha256 verifica + sfondo sfumato mesh + metadati EXIF ​​modifica la data GPS della fotocamera + tagliare le tessere con pezzi di griglia divisi + metadati musicali delle copertine audio degli album + sfondo per l\'esportazione dello sfondo + caratteri artistici di testo ascii + ai ml neurale migliora il segmento di lusso + tavolozza dei materiali della libreria colori denominata colori + studio di effetti frammento glsl shader + pdf unisci + pdf diviso in pagine separate + pdf ruotare l\'orientamento delle pagine + pdf riorganizzare riordinare le pagine ordinare + impaginazione dei numeri di pagina del pdf + il testo pdf ocr riconosce l\'estratto ricercabile + testo del logo del timbro filigrana pdf + Disegna il segno della firma in pdf + pdf proteggere la password crittografare il blocco + pdf sbloccare la password decrittografare rimuovere la protezione + comprimere pdf ridurre le dimensioni ottimizzare + pdf scala di grigi nero bianco monocromatico + riparazione pdf correzione ripristino rotto + proprietà dei metadati pdf titolo dell\'autore EXIF + pdf rimuovi elimina pagine + pdf ritagliare i margini di ritaglio + pdf appiattire le annotazioni forma livelli + pdf estrarre immagini + comprimere archivio zip pdf + stampante per stampa pdf + visualizzatore di anteprima pdf letto + immagini in pdf convertire documenti jpg jpeg png + pdf in pagine di immagini esporta jpg png + i commenti sulle annotazioni pdf vengono rimossi in modo pulito + Variante Neutrale + Errore + Sfalsa ombra + Altezza del dente + Gamma di denti orizzontali + Gamma dei denti verticali + Bordo superiore + Bordo destro + Bordo inferiore + Bordo sinistro + Bordo strappato + Reimposta le statistiche di utilizzo + Lo strumento si apre, le statistiche di salvataggio, la sequenza, i dati salvati e il formato principale verranno reimpostati. L\'apertura dell\'app rimarrà invariata. + Audio + File + Profili di esportazione + Salva il profilo corrente + Elimina il profilo di esportazione + Vuoi eliminare il profilo di esportazione \\"%1$s\\"? + Mostra un bordo sottile attorno al disegno e cancella la tela + Bordo del disegno + Ondulato + Elementi dell\'interfaccia utente arrotondati con un bordo ondulato morbido + Scoop + Tacca + Angoli arrotondati intagliati verso l\'interno + Angoli a gradino con doppio taglio + Segnaposto + Utilizza {filename} per inserire il nome file originale senza estensione + Bagliore + Importo base + Importo dell\'anello + Importo del raggio + Larghezza dell\'anello + Prospettiva distorta + Aspetto e funzionalità Java + Taglio + Angolo X + e angolo + Ridimensiona l\'output + Goccia d\'acqua + Lunghezza d\'onda + Fase + Passaggio alto + Maschera di colore + Cromo + Sciogliere + Morbidezza + Feedback + Sfocatura dell\'obiettivo + Ingresso basso + Ingresso elevato + Basso rendimento + Alto rendimento + Effetti di luce + Altezza dell\'urto + Morbidezza dell\'urto + Ondulazione + Ampiezza X + Ampiezza Y + Lunghezza d\'onda X + Lunghezza d\'onda Y + Sfocatura adattiva + Generazione di texture + Genera varie texture procedurali e salvale come immagini + Tipo di trama + Pregiudizio + Tempo + Splendore + Dispersione + Campioni + Coefficiente angolare + Coefficiente di gradiente + Tipo di griglia + Potere a distanza + Scala Y + Sfocatura + Tipo di base + Fattore di turbolenza + Ridimensionamento + Iterazioni + Anelli + Metallo spazzolato + Caustiche + Cellulare + Scacchiera + fBm + Marmo + Plasma + Trapunta + Legna + casuale + Piazza + Esagonale + Ottagonale + Triangolare + Increspato + Rumore VL + Rumore SC + Recente + Nessun filtro utilizzato di recente ancora + generatore di texture metallo spazzolato caustiche cellulare scacchiera marmo plasma trapunta legno mattone mimetico cellula nube crepa tessuto fogliame a nido d\'ape ghiaccio lava nebulosa carta ruggine sabbia fumo pietra terreno topografia acqua increspatura + Mattone + Camuffare + Cella + Nuvola + Crepa + Tessuto + Fogliame + A nido d\'ape + Ghiaccio + Lava + Nebulosa + Carta + Ruggine + Sabbia + Fumo + Calcolo + Terreno + Topografia + Ondulazione dell\'acqua + Legno avanzato + Larghezza della malta + Irregolarità + Smusso + Rugosità + Prima soglia + Seconda soglia + Terza soglia + Morbidezza dei bordi + Larghezza del bordo + Copertura + Dettaglio + Profondità + Ramificazione + Fili orizzontali + Fili verticali + Fuzz + Vene + Illuminazione + Larghezza della fessura + Gelo + Fluire + Crosta + Densità delle nuvole + Stelle + Densità delle fibre + Forza della fibra + Macchie + Corrosione + Vaiolatura + Fiocchi + Frequenza delle dune + Angolo del vento + Increspature + Ciuffi + Scala delle vene + Livello dell\'acqua + Livello della montagna + Erosione + Livello della neve + Conteggio righe + Spessore della linea + Ombreggiatura + Colore dei pori + Colore della malta + Colore mattone scuro + Colore mattone + Evidenzia il colore + Colore scuro + Colore della foresta + Colore della terra + Colore sabbia + Colore di sfondo + Colore della cella + Colore del bordo + Colore del cielo + Colore dell\'ombra + Colore chiaro + Colore della superficie + Colore variazione + Colore crepa + Colore dell\'ordito + Colore della trama + Colore foglia scuro + Colore delle foglie + Colore del bordo + Colore miele + Colore profondo + Colore ghiaccio + Colore gelo + Colore della crosta + Lavare il colore + Colore bagliore + Colore dello spazio + Colore viola + Colore blu + Colore di base + Colore della fibra + Colore della macchia + Colore metallo + Colore ruggine scuro + Colore ruggine + Colore arancione + Colore fumo + Colore della vena + Colore della pianura + Colore dell\'acqua + Colore della roccia + Colore della neve + Colore basso + Colore alto + Colore della linea + Colore poco profondo + Erba + Sporco + Pelle + Calcestruzzo + Asfalto + Muschio + Fuoco + Aurora + Chiazza di petrolio + Acquerello + Flusso astratto + Opale + Acciaio di Damasco + Fulmine + Velluto + Marmorizzazione dell\'inchiostro + Lamina olografica + Bioluminescenza + Vortice Cosmico + Lampada lavica + Orizzonte degli eventi + Fioritura frattale + Tunnel cromatico + Eclissi Corona + Strano attrattore + Corona ferrofluida + Supernova + Iris + Piuma di pavone + Conchiglia del Nautilus + Pianeta inanellato + Densità della lama + Lunghezza della lama + Vento + Irregolarità + Ciuffi + Umidità + Ciottoli + Rughe + Pori + Aggregato + Crepe + Catrame + Indossare + Macchioline + Fibre + Frequenza della fiamma + Fumo + Intensità + Nastri + Bande + Iridescenza + Oscurità + Fiorisce + Pigmento + Bordi + Carta + Diffusione + Simmetria + Nitidezza + Gioco di colori + Latticità + Strati + Pieghevole + Polacco + Rami + Direzione + Lucentezza + Pieghe + Piumaggio + Equilibrio dell\'inchiostro + Spettro + Pieghe + Diffrazione + Braccia + Intrecciare + Bagliore del nucleo + BLOB + Inclinazione del disco + Dimensione dell\'orizzonte + Larghezza del disco + Lente + Petali + Arricciare + Filigrana + Sfaccettature + Curvatura + Dimensione della luna + Dimensioni della corona + Raggi + Anello di diamanti + Lobi + Densità dell\'orbita + Spessore + Picchi + Lunghezza della punta + Dimensioni del corpo + Metallico + Raggio d\'urto + Larghezza della conchiglia + Buttato fuori + Dimensioni della pupilla + Dimensione dell\'iride + Variazione di colore + Cattura + Dimensione degli occhi + Densità della punta + Gira + Camere + Apertura + Creste + Perlescenza + Dimensioni del pianeta + Inclinazione dell\'anello + Larghezza dell\'anello + Atmosfera + Colore sporco + Colore erba scura + Colore dell\'erba + Colore della punta + Colore terra scura + Colore secco + Colore ciottolo + Colore della pelle + Colore cemento + Colore catrame + Colore asfalto + Colore pietra + Colore polvere + Colore del suolo + Colore muschio scuro + Colore muschio + Colore rosso + Colore del nucleo + Colore verde + Colore ciano + Colore magenta + Colore oro + Colore della carta + Colore del pigmento + Colore secondario + Primo colore + Secondo colore + Colore rosa + Colore acciaio scuro + Colore acciaio + Colore acciaio chiaro + Colore ossido + Colore alone + Colore del bullone + Colore velluto + Colore brillante + Colore inchiostro blu + Colore inchiostro rosso + Colore dell\'inchiostro scuro + Colore argento + Colore giallo + Colore del tessuto + Colore del disco + Colore caldo + Colore delle lenti + Colore esterno + Colore interno + Colore corona + Colore freddo + Colore caldo + Colore delle nuvole + Colore della fiamma + Colore della piuma + Colore della conchiglia + Colore perla + Colore del pianeta + Colore dell\'anello + Elimina i file originali dopo il salvataggio + Verranno eliminati solo i file di origine elaborati correttamente + File originali eliminati: %1$d + Impossibile eliminare i file originali: %1$d + File originali eliminati: %1$d, errore: %2$d + Gesti del foglio + Consenti il ​​trascinamento dei fogli delle opzioni espansi + Sottocampionamento della crominanza + Profondità di bit + Senza perdite + %1$dbit + Rinomina batch + Rinominare più file utilizzando modelli di nome file + rinominare in batch i file nome file sequenza modello estensione data + Scegli i file da rinominare + Modello del nome file + Rinominare + Origine della data + Data EXIF ​​presa + Data di modifica del file + Data di creazione del file + Data attuale + Data manuale + Data manuale + Orario manuale + La data selezionata non è disponibile per alcuni file. Per loro verrà utilizzata la data corrente. + Inserisci un modello di nome file + Il modello produce un nome file non valido o eccessivamente lungo + Il modello produce nomi di file duplicati + Tutti i nomi di file corrispondono già al modello + Questo modello utilizza token che non sono disponibili per la ridenominazione batch + Il nome rimarrà lo stesso + File rinominati correttamente + Impossibile rinominare alcuni file + Il permesso non è stato concesso + Utilizza il nome del file originale senza estensione, aiutandoti a mantenere intatta l\'identificazione della fonte. Supporta anche l\'affettatura con \\o{start:end}, la traslitterazione con \\o{t} e la sostituzione con \\o{s/old/new/}. + Contatore ad incremento automatico. Supporta la formattazione personalizzata: \\c{padding} (ad esempio \\c{3} -&gt; 001), \\c{start:step} o \\c{start:step:padding}. + Il nome della cartella principale in cui si trovava il file originale. + La dimensione del file originale. Supporta le unità: \\z{b}, \\z{kb}, \\z{mb} o semplicemente \\z per il formato leggibile dall\'uomo. + Genera un identificatore univoco universale (UUID) casuale per garantire l\'unicità del nome file. + La ridenominazione dei file non può essere annullata. Assicurati che i nuovi nomi siano corretti prima di continuare. + Conferma la ridenominazione + Impostazioni del menu laterale + Scala del menù + Menù alfa + Bilanciamento automatico del bianco + Ritaglio + Controllo delle filigrane nascoste + Magazzinaggio + Limite della cache + Svuota automaticamente la cache quando supera questa dimensione + Intervallo di pulizia + La frequenza con cui verificare se è necessario svuotare la cache + All\'avvio dell\'app + 1 giorno + 1 settimana + 1 mese + Svuota automaticamente la cache dell\'app in base al limite e all\'intervallo selezionati + Area coltivata mobile + Permette di spostare l\'intera area di ritaglio trascinandola al suo interno + Unisci GIF + Combina più clip GIF in un\'unica animazione + Ordine delle clip GIF + Inversione + Gioca al contrario + Boomerang + Gioca in avanti e poi indietro + Normalizza le dimensioni del fotogramma + Ridimensiona le cornici per adattarle alla clip più grande; spegnere per preservare la scala originale + Ritardo tra le clip (ms) + Cartella + Seleziona tutte le immagini da una cartella e dalle sue sottocartelle + Ricerca duplicati + Trova copie esatte e immagini visivamente simili + ricerca duplicati immagini simili copie esatte foto pulizia archiviazione dHash SHA-256 + Scegli le immagini per trovare i duplicati + Sensibilità alla somiglianza + Copie esatte + Immagini simili + Seleziona tutti i duplicati esatti + Seleziona tutto tranne quelli consigliati + Nessun duplicato trovato + Prova ad aggiungere più immagini o ad aumentare la sensibilità alla somiglianza + Impossibile leggere le %1$d immagini + %1$s • %2$s recuperabile + %1$d gruppi • %2$s recuperabile + %1$d selezionato • %2$s + Questa operazione non può essere annullata. Android potrebbe chiederti di confermare l\'eliminazione in una finestra di dialogo di sistema. + Non trovato + Muoviti per iniziare + Sposta alla fine + \ No newline at end of file diff --git a/core/resources/src/main/res/values-iw/strings.xml b/core/resources/src/main/res/values-iw/strings.xml new file mode 100644 index 0000000..e44e3fb --- /dev/null +++ b/core/resources/src/main/res/values-iw/strings.xml @@ -0,0 +1,3742 @@ + + + + + חריג + ערוך 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 + היפר-קישורים + קבצים מצורפים + קווים + חלונות קופצים + חותמות + צורות + הערות טקסט + סימון טקסט + שדות טופס + סימון + לֹא יְדוּעַ + הערות + בטל קבוצה + הוסף צל טשטוש מאחורי השכבה עם צבע והיסטים הניתנים להגדרה + עיוות מודע לתוכן + אין מספיק זיכרון להשלמת פעולה זו. נסה להשתמש בתמונה קטנה יותר, לסגור אפליקציות אחרות או להפעיל מחדש את האפליקציה. + עובדים מקבילים + תמונות אלו לא נשמרו מכיוון שהקבצים שהומרו יהיו גדולים יותר מהקבצים המקוריים. בדוק רשימה זו לפני מחיקת תמונות מקוריות. + קבצים שדילגו עליהם: %1$s + יומני אפליקציות + צפה ביומנים של האפליקציה כדי לזהות את הבעיות + מועדפים בכלים מקובצים + מוסיף מועדפים כלשונית כאשר הכלים מקובצים לפי סוג + הצג את המועדף כאחרון + מעביר את כרטיסיית הכלים המועדפים לסוף + מרווח אופקי + מרווח אנכי + אנרגיה לאחור + השתמש במפת אנרגיה פשוטה של ​​גודל שיפוע במקום באלגוריתם אנרגיה קדימה + הסר אזור עם מסכה + תפרים יעברו באזור המסוכה, ויסירו רק את האזור הנבחר במקום להגן עליו + VHS NTSC + הגדרות NTSC מתקדמות + כוונון אנלוגי נוסף עבור VHS וחפצי שידור חזקים יותר + דימום כרומה + בלאי סרט + מַעֲקָב + מריחת לומה + צִלצוּל + שֶׁלֶג + השתמש בשדה + סוג מסנן + מסנן לומה קלט + קלט chroma lowpass + דמודולציה של כרומה + שינוי שלבים + קיזוז שלבים + החלפת ראש + גובה החלפת ראש + היסט החלפת ראש + החלפת ראש + מיקום קו אמצע + ריצוד קו אמצע + מעקב אחר גובה הרעש + גל מעקב + מעקב אחר שלג + מעקב אחר אניזוטרופיה של שלג + רעש מעקב + מעקב אחר עוצמת הרעש + רעש מורכב + תדר רעש מורכב + עוצמת רעש מורכב + פרט רעש מורכב + תדירות הצלצול + כוח מצלצל + רעש לומה + תדר רעש Luma + עוצמת הרעש של Luma + פירוט רעש Luma + רעש כרומה + תדר רעש Chroma + עוצמת רעש הכרומה + פירוט רעש כרומה + עוצמת שלג + אניזוטרופיה של שלג + רעש פאזה Chroma + שגיאת שלב Chroma + השהיית Chroma אופקית + השהיית Chroma אנכית + מהירות קלטת VHS + אובדן כרומה של VHS + עוצמת חידוד VHS + תדר חידוד VHS + עוצמת גל קצה VHS + מהירות גלי קצה VHS + תדר גלי קצה VHS + פירוט גלי קצה של VHS + פלט chroma lowpass + קנה מידה אופקי + קנה מידה אנכי + גורם קנה מידה X + גורם קנה מידה Y + מזהה סימני מים נפוצים של תמונה וצובעת אותם עם LaMa. מוריד דגמי איתור וצביעה אוטומטית + דוטרנופיה + הרחב את התמונה + מסיר רקע מבוסס פילוח אובייקטים באמצעות YOLO v11 Segmentation + הצג זווית קו + מציג את סיבוב הקו הנוכחי במעלות בזמן הציור + אמוג\'י מונפש + הצג אמוג\'י זמין כהנפשות + שמור בתיקייה המקורית + שמור קבצים חדשים ליד הקובץ המקורי במקום התיקיה שנבחרה + סימן מים PDF + אין מספיק זיכרון + סביר להניח שהתמונה גדולה מדי לעיבוד במכשיר זה, או שהזיכרון הפנוי נגמר למערכת. נסה להפחית את רזולוציית התמונה, לסגור אפליקציות אחרות או לבחור קובץ קטן יותר. + תפריט ניפוי באגים + תפריט לבדיקת פונקציות האפליקציה, זה לא נועד להופיע במהדורת הייצור + ספק + PaddleOCR דורש דגמי ONNX נוספים במכשיר שלך. האם ברצונך להוריד נתונים %1$s? + אוניברסלי + קוריאנית + לטינית + מזרח סלאבי + תאילנדית + יוונית + אנגלית + קירילי + ערבית + דוואנגרי + טמילית + טלוגו + שאדר + Shader מוגדר מראש + לא נבחר הצללה + סטודיו Shader + צור, ערוך, אמת, ייבא וייצא הצללות קטעים מותאמות אישית + Shaders שמורים + פתח הצללות שמורות, שכפל, ייצא, שתף או מחק + הצללה חדשה + קובץ Shader + .itshader JSON + %1$d פרמטרים + מקור Shader + כתוב רק את גוף ה- void main(). השתמש ב-textureCoordinate עבור קואורדינטות UV ו-inputImageTexture בתור דגימת המקור. + פונקציות + פונקציות עוזר אופציונליות, קבועים ומבנים שהוכנסו לפני void main(). מדים נוצרים מפרמים. + הוסף כאן מדים כאשר ההצללה זקוקה לערכים הניתנים לעריכה. + אפס הצללה + טיוטת ההצללה הנוכחית תימחק + הצללה לא חוקית + גרסת הצללה לא נתמכת %1$d. גרסה נתמכת: %2$d. + שם הצללה לא יכול להיות ריק. + מקור הצללה לא חייב להיות ריק. + מקור Shader מכיל תווים שאינם נתמכים: %1$s. השתמש במקור ASCII GLSL בלבד. + הפרמטר \\\"%1$s\\\" מוכרז יותר מפעם אחת. + שמות פרמטרים לא יכולים להיות ריקים. + הפרמטר \\\"%1$s\\\" משתמש בשם GPUIimage שמור. + הפרמטר \\\"%1$s\\\" חייב להיות מזהה GLSL חוקי. + לפרמטר \\\"%1$s\\\" יש %2$s סוג ערך \\\"%3$s\\\", צפוי \\\"%4$s\\\". + הפרמטר \\\"%1$s\\\" אינו יכול להגדיר מינימום או מקסימום עבור ערכי bool. + לפרמטר \\\"%1$s\\\" יש דקות גדולות מהמקסימום. + ברירת המחדל של הפרמטר \\\"%1$s\\\" נמוכה ממינימום. + ברירת המחדל של הפרמטר \\\"%1$s\\\" גדולה מהמקסימום. + Shader חייב להכריז על \\\"uniform sampler2D %1$s;\\\". + Shader חייב להכריז על \\\"variing vec2 %1$s;\\\". + Shader חייב להגדיר \\\"void main()\\\". + Shader חייב לכתוב צבע ל-gl_FragColor. + הצללות של ShaderToy mainImage אינן נתמכות. השתמש בחוזה main() void של GPUImage. + מדי ShaderToy מונפש \\\"iTime\\\" אינם נתמכים. + המדים \\\"iFrame\\\" של ShaderToy מונפשים אינם נתמכים. + מדים של ShaderToy \\\"iResolution\\\" אינם נתמכים. + מרקמי iChannel של ShaderToy אינם נתמכים. + טקסטורות חיצוניות אינן נתמכות. + הרחבות מרקם חיצוני אינן נתמכות. + פרמטרי הצללה של Libretro אינם נתמכים. + רק מרקם קלט אחד נתמך. הסר את \\\"מדים %1$s %2$s;\\\". + יש להצהיר על הפרמטר \\\"%1$s\\\" כ\\\"אחיד %2$s %1$s;\\\". + הפרמטר \\\"%1$s\\\" מוכרז כ\\\"אחיד %2$s %1$s;\\\", צפוי \\\"אחיד %3$s %1$s;\\\". + קובץ Shader ריק. + קובץ Shader חייב להיות אובייקט ‎.itshader JSON עם שדות גרסה, שם ושדות הצללה. + קובץ Shader אינו JSON חוקי או אינו תואם לפורמט .itshader. + שדה \\\"%1$s\\\" נדרש. + %1$s נדרש. + %1$s \\\"%2$s\\\" אינו נתמך. סוגים נתמכים: %3$s. + %1$s נדרש עבור פרמטרים של \\\"%2$s\\\". + %1$s חייב להיות מספר סופי. + %1$s חייב להיות מספר שלם. + %1$s חייב להיות נכון או לא נכון. + %1$s חייב להיות מחרוזת צבע, מערך RGB/RGBA או אובייקט צבעוני. + %1$s חייב להשתמש בפורמט #RRGGBB או #RRGGBBAA. + %1$s חייב להכיל ערוצי צבע הקסדצימליים חוקיים. + %1$s חייב להכיל 3 או 4 ערוצי צבע. + %1$s חייב להיות בין 0 ל-255. + %1$s חייב להיות מערך של שני מספרים או אובייקט עם x ו-y. + %1$s חייב להכיל בדיוק 2 מספרים. + לְשַׁכְפֵּל + נקה תמיד EXIF + הסר נתוני EXIF ​​של תמונה בשמירה, גם כאשר כלי מבקש לשמור מטא נתונים + עזרה וטיפים + %1$d מדריכים + פתח את הכלי + למד את הכלים העיקריים, אפשרויות הייצוא, זרימות PDF, כלי עזר צבע ותיקונים לבעיות נפוצות + מתחילים + בחר כלים, ייבא קבצים, שמור תוצאות ועשה שימוש חוזר בהגדרות מהר יותר + עריכת תמונה + שנה גודל, חתוך, סינון, מחק רקעים, צייר וסימן מים + קבצים ומטא נתונים + המר פורמטים, השווה פלט, הגן על פרטיות ושליטה בשמות קבצים + PDF ומסמכים + צור קובצי PDF, סרוק מסמכים, דפי OCR והכן קבצים לשיתוף + טקסט, QR ונתונים + זיהוי טקסט, סרוק קודים, קידוד Base64, בדיקת hashes וקבצי חבילה + כלי צבע + בחר צבעים, בנה פלטות, חקור ספריות צבעים וצור מעברי צבע + כלים יצירתיים + צור אמנות SVG, ASCII, טקסטורות רעש, הצללות ותמונות ניסיוניות + פתרון בעיות + תקן בעיות של קבצים כבדים, שקיפות, שיתוף, ייבוא ​​ודיווח + בחר את הכלי הנכון + השתמש בקטגוריות, חיפוש, מועדפים ושתף פעולות כדי להתחיל במקום הנכון + התחל מנקודת הכניסה הטובה ביותר + ל-Image Toolbox יש הרבה כלים ממוקדים, ורבים מהם חופפים במבט ראשון. התחל מהמשימה שברצונך לסיים, ולאחר מכן בחר בכלי ששולט בחלק החשוב ביותר של התוצאה: גודל, פורמט, מטא נתונים, טקסט, דפי PDF, צבעים או פריסה. + השתמש בחיפוש כאשר אתה יודע את שם הפעולה, כגון שינוי גודל, PDF, EXIF, OCR, QR או צבע. + פתח קטגוריה כאשר אתה יודע רק את סוג המשימה, ולאחר מכן הצמד כלים תכופים כמועדפים. + כאשר אפליקציה אחרת משתפת תמונה לתוך Image Toolbox, בחר את הכלי מזרימת גיליון השיתוף. + ייבא, שמור ושתף תוצאות + הבן את הזרימה הרגילה מבחירת קלט ועד לייצוא הקובץ הערוך + עקוב אחר זרימת העריכה הנפוצה + רוב הכלים עוקבים אחר אותו קצב: בחר קלט, התאם אפשרויות, תצוגה מקדימה ואז שמור או שתף. הפרט החשוב הוא ששמירה יוצרת קובץ פלט, בעוד שהשיתוף הוא בדרך כלל הטוב ביותר להעברת מהירה לאפליקציה אחרת. + בחר קובץ אחד עבור כלים של תמונה בודדת או מספר קבצים עבור כלי אצווה כאשר הבורר מאפשר זאת. + בדוק את בקרות התצוגה המקדימה והפלט לפני השמירה, במיוחד אפשרויות פורמט, איכות ושם קובץ. + השתמש בשיתוף למסירה מהירה, או שמור כאשר אתה צריך עותק מתמשך בתיקייה שנבחרה. + עבוד עם תמונות רבות בבת אחת + כלי אצווה הם הטובים ביותר למשימות שינוי גודל, המרה, דחיסה ושמות חוזרים ונשנים + הכן אצווה צפויה + עיבוד אצווה הוא המהיר ביותר כאשר כל פלט צריך לפעול לפי אותם כללים. זה גם המקום הכי קל לעשות טעות גדולה, אז בחר שמות, איכות, מטא נתונים והתנהגות תיקיות לפני שמתחילים בייצוא ארוך. + בחר את כל התמונות הקשורות יחד ושמור את הסדר אם הפלט תלוי ברצף. + הגדר כללי שינוי גודל, פורמט, איכות, מטא נתונים ושם קובץ פעם אחת לפני תחילת הייצוא. + הפעל תחילה קבוצת בדיקה קטנה כאשר קבצי המקור גדולים או כאשר פורמט היעד חדש עבורך. + עריכה בודדת מהירה + השתמש בעורך ה-All-in-One כאשר אתה זקוק למספר שינויים פשוטים בתמונה אחת + סיים תמונה אחת ללא דילוג כלים + עריכה יחידה שימושית כאשר התמונה זקוקה לשרשרת קטנה של שינויים במקום פעולה אחת מיוחדת. זה בדרך כלל מהיר יותר לניקוי מהיר, תצוגה מקדימה וייצוא עותק סופי אחד מבלי לבנות זרימת עבודה אצווה. + פתח עריכה יחידה עבור תמונה אחת שצריכה שינויים נפוצים, סימון, חיתוך, סיבוב או ייצוא. + החל עריכות בסדר שמשנה את הכי פחות פיקסלים תחילה, כגון חיתוך לפני מסננים ומסננים לפני הדחיסה הסופית. + השתמש בכלי מיוחד במקום זאת כאשר אתה זקוק לעיבוד אצווה, יעדים מדויקים בגודל קובץ, פלט PDF, OCR או ניקוי מטא נתונים. + השתמש בהגדרות והגדרות מראש + שמור אפשרויות חוזרות ונשנות וכוונן את האפליקציה לזרימת העבודה המועדפת עליך + הימנע מלחזור על אותה הגדרה + הגדרות והגדרות מועילות כאשר אתה מייצא את אותו סוג של קובץ לעתים קרובות. הגדרה מראש טובה הופכת רשימת בדיקה ידנית חוזרת ונשנית להקשה אחת, בעוד שהגדרות גלובליות מגדירות את ברירות המחדל שמהן מתחילים כלים חדשים. + צור הגדרות מוגדרות מראש עבור גדלי פלט נפוצים, פורמטים, ערכי איכות או תבניות שמות. + עיין בהגדרות עבור פורמט ברירת מחדל, איכות, תיקיית שמירה, שם קובץ, בוחר והתנהגות הממשק. + גבה את ההגדרות לפני התקנה מחדש של האפליקציה או מעבר למכשיר אחר. + הגדר ברירות מחדל שימושיות + בחר פורמט ברירת מחדל, איכות, מצב קנה מידה, שינוי גודל סוג ומרחב צבע פעם אחת + לגרום לכלים חדשים להתחיל קרוב יותר ליעד שלך + ערכי ברירת מחדל הם לא רק העדפות קוסמטיות. הם מחליטים איזה פורמט, איכות, התנהגות שינוי גודל, מצב קנה מידה ומרחב צבע מופיעים ראשונים בכלים רבים, מה שעוזר להימנע מבחירה מחדש של אותן אפשרויות בכל ייצוא. + הגדר פורמט ואיכות תמונה כברירת מחדל כדי להתאים ליעד הרגיל שלך, כגון JPEG עבור תמונות או PNG/WebP עבור אלפא. + כוונן ברירת המחדל של סוג שינוי גודל ומצב קנה מידה אם אתה מייצא לעתים קרובות עבור מימדים קפדניים, פלטפורמות חברתיות או דפי מסמכים. + שנה את ברירת המחדל של מרחב הצבע רק כאשר זרימת העבודה שלך זקוקה לטיפול עקבי בצבע בין צגים, הדפסה או עורכים חיצוניים. + בוחר ומשגר + השתמש בהגדרות הבוחר כאשר האפליקציה פותחת יותר מדי דיאלוגים או מתחילה במקום הלא נכון + הפוך את פתיחת הקבצים להתאים להרגל שלך + הגדרות הבורר והמפעיל קובעות אם Image Toolbox מתחיל מהמסך הראשי, מכלי או זרימת בחירת קבצים. אפשרויות אלה שימושיות במיוחד כאשר אתה בדרך כלל נכנס מגיליון השיתוף של אנדרואיד או כאשר אתה רוצה שהאפליקציה תרגיש יותר כמו משגר כלים. + השתמש בהגדרות של בורר תמונות אם בורר המערכת מסתיר קבצים, מציג יותר מדי פריטים אחרונים או מטפל בצורה גרועה בקבצי ענן. + אפשר בחירה לדלג רק עבור כלים שבהם אתה בדרך כלל נכנס משיתוף או שכבר מכיר את קובץ המקור. + סקור את מצב המשגר ​​אם אתה רוצה ש-Image Toolbox יתנהג יותר כמו בוחר כלים מאשר מסך בית רגיל. + מקור תמונה + בחר את מצב הבורר שעובד הכי טוב עם גלריות, מנהלי קבצים וקבצי ענן + בחר קבצים מהמקור הנכון + הגדרות מקור תמונה משפיעות על האופן שבו האפליקציה מבקשת מאנדרואיד תמונות. הבחירה הטובה ביותר תלויה אם הקבצים שלך נמצאים בגלריית המערכת, בתיקיית מנהל קבצים, ספק ענן או אפליקציה אחרת המשתפת תוכן זמני. + השתמש בבורר ברירת המחדל כאשר אתה רוצה את החוויה המשולבת ביותר במערכת עבור תמונות גלריה נפוצות. + החלף מצב בוחר אם אלבומים, תיקיות, קבצי ענן או פורמטים לא סטנדרטיים אינם מופיעים היכן שאתה מצפה. + אם קובץ משותף נעלם לאחר עזיבת אפליקציית המקור, פתח אותו מחדש באמצעות בורר מתמיד או שמור תחילה עותק מקומי. + מועדפים ושיתוף כלים + שמור על מיקוד המסך הראשי והסתיר כלים שאינם הגיוניים בזרימת שיתוף + הפחת את רעש רשימת הכלים + מועדפים והגדרות נסתרות לשיתוף שימושיים כאשר אתה משתמש רק בכמה כלים מדי יום. הם גם שומרים על זרימת השיתוף מעשית על ידי הסתרת כלים שאינם יכולים להשתמש בסוג הקובץ שאפליקציה אחרת שולחת. + הצמד כלים תכופים כך שיישאר קל להגיע אליהם גם כאשר הכלים מקובצים לפי קטגוריות. + הסתר כלים מתזרימי שיתוף כאשר הם לא רלוונטיים לקבצים המגיעים מאפליקציה אחרת. + השתמש בהגדרות ההזמנה המועדפות אם אתה מעדיף מועדפים בסוף או מקובצים עם כלים קשורים. + גיבוי הגדרות + העבר הגדרות קבועות מראש, העדפות והתנהגות אפליקציה להתקנה אחרת בבטחה + שמור על ההגדרה שלך ניידת + גיבוי ושחזור מועילים ביותר לאחר שכווננת הגדרות מוגדרות מראש, תיקיות, כללי שמות קבצים, סדר הכלים והעדפות חזותיות. גיבוי מאפשר לך להתנסות בהגדרות או להתקין מחדש את האפליקציה מבלי לבנות מחדש את זרימת העבודה שלך מהזיכרון. + צור גיבוי לאחר שינוי הגדרות קבועות מראש, התנהגות שם קובץ, פורמטים המוגדרים כברירת מחדל או סידור הכלים. + אחסן את הגיבוי במקום כלשהו מחוץ לתיקיית האפליקציה אם אתה מתכנן לנקות נתונים, להתקין מחדש או להעביר מכשירים. + שחזר הגדרות לפני עיבוד אצוות חשובות כך שברירות המחדל והתנהגות השמירה תואמות את ההגדרה הישנה שלך. + שנה גודל והמר תמונות + שנה גודל, פורמט, איכות ומטא נתונים עבור תמונה אחת או רבות + צור עותקים בגודל שונה + שינוי גודל והמרה הוא הכלי העיקרי למידות הניתנות לחיזוי ולקבצי פלט קלים יותר. השתמש בו כאשר גודל התמונה, פורמט הפלט והתנהגות המטא נתונים חשובים בו זמנית. + בחר תמונה אחת או יותר ולאחר מכן בחר אם הממדים, קנה המידה או גודל הקובץ הכי חשובים. + בחר את פורמט הפלט ואת האיכות; השתמש ב-PNG או ב-WebP כאשר יש לשמור על שקיפות. + הצג את התוצאה בתצוגה מקדימה, ולאחר מכן שמור או שתף את העותקים שנוצרו מבלי לשנות את מסמכי המקור. + שנה את הגודל לפי גודל הקובץ + מקד למגבלת גודל כאשר אתר אינטרנט, מסנג\'ר או טופס דוחים תמונות כבדות + להתאים למגבלות העלאה קפדניות + שינוי גודל לפי גודל קובץ עדיף על ניחוש ערכי איכות כאשר היעד מקבל רק קבצים מתחת לגבול מסוים. הכלי עשוי להתאים דחיסה וממדים כדי להגיע ליעד תוך ניסיון לשמור על התוצאה שמישה. + הזן את גודל היעד מעט מתחת למגבלת ההעלאה האמיתית כדי להשאיר מקום לשינויי מטא נתונים וספקים. + העדיפו פורמטים חסרי אובדן עבור תמונות כאשר היעד קטן; PNG יכול להישאר גדול גם לאחר שינוי גודל. + בדוק פרצופים, טקסט וקצוות לאחר הייצוא מכיוון שמטרות גודל אגרסיביות עלולות להציג חפצים גלויים. + הגבל שינוי גודל + כווץ רק תמונות החורגות מהרוחב, הגובה או אילוצי הקובץ המרביים שלך + הימנע משינוי גודל של תמונות שכבר בסדר + Limit Resize שימושי עבור תיקיות מעורבות שבהן חלק מהתמונות ענקיות ואחרות כבר מוכנות. במקום לאלץ כל קובץ לעבור את אותו שינוי גודל, הוא שומר על תמונות מקובלות קרוב יותר לאיכותן המקורית. + הגדר מידות או מגבלות מקסימליות בהתבסס על היעד במקום לשנות את הגודל של כל קובץ באופן עיוור. + אפשר התנהגות בסגנון דילוג אם בגדול כאשר ברצונך להימנע מתפוקות שהופכות כבדות יותר ממקורות. + השתמש בזה עבור אצוות ממצלמות שונות, צילומי מסך או הורדות שבהן גדלי המקור משתנים מאוד. + חתוך, סובב ויישר + מסגר את האזור החשוב לפני ייצוא או שימוש בתמונה בכלים אחרים + נקה את הקומפוזיציה + חיתוך תחילה הופך את המסננים המאוחרים יותר, OCR, דפי PDF ושיתוף תוצאות לנקים יותר. + פתח את חיתוך ובחר את התמונה שברצונך להתאים. + השתמש בחיתוך חופשי או ביחס רוחב-גובה כאשר הפלט חייב להתאים לפוסט, מסמך או אווטאר. + סובב או הפוך לפני השמירה כך שכלים מאוחרים יותר יקבלו את התמונה המתוקנת. + החל מסננים והתאמות + בנה ערימת מסננים הניתנת לעריכה וצפה בתצוגה מקדימה של התוצאה לפני הייצוא + כוונן את מראה התמונה + מסננים שימושיים לתיקונים מהירים, פלט מסוגנן והכנת תמונות ל-OCR או הדפסה. שינויים קטנים בקונטרסט, בחדות ובבהירות יכולים להיות חשובים יותר מאפקטים כבדים כאשר התמונה מכילה טקסט או פרטים עדינים. + הוסף מסננים אחד אחד ופקח עין על התצוגה המקדימה לאחר כל שינוי. + התאם את הבהירות, הניגודיות, החדות, הטשטוש, הצבע או המסכות בהתאם למשימה. + ייצא רק כאשר התצוגה המקדימה תואמת למכשיר היעד, המסמך או הפלטפורמה החברתית שלך. + תחילה תצוגה מקדימה + בדוק תמונות לפני בחירת כלי עריכה, המרה או ניקוי כבדים יותר + תבדוק מה קיבלת בפועל + תצוגה מקדימה של תמונה שימושית כאשר קובץ מגיע מצ\'אט, אחסון בענן או אפליקציה אחרת ואינך בטוח מה הוא מכיל. בדיקת מידות, שקיפות, כיוון ואיכות חזותית תחילה עוזרת לבחור בכלי המעקב הנכון. + פתח את התצוגה המקדימה של תמונה כאשר אתה צריך לבדוק מספר תמונות לפני שתחליט מה לעשות איתן. + חפש בעיות סיבוב, אזורים שקופים, חפצי דחיסה וממדים גדולים באופן בלתי צפוי. + עבור לשינוי גודל, חיתוך, השוואה, EXIF ​​או מסיר רקע רק לאחר שאתה יודע מה צריך לתקן. + הסר או שחזר רקע + מחק רקעים באופן אוטומטי או צמצם את הקצוות באופן ידני עם כלי שחזור + שמור את הנושא, הסר את השאר + הסרת רקע עובדת בצורה הטובה ביותר כאשר אתה משלב בחירה אוטומטית עם ניקוי ידני קפדני. פורמט הייצוא הסופי חשוב לא פחות מהמסכה, מכיוון ש-JPEG ישטוח אזורים שקופים גם אם התצוגה המקדימה נראית נכונה. + התחל עם הסרה אוטומטית אם הנושא מופרד בבירור מהרקע. + התקרב ועבור בין מחיקה לשחזור כדי לתקן שיער, פינות, צללים ופרטים קטנים. + שמור כ-PNG או WebP כאשר אתה צריך שקיפות בקובץ הסופי. + צייר, סמן וסימן מים + הוסף חיצים, הערות, הדגשות, חתימות או שכבות חוזרות של סימני מים + הוסף מידע גלוי + כלי סימון עוזרים להסביר תמונה, בעוד סימון מים עוזר למתג או להגן על קבצים מיוצאים. + השתמש ב-Draw כאשר אתה צריך הערות ביד חופשית, צורות, הדגשה או הערות מהירות. + השתמש בסימון מים כאשר אותו טקסט או סימן תמונה אמור להופיע באופן עקבי בפלטים. + בדוק את האטימות, המיקום והקנה מידה לפני הייצוא כדי שהסימן יהיה גלוי אך לא יסיח את הדעת. + צייר ברירות מחדל + הגדר רוחב קו, צבע, מצב נתיב, נעילת כיוון וזכוכית מגדלת לסימון מהיר יותר + תנו לציור להרגיש צפוי + הגדרות ציור מועילות כאשר אתה מוסיף הערות לצילומי מסך, מסמן מסמכים או חותם על תמונות לעתים קרובות. ברירות מחדל מפחיתות את זמן ההגדרה, בעוד שהזכוכית המגדלת ונעילת הכיוון מקלים על עריכה מדויקת במסכים קטנים. + הגדר רוחב קו ברירת מחדל וצייר צבע לערכים שבהם אתה משתמש הכי הרבה עבור חיצים, הדגשות או חתימות. + השתמש במצב ברירת מחדל של נתיב כאשר אתה בדרך כלל מצייר את אותו סוג של קווים, צורות או סימון. + אפשר זכוכית מגדלת או נעילת כיוון כאשר קלט אצבע מקשה על מיקום מדויק של פרטים קטנים. + פריסות ריבוי תמונות + בחר את הכלי המתאים לריבוי תמונות לפני סידור צילומי מסך, סריקות או רצועות השוואה + השתמש בכלי הפריסה הנכון + הכלים האלה נשמעים דומים, אבל כל אחד מהם מכוון לסוג אחר של פלט ריבוי תמונות. בחירה נכונה תחילה נמנעת מאבק בפקדי פריסה שתוכננו לתוצאה שונה. + השתמש ב-Colage Maker עבור פריסות מעוצבות עם מספר תמונות בקומפוזיציה אחת. + השתמש בתפירת תמונה או ערימה כאשר תמונות צריכות להפוך לרצועה אנכית או אופקית אחת ארוכה. + השתמש בפיצול תמונה כאשר תמונה אחת גדולה צריכה להפוך לכמה חלקים קטנים יותר. + המרת פורמטים של תמונה + צור JPEG, PNG, WebP, AVIF, JXL ועותקים אחרים מבלי לערוך פיקסלים באופן ידני + בחר את הפורמט לתפקיד + פורמטים שונים טובים יותר עבור תמונות, צילומי מסך, שקיפות, דחיסה או תאימות. ההמרה הבטוחה ביותר כאשר אתה מבין במה תומכת אפליקציית היעד והאם המקור מכיל אלפא, אנימציה או מטא נתונים שברצונך לשמור. + השתמש ב-JPEG לתמונות תואמות, ב-PNG לתמונות ללא הפסדים ואלפא, וב-WebP לשיתוף קומפקטי. + התאם איכות כאשר הפורמט תומך בה; ערכים נמוכים יותר מקטינים את הגודל אך יכולים להוסיף חפצים. + שמור את המסמכים המקוריים עד שתאמת שהקבצים שהומרו פתוחים כהלכה באפליקציית היעד. + בדוק EXIF ​​ופרטיות + הצג, ערוך או הסר מטא נתונים לפני שיתוף תמונות רגישות + שליטה בנתוני תמונה נסתרים + EXIF יכול להכיל נתוני מצלמה, תאריכים, מיקום, כיוון ופרטים אחרים שאינם נראים בתמונה. כדאי לבדוק לפני שיתוף פומבי, דוחות תמיכה, רישומי שוק או כל תמונה שעלולה לחשוף מקום פרטי. + פתח את כלי EXIF ​​לפני שיתוף תמונות שעשויות להכיל מידע על מיקום או מכשיר פרטי. + הסר שדות רגישים או ערוך תגים שגויים כגון תאריך, כיוון, מחבר או נתוני מצלמה. + שמור עותק חדש ושתף את העותק במקום המקור כאשר הפרטיות חשובה. + ניקוי EXIF ​​אוטומטי + הסר מטא נתונים כברירת מחדל תוך כדי החלטה אם יש לשמור תאריכים + הפוך את הפרטיות לברירת המחדל + קבוצת ההגדרות של EXIF ​​שימושית כאשר אתה רוצה שניקוי הפרטיות יתרחש אוטומטית במקום לזכור אותו בכל ייצוא. שמירה על תאריך שעה היא החלטה נפרדת מכיוון שתאריכים יכולים להיות שימושיים למיון אך רגישים לשיתוף. + אפשר EXIF ​​נקי תמיד כאשר רוב הייצוא שלך מיועד לשיתוף ציבורי או חצי ציבורי. + אפשר שמור את תאריך השעה רק כאשר שמירה על זמן הלכידה חשובה יותר מהסרה של כל חותמת זמן. + השתמש בעריכת EXIF ​​ידנית עבור קבצים חד פעמיים שבהם עליך לשמור שדות מסוימים ולהסיר אחרים. + שליטה בשמות קבצים + השתמש בקידומות, סיומות, חותמות זמן, הגדרות קבועות מראש, סיכומי ביקורת ומספרי רצף + הפוך את הייצוא לקל למצוא + תבנית שמות קבצים טובה עוזרת למיין אצוות ומונעת החלפות בשוגג. הגדרות שמות קבצים שימושיות במיוחד כאשר הייצוא חוזר לתיקיית המקור, שם שמות דומים יכולים להפוך לבלבלים במהירות. + הגדר קידומת, סיומת, חותמת זמן, שם מקורי, מידע מוגדר מראש או אפשרויות רצף בהגדרות. + השתמש במספרי רצף עבור אצוות שבהן הסדר חשוב, כגון דפים, מסגרות או קולאז\'ים. + אפשר החלפה רק כאשר אתה בטוח שהחלפת קבצים קיימים בטוחה עבור התיקיה הנוכחית. + החלף קבצים + החלף פלטים בשמות תואמים רק כאשר זרימת העבודה שלך הרסנית בכוונה + השתמש במצב כתיבה בזהירות + החלף קבצים משנה את אופן הטיפול בהתנגשויות שמות. זה שימושי ליצוא חוזר לאותה תיקייה, אבל זה יכול גם להחליף תוצאות קודמות אם דפוס שם הקובץ שלך מייצר שוב את אותו השם. + אפשר החלפה רק עבור תיקיות שבהן החלפת קבצים שנוצרו ישנים צפויה וניתנת לשחזור. + השאר את ההחלפה מושבתת בעת ייצוא מסמכי מקור, קבצי לקוח, סריקות חד פעמיות או כל דבר ללא גיבוי. + שלב את ההחלפה עם דפוס שם קובץ מכוון כך שרק הפלטים המיועדים יוחלפו. + דפוסי שמות קבצים + שלב שמות מקוריים, קידומות, סיומות, חותמות זמן, הגדרות קבועות מראש, מצב קנה מידה וסיכומי ביקורת + בנה שמות שמסבירים את הפלט + הגדרות דפוס שמות קבצים יכולות להפוך קבצים מיוצאים להיסטוריה שימושית של מה שקרה להם. זה חשוב בקבוצות מכיוון שתצוגת התיקייה עשויה להיות המקום היחיד שבו אתה יכול להבחין בגודל המקורי, בהגדרה מראש, בפורמט ובסדר העיבוד. + השתמש בשם הקובץ המקורי, בקידומת, בסיומת ובחותמת זמן כאשר אתה צריך שמות קריאים למיון ידני. + הוסף מידע מוגדר מראש, מצב קנה מידה, גודל קובץ או מידע על סכום בדיקה כאשר הפלטים חייבים לתעד כיצד הם נוצרו. + הימנע משמות אקראיים עבור זרימות עבודה שבהן קבצים צריכים להישאר בהתאמה למקורות או לסדר עמודים. + בחר היכן נשמרים קבצים + הימנע מאובדן יצוא על ידי הגדרת תיקיות, שמירת תיקיות מקוריות ומקומות שמירה חד-פעמיים + שמור על תפוקות צפויות + התנהגות השמירה חשובה ביותר כאשר אתה מעבד קבצים רבים או משתף קבצים מספקי ענן. הגדרות התיקיה מחליטות אם הפלטים נאספים במקום ידוע אחד, יופיעו לצד מסמכי מקור, או יעברו זמנית למקום אחר עבור משימה מסוימת. + הגדר תיקיית שמירת ברירת מחדל אם אתה תמיד רוצה ייצוא במקום אחד. + השתמש בשמירה-לתיקייה המקורית עבור זרימות עבודה שבהן הפלט צריך להישאר ליד קובץ המקור. + השתמש במיקום שמירה חד פעמי כאשר משימה בודדת זקוקה ליעד אחר מבלי לשנות את ברירת המחדל שלך. + תיקיות שמירה חד פעמיות + שלח את היצוא הבא לתיקיה זמנית מבלי לשנות את היעד הרגיל שלך + יש להפריד בין עבודות מיוחדות + מיקום שמירה חד פעמי שימושי עבור פרויקטים זמניים, תיקיות לקוח, הפעלות ניקוי או ייצוא שלא אמורים להתערבב עם תיקיית ה-Image Toolbox הרגילה שלך. זה נותן יעד קצר מועד מבלי לשכתב את הגדרות ברירת המחדל שלך. + בחר תיקיה חד פעמית לפני תחילת משימה ששייכת מחוץ למיקום הייצוא הרגיל שלך. + השתמש בו עבור פרויקטים קצרים, תיקיות משותפות או קבוצות שבהן הפלטים חייבים להישאר מקובצים יחד. + חזור לתיקיית ברירת המחדל לאחר העבודה כדי שייצוא מאוחר יותר לא ינחת באופן בלתי צפוי במיקום הזמני. + איזון בין איכות וגודל הקובץ + הבן מתי לשנות ממדים, פורמט או איכות במקום לנחש + כווץ קבצים בכוונה + גודל הקובץ תלוי במידות התמונה, בפורמט, באיכות, בשקיפות ובמורכבות התוכן. אם קובץ עדיין כבד מדי, שינוי ממדים בדרך כלל עוזר יותר מאשר הורדת איכות שוב ושוב. + שנה את גודל הממדים תחילה כאשר התמונה גדולה ממה שהיעד יכול להציג. + איכות נמוכה יותר רק עבור פורמטים עם אובדן ובדוק טקסט, קצוות, מעברי צבע ופנים לאחר הייצוא. + החלף פורמט כאשר שינויים באיכות אינם מספיקים, למשל WebP לשיתוף או PNG עבור נכסים שקופים חדים. + עבודה עם פורמטים מונפשים + השתמש בכלי GIF, APNG, WebP ו-JXL כאשר לקובץ יש מסגרות במקום מפת סיביות אחת + אל תתייחסו לכל תמונה כאל תמימה + פורמטים מונפשים זקוקים לכלים שמבינים מסגרות, משך ותאימות. + השתמש בכלי GIF או APNG כאשר אתה צריך פעולות ברמת המסגרת עבור פורמטים אלה. + השתמש בכלי WebP כאשר אתה צריך דחיסה מודרנית או פלט WebP מונפש. + הצג תצוגה מקדימה של תזמון אנימציה לפני השיתוף מכיוון שפלטפורמות מסוימות ממירות או משטחות תמונות מונפשות. + השווה ותצוגה מקדימה של פלט + בדוק שינויים, גודל קובץ והבדלים חזותיים לפני שמירה על ייצוא + אמת לפני השיתוף + כלי השוואה עוזרים לתפוס חפצי דחיסה, צבעים שגויים או עריכות לא רצויות מוקדם. + פתח Compare כאשר אתה רוצה לבדוק שתי גרסאות זו לצד זו. + התקרב לקצוות, טקסט, מעברי צבע ואזורים שקופים שבהם הכי קל לפספס בעיות. + אם הפלט כבד מדי או מטושטש, חזור לכלי הקודם והתאם את האיכות או הפורמט. + השתמש ברכזת כלי PDF + מיזוג, פיצול, סובב, הסר דפים, דחיסה, הגנה, OCR וקובצי PDF עם סימן מים + בחר פעולת PDF + מרכז ה-PDF מקבץ פעולות מסמך מאחורי נקודת כניסה אחת, כך שתוכל לבחור את הפעולה המדויקת. התחל שם כשהקובץ כבר קובץ PDF, והשתמש בתמונות ל-PDF או בסורק מסמכים כשהמקור שלך עדיין קבוצת תמונות. + פתח את כלי PDF ובחר את הפעולה התואמת את המטרה שלך. + בחר את ה-PDF או את התמונות המקוריות, ולאחר מכן סקור את אפשרויות סדר הדפים, הסיבוב, ההגנה או ה-OCR. + שמור את ה-PDF שנוצר ופתח אותו פעם אחת כדי לאשר ספירת עמודים, סדר וקריאה. + הפוך תמונות ל-PDF + שלב סריקות, צילומי מסך, קבלות או תמונות למסמך אחד שניתן לשתף + בנה מסמך נקי + תמונות הופכות לדפי PDF טובים יותר כאשר הן נחתכות, מסודרות וגודלן באופן עקבי. התייחס לרשימת התמונות כאל ערימת עמודים: כל סיבוב, שוליים ובחירה בגודל עמוד משפיעים על מידת הקריאה של המסמך הסופי. + חתוך או סובב תמונות תחילה כאשר קצוות עמוד, צללים או כיוון שגויים. + בחר תמונות בסדר העמודים המיועד והתאם את גודל העמוד או השוליים אם הכלי מציע אותם. + ייצא את ה-PDF ולאחר מכן בדוק שכל הדפים ניתנים לקריאה לפני שליחתו. + סרוק מסמכים + לכיד דפי נייר והכן אותם ל-PDF, OCR או שיתוף + לכידת דפים קריאים + סריקת מסמכים פועלת בצורה הטובה ביותר עם דפים שטוחים, תאורה טובה ופרספקטיבה מתוקנת. סריקה נקייה לפני ייצוא OCR או PDF חוסכת זמן מכיוון שזיהוי טקסט ודחיסה תלויים שניהם באיכות העמוד. + הנח את המסמך על משטח מנוגד עם מספיק אור ומינימום צללים. + התאם פינות שזוהו או חיתוך ידני כך שהדף ימלא את המסגרת בצורה נקייה. + ייצא כתמונות או כ-PDF, ואז הפעל OCR אם אתה צריך טקסט לבחירה. + הגן או בטל את הנעילה של קבצי PDF + השתמש בסיסמאות בזהירות ושמור עותק מקור שניתן לעריכה במידת האפשר + טפל בבטחה בגישה ל-PDF + כלי הגנה שימושיים לשיתוף מבוקר, אך קל לאבד סיסמאות וקשה לשחזר. הגן על עותקים להפצה, שמור קבצי מקור לא מוגנים בנפרד, ואמת את התוצאה לפני מחיקת משהו. + הגן על עותק של ה-PDF, לא על מסמך המקור היחיד שלך. + אחסן את הסיסמה במקום בטוח לפני שיתוף הקובץ המוגן. + השתמש בביטול נעילה רק עבור קבצים שאתה רשאי לערוך, ולאחר מכן ודא שהעותק הבלתי נעול נפתח כהלכה. + ארגן דפי PDF + למזג, לפצל, לארגן מחדש, לסובב, לחתוך, להסיר עמודים ולהוסיף מספרי עמודים בכוונה + תקן את מבנה המסמך לפני העיטור + הכלים של דפי PDF הם הקלים ביותר לשימוש באותו סדר שבו הייתם מכינים מסמך נייר: אסוף דפים, הסר את העמודים הלא נכונים, סדר אותם, כיוון נכון, ואז הוסף פרטים אחרונים כגון מספרי עמודים או סימני מים. + השתמש תחילה במיזוג או בתמונות ל-PDF כאשר המסמך עדיין מפוצל למספר קבצים. + סידור מחדש, סובב, חתוך או הסר דפים לפני הוספת מספרי עמודים, חתימות או סימני מים. + הצג תצוגה מקדימה של ה-PDF הסופי לאחר עריכות מבניות מכיוון שדף אחד חסר או מסובב יכול להיות קשה להבחין מאוחר יותר. + הערות PDF + הסר הערות או השטח אותן כאשר הצופים מציגים הערות וסימנים בצורה שונה + שליטה במה שנשאר גלוי + הערות יכולות להיות הערות, הדגשות, שרטוטים, סימני טופס או אובייקטי PDF נוספים אחרים. חלק מהצופים מציגים אותם בצורה שונה, כך שהסרה או רידוד שלהם יכולה להפוך את המסמכים המשותפים לצפויים יותר. + הסר הערות כאשר אין לכלול הערות, הדגשות או סימני ביקורת בעותק המשותף. + השטח כאשר סימנים גלויים צריכים להישאר בדף אך אינם מתנהגים עוד כמו הערות הניתנות לעריכה. + שמור עותק מקורי מכיוון שהסרה או שיטוח של הערות עשויות להיות קשות לביטול. + זיהוי טקסט + חלץ טקסט מתמונות עם מנועי OCR ושפות לבחירה + קבל תוצאות OCR טובות יותר + דיוק ה-OCR תלוי בחדות התמונה, נתוני השפה, הניגודיות וכיוון העמוד. התוצאות הטובות ביותר מגיעות בדרך כלל מהכנת התמונה תחילה: חיתוך רעשים, סיבוב זקוף, הגברת הקריאה ואז זיהוי טקסט. + חתוך את התמונה לאזור הטקסט וסובב אותה זקוף לפני הזיהוי. + בחר את השפה הנכונה והורד נתוני שפה אם האפליקציה מבקשת זאת. + העתק או שתף את הטקסט המזוהה, ולאחר מכן בדוק ידנית שמות, מספרים וסימני פיסוק. + בחר נתוני שפת OCR + שפר את הזיהוי על ידי התאמת סקריפטים, שפות ודגמי OCR שהורדת + התאם OCR לטקסט + שפת OCR שגויה יכולה לגרום לתמונות טובות להיראות כמו זיהוי רע. נתוני השפה חשובים עבור סקריפטים, סימני פיסוק, מספרים ומסמכים מעורבים, אז בחר את השפה שמופיעה בתמונה ולא את השפה של ממשק המשתמש של הטלפון. + בחר את השפה או הסקריפט שמופיעים בתמונה, לא רק את שפת הטלפון. + הורד נתונים חסרים לפני עבודה במצב לא מקוון או עיבוד אצווה. + עבור מסמכים בשפה מעורבת, הפעל תחילה את השפה החשובה ביותר ובדוק ידנית שמות ומספרים. + קובצי PDF הניתנים לחיפוש + כתוב טקסט OCR בחזרה לקבצים כך שניתן יהיה לחפש ולהעתיק דפים סרוקים + הפוך סריקות למסמכים הניתנים לחיפוש + OCR יכול לעשות יותר מאשר להעתיק טקסט ללוח. כאשר אתה כותב טקסט מזוהה לקובץ או PDF שניתן לחיפוש, תמונת הדף נשארת גלויה בעוד שהטקסט הופך קל יותר לחיפוש, לבחירה, לאינדקס או לארכיון. + השתמש בפלט PDF שניתן לחיפוש עבור מסמכים סרוקים שעדיין צריכים להיראות כמו העמודים המקוריים. + השתמש בכתיבה לקובץ כאשר אתה צריך רק טקסט שחולץ ולא אכפת לך מתמונת העמוד. + בדוק מספרים, שמות וטבלאות חשובים באופן ידני מכיוון שטקסט OCR יכול להיות ניתן לחיפוש אך עדיין לא מושלם. + סרוק קודי QR + קרא תוכן QR מקלט המצלמה או תמונות שמורות כאשר זמין + קרא קודים בבטחה + קודי QR יכולים להכיל קישורים, נתוני Wi-Fi, אנשי קשר, טקסט או מטענים אחרים. + פתח את Scan QR Code ושמור את הקוד חד, שטוח ומלא בתוך המסגרת. + סקור את התוכן המפוענח לפני פתיחת קישורים או העתקת נתונים רגישים. + אם הסריקה נכשלת, שפר את התאורה, חתוך את הקוד או נסה תמונת מקור ברזולוציה גבוהה יותר. + השתמש ב-Base64 ובסיכומי ביקורת + קודד תמונות כטקסט, פענח טקסט חזרה לתמונות, וודא את תקינות הקובץ + מעבר בין קבצים וטקסט + Base64 שימושי עבור מטענים קטנים של תמונות, בעוד שסכומי בדיקה עוזרים לאשר שהקבצים לא השתנו. כלים אלה מועילים ביותר בזרימות עבודה טכניות, דוחות תמיכה, העברת קבצים ומקומות שבהם קבצים בינאריים צריכים להפוך לטקסט באופן זמני. + השתמש בכלי Base64 כדי לקודד תמונה קטנה כאשר זרימת עבודה זקוקה לטקסט במקום קובץ בינארי. + פענח את Base64 רק ממקורות מהימנים ואמת את התצוגה המקדימה לפני השמירה. + השתמש בכלי Checksum כאשר אתה צריך להשוות קבצים מיוצאים או לאשר העלאה/הורדה. + ארוז או הגן על נתונים + השתמש בכלי ZIP וצופן לאגד קבצים או שינוי נתוני טקסט + שמור קבצים קשורים יחד + כלי אריזה מועילים כאשר מספר פלטים צריכים לעבור כקובץ אחד. הם גם טובים לשמירה על תמונות שנוצרו, קובצי PDF, יומנים ומטא נתונים יחד כאשר אתה צריך לשלוח ערכת תוצאות מלאה. + השתמש ב-ZIP כאשר אתה צריך לשלוח תמונות או מסמכים מיוצאים רבים יחד. + השתמש בכלי צופן רק כאשר אתה מבין את השיטה שנבחרה ויכול לאחסן את המפתחות הנדרשים בבטחה. + בדוק את הארכיון שנוצר או את הטקסט שעבר טרנספורמציה לפני מחיקת קובצי מקור. + סכומי צ\'ק בשמות + השתמש בשמות קבצים מבוססי סיכום כאשר הייחודיות והשלמות חשובות יותר מהקריאה + תן שם לקבצים לפי תוכן + שמות קבצים של Checksum שימושיים כאשר לייצוא עשויים להיות שמות גלויים כפולים או כאשר אתה צריך להוכיח ששני קבצים זהים. הם פחות ידידותיים לגלישה ידנית, אז השתמש בהם עבור ארכיונים טכניים וזרימות עבודה לאימות. + אפשר checksum כשם קובץ כאשר זהות התוכן חשובה יותר מכותרת הניתנת לקריאה על ידי אדם. + השתמש בו עבור ארכיונים, ביטול כפילויות, ייצוא חוזר או קבצים שניתן להעלות ולהוריד שוב. + צמד שמות בדיקות עם תיקיות או מטא נתונים כאשר אנשים עדיין צריכים להבין מה הקובץ מייצג. + בחר צבעים מתוך תמונות + דגמי פיקסלים, העתק קודי צבע ועשה שימוש חוזר בצבעים בפלטות או בעיצובים + לדגום את הצבע המדויק + בחירת צבעים שימושית להתאמת עיצוב, חילוץ צבעי מותג או בדיקת ערכי תמונה. התקרבות חשובה מכיוון שההפרדה, צללים ודחיסה יכולים להפוך את הפיקסלים השכנים מעט שונים מהצבע שאתה מצפה. + פתח את Pick Color From Image ובחר תמונת מקור עם הצבע שאתה צריך. + התקרב לפני דגימת פרטים קטנים כך שפיקסלים קרובים לא ישפיעו על התוצאה. + העתק את הצבע בפורמט הנדרש על ידי היעד שלך, כגון HEX, RGB או HSV. + צור פלטות והשתמש בספריית הצבעים + חלץ לוחות צבעים, עיין בצבעים שמורים ושמור על מערכות חזותיות עקביות + בנה פלטה לשימוש חוזר + לוחות צבעים עוזרים לשמור על עקביות חזותית של גרפיקה, סימני מים וציורים מיוצאים. הם שימושיים במיוחד כאשר אתה מוסיף שוב ושוב צילומי מסך, מכינים נכסי מותג או זקוקים לאותם צבעים במספר כלים. + צור פלטה מתמונה או הוסף צבעים באופן ידני כאשר אתה כבר יודע את הערכים. + תן שם או ארגן צבעים חשובים כדי שתוכל למצוא אותם שוב מאוחר יותר. + השתמש בערכים שהועתקו בכלי Draw, סימן מים, שיפוע, SVG או כלי עיצוב חיצוניים. + צור מעברי צבע + בנו תמונות הדרגתיות עבור רקעים, מצייני מיקום וניסויים חזותיים + שליטה במעברי צבע + כלי מעברי צבע שימושיים כאשר אתה צריך תמונה שנוצרה במקום לערוך תמונה קיימת. הם יכולים להפוך לרקע נקי, מצייני מיקום, טפטים, מסכות או נכסים פשוטים עבור כלי עיצוב חיצוניים. + בחר את סוג ההדרגה והגדר תחילה את הצבעים החשובים. + התאם את הכיוון, העצירות, החלקות וגודל הפלט למקרה השימוש היעד. + ייצוא בפורמט התואם את היעד, בדרך כלל PNG עבור גרפיקה נקייה שנוצרה. + נגישות צבע + השתמש בצבעים דינמיים, ניגודיות ועיוורת צבעים כאשר ממשק המשתמש קשה לקריאה + הפוך את הצבעים לקלים יותר לשימוש + הגדרות הצבע משפיעות על הנוחות, על הקריאה ועל המהירות שבה אתה יכול לסרוק פקדים. אם קשה להבחין בשבבי כלים, מחוונים או מצבים נבחרים, אפשרויות עיצוב ונגישות יכולות להיות שימושיות יותר מאשר פשוט לשנות מצב כהה. + נסה צבעים דינמיים כאשר אתה רוצה שהאפליקציה תעקוב אחר לוח הטפטים הנוכחי. + הגדל את הניגודיות או שנה את הסכימה כאשר הכפתורים והמשטחים אינם בולטים מספיק. + השתמש באפשרויות ערכת עיוורת צבעים אם קשה להבחין בין מצבים או מבטאים מסוימים. + רקע אלפא + שלוט בתצוגה המקדימה או בצבע המילוי המשמש סביב PNG, WebP ושקופות פלטים דומים + הבן תצוגות מקדימות שקופות + צבע רקע עבור פורמטי אלפא יכול להקל על בדיקת תמונות שקופות, אך חשוב לדעת אם אתה משנה התנהגות תצוגה מקדימה/מילוי או משטח את התוצאה הסופית. זה חשוב למדבקות, לוגואים ותמונות שהוסרו ברקע. + השתמש תחילה בפורמט תומך אלפא, כגון PNG או WebP, כאשר פיקסלים שקופים חייבים לשרוד את הייצוא. + התאם את הגדרות צבע הרקע כאשר קשה לראות קצוות שקופים מול משטח האפליקציה. + ייצא בדיקה קטנה ופתח אותה מחדש באפליקציה אחרת כדי לאשר אם השקיפות או המילוי שנבחר נשמר. + בחירת דגם AI + בחר דגם לפי סוג התמונה והפגם במקום לבחור תחילה את הגדול ביותר + התאימו את הדגם לנזק + כלי AI יכולים להוריד או לייבא דגמי ONNX/ORT שונים, וכל דגם מאומן לסוג מסוים של בעיה. מודל עבור בלוקים של JPEG, ניקוי קווי אנימה, הסרת רקע, הסרת רקע, צביעה או שינוי קנה מידה עשוי להניב תוצאות גרועות יותר כאשר משתמשים בסוג התמונה הלא נכון. + קרא את תיאור הדגם לפני ההורדה; בחר את הדגם ששם את הבעיה האמיתית שלך, כגון דחיסה, רעש, חצי גוון, טשטוש או הסרת רקע. + התחל עם דגם קטן יותר או ספציפי יותר בעת בדיקת סוג תמונה חדש, ולאחר מכן השווה לדגמים כבדים יותר רק אם התוצאה לא מספיק טובה. + שמור רק דגמים שבהם אתה באמת משתמש, מכיוון שדגמים שהורדו ויובאו יכולים לקחת שטח אחסון ניכר. + להבין משפחות מודל + שמות דגמים מרמזים לעתים קרובות על היעד שלהם: דגמים יוקרתיים בסגנון RealESRGAN, דגמי SCUNet ו-FBCNN מנקים רעש או דחיסה, דגמי רקע יוצרים מסכות וצבעוני מוסיפים צבע לקלט בגווני אפור. מודלים מותאמים אישית מיובאים יכולים להיות חזקים, אך הם צריכים להתאים לצורת הקלט/פלט הצפויה ולהשתמש בפורמט ONNX או ORT נתמך. + השתמש ב-upscalers כאשר אתה צריך יותר פיקסלים, denoisers כאשר התמונה מגורען, ומסירי חפצים כאשר בלוקים דחיסה או פסים הם הבעיה העיקרית. + השתמש בדגמי רקע רק להפרדת נושאים; הם אינם זהים להסרת אובייקט כללי או שיפור תמונה. + ייבא דגמים מותאמים אישית רק ממקורות שאתה סומך עליהם ובדוק אותם על תמונה קטנה לפני השימוש בקבצים חשובים. + פרמטרים של AI + כוונן את גודל הנתחים, החפיפה והעובדים במקביל כדי לאזן איכות, מהירות וזיכרון + איזון מהירות עם יציבות + גודל הנתח שולט בגודל של חלקי תמונה לפני שהם מעובדים על ידי המודל. חפיפה ממזגת נתחים שכנים כדי להסתיר גבולות. עובדים מקבילים יכולים להאיץ את העיבוד, אבל כל עובד צריך זיכרון, כך שערכים גבוהים יכולים להפוך תמונות גדולות ללא יציבות בטלפונים עם זיכרון RAM מוגבל. + השתמש בנתחים גדולים יותר להקשר חלק יותר כאשר המכשיר שלך מטפל בדגם בצורה אמינה והתמונה אינה ענקית. + הגדל את החפיפה כאשר גבולות הנתחים נראים לעין, אך זכור שיותר חפיפה פירושה עבודה חוזרת יותר. + לגדל עובדים מקבילים רק לאחר מבחן יציב; אם העיבוד מאט, מחמם את המכשיר או קורס, צמצם תחילה את העובדים. + הימנע מחפצים נתחים + Chunking מאפשר לאפליקציה לעבד תמונות גדולות יותר ממה שהדגם יכול להתמודד בנוחות בבת אחת. הפשרה היא שלכל אריח יש פחות הקשר סביב, כך שחפיפה נמוכה או נתחים קטנים מדי עלולים ליצור גבולות גלויים, שינויים במרקם או פרטים לא עקביים בין אזורים שכנים. + הגדל את החפיפה אם אתה רואה גבולות מלבניים, שינויים חוזרים במרקם או קפיצות פרטים בין אזורים מעובדים. + הפחת את גודל הנתח אם התהליך נכשל לפני הפקת פלט, במיוחד בתמונות גדולות או דגמים כבדים. + שמור בדיקת חיתוך קטנה לפני עיבוד התמונה המלאה כדי שתוכל להשוות פרמטרים במהירות. + יציבות AI + הורד את גודל הנתח, את רזולוציית העובדים ואת המקור כאשר עיבוד AI קורס או קופא + התאושש מעבודות AI כבדות + עיבוד AI הוא אחד מתזרימי העבודה הכבדים ביותר באפליקציה. קריסות, הפעלות שנכשלו, הקפאות או הפעלה מחדש פתאומית של האפליקציה פירושם בדרך כלל שהדגם, רזולוציית המקור, גודל הנתחים או ספירת העובדים המקבילים תובעניים מדי עבור מצב המכשיר הנוכחי. + אם האפליקציה קורסת או לא מצליחה לפתוח הפעלת מודל, הורד את גודל העובדים המקבילים ואת גודל הנתח, ולאחר מכן נסה שוב את אותה תמונה. + שנה גודל של תמונות גדולות מאוד לפני עיבוד AI כאשר המטרה אינה דורשת את הרזולוציה המקורית. + סגור אפליקציות כבדות אחרות, השתמש בדגם קטן יותר, או עבד פחות קבצים בבת אחת כאשר לחץ הזיכרון ממשיך לחזור. + אבחון כשלים בדגם + ריצת AI כושלת לא תמיד אומרת שהתמונה גרועה. ייתכן שקובץ הדגם אינו שלם, אינו נתמך, גדול מדי לזיכרון או אינו תואם לפעולה. כאשר האפליקציה מדווחת על הפעלה שנכשלה, התייחסו אליו כאל איתות לפשט את המודל והפרמטרים תחילה. + מחק והורד מחדש דגם אם הוא נכשל מיד לאחר ההורדה או מתנהג כאילו הקובץ פגום. + נסה מודל מובנה להורדה לפני שתאשים דגם מותאם אישית מיובא, מכיוון שלדגמים מיובאים יכולים להיות תשומות לא תואמות. + השתמש ביומני אפליקציות לאחר שחזור הכשל אם עליך לדווח על קריסה או שגיאת הפעלה. + ניסוי בסטודיו Shader + השתמש באפקטי הצללה של GPU לעיבוד תמונה מתקדם ומראה מותאמים אישית + עבדו בזהירות עם הצללות + Shader Studio הוא רב עוצמה, אך קוד הצללה ופרמטרים זקוקים לשינויים קטנים הניתנים לבדיקה. התייחס לזה כמו לכלי ניסיוני: שמור על קו בסיס עובד, שנה דבר אחד בכל פעם ובדוק עם גדלי תמונה שונים לפני שאתה נותן אמון בהגדרה מראש. + התחל מהצללה מוכרת או אפקט פשוט לפני הוספת פרמטרים. + שנה פרמטר או בלוק קוד אחד בכל פעם ובדוק אם יש שגיאות בתצוגה המקדימה. + ייצא הגדרות מוגדרות מראש רק לאחר בדיקתן על כמה גדלי תמונה שונים. + צור אמנות SVG או ASCII + צור נכסים דמויי וקטור או תמונות מבוססות טקסט עבור פלט יצירתי + בחר את סוג הפלט היצירתי + כלי SVG ו-ASCII משרתים יעדים שונים: גרפיקה ניתנת להרחבה או עיבוד בסגנון טקסט. שניהם המרות יצירתיות, כך שתצוגה מקדימה בגודל הסופי חשובה יותר מאשר לשפוט את התוצאה לפי תמונה ממוזערת זעירה. + השתמש ב-SVG Maker כאשר התוצאה אמורה לעבור קנה מידה נקי או לעשות שימוש חוזר בתהליכי עבודה בעיצוב. + השתמש ב-ASCII Art כאשר אתה רוצה ייצוג טקסט מסוגנן של תמונה. + תצוגה מקדימה בגודל הסופי מכיוון שפרטים קטנים יכולים להיעלם בפלט שנוצר. + יצירת טקסטורות רעש + צור טקסטורות פרוצדורליות עבור רקעים, מסכות, שכבות או ניסויים + כוונן פלט פרוצדורלי + יצירת רעש יוצרת תמונות חדשות מפרמטרים, כך ששינויים קטנים יכולים לייצר תוצאות שונות מאוד. זה שימושי עבור טקסטורות, מסכות, שכבות, מצייני מיקום או בדיקת דחיסה עם דפוסים מפורטים. + בחר סוג רעש וגודל פלט לפני כוונון פרטים עדינים. + התאם את קנה המידה, העוצמה, הצבעים או הזרעים עד שהדפוס מתאים לשימוש היעד. + שמור תוצאות שימושיות ורשום את הפרמטרים אם תצטרך לשחזר את המרקם מאוחר יותר. + פרויקטי סימון + השתמש בקבצי פרויקט כאשר ההערות צריכות להישאר ניתנות לעריכה לאחר סגירת האפליקציה + שמור על עריכות שכבות לשימוש חוזר + קובצי פרויקט סימון שימושיים כאשר צילום מסך, דיאגרמה או תמונת הוראות עשויים להזדקק לשינויים מאוחר יותר. קובצי PNG/JPEG מיוצאים הם תמונות סופיות, בעוד שקובצי פרויקט משמרים שכבות הניתנות לעריכה ומצב עריכה עבור התאמות עתידיות. + השתמש בשכבות סימון כאשר הערות, הסברים או רכיבי פריסה צריכים להישאר ניתנים לעריכה. + שמור או פתח מחדש את קובץ הפרויקט לפני ייצוא תמונה סופית אם אתה מצפה לתיקונים נוספים. + ייצא תמונה רגילה רק כאשר אתה מוכן לשתף גרסה סופית שטוחה. + הצפין קבצים + הגן על קבצים באמצעות סיסמה ובדוק פענוח לפני מחיקת עותק מקור כלשהו + טפל בפלטים מוצפנים בזהירות + כלי צופן יכולים להגן על קבצים באמצעות הצפנה מבוססת סיסמה, אך הם אינם מהווים תחליף לזכירת המפתח או שמירת גיבויים. ההצפנה שימושית להובלה ואחסון, בעוד שה-ZIP טוב יותר כאשר המטרה העיקרית היא חיבור של מספר פלטים. + תחילה הצפין עותק ושמור את המקור עד שבדקת שהפענוח עובד עם הסיסמה ששמרת. + השתמש במנהל סיסמאות או במקום בטוח אחר עבור המפתח; האפליקציה לא יכולה לנחש עבורך סיסמה אבודה. + שתף קבצים מוצפנים רק עם אנשים שיש להם גם את הסיסמה ויודעים איזה כלי או שיטה צריכים לפענח אותם. + מסדרים כלים + קבץ, סדר, חפש והצמד כלים כך שהמסך הראשי יתאים לזרימת העבודה האמיתית שלך + הפוך את המסך הראשי למהיר יותר + כדאי לכוון את הגדרות סידור הכלים ברגע שאתה יודע באילו כלים אתה משתמש הכי הרבה. קיבוץ מסייע בחקירה, סדר מותאם אישית עוזר לזיכרון השריר, ומועדפים לשמור על כלים יומיים נגישים גם כשהרשימה המלאה הופכת לגדולה. + השתמש במצב מקובץ בזמן לימוד האפליקציה או כאשר אתה מעדיף כלים מאורגנים לפי סוג משימה. + עבור לסדר מותאם אישית כאשר אתה כבר מכיר את הכלים התכופים שלך ורוצה פחות מגילות. + השתמשו בהגדרות חיפוש ובמועדפים יחדיו עבור כלים נדירים שאתם זוכרים לפי שמם אך לא רוצים שיוצגו כל הזמן. + טיפול בקבצים גדולים + הימנע מייצוא איטי, לחץ זיכרון ותפוקה גדולה באופן בלתי צפוי + צמצם עבודה לפני הייצוא + תמונות גדולות מאוד, קבוצות ארוכות ופורמטים באיכות גבוהה עשויים לדרוש יותר זיכרון וזמן. התיקון המהיר ביותר הוא בדרך כלל הפחתת כמות העבודה לפני הפעולה הכבדה ביותר, לא לחכות לאותה קלט גדול מדי שיסתיים. + שנה את הגודל של תמונות ענק לפני החלת מסננים כבדים, המרת OCR או PDF. + השתמש תחילה באצווה קטנה יותר כדי לאשר את ההגדרות, ולאחר מכן עבד את השאר עם אותה הגדרה מראש. + ירידה באיכות או שנה פורמט כאשר קובץ הפלט גדול מהצפוי. + מטמון ותצוגות מקדימות + הבן תצוגות מקדימות שנוצרו, ניקוי מטמון ושימוש באחסון לאחר הפעלות כבדות + שמור על איזון אחסון ומהירות + יצירת תצוגה מקדימה ומטמון יכולים להפוך את הגלישה החוזרת למהירה יותר, אך הפעלות עריכה כבדות עשויות להשאיר נתונים זמניים מאחור. הגדרות מטמון עוזרות כאשר האפליקציה מרגישה איטית, אחסון מצומצם או תצוגות מקדימות נראות מיושנות לאחר ניסויים רבים. + שמירה על תצוגה מקדימה מופעלת בעת גלישה בתמונות רבות חשובה יותר מאשר צמצום האחסון הזמני. + נקה את המטמון לאחר אצווה גדולה, ניסויים כושלים או הפעלות ארוכות עם קבצים רבים ברזולוציה גבוהה. + השתמש בניקוי מטמון אוטומטי אם אתה מעדיף ניקוי אחסון על פני שמירה על תצוגות מקדימות חמות בין השקות. + התאם את התנהגות המסך + השתמש באפשרויות מסך מלא, מצב מאובטח, בהירות וסרגל המערכת לעבודה ממוקדת + הפוך את הממשק להתאים למצב + הגדרות המסך עוזרות כשאתה עורך לרוחב, מציג תמונות פרטיות או זקוק לבהירות עקבית. הם אינם נדרשים לשימוש רגיל, אבל הם פותרים מצבים מביכים כמו בדיקת QR, תצוגה מקדימה פרטית או עריכת נוף צפופה. + השתמש בהגדרות של מסך מלא או סרגל מערכת כאשר עריכת שטח חשובה יותר מאשר ניווט כרום. + השתמש במצב מאובטח כאשר תמונות פרטיות לא אמורות להופיע בצילומי מסך או בתצוגות מקדימות של אפליקציה. + השתמש באכיפת בהירות עבור כלים שבהם קשה לבדוק תמונות כהות או קודי QR. + שמור על שקיפות + מנע מרקע שקוף להפוך ללבן, שחור או שטוח + השתמש בפלט מודע לאלפא + השקיפות שורדת רק כאשר הכלי ופורמט הפלט שנבחרו תומכים באלפא. אם רקע מיוצא הופך לבן או שחור, הבעיה היא בדרך כלל פורמט הפלט, הגדרת מילוי/רקע או אפליקציית יעד ששטחה את התמונה. + בחר PNG או WebP בעת ייצוא תמונות, מדבקות, סמלי לוגו או שכבות על רקע שהוסרו. + הימנע מ-JPEG עבור פלט שקוף מכיוון שהוא אינו מאחסן ערוץ אלפא. + בדוק הגדרות הקשורות לצבע הרקע עבור פורמטי אלפא אם התצוגה המקדימה מציגה רקע מלא. + תקן בעיות שיתוף וייבוא + השתמש בהגדרות הבורר ובפורמטים הנתמכים כאשר קבצים אינם מופיעים או נפתחים כהלכה + הכנס את הקובץ לאפליקציה + נסה לפתוח את הקובץ דרך בורר המערכת במקום קיצור דרך לקבצים אחרונים. + אם פורמט אינו נתמך על ידי כלי אחד, המר אותו תחילה או פתח כלי ספציפי יותר. + אישור יציאה + הימנע מאובדן עריכות שלא נשמרו כאשר מחוות, לחיצות לאחור או החלפת כלים מתרחשים בטעות + הגן על עבודה לא גמורה + אפשר אישור אם תנועות חזרה בשוגג סגרו אי פעם כלי לפני שייצאת. + השתמש בו יחד עם הגדרות קבועות מראש עבור זרימות עבודה ארוכות יותר שבהן בנייה מחדש של הגדרות יהיה מעצבן. + השתמש ביומנים בעת דיווח על בעיות + דווח על בעיות בהקשר + הרבה יותר קל לאבחן דוח ברור עם שלבים, גרסת אפליקציה, סוג קלט ויומנים. יומנים שימושיים ביותר מיד לאחר שחזור בעיה מכיוון שהם שומרים על ההקשר שמסביב טרי. + שימו לב לשם הכלי, לפעולה המדויקת והאם זה קורה עם קובץ אחד או כל קובץ. + פתח את יומני האפליקציה לאחר שחזור הבעיה ושתף את פלט היומן הרלוונטי במידת האפשר. + עיבוד הרקע הופסק + אנדרואיד לא נתנה להתראת עיבוד הרקע להתחיל בזמן. זוהי מגבלת תזמון מערכת שלא ניתן לתקן בצורה מהימנה. הפעל מחדש את האפליקציה ונסה שוב; שמירה על האפליקציה פתוחה ומתן התראות או עבודת רקע עשויה לעזור. + בעיות בייבוא ​​נגרמות לרוב מהרשאות ספק, פורמטים לא נתמכים או התנהגות בוחר. קבצים ששותפו מיישומי ענן ומשליחים עשויים להיות זמניים, כך שבחירה במקור מתמשך יכולה להיות אמינה יותר עבור עריכות ארוכות יותר. + סקור את הגדרות בוחר התמונות והמפעיל אם זרימות שיתוף או פתיחת כלים ישירה מתנהגים בצורה שונה מהצפוי. + אישור יציאה מהכלי מועיל כאשר אתה משתמש בניווט מחוות, עורך קבצים גדולים או עובר לעתים קרובות בין אפליקציות. הוא מוסיף הפסקה קטנה לפני עזיבת כלי כך שהגדרות ותצוגות מקדימות לא גמורים לא יאבדו בטעות. + השאר אותו מושבת אם אתה מבצע המרות מהירות בשלב אחד ומעדיף ניווט מהיר יותר. + אסוף פרטים שימושיים לפני פתיחת בעיה או יצירת קשר עם המפתח + צרף צילומי מסך או קבצים לדוגמה רק כאשר הם אינם מכילים מידע פרטי. + דלג על בחירת קבצים + פתח כלים מהר יותר כאשר הקלט מגיע בדרך כלל משיתוף, לוח או מסך קודם + הפוך את השקות הכלים החוזרות למהירות יותר + דלג על בחירת קבצים משנה את השלב הראשון של הכלים הנתמכים. זה שימושי כאשר אתה בדרך כלל מזין כלי עם תמונה שכבר נבחרה או מפעולות שיתוף של אנדרואיד, אבל זה יכול להרגיש מבלבל אם אתה מצפה שכל כלי יבקש קובץ באופן מיידי. + הפעל אותו רק כאשר הזרימה הרגילה שלך כבר מספקת את תמונת המקור לפני שהכלי נפתח. + השאר אותו מושבת בזמן לימוד האפליקציה, מכיוון שבחירה מפורשת הופכת כל זרימת כלי קלה להבנה. + אם כלי נפתח ללא קלט ברור, השבת את ההגדרה הזו או התחל משיתוף/פתח עם כך ש-Android יעביר את הקובץ ראשון. + פתח את העורך קודם + דלג על תצוגה מקדימה כאשר תמונה משותפת צריכה להיכנס ישר לעריכה + בחר תצוגה מקדימה או ערוך כתחנה הראשונה + פתח עריכה במקום תצוגה מקדימה מיועד למשתמשים שכבר יודעים שהם רוצים לשנות את התמונה. תצוגה מקדימה בטוחה יותר כאשר אתה בודק קבצים מצ\'אט או מאחסון ענן; עריכה ישירה מהירה יותר עבור צילומי מסך, חיתוכים מהירים, הערות ותיקונים נקודתיים. + אפשר עריכה ישירה אם רוב התמונות המשותפות זקוקות מיד לשינויים בחיתוך, סימון, מסננים או ייצוא. + השאר תחילה תצוגה מקדימה כאשר אתה בודק לעתים קרובות מטא נתונים, מידות, שקיפות או איכות תמונה לפני שתחליט. + השתמש בזה יחד עם מועדפים כך שתמונות משותפות נוחתות קרוב לכלים שבהם אתה משתמש בפועל. + הזנת טקסט מוגדרת מראש + הזן ערכים מוגדרים מראש מדויקים במקום להתאים את הפקדים שוב ושוב + השתמש בערכים מדויקים כאשר המחוונים איטיים + הזנת טקסט מוגדרת מראש עוזרת כאשר אתה צריך מספרים מדויקים לעבודה חוזרת: גדלי תמונות חברתיות, שולי מסמכים, יעדי דחיסה, רוחב קווים או ערכים אחרים שמעצבן להגיע אליהם עם מחוות. זה שימושי גם במסכים קטנים שבהם תנועת המחוון העדינה קשה יותר. + אפשר הזנת טקסט אם אתה עושה שימוש חוזר בגדלים מדויקים, באחוזים, בערכי איכות או בפרמטרים של כלי עבודה. + שמור את הערך כקביעה מראש לאחר הקלדתו פעם אחת, ולאחר מכן השתמש בו שוב במקום לבנות מחדש את אותה הגדרה. + שמור על בקרות מחוות לכוונון חזותי גס וערכים מוקלדים לדרישות פלט קפדניות. + שמור ליד מקורי + שים קבצים שנוצרו ליד תמונות מקור כאשר ההקשר של התיקיה חשוב + שמור את הפלטים עם המקורות שלהם + Save To Original Folder שימושי עבור תיקיות מצלמה, תיקיות פרויקטים, סריקות מסמכים ואצוות לקוח שבהם העותק הערוך צריך להישאר ליד המקור. זה יכול להיות פחות מסודר מתיקיית פלט ייעודית, אז שלבו אותו עם סיומות או תבניות ברורות של שם קובץ. + הפעל זאת כאשר כל תיקיית מקור כבר בעלת משמעות והפלטים צריכים להישאר מקובצים שם. + השתמש בסיומות של שמות קבצים, בקידומות, בחותמות זמן או בתבניות כך שקל להפריד בין עותקים ערוכים למקור. + השבת אותו עבור אצוות מעורבות כאשר אתה רוצה שכל הקבצים שנוצרו יאספו בתיקיית ייצוא אחת. + דלג על פלט גדול יותר + הימנע מלשמור המרות שהופכות באופן בלתי צפוי לכבדות יותר מהמקור + הגן על קבוצות מהפתעות בגודל רע + חלק מההמרות הופכות קבצים לגדולים יותר, במיוחד צילומי מסך PNG, תמונות שכבר דחוסות, WebP באיכות גבוהה או תמונות עם מטא נתונים נשמרים. אפשר Skip If Larger מונע מהפלטים הללו להחליף מקור קטן יותר בזרימות עבודה שבהן צמצום הגודל הוא המטרה העיקרית. + הפעל זאת כאשר הייצוא נועד לדחוס, לשנות גודל או להכין קבצים למגבלות העלאה. + סקור קבצים שדילגתם עליהם לאחר אצווה; ייתכן שהם כבר עברו אופטימיזציה או שהם זקוקים לפורמט אחר. + השבת אותו כאשר האיכות, השקיפות או תאימות הפורמט חשובים יותר מגודל הקובץ הסופי. + לוח וקישורים + שלוט בקלט אוטומטי בלוח ובתצוגות מקדימות של קישורים עבור כלים מבוססי טקסט מהירים יותר + הפוך את הקלט האוטומטי לצפוי + הגדרות הלוח והקישור נוחות עבור QR, Base64, URL וזרימות עבודה עתירות טקסט, אך קלט אוטומטי צריך להישאר מכוון. אם נראה שהאפליקציה ממלאת שדות בעצמה, בדוק את התנהגות הדבקת הלוח; אם כרטיסי קישור מרגישים רועשים או איטיים, התאם את התנהגות התצוגה המקדימה של קישור. + אפשר הדבקה אוטומטית של לוח כאשר אתה מעתיק טקסט לעתים קרובות ופותח מיד כלי התאמה. + השבת את ההדבקה האוטומטית אם תוכן לוח פרטי מופיע בכלים שבהם לא ציפית לו. + כבה תצוגות מקדימות של קישורים כאשר שליפת התצוגה המקדימה איטית, מסיחה את הדעת או מיותרת עבור זרימת העבודה שלך. + בקרות קומפקטיות + השתמש בבוררים צפופים יותר ובמיקום מהיר של הגדרות כאשר שטח המסך מצומצם + התאם יותר פקדים על מסכים קטנים + בוררים קומפקטיים ומיקום מהיר של הגדרות עוזרים בטלפונים קטנים, עריכה לרוחב וכלים עם פרמטרים רבים. הפשרה היא שפקדים יכולים להרגיש צפופים יותר, אז זה הכי טוב אחרי שכבר תזהה את האפשרויות הנפוצות. + אפשר בוררים קומפקטיים כאשר תיבות דו-שיח או רשימות אפשרויות מרגישות גבוהות מדי עבור המסך. + התאם את צד ההגדרות המהירות אם קל יותר להגיע לפקדי הכלים מצד אחד של התצוגה. + חזור לפקדים מרווחים יותר אם אתה עדיין לומד את האפליקציה או מחמיץ לעתים קרובות אפשרויות צפופות. + טען תמונות מהאינטרנט + הדבק כתובת אתר של דף או תמונה, צפה בתצוגה מקדימה של תמונות ממנותחות ולאחר מכן שמור או שתף את התוצאות שנבחרו + השתמש בתמונות אינטרנט בכוונה + טעינת תמונות אינטרנט מנתחת מקורות תמונה מכתובת האתר שסופקה, צופה בתצוגה מקדימה של התמונה הזמינה הראשונה ומאפשרת לך לשמור או לשתף תמונות שנבחרו ממנותחות. חלק מהאתרים משתמשים בקישורים זמניים, מוגנים או שנוצרים בסקריפט, כך שעמוד שעובד בדפדפן עדיין עשוי להחזיר תמונות שמישות. + הדבק כתובת אתר ישירה של תמונה או כתובת אתר של דף והמתן עד להופעת רשימת התמונות המנותחות. + השתמש בפקדי המסגרת או הבחירה כאשר הדף מכיל מספר תמונות ואתה צריך רק חלק מהן. + אם שום דבר לא נטען, נסה קישור ישיר לתמונה או הורד תחילה דרך הדפדפן; האתר עשוי לחסום ניתוח או להשתמש בקישורים פרטיים. + בחר סוג שינוי גודל + הבן מפורש, גמיש, חיתוך והתאמה לפני שתכריח תמונה לממדים חדשים + שליטה על צורה, בד ויחס רוחב-גובה + סוג שינוי גודל מחליט מה קורה כאשר הרוחב והגובה המבוקשים אינם תואמים ליחס הגובה-רוחב המקורי. זה ההבדל בין מתיחת פיקסלים, שמירה על פרופורציות, חיתוך שטח נוסף או הוספת קנבס רקע. + השתמש במפורש רק כאשר עיוות מקובל או שלמקור כבר יש את אותו יחס רוחב-גובה כמו היעד. + השתמש בגמיש כדי לשמור על פרופורציות על ידי שינוי גודל מהצד החשוב, במיוחד עבור תמונות וקבוצות מעורבות. + השתמש ב-Crop עבור מסגרות קפדניות ללא גבולות, או ב-Fit כאשר כל התמונה חייבת להישאר גלויה בתוך קנבס קבוע. + בחר מצב קנה מידה של תמונה + בחר את מסנן הדגימה מחדש השולט בחדות, חלקות, מהירות וחפצי קצה + שנה גודל ללא רכות מקרית + מצב קנה המידה של התמונה שולט כיצד פיקסלים חדשים מחושבים במהלך שינוי הגודל. מצבים פשוטים הם מהירים וצפויים, בעוד שמסננים מתקדמים יכולים לשמר את הפרטים בצורה טובה יותר, אך עשויים לחדד קצוות, ליצור הילות או להימשך זמן רב יותר בתמונות גדולות. + השתמש בבסיסי או ביליניארי לשינוי גודל יומיומי מהיר כאשר התוצאה צריכה להיות קטנה יותר ונקייה בלבד. + השתמש במצבים בסגנון Lanczos, Robidoux, Spline או EWA כאשר יש חשיבות לפרטים עדינים, טקסט או הקטנת קנה מידה באיכות גבוהה. + השתמש ב-Nearest עבור אמנות פיקסלים, אייקונים וגרפיקה עם קצוות קשים שבהם פיקסלים מטושטשים יהרסו את המראה. + קנה מידה של מרחב צבע + החלט כיצד יתמזגו צבעים בעוד פיקסלים מודגמים מחדש במהלך שינוי הגודל + שמור על שיפועים וקצוות טבעיים + קנה מידה של מרחב הצבע משנה את המתמטיקה המשמשת בעת שינוי הגודל. רוב התמונות מתאימות לברירת המחדל, אבל רווחים ליניאריים או תפיסתיים יכולים לגרום להדרגות, קצוות כהים וצבעים רוויים להיראות נקיים יותר לאחר שינוי קנה מידה חזק. + השאר את ברירת המחדל כאשר המהירות והתאימות חשובות יותר מאשר הבדלי צבעים זעירים. + נסה מצבים ליניאריים או תפיסתיים כאשר קווי מתאר כהים, צילומי מסך של ממשק משתמש, מעברי צבע או פסי צבע נראים שגויים לאחר שינוי הגודל. + השווה לפני ואחרי במסך היעד האמיתי, מכיוון ששינויים במרחב הצבעים יכולים להיות עדינים ותלויים בתוכן. + הגבל מצבי שינוי גודל + דע מתי יש לדלג על תמונות מגבלת יתר, לקוד מחדש או להקטין אותן כדי שיתאימו + בחר מה יקרה בגבול + Limit Resize הוא לא רק כלי עם תמונה קטנה יותר. המצב שלה מחליט מה האפליקציה עושה כאשר מקור כבר נמצא בגבולות שלך או כאשר רק צד אחד שובר את הכלל, מה שחשוב לתיקיות מעורבות ולהכנה להעלאה. + השתמש בדלג כאשר יש להשאיר תמונות בתוך המגבלות ללא נגיעה ולא לייצא שוב. + השתמש ב-Recode כאשר הממדים מקובלים אך אתה עדיין זקוק לפורמט, התנהגות מטא נתונים, איכות או שם קובץ חדשים. + השתמש ב-Zoom כאשר יש להקטין תמונות הפורצות את המגבלות באופן פרופורציונלי עד שהן מתאימות לתיבה המותרת. + יעדי יחס גובה-רוחב + הימנע מפרצופים מתוחים, קצוות חתוכים וגבולות בלתי צפויים בגדלי פלט קפדניים + התאם את הצורה לפני שינוי הגודל + רוחב וגובה הם רק חצי ממטרה לשינוי גודל. יחס הגובה-רוחב מחליט אם התמונה יכולה להתאים באופן טבעי, צריכה חיתוך או צריכה קנבס סביבה. בדיקת הצורה תחילה מונעת את תוצאות שינוי הגודל המפתיעות ביותר. + השווה את צורת המקור לצורת היעד לפני בחירת מפורש, חיתוך או התאמה. + חתוך תחילה כאשר הנושא יכול לאבד רקע נוסף והיעד זקוק למסגרת קפדנית. + השתמש בהתאמה או גמיש כאשר כל חלק בתמונה המקורית חייב להישאר גלוי. + שינוי גודל יוקרתי או רגיל + דע מתי הוספת פיקסלים צריכה AI ומתי די בקנה מידה פשוט + אל תבלבל בין גודל לפרטים + שינוי גודל רגיל משנה מידות על ידי אינטרפולציה של פיקסלים; הוא לא יכול להמציא פרטים אמיתיים. דרגת בינה מלאכותית יכולה ליצור פרטים חדים יותר למראה, אבל היא אטית יותר ועלולה להזות מרקם, כך שהבחירה הנכונה תלויה אם אתה זקוק לתאימות, מהירות או שיקום חזותי. + השתמש בשינוי גודל רגיל עבור תמונות ממוזערות, מגבלות העלאה, צילומי מסך ושינויים פשוטים בממדים. + השתמש ב-AI upscale כאשר תמונה קטנה או רכה צריכה להיראות טוב יותר בגודל גדול יותר. + השווה פרצופים, טקסט ודפוסים לאחר יוקרה בינה מלאכותית מכיוון שפרטים שנוצרו יכולים להיראות משכנעים אך לא נכונים. + סדר הסינון חשוב + החל ניקוי, צבע, חדות ודחיסה סופית ברצף מכוון + בנה ערימת מסננים נקייה יותר + מסננים לא תמיד ניתנים להחלפה. טשטוש לפני חידוד, הגברת ניגודיות לפני OCR, או שינוי צבע לפני דחיסה יכולים להפיק פלט שונה מאוד מאותם פקדים. + חתוך וסובב קודם לכן מסננים מאוחרים יותר יפעלו רק על הפיקסלים שיישארו. + החל חשיפה, איזון לבן וניגודיות לפני חידוד או אפקטים מסוגננים. + שמור על שינוי גודל ודחיסה סופיים לקראת הסוף, כך שהפרטים המקדימים ישרדו את הייצוא באופן צפוי. + תקן קצוות רקע + נקי הילות, שאריות של צללים, שיער, חורים וקווי מתאר רכים לאחר הסרה אוטומטית + גרמו לגזרות להיראות מכוונות + מסכות אוטומטיות לרוב נראות טוב במבט חטוף אך חושפות בעיות על רקע בהיר, כהה או דוגמת. ניקוי קצה הוא ההבדל בין חיתוך מהיר לבין מדבקה, לוגו או תמונת מוצר לשימוש חוזר. + הצג תצוגה מקדימה של התוצאה על רקע מנוגדים כדי לחשוף הילות ופרטי קצה חסרים. + השתמש בשחזור לשיער, פינות וחפצים שקופים לפני מחיקת השאריות שמסביב. + ייצא בדיקה קטנה על צבע הרקע הסופי כאשר הגזרה תמוקם בעיצוב. + סימני מים קריאים + הפוך סימנים גלויים על תמונות בהירות, כהות, עמוסות וחתוכות מבלי להרוס את התמונה + הגן על התמונה מבלי להסתיר אותה + סימן מים שנראה טוב בתמונה אחת עלול להיעלם בתמונה אחרת. יש לבחור גודל, אטימות, ניגודיות, מיקום וחזרה עבור כל האצווה, לא רק בתצוגה המקדימה הראשונה. + בדוק את הסימן על התמונות הבהירות והכהות ביותר באצווה לפני ייצוא כל הקבצים. + השתמש באטימות נמוכה יותר עבור סימנים גדולים חוזרים ונשנים ובניגודיות חזקה יותר עבור סימני פינות קטנות. + שמור על פרצופים חשובים, טקסט ופרטי מוצר ברורים, אלא אם כן הסימן נועד למנוע שימוש חוזר. + פצל תמונה אחת לאריחים + חתוך תמונה בודדת לפי שורות, עמודות, אחוזים מותאמים אישית, פורמט ואיכות + הכן רשתות והזמנת חתיכות + פיצול תמונה יוצר פלטים מרובים מתמונת מקור אחת. זה יכול לפצל לפי ספירת שורות ועמודות, להתאים את אחוזי השורות או העמודות ולשמור חלקים עם פורמט הפלט והאיכות שנבחרו, וזה שימושי עבור רשתות, פרוסות דפים, פנורמות ופוסטים חברתיים מסודרים. + בחר את תמונת המקור ולאחר מכן הגדר את מספר השורות והעמודות שאתה צריך. + התאם את אחוזי השורות או העמודות כאשר החלקים לא צריכים להיות בגודל שווה. + בחר פורמט פלט ואיכות לפני השמירה, כך שכל יצירה שנוצרת משתמש באותם כללי ייצוא. + גזרו רצועות תמונה + הסר אזורים אנכיים או אופקיים ומזג את החלקים הנותרים בחזרה יחד + הסר אזור מבלי להשאיר פער + חיתוך תמונה שונה מחיתוך רגיל. זה יכול להסיר רצועה אנכית או אופקית שנבחרה, ואז למזג את חלקי התמונה הנותרים יחד. מצב הפוך שומר במקום זאת על האזור שנבחר, ושימוש בשני הכיוונים ההפוכים שומר על המלבן שנבחר. + השתמש בחיתוך אנכי עבור אזורי צד, עמודות או רצועות אמצעיות; השתמש בחיתוך אופקי עבור באנרים, רווחים או שורות. + אפשר הפוך על ציר כאשר ברצונך לשמור את החלק שנבחר במקום להסיר אותו. + תצוגה מקדימה של קצוות לאחר חיתוך מכיוון שתוכן ממוזג יכול להיראות פתאומי כאשר האזור שהוסר חצה פרטים חשובים. + בחר את פורמט הפלט + בחר JPEG, PNG, WebP, AVIF, JXL או PDF על סמך תוכן ויעד + תן ליעד להחליט על הפורמט + הפורמט הטוב ביותר תלוי במה שמכילה התמונה והיכן ישמש אותה. תמונות, צילומי מסך, נכסים שקופים, מסמכים וקבצי אנימציה, כולם נכשלים בדרכים שונות כאשר נאלצים להיכנס לפורמט הלא נכון. + השתמש ב-JPEG לתאימות תמונה רחבה כאשר אין צורך בשקיפות ובפיקסלים מדויקים. + השתמש ב-PNG לצילומי מסך חדים, לכידת ממשק משתמש, גרפיקה שקופה ועריכות ללא אובדן. + השתמש ב-WebP, AVIF או JXL כאשר יש חשיבות לפלט מודרני קומפקטי והאפליקציה המקבלת תומכת בכך. + חלץ כיסויי אודיו + שמור אמנות אלבום מוטבע או מסגרות מדיה זמינות מקובצי אודיו כתמונות PNG + משוך גרפיקה מתוך קובצי אודיו + Audio Covers משתמש במטא נתונים של מדיה של אנדרואיד כדי לקרוא גרפיקה מוטבעת מקובצי אודיו. כאשר יצירות אמנות מוטמעות אינן זמינות, הרטריבר עשוי לחזור למסגרת מדיה אם אנדרואיד יכול לספק כזו; אם אף אחד מהם לא קיים, הכלי מדווח שאין תמונה לחלץ. + פתח את כיסויי אודיו ובחר קובץ שמע אחד או יותר עם אמנות עטיפה צפויה. + סקור את הכריכה שחולצה לפני שמירה או שיתוף, במיוחד עבור קבצים מיישומי סטרימינג או הקלטה. + אם לא נמצאה תמונה, סביר להניח שלקובץ האודיו אין כיסוי משובץ או ש-Android לא יכול היה לחשוף מסגרת שמישה. + מטא נתונים של תאריכים ומיקום + החלט מה לשמר כאשר זמן הלכידה חשוב אך נתוני ה-GPS או המכשיר לא אמורים לדלוף + הפרד מטא נתונים שימושיים ממטא נתונים פרטיים + מטא נתונים לא כולם מסוכנים באותה מידה. תאריכי לכידה יכולים להיות שימושיים עבור ארכיונים ומיון, בעוד ש-GPS, שדות דמויי מכשיר, תגי תוכנה או שדות בעלים יכולים לחשוף יותר מהמתוכנן. + שמור תגי תאריך ושעה כאשר הסדר הכרונולוגי חשוב עבור אלבומים, סריקות או היסטוריית פרויקטים. + הסר GPS ותגיות מזהות מכשיר לפני שיתוף פומבי או שליחת תמונות לזרים. + ייצא דוגמה ובדוק שוב את EXIF ​​כאשר דרישות הפרטיות מחמירות. + הימנע מהתנגשויות של שמות קבצים + הגן על פלטי אצווה כאשר קבצים רבים חולקים שמות כמו image.jpg או screenshot.png + הפוך כל שם פלט לייחודי + שמות קבצים כפולים נפוצים כאשר תמונות מגיעות ממסרים, צילומי מסך, תיקיות ענן או מצלמות מרובות. תבנית טובה מונעת החלפה בשוגג ושומרת על התפוקות שניתן לעקוב אחר המקורות שלהן. + כלול שם מקורי בתוספת סיומת כאשר העותק הערוך צריך להיות מוכר. + הוסף רצף, חותמת זמן, הגדרה מראש או ממדים בעת עיבוד אצווה מעורבת גדולה. + הימנע מהחלפת זרימות עבודה עד שתוודא שתבנית השמות אינה יכולה להתנגש. + שמור או שתף + בחר בין קובץ מתמיד לבין מסירה זמנית לאפליקציה אחרת + ייצא עם הכוונה הנכונה + שמור ושתף יכול להרגיש דומה, אבל הם פותרים בעיות שונות. שמירה יוצרת קובץ שתוכל למצוא מאוחר יותר; שיתוף שולח תוצאה שנוצרת דרך אנדרואיד לאפליקציה אחרת ואולי לא ישאיר עותק מקומי עמיד. + השתמש בשמירה כאשר הפלט חייב להישאר בתיקייה ידועה, להיות מגובה או לעשות שימוש חוזר מאוחר יותר. + השתמש בשיתוף להודעות מהירות, קבצים מצורפים לדוא\"ל, פרסום חברתי או שליחת התוצאה לעורך אחר. + שמור תחילה כאשר האפליקציה המקבלת אינה אמינה או כאשר שיתוף כושל יהיה מעצבן ליצירה מחדש. + גודל ושוליים של עמוד PDF + בחר גודל נייר, התנהגות התאמה וחדר נשימה לפני יצירת מסמך + הפוך את דפי התמונה לניתנים להדפסה וקריאים + תמונות עלולות להפוך לדפי PDF מביכים כאשר צורתן אינה תואמת לגודל הנייר הנבחר. שוליים, מצב התאמה וכיוון העמוד מחליטים אם התוכן נחתך, זעיר, מתוח או נוח לקריאה. + השתמש בגודל נייר אמיתי כאשר ניתן להדפיס את ה-PDF או להגיש לטופס. + השתמש בשוליים עבור סריקות וצילומי מסך כדי שהטקסט לא יישב על קצה העמוד. + הצג תצוגה מקדימה של עמודים לאורך ולרוחב יחד כאשר תמונות המקור בעלות כיוון מעורב. + נקה סריקות + שפר את קצוות הנייר, הצללים, הניגודיות, הסיבוב והקריאה לפני PDF או OCR + הכן דפים לפני הארכיון + סריקה יכולה להיקלט טכנית אך עדיין קשה לקריאה. שלבי ניקוי קטנים לפני ייצוא PDF חשובים לעתים קרובות יותר מהגדרות דחיסה, במיוחד עבור קבלות, הערות בכתב יד ודפים עם תאורה חלשה. + תקן פרספקטיבה וחתוך את קצוות השולחן, האצבעות, הצללים והעומס ברקע. + הגדל את הניגודיות בזהירות כדי שהטקסט יהיה ברור יותר מבלי להרוס חותמות, חתימות או סימני עיפרון. + הפעל OCR רק לאחר שהדף זקוף וקריא, ולאחר מכן אמת מספרים חשובים באופן ידני. + דחוס, גווני אפור או תיקון קובצי PDF + השתמש בפעולות PDF של קובץ אחד עבור עותקים קלים יותר, ניתנים להדפסה או שנבנו מחדש + בחר את פעולת ניקוי ה-PDF + PDF Tools כולל פעולות נפרדות לדחיסה, המרת גווני אפור ותיקון. דחיסה וגוני אפור פועלים בעיקר באמצעות תמונות משובצות, בעוד תיקון בונה מחדש את מבנה ה-PDF; אין להתייחס לאף אחד מאלה כתיקון מובטח לכל מסמך. + השתמש ב-Compress PDF כאשר המסמך מכיל תמונות וגודל הקובץ חשוב לשיתוף. + השתמש בגווני אפור כאשר המסמך אמור להיות קל יותר להדפסה או שאינו זקוק לתמונות צבעוניות. + השתמש ב-Reparation PDF בעותק כאשר מסמך לא נפתח או שהמבנה הפנימי פגום, ולאחר מכן אמת את הקובץ שנבנה מחדש. + חלץ תמונות מקובצי PDF + שחזר תמונות PDF משובצות וארוז את התוצאות שחולצו לארכיון + שמור תמונות מקור ממסמכים + Extract Images סורק את ה-PDF לאיתור אובייקטי תמונה מוטבעים, מדלג על כפילויות במידת האפשר, ומאחסן תמונות משוחזרות בארכיון ZIP שנוצר. זה רק מחלץ תמונות שמוטמעות בפועל ב-PDF, לא טקסט, ציורים וקטוריים או כל עמוד גלוי כצילום מסך. + השתמש ב-Extract Images כאשר אתה צריך תמונות המאוחסנות בתוך PDF, כגון תמונות מקטלוג או נכסים סרוקים. + השתמש ב-PDF לתמונות או בחילוץ דפים במקום זאת כאשר אתה צריך דפים מלאים, כולל טקסט ופריסה. + אם הכלי מדווח שאין תמונות מוטמעות, ייתכן שהדף הוא תוכן טקסט/וקטור או שהתמונות לא יאוחסנו כאובייקטים שניתנים לחילוץ. + מטא נתונים, שיטוח ופלט הדפסה + ערוך מאפייני מסמך, רסטר דפים או הכן פריסות הדפסה מותאמות אישית בכוונה + בחר את פעולת המסמך הסופי + מטא נתונים של PDF, שיטוח והכנת הדפסה פותרים בעיות שונות בשלב הסופי. Metadata שולט במאפייני המסמך, Flatten PDF מסדר דפים ברסטר לפלט פחות ניתן לעריכה, ו-Print PDF מכין פריסה חדשה עם בקרות גודל, כיוון, שוליים ועמודים לגיליון. + השתמש במטא נתונים כאשר מחבר, כותרת, מילות מפתח, מפיק או ניקוי פרטיות חשובים. + השתמש בשטח PDF כאשר תוכן עמוד גלוי אמור להיות קשה יותר לעריכה כאובייקטי PDF נפרדים. + השתמש ב-Print PDF כאשר אתה צריך גודל עמוד, כיוון, שוליים או עמודים מרובים בכל גיליון. + הכן תמונות עבור OCR + השתמש בחיתוך, סיבוב, ניגודיות, דנויז וקנה מידה לפני שאתה מבקש מ-OCR לקרוא טקסט + תן פיקסלים נקיים יותר OCR + טעויות OCR מגיעות לרוב מתמונת הקלט ולא ממנוע ה-OCR. דפים עקומים, ניגודיות נמוכה, טשטוש, צללים, טקסט זעיר ורקעים מעורבים כולם מפחיתים את איכות הזיהוי. + חתוך לאזור הטקסט וסובב את התמונה כך שהקווים יהיו אופקיים לפני הזיהוי. + הגדל את הניגודיות או המרה לשחור-לבן נקי יותר רק כאשר זה מקל על קריאת האותיות. + שנה את גודל הטקסט הזעיר כלפי מעלה בצורה מתונה, אך הימנע מהשחזה אגרסיבית שיוצרת קצוות מזויפים. + בדוק את תוכן ה-QR + סקור קישורים, אישורי Wi-Fi, אנשי קשר ופעולות לפני שאתה נותן אמון בקוד + התייחס לטקסט מפוענח כאל קלט לא מהימן + קוד QR יכול להסתיר כתובת אתר, כרטיס איש קשר, סיסמת Wi-Fi, אירוע לוח שנה, מספר טלפון או טקסט רגיל. התווית הגלויה ליד הקוד עשויה שלא להתאים למטען בפועל, אז סקור את התוכן המפוענח לפני שתפעל על פיו. + בדוק את כתובת האתר המלאה המפוענחת לפני פתיחתה, במיוחד דומיינים מקוצרים או לא מוכרים. + היזהר עם קודי Wi-Fi, אנשי קשר, לוח שנה, טלפון ו-SMS מכיוון שהם יכולים להפעיל פעולות רגישות. + העתק תחילה את התוכן כאשר אתה צריך לאמת אותו במקום אחר במקום לפתוח אותו מיד. + צור קודי QR + צור קודים מטקסט רגיל, כתובות אתרים, Wi-Fi, אנשי קשר, דואר אלקטרוני, טלפון, SMS ונתוני מיקום + בנה את מטען QR הנכון + מסך ה-QR יכול ליצור קודים מתוכן שהוזן וכן לסרוק קודים קיימים. סוגי QR מובנים עוזרים לקודד נתונים בפורמט שאפליקציות אחרות מבינות, כגון פרטי התחברות ל-Wi-Fi, כרטיסי איש קשר, שדות דוא\"ל, מספרי טלפון, תוכן SMS, כתובות URL או קואורדינטות גיאוגרפיות. + בחר טקסט רגיל להערות פשוטות, או השתמש בסוג מובנה כאשר הקוד אמור להיפתח כ-Wi-Fi, איש קשר, כתובת אתר, דואר אלקטרוני, טלפון, SMS או נתוני מיקום. + בדוק כל שדה לפני שיתוף הקוד שנוצר מכיוון שהתצוגה המקדימה הגלויה משקפת רק את המטען שהזנת. + בדוק את קוד ה-QR השמור עם סורק כאשר הוא יודפס, יפורסם בפומבי או ישתמשו בו אנשים אחרים. + בחר מצב בדיקת סכום + חשב גיבוב מקבצים או טקסט, השווה קובץ אחד או בדוק קבצים רבים באצווה + אמת את התוכן, לא את המראה + ל-Checksum Tools יש כרטיסיות נפרדות לגיבוב קבצים, גיבוב טקסט, השוואת קובץ ל-hash ידוע והשוואת אצווה. סכום בדיקה מוכיח את זהות בית-עבור-בית עבור האלגוריתם הנבחר; זה לא מוכיח ששתי תמונות רק נראות דומות. + השתמש ב-Calculate עבור קבצים וב-Text Hash עבור נתוני טקסט מוקלדים או מודבקים. + השתמש בהשוואה כאשר מישהו נותן לך סכום בדיקה צפוי עבור קובץ אחד. + השתמש בהשוואה אצווה כאשר קבצים רבים זקוקים לגיבוב או אימות במעבר אחד, ולאחר מכן שמור על עקביות האלגוריתם שנבחר. + פרטיות הלוח + השתמש בתכונות הלוח האוטומטיות מבלי לחשוף בטעות נתונים שהועתקו באופן פרטי + שמור על תוכן מועתק בכוונה + אוטומציה של לוח הלוח היא נוחה, אך טקסט מועתק עשוי להכיל סיסמאות, קישורים, כתובות, אסימונים או הערות פרטיות. התייחס לקלט מבוסס לוח כאל תכונת מהירות שאמורה להתאים להרגלים ולציפיות הפרטיות שלך. + השבת את השימוש האוטומטי בלוח אם טקסט פרטי מופיע בכלים באופן בלתי צפוי. + השתמש בשמירה ללוח בלבד רק כאשר אתה רוצה בכוונה שהתוצאה תמנע מאחסון. + נקה או החלף את הלוח לאחר טיפול בטקסט רגיש, נתוני QR או מטענים של Base64. + פורמטים של צבע + הבן ערכי HEX, RGB, HSV, אלפא וערכי צבע מועתקים לפני הדבקה במקום אחר + העתק את הפורמט שהיעד מצפה לו + את אותו צבע גלוי אפשר לכתוב בכמה דרכים. כלי עיצוב, שדה CSS, ערך צבע אנדרואיד או עורך תמונות עשויים לצפות לסדר שונה, טיפול באלפא או מודל צבע שונה. + השתמש ב-HEX בעת הדבקה בשדות אינטרנט, עיצוב או ערכת נושא שמצפים לקודי צבע קומפקטיים. + השתמש ב-RGB או HSV כאשר אתה צריך להתאים ערוצים, בהירות, רוויה או גוון באופן ידני. + בדוק את אלפא בנפרד כאשר שקיפות חשובה מכיוון שחלק מהיעדים מתעלמים ממנה או משתמשים בסדר אחר. + עיין בספריית הצבעים + חפש צבעים בעלי שם לפי שם או HEX ושמור את המועדפים ליד החלק העליון + מצא צבעים בעלי שם לשימוש חוזר + ספריית צבע טוענת אוסף צבעים בעל שם, ממיינת אותו לפי שם, מסננת לפי שם צבע או ערך HEX, ומאחסנת צבעים מועדפים כך שיהיה קל יותר להגיע לערכים חשובים. זה שימושי כאשר אתה צריך צבע אמיתי בשם במקום דגימה מתמונה. + חפש לפי שם צבע כאשר אתה מכיר את המשפחה, כגון אדום, כחול, צפחה או מנטה. + חפש לפי HEX כאשר כבר יש לך צבע מספרי וברצונך למצוא ערכים תואמים או קרובים בשם. + סמן צבעים תכופים כמועדפים כדי שיתקדמו את הספרייה הכללית בזמן הגלישה. + השתמש באוספים של מעברי רשת + הורד משאבי מעברי רשת זמינים ושתף תמונות נבחרות + השתמש בנכסי מעברי רשת מרחוק + Mesh Gradients יכול לטעון אוסף משאבים מרוחק, להוריד תמונות הדרגתיות חסרות, להציג התקדמות ההורדה ולשתף מעברי צבע נבחרים. הזמינות תלויה בגישה לרשת ובמחסן המשאבים המרוחק, כך שרשימה ריקה אומרת בדרך כלל שהמשאבים עדיין לא היו זמינים. + פתח את Mesh Gradients מכלי Gradient כאשר אתה רוצה תמונות מעברי רשת מוכנות. + המתן להתקדמות טעינת המשאב או ההורדה לפני הנחה שהאוסף ריק. + בחר ושתף את מעברי הצבע הדרושים לך, או חזור ל- Gradient Maker כאשר אתה רוצה לבנות מעבר צבע מותאם אישית באופן ידני. + רזולוציית נכסים שנוצרה + בחר גודל פלט לפני יצירת מעברי צבע, רעש, טפטים, אמנות דמויית SVG או טקסטורות + צור בגודל שאתה צריך + לתמונות שנוצרו אין מצלמה מקורית שאפשר לחזור עליה. אם הפלט קטן מדי, קנה מידה מאוחר יותר יכול לרכך דפוסים, לשנות פרטים או לשנות את אופן העיבוד והדחיסה של הנכס. + תחילה הגדר מידות עבור מקרה השימוש הסופי, כגון טפט, סמל, רקע, הדפסה או שכבת-על. + השתמש בגדלים גדולים יותר עבור מרקמים שניתן לחתוך, להגדיל או לעשות בהם שימוש חוזר במספר פריסות. + ייצא גרסאות בדיקה לפני תפוקות ענק מכיוון שפרטים פרוצדורליים יכולים לשנות את אופן הפעולה של הדחיסה. + ייצוא טפטים נוכחיים + אחזר טפטים זמינים לבית, מנעול וטפטים מובנים מהמכשיר + שמור או שתף טפטים מותקנים + ייצוא טפטים קורא את הטפטים ש-Android חושף דרך ממשקי API של טפטים של המערכת. בהתאם למכשיר, גרסת Android, הרשאות וגרסת בנייה, ייתכן שטפטים של בית, מנעול או מובנים יהיו זמינים בנפרד או שהם חסרים. + פתח את ייצוא הטפטים כדי לטעון את הטפטים שהמערכת מאפשרת לאפליקציה לקרוא. + בחר את ערכי הבית, הנעילה או הטפטים המובנים הזמינים שברצונך לשמור, להעתיק או לשתף. + אם חסר טפט או שהתכונה אינה זמינה, לרוב מדובר במגבלה של מערכת, הרשאה או גרסת בנייה ולא הגדרת עריכה. + מצערת אנימציה של פינות + העתק צבע בשם + HEX + שם + דגם מחצלת פורטרט לגזרות מהירות של אדם עם שיער רך יותר וטיפול בקצוות. + שיפור מהיר בתאורה נמוכה באמצעות הערכת עקומת Zero-DCE++. + דגם שחזור תמונה של רסטורמר להפחתת פסי גשם וחפצים במזג אוויר רטוב. + מודל ביטול עיוות מסמך המחזה רשת קואורדינטות וממפה מחדש דפים מעוקלים לסריקה שטוחה יותר. + סולם משך תנועה + שולט במהירות הנפשה של האפליקציה: 0 משבית את התנועה, 1 הוא נורמלי, ערכים גבוהים יותר מאטים את ההנפשה + הצג כלים אחרונים + הצג גישה מהירה לכלים שבהם השתמשת לאחרונה במסך הראשי + סטטיסטיקת שימוש + חקור את פתיחת האפליקציה, השקות הכלים והכלים הנפוצים ביותר שלך + האפליקציה נפתחת + הכלי נפתח + כלי אחרון + שמירות מוצלחות + רצף פעילות + הנתונים נשמרו + פורמט עליון + כלים בשימוש + %1$d נפתח • %2$s + אין עדיין סטטיסטיקות שימוש + פתחו כמה כלים והם יופיעו כאן אוטומטית + סטטיסטיקת השימוש שלך מאוחסנת רק באופן מקומי במכשיר שלך. ImageToolbox משתמש בהם רק כדי להציג את הפעילות האישית שלך, הכלים המועדפים ותובנות הנתונים השמורים + עורך לערוך תמונה תמונה ריטוש התאמת + שנה גודל המרת קנה מידה רזולוציה ממדים רוחב גובה jpg jpeg png webp compress + לדחוס גודל משקל בתים mb kb צמצום איכות מיטוב + יחס רוחב-גובה של חיתוך לקצץ חיתוך + אפקט פילטר תיקון צבע להתאים מסכת lut + צייר מברשת צבע עיפרון סימון הערות + צופן להצפין סוד סיסמה לפענח + מסיר רקע מחק הסר מגזרת אלפא שקופה + תצוגה מקדימה של גלריית הצופה בדיקה פתוחה + תפר מיזוג שילוב פנורמה צילום מסך ארוך + כתובת אתר להורדת קישור לאינטרנט + בוחר טפטפת מדגם hex rgb צבע + פלטת צבעים דוגמיות לחלץ דומיננטי + exif metadata פרטיות הסר פס נקי מיקום GPS + השווה הבדל הבדל לפני אחרי + הגבל שינוי גודל מקסימום דקות מגה פיקסל ממדים מהדק + כלים לדפי מסמך pdf + טקסט ocr מזהה סריקה לחלץ + רשת צבע רקע שיפוע + שכבת על חותמת טקסט של לוגו סימן מים + אנימציה gif מונפשת להמיר מסגרות + אנימציה apng מונפשת png המרת מסגרות + zip archive compress pack לפרוק קבצים + jxl jpeg xl המרת פורמט תמונה של jpg + svg vector trace convert xml + פורמט המרת jpg jpeg png webp heic avif jxl bmp + סורק מסמכים סריקת נייר בפרספקטיבה חיתוך + סריקת סורק ברקוד qr + ערימת שכבת-על ממוצעת אסטרופוטוגרפיה חציונית + חלקי פרוסות רשת אריחים מפוצלים + color convert hex rgb hsl hsv cmyk lab + webp להמיר פורמט תמונה מונפש + מחולל רעש גרגר מרקם אקראי + לשלב פריסת רשת קולאז\' + שכבות סימון להעיר הערת שכבת-על + base64 מקודד לפענח נתונים uri + checksum hash md5 sha sha1 sha256 לאמת + רקע שיפוע רשת + exif metadata עריכת מצלמת תאריך gps + לחתוך חלקי רשת מפוצלים אריחים + כריכת אודיו מטא נתונים של מוסיקת אלבום + רקע ייצוא טפטים + דמויות אמנות טקסט של ascii + ai ml עצבי לשפר את הקטע היוקרתי + לוח חומרי ספריית הצבעים בשם צבעים + shader glsl fragment effect studio + pdf מיזוג שילוב הצטרף + pdf לפצל עמודים נפרדים + כיוון עמודים לסובב pdf + pdf סידור מחדש מיון דפים + עימוד מספרי דפי pdf + טקסט pdf ocr מזהה תמצית ניתנת לחיפוש + טקסט לוגו חותמת סימן מים pdf + ציור חתימה ב-pdf + pdf הגן על מנעול הצפנת סיסמה + pdf ביטול נעילת סיסמה לפענח הסר הגנה + pdf דחיסה להקטין גודל אופטימיזציה + pdf גווני אפור שחור לבן מונוכרום + תיקון pdf תיקון שחזור שבור + pdf מאפייני מטא נתונים exif כותרת המחבר + pdf הסר מחיקת דפים + שולי חיתוך pdf + הסברים לשטח ב-pdf יוצרים שכבות + תמונות לחלץ pdf תמונות + דחיסת ארכיון zip של pdf + מדפסת הדפסה pdf + תצוגה מקדימה של PDF לקרוא + תמונות ל-pdf להמיר מסמך jpg jpeg png + pdf לתמונות דפי ייצוא jpg png + pdf הערות הערות להסיר נקי + וריאנט ניטרלי + שגיאה + צל + גובה השן + טווח שיניים אופקי + טווח שיניים אנכי + Top Edge + קצה ימין + קצה תחתון + קצה שמאלי + קצה קרוע + אפס סטטיסטיקת שימוש + הכלי נפתח, שמירה נתונים סטטיסטיים, רצף, נתונים שמורים והפורמט העליון יאופסו. פתיחת האפליקציה תישאר ללא שינוי. + שֶׁמַע + קוֹבֶץ + ייצוא פרופילים + שמור את הפרופיל הנוכחי + מחק את פרופיל הייצוא + האם ברצונך למחוק את פרופיל הייצוא \\"%1$s\\"? + ציור גבול + הצג גבול דק מסביב לקנבס הציור והמחק + גַלִי + רכיבי ממשק משתמש מעוגלים עם קצה גלי רך + סְקוּפּ + לַחֲרוֹץ + פינות מעוגלות מגולפות פנימה + פינות מדורגות בחיתוך כפול + מציין מקום + השתמש ב-{filename} כדי להוסיף את שם הקובץ המקורי ללא סיומת + הַבהָקָה + כמות בסיס + כמות הטבעת + כמות ריי + רוחב הטבעת + עיוות פרספקטיבה + מראה ותחושה של Java + לִגזוֹז + זווית X + וזווית + שנה את גודל הפלט + טיפת מים + אֹרֶך גַל + שלב + High Pass + מסכת צבע + כרום + לְהִתְמוֹסֵס + רוֹך + משוב + טשטוש עדשה + קלט נמוך + קלט גבוה + תפוקה נמוכה + תפוקה גבוהה + אפקטי אור + גובה בליטה + רכות גבשושית + אַדְוָה + אמפליטודה X + משרעת Y + אורך גל X + אורך גל Y + טשטוש מסתגל + יצירת טקסטורה + צור טקסטורות פרוצדורליות שונות ושמור אותן כתמונות + סוג מרקם + הֲטָיָה + זְמַן + צִחצוּחַ + פְּזִירָה + דוגמאות + מקדם זווית + מקדם שיפוע + סוג רשת + כוח מרחק + סולם Y + חוֹסֶר בְּהִירוּת + סוג בסיס + גורם מערבולות + דֵרוּג + איטרציות + טבעות + מתכת מוברשת + קאוסטיקה + תָאִי + לוּחַ שַׁחְמָט + fBm + שַׁיִשׁ + פְּלַסמָה + כֶּסֶת + עֵץ + אַקרַאִי + מְרוּבָּע + מְשׁוּשֶׁה + מתומן + מְשּוּלָשׁ + מְתוּלָם + VL רעש + רעש SC + לאחרונה + עדיין אין מסננים בשימוש לאחרונה + גנרטור מרקם מוברש מתכת קאוסטיקס סלולר דמקה שיש פלזמה שמיכת עץ לבנים הסוואה תא ענן סדק בד עלווה חלת דבש קרח לבה ערפילית נייר חלודה חול עשן אבן שטח טופוגרפיה אדוות מים + לְבֵנָה + לְהַסווֹת + תָא + עָנָן + סֶדֶק + בַּד + עָלִים + חַלַת דְבַשׁ + קֶרַח + לָבָה + עַרְפִּילִית + נְיָר + חֲלוּדָה + חוֹל + עָשָׁן + אֶבֶן + פְּנֵי הַשֵׁטַח + טוֹפּוֹגרַפִיָה + אדוות מים + עץ מתקדם + רוחב טיט + חֲרִיגָה + שִׁפּוּעַ + חִספּוּס + סף ראשון + סף שני + סף שלישי + רכות קצה + רוחב גבול + כיסוי + פְּרָט + עוֹמֶק + מִסעָף + חוטים אופקיים + חוטים אנכיים + פאז + ורידים + תְאוּרָה + רוחב הסדק + כְּפוֹר + זְרִימָה + קְרוּם + צפיפות עננים + כוכבים + צפיפות סיבים + חוזק סיבים + כתמים + קורוזיה + פיטינג + פְּתִיתִים + תדירות דיונות + זווית רוח + אדוות + חוטים + סולם ורידים + מפלס המים + מפלס ההר + שְׁחִיקָה + מפלס שלג + ספירת שורות + עובי קו + הצללה + צבע נקבוביות + צבע מרגמה + צבע לבנים כהה + צבע לבנים + הדגש צבע + צבע כהה + צבע יער + צבע אדמה + צבע חול + צבע רקע + צבע התא + צבע קצה + צבע שמיים + צבע צל + צבע בהיר + צבע פני השטח + צבע וריאציה + צבע סדק + צבע עיוות + צבע ערב + צבע עלים כהה + צבע עלים + צבע גבול + צבע דבש + צבע עמוק + צבע קרח + צבע כפור + צבע קרום + צבע כביסה + צבע זוהר + צבע החלל + צבע סגול + צבע כחול + צבע בסיס + צבע סיבים + צבע כתם + צבע מתכת + צבע חלודה כהה + צבע חלודה + צבע כתום + צבע עשן + צבע וריד + צבע שפלה + צבע מים + צבע סלע + צבע שלג + צבע נמוך + צבע גבוה + צבע קו + צבע רדוד + דֶשֶׁא + עָפָר + עוֹר + בֵּטוֹן + אַספַלט + אֵזוֹב + אֵשׁ + זוֹהַר קוֹטבִי + כתם שמן + צִבעֵי מַיִם + זרימה מופשטת + אוֹפַּל + פלדת דמשק + בָּרָק + קְטִיפָה + מרבלינג דיו + נייר כסף הולוגרפי + ביולוגית + וורטקס קוסמי + מנורת לבה + אופק אירועים + פרקטל בלום + מנהרה כרומטית + ליקוי קורונה + מושך מוזר + כתר פרופלואיד + סופרנובה + קַשׁתִית + נוצת טווס + מעטפת נאוטילוס + כוכב לכת טבעתי + צפיפות להב + אורך להב + רוּחַ + טלאים + גושים + לַחוּת + חלוקי נחל + קמטים + נקבוביות + לְקַבֵּץ + סדקים + זֶפֶת + לִלבּוֹשׁ + כתמים + סיבים + תדר להבה + עָשָׁן + עָצמָה + סרטים + להקות + ססגוניות + חוֹשֶׁך + פורח + פִּיגמֶנט + קצוות + נְיָר + פִּעַפּוּעַ + סִימֶטרִיָה + חַדוּת + משחק צבעים + חלביות + שכבות + מִתקַפֵּל + פּוֹלָנִית + סניפים + כיוון + בָּרָק + קיפולים + נוצות + איזון דיו + ספֵּקטרוּם + קמטים + הִשׁתַבְּרוּת + נֶשֶׁק + לְהִתְפַּתֵל + זוהר ליבה + כתמים + הטיית דיסק + גודל אופק + רוחב דיסק + עדשה + עלי כותרת + סִלְסוּל + פִילִיגרָן + היבטים + עַקמוּמִיוּת + גודל ירח + גודל קורונה + קרניים + טבעת יהלום + אונות + צפיפות מסלול + עוֹבִי + קוצים + אורך ספייק + גודל גוף + מַתַכתִי + רדיוס הלם + רוחב מעטפת + נזרק החוצה + גודל אישון + גודל איריס + וריאציה בצבע + פנס + גודל עיניים + צפיפות דוקרנים + סיבובים + צ\'יימברס + פְּתִיחָה + רכסים + פנינה + גודל כוכב הלכת + הטיית טבעת + רוחב הטבעת + אַטמוֹספֵרָה + צבע לכלוך + צבע דשא כהה + צבע דשא + צבע העצה + צבע אדמה כהה + צבע יבש + צבע חלוקי נחל + צבע עור + צבע בטון + צבע זפת + צבע אספלט + צבע אבן + צבע אבק + צבע אדמה + צבע אזוב כהה + צבע אזוב + צבע אדום + צבע ליבה + צבע ירוק + צבע ציאן + צבע מגנטה + צבע זהב + צבע נייר + צבע פיגמנט + צבע משני + צבע ראשון + צבע שני + צבע ורוד + צבע פלדה כהה + צבע פלדה + צבע פלדה בהיר + צבע תחמוצת + צבע הילה + צבע בורג + צבע קטיפה + צבע ברק + צבע דיו כחול + צבע דיו אדום + צבע דיו כהה + צבע כסף + צבע צהוב + צבע רקמה + צבע דיסק + צבע חם + צבע העדשה + צבע חיצוני + צבע פנימי + צבע קורונה + צבע קר + צבע חם + צבע ענן + צבע להבה + צבע נוצה + צבע מעטפת + צבע פנינה + צבע כוכב הלכת + צבע טבעת + מחק קבצים מקוריים לאחר השמירה + רק קובצי מקור שעובדו בהצלחה יימחקו + קבצים מקוריים שנמחקו: %1$d + מחיקת הקבצים המקוריים נכשלה: %1$d + קבצים מקוריים שנמחקו: %1$d, נכשלו: %2$d + מחוות גיליון + אפשר גרירת גיליונות אפשרויות מורחבים + תת-דגימת כרומה + קצת עומק + ללא אובדן + %1$d-bit + שינוי שם אצווה + שנה שמות של קבצים מרובים באמצעות דפוסי שמות קבצים + שינוי שם קבצים שם קובץ דפוס רצף סיומת תאריך + בחר קבצים לשינוי שמות + דפוס שם קובץ + שנה שם + מקור תאריך + תאריך EXIF ​​שנלקח + תאריך שינוי הקובץ + תאריך יצירת הקובץ + תאריך נוכחי + תאריך ידני + תאריך ידני + זמן ידני + התאריך שנבחר אינו זמין עבור חלק מהקבצים. התאריך הנוכחי ישמש עבורם. + הזן דפוס של שם קובץ + התבנית מייצרת שם קובץ לא חוקי או ארוך מדי + הדפוס מייצר שמות קבצים כפולים + כל שמות הקבצים כבר תואמים לדפוס + דפוס זה משתמש באסימונים שאינם זמינים לשינוי שם אצווה + השם יישאר זהה + שמות הקבצים שונו בהצלחה + לא ניתן היה לשנות את שמם של חלק מהקבצים + הרשות לא ניתנה + משתמש בשם הקובץ המקורי ללא סיומת, ועוזר לך לשמור על זיהוי המקור ללא פגע. תומך גם בחיתוך עם \\o{start:end}, תעתיק עם \\o{t} והחלפה ב-\\o{s/old/new/}. + מונה הגדלה אוטומטית. תומך בעיצוב מותאם אישית: \\c{padding} (למשל \\c{3} -&gt; 001), \\c{start:step} או \\c{start:step:padding}. + שם תיקיית האב שבה נמצא הקובץ המקורי. + גודל הקובץ המקורי. תומך ביחידות: \\z{b}, \\z{kb}, \\z{mb} או רק \\z עבור פורמט קריא אנושי. + יוצר מזהה אקראי אוניברסלי ייחודי (UUID) כדי להבטיח ייחודיות של שם הקובץ. + לא ניתן לבטל שינוי שם של קבצים. ודא שהשמות החדשים נכונים לפני שתמשיך. + אשר את שינוי השם + הגדרות תפריט צד + סולם תפריטים + תפריט אלפא + איזון לבן אוטומטי + גֶזֶר + בודק סימני מים נסתרים + אִחסוּן + מגבלת מטמון + נקה את המטמון באופן אוטומטי כאשר הוא גדל מעבר לגודל זה + מרווח ניקוי + באיזו תדירות לבדוק אם יש לנקות את המטמון + בהפעלת האפליקציה + יום אחד + שבוע אחד + 1 חודש + נקה את מטמון האפליקציה באופן אוטומטי בהתאם למגבלה ולמרווח שנבחרו + שטח יבול נייד + מאפשר להזיז את כל אזור החיתוך על ידי גרירה בתוכו + מיזוג GIF + שלב קטעי GIF מרובים לאנימציה אחת + סדר קטעי GIF + לַהֲפוֹך + שחק כהפוך + בּוּמֵרַנְג + נגן קדימה ואז אחורה + נרמל את גודל המסגרת + קנה מידה של מסגרות כך שיתאימו לקליפ הגדול ביותר; כבה כדי לשמר את הסולם המקורי שלהם + השהיה בין קליפים (ms) + תיקיה + בחר את כל התמונות מתיקיה ומתיקיות המשנה שלה + כפיל Finder + מצא עותקים מדויקים ותמונות דומות מבחינה ויזואלית + מצא שכפול תמונות דומות עותקים מדויקים תמונות ניקוי אחסון dHash SHA-256 + בחר תמונות כדי למצוא כפילויות + רגישות לדמיון + עותקים מדויקים + תמונות דומות + בחר את כל הכפילויות המדויקות + בחר הכל מלבד מומלץ + לא נמצאו כפילויות + נסה להוסיף עוד תמונות או להגביר את רגישות הדמיון + לא ניתן היה לקרוא %1$d תמונות + %1$s • %2$s ניתן לתביעה חוזרת + %1$d קבוצות • %2$s ניתנות לתביעה חוזרת + %1$d נבחרו • %2$s + לא ניתן לבטל זאת. ייתכן ש-Android יבקש ממך לאשר את המחיקה בתיבת דו-שיח של מערכת. + לא נמצא + העבר כדי להתחיל + העבר לסוף + \ No newline at end of file diff --git a/core/resources/src/main/res/values-ja/strings.xml b/core/resources/src/main/res/values-ja/strings.xml new file mode 100644 index 0000000..dabf397 --- /dev/null +++ b/core/resources/src/main/res/values-ja/strings.xml @@ -0,0 +1,3738 @@ + + + + 問題が発生しました: %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 要素 + ファイル名の形式 + ファイル名の先頭に配置されるカスタム テキスト。プロジェクト名、ブランド、個人タグに最適です。 + ピクセル単位の画像幅。解像度の変更の追跡や結果のスケーリングに役立ちます。 + 画像の高さ (ピクセル単位)。アスペクト比やエクスポートを操作するときに役立ちます。 + ランダムな数字を生成して、一意のファイル名を保証します。重複に対する安全性を高めるために、さらに桁を追加します。 + 適用されたプリセット名をファイル名に挿入すると、画像がどのように処理されたかを簡単に思い出すことができます。 + 処理中に使用される画像スケーリング モードを表示し、サイズ変更、トリミング、またはフィットした画像を区別するのに役立ちます。 + ファイル名の末尾に配置されるカスタム テキスト。_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 ページから削除します + ハイパーリンク + 添付ファイル + ライン + ポップアップ + スタンプ + 形状 + テキストメモ + テキストのマークアップ + フォームフィールド + マークアップ + 未知 + 注釈 + グループを解除する + 構成可能な色とオフセットを使用してレイヤーの後ろにぼかしシャドウを追加します + コンテンツに応じた歪み + このアクションを完了するにはメモリが不足しています。小さい画像を使用するか、他のアプリを閉じるか、アプリを再起動してみてください。 + パラレルワーカー + 変換されたファイルは元のファイルよりも大きくなるため、これらの画像は保存されませんでした。元の画像を削除する前にこのリストを確認してください。 + スキップされたファイル: %1$s + アプリのログ + アプリのログを表示して問題を特定する + グループ化されたツールのお気に入り + ツールがタイプ別にグループ化されている場合に、お気に入りをタブとして追加します + お気に入りを最後に表示 + お気に入りツールタブを最後に移動します + 水平方向の間隔 + 垂直方向の間隔 + 逆方向エネルギー + 順方向エネルギー アルゴリズムの代わりに単純な勾配振幅エネルギー マップを使用する + マスクされた領域を削除する + 縫い目はマスクされた領域を通過し、選択した領域のみが保護されずに削除されます。 + VHS NTSC + 高度なNTSC設定 + より強力な VHS および放送アーティファクトのための追加のアナログ調整 + クロマブリード + テープの摩耗 + トラッキング + 輝度スミア + 鳴っている + + 使用フィールド + フィルターの種類 + 入力輝度フィルター + 入力クロマローパス + クロマ復調 + 位相シフト + 位相オフセット + ヘッドスイッチング + ヘッド切り替え高さ + ヘッド切り替えオフセット + ヘッド切り替えシフト + 正中位置 + ミッドラインジッター + トラッキングノイズの高さ + 追跡波 + 雪の追跡 + 雪の異方性の追跡 + トラッキングノイズ + トラッキングノイズ強度 + 複合ノイズ + 複合ノイズ周波数 + 複合ノイズ強度 + 複合ノイズの詳細 + 呼び出し周波数 + 鳴動電力 + ルマノイズ + ルマノイズ周波数 + ルマノイズ強度 + ルマノイズの詳細 + クロマノイズ + クロマノイズ周波数 + クロマノイズ強度 + クロマノイズの詳細 + 雪の強さ + 雪の異方性 + クロマ位相ノイズ + クロマ位相エラー + クロマディレイ水平 + 垂直方向のクロマディレイ + VHSテープ速度 + VHSの彩度の低下 + VHS シャープ強度 + VHS シャープ周波数 + VHSエッジウェーブ強度 + VHSエッジウェーブスピード + VHSエッジウェーブ周波数 + VHSエッジウェーブ詳細 + 出力クロマローパス + 水平スケール + 垂直スケール + スケールファクターX + スケール係数 Y + 一般的な画像の透かしを検出し、LaMa で修復します。検出および修復モデルを自動的にダウンロードします + 第二盲 + 画像を拡大する + YOLO v11 セグメンテーションを使用したオブジェクト セグメンテーション ベースの背景リムーバー + 線の角度を表示 + 描画中の現在のラインの回転を度単位で表示します。 + アニメーション絵文字 + 利用可能な絵文字をアニメーションとして表示する + 元のフォルダーに保存 + 選択したフォルダーではなく、元のファイルの隣に新しいファイルを保存します + 透かしPDF + メモリが足りません + 画像が大きすぎてこのデバイスで処理できないか、システムの使用可能なメモリが不足している可能性があります。画像の解像度を下げるか、他のアプリを閉じるか、より小さいファイルを選択してみてください。 + デバッグメニュー + アプリの機能をテストするためのメニュー。これは製品リリースでは表示されません。 + プロバイダー + PaddleOCR には、デバイスに追加の ONNX モデルが必要です。 %1$s データをダウンロードしますか? + ユニバーサル + 韓国人 + ラテン + 東スラブ語 + タイ語 + ギリシャ語 + 英語 + キリル + アラビア語 + デヴァナーガリー + タミル語 + テルグ語 + シェーダ + シェーダープリセット + シェーダが選択されていません + シェーダースタジオ + カスタム フラグメント シェーダの作成、編集、検証、インポート、エクスポート + 保存されたシェーダ + 保存されたシェーダーを開く、複製、エクスポート、共有、または削除 + 新しいシェーダ + シェーダファイル + .itshader JSON + %1$d パラメータ + シェーダソース + void main()の本体のみを記述します。 UV 座標には textureCoowned を使用し、ソース サンプラーとして inputImageTexture を使用します。 + 機能 + オプションのヘルパー関数、定数、および構造体が void main() の前に挿入されます。ユニフォームはparamsから生成されます。 + シェーダに編集可能な値が必要な場合は、ここにユニフォームを追加します。 + シェーダをリセットする + 現在のシェーダードラフトはクリアされます + 無効なシェーダ + サポートされていないシェーダ バージョン %1$d。サポートされているバージョン: %2$d。 + シェーダ名を空白にすることはできません。 + シェーダ ソースを空白にすることはできません。 + シェーダー ソースにサポートされていない文字 %1$s が含まれています。 ASCII GLSL ソースのみを使用してください。 + パラメータ \\\"%1$s\\\" が複数回宣言されています。 + パラメータ名を空白にすることはできません。 + パラメータ \\\"%1$s\\\" は予約された GPUImage 名を使用します。 + パラメータ \\\"%1$s\\\" は有効な GLSL 識別子である必要があります。 + パラメータ \\\"%1$s\\\" の %2$s 値タイプは \\\"%3$s\\\" ですが、予期される値は \\\"%4$s\\\" です。 + パラメータ \\\"%1$s\\\" はブール値の最小値または最大値を定義できません。 + パラメータ \\\"%1$s\\\" の最小値が最大値より大きくなっています。 + パラメータ \\\"%1$s\\\" のデフォルトは最小値よりも小さいです。 + パラメータ \\\"%1$s\\\" のデフォルトは最大値より大きくなります。 + シェーダーは「uniform Sampler2D %1$s;」を宣言する必要があります。 + シェーダーは「variing vec2 %1$s;」を宣言する必要があります。 + シェーダーは「void main()」を定義する必要があります。 + シェーダーはカラーを gl_FragColor に書き込む必要があります。 + ShaderToy mainImage シェーダはサポートされていません。 GPUImage の void main() コントラクトを使用します。 + アニメーション化された ShaderToy のユニフォーム「iTime」はサポートされていません。 + アニメーション化された ShaderToy の均一な「iFrame」はサポートされていません。 + ShaderToy ユニフォーム「iResolution」はサポートされていません。 + ShaderToy iChannel テクスチャはサポートされていません。 + 外部テクスチャはサポートされていません。 + 外部テクスチャ拡張機能はサポートされていません。 + Libretro シェーダ パラメータはサポートされていません。 + サポートされる入力テクスチャは 1 つだけです。 「ユニフォーム %1$s %2$s;」を削除します。 + パラメータ \\\"%1$s\\\" は、\\\"uniform %2$s %1$s;\\\" として宣言する必要があります。 + パラメータ \\\"%1$s\\\" は \\\"uniform %2$s %1$s;\\\" として宣言されていますが、予期された \\\"uniform %3$s %1$s;\\\" です。 + シェーダーファイルが空です。 + シェーダー ファイルは、バージョン、名前、およびシェーダー フィールドを含む .itshader JSON オブジェクトである必要があります。 + シェーダー ファイルが有効な JSON でないか、.itshader 形式と一致しません。 + フィールド「%1$s」は必須です。 + %1$s は必須です。 + %1$s 「%2$s」はサポートされていません。サポートされているタイプ: %3$s。 + %1$s は「%2$s」パラメータに必要です。 + %1$s は有限数でなければなりません。 + %1$s は整数でなければなりません。 + %1$s は true または false である必要があります。 + %1$s は、カラー文字列、RGB/RGBA 配列、またはカラー オブジェクトである必要があります。 + %1$s は #RRGGBB または #RRGGBBAA 形式を使用する必要があります。 + %1$s には、有効な 16 進数のカラー チャネルが含まれている必要があります。 + %1$s には 3 つまたは 4 つのカラー チャネルが含まれている必要があります。 + %1$s は 0 ~ 255 の範囲にする必要があります。 + %1$s は、2 つの数値の配列、または x と y を含むオブジェクトでなければなりません。 + %1$s には正確に 2 つの数字が含まれている必要があります。 + 重複 + EXIFを常にクリアする + ツールがメタデータの保持を要求した場合でも、保存時に画像の EXIF データを削除します + ヘルプとヒント + %1$d チュートリアル + ツールを開く + 主要なツール、エクスポート オプション、PDF フロー、カラー ユーティリティ、および一般的な問題の修正方法について学びます。 + はじめる + ツールの選択、ファイルのインポート、結果の保存、設定の再利用を迅速に行う + 画像編集 + 画像のサイズ変更、切り抜き、フィルター、背景の消去、描画、透かし入れ + ファイルとメタデータ + 形式の変換、出力の比較、プライバシーの保護、ファイル名の制御 + PDFとドキュメント + PDF の作成、ドキュメントのスキャン、OCR ページの作成、共有用のファイルの準備 + テキスト、QR、データ + テキストの認識、コードのスキャン、Base64 のエンコード、ハッシュの確認、ファイルのパッケージ化 + カラーツール + 色の選択、パレットの構築、カラー ライブラリの探索、グラデーションの作成 + クリエイティブツール + SVG、ASCII アート、ノイズ テクスチャ、シェーダー、実験的なビジュアルを生成 + トラブルシューティング + 重いファイル、透明性、共有、インポート、レポートの問題を修正する + 適切なツールを選択してください + カテゴリ、検索、お気に入り、共有アクションを使用して、適切な場所から開始します + 最適なエントリーポイントから始める + Image Toolbox には焦点を絞ったツールが多数あり、一見するとそれらの多くは重複しています。完了したいタスクから開始して、結果の最も重要な部分 (サイズ、形式、メタデータ、テキスト、PDF ページ、色、レイアウト) を制御するツールを選択します。 + サイズ変更、PDF、EXIF、OCR、QR、色などのアクション名がわかっている場合は、検索を使用します。 + タスクの種類だけがわかっている場合はカテゴリを開き、頻繁に使用するツールをお気に入りとして固定します。 + 別のアプリが画像を Image Toolbox に共有する場合、共有シート フローからツールを選択します。 + 結果をインポート、保存、共有する + 入力の選択から編集したファイルのエクスポートまでの通常の流れを理解する + 一般的な編集フローに従う + ほとんどのツールは、入力の選択、オプションの調整、プレビュー、保存または共有という同じリズムに従います。重要な点は、保存すると出力ファイルが作成されるのに対し、共有は通常、別のアプリにすばやく引き継ぐのに最適であるということです。 + 単一イメージ ツールの場合は 1 つのファイルを選択し、ピッカーで許可されている場合はバッチ ツールの場合は複数のファイルを選択します。 + 保存する前にプレビューと出力コントロール、特に形式、品質、ファイル名のオプションを確認してください。 + 共有を使用して素早いハンドオフを行うか、選択したフォルダーに永続的なコピーが必要な場合に保存します。 + 多くの画像を一度に操作する + バッチ ツールは、サイズ変更、変換、圧縮、名前付けタスクを繰り返す場合に最適です。 + 予測可能なバッチを準備する + すべての出力が同じルールに従う必要がある場合、バッチ処理が最も速くなります。また、大きな間違いを犯しやすい場所でもあるため、長いエクスポートを開始する前に、名前、品質、メタデータ、フォルダーの動作を選択してください。 + 関連する画像をすべて一緒に選択し、出力がシーケンスに依存する場合は順序を維持します。 + エクスポートを開始する前に、サイズ変更、形式、品質、メタデータ、およびファイル名のルールを 1 回設定します。 + ソース ファイルが大きい場合、またはターゲット形式が初めての場合は、最初に小さなテスト バッチを実行します。 + 素早い単一編集 + 1 つの画像に複数の簡単な変更を加える必要がある場合は、オールインワン エディターを使用します。 + ツールホッピングを行わずに 1 つの画像を仕上げる + 単一編集は、画像に 1 つの特殊な操作ではなく、一連の小さな変更が必要な場合に便利です。通常、バッチ ワークフローを構築せずに、簡単なクリーンアップ、プレビュー、最終コピーのエクスポートを行うほうが高速です。 + 一般的な調整、マークアップ、トリミング、回転、またはエクスポートの変更が必要な 1 つの画像に対して [単一編集] を開きます。 + フィルター前のトリミングや最終圧縮前のフィルターなど、変更するピクセルが最初に少ない順序で編集を適用します。 + バッチ処理、正確なファイル サイズのターゲット、PDF 出力、OCR、またはメタデータのクリーンアップが必要な場合は、代わりに専用のツールを使用してください。 + プリセットと設定を使用する + 繰り返し選択した内容を保存し、好みのワークフローに合わせてアプリを調整します + 同じ設定を繰り返さないようにする + プリセットと設定は、同じ種類のファイルを頻繁にエクスポートする場合に便利です。優れたプリセットを使用すると、繰り返し行われる手動チェックリストが 1 回のタップに変わり、グローバル設定は新しいツールの開始時のデフォルトを定義します。 + 一般的な出力サイズ、形式、品質値、または命名パターンのプリセットを作成します。 + デフォルトの形式、品質、保存フォルダー、ファイル名、ピッカー、インターフェイスの動作の設定を確認します。 + アプリを再インストールしたり、別のデバイスに移動したりする前に、設定をバックアップしてください。 + 便利なデフォルトを設定する + デフォルトの形式、品質、スケール モード、サイズ変更の種類、色空間を一度選択します + 新しいツールを目標に近づけて開始できるようにする + デフォルト値は単なる見た目の設定ではありません。これらは、多くのツールで最初に表示される形式、品質、サイズ変更動作、スケール モード、色空間を決定するため、エクスポートのたびに同じ選択肢を再選択する必要がなくなります。 + 写真の場合は JPEG、アルファの場合は PNG/WebP など、通常の出力先と一致するようにデフォルトの画像形式と品質を設定します。 + 厳密なサイズ、ソーシャル プラットフォーム、またはドキュメント ページ用にエクスポートすることが多い場合は、デフォルトのサイズ変更タイプとスケール モードを調整します。 + ワークフローでディスプレイ、印刷、または外部エディタ間で一貫したカラー処理が必要な場合にのみ、カラー スペースのデフォルトを変更します。 + ピッカーとランチャー + アプリが開くダイアログが多すぎる場合、または間違った場所から起動する場合は、ピッカー設定を使用します。 + ファイルを開く習慣を習慣に合わせる + ピッカーとランチャーの設定は、Image Toolbox をメイン画面、ツール、またはファイル選択フローのいずれから開始するかを制御します。これらのオプションは、通常 Android 共有シートから入力する場合、またはアプリをツール ランチャーのようにしたい場合に特に便利です。 + システム ピッカーがファイルを非表示にする場合、最近のアイテムが多すぎる場合、またはクラウド ファイルの処理が不十分な場合は、フォト ピッカー設定を使用します。 + 通常共有から入力するツール、またはソース ファイルをすでに知っているツールに対してのみスキップ ピッキングを有効にします。 + Image Toolbox を通常のホーム画面よりもツール ピッカーのように動作させたい場合は、ランチャー モードを検討してください。 + 画像ソース + ギャラリー、ファイル マネージャー、クラウド ファイルに最適なピッカー モードを選択してください + 適切なソースからファイルを選択する + 画像ソースの設定は、アプリが Android に画像を要求する方法に影響します。最適な選択は、ファイルがシステム ギャラリー、ファイル マネージャー フォルダー、クラウド プロバイダー、または一時コンテンツを共有する別のアプリのいずれに存在するかによって異なります。 + 一般的なギャラリー画像に対して最もシステムに統合されたエクスペリエンスが必要な場合は、デフォルトのピッカーを使用します。 + アルバム、フォルダー、クラウド ファイル、または標準以外の形式が期待した場所に表示されない場合は、ピッカー モードに切り替えてください。 + 共有ファイルがソース アプリから離れた後に消えた場合は、永続的なピッカーを使用して再度開くか、最初にローカル コピーを保存します。 + お気に入りと共有ツール + メイン画面に焦点を当てたままにして、共有フローで意味のないツールを非表示にします。 + ツールリストのノイズを減らす + お気に入りと非表示の共有設定は、毎日少数のツールしか使用しない場合に便利です。また、他のアプリが送信するファイルの種類を使用できないツールを非表示にすることで、共有フローを実用的に保ちます。 + ツールがカテゴリ別にグループ化されている場合でも、頻繁に使用するツールに簡単にアクセスできるように固定します。 + 別のアプリからのファイルに関係のないツールを共有フローから非表示にします。 + お気に入りを最後に置いたり、関連ツールとグループ化したりしたい場合は、お気に入りの順序設定を使用します。 + 設定をバックアップする + プリセット、環境設定、アプリの動作を別のインストール場所に安全に移動します + セットアップをポータブルに保ちます + バックアップと復元は、プリセット、フォルダー、ファイル名ルール、ツールの順序、および視覚的な設定を調整した後に最も役立ちます。バックアップを使用すると、メモリからワークフローを再構築することなく、設定を試したり、アプリを再インストールしたりできます。 + プリセット、ファイル名の動作、デフォルトの形式、またはツールの配置を変更した後は、バックアップを作成します。 + データの消去、再インストール、デバイスの移動を計画している場合は、アプリ フォルダー以外の場所にバックアップを保存します。 + 重要なバッチを処理する前に設定を復元して、デフォルトと保存動作が古い設定と一致するようにします。 + 画像のサイズ変更と変換 + 1 つまたは複数の画像のサイズ、形式、品質、メタデータを変更する + サイズ変更したコピーを作成する + サイズ変更と変換は、予測可能なサイズと軽量の出力ファイルを実現するための主要なツールです。画像のサイズ、出力形式、メタデータの動作がすべて同時に重要な場合に使用します。 + 1 つ以上の画像を選択し、寸法、縮尺、ファイル サイズのどれが最も重要かを選択します。 + 出力形式と品質を選択します。透明度を維持する必要がある場合は、PNG または WebP を使用してください。 + 結果をプレビューし、元のファイルを変更せずに、生成されたコピーを保存または共有します。 + ファイルサイズに応じてサイズを変更する + Web サイト、メッセンジャー、またはフォームが重い画像を拒否する場合は、サイズ制限をターゲットにする + 厳格なアップロード制限に適合する + 宛先が特定の制限を下回るファイルのみを受け入れる場合、品質値を推測するよりもファイル サイズによるサイズ変更の方が優れています。ツールは、結果を使用可能な状態に保ちながら、ターゲットに到達するように圧縮と寸法を調整する場合があります。 + メタデータとプロバイダーの変更のための余地を残すために、実際のアップロード制限よりわずかに低いターゲット サイズを入力します。 + 対象が小さい場合は、写真には非可逆形式を選択します。 PNG はサイズ変更後も大きいままです。 + 極端なサイズのターゲットを使用すると、目に見えるアーティファクトが発生する可能性があるため、エクスポート後に面、テキスト、エッジを検査してください。 + サイズ変更の制限 + 最大の幅、高さ、またはファイルの制限を超える画像のみを縮小します + すでに問題のない画像のサイズ変更は避けてください + サイズ変更の制限は、巨大な画像とすでに準備されている画像が混在するフォルダーに役立ちます。すべてのファイルに同じサイズ変更を強制するのではなく、許容可能な画像を元の品質に近づけます。 + すべてのファイルのサイズをやみくもに変更するのではなく、出力先に基づいて最大サイズまたは制限を設定します。 + ソースよりも重くなる出力を回避したい場合は、「大きい場合はスキップ」スタイルの動作を有効にします。 + これは、ソース サイズが大きく異なるさまざまなカメラ、スクリーンショット、またはダウンロードからのバッチに使用します。 + トリミング、回転、直線化 + 画像をエクスポートしたり他のツールで使用したりする前に、重要な領域をフレームに入れてください + コンポジションをクリーンアップする + 最初にトリミングすると、後のフィルター、OCR、PDF ページ、共有結果がきれいになります。 + [切り抜き] を開き、調整する画像を選択します。 + 出力が投稿、ドキュメント、またはアバターに適合する必要がある場合は、自由トリミングまたはアスペクト比を使用します。 + 保存する前に回転または反転すると、後のツールが修正された画像を受け取ることができます。 + フィルターと調整を適用する + 編集可能なフィルター スタックを構築し、エクスポート前に結果をプレビューする + 画像の外観を調整する + フィルターは、素早い修正、様式化された出力、OCR または印刷用の画像の準備に役立ちます。画像にテキストや細かい部分が含まれている場合は、コントラスト、シャープネス、明るさの小さな変更が大きな効果よりも重要になることがあります。 + フィルターを 1 つずつ追加し、変更するたびにプレビューを確認します。 + タスクに応じて、明るさ、コントラスト、シャープネス、ぼかし、色、またはマスクを調整します。 + プレビューがターゲットのデバイス、ドキュメント、またはソーシャル プラットフォームに一致する場合にのみエクスポートします。 + 最初にプレビューする + より高度な編集、変換、またはクリーンアップ ツールを選択する前に画像を検査してください + 実際に届いたものを確認する + 画像プレビューは、ファイルがチャット、クラウド ストレージ、または別のアプリから取得されたもので、その内容がわからない場合に便利です。まず寸法、透明度、向き、視覚的な品質をチェックすると、適切なフォローアップ ツールを選択するのに役立ちます。 + 処理を決定する前に複数の画像を検査する必要がある場合は、画像プレビューを開きます。 + 回転の問題、透明な領域、圧縮アーティファクト、予期せぬ大きなサイズを探してください。 + 修正が必要な部分がわかった後でのみ、サイズ変更、トリミング、比較、EXIF、または背景リムーバーに移動してください。 + 背景を削除または復元する + 背景を自動的に消去するか、復元ツールを使用してエッジを手動で調整します + 件名を保持し、残りを削除します + バックグラウンドの削除は、自動選択と慎重な手動クリーンアップを組み合わせると最も効果的です。 JPEG では、たとえプレビューが正しく見えたとしても、透明な領域が平坦化されるため、最終的なエクスポート形式はマスクと同じくらい重要です。 + 被写体が背景からはっきりと分離されている場合は、自動削除から始めます。 + ズームインして消去と復元を切り替えて、髪、角、影、細かい部分を修正します。 + 最終ファイルに透明性が必要な場合は、PNG または WebP として保存します。 + 描画、マークアップ、透かしを入れる + 矢印、メモ、ハイライト、署名、または繰り返し透かしオーバーレイを追加します。 + 目に見える情報を追加する + マークアップ ツールは画像を説明するのに役立ち、透かしはエクスポートされたファイルのブランド化や保護に役立ちます。 + フリーハンドのメモ、図形、強調表示、または簡単な注釈が必要な場合は、「描画」を使用します。 + 同じテキストまたは画像マークを出力に一貫して表示する必要がある場合は、透かしを使用します。 + マークが表示されても邪魔にならないように、エクスポートする前に不透明度、位置、スケールを確認してください。 + デフォルトの描画 + 線幅、色、パスモード、方向ロック、拡大鏡を設定してマークアップを高速化します + 描画を予測可能なものにする + 描画設定は、スクリーンショットに注釈を付けたり、文書にマークを付けたり、画像に署名したりする場合に役立ちます。デフォルトではセットアップ時間が短縮され、拡大鏡と方向ロックにより小さな画面でも正確な編集が簡単になります。 + デフォルトの線幅と描画色を、矢印、ハイライト、または署名に最もよく使用する値に設定します。 + 通常、同じ種類のストローク、シェイプ、またはマークアップを描画する場合は、デフォルトのパス モードを使用します。 + 指入力で細かい部分を正確に配置するのが難しい場合は、拡大鏡または方向ロックを有効にします。 + 複数画像のレイアウト + スクリーンショット、スキャン、または比較ストリップを配置する前に、適切なマルチ画像ツールを選択してください + 適切なレイアウト ツールを使用する + これらのツールは似ていますが、それぞれが異なる種類のマルチ画像出力用に調整されています。最初に正しいものを選択すると、異なる結果を目的として設計されたレイアウト コントロールと競合することがなくなります。 + コラージュ メーカーを使用して、1 つの構成に複数の画像を含むレイアウトを設計します。 + 画像を 1 つの長い垂直または水平のストリップにする必要がある場合は、画像のステッチまたはスタッキングを使用します。 + 1 つの大きな画像を複数の小さな部分に分割する必要がある場合は、画像の分割を使用します。 + 画像フォーマットを変換する + ピクセルを手動で編集せずに、JPEG、PNG、WebP、AVIF、JXL、その他のコピーを作成します + ジョブの形式を選択してください + 写真、スクリーンショット、透明度、圧縮、または互換性の点で、さまざまな形式が適しています。変換先のアプリが何をサポートしているのか、ソースに保持したいアルファ、アニメーション、またはメタデータが含まれているかどうかを理解している場合、変換は最も安全になります。 + 互換性のある写真には JPEG、ロスレス画像とアルファには PNG、コンパクトな共有には WebP を使用します。 + フォーマットがサポートしている場合は品質を調整します。値を小さくするとサイズは小さくなりますが、アーティファクトが追加される可能性があります。 + 変換されたファイルがターゲット アプリで正しく開くことを確認するまで、元のファイルは保管しておいてください。 + EXIF とプライバシーを確​​認する + 機密性の高い写真を共有する前にメタデータを表示、編集、または削除します + 隠し画像データの制御 + EXIF には、カメラ データ、日付、場所、向き、および画像には表示されないその他の詳細が含まれる場合があります。公開共有、サポートレポート、マーケットプレイスのリスト、またはプライベートな場所を明らかにする可能性のある写真を公開する前に、チェックする価値があります。 + 位置情報やプライベートなデバイス情報が含まれる可能性のある写真を共有する前に、EXIF ツールを開いてください。 + 機密フィールドを削除するか、日付、方向、作成者、カメラ データなどの間違ったタグを編集してください。 + プライバシーが重要な場合は、オリジナルの代わりに新しいコピーを保存し、そのコピーを共有します。 + 自動EXIFクリーンアップ + 日付を保持するかどうかを決定する際に、デフォルトでメタデータを削除します + プライバシーをデフォルトにする + EXIF 設定グループは、エクスポートのたびにプライバシーのクリーンアップを記憶するのではなく、自動的にプライバシーのクリーンアップを実行したい場合に便利です。日付は並べ替えには便利ですが、共有には機密性が高いため、日付時刻を保持するかどうかは別の決定となります。 + エクスポートのほとんどがパブリックまたはセミパブリック共有を目的としている場合は、常にクリアな EXIF を有効にします。 + すべてのタイムスタンプを削除するよりもキャプチャ時間を保持することが重要な場合にのみ、Keep Date Time を有効にします。 + 一部のフィールドを保持し、他のフィールドを削除する必要がある 1 回限りのファイルの場合は、手動 EXIF 編集を使用します。 + 制御ファイル名 + プレフィックス、サフィックス、タイムスタンプ、プリセット、チェックサム、シーケンス番号を使用する + エクスポートを見つけやすくする + 適切なファイル名パターンはバッチを並べ替えるのに役立ち、誤って上書きするのを防ぎます。ファイル名の設定は、エクスポートをソース フォルダーに戻す場合に特に役立ちます。類似した名前がすぐに混乱する可能性があるためです。 + ファイル名のプレフィックス、サフィックス、タイムスタンプ、元の名前、プリセット情報、またはシーケンスのオプションを設定で設定します。 + ページ、フレーム、コラージュなど、順序が重要なバッチにはシーケンス番号を使用します。 + 現在のフォルダーに対して既存のファイルを置き換えても安全であることが確実な場合にのみ、上書きを有効にします。 + ファイルを上書きする + ワークフローが意図的に破壊的である場合にのみ、出力を一致する名前に置き換えます。 + 上書きモードは慎重に使用してください + ファイルを上書きすると、名前の競合の処理方法が変更されます。これは、同じフォルダーに繰り返しエクスポートする場合に便利ですが、ファイル名パターンによって同じ名前が再び生成された場合、以前の結果を置き換えることもできます。 + 古い生成ファイルの置き換えが予想され、回復可能なフォルダーに対してのみ上書きを有効にします。 + オリジナル、クライアント ファイル、ワンタイム スキャンなどをバックアップなしでエクスポートする場合は、上書きを無効にしてください。 + 上書きを意図的なファイル名パターンと組み合わせて、意図した出力のみが置き換えられるようにします。 + ファイル名のパターン + 元の名前、プレフィックス、サフィックス、タイムスタンプ、プリセット、スケール モード、チェックサムを結合します。 + 出力を説明するビルド名 + ファイル名パターン設定を使用すると、エクスポートされたファイルを、ファイルに何が起こったかを示す有用な履歴に変えることができます。フォルダー ビューは元のサイズ、プリセット、形式、処理順序を区別できる唯一の場所である可能性があるため、バッチではこれが重要になります。 + 手動で並べ替えるために読みやすい名前が必要な場合は、元のファイル名、プレフィックス、サフィックス、およびタイムスタンプを使用します。 + 出力がどのように生成されたかを文書化する必要がある場合は、プリセット、スケール モード、ファイル サイズ、またはチェックサム情報を追加します。 + ファイルがオリジナルまたはページ順序とペアになっている必要があるワークフローでは、ランダムな名前を使用しないでください。 + ファイルの保存場所を選択する + フォルダー、元のフォルダーの保存、ワンタイム保存場所を設定することで、エクスポートの損失を回避します + 出力を予測可能な状態に保つ + 多くのファイルを処理したり、クラウド プロバイダーからファイルを共有したりする場合、保存動作が最も重要になります。フォルダー設定により、出力を 1 つの既知の場所に収集するか、オリジナルの横に表示するか、または特定のタスクのために一時的に別の場所に移動するかが決まります。 + エクスポートを常に 1 か所にまとめたい場合は、デフォルトの保存フォルダーを設定します。 + 出力をソース ファイルの近くに置く必要があるクリーンアップ ワークフローには、save-to-original-folder を使用します。 + 単一のタスクで別の保存先が必要な場合は、デフォルトを変更せずに 1 回限りの保存場所を使用します。 + ワンタイム保存フォルダー + 通常の宛先を変更せずに、次のエクスポートを一時フォルダーに送信します + 特別なジョブを分離する + ワンタイム保存場所は、通常の Image Toolbox フォルダーと混在させるべきではない一時プロジェクト、クライアント フォルダー、クリーンアップ セッション、またはエクスポートに役立ちます。デフォルト設定を書き換えることなく、短期間の宛先を提供します。 + 通常のエクスポート場所以外に属するタスクを開始する前に、ワンタイム フォルダーを選択してください。 + 出力をグループ化しておく必要がある短いプロジェクト、共有フォルダー、またはバッチに使用します。 + ジョブの後はデフォルトのフォルダーに戻り、後のエクスポートが予期せず一時的な場所に到達しないようにします。 + 品質とファイルサイズのバランスを取る + 推測ではなく、サイズ、形式、品質を変更するタイミングを理解する + ファイルを意図的に縮小する + ファイル サイズは、画像の寸法、形式、品質、透明度、コンテンツの複雑さによって異なります。ファイルが依然として重すぎる場合は、通常、品質を繰り返し低下させるよりも、サイズを変更する方が効果があります。 + 画像が宛先で表示できるサイズより大きい場合は、最初に寸法を変更します。 + 非可逆形式の場合のみ品質を下げ、エクスポート後にテキスト、エッジ、グラデーション、面をチェックします。 + 品質の変更だけでは不十分な場合は、共有用の WebP や鮮明な透明なアセット用の PNG など、形式を切り替えます。 + アニメーション形式を使用する + ファイルに 1 つのビットマップではなくフレームが含まれている場合は、GIF、APNG、WebP、および JXL ツールを使用します + すべての画像を静止画として扱わないでください + アニメーション形式には、フレーム、長さ、互換性を理解するツールが必要です。 + GIF または APNG ツールは、これらの形式に対してフレームレベルの操作が必要な場合に使用します。 + 最新の圧縮またはアニメーション WebP 出力が必要な場合は、WebP ツールを使用します。 + プラットフォームによってはアニメーション画像を変換または平坦化するため、共有する前にアニメーションのタイミングをプレビューしてください。 + 出力の比較とプレビュー + エクスポートを保存する前に、変更、ファイル サイズ、視覚的な違いを検査します。 + 共有する前に確認する + 比較ツールは、圧縮アーティファクト、間違った色、または不要な編集を早期に発見するのに役立ちます。 + 2 つのバージョンを並べて検査する場合は、「比較」を開きます。 + 問題が最も見落とされやすいエッジ、テキスト、グラデーション、透明な領域にズームインします。 + 出力が重すぎるか不鮮明な場合は、前のツールに戻って品質または形式を調整します。 + PDF ツール ハブを使用する + PDF ファイルの結合、分割、回転、ページの削除、圧縮、保護、OCR、透かしマークを付ける + PDF 操作を選択します + PDF ハブは、ドキュメントのアクションを 1 つのエントリ ポイントの背後にグループ化して、正確な操作を選択できるようにします。ファイルがすでに PDF である場合はそこから開始し、ソースがまだ画像のセットである場合は、[画像を PDF に変換] または [ドキュメント スキャナー] を使用します。 + PDF ツールを開き、目的に合った操作を選択します。 + ソース PDF または画像を選択し、ページ順序、回転、保護、または OCR オプションを確認します。 + 生成された PDF を保存し、一度開いてページ数、順序、読みやすさを確認します。 + 画像を PDF に変換する + スキャン、スクリーンショット、領収書、または写真を 1 つの共有可能なドキュメントに結合します + きれいなドキュメントを作成する + 画像を一貫してトリミング、順序付け、サイズ変更すると、画像はより優れた PDF ページになります。画像リストをページ スタックのように扱います。あらゆる回転、余白、ページ サイズの選択が、最終的なドキュメントの読みやすさに影響します。 + ページの端、影、方向が正しくない場合は、最初に画像をトリミングまたは回転します。 + 意図したページ順序で画像を選択し、ツールが提供する場合はページ サイズまたは余白を調整します。 + PDF をエクスポートし、送信する前にすべてのページが読めることを確認してください。 + 文書をスキャンする + 紙のページをキャプチャし、PDF、OCR、または共有用に準備します。 + 読み取り可能なページをキャプチャする + ドキュメントのスキャンは、平らなページ、良好な照明、および補正された遠近感がある場合に最適に機能します。テキスト認識と圧縮はどちらもページの品質に依存するため、OCR または PDF エクスポートの前にクリーン スキャンを行うと時間を節約できます。 + 十分な光があり、影が最小限に抑えられる、対照的な表面に文書を置きます。 + 検出された角を調整するか手動でトリミングして、ページがフレームをきれいに埋めるようにします。 + 画像または PDF としてエクスポートし、選択可能なテキストが必要な場合は OCR を実行します。 + PDF ファイルを保護またはロック解除する + パスワードは慎重に使用し、可能な場合は編集可能なソースのコピーを保管してください。 + PDF アクセスを安全に処理する + 保護ツールは共有を制御するのに役立ちますが、パスワードは紛失しやすく、回復するのが困難です。配布用にコピーを保護し、保護されていないソース ファイルを別に保管し、何かを削除する前に結果を確認します。 + 唯一のソース文書ではなく、PDF のコピーを保護します。 + 保護されたファイルを共有する前に、パスワードを安全な場所に保管してください。 + 編集が許可されているファイルに対してのみロック解除を使用し、ロック解除されたコピーが正しく開くことを確認します。 + PDF ページを整理する + ページの結合、分割、再配置、回転、トリミング、削除、およびページ番号の追加を意図的に行う + 装飾の前に文書構造を修正する + PDF ページ ツールは、紙の文書を準備するのと同じ順序で使用するのが最も簡単です。つまり、ページを収集し、間違ったページを削除し、順番に並べ、方向を修正してから、ページ番号や透かしなどの最終的な詳細を追加します。 + ドキュメントがまだ複数のファイルに分割されている場合は、最初に結合または画像から PDF を使用してください。 + ページ番号、署名、または透かしを追加する前に、ページを並べ替え、回転、トリミング、または削除します。 + 1 つのページが欠けていたり回転していたり​​すると、後で気づきにくい場合があるため、構造編集後に最終的な PDF をプレビューします。 + PDFの注釈 + 視聴者が異なるコメントやマークを表示する場合は、注釈を削除するかフラット化する + 表示されたままにするものを制御する + 注釈には、コメント、ハイライト、描画、フォームマーク、その他の追加の PDF オブジェクトを使用できます。一部のビューアでは表示方法が異なるため、それらを削除またはフラット化すると、共有ドキュメントがより予測しやすくなります。 + コメント、ハイライト、またはレビュー マークを共有コピーに含めるべきでない場合は、注釈を削除します。 + 表示マークをフラット化するとページ上に残りますが、編集可能な注釈のように動作しなくなります。 + 注釈を削除または平坦化すると元に戻すのが難しい場合があるため、元のコピーを保管しておいてください。 + テキストを認識する + 選択可能な OCR エンジンと言語で画像からテキストを抽出 + より良い OCR 結果を得る + OCR の精度は、画像の鮮明さ、言語データ、コントラスト、ページの向きによって異なります。通常、最良の結果は、最初に画像を準備することによって得られます。つまり、ノイズをトリミングし、直立に回転し、可読性を高めてから、テキストを認識します。 + 画像をテキスト領域にトリミングし、認識する前に直立に回転させます。 + 正しい言語を選択し、アプリが要求した場合は言語データをダウンロードします。 + 認識されたテキストをコピーまたは共有し、名前、数字、句読点を手動で確認します。 + OCR言語データを選択してください + スクリプト、言語、ダウンロードされた OCR モデルを一致させることで認識を向上させます + OCR をテキストと照合する + OCR 言語が間違っていると、良い写真が認識されにくいように見える可能性があります。言語データは、スクリプト、句読点、数字、および混合ドキュメントにとって重要であるため、電話 UI の言語ではなく、画像に表示される言語を選択してください。 + 電話の言語だけでなく、画像に表示される言語またはスクリプトを選択します。 + オフラインで作業したりバッチを処理したりする前に、不足しているデータをダウンロードしてください。 + 複数の言語が混在するドキュメントの場合は、最も重要な言語を最初に実行し、名前と番号を手動で確認します。 + 検索可能な PDF + OCR テキストをファイルに書き戻すことで、スキャンしたページを検索してコピーできるようになります + スキャンを検索可能なドキュメントに変換する + OCR はテキストをクリップボードにコピーするだけではありません。認識されたテキストをファイルまたは検索可能な PDF に書き込むと、ページの画像は表示されたままになり、テキストの検索、選択、インデックス付け、またはアーカイブが容易になります。 + 元のページのように見えるスキャンされた文書には、検索可能な PDF 出力を使用します。 + 抽出されたテキストのみが必要で、ページ画像を気にしない場合は、ファイルへの書き込みを使用します。 + OCR テキストは検索可能ですが不完全であるため、重要な番号、名前、表を手動で確認してください。 + QRコードをスキャンします + 利用可能な場合は、カメラ入力または保存された画像から QR コンテンツを読み取ります + コードを安全に読み取る + QR コードには、リンク、Wi-Fi データ、連絡先、テキスト、またはその他のペイロードを含めることができます。 + QR コードをスキャンして開き、コードが鋭く、平らで、フレーム内に完全に収まるように保ちます。 + リンクを開いたり機密データをコピーしたりする前に、デコードされたコンテンツを確認してください。 + スキャンが失敗した場合は、照明を改善するか、コードをトリミングするか、より高解像度のソース画像を試してください。 + Base64 とチェックサムを使用する + 画像をテキストとしてエンコードし、テキストをデコードして画像に戻し、ファイルの整合性を検証します。 + ファイルとテキストの間を移動する + Base64 は小さな画像ペイロードに便利ですが、チェックサムはファイルが変更されていないことを確認するのに役立ちます。これらのツールは、技術的なワークフロー、サポート レポート、ファイル転送、およびバイナリ ファイルを一時的にテキストにする必要がある場所で最も役立ちます。 + ワークフローでバイナリ ファイルではなくテキストが必要な場合は、Base64 ツールを使用して小さな画像をエンコードします。 + 信頼できるソースからのみ Base64 をデコードし、保存する前にプレビューを確認してください。 + エクスポートされたファイルを比較したり、アップロード/ダウンロードを確認したりする必要がある場合は、チェックサム ツールを使用します。 + データをパッケージ化または保護する + ZIP および暗号ツールを使用してファイルをバンドルしたり、テキスト データを変換したりする + 関連ファイルをまとめて保管する + パッケージ化ツールは、複数の出力を 1 つのファイルとして送信する必要がある場合に役立ちます。また、完全な結果セットを送信する必要がある場合に、生成された画像、PDF、ログ、メタデータをまとめて保持するのにも適しています。 + エクスポートした多数の画像やドキュメントをまとめて送信する必要がある場合は、ZIP を使用します。 + 選択した方法を理解し、必要なキーを安全に保存できる場合にのみ、暗号ツールを使用してください。 + ソース ファイルを削除する前に、作成されたアーカイブまたは変換されたテキストをテストします。 + 名前のチェックサム + 可読性よりも一意性と整合性が重要な場合は、チェックサムベースのファイル名を使用します。 + 内容に基づいてファイルに名前を付ける + チェックサム ファイル名は、エクスポートに重複した表示名が含まれる可能性がある場合、または 2 つのファイルが同一であることを証明する必要がある場合に便利です。これらは手動での参照には適していないため、技術アーカイブや検証ワークフローに使用してください。 + 人間が判読できるタイトルよりもコンテンツのアイデンティティが重要な場合は、ファイル名としてのチェックサムを有効にします。 + アーカイブ、重複排除、繰り返し可能なエクスポート、またはアップロードおよび再度ダウンロードできるファイルに使用します。 + ファイルが何を表しているのかを理解する必要がある場合は、チェックサム名をフォルダーまたはメタデータと組み合わせます。 + 画像から色を選択する + ピクセルをサンプリングし、カラーコードをコピーし、パレットまたはデザインで色を再利用します。 + 正確な色をサンプルする + カラーピッキングは、デザインのマッチング、ブランドカラーの抽出、イメージ値の確認などに便利です。アンチエイリアス、シャドウ、圧縮により、隣接するピクセルの色が期待したものとわずかに異なる可能性があるため、ズームは重要です。 + 「画像から色を選択」を開き、必要な色のソース画像を選択します。 + 近くのピクセルが結果に影響を与えないように、小さな詳細をサンプリングする前にズームインします。 + HEX、RGB、HSV など、コピー先に必要な形式でカラーをコピーします。 + パレットを作成してカラーライブラリを使用する + パレットを抽出し、保存されたカラーを参照し、一貫したビジュアル システムを維持します。 + 再利用可能なパレットを構築する + パレットは、エクスポートされたグラフィック、透かし、および描画の視覚的な一貫性を保つのに役立ちます。これらは、スクリーンショットに繰り返し注釈を付ける場合、ブランド資産を準備する場合、または複数のツール間で同じ色が必要な場合に特に便利です。 + 画像からパレットを生成するか、値がすでにわかっている場合は手動で色を追加します。 + 重要な色に名前を付けるか整理して、後で再度見つけられるようにします。 + コピーした値を描画、透かし、グラデーション、SVG、または外部デザイン ツールで使用します。 + グラデーションを作成する + 背景、プレースホルダー、視覚実験用のグラデーション画像を構築する + カラートランジションを制御する + グラデーション ツールは、既存の画像を編集するのではなく、生成された画像が必要な場合に便利です。これらは、クリーンな背景、プレースホルダー、壁紙、マスク、または外部デザイン ツールの単純なアセットとして使用できます。 + グラデーションの種類を選択し、最初に重要な色を設定します。 + ターゲットのユースケースに合わせて、方向、停止、滑らかさ、出力サイズを調整します。 + 出力先と一致する形式でエクスポートします。通常は、きれいに生成されたグラフィックの場合は PNG です。 + カラーアクセシビリティ + UI が読みにくい場合は、動的な色、コントラスト、色覚異常のオプションを使用します。 + 色を使いやすくする + 色の設定は、快適さ、読みやすさ、コントロールをスキャンする速度に影響します。ツールチップ、スライダー、または選択された状態が区別しにくい場合は、単にダーク モードを変更するよりもテーマとアクセシビリティ オプションの方が便利です。 + アプリを現在の壁紙パレットに従わせたい場合は、動的な色を試してください。 + ボタンや表面が十分に目立たない場合は、コントラストを増やすか、スキームを変更します。 + 一部の州やアクセントを区別するのが難しい場合は、色盲スキーム オプションを使用します。 + アルファ背景 + 透明な PNG、WebP、および同様の出力の周囲で使用されるプレビューまたは塗りつぶしの色を制御します + 透明なプレビューを理解する + アルファ形式の背景色を使用すると、透明な画像を検査しやすくなりますが、プレビュー/塗りつぶしの動作を変更するのか、それとも最終結果を平坦化するのかを知ることが重要です。これは、ステッカー、ロゴ、背景が削除された画像の場合に重要です。 + 透明なピクセルがエクスポートに耐える必要がある場合は、最初に PNG や WebP などのアルファ サポート形式を使用してください。 + アプリの表面に対して透明なエッジが見えにくい場合は、背景色の設定を調整します。 + 小さなテストをエクスポートし、別のアプリで再度開き、透明度または選択した塗りつぶしが保存されたかどうかを確認します。 + AIモデルの選択 + 最初に最大のモデルを選択するのではなく、画像のタイプと欠陥に基づいてモデルを選択します + モデルとダメージを一致させる + AI ツールはさまざまな ONNX/ORT モデルをダウンロードまたはインポートでき、各モデルは特定の種類の問題に合わせてトレーニングされます。 JPEG ブロック、アニメ ラインのクリーンアップ、ノイズ除去、背景の除去、カラー化、またはアップスケーリングのモデルは、間違った画像タイプで使用すると、より悪い結果が生じる可能性があります。 + ダウンロードする前にモデルの説明をお読みください。圧縮、ノイズ、ハーフトーン、ぼかし、背景除去など、実際の問題に名前を付けるモデルを選択します。 + 新しい画像タイプをテストするときは、より小さいモデルまたはより具体的なモデルから始めて、結果が十分でない場合にのみ、より重いモデルと比較します。 + ダウンロードおよびインポートしたモデルはかなりのストレージ容量を占有する可能性があるため、実際に使用するモデルのみを保持してください。 + モデル ファミリを理解する + モデル名は多くの場合、そのターゲットを示唆しています。RealESRGAN スタイルのモデルはアップスケール、SCUNet および FBCNN モデルはノイズや圧縮をクリーンにし、背景モデルはマスクを作成し、カラーライザーはグレースケール入力に色を追加します。インポートされたカスタム モデルは強力ですが、予想される入出力形状と一致し、サポートされている ONNX または ORT 形式を使用する必要があります。 + より多くのピクセルが必要な場合はアップスケーラーを使用し、画像が粗い場合はデノイザーを使用し、圧縮ブロックまたはバンディングが主な問題である場合はアーティファクト リムーバーを使用します。 + 背景モデルは主題の分離にのみ使用してください。これらは、一般的なオブジェクトの削除や画像の強調とは異なります。 + 信頼できるソースからのみカスタム モデルをインポートし、重要なファイルを使用する前に小さなイメージでテストします。 + AIパラメータ + チャンク サイズ、オーバーラップ、並列ワーカーを調整して、品質、速度、メモリのバランスをとります + スピードと安定性のバランス + チャンク サイズは、モデルによって処理される前の画像部分の大きさを制御します。オーバーラップは、隣接するチャンクをブレンドして境界線を隠します。並列ワーカーは処理を高速化できますが、各ワーカーにはメモリが必要であるため、RAM が限られている携帯電話では、値を大きくすると大きな画像が不安定になる可能性があります。 + デバイスがモデルを確実に処理し、画像が大きくない場合は、よりスムーズなコンテキストを得るためにより大きなチャンクを使用します。 + チャンクの境界が表示されるようになったらオーバーラップを増やしますが、オーバーラップが増えると繰り返し作業が増えることを意味することに注意してください。 + 安定したテストの後にのみ並列ワーカーを起動します。処理が遅くなったり、デバイスが熱くなったり、クラッシュしたりした場合は、まず作業員を減らします。 + チャンクアーティファクトを回避する + チャンク化により、モデルが一度に快適に処理できるサイズを超える画像をアプリで処理できるようになります。トレードオフとして、各タイルの周囲のコンテキストが少なくなるため、オーバーラップが少ないかチャンクが小さすぎると、隣接する領域間に境界線が見えたり、テクスチャが変化したり、詳細が不一致になったりする可能性があります。 + 長方形の境界線、繰り返しのテクスチャ変更、または処理領域間のディテールのジャンプが見られる場合は、オーバーラップを増やします。 + 特に大きな画像や重いモデルの場合、出力を生成する前にプロセスが失敗した場合は、チャンク サイズを減らします。 + 画像全体を処理する前に小さなクロップ テストを保存すると、パラメーターをすばやく比較できます。 + AIの安定性 + AI 処理がクラッシュまたはフリーズした場合のチャンク サイズ、ワーカー、ソース解像度の低下 + 負荷の高い AI ジョブから回復する + AI 処理は、アプリ内で最も負荷の高いワークフローの 1 つです。クラッシュ、セッションの失敗、フリーズ、アプリの突然の再起動は通常、モデル、ソース解像度、チャンク サイズ、または並列ワーカー数が現在のデバイスの状態に対して要求が高すぎることを意味します。 + アプリがクラッシュするか、モデル セッションを開けない場合は、並列ワーカーとチャンク サイズを下げて、同じイメージを再試行します。 + 目標が元の解像度を必要としない場合は、AI 処理の前に非常に大きな画像のサイズを変更します。 + メモリ不足が続く場合は、他の重いアプリを閉じるか、より小さいモデルを使用するか、一度に処理するファイルの数を減らしてください。 + モデルの障害を診断する + AI の実行が失敗しても、必ずしも画像が悪いとは限りません。モデル ファイルが不完全であるか、サポートされていないか、メモリに対して大きすぎるか、操作と一致しない可能性があります。アプリが失敗したセッションを報告した場合、それを信号として扱い、最初にモデルとパラメーターを単純化します。 + ダウンロード直後にモデルが失敗する場合、またはファイルが破損しているかのように動作する場合は、モデルを削除して再ダウンロードします。 + インポートされたモデルには互換性のない入力が含まれる可能性があるため、インポートされたカスタム モデルのせいにする前に、ダウンロード可能な組み込みモデルを試してください。 + クラッシュまたはセッション エラーを報告する必要がある場合は、障害を再現した後でアプリ ログを使用します。 + シェーダースタジオでの実験 + GPU シェーダー エフェクトを使用して高度な画像処理とカスタム外観を実現 + シェーダーを慎重に操作する + Shader Studio は強力ですが、シェーダー コードとパラメーターにはテスト可能な小さな変更が必要です。実験ツールのように扱います。プリセットを信頼する前に、機能するベースラインを維持し、一度に 1 つずつ変更し、さまざまな画像サイズでテストしてください。 + パラメータを追加する前に、既知の動作シェーダまたは単純なエフェクトから開始します。 + 一度に 1 つのパラメーターまたはコード ブロックを変更し、プレビューでエラーを確認します。 + プリセットは、いくつかの異なる画像サイズでテストした後にのみエクスポートしてください。 + SVG または ASCII アートを作成する + クリエイティブな出力用にベクター風のアセットまたはテキストベースの画像を生成 + クリエイティブの出力タイプを選択します + SVG ツールと ASCII ツールは、スケーラブルなグラフィックスやテキスト スタイルのレンダリングなど、さまざまなターゲットに対応します。どちらもクリエイティブな変換であるため、小さなサムネイルから結果を判断するよりも、最終サイズでプレビューすることが重要です。 + 結果をきれいに拡大縮小する必要がある場合、またはデザイン ワークフローで再利用する必要がある場合は、SVG Maker を使用します。 + 画像を様式化されたテキスト表現が必要な場合は、ASCII アートを使用します。 + 生成された出力では細部が見えなくなる可能性があるため、最終サイズでプレビューしてください。 + ノイズテクスチャの生成 + 背景、マスク、オーバーレイ、または実験用のプロシージャル テクスチャを作成する + プロシージャル出力を調整する + ノイズ生成ではパラメータから新しい画像が作成されるため、わずかな変更により大きく異なる結果が生じる可能性があります。これは、テクスチャ、マスク、オーバーレイ、プレースホルダー、または詳細なパターンでの圧縮のテストに役立ちます。 + 細部を調整する前に、ノイズのタイプと出力サイズを選択してください。 + パターンが目的の用途に適合するまで、スケール、強度、色、またはシードを調整します。 + 後でテクスチャを再作成する必要がある場合は、有用な結果を保存し、パラメータを書き留めます。 + マークアッププロジェクト + アプリを閉じた後も注釈を編集可能な状態にしておく必要がある場合は、プロジェクト ファイルを使用します。 + レイヤー編集を再利用可能に保つ + マークアップ プロジェクト ファイルは、スクリーンショット、図、説明画像を後で変更する必要がある場合に役立ちます。エクスポートされた PNG/JPEG ファイルは最終画像ですが、プロジェクト ファイルは将来の調整のために編集可能なレイヤーと編集状態を保存します。 + 注釈、吹き出し、またはレイアウト要素を編集可能なままにしておく必要がある場合は、マークアップ レイヤーを使用します。 + さらにリビジョンが必要な場合は、最終イメージをエクスポートする前にプロジェクト ファイルを保存するか再度開きます。 + 通常のイメージは、フラット化された最終バージョンを共有する準備ができている場合にのみエクスポートしてください。 + ファイルを暗号化する + ファイルをパスワードで保護し、ソースコピーを削除する前に復号化をテストします。 + 暗号化された出力は慎重に処理してください + 暗号ツールはパスワードベースの暗号化でファイルを保護できますが、キーを記憶したりバックアップを保存したりすることに代わるものではありません。暗号化は転送や保存には便利ですが、主な目的が複数の出力をバンドルする場合には ZIP の方が適しています。 + 最初にコピーを暗号化し、保存したパスワードで復号化が機能することをテストするまでオリジナルを保管してください。 + パスワード マネージャーまたはキーを保管する別の安全な場所を使用します。アプリは紛失したパスワードを推測することはできません。 + 暗号化されたファイルは、パスワードを知っており、どのツールや方法で復号化すべきかを知っている人とのみ共有してください。 + 道具を揃える + ツールをグループ化、順序付け、検索、ピン留めすることで、メイン画面が実際のワークフローに一致するようにします + メイン画面を高速化する + どのツールを最もよく使用するかがわかったら、ツールの配置設定を調整する価値があります。グループ化は探索に役立ち、カスタムオーダーは筋肉の記憶に役立ち、完全なリストが大きくなった場合でも、お気に入りは毎日のツールにアクセスできるようにします。 + アプリを学習するとき、またはタスクの種類ごとにツールを整理したい場合は、グループ化モードを使用します。 + 頻繁に使用するツールがすでにわかっていて、スクロールを減らしたい場合は、カスタム オーダーに切り替えます。 + 名前は覚えているものの、常に表示したくない珍しいツールについては、検索設定とお気に入りを組み合わせて使用​​します。 + 大きなファイルを処理する + エクスポートの遅さ、メモリ不足、予期せぬ大きな出力を回避します。 + 輸出前の作業を軽減 + 非常に大きな画像、長いバッチ、高品質の形式では、より多くのメモリと時間がかかる可能性があります。通常、最速の修正は、同じ大規模な入力が完了するのを待たずに、最も重い操作の前に作業量を減らすことです。 + 重いフィルター、OCR、または PDF 変換を適用する前に、巨大な画像のサイズを変更します。 + 最初に小さいバッチを使用して設定を確認し、残りを同じプリセットで処理します。 + 出力ファイルが予想より大きい場合、品質が低下するか、形式が変更されます。 + キャッシュとプレビュー + 大量のセッション後の生成されたプレビュー、キャッシュのクリーンアップ、ストレージの使用状況を理解する + ストレージと速度のバランスを保つ + プレビューの生成とキャッシュにより、繰り返しの閲覧を高速化できますが、大量の編集セッションでは一時データが残る可能性があります。キャッシュ設定は、アプリが遅いと感じたり、ストレージが不足したり、多くの実験を行った後でプレビューが古く見える場合に役立ちます。 + 多くの画像を閲覧する場合は、一時ストレージを最小限に抑えることよりもプレビューを有効にしておくことが重要です。 + 大規模なバッチ、失敗した実験、または多数の高解像度ファイルを使用した長時間のセッションの後にキャッシュをクリアします。 + 起動間でプレビューを保存し続けるよりもストレージのクリーンアップを希望する場合は、自動キャッシュ クリアを使用します。 + 画面の動作を調整する + フルスクリーン、セキュア モード、明るさ、システム バーのオプションを使用して、集中して作業できます + インターフェースを状況に合わせて調整する + 画面設定は、横向きで編集したり、プライベート画像を表示したり、一貫した明るさが必要な場合に役立ちます。通常の使用には必要ありませんが、QR 検査、プライベート プレビュー、窮屈な風景編集などの厄介な状況を解決します。 + ナビゲーション クロムよりもスペースの編集が重要な場合は、全画面設定またはシステム バー設定を使用します。 + プライベート画像をスクリーンショットやアプリのプレビューに表示しない場合は、セキュア モードを使用してください。 + 暗い画像や QR コードを検査するのが難しいツールには、明るさの強制を使用します。 + 透明性を保つ + 透明な背景が白、黒、または平らになるのを防ぎます + アルファを意識した出力を使用する + 透明性は、選択したツールと出力形式がアルファをサポートしている場合にのみ存続します。エクスポートされた背景が白または黒になる場合、問題は通常、出力形式、塗りつぶし/背景の設定、または画像を平坦化した宛先アプリにあります。 + 背景を削除した画像、ステッカー、ロゴ、またはオーバーレイをエクスポートする場合は、PNG または WebP を選択します。 + JPEG はアルファ チャネルを保存しないため、透明出力には使用しないでください。 + プレビューに塗りつぶされた背景が表示される場合は、アルファ形式の背景色に関連する設定を確認してください。 + 共有とインポートの問題を修正する + ファイルが表示されない、または正しく開かない場合は、ピッカー設定とサポートされている形式を使用する + ファイルをアプリに取り込む + インポートの問題は、多くの場合、プロバイダーのアクセス許可、サポートされていない形式、またはピッカーの動作によって発生します。クラウド アプリやメッセンジャーから共有されるファイルは一時的なものである可能性があるため、長時間の編集では永続的なソースを選択する方が信頼性が高くなります。 + 最近のファイルのショートカットではなく、システム ピッカーを使用してファイルを開いてみてください。 + あるツールで形式がサポートされていない場合は、最初に変換するか、より特定のツールを開きます。 + 共有フローまたは直接ツールを開く動作が予想と異なる場合は、イメージ ピッカーとランチャーの設定を確認してください。 + 終了確認 + ジェスチャー、バックプレス、またはツールの切り替えが誤って発生した場合に、保存されていない編集内容が失われるのを回避します。 + 未完成の作品を保護する + ツール終了の確認は、ジェスチャー ナビゲーションを使用する場合、大きなファイルを編集する場合、または頻繁にアプリを切り替える場合に役立ちます。ツールを終了する前に少し一時停止するので、未完成の設定やプレビューが誤って失われないようにします。 + エクスポートする前に、誤って「戻る」ジェスチャでツールを閉じたことがあるかどうかの確認を有効にします。 + 主に迅速なワンステップ変換を行い、より高速なナビゲーションを好む場合は、無効にしておきます。 + 設定を再構築するのが面倒な長いワークフローの場合は、プリセットと組み合わせて使用​​します。 + 問題を報告するときにログを使用する + 問題をオープンしたり開発者に連絡したりする前に、役立つ詳細を収集してください + 問題をコンテキストとともに報告する + ステップ、アプリのバージョン、入力タイプ、ログを含む明確なレポートは、診断がはるかに簡単です。ログは周囲のコンテキストを最新の状態に保つため、問題を再現した直後に最も役立ちます。 + ツール名、正確なアクション、およびそれが 1 つのファイルで発生するのかすべてのファイルで発生するのかに注意してください。 + 問題を再現した後、アプリ ログを開き、可能であれば関連するログ出力を共有します。 + スクリーンショットやサンプル ファイルは、個人情報が含まれていない場合にのみ添付してください。 + バックグラウンド処理が停止されました + Android ではバックグラウンド処理通知の開始が間に合わなかった。これはシステムのタイミング制限であり、確実に修正することはできません。アプリを再起動して、もう一度試してください。アプリを開いたままにし、通知やバックグラウンド作業を許可すると役立つ場合があります。 + ファイルの選択をスキップする + 通常、入力が共有、クリップボード、または前の画面から行われる場合、ツールをより速く開くことができます + 繰り返しのツール起動を高速化する + ファイルの選択をスキップすると、サポートされるツールの最初のステップが変更されます。これは、通常、すでに選択されている画像を使用してツールを入力する場合、または Android 共有アクションからツールを入力する場合に便利ですが、すべてのツールがすぐにファイルを要求することを期待している場合は、混乱を感じる可能性があります。 + ツールが開く前に、通常のフローでソース イメージがすでに提供されている場合にのみ有効にします。 + 明示的に選択するとすべてのツール フローが理解しやすくなるため、アプリの学習中は無効にしておいてください。 + 明らかな入力がない状態でツールが開く場合は、この設定を無効にするか、「共有/開く」から開始して、Android が最初にファイルを渡すようにします。 + まずエディタを開いてください + 共有画像を直接編集する必要がある場合はプレビューをスキップします + 最初の目的としてプレビューまたは編集を選択します + 「プレビューの代わりに編集を開く」は、画像を変更する必要があることがすでにわかっているユーザー向けです。チャットまたはクラウド ストレージからファイルを検査する場合は、プレビューの方が安全です。スクリーンショット、素早いトリミング、注釈、および 1 回限りの修正では、直接編集の方が高速です。 + ほとんどの共有画像でトリミング、マークアップ、フィルター、またはエクスポートの変更がすぐに必要な場合は、直接編集を有効にします。 + 決定する前にメタデータ、寸法、透明度、または画質を検査することが多い場合は、プレビューを最初に残しておきます。 + これをお気に入りと組み合わせて使用​​すると、共有画像が実際に使用するツールに近いものになります。 + プリセットテキスト入力 + コントロールを繰り返し調整する代わりに、正確なプリセット値を入力します。 + スライダーが遅い場合は正確な値を使用する + プリセット テキスト入力は、ソーシャル イメージ サイズ、ドキュメントの余白、圧縮ターゲット、線の幅、またはジェスチャでは到達するのが面倒なその他の値など、繰り返しの作業で正確な数値が必要な場合に役立ちます。スライダーの細かい動きが難しい小さな画面でも役立ちます。 + 正確なサイズ、パーセンテージ、品質値、またはツール パラメータを頻繁に再利用する場合は、テキスト入力を有効にします。 + 一度入力した値をプリセットとして保存し、同じ設定を再構築する代わりにそれを再利用します。 + 大まかな視覚調整のためにジェスチャー コントロールを維持し、厳密な出力要件のために入力された値を維持します。 + オリジナルに近い状態で保存 + フォルダーのコンテキストが重要な場合は、生成されたファイルをソース画像の横に置きます + 出力をソースと一緒に保持する + [オリジナル フォルダーに保存] は、編集したコピーをソースの隣に保持する必要があるカメラ フォルダー、プロジェクト フォルダー、ドキュメント スキャン、およびクライアント バッチに便利です。専用の出力フォルダーよりも整頓されない可能性があるため、明確なファイル名のサフィックスまたはパターンと組み合わせてください。 + 各ソース フォルダーがすでに意味があり、出力をそこにグループ化したままにする必要がある場合は、これを有効にします。 + ファイル名のサフィックス、プレフィックス、タイムスタンプ、またはパターンを使用すると、編集されたコピーをオリジナルから簡単に分離できます。 + 生成されたすべてのファイルを 1 つのエクスポート フォルダーに収集する場合は、混合バッチに対してこれを無効にします。 + 大きな出力をスキップする + 予期せずソースよりも重くなる変換を保存しないようにします。 + 予期せぬサイズの予期せぬ事態からバッチを保護 + 一部の変換では、特に PNG スクリーンショット、圧縮済みの写真、高品質 WebP、またはメタデータが保存された画像などのファイルが大きくなります。 「大きい場合はスキップを許可」は、サイズの削減が主な目的であるワークフローで、これらの出力が小さいソースを置き換えることを防ぎます。 + エクスポートでファイルの圧縮、サイズ変更、またはアップロード制限に備えた準備を行う場合は、これを有効にします。 + バッチ後にスキップされたファイルを確認します。すでに最適化されているか、別の形式が必要な場合があります。 + 最終的なファイル サイズよりも品質、透明性、または形式の互換性が重要な場合は、これを無効にします。 + クリップボードとリンク + 自動クリップボード入力とリンク プレビューを制御して、テキストベースのツールを高速化します + 自動入力を予測可能にする + クリップボードとリンクの設定は、QR、Base64、URL、およびテキストの多いワークフローには便利ですが、自動入力は意図的なものにしておく必要があります。アプリが自動的にフィールドに入力しているように見える場合は、クリップボードの貼り付け動作を確認してください。リンク カードのノイズや速度が遅いと感じる場合は、リンク プレビューの動作を調整します。 + 頻繁にテキストをコピーする場合に自動クリップボード貼り付けを許可し、一致するツールをすぐに開きます。 + プライベートのクリップボードのコンテンツが予期しないツールに表示される場合は、自動貼り付けを無効にします。 + プレビューの取得が遅い場合、気が散る場合、またはワークフローにとって不必要な場合は、リンク プレビューをオフにします。 + コンパクトなコントロール + 画面スペースが狭い場合は、より高密度のセレクターと高速な設定配置を使用します。 + 小さな画面にさらに多くのコントロールをフィットさせる + コンパクトなセレクターと素早い設定配置により、小型スマートフォン、ランドスケープ編集、多くのパラメーターを備えたツールに役立ちます。トレードオフとして、コントロールがより密に感じられる可能性があるため、一般的なオプションをすでに認識した後にこれを使用するのが最適です。 + ダイアログまたはオプション リストが画面に対して高すぎると感じる場合は、コンパクト セレクターを有効にします。 + ディスプレイの片側からツールのコントロールにアクセスしやすい場合は、高速設定側を調整します。 + アプリをまだ学習中である場合や、密度の高いオプションを頻繁にタップミスする場合は、より広いコントロールに戻ってください。 + Web から画像をロードする + ページまたは画像の URL を貼り付け、解析された画像をプレビューし、選択した結果を保存または共有します + ウェブ画像を意図的に使用する + Web 画像の読み込みでは、指定された URL から画像ソースを解析し、最初に利用可能な画像をプレビューし、選択した解析済み画像を保存または共有できます。一部のサイトでは、一時的なリンク、保護されたリンク、またはスクリプトによって生成されたリンクを使用しているため、ブラウザーで動作するページが使用可能な画像を返さない場合があります。 + 直接画像 URL またはページ URL を貼り付け、解析された画像リストが表示されるまで待ちます。 + ページに複数の画像が含まれており、その一部のみが必要な場合は、フレームまたは選択コントロールを使用します。 + 何も読み込まれない場合は、直接画像リンクを試すか、最初にブラウザ経由でダウンロードしてください。サイトでは解析がブロックされたり、プライベート リンクが使用されたりする場合があります。 + サイズ変更タイプを選択してください + 画像を新しい寸法に強制的に変換する前に、明示的、柔軟、切り抜き、フィットについて理解する + 形状、キャンバス、アスペクト比を制御する + サイズ変更タイプは、要求された幅と高さが元のアスペクト比と一致しない場合にどうなるかを決定します。それは、ピクセルをストレッチするか、比率を維持するか、余分な領域をトリミングするか、背景キャンバスを追加するかの違いです。 + Explicit は、歪みが許容できる場合、またはソースがすでにターゲットと同じアスペクト比を持っている場合にのみ使用してください。 + 特に写真や混合バッチの場合、重要な側からサイズを変更して比率を維持するには、「フレキシブル」を使用します。 + 境界線のない厳密なフレームには Crop を使用し、画像全体を固定キャンバス内に表示しておく必要がある場合には Fit を使用します。 + 画像スケールモードを選択してください + シャープネス、スムーズさ、スピード、エッジアーティファクトを制御するリサンプリングフィルターを選択します + 誤って柔らかくすることなくサイズを変更します + 画像スケール モードは、サイズ変更中に新しいピクセルを計算する方法を制御します。シンプル モードは高速で予測可能ですが、高度なフィルターは細部をより良く保存できますが、エッジがシャープになったり、ハローが発生したり、大きな画像では時間がかかる場合があります。 + 結果をより小さくきれいにするだけでよい場合は、Basic または Bilinear を使用して毎日のサイズ変更を迅速に行います。 + 細かいディテール、テキスト、または高品質のダウンスケーリングが重要な場合は、Lanczos、Robidoux、Spline、または EWA スタイルのモードを使用します。 + ピクセルがぼやけて見た目が損なわれるようなピクセル アート、アイコン、ハードエッジのグラフィックには、[ニアレスト] を使用します。 + スケール色空間 + サイズ変更中にピクセルがリサンプリングされるときに色をブレンドする方法を決定します。 + グラデーションとエッジを自然に保つ + スケールカラースペースは、サイズ変更中に使用される計算を変更します。ほとんどの画像はデフォルトで問題ありませんが、線形空間または知覚空間を使用すると、強いスケーリングを行った後、グラデーション、暗いエッジ、飽和した色がよりきれいに見えることがあります。 + わずかな色の違いよりも速度と互換性が重要な場合は、デフォルトのままにしてください。 + サイズ変更後に暗い輪郭、UI スクリーンショット、グラデーション、またはカラー バンドが正しく見えない場合は、リニア モードまたは知覚モードを試してください。 + 色空間の変化は微妙でコンテンツに依存する可能性があるため、実際のターゲット画面で前後を比較してください。 + サイズ変更モードの制限 + 制限を超えた画像をスキップ、再コード化、またはサイズに合わせて縮小する必要があるタイミングを把握する + 限界で何が起こるかを選択する + リサイズ制限は、画像を小さくするだけのツールではありません。そのモードは、ソースがすでに制限内にある場合、または一方のみがルールを破った場合にアプリが何を行うかを決定します。これは、混合フォルダーやアップロードの準備にとって重要です。 + 制限内の画像をそのままにして再度エクスポートしない必要がある場合は、「スキップ」を使用します。 + サイズは許容できるが、新しい形式、メタデータの動作、品質、またはファイル名が必要な場合は、再コード化を使用します。 + 制限を超えた画像を、許可されたボックスに収まるまで比例的に縮小する必要がある場合は、「ズーム」を使用します。 + アスペクト比の目標 + 厳密な出力サイズでは、引き伸ばされた面、切り取られたエッジ、および予期しない境界線を回避します。 + サイズを変更する前に形状を一致させます + 幅と高さはサイズ変更対象の半分にすぎません。アスペクト比によって、画像が自然に収まるか、トリミングが必要か、周囲にキャンバスが必要かが決まります。最初に形状をチェックすると、予期せぬサイズ変更結果が生じるのを防ぐことができます。 + [明示的]、[クロップ]、または [フィット] を選択する前に、ソース シェイプとターゲット シェイプを比較してください。 + 被写体が余分な背景を失う可能性があり、目的地に厳密なフレームが必要な場合は、最初にトリミングします。 + 元の画像のすべての部分を表示したままにする必要がある場合は、「フィット」または「フレキシブル」を使用します。 + 高級または通常のサイズ変更 + ピクセルの追加に AI が必要な場合と、単純なスケーリングで十分な場合を把握する + サイズと詳細を混同しないでください + 通常のサイズ変更はピクセルを補間することによって寸法を変更します。実際の詳細を発明することはできません。 AI アップスケールは、より鮮明に見える詳細を作成できますが、速度が遅く、テクスチャが幻覚を起こす可能性があるため、適切な選択は、互換性、速度、または視覚的な復元が必要かどうかによって異なります。 + サムネイル、アップロード制限、スクリーンショット、単純なサイズ変更には通常のサイズ変更を使用します。 + 小さい画像や柔らかい画像を大きいサイズで見栄えよくする必要がある場合は、AI アップスケールを使用します。 + AI アップスケール後に顔、テキスト、パターンを比較すると、生成された詳細は説得力があるように見えますが、正しくない可能性があるためです。 + フィルタの順序は重要です + クリーンアップ、カラー、シャープネス、最終圧縮を意図的な順序で適用します。 + よりクリーンなフィルター スタックを構築する + フィルターは常に交換できるわけではありません。シャープ化前のブラー、OCR 前のコントラストブースト、または圧縮前の色の変更により、同じコントロールからまったく異なる出力が生成される可能性があります。 + 最初にトリミングと回転を行うと、後のフィルタは残ったピクセルに対してのみ機能します。 + シャープ効果や様式化された効果の前に、露出、ホワイト バランス、コントラストを適用します。 + 最終的なサイズ変更と圧縮を最後近くに維持して、プレビューされた詳細が予想どおりエクスポートに残るようにします。 + 背景のエッジを修正する + 自動除去後にハロー、残った影、髪、穴、柔らかい輪郭をきれいにします。 + カットアウトを意図的に見せる + 自動マスクは一見すると良く見えても、明るい背景、暗い背景、または模様のある背景では問題が露呈することがよくあります。エッジのクリーンアップは、簡単な切り抜きと、再利用可能なステッカー、ロゴ、または製品画像の違いです。 + 対照的な背景に対して結果をプレビューすると、ハローや欠けているエッジの詳細が明らかになります。 + 周囲の残り物を消去する前に、髪、角、透明なオブジェクトに復元を使用します。 + カットアウトをデザインに配置するときに、最終的な背景色に関する小さなテストをエクスポートします。 + 判読可能な透かし + 写真を台無しにすることなく、明るい画像、暗い画像、混雑した画像、トリミングされた画像にマークを表示します。 + 画像を隠さずに保護する + ある画像ではきれいに見える透かしが、別の画像では消えてしまう場合があります。サイズ、不透明度、コントラスト、配置、繰り返しは、最初のプレビューだけでなくバッチ全体に対して選択する必要があります。 + すべてのファイルをエクスポートする前に、バッチ内の最も明るい画像と最も暗い画像のマークをテストします。 + 大きな繰り返しマークには低い不透明度を使用し、小さな隅のマークには強いコントラストを使用します。 + 再利用を防止するためのマークでない限り、重要な顔、テキスト、および製品の詳細を明確にしてください。 + 1 つの画像をタイルに分割する + 単一の画像を行、列、カスタムパーセンテージ、フォーマット、品質で切り取る + グリッドと注文したピースを準備する + 画像分割では、1 つのソース画像から複数の出力が作成されます。行数と列数で分割し、行または列のパーセンテージを調整し、選択した出力形式と品質で部分を保存できます。これは、グリッド、ページ スライス、パノラマ、および順序付けされたソーシャル投稿に役立ちます。 + ソース画像を選択し、必要な行数と列数を設定します。 + すべての部分を同じサイズにする必要がない場合は、行または列のパーセンテージを調整します。 + 保存する前に出力形式と品質を選択して、生成されたすべての部分が同じエクスポート ルールを使用するようにします。 + 画像ストリップを切り出す + 垂直または水平領域を削除し、残りの部分を結合し直します。 + 隙間を残さずに領域を削除します + 画像のカットは通常のクロップとは異なります。選択した垂直または水平のストリップを削除し、残りの画像部分を結合できます。逆モードでは選択された領域が保持され、両方の逆方向を使用すると選択された四角形が保持されます。 + サイド領域、列、または中央のストリップには垂直カットを使用します。バナー、ギャップ、列には水平方向のカットを使用します。 + 選択したパーツを削除するのではなく保持したい場合は、軸の反転を有効にします。 + 削除された領域が重要な詳細を横切る場合、結合されたコンテンツが不自然に見える可能性があるため、カット後にエッジをプレビューします。 + 出力形式を選択してください + コンテンツと宛先に基づいて JPEG、PNG、WebP、AVIF、JXL、または PDF を選択します + フォーマットは宛先に任せる + 最適な形式は、画像に含まれる内容とそれが使用される場所によって異なります。写真、スクリーンショット、透明なアセット、ドキュメント、アニメーション ファイルはすべて、間違った形式を強制されると、さまざまな方法で失敗します。 + 透明性と正確なピクセルが必要ない場合は、幅広い写真互換性を得るために JPEG を使用してください。 + 鮮明なスクリーンショット、UI キャプチャ、透明なグラフィック、ロスレス編集には PNG を使用します。 + コンパクトで最新の出力が重要であり、受信アプリがそれをサポートしている場合は、WebP、AVIF、または JXL を使用します。 + オーディオカバーを抽出する + 埋め込まれたアルバム アートまたはオーディオ ファイルから利用可能なメディア フレームを PNG 画像として保存 + オーディオファイルからアートワークを抽出する + Audio Covers は、Android メディア メタデータを使用して、オーディオ ファイルから埋め込まれたアートワークを読み取ります。埋め込まれたアートワークが利用できない場合、Android がメディア フレームを提供できる場合、取得者はメディア フレームにフォールバックすることがあります。どちらも存在しない場合、ツールは抽出する画像がないと報告します。 + [オーディオ カバー] を開き、予想されるカバー アートが含まれる 1 つまたは複数のオーディオ ファイルを選択します。 + 特にストリーミング アプリや録音アプリからのファイルの場合は、保存または共有する前に、抽出したカバーを確認してください。 + 画像が見つからない場合は、オーディオ ファイルにカバーが埋め込まれていないか、Android が使用可能なフレームを公開できなかった可能性があります。 + 日付と場所のメタデータ + キャプチャ時間が重要であるが、GPS やデバイスのデータが漏洩してはいけない場合に、何を保存するかを決定します。 + 有用なメタデータをプライベート メタデータから分離する + メタデータはすべて同じようにリスクがあるわけではありません。キャプチャ日付はアーカイブや並べ替えに役立ちますが、GPS、デバイスのシリアルのようなフィールド、ソフトウェア タグ、または所有者フィールドは、意図した以上の情報を明らかにする可能性があります。 + アルバム、スキャン、またはプロジェクト履歴で時系列順が重要な場合は、日付と時刻のタグを保持します。 + 見知らぬ人に画像を共有したり送信したりする前に、GPS およびデバイス識別タグを削除してください。 + プライバシー要件が厳しい場合は、サンプルをエクスポートして EXIF を再度検査します。 + ファイル名の衝突を避ける + 多くのファイルが image.jpg や Screenshot.png などの名前を共有する場合にバッチ出力を保護します + すべての出力名を一意にする + 画像がメッセンジャー、スクリーンショット、クラウド フォルダー、または複数のカメラから取得された場合、ファイル名が重複することがよくあります。適切なパターンは、偶発的な置き換えを防ぎ、出力をソースまで遡って追跡できるようにします。 + 編集したコピーを認識できるようにする必要がある場合は、元の名前と接尾辞を含めます。 + 大規模な混合バッチを処理する場合は、シーケンス、タイムスタンプ、プリセット、またはディメンションを追加します。 + 命名パターンが衝突しないことを確認するまでは、ワークフローを上書きしないでください。 + 保存または共有 + 永続的なファイルを使用するか、別のアプリへの一時的なハンドオフを選択します + 正しい意図でエクスポートする + 保存と共有は似ているように思えますが、解決する問題は異なります。保存すると、後で検索できるファイルが作成されます。共有すると、生成された結果が Android 経由で別のアプリに送信され、永続的なローカル コピーが残らない場合があります。 + 出力を既知のフォルダーに保存するか、バックアップするか、後で再利用する必要がある場合は、「保存」を使用します。 + クイック メッセージ、電子メールの添付ファイル、ソーシャル投稿、または別の編集者への結果の送信には、[共有] を使用します。 + 受信側アプリが信頼できない場合、または失敗した共有を再作成するのが面倒な場合は、最初に保存してください。 + PDFのページサイズと余白 + ドキュメントを作成する前に、用紙サイズ、フィット動作、および余裕を選択してください + 画像ページを印刷可能および読み取り可能にする + 画像の形状が選択した用紙サイズと一致しない場合、PDF ページが不自然になることがあります。余白、フィットモード、ページの向きによって、コンテンツが切り取られるか、小さくなるか、引き伸ばされるか、読みやすいかが決まります。 + PDF を印刷したりフォームに送信したりする場合は、実際の用紙サイズを使用してください。 + テキストがページの端にはみ出さないように、スキャンやスクリーンショットには余白を使用してください。 + ソース画像の向きが混在している場合、縦向きのページと横向きのページを一緒にプレビューします。 + スキャンをクリーンアップする + PDF または OCR を使用する前に、紙のエッジ、影、コントラスト、回転、読みやすさを向上させます。 + アーカイブする前にページを準備する + スキャンは技術的にはキャプチャできますが、それでも読み取るのは困難です。 PDF エクスポート前の小さなクリーンアップ手順は、特にレシート、手書きのメモ、および低照度ページの場合、圧縮設定よりも重要であることがよくあります。 + 遠近感を修正し、テーブルの端、指、影、背景の乱雑さを切り取ります。 + スタンプ、署名、または鉛筆の跡を壊すことなくテキストがより鮮明になるように、コントラストを慎重に上げます。 + ページが直立して読み取り可能になった後にのみ OCR を実行し、重要な数値を手動で確認します。 + PDF の圧縮、グレースケール、または修復 + 単一ファイルの PDF 操作を使用して、軽量で印刷可能な、または再構築されたドキュメントのコピーを作成します。 + PDF クリーンアップ操作を選択します + PDF ツールには、圧縮、グレースケール変換、修復の個別の操作が含まれています。圧縮とグレースケールは主に埋め込み画像を通じて機能しますが、修復は PDF 構造を再構築します。これらはいずれも、すべてのドキュメントに対する保証された修正として扱われるべきではありません。 + ドキュメントに画像が含まれており、ファイル サイズが共有に重要な場合は、「PDF の圧縮」を使用します。 + ドキュメントを印刷しやすくする必要がある場合、またはカラー画像を必要としない場合は、グレースケールを使用します。 + ドキュメントを開けない場合、または内部構造が破損している場合は、コピーで PDF を修復を使用し、再構築されたファイルを確認します。 + PDF から画像を抽出する + 埋め込まれた PDF 画像を復元し、抽出された結果をアーカイブにパックします + ドキュメントからソース画像を保存する + 画像の抽出は、埋め込まれた画像オブジェクトの PDF をスキャンし、可能な限り重複をスキップし、生成された ZIP アーカイブに復元された画像を保存します。 PDF に実際に埋め込まれている画像のみを抽出します。テキスト、ベクター描画、表示されているすべてのページはスクリーンショットとして抽出されません。 + カタログの写真やスキャンしたアセットなど、PDF 内に保存された画像が必要な場合は、画像の抽出を使用します。 + テキストやレイアウトを含むページ全体が必要な場合は、代わりに PDF to Images またはページ抽出を使用してください。 + ツールが埋め込み画像を報告しない場合、そのページはテキスト/ベクター コンテンツであるか、画像が抽出可能なオブジェクトとして保存されていない可能性があります。 + メタデータ、フラット化、および印刷出力 + ドキュメントのプロパティを編集したり、ページをラスタライズしたり、カスタム印刷レイアウトを意図的に準備したりする + 最終文書アクションを選択してください + PDF メタデータ、フラット化、印刷準備により、最終段階のさまざまな問題が解決されます。メタデータはドキュメントのプロパティを制御し、Flatten PDF はページを編集しにくい出力にラスタライズし、Print PDF はページ サイズ、方向、余白、およびシートあたりのページ数を制御する新しいレイアウトを準備します。 + 著者、タイトル、キーワード、プロデューサー、プライバシーのクリーンアップが重要な場合は、メタデータを使用します。 + 表示されているページ コンテンツを個別の PDF オブジェクトとして編集することが難しくなる場合は、フラット化 PDF を使用します。 + カスタムのページ サイズ、方向、余白、または 1 枚のシートに複数のページが必要な場合は、Print PDF を使用します。 + OCR用の画像を準備する + OCR にテキストの読み取りを依頼する前に、トリミング、回転、コントラスト、ノイズ除去、スケールを使用します。 + OCR によりきれいなピクセルを与える + OCR の間違いは、OCR エンジンではなく入力画像に起因することがよくあります。曲がったページ、低コントラスト、ぼやけ、影、小さなテキスト、混合背景はすべて認識品質を低下させます。 + テキスト領域をトリミングし、認識前に線が水平になるように画像を回転します。 + 文字が読みやすくなる場合にのみ、コントラストを上げるか、よりきれいな白黒に変換します。 + 小さなテキストのサイズを適度に上向きに変更しますが、偽のエッジを作成するような過度のシャープ化は避けてください。 + QRの内容を確認する + コードを信頼する前に、リンク、Wi-Fi 認証情報、連絡先、アクションを確認してください + デコードされたテキストを信頼できない入力として扱う + QR コードでは、URL、連絡先カード、Wi-Fi パスワード、カレンダーのイベント、電話番号、またはプレーン テキストを隠すことができます。コードの近くに表示されるラベルは実際のペイロードと一致しない可能性があるため、操作を行う前にデコードされたコンテンツを確認してください。 + 特に短縮されたドメインや馴染みのないドメインなど、開く前に完全にデコードされた URL を検査してください。 + Wi-Fi、連絡先、カレンダー、電話、SMS コードは機密性の高いアクションを引き起こす可能性があるため、注意してください。 + コンテンツを別の場所で確認する必要がある場合は、すぐに開くのではなく、まずコンテンツをコピーします。 + QRコードを生成する + プレーンテキスト、URL、Wi-Fi、連絡先、電子メール、電話、SMS、位置データからコードを作成 + 適切な QR ペイロードを構築する + QR 画面では、入力されたコンテンツからコードを生成したり、既存のコンテンツをスキャンしたりできます。構造化 QR タイプは、Wi-Fi ログイン詳細、連絡先カード、電子メール フィールド、電話番号、SMS コンテンツ、URL、地理座標など、他のアプリが理解できる形式でデータをエンコードするのに役立ちます。 + 単純なメモの場合はプレーン テキストを選択するか、コードを Wi-Fi、連絡先、URL、電子メール、電話、SMS、または位置データとして開く必要がある場合は構造化タイプを使用します。 + 表示されるプレビューには入力したペイロードのみが反映されるため、生成されたコードを共有する前にすべてのフィールドを確認してください。 + 保存した QR コードを印刷したり、公開したり、他の人が使用したりする場合は、スキャナーでテストしてください。 + チェックサムモードを選択してください + ファイルまたはテキストからハッシュを計算したり、1 つのファイルを比較したり、多数のファイルをバッチチェックしたりする + 外観ではなく内容を確認する + チェックサム ツールには、ファイル ハッシュ、テキスト ハッシュ、既知のハッシュとのファイルの比較、およびバッチ比較用の個別のタブがあります。チェックサムは、選択されたアルゴリズムのバイトごとの同一性を証明します。 2 つの画像が単に似ているということを証明するものではありません。 + ファイルには計算を使用し、入力または貼り付けたテキスト データにはテキスト ハッシュを使用します。 + 誰かが 1 つのファイルについて予想されるチェックサムを提供する場合は、「比較」を使用します。 + 多くのファイルで 1 回のパスでハッシュまたは検証が必要な場合は、バッチ比較を使用し、選択したアルゴリズムの一貫性を保ちます。 + クリップボードのプライバシー + プライベートにコピーされたデータを誤って公開することなく自動クリップボード機能を使用する + 意図的にコピーしたコンテンツを維持する + クリップボードの自動化は便利ですが、コピーされたテキストにはパスワード、リンク、アドレス、トークン、または個人的なメモが含まれる可能性があります。クリップボード ベースの入力は、ユーザーの習慣とプライバシーの期待に一致する速度機能として扱います。 + ツールにプライベート テキストが予期せず表示される場合は、クリップボードの自動使用を無効にします。 + 意図的に結果を保存しないようにしたい場合にのみ、クリップボードのみの保存を使用してください。 + 機密テキスト、QR データ、または Base64 ペイロードを処理した後は、クリップボードをクリアまたは置き換えます。 + カラーフォーマット + 他の場所に貼り付ける前に、HEX、RGB、HSV、アルファ、およびコピーされたカラー値を理解する + ターゲットが期待する形式をコピーします + 同じ目に見える色をいくつかの方法で書き込むことができます。デザイン ツール、CSS フィールド、Android カラー値、または画像エディターでは、異なる順序、アルファ処理、またはカラー モデルが必要になる場合があります。 + コンパクトなカラー コードが必要な Web、デザイン、またはテーマ フィールドに貼り付ける場合は、HEX を使用します。 + チャンネル、明るさ、彩度、色相を手動で調整する必要がある場合は、RGB または HSV を使用します。 + 一部の宛先では透明性が無視されたり、異なる順序が使用されたりするため、透明性が重要な場合はアルファを個別に確認してください。 + カラーライブラリを参照する + 名前付きカラーを名前または 16 進数で検索し、お気に入りを上部近くに保持します + 再利用可能な名前付きカラーを見つける + カラー ライブラリは、名前付きのカラー コレクションをロードし、名前で並べ替え、カラー名または 16 進値でフィルターし、お気に入りのカラーを保存して、重要なエントリにアクセスしやすくします。画像からサンプリングするのではなく、実際の名前付き色が必要な場合に便利です。 + レッド、ブルー、スレート、ミントなど、系統がわかっている場合は、色の名前で検索します。 + すでに数値色があり、一致する、または近くの名前付きエントリを見つけたい場合は、HEX で検索します。 + 頻繁に使用する色をお気に入りとしてマークすると、閲覧中に一般のライブラリよりも先に移動します。 + メッシュ グラデーション コレクションを使用する + 利用可能なメッシュ グラデーション リソースをダウンロードし、選択した画像を共有します + リモート メッシュ グラデーション アセットを使用する + メッシュ グラデーションは、リモート リソース コレクションの読み込み、不足しているグラデーション イメージのダウンロード、ダウンロードの進行状況の表示、および選択したグラデーションの共有を行うことができます。可用性はネットワーク アクセスとリモート リソース ストアに依存するため、リストが空である場合は、通常、リソースがまだ利用可能になっていないことを意味します。 + 既製のメッシュ グラデーション イメージが必要な場合は、グラデーション ツールからメッシュ グラデーションを開きます。 + コレクションが空であるとみなす前に、リソースの読み込みまたはダウンロードの進行を待ちます。 + 必要なグラデーションを選択して共有するか、カスタム グラデーションを手動で作成する場合はグラデーション メーカーに戻ります。 + 生成されたアセットの解決策 + グラデーション、ノイズ、壁紙、SVG 風のアート、またはテクスチャを生成する前に出力サイズを選択してください + 必要なサイズで生成 + 生成されたイメージには、頼れるカメラのオリジナルがありません。出力が小さすぎる場合、後でスケーリングするとパターンが柔らかくなったり、詳細が移動したり、アセットのタイル化や圧縮方法が変更されたりする可能性があります。 + 最初に、壁紙、アイコン、背景、印刷、オーバーレイなど、最終的なユースケースの寸法を設定します。 + トリミング、ズーム、または複数のレイアウトで再利用できるテクスチャには、より大きなサイズを使用します。 + 手順の詳細によって圧縮の動作が変わる可能性があるため、大量の出力を行う前にテスト バージョンをエクスポートしてください。 + 現在の壁紙をエクスポートする + 利用可能なホーム、ロック、および内蔵の壁紙をデバイスから取得します + インストールされた壁紙を保存または共有する + 壁紙エクスポートは、Android がシステム壁紙 API を通じて公開する壁紙を読み取ります。デバイス、Android バージョン、権限、ビルド バリアントによっては、ホーム、ロック、または組み込みの壁紙が個別に利用可能であるか、または欠落している場合があります。 + 「壁紙のエクスポート」を開いて、システムがアプリに読み取りを許可する壁紙をロードします。 + 保存、コピー、または共有したい使用可能なホーム、ロック、または組み込みの壁紙エントリを選択します。 + 壁紙が見つからない場合、または機能が利用できない場合、通常は編集設定ではなく、システム、権限、またはビルドバリアントの制限が原因です。 + コーナーアニメーションスロットル + カラーをコピー + 16 進数 + 名前 + より柔らかい髪とエッジ処理を使用して、人物を素早くカットアウトするためのポートレート マット モデル。 + Zero-DCE++ 曲線推定を使用した高速低照度強化。 + 雨の縞模様や雨天時のアーティファクトを軽減するための Restormer 画像復元モデル。 + 座標グリッドを予測し、湾曲したページをより平坦なスキャンに再マッピングするドキュメント アンワーピング モデル。 + モーション持続時間スケール + アプリのアニメーション速度を制御します。0 はモーションを無効にし、1 は通常、値を大きくするとアニメーションが遅くなります。 + 最近のツールを表示 + 最近使用したツールへのクイック アクセスをメイン画面に表示します + 使用統計 + アプリの起動、ツールの起動、最もよく使用されるツールを探索します + アプリが開きます + ツールが開きます + 最後のツール + 保存に成功しました + 連続アクティビティ + 保存されたデータ + トップフォーマット + 使用したツール + %1$d が開きます • %2$s + 使用統計はまだありません + いくつかのツールを開くと、自動的にここに表示されます + 使用状況統計は、デバイスのローカルにのみ保存されます。 ImageToolbox は、個人的なアクティビティ、お気に入りのツール、保存されたデータの洞察を表示するためにのみこれらを使用します。 + エディター編集写真画像レタッチ調整 + サイズ変更 変換 スケール 解像度 寸法 幅 高さ jpg jpeg png webp 圧縮 + サイズを圧縮 重み バイト mb kb 品質を下げる 最適化 + クロップトリムカットアスペクト比 + フィルターエフェクト色補正LUTマスクの調整 + ペイント ブラシ 鉛筆を描く マークアップに注釈を付ける + 暗号化暗号化復号化パスワードシークレット + 背景リムーバー 消去 カットアウトを削除 透明アルファ + プレビュー ビューア ギャラリー 検査を開く + ステッチマージ結合パノラマロングスクリーンショット + URL Web ダウンロード インターネット リンク 画像 + ピッカー スポイト サンプル 16 進数 RGB カラー + パレットの色見本抽出優勢 + exif メタデータ プライバシー 削除 ストリップ クリーン GPS 位置情報 + 前後の差分の差を比較する + サイズ変更の制限 最大最小メガピクセル 寸法 クランプ + PDFドキュメントページツール + ocrテキスト認識抽出スキャン + グラデーションの背景色メッシュ + 透かしロゴテキストスタンプオーバーレイ + GIF アニメーション アニメーション化されたフレームの変換 + apng アニメーション アニメーション png フレームを変換 + zip アーカイブ 圧縮パック ファイルを解凍する + jxl jpeg xl jpg画像形式を変換 + SVGベクトルトレースXML変換 + 形式変換 jpg jpeg png webp heic avif jxl bmp + ドキュメント スキャナー スキャン紙 パースペクティブ クロップ + qr バーコード コード スキャナー スキャン + スタッキング オーバーレイ 平均中央値 天体写真 + 分割タイル グリッド スライス パーツ + カラー変換 hex rgb hsl hsv cmyk lab + webp アニメーション画像形式を変換 + ノイズジェネレーターのランダムテクスチャグレイン + コラージュグリッドレイアウトの結合 + マークアップ レイヤーに注釈を付けるオーバーレイ編集 + Base64 エンコード デコード データ URI + チェックサムハッシュ md5 sha sha1 sha256 検証 + メッシュのグラデーションの背景 + exifメタデータ編集GPS日付カメラ + 分割グリッドピースタイルをカットする + オーディオ カバー アルバム アート ミュージック メタデータ + 壁紙のエクスポートの背景 + ASCII テキスト アートの文字 + ai ml ニューラルエンハンスアップスケールセグメント + カラーという名前のカラー ライブラリ マテリアル パレット + シェーダ GLSL フラグメント エフェクト スタジオ + pdf マージ 結合 結合 + PDF を別々のページに分割 + pdf ページの向きを回転 + PDF 並べ替え ページの並べ替え 並べ替え + PDFのページ番号のページネーション + PDF OCR テキスト認識検索可能な抽出 + PDF透かしスタンプのロゴテキスト + PDF署名サイン抽選 + PDF 保護パスワード暗号化ロック + PDFロック解除パスワード復号化保護を削除 + PDF 圧縮 サイズを縮小 最適化 + pdf グレースケール 黒 白 モノクロ + PDF修復修正壊れた回復 + pdf メタデータ プロパティ exif 著者タイトル + pdf 削除 ページを削除 + PDFトリミングトリムマージン + PDF フラット化注釈フォームレイヤー + pdf 抽出画像 写真 + pdf zip アーカイブ圧縮 + PDF印刷プリンター + PDF プレビュー ビューアの読み取り + 画像を PDF に変換 jpg jpeg png ドキュメント + pdf から画像ページへのエクスポート jpg png + PDF 注釈 コメント 削除 クリーン + 中立的なバリアント + エラー + ドロップシャドウ + 歯の高さ + 水平歯範囲 + 垂直歯範囲 + トップエッジ + 右端 + 下端 + 左端 + 破れたエッジ + 使用状況統計をリセットする + ツールが開き、統計、ストリーク、保存データが保存され、トップフォーマットがリセットされます。アプリの起動は変更されません。 + オーディオ + ファイル + プロファイルのエクスポート + 現在のプロファイルを保存する + エクスポートプロファイルの削除 + エクスポート プロファイル「%1$s」を削除しますか? + 枠線を描く + 描画および消去キャンバスの周囲に細い境界線を表示します + 波状 + 柔らかい波状のエッジを持つ丸みを帯びた UI 要素 + スクープ + ノッチ + 内側に彫られた丸い角 + ダブルカットの段付きコーナー + プレースホルダー + 拡張子なしの元のファイル名を挿入するには、{filename} を使用します + フレア + 基本額 + リング金額 + 光線量 + リング幅 + 遠近感を歪める + Java のルック アンド フィール + 剪断 + X角度 + と角度 + 出力のサイズを変更する + ウォータードロップ + 波長 + 段階 + ハイパス + カラーマスク + クロム + 溶解する + 柔らかさ + フィードバック + レンズのぼやけ + 低入力 + 高入力 + 低出力 + 高出力 + 光の効果 + バンプ高さ + バンプの柔らかさ + リップル + X振幅 + Y振幅 + X波長 + Y波長 + 適応ブラー + テクスチャの生成 + さまざまなプロシージャル テクスチャを生成し、画像として保存します + テクスチャの種類 + バイアス + 時間 + 輝く + 分散 + サンプル + 角度係数 + 勾配係数 + グリッドタイプ + 長距離パワー + スケールY + 曖昧さ + 基準タイプ + 乱流係数 + スケーリング + 反復 + 指輪 + つや消しメタル + コースティクス + 携帯電話 + チェッカーボード + FBm + 大理石 + プラズマ + キルト + 木材 + ランダム + 四角 + 六角 + 八角形 + 三角 + うねのある + VLノイズ + SCノイズ + 最近の + 最近使用したフィルタはまだありません + テクスチャ ジェネレーター、ブラッシュド メタル、コースティクス、セルラー チェッカーボード、大理石、プラズマ キルト、木レンガ、迷彩、細胞、雲、亀裂、生地、葉、ハニカム、氷、溶岩、星雲、紙、錆び、砂、煙、石、地形、地形、水の波紋 + レンガ + 迷彩 + 細胞 + + 割れ目 + ファブリック + 紅葉 + ハニカム + + 溶岩 + 星雲 + + さび + + + + 地形 + 地形 + 水の波紋 + アドバンスウッド + モルタル幅 + 不規則性 + ベベル + 粗さ + 最初のしきい値 + 2 番目のしきい値 + 3 番目のしきい値 + エッジの柔らかさ + 枠線の幅 + カバレッジ + 詳細 + 深さ + 分岐 + 横糸 + 縦糸 + ファズ + 静脈 + 点灯 + 亀裂の幅 + + 流れ + クラスト + 雲の密度 + スター + 繊維密度 + 繊維強度 + 汚れ + 腐食 + 孔食 + フレーク + 砂丘の周波数 + 風角 + 波紋 + ウィスプ + 静脈スケール + 水位 + 山レベル + 侵食 + 積雪量 + 行数 + 線の太さ + シェーディング + 毛穴の色 + モルタルの色 + 濃いレンガ色 + レンガ色 + ハイライトカラー + 暗色 + 森の色 + アースカラー + 砂の色 + 背景色 + セルの色 + エッジの色 + 空の色 + 影の色 + 明るい色 + 表面色 + バリエーションカラー + ひび割れの色 + ワープカラー + よこ糸の色 + 濃い葉色 + 葉の色 + 枠線の色 + はちみつ色 + 深みのある色 + 氷の色 + フロストカラー + クラストの色 + ウォッシュカラー + グローカラー + スペースカラー + バイオレットカラー + 青色 + ベースカラー + 繊維の色 + ステインカラー + メタルカラー + 濃い錆色 + 錆色 + オレンジ色 + 煙の色 + 静脈の色 + ローランドカラー + 水の色 + ロックカラー + 雪の色 + 色が薄い + ハイカラー + 線の色 + 浅い色 + + ダート + レザー + コンクリート + アスファルト + + + オーロラ + 油膜 + 水彩 + 抽象的な流れ + オパール + ダマスカス鋼 + 稲妻 + ベルベット + インクマーブリング + ホログラム箔 + 生物発光 + コズミックボルテックス + 溶岩ランプ + イベントホライズン + フラクタル ブルーム + クロマティックトンネル + エクリプスコロナ + ストレンジ・アトラクター + 磁性流体クラウン + 超新星 + 虹彩 + 孔雀の羽 + オウム貝の殻 + 環状惑星 + ブレード密度 + 刃の長さ + + 斑点 + + 水分 + 小石 + しわ + 毛穴 + 集計 + ひび割れ + タール + 着る + スペックル + 繊維 + 火炎周波数 + + 強度 + リボン + バンド + 虹色 + 暗闇 + 咲く + 顔料 + エッジ + + 拡散 + 対称 + 切れ味 + 色遊び + ミルキーさ + レイヤー + 折りたたみ + 研磨 + 支店 + 方向 + シーン + ひだ + フェザリング + インクバランス + スペクトラム + しわ + 回折 + + ねじれ + コアグロー + ブロブ + ディスクチルト + 水平線のサイズ + ディスク幅 + レンズ撮影 + 花びら + カール + フィリグリー + ファセット + 曲率 + 月の大きさ + コロナサイズ + 光線 + ダイヤモンドリング + ローブ + 軌道密度 + 厚さ + スパイク + スパイクの長さ + 本体サイズ + メタリック + 衝撃半径 + シェルの幅 + 放り出された + 瞳孔の大きさ + 虹彩サイズ + カラーバリエーション + キャッチライト + 目のサイズ + バーブ密度 + ターン + チャンバーズ + オープニング + 尾根 + 真珠光沢 + 惑星の大きさ + リングチルト + リング幅 + 雰囲気 + 汚れの色 + 濃い草の色 + 草の色 + 先端カラー + ダークアースカラー + ドライカラー + 小石の色 + 革の色 + コンクリートの色 + タールの色 + アスファルトの色 + 石の色 + ダストカラー + 土の色 + 濃い苔色 + 苔の色 + 赤色 + コアカラー + 緑色 + シアン色 + マゼンタ色 + ゴールドカラー + 紙の色 + 顔料の色 + 二次色 + 最初の色 + 2番目の色 + ピンク色 + ダークスチールカラー + スチールカラー + ライトスチールカラー + 酸化物色 + ハローカラー + ボルトの色 + ベルベットカラー + 光沢のある色 + 青色のインク色 + 赤インクの色 + 濃いイ​​ンク色 + 銀色 + 黄色 + 組織の色 + ディスクの色 + ホットカラー + レンズカラー + アウターカラー + インナーカラー + コロナカラー + 寒色系 + 暖色 + 雲の色 + 炎の色 + 羽の色 + シェルの色 + パールカラー + 惑星の色 + リングカラー + 保存後に元のファイルを削除する + 正常に処理されたソース ファイルのみが削除されます + 削除された元のファイル: %1$d + 元のファイルの削除に失敗しました: %1$d + 元のファイルが削除されました: %1$d、失敗しました: %2$d + シートジェスチャ + 展開されたオプション シートのドラッグを許可する + クロマサブサンプリング + ビット深度 + ロスレス + %1$dビット + 名前の一括変更 + ファイル名パターンを使用して複数のファイルの名前を変更する + ファイルの名前をバッチ変更する ファイル名パターン シーケンスの日付拡張子 + 名前を変更するファイルを選択してください + ファイル名のパターン + 名前の変更 + 日付ソース + 撮影されたEXIF日付 + ファイルの変更日 + ファイルの作成日 + 現在の日付 + 手動日付 + 手動日付 + 手動時間 + 選択した日付は一部のファイルでは利用できません。現在の日付が使用されます。 + ファイル名パターンを入力してください + このパターンでは、無効なファイル名または長すぎるファイル名が生成されます + このパターンでは重複したファイル名が生成されます + すべてのファイル名がすでにパターンに一致しています + このパターンでは、名前の一括変更に使用できないトークンが使用されます。 + 名前はそのまま残ります + ファイルの名前が正常に変更されました + 一部のファイルの名前を変更できませんでした + 許可が与えられませんでした + 拡張子なしの元のファイル名を使用するため、ソースの識別をそのまま維持できます。 \\o{start:end} によるスライス、\\o{t} による音訳、\\o{s/old/new/} による置換もサポートしています。 + 自動増加カウンター。カスタム書式設定をサポートします: \\c{padding} (例: \\c{3} -&gt; 001)、\\c{start:step} または \\c{start:step:padding}。 + 元のファイルが存在する親フォルダーの名前。 + 元のファイルのサイズ。サポートされる単位: \\z{b}、\\z{kb}、\\z{mb}、または人間が判読できる形式の \\z のみ。 + ファイル名の一意性を確保するために、ランダムな Universally Unique Identifier (UUID) を生成します。 + ファイル名の変更を元に戻すことはできません。続行する前に、新しい名前が正しいことを確認してください。 + 名前変更の確認 + サイドメニューの設定 + メニュースケール + メニューアルファ + オートホワイトバランス + クリッピング + 隠れた透かしのチェック + ストレージ + キャッシュ制限 + このサイズを超えるとキャッシュを自動的にクリアします + クリーンアップ間隔 + キャッシュをクリアする必要があるかどうかを確認する頻度 + アプリ起動時 + 1日 + 1週間 + 1ヶ月 + 選択した制限と間隔に従ってアプリのキャッシュを自動的にクリアします + 移動可能なクロップエリア + トリミング領域内をドラッグしてトリミング領域全体を移動できるようにします + GIFを結合 + 複数の GIF クリップを 1 つのアニメーションに結合する + GIF クリップの順序 + 逆行する + 逆向きにプレイする + ブーメラン + 順方向に再生してから逆方向に再生する + フレームサイズを正規化する + 最大のクリップに合わせてフレームをスケールします。元のスケールを維持するにはオフにします + クリップ間の遅延 (ミリ秒) + フォルダ + フォルダーとそのサブフォルダーからすべての画像を選択します + 重複ファインダー + 正確なコピーや視覚的に類似した画像を検索します + 重複ファインダー 類似画像 正確なコピー 写真のクリーンアップ ストレージ dHash SHA-256 + 画像を選択して重複を見つけます + 類似性感度 + 正確なコピー + 類似画像 + 完全に重複するものをすべて選択します + 推奨以外をすべて選択してください + 重複は見つかりませんでした + さらに画像を追加するか、類似性の感度を上げてみてください + %1$d 画像を読み取れませんでした + %1$s • %2$s 回収可能 + %1$d グループ • %2$s 再利用可能 + %1$d 選択済み • %2$s + これを元に戻すことはできません。 Android では、システム ダイアログで削除の確認を求める場合があります。 + 見つかりません + 開始位置に移動 + 最後に移動 + \ No newline at end of file diff --git a/core/resources/src/main/res/values-kk/strings.xml b/core/resources/src/main/res/values-kk/strings.xml new file mode 100644 index 0000000..0d6b707 --- /dev/null +++ b/core/resources/src/main/res/values-kk/strings.xml @@ -0,0 +1,3739 @@ + + + + Жаңартулар + Палитра + Қазір шықсаңыз, барлық сақталмаған өңдеме жоғалады + Қолданба туралы + Бастау үшін файл таңдаңыз + Суреттер: %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 & 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 беттерінен сілтемелер, түсініктемелер, ерекшеліктер, пішіндер немесе пішін өрістері сияқты таңдалған аннотация түрлерін алып тастаңыз + Гиперсілтемелер + Файл қосымшалары + Сызықтар + Қалқымалы терезелер + Маркалар + Пішіндер + Мәтіндік жазбалар + Мәтінді белгілеу + Пішін өрістері + Белгілеу + Белгісіз + Аннотациялар + Топтан шығару + Конфигурацияланатын түс пен ығысу арқылы қабаттың артына бұлыңғыр көлеңке қосыңыз + Мазмұннан хабардар бұрмалау + Бұл әрекетті орындау үшін жад жеткіліксіз. Кішірек суретті пайдаланып, басқа қолданбаларды жауып немесе қолданбаны қайта іске қосып көріңіз. + Параллель жұмысшылар + Бұл кескіндер сақталмады, себебі түрлендірілген файлдар түпнұсқалардан үлкенірек болады. Түпнұсқа суреттерді жоймас бұрын осы тізімді тексеріңіз. + Өткізілген файлдар: %1$s + Қолданба журналдары + Мәселелерді анықтау үшін қолданбаның журналдарын қараңыз + Топтастырылған құралдардағы таңдаулылар + Құралдар түрі бойынша топтастырылған кезде таңдаулыларды қойынды ретінде қосады + Таңдаулыны соңғы ретінде көрсету + Таңдаулы құралдар қойындысын соңына дейін жылжытады + Көлденең аралық + Тік аралық + Кері энергия + Алға энергия алгоритмінің орнына қарапайым градиент шамасының энергия картасын пайдаланыңыз + Маскаланған аймақты алып тастаңыз + Тігістер маскаланған аймақ арқылы өтіп, оны қорғаудың орнына тек таңдалған аумақты алып тастайды + VHS NTSC + Қосымша NTSC параметрлері + Күшті VHS және хабар тарату артефактілері үшін қосымша аналогтық баптау + Хромадан қан кету + Таспа тозуы + Бақылау + Люма жағындысы + Қоңырау + Қар + Қолдану өрісі + Сүзгі түрі + Люма сүзгісін енгізу + Кіріс хромасының төмен өтуі + Хром демодуляциясы + Фазалық жылжу + Фазалық ауытқу + Басты ауыстыру + Басын ауыстыру биіктігі + Басты ауыстыру офсетін + Бастың ауысуы + Ортаңғы сызық позициясы + Ортаңғы сызық діріл + Шудың биіктігін бақылау + Бақылау толқыны + Қарды қадағалау + Қар анизотропиясын қадағалау + Шуды бақылау + Шу қарқындылығын қадағалау + Композиттік шу + Композиттік шу жиілігі + Композиттік шудың қарқындылығы + Композиттік шу детальдары + Қоңырау жиілігі + Қоңырау қуаты + Люма шуы + Luma шуының жиілігі + Люма шуының қарқындылығы + Люма шуының егжей-тегжейі + Хрома шуы + Хрома шуының жиілігі + Хрома шуының қарқындылығы + Хрома шуының мәліметтері + Қардың қарқындылығы + Қар анизотропиясы + Хрома фазасының шуы + Хрома фазасының қатесі + Көлденең түсті кідіріс + Chroma кідірісі тік + VHS таспа жылдамдығы + VHS хромының жоғалуы + VHS қарқындылығы + VHS айқындау жиілігі + VHS жиек толқынының қарқындылығы + VHS шеткі толқын жылдамдығы + VHS шеткі толқын жиілігі + VHS жиегі толқынының егжей-тегжейі + Шығару хромының төмен өтуі + Көлденең масштабта + Масштаб тік + X шкала коэффициенті + Масштаб коэффициенті Y + Кескіннің жалпы су белгілерін анықтайды және оларды LaMa көмегімен бояйды. Анықтау және бояу үлгілерін автоматты түрде жүктеп алады + Дейтеранопия + Кескінді кеңейту + YOLO v11 Segmentation көмегімен нысан сегментациясына негізделген фондық жою құралы + Сызық бұрышын көрсетіңіз + Сурет салу кезінде сызықтың ағымдағы айналуын градуспен көрсетеді + Анимациялық эмодзилер + Қолжетімді эмодзилерді анимациялар ретінде көрсету + Түпнұсқа қалтаға сақтау + Таңдалған қалтаның орнына жаңа файлдарды бастапқы файлдың жанына сақтаңыз + Су таңбасы PDF + Жад жеткіліксіз + Кескін бұл құрылғыда өңдеу үшін тым үлкен болуы мүмкін немесе жүйеде бос жад таусылған. Кескін ажыратымдылығын азайтып, басқа қолданбаларды жауып немесе кішірек файлды таңдап көріңіз. + Түзету мәзірі + Қолданба функцияларын тексеруге арналған мәзір, бұл өндіріс шығарылымында көрсетуге арналмаған + Провайдер + PaddleOCR құрылғыңызда қосымша ONNX үлгілерін қажет етеді. %1$s деректерін жүктеп алғыңыз келе ме? + Әмбебап + корей + латын + Шығыс славян + тай + грек + Ағылшын + Кириллица + араб + Девангари + Тамил + телугу + Шейдер + Шейдер алдын ала орнатылған + Ешбір шейдер таңдалмаған + Shader Studio + Теңшелетін фрагмент шейдерлерін жасау, өңдеу, тексеру, импорттау және экспорттау + Сақталған шейдерлер + Сақталған шейдерлерді ашыңыз, көшіріңіз, экспорттаңыз, бөлісіңіз немесе жойыңыз + Жаңа шейдер + Шейдер файлы + .itshader JSON + %1$d параметрлер + Шейдер көзі + void main() мәтінін ғана жазыңыз. UV координаттары үшін textureCoordinate және бастапқы үлгі ретінде inputImageTexture пайдаланыңыз. + Функциялар + void main() алдында енгізілген қосымша көмекші функциялар, тұрақтылар және құрылымдар. Бірыңғай киімдер парамдардан жасалады. + Шейдер өңделетін мәндерді қажет еткенде, осында форманы қосыңыз. + Шейдерді қалпына келтіру + Ағымдағы шейдер жобасы тазаланады + Жарамсыз шейдер + Шейдер %1$d нұсқасына қолдау көрсетілмейді. Қолдау көрсетілетін нұсқа: %2$d. + Шейдер атауы бос болмауы керек. + Шейдер көзі бос болмауы керек. + Шейдер көзінде қолдау көрсетілмейтін таңбалар бар: %1$s. Тек ASCII GLSL көзін пайдаланыңыз. + \\\"%1$s\\\" параметрі бірнеше рет жарияланған. + Параметр атаулары бос болмауы керек. + \\\"%1$s\\\" параметрі сақталған GPUImage атауын пайдаланады. + \\\"%1$s\\\" параметрі жарамды GLSL идентификаторы болуы керек. + \\\"%1$s\\\" параметрінде \\\"%3$s\\\" %2$s мәні бар, күтілетін \\\"%4$s\\\". + \\\"%1$s\\\" параметрі bool мәндері үшін min немесе max мәндерін анықтай алмайды. + \\\"%1$s\\\" параметрінде min мәні максимумнан үлкен. + \\\"%1$s\\\" әдепкі параметрі мин. мәнінен төмен. + Параметр \\\"%1$s\\\" әдепкі мәннен үлкен. + Шейдер \\\"uniform sampler2D %1$s;\\\" жариялауы керек. + Шейдер \\\"өзгермелі vec2 %1$s;\\\" жариялауы керек. + Шейдер \\\"void main()\\\" анықтауы керек. + Шейдер gl_FragColor үшін түсті жазуы керек. + ShaderToy mainImage шейдерлеріне қолдау көрсетілмейді. GPUImage\'s void main() келісімін пайдаланыңыз. + \\\"iTime\\\" анимациялық ShaderToy формасына қолдау көрсетілмейді. + Анимациялық ShaderToy формасына \\\"iFrame\\\" қолдау көрсетілмейді. + ShaderToy формасына \\\"iResolution\\\" қолдау көрсетілмейді. + ShaderToy iChannel текстураларына қолдау көрсетілмейді. + Сыртқы текстураларға қолдау көрсетілмейді. + Сыртқы текстура кеңейтіміне қолдау көрсетілмейді. + Libretro шейдер параметрлеріне қолдау көрсетілмейді. + Тек бір енгізу текстурасына қолдау көрсетіледі. \\\"униформа %1$s %2$s;\\\" алып тастаңыз. + \\\"%1$s\\\" параметрі \\\"uniform %2$s %1$s;\\\" ретінде жариялануы керек. + \\\"%1$s\\\" параметрі \\\"uniform %2$s %1$s;\\\" деп жарияланды, күтілетін \\\"uniform %3$s %1$s;\\\". + Шейдер файлы бос. + Шейдер файлы нұсқасы, аты және шейдер өрістері бар .itshader JSON нысаны болуы керек. + Шейдер файлы JSON жарамсыз немесе .itshader пішіміне сәйкес келмейді. + \\\"%1$s\\\" өрісі қажет. + %1$s қажет. + %1$s \\\"%2$s\\\" қолданылмайды. Қолдау көрсетілетін түрлер: %3$s. + %1$s \\\"%2$s\\\" параметрлері үшін қажет. + %1$s шекті сан болуы керек. + %1$s бүтін сан болуы керек. + %1$s шын немесе жалған болуы керек. + %1$s түс жолы, RGB/RGBA массиві немесе түсті нысан болуы керек. + %1$s #RRGGBB немесе #RRGGBBAA пішімін пайдалану керек. + %1$s жарамды он алтылық түс арналарын қамтуы керек. + %1$s құрамында 3 немесе 4 түсті арна болуы керек. + %1$s 0 мен 255 арасында болуы керек. + %1$s екі санды массив немесе x және y мәндері бар нысан болуы керек. + %1$s дәл 2 саннан тұруы керек. + Көшірме + Әрқашан EXIF ​​файлын тазалаңыз + Құрал метадеректерді сақтауды сұраса да, суреттің EXIF ​​деректерін сақтау кезінде жойыңыз + Анықтама және кеңестер + %1$d оқулықтар + Ашық құрал + Негізгі құралдарды, экспорттау опцияларын, PDF ағындарын, түс утилиталарын және жалпы ақауларға арналған түзетулерді үйреніңіз + Бастау + Құралдарды таңдаңыз, файлдарды импорттаңыз, нәтижелерді сақтаңыз және параметрлерді жылдамырақ пайдаланыңыз + Суретті өңдеу + Суреттердің өлшемін өзгерту, қию, сүзгілеу, фондарды өшіру, сурет салу және су таңбасын орнату + Файлдар және метадеректер + Пішімдерді түрлендіру, нәтижені салыстыру, құпиялылықты қорғау және файл атауларын басқару + PDF және құжаттар + PDF файлдарын жасаңыз, құжаттарды, OCR беттерін сканерлеңіз және файлдарды ортақ пайдалануға дайындаңыз + Мәтін, QR және деректер + Мәтінді тану, кодтарды сканерлеу, Base64 кодтау, хэштерді және бума файлдарын тексеру + Түс құралдары + Түстерді таңдаңыз, палитралар жасаңыз, түстер кітапханаларын зерттеңіз және градиенттер жасаңыз + Шығармашылық құралдар + SVG, ASCII өнерін, шуыл текстураларын, шейдерлерді және эксперименттік көрнекі бейнелерді жасаңыз + Ақаулықтарды жою + Ауыр файлдарды, мөлдірлікті, ортақ пайдалануды, импорттауды және есеп беру мәселелерін түзетіңіз + Дұрыс құралды таңдаңыз + Дұрыс жерден бастау үшін санаттарды, іздеуді, таңдаулыларды пайдаланыңыз және әрекеттерді бөлісіңіз + Ең жақсы кіру нүктесінен бастаңыз + Image Toolbox-те көптеген бағытталған құралдар бар және олардың көпшілігі бір қарағанда бір-біріне сәйкес келеді. Аяқтағыңыз келетін тапсырмадан бастаңыз, содан кейін нәтиженің ең маңызды бөлігін басқаратын құралды таңдаңыз: өлшем, пішім, метадеректер, мәтін, PDF беттері, түстер немесе орналасу. + Өлшемін өзгерту, PDF, EXIF, OCR, QR немесе түс сияқты әрекет атауын білетін кезде іздеуді пайдаланыңыз. + Тапсырма түрін ғана білетін кезде санатты ашыңыз, содан кейін жиі қолданылатын құралдарды таңдаулылар ретінде бекітіңіз. + Басқа қолданба кескінді Image Toolbox ішіне ортақ пайдаланған кезде, ортақ парақ ағынынан құралды таңдаңыз. + Нәтижелерді импорттаңыз, сақтаңыз және бөлісіңіз + Енгізуді таңдаудан өңделген файлды экспорттауға дейінгі әдеттегі ағынды түсініңіз + Жалпы өңдеу ағынын орындаңыз + Көптеген құралдар бірдей ырғақты ұстанады: енгізуді таңдаңыз, опцияларды реттеңіз, алдын ала қараңыз, содан кейін сақтаңыз немесе бөлісіңіз. Маңызды егжей-тегжей мынада, сақтау шығыс файлын жасайды, ал ортақ пайдалану әдетте басқа қолданбаға жылдам өту үшін жақсы. + Таңдаушы рұқсат еткенде, бір кескінді құралдар үшін бір файлды немесе пакеттік құралдар үшін бірнеше файлды таңдаңыз. + Сақтау алдында алдын ала қарау және шығыс басқару элементтерін, әсіресе пішім, сапа және файл атауы опцияларын тексеріңіз. + Жылдам көшіру үшін ортақ пайдалануды пайдаланыңыз немесе таңдалған қалтада тұрақты көшірме қажет болғанда сақтаңыз. + Бір уақытта көптеген кескіндермен жұмыс жасаңыз + Пакеттік құралдар қайталанатын өлшемді өзгерту, түрлендіру, қысу және атау тапсырмалары үшін ең қолайлы + Болжамды топтаманы дайындаңыз + Пакеттік өңдеу әрбір шығыс бірдей ережелерді сақтау керек болса, ең жылдам болады. Бұл сонымен қатар үлкен қателік жасаудың ең оңай орны, сондықтан ұзақ экспортты бастамас бұрын атауды, сапаны, метадеректерді және қалта әрекетін таңдаңыз. + Барлық қатысты кескіндерді бірге таңдаңыз және шығыс реттілікке байланысты болса, ретті сақтаңыз. + Экспорттауды бастамас бұрын өлшемді, пішімді, сапаны, метадеректерді және файл атауы ережелерін бір рет орнатыңыз. + Бастапқы файлдар үлкен болғанда немесе мақсатты пішім сіз үшін жаңа болғанда, алдымен шағын сынақ бумасын іске қосыңыз. + Жылдам жалғыз өңдеу + Бір суретке бірнеше қарапайым өзгертулер қажет болғанда барлығы бір жерде өңдегішін пайдаланыңыз + Құралды секірусіз бір суретті аяқтаңыз + Бір реттік өңдеу кескінге бір арнайы операцияны емес, шағын өзгерістер тізбегін қажет еткенде пайдалы. Ол әдетте пакеттік жұмыс процесін құрмай, бір соңғы көшірмені жылдам тазалау, алдын ала қарау және экспорттау үшін жылдамырақ. + Жалпы реттеулерді, белгілеуді, қиюды, айналдыруды немесе экспорттауды өзгертуді қажет ететін бір сурет үшін Бірыңғай өңдеуді ашыңыз. + Сүзгілерден бұрын қию және соңғы қысу алдындағы сүзгілер сияқты ең аз пикселдерді алдымен өзгертетін ретпен өңдеулерді қолданыңыз. + Пакеттік өңдеу, нақты файл өлшемінің мақсаттары, PDF шығысы, OCR немесе метадеректерді тазалау қажет болғанда оның орнына арнайы құралды пайдаланыңыз. + Алдын ала орнатулар мен параметрлерді пайдаланыңыз + Қайталанатын таңдауларды сақтаңыз және қолданбаны қалаған жұмыс процесі үшін реттеңіз + Бірдей орнатуды қайталаудан аулақ болыңыз + Алдын ала орнатулар мен параметрлер бір файл түрін жиі экспорттаған кезде пайдалы болады. Жақсы алдын ала орнату қайталанатын қолмен тексеру тізімін бір түртуге айналдырады, ал жаһандық параметрлер жаңа құралдар басталатын әдепкі параметрлерді анықтайды. + Жалпы шығыс өлшемдері, пішімдері, сапа мәндері немесе атау үлгілері үшін алдын ала орнатуларды жасаңыз. + Әдепкі пішім, сапа, сақтау қалтасы, файл атауы, таңдау құралы және интерфейс әрекеті үшін Параметрлерді қарап шығыңыз. + Қолданбаны қайта орнату немесе басқа құрылғыға көшу алдында параметрлердің сақтық көшірмесін жасаңыз. + Пайдалы әдепкі мәндерді орнатыңыз + Әдепкі пішімді, сапаны, масштабтау режимін, өлшемін өзгерту түрін және түс кеңістігін бір рет таңдаңыз + Жаңа құралдарды мақсатыңызға жақындатыңыз + Әдепкі мәндер тек косметикалық артықшылықтар емес. Олар көптеген құралдарда қандай пішім, сапа, өлшемді өзгерту әрекеті, масштаб режимі және түс кеңістігі бірінші болып шығатынын шешеді, бұл әрбір экспортта бірдей таңдауларды қайта таңдауды болдырмауға көмектеседі. + Фотосуреттер үшін JPEG немесе альфа үшін PNG/WebP сияқты әдеттегі мақсатыңызға сәйкес келетін әдепкі кескін пішімін және сапасын орнатыңыз. + Қатаң өлшемдерге, әлеуметтік платформаларға немесе құжат беттеріне жиі экспорттасаңыз, әдепкі өлшем түрін және масштабтау режимін реттеңіз. + Түс кеңістігінің әдепкі мәндерін жұмыс үрдісі дисплейлерде, басып шығаруда немесе сыртқы өңдегіштерде дәйекті түстерді өңдеуді қажет еткенде ғана өзгертіңіз. + Таңдаушы және іске қосу құралы + Қолданба тым көп тілқатысу терезелерін ашқанда немесе дұрыс емес жерден басталғанда таңдаушы параметрлерін пайдаланыңыз + Ашылатын файлдарды әдеттеріңізге сәйкес етіңіз + Таңдаушы және іске қосу құралы параметрлері Image Toolbox негізгі экраннан, құралдан немесе файлды таңдау ағынынан басталатынын басқарады. Бұл опциялар әдетте Android ортақ парағынан кірген кезде немесе қолданбаны құралды іске қосу құралы сияқты сезінгіңіз келгенде әсіресе пайдалы. + Жүйе таңдаушысы файлдарды жасырса, тым көп соңғы элементтерді көрсетсе немесе бұлттық файлдарды нашар өңдесе, фотосурет таңдау құралы параметрлерін пайдаланыңыз. + Өткізіп алуды әдетте ортақ пайдаланудан енгізетін немесе бастапқы файлды білетін құралдар үшін ғана қосыңыз. + Кескін құралдар жинағы кәдімгі басты экранға қарағанда құрал таңдау құралы сияқты әрекет етуін қаласаңыз, іске қосу режимін қарап шығыңыз. + Сурет көзі + Галереялармен, файл менеджерлерімен және бұлттық файлдармен жақсы жұмыс істейтін таңдау режимін таңдаңыз + Файлдарды дұрыс көзден таңдаңыз + Кескін көзі параметрлері қолданбаның Android жүйесінен кескіндерді сұрау жолына әсер етеді. Ең жақсы таңдау файлдарыңыздың жүйелік галереяда, файл менеджері қалтасында, бұлт провайдерінде немесе уақытша мазмұнды бөлісетін басқа қолданбада тұруына байланысты. + Жалпы галерея кескіндері үшін ең көп жүйе біріктірілген тәжірибе қажет болғанда әдепкі таңдау құралын пайдаланыңыз. + Альбомдар, қалталар, бұлттық файлдар немесе стандартты емес пішімдер күткендей пайда болмаса, таңдау режимін ауыстырыңыз. + Егер ортақ файл бастапқы қолданбадан шыққаннан кейін жоғалып кетсе, оны тұрақты таңдау құралы арқылы қайта ашыңыз немесе алдымен жергілікті көшірмені сақтаңыз. + Таңдаулылар және ортақ құралдар + Негізгі экранды бір бағытта ұстаңыз және бөлісу ағындарында мағынасы жоқ құралдарды жасырыңыз + Құралдар тізімінің шуын азайтыңыз + Таңдаулылар мен бөлісуге арналған жасырын параметрлер күн сайын тек бірнеше құралдарды пайдаланған кезде пайдалы болады. Сондай-ақ олар басқа қолданба жіберетін файл түрін пайдалана алмайтын құралдарды жасыру арқылы бөлісу ағынын практикалық сақтайды. + Құралдар санат бойынша топтастырылған кезде де қол жеткізу оңай болуы үшін жиі құралдарды бекітіңіз. + Басқа қолданбадан келетін файлдарға қатысы жоқ құралдарды бөлісу ағындарынан жасырыңыз. + Таңдаулыларды соңында немесе қатысты құралдармен топтастыруды қаласаңыз, таңдаулы тапсырыс параметрлерін пайдаланыңыз. + Параметрлердің сақтық көшірмесін жасаңыз + Алдын ала орнатуларды, теңшелімдерді және қолданба әрекетін басқа орнатуға қауіпсіз жылжытыңыз + Орнатуды портативті сақтаңыз + Сақтық көшірме жасау және қалпына келтіру алдын ала орнатуларды, қалталарды, файл атауы ережелерін, құрал ретін және көрнекі теңшелімдерді реттегеннен кейін өте пайдалы. Сақтық көшірме жұмыс процесін жадтан қайта құрмай-ақ параметрлермен тәжірибе жасауға немесе қолданбаны қайта орнатуға мүмкіндік береді. + Алдын ала орнатуларды, файл атауының әрекетін, әдепкі пішімдерді немесе құрал орналасуын өзгерткеннен кейін сақтық көшірме жасаңыз. + Деректерді өшіруді, құрылғыларды қайта орнатуды немесе жылжытуды жоспарласаңыз, сақтық көшірмені қолданба қалтасынан тыс жерде сақтаңыз. + Маңызды топтамаларды өңдеуден бұрын параметрлерді қалпына келтіріңіз, осылайша әдепкі мәндер мен сақтау әрекеті ескі орнатуыңызға сәйкес келеді. + Суреттердің өлшемін өзгерту және түрлендіру + Бір немесе бірнеше кескіннің өлшемін, пішімін, сапасын және метадеректерін өзгертіңіз + Өлшемі өзгертілген көшірмелерді жасаңыз + Өлшемді өзгерту және түрлендіру болжамды өлшемдер мен жеңілірек шығыс файлдарына арналған негізгі құрал болып табылады. Кескін өлшемі, шығыс пішімі және метадеректер әрекеті бір уақытта маңызды болғанда оны пайдаланыңыз. + Бір немесе бірнеше кескінді таңдаңыз, содан кейін өлшемдердің, масштабтың немесе файл өлшемінің маңыздылығын таңдаңыз. + Шығару пішімін және сапасын таңдаңыз; мөлдірлікті сақтау қажет болғанда PNG немесе WebP пайдаланыңыз. + Нәтижені алдын ала қараңыз, содан кейін түпнұсқаларды өзгертпей жасалған көшірмелерді сақтаңыз немесе ортақ пайдаланыңыз. + Файл өлшемі бойынша өлшемін өзгерту + Веб-сайт, мессенджер немесе пішін ауыр кескіндерді қабылдамаған кезде өлшем шегін белгілеңіз + Жүктеп салуға қатаң шектеулер қойыңыз + Тағайындалған орын тек белгілі бір шектен төмен файлдарды қабылдағанда, файл өлшемі бойынша өлшемді өзгерту сапа мәндерін болжаудан жақсырақ. Құрал мақсатқа жету үшін сығымдау мен өлшемдерді реттей отырып, нәтижені жарамды күйде ұстауға тырысуы мүмкін. + Метадеректер мен провайдер өзгерістеріне орын қалдыру үшін нақты жүктеп салу шегінен сәл төмен мақсатты өлшемді енгізіңіз. + Мақсат кішкентай болған кезде фотосуреттер үшін жоғалтатын пішімдерге артықшылық беріңіз; PNG өлшемін өзгерткеннен кейін де үлкен болып қалуы мүмкін. + Экспорттаудан кейін беттерді, мәтінді және жиектерді тексеріңіз, себебі агрессивті өлшем нысандары көрінетін артефактілерді енгізуі мүмкін. + Өлшемді өзгертуді шектеңіз + Ең үлкен ені, биіктігі немесе файл шектеулерінен асатын кескіндерді ғана кішірейтіңіз + Қазірдің өзінде жақсы кескіндердің өлшемін өзгертуден аулақ болыңыз + Шектеу өлшемін өзгерту кейбір кескіндер үлкен, ал басқалары дайын болған аралас қалталар үшін пайдалы. Әрбір файлды бірдей өлшемді өзгерту арқылы мәжбүрлеудің орнына, ол қолайлы кескіндерді бастапқы сапасына жақындатады. + Әрбір файлдың өлшемін соқыр түрде өзгертудің орнына тағайындалған жерге негізделген максималды өлшемдерді немесе шектеулерді орнатыңыз. + Дереккөздерден ауыррақ болатын шығыстарды болдырғыңыз келсе, үлкенірек мәнер әрекетін өткізіп жіберіңіз. + Мұны әртүрлі камералардан, скриншоттардан немесе дереккөз өлшемдері әр түрлі болатын жүктеп алулар үшін пайдаланыңыз. + Қиып, айналдырыңыз және түзетіңіз + Кескінді экспорттау немесе басқа құралдарда пайдалану алдында маңызды аймақты жақтаңыз + Композицияны тазалаңыз + Алдымен қию кейінгі сүзгілерді, OCR, PDF беттерін және ортақ нәтижелерді тазартады. + Crop ашыңыз және реттегіңіз келетін кескінді таңдаңыз. + Шығару жазбаға, құжатқа немесе аватарға сәйкес келуі керек болса, бос қиюды немесе арақатынасты пайдаланыңыз. + Сақтау алдында бұрыңыз немесе аударыңыз, осылайша кейінірек құралдар түзетілген кескінді алады. + Сүзгілер мен реттеулерді қолданыңыз + Өңделетін сүзгі стегін жасаңыз және экспорттау алдында нәтижені алдын ала қараңыз + Кескіннің көрінісін реттеңіз + Сүзгілер жылдам түзетулер, стильдендірілген шығару және OCR немесе басып шығару үшін кескіндерді дайындау үшін пайдалы. Кескінде мәтін немесе ұсақ бөлшектер болған кезде контраст, айқындық және жарықтықтағы шағын өзгерістер ауыр әсерлерден маңыздырақ болуы мүмкін. + Сүзгілерді бір-бірлеп қосып, әр өзгертуден кейін алдын ала қарауды қадағалаңыз. + Тапсырмаға байланысты жарықтықты, контрастты, айқындықты, бұлыңғырлықты, түсті немесе маскаларды реттеңіз. + Алдын ала қарау мақсатты құрылғыға, құжатқа немесе әлеуметтік платформаға сәйкес келгенде ғана экспорттаңыз. + Алдымен алдын ала қарау + Өңдеу, түрлендіру немесе тазалау құралын таңдау алдында кескіндерді тексеріңіз + Сіз шынымен не алғаныңызды тексеріңіз + Кескінді алдын ала қарау файл чаттан, бұлттық жадтан немесе басқа қолданбадан келген кезде және оның құрамында не бар екенін білмесеңіз пайдалы. Өлшемдерді, мөлдірлікті, бағдарды және көрнекі сапаны тексеру алдымен дұрыс бақылау құралын таңдауға көмектеседі. + Бірнеше кескінді олармен не істеу керектігін шешпес бұрын тексеру қажет болғанда, кескінді алдын ала қарауды ашыңыз. + Айналу мәселелерін, мөлдір аумақтарды, қысу артефактілерін және күтпеген үлкен өлшемдерді іздеңіз. + Нені түзету керектігін білгеннен кейін ғана Өлшемді өзгерту, Қию, Салыстыру, EXIF ​​немесе Фон жою құралына жылжытыңыз. + Фонды жою немесе қалпына келтіру + Фондарды автоматты түрде өшіріңіз немесе қалпына келтіру құралдарымен жиектерді қолмен нақтылаңыз + Тақырыпты сақтаңыз, қалғанын алып тастаңыз + Фонды жою автоматты таңдауды қолмен мұқият тазалауды біріктіргенде жақсы жұмыс істейді. Соңғы экспорт пішімі маска сияқты маңызды, өйткені JPEG алдын ала қарау дұрыс болып көрінсе де мөлдір аумақтарды тегістейді. + Нысан фоннан анық бөлінген болса, автоматты жоюдан бастаңыз. + Шашты, бұрыштарды, көлеңкелерді және ұсақ бөлшектерді түзету үшін үлкейтіп, өшіру және қалпына келтіру арасында ауысыңыз. + Соңғы файлда мөлдірлік қажет болғанда PNG немесе WebP ретінде сақтаңыз. + Сурет салыңыз, белгілеңіз және су таңбасын салыңыз + Көрсеткілерді, ескертпелерді, бөлектеулерді, қолтаңбаларды немесе қайталанатын су таңбасы қабаттарын қосыңыз + Көрінетін ақпаратты қосыңыз + Белгілеу құралдары кескінді түсіндіруге көмектеседі, ал су таңбалау экспортталған файлдарды брендке немесе қорғауға көмектеседі. + Еркін жазбалар, кескіндер, бөлектеу немесе жылдам аннотациялар қажет болғанда Draw қолданбасын пайдаланыңыз. + Бірдей мәтін немесе сурет белгісі шығыстарда тұрақты түрде пайда болған кезде су таңбасын пайдаланыңыз. + Белгі көрінетін, бірақ алаңдатпайтындай етіп экспорттау алдында мөлдірлікті, орынды және масштабты тексеріңіз. + Әдепкі мәндерді сызу + Тезірек белгілеу үшін сызық енін, түсін, жол режимін, бағдар құлпын және үлкейткішті орнатыңыз + Суретті болжауға болатындай етіп жасаңыз + Сурет салу параметрлері скриншоттарды аннотациялау, құжаттарды белгілеу немесе кескіндерге жиі қол қою кезінде пайдалы болады. Әдепкі параметрлер орнату уақытын қысқартады, ал ұлғайтқыш пен бағдар құлпы шағын экрандарда нақты өңдеулерді жеңілдетеді. + Әдепкі сызық енін орнатыңыз және көрсеткілер, бөлектеулер немесе қолтаңбалар үшін жиі пайдаланатын мәндерге түс сызыңыз. + Әдепкі жол режимін әдетте штрихтардың, кескіндердің немесе белгілеудің бірдей түрін салғанда пайдаланыңыз. + Саусақпен енгізу ұсақ бөлшектерді дәл орналастыруды қиындатқанда ұлғайтқышты немесе бағдар құлпын қосыңыз. + Көп кескінді макеттер + Скриншоттарды, сканерлеулерді немесе салыстыру жолақтарын реттемес бұрын дұрыс көп кескін құралын таңдаңыз + Дұрыс орналасу құралын пайдаланыңыз + Бұл құралдар ұқсас естіледі, бірақ әрқайсысы көп кескінді шығарудың басқа түріне теңшелген. Алдымен дұрысын таңдау басқа нәтижеге арналған орналасуды басқару элементтерімен күресуді болдырмайды. + Бір композициядағы бірнеше кескіні бар жобаланған макеттер үшін Collage Maker қолданбасын пайдаланыңыз. + Кескіндерді бір ұзын тік немесе көлденең жолаққа айналдыру қажет болғанда Кескінді тігу немесе жинақтау мүмкіндігін пайдаланыңыз. + Бір үлкен кескін бірнеше кішірек бөліктерге айналуы қажет болғанда Кескінді бөлуді пайдаланыңыз. + Сурет пішімдерін түрлендіру + JPEG, PNG, WebP, AVIF, JXL және басқа көшірмелерді пикселдерді қолмен өңдеусіз жасаңыз + Жұмыс пішімін таңдаңыз + Фотосуреттер, скриншоттар, мөлдірлік, қысу немесе үйлесімділік үшін әртүрлі форматтар жақсырақ. Тағайындалған қолданба нені қолдайтынын және көзде сақтағыңыз келетін альфа, анимация немесе метадеректер бар-жоғын түсінген кезде түрлендіру ең қауіпсіз болады. + Үйлесімді фотосуреттер үшін JPEG, жоғалтпайтын кескіндер мен альфа үшін PNG және ықшам бөлісу үшін WebP пайдаланыңыз. + Пішім оны қолдайтын кезде сапаны реттеңіз; төменгі мәндер өлшемді азайтады, бірақ артефактілерді қоса алады. + Түпнұсқаларды мақсатты қолданбада дұрыс ашылған түрлендірілген файлдарды тексермейінше сақтаңыз. + EXIF және құпиялылықты тексеріңіз + Маңызды фотосуреттерді бөлісу алдында метадеректерді қараңыз, өңдеңіз немесе жойыңыз + Жасырын кескін деректерін басқару + EXIF файлында камера деректері, күндер, орын, бағдар және суретте көрінбейтін басқа мәліметтер болуы мүмкін. Қоғамдық бөлісу, қолдау көрсету есептері, нарық тізімдері немесе жеке орынды ашатын кез келген фотосурет алдында оны тексерген жөн. + Орын немесе жеке құрылғы ақпараты болуы мүмкін фотосуреттерді бөліспес бұрын EXIF ​​құралдарын ашыңыз. + Сезімтал өрістерді жойыңыз немесе күн, бағдар, автор немесе камера деректері сияқты қате тегтерді өңдеңіз. + Жаңа көшірмені сақтаңыз және құпиялылық маңызды болғанда түпнұсқаның орнына сол көшірмені бөлісіңіз. + EXIF автоматты түрде тазалау + Күндердің сақталуын немесе сақталуын шешу кезінде метадеректерді әдепкі бойынша жойыңыз + Құпиялықты әдепкі етіп жасаңыз + EXIF параметрлері тобы құпиялылықты тазалауды әрбір экспортта есте сақтаудың орнына автоматты түрде орындаған кезде пайдалы. Күн уақытын сақтау - бұл бөлек шешім, себебі күндер сұрыптау үшін пайдалы, бірақ бөлісу үшін сезімтал болуы мүмкін. + Экспорттардың көпшілігі жалпыға ортақ немесе жартылай жалпыға ортақ пайдалануға арналған болса, әрқашан таза EXIF ​​​​қосыңыз. + Түсіру уақытын сақтау әрбір уақыт белгісін жоюдан маңыздырақ болған кезде ғана Күн уақытын сақтау мүмкіндігін қосыңыз. + Кейбір өрістерді сақтау және басқаларын жою қажет болатын бір реттік файлдар үшін қолмен EXIF ​​​​ өңдеуін пайдаланыңыз. + Файл атауларын басқару + Префикстерді, жұрнақтарды, уақыт белгілерін, алдын ала орнатуларды, бақылау сомасын және реттік нөмірлерді пайдаланыңыз + Экспортты табуды жеңілдетіңіз + Жақсы файл атауы үлгісі топтамаларды сұрыптауға көмектеседі және кездейсоқ қайта жазуды болдырмайды. Файл атауының параметрлері әсіресе экспорттар бастапқы қалтаға қайтып оралғанда пайдалы, мұнда ұқсас атаулар тез шатастырылуы мүмкін. + Параметрлерде файл атауының префиксін, жұрнағын, уақыт белгісін, түпнұсқа атын, алдын ала орнатылған ақпаратты немесе реттілік опцияларын орнатыңыз. + Беттер, жақтаулар немесе коллаждар сияқты тапсырыс маңызды болатын топтамалар үшін реттік нөмірлерді пайдаланыңыз. + Бар файлдарды ауыстыру ағымдағы қалта үшін қауіпсіз екеніне сенімді болғанда ғана қайта жазуды қосыңыз. + Файлдарды қайта жазу + Жұмыс үрдісі әдейі бүлдіретін болса ғана шығыстарды сәйкес атаулармен ауыстырыңыз + Қайта жазу режимін мұқият пайдаланыңыз + Қайта жазу файлдары атау қақтығыстарын өңдеу жолын өзгертеді. Ол бір қалтаға қайталанатын экспорттау үшін пайдалы, бірақ файл атауының үлгісі бірдей атауды қайта шығарса, ол бұрынғы нәтижелерді де алмастыра алады. + Ескі жасалған файлдарды ауыстыру күтілетін және қалпына келтіруге болатын қалталар үшін ғана қайта жазуды қосыңыз. + Түпнұсқаларды, клиенттік файлдарды, бір реттік сканерлеуді немесе сақтық көшірмесінсіз кез келген нәрсені экспорттау кезінде қайта жазуды өшіріп қойыңыз. + Қайта жазуды әдейі жасалған файл атауы үлгісімен біріктіріңіз, осылайша тек жоспарланған нәтижелер ауыстырылады. + Файл атауы үлгілері + Түпнұсқа атауларды, префикстерді, жұрнақтарды, уақыт белгілерін, алдын ала орнатуларды, масштаб режимін және бақылау сомасын біріктіріңіз + Шығаруды түсіндіретін атауларды құрастырыңыз + Файл атауы үлгісінің параметрлері экспортталған файлдарды олармен болған оқиғаның пайдалы тарихына айналдыра алады. Бұл пакеттерде маңызды, себебі қалта көрінісі бастапқы өлшемді, алдын ала орнатуды, пішімді және өңдеу ретін ажырата алатын жалғыз орын болуы мүмкін. + Қолмен сұрыптау үшін оқуға болатын атаулар қажет болғанда бастапқы файл атауын, префиксті, суффиксті және уақыт белгісін пайдаланыңыз. + Алдын ала орнатылған, масштабтау режимі, файл өлшемі немесе бақылау сомасы туралы ақпаратты шығыстар олардың жасалу жолын құжаттауы қажет болғанда қосыңыз. + Файлдар түпнұсқалармен немесе бет ретімен жұптастырылуы қажет жұмыс процестері үшін кездейсоқ атаулардан аулақ болыңыз. + Файлдар сақталатын жерді таңдаңыз + Қалталарды, бастапқы қалтаны сақтауды және бір реттік сақтау орындарын орнату арқылы экспортты жоғалтпаңыз + Шығаруларды болжамды етіп сақтаңыз + Көптеген файлдарды өңдегенде немесе бұлттық провайдерлерден файлдарды ортақ пайдаланғанда, сақтау тәртібі маңызды. Қалта параметрлері шығыстардың белгілі бір жерде жиналуын, түпнұсқалардың жанында пайда болуын немесе белгілі бір тапсырма үшін уақытша басқа жерге баруды шешеді. + Әрқашан бір жерде экспорттауды қаласаңыз, әдепкі сақтау қалтаны орнатыңыз. + Шығару бастапқы файлдың жанында қалуы тиіс тазалау жұмыс үрдістері үшін бастапқы қалтаға сақтауды пайдаланыңыз. + Бір тапсырмаға әдепкі параметрді өзгертпестен басқа тағайындау қажет болғанда бір реттік сақтау орнын пайдаланыңыз. + Бір реттік қалталарды сақтау + Қалыпты тағайындалған жеріңізді өзгертпей, келесі экспорттарды уақытша қалтаға жіберіңіз + Арнайы жұмыстарды бөлек ұстаңыз + Бір реттік сақтау орны уақытша жобалар, клиент қалталары, тазалау сеанстары немесе әдеттегі Image Toolbox қалтасымен араласпауы керек экспорттар үшін пайдалы. Ол әдепкі орнатуды қайта жазбастан қысқа мерзімді тағайындауды береді. + Қалыпты экспорттау орнынан тыс тапсырманы бастамас бұрын бір реттік қалтаны таңдаңыз. + Оны қысқа жобалар, ортақ қалталар немесе шығыстар бірге топтастырылуы керек топтамалар үшін пайдаланыңыз. + Тапсырмадан кейін әдепкі қалтаға оралыңыз, осылайша кейінгі экспорттар уақытша орынға күтпеген жерден түспеуі үшін. + Сапа мен файл өлшемін теңестіріңіз + Болжаудың орнына өлшемдерді, пішімді немесе сапаны қашан өзгерту керектігін түсініңіз + Файлдарды әдейі кішірейтіңіз + Файл өлшемі кескін өлшемдеріне, пішіміне, сапасына, мөлдірлігіне және мазмұнның күрделілігіне байланысты. Егер файл әлі де тым ауыр болса, өлшемдерді өзгерту сапаны қайта-қайта төмендетуден гөрі көбірек көмектеседі. + Кескін тағайындалған жерден үлкенірек болғанда алдымен өлшемдерді өзгертіңіз. + Тек жоғалған пішімдер үшін сапаны төмендетіңіз және экспорттан кейін мәтінді, жиектерді, градиенттерді және беттерді тексеріңіз. + Сапа өзгерістері жеткіліксіз болған кезде пішімді ауыстырыңыз, мысалы, ортақ пайдалануға арналған WebP немесе анық мөлдір активтер үшін PNG. + Анимациялық форматтармен жұмыс + Файлда бір нүктелік кескіннің орнына кадрлар болған кезде GIF, APNG, WebP және JXL құралдарын пайдаланыңыз + Әрбір кескінді қозғалыссыз деп санамаңыз + Анимацияланған пішімдерге кадрларды, ұзақтығын және үйлесімділігін түсінетін құралдар қажет. + GIF немесе APNG құралдарын сол пішімдер үшін кадр деңгейіндегі әрекеттер қажет болғанда пайдаланыңыз. + Заманауи қысу немесе анимациялық WebP шығысы қажет болғанда WebP құралдарын пайдаланыңыз. + Бөлісу алдында анимация уақытын алдын ала қараңыз, себебі кейбір платформалар анимациялық кескіндерді түрлендіреді немесе тегістейді. + Шығаруды салыстыру және алдын ала қарау + Экспортты сақтамас бұрын өзгерістерді, файл өлшемін және көрнекі айырмашылықтарды тексеріңіз + Бөлісу алдында растаңыз + Салыстыру құралдары қысу артефактілерін, дұрыс емес түстерді немесе қажетсіз өңдеулерді ерте анықтауға көмектеседі. + Екі нұсқаны қатар тексергіңіз келсе, Салыстыруды ашыңыз. + Шеттерді, мәтінді, градиенттерді және проблемаларды жіберіп алу оңай болатын мөлдір аймақтарды үлкейтіңіз. + Егер шығыс тым ауыр немесе бұлыңғыр болса, алдыңғы құралға оралып, сапаны немесе пішімін реттеңіз. + PDF құралдарының хабын пайдаланыңыз + PDF файлдарын біріктіру, бөлу, бұру, жою, сығу, қорғау, OCR және су таңбасын қою + PDF операциясын таңдаңыз + PDF хабы құжат әрекеттерін бір кіру нүктесінің артына топтастырады, осылайша сіз нақты әрекетті таңдай аласыз. Файл әлдеқашан PDF болған кезде осы жерден бастаңыз және дереккөз әлі де кескіндер жинағы болған кезде PDF-ке кескіндерді немесе құжат сканерін пайдаланыңыз. + PDF құралдарын ашыңыз және мақсатыңызға сәйкес әрекетті таңдаңыз. + Бастапқы PDF немесе кескіндерді таңдаңыз, содан кейін бет ретін, айналдыруды, қорғауды немесе OCR опцияларын қараңыз. + Жасалған PDF файлын сақтаңыз және бет санын, ретін және оқылу мүмкіндігін растау үшін оны бір рет ашыңыз. + Суреттерді PDF форматына айналдырыңыз + Сканерлерді, скриншоттарды, түбіртектерді немесе фотосуреттерді бір ортақ құжатқа біріктіріңіз + Таза құжат жасаңыз + Кескіндер қиылған, реттелген және дәйекті түрде өлшемделген кезде жақсырақ PDF беттеріне айналады. Кескіндер тізімін бет стегі сияқты қарастырыңыз: әрбір айналдыру, жиектер және бет өлшемін таңдау соңғы құжаттың оқуға болатындығына әсер етеді. + Бет жиектері, көлеңкелері немесе бағдарлары дұрыс болмаған кезде алдымен кескіндерді қиыңыз немесе бұрыңыз. + Қажетті бет ретімен кескіндерді таңдаңыз және құрал оларды ұсынса, бет өлшемін немесе шеттерін реттеңіз. + PDF файлын экспорттаңыз, содан кейін оны жібермес бұрын барлық беттердің оқылатынын тексеріңіз. + Құжаттарды сканерлеу + Қағаз беттерін түсіріп, оларды PDF, OCR немесе ортақ пайдалануға дайындаңыз + Оқуға болатын беттерді түсіріңіз + Құжатты сканерлеу тегіс беттермен, жақсы жарықтандырумен және түзетілген перспективамен жақсы жұмыс істейді. OCR немесе PDF экспорттау алдында таза сканерлеу уақытты үнемдейді, себебі мәтінді тану және қысу екеуі де бет сапасына байланысты. + Құжатты жеткілікті жарық және минималды көлеңкелері бар контрастты бетке қойыңыз. + Анықталған бұрыштарды реттеңіз немесе бет жақтауды таза толтыратындай қолмен қиыңыз. + Суреттер немесе PDF ретінде экспорттаңыз, содан кейін таңдалатын мәтін қажет болса, OCR іске қосыңыз. + PDF файлдарын қорғаңыз немесе құлпын ашыңыз + Құпия сөздерді мұқият пайдаланыңыз және мүмкіндігінше өңделетін бастапқы көшірмені сақтаңыз + PDF қатынасын қауіпсіз басқарыңыз + Қорғау құралдары басқарылатын ортақ пайдалану үшін пайдалы, бірақ құпия сөздерді жоғалту оңай және қалпына келтіру қиын. Көшірмелерді тарату үшін қорғаңыз, қорғалмаған бастапқы файлдарды бөлек сақтаңыз және ештеңені жоймас бұрын нәтижені тексеріңіз. + Жалғыз бастапқы құжатты емес, PDF көшірмесін қорғаңыз. + Қорғалған файлды ортақ пайдаланбас бұрын құпия сөзді қауіпсіз жерде сақтаңыз. + Құлыпты ашуды тек өңдеуге рұқсат етілген файлдар үшін пайдаланыңыз, содан кейін құлыптан босатылған көшірменің дұрыс ашылғанын тексеріңіз. + PDF беттерін реттеңіз + Беттерді біріктіру, бөлу, қайта реттеу, бұру, қию, жою және бет нөмірлерін әдейі қосу + Декорация алдында құжат құрылымын түзетіңіз + PDF бетінің құралдарын қағаз құжатын дайындайтын ретпен пайдалану оңай: беттерді жинаңыз, қателерін алып тастаңыз, оларды ретке келтіріңіз, бағытты түзетіңіз, содан кейін бет нөмірлері немесе су белгілері сияқты соңғы мәліметтерді қосыңыз. + Құжат әлі де бірнеше файлдарға бөлінгенде, алдымен біріктіру немесе кескіндерді PDF-ке пайдаланыңыз. + Бет нөмірлерін, қолтаңбаларды немесе су белгілерін қоспас бұрын беттерді қайта реттеңіз, бұрыңыз, қиыңыз немесе жойыңыз. + Құрылымдық өңдеулерден кейін соңғы PDF файлын алдын ала қараңыз, себебі бір жетіспейтін немесе бұрылған бетті кейін байқау қиын болуы мүмкін. + PDF аннотациялары + Көрермендер пікірлер мен белгілерді басқаша көрсеткенде, аннотацияларды алып тастаңыз немесе оларды тегістеңіз + Көрінетін нәрсені басқарыңыз + Аннотациялар түсініктемелер, ерекшеліктер, сызбалар, пішін белгілері немесе басқа қосымша PDF нысандары болуы мүмкін. Кейбір көрушілер оларды басқаша көрсетеді, сондықтан оларды жою немесе тегістеу ортақ құжаттарды болжауға болады. + Түсініктемелер, ерекшеліктер немесе шолу белгілері ортақ көшірмеге қосылмауы керек кезде аннотацияларды алып тастаңыз. + Көрінетін белгілер бетте қалуы керек, бірақ өңделетін аннотациялар сияқты әрекет етпейтін кезде тегістеңіз. + Түпнұсқа көшірмені сақтаңыз, себебі аннотацияларды жою немесе тегістеу кері қайтару қиын болуы мүмкін. + Мәтінді тану + Таңдалатын OCR қозғалтқыштары мен тілдері бар суреттерден мәтінді шығарып алыңыз + Жақсырақ OCR нәтижелерін алыңыз + OCR дәлдігі кескіннің анықтығына, тіл деректеріне, контрастқа және бет бағдарына байланысты. Ең жақсы нәтиже әдетте алдымен кескінді дайындаудан келеді: шуды кесу, тік айналдыру, оқылу мүмкіндігін арттыру, содан кейін мәтінді тану. + Кескінді мәтін аймағына қиып, тану алдында оны тік бұраңыз. + Дұрыс тілді таңдап, қолданба сұраса, тіл деректерін жүктеп алыңыз. + Танылған мәтінді көшіріңіз немесе ортақ пайдаланыңыз, содан кейін атауларды, сандарды және тыныс белгілерін қолмен тексеріңіз. + OCR тіл деректерін таңдаңыз + Сценарийлерді, тілдерді және жүктелген OCR үлгілерін сәйкестендіру арқылы тануды жақсартыңыз + OCR мәтінімен сәйкестендіріңіз + Қате OCR тілі жақсы фотосуреттерді нашар тану сияқты етіп көрсетуі мүмкін. Тіл деректері сценарийлер, тыныс белгілері, сандар және аралас құжаттар үшін маңызды, сондықтан телефонның UI тілін емес, суретте көрсетілетін тілді таңдаңыз. + Тек телефон тілін емес, кескінде көрсетілетін тілді немесе сценарийді таңдаңыз. + Желіден тыс жұмыс істемес бұрын немесе топтаманы өңдеу алдында жетіспейтін деректерді жүктеп алыңыз. + Аралас тілдегі құжаттар үшін алдымен ең маңызды тілді іске қосыңыз және атаулар мен сандарды қолмен тексеріңіз. + Іздеуге болатын PDF файлдары + Сканерленген беттерді іздеу және көшіру үшін OCR мәтінін файлдарға қайта жазыңыз + Сканерлеуді іздеуге болатын құжаттарға айналдырыңыз + OCR мәтінді алмасу буферіне көшіруден көп нәрсені істей алады. Танылған мәтінді файлға немесе ізделетін PDF файлына жазғанда, беттің суреті көрінетін болып қалады, ал мәтінді іздеу, таңдау, индекстеу немесе мұрағаттау оңайырақ болады. + Бастапқы беттерге ұқсайтын сканерленген құжаттар үшін іздеуге болатын PDF шығысын пайдаланыңыз. + Файлға жазуды тек алынған мәтін қажет болғанда және бет кескініне мән бермегенде пайдаланыңыз. + Маңызды сандарды, атауларды және кестелерді қолмен тексеріңіз, себебі OCR мәтіні іздеуге болады, бірақ әлі де жетілмеген. + QR кодтарын сканерлеңіз + Қол жетімді болған кезде камера кірісінен немесе сақталған кескіндерден QR мазмұнын оқыңыз + Кодтарды қауіпсіз оқыңыз + QR кодтарында сілтемелер, Wi-Fi деректері, контактілер, мәтін немесе басқа пайдалы жүктемелер болуы мүмкін. + QR кодын сканерлеуді ашыңыз және кодты анық, тегіс және кадрдың ішінде толығымен сақтаңыз. + Сілтемелерді ашпас немесе құпия деректерді көшірмес бұрын шифрланған мазмұнды қарап шығыңыз. + Сканерлеу сәтсіз болса, жарықтандыруды жақсартыңыз, кодты қиыңыз немесе ажыратымдылығы жоғары бастапқы кескінді қолданып көріңіз. + Base64 және бақылау сомасын пайдаланыңыз + Суреттерді мәтін ретінде кодтаңыз, мәтінді суреттерге қайтарыңыз және файлдың тұтастығын тексеріңіз + Файлдар мен мәтін арасында жылжу + Base64 шағын кескін жүктемелері үшін пайдалы, ал бақылау сомасы файлдардың өзгермегенін растауға көмектеседі. Бұл құралдар техникалық жұмыс процестерінде, қолдау есептеріне, файлдарды тасымалдауда және екілік файлдар уақытша мәтінге айналуы қажет жерлерде ең пайдалы. + Жұмыс процесіне екілік файлдың орнына мәтін қажет болғанда шағын кескінді кодтау үшін Base64 құралдарын пайдаланыңыз. + Base64 кодты тек сенімді көздерден шешіп, сақтау алдында алдын ала қарауды тексеріңіз. + Экспортталған файлдарды салыстыру немесе жүктеп салуды/жүктеп алуды растау қажет болғанда бақылау сомасы құралдарын пайдаланыңыз. + Деректерді бумалау немесе қорғау + Файлдарды біріктіру немесе мәтіндік деректерді түрлендіру үшін ZIP және шифр құралдарын пайдаланыңыз + Қатысты файлдарды бірге сақтаңыз + Буып-түю құралдары бірнеше шығыс бір файл ретінде өтуі қажет болғанда пайдалы болады. Олар сонымен қатар толық нәтижелер жинағын жіберу қажет болғанда жасалған кескіндерді, PDF файлдарын, журналдарды және метадеректерді бірге сақтау үшін жақсы. + Көптеген экспортталған кескіндерді немесе құжаттарды бірге жіберу қажет болғанда ZIP пайдаланыңыз. + Таңдалған әдісті түсінгенде және қажетті кілттерді қауіпсіз сақтай алатын кезде ғана шифрлау құралдарын пайдаланыңыз. + Бастапқы файлдарды жою алдында жасалған мұрағатты немесе түрлендірілген мәтінді тексеріңіз. + Атаулардағы бақылау сомасы + Бірегейлік пен тұтастық оқылатындықтан маңыздырақ болғанда бақылау сомасына негізделген файл атауларын пайдаланыңыз + Файлдарды мазмұны бойынша атаңыз + Бақылау сомасы файл атаулары экспортта қайталанатын көрінетін атаулар болуы мүмкін болғанда немесе екі файлдың бірдей екенін дәлелдеу қажет болғанда пайдалы. Олар қолмен шолу үшін қолайлы емес, сондықтан оларды техникалық мұрағаттар мен тексеру жұмыс процестері үшін пайдаланыңыз. + Мазмұн сәйкестігі адам оқи алатын тақырыптан маңыздырақ болғанда бақылау сомасын файл атауы ретінде қосыңыз. + Оны мұрағаттар, көшірмелерді жою, қайталанатын экспорттаулар немесе жүктеп салуға және қайта жүктеуге болатын файлдар үшін пайдаланыңыз. + Адамдар әлі де файлдың нені білдіретінін түсіну қажет болғанда бақылау сомасы атауларын қалталармен немесе метадеректермен жұптаңыз. + Суреттерден түстерді таңдаңыз + Пикселдерді таңдап алыңыз, түс кодтарын көшіріңіз және бояғыштарда немесе дизайндағы түстерді қайта пайдаланыңыз + Нақты түс үлгісін алыңыз + Түс таңдау дизайнды сәйкестендіру, бренд түстерін шығару немесе кескін мәндерін тексеру үшін пайдалы. Масштабтау маңызды, себебі антиалиазинг, көлеңкелер және қысу көрші пикселдерді сіз күткен түстен сәл өзгешелеуі мүмкін. + «Кескіннен түс таңдау» тармағын ашып, қажетті түсі бар бастапқы кескінді таңдаңыз. + Маңайдағы пикселдер нәтижеге әсер етпейтіндей, кішігірім бөлшектерді таңдау алдында үлкейтіңіз. + Түсті HEX, RGB немесе HSV сияқты тағайындалған орын талап ететін пішімде көшіріңіз. + Бояғыштарды жасаңыз және түстер кітапханасын пайдаланыңыз + Бояғыштарды шығарыңыз, сақталған түстерді шолыңыз және тұрақты визуалды жүйелерді сақтаңыз + Қайта пайдалануға болатын палитра құрастырыңыз + Палитралар экспортталған графиканы, су белгілерін және сызбаларды көрнекі түрде сәйкестендіруге көмектеседі. Олар әсіресе скриншоттарға қайта-қайта аннотация жасағанда, бренд активтерін дайындағанда немесе бірнеше құралдарда бірдей түстер қажет болғанда пайдалы. + Суреттен бояғышты жасаңыз немесе мәндерді білетін болсаңыз, түстерді қолмен қосыңыз. + Маңызды түстерді кейінірек таба алатындай етіп атаңыз немесе реттеңіз. + Draw, су белгісі, градиент, SVG немесе сыртқы дизайн құралдарында көшірілген мәндерді пайдаланыңыз. + Градиенттер жасаңыз + Фондар, толтырғыштар және көрнекі эксперименттер үшін градиент кескіндерін құрастырыңыз + Түстердің ауысуын басқару + Градиент құралдары бұрыннан бар кескінді өңдеудің орнына жасалған кескін қажет болғанда пайдалы. Олар таза фонға, толтырғыштарға, тұсқағаздарға, маскаларға немесе сыртқы дизайн құралдары үшін қарапайым активтерге айналуы мүмкін. + Градиент түрін таңдап, алдымен маңызды түстерді орнатыңыз. + Мақсатты пайдалану жағдайына бағытты, тоқтауды, тегістікті және шығыс өлшемін реттеңіз. + Тағайындалғанға сәйкес келетін пішімде экспорттаңыз, әдетте таза жасалған графика үшін PNG. + Түстердің қолжетімділігі + UI оқу қиын болған кезде динамикалық түстерді, контрастты және түсті соқыр опцияларды пайдаланыңыз + Түстерді пайдалануды жеңілдетіңіз + Түс параметрлері ыңғайлылыққа, оқуға және басқару элементтерін сканерлеу жылдамдығына әсер етеді. Құрал чиптерін, жүгірткілерді немесе таңдалған күйлерді ажырату қиын болса, тақырып пен қол жетімділік опциялары жай ғана қараңғы режимді өзгертуден гөрі пайдалырақ болуы мүмкін. + Қолданбаның ағымдағы тұсқағаз палитрасын ұстануын қаласаңыз, динамикалық түстерді қолданып көріңіз. + Түймешіктер мен беттер жеткілікті түрде ерекшеленбесе, контрастты арттырыңыз немесе схеманы өзгертіңіз. + Кейбір күйлерді немесе екпіндерді ажырату қиын болса, түсті соқыр схема опцияларын пайдаланыңыз. + Альфа фон + Мөлдір PNG, WebP және ұқсас шығыстардың айналасында қолданылатын алдын ала қарауды немесе бояу түсін басқарыңыз + Мөлдір алдын ала қарауды түсіну + Альфа пішімдері үшін фон түсі мөлдір кескіндерді тексеруді жеңілдетеді, бірақ алдын ала қарау/толтыру әрекетін өзгертіп жатқаныңызды немесе соңғы нәтижені тегістейтініңізді білу маңызды. Бұл стикерлер, логотиптер және фондық өшірілген кескіндер үшін маңызды. + Мөлдір пикселдер экспорттауға жарамды болған кезде, алдымен PNG немесе WebP сияқты альфа-қолдау пішімін пайдаланыңыз. + Қолданба бетінен мөлдір жиектерді көру қиын болған кезде фондық түс параметрлерін реттеңіз. + Шағын сынақты экспорттаңыз және мөлдірліктің немесе таңдалған толтырудың сақталғанын растау үшін оны басқа қолданбада қайта ашыңыз. + AI үлгісін таңдау + Алдымен ең үлкенін таңдаудың орнына кескін түрі мен ақауы бойынша үлгіні таңдаңыз + Модельді зақымға сәйкестендіріңіз + AI құралдары әртүрлі ONNX/ORT үлгілерін жүктеп алуы немесе импорттауы мүмкін және әрбір модель мәселенің белгілі бір түріне үйретілген. JPEG блоктарына, аниме желісін тазалауға, өшіруге, фонды жоюға, бояуға немесе масштабтауға арналған үлгі дұрыс емес кескін түрінде пайдаланылғанда нашар нәтижелер беруі мүмкін. + Жүктеп алмас бұрын үлгі сипаттамасын оқыңыз; қысу, шу, жартылай реңк, бұлыңғырлық немесе өңді жою сияқты нақты мәселеңізді атайтын үлгіні таңдаңыз. + Жаңа кескін түрін сынау кезінде кішірек немесе нақтырақ үлгіден бастаңыз, содан кейін нәтиже жеткілікті жақсы болмаса ғана ауыр үлгілермен салыстырыңыз. + Шын мәнінде пайдаланатын үлгілерді ғана сақтаңыз, себебі жүктеп алынған және импортталған үлгілер айтарлықтай сақтау орнын алуы мүмкін. + Үлгілі отбасыларды түсіну + Модель атаулары көбінесе олардың мақсатына нұсқайды: RealESRGAN стиліндегі үлгілер жоғары деңгейлі, SCUNet және FBCNN үлгілері шуды немесе қысуды тазартады, фондық үлгілер маска жасайды және бояғыштар сұр реңкті енгізуге түс қосады. Импортталған теңшелетін үлгілер қуатты болуы мүмкін, бірақ олар күтілетін кіріс/шығыс пішініне сәйкес келуі және қолдау көрсетілетін ONNX немесе ORT пішімін пайдалануы керек. + Көбірек пиксельдер қажет болғанда кеңейткіштерді, кескін түйіршіктелген кезде декузерлерді және қысу блоктары немесе жолақтар негізгі мәселе болған кезде артефакт кетіргіштерді пайдаланыңыз. + Фондық үлгілерді тек нысанды бөлу үшін пайдаланыңыз; олар жалпы нысанды жою немесе кескінді жақсартумен бірдей емес. + Пайдаланушы үлгілерін тек сенетін көздерден импорттаңыз және маңызды файлдарды пайдаланбас бұрын оларды шағын кескінде сынап көріңіз. + AI параметрлері + Сапаны, жылдамдықты және жадты теңестіру үшін бөлік өлшемін, қабаттасуды және параллель жұмысшыларды реттеңіз + Жылдамдықпен тұрақтылықты теңестіріңіз + Кескін өлшемі үлгімен өңделмес бұрын кескін бөліктерінің қаншалықты үлкен екенін басқарады. Қабаттасу шекараларды жасыру үшін көрші бөліктерді араластырады. Параллель жұмысшылар өңдеуді жылдамдата алады, бірақ әрбір жұмысшыға жад қажет, сондықтан жоғары мәндер жедел жады шектеулі телефондарда үлкен кескіндерді тұрақсыз етеді. + Құрылғы модельді сенімді түрде өңдегенде және кескін үлкен емес болғанда, контекст тегістеу үшін үлкенірек бөліктерді пайдаланыңыз. + Бөлшек жиектері көрінген кезде қабаттасуды арттырыңыз, бірақ көбірек қабаттасу қайталанатын жұмысты білдіретінін есте сақтаңыз. + Тұрақты сынақтан кейін ғана параллель жұмысшыларды көтеру; өңдеу баяуласа, құрылғыны қыздырса немесе бұзылса, алдымен жұмысшыларды азайтыңыз. + Бөлшек артефактілерден аулақ болыңыз + Chunging қолданбаға бір уақытта ыңғайлы өңдеуге болатын модельден үлкенірек кескіндерді өңдеуге мүмкіндік береді. Сәйкестік мынада: әрбір тақтайшаның айналасындағы контекст аз, сондықтан төмен қабаттасу немесе тым кішкентай бөліктер көрші аймақтар арасында көрінетін жиектерді, құрылымдық өзгерістерді немесе сәйкес келмейтін бөлшектерді тудыруы мүмкін. + Тікбұрышты жиектерді, қайталанатын текстура өзгерістерін немесе өңделген аймақтар арасында егжей-тегжейлі өтулерді көрсеңіз, қабаттасуды арттырыңыз. + Шығармас бұрын, әсіресе үлкен кескіндерде немесе ауыр үлгілерде процесс сәтсіз болса, кесек өлшемін азайтыңыз. + Параметрлерді жылдам салыстыру үшін толық кескінді өңдеу алдында шағын қию сынамасын сақтаңыз. + AI тұрақтылығы + AI өңдеуі бұзылған немесе қатып қалған кезде бөлік өлшемін, жұмысшыларды және бастапқы ажыратымдылықты азайтыңыз + Ауыр AI жұмыстарынан қалпына келтіріңіз + AI өңдеу - қолданбадағы ең ауыр жұмыс процестерінің бірі. Бұзылулар, сәтсіз сеанстар, қатып қалу немесе қолданбаның кенет қайта іске қосылуы әдетте құрылғының ағымдағы күйі үшін модель, бастапқы ажыратымдылық, бөлік өлшемі немесе параллель жұмысшылар саны тым талап етілетінін білдіреді. + Қолданба бұзылса немесе үлгі сеансын аша алмаса, параллель жұмысшылар мен бөлік өлшемін азайтып, сол суретті қайталап көріңіз. + Мақсат бастапқы ажыратымдылықты қажет етпесе, AI өңдеу алдында өте үлкен кескіндердің өлшемін өзгертіңіз. + Жад қысымы қалпына келген кезде, басқа ауыр қолданбаларды жабыңыз, кішірек үлгіні пайдаланыңыз немесе бірден азырақ файлдарды өңдеңіз. + Модель ақауларын диагностикалау + Сәтсіз AI іске қосу әрқашан кескіннің нашар екенін білдірмейді. Үлгі файлы толық емес, қолдау көрсетілмеген, жад үшін тым үлкен немесе операциямен сәйкес келмеуі мүмкін. Қолданба сәтсіз сеанс туралы хабарлағанда, оны алдымен үлгі мен параметрлерді жеңілдету үшін сигнал ретінде қарастырыңыз. + Модель жүктеп алғаннан кейін бірден сәтсіз аяқталса немесе файл бүлінген сияқты әрекет етсе, оны жойыңыз және қайта жүктеңіз. + Импортталған пайдаланушы үлгісін кінәламас бұрын, кірістірілген жүктеп алынатын үлгіні қолданып көріңіз, себебі импортталған үлгілерде сәйкес келмейтін кірістер болуы мүмкін. + Бұзылу немесе сеанс қатесі туралы хабарлау қажет болса, қатені қайта жасағаннан кейін қолданба журналдарын пайдаланыңыз. + Shader Studio бағдарламасында эксперимент + Жетілдірілген кескінді өңдеу және реттелетін көріністер үшін GPU шейдер әсерлерін пайдаланыңыз + Шейдерлермен мұқият жұмыс жасаңыз + Shader Studio қуатты, бірақ шейдер коды мен параметрлері шағын, тексерілетін өзгерістерді қажет етеді. Оны эксперименттік құрал ретінде қарастырыңыз: жұмыстың бастапқы деңгейін сақтаңыз, бір уақытта бір нәрсені өзгертіңіз және алдын ала орнатуға сенбес бұрын әртүрлі кескін өлшемдерімен сынап көріңіз. + Параметрлерді қоспас бұрын белгілі жұмыс шейдерінен немесе қарапайым әсерден бастаңыз. + Бір уақытта бір параметрді немесе код блогын өзгертіңіз және қателерді алдын ала қарауды тексеріңіз. + Алдын ала орнатуларды бірнеше түрлі кескін өлшемдерінде тексергеннен кейін ғана экспорттаңыз. + SVG немесе ASCII өнерін жасаңыз + Шығармашылық нәтиже үшін вектор тәрізді активтерді немесе мәтінге негізделген кескіндерді жасаңыз + Шығармашылық шығару түрін таңдаңыз + SVG және ASCII құралдары әртүрлі мақсаттарға қызмет етеді: масштабталатын графика немесе мәтіндік стильді көрсету. Екеуі де шығармашылық түрлендірулер, сондықтан соңғы өлшемде алдын ала қарау нәтижені кішкентай нобай арқылы бағалаудан маңыздырақ. + Нәтиже таза түрде масштабталуы немесе дизайн жұмыс үрдісінде қайта пайдаланылуы қажет болғанда SVG Maker пайдаланыңыз. + Кескіннің стильдендірілген мәтіндік көрінісі қажет болғанда ASCII Art пайдаланыңыз. + Соңғы өлшемде алдын ала қарау, себебі шағын мәліметтер жасалған шығыста жоғалып кетуі мүмкін. + Шу текстураларын жасаңыз + Фондар, маскалар, қабаттасулар немесе эксперименттер үшін процедуралық текстураларды жасаңыз + Процедуралық шығысты баптау + Шуды генерациялау параметрлерден жаңа кескіндерді жасайды, сондықтан кішігірім өзгерістер мүлдем басқа нәтижелерді тудыруы мүмкін. Бұл текстуралар, маскалар, қабаттасулар, толтырғыштар немесе егжей-тегжейлі үлгілері бар қысуды тексеру үшін пайдалы. + Жіңішке бөлшектерді баптау алдында шу түрі мен шығыс өлшемін таңдаңыз. + Үлгі мақсатты пайдалануға сәйкес келгенше масштабты, қарқындылықты, түстерді немесе тұқымды реттеңіз. + Пайдалы нәтижелерді сақтаңыз және текстураны кейінірек қайта жасау қажет болса, параметрлерді жазып алыңыз. + Белгілеу жобалары + Қолданбаны жапқаннан кейін аннотацияларды өңдеуге болатын күйде қалу қажет болғанда жоба файлдарын пайдаланыңыз + Қабатты өңдеулерді қайта пайдалануға болатындай етіп сақтаңыз + Белгілеу жобасы файлдары скриншот, диаграмма немесе нұсқаулық кескіні кейінірек өзгертулер қажет болғанда пайдалы. Экспортталған PNG/JPEG файлдары соңғы кескіндер болып табылады, ал жоба файлдары болашақ реттеулер үшін өңделетін қабаттарды және өңдеу күйін сақтайды. + Аннотациялар, ескертулер немесе орналасу элементтері өңделетін болып қалуы керек кезде белгілеу қабаттарын пайдаланыңыз. + Қосымша түзетулерді күтсеңіз, соңғы кескінді экспорттамас бұрын жоба файлын сақтаңыз немесе қайта ашыңыз. + Қалыпты кескінді тек тегістелген соңғы нұсқаны ортақ пайдалануға дайын болғанда ғана экспорттаңыз. + Файлдарды шифрлау + Кез келген бастапқы көшірмені жоймас бұрын файлдарды құпия сөзбен қорғаңыз және шифрды шешуді тексеріңіз + Шифрланған шығыстарды мұқият өңдеңіз + Шифр құралдары файлдарды құпия сөзге негізделген шифрлау арқылы қорғай алады, бірақ олар кілтті есте сақтау немесе сақтық көшірмелерді сақтауды алмастырмайды. Шифрлау тасымалдау және сақтау үшін пайдалы, ал ZIP негізгі мақсат бірнеше шығыстарды біріктіру болса жақсырақ. + Алдымен көшірмені шифрлаңыз және шифрды шешу сақталған құпия сөзбен жұмыс істейтінін тексергенше түпнұсқаны сақтаңыз. + Құпия сөз реттеушісін немесе кілт үшін басқа қауіпсіз орынды пайдаланыңыз; қолданба сіз үшін жоғалған құпия сөзді болжай алмайды. + Шифрланған файлдарды тек құпия сөзі бар және қай құрал немесе әдіс шифрын ашу керектігін білетін адамдармен ғана бөлісіңіз. + Құралдарды реттеңіз + Негізгі экран нақты жұмыс үрдісіңізге сәйкес келетіндей етіп, құралдарды топтаңыз, тапсырыс беріңіз, іздеңіз және бекітіңіз + Негізгі экранды жылдамырақ жасаңыз + Сіз қай құралдарды көп қолданатыныңызды білгенде, құралды орналастыру параметрлерін реттеуге тұрарлық. Топтау зерттеуге көмектеседі, реттелетін тәртіп бұлшықет жадына көмектеседі және таңдаулылар толық тізім үлкен болған кезде де күнделікті құралдарды қол жетімді етеді. + Қолданбаны үйрену кезінде немесе тапсырма түрі бойынша ұйымдастырылған құралдарды таңдағанда топтастырылған режимді пайдаланыңыз. + Жиі қолданылатын құралдарды білетін болсаңыз және азырақ айналдыруды қаласаңыз, реттелетін ретке ауысыңыз. + Аты бойынша есте сақталатын, бірақ үнемі көрінуін қаламайтын сирек құралдар үшін іздеу параметрлері мен таңдаулыларды бірге пайдаланыңыз. + Үлкен файлдарды өңдеу + Баяу экспорттауды, жад қысымын және күтпеген үлкен шығысты болдырмаңыз + Экспортқа дейін жұмысты азайтыңыз + Өте үлкен кескіндер, ұзақ партиялар және жоғары сапалы пішімдер көбірек жад пен уақытты алуы мүмкін. Ең жылдам түзету, әдетте, бірдей үлкен өлшемді енгізудің аяқталуын күтпей, ең ауыр операция алдында жұмыс көлемін азайту болып табылады. + Ауыр сүзгілерді, OCR немесе PDF түрлендіруді қолданбас бұрын үлкен кескіндердің өлшемін өзгертіңіз. + Параметрлерді растау үшін алдымен кішірек топтаманы пайдаланыңыз, содан кейін қалғанын бірдей алдын ала орнатумен өңдеңіз. + Шығарылатын файл күтілгеннен үлкенірек болса, сапасы төмен немесе пішімін өзгертіңіз. + Кэш және алдын ала қарау + Жасалған алдын ала қарауды, кэшті тазалауды және ауыр сеанстардан кейін жадты пайдалануды түсініңіз + Сақтау мен жылдамдықты теңестіріңіз + Алдын ала қарауды жасау және кэш қайталанатын шолуды жылдамдатады, бірақ күрделі өңдеу сеанстары уақытша деректерді артта қалдыруы мүмкін. Кэш параметрлері қолданба баяу болғанда, жад тар болғанда немесе көптеген эксперименттерден кейін алдын ала қарау ескірген кезде көмектеседі. + Көптеген кескіндерді шолу кезінде алдын ала қарау мүмкіндігін қосулы ұстау уақытша жадты азайтудан маңыздырақ. + Үлкен топтамалардан, сәтсіз эксперименттерден немесе көптеген жоғары ажыратымдылығы бар файлдармен ұзақ сеанстардан кейін кэшті тазалаңыз. + Іске қосулар арасында алдын ала қарауды жылы ұстаудан гөрі жадты тазалауды қаласаңыз, кэшті автоматты тазалауды пайдаланыңыз. + Экран әрекетін реттеңіз + Фокусталған жұмыс үшін толық экран, қауіпсіз режим, жарықтық және жүйелік жолақ опцияларын пайдаланыңыз + Интерфейсті жағдайға сай етіңіз + Экран параметрлері альбомдық режимде өңдегенде, жеке суреттерді ұсынғанда немесе тұрақты жарықтық қажет болғанда көмектеседі. Олар қалыпты пайдалану үшін қажет емес, бірақ олар QR тексеруі, жеке алдын ала қарау немесе тар пейзажды өңдеу сияқты ыңғайсыз жағдайларды шешеді. + Кеңістікті өңдеу хром навигациясынан маңыздырақ болғанда, толық экранды немесе жүйелік жолақ параметрлерін пайдаланыңыз. + Жеке кескіндер скриншоттарда немесе қолданбаны алдын ала қарауда көрсетілмеуі керек кезде қауіпсіз режимді пайдаланыңыз. + Күңгірт кескіндерді немесе QR кодтарын тексеру қиын болатын құралдар үшін жарықтықты күшейтуді пайдаланыңыз. + Мөлдірлікті сақтаңыз + Мөлдір фонның ақ, қара немесе тегістелуіне жол бермеңіз + Альфа-хабарлама шығысын пайдаланыңыз + Таңдалған құрал мен шығыс пішімі альфаны қолдағанда ғана мөлдірлік сақталады. Экспортталған фон ақ немесе қара болып өзгерсе, мәселе әдетте шығыс пішімі, толтыру/фон параметрі немесе кескінді тегістейтін тағайындалған қолданба болып табылады. + Өңде жойылған кескіндерді, стикерлерді, логотиптерді немесе қабаттасуды экспорттау кезінде PNG немесе WebP таңдаңыз. + Мөлдір шығару үшін JPEG форматынан аулақ болыңыз, себебі ол альфа арнасын сақтамайды. + Алдын ала қарау толтырылған фонды көрсетсе, альфа пішімдері үшін фон түсіне қатысты параметрлерді тексеріңіз. + Бөлісу және импорттау мәселелерін түзетіңіз + Файлдар пайда болмаған немесе дұрыс ашылмаған кезде таңдау құралының параметрлерін және қолдау көрсетілетін пішімдерді пайдаланыңыз + Файлды қолданбаға жүктеңіз + Импорттау мәселелері көбінесе провайдердің рұқсаттарынан, қолдау көрсетілмейтін пішімдерден немесе таңдаушының әрекетінен туындайды. Бұлттық қолданбалар мен хабаршылардан ортақ файлдар уақытша болуы мүмкін, сондықтан тұрақты дереккөзді таңдау ұзағырақ өңдеулер үшін сенімдірек болуы мүмкін. + Файлды соңғы файлдар таңбашасының орнына жүйелік таңдау құралы арқылы ашып көріңіз. + Пішімге бір құрал қолдау көрсетпесе, алдымен оны түрлендіріңіз немесе нақтырақ құралды ашыңыз. + Бөліс ағындары немесе құралды тікелей ашу күтілгеннен басқаша әрекет етсе, кескін таңдау құралы мен іске қосу құралының параметрлерін қарап шығыңыз. + Растаудан шығу + Қимылдар, кері басулар немесе құралдарды ауыстырып қосулар кездейсоқ орын алған кезде сақталмаған өңдеулерді жоғалтпаңыз + Аяқталмаған жұмысты қорғаңыз + Құралдан шығуды растау қимылмен шарлауды пайдаланғанда, үлкен файлдарды өңдегенде немесе қолданбалар арасында жиі ауысқанда пайдалы. Аяқталмаған параметрлер мен алдын ала қарау кездейсоқ жоғалып кетпеуі үшін ол құралдан шықпас бұрын кішкене үзіліс қосады. + Экспорттау алдында кездейсоқ кері қимылдар құралды жауып тастаған болса, растауды қосыңыз. + Көбінесе жылдам бір қадамдық түрлендірулерді орындасаңыз және жылдам шарлауды қаласаңыз, оны өшіріп қойыңыз. + Параметрлерді қалпына келтіру тітіркендіретін ұзағырақ жұмыс процестері үшін оны алдын ала орнатулармен бірге пайдаланыңыз. + Мәселелерді хабарлау кезінде журналдарды пайдаланыңыз + Мәселені ашу немесе әзірлеушіге хабарласпас бұрын пайдалы мәліметтерді жинаңыз + Мәселелерді мәтінмәнмен хабарлаңыз + Қадамдары, қолданба нұсқасы, енгізу түрі және журналдары бар нақты есепті диагностикалау әлдеқайда оңай. Журналдар мәселені қайта шығарғаннан кейін ең пайдалы болады, себебі олар қоршаған контекстті жаңа күйде сақтайды. + Құрал атауын, нақты әрекетті және оның бір файлда немесе әрбір файлда орындалатынын ескеріңіз. + Мәселені қайта шығарғаннан кейін қолданба журналдарын ашыңыз және мүмкіндігінше сәйкес журнал шығысын бөлісіңіз. + Скриншоттарды немесе үлгі файлдарды оларда жеке ақпарат болмаған кезде ғана тіркеңіз. + Фондық өңдеу тоқтатылды + Android фондық өңдеу хабарландыруын уақытында бастауға мүмкіндік бермеді. Бұл сенімді түрде түзетілмейтін жүйе уақытының шектеуі. Қолданбаны қайта іске қосып, әрекетті қайталаңыз; қолданбаны ашық ұстау және хабарландыруларға немесе фондық жұмысқа рұқсат беру көмектесуі мүмкін. + Файлды таңдауды өткізіп жіберу + Кіріс әдетте бөлісуден, алмасу буферінен немесе алдыңғы экраннан келсе, құралдарды жылдамырақ ашыңыз + Қайталанатын құралды жылдамырақ іске қосыңыз + Файлды таңдауды өткізіп жіберу қолдау көрсетілетін құралдардың бірінші қадамын өзгертеді. Бұл әдетте таңдалған кескіні бар немесе Android бөлісу әрекеттерінен құралды енгізгенде пайдалы, бірақ әрбір құрал бірден файлды сұрайды деп күтсеңіз, ол түсініксіз болуы мүмкін. + Оны әдеттегі ағын құрал ашылмай тұрып бастапқы кескінді қамтамасыз еткенде ғана қосыңыз. + Қолданбаны үйрену кезінде оны өшіріп қойыңыз, себебі нақты таңдау әрбір құрал ағынын түсінуді жеңілдетеді. + Құрал анық кіріссіз ашылса, бұл параметрді өшіріңіз немесе Android файлды алдымен жіберетіндей Бөлісу/Ашу арқылы бастаңыз. + Алдымен редакторды ашыңыз + Ортақ кескін тікелей өңдеуге өткен кезде алдын ала қарауды өткізіп жіберіңіз + Бірінші аялдама ретінде алдын ала қарауды немесе өңдеуді таңдаңыз + Алдын ала қараудың орнына өңдеуді ашу кескінді өзгерткісі келетінін білетін пайдаланушыларға арналған. Чаттағы немесе бұлттық қоймадағы файлдарды тексергенде, алдын ала қарау қауіпсізрек болады; тікелей өңдеу скриншоттар, жылдам кесу, аннотациялар және бір реттік түзетулер үшін жылдамырақ. + Егер ортақ суреттердің көпшілігі бірден қиюды, белгілеуді, сүзгілерді немесе экспорттық өзгертулерді қажет етсе, тікелей өңдеуді қосыңыз. + Шешім қабылдамас бұрын метадеректерді, өлшемдерді, мөлдірлікті немесе кескін сапасын жиі тексергенде, алдымен алдын ала қарауды қалдырыңыз. + Мұны таңдаулылармен бірге пайдаланыңыз, осылайша ортақ суреттер сіз қолданатын құралдарға жақын орналасады. + Алдын ала орнатылған мәтін енгізуі + Басқару элементтерін қайта-қайта реттеудің орнына дәл алдын ала орнатылған мәндерді енгізіңіз + Жүгірткілер баяу болғанда нақты мәндерді пайдаланыңыз + Алдын ала орнатылған мәтін енгізуі сізге қайталанатын жұмыс үшін нақты сандар қажет болғанда көмектеседі: әлеуметтік кескін өлшемдері, құжат жиектері, қысу мақсаттары, жол ені немесе қимылдар арқылы қол жеткізуді тітіркендіретін басқа мәндер. Сондай-ақ, ол жүгірткі қозғалысы қиынырақ болатын шағын экрандарда пайдалы. + Нақты өлшемдерді, пайыздарды, сапа мәндерін немесе құрал параметрлерін жиі қайта пайдалансаңыз, мәтін енгізуін қосыңыз. + Бір рет тергеннен кейін мәнді алдын ала орнату ретінде сақтаңыз, содан кейін бірдей орнатуды қайта құрудың орнына оны қайта пайдаланыңыз. + Дөрекі визуалды реттеу үшін қимылды басқару элементтерін және қатаң шығыс талаптары үшін терілген мәндерді сақтаңыз. + Түпнұсқаның жанында сақтаңыз + Қалта мәтінмәні маңызды болған кезде жасалған файлдарды бастапқы кескіндердің жанына қойыңыз + Шығыстарды олардың көздерімен бірге сақтаңыз + Түпнұсқа қалтаға сақтау камера қалталарына, жоба қалталарына, құжаттарды сканерлеуге және өңделген көшірме дереккөздің жанында қалуы керек клиенттік пакеттерге ыңғайлы. Ол арнайы шығыс қалтасына қарағанда таза емес болуы мүмкін, сондықтан оны анық файл аты жұрнақтарымен немесе үлгілерімен біріктіріңіз. + Әрбір бастапқы қалта әлдеқашан мағыналы болған кезде оны қосыңыз және нәтижелер сол жерде топтастырылған күйінде қалуы керек. + Өңделген көшірмелерді түпнұсқадан оңай бөлу үшін файл аты жұрнақтарын, префикстерді, уақыт белгілерін немесе үлгілерді пайдаланыңыз. + Барлық жасалған файлдардың бір экспорттық қалтада жиналуын қаласаңыз, оны аралас топтамалар үшін өшіріңіз. + Үлкенірек шығуды өткізіп жіберіңіз + Көзден күтпеген жерден ауыр болатын түрлендірулерді сақтаудан аулақ болыңыз + Топтамаларды нашар мөлшердегі тосын сыйлардан қорғаңыз + Кейбір түрлендірулер файлдарды үлкейтеді, әсіресе PNG скриншоттары, сығылған фотосуреттер, жоғары сапалы WebP немесе метадеректер сақталған кескіндер. Үлкенірек болса, өткізіп жіберуге рұқсат беру өлшемін азайту негізгі мақсат болып табылатын жұмыс үрдістеріндегі кішірек көзді ауыстыруға жол бермейді. + Экспорттау файлдарды сығуға, өлшемін өзгертуге немесе жүктеп салу шектеулеріне дайындауға арналған болса, оны қосыңыз. + Пакеттен кейін өткізіп жіберген файлдарды қарап шығыңыз; олар әлдеқашан оңтайландырылған немесе басқа пішім қажет болуы мүмкін. + Сапа, мөлдірлік немесе пішім үйлесімділігі соңғы файл өлшемінен маңыздырақ болса, оны өшіріңіз. + Алмасу буфері және сілтемелер + Жылдамырақ мәтінге негізделген құралдар үшін алмасу буферіне автоматты енгізуді және сілтемені алдын ала қарауды басқарыңыз + Автоматты енгізуді болжамды ету + Алмасу буфері мен сілтеме параметрлері QR, Base64, URL және мәтінді қажет ететін жұмыс процестері үшін ыңғайлы, бірақ автоматты енгізу әдейі болуы керек. Қолданба өрістерді өздігінен толтыратын сияқты болса, алмасу буферін қою әрекетін тексеріңіз; сілтеме карталары шулы немесе баяу болса, сілтемені алдын ала қарау әрекетін реттеңіз. + Мәтінді жиі көшіріп, сәйкес құралды бірден ашқанда, алмасу буферіне автоматты қоюға рұқсат беріңіз. + Жеке алмасу буферінің мазмұны сіз күтпеген құралдарда пайда болса, автоматты қоюды өшіріңіз. + Алдын ала қарауды алу баяу, алаңдататын немесе жұмыс процесі үшін қажет болмаған кезде сілтемені алдын ала қарауды өшіріңіз. + Шағын басқару элементтері + Экран кеңістігі тар болған кезде тығызырақ селекторларды және жылдам параметрлерді орналастыруды пайдаланыңыз + Шағын экрандарға көбірек басқару элементтерін орналастырыңыз + Шағын телефондарға, пейзажды өңдеуге және көптеген параметрлері бар құралдарға ықшам селекторлар мен жылдам параметрлерді орналастыру көмектеседі. Мәселе мынада, басқару элементтері тығызырақ болуы мүмкін, сондықтан бұл жалпы опцияларды танығаннан кейін ең жақсысы. + Диалогтар немесе опциялар тізімдері экран үшін тым биік болып көрінгенде, шағын селекторларды қосыңыз. + Құрал басқару элементтері дисплейдің бір жағынан қол жеткізу оңай болса, жылдам параметрлер жағын реттеңіз. + Қолданбаны әлі де үйреніп жатсаңыз немесе тығыз опцияларды жиі түртіп алмасаңыз, кеңірек басқару элементтеріне оралыңыз. + Интернеттен кескіндерді жүктеңіз + Бетті немесе суреттің URL мекенжайын қойыңыз, талданған кескіндерді алдын ала қараңыз, содан кейін таңдалған нәтижелерді сақтаңыз немесе бөлісіңіз + Веб кескіндерін әдейі пайдаланыңыз + Веб кескінді жүктеу берілген URL мекенжайынан кескін көздерін талдайды, бірінші қол жетімді кескінді алдын ала қарайды және таңдалған талданған кескіндерді сақтауға немесе ортақ пайдалануға мүмкіндік береді. Кейбір сайттар уақытша, қорғалған немесе сценарий арқылы жасалған сілтемелерді пайдаланады, сондықтан браузерде жұмыс істейтін бет әлі де қолдануға болатын кескіндерді қайтармауы мүмкін. + Тікелей кескін URL мекенжайын немесе бет URL мекенжайын қойып, талданған кескіндер тізімінің пайда болуын күтіңіз. + Бетте бірнеше кескіндер болса және сізге олардың кейбіреулері ғана қажет болғанда жақтауды немесе таңдауды басқару элементтерін пайдаланыңыз. + Егер ештеңе жүктелмесе, суреттің тікелей сілтемесін қолданып көріңіз немесе алдымен браузер арқылы жүктеп алыңыз; сайт талдауды бұғаттауы немесе жеке сілтемелерді пайдалануы мүмкін. + Өлшем түрін таңдаңыз + Кескінді жаңа өлшемдерге мәжбүрлемес бұрын, \"Айқын\", \"Икемді\", \"Қию\" және \"Сәйкестендіру\" параметрлерін түсініңіз + Пішінді, кенепті және арақатынасты басқару + Өлшемді өзгерту түрі сұралған ені мен биіктігі бастапқы арақатынасына сәйкес келмегенде не болатынын шешеді. Бұл пикселдерді созу, пропорцияларды сақтау, қосымша аумақты қию немесе фондық кенеп қосу арасындағы айырмашылық. + Айқынды бұрмалау қолайлы болғанда ғана пайдаланыңыз немесе көзде мақсатты пішіммен бірдей арақатынасы бар. + Маңызды жағынан өлшемін өзгерту арқылы пропорцияларды сақтау үшін, әсіресе фотосуреттер мен аралас топтамалар үшін Икемді пайдаланыңыз. + Жиектері жоқ қатаң жақтаулар үшін «Кесу» пәрменін немесе бүкіл кескін бекітілген кенепте көрінетін күйде қалуы қажет болғанда Fit опциясын пайдаланыңыз. + Кескінді масштабтау режимін таңдаңыз + Айқындықты, тегістікті, жылдамдықты және жиек артефактілерін басқаратын қайта үлгілеу сүзгісін таңдаңыз + Кездейсоқ жұмсақтықсыз өлшемін өзгерту + Кескін масштабы режимі өлшемін өзгерту кезінде жаңа пикселдер қалай есептелетінін басқарады. Қарапайым режимдер жылдам және болжамды, ал жетілдірілген сүзгілер егжей-тегжейлерді жақсы сақтай алады, бірақ жиектерді айқындауы, ореол жасауы немесе үлкен кескіндерде ұзағырақ уақыт алуы мүмкін. + Нәтиже тек кішірек және таза болуы қажет болғанда, күнделікті жылдам өлшемді өзгерту үшін Basic немесе Billinear пайдаланыңыз. + Ұсақ бөлшектер, мәтін немесе жоғары сапалы масштабты азайту маңызды болғанда Lanczos, Robidoux, Spline немесе EWA стиліндегі режимдерді пайдаланыңыз. + Бұлыңғыр пикселдер көріністі бұзатын пиксель өнері, белгішелер және қатты жиектері бар графика үшін Nearest пайдаланыңыз. + Түс кеңістігін масштабтау + Өлшемді өзгерту кезінде пикселдер қайта сыналған кезде түстер қалай араласатынын шешіңіз + Градиенттер мен жиектерді табиғи сақтаңыз + Масштабтың түс кеңістігі өлшемді өзгерту кезінде пайдаланылатын математиканы өзгертеді. Кескіндердің көпшілігі әдепкі бойынша жақсы, бірақ сызықтық немесе перцептивті кеңістіктер күшті масштабтаудан кейін градиенттерді, күңгірт жиектерді және қанық түстерді тазарақ көрсете алады. + Жылдамдық пен үйлесімділік кішкентай түс айырмашылықтарынан маңыздырақ болса, әдепкі параметрді қалдырыңыз. + Өлшемді өзгерткеннен кейін күңгірт контурлар, UI скриншоттары, градиенттер немесе түсті жолақтар дұрыс емес болып көрінсе, сызықтық немесе қабылдау режимдерін қолданып көріңіз. + Нақты мақсатты экранда бұрын және кейін салыстырыңыз, себебі түс кеңістігіндегі өзгерістер нәзік және мазмұнға байланысты болуы мүмкін. + Өлшемді өзгерту режимдерін шектеу + Шектеуден асып кеткен кескіндерді қай кезде өткізіп жіберу, қайта кодтау немесе сәйкестендіру үшін кішірейту керектігін біліңіз + Шекте не болатынын таңдаңыз + Limit Reize - бұл кішірек кескін құралы ғана емес. Оның режимі дереккөз сіздің шектеулеріңізде болғанда немесе тек бір жағы аралас қалталар мен жүктеп салуды дайындау үшін маңызды ережені бұзған кезде қолданба не істейтінін анықтайды. + Шектердің ішіндегі кескіндер қозғалмай, қайта экспортталмаған кезде Өткізіп жіберуді пайдаланыңыз. + Өлшемдер қолайлы болған кезде Қайта кодтауды пайдаланыңыз, бірақ сізге әлі де жаңа пішім, метадеректер әрекеті, сапа немесе файл атауы қажет. + Шектеулерді бұзатын кескіндер рұқсат етілген жолаққа сәйкес келгенше пропорционалды түрде кішірейту қажет болғанда Масштабтауды пайдаланыңыз. + Формат қатынасының мақсаттары + Қатаң шығыс өлшемдерінде созылған беттерді, кесілген жиектерді және күтпеген жиектерді болдырмаңыз + Өлшемін өзгерту алдында пішінді сәйкестендіріңіз + Ені мен биіктігі өлшемді өзгерту мақсатының жартысы ғана. Пікірлердің арақатынасы кескіннің табиғи түрде сәйкес келетінін, қиюды немесе оның айналасында кенепті қажет ететінін анықтайды. Алдымен пішінді тексеру таңқаларлық өлшемді өзгерту нәтижелерін болдырмайды. + «Ашық», «Қию» немесе «Сәйкестендіру» опциясын таңдамас бұрын бастапқы пішінді мақсатты пішінмен салыстырыңыз. + Нысан қосымша фонды жоғалтуы мүмкін болғанда және тағайындалған жерге қатаң кадр қажет болғанда, алдымен қиып алыңыз. + Түпнұсқа кескіннің әрбір бөлігі көрініп тұруы керек кезде Fit немесе Flexible пайдаланыңыз. + Жоғары деңгейлі немесе қалыпты өлшемді өзгерту + Пикселдерді қосу үшін AI қажет болғанда және қарапайым масштабтау жеткілікті болғанда біліңіз + Өлшемді бөлшектермен шатастырмаңыз + Қалыпты өлшем пикселдерді интерполяциялау арқылы өлшемдерді өзгертеді; ол нақты бөлшектерді ойлап таба алмайды. AI жоғары деңгейлері анық көрінетін бөлшектерді жасай алады, бірақ ол баяу және текстураны галлюцинациялауы мүмкін, сондықтан дұрыс таңдау үйлесімділікті, жылдамдықты немесе көрнекі қалпына келтіруді қажет ететініне байланысты. + Нобайлар, жүктеп салу шектеулері, скриншоттар және қарапайым өлшем өзгертулері үшін қалыпты өлшемді пайдаланыңыз. + Кішкентай немесе жұмсақ кескін үлкенірек өлшемде жақсырақ көрінуі қажет болғанда AI жоғары деңгейін пайдаланыңыз. + Жасалған мәліметтер сенімді, бірақ дұрыс емес көрінуі мүмкін болғандықтан, AI жоғары деңгейге жеткеннен кейін беттерді, мәтінді және үлгілерді салыстырыңыз. + Сүзгі тәртібі маңызды + Тазалауды, түсті, айқындықты және соңғы қысуды әдейі ретпен қолданыңыз + Тазалау сүзгі дестесін құрастырыңыз + Сүзгілер әрқашан бір-бірін алмастыра бермейді. Айқындау алдында бұлыңғырлық, OCR алдында контрастты күшейту немесе қысу алдында түсті өзгерту бірдей басқару элементтерінен мүлдем басқа нәтиже шығаруы мүмкін. + Алдымен қию және айналдыру үшін кейінірек сүзгілер тек қалатын пикселдерде жұмыс істейді. + Айқындау немесе стильдендірілген әсерлер алдында экспозицияны, ақ балансты және контрастты қолданыңыз. + Алдын ала қаралған мәліметтер экспортқа болжамды түрде аман қалуы үшін соңғы өлшемді өзгерту мен қысуды соңына жақын ұстаңыз. + Фондық жиектерді түзетіңіз + Автоматты түрде алып тастағаннан кейін ореолдарды, қалған көлеңкелерді, шашты, тесіктерді және жұмсақ контурларды тазалаңыз + Кесулерді әдейі етіп жасаңыз + Автоматты маскалар бір қарағанда жақсы көрінеді, бірақ жарқын, күңгірт немесе өрнекті фондардағы мәселелерді көрсетеді. Жиекті тазалау - жылдам кесу мен қайта пайдалануға болатын жапсырма, логотип немесе өнім кескіні арасындағы айырмашылық. + Ореолдар мен жетіспейтін шет бөлшектерін ашу үшін нәтижені қарама-қарсы фонда қарап көріңіз. + Айналадағы қалдықтарды өшірмес бұрын шашты, бұрыштарды және мөлдір заттарды қалпына келтіруді пайдаланыңыз. + Қиындық дизайнға орналастырылған кезде соңғы өң түсі бойынша шағын сынақты экспорттаңыз. + Оқуға болатын су белгілері + Фотосуретті бұзбай, жарық, қараңғы, бос емес және кесілген кескіндерде белгілерді көрінетін етіп жасаңыз + Кескінді жасырмай қорғаңыз + Бір суретте жақсы көрінетін су таңбасы екіншісінде жоғалып кетуі мүмкін. Өлшемді, мөлдірлікті, контрастты, орналастыруды және қайталауды бірінші алдын ала қарау үшін ғана емес, бүкіл топтама үшін таңдау керек. + Барлық файлдарды экспорттаудан бұрын пакеттегі ең жарқын және ең күңгірт кескіндердегі белгіні тексеріңіз. + Үлкен қайталанатын белгілер үшін төмен мөлдірлікті және кішкентай бұрыш белгілері үшін күшті контрастты пайдаланыңыз. + Белгі қайта пайдалануға жол бермеу үшін болмаса, маңызды беттерді, мәтінді және өнім мәліметтерін таза ұстаңыз. + Бір кескінді тақтайшаларға бөліңіз + Бір кескінді жолдар, бағандар, теңшелетін пайыздар, пішім және сапа бойынша кесіңіз + Торларды және тапсырыс берілген бөліктерді дайындаңыз + Кескінді бөлу бір бастапқы кескіннен бірнеше шығыстарды жасайды. Ол жолдар мен бағандар саны бойынша бөлуге, жол немесе баған пайыздарын реттеуге және торларға, бет кесінділеріне, панорамаларға және реттелген әлеуметтік жазбаларға пайдалы болып табылатын таңдалған шығыс пішімі мен сапасымен бөліктерді сақтай алады. + Бастапқы кескінді таңдаңыз, содан кейін қажетті жолдар мен бағандардың санын орнатыңыз. + Бөлшектердің барлығы бірдей өлшемде болмауы керек кезде жол немесе баған пайыздарын реттеңіз. + Әрбір жасалған бөлік бірдей экспорттау ережелерін қолдануы үшін сақтау алдында шығыс пішімін және сапасын таңдаңыз. + Кескін жолақтарын кесіңіз + Тік немесе көлденең аймақтарды алып тастап, қалған бөліктерді қайтадан біріктіріңіз + Аймақты бос орын қалдырмай алып тастаңыз + Кескінді кесу қалыпты қиюдан ерекшеленеді. Ол таңдалған тік немесе көлденең жолақты алып тастай алады, содан кейін қалған кескін бөліктерін біріктіреді. Кері режим оның орнына таңдалған аймақты сақтайды және екі кері бағытты пайдалану таңдалған тіктөртбұрышты сақтайды. + Бүйірлік аймақтар, бағандар немесе ортаңғы жолақтар үшін тік кесуді пайдаланыңыз; баннерлер, бос орындар немесе жолдар үшін көлденең кесуді пайдаланыңыз. + Таңдалған бөлікті алып тастаудың орнына сақтағыңыз келсе, осьте кері әрекетті қосыңыз. + Кесуден кейін жиектерді алдын ала қараңыз, себебі жойылған аумақ маңызды мәліметтерді кесіп өткенде біріктірілген мазмұн кенеттен көрінуі мүмкін. + Шығару пішімін таңдаңыз + Мазмұн мен тағайындалған жерге негізделген JPEG, PNG, WebP, AVIF, JXL немесе PDF таңдаңыз + Пішімді тағайындалған жер шешсін + Ең жақсы пішім кескінде не бар екеніне және қай жерде қолданылатынына байланысты. Фотосуреттер, скриншоттар, мөлдір активтер, құжаттар және анимациялық файлдар дұрыс емес пішімге мәжбүрленген кезде әртүрлі жолдармен істен шығады. + Мөлдірлік пен нақты пикселдер қажет болмаған кезде кең фотосурет үйлесімділігі үшін JPEG пайдаланыңыз. + Анық скриншоттар, UI түсірулері, мөлдір графика және жоғалтпай өңдеулер үшін PNG пайдаланыңыз. + Шағын заманауи шығыс маңызды болғанда және қабылдаушы қолданба оны қолдайтын кезде WebP, AVIF немесе JXL пайдаланыңыз. + Аудио қақпақтарды шығарып алыңыз + Енгізілген альбом суретін немесе аудио файлдардағы қолжетімді мультимедиа жақтауларын PNG кескіндері ретінде сақтаңыз + Аудио файлдардан өнер туындысын шығарыңыз + Audio Covers аудио файлдарынан ендірілген өнер туындыларын оқу үшін Android медиа метадеректерін пайдаланады. Енгізілген өнер туындысы қолжетімсіз болған кезде, Android жүйесі қамтамасыз ете алатын болса, ретривер медиа жақтауға қайта түсуі мүмкін; егер олардың ешқайсысы да болмаса, құрал шығарып алатын сурет жоқ деп хабарлайды. + Аудио мұқабаларды ашыңыз және күтілетін мұқаба суреті бар бір немесе бірнеше аудио файлдарды таңдаңыз. + Сақтау немесе ортақ пайдалану алдында, әсіресе ағынмен жіберу немесе жазу қолданбаларынан алынған файлдар үшін алынған мұқабаны қарап шығыңыз. + Ешқандай сурет табылмаса, аудио файлдың ендірілген мұқабасы болмауы мүмкін немесе Android пайдалануға жарамды жақтауды көрсете алмайды. + Күндер мен орын метадеректері + Түсіру уақыты маңызды болған кезде нені сақтау керектігін шешіңіз, бірақ GPS немесе құрылғы деректері ағып кетпеуі керек + Жеке метадеректерден пайдалы метадеректерді бөліңіз + Метадеректер бірдей қауіпті емес. Түсіру күндері мұрағат пен сұрыптау үшін пайдалы болуы мүмкін, ал GPS, құрылғының сериялық өрістері, бағдарламалық құрал тегтері немесе иесі өрістері жоспарланғаннан көп нәрсені көрсете алады. + Альбомдар, сканерлеулер немесе жоба тарихы үшін хронологиялық тәртіп маңызды болған кезде күн мен уақыт тегтерін сақтаңыз. + Бейтаныс адамдарға кескіндерді ортақ бөліспес немесе жібермес бұрын GPS және құрылғыны анықтайтын тегтерді алып тастаңыз. + Құпиялық талаптары қатаң болған кезде үлгіні экспорттаңыз және EXIF ​​файлын қайта тексеріңіз. + Файл атауларының соқтығысуын болдырмаңыз + Көптеген файлдар image.jpg немесе screenshot.png сияқты атауларды ортақ пайдаланса, пакеттік шығыстарды қорғаңыз + Әрбір шығыс атауын бірегей етіңіз + Қайталанатын файл атаулары кескіндер мессенджерлерден, скриншоттардан, бұлт қалталарынан немесе бірнеше камералардан келген кезде жиі кездеседі. Жақсы үлгі кездейсоқ ауыстыруды болдырмайды және шығыстарды көздеріне дейін қадағалап отырады. + Түпнұсқа атын және өңделген көшірмені тануға болатын жұрнақты қосыңыз. + Үлкен аралас партияларды өңдеу кезінде ретті, уақыт белгісін, алдын ала орнатуды немесе өлшемдерді қосыңыз. + Атау үлгісінің соқтығыспайтынына көз жеткізбейінше, жұмыс процестерін қайта жазудан аулақ болыңыз. + Сақтау немесе бөлісу + Тұрақты файл мен басқа қолданбаға уақытша көшіру арасында таңдаңыз + Дұрыс ниетпен экспорттау + «Сақтау және бөлісу» функциялары ұқсас болуы мүмкін, бірақ олар әртүрлі мәселелерді шешеді. Сақтау кейінірек табуға болатын файлды жасайды; ортақ пайдалану Android арқылы жасалған нәтижені басқа қолданбаға жібереді және тұрақты жергілікті көшірмені қалдырмауы мүмкін. + Шығару белгілі қалтада қалуы, сақтық көшірмесі жасалуы немесе кейінірек қайта пайдаланылуы қажет болғанда Сақтау пәрменін пайдаланыңыз. + Жылдам хабарлар, электрондық пошта тіркемелері, әлеуметтік жариялау немесе нәтижені басқа өңдегішке жіберу үшін Бөлісу мүмкіндігін пайдаланыңыз. + Қабылдаушы қолданба сенімсіз болғанда немесе сәтсіз бөлісуді қайта жасау тітіркендіретін кезде алдымен сақтаңыз. + PDF бетінің өлшемі және шеттері + Құжатты жасамас бұрын қағаз өлшемін, сыйымдылықты және тыныс алу бөлмесін таңдаңыз + Кескін беттерін басып шығаруға және оқуға болатын етіп жасаңыз + Пішіні таңдалған қағаз өлшеміне сәйкес келмесе, кескіндер ыңғайсыз PDF беттеріне айналуы мүмкін. Жиектер, сәйкестендіру режимі және бет бағдары мазмұнның кесілгенін, кішкентай, созылғанын немесе оқуға ыңғайлы екенін анықтайды. + PDF басып шығаруға немесе пішінге жіберуге болатын кезде нақты қағаз өлшемін пайдаланыңыз. + Сканерлеу және скриншоттар үшін шеттерді пайдаланыңыз, осылайша мәтін беттің жиегіне қарсы тұрмайды. + Бастапқы кескіндердің бағдары аралас болғанда, портреттік және альбомдық беттерді бірге алдын ала қараңыз. + Сканерлерді тазалау + PDF немесе OCR алдында қағаз жиектерін, көлеңкелерін, контрастын, айналуын және оқылу мүмкіндігін жақсартыңыз + Мұрағаттау алдында беттерді дайындаңыз + Сканерлеуді техникалық түсіруге болады, бірақ оқу қиын. PDF экспорттау алдындағы кішігірім тазалау қадамдары көбінесе қысу параметрлерінен маңыздырақ, әсіресе түбіртектер, қолжазба жазбалар және жарық аз беттер үшін. + Перспективаны түзетіп, үстелдің жиектерін, саусақтарды, көлеңкелерді және өңдік бөгеттерді кесіңіз. + Мөрлерді, қолтаңбаларды немесе қарындаш белгілерін жоймай мәтін анық болуы үшін контрастты мұқият арттырыңыз. + Бет тік және оқылатын болғаннан кейін ғана OCR іске қосыңыз, содан кейін маңызды сандарды қолмен тексеріңіз. + PDF файлдарын сығу, сұр реңкті өзгерту немесе жөндеу + Жеңілірек, басып шығаруға болатын немесе қайта жасалған құжат көшірмелері үшін бір файлдық PDF операцияларын пайдаланыңыз + PDF тазалау әрекетін таңдаңыз + PDF құралдары қысу, сұр реңкті түрлендіру және жөндеуге арналған бөлек операцияларды қамтиды. Қысу және сұр реңк негізінен ендірілген кескіндер арқылы жұмыс істейді, ал жөндеу PDF құрылымын қайта құрады; бұлардың ешқайсысы әрбір құжат үшін кепілдік берілген түзету ретінде қарастырылмауы керек. + Құжатта ортақ пайдалану үшін маңызды суреттер мен файл өлшемі болған кезде Compress PDF қолданбасын пайдаланыңыз. + Құжатты басып шығару оңайырақ немесе түрлі-түсті кескіндерді қажет етпейтін кезде сұр реңкті пайдаланыңыз. + Құжат ашылмаған немесе ішкі құрылымы зақымдалған кезде көшірмеде PDF жөндеуді пайдаланыңыз, содан кейін қайта жасалған файлды тексеріңіз. + PDF файлдарынан суреттерді шығарып алыңыз + Енгізілген PDF кескіндерін қалпына келтіріп, алынған нәтижелерді мұрағатқа жинаңыз + Құжаттардан бастапқы кескіндерді сақтаңыз + Extract Images PDF файлын ендірілген кескін нысандарына сканерлейді, мүмкіндігінше көшірмелерді өткізіп жібереді және қалпына келтірілген кескіндерді жасалған ZIP мұрағатында сақтайды. Ол мәтінді, векторлық сызбаларды немесе скриншот ретінде әрбір көрінетін бетті емес, PDF файлына шын мәнінде енгізілген кескіндерді ғана шығарады. + Каталогтағы фотосуреттер немесе сканерленген активтер сияқты PDF ішінде сақталған суреттер қажет болғанда Extract Images қолданбасын пайдаланыңыз. + Толық беттер, соның ішінде мәтін мен орналасу қажет болғанда, оның орнына PDF-суреттерді немесе бетті шығаруды пайдаланыңыз. + Құрал ендірілген кескіндердің жоқтығы туралы есеп берсе, бет мәтіндік/векторлық мазмұн болуы мүмкін немесе кескіндер шығарылатын нысандар ретінде сақталмауы мүмкін. + Метадеректер, тегістеу және басып шығару шығысы + Құжат сипаттарын өңдеңіз, беттерді растризациялаңыз немесе реттелетін басып шығару макеттерін әдейі дайындаңыз + Соңғы құжат әрекетін таңдаңыз + PDF метадеректері, тегістеу және басып шығаруды дайындау соңғы кезеңдегі әртүрлі мәселелерді шешеді. Метадеректер құжат сипаттарын басқарады, PDF тегістеу беттерді аз өңделетін шығарылымға растрлайды және PDF басып шығару бет өлшемі, бағдары, жиегі және парақтағы беттерді басқару элементтері бар жаңа орналасуды дайындайды. + Автор, тақырып, кілт сөздер, өндіруші немесе құпиялылықты тазалау маңызды болғанда метадеректерді пайдаланыңыз. + Көрінетін бет мазмұнын бөлек PDF нысандары ретінде өңдеу қиынға соғатын кезде PDF Flatten пайдаланыңыз. + Теңшелетін бет өлшемі, бағдары, шеттері немесе бір параққа бірнеше бет қажет болғанда PDF басып шығаруды пайдаланыңыз. + OCR үшін кескіндерді дайындаңыз + Мәтінді оқу үшін OCR сұрамас бұрын, қиюды, айналдыруды, контрастты, сызуды және масштабты пайдаланыңыз + OCR тазартқыш пикселдерін беріңіз + OCR қателері көбінесе OCR қозғалтқышынан гөрі кіріс кескінінен туындайды. Қисық беттер, төмен контраст, бұлыңғырлық, көлеңкелер, кішкентай мәтін және аралас фон барлығы тану сапасын төмендетеді. + Мәтін аймағына қиып, тану алдында сызықтар көлденең болатындай кескінді бұрыңыз. + Әріптерді оқуды жеңілдеткен кезде ғана контрастты арттырыңыз немесе таза ақ-қараға түрлендіріңіз. + Кішкентай мәтіннің өлшемін қалыпты түрде жоғары қарай өзгертіңіз, бірақ жалған жиектерді жасайтын агрессивті қайраудан аулақ болыңыз. + QR мазмұнын тексеріңіз + Кодқа сенбес бұрын сілтемелерді, Wi-Fi тіркелгі деректерін, контактілерді және әрекеттерді қарап шығыңыз + Дешифрленген мәтінді сенімсіз енгізу ретінде қарастырыңыз + QR коды URL мекенжайын, контакт картасын, Wi-Fi құпия сөзін, күнтізбе оқиғасын, телефон нөмірін немесе қарапайым мәтінді жасыра алады. Кодтың жанындағы көрінетін жапсырма нақты пайдалы жүктемеге сәйкес келмеуі мүмкін, сондықтан оған әрекет ету алдында декодталған мазмұнды қарап шығыңыз. + Толық декодталған URL мекенжайын ашпас бұрын, әсіресе қысқартылған немесе бейтаныс домендерді тексеріңіз. + Wi-Fi, контакт, күнтізбе, телефон және SMS кодтарымен абай болыңыз, себебі олар маңызды әрекеттерді тудыруы мүмкін. + Мазмұнды бірден ашудың орнына басқа жерде растау қажет болғанда алдымен оны көшіріп алыңыз. + QR кодтарын жасаңыз + Қарапайым мәтін, URL мекенжайлары, Wi-Fi, контактілер, электрондық пошта, телефон, SMS және орын деректерінен кодтар жасаңыз + Дұрыс QR пайдалы жүктемесін жасаңыз + QR экраны енгізілген мазмұннан кодтарды жасай алады, сондай-ақ барларын сканерлей алады. Құрылымдық QR түрлері Wi-Fi кіру мәліметтері, контакт карталары, электрондық пошта өрістері, телефон нөмірлері, SMS мазмұны, URL мекенжайлары немесе географиялық координаттар сияқты басқа қолданбалар түсінетін пішімдегі деректерді кодтауға көмектеседі. + Қарапайым жазбалар үшін кәдімгі мәтінді таңдаңыз немесе код Wi-Fi, контакт, URL, электрондық пошта, телефон, SMS немесе орын деректері ретінде ашылатын кезде құрылымдық түрін пайдаланыңыз. + Жасалған кодты бөліспес бұрын әрбір өрісті тексеріңіз, себебі көрінетін алдын ала қарау тек сіз енгізген пайдалы жүктемені көрсетеді. + Сақталған QR кодын сканермен басып шығарғанда, жалпыға жариялағанда немесе басқа адамдар пайдаланғанда тексеріңіз. + Бақылау сомасы режимін таңдаңыз + Файлдардан немесе мәтіннен хэштерді есептеңіз, бір файлды салыстырыңыз немесе көптеген файлдарды пакеттік тексеріңіз + Сыртқы түрін емес, мазмұнын тексеріңіз + Checksum Tools файлды хэштеу, мәтінді хэштеу, файлды белгілі хэшпен салыстыру және пакетті салыстыру үшін бөлек қойындыларға ие. Бақылау сомасы таңдалған алгоритм үшін байт үшін байт сәйкестігін дәлелдейді; бұл екі суреттің жай ғана ұқсас екенін дәлелдемейді. + Файлдар үшін Есептеуді және терілген немесе қойылған мәтіндік деректер үшін мәтін хэшін пайдаланыңыз. + Біреу сізге бір файл үшін күтілетін бақылау сомасын бергенде Салыстыру мүмкіндігін пайдаланыңыз. + Көптеген файлдарға хэштер немесе тексеру қажет болғанда пакеттік салыстыруды пайдаланыңыз, содан кейін таңдалған алгоритмді дәйекті сақтаңыз. + Алмасу буферінің құпиялылығы + Жеке көшірілген деректерді кездейсоқ ашпастан автоматты алмасу буферінің мүмкіндіктерін пайдаланыңыз + Көшірілген мазмұнды әдейі сақтаңыз + Алмасу буферін автоматтандыру ыңғайлы, бірақ көшірілген мәтінде құпия сөздер, сілтемелер, мекенжайлар, белгілер немесе жеке жазбалар болуы мүмкін. Алмасу буферіне негізделген енгізуді әдеттеріңізге және құпиялылық күтулеріңізге сәйкес келетін жылдамдық мүмкіндігі ретінде қарастырыңыз. + Құралдарда күтпеген жерден жеке мәтін пайда болса, автоматты алмасу буферін пайдалануды өшіріңіз. + Нәтижені сақтауды болдырмау үшін әдейі қажет болғанда ғана алмасу буферінде сақтауды пайдаланыңыз. + Сезімтал мәтінді, QR деректерін немесе Base64 пайдалы жүктемелерін өңдегеннен кейін алмасу буферін тазалаңыз немесе ауыстырыңыз. + Түс пішімдері + Басқа жерге қою алдында HEX, RGB, HSV, альфа және көшірілген түс мәндерін түсініңіз + Мақсат күткен пішімді көшіріңіз + Бірдей көрінетін түсті бірнеше жолмен жазуға болады. Дизайн құралы, CSS өрісі, Android түс мәні немесе кескін өңдегіші басқа ретті, альфа өңдеуін немесе түс үлгісін күтуі мүмкін. + Шағын түс кодтарын күтетін веб, дизайн немесе тақырып өрістеріне қою кезінде HEX пайдаланыңыз. + Арналарды, жарықтықты, қанықтылықты немесе реңкті қолмен реттеу қажет болғанда RGB немесе HSV пайдаланыңыз. + Кейбір бағыттар оны елемейді немесе басқа ретті пайдаланатындықтан, мөлдірлік маңызды болғанда альфаны бөлек тексеріңіз. + Түстер кітапханасын шолыңыз + Атаулы түстерді аты немесе HEX бойынша іздеңіз және таңдаулыларды жоғарғы жағында ұстаңыз + Қайта пайдалануға болатын атаулы түстерді табыңыз + Түстер кітапханасы атаулы түстер жинағын жүктейді, оны аты бойынша сұрыптайды, түс атауы немесе HEX мәні бойынша сүзеді және маңызды жазбаларға қол жеткізу оңай болуы үшін таңдаулы түстерді сақтайды. Бұл кескіннен үлгі алудың орнына нақты аталған түс қажет болғанда пайдалы. + Қызыл, көк, шифер немесе жалбыз сияқты отбасын білетін кезде түс атауы бойынша іздеңіз. + Сандық түс бұрыннан бар болса және сәйкес немесе жақын маңдағы аталған жазбаларды тапқыңыз келсе, HEX бойынша іздеңіз. + Жиі түстерді таңдаулылар ретінде белгілеңіз, сонда олар шолу кезінде жалпы кітапханадан алға жылжиды. + Тор градиент жинақтарын пайдаланыңыз + Қол жетімді торлы градиент ресурстарын жүктеп алыңыз және таңдалған кескіндерді бөлісіңіз + Қашықтағы тор градиент активтерін пайдаланыңыз + Mesh Gradients қашықтағы ресурстар жинағын жүктей алады, жетіспейтін градиент кескіндерін жүктей алады, жүктеп алу барысын көрсете алады және таңдалған градиенттерді бөлісе алады. Қол жетімділік желіге кіруге және қашықтағы ресурстар қоймасына байланысты, сондықтан бос тізім әдетте ресурстардың әлі қол жетімді емес екенін білдіреді. + Дайын торлы градиент кескіндерін алғыңыз келсе, градиент құралдарынан тор градиенттерін ашыңыз. + Жинақ бос деп есептемес бұрын ресурстың жүктелуін немесе жүктеп алу барысын күтіңіз. + Қажетті градиенттерді таңдап, бөлісіңіз немесе теңшелетін градиентті қолмен жасағыңыз келсе, Gradient Maker қолданбасына оралыңыз. + Жасалған актив рұқсаты + Градиенттер, шу, тұсқағаздар, SVG тәрізді өнер немесе текстураларды жасамас бұрын шығыс өлшемін таңдаңыз. + Қажетті өлшемде жасаңыз + Жасалған кескіндерде қайта түсетін камераның түпнұсқасы жоқ. Егер шығыс тым кішкентай болса, кейінірек масштабтау үлгілерді жұмсартуы, мәліметтерді ауыстыруы немесе активтің тақтайшалары мен қысу жолын өзгертуі мүмкін. + Алдымен тұсқағаз, белгіше, фон, басып шығару немесе қабаттасу сияқты соңғы пайдалану жағдайының өлшемдерін орнатыңыз. + Бірнеше орналасуларда қиюға, масштабтауға немесе қайта пайдалануға болатын текстуралар үшін үлкенірек өлшемдерді пайдаланыңыз. + Сынақ нұсқаларын үлкен нәтижелерге дейін экспорттаңыз, себебі процедуралық мәліметтер қысу әрекетін өзгерте алады. + Ағымдағы тұсқағаздарды экспорттау + Құрылғыдан қолжетімді Home, Lock және кірістірілген тұсқағаздарды шығарып алыңыз + Орнатылған тұсқағаздарды сақтаңыз немесе бөлісіңіз + Түсқағаздар экспорты жүйелік тұсқағаз API интерфейстері арқылы Android шығаратын тұсқағаздарды оқиды. Құрылғыға, Android нұсқасына, рұқсаттарға және құрастыру нұсқасына байланысты Home, Lock немесе кірістірілген тұсқағаздар бөлек қол жетімді немесе жоқ болуы мүмкін. + Жүйе қолданба оқуға мүмкіндік беретін тұсқағаздарды жүктеу үшін Түсқағаздар экспортын ашыңыз. + Сақтағыңыз, көшіргіңіз немесе бөліскіңіз келетін қолжетімді Басты, Құлыптау немесе кірістірілген тұсқағаз жазбаларын таңдаңыз. + Егер тұсқағаз жоқ болса немесе мүмкіндік қолжетімсіз болса, бұл әдетте өңдеу параметрі емес, жүйе, рұқсат немесе құрастыру нұсқасының шектеуі болып табылады. + Бұрыштар анимациялық дроссель + Түсті келесідей көшіріңіз + HEX + атауы + Жұмсақ шаш және жиектері бар жылдам кесуге арналған портрет төсеніш үлгісі. + Zero-DCE++ қисығын бағалау арқылы төмен жарықты жылдам жақсарту. + Жаңбыр жолақтары мен ылғалды ауа-райындағы артефактілерді азайтуға арналған рестормер кескінін қалпына келтіру үлгісі. + Координаттар торын болжайтын және қисық беттерді тегіс сканерлеуге қайта салатын құжатты ашу үлгісі. + Қозғалыс ұзақтығы шкаласы + Қолданба анимациясының жылдамдығын басқарады: 0 қозғалысты өшіреді, 1 қалыпты, жоғары мәндер анимацияларды баяулатады + Соңғы құралдарды көрсету + Негізгі экранда соңғы пайдаланылған құралдарға жылдам қол жеткізуді көрсету + Пайдалану статистикасы + Қолданбаның ашылуын, құралды іске қосуды және ең көп қолданылатын құралдарды зерттеңіз + Қолданба ашылады + Құрал ашылады + Соңғы құрал + Сәтті құтқару + Белсенділік сызығы + Деректер сақталды + Жоғарғы формат + Қолданылатын құралдар + %1$d ашылады • %2$s + Қолдану статистикасы әлі жоқ + Бірнеше құралдарды ашыңыз және олар осы жерде автоматты түрде пайда болады + Пайдалану статистикасы құрылғыңызда тек жергілікті түрде сақталады. ImageToolbox оларды жеке әрекетті, таңдаулы құралдарды және сақталған деректер туралы түсініктерді көрсету үшін ғана пайдаланады + өңдегіш фотосуретті өңдеу суретін ретушпен реттеу + өлшемін өзгерту масштабты түрлендіру ажыратымдылық өлшемдерін ені биіктігі jpg jpeg png webp қысу + қысу өлшемі салмақ байт мб кб сапаны азайту + кесу пішімін кесу арақатынасы + сүзгі әсері түсті түзету лют маскасын реттеу + бояу щеткасы қарындашпен аннотация белгілеуді салу + шифрды шифрлау құпия сөздің құпиясын шешу + фондық тазалау құралы өшіру кесілген мөлдір альфаны жою + алдын ала қарау қарау галереясы ашық тексеру + stitch біріктіру панораманың ұзын скриншотын біріктіру + url веб жүктеп алу интернет сілтеме суреті + таңдағыш көз тамшысының үлгісі hex rgb түсі + палитра түс үлгілері сығындысы басым + exif метадеректерінің құпиялылығы жолақты таза gps орнын жою + кейінгі айырмашылықты салыстырыңыз + шектеу өлшемін өзгерту максимум мин мегапиксель өлшемдер қысқыш + pdf құжат беттерінің құралдары + ocr мәтіні үзінді сканерлеуді таниды + градиенттік фондық түсті тор + су таңбасының логотипі мәтін мөрінің қабаттасуы + gif анимациясы анимациялық кадрларды түрлендіру + apng анимациялық PNG фреймдерді түрлендіру + zip мұрағаты сығу бумасы файлдарды қаптамадан шығарады + jxl jpeg xl jpg кескін пішімін түрлендіру + svg векторлық ізі xml түрлендіру + пішімі jpg jpeg png webp heic avif jxl bmp түрлендіру + құжат сканері сканерленген қағаз перспективалық қию + qr штрих-код сканерін сканерлеу + қабаттасудың орташа медианалық астрофотографиясы + бөлу тақтайшалар тор тілім бөліктері + түсті түрлендіру hex rgb hsl hsv cmyk lab + webp анимациялық кескін пішімін түрлендіру + шу генераторы кездейсоқ құрылымды дән + коллаж торының орналасуын біріктіру + белгілеу қабаттары өңдеуге түсініктеме береді + base64 кодты декодтау деректер uri + бақылау сомасы хэш md5 sha sha1 sha256 тексеру + торлы градиент фон + exif метадеректерін өңдеу gps күн камерасы + бөлінген тор бөліктерін кесіңіз + аудио мұқаба альбом өнері музыкасының метадеректері + тұсқағазды экспорттау фоны + ascii мәтін көркем кейіпкерлері + ai ml нейрондық күшейту жоғары деңгейлі сегмент + түстер кітапханасының материалдар палитрасы түстер деп аталады + shader glsl фрагмент эффекті студиясы + pdf біріктіру біріктіру + pdf бөлек беттерді бөледі + pdf беттерді бұру бағыты + pdf ретін өзгерту беттерді сұрыптау + pdf бет нөмірлерін беттеу + pdf ocr мәтіні іздеуге болатын үзіндіні таниды + pdf су таңбасы логотипінің мәтіні + pdf қолтаңбасының сызбасы + pdf қорғау құпия сөзді шифрлау құлпы + pdf құлпын ашу құпия сөзінің шифрын ашу қорғауды жою + pdf қысу өлшемін азайту + pdf сұр түсті қара ақ монохромды + pdf жөндеуді түзету сынған қалпына келтіру + pdf метадеректер сипаттары exif авторының тақырыбы + pdf жою беттерін жою + pdf қию жиектері + pdf тегістеу аннотациялары қабаттарды құрайды + pdf суреттерді шығарып алыңыз + pdf zip мұрағатын қысу + pdf принтері + pdf алдын ала қарау құралы оқылды + суреттерді pdf форматына jpg jpeg png құжатына түрлендіру + pdf файлдарын кескін беттеріне jpg png экспорттау + pdf аннотациялары түсініктемелері таза жойылады + Бейтарап нұсқа + Қате + Көлеңке + Тіс биіктігі + Көлденең тіс диапазоны + Тік тістер диапазоны + Жоғарғы жиегі + Оң жақ жиегі + Төменгі жиек + Сол жақ жиегі + Жыртылған жиегі + Пайдалану статистикасын қалпына келтіріңіз + Құрал ашылады, статистиканы сақтау, жолақ, сақталған деректер және жоғарғы пішім қалпына келтіріледі. Қолданбаның ашылуы өзгеріссіз қалады. + Аудио + Файл + Профильдерді экспорттау + Ағымдағы профильді сақтаңыз + Экспорттау профилін жою + \\"%1$s\\" экспорттау профилін жойғыңыз келе ме? + Жиек сызу + Суреттің айналасында жіңішке жиекті көрсетіңіз және кенепті өшіріңіз + Толқынды + Жұмсақ толқынды жиегі бар дөңгелектенген UI элементтері + Кескіш + ойық + Дөңгеленген бұрыштар ішке қарай ойылған + Қосарланған кесілген бұрыштар + Толтырғыш + Түпнұсқа файл атауын кеңейтусіз енгізу үшін {filename} пайдаланыңыз + Алау + Негізгі сома + Қоңырау сомасы + Сәуле мөлшері + Сақина ені + Перспективаны бұрмалау + Java көрінісі мен сезімі + Қырқу + X бұрышы + және бұрыш + Шығарылым өлшемін өзгерту + Су тамшысы + Толқын ұзындығы + Кезең + Жоғары асу + Түс маскасы + Chrome + Ерітіңіз + Жұмсақтық + Кері байланыс + Lens Blur + Төмен кіріс + Жоғары кіріс + Төмен шығыс + Жоғары өнімділік + Жарық әсерлері + Төбенің биіктігі + Соққы жұмсақтығы + Ripple + X амплитудасы + Y амплитудасы + X толқын ұзындығы + Y толқын ұзындығы + Бейімделетін бұлыңғыр + Текстура генерациясы + Әртүрлі процедуралық текстураларды жасаңыз және оларды кескін ретінде сақтаңыз + Текстура түрі + Біржақтылық + Уақыт + Жылтыр + Дисперсия + Үлгілер + Бұрыш коэффициенті + Градиент коэффициенті + Тор түрі + Қашықтық қуаты + Y шкаласы + Бұлыңғырлық + Базалық түрі + Турбуленттілік факторы + Масштабтау + Итерациялар + Сақиналар + Қылқалам металл + Каустика + Ұялы + Шахмат тақтасы + фБм + Мәрмәр + Плазма + Көрпе + Ағаш + кездейсоқ + Шаршы + Алтыбұрышты + Сегізбұрышты + Үшбұрышты + Жоталы + VL шуы + SC шуы + Соңғы + Жақында пайдаланылған сүзгілер әлі жоқ + текстура генераторы щеткалы металл каустика ұялы шахмат мәрмәр плазмалық көрпе ағаш кірпіш камуфляж ұяшық бұлт жарылған мата жапырақ бал ұясы мұз лава тұмандық қағаз тот құм түтін тас жер бедері топография су толқыны + Кірпіш + Камуфляж + Ұяшық + Бұлт + Жарық + Мата + Жапырақ + Бал ұясы + Мұз + Лава + Тұмандық + Қағаз + Тот + Құм + Түтін + Тас + Жер бедері + Топография + Су толқыны + Жетілдірілген ағаш + Ерітінді ені + Тұрақсыздық + Беткей + Кедір-бұдыр + Бірінші табалдырық + Екінші табалдырық + Үшінші табалдырық + Жиектердің жұмсақтығы + Жиек ені + Қамту + Мәлімет + Тереңдігі + Тармақтану + Көлденең жіптер + Тік жіптер + Fuzz + Веналар + Жарықтандыру + Жарықтың ені + Аяз + Ағын + Жер қыртысы + Бұлттың тығыздығы + Жұлдыздар + Талшық тығыздығы + Талшық күші + Дақтар + Коррозия + Питинг + Үлпек + Шатыр жиілігі + Жел бұрышы + Толқындар + Шырша + Вена шкаласы + Су деңгейі + Тау деңгейі + Эрозия + Қар деңгейі + Жолдар саны + Сызықтың қалыңдығы + Көлеңкелеу + Кеуектің түсі + Ерітінді түсі + Қараңғы кірпіш түсі + Кірпіш түсі + Түсті бөлектеу + Қою түсті + Орман түсі + Жер түсі + Құм түсі + Фон түсі + Жасуша түсі + Жиек түсі + Аспан түсі + Көлеңке түсі + Ашық түс + Бетінің түсі + Вариация түсі + Жарық түсі + Қабық түсі + Тоқ түсі + Қара жапырақ түсі + Жапырақ түсі + Жиек түсі + Бал түсі + Қою түс + Мұз түсі + Аяз түсі + Қабықтың түсі + Жуу түсі + Жарқыраған түс + Кеңістік түсі + Күлгін түс + Көк түс + Негізгі түс + Талшық түсі + Бояу түсі + Металл түсі + Қараңғы тот түсі + Тот түсі + Қызғылт сары түс + Түтін түсі + Вена түсі + Төменгі түсті + Су түсі + Рок түсі + Қардың түсі + Төмен түсті + Жоғары түс + Сызық түсі + Таяз түс + Шөп + Кір + Былғары + Бетон + Асфальт + Мүк + Өрт + Аврора + Майлы дақ + Акварель + Абстрактілі ағын + Опал + Дамаск болаты + Найзағай + Барқыт + Сия мраморы + Голографиялық фольга + Биолюминесценция + Ғарыштық құйын + Лава шамы + Оқиға көкжиегі + Фракталды гүлдену + Хроматикалық туннель + Корона тұтылуы + Біртүрлі тартымды + Темір сұйықтық тәжі + Супернова + Ирис + Тауыс қауырсыны + Nautilus Shell + Сақина планетасы + Пышақ тығыздығы + Пышақ ұзындығы + Жел + Жаппайлық + Түйіндер + Ылғалдылық + Малтатас + Әжімдер + Кеуектер + Жиынтық + Жарықтар + шайыр + Кию + Дақтар + Талшықтар + Жалын жиілігі + Түтін + Қарқындылық + Таспалар + Топтар + Иридесенция + Қараңғылық + Гүлдейді + Пигмент + Жиектер + Қағаз + Диффузия + Симметрия + Айқындық + Түсті ойын + Сүттілік + Қабаттар + Бүктеу + поляк + Филиалдар + Бағыт + Шин + Бүктемелер + Қауырсындар + Сия балансы + Спектр + Қыртыстар + Дифракция + Қару-жарақ + Твист + Негізгі жарқырау + Блобтар + Дискіні еңкейту + Көкжиек өлшемі + Диск ені + Линзинг + Жапырақтары + Бұйралау + Филигр + Аспекттер + Қисықтық + Ай өлшемі + Корона өлшемі + Сәулелер + Алмаз сақина + Лобтар + Орбитаның тығыздығы + Қалыңдығы + Масақтар + Таяқтың ұзындығы + Дене мөлшері + Металл + Соққы радиусы + Қабық ені + Лақтырылды + Оқушы мөлшері + Ирис мөлшері + Түс вариациясы + Жарықтандыру + Көз өлшемі + Тікенек тығыздығы + Бұрылыстар + Палаталар + Ашылу + Жоталар + Жемчужина + Планетаның өлшемі + Сақинаның қисаюы + Сақина ені + Атмосфера + Ластану түсі + Қара шөп түсі + Шөп түсі + Ұштың түсі + Қара жер түсі + Құрғақ түс + Тас түсі + Былғары түсі + Бетон түсі + Шайыр түсі + Асфальт түсі + Тас түсі + Шаң түсі + Топырақ түсі + Қою мүк түсі + Мүк түсі + Қызыл түс + Негізгі түс + Жасыл түс + Көгілдір түсті + Қызыл қызыл түс + Алтын түс + Қағаз түсі + Пигмент түсі + Қосымша түс + Бірінші түс + Екінші түс + Қызғылт түсті + Қою болат түсі + Болат түсі + Ашық болат түсі + Оксидті түс + Гало түсі + Болт түсі + Барқыт түсі + Жылтыр түсі + Көк сия түсі + Қызыл сия түсі + Қараңғы сия түсі + Күміс түсті + Сары түс + Тіндердің түсі + Диск түсі + Ыстық түс + Линзаның түсі + Сыртқы түсі + Ішкі түс + Корона түсі + Суық түс + Жылы түс + Бұлт түсі + Жалын түсі + Қауырсынның түсі + Қабық түсі + Інжу түсі + Планетаның түсі + Сақина түсі + Сақтағаннан кейін түпнұсқа файлдарды жойыңыз + Сәтті өңделген бастапқы файлдар ғана жойылады + Түпнұсқа файлдар жойылды: %1$d + Түпнұсқа файлдар жойылмады: %1$d + Түпнұсқа файлдар жойылды: %1$d, орындалмады: %2$d + Парақ қимылдары + Кеңейтілген опция парақтарын сүйреп апаруға рұқсат етіңіз + Хроманың ішкі сынамасы + Бит тереңдігі + Жоғалсыз + %1$d-бит + Топтаманың атын өзгерту + Файл атауы үлгілерін пайдаланып бірнеше файлдардың атын өзгерту + пакеттік файлдардың атын өзгерту файл аты үлгісі реттілігі күн кеңейтімі + Атын өзгерту үшін файлдарды таңдаңыз + Файл атауы үлгісі + Атын өзгерту + Күн көзі + EXIF күні алынған + Файл өзгертілген күні + Файл жасалған күні + Ағымдағы күн + Қолмен берілген күн + Қолмен берілген күн + Қолмен уақыт + Таңдалған күн кейбір файлдар үшін қолжетімді емес. Олар үшін ағымдағы күн пайдаланылады. + Файл атауы үлгісін енгізіңіз + Үлгі жарамсыз немесе тым ұзын файл атауын жасайды + Үлгі қайталанатын файл атауларын жасайды + Барлық файл атаулары үлгіге сәйкес келеді + Бұл үлгі топтаманың атын өзгерту үшін қолжетімді емес таңбалауыштарды пайдаланады + Аты сол қалпында қалады + Файлдардың атауы сәтті өзгертілді + Кейбір файлдардың атын өзгерту мүмкін болмады + Рұқсат берілмеді + Бастапқы файл атауын кеңейтусіз пайдаланады, бұл бастапқы идентификацияны сақтауыңызға көмектеседі. Сондай-ақ \\o{start:end} арқылы кесуді, \\o{t} арқылы транслитерациялауды және \\o{s/old/new/} арқылы ауыстыруды қолдайды. + Автоматты ұлғайту есептегіші. Арнаулы пішімдеуді қолдайды: \\c{padding} (мысалы, \\c{3} -&gt; 001), \\c{start:step} немесе \\c{start:step:padding}. + Бастапқы файл орналасқан негізгі қалтаның атауы. + Бастапқы файлдың өлшемі. Бірліктерді қолдайды: \\z{b}, \\z{kb}, \\z{mb} немесе адам оқи алатын пішім үшін жай \\z. + Файл атауының бірегейлігін қамтамасыз ету үшін кездейсоқ әмбебап бірегей идентификаторды (UUID) жасайды. + Файлдардың атын өзгертуді қайтару мүмкін емес. Жалғастырмас бұрын жаңа атаулардың дұрыс екеніне көз жеткізіңіз. + Атын өзгертуді растаңыз + Бүйірлік мәзір параметрлері + Мәзір масштабы + Альфа мәзірі + Автоматты ақ баланс + Қиып алу + Жасырын су белгілерін тексеру + Сақтау + Кэш шегі + Бұл өлшемнен асып кеткен кезде кэшті автоматты түрде тазалаңыз + Тазалау аралығы + Кэшті тазалау қаншалықты жиі болуы керек + Қолданбаны іске қосу кезінде + 1 күн + 1 апта + 1 ай + Таңдалған шектеу мен аралыққа сәйкес қолданба кэшін автоматты түрде тазалаңыз + Жылжымалы егістік алаңы + Бүкіл кесу аймағын ішіне апару арқылы жылжытуға мүмкіндік береді + GIF біріктіру + Бірнеше GIF клиптерін бір анимацияға біріктіріңіз + GIF клиптерінің реті + Кері + Керісінше ойнаңыз + Бумеранг + Алға, содан кейін артқа ойнаңыз + Жақтау өлшемін қалыпқа келтіру + Ең үлкен клипке сәйкес келетін жақтауларды масштабтаңыз; бастапқы масштабын сақтау үшін өшіріңіз + Клиптер арасындағы кідіріс (мс) + Қалта + Қалтадағы және оның ішкі қалталарынан барлық кескіндерді таңдаңыз + Қайталанатын іздеу құралы + Нақты көшірмелерді және көрнекі ұқсас кескіндерді табыңыз + қайталанатын іздеу құралы ұқсас кескіндер дәл көшірмелер фотосуреттерді тазалау сақтау dHash SHA-256 + Көшірмелерді табу үшін кескіндерді таңдаңыз + Ұқсастық сезімталдығы + Нақты көшірмелер + Ұқсас суреттер + Барлық нақты көшірмелерді таңдаңыз + Ұсынылғаннан басқа барлығын таңдаңыз + Көшірмелер табылмады + Қосымша кескіндерді қосып көріңіз немесе ұқсастық сезімталдығын арттырыңыз + %1$d суретті оқу мүмкін емес + %1$s • %2$s қалпына келтіруге болады + %1$d топтар • %2$s қалпына келтіруге болады + %1$d таңдалды • %2$s + Бұл әрекетті қайтару мүмкін емес. Android жүйе диалогында жоюды растауды сұрауы мүмкін. + Табылмады + Бастау үшін жылжытыңыз + Соңына қарай жылжытыңыз + \ No newline at end of file diff --git a/core/resources/src/main/res/values-ko/strings.xml b/core/resources/src/main/res/values-ko/strings.xml new file mode 100644 index 0000000..bf7078d --- /dev/null +++ b/core/resources/src/main/res/values-ko/strings.xml @@ -0,0 +1,3738 @@ + + + + 이미지 선택 + 너비 %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 크기로만 저장할 수 있습니다. + GIF를 WEBP로 + 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 페이지에서 링크, 주석, 강조 표시, 모양 또는 양식 필드와 같은 선택한 주석 유형을 제거합니다. + 하이퍼링크 + 파일 첨부 + 윤곽 + 팝업 + 우표 + 모양 + 텍스트 참고 + 텍스트 마크업 + 양식 필드 + 마크업 + 알려지지 않은 + 주석 + 그룹 해제 + 구성 가능한 색상과 오프셋을 사용하여 레이어 뒤에 흐림 그림자 추가 + 콘텐츠 인식 왜곡 + 이 작업을 완료하려면 메모리가 부족합니다. 더 작은 이미지를 사용하거나, 다른 앱을 닫거나, 앱을 다시 시작해 보세요. + 병렬 작업자 + 변환된 파일이 원본보다 크기 때문에 이러한 이미지는 저장되지 않았습니다. 원본 이미지를 삭제하기 전에 이 목록을 확인하세요. + 건너뛴 파일: %1$s + 앱 로그 + 문제를 파악하려면 앱 로그를 확인하세요. + 그룹화된 도구의 즐겨찾기 + 도구가 유형별로 그룹화되면 즐겨찾기를 탭으로 추가합니다. + 즐겨찾기를 마지막으로 표시 + 즐겨찾는 도구 탭을 끝으로 이동합니다. + 가로 간격 + 수직 간격 + 역방향 에너지 + 순방향 에너지 알고리즘 대신 간단한 그래디언트 크기 에너지 맵을 사용합니다. + 마스크된 영역 제거 + 솔기는 마스크된 영역을 통과하여 보호하는 대신 선택한 영역만 제거합니다. + VHS NTSC + 고급 NTSC 설정 + 더욱 강력한 VHS 및 방송 아티팩트를 위한 추가 아날로그 튜닝 + 크로마 블리드 + 테이프 마모 + 추적 + 루마 스미어 + 울리는 + + 필드 사용 + 필터 유형 + 입력 루마 필터 + 입력 크로마 로우패스 + 크로마 복조 + 위상 변화 + 위상 오프셋 + 헤드 스위칭 + 헤드 전환 높이 + 헤드 스위칭 오프셋 + 헤드 스위칭 시프트 + 미드라인 위치 + 미드라인 지터 + 추적 소음 높이 + 추적파 + 눈 추적 + 눈 이방성 추적 + 소음 추적 + 소음 강도 추적 + 복합 소음 + 복합 소음 주파수 + 복합 소음 강도 + 복합 소음 세부 사항 + 울리는 주파수 + 울리는 힘 + 루마 노이즈 + 루마 노이즈 주파수 + 루마 노이즈 강도 + 루마 노이즈 세부정보 + 크로마 노이즈 + 크로마 노이즈 주파수 + 크로마 노이즈 강도 + 크로마 노이즈 세부정보 + 눈의 강도 + 눈 이방성 + 크로마 위상 노이즈 + 크로마 위상 오류 + 수평 크로마 지연 + 크로마 지연 수직 + VHS 테이프 속도 + VHS 크로마 손실 + VHS 선명도 + VHS 선명 주파수 + VHS 가장자리 파동 강도 + VHS 가장자리 파동 속도 + VHS 에지파 주파수 + VHS 에지 웨이브 디테일 + 출력 크로마 로우패스 + 수평으로 확장 + 수직으로 확장 + 배율 X + 스케일 팩터 Y + 일반적인 이미지 워터마크를 감지하고 이를 LaMa로 인페인팅합니다. 감지 및 인페인팅 모델을 자동으로 다운로드합니다. + 중수소맹 + 이미지 확장 + YOLO v11 분할을 사용한 객체 분할 기반 배경 제거 프로그램 + 선 각도 표시 + 그리는 동안 현재 선 회전을 각도 단위로 표시합니다. + 애니메이션 이모티콘 + 사용 가능한 이모티콘을 애니메이션으로 표시 + 원본 폴더에 저장 + 선택한 폴더 대신 원본 파일 옆에 새 파일을 저장합니다. + 워터마크 PDF + 메모리가 부족합니다 + 이미지가 너무 커서 이 장치에서 처리할 수 없거나 시스템에 사용 가능한 메모리가 부족합니다. 이미지 해상도를 낮추거나, 다른 앱을 닫거나, 더 작은 파일을 선택해 보세요. + 디버그 메뉴 + 앱 기능을 테스트하기 위한 메뉴입니다. 이는 프로덕션 릴리스에 표시되지 않습니다. + 공급자 + PaddleOCR에는 장치에 추가 ONNX 모델이 필요합니다. %1$s 데이터를 다운로드하시겠습니까? + 만능인 + 한국인 + 라틴어 + 동슬라브어 + 태국어 + 그리스 사람 + 영어 + 키릴 문자 + 아라비아 말 + 데바나가리 문자 + 타밀 사람 + 텔루구어 + 셰이더 + 셰이더 사전 설정 + 선택된 셰이더가 없습니다. + 셰이더 스튜디오 + 사용자 정의 프래그먼트 셰이더 생성, 편집, 검증, 가져오기 및 내보내기 + 저장된 셰이더 + 저장된 셰이더 열기, 복제, 내보내기, 공유 또는 삭제 + 새로운 셰이더 + 셰이더 파일 + .itshader JSON + %1$d 매개변수 + 셰이더 소스 + void main()의 본문만 작성합니다. UV 좌표에는 TextureCoordinate를 사용하고 소스 샘플러로는 inputImageTexture를 사용합니다. + 기능 + void main() 앞에 삽입된 선택적 도우미 함수, 상수 및 구조체입니다. 유니폼은 매개변수에서 생성됩니다. + 셰이더에 편집 가능한 값이 필요한 경우 여기에 유니폼을 추가하세요. + 셰이더 재설정 + 현재 셰이더 초안이 지워집니다. + 잘못된 셰이더 + 지원되지 않는 셰이더 버전 %1$d. 지원되는 버전: %2$d. + 셰이더 이름은 비워둘 수 없습니다. + 셰이더 소스는 비워둘 수 없습니다. + 셰이더 소스에 지원되지 않는 문자(%1$s)가 포함되어 있습니다. ASCII GLSL 소스만 사용하세요. + \\\"%1$s\\\" 매개변수가 두 번 이상 선언되었습니다. + 매개변수 이름은 비워둘 수 없습니다. + \\\"%1$s\\\" 매개변수는 예약된 GPUImage 이름을 사용합니다. + \\\"%1$s\\\" 매개변수는 유효한 GLSL 식별자여야 합니다. + \\\"%1$s\\\" 매개변수에는 %2$s 값 유형 \\\"%3$s\\\"이 있습니다. 예상되는 \\\"%4$s\\\"입니다. + \\\"%1$s\\\" 매개변수는 bool 값의 최소 또는 최대를 정의할 수 없습니다. + \\\"%1$s\\\" 매개변수의 최소값이 최대값보다 큽니다. + 매개변수 \\\"%1$s\\\" 기본값은 최소값보다 낮습니다. + 매개변수 \\\"%1$s\\\" 기본값은 최대값보다 큽니다. + 셰이더는 \\\"uniform Sampler2D %1$s;\\\"을 선언해야 합니다. + 셰이더는 \\\"Varying vec2 %1$s;\\\"을 선언해야 합니다. + 셰이더는 \\\"void main()\\\"을 정의해야 합니다. + 셰이더는 gl_FragColor에 색상을 써야 합니다. + ShaderToy mainImage 셰이더는 지원되지 않습니다. GPUImage void main() 계약을 사용하세요. + 애니메이션 ShaderToy 유니폼 \\\"iTime\\\"은 지원되지 않습니다. + 애니메이션 ShaderToy 유니폼 \\\"iFrame\\\"은 지원되지 않습니다. + ShaderToy 유니폼 \\\"iResolution\\\"은 지원되지 않습니다. + ShaderToy iChannel 텍스처는 지원되지 않습니다. + 외부 텍스처는 지원되지 않습니다. + 외부 텍스처 확장은 지원되지 않습니다. + Libretro 셰이더 매개변수는 지원되지 않습니다. + 입력 텍스처는 하나만 지원됩니다. \\\"유니폼 %1$s %2$s;\\\"을 제거합니다. + \\\"%1$s\\\" 매개변수는 \\\"uniform %2$s %1$s;\\\"로 선언되어야 합니다. + \\\"%1$s\\\" 매개변수는 \\\"uniform %2$s %1$s;\\\"로 선언됩니다. 예상되는 \\\"uniform %3$s %1$s;\\\"입니다. + 셰이더 파일이 비어 있습니다. + 셰이더 파일은 버전, 이름 및 셰이더 필드가 포함된 .itshader JSON 객체여야 합니다. + 셰이더 파일이 유효한 JSON이 아니거나 .itshader 형식과 일치하지 않습니다. + \\\"%1$s\\\" 필드가 필요합니다. + %1$s이(가) 필요합니다. + %1$s \\\"%2$s\\\"은(는) 지원되지 않습니다. 지원되는 유형: %3$s. + \\\"%2$s\\\" 매개변수에는 %1$s이(가) 필요합니다. + %1$s은(는) 유한 숫자여야 합니다. + %1$s은(는) 정수여야 합니다. + %1$s은(는) 참이거나 거짓이어야 합니다. + %1$s은(는) 색상 문자열, RGB/RGBA 배열 또는 색상 객체여야 합니다. + %1$s은(는) #RRGGBB 또는 #RRGGBBAA 형식을 사용해야 합니다. + %1$s에는 유효한 16진수 색상 채널이 포함되어야 합니다. + %1$s에는 3개 또는 4개의 색상 채널이 포함되어야 합니다. + %1$s은(는) 0에서 255 사이여야 합니다. + %1$s은(는) 두 개의 숫자 배열이거나 x와 y가 포함된 객체여야 합니다. + %1$s에는 정확히 2개의 숫자가 포함되어야 합니다. + 복제하다 + 항상 EXIF ​​지우기 + 도구가 메타데이터 유지를 요청하는 경우에도 저장 시 이미지 EXIF ​​데이터 제거 + 도움말 및 팁 + %1$d 튜토리얼 + 도구 열기 + 주요 도구, 내보내기 옵션, PDF 흐름, 색상 유틸리티 및 일반적인 문제에 대한 수정 사항에 대해 알아보세요. + 시작하기 + 도구 선택, 파일 가져오기, 결과 저장 및 설정 재사용 속도 향상 + 이미지 편집 + 이미지 크기 조정, 자르기, 필터링, 배경 지우기, 그리기 및 워터마크 표시 + 파일 및 메타데이터 + 형식 변환, 출력 비교, 개인 정보 보호 및 파일 이름 제어 + PDF 및 문서 + PDF 작성, 문서, OCR 페이지 스캔, 공유할 파일 준비 + 텍스트, QR 및 데이터 + 텍스트 인식, 코드 스캔, Base64 인코딩, 해시 확인 및 파일 패키지 + 색상 도구 + 색상을 선택하고, 팔레트를 만들고, 색상 라이브러리를 탐색하고, 그라디언트를 만듭니다. + 창의적인 도구 + SVG, ASCII 아트, 노이즈 텍스처, 셰이더 및 실험적인 비주얼 생성 + 문제 해결 + 무거운 파일, 투명성, 공유, 가져오기 및 보고 문제 해결 + 올바른 도구를 선택하세요 + 카테고리, 검색, 즐겨찾기, 공유 작업을 사용하여 올바른 위치에서 시작하세요. + 최적의 진입점에서 시작 + Image Toolbox에는 집중된 도구가 많이 있으며, 그 중 많은 도구가 언뜻 보면 중복됩니다. 완료하려는 작업부터 시작한 다음 결과의 가장 중요한 부분(크기, 형식, 메타데이터, 텍스트, PDF 페이지, 색상 또는 레이아웃)을 제어하는 ​​도구를 선택하세요. + 크기 조정, PDF, EXIF, OCR, QR, 색상 등 작업 이름을 알고 있는 경우 검색을 사용하세요. + 작업 유형만 알고 있는 경우 카테고리를 열고 자주 사용하는 도구를 즐겨찾기로 고정하세요. + 다른 앱이 Image Toolbox에 이미지를 공유하는 경우 공유 시트 흐름에서 도구를 선택하세요. + 결과 가져오기, 저장 및 공유 + 입력 선택부터 편집된 파일 내보내기까지의 일반적인 흐름을 이해합니다. + 일반적인 편집 흐름을 따르세요. + 대부분의 도구는 입력 선택, 옵션 조정, 미리 보기, 저장 또는 공유 등 동일한 리듬을 따릅니다. 중요한 세부 사항은 저장하면 출력 파일이 생성되는 반면 공유는 일반적으로 다른 앱으로 빠르게 전달하는 데 가장 적합하다는 것입니다. + 선택기에서 허용하는 경우 단일 이미지 도구용 파일 하나를 선택하거나 일괄 도구용 파일 여러 개를 선택합니다. + 저장하기 전에 미리보기 및 출력 컨트롤, 특히 형식, 품질 및 파일 이름 옵션을 확인하세요. + 빠른 전달을 위해 공유를 사용하거나 선택한 폴더에 영구 복사본이 필요할 때 저장하세요. + 한 번에 많은 이미지 작업 + 배치 도구는 반복적인 크기 조정, 변환, 압축 및 이름 지정 작업에 가장 적합합니다. + 예측 가능한 배치 준비 + 모든 출력이 동일한 규칙을 따라야 할 때 일괄 처리가 가장 빠릅니다. 큰 실수를 저지르기 가장 쉬운 곳이기도 하므로 긴 내보내기를 시작하기 전에 이름 지정, 품질, 메타데이터, 폴더 동작을 선택하세요. + 관련된 이미지를 모두 함께 선택하고 출력이 순서에 따라 달라지는 경우 순서를 유지합니다. + 내보내기를 시작하기 전에 크기 조정, 형식, 품질, 메타데이터 및 파일 이름 규칙을 한 번 설정하세요. + 소스 파일이 크거나 대상 형식이 새로운 경우 먼저 작은 테스트 배치를 실행하십시오. + 빠른 단일 편집 + 하나의 이미지에 여러 가지 간단한 변경이 필요한 경우 올인원 편집기를 사용하십시오. + 도구 호핑 없이 하나의 이미지 완성 + 단일 편집은 이미지에 하나의 특수 작업이 아닌 작은 변경 체인이 필요할 때 유용합니다. 일반적으로 일괄 작업 흐름을 구축하지 않고 최종 복사본 하나를 빠르게 정리하고 미리 보고 내보내는 것이 더 빠릅니다. + 일반적인 조정, 마크업, 자르기, 회전 또는 내보내기 변경이 필요한 이미지 하나에 대해 단일 편집을 엽니다. + 필터 전 자르기, 최종 압축 전 필터 등 가장 적은 픽셀을 먼저 변경하는 순서대로 편집 내용을 적용합니다. + 일괄 처리, 정확한 파일 크기 대상, PDF 출력, OCR 또는 메타데이터 정리가 필요한 경우 대신 특수 도구를 사용하십시오. + 사전 설정 및 설정 사용 + 반복되는 선택 사항을 저장하고 선호하는 작업 흐름에 맞게 앱을 조정하세요. + 동일한 설정을 반복하지 마세요. + 사전 설정 및 설정은 동일한 종류의 파일을 자주 내보낼 때 유용합니다. 좋은 사전 설정은 반복되는 수동 체크리스트를 한 번의 탭으로 바꾸는 반면, 전역 설정은 새 도구가 시작되는 기본값을 정의합니다. + 일반적인 출력 크기, 형식, 품질 값 또는 이름 지정 패턴에 대한 사전 설정을 만듭니다. + 기본 형식, 품질, 저장 폴더, 파일 이름, 선택기 및 인터페이스 동작에 대한 설정을 검토하세요. + 앱을 재설치하거나 다른 기기로 이동하기 전에 설정을 백업하세요. + 유용한 기본값 설정 + 기본 형식, 품질, 배율 모드, 크기 조정 유형 및 색상 공간을 한 번 선택하십시오. + 새로운 도구를 목표에 더 가깝게 시작하세요 + 기본값은 단순한 미용적 선호가 아닙니다. 많은 도구에서 먼저 표시되는 형식, 품질, 크기 조정 동작, 배율 모드 및 색상 공간을 결정하므로 내보낼 때마다 동일한 선택을 다시 선택하지 않아도 됩니다. + 사진의 경우 JPEG, 알파의 경우 PNG/WebP 등 일반적인 대상과 일치하도록 기본 이미지 형식과 품질을 설정하세요. + 엄격한 치수, 소셜 플랫폼 또는 문서 페이지에 대해 자주 내보내는 경우 기본 크기 조정 유형 및 배율 모드를 조정하십시오. + 워크플로우에서 디스플레이, 인쇄 또는 외부 편집기 전반에 걸쳐 일관된 색상 처리가 필요한 경우에만 색상 공간 기본값을 변경하십시오. + 선택기 및 실행기 + 앱이 너무 많은 대화 상자를 열거나 잘못된 위치에서 시작되는 경우 선택기 설정을 사용하세요. + 파일 열기를 습관에 맞게 만들기 + 선택기 및 실행기 설정은 Image Toolbox가 기본 화면, 도구 또는 파일 선택 흐름에서 시작되는지 여부를 제어합니다. 이러한 옵션은 일반적으로 Android 공유 시트에서 입력하거나 앱이 도구 실행기 같은 느낌을 주기를 원하는 경우에 특히 유용합니다. + 시스템 선택기가 파일을 숨기거나, 최근 항목을 너무 많이 표시하거나, 클라우드 파일을 제대로 처리하지 못하는 경우 사진 선택기 설정을 사용하세요. + 일반적으로 공유에서 입력하거나 소스 파일을 이미 알고 있는 도구에 대해서만 건너뛰기 선택을 활성화합니다. + Image Toolbox가 일반 홈 화면보다 도구 선택기처럼 작동하도록 하려면 실행 프로그램 모드를 검토하세요. + 이미지 출처 + 갤러리, 파일 관리자, 클라우드 파일에 가장 적합한 선택기 모드를 선택하세요. + 올바른 소스에서 파일 선택 + 이미지 소스 설정은 앱이 Android에 이미지를 요청하는 방식에 영향을 미칩니다. 최선의 선택은 파일이 시스템 갤러리, 파일 관리자 폴더, 클라우드 공급자 또는 임시 콘텐츠를 공유하는 다른 앱에 있는지 여부에 따라 달라집니다. + 일반적인 갤러리 이미지에 대해 시스템과 가장 통합된 환경을 원할 경우 기본 선택기를 사용하세요. + 앨범, 폴더, 클라우드 파일 또는 비표준 형식이 예상한 곳에 표시되지 않으면 선택기 모드를 전환하세요. + 원본 앱을 종료한 후 공유 파일이 사라지면 영구 선택기를 통해 파일을 다시 열거나 먼저 로컬 복사본을 저장하세요. + 즐겨찾기 및 공유 도구 + 메인 화면에 집중을 유지하고 공유 흐름에 적합하지 않은 도구를 숨깁니다. + 도구 목록 소음 감소 + 즐겨찾기 및 공유를 위해 숨김 설정은 매일 몇 가지 도구만 사용하는 경우 유용합니다. 또한 다른 앱이 전송하는 파일 형식을 사용할 수 없는 도구를 숨김으로써 공유 흐름을 실용적으로 유지합니다. + 자주 사용하는 도구를 고정하면 도구가 카테고리별로 그룹화되어 있어도 쉽게 접근할 수 있습니다. + 다른 앱에서 오는 파일과 관련이 없는 경우 공유 흐름에서 도구를 숨깁니다. + 마지막에 즐겨찾기를 선호하거나 관련 도구와 함께 그룹화하는 경우 즐겨찾기 순서 설정을 사용하세요. + 백업 설정 + 사전 설정, 기본 설정 및 앱 동작을 다른 설치로 안전하게 이동하세요. + 설정을 휴대 가능하게 유지하세요 + 백업 및 복원은 사전 설정, 폴더, 파일 이름 규칙, 도구 순서 및 시각적 기본 설정을 조정한 후에 가장 유용합니다. 백업을 사용하면 메모리에서 작업 흐름을 재구성하지 않고도 설정을 실험하거나 앱을 다시 설치할 수 있습니다. + 사전 설정, 파일 이름 동작, 기본 형식 또는 도구 배열을 변경한 후 백업을 만듭니다. + 데이터를 지우거나, 재설치하거나, ​​장치를 이동하려는 경우 앱 폴더 외부 어딘가에 백업을 저장하세요. + 중요한 배치를 처리하기 전에 설정을 복원하여 기본값과 저장 동작이 이전 설정과 일치하도록 하세요. + 이미지 크기 조정 및 변환 + 하나 이상의 이미지에 대한 크기, 형식, 품질, 메타데이터 변경 + 크기가 조정된 복사본 만들기 + 크기 조정 및 변환은 예측 가능한 크기와 더 가벼운 출력 파일을 위한 주요 도구입니다. 이미지 크기, 출력 형식, 메타데이터 동작이 모두 동시에 중요한 경우 이를 사용하세요. + 하나 이상의 이미지를 선택한 다음 크기, 배율 또는 파일 크기가 가장 중요한지 선택하세요. + 출력 형식과 품질을 선택하십시오. 투명도를 유지해야 하는 경우 PNG 또는 WebP를 사용하세요. + 결과를 미리 본 다음 원본을 변경하지 않고 생성된 복사본을 저장하거나 공유하세요. + 파일 크기에 따라 크기 조정 + 웹사이트, 메신저, 양식이 무거운 이미지를 거부하는 경우 크기 제한을 목표로 삼으세요. + 엄격한 업로드 제한에 적합 + 대상이 특정 한도 미만의 파일만 허용하는 경우 파일 크기로 크기 조정이 품질 값을 추측하는 것보다 낫습니다. 도구는 결과를 사용 가능한 상태로 유지하려고 노력하면서 목표에 도달하도록 압축 및 크기를 조정할 수 있습니다. + 메타데이터 및 공급자 변경을 위한 공간을 남겨두기 위해 실제 업로드 제한보다 약간 낮은 대상 크기를 입력합니다. + 대상이 작을 때는 사진에 손실이 있는 형식을 선호합니다. PNG는 크기를 조정한 후에도 크게 유지될 수 있습니다. + 공격적인 크기 목표로 인해 눈에 보이는 아티팩트가 발생할 수 있으므로 내보낸 후 면, 텍스트 및 가장자리를 검사하십시오. + 크기 조정 제한 + 최대 너비, 높이 또는 파일 제한을 초과하는 이미지만 축소 + 이미 괜찮은 이미지의 크기를 조정하지 마세요. + 크기 조정 제한은 일부 이미지는 크고 다른 이미지는 이미 준비되어 있는 혼합 폴더에 유용합니다. 모든 파일에 동일한 크기 조정을 적용하는 대신 허용 가능한 이미지를 원본 품질에 더 가깝게 유지합니다. + 맹목적으로 모든 파일의 크기를 조정하는 대신 대상을 기준으로 최대 크기 또는 제한을 설정하십시오. + 소스보다 무거워지는 출력을 피하려면 더 큰 경우 건너뛰기 스타일 동작을 활성화하십시오. + 소스 크기가 많이 다른 다양한 카메라, 스크린샷 또는 다운로드의 일괄 처리에 이 기능을 사용하세요. + 자르기, 회전 및 똑바르게 하기 + 이미지를 내보내거나 다른 도구에서 사용하기 전에 중요한 영역의 프레임을 지정하세요. + 컴포지션을 정리하세요. + 먼저 자르면 나중에 필터, OCR, PDF 페이지 및 공유 결과가 더 깔끔해집니다. + 자르기를 열고 조정하려는 이미지를 선택합니다. + 출력물이 게시물, 문서 또는 아바타에 맞아야 하는 경우 자유 자르기 또는 가로 세로 비율을 사용하십시오. + 나중에 도구가 수정된 이미지를 받을 수 있도록 저장하기 전에 회전하거나 뒤집으세요. + 필터 및 조정 적용 + 편집 가능한 필터 스택을 구축하고 내보내기 전에 결과를 미리 봅니다. + 이미지 모양 조정 + 필터는 빠른 수정, 스타일화된 출력, OCR 또는 인쇄용 이미지 준비에 유용합니다. 이미지에 텍스트나 미세한 세부 묘사가 포함되어 있으면 대비, 선명도 및 밝기를 조금만 변경해도 큰 효과보다 더 중요할 수 있습니다. + 필터를 하나씩 추가하고 변경할 때마다 미리보기를 확인하세요. + 작업에 따라 밝기, 대비, 선명도, 흐림, 색상 또는 마스크를 조정합니다. + 미리보기가 대상 장치, 문서 또는 소셜 플랫폼과 일치하는 경우에만 내보냅니다. + 먼저 미리보기 + 더 무거운 편집, 변환 또는 정리 도구를 선택하기 전에 이미지를 검사하세요. + 실제로 받은 내용을 확인해보세요 + 이미지 미리보기는 채팅, 클라우드 저장소 또는 다른 앱에서 가져온 파일이고 그 내용이 확실하지 않을 때 유용합니다. 치수, 투명도, 방향 및 시각적 품질을 먼저 확인하면 올바른 후속 도구를 선택하는 데 도움이 됩니다. + 여러 이미지를 어떻게 처리할지 결정하기 전에 검사해야 할 경우 이미지 미리보기를 엽니다. + 회전 문제, 투명한 영역, 압축 아티팩트 및 예상치 못한 큰 크기를 찾으십시오. + 수정이 필요한 부분을 확인한 후에만 크기 조정, 자르기, 비교, EXIF ​​또는 배경 제거 프로그램으로 이동하세요. + 배경 제거 또는 복원 + 복원 도구를 사용하여 배경을 자동으로 지우거나 가장자리를 수동으로 다듬습니다. + 제목을 유지하고 나머지는 삭제하세요. + 배경 제거는 자동 선택과 신중한 수동 정리를 결합할 때 가장 잘 작동합니다. 최종 내보내기 형식은 마스크만큼 중요합니다. 미리 보기가 정확해 보이더라도 JPEG는 투명 영역을 병합하기 때문입니다. + 피사체가 배경과 명확하게 분리되어 있으면 자동 제거를 시작합니다. + 확대하고 지우기와 복원 사이를 전환하여 머리카락, 모서리, 그림자 및 작은 세부 사항을 수정합니다. + 최종 파일에 투명성이 필요한 경우 PNG 또는 WebP로 저장하세요. + 그리기, 마크업 및 워터마크 + 화살표, 메모, 강조 표시, 서명 또는 반복되는 워터마크 오버레이를 추가하세요. + 눈에 보이는 정보 추가 + 마크업 도구는 이미지를 설명하는 데 도움이 되고 워터마킹은 내보낸 파일을 브랜드화하거나 보호하는 데 도움이 됩니다. + 자유형 메모, 모양, 강조 표시 또는 빠른 주석이 필요할 때 그리기를 사용하세요. + 동일한 텍스트나 이미지 표시가 출력물에 일관되게 표시되어야 하는 경우 워터마킹을 사용합니다. + 표시가 눈에 띄지만 방해가 되지 않도록 내보내기 전에 불투명도, 위치 및 크기를 확인하세요. + 기본값 그리기 + 더 빠른 마크업을 위해 선 너비, 색상, 경로 모드, 방향 잠금 및 돋보기 설정 + 그림을 예측 가능하게 만드세요 + 그리기 설정은 스크린샷에 주석을 달거나, 문서에 표시하거나, 이미지에 자주 서명할 때 유용합니다. 기본값을 사용하면 설정 시간이 단축되고 돋보기와 방향 잠금 기능을 사용하면 작은 화면에서 정밀한 편집을 더 쉽게 할 수 있습니다. + 기본 선 너비를 설정하고 화살표, 강조 표시 또는 서명에 가장 많이 사용하는 값으로 색상을 그립니다. + 일반적으로 동일한 종류의 획, 모양 또는 마크업을 그리는 경우 기본 경로 모드를 사용합니다. + 손가락 입력으로 인해 작은 세부 사항을 정확하게 배치하기 어려운 경우 돋보기 또는 방향 잠금을 활성화하십시오. + 다중 이미지 레이아웃 + 스크린샷, 스캔 또는 비교 스트립을 정렬하기 전에 올바른 다중 이미지 도구를 선택하세요. + 올바른 레이아웃 도구 사용 + 이러한 도구는 비슷해 보이지만 각각은 서로 다른 종류의 다중 이미지 출력에 맞게 조정되었습니다. 올바른 것을 먼저 선택하면 다른 결과를 위해 설계된 레이아웃 컨트롤과의 싸움을 피할 수 있습니다. + 하나의 구성에 여러 이미지가 포함된 레이아웃을 디자인하려면 콜라주 메이커를 사용하세요. + 이미지가 하나의 긴 수직 또는 수평 스트립이 되어야 하는 경우 이미지 스티칭 또는 스태킹을 사용하십시오. + 하나의 큰 이미지를 여러 개의 작은 조각으로 만들어야 하는 경우 이미지 분할을 사용합니다. + 이미지 형식 변환 + 수동으로 픽셀을 편집하지 않고도 JPEG, PNG, WebP, AVIF, JXL 및 기타 복사본을 만들 수 있습니다. + 작업 형식을 선택하세요. + 사진, 스크린샷, 투명도, 압축 또는 호환성에는 다양한 형식이 더 좋습니다. 변환은 대상 앱이 무엇을 지원하는지, 소스에 보관하려는 알파, 애니메이션 또는 메타데이터가 포함되어 있는지 이해할 때 가장 안전합니다. + 호환되는 사진에는 JPEG를, 무손실 이미지와 알파에는 PNG를, 압축 공유에는 WebP를 사용하세요. + 형식이 지원하는 경우 품질을 조정하십시오. 값이 낮을수록 크기는 줄어들지만 아티팩트가 추가될 수 있습니다. + 변환된 파일이 대상 앱에서 올바르게 열리는지 확인할 때까지 원본을 보관하세요. + EXIF 및 개인정보 확인 + 민감한 사진을 공유하기 전에 메타데이터 보기, 편집 또는 제거 + 숨겨진 이미지 데이터 제어 + EXIF에는 이미지에 표시되지 않는 카메라 데이터, 날짜, 위치, 방향 및 기타 세부 정보가 포함될 수 있습니다. 공개 공유, 지원 보고서, 마켓플레이스 목록 또는 사적인 장소를 드러낼 수 있는 사진을 확인하기 전에 확인해 보는 것이 좋습니다. + 위치 또는 개인 장치 정보가 포함될 수 있는 사진을 공유하기 전에 EXIF ​​도구를 여십시오. + 민감한 필드를 제거하거나 날짜, 방향, 작성자, 카메라 데이터 등 잘못된 태그를 편집하세요. + 개인 정보 보호가 중요한 경우 새 복사본을 저장하고 원본 대신 해당 복사본을 공유하세요. + 자동 EXIF ​​정리 + 날짜 보존 여부를 결정하는 동안 기본적으로 메타데이터를 제거합니다. + 개인 정보 보호를 기본값으로 설정 + EXIF 설정 그룹은 내보낼 때마다 개인 정보 정리를 기억하는 대신 자동으로 개인 정보 정리를 수행하려는 경우에 유용합니다. 날짜는 정렬에는 유용하지만 공유에는 민감하기 때문에 날짜 시간 유지는 별도의 결정입니다. + 대부분의 내보내기가 공개 또는 준공용 공유용인 경우 항상 명확한 EXIF를 활성화하십시오. + 모든 타임스탬프를 제거하는 것보다 캡처 시간을 보존하는 것이 더 중요한 경우에만 날짜 시간 유지를 활성화합니다. + 일부 필드를 유지하고 다른 필드를 제거해야 하는 일회용 파일의 경우 수동 EXIF ​​편집을 사용하세요. + 제어 파일 이름 + 접두사, 접미사, 타임스탬프, 사전 설정, 체크섬 및 시퀀스 번호 사용 + 내보내기를 쉽게 찾을 수 있도록 만들기 + 좋은 파일 이름 패턴은 배치를 정렬하는 데 도움이 되며 실수로 덮어쓰는 것을 방지합니다. 파일 이름 설정은 유사한 이름이 빠르게 혼동될 수 있는 소스 폴더로 내보내기를 수행할 때 특히 유용합니다. + 설정에서 파일 이름 접두사, 접미사, 타임스탬프, 원본 이름, 사전 설정 정보 또는 시퀀스 옵션을 설정하세요. + 페이지, 프레임, 콜라주 등 순서가 중요한 배치에는 시퀀스 번호를 사용하세요. + 현재 폴더에 대해 기존 파일을 바꾸는 것이 안전하다고 확신하는 경우에만 덮어쓰기를 활성화하십시오. + 파일 덮어쓰기 + 워크플로가 의도적으로 파괴적인 경우에만 출력을 일치하는 이름으로 바꿉니다. + 덮어쓰기 모드를 주의해서 사용하세요 + 파일 덮어쓰기는 이름 충돌이 처리되는 방식을 변경합니다. 동일한 폴더로 반복 내보내기에 유용하지만 파일 이름 패턴이 다시 동일한 이름을 생성하는 경우 이전 결과를 대체할 수도 있습니다. + 이전에 생성된 파일 교체가 예상되고 복구 가능한 폴더에 대해서만 덮어쓰기를 활성화합니다. + 원본, 클라이언트 파일, 일회성 스캔 등을 백업 없이 내보낼 때는 덮어쓰기를 비활성화 상태로 유지하세요. + 덮어쓰기를 의도적인 파일 이름 패턴과 결합하여 의도한 출력만 교체합니다. + 파일 이름 패턴 + 원래 이름, 접두사, 접미사, 타임스탬프, 사전 설정, 배율 모드 및 체크섬 결합 + 출력을 설명하는 빌드 이름 + 파일 이름 패턴 설정은 내보낸 파일을 해당 파일에 발생한 일에 대한 유용한 기록으로 바꿀 수 있습니다. 폴더 보기는 원본 크기, 사전 설정, 형식 및 처리 순서를 구별할 수 있는 유일한 장소일 수 있으므로 일괄적으로 중요합니다. + 수동 정렬을 위해 읽을 수 있는 이름이 필요한 경우 원본 파일 이름, 접두사, 접미사 및 타임스탬프를 사용하세요. + 출력이 생성된 방법을 문서화해야 하는 경우 사전 설정, 배율 모드, 파일 크기 또는 체크섬 정보를 추가합니다. + 파일이 원본 또는 페이지 순서와 짝을 이루어야 하는 작업 흐름에는 임의의 이름을 사용하지 마세요. + 파일이 저장되는 위치를 선택하세요 + 폴더, 원본 폴더 저장 및 일회성 저장 위치를 ​​설정하여 내보내기 손실을 방지하세요. + 출력을 예측 가능하게 유지 + 많은 파일을 처리하거나 클라우드 제공업체의 파일을 공유할 때 저장 동작이 가장 중요합니다. 폴더 설정은 출력을 알려진 한 위치에 수집할지, 원본 옆에 표시할지, 특정 작업을 위해 일시적으로 다른 위치로 이동할지를 결정합니다. + 항상 한 곳에서 내보내려면 기본 저장 폴더를 설정하세요. + 출력이 소스 파일 근처에 유지되어야 하는 정리 작업 흐름에는 원본 폴더에 저장을 사용합니다. + 단일 작업에 기본값을 변경하지 않고 다른 대상이 필요한 경우 일회성 저장 위치를 ​​사용하세요. + 일회용 저장 폴더 + 일반 대상을 변경하지 않고 다음 내보내기를 임시 폴더로 보냅니다. + 특별한 직업을 따로 두세요 + 일회성 저장 위치는 임시 프로젝트, 클라이언트 폴더, 정리 세션 또는 일반 Image Toolbox 폴더와 혼합되어서는 안 되는 내보내기에 유용합니다. 기본 설정을 다시 작성하지 않고도 단기 목적지를 제공합니다. + 일반 내보내기 위치 외부에 속하는 작업을 시작하기 전에 일회용 폴더를 선택하십시오. + 짧은 프로젝트, 공유 폴더 또는 출력이 함께 그룹화되어야 하는 배치에 사용하세요. + 작업 후 기본 폴더로 돌아가서 나중에 내보내기가 예기치 않게 임시 위치에 저장되지 않도록 하세요. + 품질과 파일 크기의 균형 + 추측하는 대신 크기, 형식, 품질을 언제 변경해야 하는지 이해하세요. + 의도적으로 파일 축소 + 파일 크기는 이미지 크기, 형식, 품질, 투명도 및 콘텐츠 복잡성에 따라 달라집니다. 파일이 여전히 너무 무거우면 일반적으로 크기를 변경하는 것이 반복적으로 품질을 낮추는 것보다 더 도움이 됩니다. + 이미지가 대상이 표시할 수 있는 것보다 큰 경우 먼저 크기를 조정하십시오. + 손실이 있는 형식의 경우에만 품질을 낮추고 내보낸 후 텍스트, 가장자리, 그라데이션 및 면을 확인하세요. + 품질 변경만으로는 충분하지 않은 경우 형식을 전환하세요(예: 공유를 위한 WebP 또는 선명하고 투명한 자산을 위한 PNG). + 애니메이션 형식으로 작업 + 파일에 하나의 비트맵 대신 프레임이 있는 경우 GIF, APNG, WebP 및 JXL 도구를 사용하십시오. + 모든 이미지를 정지된 이미지로 취급하지 마세요. + 애니메이션 형식에는 프레임, 지속 시간, 호환성을 이해하는 도구가 필요합니다. + 해당 형식에 대해 프레임 수준 작업이 필요한 경우 GIF 또는 APNG 도구를 사용하세요. + 최신 압축이나 애니메이션 WebP 출력이 필요할 때 WebP 도구를 사용하세요. + 일부 플랫폼에서는 애니메이션 이미지를 변환하거나 병합하므로 공유하기 전에 애니메이션 타이밍을 미리 보세요. + 출력 비교 및 ​​미리보기 + 내보내기를 유지하기 전에 변경 사항, 파일 크기, 시각적 차이를 검사하세요. + 공유하기 전에 확인하세요 + 비교 도구는 압축 아티팩트, 잘못된 색상 또는 원치 않는 편집을 조기에 발견하는 데 도움이 됩니다. + 두 버전을 나란히 검사하려면 비교를 엽니다. + 문제를 놓치기 쉬운 가장자리, 텍스트, 그라데이션 및 투명 영역을 확대합니다. + 출력물이 너무 무겁거나 흐릿한 경우 이전 도구로 돌아가 품질이나 형식을 조정하세요. + PDF 도구 허브 사용 + PDF 파일 병합, 분할, 회전, 제거, 압축, 보호, OCR 및 워터마크 표시 + PDF 작업 선택 + PDF 허브는 하나의 진입점 뒤에 문서 작업을 그룹화하므로 정확한 작업을 선택할 수 있습니다. 파일이 이미 PDF인 경우 시작하고, 소스가 여전히 이미지 세트인 경우 이미지를 PDF로 변환 또는 문서 스캐너를 사용하세요. + PDF 도구를 열고 목표에 맞는 작업을 선택하십시오. + 소스 PDF나 이미지를 선택한 다음 페이지 순서, 회전, 보호 또는 OCR 옵션을 검토하세요. + 생성된 PDF를 저장하고 한 번 열어서 페이지 수, 순서, 가독성을 확인하세요. + 이미지를 PDF로 변환 + 스캔, 스크린샷, 영수증, 사진을 하나의 공유 가능한 문서로 결합하세요. + 깔끔한 문서 만들기 + 이미지를 일관되게 자르고, 정렬하고, 크기를 조정하면 더 나은 PDF 페이지가 됩니다. 이미지 목록을 페이지 스택처럼 처리합니다. 모든 회전, 여백 및 페이지 크기 선택은 최종 문서의 가독성에 영향을 미칩니다. + 페이지 가장자리, 그림자 또는 방향이 올바르지 않은 경우 먼저 이미지를 자르거나 회전하십시오. + 원하는 페이지 순서로 이미지를 선택하고 도구에서 제공하는 경우 페이지 크기나 여백을 조정합니다. + PDF를 내보낸 다음 보내기 전에 모든 페이지를 읽을 수 있는지 확인하세요. + 문서 스캔 + 종이 페이지를 캡처하고 PDF, OCR 또는 공유용으로 준비합니다. + 읽을 수 있는 페이지 캡처 + 문서 스캔은 평평한 페이지, 좋은 조명, 올바른 원근에서 가장 잘 작동합니다. 텍스트 인식과 압축은 모두 페이지 품질에 따라 달라지므로 OCR 또는 PDF 내보내기 전에 깨끗하게 스캔하면 시간이 절약됩니다. + 빛이 충분하고 그림자가 최소화된 대비되는 표면에 문서를 놓습니다. + 감지된 모서리를 조정하거나 수동으로 자르면 페이지가 프레임을 깔끔하게 채울 수 있습니다. + 이미지나 PDF로 내보낸 다음 선택 가능한 텍스트가 필요한 경우 OCR을 실행하세요. + PDF 파일 보호 또는 잠금 해제 + 비밀번호를 신중하게 사용하고 가능하면 편집 가능한 소스 사본을 보관하세요. + PDF 액세스를 안전하게 처리 + 보호 도구는 공유를 제어하는 ​​데 유용하지만 비밀번호는 분실하기 쉽고 복구하기 어렵습니다. 배포용 복사본을 보호하고, 보호되지 않은 소스 파일을 별도로 보관하고, 삭제하기 전에 결과를 확인하세요. + 유일한 소스 문서가 아닌 PDF 사본을 보호하세요. + 보호된 파일을 공유하기 전에 안전한 곳에 비밀번호를 저장하세요. + 편집이 허용된 파일에 대해서만 잠금 해제를 사용한 다음 잠금 해제된 복사본이 올바르게 열리는지 확인하세요. + PDF 페이지 구성 + 페이지 병합, 분할, 재배열, 회전, 자르기, 제거 및 의도적으로 페이지 번호 추가 + 장식 전 문서 구조 수정 + PDF 페이지 도구는 종이 문서를 준비하는 것과 같은 순서로 사용하기 가장 쉽습니다. 즉, 페이지를 수집하고, 잘못된 페이지를 제거하고, 순서대로 배치하고, 방향을 수정한 다음, 페이지 번호나 워터마크와 같은 최종 세부 정보를 추가합니다. + 문서가 여러 파일로 분할되어 있는 경우 먼저 병합 또는 이미지를 PDF로 변환을 사용하세요. + 페이지 번호, 서명 또는 워터마크를 추가하기 전에 페이지를 재정렬, 회전, 자르기 또는 제거하세요. + 하나의 누락되거나 회전된 페이지는 나중에 확인하기 어려울 수 있으므로 구조적 편집 후 최종 PDF를 미리 봅니다. + PDF 주석 + 시청자가 댓글과 표시를 다르게 표시하는 경우 주석을 제거하거나 병합합니다. + 계속 표시되는 항목 제어 + 주석은 주석, 강조 표시, 그림, 양식 표시 또는 기타 추가 PDF 개체일 수 있습니다. 일부 뷰어는 이를 다르게 표시하므로 이를 제거하거나 병합하면 공유 문서를 더 예측하기 쉽게 만들 수 있습니다. + 공유 사본에 주석, 강조 표시 또는 검토 표시를 포함하지 않아야 하는 경우 주석을 제거하십시오. + 표시되는 표시가 페이지에 유지되어야 하지만 더 이상 편집 가능한 주석처럼 작동하지 않을 때 병합합니다. + 주석을 제거하거나 병합하면 되돌리기 어려울 수 있으므로 원본을 보관하세요. + 텍스트 인식 + 선택 가능한 OCR 엔진 및 언어를 사용하여 이미지에서 텍스트 추출 + 더 나은 OCR 결과 얻기 + OCR 정확도는 이미지 선명도, 언어 데이터, 대비 및 페이지 방향에 따라 달라집니다. 일반적으로 가장 좋은 결과는 이미지를 먼저 준비하는 것에서 나옵니다. 노이즈를 잘라내고, 수직으로 회전하고, 가독성을 높인 다음 텍스트를 인식합니다. + 인식하기 전에 이미지를 텍스트 영역으로 자르고 수직으로 회전하십시오. + 올바른 언어를 선택하고 앱에서 요청하는 경우 언어 데이터를 다운로드하세요. + 인식된 텍스트를 복사하거나 공유한 다음 이름, 숫자, 구두점을 수동으로 확인하세요. + OCR 언어 데이터 선택 + 스크립트, 언어 및 다운로드한 OCR 모델을 일치시켜 인식을 향상시킵니다. + OCR을 텍스트와 일치시키세요 + 잘못된 OCR 언어로 인해 좋은 사진이 인식 불량처럼 보일 수 있습니다. 언어 데이터는 스크립트, 구두점, 숫자 및 혼합 문서에 중요하므로 휴대폰 UI의 언어보다는 이미지에 나타나는 언어를 선택하세요. + 전화 언어뿐만 아니라 이미지에 나타나는 언어나 스크립트를 선택하세요. + 오프라인으로 작업하거나 배치를 처리하기 전에 누락된 데이터를 다운로드하세요. + 언어가 혼합된 문서의 경우 가장 중요한 언어를 먼저 실행하고 이름과 번호를 수동으로 확인하세요. + 검색 가능한 PDF + 스캔한 페이지를 검색하고 복사할 수 있도록 OCR 텍스트를 파일에 다시 기록합니다. + 스캔한 내용을 검색 가능한 문서로 변환 + OCR은 텍스트를 클립보드에 복사하는 것 이상의 기능을 수행할 수 있습니다. 인식된 텍스트를 파일이나 검색 가능한 PDF에 쓰면 페이지 이미지는 계속 표시되지만 텍스트는 검색, 선택, 색인화 또는 보관하기가 더 쉬워집니다. + 원본 페이지와 동일하게 보이는 스캔한 문서에 대해서는 검색 가능한 PDF 출력을 사용하십시오. + 추출된 텍스트만 필요하고 페이지 이미지는 신경 쓰지 않는 경우 파일에 쓰기를 사용하십시오. + OCR 텍스트는 검색 가능하지만 여전히 불완전하므로 중요한 숫자, 이름, 표를 수동으로 확인하세요. + QR 코드 스캔 + 가능한 경우 카메라 입력 또는 저장된 이미지에서 QR 콘텐츠를 읽습니다. + 안전하게 코드 읽기 + QR 코드에는 링크, Wi-Fi 데이터, 연락처, 텍스트 또는 기타 페이로드가 포함될 수 있습니다. + QR 코드 스캔을 열고 코드를 날카롭고 평평하게 프레임 안에 완전히 보관하세요. + 링크를 열거나 민감한 데이터를 복사하기 전에 디코딩된 콘텐츠를 검토하세요. + 스캔에 실패하면 조명을 개선하거나 코드를 자르거나 고해상도 소스 이미지를 사용해 보세요. + Base64 및 체크섬 사용 + 이미지를 텍스트로 인코딩하고, 텍스트를 다시 이미지로 디코딩하고, 파일 무결성을 확인합니다. + 파일과 텍스트 간 이동 + Base64는 작은 이미지 페이로드에 유용한 반면, 체크섬은 파일이 변경되지 않았는지 확인하는 데 도움이 됩니다. 이러한 도구는 기술 워크플로, 지원 보고서, 파일 전송 및 바이너리 파일을 일시적으로 텍스트로 변환해야 하는 장소에서 가장 유용합니다. + 워크플로에 바이너리 파일 대신 텍스트가 필요한 경우 Base64 도구를 사용하여 작은 이미지를 인코딩합니다. + 신뢰할 수 있는 소스에서만 Base64를 디코딩하고 저장하기 전에 미리보기를 확인하세요. + 내보낸 파일을 비교하거나 업로드/다운로드를 확인해야 할 때 체크섬 도구를 사용하세요. + 데이터 패키징 또는 보호 + 파일을 묶거나 텍스트 데이터를 변환하는 데 ZIP 및 암호화 도구를 사용하세요. + 관련 파일을 함께 보관 + 패키징 도구는 여러 출력이 하나의 파일로 이동해야 할 때 유용합니다. 또한 완전한 결과 세트를 보내야 할 때 생성된 이미지, PDF, 로그 및 메타데이터를 함께 보관하는 데에도 좋습니다. + 내보낸 여러 이미지나 문서를 함께 보내야 하는 경우 ZIP을 사용하세요. + 선택한 방법을 이해하고 필요한 키를 안전하게 저장할 수 있는 경우에만 암호화 도구를 사용하십시오. + 소스 파일을 삭제하기 전에 생성된 아카이브나 변환된 텍스트를 테스트하세요. + 이름의 체크섬 + 가독성보다 고유성과 무결성이 더 중요한 경우 체크섬 기반 파일 이름을 사용하세요. + 콘텐츠별로 파일 이름 지정 + 체크섬 파일 이름은 내보내기에 중복된 이름이 표시되거나 두 파일이 동일하다는 것을 증명해야 할 때 유용합니다. 수동 검색에는 덜 친숙하므로 기술 아카이브 및 확인 작업 흐름에 사용하세요. + 사람이 읽을 수 있는 제목보다 콘텐츠 ID가 더 중요한 경우 파일 이름으로 체크섬을 활성화합니다. + 아카이브, 중복 제거, 반복 가능한 내보내기 또는 다시 업로드하고 다운로드할 수 있는 파일에 사용하세요. + 사람들이 여전히 파일이 나타내는 내용을 이해해야 하는 경우 체크섬 이름을 폴더 또는 메타데이터와 쌍으로 연결합니다. + 이미지에서 색상 선택 + 픽셀 샘플링, 색상 코드 복사, 팔레트 또는 디자인에서 색상 재사용 + 정확한 색상 샘플 + 색상 선택은 디자인 매칭, 브랜드 색상 추출, 이미지 값 확인 등에 유용합니다. 앤티앨리어싱, 그림자 및 압축으로 인해 주변 픽셀이 예상한 색상과 약간 다를 수 있으므로 확대/축소가 중요합니다. + 이미지에서 색상 선택을 열고 필요한 색상이 포함된 소스 이미지를 선택합니다. + 가까운 픽셀이 결과에 영향을 미치지 않도록 작은 세부 사항을 샘플링하기 전에 확대하십시오. + HEX, RGB, HSV 등 대상에 필요한 형식으로 색상을 복사합니다. + 팔레트 생성 및 색상 라이브러리 사용 + 팔레트를 추출하고, 저장된 색상을 찾아보고, 일관된 시각적 시스템을 유지하세요. + 재사용 가능한 팔레트 만들기 + 팔레트는 내보낸 그래픽, 워터마크 및 그림의 시각적 일관성을 유지하는 데 도움이 됩니다. 스크린샷에 반복적으로 주석을 추가하거나, 브랜드 자산을 준비하거나, 여러 도구에서 동일한 색상이 필요할 때 특히 유용합니다. + 이미지에서 팔레트를 생성하거나 값을 이미 알고 있는 경우 수동으로 색상을 추가하세요. + 나중에 다시 찾을 수 있도록 중요한 색상의 이름을 지정하거나 정리하세요. + 그리기, 워터마크, 그라데이션, SVG 또는 외부 디자인 도구에서 복사된 값을 사용합니다. + 그라데이션 만들기 + 배경, 자리 표시자 및 시각적 실험을 위한 그라데이션 이미지 구축 + 색상 전환 제어 + 그라디언트 도구는 기존 이미지를 편집하는 대신 생성된 이미지가 필요할 때 유용합니다. 깨끗한 배경, 자리 표시자, 배경 화면, 마스크 또는 외부 디자인 도구를 위한 간단한 자산이 될 수 있습니다. + 그라데이션 유형을 선택하고 중요한 색상을 먼저 설정하세요. + 대상 사용 사례에 맞게 방향, 중지, 부드러움 및 출력 크기를 조정합니다. + 대상과 일치하는 형식으로 내보내십시오. 일반적으로 깔끔하게 생성된 그래픽의 경우 PNG입니다. + 색상 접근성 + UI를 읽기 어려운 경우 동적 색상, 대비 및 색맹 옵션을 사용하세요. + 색상을 사용하기 쉽게 만들기 + 색상 설정은 편안함, 가독성 및 컨트롤 스캔 속도에 영향을 미칩니다. 도구 칩, 슬라이더 또는 선택한 상태를 구별하기 어려운 경우 단순히 어두운 모드를 변경하는 것보다 테마 및 접근성 옵션이 더 유용할 수 있습니다. + 앱이 현재 배경화면 팔레트를 따르도록 하려면 동적 색상을 사용해 보세요. + 버튼과 표면이 충분히 눈에 띄지 않으면 대비를 높이거나 구성을 변경하세요. + 일부 주나 액센트를 구별하기 어려운 경우 색맹 구성표 옵션을 사용하십시오. + 알파 배경 + 투명한 PNG, WebP 및 유사한 출력 주위에 사용되는 미리보기 또는 채우기 색상을 제어합니다. + 투명한 미리보기 이해 + 알파 형식의 배경색을 사용하면 투명한 이미지를 더 쉽게 검사할 수 있지만 미리 보기/채우기 동작을 변경하는지 아니면 최종 결과를 병합하는지 아는 것이 중요합니다. 이는 스티커, 로고, 배경이 제거된 이미지에 중요합니다. + 투명 픽셀을 내보낸 후에도 유지해야 하는 경우에는 PNG 또는 WebP와 같은 알파 지원 형식을 먼저 사용하십시오. + 앱 표면에서 투명한 가장자리가 잘 보이지 않으면 배경색 설정을 조정하세요. + 작은 테스트를 내보내고 다른 앱에서 다시 열어 투명도나 선택한 채우기가 저장되었는지 확인하세요. + AI 모델 선택 + 가장 큰 것을 먼저 선택하는 대신 이미지 유형 및 결함별로 모델을 선택하십시오. + 모델을 손상 부위와 일치시키세요. + AI 도구는 다양한 ONNX/ORT 모델을 다운로드하거나 가져올 수 있으며 각 모델은 특정 종류의 문제에 대해 훈련됩니다. JPEG 블록, 애니메이션 라인 정리, 노이즈 제거, 배경 제거, 색상화 또는 업스케일링 모델을 잘못된 이미지 유형에 사용할 경우 더 나쁜 결과가 발생할 수 있습니다. + 다운로드하기 전에 모델 설명을 읽어보세요. 압축, 노이즈, 하프톤, 흐림, 배경 제거 등 실제 문제를 나타내는 모델을 선택하세요. + 새로운 이미지 유형을 테스트할 때 더 작거나 더 구체적인 모델로 시작한 다음 결과가 충분하지 않은 경우에만 더 무거운 모델과 비교하십시오. + 다운로드하고 가져온 모델은 저장 공간을 많이 차지할 수 있으므로 실제로 사용하는 모델만 보관하세요. + 모델 패밀리 이해 + 모델 이름은 종종 목표를 암시합니다. RealESRGAN 스타일 모델은 고급화되고, SCUNet 및 FBCNN 모델은 노이즈 또는 압축을 제거하고, 배경 모델은 마스크를 생성하고, 컬러라이저는 회색조 입력에 색상을 추가합니다. 가져온 사용자 정의 모델은 강력할 수 있지만 예상되는 입력/출력 형태와 일치하고 지원되는 ONNX 또는 ORT 형식을 사용해야 합니다. + 더 많은 픽셀이 필요한 경우 업스케일러를 사용하고, 이미지가 거칠면 디노이저를 사용하고, 압축 블록이나 밴딩이 주요 문제인 경우 아티팩트 제거기를 사용하십시오. + 피사체 분리를 위해서만 배경 모델을 사용하십시오. 이는 일반적인 개체 제거 또는 이미지 향상과 동일하지 않습니다. + 신뢰할 수 있는 소스에서만 사용자 정의 모델을 가져오고 중요한 파일을 사용하기 전에 작은 이미지에서 테스트하세요. + AI 매개변수 + 청크 크기, 중첩 및 병렬 작업자를 조정하여 품질, 속도 및 메모리의 균형을 유지하세요. + 속도와 안정성의 균형 + 청크 크기는 모델에서 처리되기 전 이미지 조각의 크기를 제어합니다. Overlap은 인접한 청크를 혼합하여 테두리를 숨깁니다. 병렬 워커는 처리 속도를 높일 수 있지만 각 워커에는 메모리가 필요하므로 값이 높으면 RAM이 제한된 휴대폰에서는 큰 이미지가 불안정해질 수 있습니다. + 장치가 모델을 안정적으로 처리하고 이미지가 크지 않은 경우 보다 원활한 컨텍스트를 위해 더 큰 청크를 사용하십시오. + 청크 테두리가 보이면 겹치는 부분을 늘리세요. 하지만 겹치는 부분이 많으면 작업이 더 많이 반복된다는 점을 기억하세요. + 안정적인 테스트 후에만 병렬 작업자를 올립니다. 처리 속도가 느려지거나 장치가 과열되거나 충돌이 발생하면 먼저 작업자를 줄이십시오. + 청크 아티팩트 방지 + 청킹을 사용하면 모델이 한 번에 편안하게 처리할 수 있는 것보다 더 큰 이미지를 앱에서 처리할 수 있습니다. 단점은 각 타일의 주변 상황이 적기 때문에 겹치는 부분이 적거나 청크가 너무 작으면 경계가 눈에 띄거나 텍스처가 변경되거나 인접 영역 간에 일관되지 않은 세부 정보가 생성될 수 있다는 것입니다. + 직사각형 테두리, 반복되는 텍스처 변경 또는 처리된 영역 간의 세부 사항 점프가 보이는 경우 오버랩을 늘립니다. + 특히 큰 이미지나 무거운 모델의 경우 출력을 생성하기 전에 프로세스가 실패하는 경우 청크 크기를 줄입니다. + 전체 이미지를 처리하기 전에 작은 자르기 테스트를 저장하면 매개변수를 빠르게 비교할 수 있습니다. + AI 안정성 + AI 처리가 중단되거나 정지될 때 청크 크기, 작업자 및 소스 해상도가 낮아집니다. + 과도한 AI 작업에서 복구 + AI 처리는 앱에서 가장 무거운 작업 흐름 중 하나입니다. 충돌, 세션 실패, 정지 또는 갑작스러운 앱 재시작은 일반적으로 모델, 소스 해상도, 청크 크기 또는 병렬 작업자 수가 현재 장치 상태에 비해 너무 까다롭다는 것을 의미합니다. + 앱이 충돌하거나 모델 세션을 열지 못하는 경우 병렬 작업자와 청크 크기를 낮추고 동일한 이미지를 다시 시도하십시오. + 목표에 원래 해상도가 필요하지 않은 경우 AI 처리 전에 매우 큰 이미지의 크기를 조정합니다. + 메모리 부족이 계속 발생하면 다른 무거운 앱을 닫거나, 더 작은 모델을 사용하거나, 한 번에 더 적은 수의 파일을 처리하세요. + 모델 실패 진단 + AI 실행이 실패했다고 해서 항상 이미지가 좋지 않다는 의미는 아닙니다. 모델 파일이 불완전하거나 지원되지 않거나 메모리가 너무 크거나 작업과 일치하지 않을 수 있습니다. 앱이 실패한 세션을 보고하면 이를 신호로 처리하여 먼저 모델과 매개변수를 단순화합니다. + 다운로드 직후 실패하거나 파일이 손상된 것처럼 작동하는 경우 모델을 삭제하고 다시 다운로드하십시오. + 가져온 모델에 호환되지 않는 입력이 있을 수 있으므로 가져온 사용자 지정 모델을 비난하기 전에 내장된 다운로드 가능한 모델을 사용해 보세요. + 충돌이나 세션 오류를 보고해야 하는 경우 실패를 재현한 후 앱 로그를 사용하세요. + 셰이더 스튜디오에서 실험하기 + 고급 이미지 처리 및 사용자 정의 모양을 위해 GPU 셰이더 효과 사용 + 셰이더를 신중하게 사용하세요. + Shader Studio는 강력하지만 셰이더 코드와 매개변수에는 테스트 가능한 작은 변경이 필요합니다. 실험 도구처럼 사용하세요. 작업 기준을 유지하고, 한 번에 하나씩 변경하고, 사전 설정을 신뢰하기 전에 다양한 이미지 크기로 테스트하세요. + 매개변수를 추가하기 전에 알려진 작동 셰이더나 간단한 효과부터 시작하세요. + 한 번에 하나의 매개변수나 코드 블록을 변경하고 미리보기에서 오류를 확인하세요. + 몇 가지 다른 이미지 크기에서 사전 설정을 테스트한 후에만 사전 설정을 내보냅니다. + SVG 또는 ASCII 아트 만들기 + 창의적인 출력을 위해 벡터와 유사한 자산 또는 텍스트 기반 이미지를 생성합니다. + 광고 소재 출력 유형 선택 + SVG 및 ASCII 도구는 확장 가능한 그래픽 또는 텍스트 스타일 렌더링과 같은 다양한 대상을 제공합니다. 둘 다 창의적인 변환이므로 작은 썸네일로 결과를 판단하는 것보다 최종 크기로 미리 보는 것이 더 중요합니다. + 결과의 크기를 깔끔하게 조정하거나 디자인 작업 흐름에서 재사용해야 하는 경우 SVG Maker를 사용하세요. + 이미지를 스타일화된 텍스트로 표현하려면 ASCII Art를 사용하세요. + 생성된 출력에서 ​​작은 세부 사항이 사라질 수 있으므로 최종 크기로 미리 봅니다. + 노이즈 텍스처 생성 + 배경, 마스크, 오버레이 또는 실험을 위한 절차적 텍스처 만들기 + 절차적 출력 조정 + 노이즈 생성은 매개변수로부터 새로운 이미지를 생성하므로 작은 변화로 인해 매우 다른 결과가 생성될 수 있습니다. 텍스처, 마스크, 오버레이, 자리 표시자 또는 세부 패턴으로 압축을 테스트하는 데 유용합니다. + 미세한 세부 사항을 조정하기 전에 노이즈 유형과 출력 크기를 선택하십시오. + 패턴이 대상 용도에 맞을 때까지 규모, 강도, 색상 또는 시드를 조정합니다. + 나중에 텍스처를 다시 생성해야 하는 경우 유용한 결과를 저장하고 매개변수를 적어 두십시오. + 마크업 프로젝트 + 앱을 닫은 후에도 주석을 편집 가능한 상태로 유지해야 하는 경우 프로젝트 파일을 사용하세요. + 계층화된 편집 내용을 재사용 가능하게 유지 + 마크업 프로젝트 파일은 나중에 스크린샷, 다이어그램 또는 지침 이미지를 변경해야 할 때 유용합니다. 내보낸 PNG/JPEG 파일은 최종 이미지이며, 프로젝트 파일은 향후 조정을 위해 편집 가능한 레이어와 편집 상태를 유지합니다. + 주석, 콜아웃 또는 레이아웃 요소를 편집 가능한 상태로 유지해야 하는 경우 마크업 레이어를 사용합니다. + 더 많은 개정이 필요한 경우 최종 이미지를 내보내기 전에 프로젝트 파일을 저장하거나 다시 엽니다. + 병합된 최종 버전을 공유할 준비가 된 경우에만 일반 이미지를 내보냅니다. + 파일 암호화 + 소스 사본을 삭제하기 전에 비밀번호로 파일을 보호하고 암호 해독을 테스트하세요. + 암호화된 출력을 신중하게 처리 + 암호화 도구는 비밀번호 기반 암호화로 파일을 보호할 수 있지만 키 기억이나 백업 유지를 대체할 수는 없습니다. 암호화는 전송 및 저장에 유용한 반면, 주요 목표가 여러 출력을 묶는 경우 ZIP이 더 좋습니다. + 먼저 사본을 암호화하고 저장한 비밀번호로 암호 해독이 작동하는지 테스트할 때까지 원본을 보관하세요. + 비밀번호 관리자나 키를 보관할 수 있는 다른 안전한 장소를 사용하세요. 앱은 분실한 비밀번호를 추측할 수 없습니다. + 암호화된 파일은 비밀번호를 알고 있고 어떤 도구나 방법으로 파일을 해독해야 하는지 알고 있는 사람들에게만 공유하세요. + 도구 정렬 + 기본 화면이 실제 작업흐름과 일치하도록 도구를 그룹화하고, 주문하고, 검색하고 고정합니다. + 메인 화면을 더 빠르게 만들기 + 어떤 도구를 가장 많이 사용하는지 알고 나면 도구 배열 설정을 조정할 가치가 있습니다. 그룹화는 탐색에 도움이 되고, 사용자 정의 순서는 근육 기억에 도움이 되며, 즐겨찾기는 전체 목록이 커져도 매일 도구에 접근할 수 있도록 유지합니다. + 앱을 학습하는 동안 또는 작업 유형별로 정리된 도구를 선호하는 경우 그룹 모드를 사용하세요. + 자주 사용하는 도구를 이미 알고 있고 스크롤 횟수를 줄이고 싶다면 맞춤 주문으로 전환하세요. + 이름만 기억하지만 항상 표시하고 싶지 않은 희귀한 도구에 대해 검색 설정과 즐겨찾기를 함께 사용하세요. + 대용량 파일 처리 + 느린 내보내기, 메모리 부족, 예상치 못한 큰 출력 방지 + 수출 전 작업 감소 + 매우 큰 이미지, 긴 배치 및 고품질 형식은 더 많은 메모리와 시간을 차지할 수 있습니다. 가장 빠른 수정은 일반적으로 동일한 대형 입력이 완료될 때까지 기다리지 않고 가장 무거운 작업 전에 작업량을 줄이는 것입니다. + 무거운 필터, OCR 또는 PDF 변환을 적용하기 전에 큰 이미지의 크기를 조정하십시오. + 먼저 더 작은 배치를 사용하여 설정을 확인한 다음 동일한 사전 설정으로 나머지를 처리합니다. + 출력 파일이 예상보다 크면 품질이 낮아지거나 형식이 변경됩니다. + 캐시 및 미리보기 + 생성된 미리보기, 캐시 정리, 과도한 세션 후 저장소 사용 이해 + 스토리지와 속도의 균형 유지 + 미리보기 생성 및 캐시를 사용하면 반복 검색 속도가 빨라지지만 과도한 편집 세션으로 인해 임시 데이터가 남을 수 있습니다. 캐시 설정은 앱이 느리다고 느껴지거나, 저장 공간이 부족하거나, 많은 실험을 거친 후 미리 보기가 오래되어 보일 때 도움이 됩니다. + 많은 이미지를 탐색할 때 미리보기를 활성화하는 것이 임시 저장 공간을 최소화하는 것보다 더 중요합니다. + 대규모 배치, 실패한 실험 또는 많은 고해상도 파일이 포함된 긴 세션 후에 캐시를 지웁니다. + 실행 사이에 미리 보기를 따뜻하게 유지하는 것보다 저장소 정리를 선호하는 경우 자동 캐시 지우기를 사용하세요. + 화면 동작 조정 + 집중적인 작업을 위해 전체 화면, 보안 모드, 밝기, 시스템 표시줄 옵션을 사용하세요. + 인터페이스를 상황에 맞게 만들어라 + 화면 설정은 가로 방향으로 편집하거나 개인 이미지를 표시하거나 일관된 밝기가 필요할 때 도움이 됩니다. 일반적인 사용에는 필요하지 않지만 QR 검사, 비공개 미리보기 또는 비좁은 가로 편집 등 어색한 상황을 해결합니다. + 탐색 크롬보다 편집 공간이 더 중요한 경우 전체 화면 또는 시스템 표시줄 설정을 사용하세요. + 비공개 이미지가 스크린샷이나 앱 미리보기에 표시되어서는 안 되는 경우 보안 모드를 사용하세요. + 어두운 이미지나 QR 코드를 검사하기 어려운 도구에는 밝기 적용을 사용하세요. + 투명성 유지 + 투명한 배경이 흰색, 검정색 또는 평면화되는 것을 방지합니다. + 알파 인식 출력 사용 + 투명성은 선택한 도구와 출력 형식이 알파를 지원하는 경우에만 유지됩니다. 내보낸 배경이 흰색이나 검은색으로 변하는 경우 일반적으로 문제는 출력 형식, 채우기/배경 설정 또는 이미지를 병합한 대상 앱입니다. + 배경이 제거된 이미지, 스티커, 로고, 오버레이를 내보낼 때 PNG 또는 WebP를 선택하세요. + JPEG는 알파 채널을 저장하지 않으므로 투명한 출력에는 사용하지 마십시오. + 미리보기에 채워진 배경이 표시되면 알파 형식의 배경색과 관련된 설정을 확인하세요. + 공유 및 가져오기 문제 해결 + 파일이 표시되지 않거나 올바르게 열리지 않는 경우 선택기 설정 및 지원되는 형식을 사용하세요. + 파일을 앱으로 가져오기 + 가져오기 문제는 공급자 권한, 지원되지 않는 형식 또는 선택기 동작으로 인해 발생하는 경우가 많습니다. 클라우드 앱 및 메신저에서 공유된 파일은 일시적일 수 있으므로 장기간 편집 시 영구 소스를 선택하는 것이 더 안정적일 수 있습니다. + 최근 파일 바로가기 대신 시스템 선택기를 통해 파일을 열어보세요. + 하나의 도구에서 형식을 지원하지 않는 경우 먼저 변환하거나 보다 구체적인 도구를 엽니다. + 공유 흐름이나 직접 도구 열기가 예상과 다르게 작동하는 경우 이미지 선택기 및 실행기 설정을 검토하세요. + 종료 확인 + 제스처, 뒤로 누르기 또는 도구 전환이 실수로 발생하는 경우 저장되지 않은 편집 내용이 손실되지 않도록 방지 + 완료되지 않은 작업을 보호하세요 + 도구 종료 확인은 동작 탐색을 사용하거나, 대용량 파일을 편집하거나, 앱 간에 자주 전환할 때 유용합니다. 완료되지 않은 설정과 미리 보기가 실수로 손실되는 일이 없도록 도구를 종료하기 전에 약간의 일시 중지를 추가합니다. + 내보내기 전에 실수로 뒤로 동작으로 인해 도구가 닫힌 경우 확인을 활성화합니다. + 주로 빠른 한 단계 변환을 수행하고 더 빠른 탐색을 선호하는 경우 비활성화된 상태로 유지하십시오. + 설정을 다시 작성하는 것이 번거로울 수 있는 더 긴 작업 흐름을 위해 사전 설정과 함께 사용하세요. + 문제 보고 시 로그 사용 + 문제를 열거나 개발자에게 문의하기 전에 유용한 세부정보를 수집하세요. + 맥락 관련 문제 신고 + 단계, 앱 버전, 입력 유형 및 로그가 포함된 명확한 보고서는 진단하기가 훨씬 쉽습니다. 로그는 주변 컨텍스트를 최신 상태로 유지하므로 문제를 재현한 직후 가장 유용합니다. + 도구 이름, 정확한 동작, 하나의 파일에서 발생하는지 모든 파일에서 발생하는지 여부를 기록해 두십시오. + 문제를 재현한 후 앱 로그를 열고 가능하면 관련 로그 출력을 공유하세요. + 개인 정보가 포함되지 않은 경우에만 스크린샷이나 샘플 파일을 첨부하세요. + 백그라운드 처리가 중지되었습니다. + Android에서는 백그라운드 처리 알림이 제 시간에 시작되지 않았습니다. 이는 안정적으로 수정할 수 없는 시스템 타이밍 제한입니다. 앱을 다시 시작한 후 다시 시도해 보세요. 앱을 열어두고 알림이나 백그라운드 작업을 허용하면 도움이 될 수 있습니다. + 파일 선택 건너뛰기 + 일반적으로 공유, 클립보드 또는 이전 화면에서 입력을 받을 때 도구를 더 빠르게 엽니다. + 반복되는 도구 실행을 더 빠르게 만들기 + 파일 선택 건너뛰기는 지원되는 도구의 첫 번째 단계를 변경합니다. 일반적으로 이미 선택된 이미지가 포함된 도구를 입력하거나 Android 공유 작업을 통해 도구를 입력할 때 유용하지만 모든 도구에서 즉시 파일을 요청하도록 기대하면 혼란스러울 수 있습니다. + 도구가 열리기 전에 일반적인 흐름이 이미 소스 이미지를 제공하는 경우에만 활성화하십시오. + 명시적인 선택을 통해 모든 도구 흐름을 더 쉽게 이해할 수 있으므로 앱을 학습하는 동안 비활성화된 상태로 유지하세요. + 명확한 입력 없이 도구가 열리는 경우 이 설정을 비활성화하거나 다음으로 공유/열기에서 시작하여 Android가 파일을 먼저 전달하도록 하세요. + 먼저 편집기를 엽니다 + 공유 이미지를 바로 편집해야 하는 경우 미리보기를 건너뜁니다. + 첫 번째 중지로 미리보기 또는 편집을 선택하세요. + 미리보기 대신 편집 열기는 이미지 수정을 원하는 사용자를 위한 기능입니다. 채팅이나 클라우드 저장소에서 파일을 검사할 때 미리보기가 더 안전합니다. 스크린샷, 빠른 자르기, 주석 및 일회성 수정의 경우 직접 편집이 더 빠릅니다. + 대부분의 공유 이미지에 즉시 자르기, 마크업, 필터 또는 내보내기 변경이 필요한 경우 직접 편집을 활성화하십시오. + 메타데이터, 크기, 투명도 또는 이미지 품질을 자주 검사하는 경우 결정하기 전에 미리보기를 먼저 남겨두세요. + 즐겨찾기와 함께 사용하면 공유된 이미지가 실제로 사용하는 도구에 가깝게 표시됩니다. + 사전 설정된 텍스트 입력 + 컨트롤을 반복적으로 조정하는 대신 정확한 사전 설정 값을 입력하세요. + 슬라이더가 느릴 때 정확한 값을 사용하십시오. + 미리 설정된 텍스트 입력은 소셜 이미지 크기, 문서 여백, 압축 대상, 선 너비 또는 제스처로 접근하기 어려운 기타 값과 같이 반복 작업을 위해 정확한 숫자가 필요할 때 도움이 됩니다. 미세한 슬라이더 움직임이 어려운 작은 화면에서도 유용합니다. + 정확한 크기, 백분율, 품질 값 또는 도구 매개변수를 자주 재사용하는 경우 텍스트 입력을 활성화하십시오. + 값을 한 번 입력한 후 사전 설정으로 저장한 다음 동일한 설정을 다시 작성하는 대신 다시 사용하세요. + 대략적인 시각적 조정을 위한 제스처 컨트롤과 엄격한 출력 요구 사항에 대한 입력된 값을 유지합니다. + 원본 근처에 저장 + 폴더 컨텍스트가 중요한 경우 생성된 파일을 소스 이미지 옆에 배치 + 출력을 소스와 함께 유지 + 원본 폴더에 저장은 편집된 복사본이 소스 옆에 있어야 하는 카메라 폴더, 프로젝트 폴더, 문서 스캔 및 클라이언트 배치에 유용합니다. 전용 출력 폴더보다 덜 깔끔할 수 있으므로 명확한 파일 이름 접미사 또는 패턴과 결합하세요. + 각 소스 폴더가 이미 의미가 있고 출력이 거기에 그룹화되어 있어야 하는 경우 활성화하십시오. + 파일 이름 접미사, 접두사, 타임스탬프 또는 패턴을 사용하면 편집된 사본을 원본과 쉽게 분리할 수 있습니다. + 생성된 모든 파일을 하나의 내보내기 폴더에 수집하려면 혼합 배치에 대해 이 기능을 비활성화하십시오. + 더 큰 출력 건너뛰기 + 예기치 않게 소스보다 무거워지는 전환을 저장하지 마세요. + 예상치 못한 크기로 인한 배치 보호 + 일부 변환은 특히 PNG 스크린샷, 이미 압축된 사진, 고품질 WebP 또는 메타데이터가 보존된 이미지 등의 파일을 더 크게 만듭니다. 더 큰 경우 건너뛰기 허용은 크기를 줄이는 것이 주요 목표인 워크플로우에서 해당 출력이 더 작은 소스를 대체하는 것을 방지합니다. + 내보내기가 업로드 제한에 맞게 파일을 압축, 크기 조정 또는 준비하려는 경우 활성화합니다. + 일괄 처리 후 건너뛴 파일을 검토합니다. 이미 최적화되었거나 다른 형식이 필요할 수 있습니다. + 최종 파일 크기보다 품질, 투명성 또는 형식 호환성이 더 중요한 경우에는 비활성화하세요. + 클립보드 및 링크 + 더 빠른 텍스트 기반 도구를 위해 자동 클립보드 입력 및 링크 미리 보기를 제어합니다. + 자동 입력을 예측 가능하게 만들기 + 클립보드 및 링크 설정은 QR, Base64, URL 및 텍스트가 많은 작업 흐름에 편리하지만 자동 입력은 의도적으로 유지되어야 합니다. 앱이 자동으로 필드를 채우는 것처럼 보이면 클립보드 붙여넣기 동작을 확인하세요. 링크 카드가 시끄럽거나 느리게 느껴지면 링크 미리보기 동작을 조정하세요. + 텍스트를 자주 복사하고 즉시 일치 도구를 열 때 자동 클립보드 붙여넣기를 허용합니다. + 개인 클립보드 내용이 예상치 못한 도구에 나타나는 경우 자동 붙여넣기를 비활성화합니다. + 미리 보기 가져오기가 느리거나 방해가 되거나 작업 흐름에 불필요한 경우 링크 미리 보기를 끄세요. + 컴팩트한 컨트롤 + 화면 공간이 부족할 때 더 조밀한 선택기와 빠른 설정 배치를 사용하십시오. + 작은 화면에 더 많은 컨트롤을 장착하세요 + 컴팩트한 선택기와 빠른 설정 배치는 소형 휴대폰, 가로 편집 및 매개변수가 많은 도구에 도움이 됩니다. 단점은 컨트롤이 더 조밀하게 느껴질 수 있다는 것입니다. 따라서 일반적인 옵션을 이미 인식한 후에 이것이 가장 좋습니다. + 대화 상자나 옵션 목록이 화면에 비해 너무 크다고 느껴질 때 컴팩트 선택기를 활성화하세요. + 디스플레이 한쪽에서 도구 컨트롤에 쉽게 접근할 수 있는 경우 빠른 설정을 조정하세요. + 아직 앱을 배우는 중이거나 밀집된 옵션을 자주 놓치는 경우 더 넓은 컨트롤로 돌아가세요. + 웹에서 이미지 로드 + 페이지 또는 이미지 URL을 붙여넣고 구문 분석된 이미지를 미리 본 다음 선택한 결과를 저장하거나 공유하세요. + 의도적으로 웹 이미지 사용 + 웹 이미지 로딩은 제공된 URL에서 이미지 소스를 구문 분석하고, 사용 가능한 첫 번째 이미지를 미리 보고, 선택한 구문 분석된 이미지를 저장하거나 공유할 수 있도록 해줍니다. 일부 사이트는 임시, 보호 또는 스크립트 생성 링크를 사용하므로 브라우저에서 작동하는 페이지는 여전히 사용 가능한 이미지를 반환하지 않을 수 있습니다. + 직접 이미지 URL 또는 페이지 URL을 붙여넣고 구문 분석된 이미지 목록이 나타날 때까지 기다립니다. + 페이지에 여러 이미지가 포함되어 있고 그 중 일부만 필요한 경우 프레임 또는 선택 컨트롤을 사용하십시오. + 아무 것도 로드되지 않으면 직접 이미지 링크를 시도하거나 먼저 브라우저를 통해 다운로드하십시오. 사이트에서 구문 분석을 차단하거나 개인 링크를 사용할 수 있습니다. + 크기 조정 유형 선택 + 이미지를 새로운 차원으로 만들기 전에 명시적, 유연함, 자르기 및 맞춤을 이해하세요. + 모양, 캔버스 및 종횡비 제어 + 크기 조정 유형은 요청된 너비와 높이가 원래 가로 세로 비율과 일치하지 않을 때 발생하는 상황을 결정합니다. 픽셀 늘리기, 비율 유지, 추가 영역 자르기 또는 배경 캔버스 추가 간의 차이입니다. + 왜곡이 허용되거나 소스가 이미 대상과 동일한 종횡비를 갖고 있는 경우에만 명시적을 사용하십시오. + 특히 사진과 혼합 배치의 경우 중요한 쪽의 크기를 조정하여 비율을 유지하려면 유연성을 사용하세요. + 테두리가 없는 엄격한 프레임에는 자르기를 사용하고, 고정된 캔버스 안에 전체 이미지가 계속 표시되어야 하는 경우 맞춤을 사용합니다. + 이미지 크기 조정 모드 선택 + 선명도, 부드러움, 속도 및 가장자리 아티팩트를 제어하는 ​​리샘플링 필터를 선택하세요. + 실수로 인한 부드러움 없이 크기 조정 + 이미지 크기 조정 모드는 크기 조정 중에 새 픽셀이 계산되는 방식을 제어합니다. 단순 모드는 빠르고 예측 가능한 반면, 고급 필터는 세부 사항을 더 잘 보존할 수 있지만 가장자리를 선명하게 하고 후광을 생성하거나 큰 이미지의 경우 시간이 더 오래 걸릴 수 있습니다. + 결과가 더 작고 깨끗해야 하는 경우 매일 빠르게 크기를 조정하려면 기본 또는 이중선형을 사용하세요. + 미세한 디테일, 텍스트 또는 고품질 축소가 중요한 경우 Lanczos, Robidoux, Spline 또는 EWA 스타일 모드를 사용하십시오. + 흐릿한 픽셀이 모양을 망칠 수 있는 픽셀 아트, 아이콘 및 가장자리가 선명한 그래픽에는 가장 가까운 것을 사용하십시오. + 스케일 색상 공간 + 크기 조정 중에 픽셀이 리샘플링되는 동안 색상이 혼합되는 방식을 결정합니다. + 그라데이션과 가장자리를 자연스럽게 유지하세요. + 스케일 색상 공간은 크기를 조정하는 동안 사용되는 수학을 변경합니다. 대부분의 이미지는 기본값으로도 문제가 없지만 선형 또는 지각 공간은 강력한 크기 조정 후에 그라데이션, 어두운 가장자리 및 채도가 높은 색상을 더 깔끔하게 보이게 만들 수 있습니다. + 작은 색상 차이보다 속도와 호환성이 더 중요하다면 기본값을 그대로 두십시오. + 크기 조정 후 어두운 윤곽선, UI 스크린샷, 그라데이션 또는 색상 띠가 잘못 보이는 경우 선형 또는 지각 모드를 사용해 보세요. + 색상 공간 변화는 미묘하고 내용에 따라 달라질 수 있으므로 실제 대상 화면에서 전후를 비교하십시오. + 크기 조정 모드 제한 + 제한을 초과한 이미지를 건너뛰거나, 기록하거나, 크기에 맞게 축소해야 하는 경우를 파악하세요. + 한계에서 무슨 일이 일어날지 선택하세요 + 크기 조정 제한은 더 작은 이미지 도구만은 아닙니다. 해당 모드는 소스가 이미 제한 내에 있거나 한쪽에서만 규칙을 위반할 때 앱이 수행하는 작업을 결정합니다. 이는 혼합 폴더 및 업로드 준비에 중요합니다. + 제한 내에 있는 이미지를 그대로 두고 다시 내보내지 않으려면 건너뛰기를 사용하세요. + 크기가 허용되지만 여전히 새로운 형식, 메타데이터 동작, 품질 또는 파일 이름이 필요한 경우 레코딩을 사용하십시오. + 한계를 넘는 이미지를 허용된 상자에 맞을 때까지 비례적으로 축소해야 하는 경우 확대/축소를 사용하십시오. + 가로세로 비율 타겟 + 엄격한 출력 크기에서 늘어난 면, 잘린 가장자리 및 예상치 못한 테두리를 방지하세요. + 크기를 조정하기 전에 모양을 일치시키세요. + 너비와 높이는 크기 조정 대상의 절반에 불과합니다. 종횡비는 이미지가 자연스럽게 들어갈 수 있는지, 잘라야 하는지, 주변에 캔버스가 필요한지 결정합니다. 모양을 먼저 확인하면 가장 놀라운 크기 조정 결과를 방지할 수 있습니다. + 명시적, 자르기 또는 맞춤을 ​​선택하기 전에 소스 모양을 대상 모양과 비교하십시오. + 피사체가 추가 배경을 잃을 수 있고 대상에 엄격한 프레임이 필요한 경우 먼저 자릅니다. + 원본 이미지의 모든 부분을 계속 표시해야 하는 경우 맞춤 또는 유연함을 사용하세요. + 고급 또는 일반 크기 조정 + 픽셀을 추가할 때 AI가 필요한지, 간단한 크기 조정만으로 충분한지 파악하세요. + 사이즈와 디테일을 혼동하지 마세요 + 일반적인 크기 조정은 픽셀을 보간하여 치수를 변경합니다. 실제 세부 사항을 발명할 수는 없습니다. AI 업스케일은 더욱 선명해 보이는 디테일을 만들 수 있지만 속도가 느리고 질감이 환각적일 수 있으므로 호환성, 속도 또는 시각적 복원이 필요한지 여부에 따라 올바른 선택이 달라집니다. + 썸네일, 업로드 제한, 스크린샷 및 간단한 크기 변경에는 일반 크기 조정을 사용하세요. + 작거나 부드러운 이미지가 더 큰 크기에서 더 잘 보이도록 해야 하는 경우 AI 업스케일을 사용하세요. + 생성된 세부 정보가 설득력 있게 보일 수 있지만 부정확할 수 있으므로 AI 업스케일 후 얼굴, 텍스트 및 패턴을 비교하세요. + 필터 순서가 중요합니다 + 의도적인 순서에 따라 정리, 색상, 선명도 및 최종 압축을 적용합니다. + 더 깨끗한 필터 스택 구축 + 필터가 항상 교체 가능한 것은 아닙니다. 선명하게 하기 전의 흐림, OCR 전의 대비 향상 또는 압축 전의 색상 변경은 동일한 컨트롤에서도 매우 다른 출력을 생성할 수 있습니다. + 먼저 자르고 회전하면 나중에 필터가 남아 있는 픽셀에서만 작동합니다. + 선명하게 하거나 스타일화된 효과를 적용하기 전에 노출, 화이트 밸런스, 대비를 적용하세요. + 최종 크기 조정 및 압축을 거의 끝 부분에 유지하여 미리 본 세부 정보가 예상대로 내보낼 수 있도록 합니다. + 배경 가장자리 수정 + 자동 제거 후 후광, 남은 그림자, 머리카락, 구멍 및 부드러운 윤곽선을 정리합니다. + 컷아웃을 의도적으로 보이도록 만들기 + 자동 마스크는 한 눈에 보기에는 괜찮아 보이지만 밝거나 어두우거나 패턴이 있는 배경에서는 문제가 드러납니다. 가장자리 정리는 빠른 컷아웃과 재사용 가능한 스티커, 로고 또는 제품 이미지의 차이입니다. + 대비되는 배경과 비교하여 결과를 미리 보고 후광과 누락된 가장자리 세부 정보를 확인하세요. + 주변의 남은 부분을 지우기 전에 머리카락, 모서리 및 투명한 물체에 대한 복원을 사용하십시오. + 컷아웃을 디자인에 배치할 때 최종 배경색에 대한 작은 테스트를 내보냅니다. + 읽을 수 있는 워터마크 + 사진을 망치지 않고 밝고 어둡고 바쁜 이미지와 잘린 이미지에 표시를 표시합니다. + 이미지를 숨기지 않고 보호하세요 + 한 이미지에서는 좋아 보이는 워터마크가 다른 이미지에서는 사라질 수도 있습니다. 첫 번째 미리보기뿐만 아니라 전체 배치에 대해 크기, 불투명도, 대비, 배치 및 반복을 선택해야 합니다. + 모든 파일을 내보내기 전에 배치에서 가장 밝은 이미지와 가장 어두운 이미지에 대한 표시를 테스트하십시오. + 큰 반복 표시에는 낮은 불투명도를 사용하고 작은 모서리 표시에는 강한 대비를 사용합니다. + 재사용을 방지하기 위한 표시가 아닌 이상 중요한 얼굴, 텍스트, 제품 세부정보를 명확하게 유지하세요. + 하나의 이미지를 타일로 분할 + 행, 열, 사용자 지정 비율, 형식, 품질별로 단일 이미지를 잘라냅니다. + 그리드와 주문한 조각 준비 + 이미지 분할은 하나의 소스 이미지에서 여러 출력을 생성합니다. 행 및 열 개수로 분할하고, 행 또는 열 비율을 조정하고, 선택한 출력 형식 및 품질로 조각을 저장할 수 있습니다. 이는 그리드, 페이지 조각, 파노라마 및 정렬된 소셜 게시물에 유용합니다. + 소스 이미지를 선택한 다음 필요한 행과 열 수를 설정하세요. + 조각의 크기가 모두 동일하지 않아야 하는 경우 행 또는 열 비율을 조정합니다. + 생성된 모든 작품이 동일한 내보내기 규칙을 사용하도록 저장하기 전에 출력 형식과 품질을 선택하세요. + 이미지 스트립 잘라내기 + 수직 또는 수평 영역을 제거하고 나머지 부분을 다시 병합합니다. + 간격을 두지 않고 영역 제거 + 이미지 커팅은 일반 크롭과 다릅니다. 선택한 수직 또는 수평 스트립을 제거한 다음 나머지 이미지 부분을 병합할 수 있습니다. 역방향 모드는 선택한 영역을 대신 유지하고 두 역방향을 모두 사용하면 선택한 직사각형이 유지됩니다. + 측면 영역, 기둥 또는 중간 스트립에는 수직 절단을 사용하십시오. 배너, 간격 또는 행에는 수평 절단을 사용하십시오. + 선택한 부분을 제거하는 대신 유지하려는 경우 축에서 반전을 활성화합니다. + 제거된 영역이 중요한 세부 사항을 지나갈 때 병합된 콘텐츠가 갑자기 보일 수 있으므로 절단 후 가장자리를 미리 봅니다. + 출력 형식을 선택하세요 + 콘텐츠와 대상을 기준으로 JPEG, PNG, WebP, AVIF, JXL 또는 PDF를 선택하세요. + 대상이 형식을 결정하게 하세요 + 가장 좋은 형식은 이미지에 포함된 내용과 사용 위치에 따라 다릅니다. 사진, 스크린샷, 투명 자산, 문서 및 애니메이션 파일은 모두 잘못된 형식을 강제로 적용하면 다양한 방식으로 실패합니다. + 투명도와 정확한 픽셀이 필요하지 않은 경우 광범위한 사진 호환성을 위해 JPEG를 사용하십시오. + 선명한 스크린샷, UI 캡처, 투명한 그래픽 및 무손실 편집을 위해 PNG를 사용하세요. + 컴팩트한 최신 출력이 중요하고 수신 앱이 이를 지원하는 경우 WebP, AVIF 또는 JXL을 사용하세요. + 오디오 커버 추출 + 포함된 앨범 아트 또는 오디오 파일의 사용 가능한 미디어 프레임을 PNG 이미지로 저장 + 오디오 파일에서 아트워크 가져오기 + Audio Covers는 Android 미디어 메타데이터를 사용하여 오디오 파일에 포함된 아트워크를 읽습니다. 삽입된 아트워크를 사용할 수 없는 경우 Android에서 제공할 수 있는 경우 검색기는 미디어 프레임으로 대체될 수 있습니다. 둘 다 없으면 도구는 추출할 이미지가 없다고 보고합니다. + 오디오 커버를 열고 예상되는 커버 아트가 포함된 오디오 파일을 하나 이상 선택하세요. + 특히 스트리밍 또는 녹음 앱의 파일을 저장하거나 공유하기 전에 추출된 표지를 검토하세요. + 이미지가 발견되지 않으면 오디오 파일에 삽입된 표지가 없거나 Android에서 사용 가능한 프레임을 노출할 수 없는 것일 수 있습니다. + 날짜 및 위치 메타데이터 + 캡처 시간이 중요하지만 GPS 또는 장치 데이터가 유출되어서는 안 되는 경우 무엇을 보존할지 결정하세요. + 유용한 메타데이터와 비공개 메타데이터를 분리하세요 + 메타데이터가 모두 똑같이 위험한 것은 아닙니다. 캡처 날짜는 보관 및 정렬에 유용할 수 있는 반면, GPS, 장치 일련번호와 유사한 필드, 소프트웨어 태그 또는 소유자 필드는 의도한 것보다 더 많은 것을 드러낼 수 있습니다. + 앨범, 스캔 또는 프로젝트 기록의 시간순이 중요한 경우 날짜 및 시간 태그를 유지하세요. + 공개적으로 이미지를 공유하거나 낯선 사람에게 보내기 전에 GPS 및 장치 식별 태그를 제거하십시오. + 개인 정보 보호 요구 사항이 엄격한 경우 샘플을 내보내고 EXIF를 다시 검사하세요. + 파일 이름 충돌 방지 + image.jpg 또는 스크린샷.png와 같은 이름을 공유하는 파일이 많은 경우 일괄 출력을 보호합니다. + 모든 출력 이름을 고유하게 만드십시오. + 이미지가 메신저, 스크린샷, 클라우드 폴더 또는 여러 카메라에서 가져온 경우 중복된 파일 이름이 흔히 발생합니다. 좋은 패턴은 우발적인 교체를 방지하고 출력을 소스까지 추적할 수 있도록 유지합니다. + 편집된 사본을 알아볼 수 있어야 하는 경우 원본 이름과 접미사를 포함합니다. + 대규모 혼합 배치를 처리할 때 시퀀스, 타임스탬프, 사전 설정 또는 차원을 추가합니다. + 이름 지정 패턴이 충돌할 수 없다는 것을 확인할 때까지 워크플로 덮어쓰기를 피하세요. + 저장 또는 공유 + 영구 파일과 다른 앱으로의 임시 전달 중에서 선택하세요 + 올바른 의도로 내보내기 + 저장과 공유는 비슷하게 느껴질 수 있지만 서로 다른 문제를 해결합니다. 저장하면 나중에 찾을 수 있는 파일이 생성됩니다. 공유는 생성된 결과를 Android를 통해 다른 앱으로 전송하며 지속 가능한 로컬 복사본을 남기지 않을 수 있습니다. + 출력을 알려진 폴더에 유지하거나 백업하거나 나중에 재사용해야 하는 경우 저장을 사용합니다. + 빠른 메시지, 이메일 첨부, 소셜 게시 또는 다른 편집자에게 결과 전송을 위해 공유를 사용하세요. + 수신 앱이 신뢰할 수 없거나 실패한 공유가 다시 생성하기 귀찮을 경우 먼저 저장하세요. + PDF 페이지 크기 및 여백 + 문서를 만들기 전에 용지 크기, 맞춤 동작 및 여유 공간을 선택하세요. + 이미지 페이지를 인쇄하고 읽을 수 있도록 설정 + 이미지의 모양이 선택한 용지 크기와 일치하지 않으면 이미지가 어색한 PDF 페이지가 될 수 있습니다. 여백, 맞춤 모드 및 페이지 방향에 따라 콘텐츠가 잘릴지, 작게, 늘어날지 또는 읽기 편한지 여부가 결정됩니다. + PDF를 인쇄하거나 양식에 제출할 때 실제 용지 크기를 사용하십시오. + 텍스트가 페이지 가장자리에 닿지 않도록 스캔 및 스크린샷에 여백을 사용하세요. + 소스 이미지의 방향이 혼합된 경우 세로 및 가로 페이지를 함께 미리 봅니다. + 스캔 정리 + PDF 또는 OCR 이전에 용지 가장자리, 그림자, 대비, 회전 및 가독성을 향상시킵니다. + 보관하기 전에 페이지 준비 + 스캔은 기술적으로 캡처할 수 있지만 여전히 읽기가 어렵습니다. PDF를 내보내기 전 작은 정리 단계는 압축 설정보다 중요한 경우가 많습니다. 특히 영수증, 손으로 쓴 메모 및 저조도 페이지의 경우 더욱 그렇습니다. + 원근을 수정하고 테이블 가장자리, 손가락, 그림자 및 배경의 어수선한 부분을 잘라냅니다. + 스탬프, 서명 또는 연필 표시를 손상시키지 않고 텍스트가 더 명확해 지도록 대비를 주의 깊게 높이십시오. + 페이지가 똑바로 펴져 읽을 수 있게 된 후에만 OCR을 실행하고 중요한 숫자를 수동으로 확인하십시오. + PDF 압축, 회색조 또는 복구 + 더 가볍고 인쇄 가능하거나 재구성된 문서 사본을 위해 단일 파일 PDF 작업을 사용합니다. + PDF 정리 작업을 선택하세요 + PDF 도구에는 압축, 회색조 변환 및 복구를 위한 별도의 작업이 포함되어 있습니다. 압축 및 회색조는 주로 포함된 이미지를 통해 작동하는 반면, 복구는 PDF 구조를 재구성합니다. 이들 중 어느 것도 모든 문서에 대해 보장된 수정으로 취급되어서는 안 됩니다. + 문서에 이미지가 포함되어 있고 공유할 파일 크기가 중요한 경우 PDF 압축을 사용하세요. + 문서를 더 쉽게 인쇄해야 하거나 컬러 이미지가 필요하지 않은 경우 회색조를 사용합니다. + 문서가 열리지 않거나 내부 구조가 손상된 경우 복사본에서 PDF 복구를 사용한 다음 재구성된 파일을 확인하세요. + PDF에서 이미지 추출 + 포함된 PDF 이미지를 복구하고 추출된 결과를 아카이브에 압축 + 문서의 소스 이미지 저장 + 이미지 추출은 PDF에서 포함된 이미지 개체를 검색하고, 가능한 경우 중복을 건너뛰고, 복구된 이미지를 생성된 ZIP 아카이브에 저장합니다. 텍스트, 벡터 그림 또는 보이는 모든 페이지가 아닌 PDF에 실제로 포함된 이미지만 스크린샷으로 추출합니다. + 카탈로그의 사진이나 스캔한 자산 등 PDF 내부에 저장된 그림이 필요한 경우 이미지 추출을 사용하세요. + 텍스트와 레이아웃을 포함한 전체 페이지가 필요한 경우 대신 PDF를 이미지로 변환 또는 페이지 추출을 사용하세요. + 도구에서 삽입된 이미지가 없다고 보고하는 경우 페이지가 텍스트/벡터 콘텐츠이거나 이미지가 추출 가능한 개체로 저장되지 않은 것일 수 있습니다. + 메타데이터, 병합 및 인쇄 출력 + 문서 속성을 편집하고, 페이지를 래스터화하거나 의도적으로 사용자 정의 인쇄 레이아웃을 준비합니다. + 최종 문서 작업 선택 + PDF 메타데이터, 병합 및 인쇄 준비는 다양한 최종 단계 문제를 해결합니다. 메타데이터는 문서 속성을 제어하고, Flatten PDF는 페이지를 래스터화하여 편집하기 어려운 출력으로 만들고, PDF 인쇄는 페이지 크기, 방향, 여백 ​​및 시트당 페이지 제어를 통해 새로운 레이아웃을 준비합니다. + 작성자, 제목, 키워드, 제작자 또는 개인정보 정리가 중요한 경우 메타데이터를 사용하세요. + 표시되는 페이지 내용이 별도의 PDF 개체로 편집하기 어려워지면 PDF 병합을 사용하세요. + 사용자 정의 페이지 크기, 방향, 여백 ​​또는 시트당 여러 페이지가 필요한 경우 PDF 인쇄를 사용하십시오. + OCR용 이미지 준비 + OCR에 텍스트 읽기를 요청하기 전에 자르기, 회전, 대비, 노이즈 제거 및 크기 조정을 사용하십시오. + OCR에 더 깨끗한 픽셀 제공 + OCR 실수는 종종 OCR 엔진이 아닌 입력 이미지에서 발생합니다. 구부러진 페이지, 낮은 대비, 흐림, 그림자, 작은 텍스트, 혼합된 배경 등은 모두 인식 품질을 저하시킵니다. + 인식하기 전에 텍스트 영역을 자르고 선이 수평이 되도록 이미지를 회전합니다. + 문자를 더 쉽게 읽을 수 있는 경우에만 대비를 높이거나 보다 깨끗한 흑백으로 변환하십시오. + 작은 텍스트의 크기를 적당히 위쪽으로 조정하되 가짜 가장자리를 만드는 공격적인 선명화는 피하세요. + QR 콘텐츠 확인 + 코드를 신뢰하기 전에 링크, Wi-Fi 자격 증명, 연락처, 작업을 검토하세요. + 디코딩된 텍스트를 신뢰할 수 없는 입력으로 처리 + QR 코드는 URL, 연락처 카드, Wi-Fi 비밀번호, 캘린더 이벤트, 전화번호 또는 일반 텍스트를 숨길 수 있습니다. 코드 근처에 보이는 레이블이 실제 페이로드와 일치하지 않을 수 있으므로 작업을 수행하기 전에 디코딩된 콘텐츠를 검토하세요. + 특히 단축되거나 익숙하지 않은 도메인을 열기 전에 디코딩된 전체 URL을 검사하십시오. + Wi-Fi, 연락처, 캘린더, 전화, SMS 코드는 민감한 작업을 유발할 수 있으므로 주의하세요. + 내용을 바로 열지 말고 다른 곳에서 확인해야 할 경우 먼저 복사해 두세요. + QR 코드 생성 + 일반 텍스트, URL, Wi-Fi, 연락처, 이메일, 전화, SMS 및 위치 데이터에서 코드 생성 + 올바른 QR 페이로드 구축 + QR 화면은 입력된 콘텐츠에서 코드를 생성할 수 있을 뿐만 아니라 기존 콘텐츠를 스캔할 수도 있습니다. 구조화된 QR 유형은 Wi-Fi 로그인 세부정보, 연락처 카드, 이메일 필드, 전화번호, SMS 콘텐츠, URL, 지리적 좌표 등 다른 앱이 이해할 수 있는 형식으로 데이터를 인코딩하는 데 도움이 됩니다. + 간단한 메모에는 일반 텍스트를 선택하고, Wi-Fi, 연락처, URL, 이메일, 전화, SMS 또는 위치 데이터로 코드를 열어야 하는 경우 구조화된 유형을 사용하세요. + 표시되는 미리보기에는 입력한 페이로드만 반영되므로 생성된 코드를 공유하기 전에 모든 필드를 확인하세요. + 저장된 QR 코드를 인쇄하거나 공개적으로 게시하거나 다른 사람이 사용할 경우 스캐너로 테스트해 보세요. + 체크섬 모드 선택 + 파일이나 텍스트에서 해시를 계산하고, 하나의 파일을 비교하거나, 여러 파일을 일괄 검사합니다. + 겉모습이 아닌 내용을 확인하라 + 체크섬 도구에는 파일 해싱, 텍스트 해싱, 파일을 알려진 해시와 비교 및 ​​일괄 비교를 위한 별도의 탭이 있습니다. 체크섬은 선택한 알고리즘의 바이트 단위 ID를 증명합니다. 두 이미지가 단순히 유사해 보인다는 것을 증명하는 것은 아닙니다. + 파일에는 계산을 사용하고, 입력하거나 붙여넣은 텍스트 데이터에는 텍스트 해시를 사용하세요. + 누군가가 하나의 파일에 대해 예상되는 체크섬을 제공하는 경우 비교를 사용하십시오. + 한 번에 많은 파일에 해시나 확인이 필요한 경우 일괄 비교를 사용한 다음 선택한 알고리즘을 일관되게 유지하세요. + 클립보드 개인정보 보호 + 개인 복사본 데이터를 실수로 노출시키지 않고 자동 클립보드 기능을 사용하세요. + 복사된 콘텐츠를 의도적으로 유지 + 클립보드 자동화는 편리하지만 복사된 텍스트에는 비밀번호, 링크, 주소, 토큰 또는 개인 메모가 포함될 수 있습니다. 클립보드 기반 입력을 사용자의 습관과 개인정보 보호 기대치에 부합하는 속도 기능으로 취급하세요. + 비공개 텍스트가 도구에 예기치 않게 나타나는 경우 자동 클립보드 사용을 비활성화합니다. + 결과를 의도적으로 저장하지 않으려는 경우에만 클립보드 전용 저장을 사용하세요. + 민감한 텍스트, QR 데이터 또는 Base64 페이로드를 처리한 후 클립보드를 지우거나 교체하세요. + 색상 형식 + 다른 곳에 붙여넣기 전에 HEX, RGB, HSV, 알파 및 복사된 색상 값을 이해하세요. + 대상이 예상하는 형식을 복사합니다. + 눈에 보이는 동일한 색상은 여러 가지 방법으로 쓸 수 있습니다. 디자인 도구, CSS 필드, Android 색상 값 또는 이미지 편집기에서는 다른 순서, 알파 처리 또는 색상 모델이 필요할 수 있습니다. + 간결한 색상 코드가 필요한 웹, 디자인 또는 테마 필드에 붙여넣을 때 HEX를 사용하세요. + 채널, 밝기, 채도 또는 색조를 수동으로 조정해야 하는 경우 RGB 또는 HSV를 사용하십시오. + 일부 대상에서는 이를 무시하거나 다른 순서를 사용하므로 투명도가 중요한 경우 알파를 별도로 확인하세요. + 색상 라이브러리 탐색 + 이름이나 HEX로 이름이 지정된 색상을 검색하고 즐겨찾기를 상단에 유지하세요. + 재사용 가능한 이름이 붙은 색상 찾기 + 색상 라이브러리는 명명된 색상 컬렉션을 로드하고, 이름별로 정렬하고, 색상 이름 또는 HEX 값으로 필터링하고, 즐겨찾는 색상을 저장하므로 중요한 항목에 더 쉽게 접근할 수 있습니다. 이미지에서 샘플링하는 대신 실제 명명된 색상이 필요할 때 유용합니다. + 빨간색, 파란색, 슬레이트, 민트 등 계열을 알고 있는 경우 색상 이름으로 검색하세요. + 이미 숫자 색상이 있고 일치하거나 근처에 이름이 지정된 항목을 찾으려는 경우 HEX로 검색하세요. + 자주 사용하는 색상을 즐겨찾기로 표시하면 탐색하는 동안 일반 라이브러리보다 먼저 표시됩니다. + 메시 그래디언트 컬렉션 사용 + 사용 가능한 메시 그라디언트 리소스를 다운로드하고 선택한 이미지를 공유하세요. + 원격 메시 그래디언트 자산 사용 + 메시 그라디언트는 원격 리소스 컬렉션을 로드하고, 누락된 그라디언트 이미지를 다운로드하고, 다운로드 진행률을 표시하고, 선택한 그라디언트를 공유할 수 있습니다. 가용성은 네트워크 액세스 및 원격 리소스 저장소에 따라 달라지므로 일반적으로 빈 목록은 리소스를 아직 사용할 수 없음을 의미합니다. + 미리 만들어진 메시 그라디언트 이미지를 원할 경우 그라디언트 도구에서 메시 그라디언트를 엽니다. + 컬렉션이 비어 있다고 가정하기 전에 리소스 로드 또는 다운로드 진행을 기다리십시오. + 필요한 그라디언트를 선택하고 공유하거나, 사용자 정의 그라디언트를 수동으로 만들고 싶을 때 Gradient Maker로 돌아가세요. + 생성된 자산 해결 + 그라디언트, 노이즈, 배경화면, SVG와 같은 아트 또는 텍스처를 생성하기 전에 출력 크기를 선택하세요. + 필요한 크기로 생성 + 생성된 이미지에는 대체할 카메라 원본이 없습니다. 출력이 너무 작은 경우 나중에 크기를 조정하면 패턴이 부드러워지고 세부 정보가 이동되거나 자산 타일링 및 압축 방식이 변경될 수 있습니다. + 배경화면, 아이콘, 배경, 인쇄, 오버레이 등 최종 사용 사례의 크기를 먼저 설정하세요. + 여러 레이아웃에서 자르거나 확대/축소하거나 재사용할 수 있는 텍스처에는 더 큰 크기를 사용하세요. + 절차적 세부 사항이 압축 동작 방식을 변경할 수 있으므로 대규모 출력 전에 테스트 버전을 내보내십시오. + 현재 배경화면 내보내기 + 장치에서 사용 가능한 홈, 잠금 및 내장 배경화면을 검색합니다. + 설치된 배경화면 저장 또는 공유 + 배경화면 내보내기는 Android가 시스템 배경화면 API를 통해 노출하는 배경화면을 읽습니다. 기기, Android 버전, 권한 및 빌드 변형에 따라 홈, 잠금 또는 내장 배경화면이 별도로 제공되거나 누락될 수 있습니다. + 배경 화면 내보내기를 열어 시스템에서 앱이 읽을 수 있도록 허용하는 배경 화면을 로드합니다. + 저장, 복사 또는 공유하려는 사용 가능한 홈, 잠금 또는 내장 배경 화면 항목을 선택하십시오. + 배경화면이 없거나 기능을 사용할 수 없는 경우 이는 일반적으로 편집 설정이 아닌 시스템, 권한 또는 빌드 변형 제한 사항입니다. + 코너 애니메이션 스로틀 + 색상을 다음으로 복사 + HEX + 이름 + 더 부드러운 머리카락과 가장자리 처리 기능을 갖춘 빠른 ​​인물 컷아웃을 위한 인물 사진 매트 모델입니다. + Zero-DCE++ 곡선 추정을 사용한 빠른 저조도 향상. + 빗줄기 및 습한 날씨 인공물을 줄이기 위한 Restormer 이미지 복원 모델. + 좌표 격자를 예측하고 곡선 페이지를 더 평평한 스캔으로 다시 매핑하는 문서 뒤틀림 모델입니다. + 모션 지속 시간 척도 + 앱 애니메이션 속도 제어: 0은 동작을 비활성화하고, 1은 정상이며, 값이 높을수록 애니메이션이 느려집니다. + 최근 도구 표시 + 메인 화면에 최근 사용한 도구에 대한 빠른 액세스 표시 + 사용 통계 + 앱 열기, 도구 실행 및 가장 많이 사용되는 도구를 살펴보세요. + 앱이 열립니다 + 도구가 열립니다 + 마지막 도구 + 성공적인 저장 + 연속 활동 + 저장된 데이터 + 최고 형식 + 사용된 도구 + %1$d 열림 • %2$s + 아직 사용 통계가 없습니다. + 몇 가지 도구를 열면 여기에 자동으로 표시됩니다. + 귀하의 사용 통계는 귀하의 장치에 로컬로만 저장됩니다. ImageToolbox는 이를 개인 활동, 즐겨찾는 도구 및 저장된 데이터 통찰력을 표시하는 데에만 사용합니다. + 편집자 편집 사진 사진 수정 조정 + 자르기 트림 컷 종횡비 + 필터 효과 색상 보정 LUT 마스크 조정 + 암호 암호화 해독 암호 비밀 + 스티치 병합 결합 파노라마 긴 스크린샷 + URL 웹 다운로드 인터넷 링크 이미지 + 선택기 스포이드 샘플 16진수 RGB 색상 + 제한 크기 조정 최대 최소 메가픽셀 크기 클램프 + ocr 텍스트 인식 추출 스캔 + 워터마크 로고 텍스트 스탬프 오버레이 + svg 벡터 추적 변환 xml + 문서 스캐너 스캔 종이 관점 자르기 + 색상 변환 hex rgb hsl hsv cmyk lab + base64 인코딩 데이터 uri 디코딩 + AI ML 신경 강화 고급 세그먼트 + PDF 회전 페이지 방향 + pdf ocr 텍스트는 검색 가능한 추출을 인식합니다. + PDF 잠금 해제 비밀번호 해독 보호 제거 + PDF 병합 주석 양식 레이어 + PDF 추출 이미지 사진 + 이미지를 PDF로 변환 jpg JPEG png 문서 + PDF를 이미지 페이지로 내보내기 jpg png + PDF 주석 주석 제거 제거 + 중립 변형 + 오류 + 그림자 + 치아 높이 + 수평 치아 범위 + 수직 치아 범위 + 위쪽 가장자리 + 오른쪽 가장자리 + 하단 가장자리 + 왼쪽 가장자리 + 찢어진 가장자리 + 크기 조정 변환 스케일 해상도 치수 너비 높이 jpg jpeg png webp 압축 + 압축 크기 무게 바이트 mb kb 품질 감소 최적화 + 그리기 페인트 브러시 연필 주석 표시 + 배경 제거제 지우기 제거 컷아웃 투명 알파 + 미리보기 뷰어 갤러리 검사 열기 + 팔레트 색상 견본 추출물 지배적 + EXIF 메타데이터 개인 정보 보호 스트립 청소 GPS 위치 제거 + 전후 차이 비교 + pdf 문서 페이지 도구 + 그라데이션 배경 색상 메쉬 + gif 애니메이션 애니메이션 변환 프레임 + apng 애니메이션 애니메이션 png 프레임 변환 + zip 아카이브 압축 팩 압축 풀기 파일 + jxl jpeg xl jpg 이미지 형식 변환 + 형식 변환 jpg jpeg png webp heic avid jxl bmp + qr 바코드 코드 스캐너 스캔 + 스태킹 오버레이 평균 중앙값 천체 사진 + 분할 타일 그리드 조각 부품 + webp 변환 애니메이션 이미지 형식 + 노이즈 생성기 무작위 텍스처 그레인 + 콜라주 그리드 레이아웃 결합 + 마크업 레이어 주석 오버레이 편집 + 체크섬 해시 md5 sha sha1 sha256 확인 + 메쉬 그라데이션 배경 + EXIF 메타데이터 편집 GPS 날짜 카메라 + 분할 그리드 조각 타일 자르기 + 오디오 커버 앨범 아트 음악 메타데이터 + 배경 수출 배경 + ASCII 텍스트 아트 문자 + 색상이라는 색상 라이브러리 재료 팔레트 + 셰이더 glsl 조각 효과 스튜디오 + pdf 병합 결합 결합 + pdf 별도 페이지 분할 + pdf 재정렬 재정렬 페이지 정렬 + pdf 페이지 번호 페이지 매기기 + pdf 워터마크 스탬프 로고 텍스트 + pdf 서명 서명 그리기 + pdf 비밀번호 보호 암호화 잠금 + PDF 압축 크기 줄이기 최적화 + pdf 회색조 검정 흰색 흑백 + PDF 수리 수정 복구 깨진 + pdf 메타데이터 속성 EXIF ​​작성자 제목 + pdf 제거 삭제 페이지 + pdf 자르기 트림 여백 + pdf zip 아카이브 압축 + PDF 인쇄 프린터 + PDF 미리보기 뷰어 읽기 + 사용 통계 재설정 + 도구 열기, 저장 통계, 연속 기록, 저장된 데이터 및 최고 형식이 재설정됩니다. 앱 열기는 변경되지 않습니다. + 오디오 + 파일 + 프로필 내보내기 + 현재 프로필 저장 + 내보내기 프로필 삭제 + 내보내기 프로필 \\"%1$s\\"을(를) 삭제하시겠습니까? + 도면 경계 + 그리기 및 지우기 캔버스 주위에 얇은 테두리를 표시합니다 + 떨리는 + 부드러운 물결 모양의 가장자리가 있는 둥근 UI 요소 + 국자 + 골짜기 + 둥근 모서리가 안쪽으로 새겨져 있습니다. + 이중 절단이 있는 계단형 모서리 + 자리 표시자 + 확장자 없이 원본 파일 이름을 삽입하려면 {filename}을 사용하세요. + 플레어 + 기본 금액 + 광선량 + 링 폭 + 왜곡된 관점 + 자바 모양과 느낌 + 전단 + X 각도 + 그리고 각도 + 출력 크기 조정 + 물방울 + 파장 + 단계 + 컬러 마스크 + 링 금액 + 하이패스 + 크롬 + 디졸브 + 연성 + 피드백 + 렌즈 흐림 + 낮은 입력 + 높은 입력 + 낮은 출력 + 고출력 + 조명 효과 + 범프 높이 + 범프 부드러움 + 리플 + X 진폭 + Y 진폭 + X 파장 + Y 파장 + 적응형 흐림 + 텍스처 생성 + 다양한 절차적 텍스처를 생성하여 이미지로 저장 + 텍스처 유형 + 편견 + 시간 + 빛나는 + 분산 + 샘플 + 각도 계수 + 기울기 계수 + 그리드 유형 + 거리 전력 + 스케일 Y + 흐릿함 + 기본형 + 난류 인자 + 스케일링 + 반복 + 반지 + 브러시드 메탈 + 화선 + 셀룰러 + 서양 장기판 + fBm + 대리석 + 혈장 + 이불 + 목재 + 무작위의 + 정사각형 + 육각형 + 팔각형 + 삼각형 + 능선 + VL 노이즈 + SC 소음 + 최근의 + 최근에 사용한 필터가 아직 없습니다. + 조직 생성기 닦았 금속 가성 세포질 서양 장기판 대리석 플라즈마 이불 나무 벽돌 위장 세포 구름 갈라진 금 직물 잎 벌집 얼음 용암 성운 종이 녹 모래 연기 돌 지역 지형 물 리플 + 벽돌 + 위장 + + 구름 + 금이 가다 + 구조 + + 벌집 + 얼음 + 용암 + 성운 + 종이 + + 모래 + 연기 + 결석 + 지역 + 지형 + 물 파문 + 고급 목재 + 모르타르 폭 + 불규칙 + 사각 + + 첫 번째 임계값 + 두 번째 임계값 + 세 번째 임계값 + 가장자리 부드러움 + 테두리 너비 + 적용 범위 + 세부 사항 + 깊이 + 분기 + 수평 스레드 + 수직 스레드 + 솜털 + 정맥 + 조명 + 균열폭 + 서리 + 흐름 + 빵 껍질 + 구름 밀도 + + 섬유 밀도 + 섬유 강도 + 얼룩 + 부식 + 피팅 + 플레이크 + 모래 언덕 빈도 + 바람의 각도 + 잔물결 + 위습 + 정맥 규모 + 수위 + 산 수준 + 부식 + 눈높이 + 줄 수 + 선 두께 + 농담 + 모공색 + 모르타르 색상 + 어두운 벽돌색 + 벽돌 색상 + 하이라이트 컬러 + 어두운 색 + 숲의 색 + 지구색 + 모래색 + 배경색 + 셀 색상 + 가장자리 색상 + 하늘색 + 그림자 색상 + 연한 색 + 표면 색상 + 변형 색상 + 균열 색상 + 워프 색상 + 씨실색 + 어두운 잎 색깔 + 잎 색깔 + 테두리 색상 + 허니 컬러 + 딥컬러 + 얼음 색깔 + 프로스트 컬러 + 크러스트 색상 + 워시 컬러 + 글로우 컬러 + 공간색 + 보라색 + 파란색 + 베이스 컬러 + 섬유색상 + 얼룩 색깔 + 메탈 컬러 + 어두운 녹 색 + 녹 색 + 오렌지색 + 연기 색 + 정맥 색 + 저지대 색상 + 수채화 + 록 컬러 + 눈 색깔 + 낮은 색상 + 좋은 혈색 + 선 색상 + 얕은 색상 + 잔디 + + 가죽 + 콘크리트 + 아스팔트 + 이끼 + + 오로라 + 오일슬릭 + 수채화 + 추상 흐름 + 오팔 + 다마스커스 강철 + 번개 + 벨벳 + 잉크 마블링 + 홀로그램 포일 + 생물발광 + 우주 소용돌이 + 용암 램프 + 이벤트 호라이즌 + 프랙탈 블룸 + 색채 터널 + 이클립스 코로나 + 이상한 끌개 + 자성유체 크라운 + 초신성 + 아이리스 + 공작 깃털 + 노틸러스 셸 + 고리 행성 + 블레이드 밀도 + 블레이드 길이 + 바람 + 고르지 않음 + 덩어리 + 수분 + 조약돌 + 주름 + 모공 + 골재 + 균열 + 타르 + 입다 + 얼룩 + 섬유 + 화염 빈도 + 연기 + 강함 + 고삐 + 밴드 + 무지개 빛 + 어둠 + 꽃이 핀다 + 그림 물감 + 가장자리 + 종이 + 확산 + 대칭 + 날카로움 + 컬러 플레이 + 우유빛 + 레이어 + 접는 + 광택 + 지점 + 방향 + 광휘 + 주름 + 깃털 + 잉크 밸런스 + 스펙트럼 + 주름 + 회절 + 무기 + 트위스트 + 코어 글로우 + 얼룩 + 디스크 틸트 + 지평선 크기 + 디스크 너비 + 렌싱 + 꽃잎 + + 선조 + 패싯 + 곡률 + 달의 크기 + 코로나 크기 + 광선 + 다이아몬드 반지 + 로브 + 궤도 밀도 + 두께 + 스파이크 + 스파이크 길이 + 본체 사이즈 + 메탈릭 + 충격 반경 + 쉘 폭 + 버림받은 + 동공 크기 + 홍채 크기 + 색상 변화 + 캐치라이트 + 눈 크기 + 미늘 밀도 + 회전 + 변호사 사무실 + 열기 + 능선 + 진주광택 + 행성의 크기 + 링 틸트 + 링 폭 + 대기 + 더러운 색 + 어두운 잔디 색상 + 잔디 색상 + 팁 색상 + 어두운 흙색 + 드라이 컬러 + 조약돌 색상 + 가죽 색상 + 콘크리트 색상 + 타르색소 + 아스팔트 색상 + 스톤 컬러 + 먼지 색깔 + 토양색 + 어두운 이끼색 + 이끼색 + 붉은 색 + 코어 컬러 + 채색 + 청록색 + 마젠타색 + 골드 컬러 + 종이 색상 + 안료 색상 + 보조 색상 + 첫 번째 색상 + 두 번째 색상 + 핑크 색상 + 어두운 강철 색상 + 스틸 색상 + 라이트 스틸 색상 + 산화물 색상 + 헤일로 컬러 + 볼트 색상 + 벨벳 컬러 + 광택 색상 + 파란색 잉크 색상 + 빨간색 잉크 색상 + 어두운 잉크 색상 + 실버 색상 + + 조직 색상 + 디스크 색상 + 핫 컬러 + 렌즈 색상 + 외부 색상 + 내부 색상 + 코로나 컬러 + 차가운 색 + 따뜻한 색상 + 구름색 + 불꽃색 + 깃털 색 + 쉘 색상 + 진주색 + 행성의 색 + 링 색상 + 저장 후 원본 파일 삭제 + 성공적으로 처리된 소스 파일만 삭제됩니다. + 삭제된 원본 파일: %1$d + 원본 파일 삭제 실패: %1$d + 삭제된 원본 파일: %1$d, 실패: %2$d + 시트 동작 + 확장된 옵션 시트 드래그 허용 + 크로마 서브샘플링 + 비트 심도 + 무손실 + %1$d비트 + 일괄 이름 바꾸기 + 파일 이름 패턴을 사용하여 여러 파일의 이름 바꾸기 + 일괄 이름 바꾸기 파일 파일 이름 패턴 순서 날짜 확장자 + 이름을 바꿀 파일을 선택하세요 + 파일 이름 패턴 + 이름 바꾸기 + 날짜 소스 + EXIF 촬영 날짜 + 파일 수정 날짜 + 파일 생성 날짜 + 현재 날짜 + 수동 날짜 + 수동 날짜 + 수동 시간 + 일부 파일에서는 선택한 날짜를 사용할 수 없습니다. 현재 날짜가 사용됩니다. + 파일 이름 패턴을 입력하세요. + 패턴이 유효하지 않거나 지나치게 긴 파일 이름을 생성합니다. + 패턴이 중복된 파일 이름을 생성합니다. + 모든 파일 이름이 이미 패턴과 일치합니다. + 이 패턴은 일괄 이름 바꾸기에 사용할 수 없는 토큰을 사용합니다. + 이름은 그대로 유지됩니다 + 파일 이름이 성공적으로 변경되었습니다. + 일부 파일의 이름을 바꿀 수 없습니다 + 권한이 부여되지 않았습니다. + 확장자 없이 원본 파일 이름을 사용하므로 소스 식별을 그대로 유지하는 데 도움이 됩니다. 또한 \\o{start:end}로 슬라이싱, \\o{t}로 음역, \\o{s/old/new/}로 교체를 지원합니다. + 자동 증가 카운터. 사용자 정의 형식을 지원합니다: \\c{padding}(예: \\c{3} -&gt; 001), \\c{start:step} 또는 \\c{start:step:padding}. + 원본 파일이 위치한 상위 폴더의 이름입니다. + 원본 파일의 크기입니다. 지원되는 단위는 \\z{b}, \\z{kb}, \\z{mb} 또는 사람이 읽을 수 있는 형식의 경우 \\z입니다. + 파일 이름의 고유성을 보장하기 위해 임의의 UUID(Universally Unique Identifier)를 생성합니다. + 파일 이름 변경은 취소할 수 없습니다. 계속하기 전에 새 이름이 올바른지 확인하세요. + 이름 변경 확인 + 사이드 메뉴 설정 + 메뉴 규모 + 메뉴 알파 + 자동 화이트 밸런스 + 깎는 + 숨겨진 워터마크 확인 + 저장 + 캐시 한도 + 캐시가 이 크기 이상으로 커지면 자동으로 캐시를 지웁니다. + 정리 간격 + 캐시를 지워야 하는지 확인하는 빈도 + 앱 실행 시 + 1일 + 1주 + 1개월 + 선택한 제한 및 간격에 따라 자동으로 앱 캐시를 지웁니다. + 이동 가능한 작물 영역 + 전체 자르기 영역 내부를 드래그하여 이동할 수 있습니다. + GIF 병합 + 여러 GIF 클립을 하나의 애니메이션으로 결합 + GIF 클립 순서 + 뒤집다 + 역방향으로 재생 + 부메랑 + 앞으로 재생한 다음 뒤로 재생 + 프레임 크기 표준화 + 가장 큰 클립에 맞게 프레임 크기를 조정합니다. 원래 크기를 유지하려면 끄세요 + 클립 간 지연(ms) + 접는 사람 + 폴더와 하위 폴더에서 모든 이미지를 선택하세요. + 중복된 파인더 + 정확한 사본과 시각적으로 유사한 이미지 찾기 + 중복 찾기 유사한 이미지 정확한 사본 사진 정리 저장 dHash SHA-256 + 중복된 이미지를 찾으려면 이미지를 선택하세요. + 유사성 민감도 + 정확한 사본 + 유사한 이미지 + 정확한 중복 항목을 모두 선택하세요. + 권장사항을 제외한 모두 선택 + 중복된 항목이 없습니다. + 이미지를 더 추가하거나 유사성 민감도를 높여보세요. + %1$d 이미지를 읽을 수 없습니다. + %1$s • %2$s 회수 가능 + %1$d 그룹 • %2$s 회수 가능 + %1$d 선택됨 • %2$s + 이 작업은 취소할 수 없습니다. Android는 시스템 대화상자에서 삭제 확인을 요청할 수 있습니다. + 찾을 수 없음 + 시작으로 이동 + 끝으로 이동 + \ No newline at end of file diff --git a/core/resources/src/main/res/values-lt/strings.xml b/core/resources/src/main/res/values-lt/strings.xml new file mode 100644 index 0000000..b26e727 --- /dev/null +++ b/core/resources/src/main/res/values-lt/strings.xml @@ -0,0 +1,3741 @@ + + + + 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 + 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 + 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 & 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 spalva + %1$s spalvos + %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. + 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ų. + Į 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 + Turinio suvokimo iškraipymas + Nepakanka atminties šiam veiksmui atlikti. Pabandykite naudoti mažesnį vaizdą, uždaryti kitas programas arba iš naujo paleisti programą. + Lygiagretūs darbuotojai + Šie vaizdai nebuvo išsaugoti, nes konvertuoti failai būtų didesni nei originalai. Prieš ištrindami originalius vaizdus, ​​patikrinkite šį sąrašą. + Praleisti failai: %1$s + Programų žurnalai + Peržiūrėkite programos žurnalus, kad pastebėtumėte problemas + Mėgstamiausios sugrupuotose priemonėse + Prideda parankinius kaip skirtuką, kai įrankiai sugrupuojami pagal tipą + Rodyti mėgstamiausią kaip paskutinį + Perkelia mėgstamiausių įrankių skirtuką į pabaigą + Horizontalus atstumas + Vertikalus tarpas + Atgalinė energija + Naudokite paprastą gradiento dydžio energijos žemėlapį, o ne tiesioginės energijos algoritmą + Pašalinkite užmaskuotą vietą + Siūlės praeis per užmaskuotą sritį, pašalindamos tik pasirinktą sritį, o ne ją apsaugodamos + VHS NTSC + Išplėstiniai NTSC nustatymai + Papildomas analoginis derinimas stipresniam VHS ir transliacijos artefaktams + Chrominis kraujavimas + Juostos nusidėvėjimas + Stebėjimas + Luma tepinėlis + Skambėjimas + Sniegas + Naudokite lauką + Filtro tipas + Įvesties luma filtras + Įvesties chroma žemo dažnio dažnis + Chroma demoduliacija + Fazės poslinkis + Fazės poslinkis + Galvos perjungimas + Galvos perjungimo aukštis + Galvutės perjungimo poslinkis + Galvos perjungimas + Vidurinės linijos padėtis + Vidurinės linijos virpėjimas + Triukšmo aukščio stebėjimas + Stebėjimo banga + Sniego sekimas + Sniego anizotropijos stebėjimas + Stebėjimo triukšmas + Triukšmo intensyvumo stebėjimas + Sudėtinis triukšmas + Sudėtinis triukšmo dažnis + Sudėtinis triukšmo intensyvumas + Sudėtinė triukšmo detalė + Skambėjimo dažnis + Skambėjimo galia + Luma triukšmas + Luma triukšmo dažnis + Luma triukšmo intensyvumas + Luma triukšmo detalė + Chrominis triukšmas + Chromato triukšmo dažnis + Chromato triukšmo intensyvumas + Chroma triukšmo detalė + Sniego intensyvumas + Sniego anizotropija + Chromos fazės triukšmas + Chroma fazės klaida + Chroma delsa horizontaliai + Chromo delsa vertikali + VHS juostos greitis + VHS chromos praradimas + VHS aštrinimo intensyvumas + VHS aštrinimo dažnis + VHS kraštinės bangos intensyvumas + VHS kraštinės bangos greitis + VHS kraštinių bangų dažnis + VHS krašto bangos detalė + Išvesties chroma žemo dažnio pralaidumas + Skalė horizontaliai + Vertikalus mastelis + X mastelio koeficientas + Mastelio koeficientas Y + Aptinka įprastus vaizdo vandens ženklus ir nudažo juos LaMa. Automatiškai atsisiunčia aptikimo ir dažymo modelius + Deuteranopija + Išskleisti vaizdą + Objektų segmentavimu pagrįstas fono šalinimo įrankis, naudojant YOLO v11 segmentavimą + Rodyti linijos kampą + Piešimo metu rodomas dabartinis linijos pasukimas laipsniais + Animuoti jaustukai + Rodyti galimus jaustukus kaip animacijas + Išsaugoti originaliame aplanke + Išsaugokite naujus failus šalia pradinio failo, o ne pasirinkto aplanko + Vandens ženklas PDF + Nepakanka atminties + Tikėtina, kad vaizdas per didelis, kad jį būtų galima apdoroti šiame įrenginyje, arba sistemoje baigėsi laisvos atminties kiekis. Pabandykite sumažinti vaizdo skyrą, uždaryti kitas programas arba pasirinkti mažesnį failą. + Derinimo meniu + Meniu programos funkcijoms išbandyti. Jis nerodomas gamybiniame leidime + Teikėjas + „PaddleOCR“ jūsų įrenginyje reikia papildomų ONNX modelių. Ar norite atsisiųsti %1$s duomenis? + Universalus + korėjiečių + lotynų kalba + Rytų slavų + tajų + graikų + anglų kalba + Kirilica + arabų + Devanagari + tamilų + telugų + Shader + Shader iš anksto nustatytas + Nepasirinktas atspalvis + Shader studija + Kurkite, redaguokite, patvirtinkite, importuokite ir eksportuokite pasirinktinius fragmentų atspalvius + Išsaugoti šešėliai + Atidarykite išsaugotus šešėlius, kopijuokite, eksportuokite, bendrinkite arba ištrinkite + Naujas šešėliuotojas + Shader failas + .itshader JSON + %1$d parametrai + Shader šaltinis + Parašykite tik galios main() turinį. Naudokite textureCoordinate UV koordinatėms ir inputImageTexture kaip šaltinio mėginių ėmimo priemonę. + Funkcijos + Pasirenkamos pagalbinės funkcijos, konstantos ir struktūros, įterptos prieš void main(). Uniformos generuojamos iš parametrų. + Pridėkite uniformas čia, kai šešėliui reikia redaguojamų verčių. + Iš naujo nustatyti šešėlį + Dabartinis šešėlio juodraštis bus išvalytas + Netinkamas šešėliuotojas + Nepalaikoma šešėlių versija %1$d. Palaikoma versija: %2$d. + Shader pavadinimo laukas negali būti tuščias. + Shader šaltinio laukas negali būti tuščias. + Shader šaltinyje yra nepalaikomų simbolių: %1$s. Naudokite tik ASCII GLSL šaltinį. + Parametras \\\"%1$s\\\" deklaruojamas daugiau nei vieną kartą. + Parametrų pavadinimų laukas negali būti tuščias. + Parametras \\\"%1$s\\\" naudoja rezervuotą GPUI vaizdo pavadinimą. + Parametras \\\"%1$s\\\" turi būti galiojantis GLSL identifikatorius. + Parametras \\\"%1$s\\\" turi %2$s vertės tipą \\\"%3$s\\\", numatomas \\\"%4$s\\\". + Parametras \\\"%1$s\\\" negali apibrėžti minimalios arba didžiausios būties reikšmių. + Parametras \\\"%1$s\\\" yra mažesnis nei maks. + Numatytasis parametras \\\"%1$s\\\" yra mažesnis nei min. + Numatytasis parametras \\\"%1$s\\\" yra didesnis nei maks. + Shader turi deklaruoti \\\"uniform sampler2D %1$s;\\\". + Shader turi deklaruoti \\\"varying vec2 %1$s;\\\". + Shader turi apibrėžti \\\"void main()\\\". + Shader turi įrašyti spalvą į gl_FragColor. + ShaderToy mainImage šešėliai nepalaikomi. Naudokite GPUImage negaliojančią pagrindinę () sutartį. + Animuota ShaderToy uniforma \\\"iTime\\\" nepalaikoma. + Animuota ShaderToy uniforma \\\"iFrame\\\" nepalaikoma. + ShaderToy uniforma \\\"iResolution\\\" nepalaikoma. + „ShaderToy iChannel“ tekstūros nepalaikomos. + Išorinės tekstūros nepalaikomos. + Išoriniai tekstūros plėtiniai nepalaikomi. + Libretro šešėlių parametrai nepalaikomi. + Palaikoma tik viena įvesties tekstūra. Pašalinti \\\"uniformą %1$s %2$s;\\\". + Parametras \\\"%1$s\\\" turi būti deklaruotas kaip \\\"vienodas %2$s %1$s;\\\". + Parametras \\\"%1$s\\\" deklaruojamas kaip \\\"vienodas %2$s %1$s;\\\", numatomas \\\"vienodas %3$s %1$s;\\\". + Shader failas tuščias. + Shader failas turi būti .itshader JSON objektas su versijos, pavadinimo ir šešėlio laukais. + Shader failas netinkamas JSON arba neatitinka .itshader formato. + Lauką \\\"%1$s\\\" būtina užpildyti. + %1$s reikalingas. + %1$s \\\"%2$s\\\" nepalaikomas. Palaikomi tipai: %3$s. + %1$s reikalingas \\\"%2$s\\\" parametrams. + %1$s turi būti baigtinis skaičius. + %1$s turi būti sveikas skaičius. + %1$s turi būti teisinga arba klaidinga. + %1$s turi būti spalvų eilutė, RGB/RGBA masyvas arba spalvos objektas. + %1$s turi naudoti #RRGGBBA arba #RRGGBBAA formatą. + %1$s turi turėti galiojančių šešioliktainių spalvų kanalų. + %1$s turi turėti 3 arba 4 spalvų kanalus. + %1$s turi būti nuo 0 iki 255. + %1$s turi būti dviejų skaičių masyvas arba objektas su x ir y. + %1$s turi būti tiksliai 2 skaičiai. + Pasikartoti + Visada išvalykite EXIF + Išsaugodami pašalinkite vaizdo EXIF ​​duomenis, net kai įrankis prašo išsaugoti metaduomenis + Pagalba ir patarimai + %1$d vadovėliai + Atidarykite įrankį + Sužinokite apie pagrindinius įrankius, eksporto parinktis, PDF srautus, spalvų paslaugų programas ir dažniausiai pasitaikančių problemų pataisymus + Darbo pradžia + Pasirinkite įrankius, importuokite failus, išsaugokite rezultatus ir greičiau pakartotinai naudokite nustatymus + Vaizdo redagavimas + Keiskite vaizdų dydį, apkarpykite, filtruokite, ištrinkite foną, pieškite ir naudokite vandens ženklus + Failai ir metaduomenys + Konvertuokite formatus, palyginkite išvestį, apsaugokite privatumą ir valdykite failų pavadinimus + PDF ir dokumentai + Kurkite PDF failus, nuskaitykite dokumentus, OCR puslapius ir paruoškite failus bendrinimui + Tekstas, QR ir duomenys + Atpažinkite tekstą, nuskaitykite kodus, užkoduokite Base64, patikrinkite maišą ir paketų failus + Spalvų įrankiai + Pasirinkite spalvas, kurkite paletes, tyrinėkite spalvų bibliotekas ir kurkite gradientus + Kūrybiniai įrankiai + Generuokite SVG, ASCII meną, triukšmo tekstūras, šešėlius ir eksperimentinius vaizdus + Trikčių šalinimas + Išspręskite sunkių failų, skaidrumo, bendrinimo, importavimo ir ataskaitų teikimo problemas + Pasirinkite tinkamą įrankį + Naudokite kategorijas, paiešką, parankinius ir bendrinkite veiksmus, kad pradėtumėte tinkamoje vietoje + Pradėkite nuo geriausio įėjimo taško + Vaizdo įrankių dėžutėje yra daug tikslinių įrankių ir daugelis jų iš pirmo žvilgsnio sutampa. Pradėkite nuo užduoties, kurią norite užbaigti, tada pasirinkite įrankį, kuris valdo svarbiausią rezultato dalį: dydį, formatą, metaduomenis, tekstą, PDF puslapius, spalvas arba išdėstymą. + Naudokite paiešką, kai žinote veiksmo pavadinimą, pvz., dydžio keitimas, PDF, EXIF, OCR, QR arba spalva. + Atidarykite kategoriją, kai žinote tik užduoties tipą, tada dažnai prisekite įrankius kaip mėgstamiausius. + Kai kita programa bendrina vaizdą į „Image Toolbox“, pasirinkite įrankį iš bendrinimo lapo srauto. + Importuokite, išsaugokite ir bendrinkite rezultatus + Supraskite įprastą eigą nuo įvesties rinkimo iki redaguoto failo eksportavimo + Vykdykite įprastą redagavimo eigą + Dauguma įrankių veikia tuo pačiu ritmu: pasirinkite įvestį, koreguokite parinktis, peržiūrėkite, tada išsaugokite arba bendrinkite. Svarbi detalė yra ta, kad išsaugant sukuriamas išvesties failas, o bendrinimas dažniausiai yra geriausias norint greitai perkelti į kitą programą. + Pasirinkite vieną failą vieno vaizdo įrankiams arba kelis paketinių įrankių failus, kai rinkiklis tai leidžia. + Prieš išsaugodami patikrinkite peržiūros ir išvesties valdiklius, ypač formato, kokybės ir failo pavadinimo parinktis. + Greitam perdavimui naudokite bendrinimą arba išsaugokite, kai reikia nuolatinės kopijos pasirinktame aplanke. + Dirbkite su daug vaizdų vienu metu + Paketiniai įrankiai geriausiai tinka kartotinėms dydžio keitimo, konvertavimo, glaudinimo ir pavadinimo užduotims atlikti + Paruoškite nuspėjamą partiją + Paketinis apdorojimas yra greičiausias, kai kiekviena produkcija turi atitikti tas pačias taisykles. Taip pat lengviausia padaryti didelę klaidą, todėl prieš pradėdami ilgą eksportavimą pasirinkite pavadinimą, kokybę, metaduomenis ir aplankų veikimą. + Pasirinkite visus susijusius vaizdus kartu ir išlaikykite tvarką, jei išvestis priklauso nuo sekos. + Prieš pradėdami eksportuoti, vieną kartą nustatykite dydžio, formato, kokybės, metaduomenų ir failo pavadinimo taisykles. + Pirmiausia paleiskite nedidelę bandomąją partiją, kai šaltinio failai yra dideli arba kai tikslinis formatas jums naujas. + Greitas pavienis redagavimas + Naudokite „viskas viename“ redaktorių, kai reikia kelių paprastų vieno vaizdo pakeitimų + Užbaikite vieną vaizdą be šokinėjimo įrankiu + Vieno redagavimo funkcija yra naudinga, kai paveikslėlyje reikia nedidelės pakeitimų grandinės, o ne vienos specializuotos operacijos. Paprastai tai yra greitesnė norint greitai išvalyti, peržiūrėti ir eksportuoti vieną galutinę kopiją nekuriant paketinės darbo eigos. + Atidarykite vieną redagavimą vienam vaizdui, kuriam reikia bendrų koregavimų, žymėjimo, apkarpymo, pasukimo arba eksportavimo pakeitimų. + Redagavimus taikykite tokia tvarka, kuria pirmiausia pakeičiama mažiau pikselių, pvz., apkarpyti prieš filtrus ir filtrus prieš galutinį suspaudimą. + Vietoj to naudokite specializuotą įrankį, kai reikia paketinio apdorojimo, tikslių failo dydžio tikslų, PDF išvesties, OCR ar metaduomenų valymo. + Naudokite išankstinius nustatymus ir nustatymus + Išsaugokite pasikartojančius pasirinkimus ir suderinkite programą pagal pageidaujamą darbo eigą + Venkite kartoti tos pačios sąrankos + Išankstiniai nustatymai ir nustatymai yra naudingi, kai dažnai eksportuojate to paties tipo failą. Geras išankstinis nustatymas paverčia pakartotinį rankinį kontrolinį sąrašą vienu bakstelėjimu, o bendrieji nustatymai apibrėžia numatytuosius nustatymus, nuo kurių prasideda nauji įrankiai. + Kurkite išankstinius įprastų išvesties dydžių, formatų, kokybės verčių ar pavadinimų modelių nustatymus. + Peržiūrėkite numatytąjį formatą, kokybę, išsaugojimo aplanką, failo pavadinimą, rinkiklį ir sąsajos veikimą. + Prieš iš naujo įdiegdami programą arba perkeldami į kitą įrenginį, sukurkite atsargines nustatymų kopijas. + Nustatykite naudingus numatytuosius nustatymus + Vieną kartą pasirinkite numatytąjį formatą, kokybę, mastelio režimą, dydžio keitimo tipą ir spalvų erdvę + Priverskite naujus įrankius pradėti arčiau savo tikslo + Numatytosios vertės nėra tik kosmetinės nuostatos. Jie nusprendžia, koks formatas, kokybė, dydžio keitimo elgsena, mastelio režimas ir spalvų erdvė rodomi pirmiausia daugelyje įrankių, o tai padeda išvengti tų pačių pasirinkimų iš naujo kiekvieną eksportuojant. + Nustatykite numatytąjį vaizdo formatą ir kokybę, kad jie atitiktų jūsų įprastą paskirties vietą, pvz., JPEG nuotraukoms arba PNG / WebP, jei norite naudoti alfa. + Sureguliuokite numatytąjį dydžio keitimo tipą ir mastelio režimą, jei dažnai eksportuojate griežtus matmenis, socialines platformas ar dokumentų puslapius. + Keiskite numatytuosius spalvų erdvės nustatymus tik tada, kai jūsų darbo eigai reikia nuoseklaus spalvų tvarkymo ekranuose, spausdinimo priemonėse ar išoriniuose redaktoriuose. + Rinkiklis ir paleidimo priemonė + Naudokite rinkiklio nustatymus, kai programa atidaro per daug dialogo langų arba paleidžiama netinkamoje vietoje + Padarykite, kad failų atidarymas atitiktų jūsų įpročius + Rinkiklio ir paleidimo priemonės nustatymai valdo, ar vaizdo įrankių dėžė paleidžiama iš pagrindinio ekrano, įrankio ar failų pasirinkimo srauto. Šios parinktys ypač naudingos, kai paprastai įvedate iš „Android“ bendrinimo lapo arba kai norite, kad programa jaustųsi kaip įrankių paleidimo priemonė. + Naudokite nuotraukų rinkiklio nustatymus, jei sistemos rinkiklis paslepia failus, rodo per daug naujausių elementų arba blogai tvarko debesies failus. + Įgalinti praleisti pasirinkimą tik tiems įrankiams, kuriuos paprastai įvedate iš bendrinimo arba jau žinote šaltinio failą. + Peržiūrėkite paleidimo priemonės režimą, jei norite, kad „Image Toolbox“ veiktų labiau kaip įrankių rinkiklis nei įprastas pagrindinis ekranas. + Vaizdo šaltinis + Pasirinkite rinkiklio režimą, kuris geriausiai veikia su galerijomis, failų tvarkytuvėmis ir debesies failais + Pasirinkite failus iš tinkamo šaltinio + Vaizdo šaltinio nustatymai turi įtakos tai, kaip programa prašo „Android“ vaizdų. Geriausias pasirinkimas priklauso nuo to, ar jūsų failai yra sistemos galerijoje, failų tvarkyklės aplanke, debesies tiekėjo programoje ar kitoje programoje, kuri bendrina laikiną turinį. + Naudokite numatytąjį rinkiklį, kai norite, kad įprasti galerijos vaizdai būtų labiausiai integruoti į sistemą. + Perjunkite rinkiklio režimą, jei albumai, aplankai, debesies failai arba nestandartiniai formatai nerodomi ten, kur tikitės. + Jei bendrinamas failas dingsta išėjus iš šaltinio programos, iš naujo atidarykite jį naudodami nuolatinį rinkiklį arba pirmiausia išsaugokite vietinę kopiją. + Mėgstamiausi ir bendrinkite įrankius + Sufokusuokite pagrindinį ekraną ir paslėpkite įrankius, kurie nėra prasmingi bendrinimo srautuose + Sumažinkite įrankių sąrašo triukšmą + Mėgstamiausi ir paslėpti bendrinimo nustatymai yra naudingi, kai kasdien naudojate tik kelis įrankius. Jie taip pat užtikrina praktišką bendrinimo srautą, nes slepia įrankius, kurie negali naudoti failo tipo, kurį siunčia kita programa. + Dažnai prisekite įrankius, kad juos būtų lengva pasiekti, net kai įrankiai sugrupuoti pagal kategorijas. + Slėpti įrankius nuo bendrinimo srautų, kai jie nėra svarbūs failams, gaunamiems iš kitos programos. + Naudokite mėgstamiausius užsakymo nustatymus, jei pageidaujate, kad parankiniai būtų pabaigoje arba sugrupuoti su susijusiais įrankiais. + Nustatymų atsarginė kopija + Saugiai perkelkite išankstinius nustatymus, nuostatas ir programos veikimą į kitą diegimą + Laikykite savo sąranką nešiojamą + Atsarginė kopija ir atkūrimas yra naudingiausias, kai suderinote išankstinius nustatymus, aplankus, failų pavadinimų taisykles, įrankių tvarką ir vizualines nuostatas. Sukūrę atsarginę kopiją galite eksperimentuoti su nustatymais arba iš naujo įdiegti programą, neatkuriant darbo eigos iš atminties. + Sukurkite atsarginę kopiją pakeitę išankstinius nustatymus, failo pavadinimo elgseną, numatytuosius formatus ar įrankių išdėstymą. + Jei planuojate išvalyti duomenis, iš naujo įdiegti ar perkelti įrenginius, saugokite atsarginę kopiją kur nors už programos aplanko ribų. + Atkurkite nustatymus prieš apdorodami svarbias partijas, kad numatytieji ir išsaugojimo veiksmai atitiktų senąją sąranką. + Pakeiskite ir konvertuokite vaizdus + Pakeiskite vieno ar kelių vaizdų dydį, formatą, kokybę ir metaduomenis + Sukurkite pakeisto dydžio kopijas + Keisti dydį ir konvertuoti yra pagrindinis įrankis nuspėjamiems matmenims ir lengvesniems išvesties failams. Naudokite jį, kai tuo pačiu metu svarbūs vaizdo dydis, išvesties formatas ir metaduomenų elgsena. + Pasirinkite vieną ar daugiau vaizdų, tada pasirinkite, ar matmenys, mastelis ar failo dydis yra svarbiausi. + Pasirinkite išvesties formatą ir kokybę; naudoti PNG arba WebP, kai reikia išsaugoti skaidrumą. + Peržiūrėkite rezultatą, tada išsaugokite arba bendrinkite sukurtas kopijas nekeisdami originalų. + Keisti dydį pagal failo dydį + Taikykite dydžio apribojimą, kai svetainė, pranešimų programa ar forma atmeta sunkius vaizdus + Laikykitės griežtų įkėlimo apribojimų + Pakeisti dydį pagal failo dydį yra geriau nei spėlioti kokybės reikšmes, kai paskirties vieta priima tik tuos failus, kurie yra mažesni už tam tikrą ribą. Įrankis gali koreguoti suspaudimą ir matmenis, kad pasiektų tikslą, o rezultatas būtų tinkamas naudoti. + Įveskite tikslinį dydį, šiek tiek mažesnį nei tikrasis įkėlimo limitas, kad liktų vietos metaduomenų ir teikėjo pakeitimams. + Pirmenybę teikite nuostolingiems nuotraukų formatams, kai taikinys mažas; PNG gali išlikti didelis net ir pakeitus dydį. + Eksportavę patikrinkite veidus, tekstą ir kraštus, nes dėl agresyvaus dydžio taikinių gali atsirasti matomų artefaktų. + Apriboti dydžio keitimą + Sumažinkite tik tuos vaizdus, ​​kurie viršija maksimalų plotį, aukštį arba failo apribojimus + Venkite keisti vaizdų, kurie jau yra tinkami, dydį + Limit Resize yra naudinga mišriems aplankams, kur vieni vaizdai yra didžiuliai, o kiti jau paruošti. Užuot priverstą keisti kiekvieno failo dydį, jis išlaiko priimtinus vaizdus arčiau pradinės kokybės. + Nustatykite maksimalius matmenis arba ribas pagal paskirties vietą, o ne keiskite kiekvieno failo dydį aklai. + Įgalinkite „praleisti, jei didesnis“ stiliaus elgseną, kai norite išvengti išvesties, kuri tampa sunkesnė už šaltinius. + Naudokite tai skirtingų kamerų paketams, ekrano kopijoms ar atsisiuntimams, kai šaltinio dydžiai labai skiriasi. + Apkarpykite, pasukite ir ištiesinkite + Prieš eksportuodami arba naudodami vaizdą kituose įrankiuose, įrėminkite svarbią sritį + Išvalykite kompoziciją + Apkarpius, vėliau filtrai, OCR, PDF puslapiai ir bendrinimo rezultatai tampa švaresni. + Atidarykite Apkarpyti ir pasirinkite norimą koreguoti vaizdą. + Naudokite laisvą apkarpymą arba formato koeficientą, kai išvestis turi atitikti įrašą, dokumentą ar pseudoportretą. + Prieš išsaugodami pasukite arba apverskite, kad vėliau įrankiai gautų pataisytą vaizdą. + Taikykite filtrus ir reguliuokite + Prieš eksportuodami sukurkite redaguojamą filtrų krūvą ir peržiūrėkite rezultatą + Sureguliuokite vaizdo išvaizdą + Filtrai yra naudingi norint greitai taisyti, stilizuoti išvestį ir paruošti vaizdus OCR arba spausdinti. Maži kontrasto, ryškumo ir ryškumo pakeitimai gali būti svarbesni nei stiprūs efektai, kai vaizde yra teksto arba smulkių detalių. + Pridėkite filtrus po vieną ir stebėkite peržiūrą po kiekvieno pakeitimo. + Priklausomai nuo užduoties reguliuokite ryškumą, kontrastą, ryškumą, suliejimą, spalvą arba kaukes. + Eksportuokite tik tada, kai peržiūra atitinka jūsų tikslinį įrenginį, dokumentą ar socialinę platformą. + Pirmiausia peržiūrėkite + Patikrinkite vaizdus prieš rinkdamiesi sunkesnį redagavimo, konvertavimo ar valymo įrankį + Patikrinkite, ką iš tikrųjų gavote + Vaizdo peržiūra naudinga, kai failas gaunamas iš pokalbių, debesies saugyklos ar kitos programos ir nesate tikri, kas jame yra. Matmenų, skaidrumo, orientacijos ir vaizdo kokybės patikrinimas pirmiausia padeda pasirinkti tinkamą tolesnių veiksmų įrankį. + Atidarykite vaizdo peržiūrą, kai reikia apžiūrėti kelis vaizdus prieš nuspręsdami, ką su jais daryti. + Ieškokite sukimosi problemų, skaidrių sričių, suspaudimo artefaktų ir netikėtai didelių matmenų. + Pereikite į Keisti dydį, Apkarpyti, Palyginti, EXIF ​​arba Fono šalinimo priemonė tik tada, kai žinote, ką reikia taisyti. + Pašalinkite arba atkurkite foną + Automatiškai ištrinkite foną arba patobulinkite kraštus rankiniu būdu naudodami atkūrimo įrankius + Laikykite temą, pašalinkite likusią dalį + Fono pašalinimas geriausiai veikia, kai derinate automatinį pasirinkimą su kruopščiu rankiniu valymu. Galutinis eksporto formatas yra svarbus tiek pat, kiek ir kaukė, nes JPEG formatu permatomas sritis išlygins, net jei peržiūra atrodo teisinga. + Pradėkite nuo automatinio pašalinimo, jei objektas aiškiai atskirtas nuo fono. + Priartinkite ir perjunkite iš trynimo ir atkūrimo, kad pataisytumėte plaukus, kampus, šešėlius ir smulkias detales. + Išsaugokite kaip PNG arba WebP, kai galutiniame faile reikia skaidrumo. + Pieškite, pažymėkite ir vandens ženklą + Pridėkite rodyklių, pastabų, paryškinimų, parašų arba pasikartojančių vandens ženklų perdangų + Pridėkite matomą informaciją + Žymėjimo įrankiai padeda paaiškinti vaizdą, o vandens ženklai padeda pažymėti arba apsaugoti eksportuotus failus. + Naudokite Draw, kai reikia užrašų, formų, paryškinimo ar greitų komentarų. + Naudokite vandens ženklą, kai išvestyje turėtų būti nuolat rodomas tas pats tekstas arba vaizdo ženklas. + Prieš eksportuodami patikrinkite neskaidrumą, padėtį ir mastelį, kad ženklas būtų matomas, bet neblaškytų dėmesio. + Nubraižyti numatytuosius nustatymus + Norėdami greičiau žymėti, nustatykite linijos plotį, spalvą, kelio režimą, orientacijos užraktą ir didintuvą + Padarykite, kad piešimas būtų nuspėjamas + Piešimo nustatymai yra naudingi, kai dažnai komentuojate ekrano kopijas, pažymite dokumentus arba pasirašote vaizdus. Numatytieji nustatymai sumažina sąrankos laiką, o didintuvas ir orientacijos užraktas palengvina tikslų redagavimą mažuose ekranuose. + Nustatykite numatytąjį linijos plotį ir pieškite spalvas pagal vertes, kurias dažniausiai naudojate rodyklėms, paryškinimams ar parašams. + Naudokite numatytąjį kelio režimą, kai dažniausiai piešiate tos pačios rūšies brūkšnius, figūras ar žymėjimą. + Įgalinkite didintuvą arba orientacijos užraktą, kai pirštu įvedus mažas detales sunku tiksliai įdėti. + Kelių vaizdų maketai + Prieš tvarkydami ekrano kopijas, nuskaitymus ar palyginimo juosteles, pasirinkite tinkamą kelių vaizdų įrankį + Naudokite tinkamą išdėstymo įrankį + Šie įrankiai skamba panašiai, tačiau kiekvienas iš jų yra pritaikytas skirtingos rūšies kelių vaizdų išvestims. Pirmiausia pasirenkant tinkamą, išvengiama kovos su išdėstymo valdikliais, kurie buvo sukurti siekiant kitokio rezultato. + Naudokite Collage Maker kurdami maketus su keliais vaizdais vienoje kompozicijoje. + Jei vaizdai turėtų tapti viena ilga vertikalia arba horizontalia juostele, naudokite vaizdų susiuvimą arba krovimą. + Naudokite vaizdo padalijimą, kai vienas didelis vaizdas turi tapti keliomis mažesnėmis dalimis. + Konvertuoti vaizdo formatus + Kurkite JPEG, PNG, WebP, AVIF, JXL ir kitas kopijas neredaguodami pikselių rankiniu būdu + Pasirinkite darbo formatą + Skirtingi formatai tinka nuotraukoms, ekrano kopijoms, skaidrumui, glaudinimui ar suderinamumui. Konvertavimas yra saugiausias, kai suprantate, ką palaiko paskirties programa ir ar šaltinyje yra alfa, animacijos ar metaduomenų, kuriuos norite išsaugoti. + Naudokite JPEG suderinamoms nuotraukoms, PNG vaizdams be nuostolių ir alfa, o WebP kompaktiškam bendrinimui. + Sureguliuokite kokybę, kai formatas ją palaiko; mažesnės reikšmės sumažina dydį, bet gali pridėti artefaktų. + Laikykite originalus, kol patikrinsite, ar konvertuoti failai tinkamai atidaryti tikslinėje programoje. + Patikrinkite EXIF ​​ir privatumą + Prieš bendrindami jautrias nuotraukas, peržiūrėkite, redaguokite arba pašalinkite metaduomenis + Valdykite paslėptus vaizdo duomenis + EXIF gali būti fotoaparato duomenų, datų, vietos, orientacijos ir kitos vaizde nematomos informacijos. Verta patikrinti prieš viešai bendrinant, palaikymo ataskaitas, prekyvietės sąrašus ar bet kokią nuotrauką, kuri gali atskleisti privačią vietą. + Prieš bendrindami nuotraukas, kuriose gali būti vietos ar privataus įrenginio informacijos, atidarykite EXIF ​​įrankius. + Pašalinkite jautrius laukus arba redaguokite neteisingas žymas, pvz., datą, orientaciją, autorių ar fotoaparato duomenis. + Išsaugokite naują kopiją ir bendrinkite tą kopiją, o ne originalą, kai svarbu privatumas. + Automatinis EXIF ​​valymas + Pagal numatytuosius nustatymus pašalinkite metaduomenis spręsdami, ar reikia išsaugoti datas + Padarykite privatumą numatytuoju + EXIF nustatymų grupė naudinga, kai norite, kad privatumo išvalymas vyktų automatiškai, o ne prisimintų kiekvieną kartą eksportuojant. Laikyti datą Laikas yra atskiras sprendimas, nes datos gali būti naudingos rūšiuojant, bet svarbios bendrinant. + Įgalinkite visada aiškų EXIF, kai dauguma jūsų eksportuojamų failų yra skirti viešam arba pusiau viešam bendrinimui. + Įgalinkite Laikyti datos laiką tik tada, kai išsaugoti fiksavimo laiką yra svarbiau nei pašalinti kiekvieną laiko žymą. + Naudokite rankinį EXIF ​​redagavimą vienkartiniams failams, kai reikia palikti kai kuriuos laukus, o kitus pašalinti. + Valdykite failų pavadinimus + Naudokite priešdėlius, priesagas, laiko žymes, išankstinius nustatymus, kontrolines sumas ir eilės numerius + Kad būtų lengva rasti eksportuojamus elementus + Geras failo pavadinimo modelis padeda rūšiuoti paketus ir apsaugo nuo atsitiktinio perrašymo. Failo vardo nustatymai ypač naudingi, kai eksportuojami failai grįžta į šaltinio aplanką, kur panašūs pavadinimai gali greitai susipainioti. + Nustatymuose nustatykite failo pavadinimo priešdėlį, priesagą, laiko žymą, pradinį pavadinimą, iš anksto nustatytą informaciją arba sekos parinktis. + Naudokite eilės numerius paketams, kur tvarka svarbi, pvz., puslapiams, rėmeliams ar koliažams. + Įjunkite perrašymą tik tada, kai esate tikri, kad esamo aplanko esamus failus saugu pakeisti. + Perrašyti failus + Pakeiskite išvestis atitinkamais pavadinimais tik tada, kai jūsų darbo eiga yra tyčia žalinga + Atsargiai naudokite perrašymo režimą + Perrašyti failus keičia tai, kaip tvarkomi pavadinimų konfliktai. Tai naudinga pakartotinai eksportuojant į tą patį aplanką, bet taip pat gali pakeisti ankstesnius rezultatus, jei failo pavadinimo šablonas vėl sukuria tą patį pavadinimą. + Įgalinti perrašymą tik tiems aplankams, kuriuose tikimasi pakeisti senesnius sugeneruotus failus ir kuriuos galima atkurti. + Eksportuodami originalus, kliento failus, vienkartinius nuskaitymus ar bet ką be atsarginės kopijos palikite perrašymą išjungtą. + Sujunkite perrašymą su apgalvotu failo pavadinimo šablonu, kad būtų pakeisti tik numatyti išėjimai. + Failų vardų modeliai + Sujunkite originalius pavadinimus, priešdėlius, priesagas, laiko žymes, išankstinius nustatymus, mastelio režimą ir kontrolines sumas + Sukurkite pavadinimus, paaiškinančius išvestį + Failų pavadinimo šablono nustatymai gali paversti eksportuotus failus naudinga istorija, kas jiems nutiko. Tai svarbu paketais, nes aplanko rodinys gali būti vienintelė vieta, kur galite atskirti pradinį dydį, išankstinį nustatymą, formatą ir apdorojimo tvarką. + Naudokite originalų failo pavadinimą, priešdėlį, priesagą ir laiko žymą, kai reikia skaitomų pavadinimų rūšiuojant rankiniu būdu. + Pridėkite išankstinį nustatymą, mastelio režimą, failo dydį arba kontrolinės sumos informaciją, kai išėjimai turi dokumentuoti, kaip jie buvo sukurti. + Venkite atsitiktinių pavadinimų darbo eigoms, kuriose failai turi būti susieti su originalais arba puslapių tvarka. + Pasirinkite, kur bus saugomi failai + Nepraraskite eksporto, nustatydami aplankus, originalaus aplanko išsaugojimą ir vienkartinio išsaugojimo vietas + Išlaikykite nuspėjamus rezultatus + Išsaugoti elgsena yra svarbiausia, kai apdorojate daug failų arba bendrinate failus iš debesies paslaugų teikėjų. Aplanko nustatymai nusprendžia, ar išvestis renkama vienoje žinomoje vietoje, rodoma šalia originalų, ar laikinai nukeliauja kur nors kitur atlikti konkrečią užduotį. + Jei visada norite eksportuoti vienoje vietoje, nustatykite numatytąjį išsaugojimo aplanką. + Norėdami išvalyti darbo eigą, kai išvestis turėtų likti šalia šaltinio failo, naudokite aplanką „Išsaugoti pradiniame aplanke“. + Naudokite vienkartinę išsaugojimo vietą, kai vienai užduočiai reikia kitos paskirties vietos nekeičiant numatytosios. + Vienkartiniai išsaugojimo aplankai + Siųskite kitą eksportą į laikinąjį aplanką nekeisdami įprastos paskirties vietos + Specialius darbus laikykite atskirai + Vienkartinė išsaugojimo vieta naudinga laikiniems projektams, klientų aplankams, valymo seansams ar eksportuojant, kurie neturėtų būti derinami su įprastu aplanku „Image Toolbox“. Tai suteikia trumpalaikį tikslą neperrašant numatytosios sąrankos. + Prieš pradėdami užduotį, kuri nepriklauso įprastoje eksportavimo vietoje, pasirinkite vienkartinį aplanką. + Naudokite jį trumpiems projektams, bendrinamiems aplankams ar paketams, kur išvestis turi likti sugrupuota. + Atlikę užduotį grįžkite į numatytąjį aplanką, kad vėliau eksportuoti elementai netikėtai nepatektų į laikinąją vietą. + Subalansuokite kokybę ir failo dydį + Supraskite, kada reikia keisti matmenis, formatą ar kokybę, o ne spėlioti + Sąmoningai sutraukite failus + Failo dydis priklauso nuo vaizdo matmenų, formato, kokybės, skaidrumo ir turinio sudėtingumo. Jei failas vis dar per sunkus, matmenų keitimas paprastai padeda labiau nei nuolatinis kokybės pablogėjimas. + Pirmiausia pakeiskite matmenis, kai vaizdas didesnis, nei gali rodyti paskirties vieta. + Žemesnė kokybė tik nuostolingiems formatams ir patikrinkite tekstą, kraštus, gradientus ir paviršius po eksportavimo. + Perjunkite formatą, kai kokybės pakeitimų nepakanka, pvz., WebP bendrinimui arba PNG, kad ištekliai būtų aiškiai skaidrūs. + Darbas su animaciniais formatais + Naudokite GIF, APNG, WebP ir JXL įrankius, kai faile yra rėmeliai, o ne viena taškinė schema + Nelaikykite kiekvieno vaizdo nejudančio + Animuotiems formatams reikia įrankių, kurie supranta kadrus, trukmę ir suderinamumą. + Naudokite GIF arba APNG įrankius, kai jums reikia kadro lygio operacijų tiems formatams. + Naudokite WebP įrankius, kai jums reikia modernaus glaudinimo arba animuotos WebP išvesties. + Prieš bendrindami peržiūrėkite animacijos laiką, nes kai kurios platformos konvertuoja arba išlygina animuotus vaizdus. + Palyginkite ir peržiūrėkite išvestį + Prieš eksportuodami patikrinkite pakeitimus, failo dydį ir vizualinius skirtumus + Prieš bendrindami patikrinkite + Palyginimo įrankiai padeda anksti užfiksuoti suspaudimo artefaktus, netinkamas spalvas ar nepageidaujamus pakeitimus. + Atidarykite Palyginti, kai norite peržiūrėti dvi versijas greta. + Padidinkite kraštus, tekstą, gradientus ir skaidrias sritis, kuriose lengviausia nepastebėti problemų. + Jei išvestis per sunki arba neryški, grįžkite į ankstesnį įrankį ir sureguliuokite kokybę arba formatą. + Naudokite PDF įrankių centrą + Sujunkite, padalinkite, pasukite, pašalinkite puslapius, suspauskite, apsaugokite, OCR ir vandenženkliais PDF failus + Pasirinkite PDF operaciją + PDF šakotuvas sugrupuoja dokumentų veiksmus už vieno įvesties taško, kad galėtumėte pasirinkti tikslią operaciją. Pradėkite ten, kai failas jau yra PDF, ir naudokite „Images to PDF“ arba „Dokumentų skaitytuvą“, kai šaltinis vis dar yra vaizdų rinkinys. + Atidarykite PDF įrankius ir pasirinkite veiksmą, atitinkantį jūsų tikslą. + Pasirinkite šaltinio PDF arba vaizdus, ​​tada peržiūrėkite puslapių tvarką, pasukimą, apsaugą arba OCR parinktis. + Išsaugokite sugeneruotą PDF ir atidarykite jį vieną kartą, kad patvirtintumėte puslapių skaičių, tvarką ir skaitomumą. + Paverskite vaizdus į PDF + Sujunkite nuskaitytus dokumentus, ekrano kopijas, kvitus ar nuotraukas į vieną bendrinamą dokumentą + Sukurkite švarų dokumentą + Vaizdai tampa geresniais PDF puslapiais, kai jie apkerpami, tvarkomi ir nuosekliai keičiamas jų dydis. Vaizdų sąrašą laikykite kaip puslapių krūvą: kiekvienas pasukimas, paraštės ir puslapio dydžio pasirinkimas turi įtakos galutinio dokumento skaitomumui. + Pirmiausia apkarpykite arba pasukite vaizdus, ​​kai puslapio kraštai, šešėliai ar padėtis yra neteisingi. + Pasirinkite vaizdus numatyta puslapių tvarka ir pakoreguokite puslapio dydį arba paraštes, jei įrankis juos siūlo. + Eksportuokite PDF, tada prieš siųsdami patikrinkite, ar visi puslapiai yra įskaitomi. + Nuskaityti dokumentus + Užfiksuokite popierinius puslapius ir paruoškite juos PDF, OCR ar bendrinimui + Užfiksuokite skaitomus puslapius + Dokumentų nuskaitymas geriausiai veikia su plokščiais puslapiais, geru apšvietimu ir pataisyta perspektyva. Švarus nuskaitymas prieš OCR arba PDF eksportavimą taupo laiką, nes teksto atpažinimas ir glaudinimas priklauso nuo puslapio kokybės. + Padėkite dokumentą ant kontrastingo paviršiaus, kuriame būtų pakankamai šviesos ir minimalūs šešėliai. + Sureguliuokite aptiktus kampus arba apkirpkite rankiniu būdu, kad puslapis švariai užpildytų kadrą. + Eksportuokite kaip vaizdus arba PDF, tada paleiskite OCR, jei reikia pasirenkamo teksto. + Apsaugokite arba atrakinkite PDF failus + Atidžiai naudokite slaptažodžius ir, jei įmanoma, pasilikite redaguojamą šaltinio kopiją + Saugiai tvarkykite PDF prieigą + Apsaugos įrankiai yra naudingi kontroliuojant bendrinimą, tačiau slaptažodžius lengva prarasti ir sunku atkurti. Apsaugokite kopijas platinimui, laikykite neapsaugotus šaltinio failus atskirai ir patikrinkite rezultatą prieš ką nors ištrindami. + Apsaugokite PDF kopiją, o ne vienintelį šaltinio dokumentą. + Prieš bendrindami apsaugotą failą, išsaugokite slaptažodį saugioje vietoje. + Naudokite atrakinimą tik tiems failams, kuriuos galite redaguoti, tada patikrinkite, ar atrakinta kopija tinkamai atidaryta. + Tvarkykite PDF puslapius + Sąmoningai sujunkite, padalinkite, pertvarkykite, pasukite, apkarpykite, pašalinkite puslapius ir pridėkite puslapių numerius + Pataisykite dokumento struktūrą prieš dekoravimą + PDF puslapių įrankius lengviausia naudoti ta pačia tvarka, kaip ruošiate popierinį dokumentą: surinkite puslapius, pašalinkite netinkamus, sutvarkykite juos, pataisykite orientaciją, tada pridėkite galutinę informaciją, pvz., puslapių numerius ar vandens ženklus. + Jei dokumentas vis dar padalintas į kelis failus, pirmiausia naudokite sujungimą arba vaizdų į PDF formatą. + Pertvarkykite, pasukite, apkarpykite arba pašalinkite puslapius prieš pridėdami puslapių numerius, parašus ar vandens ženklus. + Peržiūrėkite galutinį PDF failą po struktūrinių redagavimo, nes vieną trūkstamą arba pasuktą puslapį vėliau gali būti sunku pastebėti. + PDF anotacijos + Pašalinkite komentarus arba išlyginkite juos, kai žiūrintieji skirtingai rodo komentarus ir žymes + Valdykite, kas lieka matoma + Komentarai gali būti komentarai, paryškinimai, brėžiniai, formų ženklai ar kiti papildomi PDF objektai. Kai kurie žiūrovai juos rodo skirtingai, todėl juos pašalinus arba išlyginus bendrinamus dokumentus gali būti lengviau nuspėjami. + Pašalinkite komentarus, kai komentarai, paryškinimai ar peržiūros ženklai neturėtų būti įtraukti į bendrinamą kopiją. + Išlyginkite, kai matomi ženklai turėtų likti puslapyje, bet nebeveikti kaip redaguojami komentarai. + Išsaugokite originalų kopiją, nes panaikinus arba išlyginus komentarus gali būti sunku atšaukti. + Atpažinti tekstą + Ištraukite tekstą iš vaizdų naudodami pasirenkamus OCR variklius ir kalbas + Gaukite geresnių OCR rezultatų + OCR tikslumas priklauso nuo vaizdo ryškumo, kalbos duomenų, kontrasto ir puslapio orientacijos. Geriausi rezultatai paprastai gaunami pirmiausia paruošus vaizdą: pašalinkite triukšmą, pasukite vertikaliai, padidinkite skaitomumą, tada atpažinkite tekstą. + Apkarpykite vaizdą iki teksto srities ir pasukite jį vertikaliai prieš atpažindami. + Pasirinkite tinkamą kalbą ir atsisiųskite kalbos duomenis, jei programa to prašo. + Nukopijuokite arba bendrinkite atpažintą tekstą, tada rankiniu būdu patikrinkite vardus, skaičius ir skyrybos ženklus. + Pasirinkite OCR kalbos duomenis + Pagerinkite atpažinimą suderindami scenarijus, kalbas ir atsisiųstus OCR modelius + Suderinkite OCR su tekstu + Dėl netinkamos OCR kalbos geros nuotraukos gali atrodyti kaip netinkamas atpažinimas. Kalbos duomenys svarbūs rašant scenarijus, skyrybos ženklus, skaičius ir mišrius dokumentus, todėl pasirinkite kalbą, kuri rodoma paveikslėlyje, o ne telefono vartotojo sąsajos kalbą. + Pasirinkite kalbą arba scenarijų, kuri rodoma paveikslėlyje, o ne tik telefono kalbą. + Atsisiųskite trūkstamus duomenis prieš dirbdami neprisijungę arba apdorodami paketą. + Jei dokumentai yra mišrių kalbų, pirmiausia paleiskite svarbiausią kalbą ir rankiniu būdu patikrinkite pavadinimus ir numerius. + Ieškomi PDF failai + Įrašykite OCR tekstą atgal į failus, kad būtų galima ieškoti nuskaitytų puslapių ir juos kopijuoti + Paverskite nuskaitytus dokumentus, kuriuose galima ieškoti + OCR gali padaryti daugiau nei nukopijuoti tekstą į mainų sritį. Kai rašote atpažintą tekstą į failą arba PDF, kuriame galima ieškoti, puslapio vaizdas lieka matomas, o teksto tampa lengviau ieškoti, pasirinkti, indeksuoti ar archyvuoti. + Naudokite ieškomą PDF išvestį nuskaitytiems dokumentams, kurie vis tiek turėtų atrodyti kaip originalūs puslapiai. + Naudokite rašymą į failą, kai jums reikia tik ištraukto teksto ir jums nerūpi puslapio vaizdas. + Patikrinkite svarbius skaičius, pavadinimus ir lenteles rankiniu būdu, nes OCR teksto galima ieškoti, bet vis tiek netobulas. + Nuskaitykite QR kodus + Skaitykite QR turinį iš fotoaparato įvesties arba išsaugotų vaizdų, jei įmanoma + Saugiai skaitykite kodus + QR koduose gali būti nuorodų, „Wi-Fi“ duomenų, kontaktų, teksto ar kitų naudingų dalykų. + Atidarykite Scan QR Code ir laikykite kodą aštrų, plokščią ir visiškai rėmelio viduje. + Prieš atidarydami nuorodas arba kopijuodami neskelbtinus duomenis, peržiūrėkite iššifruotą turinį. + Jei nuskaityti nepavyksta, pagerinkite apšvietimą, apkirpkite kodą arba išbandykite didesnės raiškos šaltinio vaizdą. + Naudokite Base64 ir kontrolines sumas + Užkoduokite vaizdus kaip tekstą, iššifruokite tekstą atgal į vaizdus ir patikrinkite failo vientisumą + Pereiti tarp failų ir teksto + „Base64“ yra naudinga mažiems vaizdams, o kontrolinės sumos padeda patvirtinti, kad failai nepasikeitė. Šie įrankiai yra naudingiausi atliekant technines darbo eigas, teikiant palaikymo ataskaitas, perduodant failus ir ten, kur dvejetainiai failai turi laikinai tapti tekstu. + Naudokite Base64 įrankius, norėdami užkoduoti mažą vaizdą, kai darbo eigai reikia teksto, o ne dvejetainio failo. + Iššifruokite Base64 tik iš patikimų šaltinių ir prieš išsaugodami patikrinkite peržiūrą. + Naudokite kontrolinės sumos įrankius, kai reikia palyginti eksportuotus failus arba patvirtinti įkėlimą / atsisiuntimą. + Supakuokite arba apsaugokite duomenis + Naudokite ZIP ir šifravimo įrankius failams sujungti arba tekstiniams duomenims transformuoti + Laikykite susijusius failus kartu + Pakavimo įrankiai yra naudingi, kai keli išėjimai turi keliauti kaip vienas failas. Jie taip pat tinka generuotiems vaizdams, PDF failams, žurnalams ir metaduomenims laikyti kartu, kai reikia išsiųsti visą rezultatų rinkinį. + Naudokite ZIP, kai reikia kartu siųsti daug eksportuotų vaizdų ar dokumentų. + Šifravimo įrankius naudokite tik tada, kai suprantate pasirinktą metodą ir galite saugiai saugoti reikiamus raktus. + Prieš ištrindami šaltinio failus, patikrinkite sukurtą archyvą arba pakeistą tekstą. + Kontrolinės sumos pavadinimuose + Naudokite kontroline suma pagrįstą failų pavadinimus, kai unikalumas ir vientisumas yra svarbesni nei skaitomumas + Pavadinkite failus pagal turinį + Kontrolinės sumos failų pavadinimai yra naudingi, kai eksportuojami failai gali turėti pasikartojančius matomus pavadinimus arba kai reikia įrodyti, kad du failai yra identiški. Jie yra mažiau patogūs naršyti rankiniu būdu, todėl naudokite juos techniniams archyvams ir tikrinimo darbo eigoms. + Įgalinkite kontrolinę sumą kaip failo pavadinimą, kai turinio tapatybė yra svarbesnė nei žmogaus skaitomas pavadinimas. + Naudokite jį archyvams, dubliavimo panaikinimui, pakartotiniam eksportavimui arba failams, kuriuos galima įkelti ir vėl atsisiųsti. + Suporuokite kontrolinių sumų pavadinimus su aplankais arba metaduomenimis, kai žmonėms vis tiek reikia suprasti, ką reiškia failas. + Pasirinkite spalvas iš paveikslėlių + Paimkite pikselių pavyzdžius, nukopijuokite spalvų kodus ir pakartotinai naudokite spalvas paletėse ar dizainuose + Išbandykite tikslią spalvą + Spalvų parinkimas naudingas derinant dizainą, išgaunant prekės ženklo spalvas arba tikrinant vaizdo vertes. Mastelio keitimas yra svarbus, nes dėl antialias, šešėlių ir suspaudimo gretimi pikseliai gali šiek tiek skirtis nuo spalvos, kurių tikitės. + Atidarykite Pasirinkti spalvą iš vaizdo ir pasirinkite šaltinio vaizdą su reikiama spalva. + Prieš imdami smulkias detales, priartinkite, kad šalia esantys pikseliai nepaveiktų rezultato. + Nukopijuokite spalvą formatu, kurio reikalauja jūsų paskirties vieta, pvz., HEX, RGB arba HSV. + Sukurkite paletes ir naudokite spalvų biblioteką + Išskleiskite paletes, naršykite išsaugotas spalvas ir palaikykite nuoseklias vaizdo sistemas + Sukurkite daugkartinę paletę + Paletės padeda išlaikyti eksportuojamą grafiką, vandens ženklus ir piešinius vizualiai nuoseklius. Jie ypač naudingi, kai pakartotinai komentuojate ekrano kopijas, ruošiate prekės ženklo išteklius arba kai reikia tų pačių spalvų keliuose įrankiuose. + Sukurkite paletę iš vaizdo arba pridėkite spalvas rankiniu būdu, kai jau žinote reikšmes. + Pavadinkite arba sutvarkykite svarbias spalvas, kad vėliau galėtumėte jas rasti. + Naudokite nukopijuotas reikšmes Draw, vandens ženkle, gradiente, SVG arba išoriniuose projektavimo įrankiuose. + Sukurkite gradientus + Kurkite gradiento vaizdus, ​​​​sukurkite fono, vietos rezervavimo ženklus ir vaizdinius eksperimentus + Valdykite spalvų perėjimus + Gradiento įrankiai naudingi, kai reikia sugeneruoto vaizdo, o ne redaguoti esamą. Jie gali tapti švariais fonais, vietos rezervavimo ženklais, tapetais, kaukėmis ar paprastais išorinių projektavimo įrankių ištekliais. + Pasirinkite gradiento tipą ir pirmiausia nustatykite svarbias spalvas. + Sureguliuokite kryptį, sustojimus, lygumą ir išvesties dydį pagal tikslinį naudojimo atvejį. + Eksportuokite formatu, kuris atitinka paskirties vietą, paprastai PNG formatu, kad būtų sukurta švari grafika. + Spalvų prieinamumas + Jei vartotojo sąsają sunku perskaityti, naudokite dinamines spalvas, kontrastą ir spalvų aklumo parinktis + Lengviau naudoti spalvas + Spalvų nustatymai turi įtakos patogumui, skaitomumui ir valdiklių nuskaitymo greičiui. Jei įrankių lustus, slankiklius ar pasirinktas būsenas sunku atskirti, temos ir pritaikymo neįgaliesiems parinktys gali būti naudingesnės nei tiesiog tamsaus režimo keitimas. + Išbandykite dinamines spalvas, kai norite, kad programa atitiktų esamą tapetų paletę. + Padidinkite kontrastą arba pakeiskite schemą, kai mygtukai ir paviršiai nepakankamai išsiskiria. + Jei sunku atskirti kai kurias būsenas ar akcentus, naudokite spalvų aklumo schemos parinktis. + Alfa fonas + Valdykite peržiūros arba užpildymo spalvą, naudojamą aplink skaidrius PNG, WebP ir panašius išėjimus + Supraskite skaidrias peržiūras + Alfa formatų fono spalva gali palengvinti skaidrių vaizdų patikrinimą, tačiau svarbu žinoti, ar keičiate peržiūros / užpildymo elgseną, ar išlyginate galutinį rezultatą. Tai svarbu lipdukams, logotipams ir fone pašalintiems vaizdams. + Pirmiausia naudokite alfa palaikantį formatą, pvz., PNG arba WebP, kai skaidrūs pikseliai turi išgyventi eksportuojant. + Sureguliuokite fono spalvų nustatymus, kai permatomus kraštus sunku įžiūrėti programos paviršiuje. + Eksportuokite nedidelį testą ir iš naujo atidarykite jį kitoje programoje, kad patikrintumėte, ar išsaugotas skaidrumas arba pasirinktas užpildymas. + AI modelio pasirinkimas + Pasirinkite modelį pagal vaizdo tipą ir defektą, o ne pirmiausia pasirinkite didžiausią + Suderinkite modelį su pažeidimu + AI įrankiai gali atsisiųsti arba importuoti skirtingus ONNX / ORT modelius ir kiekvienas modelis yra apmokytas spręsti tam tikros rūšies problemą. JPEG blokų, anime linijos valymo, triukšmo mažinimo, fono pašalinimo, spalvinimo ar mastelio padidinimo modelis gali duoti blogesnių rezultatų, kai naudojamas netinkamo tipo vaizdams. + Prieš atsisiųsdami perskaitykite modelio aprašymą; pasirinkite modelį, įvardijantį tikrąją jūsų problemą, pvz., suspaudimą, triukšmą, pustonius, suliejimą ar fono pašalinimą. + Bandydami naują vaizdo tipą pradėkite nuo mažesnio ar konkretesnio modelio, tada palyginkite su sunkesniais modeliais tik tuo atveju, jei rezultatas nėra pakankamai geras. + Laikykite tik modelius, kuriuos tikrai naudojate, nes atsisiųsti ir importuoti modeliai gali užimti daug vietos saugykloje. + Suprasti pavyzdines šeimas + Modelių pavadinimai dažnai užsimena apie jų tikslą: „RealESRGAN“ stiliaus modeliai aukštesnio lygio, „SCUNet“ ir „FBCNN“ modeliai pašalina triukšmą arba suspaudimą, foniniai modeliai sukuria kaukes, o spalvintojai prideda spalvų pilkos spalvos įvestis. Importuoti pasirinktiniai modeliai gali būti galingi, tačiau jie turi atitikti numatomą įvesties / išvesties formą ir naudoti palaikomą ONNX arba ORT formatą. + Naudokite didintuvus, kai reikia daugiau pikselių, triukšmo slopintuvus, kai vaizdas yra grūdėtas, ir artefaktų šalinimo priemones, kai pagrindinė problema yra suspaudimo blokai arba juostos. + Naudokite foninius modelius tik objekto atskyrimui; jie nėra tas pats, kas bendras objekto pašalinimas ar vaizdo pagerinimas. + Importuokite pasirinktinius modelius tik iš šaltinių, kuriais pasitikite, ir prieš naudodami svarbius failus išbandykite juos mažame paveikslėlyje. + AI parametrai + Sureguliuokite dalies dydį, persidengimą ir lygiagrečius darbuotojus, kad subalansuotumėte kokybę, greitį ir atmintį + Subalansuokite greitį su stabilumu + Gabalo dydis valdo vaizdo dalių dydį, kol modelis juos apdoroja. Persidengimas sulieja gretimus gabalus, kad paslėptų kraštelius. Lygiagrečiai dirbantys darbuotojai gali pagreitinti apdorojimą, tačiau kiekvienam darbuotojui reikia atminties, todėl didelės reikšmės gali padaryti didelius vaizdus nestabilius telefonuose su ribota RAM. + Naudokite didesnius gabalus, kad kontekstas būtų sklandesnis, kai įrenginys patikimai tvarko modelį ir vaizdas nėra didžiulis. + Padidinkite persidengimą, kai tampa matomos gabalų kraštinės, tačiau atminkite, kad didesnis persidengimas reiškia, kad darbas kartojamas. + Lygiagrečius darbuotojus kelti tik po stabilaus testo; jei apdorojimas sulėtėja, įkaista įrenginys arba sugenda, pirmiausia sumažinkite darbuotojų skaičių. + Venkite gabalėlių artefaktų + Cunking leidžia programai vienu metu apdoroti didesnius vaizdus, ​​nei gali patogiai apdoroti modelis. Kompromisas yra tas, kad kiekvieną plytelę supantis kontekstas yra mažesnis, todėl dėl mažo persidengimo arba per mažų gabalėlių gali atsirasti matomų kraštinių, tekstūros pokyčių arba nenuoseklių gretimų sričių detalių. + Padidinkite persidengimą, jei matote stačiakampes kraštines, pasikartojančius tekstūros pokyčius arba detalių šuolius tarp apdorotų regionų. + Sumažinkite gabalo dydį, jei procesas nepavyksta prieš sukuriant išvestį, ypač naudojant didelius vaizdus arba sunkius modelius. + Išsaugokite nedidelį apkarpymo testą prieš apdorodami visą vaizdą, kad galėtumėte greitai palyginti parametrus. + AI stabilumas + Mažesnis gabalo dydis, darbuotojai ir šaltinio skiriamoji geba, kai dirbtinio intelekto apdorojimas sugenda arba užstringa + Atsigaukite po sunkių dirbtinio intelekto darbų + AI apdorojimas yra viena sunkiausių darbo eigų programoje. Strigtys, nepavykusios sesijos, užstrigimai arba staigus programos paleidimas iš naujo paprastai reiškia, kad modelis, šaltinio skiriamoji geba, gabalo dydis arba lygiagrečių darbuotojų skaičius yra per daug sudėtingas esamai įrenginio būsenai. + Jei programa užstringa arba nepavyksta atidaryti modelio seanso, sumažinkite lygiagrečių darbuotojų skaičių ir gabalo dydį, tada dar kartą bandykite tą patį vaizdą. + Pakeiskite labai didelių vaizdų dydį prieš apdorojant dirbtinį intelektą, kai tikslui nereikia pradinės raiškos. + Uždarykite kitas sunkias programas, naudokite mažesnį modelį arba vienu metu apdorokite mažiau failų, kai atminties įtampa vis atsinaujina. + Diagnozuoti modelio gedimus + Nesėkmingas AI paleidimas ne visada reiškia, kad vaizdas yra blogas. Modelio failas gali būti neišsamus, nepalaikomas, per didelis atminčiai arba neatitiktis operacijos. Kai programa praneša apie nesėkmingą seansą, laikykite tai signalu, kad pirmiausia supaprastintumėte modelį ir parametrus. + Ištrinkite ir iš naujo atsisiųskite modelį, jei jis sugenda iškart po atsisiuntimo arba elgiasi taip, lyg failas būtų sugadintas. + Prieš kaltindami importuotą tinkintą modelį, išbandykite integruotą atsisiunčiamą modelį, nes importuotų modelių įvestis gali būti nesuderinama. + Jei reikia pranešti apie gedimą arba seanso klaidą, atkūrę gedimą naudokite programų žurnalus. + Eksperimentuokite naudodami „Shader Studio“. + Naudokite GPU šešėlių efektus, kad sukurtumėte pažangų vaizdo apdorojimą ir pritaikytą išvaizdą + Atsargiai dirbkite su šešėliais + „Shader Studio“ yra galinga, tačiau „Shader“ kodui ir parametrams reikia nedidelių, patikrinamų pakeitimų. Laikykite jį kaip eksperimentinį įrankį: išlaikykite veikiančią bazinę liniją, keiskite vieną dalyką vienu metu ir išbandykite naudodami skirtingus vaizdo dydžius, prieš pasitikėdami išankstiniu nustatymu. + Prieš pridėdami parametrus pradėkite nuo žinomo veikiančio atspalvio arba paprasto efekto. + Vienu metu keiskite vieną parametrą arba kodo bloką ir patikrinkite, ar peržiūroje nėra klaidų. + Eksportuokite išankstinius nustatymus tik išbandę juos su keliais skirtingų dydžių vaizdais. + Padarykite SVG arba ASCII men + Generuokite vektorinius išteklius arba tekstinius vaizdus, ​​​​kad sukurtumėte kūrybinius rezultatus + Pasirinkite skelbimo išvesties tipą + SVG ir ASCII įrankiai aptarnauja skirtingus tikslus: keičiamą grafiką arba teksto stiliaus atvaizdavimą. Abi yra kūrybinės konversijos, todėl galutinio dydžio peržiūra yra svarbesnė nei rezultato vertinimas iš mažos miniatiūros. + Naudokite SVG Maker, kai rezultatas turėtų būti švarus arba pakartotinai naudojamas projektavimo darbo eigoje. + Naudokite ASCII Art, kai norite stilizuoto teksto vaizdo. + Peržiūra galutinio dydžio, nes smulkios detalės gali išnykti generuojamoje išvestyje. + Sukurkite triukšmo tekstūras + Kurkite procedūrines tekstūras fonams, kaukėms, perdangoms ar eksperimentams + Suderinkite procedūrinę išvestį + Triukšmo generavimas iš parametrų sukuria naujus vaizdus, ​​todėl nedideli pakeitimai gali duoti labai skirtingus rezultatus. Tai naudinga tekstūroms, kaukėms, perdangoms, vietos rezervavimo priemonėms arba bandant suspaudimą naudojant išsamius raštus. + Prieš derindami smulkias detales, pasirinkite triukšmo tipą ir išvesties dydį. + Koreguokite mastelį, intensyvumą, spalvas arba sėklas, kol modelis atitiks tikslinę paskirtį. + Išsaugokite naudingus rezultatus ir užsirašykite parametrus, jei vėliau reikės atkurti tekstūrą. + Žymėjimo projektai + Naudokite projekto failus, kai uždarius programą komentarus reikia redaguoti + Sluoksniuotus pakeitimus laikykite pakartotinai naudojamus + Žymėjimo projekto failai yra naudingi, kai vėliau gali reikėti pakeisti ekrano kopiją, diagramą ar instrukcijos vaizdą. Eksportuoti PNG / JPEG failai yra galutiniai vaizdai, o projekto failai išsaugo redaguojamus sluoksnius ir redagavimo būseną, kad būtų galima juos koreguoti ateityje. + Naudokite žymėjimo sluoksnius, kai komentarus, figūrines išnašas ar išdėstymo elementus galima redaguoti. + Išsaugokite arba iš naujo atidarykite projekto failą prieš eksportuodami galutinį vaizdą, jei tikitės daugiau pataisymų. + Eksportuokite įprastą vaizdą tik tada, kai būsite pasirengę bendrinti suplotą galutinę versiją. + Šifruoti failus + Apsaugokite failus slaptažodžiu ir išbandykite iššifravimą prieš ištrindami bet kokią šaltinio kopiją + Atsargiai tvarkykite šifruotus išvestis + Šifravimo įrankiai gali apsaugoti failus naudodami slaptažodžiu pagrįstą šifravimą, tačiau jie nepakeičia rakto atsiminimo ar atsarginių kopijų saugojimo. Šifravimas yra naudingas transportuojant ir saugojant, o ZIP yra geresnis, kai pagrindinis tikslas yra sujungti kelis išėjimus. + Pirmiausia užšifruokite kopiją ir laikykite originalą, kol patikrinsite, ar iššifravimas veikia naudojant jūsų saugomą slaptažodį. + Naudokite slaptažodžių tvarkyklę arba kitą saugią vietą raktui; programa negali atspėti už jus prarasto slaptažodžio. + Bendrinkite šifruotus failus tik su žmonėmis, kurie taip pat turi slaptažodį ir žino, kuris įrankis ar metodas turėtų juos iššifruoti. + Sutvarkyti įrankius + Grupuokite, užsakykite, ieškokite ir prisekite įrankius, kad pagrindinis ekranas atitiktų jūsų tikrąją darbo eigą + Padarykite pagrindinį ekraną greičiau + Įrankių išdėstymo nustatymus verta reguliuoti, kai žinote, kuriuos įrankius naudojate dažniausiai. Grupavimas padeda tyrinėti, tinkinta tvarka padeda raumenų atmintims, o mėgstamiausios leidžia kasdienius įrankius pasiekti net tada, kai visas sąrašas tampa didelis. + Naudokite sugrupuotą režimą mokydamiesi programos arba kai pageidaujate įrankių, suskirstytų pagal užduoties tipą. + Perjunkite į tinkintą tvarką, kai jau žinote dažnai naudojamus įrankius ir norite mažiau slinkti. + Kartu naudokite paieškos nustatymus ir mėgstamiausius retus įrankius, kuriuos prisimenate pagal pavadinimą, bet nenorite, kad jie būtų matomi visą laiką. + Tvarkykite didelius failus + Venkite lėto eksporto, atminties spaudimo ir netikėtai didelės išvesties + Sumažinkite darbą prieš eksportą + Labai dideli vaizdai, ilgos partijos ir aukštos kokybės formatai gali užimti daugiau atminties ir laiko. Greičiausias sprendimas paprastai yra sumažinti darbo kiekį prieš sunkiausią operaciją, nelaukiant, kol baigsis ta pati per didelė įvestis. + Pakeiskite didžiulių vaizdų dydį prieš taikydami sunkius filtrus, OCR arba PDF konvertavimą. + Pirmiausia naudokite mažesnę partiją, kad patvirtintumėte nustatymus, tada apdorokite likusią dalį naudodami tą patį išankstinį nustatymą. + Žemesnė kokybė arba pakeiskite formatą, kai išvesties failas didesnis nei tikėtasi. + Talpykla ir peržiūros + Supraskite sugeneruotas peržiūras, talpyklos valymą ir saugyklos naudojimą po sunkių seansų + Subalansuokite saugyklą ir greitį + Peržiūros generavimas ir talpykla gali pagreitinti pakartotinį naršymą, tačiau intensyvios redagavimo sesijos gali palikti laikinus duomenis. Talpyklos nustatymai padeda, kai programa veikia lėtai, trūksta saugyklos arba peržiūros po daugelio eksperimentų atrodo pasenusios. + Naršant daug vaizdų, kad peržiūros būtų įjungtos, yra svarbiau nei sumažinti laikiną saugyklą. + Išvalykite talpyklą po didelių partijų, nesėkmingų eksperimentų ar ilgų seansų su daugybe didelės raiškos failų. + Naudokite automatinį talpyklos išvalymą, jei norite išvalyti saugyklą, o ne palaikyti šiltas peržiūras tarp paleidimų. + Sureguliuokite ekrano elgesį + Sutelktam darbui naudokite viso ekrano, saugaus režimo, šviesumo ir sistemos juostos parinktis + Kad sąsaja atitiktų situaciją + Ekrano nustatymai padeda, kai redaguojate gulsčią padėtį, pateikiate privačius vaizdus arba reikia pastovaus šviesumo. Jie nėra būtini įprastam naudojimui, tačiau jie išsprendžia nepatogias situacijas, tokias kaip QR tikrinimas, privačios peržiūros ar siauras kraštovaizdžio redagavimas. + Naudokite viso ekrano arba sistemos juostos nustatymus, kai vietos redagavimas yra svarbesnis nei naršymo chromas. + Naudokite saugų režimą, kai privatūs vaizdai neturėtų būti rodomi ekrano kopijose ar programų peržiūrose. + Naudokite ryškumo užtikrinimą įrankiams, kuriuose tamsius vaizdus ar QR kodus sunku patikrinti. + Išsaugokite skaidrumą + Neleiskite, kad permatomas fonas taptų baltas, juodas arba suplotas + Naudokite alfa-aware išvestį + Skaidrumas išlieka tik tada, kai pasirinktas įrankis ir išvesties formatas palaiko alfa. Jei eksportuotas fonas pasidaro baltas arba juodas, problema dažniausiai yra išvesties formatas, užpildymo / fono nustatymas arba paskirties programa, kuri išlygino vaizdą. + Pasirinkite PNG arba WebP, kai eksportuojate fone pašalintus vaizdus, ​​lipdukus, logotipus ar perdangas. + Venkite JPEG, kad išvestis būtų skaidri, nes ji nesaugo alfa kanalo. + Jei peržiūroje rodomas užpildytas fonas, patikrinkite nustatymus, susijusius su alfa formatų fono spalva. + Išspręskite bendrinimo ir importavimo problemas + Naudokite rinkiklio nustatymus ir palaikomus formatus, kai failai nerodomi arba tinkamai neatsidaro + Įkelkite failą į programą + Importavimo problemas dažnai sukelia teikėjo leidimai, nepalaikomi formatai arba rinkiklio elgsena. Failai, bendrinami iš debesies programų ir pasiuntinių, gali būti laikini, todėl ilgalaikio šaltinio pasirinkimas gali būti patikimesnis atliekant ilgesnius redagavimus. + Pabandykite atidaryti failą naudodami sistemos rinkiklį, o ne naujausių failų nuorodą. + Jei formato nepalaiko vienas įrankis, pirmiausia konvertuokite jį arba atidarykite konkretesnį įrankį. + Peržiūrėkite vaizdo rinkiklio ir paleidimo priemonės nustatymus, jei bendrinimo srautai arba tiesioginis įrankio atidarymas veikia kitaip, nei tikėtasi. + Išėjimo patvirtinimas + Nepraraskite neišsaugotų pakeitimų, kai netyčia atliekami gestai, paspaudimai atgal arba įrankių perjungimas + Apsaugokite nebaigtus darbus + Įrankio išjungimo patvirtinimas yra naudingas, kai naudojate naršymą gestais, redaguojate didelius failus arba dažnai perjungiate programas. Tai prideda nedidelę pauzę prieš paliekant įrankį, kad nebaigti nustatymai ir peržiūros nebūtų prarasti atsitiktinai. + Įgalinkite patvirtinimą, jei atsitiktiniai atgaliniai gestai kada nors uždarė įrankį prieš eksportuojant. + Laikykite jį išjungtą, jei dažniausiai atliekate greitas vieno veiksmo konversijas ir pageidaujate greitesnės naršymo. + Naudokite jį kartu su išankstiniais nustatymais ilgesnėms darbo eigoms, kai nustatymų atkūrimas erzintų. + Pranešdami apie problemas naudokite žurnalus + Prieš pradėdami spręsti problemą arba susisiekdami su kūrėju, surinkite naudingos informacijos + Praneškite apie problemas, susijusias su kontekstu + Aiškią ataskaitą su veiksmais, programos versija, įvesties tipu ir žurnalais diagnozuoti daug lengviau. Žurnalai yra naudingiausi iškart po problemos atkūrimo, nes juos supantis kontekstas išlieka šviežias. + Atkreipkite dėmesį į įrankio pavadinimą, tikslų veiksmą ir tai, ar tai vyksta su vienu failu, ar su kiekvienu failu. + Atkūrę problemą atidarykite programų žurnalus ir, jei įmanoma, bendrinkite atitinkamą žurnalo išvestį. + Pridėkite ekrano kopijas arba pavyzdinius failus tik tada, kai juose nėra privačios informacijos. + Fono apdorojimas buvo sustabdytas + „Android“ neleido laiku pradėti foninio apdorojimo pranešimo. Tai yra sistemos laiko apribojimas, kurio negalima patikimai nustatyti. Iš naujo paleiskite programą ir bandykite dar kartą; gali padėti išlaikyti programą atidarytą ir leisti pranešimus arba atlikti foninį darbą. + Praleisti failų rinkimą + Atidarykite įrankius greičiau, kai įvestis paprastai gaunama iš bendrinimo, mainų srities arba ankstesnio ekrano + Paspartinkite pakartotinį įrankių paleidimą + Praleisti failų pasirinkimą pakeičia pirmąjį palaikomų įrankių veiksmą. Tai naudinga, kai paprastai įvedate įrankį su jau pasirinktu vaizdu arba iš Android bendrinimo veiksmų, tačiau gali kilti painiava, jei tikitės, kad kiekvienas įrankis iš karto paprašys failo. + Įgalinkite jį tik tada, kai įprastas srautas jau pateikia šaltinio vaizdą prieš atidarant įrankį. + Mokydamiesi programos palikite jį išjungtą, nes aiškus pasirinkimas leidžia lengviau suprasti kiekvieną įrankį. + Jei įrankis atidaromas be akivaizdžios įvesties, išjunkite šį nustatymą arba pradėkite nuo „Bendrinti / Atidaryti naudojant“, kad „Android“ pirmiausia perduotų failą. + Pirmiausia atidarykite redaktorių + Praleiskite peržiūrą, kai bendrinamas vaizdas turėtų būti iškart redaguojamas + Pasirinkite peržiūrą arba redagavimą kaip pirmąją stotelę + Atidaryti redaguoti vietoj peržiūros skirta vartotojams, kurie jau žino, kad nori pakeisti vaizdą. Peržiūra yra saugesnė, kai tikrinate failus iš pokalbių arba debesies saugyklos; Tiesioginis redagavimas yra greitesnis ekrano kopijoms, greitam apkarpymui, komentarams ir vienkartiniams pataisymams. + Įgalinkite tiesioginį redagavimą, jei daugumą bendrinamų vaizdų reikia nedelsiant apkarpyti, pažymėti, filtruoti arba eksportuoti. + Pirmiausia palikite peržiūrą, kai dažnai tikrinate metaduomenis, matmenis, skaidrumą ar vaizdo kokybę prieš priimdami sprendimą. + Naudokite tai kartu su mėgstamiausiais, kad bendrinami vaizdai būtų artimi tiems įrankiams, kuriuos iš tikrųjų naudojate. + Iš anksto nustatytas teksto įvedimas + Įveskite tikslias iš anksto nustatytas reikšmes, o ne pakartotinai reguliuokite valdiklius + Naudokite tikslias reikšmes, kai slankikliai veikia lėtai + Iš anksto nustatytas teksto įvedimas padeda, kai reikia tikslių skaičių pakartotiniam darbui: socialinių vaizdų dydžių, dokumento paraščių, suspaudimo tikslų, linijų pločio ar kitų verčių, kurias erzina pasiekti gestais. Tai taip pat naudinga mažuose ekranuose, kur smulkus slankiklio judėjimas yra sunkesnis. + Įgalinkite teksto įvedimą, jei dažnai pakartotinai naudojate tikslius dydžius, procentus, kokybės reikšmes arba įrankio parametrus. + Vieną kartą įvedę reikšmę išsaugokite kaip išankstinį nustatymą, tada naudokite ją dar kartą, užuot atkūrę tą pačią sąranką. + Išsaugokite gestų valdiklius, kad apytikriai derintumėte vaizdą, ir įvestas reikšmes, kad atitiktumėte griežtus išvesties reikalavimus. + Išsaugoti šalia originalo + Padėkite sugeneruotus failus šalia šaltinio vaizdų, kai svarbus aplanko kontekstas + Išveskite duomenis su jų šaltiniais + Įrašyti į originalų aplanką yra patogus fotoaparato aplankams, projektų aplankams, dokumentų nuskaitymui ir klientų paketams, kur redaguota kopija turėtų likti šalia šaltinio. Jis gali būti ne toks tvarkingas nei tam skirtas išvesties aplankas, todėl derinkite jį su aiškiomis failo pavadinimo priesagomis ar raštais. + Įjunkite jį, kai kiekvienas šaltinio aplankas jau yra prasmingas ir išėjimai turėtų likti ten sugrupuoti. + Naudokite failų pavadinimų priesagas, priešdėlius, laiko žymes arba šablonus, kad redaguotas kopijas būtų lengva atskirti nuo originalų. + Jei norite, kad visi sugeneruoti failai būtų surinkti į vieną eksportavimo aplanką, jį išjunkite mišrioms grupėms. + Praleisti didesnę produkciją + Venkite išsaugoti konversijų, kurios netikėtai tampa sunkesnės už šaltinį + Apsaugokite partijas nuo blogo dydžio netikėtumų + Kai kurios konversijos padidina failus, ypač PNG ekrano kopijas, jau suglaudintas nuotraukas, aukštos kokybės WebP arba vaizdus su išsaugotais metaduomenimis. Leisti praleisti, jei didesnis neleidžia tiems išvestims pakeisti mažesnio šaltinio darbo eigose, kuriose pagrindinis tikslas yra sumažinti dydį. + Įgalinkite jį, kai eksportuojant siekiama suspausti, pakeisti dydį arba paruošti failus įkėlimo apribojimams. + Peržiūrėkite praleistus failus po paketo; jie jau gali būti optimizuoti arba gali prireikti kito formato. + Išjunkite jį, kai kokybė, skaidrumas ar formato suderinamumas yra svarbesni nei galutinis failo dydis. + Iškarpinė ir nuorodos + Valdykite automatinį iškarpinės įvestį ir nuorodų peržiūras, kad gautumėte greitesnius tekstinius įrankius + Padarykite automatinį įvestį nuspėjamą + Iškarpinės ir nuorodų nustatymai yra patogūs QR, Base64, URL ir daug teksto reikalaujančioms darbo eigoms, tačiau automatinė įvestis turėtų likti apgalvota. Jei atrodo, kad programa pati užpildo laukus, patikrinkite iškarpinės įklijavimo elgseną; jei nuorodų kortelės yra triukšmingos arba lėtos, pakoreguokite nuorodų peržiūros veikimą. + Leiskite automatinį įklijavimą į iškarpinę, kai dažnai kopijuojate tekstą ir iškart atidarote atitikimo įrankį. + Išjunkite automatinį įklijavimą, jei privatus iškarpinės turinys rodomas įrankiuose, kur to nesitikėjote. + Išjunkite nuorodų peržiūras, kai peržiūros gavimas yra lėtas, blaško dėmesį arba nereikalingas jūsų darbo eigai. + Kompaktiški valdikliai + Naudokite tankesnius parinkiklius ir greitą nustatymų vietą, kai ekrane trūksta vietos + Mažuose ekranuose pritaikykite daugiau valdiklių + Kompaktiški parinkikliai ir greitas nustatymų išdėstymas padeda mažiems telefonams, kraštovaizdžio redagavimui ir įrankiams su daugybe parametrų. Kompromisas yra tas, kad valdikliai gali atrodyti tankesni, todėl tai geriausia tada, kai jau atpažįstate įprastas parinktis. + Įgalinkite kompaktiškus parinkiklius, kai dialogo langai arba parinkčių sąrašai atrodo per aukšti ekranui. + Sureguliuokite greitųjų nustatymų pusę, jei įrankių valdiklius lengviau pasiekti iš vienos ekrano pusės. + Grįžkite į erdvesnius valdiklius, jei vis dar mokate programą arba dažnai nepaliečiate tankių parinkčių. + Įkelti vaizdus iš žiniatinklio + Įklijuokite puslapio arba vaizdo URL, peržiūrėkite analizuotus vaizdus, ​​tada išsaugokite arba bendrinkite pasirinktus rezultatus + Sąmoningai naudokite žiniatinklio vaizdus + Web Image Loading analizuoja vaizdo šaltinius iš pateikto URL, peržiūri pirmąjį galimą vaizdą ir leidžia išsaugoti arba bendrinti pasirinktus analizuotus vaizdus. Kai kuriose svetainėse naudojamos laikinos, apsaugotos arba scenarijaus sukurtos nuorodos, todėl naršyklėje veikiantis puslapis vis tiek gali nepateikti tinkamų vaizdų. + Įklijuokite tiesioginį vaizdo URL arba puslapio URL ir palaukite, kol pasirodys išnagrinėtų vaizdų sąrašas. + Naudokite rėmelio arba pasirinkimo valdiklius, kai puslapyje yra keli vaizdai ir jums reikia tik kai kurių iš jų. + Jei nieko neįkeliama, pirmiausia pabandykite tiesioginę vaizdo nuorodą arba atsisiųskite per naršyklę; svetainė gali blokuoti analizę arba naudoti privačias nuorodas. + Pasirinkite dydžio keitimo tipą + Supraskite aiškius, lanksčius, apkirptus ir pritaikytus elementus prieš pakeisdami vaizdą į naujus matmenis + Valdykite formą, drobę ir formato santykį + Dydžio keitimo tipas nusprendžia, kas nutiks, kai prašomas plotis ir aukštis neatitinka pradinio formato santykio. Tai yra skirtumas tarp pikselių ištempimo, proporcijų išsaugojimo, papildomo ploto apkarpymo ar fono drobės pridėjimo. + Naudokite aiškų tik tada, kai iškraipymas yra priimtinas arba šaltinio formato santykis jau yra toks pat kaip objekto. + Naudokite Flexible, kad išlaikytumėte proporcijas keisdami dydį iš svarbios pusės, ypač nuotraukoms ir mišrioms partijoms. + Naudokite Apkarpyti, jei norite sukurti griežtus rėmelius be kraštinių, arba pritaikyti, kai visas vaizdas turi likti matomas fiksuotoje drobėje. + Pasirinkite vaizdo mastelio režimą + Pasirinkite pakartotinio atrankos filtrą, kuris valdo ryškumą, lygumą, greitį ir kraštų artefaktus + Pakeiskite dydį be atsitiktinio minkštumo + Vaizdo mastelio režimas valdo, kaip keičiant dydį apskaičiuojami nauji pikseliai. Paprasti režimai yra greiti ir nuspėjami, o pažangūs filtrai gali geriau išsaugoti detales, tačiau gali paryškinti kraštus, sukurti aureolę arba užtrukti ilgiau dideliems vaizdams. + Naudokite „Basic“ arba „Blinear“, kad kasdien greitai pakeistumėte dydį, kai rezultatas turi būti mažesnis ir švarus. + Naudokite Lanczos, Robidoux, Spline arba EWA stiliaus režimus, kai svarbios smulkios detalės, tekstas arba aukštos kokybės sumažinimas. + Naudokite „Arčiausiai“ pikselių piešimui, piktogramoms ir grafinei grafikai, kai susilieję pikseliai sugadintų vaizdą. + Spalvų erdvės skalė + Nuspręskite, kaip spalvos maišomos, o pikseliai atrenkami iš naujo keičiant dydį + Išlaikykite gradientus ir kraštus natūralius + Spalvų erdvės mastelio keitimas keičia matematiką, naudojamą keičiant dydį. Daugumai vaizdų tinka numatytasis nustatymas, tačiau dėl linijinių ar suvokiamųjų erdvių gradientai, tamsūs kraštai ir sodrios spalvos gali atrodyti švaresnės po stipraus mastelio. + Palikite numatytuosius nustatymus, kai greitis ir suderinamumas yra svarbesni nei maži spalvų skirtumai. + Išbandykite linijinius arba suvokimo režimus, kai pakeitus dydį tamsūs kontūrai, vartotojo sąsajos ekrano kopijos, gradientai arba spalvų juostos atrodo netinkamai. + Palyginkite prieš ir po tikrojo tikslinio ekrano, nes spalvų erdvės pokyčiai gali būti subtilūs ir priklausyti nuo turinio. + Apriboti dydžio keitimo režimus + Žinokite, kada viršijančius vaizdus reikia praleisti, perkoduoti arba sumažinti, kad tilptų + Pasirinkite, kas atsitiks ties riba + Limit Resize yra ne tik mažesnio vaizdo įrankis. Jo režimas nusprendžia, ką programa daro, kai šaltinis jau yra jūsų ribose arba kai tik viena pusė pažeidžia taisyklę, o tai svarbu mišriam aplankui ir pasiruošimui įkelti. + Naudokite Praleisti, kai vaizdai, esantys ribose, turėtų būti nepaliesti ir daugiau neeksportuojami. + Naudokite perkodavimą, kai matmenys yra priimtini, bet vis tiek reikia naujo formato, metaduomenų veikimo, kokybės arba failo pavadinimo. + Naudokite mastelio keitimą, kai vaizdus, ​​kurie pažeidžia ribas, reikia proporcingai sumažinti, kol jie tilps į leistiną langelį. + Kraštinių santykio tikslai + Venkite ištemptų veidų, nupjautų kraštų ir netikėtų kraštinių, kai naudojami griežti išvesties dydžiai + Prieš keisdami dydį suderinkite formą + Plotis ir aukštis yra tik pusė dydžio keitimo tikslo. Kraštinių santykis lemia, ar vaizdas gali tilpti natūraliai, jį reikia apkarpyti, ar aplink jį reikia drobės. Pirmiausia patikrinus formą išvengiama labiausiai stebinančių dydžio keitimo rezultatų. + Palyginkite šaltinio formą su tiksline forma prieš pasirinkdami Aiškus, Apkarpyti arba Pritaikyti. + Pirmiausia apkarpykite, kai objektas gali prarasti papildomą foną ir paskirties vietai reikia griežto rėmelio. + Naudokite Fit arba Flexible, kai turi likti matoma kiekviena pradinio vaizdo dalis. + Didesnis arba normalus dydžio keitimas + Žinokite, kada pridedant pikselių reikia AI ir kada pakanka paprasto mastelio keitimo + Nepainiokite dydžio su detalėmis + Įprastas dydžio keitimas keičia matmenis interpoliuojant pikselius; jis negali sugalvoti tikrų detalių. AI gali sukurti ryškesnes detales, tačiau ji yra lėtesnė ir gali haliucinuoti tekstūrą, todėl teisingas pasirinkimas priklauso nuo to, ar jums reikia suderinamumo, greičio ar vaizdo atkūrimo. + Naudokite įprastą dydžio keitimą miniatiūroms, įkėlimo apribojimams, ekrano kopijoms ir paprastiems matmenų keitimams. + Naudokite didesnę AI skalę, kai mažas arba minkštas vaizdas turi atrodyti geriau esant didesniam dydžiui. + Palyginkite veidus, tekstą ir raštus po AI padidinimo, nes sugeneruotos detalės gali atrodyti įtikinamai, bet neteisingai. + Svarbi filtrų tvarka + Taikykite valymą, spalvą, ryškumą ir galutinį suspaudimą apgalvota seka + Sukurkite švaresnį filtrų krūvą + Filtrus ne visada galima pakeisti. Suliejimas prieš ryškinimą, kontrasto padidinimas prieš OCR arba spalvos pasikeitimas prieš suspaudimą gali sukurti labai skirtingą išvestį naudojant tuos pačius valdiklius. + Pirmiausia apkarpykite ir pasukite, kad vėliau filtrai veiktų tik su likusiais pikseliais. + Taikykite ekspoziciją, baltos spalvos balansą ir kontrastą prieš paryškindami arba stilizuodami efektus. + Laikykite galutinį dydžio keitimą ir glaudinimą prie pabaigos, kad peržiūrėtos detalės išliktų nuspėjamai eksportuojamos. + Pataisykite fono kraštus + Po automatinio pašalinimo nuvalykite aureoles, likusius šešėlius, plaukus, skylutes ir minkštus kontūrus + Kad išpjovos atrodytų apgalvotos + Automatinės kaukės dažnai atrodo gerai iš pirmo žvilgsnio, tačiau atskleidžia problemas šviesiame, tamsiame arba raštuotame fone. Kraštų valymas – tai skirtumas tarp greito iškirpimo ir daugkartinio naudojimo lipduko, logotipo ar gaminio vaizdo. + Peržiūrėkite rezultatą kontrastinguose fonuose, kad atskleistumėte aureolę ir trūkstamas krašto detales. + Prieš ištrindami aplinkinius likučius, naudokite plaukų, kampų ir permatomų objektų atkūrimo priemonę. + Eksportuokite nedidelį galutinės fono spalvos testą, kai išpjova bus įtraukta į dizainą. + Įskaitomi vandens ženklai + Padarykite žymes ant ryškių, tamsių, užimtų ir apkarpytų vaizdų, nesugadindami nuotraukos + Apsaugokite vaizdą jo neslėpdami + Vandens ženklas, kuris gerai atrodo viename paveikslėlyje, gali išnykti kitame. Dydis, neskaidrumas, kontrastas, vieta ir pasikartojimas turėtų būti pasirinkti visai partijai, o ne tik pirmai peržiūrai. + Prieš eksportuodami visus failus patikrinkite ryškiausių ir tamsiausių vaizdų ženklą. + Didelėms pasikartojančioms žymėms naudokite mažesnį neskaidrumą, o mažiems kampų žymėms – didesnį kontrastą. + Laikykite aiškius svarbius veidus, tekstą ir gaminio informaciją, nebent ženklas skirtas užkirsti kelią pakartotiniam naudojimui. + Padalinkite vieną vaizdą į plyteles + Iškirpkite vieną vaizdą pagal eilutes, stulpelius, tinkintus procentus, formatą ir kokybę + Paruoškite tinklelius ir užsakytus gabalus + Vaizdo padalijimas sukuria kelias išvestis iš vieno šaltinio vaizdo. Jis gali būti padalintas pagal eilučių ir stulpelių skaičių, koreguoti eilučių ar stulpelių procentus ir išsaugoti gabalus pasirinktu išvesties formatu ir kokybe, o tai naudinga tinkleliams, puslapių skiltims, panoramoms ir užsakytiems socialiniams įrašams. + Pasirinkite šaltinio vaizdą, tada nustatykite reikiamą eilučių ir stulpelių skaičių. + Koreguokite eilučių ar stulpelių procentines dalis, kai gabalai neturėtų būti vienodo dydžio. + Prieš išsaugodami pasirinkite išvesties formatą ir kokybę, kad kiekvienas sugeneruotas gabalas naudotų tas pačias eksporto taisykles. + Iškirpkite vaizdo juosteles + Pašalinkite vertikalias arba horizontalias sritis ir sujunkite likusias dalis + Pašalinkite regioną nepalikdami tarpo + Vaizdas Pjovimas skiriasi nuo įprasto apkarpymo. Jis gali pašalinti pasirinktą vertikalią arba horizontalią juostelę, tada sujungti likusias vaizdo dalis. Atvirkštinis režimas išlaiko pasirinktą sritį, o naudojant abi atvirkštines kryptis – pasirinktas stačiakampis. + Šoninėms sritims, kolonoms ar vidurinėms juostoms naudokite vertikalų pjovimą; naudokite horizontalų pjovimą reklamjuosčiams, tarpams ar eilėms. + Įgalinti atvirkštinę ašies dalį, kai norite išsaugoti pasirinktą dalį, o ne ją pašalinti. + Iškirpę peržiūrėkite kraštus, nes sujungtas turinys gali atrodyti staigus, kai pašalinta sritis kerta svarbias detales. + Pasirinkite išvesties formatą + Rinkitės JPEG, PNG, WebP, AVIF, JXL arba PDF, atsižvelgdami į turinį ir paskirties vietą + Tegul paskirties vieta nustato formatą + Geriausias formatas priklauso nuo to, kas vaizde yra ir kur jis bus naudojamas. Nuotraukos, ekrano kopijos, skaidrūs ištekliai, dokumentai ir animuoti failai sugenda įvairiais būdais, kai įjungiami netinkamu formatu. + Naudokite JPEG, kad suderintumėte nuotraukas, kai nereikia skaidrumo ir tikslių pikselių. + Naudokite PNG ryškioms ekrano nuotraukoms, UI fiksavimui, skaidriai grafikai ir be nuostolių redagavimui. + Naudokite WebP, AVIF arba JXL, kai svarbu kompaktiška moderni išvestis ir priimančioji programa ją palaiko. + Ištraukite garso viršelius + Išsaugokite įterptą albumo viršelį arba turimus garso failų medijos rėmelius kaip PNG vaizdus + Ištraukite meno kūrinius iš garso failų + „Audio Covers“ naudoja „Android“ medijos metaduomenis, kad skaitytų įterptus meno kūrinius iš garso failų. Kai įterptasis meno kūrinys nepasiekiamas, retriveris gali grįžti į medijos rėmelį, jei „Android“ gali jį pateikti; jei nė vieno iš jų nėra, įrankis praneša, kad nėra vaizdo, kurį būtų galima išgauti. + Atidarykite garso viršelius ir pasirinkite vieną ar daugiau garso failų su numatomu viršelio piešiniu. + Prieš išsaugodami arba bendrindami peržiūrėkite ištrauktą viršelį, ypač jei tai failai iš srautinio perdavimo ar įrašymo programų. + Jei vaizdas nerastas, garso faile greičiausiai nėra įdėto viršelio arba „Android“ negali atskleisti tinkamo rėmelio. + Datos ir vietos metaduomenys + Nuspręskite, ką išsaugoti, kai fiksavimo laikas yra svarbus, bet GPS ar įrenginio duomenys neturėtų nutekėti + Atskirkite naudingus metaduomenis nuo privačių metaduomenų + Ne visi metaduomenys yra vienodai rizikingi. Užfiksavimo datos gali būti naudingos archyvuojant ir rūšiuojant, o GPS, įrenginių serijiniai laukai, programinės įrangos žymos arba savininko laukai gali atskleisti daugiau nei numatyta. + Išsaugokite datos ir laiko žymas, kai svarbi albumų, nuskaitymų ar projekto istorijos chronologinė tvarka. + Prieš viešai bendrindami arba siųsdami vaizdus nepažįstamiems žmonėms, pašalinkite GPS ir įrenginio identifikavimo žymas. + Eksportuokite pavyzdį ir dar kartą patikrinkite EXIF, kai privatumo reikalavimai yra griežti. + Venkite failų pavadinimų susidūrimų + Apsaugokite paketinius išvestis, kai daugelis failų bendrina pavadinimus, pvz., image.jpg arba screenshot.png + Padarykite kiekvieną išvesties pavadinimą unikalų + Pasikartojantys failų pavadinimai yra dažni, kai vaizdai gaunami iš pasiuntinių, ekrano kopijų, debesies aplankų ar kelių kamerų. Geras modelis apsaugo nuo atsitiktinio pakeitimo ir leidžia atsekti išvesties šaltinius. + Įtraukite originalų pavadinimą ir priesagą, kai redaguota kopija turėtų būti atpažįstama. + Apdorojant dideles mišrias partijas, pridėkite seką, laiko žymą, išankstinį nustatymą arba matmenis. + Venkite perrašyti darbo eigos, kol neįsitikinsite, kad pavadinimų šablonas negali susidurti. + Išsaugoti arba bendrinti + Pasirinkite tarp nuolatinio failo ir laikino perdavimo kitai programai + Eksportuokite turėdami tinkamą tikslą + Išsaugoti ir bendrinti gali atrodyti panašiai, tačiau jie išsprendžia skirtingas problemas. Išsaugojus sukuriamas failas, kurį galėsite rasti vėliau; bendrinimas siunčia sugeneruotą rezultatą per „Android“ į kitą programą ir gali nepalikti patvarios vietinės kopijos. + Naudokite Išsaugoti, kai išvestis turi likti žinomame aplanke, turėti atsarginę kopiją arba naudoti ją vėliau. + Naudokite bendrinimo funkciją greitiems pranešimams, el. pašto priedams, socialiniams įrašams arba rezultatams kitam redaktoriui siųsti. + Pirmiausia išsaugokite, kai gaunanti programa yra nepatikima arba kai nepavykusį bendrinimą būtų nemalonu atkurti. + PDF puslapio dydis ir paraštės + Prieš kurdami dokumentą pasirinkite popieriaus dydį, tinkamumo elgesį ir kvėpavimo erdvę + Padarykite vaizdo puslapius spausdinamus ir skaitomus + Vaizdai gali tapti nepatogiais PDF puslapiais, kai jų forma neatitinka pasirinkto popieriaus dydžio. Paraštės, pritaikymo režimas ir puslapio orientacija lemia, ar turinys apkarpytas, mažas, ištemptas ar patogus skaityti. + Naudokite tikro dydžio popierių, kai PDF gali būti atspausdintas arba pateikiamas į formą. + Nuskaitymui ir ekrano kopijoms naudokite paraštes, kad tekstas neatsidurtų prie puslapio krašto. + Peržiūrėkite stačią ir gulsčią puslapius kartu, kai šaltinio vaizdai yra skirtingos orientacijos. + Išvalykite nuskaitymus + Pagerinkite popieriaus kraštus, šešėlius, kontrastą, pasukimą ir skaitomumą prieš PDF arba OCR + Prieš archyvuodami paruoškite puslapius + Nuskaitymas gali būti techniškai užfiksuotas, bet vis tiek sunkiai skaitomas. Maži valymo veiksmai prieš eksportuojant PDF dažnai yra svarbesni nei suspaudimo nustatymai, ypač kvitų, ranka rašytų pastabų ir prasto apšvietimo puslapių atveju. + Pataisykite perspektyvą ir apkarpykite stalo kraštus, pirštus, šešėlius ir fono netvarką. + Atsargiai didinkite kontrastą, kad tekstas taptų aiškesnis nesunaikinant antspaudų, parašų ar pieštuko žymių. + Paleiskite OCR tik tada, kai puslapis yra vertikaliai ir įskaitomas, tada rankiniu būdu patikrinkite svarbius skaičius. + Suspausti, pilkos spalvos arba taisyti PDF failus + Naudokite vieno failo PDF operacijas, kad padarytumėte lengvesnes, tinkamas spausdinti arba atkurtas dokumentų kopijas + Pasirinkite PDF valymo operaciją + PDF įrankiai apima atskiras glaudinimo, pilkos spalvos konvertavimo ir taisymo operacijas. Suspaudimas ir pilkos spalvos tonai veikia daugiausia naudojant įterptus vaizdus, ​​o taisymas atkuria PDF struktūrą; nė vienas iš jų neturėtų būti traktuojamas kaip garantuotas kiekvieno dokumento pataisymas. + Naudokite Suspausti PDF, kai dokumente yra vaizdų, o failo dydis yra svarbus bendrinimui. + Naudokite Grayscale, kai dokumentą turėtų būti lengviau spausdinti arba nereikia spalvotų vaizdų. + Jei nepavyksta atidaryti dokumento arba jo vidinė struktūra yra pažeista, naudokite Taisyti PDF kopijoje, tada patikrinkite atkurtą failą. + Ištraukite vaizdus iš PDF + Atkurkite įterptus PDF vaizdus ir supakuokite ištrauktus rezultatus į archyvą + Išsaugokite šaltinio vaizdus iš dokumentų + Vaizdų ištraukimas nuskaito PDF, ieškodamas įterptųjų vaizdo objektų, kai įmanoma, praleidžia dublikatus ir išsaugo atkurtus vaizdus sugeneruotame ZIP archyve. Jis ištraukia tik tuos vaizdus, ​​kurie iš tikrųjų yra įterpti į PDF, o ne tekstą, vektorinius brėžinius ar kiekvieną matomą puslapį kaip ekrano kopiją. + Naudokite Išskleisti vaizdus, ​​kai reikia nuotraukų, saugomų PDF faile, pvz., nuotraukų iš katalogo arba nuskaitytų išteklių. + Vietoj to naudokite PDF į vaizdus arba puslapių ištraukimą, kai reikia pilnų puslapių, įskaitant tekstą ir išdėstymą. + Jei įrankis praneša, kad nėra įterptų vaizdų, puslapyje gali būti teksto / vektorinio turinio arba vaizdai gali būti nesaugomi kaip ištraukiami objektai. + Metaduomenys, išlyginimas ir spausdinimo išvestis + Sąmoningai redaguokite dokumento ypatybes, rastruokite puslapius arba paruoškite pasirinktinius spausdinimo maketus + Pasirinkite veiksmą su galutiniu dokumentu + PDF metaduomenys, išlyginimas ir spausdinimo paruošimas išsprendžia įvairias galutinio etapo problemas. Metaduomenys valdo dokumento ypatybes, „Flatten PDF“ rastrizuoja puslapius į mažiau redaguojamą išvestį, o „Print PDF“ parengia naują maketą su puslapio dydžio, orientacijos, paraštės ir puslapių lape valdikliais. + Naudokite metaduomenis, kai svarbu autorius, pavadinimas, raktiniai žodžiai, gamintojas ar privatumo išvalymas. + Naudokite išlygintą PDF, kai matomą puslapio turinį turėtų būti sunkiau redaguoti kaip atskirus PDF objektus. + Naudokite Print PDF, kai reikia pasirinktinio puslapio dydžio, orientacijos, paraščių arba kelių puslapių viename lape. + Paruoškite vaizdus OCR + Prieš prašydami OCR skaityti tekstą, naudokite apkarpymą, pasukimą, kontrastą, triukšmą ir mastelį + Suteikite OCR švaresnius pikselius + OCR klaidos dažnai kyla dėl įvesties vaizdo, o ne dėl OCR variklio. Kreivi puslapiai, mažas kontrastas, susiliejimas, šešėliai, mažas tekstas ir mišrūs fonai sumažina atpažinimo kokybę. + Apkarpykite iki teksto srities ir pasukite vaizdą, kad linijos būtų horizontalios prieš atpažinimą. + Padidinkite kontrastą arba konvertuokite į švaresnę nespalvotą tik tada, kai raidės lengviau skaitomos. + Vidutiniškai padidinkite mažo teksto dydį, bet venkite agresyvaus ryškinimo, kuris sukuria netikrus kraštus. + Patikrinkite QR turinį + Prieš pasitikėdami kodu peržiūrėkite nuorodas, „Wi-Fi“ kredencialus, kontaktus ir veiksmus + Iššifruotą tekstą traktuokite kaip nepatikimą įvestį + QR kodas gali paslėpti URL, adresato kortelę, „Wi-Fi“ slaptažodį, kalendoriaus įvykį, telefono numerį arba paprastą tekstą. Matoma etiketė šalia kodo gali neatitikti tikrosios naudingosios apkrovos, todėl prieš imdamiesi veiksmų peržiūrėkite iššifruotą turinį. + Prieš atidarydami patikrinkite visą iššifruotą URL, ypač sutrumpintus arba nepažįstamus domenus. + Būkite atsargūs su „Wi-Fi“, kontaktų, kalendoriaus, telefono ir SMS kodais, nes jie gali sukelti jautrius veiksmus. + Pirmiausia nukopijuokite turinį, kai turėsite jį patikrinti kitur, o ne iškart atidarykite. + Generuokite QR kodus + Kurkite kodus iš paprasto teksto, URL, „Wi-Fi“, kontaktų, el. pašto, telefono, SMS ir vietos duomenų + Sukurkite tinkamą QR apkrovą + QR ekranas gali generuoti kodus iš įvesto turinio ir nuskaityti esamus. Struktūriniai QR tipai padeda užkoduoti duomenis kitoms programoms suprantamu formatu, pvz., „Wi-Fi“ prisijungimo duomenis, kontaktų korteles, el. pašto laukus, telefonų numerius, SMS turinį, URL adresus arba geografines koordinates. + Pasirinkite paprastą tekstą paprastiems užrašams arba naudokite struktūrinį tipą, kai kodas turėtų būti atidarytas kaip „Wi-Fi“, kontaktas, URL, el. paštas, telefonas, SMS arba vietos duomenys. + Patikrinkite kiekvieną lauką prieš dalindamiesi sugeneruotu kodu, nes matoma peržiūra atspindi tik įvestą naudingą apkrovą. + Išsaugotą QR kodą patikrinkite skaitytuvu, kai jis bus atspausdintas, paskelbtas viešai arba naudojamas kitų žmonių. + Pasirinkite kontrolinės sumos režimą + Apskaičiuokite maišą iš failų ar teksto, palyginkite vieną failą arba patikrinkite daug failų paketais + Tikrinkite turinį, o ne išvaizdą + Kontrolinės sumos įrankiai turi atskirus skirtukus, skirtus failų maišai, teksto maišai, failo palyginimui su žinoma maiša ir paketiniam palyginimui. Kontrolinė suma įrodo pasirinkto algoritmo baitų tapatumą; tai neįrodo, kad du vaizdai tik atrodo panašūs. + Naudokite Skaičiuoti failams ir Teksto maišą įvestiems arba įklijuotiems tekstiniams duomenims. + Naudokite Palyginti, kai kas nors pateikia tikėtiną vieno failo kontrolinę sumą. + Naudokite paketinį palyginimą, kai daugeliui failų reikia maišos arba patvirtinimo vienu metu, tada laikykite pasirinktą algoritmą nuoseklų. + Iškarpinės privatumas + Naudokite automatines mainų srities funkcijas netyčia neatskleiskite privačių nukopijuotų duomenų + Nukopijuotą turinį laikykite sąmoningai + Iškarpinės automatizavimas yra patogus, tačiau nukopijuotame tekste gali būti slaptažodžių, nuorodų, adresų, žetonų ar privačių pastabų. Laikykite iškarpine pagrįstą įvestį kaip greičio funkciją, kuri turėtų atitikti jūsų įpročius ir privatumo lūkesčius. + Išjungti automatinį iškarpinės naudojimą, jei privatus tekstas netikėtai pasirodo įrankiuose. + Išsaugojimą tik iškarpinėje naudokite tik tada, kai sąmoningai norite, kad rezultatas nebūtų saugomas. + Išvalykite arba pakeiskite mainų sritį po to, kai tvarkote jautrų tekstą, QR duomenis arba „Base64“ naudingąsias apkrovas. + Spalvų formatai + Prieš įklijuodami kitur, supraskite HEX, RGB, HSV, alfa ir nukopijuotas spalvų reikšmes + Nukopijuokite formatą, kurio tikisi tikslas + Ta pati matoma spalva gali būti užrašoma keliais būdais. Dizaino įrankis, CSS laukas, „Android“ spalvų reikšmė arba vaizdo redagavimo priemonė gali tikėtis kitokios tvarkos, alfa apdorojimo ar spalvų modelio. + Naudokite HEX, kai įklijuojate į žiniatinklio, dizaino ar temos laukus, kuriuose tikimasi kompaktiškų spalvų kodų. + Naudokite RGB arba HSV, kai reikia rankiniu būdu reguliuoti kanalus, ryškumą, sodrumą ar atspalvį. + Atskirai pažymėkite alfa, kai svarbus skaidrumas, nes kai kurios paskirties vietos į tai nepaiso arba naudoja kitą tvarką. + Naršykite spalvų biblioteką + Ieškokite pavadintų spalvų pagal pavadinimą arba HEX ir laikykite mėgstamiausias šalia viršaus + Raskite daugkartinio naudojimo pavadintas spalvas + Spalvų biblioteka įkelia pavadintą spalvų kolekciją, surūšiuoja ją pagal pavadinimą, filtruoja pagal spalvos pavadinimą arba HEX reikšmę ir išsaugo mėgstamiausias spalvas, kad svarbūs įrašai būtų lengviau pasiekiami. Tai naudinga, kai reikia tikros spalvos, o ne paimti iš paveikslėlio. + Ieškokite pagal spalvos pavadinimą, kai žinote šeimą, pvz., raudona, mėlyna, skalūnų ar mėtinė. + Ieškokite pagal HEX, kai jau turite skaitinę spalvą ir norite rasti atitinkamus arba šalia esančius pavadinimus. + Dažnas spalvas pažymėkite kaip mėgstamiausias, kad naršant jos būtų pirmiau nei bendroji biblioteka. + Naudokite tinklinio gradiento kolekcijas + Atsisiųskite galimus tinklelio gradiento išteklius ir bendrinkite pasirinktus vaizdus + Naudokite nuotolinio tinklelio gradiento išteklius + „Mesh Gradients“ gali įkelti nuotolinių išteklių kolekciją, atsisiųsti trūkstamus gradiento vaizdus, ​​rodyti atsisiuntimo eigą ir bendrinti pasirinktus gradientus. Prieinamumas priklauso nuo prieigos prie tinklo ir nuotolinės išteklių saugyklos, todėl tuščias sąrašas paprastai reiškia, kad išteklių dar nebuvo. + Atidarykite tinklelio gradientus naudodami gradiento įrankius, kai norite paruoštų tinklinio gradiento vaizdų. + Palaukite, kol ištekliai bus įkeliami arba atsisiuntimo eiga, prieš manydami, kad kolekcija tuščia. + Pasirinkite ir bendrinkite reikalingus gradientus arba grįžkite į Gradient Maker, kai norite sukurti pasirinktinį gradientą rankiniu būdu. + Sukurta turto rezoliucija + Prieš kurdami gradientus, triukšmą, fono paveikslėlius, į SVG panašų meną ar tekstūras, pasirinkite išvesties dydį + Sukurkite tokio dydžio, kokio jums reikia + Sugeneruoti vaizdai neturi originalaus fotoaparato, į kurį būtų galima sugrįžti. Jei išvestis per maža, vėlesnis mastelio keitimas gali sušvelninti raštus, pakeisti detales arba pakeisti ištekliaus išklotinę ir suspaudimą. + Pirmiausia nustatykite galutinio naudojimo atvejo matmenis, pvz., ekrano užsklandą, piktogramą, foną, spaudinį arba perdangą. + Naudokite didesnius dydžius tekstūroms, kurias galima apkarpyti, keisti mastelį arba pakartotinai panaudoti keliuose maketuose. + Eksportuokite bandomąsias versijas prieš didžiulius išėjimus, nes procedūros detalės gali pakeisti glaudinimo veikimą. + Eksportuokite esamus tapetus + Iš įrenginio gaukite galimus namų, užrakto ir įmontuotus fono paveikslėlius + Išsaugokite arba bendrinkite įdiegtus fono paveikslėlius + „Wallpapers Export“ nuskaito ekrano fonus, kuriuos „Android“ atskleidžia per sistemos ekrano fono API. Atsižvelgiant į įrenginį, „Android“ versiją, leidimus ir kūrimo variantą, „Home“, „Lock“ arba įtaisytieji ekrano užsklandos gali būti pasiekiami atskirai arba jų gali nebūti. + Atidarykite Wallpapers Export, kad įkeltumėte fono paveikslėlius, kuriuos programa leidžia skaityti. + Pasirinkite norimus išsaugoti, kopijuoti arba bendrinti galimus pagrindinio ekrano, užrakto arba įtaisytuosius ekrano fono įrašus. + Jei trūksta ekrano fono arba funkcija nepasiekiama, tai dažniausiai yra sistemos, leidimo arba versijos varianto apribojimas, o ne redagavimo nustatymas. + Corners Animacija Droselis + Kopijuoti spalvą kaip + HEX + vardas + Portretinis matinis modelis greitam žmonių iškirpimui su švelnesniais plaukais ir kraštų valdymu. + Greitas pagerinimas esant silpnam apšvietimui naudojant Zero-DCE++ kreivės įvertinimą. + Restormer vaizdo atkūrimo modelis, skirtas sumažinti lietaus juosteles ir šlapio oro artefaktus. + Dokumentuokite iškreiptą modelį, kuris numato koordinačių tinklelį ir perskirsto išlenktus puslapius į plokštesnį nuskaitymą. + Judesio trukmės skalė + Valdo programos animacijos greitį: 0 išjungia judėjimą, 1 yra normalus, didesnės reikšmės sulėtina animaciją + Rodyti naujausius įrankius + Pagrindiniame ekrane parodykite greitą prieigą prie neseniai naudotų įrankių + Naudojimo statistika + Naršykite programų atidarymus, įrankių paleidimus ir dažniausiai naudojamus įrankius + Atsidaro programėlė + Atsidaro įrankis + Paskutinis įrankis + Sėkmingi išsaugojimai + Veiklos serija + Duomenys išsaugoti + Aukščiausias formatas + Naudoti įrankiai + %1$d atsidaro • %2$s + Kol kas nėra naudojimo statistikos + Atidarykite kelis įrankius ir jie čia pasirodys automatiškai + Jūsų naudojimo statistika saugoma tik lokaliai jūsų įrenginyje. „ImageToolbox“ juos naudoja tik jūsų asmeninei veiklai, mėgstamiems įrankiams ir išsaugotų duomenų įžvalgoms rodyti + redaktorius redaguoti nuotraukos retušavimą koreguoti + keisti dydį konvertuoti mastelio skiriamąją gebą matmenys plotis aukštis jpg jpeg png webp suspausti + suspausti dydis svoris baitų mb kb sumažinti kokybę optimizuoti + apkarpymo apkarpymo pjūvio kraštinių santykis + filtro efekto spalvų korekcija koreguoti lut kaukę + piešti teptuku pieštuku komentuoti žymėjimą + šifruoti šifruoti iššifruoti slaptažodžio paslaptį + fono šalinimo priemonė ištrinti pašalinti išpjovą skaidri alfa + peržiūrėti peržiūros programos galerija tikrinti atidaryta + dygsnio sujungimas sujungti panorama ilga ekrano kopija + url web atsisiųsti interneto nuorodos paveikslėlį + rinkiklis lašintuvo pavyzdys šešiakampio rgb spalvos + paletės spalvos swatches ekstraktas dominuoja + exif metaduomenų privatumas pašalinkite juostelę išvalykite GPS vietą + palyginkite skirtumą prieš po + limito dydžio keitimas max min megapikseliai matmenys spaustukas + Pdf dokumentų puslapių įrankiai + ocr tekstas atpažinti ekstrakto nuskaitymą + gradiento fono spalvos tinklelis + vandens ženklo logotipo teksto antspaudo perdanga + gif animacija animuotus konvertuoti kadrus + apng animacija animacinis png konvertuoti kadrus + zip archyvas suspaudimo paketas išpakuokite failus + jxl jpeg xl konvertuoti į jpg vaizdo formatą + svg vector trace konvertuoti xml + formatu konvertuoti jpg jpeg png webp heic avif jxl bmp + dokumentų skaitytuvas nuskaitymo popieriaus perspektyvinis apkarpymas + qr brūkšninio kodo skaitytuvo nuskaitymas + stacking perdangos vidutinė astrofotografija + padalintos plytelės tinklelio pjūvio dalys + color convert hex rgb hsl hsv cmyk lab + webp konvertuoti animacinio vaizdo formatą + triukšmo generatorius atsitiktinės tekstūros grūdėtumas + koliažo tinklelio išdėstymo derinys + žymėjimo sluoksniai komentuoja perdangos redagavimą + base64 koduoti dekoduoti duomenis uri + kontrolinės sumos maiša md5 sha sha1 sha256 patikrinti + tinklinio gradiento fonas + exif metaduomenys redaguoti gps datos kamerą + supjaustyti padalintas tinklelio dalis plyteles + garso viršelio albumo viršelio muzikos metaduomenys + tapetų eksporto fonas + ascii teksto meno personažai + ai ml nervinio stiprinimo aukštesnio lygio segmentas + spalvų bibliotekos medžiagų paletė pavadinta spalvomis + Shader glsl fragmento efektų studija + pdf sujungti sujungti prisijungti + Pdf padalinti į atskirus puslapius + Pdf pasukti puslapius orientacija + pdf pertvarkyti pertvarkyti puslapius + Pdf puslapių numerių puslapiai + pdf ocr tekstas atpažinti ieškomą ištrauką + Pdf vandens ženklo antspaudo logotipo tekstas + Pdf parašo piešimas + Pdf apsaugoti slaptažodžiu šifruoti užraktą + pdf atrakinti slaptažodį iššifruoti pašalinti apsaugą + Pdf kompresas sumažinti dydį optimizuoti + Pdf pilkų tonų juoda balta vienspalvė + pdf remonto taisymas atkurti sugedusį + Pdf metaduomenų savybės exif autoriaus pavadinimas + Pdf pašalinti ištrinti puslapius + Pdf apkarpymo paraštės + Pdf išlygintos anotacijos sudaro sluoksnius + Pdf ištraukos nuotraukos nuotraukos + Pdf zip archyvo suspaudimas + Pdf spausdinimo spausdintuvas + Skaityti pdf peržiūros peržiūros programą + Vaizdai į pdf konvertuoti jpg jpeg png dokumentą + pdf į vaizdų puslapius eksportuoti jpg png + pdf komentarai komentarai pašalinti švarus + Neutralus variantas + Klaida + Nuleidžiamas šešėlis + Danties aukštis + Horizontalus dantų diapazonas + Vertikalus dantų diapazonas + Viršutinis kraštas + Dešinysis kraštas + Apatinis kraštas + Kairysis kraštas + Suplyšęs kraštas + Iš naujo nustatyti naudojimo statistiką + Atidaromas įrankis, išsaugoma statistika, seka, išsaugoti duomenys ir viršutinis formatas bus nustatytas iš naujo. Programos atidarymas išliks nepakitęs. + Garsas + Failas + Eksportuoti profilius + Išsaugoti esamą profilį + Ištrinti eksporto profilį + Ar norite ištrinti eksporto profilį \\"%1$s\\"? + Krašto piešimas + Parodykite ploną kraštelį aplink piešimo ir ištrynimo drobę + Banguotas + Suapvalinti vartotojo sąsajos elementai su minkštu banguotu kraštu + Samtelis + Įpjova + Suapvalinti kampai išraižyti į vidų + Pakopiniai kampai su dvigubu pjūviu + Vietos rezervuaras + Naudokite {filename}, kad įterptumėte originalų failo pavadinimą be plėtinio + Blyksnis + Bazinė suma + Žiedo suma + Spindulių kiekis + Žiedo plotis + Iškreipti perspektyvą + Java išvaizda ir pojūtis + Kirpimas + X kampas + ir kampas + Pakeisti išvesties dydį + Vandens lašas + Bangos ilgis + Fazė + Aukštas leidimas + Spalvota kaukė + Chrome + Ištirpinti + Minkštumas + Atsiliepimai + Objektyvo suliejimas + Maža įvestis + Didelė įvestis + Maža galia + Didelė išeiga + Šviesos efektai + Guolio aukštis + Gumbo švelnumas + Ripple + X amplitudė + Y amplitudė + X bangos ilgis + Y bangos ilgis + Prisitaikantis suliejimas + Tekstūros generavimas + Generuokite įvairias procedūrines tekstūras ir išsaugokite jas kaip vaizdus + Tekstūros tipas + Šališkumas + Laikas + Blizgesys + Sklaida + Mėginiai + Kampo koeficientas + Gradiento koeficientas + Tinklelio tipas + Atstumo galia + Y skalė + Neryškumas + Pagrindo tipas + Turbulencijos faktorius + Mastelio keitimas + Iteracijos + Žiedai + Šlifuotas metalas + Kaustinės medžiagos + Korinis + Šaškių lenta + fBm + Marmuras + Plazma + Antklodė + Mediena + atsitiktinis + Kvadratas + Šešiakampis + Aštuonkampis + Trikampis + Rieduotas + VL triukšmas + SC triukšmas + Neseniai + Dar nėra neseniai naudotų filtrų + tekstūros generatorius šlifuotas metalas kaustinės medžiagos korinis šachmatų lenta marmuras plazminis antklodė medžio plyta kamufliažas ląstelių debesis įtrūkimai audinys lapija korio ledas lavos ūkas popierius rūdys smėlio dūmai akmuo vietovė topografija vandens bangavimas + Plyta + Kamufliažas + Ląstelė + Debesis + Plyšys + Audinys + Lapija + Koris + Ledas + Lava + Ūkas + Popierius + Rūdys + Smėlis + Rūkyti + Akmuo + Reljefas + Topografija + Vandens bangavimas + Išplėstinė mediena + Skiedinio plotis + Netvarkingumas + Nuožulnus + Šiurkštumas + Pirmas slenkstis + Antras slenkstis + Trečias slenkstis + Kraštų minkštumas + Krašto plotis + Aprėptis + Detalė + Gylis + Išsišakojimas + Horizontalūs siūlai + Vertikalūs siūlai + Fuzz + Venos + Apšvietimas + Plyšio plotis + Šerkšnas + Srautas + Pluta + Debesų tankis + Žvaigždės + Pluošto tankis + Pluošto stiprumas + Dėmės + Korozija + Įdubimas + Dribsniai + Kopų dažnis + Vėjo kampas + Ripples + vyteliai + Venų skalė + Vandens lygis + Kalnų lygis + Erozija + Sniego lygis + Eilučių skaičius + Linijos storis + Šešėliavimas + Porų spalva + Skiedinio spalva + Tamsi plyta spalva + Plytų spalva + Paryškinkite spalvą + Tamsi spalva + Miško spalva + Žemės spalva + Smėlio spalva + Fono spalva + Ląstelių spalva + Krašto spalva + Dangaus spalva + Šešėlių spalva + Šviesios spalvos + Paviršiaus spalva + Variacinė spalva + Plyšio spalva + Metmenų spalva + Ataudų spalva + Tamsi lapų spalva + Lapų spalva + Krašto spalva + Medaus spalva + Gili spalva + Ledo spalva + Šerkšno spalva + Plutos spalva + Skalbimo spalva + Švytinti spalva + Erdvės spalva + Violetinė spalva + Mėlyna spalva + Bazinė spalva + Pluošto spalva + Dėmės spalva + Metalo spalva + Tamsi rudziu spalva + Rūdžių spalva + Oranžinė spalva + Dūmų spalva + Venų spalva + Žemumos spalva + Vandens spalva + Roko spalva + Sniego spalva + Žema spalva + Aukšta spalva + Linijos spalva + Sekli spalva + Žolė + Purvas + Oda + Betono + Asfaltas + Samanos + Ugnis + Aurora + Alyvos dėmė + Akvarelė + Abstraktus srautas + Opalas + Damasko plienas + Žaibas + Velvet + Rašalo marmuravimas + Holografinė folija + Bioliuminescencija + Kosminis sūkurys + Lavos lempa + Įvykių horizontas + Fractal Bloom + Chromatinis tunelis + Koronos užtemimas + Keistas aktorius + Ferrofluid karūna + Supernova + Irisas + Povo plunksna + Nautilus Shell + Žieduota planeta + Ašmenų tankis + Ašmenų ilgis + Vėjas + Netvarkingumas + Grumeliai + Drėgmė + Akmenukai + Raukšlės + Poros + Suvestinė + Įtrūkimai + Degutas + Nešioti + Dėmės + Skaidulos + Liepsnos dažnis + Rūkyti + Intensyvumas + Kaspinai + Juostos + Vaivorykštė + Tamsa + Žydi + Pigmentas + Kraštai + Popierius + Difuzija + Simetrija + Ryškumas + Spalvų žaismas + Pieniškumas + Sluoksniai + Sulankstoma + lenkų + Filialai + Kryptis + Blizgesys + Sulenkimai + Plunksnavimas + Rašalo likutis + Spektras + Raukšlės + Difrakcija + Ginklai + Pasukti + Pagrindinis švytėjimas + Dėmės + Disko pakreipimas + Horizonto dydis + Disko plotis + Objektyvas + Žiedlapiai + Garbanė + Filigranas + Aspektai + Kreivumas + Mėnulio dydis + Koronos dydis + Spinduliai + Deimantinis žiedas + Skiltys + Orbitos tankis + Storis + Spygliai + Smailės ilgis + Kūno dydis + Metalinis + Smūgio spindulys + Korpuso plotis + Išmestas + Mokinio dydis + Rainelės dydis + Spalvų variacija + Žibintuvėlis + Akių dydis + Spygliuočių tankis + Posūkiai + Kameros + Atidarymas + Ridges + Perlamutriškumas + Planetos dydis + Žiedo pakreipimas + Žiedo plotis + Atmosfera + Purvo spalva + Tamsi žolės spalva + Žolės spalva + Patarimo spalva + Tamsios žemės spalva + Sausa spalva + Akmenuko spalva + Odos spalva + Betono spalva + Deguto spalva + Asfalto spalva + Akmens spalva + Dulkių spalva + Dirvožemio spalva + Tamsi samanų spalva + Samanų spalva + Raudona spalva + Pagrindinė spalva + Žalia spalva + Žydra spalva + Magenta spalva + Aukso spalva + Popieriaus spalva + Pigmento spalva + Antrinė spalva + Pirmoji spalva + Antra spalva + Rožinė spalva + Tamsi plieno spalva + Plieno spalva + Šviesios plieno spalvos + Oksido spalva + Halo spalva + Varžto spalva + Aksominė spalva + Blizgesio spalva + Mėlyna rašalo spalva + Raudono rašalo spalva + Tamsaus rašalo spalva + Sidabrinės spalvos + Geltona spalva + Audinio spalva + Disko spalva + Karšta spalva + Objektyvo spalva + Išorinė spalva + Vidinė spalva + Koronos spalva + Šalta spalva + Šilta spalva + Debesų spalva + Liepsnos spalva + Plunksnos spalva + Korpuso spalva + Perlų spalva + Planetos spalva + Žiedo spalva + Ištrinkite originalius failus po išsaugojimo + Bus ištrinti tik sėkmingai apdoroti šaltinio failai + Ištrinti originalūs failai: %1$d + Nepavyko ištrinti originalių failų: %1$d + Originalūs failai ištrinti: %1$d, nepavyko: %2$d + Lakšto gestai + Leisti vilkti išplėstų parinkčių lapus + Chroma atranka + Bitelio gylis + Be nuostolių + %1$d bitų + Partijos pervadinimas + Pervardykite kelis failus naudodami failų pavadinimų šablonus + partijos pervardyti failus failo pavadinimo šablono sekos datos plėtinys + Pasirinkite failus, kuriuos norite pervardyti + Failo vardo šablonas + Pervardyti + Datos šaltinis + EXIF data paimta + Failo modifikavimo data + Failo sukūrimo data + Dabartinė data + Rankinė data + Rankinė data + Rankinis laikas + Kai kuriems failams pasirinkta data nepasiekiama. Jiems bus naudojama dabartinė data. + Įveskite failo pavadinimo šabloną + Šablonas sukuria netinkamą arba per ilgą failo pavadinimą + Modelis sukuria pasikartojančius failų pavadinimus + Visi failų pavadinimai jau atitinka šabloną + Šis šablonas naudoja žetonus, kurių negalima pervardyti paketu + Vardas liks toks pat + Failai sėkmingai pervardyti + Kai kurių failų pervardyti nepavyko + Leidimas nebuvo suteiktas + Naudoja originalų failo pavadinimą be plėtinio, padedantį išlaikyti šaltinio identifikavimo duomenis. Taip pat palaikomas pjaustymas naudojant \\o{start:end}, transliteracija su \\o{t} ir keitimas \\o{s/old/new/}. + Automatiškai didėjantis skaitiklis. Palaiko pasirinktinį formatavimą: \\c{padding} (pvz., \\c{3} -&gt; 001), \\c{start:step} arba \\c{start:step:padding}. + Pirminio aplanko, kuriame buvo pradinis failas, pavadinimas. + Originalaus failo dydis. Palaiko vienetus: \\z{b}, \\z{kb}, \\z{mb} arba tiesiog \\z, kad būtų skaitomas žmogus. + Sugeneruoja atsitiktinį universalų unikalų identifikatorių (UUID), kad užtikrintų failo pavadinimo unikalumą. + Failų pervadinimo anuliuoti negalima. Prieš tęsdami įsitikinkite, kad nauji pavadinimai yra teisingi. + Patvirtinkite pervadinimą + Šoninio meniu nustatymai + Meniu skalė + Alfa meniu + Automatinis baltos spalvos balansas + Apkarpymas + Tikrinama, ar nėra paslėptų vandens ženklų + Sandėliavimas + Talpyklos limitas + Automatiškai išvalykite talpyklą, kai ji viršys šį dydį + Valymo intervalas + Kaip dažnai tikrinti, ar reikia išvalyti talpyklą + Paleidus programą + 1 diena + 1 savaitė + 1 mėnuo + Automatiškai išvalykite programos talpyklą pagal pasirinktą limitą ir intervalą + Kilnojamas pasėlių plotas + Leidžia perkelti visą apkarpymo plotą tempiant į jį + Sujungti GIF + Sujunkite kelis GIF klipus į vieną animaciją + GIF klipų užsakymas + Atvirkščiai + Žaisti atvirkščiai + Bumerangas + Žaisti pirmyn ir tada atgal + Normalizuokite rėmo dydį + Pakeiskite rėmelius, kad tilptų didžiausias segtukas; išjunkite, kad išsaugotumėte pradinį mastelį + Vėlavimas tarp klipų (ms) + Aplankas + Pasirinkite visus vaizdus iš aplanko ir jo poaplankių + Dublikatų ieškiklis + Raskite tikslias kopijas ir vizualiai panašius vaizdus + dublikatų ieškiklis panašių vaizdų tikslios kopijos nuotraukų išvalymas saugykla dHash SHA-256 + Pasirinkite vaizdus, ​​​​kad rastumėte dublikatus + Panašumo jautrumas + Tikslios kopijos + Panašūs vaizdai + Pasirinkite visus tikslius dublikatus + Pasirinkite visus, išskyrus rekomenduojamus + Pasikartojančių nerasta + Pabandykite pridėti daugiau vaizdų arba padidinti panašumo jautrumą + Nepavyko perskaityti %1$d vaizdo (-ų) + %1$s • %2$s galima susigrąžinti + %1$d grupės • %2$s galima grąžinti + %1$d pasirinkta • %2$s + To negalima anuliuoti. „Android“ gali paprašyti patvirtinti ištrynimą sistemos dialogo lange. + Nerasta + Perkelkite, kad pradėtumėte + Perkelti į pabaigą + \ No newline at end of file diff --git a/core/resources/src/main/res/values-mr/strings.xml b/core/resources/src/main/res/values-mr/strings.xml new file mode 100644 index 0000000..7b0074b --- /dev/null +++ b/core/resources/src/main/res/values-mr/strings.xml @@ -0,0 +1,3739 @@ + + + + काहीतरी चूक झाली: %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 प्रतिमा आणि निवडण्यायोग्य मजकूर स्तरासह जतन करा + लेयर अल्फा + क्षैतिज फ्लिप + उभ्या फ्लिप + कुलूप + सावली जोडा + सावलीचा रंग + मजकूर भूमिती + तीक्ष्ण शैलीकरणासाठी मजकूर स्ट्रेच किंवा स्क्यू करा + स्केल एक्स + स्क्यू एक्स + भाष्ये काढा + पीडीएफ पेजेसमधून निवडक भाष्य प्रकार जसे की लिंक्स, टिप्पण्या, हायलाइट्स, आकार किंवा फॉर्म फील्ड काढून टाका + हायपरलिंक्स + फाइल संलग्नक + ओळी + पॉपअप + शिक्के + आकार + मजकूर नोट्स + मजकूर मार्कअप + फॉर्म फील्ड + मार्कअप + अज्ञात + भाष्ये + गट रद्द करा + कॉन्फिगर करण्यायोग्य रंग आणि ऑफसेटसह लेयरच्या मागे ब्लर शॅडो जोडा + सामग्री जागरूक विकृती + ही क्रिया पूर्ण करण्यासाठी पुरेशी मेमरी नाही. कृपया लहान प्रतिमा वापरून पहा, इतर ॲप्स बंद करून किंवा ॲप रीस्टार्ट करून पहा. + समांतर कामगार + या प्रतिमा जतन केल्या गेल्या नाहीत कारण रूपांतरित केलेल्या फायली मूळपेक्षा मोठ्या असतील. मूळ प्रतिमा हटवण्यापूर्वी ही यादी तपासा. + फायली वगळल्या: %1$s + ॲप लॉग + समस्या शोधण्यासाठी ॲपचे लॉग पहा + गटबद्ध साधनांमध्ये आवडते + जेव्हा साधने प्रकारानुसार गटबद्ध केली जातात तेव्हा टॅब म्हणून आवडी जोडते + शेवटचे म्हणून आवडते दाखवा + आवडते टूल्स टॅब शेवटी हलवते + क्षैतिज अंतर + अनुलंब अंतर + मागासलेली ऊर्जा + फॉरवर्ड एनर्जी अल्गोरिदमऐवजी साधा ग्रेडियंट मॅग्निट्यूड एनर्जी मॅप वापरा + मास्क केलेले क्षेत्र काढा + सीम मुखवटा घातलेल्या प्रदेशातून जातील, संरक्षित करण्याऐवजी केवळ निवडलेल्या क्षेत्रास काढून टाकतील + VHS NTSC + प्रगत NTSC सेटिंग्ज + मजबूत VHS आणि ब्रॉडकास्ट आर्टिफॅक्ट्ससाठी अतिरिक्त ॲनालॉग ट्यूनिंग + क्रोमा रक्तस्त्राव + टेप पोशाख + ट्रॅकिंग + लुमा स्मीअर + वाजत आहे + बर्फ + फील्ड वापरा + फिल्टर प्रकार + लुमा फिल्टर इनपुट करा + इनपुट क्रोमा लोपास + क्रोमा डिमॉड्युलेशन + फेज शिफ्ट + फेज ऑफसेट + डोके स्विचिंग + डोके स्विचिंग उंची + हेड स्विचिंग ऑफसेट + हेड स्विचिंग शिफ्ट + मिड-लाइन स्थिती + मधल्या ओळीतील चिडचिड + आवाजाची उंची ट्रॅक करणे + ट्रॅकिंग लहर + बर्फाचा मागोवा घेत आहे + स्नो ॲनिसोट्रॉपीचा मागोवा घेत आहे + ट्रॅकिंग आवाज + आवाजाच्या तीव्रतेचा मागोवा घेणे + संमिश्र आवाज + संमिश्र आवाज वारंवारता + संमिश्र आवाज तीव्रता + संमिश्र आवाज तपशील + रिंगिंग वारंवारता + रिंगिंग पॉवर + लुमा आवाज + लुमा आवाज वारंवारता + लुमा आवाजाची तीव्रता + लुमा आवाज तपशील + क्रोमा आवाज + क्रोमा आवाज वारंवारता + क्रोमा आवाजाची तीव्रता + क्रोमा आवाज तपशील + बर्फाची तीव्रता + स्नो ॲनिसोट्रॉपी + क्रोमा फेज आवाज + क्रोमा फेज त्रुटी + क्रोमा विलंब क्षैतिज + क्रोमा विलंब अनुलंब + VHS टेप गती + व्हीएचएस क्रोमा नुकसान + व्हीएचएस तीव्रतेची तीव्रता + VHS तीक्ष्ण वारंवारता + VHS काठ लहर तीव्रता + व्हीएचएस काठ लहर गती + व्हीएचएस एज वेव्ह वारंवारता + व्हीएचएस एज वेव्ह तपशील + आउटपुट क्रोमा लोपास + स्केल क्षैतिज + स्केल अनुलंब + स्केल फॅक्टर X + स्केल फॅक्टर Y + सामान्य प्रतिमा वॉटरमार्क शोधते आणि त्यांना LaMa सह रंगवते. डिटेक्शन आणि इनपेंटिंग मॉडेल स्वयंचलितपणे डाउनलोड करते + Deuteranopia + प्रतिमा विस्तृत करा + YOLO v11 सेगमेंटेशन वापरून ऑब्जेक्ट सेगमेंटेशन आधारित बॅकग्राउंड रिमूव्हर + रेषा कोन दाखवा + रेखाचित्र काढताना वर्तमान रेषा रोटेशन अंशांमध्ये दाखवते + ॲनिमेटेड इमोजी + उपलब्ध इमोजी ॲनिमेशन म्हणून दाखवा + मूळ फोल्डरमध्ये सेव्ह करा + निवडलेल्या फोल्डरऐवजी मूळ फाइलच्या पुढे नवीन फाइल्स सेव्ह करा + वॉटरमार्क पीडीएफ + पुरेशी मेमरी नाही + या डिव्हाइसवर प्रक्रिया करण्यासाठी इमेज खूप मोठी असल्याची किंवा सिस्टीमची उपलब्ध मेमरी संपली असल्याची शक्यता आहे. इमेज रिझोल्यूशन कमी करून, इतर ॲप्स बंद करून किंवा छोटी फाइल निवडून पहा. + डीबग मेनू + ॲप फंक्शन्सची चाचणी घेण्यासाठी मेनू, हे उत्पादन रिलीझमध्ये दर्शविण्याचा हेतू नाही + प्रदाता + PaddleOCR ला तुमच्या डिव्हाइसवर अतिरिक्त ONNX मॉडेल आवश्यक आहेत. तुम्हाला %1$s डेटा डाउनलोड करायचा आहे का? + सार्वत्रिक + कोरियन + लॅटिन + पूर्व स्लाव्हिक + थाई + ग्रीक + इंग्रजी + सिरिलिक + अरबी + देवनागरी + तमिळ + तेलुगु + शेडर + शेडर प्रीसेट + कोणताही शेडर निवडलेला नाही + शेडर स्टुडिओ + सानुकूल फ्रॅगमेंट शेडर्स तयार करा, संपादित करा, प्रमाणित करा, आयात करा आणि निर्यात करा + जतन केलेले शेडर्स + जतन केलेले शेडर्स उघडा, डुप्लिकेट, निर्यात, शेअर करा किंवा हटवा + नवीन शेडर + शेडर फाइल + .itshader JSON + %1$d परम + शेडर स्रोत + फक्त void main() चा मुख्य भाग लिहा. UV निर्देशांकांसाठी टेक्सचर कोऑर्डिनेट वापरा आणि स्रोत नमुना म्हणून इनपुट इमेज टेक्चर वापरा. + कार्ये + पर्यायी हेल्पर फंक्शन्स, कॉन्स्टंट्स आणि स्ट्रक्चर्स void main() च्या आधी घातले जातात. परमांपासून गणवेश तयार केले जातात. + जेव्हा शेडरला संपादनयोग्य मूल्यांची आवश्यकता असते तेव्हा येथे गणवेश जोडा. + शेडर रीसेट करा + वर्तमान शेडर मसुदा साफ केला जाईल + अवैध शेडर + असमर्थित शेडर आवृत्ती %1$d. समर्थित आवृत्ती: %2$d. + शेडरचे नाव रिक्त नसावे. + शेडर स्त्रोत रिक्त नसावा. + शेडर स्त्रोतामध्ये असमर्थित वर्ण आहेत: %1$s. फक्त ASCII GLSL स्रोत वापरा. + पॅरामीटर \\\"%1$s\\\" एकापेक्षा जास्त वेळा घोषित केले आहे. + पॅरामीटर नावे रिक्त नसावीत. + पॅरामीटर \\\"%1$s\\\" आरक्षित GPUI इमेज नाव वापरते. + पॅरामीटर \\\"%1$s\\\" एक वैध GLSL अभिज्ञापक असणे आवश्यक आहे. + पॅरामीटर \\\"%1$s\\\" मध्ये %2$s मूल्य प्रकार आहे \\\"%3$s\\\", अपेक्षित \\\"%4$s\\\". + पॅरामीटर \\\"%1$s\\\" bool मूल्यांसाठी किमान किंवा कमाल परिभाषित करू शकत नाही. + पॅरामीटर \\\"%1$s\\\" मध्ये कमाल पेक्षा किमान आहे. + पॅरामीटर \\\"%1$s\\\" डीफॉल्ट किमान पेक्षा कमी आहे. + पॅरामीटर \\\"%1$s\\\" डीफॉल्ट कमाल पेक्षा मोठे आहे. + शेडरने \\\"एकसमान नमुना2D %1$s;\\\" घोषित करणे आवश्यक आहे. + शेडरने \\\"वेरिंग vec2 %1$s;\\\" घोषित करणे आवश्यक आहे. + शेडरने \\\"void main()\\\" परिभाषित करणे आवश्यक आहे. + शेडरने gl_FragColor वर रंग लिहिला पाहिजे. + ShaderToy mainImage shaders समर्थित नाहीत. GPUImage च्या void main() कराराचा वापर करा. + ॲनिमेटेड ShaderToy युनिफॉर्म \\\"iTime\\\" समर्थित नाही. + ॲनिमेटेड ShaderToy युनिफॉर्म \\\"iFrame\\\" समर्थित नाही. + ShaderToy युनिफॉर्म \\\"iResolution\\\" समर्थित नाही. + ShaderToy iChannel पोत समर्थित नाहीत. + बाह्य पोत समर्थित नाहीत. + बाह्य पोत विस्तार समर्थित नाहीत. + लिब्रेट्रो शेडर पॅरामीटर्स समर्थित नाहीत. + फक्त एक इनपुट पोत समर्थित आहे. \\\"गणवेश %1$s %2$s;\\\" काढा. + पॅरामीटर \\\"%1$s\\\" हे \\\"युनिफॉर्म %2$s %1$s;\\\" म्हणून घोषित करणे आवश्यक आहे. + पॅरामीटर \\\"%1$s\\\" हे \\\"युनिफॉर्म %2$s %1$s;\\\" म्हणून घोषित केले आहे, अपेक्षित \\\"युनिफॉर्म %3$s %1$s;\\\". + शेडर फाइल रिकामी आहे. + शेडर फाइल आवृत्ती, नाव आणि शेडर फील्डसह .itshader JSON ऑब्जेक्ट असणे आवश्यक आहे. + शेडर फाइल वैध JSON नाही किंवा .itshader फॉरमॅटशी जुळत नाही. + फील्ड \\\"%1$s\\\" आवश्यक आहे. + %1$s आवश्यक आहे. + %1$s \\\"%2$s\\\" समर्थित नाही. समर्थित प्रकार: %3$s. + \\\"%2$s\\\" पॅरामीटर्ससाठी %1$s आवश्यक आहे. + %1$s ही मर्यादित संख्या असणे आवश्यक आहे. + %1$s पूर्णांक असणे आवश्यक आहे. + %1$s सत्य किंवा असत्य असणे आवश्यक आहे. + %1$s एक रंग स्ट्रिंग, RGB/RGBA ॲरे किंवा रंग ऑब्जेक्ट असणे आवश्यक आहे. + %1$s ने #RRGGBB किंवा #RRGGBBAA फॉरमॅट वापरणे आवश्यक आहे. + %1$s मध्ये वैध हेक्साडेसिमल कलर चॅनेल असणे आवश्यक आहे. + %1$s मध्ये 3 किंवा 4 रंगीत चॅनेल असणे आवश्यक आहे. + %1$s 0 आणि 255 च्या दरम्यान असणे आवश्यक आहे. + %1$s हा दोन-संख्या ॲरे किंवा x आणि y असलेली ऑब्जेक्ट असणे आवश्यक आहे. + %1$s मध्ये अगदी 2 संख्या असणे आवश्यक आहे. + डुप्लिकेट + नेहमी EXIF ​​साफ करा + सेव्ह करताना इमेज EXIF ​​डेटा काढून टाका, जरी एखादे साधन मेटाडेटा ठेवण्याची विनंती करते + मदत आणि टिपा + %1$d ट्यूटोरियल + साधन उघडा + मुख्य साधने, निर्यात पर्याय, PDF प्रवाह, रंग उपयुक्तता आणि सामान्य समस्यांचे निराकरण जाणून घ्या + सुरू करणे + साधने निवडा, फायली आयात करा, परिणाम जतन करा आणि सेटिंग्ज जलद वापरा + प्रतिमा संपादन + आकार बदला, क्रॉप करा, फिल्टर करा, पार्श्वभूमी पुसून टाका, काढा आणि वॉटरमार्क प्रतिमा + फाइल्स आणि मेटाडेटा + स्वरूप रूपांतरित करा, आउटपुटची तुलना करा, गोपनीयतेचे संरक्षण करा आणि फाइलनावे नियंत्रित करा + पीडीएफ आणि कागदपत्रे + पीडीएफ तयार करा, दस्तऐवज स्कॅन करा, ओसीआर पृष्ठे आणि शेअरिंगसाठी फाइल्स तयार करा + मजकूर, QR आणि डेटा + मजकूर ओळखा, कोड स्कॅन करा, बेस64 एन्कोड करा, हॅश तपासा आणि पॅकेज फाइल्स + रंगीत साधने + रंग निवडा, पॅलेट तयार करा, रंग लायब्ररी एक्सप्लोर करा आणि ग्रेडियंट तयार करा + सर्जनशील साधने + SVG, ASCII कला, नॉइज टेक्सचर, शेडर्स आणि प्रायोगिक व्हिज्युअल व्युत्पन्न करा + समस्यानिवारण + जड फाइल्स, पारदर्शकता, शेअरिंग, इंपोर्ट आणि रिपोर्टिंग समस्यांचे निराकरण करा + योग्य साधन निवडा + योग्य ठिकाणी सुरू करण्यासाठी श्रेण्या, शोध, आवडी आणि शेअर क्रिया वापरा + सर्वोत्तम प्रवेश बिंदूपासून प्रारंभ करा + इमेज टूलबॉक्समध्ये अनेक केंद्रित साधने आहेत आणि त्यापैकी अनेक पहिल्या दृष्टीक्षेपात ओव्हरलॅप होतात. तुम्ही पूर्ण करू इच्छित असलेल्या कार्यापासून सुरुवात करा, त्यानंतर परिणामाचा सर्वात महत्त्वाचा भाग नियंत्रित करणारे साधन निवडा: आकार, स्वरूप, मेटाडेटा, मजकूर, PDF पृष्ठे, रंग किंवा लेआउट. + जेव्हा तुम्हाला क्रियेचे नाव माहित असेल, जसे की आकार बदला, PDF, EXIF, OCR, QR किंवा रंग. + जेव्हा तुम्हाला फक्त कार्य प्रकार माहित असेल तेव्हा श्रेणी उघडा, त्यानंतर वारंवार साधने आवडते म्हणून पिन करा. + जेव्हा दुसरे ॲप इमेज टूलबॉक्समध्ये इमेज शेअर करते, तेव्हा शेअर शीट फ्लोमधून टूल निवडा. + परिणाम आयात करा, जतन करा आणि सामायिक करा + इनपुट निवडण्यापासून संपादित फाइल निर्यात करण्यापर्यंतचा नेहमीचा प्रवाह समजून घ्या + सामान्य संपादन प्रवाहाचे अनुसरण करा + बहुतेक साधने समान ताल पाळतात: इनपुट निवडा, पर्याय समायोजित करा, पूर्वावलोकन करा, नंतर जतन करा किंवा शेअर करा. महत्त्वाचा तपशील असा आहे की जतन केल्याने आउटपुट फाइल तयार होते, तर शेअर करणे सामान्यतः दुसऱ्या ॲपवर द्रुत हँडऑफसाठी सर्वोत्तम असते. + पिकरने परवानगी दिल्यावर सिंगल-इमेज टूल्ससाठी एक फाइल किंवा बॅच टूल्ससाठी अनेक फाइल्स निवडा. + जतन करण्यापूर्वी पूर्वावलोकन आणि आउटपुट नियंत्रणे तपासा, विशेषत: स्वरूप, गुणवत्ता आणि फाइलनाव पर्याय. + द्रुत हँडऑफसाठी शेअर वापरा किंवा निवडलेल्या फोल्डरमध्ये तुम्हाला सक्तीची कॉपी हवी असेल तेव्हा सेव्ह करा. + एकाच वेळी अनेक प्रतिमांसह कार्य करा + बॅच टूल्स पुनरावृत्ती आकार बदलणे, रूपांतरण, कॉम्प्रेशन आणि नामकरण कार्यांसाठी सर्वोत्तम आहेत + अंदाज लावता येणारी बॅच तयार करा + जेव्हा प्रत्येक आउटपुटने समान नियमांचे पालन केले पाहिजे तेव्हा बॅच प्रक्रिया जलद होते. मोठी चूक करण्यासाठी हे सर्वात सोपे ठिकाण देखील आहे, म्हणून दीर्घ निर्यात सुरू करण्यापूर्वी नाव, गुणवत्ता, मेटाडेटा आणि फोल्डर वर्तन निवडा. + सर्व संबंधित प्रतिमा एकत्र निवडा आणि आउटपुट अनुक्रमांवर अवलंबून असल्यास क्रम ठेवा. + निर्यात सुरू करण्यापूर्वी एकदा आकार बदला, स्वरूप, गुणवत्ता, मेटाडेटा आणि फाइलनाव नियम सेट करा. + जेव्हा स्त्रोत फाइल्स मोठ्या असतील किंवा लक्ष्य स्वरूप तुमच्यासाठी नवीन असेल तेव्हा प्रथम एक लहान चाचणी बॅच चालवा. + द्रुत एकल संपादन + तुम्हाला एका प्रतिमेवर अनेक सोप्या बदलांची आवश्यकता असेल तेव्हा सर्व-इन-वन संपादक वापरा + टूल हॉपिंगशिवाय एक प्रतिमा पूर्ण करा + जेव्हा प्रतिमेला एका विशिष्ट ऑपरेशनऐवजी बदलांच्या छोट्या साखळीची आवश्यकता असते तेव्हा एकल संपादन उपयुक्त ठरते. बॅच वर्कफ्लो तयार न करता जलद साफ करणे, पूर्वावलोकन करणे आणि एक अंतिम प्रत निर्यात करणे हे सहसा जलद असते. + सामान्य समायोजन, मार्कअप, क्रॉप, रोटेशन किंवा निर्यात बदल आवश्यक असलेल्या एका प्रतिमेसाठी एकल संपादन उघडा. + सर्वात कमी पिक्सेल प्रथम बदलणाऱ्या क्रमाने संपादने लागू करा, जसे की फिल्टर्सपूर्वी क्रॉप करा आणि अंतिम कॉम्प्रेशनपूर्वी फिल्टर करा. + जेव्हा तुम्हाला बॅच प्रोसेसिंग, अचूक फाइल-आकार लक्ष्य, PDF आउटपुट, OCR किंवा मेटाडेटा क्लीनअपची आवश्यकता असेल तेव्हा त्याऐवजी एक विशेष साधन वापरा. + प्रीसेट आणि सेटिंग्ज वापरा + वारंवार निवडी जतन करा आणि तुमच्या पसंतीच्या वर्कफ्लोसाठी ॲप ट्यून करा + समान सेटअपची पुनरावृत्ती टाळा + जेव्हा तुम्ही एकाच प्रकारची फाईल वारंवार निर्यात करता तेव्हा प्रीसेट आणि सेटिंग्ज उपयुक्त ठरतात. एक चांगला प्रीसेट पुनरावृत्ती केलेल्या मॅन्युअल चेकलिस्टला एका टॅपमध्ये बदलतो, तर जागतिक सेटिंग्ज नवीन टूल्ससह सुरू होणाऱ्या डीफॉल्ट परिभाषित करतात. + सामान्य आउटपुट आकार, स्वरूप, गुणवत्ता मूल्ये किंवा नामकरण पद्धतींसाठी प्रीसेट तयार करा. + डीफॉल्ट स्वरूप, गुणवत्ता, सेव्ह फोल्डर, फाइलनाव, पिकर आणि इंटरफेस वर्तनासाठी सेटिंग्जचे पुनरावलोकन करा. + ॲप पुन्हा इंस्टॉल करण्यापूर्वी किंवा दुसऱ्या डिव्हाइसवर जाण्यापूर्वी सेटिंग्जचा बॅकअप घ्या. + उपयुक्त डीफॉल्ट सेट करा + डीफॉल्ट स्वरूप, गुणवत्ता, स्केल मोड, आकार बदलण्याचा प्रकार आणि रंगाची जागा एकदा निवडा + नवीन साधने तुमच्या ध्येयाच्या जवळ सुरू करा + डीफॉल्ट मूल्ये केवळ कॉस्मेटिक प्राधान्ये नाहीत. अनेक साधनांमध्ये कोणते स्वरूप, गुणवत्ता, आकार बदलण्याची वर्तणूक, स्केल मोड आणि रंगाची जागा प्रथम दिसावी हे ते ठरवतात, जे प्रत्येक निर्यातीवर समान निवडी पुन्हा निवडणे टाळण्यास मदत करतात. + तुमच्या नेहमीच्या गंतव्याशी जुळण्यासाठी डीफॉल्ट प्रतिमा स्वरूप आणि गुणवत्ता सेट करा, जसे की फोटोंसाठी JPEG किंवा अल्फासाठी PNG/WebP. + तुम्ही अनेकदा कठोर परिमाणे, सोशल प्लॅटफॉर्म किंवा दस्तऐवज पृष्ठांसाठी निर्यात करत असल्यास डीफॉल्ट आकार बदला प्रकार आणि स्केल मोड ट्यून करा. + जेव्हा तुमच्या वर्कफ्लोला डिस्प्ले, प्रिंट किंवा बाह्य संपादकांमध्ये सातत्यपूर्ण रंग हाताळण्याची आवश्यकता असते तेव्हाच रंग स्पेस डीफॉल्ट बदला. + पिकर आणि लाँचर + जेव्हा ॲप खूप डायलॉग उघडतो किंवा चुकीच्या ठिकाणी सुरू होतो तेव्हा पिकर सेटिंग्ज वापरा + फाइल उघडणे तुमच्या सवयीशी जुळवून घ्या + पिकर आणि लाँचर सेटिंग्ज इमेज टूलबॉक्स मुख्य स्क्रीन, टूल किंवा फाइल सिलेक्शन फ्लोवरून सुरू होते की नाही हे नियंत्रित करतात. हे पर्याय विशेषतः जेव्हा तुम्ही Android शेअर शीटमधून एंटर करता किंवा जेव्हा तुम्हाला ॲप एखाद्या टूल लाँचरसारखे वाटू इच्छित असेल तेव्हा उपयुक्त ठरतात. + सिस्टीम पिकर फाइल्स लपवत असल्यास, बर्याच अलीकडील आयटम दाखवत असल्यास किंवा क्लाउड फाइल्स खराब हाताळत असल्यास फोटो पिकर सेटिंग्ज वापरा. + फक्त त्या साधनांसाठी वगळा पिकिंग सक्षम करा जिथे तुम्ही सहसा शेअरमधून एंटर करता किंवा स्त्रोत फाइल आधीच ओळखता. + तुम्हाला इमेज टूलबॉक्स सामान्य होम स्क्रीनपेक्षा टूल पिकरसारखे वागायचे असल्यास लाँचर मोडचे पुनरावलोकन करा. + प्रतिमा स्रोत + गॅलरी, फाइल व्यवस्थापक आणि क्लाउड फायलींसह सर्वोत्तम कार्य करणारा पिकर मोड निवडा + योग्य स्त्रोतावरून फायली निवडा + ॲप Android प्रतिमांसाठी कसे विचारते यावर प्रतिमा स्त्रोत सेटिंग्ज प्रभावित करतात. तुमच्या फाइल सिस्टम गॅलरी, फाइल व्यवस्थापक फोल्डर, क्लाउड प्रदाता किंवा तात्पुरती सामग्री सामायिक करण्याचे दुसरे ॲप यामध्ये राहतात की नाही यावर सर्वोत्तम निवड अवलंबून असते. + जेव्हा तुम्हाला सामान्य गॅलरी प्रतिमांसाठी सर्वात सिस्टम-एकत्रित अनुभव हवा असेल तेव्हा डीफॉल्ट पिकर वापरा. + अल्बम, फोल्डर, क्लाउड फाइल्स किंवा नॉनस्टँडर्ड फॉरमॅट्स तुमच्या अपेक्षेनुसार दिसत नसल्यास पिकर मोड स्विच करा. + स्रोत ॲप सोडल्यानंतर शेअर केलेली फाइल गायब झाल्यास, ती पर्सिस्टंट पिकरद्वारे पुन्हा उघडा किंवा प्रथम स्थानिक प्रत जतन करा. + आवडते आणि सामायिक साधने + मुख्य स्क्रीनवर फोकस ठेवा आणि शेअर फ्लोमध्ये अर्थ नसलेली साधने लपवा + टूल सूचीचा आवाज कमी करा + तुम्ही दररोज फक्त काही साधने वापरता तेव्हा आवडी आणि शेअरसाठी लपविलेल्या सेटिंग्ज उपयुक्त ठरतात. दुसऱ्या ॲपने पाठवलेल्या फाईलचा प्रकार वापरू शकत नाही अशी साधने लपवून ते शेअर प्रवाह व्यावहारिक ठेवतात. + वारंवार साधने पिन करा जेणेकरुन साधने श्रेणीनुसार गटबद्ध केली तरीही ते पोहोचण्यास सोपे राहतील. + साधने दुसऱ्या ॲपवरून येणाऱ्या फायलींसाठी अप्रासंगिक असताना शेअर फ्लोपासून लपवा. + तुम्ही शेवटी आवडींना प्राधान्य देत असल्यास किंवा संबंधित साधनांसह गटबद्ध करत असल्यास आवडते ऑर्डरिंग सेटिंग्ज वापरा. + बॅकअप सेटिंग्ज + प्रीसेट, प्राधान्ये आणि ॲप वर्तन सुरक्षितपणे दुसऱ्या इंस्टॉलमध्ये हलवा + तुमचा सेटअप पोर्टेबल ठेवा + तुम्ही प्रीसेट, फोल्डर्स, फाइलनाव नियम, टूल ऑर्डर आणि व्हिज्युअल प्राधान्ये ट्यून केल्यानंतर बॅकअप आणि रिस्टोर सर्वात उपयुक्त आहे. बॅकअप तुम्हाला मेमरीमधून तुमचा वर्कफ्लो पुन्हा तयार न करता सेटिंग्जसह प्रयोग करू देतो किंवा ॲप पुन्हा इंस्टॉल करू देतो. + प्रीसेट, फाइलनाव वर्तन, डीफॉल्ट स्वरूप किंवा साधन व्यवस्था बदलल्यानंतर बॅकअप तयार करा. + तुम्ही डेटा साफ करण्याची, पुन्हा इंस्टॉल करण्याची किंवा डिव्हाइसेस हलवण्याची योजना करत असल्यास बॅकअप ॲप फोल्डरच्या बाहेर कुठेतरी साठवा. + महत्त्वाच्या बॅचवर प्रक्रिया करण्यापूर्वी सेटिंग्ज पुनर्संचयित करा जेणेकरून डीफॉल्ट आणि सेव्ह वर्तन तुमच्या जुन्या सेटअपशी जुळेल. + आकार बदला आणि प्रतिमा रूपांतरित करा + एक किंवा अनेक प्रतिमांसाठी आकार, स्वरूप, गुणवत्ता आणि मेटाडेटा बदला + आकार बदललेल्या प्रती तयार करा + आकार बदलणे आणि रूपांतरित करणे हे अंदाजे आकारमान आणि हलक्या आउटपुट फाइल्ससाठी मुख्य साधन आहे. जेव्हा प्रतिमेचा आकार, आउटपुट स्वरूप आणि मेटाडेटा वर्तन एकाच वेळी महत्त्वाचे असते तेव्हा ते वापरा. + एक किंवा अधिक प्रतिमा निवडा, नंतर परिमाण, स्केल किंवा फाइल आकार सर्वात महत्त्वाचा आहे की नाही ते निवडा. + आउटपुट स्वरूप आणि गुणवत्ता निवडा; जेव्हा पारदर्शकता जतन करणे आवश्यक आहे तेव्हा PNG किंवा WebP वापरा. + निकालाचे पूर्वावलोकन करा, नंतर मूळ न बदलता व्युत्पन्न केलेल्या प्रती जतन करा किंवा सामायिक करा. + फाइल आकारानुसार आकार बदला + वेबसाइट, मेसेंजर किंवा फॉर्म जड प्रतिमा नाकारतात तेव्हा आकार मर्यादा लक्ष्यित करा + कठोर अपलोड मर्यादा फिट करा + जेव्हा गंतव्यस्थान विशिष्ट मर्यादेपेक्षा कमी फाइल्स स्वीकारते तेव्हा गुणवत्तेच्या मूल्यांचा अंदाज लावण्यापेक्षा फाइल आकारानुसार आकार बदलणे चांगले असते. परिणाम वापरण्यायोग्य ठेवण्याचा प्रयत्न करताना लक्ष्यापर्यंत पोहोचण्यासाठी साधन कॉम्प्रेशन आणि परिमाण समायोजित करू शकते. + मेटाडेटा आणि प्रदाता बदलांसाठी जागा सोडण्यासाठी वास्तविक अपलोड मर्यादेपेक्षा किंचित खाली लक्ष्य आकार प्रविष्ट करा. + लक्ष्य लहान असताना फोटोंसाठी नुकसानदायक स्वरूपांना प्राधान्य द्या; आकार बदलल्यानंतरही PNG मोठा राहू शकतो. + निर्यात केल्यानंतर चेहरे, मजकूर आणि कडा तपासा कारण आक्रमक आकाराचे लक्ष्य दृश्यमान कलाकृती सादर करू शकतात. + आकार बदलण्याची मर्यादा + तुमची कमाल रुंदी, उंची किंवा फाइल मर्यादा ओलांडणाऱ्या केवळ प्रतिमा संकुचित करा + आधीच ठीक असलेल्या प्रतिमांचा आकार बदलणे टाळा + लिमिट रिसाईज मिश्रित फोल्डर्ससाठी उपयुक्त आहे जेथे काही प्रतिमा मोठ्या आहेत आणि इतर आधीच तयार आहेत. प्रत्येक फाईलला समान आकार बदलून सक्ती करण्याऐवजी, ते स्वीकार्य प्रतिमा त्यांच्या मूळ गुणवत्तेच्या जवळ ठेवते. + प्रत्येक फाईलचा आंधळेपणाने आकार बदलण्याऐवजी गंतव्यस्थानावर आधारित कमाल परिमाणे किंवा मर्यादा सेट करा. + जेव्हा तुम्ही स्रोतांपेक्षा जास्त वजनदार होणारे आउटपुट टाळू इच्छित असाल तर वगळा-मोठे शैलीचे वर्तन सक्षम करा. + वेगवेगळ्या कॅमेऱ्यांच्या बॅचसाठी, स्क्रीनशॉटसाठी किंवा डाउनलोडसाठी वापरा जिथे स्त्रोत आकार खूप बदलतात. + क्रॉप करा, फिरवा आणि सरळ करा + निर्यात करण्यापूर्वी किंवा इतर साधनांमध्ये प्रतिमा वापरण्यापूर्वी महत्त्वाचे क्षेत्र फ्रेम करा + रचना साफ करा + प्रथम क्रॉप केल्याने नंतरचे फिल्टर, ओसीआर, पीडीएफ पृष्ठे आणि शेअरिंग परिणाम स्वच्छ होतात. + क्रॉप उघडा आणि तुम्हाला समायोजित करायची असलेली प्रतिमा निवडा. + आउटपुट पोस्ट, दस्तऐवज किंवा अवतारमध्ये फिट असणे आवश्यक असताना विनामूल्य क्रॉप किंवा आस्पेक्ट रेशो वापरा. + जतन करण्यापूर्वी फिरवा किंवा फ्लिप करा जेणेकरून नंतरच्या साधनांना दुरुस्त केलेली प्रतिमा प्राप्त होईल. + फिल्टर आणि समायोजन लागू करा + संपादन करण्यायोग्य फिल्टर स्टॅक तयार करा आणि निर्यात करण्यापूर्वी निकालाचे पूर्वावलोकन करा + प्रतिमा देखावा ट्यून करा + जलद सुधारणा, शैलीबद्ध आउटपुट आणि OCR किंवा छपाईसाठी प्रतिमा तयार करण्यासाठी फिल्टर उपयुक्त आहेत. जेव्हा प्रतिमेमध्ये मजकूर किंवा बारीक तपशील असतात तेव्हा तीव्र प्रभावांपेक्षा कॉन्ट्रास्ट, तीक्ष्णता आणि ब्राइटनेसमधील लहान बदल अधिक महत्त्वाचे असतात. + एक-एक फिल्टर्स जोडा आणि प्रत्येक बदलानंतर पूर्वावलोकनावर लक्ष ठेवा. + कार्यानुसार ब्राइटनेस, कॉन्ट्रास्ट, तीक्ष्णता, अस्पष्टता, रंग किंवा मुखवटे समायोजित करा. + पूर्वावलोकन तुमच्या लक्ष्य डिव्हाइस, दस्तऐवज किंवा सामाजिक प्लॅटफॉर्मशी जुळते तेव्हाच निर्यात करा. + प्रथम पूर्वावलोकन करा + एखादे वजनदार संपादन, रूपांतरण किंवा क्लीनअप साधन निवडण्यापूर्वी प्रतिमांची तपासणी करा + तुम्हाला प्रत्यक्षात काय मिळाले ते तपासा + जेव्हा एखादी फाइल चॅट, क्लाउड स्टोरेज किंवा अन्य ॲपमधून येते आणि त्यात काय आहे याची तुम्हाला खात्री नसते तेव्हा इमेज पूर्वावलोकन उपयुक्त ठरते. परिमाण, पारदर्शकता, अभिमुखता आणि व्हिज्युअल गुणवत्ता तपासणे प्रथम योग्य फॉलो-अप साधन निवडण्यात मदत करते. + तुम्हाला अनेक प्रतिमांचे काय करायचे हे ठरवण्यापूर्वी त्यांची तपासणी करायची असेल तेव्हा प्रतिमा पूर्वावलोकन उघडा. + रोटेशन समस्या, पारदर्शक क्षेत्रे, कॉम्प्रेशन आर्टिफॅक्ट्स आणि अनपेक्षितपणे मोठे परिमाण पहा. + आपल्याला काय निश्चित करणे आवश्यक आहे हे समजल्यानंतरच आकार बदला, क्रॉप करा, तुलना करा, EXIF ​​किंवा बॅकग्राउंड रिमूव्हरवर जा. + पार्श्वभूमी काढा किंवा पुनर्संचयित करा + पार्श्वभूमी आपोआप पुसून टाका किंवा पुनर्संचयित साधनांसह किनारी व्यक्तिचलितपणे परिष्कृत करा + विषय ठेवा, बाकीचे काढा + तुम्ही काळजीपूर्वक मॅन्युअल क्लीनअपसह स्वयंचलित निवड एकत्र करता तेव्हा पार्श्वभूमी काढणे सर्वोत्तम कार्य करते. अंतिम निर्यात स्वरूप हे मुखवटाइतकेच महत्त्वाचे आहे, कारण पूर्वावलोकन योग्य दिसत असले तरीही JPEG पारदर्शक क्षेत्रे सपाट करेल. + जर विषय पार्श्वभूमीपासून स्पष्टपणे विभक्त केला असेल तर स्वयंचलितपणे काढून टाकण्यास प्रारंभ करा. + झूम इन करा आणि केस, कोपरे, सावल्या आणि लहान तपशील निश्चित करण्यासाठी मिटवा आणि पुनर्संचयित करा दरम्यान स्विच करा. + तुम्हाला अंतिम फाइलमध्ये पारदर्शकता हवी असेल तेव्हा PNG किंवा WebP म्हणून सेव्ह करा. + काढा, चिन्हांकित करा आणि वॉटरमार्क करा + बाण, नोट्स, हायलाइट, स्वाक्षरी किंवा वारंवार वॉटरमार्क आच्छादन जोडा + दृश्यमान माहिती जोडा + मार्कअप साधने प्रतिमा स्पष्ट करण्यात मदत करतात, तर वॉटरमार्किंग निर्यात केलेल्या फायलींना ब्रँड किंवा संरक्षित करण्यात मदत करते. + जेव्हा तुम्हाला फ्रीहँड नोट्स, आकार, हायलाइटिंग किंवा द्रुत भाष्ये आवश्यक असतील तेव्हा ड्रॉ वापरा. + जेव्हा समान मजकूर किंवा प्रतिमा चिन्ह आउटपुटवर सातत्याने दिसले पाहिजे तेव्हा वॉटरमार्किंग वापरा. + निर्यात करण्यापूर्वी अपारदर्शकता, स्थिती आणि स्केल तपासा जेणेकरून चिन्ह दृश्यमान असेल परंतु विचलित होणार नाही. + डीफॉल्ट काढा + वेगवान मार्कअपसाठी रेषेची रुंदी, रंग, पथ मोड, अभिमुखता लॉक आणि भिंग सेट करा + रेखाचित्र अंदाज करण्यायोग्य बनवा + जेव्हा तुम्ही स्क्रीनशॉट भाष्य करता, दस्तऐवज चिन्हांकित करता किंवा प्रतिमांवर अनेकदा स्वाक्षरी करता तेव्हा ड्रॉ सेटिंग्ज उपयुक्त ठरतात. डीफॉल्ट सेटअप वेळ कमी करतात, तर भिंग आणि ओरिएंटेशन लॉक लहान स्क्रीनवर अचूक संपादने सुलभ करतात. + डीफॉल्ट रेषेची रुंदी सेट करा आणि बाण, हायलाइट किंवा स्वाक्षरीसाठी तुम्ही सर्वाधिक वापरता त्या मूल्यांसाठी रंग काढा. + जेव्हा तुम्ही सामान्यतः समान प्रकारचे स्ट्रोक, आकार किंवा मार्कअप काढता तेव्हा डीफॉल्ट पथ मोड वापरा. + जेव्हा बोट इनपुटमुळे लहान तपशील अचूकपणे ठेवणे कठीण होते तेव्हा मॅग्निफायर किंवा ओरिएंटेशन लॉक सक्षम करा. + मल्टी-इमेज लेआउट + स्क्रीनशॉट, स्कॅन किंवा तुलनात्मक पट्ट्यांची व्यवस्था करण्यापूर्वी योग्य मल्टी-इमेज टूल निवडा + योग्य लेआउट साधन वापरा + ही साधने सारखीच वाटतात, परंतु प्रत्येकाला वेगवेगळ्या प्रकारच्या मल्टी-इमेज आउटपुटसाठी ट्यून केले जाते. प्रथम योग्य निवडणे भिन्न परिणामांसाठी डिझाइन केलेल्या लेआउट नियंत्रणांशी लढणे टाळते. + एका रचनामध्ये अनेक प्रतिमांसह डिझाइन केलेल्या लेआउटसाठी कोलाज मेकर वापरा. + प्रतिमा एक लांब उभ्या किंवा क्षैतिज पट्टी बनल्या पाहिजेत तेव्हा प्रतिमा स्टिचिंग किंवा स्टॅकिंग वापरा. + जेव्हा एका मोठ्या प्रतिमेचे अनेक छोटे तुकडे होण्याची आवश्यकता असते तेव्हा इमेज स्प्लिटिंग वापरा. + प्रतिमा स्वरूप रूपांतरित करा + पिक्सेल मॅन्युअली संपादित न करता JPEG, PNG, WebP, AVIF, JXL आणि इतर प्रती तयार करा + नोकरीसाठी स्वरूप निवडा + फोटो, स्क्रीनशॉट, पारदर्शकता, कॉम्प्रेशन किंवा सुसंगततेसाठी भिन्न स्वरूप चांगले आहेत. डेस्टिनेशन ॲप कशाचे समर्थन करते आणि स्त्रोतामध्ये अल्फा, ॲनिमेशन किंवा मेटाडेटा आहे की नाही हे तुम्हाला समजते तेव्हा रूपांतरण सर्वात सुरक्षित असते. + सुसंगत फोटोंसाठी JPEG, लॉसलेस इमेजसाठी PNG आणि अल्फा आणि कॉम्पॅक्ट शेअरिंगसाठी WebP वापरा. + जेव्हा स्वरूप त्यास समर्थन देते तेव्हा गुणवत्ता समायोजित करा; कमी मूल्ये आकार कमी करतात परंतु कलाकृती जोडू शकतात. + तुम्ही लक्ष्य ॲपमध्ये रूपांतरित फायली अचूकपणे उघडत नाही याची पडताळणी करेपर्यंत मूळ ठेवा. + EXIF आणि गोपनीयता तपासा + संवेदनशील फोटो शेअर करण्यापूर्वी मेटाडेटा पहा, संपादित करा किंवा काढा + लपविलेले प्रतिमा डेटा नियंत्रित करा + EXIF मध्ये कॅमेरा डेटा, तारखा, स्थान, अभिमुखता आणि इमेजमध्ये दिसणारे इतर तपशील असू शकतात. सार्वजनिक सामायिकरण, समर्थन अहवाल, मार्केटप्लेस सूची किंवा खाजगी ठिकाण उघड करू शकणारे कोणतेही फोटो करण्यापूर्वी हे तपासण्यासारखे आहे. + फोटो शेअर करण्यापूर्वी EXIF ​​टूल्स उघडा ज्यात स्थान किंवा खाजगी डिव्हाइस माहिती असू शकते. + संवेदनशील फील्ड काढा किंवा तारीख, अभिमुखता, लेखक किंवा कॅमेरा डेटा यासारखे चुकीचे टॅग संपादित करा. + एक नवीन प्रत जतन करा आणि गोपनीयतेला महत्त्व असताना मूळ ऐवजी ती प्रत शेअर करा. + ऑटो EXIF ​​क्लीनअप + तारखा जतन करायच्या की नाही हे ठरवताना डीफॉल्टनुसार मेटाडेटा काढून टाका + गोपनीयतेला डीफॉल्ट बनवा + EXIF सेटिंग्ज गट उपयोगी ठरतो जेव्हा तुम्हाला प्रत्येक निर्यातीसाठी गोपनीयतेची साफसफाई आपोआप लक्षात ठेवायची असते. तारीख वेळ ठेवा हा वेगळा निर्णय आहे कारण तारखा क्रमवारी लावण्यासाठी उपयुक्त पण शेअरिंगसाठी संवेदनशील असू शकतात. + जेव्हा तुमची बहुतेक निर्यात सार्वजनिक किंवा अर्ध-सार्वजनिक शेअरिंगसाठी असते तेव्हा नेहमी-स्पष्ट EXIF ​​सक्षम करा. + प्रत्येक टाइमस्टॅम्प काढण्यापेक्षा कॅप्चर वेळ जतन करणे अधिक महत्त्वाचे असते तेव्हाच तारीख वेळ ठेवा सक्षम करा. + एकल-ऑफ फाइल्ससाठी मॅन्युअल EXIF ​​संपादन वापरा जिथे तुम्हाला काही फील्ड ठेवणे आणि इतर काढणे आवश्यक आहे. + फाइलनावे नियंत्रित करा + उपसर्ग, प्रत्यय, टाइमस्टॅम्प, प्रीसेट, चेकसम आणि अनुक्रम संख्या वापरा + निर्यात शोधणे सोपे करा + एक चांगला फाइलनाव नमुना बॅचेस क्रमवारीत मदत करतो आणि अपघाती ओव्हरराईट टाळतो. फाईलनाव सेटिंग्ज विशेषतः उपयोगी असतात जेव्हा निर्यात स्त्रोत फोल्डरवर परत जातात, जेथे समान नावे पटकन गोंधळात टाकतात. + फाइलनाव उपसर्ग, प्रत्यय, टाइमस्टॅम्प, मूळ नाव, प्रीसेट माहिती किंवा सेटिंग्जमध्ये अनुक्रम पर्याय सेट करा. + बॅचसाठी क्रम संख्या वापरा जिथे ऑर्डर महत्त्वाची आहे, जसे की पेज, फ्रेम किंवा कोलाज. + जेव्हा तुम्हाला खात्री असेल की विद्यमान फायली बदलणे वर्तमान फोल्डरसाठी सुरक्षित आहे तेव्हाच अधिलेखन सक्षम करा. + फायली अधिलिखित करा + जेव्हा तुमचा कार्यप्रवाह हेतुपुरस्सर विनाशकारी असेल तेव्हाच आउटपुट जुळणाऱ्या नावांसह बदला + ओव्हरराइट मोड काळजीपूर्वक वापरा + फाईल्स ओव्हरराइट केल्याने नामकरण विवाद कसे हाताळले जातात ते बदलते. हे त्याच फोल्डरमध्ये पुनरावृत्ती निर्यात करण्यासाठी उपयुक्त आहे, परंतु तुमच्या फाइलनाव पॅटर्नने तेच नाव पुन्हा तयार केल्यास ते पूर्वीचे परिणाम देखील बदलू शकते. + केवळ फोल्डरसाठी अधिलेखन सक्षम करा जेथे जुन्या व्युत्पन्न केलेल्या फायली बदलणे अपेक्षित आहे आणि पुनर्प्राप्त करण्यायोग्य आहे. + मूळ, क्लायंट फायली, एक-वेळ स्कॅन किंवा बॅकअपशिवाय काहीही निर्यात करताना ओव्हरराईट अक्षम ठेवा. + मुद्दाम फाइलनाव पॅटर्नसह अधिलेखन एकत्र करा जेणेकरून केवळ इच्छित आउटपुट बदलले जातील. + फाइलनाव नमुने + मूळ नावे, उपसर्ग, प्रत्यय, टाइमस्टॅम्प, प्रीसेट, स्केल मोड आणि चेकसम एकत्र करा + आउटपुट स्पष्ट करणारी नावे तयार करा + फाइलनाव पॅटर्न सेटिंग्ज निर्यात केलेल्या फायलींना त्यांच्याशी काय घडले याचा उपयुक्त इतिहास बनवू शकतात. हे बॅचमध्ये महत्त्वाचे आहे कारण फोल्डर दृश्य हे एकमेव ठिकाण असू शकते जिथे तुम्ही मूळ आकार, प्रीसेट, स्वरूप आणि प्रक्रिया क्रम वेगळे करू शकता. + जेव्हा तुम्हाला मॅन्युअल क्रमवारीसाठी वाचनीय नावांची आवश्यकता असेल तेव्हा मूळ फाइलनाव, उपसर्ग, प्रत्यय आणि टाइमस्टॅम्प वापरा. + प्रीसेट, स्केल मोड, फाइल आकार किंवा चेकसम माहिती जोडा जेव्हा आउटपुटने ते कसे तयार केले गेले ते दस्तऐवजीकरण करणे आवश्यक आहे. + वर्कफ्लोसाठी यादृच्छिक नावे टाळा जिथे फायली मूळ किंवा पृष्ठ ऑर्डरसह जोडल्या जाव्यात. + फाइल्स कुठे सेव्ह केल्या आहेत ते निवडा + फोल्डर, मूळ-फोल्डर सेव्हिंग आणि वन-टाइम सेव्ह लोकेशन सेट करून निर्यात गमावणे टाळा + आउटपुट अंदाजे ठेवा + जेव्हा तुम्ही अनेक फायलींवर प्रक्रिया करता किंवा क्लाउड प्रदात्यांकडून फायली शेअर करता तेव्हा वर्तन जतन करणे महत्त्वाचे असते. फोल्डर सेटिंग्ज हे ठरवतात की आउटपुट एका ज्ञात ठिकाणी गोळा केले जातात, मूळच्या बाजूला दिसतात किंवा विशिष्ट कार्यासाठी तात्पुरते कुठेतरी जातात. + तुम्हाला नेहमी एकाच ठिकाणी निर्यात हवी असल्यास डिफॉल्ट सेव्हिंग फोल्डर सेट करा. + क्लीनअप वर्कफ्लोसाठी सेव्ह-टू-ओरिजिनल-फोल्डर वापरा जेथे आउटपुट स्त्रोत फाइलजवळ राहावे. + तुमचा डीफॉल्ट न बदलता एकाच टास्कसाठी वेगळ्या गंतव्याची आवश्यकता असताना एक-वेळ सेव्ह स्थान वापरा. + एक-वेळ फोल्डर जतन करा + तुमचे सामान्य गंतव्यस्थान न बदलता पुढील निर्यात तात्पुरत्या फोल्डरमध्ये पाठवा + विशेष काम वेगळे ठेवा + तात्पुरत्या प्रकल्पांसाठी, क्लायंट फोल्डर्ससाठी, क्लीनअप सत्रांसाठी किंवा तुमच्या नियमित इमेज टूलबॉक्स फोल्डरमध्ये मिसळू नयेत अशा निर्यातीसाठी वन-टाइम सेव्ह लोकेशन उपयुक्त आहे. हे तुमचे डीफॉल्ट सेटअप पुन्हा न लिहिता अल्पकालीन गंतव्य देते. + तुमच्या सामान्य निर्यात स्थानाबाहेरील कार्य सुरू करण्यापूर्वी एक-वेळ फोल्डर निवडा. + लहान प्रकल्पांसाठी, सामायिक केलेल्या फोल्डर्ससाठी किंवा बॅचेससाठी वापरा जेथे आउटपुट एकत्रितपणे गटबद्ध केले पाहिजेत. + नोकरीनंतर डीफॉल्ट फोल्डरवर परत या जेणेकरून नंतरची निर्यात अनपेक्षितपणे तात्पुरत्या ठिकाणी येऊ नये. + गुणवत्ता आणि फाइल आकार संतुलित करा + अंदाज लावण्याऐवजी परिमाणे, स्वरूप किंवा गुणवत्ता कधी बदलायची ते समजून घ्या + फायली जाणूनबुजून संकुचित करा + फाइल आकार प्रतिमा परिमाणे, स्वरूप, गुणवत्ता, पारदर्शकता आणि सामग्री जटिलता यावर अवलंबून असते. फाइल अजूनही खूप जड असल्यास, आकारमान बदलणे सहसा वारंवार गुणवत्ता कमी करण्यापेक्षा अधिक मदत करते. + जेव्हा प्रतिमा गंतव्यस्थानापेक्षा मोठी असेल तेव्हा प्रथम आकारमानाचा आकार बदला. + केवळ हानीकारक फॉरमॅटसाठी खालची गुणवत्ता आणि निर्यात केल्यानंतर मजकूर, कडा, ग्रेडियंट आणि चेहरे तपासा. + गुणवत्तेत बदल पुरेसे नसतात तेव्हा फॉरमॅट स्विच करा, उदाहरणार्थ शेअरिंगसाठी WebP किंवा कुरकुरीत पारदर्शक मालमत्तेसाठी PNG. + ॲनिमेटेड स्वरूपांसह कार्य करा + फाइलमध्ये एका बिटमॅपऐवजी फ्रेम्स असतात तेव्हा GIF, APNG, WebP आणि JXL टूल्स वापरा + प्रत्येक प्रतिमेला स्थिर मानू नका + ॲनिमेटेड फॉरमॅटला फ्रेम, कालावधी आणि सुसंगतता समजणारी साधने आवश्यक असतात. + जेव्हा तुम्हाला त्या फॉरमॅटसाठी फ्रेम-स्तरीय ऑपरेशन्सची आवश्यकता असेल तेव्हा GIF किंवा APNG टूल्स वापरा. + जेव्हा तुम्हाला आधुनिक कॉम्प्रेशन किंवा ॲनिमेटेड WebP आउटपुट आवश्यक असेल तेव्हा WebP टूल्स वापरा. + शेअर करण्यापूर्वी ॲनिमेशन वेळेचे पूर्वावलोकन करा कारण काही प्लॅटफॉर्म ॲनिमेटेड प्रतिमा रूपांतरित किंवा सपाट करतात. + आउटपुटची तुलना करा आणि पूर्वावलोकन करा + निर्यात ठेवण्यापूर्वी बदल, फाइल आकार आणि व्हिज्युअल फरक तपासा + शेअर करण्यापूर्वी पडताळणी करा + तुलना साधने कॉम्प्रेशन आर्टिफॅक्ट्स, चुकीचे रंग किंवा अवांछित संपादने लवकर पकडण्यात मदत करतात. + जेव्हा तुम्हाला दोन आवृत्त्यांची शेजारी शेजारी तपासणी करायची असेल तेव्हा तुलना उघडा. + कडा, मजकूर, ग्रेडियंट आणि पारदर्शक प्रदेशांमध्ये झूम करा जिथे समस्या चुकणे सर्वात सोपे आहे. + आउटपुट खूप जड किंवा अस्पष्ट असल्यास, मागील टूलवर परत या आणि गुणवत्ता किंवा स्वरूप समायोजित करा. + PDF टूल्स हब वापरा + विलीन करा, विभाजित करा, फिरवा, पृष्ठे काढा, संकुचित करा, संरक्षण करा, OCR आणि वॉटरमार्क PDF फाइल्स + पीडीएफ ऑपरेशन निवडा + पीडीएफ हब एका एंट्री पॉइंटच्या मागे दस्तऐवज क्रियांचे गट बनवते जेणेकरून तुम्ही अचूक ऑपरेशन निवडू शकता. जेव्हा फाइल आधीपासूनच PDF असेल तेव्हा तेथून प्रारंभ करा आणि जेव्हा तुमचा स्रोत अद्याप प्रतिमांचा संच असेल तेव्हा PDF किंवा दस्तऐवज स्कॅनरमध्ये प्रतिमा वापरा. + PDF टूल्स उघडा आणि तुमच्या ध्येयाशी जुळणारे ऑपरेशन निवडा. + स्रोत पीडीएफ किंवा प्रतिमा निवडा, त्यानंतर पृष्ठ क्रम, रोटेशन, संरक्षण किंवा OCR पर्यायांचे पुनरावलोकन करा. + व्युत्पन्न केलेली PDF जतन करा आणि पृष्ठ संख्या, ऑर्डर आणि वाचनीयतेची पुष्टी करण्यासाठी एकदा ती उघडा. + प्रतिमा PDF मध्ये बदला + एका शेअर करण्यायोग्य दस्तऐवजात स्कॅन, स्क्रीनशॉट, पावत्या किंवा फोटो एकत्र करा + स्वच्छ दस्तऐवज तयार करा + प्रतिमा क्रॉप केल्या गेल्या, क्रम दिल्या गेल्या आणि आकारात सुसंगतपणे पीडीएफ पृष्ठे बनतात. प्रतिमा सूचीला पृष्ठ स्टॅकप्रमाणे हाताळा: प्रत्येक रोटेशन, मार्जिन आणि पृष्ठ-आकार निवड अंतिम दस्तऐवज किती वाचनीय वाटते यावर परिणाम करते. + जेव्हा पृष्ठाच्या कडा, सावल्या किंवा अभिमुखता चुकीची असतात तेव्हा प्रथम प्रतिमा क्रॉप करा किंवा फिरवा. + इच्छित पृष्ठ क्रमानुसार प्रतिमा निवडा आणि जर टूल त्यांना ऑफर करत असेल तर पृष्ठ आकार किंवा समास समायोजित करा. + पीडीएफ निर्यात करा, नंतर ते पाठवण्यापूर्वी सर्व पृष्ठे वाचनीय आहेत का ते तपासा. + कागदपत्रे स्कॅन करा + पेपर पृष्ठे कॅप्चर करा आणि पीडीएफ, ओसीआर किंवा शेअरिंगसाठी तयार करा + वाचनीय पृष्ठे कॅप्चर करा + दस्तऐवज स्कॅनिंग सपाट पृष्ठे, चांगली प्रकाशयोजना आणि दुरुस्त केलेल्या दृष्टीकोनासह उत्कृष्ट कार्य करते. ओसीआर किंवा पीडीएफ निर्यात करण्यापूर्वी स्वच्छ स्कॅन वेळेची बचत करते कारण मजकूर ओळखणे आणि कॉम्प्रेशन दोन्ही पृष्ठ गुणवत्तेवर अवलंबून असतात. + पुरेसा प्रकाश आणि कमीतकमी सावल्या असलेल्या विरोधाभासी पृष्ठभागावर दस्तऐवज ठेवा. + सापडलेले कोपरे समायोजित करा किंवा मॅन्युअली क्रॉप करा जेणेकरून पृष्ठ फ्रेम स्वच्छपणे भरेल. + प्रतिमा किंवा PDF म्हणून निर्यात करा, नंतर तुम्हाला निवडण्यायोग्य मजकूर हवा असल्यास OCR चालवा. + पीडीएफ फाइल्स संरक्षित करा किंवा अनलॉक करा + संकेतशब्द काळजीपूर्वक वापरा आणि शक्य असेल तेव्हा संपादनयोग्य स्त्रोत प्रत ठेवा + पीडीएफ प्रवेश सुरक्षितपणे हाताळा + संरक्षण साधने नियंत्रित शेअरिंगसाठी उपयुक्त आहेत, परंतु पासवर्ड गमावणे सोपे आणि पुनर्प्राप्त करणे कठीण आहे. वितरणासाठी प्रती सुरक्षित करा, असुरक्षित स्त्रोत फाइल्स स्वतंत्रपणे ठेवा आणि काहीही हटवण्यापूर्वी निकाल सत्यापित करा. + PDF ची प्रत सुरक्षित करा, तुमचा एकमेव स्त्रोत दस्तऐवज नाही. + संरक्षित फाइल शेअर करण्यापूर्वी पासवर्ड कुठेतरी सुरक्षित ठेवा. + तुम्हाला संपादित करण्याची परवानगी असलेल्या फायलींसाठीच अनलॉक वापरा, त्यानंतर अनलॉक केलेली प्रत योग्यरित्या उघडली आहे याची पडताळणी करा. + पीडीएफ पृष्ठे आयोजित करा + विलीन करा, विभाजित करा, पुनर्रचना करा, फिरवा, क्रॉप करा, पृष्ठे काढा आणि पृष्ठ क्रमांक मुद्दाम जोडा + सजावट करण्यापूर्वी दस्तऐवजाची रचना निश्चित करा + पीडीएफ पेज टूल्स वापरणे सर्वात सोपी आहे ज्या क्रमाने तुम्ही कागदी दस्तऐवज तयार कराल: पृष्ठे गोळा करा, चुकीची काढा, त्यांना क्रमाने ठेवा, योग्य दिशा द्या, नंतर पृष्ठ क्रमांक किंवा वॉटरमार्क सारखे अंतिम तपशील जोडा. + दस्तऐवज अजूनही अनेक फायलींमध्ये विभाजित असताना प्रथम मर्ज किंवा प्रतिमा-टू-पीडीएफ वापरा. + पृष्ठ क्रमांक, स्वाक्षरी किंवा वॉटरमार्क जोडण्यापूर्वी पृष्ठांची पुनर्रचना करा, फिरवा, क्रॉप करा किंवा काढा. + संरचनात्मक संपादनानंतर अंतिम PDF चे पूर्वावलोकन करा कारण एक गहाळ किंवा फिरवलेले पृष्ठ नंतर लक्षात घेणे कठीण होऊ शकते. + पीडीएफ भाष्ये + जेव्हा दर्शक टिप्पण्या आणि गुण वेगळ्या पद्धतीने दाखवतात तेव्हा भाष्ये काढा किंवा त्यांना सपाट करा + काय दृश्यमान राहते ते नियंत्रित करा + भाष्ये टिप्पण्या, हायलाइट्स, रेखाचित्रे, फॉर्म मार्क्स किंवा इतर अतिरिक्त PDF ऑब्जेक्ट असू शकतात. काही दर्शक त्यांना वेगळ्या पद्धतीने दाखवतात, त्यामुळे त्यांना काढून टाकणे किंवा सपाट करणे शेअर केलेले दस्तऐवज अधिक अंदाज लावू शकतात. + शेअर केलेल्या कॉपीमध्ये टिप्पण्या, हायलाइट्स किंवा रिव्ह्यू मार्क समाविष्ट केले जाऊ नयेत तेव्हा भाष्ये काढून टाका. + पृष्ठावर दृश्यमान खुणा राहिल्या पाहिजेत परंतु यापुढे संपादन करण्यायोग्य भाष्यांसारखे वागू नये तेव्हा सपाट करा. + मूळ प्रत ठेवा कारण भाष्ये काढणे किंवा सपाट करणे उलट करणे कठीण होऊ शकते. + मजकूर ओळखा + निवडण्यायोग्य OCR इंजिन आणि भाषांसह प्रतिमांमधून मजकूर काढा + चांगले OCR परिणाम मिळवा + OCR अचूकता प्रतिमेची तीक्ष्णता, भाषा डेटा, कॉन्ट्रास्ट आणि पृष्ठ अभिमुखता यावर अवलंबून असते. सामान्यत: प्रथम प्रतिमा तयार केल्याने सर्वोत्कृष्ट परिणाम मिळतात: आवाज दूर करा, सरळ फिरवा, वाचनीयता वाढवा, नंतर मजकूर ओळखा. + मजकूर क्षेत्रामध्ये प्रतिमा क्रॉप करा आणि ओळखण्यापूर्वी ती सरळ फिरवा. + योग्य भाषा निवडा आणि ॲपने विनंती केल्यास भाषा डेटा डाउनलोड करा. + मान्यताप्राप्त मजकूर कॉपी करा किंवा शेअर करा, नंतर नावे, संख्या आणि विरामचिन्हे व्यक्तिचलितपणे तपासा. + OCR भाषा डेटा निवडा + स्क्रिप्ट, भाषा आणि डाउनलोड केलेले OCR मॉडेल जुळवून ओळख सुधारा + मजकुराशी OCR जुळवा + चुकीची OCR भाषा चांगले फोटो खराब ओळखल्यासारखे बनवू शकते. स्क्रिप्ट, विरामचिन्हे, संख्या आणि मिश्रित दस्तऐवजांसाठी भाषा डेटा महत्त्वाचा आहे, त्यामुळे फोन UI च्या भाषेऐवजी इमेजमध्ये दिसणारी भाषा निवडा. + केवळ फोनची भाषाच नव्हे तर इमेजमध्ये दिसणारी भाषा किंवा स्क्रिप्ट निवडा. + ऑफलाइन काम करण्यापूर्वी किंवा बॅचवर प्रक्रिया करण्यापूर्वी गहाळ डेटा डाउनलोड करा. + मिश्र-भाषेच्या दस्तऐवजांसाठी, सर्वात महत्त्वाची भाषा प्रथम चालवा आणि व्यक्तिचलितपणे नावे आणि संख्या तपासा. + शोधण्यायोग्य PDF + ओसीआर मजकूर फायलींमध्ये परत लिहा जेणेकरून स्कॅन केलेली पृष्ठे शोधली आणि कॉपी केली जाऊ शकतात + स्कॅनला शोधण्यायोग्य दस्तऐवजांमध्ये बदला + OCR क्लिपबोर्डवर मजकूर कॉपी करण्यापेक्षा बरेच काही करू शकते. जेव्हा तुम्ही एखाद्या फाईलमध्ये किंवा शोधण्यायोग्य PDF मध्ये ओळखला जाणारा मजकूर लिहिता तेव्हा, पृष्ठाची प्रतिमा दृश्यमान राहते आणि मजकूर शोधणे, निवडणे, अनुक्रमणिका किंवा संग्रहण करणे सोपे होते. + स्कॅन केलेल्या दस्तऐवजांसाठी शोधण्यायोग्य PDF आउटपुट वापरा जे अद्याप मूळ पृष्ठांसारखे दिसले पाहिजेत. + जेव्हा तुम्हाला फक्त एक्सट्रॅक्ट केलेला मजकूर हवा असतो आणि पेज इमेजची काळजी करत नाही तेव्हा राइट-टू-फाइल वापरा. + महत्त्वाचे क्रमांक, नावे आणि तक्ते व्यक्तिचलितपणे तपासा कारण OCR मजकूर शोधण्यायोग्य असू शकतो परंतु तरीही अपूर्ण असू शकतो. + QR कोड स्कॅन करा + कॅमेरा इनपुटमधून QR सामग्री वाचा किंवा उपलब्ध असताना जतन केलेल्या प्रतिमा + कोड सुरक्षितपणे वाचा + QR कोडमध्ये लिंक, वाय-फाय डेटा, संपर्क, मजकूर किंवा इतर पेलोड असू शकतात. + स्कॅन QR कोड उघडा आणि कोड धारदार, सपाट आणि पूर्णपणे फ्रेममध्ये ठेवा. + लिंक उघडण्यापूर्वी किंवा संवेदनशील डेटा कॉपी करण्यापूर्वी डीकोड केलेल्या सामग्रीचे पुनरावलोकन करा. + स्कॅनिंग अयशस्वी झाल्यास, प्रकाश सुधारा, कोड क्रॉप करा किंवा उच्च-रिझोल्यूशन स्रोत प्रतिमा वापरून पहा. + बेस64 आणि चेकसम वापरा + मजकूर म्हणून प्रतिमा एन्कोड करा, मजकूर परत प्रतिमांवर डीकोड करा आणि फाइल अखंडता सत्यापित करा + फायली आणि मजकूर दरम्यान हलवा + बेस64 लहान प्रतिमा पेलोडसाठी उपयुक्त आहे, तर चेकसम फायली बदलल्या नाहीत याची पुष्टी करण्यात मदत करतात. ही साधने तांत्रिक कार्यप्रवाह, समर्थन अहवाल, फाइल हस्तांतरण आणि बायनरी फाइल्स तात्पुरते मजकूर बनण्यासाठी आवश्यक असलेल्या ठिकाणी सर्वात उपयुक्त आहेत. + जेव्हा वर्कफ्लोला बायनरी फाईलऐवजी मजकूराची आवश्यकता असते तेव्हा लहान इमेज एन्कोड करण्यासाठी Base64 टूल्स वापरा. + केवळ विश्वासार्ह स्त्रोतांकडून बेस64 डीकोड करा आणि सेव्ह करण्यापूर्वी पूर्वावलोकन सत्यापित करा. + जेव्हा तुम्हाला एक्सपोर्ट केलेल्या फाइल्सची तुलना करायची असेल किंवा अपलोड/डाउनलोडची पुष्टी करायची असेल तेव्हा चेकसम टूल्स वापरा. + पॅकेज किंवा डेटा संरक्षित करा + फाइल्स बंडल करण्यासाठी किंवा मजकूर डेटा बदलण्यासाठी ZIP आणि सायफर टूल्स वापरा + संबंधित फाइल्स एकत्र ठेवा + जेव्हा अनेक आउटपुट एकाच फाईलप्रमाणे प्रवास करतात तेव्हा पॅकेजिंग साधने उपयुक्त ठरतात. जेव्हा तुम्हाला संपूर्ण परिणाम संच पाठवायचा असेल तेव्हा व्युत्पन्न केलेल्या प्रतिमा, PDF, लॉग आणि मेटाडेटा एकत्र ठेवण्यासाठी ते देखील चांगले आहेत. + तुम्हाला अनेक निर्यात केलेल्या प्रतिमा किंवा दस्तऐवज एकत्र पाठवायचे असतील तेव्हा ZIP वापरा. + जेव्हा तुम्हाला निवडलेली पद्धत समजली असेल आणि आवश्यक की सुरक्षितपणे साठवता तेव्हाच सिफर टूल्स वापरा. + स्रोत फाइल्स हटवण्यापूर्वी तयार केलेले संग्रहण किंवा रूपांतरित मजकूर तपासा. + नावे चेकसम + वाचनीयतेपेक्षा वेगळेपणा आणि अखंडता महत्त्वाची असताना चेकसम-आधारित फाइलनावे वापरा + सामग्रीनुसार फायलींना नाव द्या + जेव्हा एक्सपोर्टमध्ये डुप्लिकेट दृश्यमान नावे असू शकतात किंवा जेव्हा तुम्हाला दोन फाइल्स एकसारख्या असल्याचे सिद्ध करण्याची आवश्यकता असते तेव्हा चेकसम फाइलनावे उपयुक्त ठरतात. ते मॅन्युअल ब्राउझिंगसाठी कमी अनुकूल आहेत, म्हणून त्यांचा वापर तांत्रिक संग्रह आणि सत्यापन वर्कफ्लोसाठी करा. + जेव्हा मानवी-वाचनीय शीर्षकापेक्षा सामग्री ओळख अधिक महत्त्वाची असते तेव्हा फाइलनाव म्हणून चेकसम सक्षम करा. + संग्रहण, डुप्लिकेशन, पुनरावृत्ती करण्यायोग्य निर्यात किंवा अपलोड आणि पुन्हा डाउनलोड केल्या जाऊ शकतील अशा फायलींसाठी याचा वापर करा. + जेव्हा लोकांना फाइल काय दर्शवते हे समजून घेणे आवश्यक असते तेव्हा फोल्डर किंवा मेटाडेटासह चेकसम नावांची जोडणी करा. + प्रतिमांमधून रंग निवडा + नमुना पिक्सेल, रंग कोड कॉपी करा आणि पॅलेट किंवा डिझाइनमध्ये रंग पुन्हा वापरा + अचूक रंग नमुना + रंग निवडणे एखाद्या डिझाइनशी जुळण्यासाठी, ब्रँडचे रंग काढण्यासाठी किंवा प्रतिमा मूल्ये तपासण्यासाठी उपयुक्त आहे. झूम करणे महत्त्वाचे आहे कारण अँटिलायझिंग, सावल्या आणि कॉम्प्रेशन शेजारील पिक्सेल तुम्हाला अपेक्षित रंगापेक्षा थोडे वेगळे करू शकतात. + प्रतिमेतून रंग निवडा उघडा आणि आपल्याला आवश्यक असलेल्या रंगासह स्त्रोत प्रतिमा निवडा. + लहान तपशीलांचा नमुना घेण्यापूर्वी झूम इन करा जेणेकरून जवळपासचे पिक्सेल परिणामांवर परिणाम करत नाहीत. + तुमच्या गंतव्यस्थानासाठी आवश्यक असलेल्या फॉरमॅटमध्ये रंग कॉपी करा, जसे की HEX, RGB किंवा HSV. + पॅलेट तयार करा आणि कलर लायब्ररी वापरा + पॅलेट काढा, जतन केलेले रंग ब्राउझ करा आणि सातत्यपूर्ण व्हिज्युअल सिस्टम ठेवा + पुन्हा वापरण्यायोग्य पॅलेट तयार करा + पॅलेट निर्यात केलेले ग्राफिक्स, वॉटरमार्क आणि रेखाचित्रे दृष्यदृष्ट्या सुसंगत ठेवण्यास मदत करतात. जेव्हा तुम्ही वारंवार स्क्रीनशॉट भाष्य करता, ब्रँड मालमत्ता तयार करता किंवा अनेक टूल्सवर समान रंगांची आवश्यकता असते तेव्हा ते विशेषतः उपयुक्त असतात. + जेव्हा तुम्हाला मूल्ये आधीच माहित असतील तेव्हा प्रतिमेमधून पॅलेट तयार करा किंवा व्यक्तिचलितपणे रंग जोडा. + महत्त्वाच्या रंगांना नाव द्या किंवा व्यवस्थापित करा जेणेकरून तुम्ही ते नंतर पुन्हा शोधू शकाल. + ड्रॉ, वॉटरमार्क, ग्रेडियंट, एसव्हीजी किंवा बाह्य डिझाइन टूल्समध्ये कॉपी केलेली मूल्ये वापरा. + ग्रेडियंट तयार करा + पार्श्वभूमी, प्लेसहोल्डर्स आणि व्हिज्युअल प्रयोगांसाठी ग्रेडियंट प्रतिमा तयार करा + रंग संक्रमण नियंत्रित करा + जेव्हा आपल्याला विद्यमान प्रतिमा संपादित करण्याऐवजी व्युत्पन्न केलेली प्रतिमा आवश्यक असते तेव्हा ग्रेडियंट साधने उपयुक्त असतात. ते स्वच्छ पार्श्वभूमी, प्लेसहोल्डर, वॉलपेपर, मुखवटे किंवा बाह्य डिझाइन साधनांसाठी साधी मालमत्ता बनू शकतात. + ग्रेडियंट प्रकार निवडा आणि प्रथम महत्त्वाचे रंग सेट करा. + लक्ष्य वापर केससाठी दिशा, थांबे, गुळगुळीतपणा आणि आउटपुट आकार समायोजित करा. + गंतव्यस्थानाशी जुळणाऱ्या फॉरमॅटमध्ये निर्यात करा, सामान्यतः स्वच्छ व्युत्पन्न केलेल्या ग्राफिक्ससाठी PNG. + रंग प्रवेशयोग्यता + जेव्हा UI वाचणे कठीण असते तेव्हा डायनॅमिक रंग, कॉन्ट्रास्ट आणि रंग-अंध पर्याय वापरा + रंग वापरण्यास सोपे करा + रंग सेटिंग्ज आराम, वाचनीयता आणि तुम्ही नियंत्रणे किती लवकर स्कॅन करू शकता यावर परिणाम करतात. टूल चिप्स, स्लाइडर किंवा निवडलेल्या राज्यांमध्ये फरक करणे कठीण वाटत असल्यास, थीम आणि प्रवेशयोग्यता पर्याय फक्त गडद मोड बदलण्यापेक्षा अधिक उपयुक्त असू शकतात. + जेव्हा तुम्हाला ॲपने वर्तमान वॉलपेपर पॅलेटचे अनुसरण करायचे असेल तेव्हा डायनॅमिक रंग वापरून पहा. + जेव्हा बटणे आणि पृष्ठभाग पुरेसे दिसत नाहीत तेव्हा कॉन्ट्रास्ट वाढवा किंवा योजना बदला. + काही अवस्था किंवा उच्चार वेगळे करणे कठीण असल्यास रंग-अंध योजना पर्याय वापरा. + अल्फा पार्श्वभूमी + पारदर्शक PNG, WebP आणि तत्सम आउटपुटच्या आसपास वापरलेले पूर्वावलोकन किंवा रंग भरणे नियंत्रित करा + पारदर्शक पूर्वावलोकने समजून घ्या + अल्फा फॉरमॅटसाठी पार्श्वभूमीचा रंग पारदर्शक प्रतिमा तपासणे सोपे करू शकतो, परंतु तुम्ही पूर्वावलोकन/फिल वर्तन बदलत आहात की अंतिम परिणाम सपाट करत आहात हे जाणून घेणे महत्त्वाचे आहे. हे स्टिकर्स, लोगो आणि पार्श्वभूमी-काढलेल्या प्रतिमांसाठी महत्त्वाचे आहे. + जेव्हा पारदर्शक पिक्सेल निर्यात टिकले पाहिजेत तेव्हा प्रथम अल्फा-सपोर्टिंग फॉरमॅट वापरा, जसे की PNG किंवा WebP. + ॲपच्या पृष्ठभागावर पारदर्शक कडा दिसणे कठीण असताना पार्श्वभूमी रंग सेटिंग्ज समायोजित करा. + एक छोटी चाचणी एक्सपोर्ट करा आणि पारदर्शकता किंवा निवडलेले भरण सेव्ह केले आहे की नाही याची पुष्टी करण्यासाठी ती दुसऱ्या ॲपमध्ये पुन्हा उघडा. + एआय मॉडेल निवड + प्रथम सर्वात मोठे निवडण्याऐवजी प्रतिमा प्रकार आणि दोषानुसार मॉडेल निवडा + मॉडेलला नुकसानाशी जुळवा + AI टूल्स भिन्न ONNX/ORT मॉडेल डाउनलोड किंवा आयात करू शकतात आणि प्रत्येक मॉडेलला विशिष्ट प्रकारच्या समस्येसाठी प्रशिक्षित केले जाते. चुकीच्या इमेज प्रकारावर वापरल्यास JPEG ब्लॉक्स, ॲनिम लाइन क्लीनअप, डिनोइझिंग, बॅकग्राउंड रिमूव्हल, कलरलायझेशन किंवा अपस्केलिंगचे मॉडेल वाईट परिणाम देऊ शकतात. + डाउनलोड करण्यापूर्वी मॉडेलचे वर्णन वाचा; कॉम्प्रेशन, नॉइज, हाफटोन, ब्लर किंवा पार्श्वभूमी काढणे यासारख्या तुमच्या वास्तविक समस्येचे नाव देणारे मॉडेल निवडा. + नवीन प्रतिमा प्रकाराची चाचणी करताना लहान किंवा अधिक विशिष्ट मॉडेलसह प्रारंभ करा, नंतर परिणाम पुरेसा चांगला नसल्यासच जड मॉडेलशी तुलना करा. + तुम्ही खरोखर वापरता तेच मॉडेल ठेवा, कारण डाउनलोड केलेले आणि आयात केलेले मॉडेल लक्षात येण्याजोगे स्टोरेज जागा घेऊ शकतात. + मॉडेल कुटुंबे समजून घ्या + मॉडेलची नावे अनेकदा त्यांच्या लक्ष्यावर इशारा देतात: RealESRGAN-शैलीतील मॉडेल्स अपस्केल, SCUNet आणि FBCNN मॉडेल्स आवाज किंवा कॉम्प्रेशन स्वच्छ करतात, पार्श्वभूमी मॉडेल मास्क तयार करतात आणि कलरलायझर ग्रेस्केल इनपुटमध्ये रंग जोडतात. आयात केलेले सानुकूल मॉडेल शक्तिशाली असू शकतात, परंतु ते अपेक्षित इनपुट/आउटपुट आकाराशी जुळले पाहिजेत आणि समर्थित ONNX किंवा ORT स्वरूप वापरावे. + जेव्हा तुम्हाला अधिक पिक्सेल्सची आवश्यकता असेल तेव्हा अपस्केलर्स वापरा, प्रतिमा दाणेदार असेल तेव्हा डिनोइझर आणि आर्टिफॅक्ट रिमूव्हर्स वापरा जेव्हा कॉम्प्रेशन ब्लॉक्स किंवा बँडिंग ही मुख्य समस्या असेल. + केवळ विषय वेगळे करण्यासाठी पार्श्वभूमी मॉडेल वापरा; ते सामान्य ऑब्जेक्ट काढणे किंवा प्रतिमा सुधारणेसारखे नसतात. + तुम्हाला विश्वास असल्या स्रोतांमधूनच सानुकूल मॉडेल इंपोर्ट करा आणि महत्त्वाच्या फाइल वापरण्यापूर्वी छोट्या प्रतिमेवर त्यांची चाचणी करा. + एआय पॅरामीटर्स + गुणवत्ता, वेग आणि मेमरी संतुलित करण्यासाठी भाग आकार, ओव्हरलॅप आणि समांतर कामगार ट्यून करा + स्थिरतेसह वेग संतुलित करा + मॉडेलद्वारे प्रक्रिया करण्यापूर्वी प्रतिमेचे तुकडे किती मोठे आहेत हे भाग आकार नियंत्रित करतो. सीमा लपविण्यासाठी ओव्हरलॅप शेजारच्या भागांचे मिश्रण करते. समांतर कामगार प्रक्रियेची गती वाढवू शकतात, परंतु प्रत्येक कामगाराला मेमरीची आवश्यकता असते, त्यामुळे उच्च मूल्ये मर्यादित RAM असलेल्या फोनवर मोठ्या प्रतिमा अस्थिर करू शकतात. + जेव्हा तुमचे डिव्हाइस मॉडेलला विश्वासार्हतेने हाताळते आणि प्रतिमा मोठी नसते तेव्हा नितळ संदर्भासाठी मोठे भाग वापरा. + जेव्हा भाग सीमा दृश्यमान होतात तेव्हा ओव्हरलॅप वाढवा, परंतु लक्षात ठेवा की अधिक ओव्हरलॅप म्हणजे अधिक पुनरावृत्ती केलेले काम. + स्थिर चाचणीनंतरच समांतर कामगार वाढवा; प्रक्रिया मंद होत असल्यास, उपकरण गरम होत असल्यास किंवा क्रॅश झाल्यास, प्रथम कामगार कमी करा. + चंक आर्टिफॅक्ट्स टाळा + Chunking ॲपला मॉडेलपेक्षा मोठ्या प्रतिमांवर प्रक्रिया करू देते जे एकाच वेळी आरामात हाताळू शकतात. ट्रेडऑफ असा आहे की प्रत्येक टाइलला कमी सभोवतालचा संदर्भ असतो, त्यामुळे कमी ओव्हरलॅप किंवा खूप-लहान भाग दृश्यमान सीमा, पोत बदल किंवा शेजारच्या भागांमध्ये विसंगत तपशील तयार करू शकतात. + तुम्हाला आयताकृती किनारी, पुनरावृत्ती पोत बदल किंवा प्रक्रिया केलेल्या प्रदेशांमधील तपशील उडी दिसल्यास ओव्हरलॅप वाढवा. + आउटपुट तयार करण्यापूर्वी प्रक्रिया अयशस्वी झाल्यास भागाचा आकार कमी करा, विशेषतः मोठ्या प्रतिमा किंवा भारी मॉडेल्सवर. + पूर्ण प्रतिमेवर प्रक्रिया करण्यापूर्वी एक लहान क्रॉप चाचणी जतन करा जेणेकरून तुम्ही पॅरामीटर्सची द्रुतपणे तुलना करू शकता. + AI स्थिरता + जेव्हा AI प्रक्रिया क्रॅश होते किंवा गोठते तेव्हा कमी भाग आकार, कामगार आणि स्त्रोत रिझोल्यूशन + भारी AI नोकऱ्यांमधून पुनर्प्राप्त करा + एआय प्रोसेसिंग हे ॲपमधील सर्वात भारी वर्कफ्लो आहे. क्रॅश, अयशस्वी सत्रे, फ्रीझ किंवा अचानक ॲप रीस्टार्ट होणे याचा अर्थ सामान्यतः मॉडेल, स्त्रोत रिझोल्यूशन, भाग आकार किंवा समांतर कामगार संख्या सध्याच्या डिव्हाइस स्थितीसाठी खूप मागणी आहे. + ॲप क्रॅश झाल्यास किंवा मॉडेल सत्र उघडण्यात अयशस्वी झाल्यास, समांतर कामगार आणि भागाचा आकार कमी केल्यास, तीच प्रतिमा पुन्हा वापरून पहा. + जेव्हा उद्दिष्टाला मूळ रिझोल्यूशनची आवश्यकता नसते तेव्हा AI प्रक्रिया करण्यापूर्वी खूप मोठ्या प्रतिमांचा आकार बदला. + इतर जड ॲप्स बंद करा, लहान मॉडेल वापरा किंवा मेमरी प्रेशर परत येत असताना एकाच वेळी कमी फाइल्सवर प्रक्रिया करा. + मॉडेल अपयशांचे निदान करा + अयशस्वी एआय रनचा अर्थ नेहमीच प्रतिमा खराब होत नाही. मॉडेल फाइल अपूर्ण, असमर्थित, मेमरीसाठी खूप मोठी असू शकते किंवा ऑपरेशनशी जुळत नाही. जेव्हा ॲप अयशस्वी सत्राचा अहवाल देतो, तेव्हा प्रथम मॉडेल आणि पॅरामीटर्स सुलभ करण्यासाठी त्यास सिग्नल म्हणून हाताळा. + एखादे मॉडेल डाउनलोड केल्यानंतर लगेच अयशस्वी झाल्यास किंवा फाइल दूषित झाल्यासारखे वागल्यास हटवा आणि पुन्हा डाउनलोड करा. + आयात केलेल्या सानुकूल मॉडेलला दोष देण्यापूर्वी अंगभूत डाउनलोड करण्यायोग्य मॉडेल वापरून पहा, कारण आयात केलेल्या मॉडेलमध्ये विसंगत इनपुट असू शकतात. + तुम्हाला क्रॅश किंवा सत्र त्रुटीची तक्रार करायची असल्यास अपयशाचे पुनरुत्पादन केल्यानंतर ॲप लॉग वापरा. + शेडर स्टुडिओमध्ये प्रयोग + प्रगत प्रतिमा प्रक्रिया आणि सानुकूल स्वरूपासाठी GPU शेडर प्रभाव वापरा + शेडर्ससह काळजीपूर्वक कार्य करा + शेडर स्टुडिओ शक्तिशाली आहे, परंतु शेडर कोड आणि पॅरामीटर्समध्ये लहान, चाचणी करण्यायोग्य बदल आवश्यक आहेत. यास प्रायोगिक साधनाप्रमाणे वागवा: कार्यरत बेसलाइन ठेवा, एका वेळी एक गोष्ट बदला आणि प्रीसेटवर विश्वास ठेवण्यापूर्वी वेगवेगळ्या प्रतिमा आकारांसह चाचणी करा. + मापदंड जोडण्यापूर्वी ज्ञात कार्यरत शेडर किंवा साध्या प्रभावापासून प्रारंभ करा. + एका वेळी एक पॅरामीटर किंवा कोड ब्लॉक बदला आणि त्रुटींसाठी पूर्वावलोकन तपासा. + काही भिन्न प्रतिमा आकारांवर चाचणी केल्यानंतरच प्रीसेट निर्यात करा. + SVG किंवा ASCII कला बनवा + सर्जनशील आउटपुटसाठी वेक्टर-सारखी मालमत्ता किंवा मजकूर-आधारित प्रतिमा व्युत्पन्न करा + क्रिएटिव्ह आउटपुट प्रकार निवडा + SVG आणि ASCII साधने भिन्न लक्ष्ये देतात: स्केलेबल ग्राफिक्स किंवा मजकूर-शैली प्रस्तुतीकरण. दोन्ही सर्जनशील रूपांतरणे आहेत, त्यामुळे एका लहान लघुप्रतिमावरून निकाल ठरवण्यापेक्षा अंतिम आकाराचे पूर्वावलोकन करणे अधिक महत्त्वाचे आहे. + जेव्हा परिणाम स्वच्छपणे मोजला जावा किंवा डिझाइन वर्कफ्लोमध्ये पुन्हा वापरला जावा तेव्हा SVG मेकर वापरा. + जेव्हा तुम्हाला प्रतिमेचे शैलीबद्ध मजकूर प्रतिनिधित्व हवे असेल तेव्हा ASCII कला वापरा. + अंतिम आकाराचे पूर्वावलोकन करा कारण व्युत्पन्न केलेल्या आउटपुटमध्ये लहान तपशील अदृश्य होऊ शकतात. + आवाज पोत व्युत्पन्न करा + पार्श्वभूमी, मुखवटे, आच्छादन किंवा प्रयोगांसाठी प्रक्रियात्मक पोत तयार करा + प्रक्रियात्मक आउटपुट ट्यून करा + आवाज निर्मिती पॅरामीटर्समधून नवीन प्रतिमा तयार करते, त्यामुळे लहान बदल खूप भिन्न परिणाम देऊ शकतात. हे टेक्सचर, मास्क, ओव्हरले, प्लेसहोल्डर्स किंवा तपशीलवार नमुन्यांसह कॉम्प्रेशन चाचणीसाठी उपयुक्त आहे. + सूक्ष्म तपशील ट्यून करण्यापूर्वी आवाज प्रकार आणि आउटपुट आकार निवडा. + पॅटर्न लक्ष्यित वापराशी जुळत नाही तोपर्यंत स्केल, तीव्रता, रंग किंवा बिया समायोजित करा. + उपयुक्त परिणाम जतन करा आणि आपल्याला नंतर पोत पुन्हा तयार करण्याची आवश्यकता असल्यास पॅरामीटर्स लिहा. + मार्कअप प्रकल्प + ॲप बंद केल्यानंतर भाष्ये संपादन करण्यायोग्य राहणे आवश्यक असताना प्रकल्प फाइल्स वापरा + स्तरित संपादने पुन्हा वापरण्यायोग्य ठेवा + जेव्हा स्क्रीनशॉट, आकृती किंवा सूचना प्रतिमेमध्ये नंतर बदलांची आवश्यकता असू शकते तेव्हा मार्कअप प्रकल्प फायली उपयुक्त ठरतात. निर्यात केलेल्या PNG/JPEG फाइल्स अंतिम प्रतिमा आहेत, तर प्रकल्प फाइल्स भविष्यातील समायोजनासाठी संपादन करण्यायोग्य स्तर आणि संपादन स्थिती जतन करतात. + जेव्हा भाष्ये, कॉलआउट्स किंवा लेआउट घटक संपादन करण्यायोग्य राहतील तेव्हा मार्कअप स्तर वापरा. + तुम्हाला अधिक पुनरावृत्ती अपेक्षित असल्यास अंतिम प्रतिमा निर्यात करण्यापूर्वी प्रकल्प फाइल जतन करा किंवा पुन्हा उघडा. + जेव्हा तुम्ही सपाट अंतिम आवृत्ती शेअर करण्यास तयार असाल तेव्हाच सामान्य प्रतिमा निर्यात करा. + फायली एनक्रिप्ट करा + कोणतीही स्त्रोत कॉपी हटवण्यापूर्वी फायलींना पासवर्डसह संरक्षित करा आणि डिक्रिप्शन चाचणी करा + एनक्रिप्टेड आउटपुट काळजीपूर्वक हाताळा + सायफर टूल्स पासवर्ड-आधारित एनक्रिप्शनसह फाइल्सचे संरक्षण करू शकतात, परंतु ते की लक्षात ठेवण्यासाठी किंवा बॅकअप ठेवण्यासाठी बदली नाहीत. एन्क्रिप्शन वाहतूक आणि स्टोरेजसाठी उपयुक्त आहे, तर ZIP अधिक चांगले आहे जेव्हा मुख्य ध्येय अनेक आउटपुट बंडलिंग आहे. + प्रथम एक प्रत कूटबद्ध करा आणि तुम्ही संचयित केलेल्या पासवर्डसह डिक्रिप्शन कार्य करते याची चाचणी होईपर्यंत मूळ ठेवा. + की साठी पासवर्ड व्यवस्थापक किंवा दुसरी सुरक्षित जागा वापरा; ॲप तुमच्यासाठी हरवलेल्या पासवर्डचा अंदाज लावू शकत नाही. + कूटबद्ध केलेल्या फायली फक्त अशा लोकांसह सामायिक करा ज्यांच्याकडे पासवर्ड देखील आहे आणि त्यांना कोणते साधन किंवा पद्धत डीक्रिप्ट करावी हे माहित आहे. + साधनांची व्यवस्था करा + गट, ऑर्डर, शोध आणि पिन टूल्स करा जेणेकरून मुख्य स्क्रीन तुमच्या वास्तविक कार्यप्रवाहाशी जुळेल + मुख्य स्क्रीन जलद करा + तुम्ही सर्वात जास्त कोणती साधने वापरता हे समजल्यानंतर टूल व्यवस्था सेटिंग्ज ट्यून करणे योग्य आहे. ग्रुपिंगमुळे एक्सप्लोरेशन करण्यात मदत होते, सानुकूल ऑर्डर स्नायूंच्या स्मरणशक्तीला मदत करते आणि आवडी पूर्ण यादी मोठी असतानाही दैनंदिन साधने पोहोचण्यायोग्य ठेवतात. + ॲप शिकत असताना किंवा तुम्ही टास्क प्रकारानुसार आयोजित केलेल्या टूल्सला प्राधान्य देताना गटबद्ध मोड वापरा. + जेव्हा तुम्हाला तुमची वारंवार साधने माहित असतील आणि कमी स्क्रोल हवे असतील तेव्हा कस्टम ऑर्डरवर स्विच करा. + तुम्हाला नावाने आठवत असलेल्या दुर्मिळ साधनांसाठी शोध सेटिंग्ज आणि आवडी एकत्र वापरा परंतु नेहमी दृश्यमान नको. + मोठ्या फाइल्स हाताळा + मंद निर्यात, मेमरी प्रेशर आणि अनपेक्षितपणे मोठे आउटपुट टाळा + निर्यात करण्यापूर्वी काम कमी करा + खूप मोठ्या प्रतिमा, लांब बॅच आणि उच्च-गुणवत्तेचे स्वरूप अधिक मेमरी आणि वेळ घेऊ शकतात. सर्वात वेगवान निराकरण म्हणजे सामान्यत: जड ऑपरेशनपूर्वी कामाचे प्रमाण कमी करणे, समान आकाराचे इनपुट पूर्ण होण्याची प्रतीक्षा न करणे. + हेवी फिल्टर, ओसीआर किंवा पीडीएफ रूपांतरण लागू करण्यापूर्वी मोठ्या प्रतिमांचा आकार बदला. + सेटिंग्जची पुष्टी करण्यासाठी प्रथम एक लहान बॅच वापरा, नंतर त्याच प्रीसेटसह उर्वरित प्रक्रिया करा. + जेव्हा आउटपुट फाइल अपेक्षेपेक्षा मोठी असेल तेव्हा गुणवत्ता कमी करा किंवा स्वरूप बदला. + कॅशे आणि पूर्वावलोकने + जनरेट केलेले पूर्वावलोकन, कॅशे क्लीनअप आणि भारी सत्रांनंतर स्टोरेज वापर समजून घ्या + स्टोरेज आणि वेग संतुलित ठेवा + पूर्वावलोकन निर्मिती आणि कॅशे वारंवार ब्राउझिंग जलद बनवू शकतात, परंतु जड संपादन सत्रे तात्पुरता डेटा मागे ठेवू शकतात. जेव्हा ॲप धीमे वाटत असेल, स्टोरेज कमी असेल किंवा अनेक प्रयोगांनंतर पूर्वावलोकन जुने दिसत असेल तेव्हा कॅशे सेटिंग्ज मदत करतात. + तात्पुरते स्टोरेज कमी करण्यापेक्षा अनेक प्रतिमा ब्राउझ करताना पूर्वावलोकने सक्षम ठेवा. + मोठ्या बॅचेस, अयशस्वी प्रयोग किंवा अनेक उच्च-रिझोल्यूशन फाइल्ससह दीर्घ सत्रांनंतर कॅशे साफ करा. + लॉन्च दरम्यान पूर्वावलोकने उबदार ठेवण्यापेक्षा तुम्ही स्टोरेज क्लीनअपला प्राधान्य देत असल्यास स्वयंचलित कॅशे क्लिअरिंग वापरा. + स्क्रीन वर्तन समायोजित करा + फोकस केलेल्या कामासाठी फुलस्क्रीन, सुरक्षित मोड, ब्राइटनेस आणि सिस्टम बार पर्याय वापरा + इंटरफेस परिस्थितीशी जुळवून घ्या + जेव्हा तुम्ही लँडस्केपमध्ये संपादित करता, खाजगी प्रतिमा सादर करता किंवा सातत्यपूर्ण ब्राइटनेसची आवश्यकता असते तेव्हा स्क्रीन सेटिंग्ज मदत करतात. ते सामान्य वापरासाठी आवश्यक नाहीत, परंतु ते QR तपासणी, खाजगी पूर्वावलोकन किंवा अरुंद लँडस्केप संपादन यासारख्या विचित्र परिस्थितींचे निराकरण करतात. + नेव्हिगेशन क्रोमपेक्षा जागा संपादित करणे अधिक महत्त्वाचे असते तेव्हा फुलस्क्रीन किंवा सिस्टम बार सेटिंग्ज वापरा. + जेव्हा खाजगी प्रतिमा स्क्रीनशॉट किंवा ॲप पूर्वावलोकनांमध्ये दिसू नयेत तेव्हा सुरक्षित मोड वापरा. + गडद प्रतिमा किंवा QR कोड तपासणे कठीण असलेल्या साधनांसाठी ब्राइटनेस अंमलबजावणी वापरा. + पारदर्शकता ठेवा + पारदर्शक पार्श्वभूमी पांढरे, काळे किंवा सपाट होण्यापासून प्रतिबंधित करा + अल्फा-अवेअर आउटपुट वापरा + जेव्हा निवडलेले साधन आणि आउटपुट स्वरूप अल्फाला समर्थन देते तेव्हाच पारदर्शकता टिकून राहते. जर निर्यात केलेली पार्श्वभूमी पांढरी किंवा काळी झाली, तर समस्या सामान्यतः आउटपुट स्वरूप, फिल/पार्श्वभूमी सेटिंग किंवा प्रतिमा सपाट करणारे गंतव्य ॲप असते. + पार्श्वभूमी-काढलेल्या प्रतिमा, स्टिकर्स, लोगो किंवा आच्छादन निर्यात करताना PNG किंवा WebP निवडा. + पारदर्शक आउटपुटसाठी JPEG टाळा कारण ते अल्फा चॅनेल संचयित करत नाही. + पूर्वावलोकनाने भरलेली पार्श्वभूमी दर्शविल्यास अल्फा स्वरूपांसाठी पार्श्वभूमी रंगाशी संबंधित सेटिंग्ज तपासा. + सामायिकरण आणि आयात समस्यांचे निराकरण करा + जेव्हा फायली दिसत नाहीत किंवा योग्यरित्या उघडत नाहीत तेव्हा पिकर सेटिंग्ज आणि समर्थित फॉरमॅट वापरा + ॲपमध्ये फाइल मिळवा + आयात समस्या बऱ्याचदा प्रदाता परवानग्या, असमर्थित स्वरूप किंवा निवडक वर्तनामुळे उद्भवतात. क्लाउड ॲप्स आणि मेसेंजरवरून शेअर केलेल्या फायली तात्पुरत्या असू शकतात, त्यामुळे दीर्घकाळ संपादनांसाठी कायम स्रोत निवडणे अधिक विश्वासार्ह असू शकते. + अलीकडील-फाइल्स शॉर्टकटऐवजी सिस्टम पिकरद्वारे फाइल उघडण्याचा प्रयत्न करा. + फॉरमॅट एका टूलद्वारे समर्थित नसल्यास, प्रथम ते रूपांतरित करा किंवा अधिक विशिष्ट साधन उघडा. + शेअर फ्लो किंवा डायरेक्ट टूल ओपनिंग अपेक्षेपेक्षा वेगळ्या पद्धतीने वागत असल्यास इमेज पिकर आणि लाँचर सेटिंग्जचे पुनरावलोकन करा. + निर्गमन पुष्टीकरण + जेश्चर, बॅक प्रेस किंवा टूल स्विच चुकून होतात तेव्हा जतन न केलेली संपादने गमावणे टाळा + अपूर्ण कामांचे संरक्षण करा + तुम्ही जेश्चर नेव्हिगेशन वापरता, मोठ्या फाइल्स संपादित करता किंवा अनेकदा ॲप्समध्ये स्विच करता तेव्हा टूल एक्झिट कन्फर्मेशन उपयुक्त ठरते. हे साधन सोडण्यापूर्वी एक लहान विराम जोडते जेणेकरून अपूर्ण सेटिंग्ज आणि पूर्वावलोकने चुकून गमावली जाणार नाहीत. + आपण निर्यात करण्यापूर्वी अपघाती बॅक जेश्चरने एखादे साधन बंद केले असल्यास पुष्टीकरण सक्षम करा. + तुम्ही मुख्यतः जलद वन-स्टेप रूपांतरण करत असल्यास आणि जलद नेव्हिगेशनला प्राधान्य देत असल्यास ते अक्षम ठेवा. + दीर्घ कार्यप्रवाहांसाठी प्रीसेटसह एकत्र वापरा जेथे सेटिंग्ज पुनर्बांधणी त्रासदायक असेल. + समस्या नोंदवताना लॉग वापरा + समस्या उघडण्यापूर्वी किंवा विकासकाशी संपर्क साधण्यापूर्वी उपयुक्त तपशील गोळा करा + संदर्भासह समस्यांची तक्रार करा + पायऱ्या, ॲप आवृत्ती, इनपुट प्रकार आणि लॉगसह स्पष्ट अहवाल निदान करणे खूप सोपे आहे. समस्या पुनरुत्पादित केल्यावर लॉग सर्वात उपयुक्त असतात कारण ते सभोवतालचे संदर्भ ताजे ठेवतात. + साधनाचे नाव, नेमकी क्रिया आणि ती एका फाईल किंवा प्रत्येक फाईलसह होते की नाही ते लक्षात ठेवा. + समस्येचे पुनरुत्पादन केल्यानंतर ॲप लॉग उघडा आणि शक्य असेल तेव्हा संबंधित लॉग आउटपुट शेअर करा. + स्क्रीनशॉट किंवा नमुना फायली केवळ तेव्हाच संलग्न करा जेव्हा त्यामध्ये खाजगी माहिती नसेल. + पार्श्वभूमी प्रक्रिया थांबवली + Android ने पार्श्वभूमी प्रक्रिया सूचना वेळेत सुरू होऊ दिली नाही. ही एक प्रणाली वेळेची मर्यादा आहे जी विश्वसनीयरित्या निश्चित केली जाऊ शकत नाही. ॲप रीस्टार्ट करा आणि पुन्हा प्रयत्न करा; ॲप उघडे ठेवणे आणि सूचना किंवा पार्श्वभूमी कार्यास अनुमती देणे मदत करू शकते. + फाइल निवडणे वगळा + इनपुट सहसा शेअर, क्लिपबोर्ड किंवा मागील स्क्रीनवरून येते तेव्हा टूल्स जलद उघडा + पुनरावृत्ती टूल लाँच जलद करा + स्किप फाइल पिकिंग समर्थित साधनांची पहिली पायरी बदलते. तुम्ही सामान्यत: आधीपासून निवडलेल्या प्रतिमेसह किंवा Android शेअर क्रियांमधून एखादे साधन एंटर करता तेव्हा ते उपयुक्त ठरते, परंतु प्रत्येक साधनाने त्वरित फाइल मागण्याची अपेक्षा केल्यास ते गोंधळात टाकणारे वाटू शकते. + जेव्हा तुमचा नेहमीचा प्रवाह साधन उघडण्यापूर्वी स्रोत प्रतिमा प्रदान करतो तेव्हाच ते सक्षम करा. + ॲप शिकत असताना ते अक्षम ठेवा, कारण स्पष्ट निवडीमुळे प्रत्येक टूलचा प्रवाह समजण्यास सुलभ होतो. + एखादे साधन कोणत्याही स्पष्ट इनपुटशिवाय उघडत असल्यास, हे सेटिंग अक्षम करा किंवा शेअर/ओपन विथ पासून प्रारंभ करा जेणेकरून Android फाईल प्रथम पास करेल. + प्रथम संपादक उघडा + जेव्हा एखादी शेअर केलेली प्रतिमा थेट संपादनात जावी तेव्हा पूर्वावलोकन वगळा + पहिला थांबा म्हणून पूर्वावलोकन निवडा किंवा संपादित करा + पूर्वावलोकनाऐवजी संपादन उघडा हे वापरकर्त्यांसाठी आहे ज्यांना आधीच माहित आहे की त्यांना प्रतिमा सुधारित करायची आहे. तुम्ही चॅट किंवा क्लाउड स्टोरेजमधून फाइल्सची तपासणी करता तेव्हा पूर्वावलोकन अधिक सुरक्षित असते; स्क्रीनशॉट, झटपट क्रॉप्स, भाष्ये आणि एक-दोन सुधारणांसाठी थेट संपादन जलद आहे. + जर बहुतेक सामायिक केलेल्या प्रतिमांना त्वरित क्रॉप, मार्कअप, फिल्टर किंवा निर्यात बदलांची आवश्यकता असेल तर थेट संपादन सक्षम करा. + निर्णय घेण्यापूर्वी तुम्ही अनेकदा मेटाडेटा, परिमाणे, पारदर्शकता किंवा प्रतिमेची गुणवत्ता तपासता तेव्हा प्रथम पूर्वावलोकन सोडा. + हे आवडीसह एकत्र वापरा जेणेकरून सामायिक केलेल्या प्रतिमा तुम्ही प्रत्यक्षात वापरत असलेल्या साधनांच्या जवळ येतात. + प्रीसेट मजकूर एंट्री + नियंत्रणे वारंवार समायोजित करण्याऐवजी अचूक प्रीसेट मूल्ये प्रविष्ट करा + जेव्हा स्लाइडर हळू असतात तेव्हा अचूक मूल्ये वापरा + प्रीसेट मजकूर एंट्री आपल्याला वारंवार कामासाठी अचूक संख्या आवश्यक असल्यास मदत करते: सामाजिक प्रतिमा आकार, दस्तऐवज मार्जिन, कॉम्प्रेशन लक्ष्य, रेषा रुंदी किंवा इतर मूल्ये जे जेश्चरसह पोहोचण्यासाठी त्रासदायक आहेत. हे लहान स्क्रीनवर देखील उपयुक्त आहे जेथे बारीक स्लाइडर हालचाल करणे कठीण आहे. + तुम्ही अनेकदा अचूक आकार, टक्केवारी, गुणवत्ता मूल्ये किंवा टूल पॅरामीटर्स पुन्हा वापरत असल्यास मजकूर एंट्री सक्षम करा. + मूल्य एकदा टाईप केल्यावर प्रीसेट म्हणून सेव्ह करा, नंतर त्याच सेटअपची पुनर्बांधणी करण्याऐवजी पुन्हा वापरा. + कठोर आउटपुट आवश्यकतांसाठी रफ व्हिज्युअल ट्यूनिंग आणि टाइप केलेल्या मूल्यांसाठी जेश्चर नियंत्रणे ठेवा. + मूळ जवळ जतन करा + फोल्डर संदर्भ महत्त्वाचे असताना व्युत्पन्न केलेल्या फायली स्त्रोत प्रतिमांच्या बाजूला ठेवा + आउटपुट त्यांच्या स्त्रोतांसह ठेवा + मूळ फोल्डरमध्ये जतन करा कॅमेरा फोल्डर, प्रोजेक्ट फोल्डर, दस्तऐवज स्कॅन आणि क्लायंट बॅचसाठी सुलभ आहे जेथे संपादित प्रत स्त्रोताशेजारी राहिली पाहिजे. हे समर्पित आउटपुट फोल्डरपेक्षा कमी नीटनेटके असू शकते, म्हणून ते स्पष्ट फाइलनाव प्रत्यय किंवा पॅटर्नसह एकत्र करा. + जेव्हा प्रत्येक स्त्रोत फोल्डर आधीपासूनच अर्थपूर्ण असेल आणि आउटपुट तेथे गटबद्ध केले जावे तेव्हा ते सक्षम करा. + फाइलनाव प्रत्यय, उपसर्ग, टाइमस्टॅम्प किंवा नमुने वापरा जेणेकरून संपादित प्रती मूळपासून विभक्त करणे सोपे होईल. + जेव्हा तुम्हाला सर्व व्युत्पन्न केलेल्या फाइल्स एका निर्यात फोल्डरमध्ये गोळा करायच्या असतील तेव्हा मिश्र बॅचसाठी ते अक्षम करा. + मोठे आउटपुट वगळा + अनपेक्षितपणे स्त्रोतापेक्षा जड होणारी रूपांतरणे जतन करणे टाळा + खराब आकाराच्या आश्चर्यांपासून बॅचचे संरक्षण करा + काही रूपांतरणे फाइल्स मोठ्या बनवतात, विशेषत: PNG स्क्रीनशॉट, आधीच संकुचित केलेले फोटो, उच्च-गुणवत्तेचे WebP किंवा मेटाडेटा जतन केलेल्या प्रतिमा. वर्कफ्लोमध्ये लहान स्रोत बदलण्यापासून मोठे आउटपुट प्रतिबंधित करत असल्यास वगळा, जेथे आकार कमी करणे हे मुख्य उद्दिष्ट आहे. + जेव्हा निर्यात संकुचित करणे, आकार बदलणे किंवा अपलोड मर्यादांसाठी फायली तयार करणे असेल तेव्हा ते सक्षम करा. + बॅच नंतर वगळलेल्या फायलींचे पुनरावलोकन करा; ते आधीच ऑप्टिमाइझ केलेले असू शकतात किंवा त्यांना वेगळ्या स्वरूपाची आवश्यकता असू शकते. + जेव्हा गुणवत्ता, पारदर्शकता किंवा फॉरमॅट सुसंगतता अंतिम फाइल आकारापेक्षा जास्त महत्त्वाची असते तेव्हा ते अक्षम करा. + क्लिपबोर्ड आणि दुवे + जलद मजकूर-आधारित साधनांसाठी स्वयंचलित क्लिपबोर्ड इनपुट आणि लिंक पूर्वावलोकन नियंत्रित करा + स्वयंचलित इनपुट अंदाजे बनवा + क्लिपबोर्ड आणि लिंक सेटिंग्ज QR, Base64, URL आणि मजकूर-हेवी वर्कफ्लोसाठी सोयीस्कर आहेत, परंतु स्वयंचलित इनपुट हे हेतुपुरस्सर असले पाहिजे. ॲप स्वतःच फील्ड भरत असल्याचे दिसत असल्यास, क्लिपबोर्ड पेस्ट वर्तन तपासा; लिंक कार्ड गोंगाट किंवा हळू वाटत असल्यास, लिंक पूर्वावलोकन वर्तन समायोजित करा. + तुम्ही अनेकदा मजकूर कॉपी करता आणि लगेच जुळणारे साधन उघडता तेव्हा स्वयंचलित क्लिपबोर्ड पेस्टला अनुमती द्या. + तुम्हाला अपेक्षित नसलेल्या साधनांमध्ये खाजगी क्लिपबोर्ड सामग्री दिसल्यास स्वयंचलित पेस्ट अक्षम करा. + तुमच्या वर्कफ्लोसाठी पूर्वावलोकन आणणे धीमे, विचलित करणारे किंवा अनावश्यक असेल तेव्हा लिंक पूर्वावलोकन बंद करा. + कॉम्पॅक्ट नियंत्रणे + जेव्हा स्क्रीनची जागा घट्ट असते तेव्हा घनता निवडक आणि जलद सेटिंग्ज प्लेसमेंट वापरा + लहान स्क्रीनवर अधिक नियंत्रणे बसवा + लहान फोन, लँडस्केप एडिटिंग आणि अनेक पॅरामीटर्स असलेल्या टूल्सवर कॉम्पॅक्ट सिलेक्टर आणि जलद सेटिंग्ज प्लेसमेंट मदत करतात. ट्रेडऑफ असा आहे की नियंत्रणे अधिक घनता जाणवू शकतात, म्हणून तुम्ही सामान्य पर्याय ओळखल्यानंतर हे सर्वोत्तम आहे. + जेव्हा संवाद किंवा पर्याय सूची स्क्रीनसाठी खूप उंच वाटतात तेव्हा संक्षिप्त निवडक सक्षम करा. + उपकरण नियंत्रणे डिस्प्लेच्या एका बाजूने पोहोचणे सोपे असल्यास जलद सेटिंग्ज बाजू समायोजित करा. + तुम्ही अजूनही ॲप शिकत असल्यास किंवा वारंवार चुकून टॅप केलेले दाट पर्याय शिकत असल्यास रूमियर कंट्रोल्सवर परत या. + वेबवरून प्रतिमा लोड करा + पृष्ठ किंवा प्रतिमा URL पेस्ट करा, विश्लेषण केलेल्या प्रतिमांचे पूर्वावलोकन करा, नंतर निवडलेले परिणाम जतन करा किंवा सामायिक करा + जाणूनबुजून वेब प्रतिमा वापरा + वेब प्रतिमा लोडिंग प्रदान केलेल्या URL वरून प्रतिमा स्त्रोतांचे विश्लेषण करते, प्रथम उपलब्ध प्रतिमेचे पूर्वावलोकन करते आणि तुम्हाला निवडलेल्या विश्लेषित प्रतिमा जतन किंवा सामायिक करू देते. काही साइट तात्पुरत्या, संरक्षित किंवा स्क्रिप्ट-व्युत्पन्न दुवे वापरतात, त्यामुळे ब्राउझरमध्ये कार्य करणारे पृष्ठ अद्याप वापरण्यायोग्य प्रतिमा देऊ शकत नाही. + थेट प्रतिमा URL किंवा पृष्ठ URL पेस्ट करा आणि विश्लेषित प्रतिमा सूची दिसण्याची प्रतीक्षा करा. + जेव्हा पृष्ठामध्ये अनेक प्रतिमा असतात आणि आपल्याला त्यापैकी काही आवश्यक असतात तेव्हा फ्रेम किंवा निवड नियंत्रणे वापरा. + काहीही लोड होत नसल्यास, थेट प्रतिमा दुवा वापरून पहा किंवा प्रथम ब्राउझरद्वारे डाउनलोड करा; साइट पार्सिंग अवरोधित करू शकते किंवा खाजगी दुवे वापरू शकते. + आकार बदलण्याचा प्रकार निवडा + प्रतिमेला नवीन परिमाणे लावण्यापूर्वी स्पष्ट, लवचिक, क्रॉप आणि फिट समजून घ्या + आकार, कॅनव्हास आणि आस्पेक्ट रेशो नियंत्रित करा + जेव्हा विनंती केलेली रुंदी आणि उंची मूळ गुणोत्तराशी जुळत नाही तेव्हा काय होते हे आकार बदलण्याचा प्रकार ठरवतो. पिक्सेल स्ट्रेच करणे, प्रमाण जतन करणे, अतिरिक्त क्षेत्र क्रॉप करणे किंवा पार्श्वभूमी कॅनव्हास जोडणे यात फरक आहे. + जेव्हा विकृती स्वीकार्य असेल किंवा स्त्रोतामध्ये लक्ष्याप्रमाणेच गुणोत्तर असेल तेव्हाच स्पष्ट वापरा. + महत्त्वाच्या बाजूने आकार बदलून प्रमाण ठेवण्यासाठी लवचिक वापरा, विशेषत: फोटो आणि मिश्रित बॅचसाठी. + बॉर्डरशिवाय कडक फ्रेमसाठी क्रॉप वापरा किंवा जेव्हा संपूर्ण इमेज निश्चित कॅनव्हासमध्ये दिसली पाहिजे तेव्हा फिट करा. + प्रतिमा स्केल मोड निवडा + तीक्ष्णता, गुळगुळीतपणा, वेग आणि किनारी कलाकृती नियंत्रित करणारे पुनर्नमुनाकरण फिल्टर निवडा + अपघाती मऊपणाशिवाय आकार बदला + प्रतिमा स्केल मोड हे नियंत्रित करते की आकार बदलताना नवीन पिक्सेल कसे मोजले जातात. साधे मोड जलद आणि अंदाज लावता येण्याजोगे असतात, तर प्रगत फिल्टर तपशील चांगल्या प्रकारे जतन करू शकतात परंतु कडा तीक्ष्ण करू शकतात, हलोस तयार करू शकतात किंवा मोठ्या प्रतिमांवर जास्त वेळ घेऊ शकतात. + जेव्हा परिणाम फक्त लहान आणि स्वच्छ असणे आवश्यक असते तेव्हा जलद दैनंदिन आकार बदलण्यासाठी मूलभूत किंवा द्विरेखीय वापरा. + बारीक तपशील, मजकूर किंवा उच्च-गुणवत्तेच्या डाउनस्केलिंग बाबी असताना Lanczos, Robidoux, Spline किंवा EWA-शैली मोड वापरा. + पिक्सेल आर्ट, आयकॉन आणि हार्ड-एज्ड ग्राफिक्ससाठी सर्वात जवळचा वापर करा जिथे अस्पष्ट पिक्सेल लूक खराब करेल. + स्केल रंग जागा + आकार बदलताना पिक्सेल पुन्हा नमुने केले जात असताना रंग कसे मिसळले जातात ते ठरवा + ग्रेडियंट आणि कडा नैसर्गिक ठेवा + स्केल कलर स्पेस आकार बदलताना वापरलेले गणित बदलते. बहुतेक प्रतिमा डीफॉल्टसह ठीक आहेत, परंतु रेखीय किंवा आकलनीय स्थाने मजबूत स्केलिंगनंतर ग्रेडियंट, गडद कडा आणि संतृप्त रंग अधिक स्वच्छ दिसू शकतात. + लहान रंग फरकांपेक्षा वेग आणि सुसंगतता महत्त्वाची असताना डीफॉल्ट सोडा. + आकार बदलल्यानंतर गडद बाह्यरेखा, UI स्क्रीनशॉट, ग्रेडियंट किंवा कलर बँड चुकीचे दिसत असताना रेखीय किंवा आकलनीय मोड वापरून पहा. + वास्तविक लक्ष्य स्क्रीनवर आधी आणि नंतरची तुलना करा, कारण रंग-स्पेस बदल सूक्ष्म आणि सामग्रीवर अवलंबून असू शकतात. + आकार बदलणे मोड मर्यादित करा + ओव्हर-लिमिट प्रतिमा कधी वगळल्या पाहिजेत, रिकोड केल्या पाहिजेत किंवा फिट करण्यासाठी कमी कराव्यात हे जाणून घ्या + मर्यादेवर काय होते ते निवडा + मर्यादा आकार बदलणे हे केवळ लहान-प्रतिमा साधन नाही. जेव्हा एखादा स्रोत तुमच्या मर्यादेत असतो किंवा फक्त एका बाजूने नियम मोडतो तेव्हा ॲप काय करतो हे त्याचा मोड ठरवतो, जो मिश्र फोल्डर आणि अपलोड तयारीसाठी महत्त्वाचा आहे. + मर्यादांमधील प्रतिमांना स्पर्श न करता सोडले जावे आणि पुन्हा निर्यात केले जाऊ नये तेव्हा वगळा वापरा. + जेव्हा परिमाण स्वीकार्य असतील तेव्हा Recode वापरा परंतु तरीही तुम्हाला नवीन स्वरूप, मेटाडेटा वर्तन, गुणवत्ता किंवा फाइलनाव आवश्यक आहे. + जेव्हा मर्यादा मोडणाऱ्या प्रतिमा अनुमत बॉक्समध्ये बसत नाहीत तोपर्यंत त्या प्रमाणात कमी केल्या जाव्यात तेव्हा झूम वापरा. + गुणोत्तर लक्ष्य + कडक आउटपुट आकारात ताणलेले चेहरे, कट ऑफ कडा आणि अनपेक्षित सीमा टाळा + आकार बदलण्यापूर्वी आकार जुळवा + रुंदी आणि उंची हे आकार बदलण्याच्या लक्ष्याच्या फक्त अर्ध्या आहेत. आस्पेक्ट रेशो हे ठरवते की इमेज नैसर्गिकरित्या फिट होऊ शकते, क्रॉप करणे आवश्यक आहे किंवा तिच्याभोवती कॅनव्हास आवश्यक आहे. प्रथम आकार तपासणे सर्वात आश्चर्यकारक आकार परिणाम प्रतिबंधित करते. + स्पष्ट, क्रॉप किंवा फिट निवडण्यापूर्वी स्त्रोत आकाराची लक्ष्य आकारासह तुलना करा. + जेव्हा विषय अतिरिक्त पार्श्वभूमी गमावू शकतो आणि गंतव्यस्थानाला कठोर फ्रेमची आवश्यकता असते तेव्हा प्रथम क्रॉप करा. + मूळ प्रतिमेचा प्रत्येक भाग दृश्यमान असायला हवा तेव्हा फिट किंवा लवचिक वापरा. + अपस्केल किंवा सामान्य आकार बदला + पिक्सेल जोडण्यासाठी AI आवश्यक असताना आणि साधे स्केलिंग केव्हा पुरेसे आहे हे जाणून घ्या + तपशीलासह आकार गोंधळात टाकू नका + सामान्य आकार बदलल्याने पिक्सेल इंटरपोलेटिंग करून परिमाणे बदलतात; तो वास्तविक तपशील शोधू शकत नाही. एआय अपस्केल अधिक स्पष्ट दिसणारे तपशील तयार करू शकते, परंतु ते हळू आहे आणि पोत भ्रमित करू शकते, त्यामुळे योग्य निवड आपल्याला अनुकूलता, वेग किंवा व्हिज्युअल पुनर्संचयनाची आवश्यकता आहे यावर अवलंबून असते. + लघुप्रतिमा, अपलोड मर्यादा, स्क्रीनशॉट आणि साध्या आकारमान बदलांसाठी सामान्य आकार बदला. + जेव्हा एखादी लहान किंवा सॉफ्ट इमेज मोठ्या आकारात चांगली दिसायची असते तेव्हा AI अपस्केल वापरा. + AI अपस्केल नंतर चेहरे, मजकूर आणि नमुन्यांची तुलना करा कारण व्युत्पन्न केलेला तपशील खात्रीलायक पण चुकीचा दिसू शकतो. + फिल्टर ऑर्डर महत्त्वाचे + जाणूनबुजून क्रमाने क्लीनअप, रंग, तीक्ष्णता आणि अंतिम कॉम्प्रेशन लागू करा + क्लिनर फिल्टर स्टॅक तयार करा + फिल्टर नेहमी बदलण्यायोग्य नसतात. तीक्ष्ण होण्याआधी अस्पष्टता, ओसीआरपूर्वी कॉन्ट्रास्ट बूस्ट किंवा कॉम्प्रेशनपूर्वी रंग बदलणे समान नियंत्रणांमधून खूप वेगळे आउटपुट देऊ शकते. + प्रथम क्रॉप करा आणि फिरवा जेणेकरून नंतर फिल्टर फक्त पिक्सेलवर कार्य करतील जे शिल्लक राहतील. + एक्सपोजर, व्हाईट बॅलन्स, आणि कॉन्ट्रास्ट धारदार किंवा शैलीबद्ध प्रभाव लागू करण्यापूर्वी. + अंतिम आकार आणि कॉम्प्रेशन शेवटच्या जवळ ठेवा जेणेकरून पूर्वावलोकन केलेले तपशील निर्यात अंदाजानुसार टिकून राहतील. + पार्श्वभूमीच्या कडांचे निराकरण करा + आपोआप काढून टाकल्यानंतर हॅलोस, उरलेल्या सावल्या, केस, छिद्र आणि मऊ बाह्यरेखा स्वच्छ करा + कटआउट्स जाणूनबुजून बनवा + स्वयंचलित मुखवटे सहसा एका दृष्टीक्षेपात चांगले दिसतात परंतु चमकदार, गडद किंवा नमुना असलेल्या पार्श्वभूमीवर समस्या प्रकट करतात. एज क्लीनअप हा द्रुत कटआउट आणि पुन्हा वापरता येण्याजोगा स्टिकर, लोगो किंवा उत्पादन प्रतिमेमधील फरक आहे. + हेलोस आणि गहाळ किनार तपशील प्रकट करण्यासाठी विरोधाभासी पार्श्वभूमीच्या विरूद्ध निकालाचे पूर्वावलोकन करा. + आजूबाजूचे उरलेले मिटवण्यापूर्वी केस, कोपरे आणि पारदर्शक वस्तूंसाठी पुनर्संचयित करा. + जेव्हा कटआउट डिझाइनमध्ये ठेवला जाईल तेव्हा अंतिम पार्श्वभूमी रंगावर एक लहान चाचणी निर्यात करा. + वाचनीय वॉटरमार्क + फोटो खराब न करता चमकदार, गडद, ​​व्यस्त आणि क्रॉप केलेल्या प्रतिमांवर चिन्हे दृश्यमान करा + प्रतिमा लपविल्याशिवाय संरक्षित करा + एका इमेजवर चांगला दिसणारा वॉटरमार्क दुसऱ्या इमेजवर अदृश्य होऊ शकतो. आकार, अपारदर्शकता, कॉन्ट्रास्ट, प्लेसमेंट आणि पुनरावृत्ती केवळ पहिल्या पूर्वावलोकनासाठीच नव्हे तर संपूर्ण बॅचसाठी निवडली पाहिजे. + सर्व फायली निर्यात करण्यापूर्वी बॅचमधील सर्वात उजळ आणि गडद प्रतिमांवर चिन्हाची चाचणी घ्या. + मोठ्या पुनरावृत्ती झालेल्या खुणांसाठी कमी अपारदर्शकता वापरा आणि लहान कोपऱ्याच्या खुणांसाठी मजबूत कॉन्ट्रास्ट वापरा. + महत्त्वाचे चेहरे, मजकूर आणि उत्पादन तपशील स्पष्ट ठेवा जोपर्यंत चिन्हाचा पुनर्वापर रोखण्यासाठी केला जात नाही. + एक प्रतिमा टाइलमध्ये विभाजित करा + पंक्ती, स्तंभ, सानुकूल टक्केवारी, स्वरूप आणि गुणवत्तेनुसार एकच प्रतिमा कट करा + ग्रिड आणि ऑर्डर केलेले तुकडे तयार करा + इमेज स्प्लिटिंग एका सोर्स इमेजमधून अनेक आउटपुट तयार करते. हे पंक्ती आणि स्तंभ संख्येनुसार विभाजित करू शकते, पंक्ती किंवा स्तंभ टक्केवारी समायोजित करू शकते आणि निवडलेल्या आउटपुट स्वरूप आणि गुणवत्तेसह तुकडे जतन करू शकते, जे ग्रिड, पृष्ठ स्लाइस, पॅनोरामा आणि ऑर्डर केलेल्या सामाजिक पोस्टसाठी उपयुक्त आहे. + स्त्रोत प्रतिमा निवडा, नंतर आपल्याला आवश्यक असलेल्या पंक्ती आणि स्तंभांची संख्या सेट करा. + जेव्हा सर्व तुकडे समान आकाराचे नसावेत तेव्हा पंक्ती किंवा स्तंभाची टक्केवारी समायोजित करा. + जतन करण्यापूर्वी आउटपुट स्वरूप आणि गुणवत्ता निवडा जेणेकरून प्रत्येक व्युत्पन्न केलेला भाग समान निर्यात नियम वापरतो. + प्रतिमा पट्ट्या कापून टाका + अनुलंब किंवा क्षैतिज प्रदेश काढा आणि उर्वरित भाग परत एकत्र विलीन करा + अंतर न ठेवता प्रदेश काढा + प्रतिमा कटिंग हे सामान्य पिकापेक्षा वेगळे आहे. ते निवडलेली अनुलंब किंवा क्षैतिज पट्टी काढू शकते, नंतर उर्वरित प्रतिमा भाग एकत्र विलीन करू शकते. उलटा मोड त्याऐवजी निवडलेला प्रदेश ठेवतो आणि दोन्ही व्यस्त दिशा वापरल्याने निवडलेला आयत ठेवतो. + बाजूचे प्रदेश, स्तंभ किंवा मध्यम पट्ट्यांसाठी अनुलंब कटिंग वापरा; बॅनर, अंतर किंवा पंक्तीसाठी क्षैतिज कटिंग वापरा. + जेव्हा तुम्ही निवडलेला भाग काढून टाकण्याऐवजी ठेवू इच्छित असाल तेव्हा अक्षावर व्युत्क्रम सक्षम करा. + कट केल्यानंतर कडांचे पूर्वावलोकन करा कारण काढलेले क्षेत्र महत्त्वाचे तपशील ओलांडल्यावर विलीन केलेली सामग्री अचानक दिसू शकते. + आउटपुट स्वरूप निवडा + सामग्री आणि गंतव्यस्थानावर आधारित JPEG, PNG, WebP, AVIF, JXL किंवा PDF निवडा + गंतव्यस्थानाला स्वरूप ठरवू द्या + इमेजमध्ये काय आहे आणि ते कुठे वापरले जाईल यावर सर्वोत्कृष्ट स्वरूप अवलंबून असते. फोटो, स्क्रीनशॉट, पारदर्शक मालमत्ता, दस्तऐवज आणि ॲनिमेटेड फाइल्स चुकीच्या फॉरमॅटमध्ये सक्ती केल्यावर वेगवेगळ्या प्रकारे अयशस्वी होतात. + जेव्हा पारदर्शकता आणि अचूक पिक्सेल आवश्यक नसतील तेव्हा विस्तृत फोटो अनुकूलतेसाठी JPEG वापरा. + शार्प स्क्रीनशॉट, UI कॅप्चर, पारदर्शक ग्राफिक्स आणि दोषरहित संपादनांसाठी PNG वापरा. + जेव्हा कॉम्पॅक्ट आधुनिक आउटपुट महत्त्वाचे असते आणि प्राप्त करणारे ॲप त्यास समर्थन देते तेव्हा WebP, AVIF किंवा JXL वापरा. + ऑडिओ कव्हर्स काढा + PNG प्रतिमा म्हणून ऑडिओ फायलींमधून एम्बेडेड अल्बम आर्ट किंवा उपलब्ध मीडिया फ्रेम जतन करा + ऑडिओ फायलींमधून कलाकृती काढा + ऑडिओ कव्हर्स ऑडिओ फाइल्समधून एम्बेडेड आर्टवर्क वाचण्यासाठी Android मीडिया मेटाडेटा वापरतात. एम्बेडेड आर्टवर्क अनुपलब्ध असताना, Android प्रदान करू शकत असल्यास पुनर्प्राप्ती मीडिया फ्रेमवर परत येऊ शकते; जर दोन्ही अस्तित्वात नसेल, तर टूल कळवतो की काढण्यासाठी कोणतीही प्रतिमा नाही. + ऑडिओ कव्हर्स उघडा आणि अपेक्षित कव्हर आर्टसह एक किंवा अधिक ऑडिओ फाइल्स निवडा. + जतन किंवा शेअर करण्यापूर्वी काढलेल्या कव्हरचे पुनरावलोकन करा, विशेषत: स्ट्रीमिंग किंवा रेकॉर्डिंग ॲप्समधील फाइल्ससाठी. + कोणतीही प्रतिमा न आढळल्यास, ऑडिओ फाइलमध्ये एम्बेड केलेले कव्हर नसण्याची शक्यता आहे किंवा Android वापरण्यायोग्य फ्रेम उघड करू शकत नाही. + तारखा आणि स्थान मेटाडेटा + कॅप्चर टाइम महत्त्वाचे असताना काय जतन करायचे ते ठरवा परंतु GPS किंवा डिव्हाइस डेटा लीक होऊ नये + खाजगी मेटाडेटा पासून उपयुक्त मेटाडेटा वेगळे करा + मेटाडेटा सर्व समान धोकादायक नाही. कॅप्चर तारखा संग्रहण आणि क्रमवारीसाठी उपयुक्त ठरू शकतात, तर GPS, डिव्हाइस सिरीयल सारखी फील्ड, सॉफ्टवेअर टॅग किंवा मालक फील्ड हेतूपेक्षा जास्त प्रकट करू शकतात. + अल्बम, स्कॅन किंवा प्रकल्प इतिहासासाठी कालक्रमानुसार महत्त्वाची असते तेव्हा तारीख आणि वेळ टॅग ठेवा. + सार्वजनिक शेअरिंग करण्यापूर्वी किंवा अनोळखी व्यक्तींना प्रतिमा पाठवण्यापूर्वी GPS आणि डिव्हाइस-ओळखणारे टॅग काढा. + गोपनीयतेच्या आवश्यकता कठोर असताना नमुना निर्यात करा आणि EXIF ​​ची पुन्हा तपासणी करा. + फाइलनाव टक्कर टाळा + जेव्हा अनेक फायली image.jpg किंवा screenshot.png सारखी नावे शेअर करतात तेव्हा बॅच आउटपुटचे संरक्षण करा + प्रत्येक आउटपुट नाव अद्वितीय बनवा + मेसेंजर, स्क्रीनशॉट, क्लाउड फोल्डर किंवा एकाधिक कॅमेऱ्यांमधून प्रतिमा येतात तेव्हा डुप्लिकेट फाइलनावे सामान्य असतात. चांगला पॅटर्न अपघाती पुनर्स्थापना प्रतिबंधित करतो आणि आउटपुट त्यांच्या स्त्रोतांकडे परत शोधण्यायोग्य ठेवतो. + जेव्हा संपादित प्रत ओळखण्यायोग्य असावी तेव्हा मूळ नाव आणि प्रत्यय समाविष्ट करा. + मोठ्या मिश्रित बॅचवर प्रक्रिया करताना अनुक्रम, टाइमस्टॅम्प, प्रीसेट किंवा परिमाणे जोडा. + नामकरण पॅटर्न टक्कर होऊ शकत नाही हे तुम्ही सत्यापित करेपर्यंत ओव्हरराइट वर्कफ्लो टाळा. + सेव्ह करा किंवा शेअर करा + पर्सिस्टंट फाइल आणि दुसऱ्या ॲपसाठी तात्पुरता हँडऑफ यापैकी निवडा + योग्य हेतूने निर्यात करा + जतन करा आणि सामायिक करा सारखे वाटू शकते, परंतु ते भिन्न समस्यांचे निराकरण करतात. जतन केल्याने तुम्हाला नंतर सापडेल अशी फाइल तयार होते; सामायिकरण Android द्वारे व्युत्पन्न परिणाम दुसऱ्या ॲपवर पाठवते आणि टिकाऊ स्थानिक प्रत सोडू शकत नाही. + जेव्हा आउटपुट ज्ञात फोल्डरमध्ये राहणे आवश्यक आहे, बॅक अप घेणे आवश्यक आहे किंवा नंतर पुन्हा वापरणे आवश्यक आहे तेव्हा सेव्ह वापरा. + द्रुत संदेश, ईमेल संलग्नक, सामाजिक पोस्टिंग किंवा दुसऱ्या संपादकाला निकाल पाठवण्यासाठी शेअर वापरा. + जेव्हा प्राप्त करणारे ॲप अविश्वसनीय असेल किंवा अयशस्वी शेअर पुन्हा तयार करणे त्रासदायक असेल तेव्हा प्रथम जतन करा. + पीडीएफ पृष्ठ आकार आणि समास + कागदपत्र तयार करण्यापूर्वी कागदाचा आकार, योग्य वागणूक आणि श्वास घेण्याची खोली निवडा + प्रतिमा पृष्ठे छापण्यायोग्य आणि वाचनीय बनवा + प्रतिमा अस्ताव्यस्त PDF पृष्ठे बनू शकतात जेव्हा त्यांचा आकार निवडलेल्या कागदाच्या आकाराशी जुळत नाही. समास, फिट मोड आणि पृष्ठ अभिमुखता सामग्री क्रॉप केलेली, लहान, ताणलेली किंवा वाचण्यासाठी आरामदायक आहे की नाही हे ठरवते. + पीडीएफ मुद्रित किंवा फॉर्ममध्ये सबमिट केल्यावर वास्तविक कागदाचा आकार वापरा. + स्कॅन आणि स्क्रीनशॉटसाठी समास वापरा जेणेकरून मजकूर पृष्ठाच्या काठावर बसणार नाही. + पोर्ट्रेट आणि लँडस्केप पृष्ठांचे पूर्वावलोकन करा जेव्हा स्त्रोत प्रतिमा मिश्रित अभिमुखता असतात. + स्कॅन साफ ​​करा + PDF किंवा OCR आधी कागदाच्या कडा, सावल्या, कॉन्ट्रास्ट, रोटेशन आणि वाचनीयता सुधारा + संग्रहित करण्यापूर्वी पृष्ठे तयार करा + स्कॅन तांत्रिकदृष्ट्या कॅप्चर केले जाऊ शकते परंतु तरीही वाचणे कठीण आहे. पीडीएफ एक्सपोर्ट करण्यापूर्वी लहान क्लीनअप टप्पे सहसा कॉम्प्रेशन सेटिंग्जपेक्षा जास्त महत्त्वाचे असतात, विशेषत: पावत्या, हस्तलिखित नोट्स आणि कमी प्रकाश असलेल्या पृष्ठांसाठी. + योग्य दृष्टीकोन करा आणि टेबलच्या कडा, बोटे, सावल्या आणि पार्श्वभूमीतील गोंधळ काढून टाका. + कॉन्ट्रास्ट काळजीपूर्वक वाढवा जेणेकरून स्टॅम्प, स्वाक्षरी किंवा पेन्सिल चिन्हे नष्ट न करता मजकूर स्पष्ट होईल. + पृष्ठ सरळ आणि वाचनीय झाल्यानंतरच OCR चालवा, नंतर महत्वाचे क्रमांक स्वतः सत्यापित करा. + पीडीएफ कॉम्प्रेस, ग्रेस्केल किंवा दुरुस्त करा + हलक्या, मुद्रणयोग्य किंवा पुनर्निर्मित दस्तऐवज प्रतींसाठी एक-फाइल PDF ऑपरेशन्स वापरा + पीडीएफ क्लीनअप ऑपरेशन निवडा + पीडीएफ टूल्समध्ये कॉम्प्रेशन, ग्रेस्केल रूपांतरण आणि दुरुस्तीसाठी स्वतंत्र ऑपरेशन्स समाविष्ट आहेत. कॉम्प्रेशन आणि ग्रेस्केल मुख्यतः एम्बेड केलेल्या प्रतिमांद्वारे कार्य करते, तर दुरुस्ती पीडीएफ संरचना पुन्हा तयार करते; यापैकी काहीही प्रत्येक दस्तऐवजासाठी हमी निश्चित म्हणून मानले जाऊ नये. + सामायिकरणासाठी दस्तऐवजात प्रतिमा आणि फाइल आकाराच्या बाबी असतील तेव्हा कॉम्प्रेस पीडीएफ वापरा. + जेव्हा दस्तऐवज मुद्रित करणे सोपे असेल किंवा रंगीत प्रतिमांची आवश्यकता नसेल तेव्हा ग्रेस्केल वापरा. + दस्तऐवज उघडण्यात अयशस्वी झाल्यास किंवा अंतर्गत संरचना खराब झाल्यास कॉपीवर पीडीएफ दुरुस्ती वापरा, नंतर पुनर्निर्मित फाइल सत्यापित करा. + PDF मधून प्रतिमा काढा + एम्बेड केलेल्या PDF प्रतिमा पुनर्प्राप्त करा आणि काढलेले परिणाम संग्रहणात पॅक करा + दस्तऐवजांमधून स्त्रोत प्रतिमा जतन करा + Extract Images एम्बेड केलेल्या इमेज ऑब्जेक्ट्ससाठी PDF स्कॅन करते, शक्य असेल तिथे डुप्लिकेट वगळते आणि व्युत्पन्न केलेल्या झिप आर्काइव्हमध्ये पुनर्प्राप्त केलेल्या प्रतिमा संग्रहित करते. हे केवळ पीडीएफमध्ये एम्बेड केलेल्या प्रतिमा काढते, मजकूर, वेक्टर रेखाचित्रे किंवा स्क्रीनशॉट म्हणून दिसणारे प्रत्येक पृष्ठ नाही. + जेव्हा तुम्हाला PDF मध्ये संग्रहित केलेल्या चित्रांची आवश्यकता असेल, जसे की कॅटलॉगमधील फोटो किंवा स्कॅन केलेल्या मालमत्तेची आवश्यकता असेल तेव्हा चित्र काढा. + जेव्हा तुम्हाला मजकूर आणि लेआउटसह संपूर्ण पृष्ठांची आवश्यकता असेल तेव्हा त्याऐवजी प्रतिमा किंवा पृष्ठ काढण्यासाठी PDF वापरा. + जर टूल एम्बेडेड इमेजेसचा अहवाल देत नसेल, तर पेज मजकूर/वेक्टर सामग्री असू शकते किंवा इमेज एक्सट्रॅक्ट करण्यायोग्य वस्तू म्हणून संग्रहित केल्या जाऊ शकत नाहीत. + मेटाडेटा, फ्लॅटनिंग आणि प्रिंट आउटपुट + दस्तऐवज गुणधर्म संपादित करा, पृष्ठे रास्टराइझ करा किंवा सानुकूल प्रिंट लेआउट्स जाणूनबुजून तयार करा + अंतिम-दस्तऐवज क्रिया निवडा + पीडीएफ मेटाडेटा, फ्लॅटनिंग आणि प्रिंटची तयारी अंतिम टप्प्यातील विविध समस्यांचे निराकरण करते. मेटाडेटा दस्तऐवज गुणधर्म नियंत्रित करतो, फ्लॅटन पीडीएफ पृष्ठांना कमी संपादन करण्यायोग्य आउटपुटमध्ये रास्टराइज करते आणि प्रिंट पीडीएफ पृष्ठ आकार, अभिमुखता, मार्जिन आणि पृष्ठ-प्रति-शीट नियंत्रणांसह नवीन लेआउट तयार करते. + लेखक, शीर्षक, कीवर्ड, निर्माता किंवा गोपनीयता क्लीनअप महत्त्वाचे असताना मेटाडेटा वापरा. + जेव्हा दृश्यमान पृष्ठ सामग्री स्वतंत्र PDF ऑब्जेक्ट्स म्हणून संपादित करणे कठीण होईल तेव्हा सपाट PDF वापरा. + जेव्हा तुम्हाला सानुकूल पृष्ठ आकार, अभिमुखता, समास किंवा प्रति शीट एकाधिक पृष्ठे आवश्यक असतील तेव्हा प्रिंट PDF वापरा. + OCR साठी प्रतिमा तयार करा + ओसीआरला मजकूर वाचण्यास सांगण्यापूर्वी क्रॉप, रोटेशन, कॉन्ट्रास्ट, डिनोईज आणि स्केल वापरा + ओसीआर क्लिनर पिक्सेल द्या + OCR चुका अनेकदा OCR इंजिन ऐवजी इनपुट इमेजमधून येतात. वाकडी पृष्ठे, कमी कॉन्ट्रास्ट, अस्पष्टता, सावल्या, लहान मजकूर आणि मिश्र पार्श्वभूमी या सर्वांमुळे ओळख गुणवत्ता कमी होते. + मजकूर क्षेत्रावर क्रॉप करा आणि प्रतिमा फिरवा जेणेकरून ओळखीपूर्वी रेषा क्षैतिज असतील. + कॉन्ट्रास्ट वाढवा किंवा क्लिनर ब्लॅक-अँड-व्हाइटमध्ये रूपांतरित करा जेव्हा ते अक्षरे वाचणे सोपे करते. + वरच्या दिशेने लहान मजकूराचा आकार बदला, परंतु आक्रमक तीक्ष्ण करणे टाळा ज्यामुळे बनावट कडा तयार होतात. + QR सामग्री तपासा + कोडवर विश्वास ठेवण्यापूर्वी लिंक्स, वाय-फाय क्रेडेंशियल, संपर्क आणि क्रियांचे पुनरावलोकन करा + डीकोड केलेला मजकूर अविश्वसनीय इनपुट म्हणून हाताळा + QR कोड URL, संपर्क कार्ड, Wi-Fi पासवर्ड, कॅलेंडर इव्हेंट, फोन नंबर किंवा साधा मजकूर लपवू शकतो. कोडच्या जवळ दिसणारे लेबल वास्तविक पेलोडशी जुळत नाही, त्यामुळे त्यावर कार्य करण्यापूर्वी डीकोड केलेल्या सामग्रीचे पुनरावलोकन करा. + पूर्ण डीकोड केलेली URL उघडण्यापूर्वी त्याची तपासणी करा, विशेषतः लहान किंवा अपरिचित डोमेन. + वाय-फाय, संपर्क, कॅलेंडर, फोन आणि SMS कोडसह सावधगिरी बाळगा कारण ते संवेदनशील क्रिया ट्रिगर करू शकतात. + तुम्हाला सामग्री तत्काळ उघडण्याऐवजी ती इतरत्र सत्यापित करायची असेल तेव्हा प्रथम कॉपी करा. + QR कोड व्युत्पन्न करा + साधा मजकूर, URL, वाय-फाय, संपर्क, ईमेल, फोन, एसएमएस आणि स्थान डेटावरून कोड तयार करा + योग्य QR पेलोड तयार करा + QR स्क्रीन एंटर केलेल्या सामग्रीवरून कोड तयार करू शकते तसेच विद्यमान असलेले स्कॅन करू शकते. संरचित QR प्रकार इतर ॲप्सना वाय-फाय लॉगिन तपशील, संपर्क कार्ड, ईमेल फील्ड, फोन नंबर, SMS सामग्री, URL किंवा भौगोलिक निर्देशांक यांसारख्या फॉरमॅटमध्ये डेटा एन्कोड करण्यात मदत करतात. + साध्या नोट्ससाठी साधा मजकूर निवडा किंवा कोड जेव्हा Wi-Fi, संपर्क, URL, ईमेल, फोन, SMS किंवा स्थान डेटा म्हणून उघडला पाहिजे तेव्हा संरचित प्रकार वापरा. + व्युत्पन्न केलेला कोड सामायिक करण्यापूर्वी प्रत्येक फील्ड तपासा कारण दृश्यमान पूर्वावलोकन केवळ तुम्ही प्रविष्ट केलेला पेलोड प्रतिबिंबित करतो. + जतन केलेला QR कोड मुद्रित केला जाईल, सार्वजनिकपणे पोस्ट केला जाईल किंवा इतर लोक वापरतील तेव्हा स्कॅनरसह चाचणी करा. + चेकसम मोड निवडा + फायली किंवा मजकूरातून हॅशची गणना करा, एका फाइलची तुलना करा किंवा अनेक फायली बॅच-चेक करा + सामग्री सत्यापित करा, देखावा नाही + चेकसम टूल्समध्ये फाइल हॅशिंग, टेक्स्ट हॅशिंग, ज्ञात हॅशशी फाइलची तुलना आणि बॅच तुलना यासाठी स्वतंत्र टॅब आहेत. चेकसम निवडलेल्या अल्गोरिदमसाठी बाइट-फॉर-बाइट ओळख सिद्ध करते; हे सिद्ध होत नाही की दोन प्रतिमा फक्त समान दिसतात. + फायलींसाठी गणना करा आणि टाइप केलेल्या किंवा पेस्ट केलेल्या मजकूर डेटासाठी टेक्स्ट हॅश वापरा. + जेव्हा कोणी तुम्हाला एका फाईलसाठी अपेक्षित चेकसम देते तेव्हा तुलना वापरा. + जेव्हा अनेक फायलींना एका पासमध्ये हॅश किंवा सत्यापन आवश्यक असते तेव्हा बॅच तुलना वापरा, नंतर निवडलेला अल्गोरिदम सुसंगत ठेवा. + क्लिपबोर्ड गोपनीयता + खाजगी कॉपी केलेला डेटा चुकून उघड न करता स्वयंचलित क्लिपबोर्ड वैशिष्ट्ये वापरा + कॉपी केलेली सामग्री जाणूनबुजून ठेवा + क्लिपबोर्ड ऑटोमेशन सोयीस्कर आहे, परंतु कॉपी केलेल्या मजकुरात पासवर्ड, लिंक्स, पत्ते, टोकन किंवा खाजगी नोट्स असू शकतात. क्लिपबोर्ड-आधारित इनपुटला वेग वैशिष्ट्य म्हणून हाताळा जे तुमच्या सवयी आणि गोपनीयता अपेक्षांशी जुळले पाहिजे. + अनपेक्षितपणे टूल्समध्ये खाजगी मजकूर दिसल्यास स्वयंचलित क्लिपबोर्ड वापर अक्षम करा. + जेव्हा तुम्हाला जाणीवपूर्वक परिणाम स्टोरेज टाळायचा असेल तेव्हाच क्लिपबोर्ड-फक्त बचत वापरा. + संवेदनशील मजकूर, QR डेटा किंवा बेस64 पेलोड हाताळल्यानंतर क्लिपबोर्ड साफ करा किंवा बदला. + रंग स्वरूप + इतरत्र पेस्ट करण्यापूर्वी HEX, RGB, HSV, अल्फा आणि कॉपी केलेले रंग मूल्य समजून घ्या + लक्ष्याला अपेक्षित असलेले स्वरूप कॉपी करा + समान दृश्यमान रंग अनेक प्रकारे लिहिले जाऊ शकते. डिझाईन टूल, CSS फील्ड, अँड्रॉइड कलर व्हॅल्यू किंवा इमेज एडिटर वेगळ्या ऑर्डर, अल्फा हँडलिंग किंवा कलर मॉडेलची अपेक्षा करू शकतात. + कॉम्पॅक्ट कलर कोडची अपेक्षा करणाऱ्या वेब, डिझाइन किंवा थीम फील्डमध्ये पेस्ट करताना HEX वापरा. + तुम्हाला चॅनेल, ब्राइटनेस, सॅच्युरेशन किंवा ह्यू मॅन्युअली समायोजित करण्याची आवश्यकता असेल तेव्हा RGB किंवा HSV वापरा. + पारदर्शकता महत्त्वाची असताना अल्फा स्वतंत्रपणे तपासा कारण काही गंतव्यस्थान त्याकडे दुर्लक्ष करतात किंवा भिन्न क्रम वापरतात. + कलर लायब्ररी ब्राउझ करा + नाव किंवा HEX द्वारे नामांकित रंग शोधा आणि आवडते शीर्षस्थानी ठेवा + पुन्हा वापरता येणारे नामांकित रंग शोधा + कलर लायब्ररी नामांकित रंग संग्रह लोड करते, नावानुसार क्रमवारी लावते, रंगाच्या नावाने किंवा HEX मूल्यानुसार फिल्टर करते आणि आवडते रंग संग्रहित करते जेणेकरून महत्त्वाच्या नोंदी सहज पोहोचू शकतील. जेव्हा तुम्हाला प्रतिमेतून नमुने घेण्याऐवजी वास्तविक नावाच्या रंगाची आवश्यकता असते तेव्हा ते उपयुक्त ठरते. + लाल, निळा, स्लेट किंवा मिंट यासारखे कुटुंब ओळखता तेव्हा रंगाच्या नावाने शोधा. + जेव्हा तुमच्याकडे आधीच अंकीय रंग असेल आणि तुम्हाला जुळणाऱ्या किंवा जवळपासच्या नावाच्या नोंदी शोधायच्या असतील तेव्हा HEX द्वारे शोधा. + वारंवार रंग आवडते म्हणून चिन्हांकित करा जेणेकरून ते ब्राउझ करताना सामान्य लायब्ररीच्या पुढे जातील. + जाळी ग्रेडियंट संग्रह वापरा + उपलब्ध मेश ग्रेडियंट संसाधने डाउनलोड करा आणि निवडलेल्या प्रतिमा सामायिक करा + रिमोट मेश ग्रेडियंट मालमत्ता वापरा + मेश ग्रेडियंट्स रिमोट रिसोर्स कलेक्शन लोड करू शकतात, गहाळ ग्रेडियंट इमेज डाउनलोड करू शकतात, डाउनलोड प्रगती दाखवू शकतात आणि निवडलेले ग्रेडियंट शेअर करू शकतात. उपलब्धता नेटवर्क ऍक्सेस आणि रिमोट रिसोर्स स्टोअरवर अवलंबून असते, त्यामुळे रिकाम्या यादीचा अर्थ असा होतो की संसाधने अद्याप उपलब्ध नव्हती. + जेव्हा तुम्हाला रेडीमेड मेश ग्रेडियंट प्रतिमा हव्या असतील तेव्हा ग्रेडियंट टूल्समधून मेश ग्रेडियंट उघडा. + संकलन रिकामे आहे असे मानण्यापूर्वी संसाधन लोड होण्याची किंवा डाउनलोड प्रगतीची प्रतीक्षा करा. + तुम्हाला आवश्यक असलेले ग्रेडियंट निवडा आणि सामायिक करा, किंवा तुम्ही स्वतः सानुकूल ग्रेडियंट तयार करू इच्छिता तेव्हा ग्रेडियंट मेकरवर परत जा. + व्युत्पन्न मालमत्ता ठराव + ग्रेडियंट, आवाज, वॉलपेपर, SVG सारखी कला किंवा पोत निर्माण करण्यापूर्वी आउटपुट आकार निवडा + आपल्याला आवश्यक आकारात व्युत्पन्न करा + व्युत्पन्न केलेल्या प्रतिमांमध्ये परत पडण्यासाठी मूळ कॅमेरा नसतो. आउटपुट खूप लहान असल्यास, नंतरचे स्केलिंग पॅटर्न मऊ करू शकते, तपशील बदलू शकते किंवा मालमत्ता टाइल आणि कॉम्प्रेस कसे बदलते. + वॉलपेपर, चिन्ह, पार्श्वभूमी, प्रिंट किंवा आच्छादन यांसारख्या अंतिम वापरासाठी प्रथम परिमाणे सेट करा. + अनेक लेआउटमध्ये क्रॉप, झूम किंवा पुन्हा वापरल्या जाणाऱ्या टेक्सचरसाठी मोठ्या आकारांचा वापर करा. + प्रचंड आउटपुटपूर्वी चाचणी आवृत्त्या निर्यात करा कारण प्रक्रियात्मक तपशील कॉम्प्रेशन कसे वागतात ते बदलू शकतात. + वर्तमान वॉलपेपर निर्यात करा + डिव्हाइसवरून उपलब्ध होम, लॉक आणि अंगभूत वॉलपेपर पुनर्प्राप्त करा + स्थापित केलेले वॉलपेपर जतन करा किंवा सामायिक करा + Wallpapers Export Android ने सिस्टीम वॉलपेपर API द्वारे उघड केलेले वॉलपेपर वाचते. डिव्हाइस, Android आवृत्ती, परवानग्या आणि बिल्ड प्रकारानुसार, होम, लॉक किंवा अंगभूत वॉलपेपर स्वतंत्रपणे उपलब्ध असू शकतात किंवा गहाळ असू शकतात. + ॲपला वाचण्याची अनुमती देणारे वॉलपेपर लोड करण्यासाठी वॉलपेपर एक्सपोर्ट उघडा. + उपलब्ध होम, लॉक किंवा बिल्ट-इन वॉलपेपर एंट्री निवडा ज्या तुम्हाला सेव्ह, कॉपी किंवा शेअर करायच्या आहेत. + वॉलपेपर गहाळ असल्यास किंवा वैशिष्ट्य अनुपलब्ध असल्यास, ते सहसा संपादन सेटिंग ऐवजी सिस्टम, परवानगी किंवा बिल्ड-व्हेरिएंट मर्यादा असते. + कॉर्नर्स ॲनिमेशन थ्रॉटल + रंग म्हणून कॉपी करा + HEX + नाव + मऊ केस आणि काठ हाताळणीसह वेगवान व्यक्ती कटआउटसाठी पोर्ट्रेट मॅटिंग मॉडेल. + शून्य-DCE++ वक्र अंदाज वापरून जलद कमी-प्रकाश सुधारणा. + पावसाच्या रेषा आणि ओल्या हवामानातील कलाकृती कमी करण्यासाठी पुनर्संचयित प्रतिमा पुनर्संचयित मॉडेल. + दस्तऐवज अनवारिंग मॉडेल जे एका समन्वय ग्रिडचा अंदाज लावते आणि वक्र पृष्ठांचे फ्लॅटर स्कॅनमध्ये रीमॅप करते. + गती कालावधी स्केल + ॲप ॲनिमेशन गती नियंत्रित करते: 0 गती अक्षम करते, 1 सामान्य आहे, उच्च मूल्ये ॲनिमेशन कमी करतात + अलीकडील साधने दाखवा + मुख्य स्क्रीनवर अलीकडे वापरलेल्या साधनांचा द्रुत प्रवेश प्रदर्शित करा + वापर आकडेवारी + ॲप उघडते, टूल लॉन्च होते आणि तुमची सर्वाधिक वापरलेली साधने एक्सप्लोर करा + ॲप उघडतो + साधन उघडते + शेवटचे साधन + यशस्वी बचत + क्रियाकलाप स्ट्रीक + डेटा जतन केला + शीर्ष स्वरूप + साधने वापरली + %1$d उघडेल • %2$s + अद्याप वापराची आकडेवारी नाही + काही साधने उघडा आणि ती येथे आपोआप दिसून येतील + तुमची वापर आकडेवारी तुमच्या डिव्हाइसवर फक्त स्थानिक पातळीवर संग्रहित केली जाते. इमेजटूलबॉक्स त्यांचा वापर फक्त तुमची वैयक्तिक गतिविधी, आवडती साधने आणि सेव्ह केलेला डेटा इनसाइट दर्शविण्यासाठी करते + आकार बदला रूपांतर स्केल रिझोल्यूशन परिमाणे रुंदी उंची jpg jpeg png वेबपी कॉम्प्रेस + कॉम्प्रेस आकार वजन बाइट्स mb kb गुणवत्ता ऑप्टिमाइझ कमी करा + पार्श्वभूमी रिमूव्हर मिटवा कटआउट पारदर्शक अल्फा काढा + exif मेटाडेटा गोपनीयता पट्टी साफ जीपीएस स्थान काढा + स्वरूप रूपांतरित jpg jpeg png webp heic avif jxl bmp + वेबपी रूपांतरित ॲनिमेटेड प्रतिमा स्वरूप + बेस64 एन्कोड डीकोड डेटा यूआरआय + चेकसम हॅश md5 sha sha1 sha256 सत्यापित करा + वॉलपेपर निर्यात पार्श्वभूमी + शेडर जीएलएसएल फ्रॅगमेंट इफेक्ट स्टुडिओ + pdf पृष्ठे फिरवा + pdf पानांची क्रमवारी पुनर्रचना करा + pdf पृष्ठ क्रमांक पृष्ठांकन + पीडीएफ ओसीआर मजकूर शोधण्यायोग्य अर्क ओळखा + पीडीएफ वॉटरमार्क स्टॅम्प लोगो मजकूर + पीडीएफ कॉम्प्रेस कमी आकार ऑप्टिमाइझ करा + pdf मेटाडेटा गुणधर्म exif लेखक शीर्षक + pdf सपाट भाष्ये स्तर तयार करतात + प्रतिमा pdf मध्ये रूपांतरित करा jpg jpeg png दस्तऐवज + प्रतिमा पृष्ठांवर pdf निर्यात jpg png + तटस्थ प्रकार + त्रुटी + सावली सोडा + दात उंची + क्षैतिज दात श्रेणी + अनुलंब दात श्रेणी + टॉप एज + उजवी किनार + तळाशी कडा + डावा किनारा + फाटलेला कडा + संपादक संपादन फोटो चित्र रीटच समायोजित + क्रॉप ट्रिम कट आस्पेक्ट रेशो + फिल्टर प्रभाव रंग सुधारणा समायोजित lut मुखवटा + पेंट ब्रश पेन्सिल भाष्य मार्कअप काढा + सायफर एनक्रिप्ट डिक्रिप्ट पासवर्ड गुप्त + पूर्वावलोकन दर्शक गॅलरी तपासणी उघडा + स्टिच मर्ज पॅनोरामा लाँग स्क्रीनशॉट एकत्र करा + url वेब डाउनलोड इंटरनेट लिंक प्रतिमा + पिकर आयड्रॉपर नमुना हेक्स आरजीबी रंग + पॅलेट रंग swatches प्राबल्य अर्क + नंतरच्या आधी भिन्न फरकाची तुलना करा + मर्यादा आकार बदला कमाल किमान मेगापिक्सेल परिमाण क्लॅम्प + पीडीएफ दस्तऐवज पृष्ठे साधने + ocr मजकूर ओळख अर्क स्कॅन + ग्रेडियंट पार्श्वभूमी रंग जाळी + वॉटरमार्क लोगो मजकूर स्टॅम्प आच्छादन + gif ॲनिमेशन ॲनिमेटेड रूपांतरित फ्रेम्स + apng ॲनिमेशन ॲनिमेटेड png रूपांतर फ्रेम + zip आर्काइव्ह कॉम्प्रेस पॅक अनपॅक फाइल्स + jxl jpeg xl रूपांतरित jpg प्रतिमा स्वरूप + svg वेक्टर ट्रेस रूपांतरित xml + दस्तऐवज स्कॅनर स्कॅन पेपर दृष्टीकोन पीक + क्यूआर बारकोड कोड स्कॅनर स्कॅन + स्टॅकिंग आच्छादन सरासरी मध्यवर्ती खगोल छायाचित्रण + स्प्लिट टाइल्स ग्रिड स्लाइस भाग + कलर कन्व्हर्ट हेक्स आरजीबी एचएसएल एचएसव्ही सीएमआयके लॅब + आवाज जनरेटर यादृच्छिक पोत धान्य + कोलाज ग्रिड लेआउट एकत्र + मार्कअप स्तर भाष्य आच्छादन संपादन + जाळीदार ग्रेडियंट पार्श्वभूमी + exif मेटाडेटा संपादित करा जीपीएस तारीख कॅमेरा + विभाजित ग्रिड तुकडे फरशा + ऑडिओ कव्हर अल्बम कला संगीत मेटाडेटा + ascii मजकूर कला वर्ण + एआय एमएल न्यूरल एन्हांस अपस्केल सेगमेंट + कलर लायब्ररी मटेरियल पॅलेट नावाचे रंग + pdf मर्ज एकत्र करा + पीडीएफ स्वतंत्र पृष्ठे विभाजित करा + pdf स्वाक्षरी चिन्ह काढा + पीडीएफ पासवर्ड एन्क्रिप्ट लॉक संरक्षित करा + pdf अनलॉक पासवर्ड डिक्रिप्ट संरक्षण काढून टाका + पीडीएफ ग्रेस्केल काळा पांढरा मोनोक्रोम + पीडीएफ दुरुस्ती दुरुस्त पुनर्प्राप्ती तुटलेली + pdf पाने हटवा + पीडीएफ क्रॉप ट्रिम मार्जिन + pdf चित्रे काढा + पीडीएफ झिप आर्काइव्ह कॉम्प्रेस + पीडीएफ प्रिंट प्रिंटर + pdf पूर्वावलोकन दर्शक वाचले + pdf भाष्य टिप्पण्या स्वच्छ काढा + वापर आकडेवारी रीसेट करा + टूल उघडेल, आकडेवारी जतन करा, स्ट्रीक, जतन केलेला डेटा आणि शीर्ष स्वरूप रीसेट केले जाईल. ॲप उघडेल ते अपरिवर्तित राहील. + ऑडिओ + फाईल + प्रोफाइल निर्यात करा + वर्तमान प्रोफाइल जतन करा + निर्यात प्रोफाइल हटवा + तुम्ही एक्सपोर्ट प्रोफाईल \\"%1$s\\" हटवू इच्छिता? + सीमा रेखाटणे + रेखांकन आणि कॅनव्हास मिटवण्याभोवती एक पातळ सीमा दर्शवा + लहरी + गोलाकार UI घटक मऊ लहरी काठासह + स्कूप + खाच + आतील बाजूस कोरलेले गोलाकार कोपरे + दुहेरी कट सह चरणबद्ध कोपरे + प्लेसहोल्डर + विस्ताराशिवाय मूळ फाइलनाव घालण्यासाठी {filename} वापरा + भडकणे + मूळ रक्कम + रिंग रक्कम + किरण रक्कम + रिंग रुंदी + दृष्टीकोन विकृत करा + जावा लुक आणि फील + कातरणे + X कोन + आणि कोन + आउटपुटचा आकार बदला + पाण्याचा थेंब + तरंगलांबी + टप्पा + उच्च पास + कलर मास्क + क्रोम + विरघळणे + कोमलता + अभिप्राय + लेन्स ब्लर + कमी इनपुट + उच्च इनपुट + कमी आउटपुट + उच्च आउटपुट + प्रकाश प्रभाव + दणका उंची + दणका मऊपणा + तरंग + एक्स मोठेपणा + Y मोठेपणा + X तरंगलांबी + Y तरंगलांबी + अनुकूली अस्पष्टता + पोत निर्मिती + विविध प्रक्रियात्मक पोत तयार करा आणि त्यांना प्रतिमा म्हणून जतन करा + पोत प्रकार + पक्षपात + वेळ + चमकणे + फैलाव + नमुने + कोन गुणांक + ग्रेडियंट गुणांक + ग्रिड प्रकार + अंतर शक्ती + स्केल Y + अस्पष्टता + आधार प्रकार + अशांतता घटक + स्केलिंग + पुनरावृत्ती + रिंग्ज + ब्रश केलेले धातू + कॉस्टिक्स + सेल्युलर + चेकरबोर्ड + fBm + संगमरवरी + प्लाझ्मा + रजाई + लाकूड + यादृच्छिक + चौरस + षटकोनी + अष्टकोनी + त्रिकोणी + खडखडाट + VL आवाज + एससी आवाज + अलीकडील + अद्याप कोणतेही अलीकडे वापरलेले फिल्टर नाहीत + टेक्सचर जनरेटर ब्रश मेटल कॉस्टिक्स सेल्युलर चेकबोर्ड मार्बल प्लाझ्मा रजाई लाकूड वीट छलावरण सेल क्लाउड क्रॅक फॅब्रिक पर्णसंभार हनीकॉम्ब बर्फ लावा नेबुला पेपर गंज वाळूचा धूर दगड भूप्रदेश स्थलाकृति पाणी तरंग + वीट + क्लृप्ती + सेल + ढग + क्रॅक + फॅब्रिक + पर्णसंभार + मधाची पोळी + बर्फ + लावा + नेबुला + कागद + गंज + वाळू + धूर + दगड + भूप्रदेश + टोपोग्राफी + पाण्याची लहर + प्रगत लाकूड + मोर्टार रुंदी + अनियमितता + बेवेल + उग्रपणा + पहिला उंबरठा + दुसरा उंबरठा + तिसरा उंबरठा + काठ कोमलता + सीमा रुंदी + कव्हरेज + तपशील + खोली + शाखा + आडवे धागे + अनुलंब धागे + फज + शिरा + प्रकाशयोजना + क्रॅक रुंदी + दंव + प्रवाह + कवच + ढग घनता + तारे + फायबर घनता + फायबर ताकद + डाग + गंज + पिटिंग + फ्लेक्स + ढिगारा वारंवारता + वारा कोन + तरंग + विस्प्स + शिरा स्केल + पाण्याची पातळी + पर्वत पातळी + धूप + बर्फ पातळी + ओळींची संख्या + रेषेची जाडी + शेडिंग + छिद्र रंग + मोर्टार रंग + गडद विटांचा रंग + वीट रंग + रंग हायलाइट करा + गडद रंग + जंगलाचा रंग + पृथ्वीचा रंग + वाळूचा रंग + पार्श्वभूमी रंग + सेल रंग + कडा रंग + आकाशी रंग + सावलीचा रंग + हलका रंग + पृष्ठभाग रंग + भिन्नता रंग + क्रॅक रंग + ताना रंग + वेफ्ट रंग + गडद पानांचा रंग + पानांचा रंग + सीमा रंग + मधाचा रंग + खोल रंग + बर्फाचा रंग + दंव रंग + कवच रंग + रंग धुवा + चमकणारा रंग + अंतराळ रंग + व्हायलेट रंग + निळा रंग + बेस रंग + फायबर रंग + डाग रंग + धातूचा रंग + गडद गंज रंग + गंज रंग + केशरी रंग + धुराचा रंग + शिरा रंग + सखल प्रदेशाचा रंग + पाण्याचा रंग + रॉक रंग + बर्फाचा रंग + कमी रंग + उच्च रंग + रेषा रंग + उथळ रंग + गवत + घाण + लेदर + काँक्रीट + डांबर + शेवाळ + आग + अरोरा + तेल स्लिक + जलरंग + अमूर्त प्रवाह + ओपल + दमास्कस स्टील + विजा + मखमली + इंक मार्बलिंग + होलोग्राफिक फॉइल + बायोल्युमिनेसन्स + कॉस्मिक व्होर्टेक्स + लावा दिवा + इव्हेंट होरायझन + फ्रॅक्टल ब्लूम + रंगीत बोगदा + ग्रहण कोरोना + विचित्र आकर्षक + फेरोफ्लुइड मुकुट + सुपरनोव्हा + बुबुळ + मोराचे पंख + नॉटिलस शेल + रिंग्ड प्लॅनेट + ब्लेड घनता + ब्लेडची लांबी + वारा + पॅचनेस + गुठळ्या + ओलावा + खडे + सुरकुत्या + छिद्र + एकूण + तडे + तार + परिधान करा + ठिपके + तंतू + ज्वाला वारंवारता + धूर + तीव्रता + फिती + बँड + इंद्रधनुष्य + अंधार + फुलतो + रंगद्रव्य + कडा + कागद + प्रसार + सममिती + तीक्ष्णपणा + रंग खेळ + दुधाळपणा + स्तर + फोल्डिंग + पोलिश + शाखा + दिशा + शीन + पट + फेदरिंग + शाई शिल्लक + स्पेक्ट्रम + क्रिंकल्स + विवर्तन + शस्त्र + ट्विस्ट + कोर ग्लो + ब्लॉब्स + डिस्क टिल्ट + क्षितिज आकार + डिस्क रुंदी + लेन्सिंग + पाकळ्या + कर्ल + फिलीग्री + पैलू + वक्रता + चंद्राचा आकार + कोरोनाचा आकार + किरण + डायमंड रिंग + लोब्स + कक्षा घनता + जाडी + स्पाइक्स + स्पाइक लांबी + शरीराचा आकार + धातूचा + शॉक त्रिज्या + शेल रुंदी + बाहेर फेकले + विद्यार्थ्याचा आकार + बुबुळ आकार + रंग भिन्नता + कॅचलाइट + डोळ्यांचा आकार + बार्ब घनता + वळते + चेंबर्स + उघडत आहे + कड्या + मोती + ग्रह आकार + रिंग टिल्ट + रिंग रुंदी + वातावरण + घाण रंग + गडद गवत रंग + गवताचा रंग + टीप रंग + गडद पृथ्वी रंग + कोरडा रंग + खडे रंग + लेदर रंग + कंक्रीट रंग + टार रंग + डांबरी रंग + दगडाचा रंग + धुळीचा रंग + मातीचा रंग + गडद मॉस रंग + शेवाळ रंग + लाल रंग + कोर रंग + हिरवा रंग + निळसर रंग + किरमिजी रंग + सोनेरी रंग + कागदाचा रंग + रंगद्रव्य रंग + दुय्यम रंग + पहिला रंग + दुसरा रंग + गुलाबी रंग + गडद स्टील रंग + स्टील रंग + हलका स्टील रंग + ऑक्साईड रंग + हेलो रंग + बोल्ट रंग + मखमली रंग + चमकणारा रंग + निळा शाई रंग + लाल शाईचा रंग + गडद शाई रंग + चांदीचा रंग + पिवळा रंग + ऊतींचा रंग + डिस्क रंग + गरम रंग + लेन्सचा रंग + बाह्य रंग + आतील रंग + कोरोना रंग + थंड रंग + उबदार रंग + ढग रंग + ज्योत रंग + पंख रंग + शेल रंग + मोत्याचा रंग + ग्रह रंग + रिंग रंग + सेव्ह केल्यानंतर मूळ फाइल्स हटवा + केवळ यशस्वीरित्या प्रक्रिया केलेल्या स्त्रोत फायली हटविल्या जातील + मूळ फायली हटवल्या: %1$d + मूळ फाइल हटवण्यात अयशस्वी: %1$d + मूळ फाइल हटवल्या: %1$d, अयशस्वी: %2$d + पत्रक जेश्चर + विस्तारित पर्याय पत्रके ड्रॅग करण्यास अनुमती द्या + क्रोमा सबसॅम्पलिंग + थोडी खोली + दोषरहित + %1$d-बिट + बॅचचे नाव बदला + फाइलनाव पॅटर्न वापरून एकाधिक फाइल्सचे नाव बदला + बॅच फाइल्सचे नाव बदला फाइलनाव नमुना क्रम तारीख विस्तार + नाव बदलण्यासाठी फायली निवडा + फाइलनाव नमुना + नाव बदला + तारीख स्रोत + EXIF तारीख घेतली + फाइल सुधारित तारीख + फाइल तयार करण्याची तारीख + सध्याची तारीख + मॅन्युअल तारीख + मॅन्युअल तारीख + मॅन्युअल वेळ + निवडलेली तारीख काही फाइल्ससाठी अनुपलब्ध आहे. सध्याची तारीख त्यांच्यासाठी वापरली जाईल. + फाइलनाव नमुना प्रविष्ट करा + नमुना अवैध किंवा जास्त लांब फाइलनाव तयार करतो + नमुना डुप्लिकेट फाइलनावे तयार करतो + सर्व फाइलनावे आधीपासून पॅटर्नशी जुळतात + हा नमुना टोकन वापरतो जे बॅच पुनर्नामित करण्यासाठी उपलब्ध नाहीत + नाव तेच राहील + फायली यशस्वीरित्या पुनर्नामित केल्या + काही फाइल्सचे नाव बदलता आले नाही + परवानगी मिळाली नाही + मूळ फाइल नाव विस्ताराशिवाय वापरते, तुम्हाला स्त्रोत ओळख अबाधित ठेवण्यास मदत करते. \\o{start:end} सह स्लाइसिंग, \\o{t} सह लिप्यंतरण आणि \\o{s/old/new/} सह बदलण्यास देखील समर्थन देते. + स्वयं-वाढीव काउंटर. कस्टम फॉरमॅटिंगला सपोर्ट करते: \\c{padding} (उदा. \\c{3} -&gt; 001), \\c{start:step} किंवा \\c{start:step:padding}. + मूळ फाईल जिथे होती त्या मूळ फोल्डरचे नाव. + मूळ फाइलचा आकार. एककांना सपोर्ट करते: \\z{b}, \\z{kb}, \\z{mb} किंवा फक्त \\z मानवी वाचनीय फॉरमॅटसाठी. + फाइलनावाची विशिष्टता सुनिश्चित करण्यासाठी यादृच्छिक युनिव्हर्सली युनिक आयडेंटिफायर (UUID) व्युत्पन्न करते. + फायलींचे नाव बदलणे पूर्ववत केले जाऊ शकत नाही. सुरू ठेवण्यापूर्वी नवीन नावे बरोबर असल्याची खात्री करा. + नाव बदलण्याची पुष्टी करा + साइड मेनू सेटिंग्ज + मेनू स्केल + मेनू अल्फा + ऑटो व्हाइट बॅलन्स + क्लिपिंग + लपलेले वॉटरमार्क तपासत आहे + स्टोरेज + कॅशे मर्यादा + कॅशे या आकाराच्या पलीकडे वाढल्यावर स्वयंचलितपणे साफ करा + साफसफाईचे अंतराल + कॅशे साफ करणे आवश्यक आहे की नाही हे किती वेळा तपासावे + ॲप लाँच करताना + 1 दिवस + 1 आठवडा + 1 महिना + निवडलेल्या मर्यादा आणि मध्यांतरानुसार ॲप कॅशे स्वयंचलितपणे साफ करा + जंगम पीक क्षेत्र + संपूर्ण पीक क्षेत्र आत ड्रॅग करून हलविण्यास अनुमती देते + GIF मर्ज करा + एका ॲनिमेशनमध्ये अनेक GIF क्लिप एकत्र करा + GIF क्लिप ऑर्डर + उलट + उलट खेळा + बूमरँग + पुढे खेळा आणि नंतर मागे + फ्रेम आकार सामान्य करा + सर्वात मोठी क्लिप फिट करण्यासाठी स्केल फ्रेम; त्यांचे मूळ स्केल जतन करण्यासाठी बंद करा + क्लिप दरम्यान विलंब (ms) + फोल्डर + फोल्डर आणि त्याच्या सबफोल्डर्समधून सर्व प्रतिमा निवडा + डुप्लिकेट शोधक + अचूक प्रती आणि दृष्यदृष्ट्या समान प्रतिमा शोधा + डुप्लिकेट फाइंडर समान प्रतिमा अचूक कॉपी फोटो क्लीनअप स्टोरेज dHash SHA-256 + डुप्लिकेट शोधण्यासाठी प्रतिमा निवडा + समानता संवेदनशीलता + अचूक प्रती + तत्सम प्रतिमा + सर्व अचूक डुप्लिकेट निवडा + शिफारस केलेले वगळता सर्व निवडा + कोणतेही डुप्लिकेट आढळले नाहीत + अधिक प्रतिमा जोडण्याचा प्रयत्न करा किंवा समानतेची संवेदनशीलता वाढवा + %1$d प्रतिमा वाचू शकलो नाही + %1$s • %2$s पुन्हा दावा करण्यायोग्य + %1$d गट • %2$s पुन्हा दावा करण्यायोग्य + %1$d निवडले • %2$s + हे पूर्ववत केले जाऊ शकत नाही. Android तुम्हाला सिस्टम डायलॉगमध्ये हटवण्याची पुष्टी करण्यास सांगू शकते. + सापडले नाही + सुरू करण्यासाठी हलवा + शेवटी हलवा + \ No newline at end of file diff --git a/core/resources/src/main/res/values-night-v31/colors.xml b/core/resources/src/main/res/values-night-v31/colors.xml new file mode 100644 index 0000000..bc1f3d4 --- /dev/null +++ b/core/resources/src/main/res/values-night-v31/colors.xml @@ -0,0 +1,21 @@ + + + + @android:color/system_accent2_800 + @android:color/system_accent1_100 + \ No newline at end of file diff --git a/core/resources/src/main/res/values-night/colors.xml b/core/resources/src/main/res/values-night/colors.xml new file mode 100644 index 0000000..60f25b1 --- /dev/null +++ b/core/resources/src/main/res/values-night/colors.xml @@ -0,0 +1,21 @@ + + + + #2A3025 + #ABE87C + \ No newline at end of file diff --git a/core/resources/src/main/res/values-nl/strings.xml b/core/resources/src/main/res/values-nl/strings.xml new file mode 100644 index 0000000..b5a819d --- /dev/null +++ b/core/resources/src/main/res/values-nl/strings.xml @@ -0,0 +1,3739 @@ + + + + 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 + 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 + 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. + 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. + 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 + Inhoudsbewuste vervorming + Onvoldoende geheugen om deze actie te voltooien. Probeer een kleinere afbeelding te gebruiken, andere apps te sluiten of de app opnieuw te starten. + Parallelle werkers + Deze afbeeldingen zijn niet opgeslagen omdat de geconverteerde bestanden groter zouden zijn dan de originelen. Controleer deze lijst voordat u originele afbeeldingen verwijdert. + Bestanden overgeslagen: %1$s + App-logboeken + Bekijk logboeken van de app om de problemen op te sporen + Favorieten in gegroepeerde tools + Voegt favorieten toe als tabblad wanneer tools op type zijn gegroepeerd + Toon favoriet als laatste + Verplaatst het tabblad met favoriete hulpmiddelen naar het einde + Horizontale afstand + Verticale afstand + Achterwaartse energie + Gebruik een eenvoudige energiekaart met gradiëntgrootte in plaats van een voorwaarts energiealgoritme + Verwijder het gemaskeerde gebied + De naden gaan door het gemaskeerde gebied en verwijderen alleen het geselecteerde gebied in plaats van het te beschermen + VHS NTSC + Geavanceerde NTSC-instellingen + Extra analoge afstemming voor sterkere VHS- en uitzendartefacten + Chroma-bloeding + Slijtage van tape + Volgen + Luma-uitstrijkje + Bellen + Sneeuw + Gebruik veld + Filtertype + Invoer lumafilter + Ingangschroma laagdoorlaat + Chroma-demodulatie + Faseverschuiving + Fase-offset + Hoofd wisselen + Hoofdschakelhoogte + Offset kopschakeling + Hoofdwisseling + Middenlijn positie + Jitter in de middenlijn + Geluidshoogte volgen + Golf volgen + Sneeuw volgen + Sneeuwanisotropie volgen + Volggeluid + Geluidsintensiteit volgen + Samengesteld geluid + Samengestelde geluidsfrequentie + Samengestelde geluidsintensiteit + Samengesteld geluidsdetail + Belfrequentie + Bellende kracht + Luma-geluid + Luma-ruisfrequentie + Intensiteit van Luma-geluid + Detail van Luma-ruis + Chroma-ruis + Chroma-ruisfrequentie + Intensiteit van chromaruis + Chromaruisdetail + Sneeuwintensiteit + Sneeuwanisotropie + Chromafaseruis + Chromafasefout + Chromavertraging horizontaal + Chromavertraging verticaal + VHS-bandsnelheid + VHS-chromaverlies + VHS verscherpt de intensiteit + VHS verscherpt frequentie + VHS-randgolfintensiteit + VHS-randgolfsnelheid + VHS-randgolffrequentie + VHS-randgolfdetail + Uitgangschroma laagdoorlaat + Schaal horizontaal + Schaal verticaal + Schaalfactor X + Schaalfactor Y + Detecteert veelvoorkomende afbeeldingswatermerken en kleurt deze in met LaMa. Downloadt automatisch detectie- en inpainting-modellen + Deuteranopie + Vouw afbeelding uit + Op objectsegmentatie gebaseerde achtergrondverwijderaar met behulp van YOLO v11-segmentatie + Toon lijnhoek + Toont de huidige lijnrotatie in graden tijdens het tekenen + Geanimeerde emoji\'s + Toon beschikbare emoji als animaties + Opslaan in originele map + Sla nieuwe bestanden op naast het originele bestand in plaats van de geselecteerde map + Watermerk-pdf + Niet genoeg geheugen + De afbeelding is waarschijnlijk te groot om op dit apparaat te verwerken, of het systeem heeft onvoldoende beschikbaar geheugen. Probeer de beeldresolutie te verlagen, andere apps te sluiten of een kleiner bestand te kiezen. + Foutopsporingsmenu + Menu om app-functies te testen, dit is niet bedoeld om te verschijnen in de productierelease + Aanbieder + PaddleOCR vereist extra ONNX-modellen op uw apparaat. Wilt u %1$s gegevens downloaden? + Universeel + Koreaans + Latijns + Oost-Slavisch + Thais + Grieks + Engels + Cyrillisch + Arabisch + Devanagari + Tamil + Telugu + Schaduw + Voorinstelling voor arcering + Geen arcering geselecteerd + Schaduwstudio + Aangepaste fragmentshaders maken, bewerken, valideren, importeren en exporteren + Opgeslagen shaders + Open opgeslagen shaders, dupliceer, exporteer, deel of verwijder + Nieuwe schaduw + Shader-bestand + .itshader JSON + %1$d parameters + Shader-bron + Schrijf alleen de hoofdtekst van void main(). Gebruik textureCominate voor UV-coördinaten en voerImageTexture in als bronsampler. + Functies + Optionele helperfuncties, constanten en structuren ingevoegd vóór void main(). Uniformen worden gegenereerd op basis van params. + Voeg hier uniformen toe als de arcering bewerkbare waarden nodig heeft. + Arcering opnieuw instellen + Het huidige shader-concept wordt gewist + Ongeldige arcering + Niet-ondersteunde shader-versie %1$d. Ondersteunde versie: %2$d. + De naam van de shader mag niet leeg zijn. + De arceringsbron mag niet leeg zijn. + De arceringsbron bevat niet-ondersteunde tekens: %1$s. Gebruik alleen ASCII GLSL-bron. + Parameter \\\"%1$s\\\" wordt meer dan één keer gedeclareerd. + Parameternamen mogen niet leeg zijn. + Parameter \\\"%1$s\\\" gebruikt een gereserveerde GPUImage-naam. + Parameter \\\"%1$s\\\" moet een geldige GLSL-identificatie zijn. + Parameter \\\"%1$s\\\" heeft %2$s waardetype \\\"%3$s\\\", verwacht \\\"%4$s\\\". + Parameter \\\"%1$s\\\" kan min of max voor boolwaarden niet definiëren. + Parameter \\\"%1$s\\\" heeft min groter dan max. + Parameter \\\"%1$s\\\" standaard is lager dan min. + Parameter \\\"%1$s\\\" standaard is groter dan max. + Shader moet \\\"uniforme sampler2D %1$s;\\\" declareren. + Shader moet \\\"variërende vec2 %1$s;\\\" declareren. + Shader moet \\\"void main()\\\" definiëren. + Shader moet een kleur naar gl_FragColor schrijven. + ShaderToy mainImage-shaders worden niet ondersteund. Gebruik het void main()-contract van GPUImage. + Geanimeerd ShaderToy-uniform \\\"iTime\\\" wordt niet ondersteund. + Geanimeerd ShaderToy-uniform \\\"iFrame\\\" wordt niet ondersteund. + ShaderToy-uniform \\\"iResolution\\\" wordt niet ondersteund. + ShaderToy iChannel-texturen worden niet ondersteund. + Externe texturen worden niet ondersteund. + Externe textuurextensies worden niet ondersteund. + Libretro-shaderparameters worden niet ondersteund. + Er wordt slechts één invoertextuur ondersteund. Verwijder \\\"uniform %1$s %2$s;\\\". + Parameter \\\"%1$s\\\" moet worden gedeclareerd als \\\"uniform %2$s %1$s;\\\". + Parameter \\\"%1$s\\\" wordt gedeclareerd als \\\"uniform %2$s %1$s;\\\", verwacht \\\"uniform %3$s %1$s;\\\". + Shader-bestand is leeg. + Het arceringsbestand moet een .itshader JSON-object zijn met versie-, naam- en arceringsvelden. + Het Shader-bestand is geen geldige JSON of komt niet overeen met de .itshader-indeling. + Veld \\\"%1$s\\\" is verplicht. + %1$s is vereist. + %1$s \\\"%2$s\\\" wordt niet ondersteund. Ondersteunde typen: %3$s. + %1$s is vereist voor \\\"%2$s\\\" parameters. + %1$s moet een eindig getal zijn. + %1$s moet een geheel getal zijn. + %1$s moet waar of onwaar zijn. + %1$s moet een kleurreeks, RGB/RGBA-array of kleurobject zijn. + %1$s moet de indeling #RRGGBB of #RRGGBBAA gebruiken. + %1$s moet geldige hexadecimale kleurkanalen bevatten. + %1$s moet 3 of 4 kleurkanalen bevatten. + %1$s moet tussen 0 en 255 liggen. + %1$s moet een array met twee getallen zijn of een object met x en y. + %1$s moet exact 2 cijfers bevatten. + Duplicaat + Wis EXIF ​​altijd + Verwijder EXIF-gegevens van afbeeldingen bij het opslaan, zelfs wanneer een tool vraagt ​​om het bewaren van metagegevens + Hulp & Tips + %1$d zelfstudies + Gereedschap openen + Leer de belangrijkste tools, exportopties, PDF-stromen, kleurhulpprogramma\'s en oplossingen voor veelvoorkomende problemen + Aan de slag + Kies tools, importeer bestanden, sla resultaten op en hergebruik instellingen sneller + Beeldbewerking + Formaat wijzigen, bijsnijden, filteren, achtergronden wissen, afbeeldingen tekenen en watermerken + Bestanden en metagegevens + Converteer formaten, vergelijk uitvoer, bescherm privacy en beheer bestandsnamen + PDF en documenten + Maak PDF\'s, scan documenten, OCR-pagina\'s en bereid bestanden voor om te delen + Tekst, QR en gegevens + Herken tekst, scan codes, codeer Base64, controleer hashes en verpak bestanden + Kleur gereedschap + Kies kleuren, bouw paletten, verken kleurenbibliotheken en maak verlopen + Creatieve hulpmiddelen + Genereer SVG, ASCII-kunst, ruistexturen, shaders en experimentele beelden + Problemen oplossen + Los problemen met zware bestanden, transparantie, delen, importeren en rapporteren op + Kies het juiste gereedschap + Gebruik categorieën, zoek-, favorieten- en deelacties om op de juiste plek te beginnen + Begin vanaf het beste toegangspunt + Image Toolbox heeft veel gerichte hulpmiddelen, en veel daarvan overlappen elkaar op het eerste gezicht. Begin met de taak die u wilt voltooien en kies vervolgens de tool die het belangrijkste deel van het resultaat bepaalt: grootte, formaat, metagegevens, tekst, PDF-pagina\'s, kleuren of lay-out. + Gebruik zoeken als u de actienaam kent, zoals formaat wijzigen, PDF, EXIF, OCR, QR of kleur. + Open een categorie als u alleen het taaktype kent en maak vervolgens veelgebruikte tools vast als favoriet. + Wanneer een andere app een afbeelding deelt in Image Toolbox, kiest u de tool uit de stroom voor het delen van werkbladen. + Resultaten importeren, opslaan en delen + Begrijp de gebruikelijke stroom vanaf het kiezen van invoer tot het exporteren van het bewerkte bestand + Volg de algemene bewerkingsstroom + De meeste tools volgen hetzelfde ritme: invoer kiezen, opties aanpassen, een voorbeeld bekijken en vervolgens opslaan of delen. Het belangrijke detail is dat bij het opslaan een uitvoerbestand wordt aangemaakt, terwijl delen meestal het beste is voor snelle overdracht naar een andere app. + Kies één bestand voor tools met één afbeelding of meerdere bestanden voor batchtools als de kiezer dit toestaat. + Controleer de voorbeeld- en uitvoeropties voordat u opslaat, vooral de opties voor formaat, kwaliteit en bestandsnaam. + Gebruik delen voor een snelle overdracht, of sla op als u een permanente kopie in de geselecteerde map nodig heeft. + Werk met veel afbeeldingen tegelijk + Batchtools zijn het beste voor herhaalde formaat-, conversie-, compressie- en naamgevingstaken + Bereid een voorspelbare batch voor + Batchverwerking gaat het snelst als elke uitvoer dezelfde regels moet volgen. Het is ook de gemakkelijkste plek om een ​​grote fout te maken, dus kies de naamgeving, kwaliteit, metagegevens en mapgedrag voordat u een lange export start. + Selecteer alle gerelateerde afbeeldingen samen en houd de volgorde aan als de uitvoer afhankelijk is van de volgorde. + Stel de regels voor formaat, formaat, kwaliteit, metagegevens en bestandsnaam één keer in voordat u met exporteren begint. + Voer eerst een kleine testbatch uit als de bronbestanden groot zijn of als het doelformaat nieuw voor u is. + Snelle enkele bewerking + Gebruik de alles-in-één editor als u meerdere eenvoudige wijzigingen in één afbeelding nodig heeft + Maak één afbeelding af zonder tool-hopping + Enkelvoudige bewerking is handig wanneer de afbeelding een kleine reeks wijzigingen nodig heeft in plaats van één gespecialiseerde bewerking. Het is meestal sneller voor snel opschonen, bekijken en exporteren van een definitieve kopie zonder een batchworkflow op te bouwen. + Open Enkelvoudige bewerking voor één afbeelding waarvoor algemene aanpassingen, markeringen, bijsnijden, rotatie of exportwijzigingen nodig zijn. + Pas bewerkingen toe in de volgorde waarbij eerst de minste pixels worden gewijzigd, zoals bijsnijden vóór filters en filters vóór de uiteindelijke compressie. + Gebruik in plaats daarvan een gespecialiseerde tool als u batchverwerking, exacte bestandsgroottedoelen, PDF-uitvoer, OCR of het opschonen van metagegevens nodig heeft. + Gebruik voorinstellingen en instellingen + Bewaar herhaalde keuzes en stem de app af op de workflow van uw voorkeur + Vermijd het herhalen van dezelfde configuratie + Voorinstellingen en instellingen zijn handig als u vaak hetzelfde soort bestand exporteert. Een goede preset verandert een herhaalde handmatige checklist in één tik, terwijl globale instellingen de standaardinstellingen definiëren waarmee nieuwe tools beginnen. + Maak voorinstellingen voor algemene uitvoerformaten, formaten, kwaliteitswaarden of naamgevingspatronen. + Controleer de instellingen voor standaardformaat, kwaliteit, opslagmap, bestandsnaam, kiezer en interfacegedrag. + Maak een back-up van de instellingen voordat u de app opnieuw installeert of naar een ander apparaat gaat. + Stel nuttige standaardinstellingen in + Kies één keer het standaardformaat, de kwaliteit, de schaalmodus, het formaatwijzigingstype en de kleurruimte + Zorg ervoor dat nieuwe tools dichter bij uw doel beginnen + Standaardwaarden zijn niet alleen cosmetische voorkeuren. Zij bepalen welk formaat, kwaliteit, formaatwijziging, schaalmodus en kleurruimte als eerste verschijnen in veel tools, waardoor wordt voorkomen dat bij elke export opnieuw dezelfde keuzes moeten worden geselecteerd. + Stel het standaard afbeeldingsformaat en de kwaliteit in zodat deze overeenkomen met uw gebruikelijke bestemming, zoals JPEG voor foto\'s of PNG/WebP voor alpha. + Stem het standaard formaatwijzigingstype en de schaalmodus af als u vaak exporteert voor strikte dimensies, sociale platforms of documentpagina\'s. + Wijzig de standaardinstellingen voor de kleurruimte alleen als uw workflow consistente kleurverwerking nodig heeft op beeldschermen, drukwerk of externe editors. + Picker en draagraket + Gebruik de kiezerinstellingen wanneer de app te veel dialoogvensters opent of op de verkeerde plaats start + Zorg ervoor dat het openen van bestanden aansluit bij uw gewoonte + De instellingen voor de kiezer en het opstartprogramma bepalen of Image Toolbox start vanuit het hoofdscherm, een tool of een bestandsselectiestroom. Deze opties zijn vooral handig als u meestal vanuit het Android-aandeelblad invoert of als u wilt dat de app meer op een tool-launcher lijkt. + Gebruik de instellingen voor de fotokiezer als de systeemkiezer bestanden verbergt, te veel recente items weergeeft of cloudbestanden slecht verwerkt. + Schakel het overslaan van selectie alleen in voor tools waarbij u gewoonlijk vanuit een gedeelde map invoert of het bronbestand al kent. + Bekijk de opstartmodus als u wilt dat Image Toolbox zich meer als een gereedschapskiezer gedraagt ​​dan als een normaal startscherm. + Afbeeldingsbron + Kies de kiezermodus die het beste werkt met galerijen, bestandsbeheerders en cloudbestanden + Kies bestanden uit de juiste bron + De instellingen voor de afbeeldingsbron zijn van invloed op de manier waarop de app Android om afbeeldingen vraagt. De beste keuze hangt af van of uw bestanden zich in de systeemgalerij, een map voor bestandsbeheer, een cloudprovider of een andere app bevinden die tijdelijke inhoud deelt. + Gebruik de standaardkiezer als u de meest systeemgeïntegreerde ervaring voor algemene galerijafbeeldingen wilt. + Schakel over naar de kiezermodus als albums, mappen, cloudbestanden of niet-standaardformaten niet op de verwachte plek verschijnen. + Als een gedeeld bestand verdwijnt nadat u de bron-app hebt verlaten, kunt u het opnieuw openen via een permanente kiezer of eerst een lokale kopie opslaan. + Favorieten en deelhulpmiddelen + Houd het hoofdscherm gefocust en verberg tools die geen zin hebben in deelstromen + Verminder de ruis van de gereedschapslijst + Favorieten en instellingen voor verborgen voor delen zijn handig als u slechts een paar tools per dag gebruikt. Ze houden de deelstroom ook praktisch door tools te verbergen die het type bestand niet kunnen gebruiken dat een andere app verzendt. + Zet veelgebruikte tools vast, zodat ze gemakkelijk bereikbaar blijven, zelfs als tools op categorie zijn gegroepeerd. + Verberg tools voor deelstromen als ze niet relevant zijn voor bestanden die uit een andere app komen. + Gebruik de favoriete bestelinstellingen als u de voorkeur geeft aan favorieten aan het einde of gegroepeerd met gerelateerde tools. + Maak een back-up van instellingen + Verplaats voorinstellingen, voorkeuren en app-gedrag veilig naar een andere installatie + Houd uw installatie draagbaar + Back-up en herstel zijn vooral nuttig nadat u voorinstellingen, mappen, bestandsnaamregels, gereedschapsvolgorde en visuele voorkeuren hebt afgestemd. Met een back-up kunt u experimenteren met instellingen of de app opnieuw installeren zonder uw workflow vanuit het geheugen opnieuw op te bouwen. + Maak een back-up na het wijzigen van presets, bestandsnaamgedrag, standaardformaten of toolindeling. + Bewaar de back-up ergens buiten de app-map als u van plan bent gegevens te wissen, apparaten opnieuw te installeren of te verplaatsen. + Herstel de instellingen voordat u belangrijke batches verwerkt, zodat de standaardinstellingen en het opslaggedrag overeenkomen met uw oude instellingen. + Formaat wijzigen en afbeeldingen converteren + Wijzig de grootte, het formaat, de kwaliteit en de metagegevens voor een of meer afbeeldingen + Kopieën met aangepast formaat maken + Formaat wijzigen en converteren is het belangrijkste hulpmiddel voor voorspelbare afmetingen en lichtere uitvoerbestanden. Gebruik het wanneer de grootte van de afbeelding, het uitvoerformaat en het gedrag van de metagegevens er allemaal tegelijkertijd toe doen. + Kies een of meer afbeeldingen en kies vervolgens of afmetingen, schaal of bestandsgrootte het belangrijkst zijn. + Selecteer het uitvoerformaat en de kwaliteit; gebruik PNG of WebP wanneer de transparantie behouden moet blijven. + Bekijk een voorbeeld van het resultaat en bewaar of deel de gegenereerde kopieën zonder de originelen te wijzigen. + Formaat wijzigen op bestandsgrootte + Streef naar een maximale grootte wanneer een website, messenger of formulier zware afbeeldingen weigert + Zorg voor strikte uploadlimieten + Het formaat wijzigen op basis van de bestandsgrootte is beter dan het raden van kwaliteitswaarden wanneer de bestemming alleen bestanden onder een specifieke limiet accepteert. De tool kan de compressie en afmetingen aanpassen om het doel te bereiken en tegelijkertijd het resultaat bruikbaar te houden. + Voer de doelgrootte in die iets onder de werkelijke uploadlimiet ligt, zodat er ruimte is voor wijzigingen in de metadata en provider. + Geef de voorkeur aan verliesgevende formaten voor foto\'s als het doel klein is; PNG kan groot blijven, zelfs nadat het formaat is gewijzigd. + Inspecteer vlakken, tekst en randen na het exporteren, omdat doelen met agressieve afmetingen zichtbare artefacten kunnen introduceren. + Beperk het formaat + Verklein alleen afbeeldingen die uw maximale breedte-, hoogte- of bestandsbeperkingen overschrijden + Vermijd het wijzigen van het formaat van afbeeldingen die al goed zijn + Beperk het formaat wijzigen is handig voor gemengde mappen waarin sommige afbeeldingen enorm groot zijn en andere al zijn voorbereid. In plaats van dat elk bestand hetzelfde formaat moet wijzigen, blijven acceptabele afbeeldingen dichter bij hun oorspronkelijke kwaliteit. + Stel maximale afmetingen of limieten in op basis van de bestemming, in plaats van elk bestand blindelings te verkleinen. + Schakel stijlgedrag overslaan als groter in als u wilt voorkomen dat uitvoer zwaarder wordt dan bronnen. + Gebruik dit voor batches van verschillende camera\'s, schermafbeeldingen of downloads waarbij de brongroottes sterk variëren. + Bijsnijden, roteren en rechttrekken + Kadreer het belangrijke gebied voordat u de afbeelding exporteert of in andere hulpmiddelen gebruikt + Maak de compositie schoon + Als u eerst bijsnijdt, worden latere filters, OCR, PDF-pagina\'s en het delen van resultaten schoner. + Open Bijsnijden en kies de afbeelding die je wilt aanpassen. + Gebruik gratis bijsnijden of een beeldverhouding wanneer de uitvoer in een bericht, document of avatar moet passen. + Roteer of spiegel voordat u het opslaat, zodat latere gereedschappen de gecorrigeerde afbeelding ontvangen. + Pas filters en aanpassingen toe + Bouw een bewerkbare filterstapel en bekijk een voorbeeld van het resultaat voordat u het exporteert + Beeldweergave afstemmen + Filters zijn handig voor snelle correcties, gestileerde uitvoer en het voorbereiden van afbeeldingen voor OCR of afdrukken. Kleine veranderingen in contrast, scherpte en helderheid kunnen belangrijker zijn dan zware effecten wanneer de afbeelding tekst of fijne details bevat. + Voeg filters één voor één toe en houd na elke wijziging het voorbeeld in de gaten. + Pas de helderheid, het contrast, de scherpte, de vervaging, de kleur of de maskers aan, afhankelijk van de taak. + Exporteer alleen als het voorbeeld overeenkomt met uw doelapparaat, document of sociaal platform. + Eerst een voorbeeld + Inspecteer afbeeldingen voordat u een zwaardere bewerkings-, conversie- of opruimtool kiest + Controleer wat u daadwerkelijk heeft ontvangen + Afbeeldingsvoorbeeld is handig als een bestand afkomstig is uit chat, cloudopslag of een andere app en u niet zeker weet wat het bevat. Het eerst controleren van afmetingen, transparantie, oriëntatie en visuele kwaliteit helpt bij het kiezen van de juiste vervolgtool. + Open Afbeeldingsvoorbeeld als u meerdere afbeeldingen wilt inspecteren voordat u besluit wat u ermee gaat doen. + Zoek naar rotatieproblemen, transparante gebieden, compressieartefacten en onverwacht grote afmetingen. + Ga pas naar Formaat wijzigen, Bijsnijden, Vergelijken, EXIF ​​of Achtergrond verwijderen nadat u weet wat moet worden opgelost. + Verwijder of herstel een achtergrond + Wis achtergronden automatisch of verfijn randen handmatig met hersteltools + Behoud het onderwerp, verwijder de rest + Achtergrondverwijdering werkt het beste als u automatische selectie combineert met zorgvuldig handmatig opschonen. Het uiteindelijke exportformaat is net zo belangrijk als het masker, omdat JPEG transparante gebieden vlak maakt, zelfs als het voorbeeld er correct uitziet. + Begin met automatische verwijdering als het onderwerp duidelijk gescheiden is van de achtergrond. + Zoom in en schakel tussen wissen en herstellen om haar, hoeken, schaduwen en kleine details te corrigeren. + Bewaar als PNG of WebP als u transparantie in het uiteindelijke bestand nodig heeft. + Teken, markeer en watermerk + Voeg pijlen, notities, markeringen, handtekeningen of herhaalde watermerkoverlays toe + Voeg zichtbare informatie toe + Markeringstools helpen bij het uitleggen van een afbeelding, terwijl watermerken helpen bij het brandmerken of beschermen van geëxporteerde bestanden. + Gebruik Draw als u uit de vrije hand aantekeningen, vormen, markeringen of snelle annotaties nodig heeft. + Gebruik watermerken als dezelfde tekst of afbeelding consistent op de afdrukken moet verschijnen. + Controleer de dekking, positie en schaal voordat u exporteert, zodat het merkteken zichtbaar is maar niet afleidt. + Standaardwaarden tekenen + Stel de lijndikte, kleur, padmodus, oriëntatievergrendeling en vergrootglas in voor snellere markeringen + Zorg ervoor dat tekenen voorspelbaar aanvoelt + Tekeninstellingen zijn handig als u aantekeningen maakt op schermafbeeldingen, documenten markeert of afbeeldingen vaak ondertekent. Standaardinstellingen verkorten de insteltijd, terwijl het vergrootglas en de oriëntatievergrendeling nauwkeurige bewerkingen eenvoudiger maken op kleine schermen. + Stel de standaard lijndikte in en teken de kleur naar de waarden die u het meest gebruikt voor pijlen, markeringen of handtekeningen. + Gebruik de standaardpadmodus wanneer u meestal dezelfde soort lijnen, vormen of markeringen tekent. + Schakel een vergrootglas of oriëntatievergrendeling in wanneer vingerinvoer het moeilijk maakt om kleine details nauwkeurig te plaatsen. + Lay-outs met meerdere afbeeldingen + Kies de juiste tool voor meerdere afbeeldingen voordat u schermafbeeldingen, scans of vergelijkingsstroken rangschikt + Gebruik de juiste lay-outtool + Deze tools klinken hetzelfde, maar ze zijn allemaal afgestemd op een ander soort uitvoer van meerdere afbeeldingen. Als u eerst de juiste kiest, vermijdt u dat u moet vechten tegen lay-outbesturingselementen die zijn ontworpen voor een ander resultaat. + Gebruik Collage Maker voor ontworpen lay-outs met meerdere afbeeldingen in één compositie. + Gebruik Image Stitching of Stacking als afbeeldingen één lange verticale of horizontale strook moeten worden. + Gebruik afbeeldingssplitsing wanneer één grote afbeelding meerdere kleinere delen moet worden. + Converteer afbeeldingsformaten + Maak JPEG-, PNG-, WebP-, AVIF-, JXL- en andere kopieën zonder pixels handmatig te bewerken + Kies het formaat voor de taak + Verschillende formaten zijn beter voor foto\'s, schermafbeeldingen, transparantie, compressie of compatibiliteit. Conversie is het veiligst als u begrijpt wat de doelapp ondersteunt en of de bron alfa, animatie of metagegevens bevat die u wilt behouden. + Gebruik JPEG voor compatibele foto\'s, PNG voor verliesvrije afbeeldingen en alpha, en WebP voor compact delen. + Pas de kwaliteit aan wanneer het formaat dit ondersteunt; lagere waarden verkleinen de grootte, maar kunnen artefacten toevoegen. + Bewaar de originelen totdat u controleert of de geconverteerde bestanden correct zijn geopend in de doel-app. + Controleer EXIF ​​en privacy + Bekijk, bewerk of verwijder metagegevens voordat u gevoelige foto\'s deelt + Beheer verborgen afbeeldingsgegevens + EXIF kan cameragegevens, datums, locatie, oriëntatie en andere details bevatten die niet zichtbaar zijn in de afbeelding. Het is de moeite waard om dit te controleren voordat u deze openbaar deelt, ondersteuningsrapporten, marktplaatsvermeldingen of foto\'s die een privélocatie kunnen onthullen. + Open EXIF-tools voordat u foto\'s deelt die mogelijk locatie- of privéapparaatinformatie bevatten. + Verwijder gevoelige velden of bewerk onjuiste tags zoals datum, oriëntatie, auteur of cameragegevens. + Sla een nieuwe kopie op en deel die kopie in plaats van het origineel als privacy belangrijk is. + Automatische EXIF-opschoning + Verwijder standaard metagegevens terwijl u beslist of datums behouden moeten blijven + Maak privacy tot de standaard + De EXIF-instellingengroep is handig als u wilt dat het opschonen van de privacy automatisch gebeurt in plaats van dat u dit bij elke export onthoudt. Datum en tijd behouden is een afzonderlijke beslissing, omdat datums nuttig kunnen zijn bij het sorteren, maar gevoelig zijn voor delen. + Schakel EXIF ​​altijd wissen in als de meeste van uw exports bedoeld zijn voor openbaar of semi-openbaar delen. + Schakel Datum/tijd behouden alleen in als het behouden van de opnametijd belangrijker is dan het verwijderen van elke tijdstempel. + Gebruik handmatige EXIF-bewerking voor eenmalige bestanden waarbij u sommige velden moet behouden en andere moet verwijderen. + Beheer bestandsnamen + Gebruik voorvoegsels, achtervoegsels, tijdstempels, voorinstellingen, controlesommen en volgnummers + Zorg ervoor dat exporten gemakkelijk te vinden zijn + Een goed bestandsnaampatroon helpt bij het sorteren van batches en voorkomt onbedoeld overschrijven. Bestandsnaaminstellingen zijn vooral handig wanneer exports teruggaan naar de bronmap, waar soortgelijke namen snel verwarrend kunnen worden. + Stel het voorvoegsel, achtervoegsel, tijdstempel, oorspronkelijke naam, vooraf ingestelde informatie of reeksopties in voor de bestandsnaam in Instellingen. + Gebruik volgnummers voor batches waarbij de volgorde van belang is, zoals pagina\'s, kaders of collages. + Schakel overschrijven alleen in als u zeker weet dat het vervangen van bestaande bestanden veilig is voor de huidige map. + Bestanden overschrijven + Vervang uitvoer alleen door overeenkomende namen als uw workflow opzettelijk destructief is + Gebruik de overschrijfmodus zorgvuldig + Bestanden overschrijven verandert de manier waarop naamgevingsconflicten worden afgehandeld. Het is handig voor herhaalde exporten naar dezelfde map, maar het kan ook eerdere resultaten vervangen als uw bestandsnaampatroon opnieuw dezelfde naam oplevert. + Schakel overschrijven alleen in voor mappen waarin vervanging van oudere gegenereerde bestanden wordt verwacht en herstelbaar is. + Houd overschrijven uitgeschakeld bij het exporteren van originelen, clientbestanden, eenmalige scans of iets anders zonder back-up. + Combineer overschrijven met een opzettelijk bestandsnaampatroon, zodat alleen de beoogde uitvoer wordt vervangen. + Patronen van bestandsnamen + Combineer originele namen, voorvoegsels, achtervoegsels, tijdstempels, voorinstellingen, schaalmodus en controlesommen + Bouw namen die de uitvoer verklaren + Bestandsnaampatrooninstellingen kunnen geëxporteerde bestanden omzetten in een nuttige geschiedenis van wat er met hen is gebeurd. Dit is in batches van belang omdat de mapweergave mogelijk de enige plaats is waar u het originele formaat, de voorinstelling, het formaat en de verwerkingsvolgorde kunt onderscheiden. + Gebruik de originele bestandsnaam, voorvoegsel, achtervoegsel en tijdstempel als u leesbare namen nodig heeft voor handmatig sorteren. + Voeg informatie over voorinstellingen, schaalmodus, bestandsgrootte of controlesom toe wanneer de uitvoer moet documenteren hoe deze is geproduceerd. + Vermijd willekeurige namen voor workflows waarbij bestanden gekoppeld moeten blijven aan originelen of paginavolgorde. + Kies waar bestanden worden opgeslagen + Voorkom verlies van export door mappen in te stellen, originele mappen op te slaan en eenmalige opslaglocaties + Houd de resultaten voorspelbaar + Het opslaggedrag is het belangrijkst als u veel bestanden verwerkt of bestanden deelt van cloudproviders. Mapinstellingen bepalen of de uitvoer op één bekende plaats wordt verzameld, naast originelen verschijnt of tijdelijk ergens anders naartoe gaat voor een specifieke taak. + Stel een standaard opslagmap in als u de export altijd op één plek wilt hebben. + Gebruik opslaan in originele map voor opschoningsworkflows waarbij de uitvoer dichtbij het bronbestand moet blijven. + Gebruik een eenmalige opslaglocatie wanneer een enkele taak een andere bestemming nodig heeft, zonder uw standaardlocatie te wijzigen. + Mappen voor eenmalige opslag + Stuur de volgende exports naar een tijdelijke map zonder uw normale bestemming te wijzigen + Houd speciale taken gescheiden + Een eenmalige opslaglocatie is handig voor tijdelijke projecten, klantmappen, opschoonsessies of exports die niet mogen worden gecombineerd met uw gewone Image Toolbox-map. Het biedt een bestemming van korte duur zonder uw standaardinstellingen te herschrijven. + Kies een eenmalige map voordat u een taak start die buiten uw normale exportlocatie thuishoort. + Gebruik het voor korte projecten, gedeelde mappen of batches waarbij de uitvoer gegroepeerd moet blijven. + Keer na de taak terug naar de standaardmap, zodat latere exports niet onverwachts op de tijdelijke locatie terechtkomen. + Breng kwaliteit en bestandsgrootte in evenwicht + Begrijp wanneer u de afmetingen, het formaat of de kwaliteit moet wijzigen in plaats van te raden + Bestanden opzettelijk verkleinen + De bestandsgrootte is afhankelijk van de afmetingen van de afbeelding, het formaat, de kwaliteit, de transparantie en de complexiteit van de inhoud. Als een bestand nog steeds te zwaar is, helpt het veranderen van de afmetingen meestal meer dan het herhaaldelijk verlagen van de kwaliteit. + Pas eerst de afmetingen aan als de afbeelding groter is dan de bestemming kan weergeven. + Lagere kwaliteit alleen voor formaten met verlies en controleer tekst, randen, verlopen en vlakken na het exporteren. + Schakel van formaat wanneer kwaliteitsveranderingen niet genoeg zijn, bijvoorbeeld WebP voor delen of PNG voor scherpe, transparante elementen. + Werk met geanimeerde formaten + Gebruik GIF-, APNG-, WebP- en JXL-tools wanneer een bestand frames bevat in plaats van één bitmap + Beschouw niet elk beeld als stilstaand + Geanimeerde formaten hebben tools nodig die frames, duur en compatibiliteit begrijpen. + Gebruik GIF- of APNG-tools als u bewerkingen op frameniveau nodig hebt voor die formaten. + Gebruik WebP-tools als u moderne compressie of geanimeerde WebP-uitvoer nodig heeft. + Bekijk een voorbeeld van de animatietiming voordat u deze deelt, omdat sommige platforms geanimeerde afbeeldingen converteren of plat maken. + Vergelijk en bekijk een voorbeeld van de uitvoer + Inspecteer wijzigingen, bestandsgrootte en visuele verschillen voordat u een export bewaart + Verifieer voordat u deelt + Met vergelijkingstools kunnen compressieartefacten, verkeerde kleuren of ongewenste bewerkingen vroegtijdig worden opgemerkt. + Open Vergelijken als u twee versies naast elkaar wilt bekijken. + Zoom in op randen, tekst, verlopen en transparante gebieden waar problemen het gemakkelijkst te missen zijn. + Als de uitvoer te zwaar of wazig is, keer dan terug naar het vorige hulpmiddel en pas de kwaliteit of het formaat aan. + Gebruik de PDF-hulpmiddelenhub + PDF-bestanden samenvoegen, splitsen, roteren, verwijderen, comprimeren, beveiligen, OCR en watermerken + Kies een PDF-bewerking + De PDF-hub groepeert documentacties achter één ingangspunt, zodat u de exacte bewerking kunt kiezen. Begin daar als het bestand al een PDF is en gebruik Images to PDF of Document Scanner als uw bron nog steeds een reeks afbeeldingen is. + Open PDF Tools en kies de bewerking die bij uw doel past. + Selecteer de bron-PDF of -afbeeldingen en bekijk vervolgens de opties voor paginavolgorde, rotatie, beveiliging of OCR. + Sla de gegenereerde PDF op en open deze één keer om het aantal pagina\'s, de volgorde en de leesbaarheid te bevestigen. + Zet afbeeldingen om in een PDF + Combineer scans, screenshots, bonnen of foto\'s in één deelbaar document + Bouw een schoon document + Afbeeldingen worden betere PDF-pagina\'s als ze worden bijgesneden, geordend en op consistente wijze worden aangepast. Behandel de afbeeldingenlijst als een stapel pagina\'s: elke rotatie, marge en paginagrootte heeft invloed op hoe leesbaar het uiteindelijke document aanvoelt. + Snijd of roteer afbeeldingen eerst bij als paginaranden, schaduwen of richting onjuist zijn. + Selecteer afbeeldingen in de beoogde paginavolgorde en pas het paginaformaat of de marges aan als de tool deze biedt. + Exporteer de PDF en controleer vervolgens of alle pagina\'s leesbaar zijn voordat u deze verzendt. + Documenten scannen + Leg papieren pagina\'s vast en bereid ze voor op PDF, OCR of om te delen + Leg leesbare pagina\'s vast + Het scannen van documenten werkt het beste met vlakke pagina\'s, goede belichting en gecorrigeerd perspectief. Een schone scan vóór OCR- of PDF-export bespaart tijd omdat tekstherkenning en compressie beide afhankelijk zijn van de paginakwaliteit. + Plaats het document op een contrasterend oppervlak met voldoende licht en minimale schaduwen. + Pas gedetecteerde hoeken aan of snij handmatig bij, zodat de pagina het frame netjes vult. + Exporteer als afbeeldingen of PDF en voer vervolgens OCR uit als u selecteerbare tekst nodig heeft. + Bescherm of ontgrendel PDF-bestanden + Gebruik wachtwoorden zorgvuldig en bewaar indien mogelijk een bewerkbare bronkopie + Veilig omgaan met PDF-toegang + Beveiligingstools zijn handig voor gecontroleerd delen, maar wachtwoorden zijn gemakkelijk te verliezen en moeilijk te herstellen. Beveilig kopieën voor distributie, bewaar onbeveiligde bronbestanden apart en controleer het resultaat voordat u iets verwijdert. + Bescherm een ​​kopie van de PDF, niet uw enige brondocument. + Bewaar het wachtwoord op een veilige plek voordat u het beveiligde bestand deelt. + Gebruik ontgrendeling alleen voor bestanden die u mag bewerken en controleer vervolgens of de ontgrendelde kopie correct wordt geopend. + Organiseer PDF-pagina\'s + Pagina\'s samenvoegen, splitsen, herschikken, roteren, bijsnijden, verwijderen en paginanummers opzettelijk toevoegen + Corrigeer de documentstructuur vóór decoratie + Hulpmiddelen voor PDF-pagina\'s zijn het gemakkelijkst te gebruiken in dezelfde volgorde waarin u een papieren document zou voorbereiden: verzamel pagina\'s, verwijder de verkeerde pagina\'s, plaats ze in de juiste volgorde, corrigeer de richting en voeg vervolgens de laatste details toe, zoals paginanummers of watermerken. + Gebruik eerst samenvoegen of afbeeldingen-naar-PDF als het document nog over meerdere bestanden is verdeeld. + Herschik, roteer, snij pagina\'s bij of verwijder pagina\'s voordat u paginanummers, handtekeningen of watermerken toevoegt. + Bekijk een voorbeeld van de uiteindelijke PDF na structurele bewerkingen, omdat een ontbrekende of geroteerde pagina later moeilijk op te merken is. + PDF-annotaties + Verwijder annotaties of maak ze vlak als kijkers opmerkingen en markeringen anders weergeven + Beheers wat zichtbaar blijft + Annotaties kunnen opmerkingen, markeringen, tekeningen, formuliermarkeringen of andere extra PDF-objecten zijn. Sommige kijkers geven ze anders weer, dus als je ze verwijdert of plat maakt, kunnen gedeelde documenten voorspelbaarder worden. + Verwijder annotaties wanneer opmerkingen, markeringen of recensiemarkeringen niet in de gedeelde kopie mogen worden opgenomen. + Afvlakken wanneer zichtbare markeringen op de pagina moeten blijven staan, maar zich niet langer gedragen als bewerkbare annotaties. + Bewaar een originele kopie, omdat het moeilijk kan zijn om annotaties ongedaan te maken of af te vlakken. + Herken tekst + Extraheer tekst uit afbeeldingen met selecteerbare OCR-engines en talen + Krijg betere OCR-resultaten + De nauwkeurigheid van de OCR is afhankelijk van de beeldscherpte, taalgegevens, contrast en paginarichting. De beste resultaten worden meestal behaald als u eerst de afbeelding voorbereidt: ruis wegsnijden, rechtop draaien, de leesbaarheid vergroten en vervolgens de tekst herkennen. + Snijd de afbeelding bij tot het tekstgebied en draai deze rechtop voordat deze wordt herkend. + Kies de juiste taal en download taalgegevens als de app daarom vraagt. + Kopieer of deel de herkende tekst en controleer vervolgens handmatig de namen, cijfers en interpunctie. + Kies OCR-taalgegevens + Verbeter de herkenning door scripts, talen en gedownloade OCR-modellen op elkaar af te stemmen + Match OCR met de tekst + De verkeerde OCR-taal kan ervoor zorgen dat goede foto\'s op een slechte herkenning lijken. Taalgegevens zijn van belang voor scripts, interpunctie, cijfers en gemengde documenten, dus kies de taal die in de afbeelding verschijnt in plaats van de taal van de gebruikersinterface van de telefoon. + Kies de taal of het script dat in de afbeelding verschijnt, niet alleen de taal van de telefoon. + Download ontbrekende gegevens voordat u offline gaat werken of een batch verwerkt. + Voor documenten met meerdere talen voert u eerst de belangrijkste taal in en controleert u handmatig de namen en nummers. + Doorzoekbare PDF\'s + Schrijf OCR-tekst terug in bestanden, zodat gescande pagina\'s kunnen worden doorzocht en gekopieerd + Zet scans om in doorzoekbare documenten + OCR kan meer dan alleen tekst naar het klembord kopiëren. Wanneer u herkende tekst naar een bestand of doorzoekbare PDF schrijft, blijft de afbeelding van de pagina zichtbaar terwijl tekst gemakkelijker kan worden doorzocht, geselecteerd, geïndexeerd of gearchiveerd. + Gebruik doorzoekbare PDF-uitvoer voor gescande documenten die er nog steeds uit moeten zien als de originele pagina\'s. + Gebruik write-to-file als u alleen geëxtraheerde tekst nodig heeft en u zich niets aantrekt van de pagina-afbeelding. + Controleer belangrijke getallen, namen en tabellen handmatig, omdat OCR-tekst doorzoekbaar kan zijn, maar nog steeds onvolmaakt. + QR-codes scannen + Lees QR-inhoud van camera-invoer of opgeslagen afbeeldingen, indien beschikbaar + Codes veilig lezen + QR-codes kunnen links, Wi-Fi-gegevens, contacten, tekst of andere payloads bevatten. + Open QR-code scannen en houd de code scherp, vlak en volledig binnen het frame. + Controleer de gedecodeerde inhoud voordat u links opent of gevoelige gegevens kopieert. + Als het scannen mislukt, verbetert u de belichting, snijdt u de code bij of probeert u een bronafbeelding met een hogere resolutie. + Gebruik Base64 en controlesommen + Codeer afbeeldingen als tekst, decodeer tekst terug naar afbeeldingen en verifieer de bestandsintegriteit + Schakel tussen bestanden en tekst + Base64 is handig voor kleine afbeeldingsladingen, terwijl controlesommen helpen bevestigen dat bestanden niet zijn gewijzigd. Deze tools zijn het nuttigst bij technische workflows, ondersteuningsrapporten, bestandsoverdrachten en plaatsen waar binaire bestanden tijdelijk tekst moeten worden. + Gebruik Base64-tools om een ​​kleine afbeelding te coderen wanneer een workflow tekst nodig heeft in plaats van een binair bestand. + Decodeer Base64 alleen van vertrouwde bronnen en verifieer het voorbeeld voordat u het opslaat. + Gebruik controlesomhulpmiddelen wanneer u geëxporteerde bestanden wilt vergelijken of een upload/download wilt bevestigen. + Verpak of bescherm gegevens + Gebruik ZIP- en coderingstools voor het bundelen van bestanden of het transformeren van tekstgegevens + Houd gerelateerde bestanden bij elkaar + Verpakkingstools zijn handig wanneer verschillende uitvoer als één bestand moet worden verzonden. Ze zijn ook goed voor het bij elkaar houden van gegenereerde afbeeldingen, PDF\'s, logbestanden en metagegevens wanneer u een volledige resultatenset moet verzenden. + Gebruik ZIP als u veel geëxporteerde afbeeldingen of documenten samen wilt verzenden. + Gebruik coderingstools alleen als u de geselecteerde methode begrijpt en de vereiste sleutels veilig kunt opslaan. + Test het gemaakte archief of de getransformeerde tekst voordat u de bronbestanden verwijdert. + Controlesommen in namen + Gebruik op checksums gebaseerde bestandsnamen wanneer uniciteit en integriteit belangrijker zijn dan leesbaarheid + Geef bestanden een naam op inhoud + Checksum-bestandsnamen zijn handig wanneer exports dubbele zichtbare namen kunnen hebben of wanneer u moet bewijzen dat twee bestanden identiek zijn. Ze zijn minder geschikt voor handmatig bladeren, dus gebruik ze voor technische archieven en verificatieworkflows. + Schakel de controlesom in als bestandsnaam als de identiteit van de inhoud belangrijker is dan een voor mensen leesbare titel. + Gebruik het voor archieven, deduplicatie, herhaalbare exports of bestanden die opnieuw kunnen worden geüpload en gedownload. + Koppel checksum-namen aan mappen of metagegevens wanneer mensen nog steeds moeten begrijpen wat het bestand vertegenwoordigt. + Kies kleuren uit afbeeldingen + Monsterpixels, kopieer kleurcodes en hergebruik kleuren in paletten of ontwerpen + Proef de exacte kleur + Kleur kiezen is handig voor het matchen van een ontwerp, het extraheren van merkkleuren of het controleren van afbeeldingswaarden. Zoomen is van belang omdat anti-aliasing, schaduwen en compressie ervoor kunnen zorgen dat aangrenzende pixels enigszins afwijken van de kleur die u verwacht. + Open Kleur kiezen uit afbeelding en kies een bronafbeelding met de kleur die u nodig heeft. + Zoom in voordat u kleine details bemonstert, zodat nabijgelegen pixels het resultaat niet beïnvloeden. + Kopieer de kleur in het formaat dat vereist is voor uw bestemming, zoals HEX, RGB of HSV. + Maak paletten en gebruik de kleurenbibliotheek + Extraheer paletten, blader door opgeslagen kleuren en behoud consistente visuele systemen + Bouw een herbruikbaar palet + Paletten helpen geëxporteerde afbeeldingen, watermerken en tekeningen visueel consistent te houden. Ze zijn vooral handig als u herhaaldelijk aantekeningen op schermafbeeldingen maakt, merkelementen voorbereidt of dezelfde kleuren voor verschillende tools nodig hebt. + Genereer een palet uit een afbeelding of voeg handmatig kleuren toe als u de waarden al kent. + Benoem of organiseer belangrijke kleuren, zodat je ze later kunt terugvinden. + Gebruik gekopieerde waarden in Draw, watermerk, verloop, SVG of externe ontwerptools. + Creëer verlopen + Bouw verloopafbeeldingen voor achtergronden, tijdelijke aanduidingen en visuele experimenten + Controle kleurovergangen + Verloophulpmiddelen zijn handig wanneer u een gegenereerde afbeelding nodig heeft in plaats van een bestaande afbeelding te bewerken. Het kunnen schone achtergronden, tijdelijke aanduidingen, achtergronden, maskers of eenvoudige middelen voor externe ontwerptools worden. + Kies het verlooptype en stel eerst de belangrijke kleuren in. + Pas richting, stops, vloeiendheid en uitvoergrootte aan voor het beoogde gebruiksscenario. + Exporteer in een formaat dat overeenkomt met de bestemming, meestal PNG voor schoon gegenereerde afbeeldingen. + Toegankelijkheid van kleuren + Gebruik dynamische kleuren, contrast en kleurenblinde opties wanneer de gebruikersinterface moeilijk leesbaar is + Maak kleuren gemakkelijker te gebruiken + Kleurinstellingen zijn van invloed op het comfort, de leesbaarheid en hoe snel u bedieningselementen kunt scannen. Als toolchips, schuifregelaars of geselecteerde statussen moeilijk te onderscheiden zijn, kunnen thema- en toegankelijkheidsopties nuttiger zijn dan simpelweg de donkere modus wijzigen. + Probeer dynamische kleuren als u wilt dat de app het huidige achtergrondpalet volgt. + Verhoog het contrast of verander het schema als knoppen en vlakken niet voldoende opvallen. + Gebruik kleurenblinde schemaopties als sommige toestanden of accenten moeilijk te onderscheiden zijn. + Alfa-achtergrond + Beheer de voorbeeld- of opvulkleur die wordt gebruikt rond transparante PNG-, WebP- en soortgelijke uitvoer + Begrijp transparante voorbeelden + Achtergrondkleur voor alfaformaten kan transparante afbeeldingen gemakkelijker te inspecteren maken, maar het is belangrijk om te weten of u het voorbeeld-/opvulgedrag wijzigt of het eindresultaat afvlakt. Dit is van belang voor stickers, logo\'s en afbeeldingen zonder achtergrond. + Gebruik eerst een alfa-ondersteunend formaat, zoals PNG of WebP, wanneer transparante pixels de export moeten overleven. + Pas de achtergrondkleurinstellingen aan als transparante randen moeilijk zichtbaar zijn tegen het app-oppervlak. + Exporteer een kleine test en open deze opnieuw in een andere app om te bevestigen of de transparantie of de gekozen vulling is opgeslagen. + Keuze van AI-modellen + Kies een model op basis van afbeeldingstype en defect, in plaats van eerst de grootste te kiezen + Match het model met de schade + AI Tools kan verschillende ONNX/ORT-modellen downloaden of importeren, en elk model is getraind voor een bepaald soort probleem. Een model voor JPEG-blokken, het opschonen van anime-lijnen, het verwijderen van ruis, het verwijderen van de achtergrond, het inkleuren of het opschalen kan slechtere resultaten opleveren als het op het verkeerde afbeeldingstype wordt gebruikt. + Lees de modelbeschrijving voordat u downloadt; kies het model dat uw werkelijke probleem benoemt, zoals compressie, ruis, halftoon, onscherpte of achtergrondverwijdering. + Begin met een kleiner of specifieker model bij het testen van een nieuw beeldtype en vergelijk het vervolgens alleen met zwaardere modellen als het resultaat niet goed genoeg is. + Bewaar alleen modellen die u echt gebruikt, omdat gedownloade en geïmporteerde modellen aanzienlijke opslagruimte in beslag kunnen nemen. + Begrijp modelfamilies + Modelnamen verwijzen vaak naar hun doel: modellen in RealESRGAN-stijl worden opgewaardeerd, SCUNet- en FBCNN-modellen zuiveren ruis of compressie, achtergrondmodellen creëren maskers en kleurmakers voegen kleur toe aan grijswaardeninvoer. Geïmporteerde aangepaste modellen kunnen krachtig zijn, maar ze moeten overeenkomen met de verwachte invoer/uitvoervorm en de ondersteunde ONNX- of ORT-indeling gebruiken. + Gebruik upscalers als je meer pixels nodig hebt, denoisers als de afbeelding korrelig is, en artefactverwijderaars als compressieblokken of banding het grootste probleem zijn. + Gebruik achtergrondmodellen alleen voor onderwerpscheiding; ze zijn niet hetzelfde als algemene objectverwijdering of beeldverbetering. + Importeer aangepaste modellen alleen van bronnen die u vertrouwt en test ze op een kleine afbeelding voordat u belangrijke bestanden gebruikt. + AI-parameters + Stem de chunkgrootte, overlap en parallelle werkers af om kwaliteit, snelheid en geheugen in evenwicht te brengen + Breng snelheid in evenwicht met stabiliteit + De chunkgrootte bepaalt hoe groot de afbeeldingsstukken zijn voordat ze door het model worden verwerkt. Overlap combineert aangrenzende delen om randen te verbergen. Parallelle werkers kunnen de verwerking versnellen, maar elke werker heeft geheugen nodig, dus hoge waarden kunnen grote afbeeldingen onstabiel maken op telefoons met beperkt RAM-geheugen. + Gebruik grotere stukken voor een vloeiendere context wanneer uw apparaat het model betrouwbaar verwerkt en de afbeelding niet groot is. + Vergroot de overlap wanneer de randen van de stukken zichtbaar worden, maar onthoud dat meer overlap meer herhaald werk betekent. + Verhoog parallelle werkers alleen na een stabiele test; Als de verwerking vertraagt, het apparaat verhit of crasht, moet u eerst het aantal werknemers verminderen. + Vermijd chunk-artefacten + Met chunking kan de app afbeeldingen verwerken die groter zijn dan het model in één keer gemakkelijk aankan. Het nadeel is dat elke tegel minder omringende context heeft, dus een lage overlap of te kleine stukjes kunnen zichtbare randen, textuurveranderingen of inconsistente details tussen aangrenzende gebieden creëren. + Verhoog de overlap als u rechthoekige randen, herhaalde structuurwijzigingen of detailsprongen tussen verwerkte gebieden ziet. + Verklein de chunkgrootte als het proces mislukt voordat de uitvoer wordt geproduceerd, vooral bij grote afbeeldingen of zware modellen. + Sla een kleine croptest op voordat u de volledige afbeelding verwerkt, zodat u de parameters snel kunt vergelijken. + AI-stabiliteit + Verlaag de chunkgrootte, werkers en bronresolutie wanneer de AI-verwerking crasht of vastloopt + Herstel van zware AI-klussen + AI-verwerking is een van de zwaarste workflows in de app. Crashes, mislukte sessies, vastlopen of plotseling opnieuw opstarten van apps betekenen meestal dat het model, de bronresolutie, de chunkgrootte of het aantal parallelle werknemers te veeleisend is voor de huidige apparaatstatus. + Als de app crasht of er niet in slaagt een modelsessie te openen, verlaag dan de parallelle werkers en de chunkgrootte en probeer dezelfde afbeelding opnieuw. + Pas het formaat van zeer grote afbeeldingen aan vóór AI-verwerking wanneer het doel niet de oorspronkelijke resolutie vereist. + Sluit andere zware apps, gebruik een kleiner model of verwerk minder bestanden tegelijk als de geheugendruk steeds terugkeert. + Diagnose van modelfouten + Een mislukte AI-run betekent niet altijd dat het imago slecht is. Het modelbestand is mogelijk onvolledig, wordt niet ondersteund, is te groot voor geheugen of komt niet overeen met de bewerking. Wanneer de app een mislukte sessie rapporteert, behandel dit dan als een signaal om eerst het model en de parameters te vereenvoudigen. + Verwijder een model en download het opnieuw als het onmiddellijk na het downloaden mislukt of zich gedraagt ​​alsof het bestand beschadigd is. + Probeer een ingebouwd downloadbaar model voordat u een geïmporteerd aangepast model de schuld geeft, omdat geïmporteerde modellen incompatibele invoer kunnen hebben. + Gebruik app-logboeken nadat u de fout heeft gereproduceerd als u een crash- of sessiefout moet melden. + Experimenteer in Shader Studio + Gebruik GPU-shadereffecten voor geavanceerde beeldverwerking en aangepaste looks + Werk voorzichtig met shaders + Shader Studio is krachtig, maar shadercode en parameters hebben kleine, testbare wijzigingen nodig. Behandel het als een experimenteel hulpmiddel: houd een werkende basislijn aan, verander één ding tegelijk en test met verschillende afbeeldingsformaten voordat u een voorinstelling vertrouwt. + Begin met een bekende werkende shader of een eenvoudig effect voordat u parameters toevoegt. + Wijzig één parameter of codeblok tegelijk en controleer het voorbeeld op fouten. + Exporteer voorinstellingen alleen nadat u ze op een paar verschillende afbeeldingsformaten hebt getest. + Maak SVG- of ASCII-kunst + Genereer vectorachtige elementen of op tekst gebaseerde afbeeldingen voor creatieve uitvoer + Kies het creatieve uitvoertype + SVG- en ASCII-tools dienen verschillende doelen: schaalbare afbeeldingen of weergave in tekststijl. Beide zijn creatieve conversies, dus een voorbeeld op het uiteindelijke formaat bekijken is belangrijker dan het resultaat beoordelen aan de hand van een kleine miniatuur. + Gebruik SVG Maker wanneer het resultaat netjes moet worden geschaald of opnieuw moet worden gebruikt in ontwerpworkflows. + Gebruik ASCII Art als u een gestileerde tekstweergave van een afbeelding wilt. + Bekijk een voorbeeld op eindformaat, omdat kleine details in de gegenereerde uitvoer kunnen verdwijnen. + Genereer ruistexturen + Maak procedurele texturen voor achtergronden, maskers, overlays of experimenten + Stem de procedurele uitvoer af + Bij het genereren van ruis worden nieuwe afbeeldingen gemaakt op basis van parameters, waardoor kleine veranderingen zeer verschillende resultaten kunnen opleveren. Het is handig voor texturen, maskers, overlays, tijdelijke aanduidingen of het testen van compressie met gedetailleerde patronen. + Kies een ruistype en uitvoergrootte voordat u de fijne details afstemt. + Pas de schaal, intensiteit, kleuren of zaad aan totdat het patroon past bij het beoogde gebruik. + Bewaar nuttige resultaten en noteer de parameters als u de textuur later opnieuw wilt maken. + Opmaakprojecten + Gebruik projectbestanden wanneer annotaties bewerkbaar moeten blijven na het sluiten van de app + Zorg ervoor dat gelaagde bewerkingen herbruikbaar zijn + Opmaakprojectbestanden zijn handig wanneer een schermafbeelding, diagram of instructieafbeelding later mogelijk moet worden gewijzigd. Geëxporteerde PNG/JPEG-bestanden zijn definitieve afbeeldingen, terwijl projectbestanden bewerkbare lagen en bewerkingsstatus behouden voor toekomstige aanpassingen. + Gebruik opmaaklagen wanneer annotaties, toelichtingen of lay-outelementen bewerkbaar moeten blijven. + Sla het projectbestand op of open het opnieuw voordat u een definitieve afbeelding exporteert als u meer revisies verwacht. + Exporteer een normale afbeelding alleen als u klaar bent om een ​​afgeplatte definitieve versie te delen. + Versleutel bestanden + Bescherm bestanden met een wachtwoord en test de decodering voordat u een bronkopie verwijdert + Ga zorgvuldig om met gecodeerde uitvoer + Cipher-tools kunnen bestanden beschermen met op wachtwoorden gebaseerde codering, maar zijn geen vervanging voor het onthouden van de sleutel of het maken van back-ups. Encryptie is handig voor transport en opslag, terwijl ZIP beter is als het hoofddoel het bundelen van meerdere outputs is. + Versleutel eerst een kopie en bewaar het origineel totdat u hebt getest of de ontsleuteling werkt met het wachtwoord dat u hebt opgeslagen. + Gebruik een wachtwoordbeheerder of een andere veilige plaats voor de sleutel; de app kan een verloren wachtwoord niet voor u raden. + Deel versleutelde bestanden alleen met mensen die ook het wachtwoord hebben en weten welke tool of methode ze moet ontsleutelen. + Gereedschap regelen + Groepeer, orden, zoek en pin tools zodat het hoofdscherm overeenkomt met uw echte workflow + Maak het hoofdscherm sneller + Instellingen voor gereedschapsindeling zijn de moeite waard om af te stemmen als u eenmaal weet welke gereedschappen u het meest gebruikt. Groeperen helpt bij het verkennen, aangepaste volgorde helpt het spiergeheugen te vergroten en favorieten zorgen ervoor dat dagelijkse hulpmiddelen bereikbaar blijven, zelfs als de volledige lijst groot wordt. + Gebruik de gegroepeerde modus tijdens het leren van de app of wanneer u de voorkeur geeft aan tools geordend op taaktype. + Schakel over naar een aangepaste volgorde als u uw gebruikelijke gereedschappen al kent en minder scrollen wilt. + Gebruik zoekinstellingen en favorieten samen voor zeldzame tools die u zich bij naam herinnert, maar die u niet altijd zichtbaar wilt hebben. + Behandel grote bestanden + Vermijd trage export, geheugendruk en onverwacht grote output + Verminder het werk vóór de export + Zeer grote afbeeldingen, lange batches en indelingen van hoge kwaliteit kunnen meer geheugen en tijd in beslag nemen. De snelste oplossing is meestal het verminderen van de hoeveelheid werk vóór de zwaarste bewerking, en niet wachten tot dezelfde extra grote invoer is voltooid. + Pas het formaat van grote afbeeldingen aan voordat u zware filters, OCR of PDF-conversie toepast. + Gebruik eerst een kleinere batch om de instellingen te bevestigen en verwerk vervolgens de rest met dezelfde preset. + Lagere kwaliteit of wijzig het formaat als het uitvoerbestand groter is dan verwacht. + Cache en voorbeelden + Begrijp gegenereerde previews, cache-opschoning en opslaggebruik na zware sessies + Houd opslag en snelheid in evenwicht + Het genereren van voorvertoningen en het cachegeheugen kunnen ervoor zorgen dat herhaaldelijk browsen sneller gaat, maar bij zware bewerkingssessies kunnen tijdelijke gegevens achterblijven. Cache-instellingen helpen wanneer de app traag aanvoelt, de opslagruimte krap is of de voorbeelden er na veel experimenten oud uitzien. + Voorvertoningen ingeschakeld houden wanneer u door veel afbeeldingen bladert, is belangrijker dan het minimaliseren van de tijdelijke opslag. + Wis de cache na grote batches, mislukte experimenten of lange sessies met veel bestanden met hoge resolutie. + Gebruik het automatisch wissen van de cache als u de voorkeur geeft aan het opschonen van de opslag boven het warmhouden van voorbeelden tussen lanceringen. + Schermgedrag aanpassen + Gebruik opties voor volledig scherm, veilige modus, helderheid en systeembalk om gefocust te werken + Zorg ervoor dat de interface bij de situatie past + Scherminstellingen helpen wanneer u in landschap bewerkt, privéafbeeldingen presenteert of een consistente helderheid nodig heeft. Ze zijn niet vereist voor normaal gebruik, maar ze lossen lastige situaties op, zoals QR-inspectie, privévoorbeelden of krappe landschapsbewerking. + Gebruik instellingen voor volledig scherm of systeembalk wanneer het bewerken van de ruimte belangrijker is dan navigatiechroom. + Gebruik de veilige modus wanneer privéafbeeldingen niet mogen verschijnen in schermafbeeldingen of app-voorbeelden. + Gebruik helderheidshandhaving voor tools waarbij donkere afbeeldingen of QR-codes moeilijk te inspecteren zijn. + Houd transparantie + Voorkom dat transparante achtergronden wit, zwart of afgevlakt worden + Gebruik alfabewuste uitvoer + Transparantie blijft alleen bestaan ​​als de geselecteerde tool en het uitvoerformaat alfa ondersteunen. Als een geëxporteerde achtergrond wit of zwart wordt, is het probleem meestal het uitvoerformaat, een opvul-/achtergrondinstelling of een doel-app die de afbeelding plat heeft gemaakt. + Kies PNG of WebP bij het exporteren van afbeeldingen, stickers, logo\'s of overlays die op de achtergrond zijn verwijderd. + Vermijd JPEG voor transparante uitvoer, omdat hierin geen alfakanaal wordt opgeslagen. + Controleer de instellingen met betrekking tot de achtergrondkleur voor alfaformaten als het voorbeeld een gevulde achtergrond toont. + Problemen met delen en importeren oplossen + Gebruik kiezerinstellingen en ondersteunde indelingen wanneer bestanden niet correct verschijnen of worden geopend + Haal het bestand in de app + Importproblemen worden vaak veroorzaakt door providermachtigingen, niet-ondersteunde indelingen of kiezergedrag. Bestanden die worden gedeeld vanuit cloud-apps en messengers kunnen tijdelijk zijn, dus het kiezen van een persistente bron kan betrouwbaarder zijn voor langere bewerkingen. + Probeer het bestand te openen via de systeemkiezer in plaats van via een snelkoppeling naar recente bestanden. + Als een formaat niet door één tool wordt ondersteund, converteer het dan eerst of open een specifiekere tool. + Controleer de instellingen voor de afbeeldingskiezer en het opstartprogramma als deelstromen of het direct openen van tools zich anders gedragen dan verwacht. + Bevestiging afsluiten + Voorkom dat niet-opgeslagen bewerkingen verloren gaan wanneer gebaren, terugdrukken of gereedschapswisselingen per ongeluk plaatsvinden + Bescherm onvoltooid werk + Bevestiging van het afsluiten van een tool is handig als u gebarennavigatie gebruikt, grote bestanden bewerkt of vaak tussen apps schakelt. Er wordt een kleine pauze toegevoegd voordat u een tool verlaat, zodat onvoltooide instellingen en voorbeelden niet per ongeluk verloren gaan. + Schakel bevestiging in als onbedoelde teruggebaren ooit een tool hebben gesloten voordat u exporteerde. + Houd het uitgeschakeld als u vooral snelle conversies in één stap uitvoert en de voorkeur geeft aan snellere navigatie. + Gebruik het samen met voorinstellingen voor langere workflows waarbij het opnieuw opbouwen van instellingen vervelend zou zijn. + Gebruik logboeken bij het melden van problemen + Verzamel nuttige details voordat u een probleem opent of contact opneemt met de ontwikkelaar + Rapporteer problemen met context + Een duidelijk rapport met stappen, app-versie, invoertype en logboeken is veel gemakkelijker te diagnosticeren. Logboeken zijn het nuttigst direct na het reproduceren van een probleem, omdat ze de omringende context actueel houden. + Noteer de toolnaam, de exacte actie en of dit bij één bestand of bij elk bestand gebeurt. + Open app-logboeken nadat het probleem is gereproduceerd en deel indien mogelijk de relevante loguitvoer. + Voeg alleen schermafbeeldingen of voorbeeldbestanden toe als deze geen privégegevens bevatten. + Achtergrondverwerking is gestopt + Android liet de melding voor achtergrondverwerking niet op tijd starten. Dit is een systeemtimingbeperking die niet op betrouwbare wijze kan worden verholpen. Start de app opnieuw en probeer het opnieuw; Het kan helpen om de app open te houden en meldingen of achtergrondwerk toe te staan. + Sla het selecteren van bestanden over + Open tools sneller wanneer invoer meestal afkomstig is van delen, het klembord of een vorig scherm + Zorg ervoor dat herhaalde toollanceringen sneller verlopen + Bestanden kiezen overslaan verandert de eerste stap van ondersteunde tools. Het is handig als je meestal een tool invoert met een al geselecteerde afbeelding of vanuit Android-deelacties, maar het kan verwarrend zijn als je verwacht dat elke tool onmiddellijk om een ​​bestand vraagt. + Schakel dit alleen in als uw gebruikelijke stroom de bronafbeelding al bevat voordat de tool wordt geopend. + Houd het uitgeschakeld tijdens het leren van de app, omdat expliciet kiezen elke toolflow gemakkelijker te begrijpen maakt. + Als een tool wordt geopend zonder duidelijke invoer, schakelt u deze instelling uit of start u vanaf Share/Open With, zodat Android het bestand als eerste doorgeeft. + Open eerst de editor + Sla het voorbeeld over als een gedeelde afbeelding direct moet worden bewerkt + Kies een voorbeeld of bewerk als eerste stop + Bewerken openen in plaats van voorbeeld is bedoeld voor gebruikers die al weten dat ze de afbeelding willen wijzigen. Voorvertoning is veiliger wanneer u bestanden inspecteert vanuit chat of cloudopslag; directe bewerking is sneller voor schermafbeeldingen, snelle uitsneden, annotaties en eenmalige correcties. + Schakel directe bewerking in als de meeste gedeelde afbeeldingen onmiddellijk moeten worden bijgesneden, opgemaakt, gefilterd of geëxporteerd. + Verlaat eerst het voorbeeld als u vaak metadata, afmetingen, transparantie of beeldkwaliteit inspecteert voordat u een beslissing neemt. + Gebruik dit samen met favorieten, zodat gedeelde afbeeldingen dichtbij de tools komen die u daadwerkelijk gebruikt. + Vooraf ingestelde tekstinvoer + Voer exacte vooraf ingestelde waarden in in plaats van de bedieningselementen herhaaldelijk aan te passen + Gebruik nauwkeurige waarden wanneer schuifregelaars langzaam zijn + Vooraf ingestelde tekstinvoer helpt wanneer u exacte cijfers nodig heeft voor herhaaldelijk werk: sociale afbeeldingsformaten, documentmarges, compressiedoelen, lijndiktes of andere waarden die vervelend zijn om met gebaren te bereiken. Het is ook handig op kleine schermen waar fijne schuifbewegingen moeilijker zijn. + Schakel tekstinvoer in als u vaak exacte maten, percentages, kwaliteitswaarden of gereedschapsparameters gebruikt. + Sla de waarde op als een voorinstelling nadat u deze één keer hebt getypt, en gebruik deze vervolgens opnieuw in plaats van dezelfde configuratie opnieuw te maken. + Behoud gebarenbediening voor ruwe visuele afstemming en getypte waarden voor strikte uitvoervereisten. + Opslaan in de buurt van origineel + Plaats gegenereerde bestanden naast bronafbeeldingen wanneer de mapcontext ertoe doet + Houd de uitvoer bij de bron + Opslaan in originele map is handig voor cameramappen, projectmappen, documentscans en clientbatches waarbij de bewerkte kopie naast de bron moet blijven. Het kan minder netjes zijn dan een speciale uitvoermap, dus combineer het met duidelijke achtervoegsels of patronen voor bestandsnamen. + Schakel het in als elke bronmap al betekenisvol is en de uitvoer daar gegroepeerd moet blijven. + Gebruik achtervoegsels, voorvoegsels, tijdstempels of patronen van bestandsnamen, zodat bewerkte kopieën gemakkelijk van originelen kunnen worden gescheiden. + Schakel het uit voor gemengde batches als u wilt dat alle gegenereerde bestanden in één exportmap worden verzameld. + Sla grotere uitvoer over + Vermijd het opslaan van conversies die onverwacht zwaarder worden dan de bron + Bescherm batches tegen verrassingen van slechte grootte + Sommige conversies maken bestanden groter, vooral PNG-screenshots, reeds gecomprimeerde foto\'s, hoogwaardige WebP of afbeeldingen waarbij de metagegevens behouden blijven. Met Toestaan ​​als groter wordt voorkomen dat deze uitvoer een kleinere bron vervangt in workflows waarbij het verkleinen van de omvang het hoofddoel is. + Schakel dit in als de export bedoeld is om bestanden te comprimeren, te vergroten of verkleinen of voor te bereiden op uploadlimieten. + Bekijk overgeslagen bestanden na een batch; ze zijn mogelijk al geoptimaliseerd of hebben mogelijk een ander formaat nodig. + Schakel het uit als kwaliteit, transparantie of formaatcompatibiliteit belangrijker zijn dan de uiteindelijke bestandsgrootte. + Klembord en links + Beheer automatische klembordinvoer en linkvoorbeelden voor snellere op tekst gebaseerde tools + Maak automatische invoer voorspelbaar + Klembord- en linkinstellingen zijn handig voor QR-, Base64-, URL- en tekstintensieve workflows, maar automatische invoer moet opzettelijk blijven. Als de app de velden zelf lijkt te vullen, controleer dan het plakgedrag op het klembord; Als linkkaarten luidruchtig of traag aanvoelen, pas dan het gedrag van het linkvoorbeeld aan. + Sta automatisch plakken op het klembord toe wanneer u vaak tekst kopieert en open onmiddellijk een matchingtool. + Schakel automatisch plakken uit als privé-klembordinhoud verschijnt in tools waar u dit niet had verwacht. + Schakel linkvoorbeelden uit wanneer het ophalen van voorbeelden traag, afleidend of onnodig is voor uw workflow. + Compacte bedieningselementen + Gebruik dichtere selectors en snelle plaatsing van instellingen wanneer de schermruimte krap is + Plaats meer bedieningselementen op kleine schermen + Compacte selectors en snelle plaatsing van instellingen helpen op kleine telefoons, landschapsbewerking en tools met veel parameters. Het nadeel is dat de bedieningselementen compacter kunnen aanvoelen, dus dit is het beste nadat je de algemene opties al kent. + Schakel compacte selectors in wanneer dialoogvensters of optielijsten te lang lijken voor het scherm. + Pas de kant van de snelle instellingen aan als de gereedschapsbedieningen gemakkelijker te bereiken zijn vanaf één kant van het display. + Keer terug naar ruimere bedieningselementen als u de app nog aan het leren bent of als u regelmatig dichte opties mist. + Afbeeldingen van internet laden + Plak een pagina- of afbeeldings-URL, bekijk een voorbeeld van de geparseerde afbeeldingen en sla of deel de geselecteerde resultaten op + Gebruik bewust webafbeeldingen + Web Image Loading parseert afbeeldingsbronnen vanaf de opgegeven URL, geeft een voorbeeld weer van de eerste beschikbare afbeelding en biedt u de mogelijkheid geselecteerde geparseerde afbeeldingen op te slaan of te delen. Sommige sites gebruiken tijdelijke, beveiligde of door scripts gegenereerde links, dus een pagina die in een browser werkt, retourneert mogelijk nog steeds geen bruikbare afbeeldingen. + Plak een directe afbeeldings-URL of een pagina-URL en wacht tot de geparseerde afbeeldingenlijst verschijnt. + Gebruik de frame- of selectieknoppen als de pagina meerdere afbeeldingen bevat en u er maar een paar nodig heeft. + Als er niets wordt geladen, probeer dan eerst een directe afbeeldingslink of download via de browser; de site kan het parseren blokkeren of privélinks gebruiken. + Kies het formaatwijzigingstype + Begrijp expliciet, flexibel, bijsnijden en passend voordat u een afbeelding naar nieuwe dimensies forceert + Bepaal de vorm, het canvas en de beeldverhouding + Het formaatwijzigingstype bepaalt wat er gebeurt als de gevraagde breedte en hoogte niet overeenkomen met de oorspronkelijke beeldverhouding. Het is het verschil tussen het uitrekken van pixels, het behouden van proporties, het bijsnijden van een extra gebied of het toevoegen van een achtergrondcanvas. + Gebruik Explicit alleen als vervorming acceptabel is of als de bron al dezelfde beeldverhouding heeft als het doel. + Gebruik Flexibel om de verhoudingen te behouden door het formaat van de belangrijke kant aan te passen, vooral voor foto\'s en gemengde batches. + Gebruik Bijsnijden voor strikte kaders zonder randen, of Aanpassen als de hele afbeelding zichtbaar moet blijven binnen een vast canvas. + Kies de afbeeldingsschaalmodus + Kies het resamplingfilter dat de scherpte, vloeiendheid, snelheid en randartefacten regelt + Formaat wijzigen zonder toevallige zachtheid + De afbeeldingsschaalmodus bepaalt hoe nieuwe pixels worden berekend tijdens het wijzigen van het formaat. Eenvoudige modi zijn snel en voorspelbaar, terwijl geavanceerde filters details beter kunnen behouden, maar randen kunnen verscherpen, halo\'s kunnen creëren of langer kunnen duren bij grote afbeeldingen. + Gebruik Basic of Bilinear voor het snel aanpassen van de afmetingen wanneer het resultaat alleen maar kleiner en zuiver hoeft te zijn. + Gebruik Lanczos-, Robidoux-, Spline- of EWA-stijlmodi wanneer fijne details, tekst of hoogwaardige verkleining van belang zijn. + Gebruik Dichtstbijzijnd voor pixelkunst, pictogrammen en afbeeldingen met harde randen waarbij vage pixels het uiterlijk zouden verpesten. + Schaal kleurruimte + Bepaal hoe kleuren worden gemengd terwijl de pixels opnieuw worden bemonsterd tijdens het wijzigen van het formaat + Houd hellingen en randen natuurlijk + Schaalkleurruimte verandert de wiskunde die wordt gebruikt bij het wijzigen van de grootte. De meeste afbeeldingen voldoen prima aan de standaardwaarden, maar lineaire of perceptuele ruimtes kunnen ervoor zorgen dat verlopen, donkere randen en verzadigde kleuren er schoner uitzien na sterke schaling. + Laat de standaardinstelling staan ​​als snelheid en compatibiliteit belangrijker zijn dan kleine kleurverschillen. + Probeer de lineaire of perceptuele modi wanneer donkere contouren, schermafbeeldingen van de gebruikersinterface, verlopen of kleurbanden er verkeerd uitzien na het wijzigen van het formaat. + Vergelijk voor en na op het echte doelscherm, omdat veranderingen in de kleurruimte subtiel kunnen zijn en afhankelijk van de inhoud. + Beperk de formaatwijzigingsmodi + Weet wanneer afbeeldingen die te hoog zijn, moeten worden overgeslagen, opnieuw gecodeerd of verkleind om te passen + Kies wat er op de limiet gebeurt + Limit Resize is niet alleen een hulpmiddel voor kleinere afbeeldingen. De modus bepaalt wat de app doet wanneer een bron zich al binnen uw limieten bevindt of wanneer slechts één kant de regel overtreedt, wat belangrijk is voor gemengde mappen en uploadvoorbereiding. + Gebruik Overslaan als afbeeldingen binnen de limieten onaangeroerd moeten blijven en niet opnieuw moeten worden geëxporteerd. + Gebruik Recode wanneer afmetingen acceptabel zijn, maar u nog steeds een nieuw formaat, metadatagedrag, kwaliteit of bestandsnaam nodig heeft. + Gebruik Zoom wanneer afbeeldingen die de limieten overschrijden, proportioneel moeten worden verkleind totdat ze in het toegestane vak passen. + Doelen voor beeldverhouding + Vermijd uitgerekte vlakken, afgesneden randen en onverwachte randen bij strikte uitvoerformaten + Pas de vorm aan voordat u het formaat wijzigt + Breedte en hoogte zijn slechts de helft van een formaatwijzigingsdoel. De beeldverhouding bepaalt of de afbeelding op natuurlijke wijze kan passen, moet worden bijgesneden of dat er een canvas omheen moet worden geplaatst. Als u eerst de vorm controleert, voorkomt u de meest verrassende resultaten bij het wijzigen van de grootte. + Vergelijk de bronvorm met de doelvorm voordat u Expliciet, Bijsnijden of Passend kiest. + Snijd eerst bij als het onderwerp extra achtergrond kan verliezen en de bestemming een strikt kader nodig heeft. + Gebruik Fit of Flexibel wanneer elk deel van de originele afbeelding zichtbaar moet blijven. + Upscale of normaal formaat + Weet wanneer het toevoegen van pixels AI nodig heeft en wanneer eenvoudig schalen voldoende is + Verwar grootte niet met details + Normaal formaat wijzigen verandert de afmetingen door pixels te interpoleren; het kan geen echte details verzinnen. AI-opschaling kan scherpere details creëren, maar is langzamer en kan textuur hallucineren, dus de juiste keuze hangt af van of je compatibiliteit, snelheid of visueel herstel nodig hebt. + Gebruik het normale formaat voor miniaturen, uploadlimieten, schermafbeeldingen en eenvoudige wijzigingen in afmetingen. + Gebruik AI-upscale wanneer een kleine of zachte afbeelding er beter uit moet zien op een groter formaat. + Vergelijk gezichten, tekst en patronen na AI-opschaling, omdat gegenereerde details er overtuigend maar onjuist uit kunnen zien. + Filtervolgorde is belangrijk + Pas opschoning, kleur, scherpte en uiteindelijke compressie toe in een opzettelijke volgorde + Bouw een schonere filterstapel + Filters zijn niet altijd uitwisselbaar. Een vervaging vóór verscherping, een contrastverbetering vóór OCR of een kleurverandering vóór compressie kunnen heel verschillende resultaten opleveren met dezelfde bedieningselementen. + Eerst bijsnijden en roteren, zodat latere filters alleen werken op de resterende pixels. + Pas belichting, witbalans en contrast toe vóór verscherping of gestileerde effecten. + Houd de uiteindelijke grootte en compressie aan het einde, zodat de voorbeelddetails de export voorspelbaar overleven. + Achtergrondranden corrigeren + Reinig halo\'s, overgebleven schaduwen, haar, gaten en zachte contouren na automatische verwijdering + Laat uitsparingen er opzettelijk uitzien + Automatische maskers zien er in één oogopslag vaak goed uit, maar onthullen problemen op heldere, donkere of patroonachtergronden. Het opruimen van randen is het verschil tussen een snelle uitsnede en een herbruikbare sticker, logo of productafbeelding. + Bekijk een voorbeeld van het resultaat tegen contrasterende achtergronden om halo\'s en ontbrekende randdetails zichtbaar te maken. + Gebruik herstel voor haar, hoeken en transparante voorwerpen voordat u omliggende restjes verwijdert. + Exporteer een kleine test van de uiteindelijke achtergrondkleur wanneer de uitsnede in een ontwerp wordt geplaatst. + Leesbare watermerken + Maak markeringen zichtbaar op heldere, donkere, drukke en bijgesneden afbeeldingen zonder de foto te verpesten + Bescherm de afbeelding zonder deze te verbergen + Een watermerk dat er op de ene afbeelding goed uitziet, kan op een andere afbeelding verdwijnen. Grootte, dekking, contrast, plaatsing en herhaling moeten voor de hele batch worden gekozen, niet alleen voor het eerste voorbeeld. + Test de markering op de helderste en donkerste afbeeldingen in de batch voordat u alle bestanden exporteert. + Gebruik een lagere dekking voor grote herhaalde markeringen en een sterker contrast voor kleine hoekmarkeringen. + Houd belangrijke gezichten, tekst en productdetails duidelijk, tenzij het merkteken bedoeld is om hergebruik te voorkomen. + Splits één afbeelding in tegels + Knip een enkele afbeelding op basis van rijen, kolommen, aangepaste percentages, formaat en kwaliteit + Maak roosters klaar en bestel stukken + Met het splitsen van afbeeldingen worden meerdere uitvoer van één bronafbeelding gemaakt. Het kan splitsen op rij- en kolomtellingen, rij- of kolompercentages aanpassen en stukken opslaan met het geselecteerde uitvoerformaat en de geselecteerde kwaliteit, wat handig is voor rasters, paginasegmenten, panorama\'s en geordende sociale berichten. + Kies de bronafbeelding en stel vervolgens het aantal rijen en kolommen in dat u nodig heeft. + Pas rij- of kolompercentages aan als de stukken niet allemaal even groot mogen zijn. + Kies het uitvoerformaat en de kwaliteit voordat u het opslaat, zodat elk gegenereerd stuk dezelfde exportregels gebruikt. + Knip afbeeldingsstroken uit + Verwijder verticale of horizontale gebieden en voeg de resterende delen weer samen + Verwijder een gebied zonder een opening achter te laten + Beeldsnijden verschilt van normaal bijsnijden. Het kan een geselecteerde verticale of horizontale strook verwijderen en vervolgens de resterende afbeeldingsdelen samenvoegen. In de omgekeerde modus blijft het geselecteerde gebied behouden, en bij gebruik van beide omgekeerde richtingen blijft de geselecteerde rechthoek behouden. + Gebruik verticaal snijden voor zijgebieden, kolommen of middenstroken; gebruik horizontaal snijden voor banners, gaten of rijen. + Schakel Omkeren op een as in als u het geselecteerde onderdeel wilt behouden in plaats van het te verwijderen. + Bekijk een voorbeeld van de randen na het knippen, omdat samengevoegde inhoud er abrupt uit kan zien als het verwijderde gebied belangrijke details overschrijdt. + Kies het uitvoerformaat + Kies JPEG, PNG, WebP, AVIF, JXL of PDF op basis van inhoud en bestemming + Laat de bestemming het formaat bepalen + Het beste formaat hangt af van wat de afbeelding bevat en waar deze zal worden gebruikt. Foto\'s, schermafbeeldingen, transparante elementen, documenten en geanimeerde bestanden mislukken allemaal op verschillende manieren wanneer ze in het verkeerde formaat worden geforceerd. + Gebruik JPEG voor brede fotocompatibiliteit wanneer transparantie en exacte pixels niet vereist zijn. + Gebruik PNG voor scherpe schermafbeeldingen, UI-opnamen, transparante afbeeldingen en verliesvrije bewerkingen. + Gebruik WebP, AVIF of JXL wanneer compacte, moderne uitvoer belangrijk is en de ontvangende app dit ondersteunt. + Audiocovers extraheren + Bewaar ingesloten albumhoezen of beschikbare mediaframes van audiobestanden als PNG-afbeeldingen + Haal illustraties uit audiobestanden + Audio Covers gebruikt Android-mediametagegevens om ingesloten illustraties uit audiobestanden te lezen. Wanneer ingebedde illustraties niet beschikbaar zijn, kan de retriever terugvallen op een mediaframe als Android er een kan bieden; als geen van beide bestaat, meldt de tool dat er geen afbeelding is om uit te pakken. + Open Audiocovers en selecteer een of meer audiobestanden met de verwachte albumhoezen. + Controleer de uitgepakte omslag voordat u deze opslaat of deelt, vooral als het gaat om bestanden van streaming- of opname-apps. + Als er geen afbeelding wordt gevonden, heeft het audiobestand waarschijnlijk geen ingesloten omslag of kan Android geen bruikbaar frame weergeven. + Metagegevens van data en locaties + Bepaal wat u wilt bewaren wanneer de opnametijd van belang is, maar GPS- of apparaatgegevens niet mogen lekken + Scheid nuttige metadata van privé-metagegevens + Metadata zijn niet allemaal even riskant. Vastleggingsdatums kunnen nuttig zijn voor archieven en sorteren, terwijl GPS, seriële velden van apparaten, softwaretags of eigenaarvelden meer kunnen onthullen dan bedoeld. + Bewaar datum- en tijdtags wanneer de chronologische volgorde van belang is voor albums, scans of projectgeschiedenis. + Verwijder GPS- en apparaatidentificerende tags voordat u afbeeldingen openbaar deelt of naar vreemden verzendt. + Exporteer een voorbeeld en inspecteer EXIF ​​opnieuw als de privacyvereisten streng zijn. + Vermijd botsingen tussen bestandsnamen + Bescherm batchuitvoer wanneer veel bestanden namen delen zoals image.jpg of screenshot.png + Maak elke uitvoernaam uniek + Dubbele bestandsnamen komen vaak voor als afbeeldingen afkomstig zijn van messengers, schermafbeeldingen, cloudmappen of meerdere camera\'s. Een goed patroon voorkomt onbedoelde vervanging en zorgt ervoor dat de output traceerbaar blijft naar de bron. + Vermeld de originele naam plus een achtervoegsel wanneer de bewerkte kopie herkenbaar moet zijn. + Voeg volgorde, tijdstempel, voorinstelling of dimensies toe bij het verwerken van grote gemengde batches. + Vermijd het overschrijven van workflows totdat u hebt gecontroleerd of het naamgevingspatroon niet met elkaar in conflict kan komen. + Bewaar of deel + Kies tussen een persistent bestand en een tijdelijke overdracht naar een andere app + Exporteer met de juiste intentie + Opslaan en delen kan hetzelfde aanvoelen, maar ze lossen verschillende problemen op. Door op te slaan wordt een bestand gemaakt dat u later kunt terugvinden; delen stuurt een gegenereerd resultaat via Android naar een andere app en laat mogelijk geen duurzame lokale kopie achter. + Gebruik Opslaan als de uitvoer in een bekende map moet blijven, een back-up moet worden gemaakt of later opnieuw moet worden gebruikt. + Gebruik Delen voor snelle berichten, e-mailbijlagen, sociale berichten of het verzenden van het resultaat naar een andere editor. + Sla eerst op als de ontvangende app onbetrouwbaar is of als het mislukte delen vervelend zou zijn om opnieuw te maken. + PDF-paginagrootte en marges + Kies het papierformaat, het aanpassingsgedrag en de ademruimte voordat u een document maakt + Maak afbeeldingspagina\'s afdrukbaar en leesbaar + Afbeeldingen kunnen lastige PDF-pagina\'s worden als hun vorm niet overeenkomt met het gekozen papierformaat. Marges, aanpassingsmodus en paginarichting bepalen of de inhoud bijgesneden, klein, uitgerekt of prettig leesbaar is. + Gebruik een echt papierformaat wanneer de PDF kan worden afgedrukt of in een formulier kan worden ingediend. + Gebruik marges voor scans en schermafbeeldingen, zodat de tekst niet tegen de paginarand komt te liggen. + Bekijk samen een voorbeeld van staande en liggende pagina\'s als de bronafbeeldingen een gemengde richting hebben. + Scans opruimen + Verbeter papierranden, schaduwen, contrast, rotatie en leesbaarheid vóór PDF of OCR + Bereid pagina\'s voor voordat u ze archiveert + Een scan kan technisch worden vastgelegd, maar is nog steeds moeilijk te lezen. Kleine opschoonstappen vóór het exporteren van PDF\'s zijn vaak belangrijker dan compressie-instellingen, vooral voor bonnen, handgeschreven notities en pagina\'s met weinig licht. + Corrigeer het perspectief en snijd tafelranden, vingers, schaduwen en achtergrondruis weg. + Verhoog het contrast zorgvuldig, zodat de tekst duidelijker wordt zonder stempels, handtekeningen of potloodstrepen te vernietigen. + Voer OCR pas uit nadat de pagina rechtop en leesbaar is, en verifieer vervolgens belangrijke cijfers handmatig. + PDF\'s comprimeren, grijswaarden weergeven of repareren + Gebruik PDF-bewerkingen uit één bestand voor lichtere, afdrukbare of opnieuw opgebouwde documentkopieën + Kies de bewerking voor het opschonen van PDF\'s + PDF Tools omvat afzonderlijke bewerkingen voor compressie, grijswaardenconversie en reparatie. Compressie en grijswaarden werken voornamelijk via ingebedde afbeeldingen, terwijl reparatie de PDF-structuur opnieuw opbouwt; geen van deze mag worden beschouwd als een gegarandeerde oplossing voor elk document. + Gebruik PDF comprimeren als het document afbeeldingen bevat en de bestandsgrootte belangrijk is om te delen. + Gebruik Grijstinten als het document gemakkelijker af te drukken moet zijn of als er geen kleurenafbeeldingen nodig zijn. + Gebruik Reparatie PDF op een kopie als een document niet kan worden geopend of als de interne structuur is beschadigd, en verifieer vervolgens het opnieuw opgebouwde bestand. + Afbeeldingen extraheren uit PDF\'s + Herstel ingesloten PDF-afbeeldingen en verpak de geëxtraheerde resultaten in een archief + Bewaar bronafbeeldingen uit documenten + Afbeeldingen extraheren scant de PDF op ingesloten afbeeldingsobjecten, slaat duplicaten waar mogelijk over en slaat herstelde afbeeldingen op in een gegenereerd ZIP-archief. Het extraheert alleen afbeeldingen die daadwerkelijk in de PDF zijn ingesloten, geen tekst, vectortekeningen of elke zichtbare pagina als screenshot. + Gebruik Afbeeldingen extraheren als u afbeeldingen nodig heeft die in een PDF zijn opgeslagen, zoals foto\'s uit een catalogus of gescande middelen. + Gebruik in plaats daarvan PDF naar afbeeldingen of pagina-extractie als u volledige pagina\'s nodig heeft, inclusief tekst en lay-out. + Als de tool geen ingesloten afbeeldingen rapporteert, bevat de pagina mogelijk tekst-/vectorinhoud of zijn de afbeeldingen mogelijk niet opgeslagen als extraheerbare objecten. + Metagegevens, afvlakking en afdrukuitvoer + Bewerk documenteigenschappen, raster pagina\'s of bereid opzettelijk aangepaste afdruklay-outs voor + Kies de actie voor het definitieve document + PDF-metagegevens, afvlakking en afdrukvoorbereiding lossen verschillende problemen in de laatste fase op. Metagegevens bepalen de documenteigenschappen, Flatten PDF rastert pagina\'s in minder bewerkbare uitvoer, en Print PDF bereidt een nieuwe lay-out voor met opties voor paginaformaat, oriëntatie, marge en pagina\'s per vel. + Gebruik metadata wanneer auteur, titel, trefwoorden, producent of opschoning van de privacy van belang zijn. + Gebruik Flatten PDF wanneer zichtbare pagina-inhoud moeilijker te bewerken is als afzonderlijke PDF-objecten. + Gebruik Print PDF als u een aangepast paginaformaat, afdrukstand, marges of meerdere pagina\'s per vel nodig heeft. + Afbeeldingen voorbereiden voor OCR + Gebruik bijsnijden, roteren, contrast, ruis verwijderen en schalen voordat u OCR vraagt ​​om tekst te lezen + Geef OCR schonere pixels + OCR-fouten komen vaak voort uit de invoerafbeelding en niet uit de OCR-engine. Scheve pagina\'s, laag contrast, onscherpte, schaduwen, kleine tekst en gemengde achtergronden verminderen allemaal de herkenningskwaliteit. + Snijd bij tot het tekstgebied en roteer de afbeelding zodat de lijnen horizontaal zijn voordat ze worden herkend. + Verhoog het contrast of converteer alleen naar helderder zwart-wit als letters daardoor beter leesbaar zijn. + Verklein kleine tekst enigszins naar boven, maar vermijd agressieve verscherping waardoor nepranden ontstaan. + Controleer QR-inhoud + Controleer koppelingen, Wi-Fi-inloggegevens, contacten en acties voordat u een code vertrouwt + Behandel gedecodeerde tekst als niet-vertrouwde invoer + Een QR-code kan een URL, contactkaart, Wi-Fi-wachtwoord, agenda-evenement, telefoonnummer of platte tekst verbergen. Het zichtbare label naast de code komt mogelijk niet overeen met de daadwerkelijke payload, dus bekijk de gedecodeerde inhoud voordat u ernaar handelt. + Inspecteer de volledige gedecodeerde URL voordat u deze opent, vooral bij verkorte of onbekende domeinen. + Wees voorzichtig met wifi-, contact-, agenda-, telefoon- en sms-codes, omdat deze gevoelige acties kunnen activeren. + Kopieer de inhoud eerst als u deze ergens anders moet verifiëren, in plaats van deze onmiddellijk te openen. + Genereer QR-codes + Maak codes op basis van platte tekst, URL\'s, wifi, contacten, e-mail, telefoon, sms en locatiegegevens + Bouw de juiste QR-payload + Het QR-scherm kan codes genereren uit ingevoerde inhoud en bestaande inhoud scannen. Gestructureerde QR-typen helpen gegevens te coderen in een formaat dat andere apps begrijpen, zoals Wi-Fi-inloggegevens, contactkaarten, e-mailvelden, telefoonnummers, sms-inhoud, URL\'s of geografische coördinaten. + Kies platte tekst voor eenvoudige notities, of gebruik een gestructureerd type wanneer de code moet worden geopend als Wi-Fi, contact, URL, e-mail, telefoon, sms of locatiegegevens. + Controleer elk veld voordat u de gegenereerde code deelt, omdat het zichtbare voorbeeld alleen de door u ingevoerde payload weergeeft. + Test de opgeslagen QR-code met een scanner wanneer deze wordt afgedrukt, openbaar wordt geplaatst of door andere mensen wordt gebruikt. + Kies een checksum-modus + Bereken hashes van bestanden of tekst, vergelijk één bestand of controleer meerdere bestanden batchgewijs + Controleer de inhoud, niet het uiterlijk + Checksum Tools heeft aparte tabbladen voor het hashen van bestanden, het hashen van tekst, het vergelijken van een bestand met een bekende hash en het vergelijken van batches. Een controlesom bewijst byte-voor-byte identiteit voor het geselecteerde algoritme; het bewijst niet dat twee afbeeldingen alleen maar op elkaar lijken. + Gebruik Berekenen voor bestanden en Teksthash voor getypte of geplakte tekstgegevens. + Gebruik Vergelijken wanneer iemand u een verwachte controlesom voor één bestand geeft. + Gebruik batchvergelijking wanneer veel bestanden in één keer hashes of verificatie nodig hebben, en zorg er vervolgens voor dat het geselecteerde algoritme consistent blijft. + Privacy op het klembord + Gebruik automatische klembordfuncties zonder per ongeluk gekopieerde privégegevens bloot te leggen + Houd gekopieerde inhoud opzettelijk + Automatisering van het klembord is handig, maar gekopieerde tekst kan wachtwoorden, links, adressen, tokens of privénotities bevatten. Beschouw op het klembord gebaseerde invoer als een snelheidsfunctie die moet aansluiten bij uw gewoonten en privacyverwachtingen. + Schakel automatisch klembordgebruik uit als er onverwacht privétekst in tools verschijnt. + Gebruik alleen opslaan op het klembord als u opzettelijk wilt dat het resultaat niet wordt opgeslagen. + Wis of vervang het klembord na het verwerken van gevoelige tekst, QR-gegevens of Base64-payloads. + Kleurformaten + Begrijp HEX-, RGB-, HSV-, alfa- en gekopieerde kleurwaarden voordat u deze ergens anders plakt + Kopieer het formaat dat het doel verwacht + Dezelfde zichtbare kleur kan op verschillende manieren worden geschreven. Een ontwerptool, CSS-veld, Android-kleurwaarde of afbeeldingseditor kan een andere volgorde, alfabehandeling of kleurmodel verwachten. + Gebruik HEX bij het plakken in web-, ontwerp- of themavelden die compacte kleurcodes verwachten. + Gebruik RGB of HSV als u kanalen, helderheid, verzadiging of tint handmatig moet aanpassen. + Vink alpha afzonderlijk aan als transparantie ertoe doet, omdat sommige bestemmingen dit negeren of een andere volgorde gebruiken. + Blader door de kleurenbibliotheek + Zoek benoemde kleuren op naam of HEX en houd favorieten bovenaan + Vind herbruikbare benoemde kleuren + Kleurenbibliotheek laadt een benoemde kleurencollectie, sorteert deze op naam, filtert op kleurnaam of HEX-waarde en slaat favoriete kleuren op, zodat belangrijke vermeldingen gemakkelijker te bereiken blijven. Dit is handig als u een kleur met een echte naam nodig heeft in plaats van een monster uit een afbeelding te nemen. + Zoek op kleurnaam als u de familie kent, zoals rood, blauw, leisteen of mint. + Zoek op HEX als u al een numerieke kleur heeft en overeenkomende of nabijgelegen namen wilt vinden. + Markeer veelgebruikte kleuren als favoriet, zodat ze tijdens het browsen een voorsprong hebben op de algemene bibliotheek. + Gebruik mesh-gradiëntcollecties + Download beschikbare mesh-gradiëntbronnen en deel geselecteerde afbeeldingen + Gebruik externe mesh-gradiëntmiddelen + Mesh Gradients kunnen een externe bronnenverzameling laden, ontbrekende verloopafbeeldingen downloaden, de downloadvoortgang weergeven en geselecteerde verlopen delen. De beschikbaarheid is afhankelijk van de netwerktoegang en de externe bronnenopslag, dus een lege lijst betekent meestal dat er nog geen bronnen beschikbaar waren. + Open Mesh Gradients vanuit de verloopgereedschappen als u kant-en-klare mesh-gradiëntafbeeldingen wilt. + Wacht op de voortgang van het laden van de bronnen of het downloaden voordat u ervan uitgaat dat de verzameling leeg is. + Selecteer en deel de verlopen die u nodig hebt, of ga terug naar Gradient Maker als u handmatig een aangepast verloop wilt maken. + Gegenereerde activaresolutie + Kies de uitvoergrootte voordat u verlopen, ruis, achtergronden, SVG-achtige kunst of texturen genereert + Genereer op het formaat dat u nodig heeft + Gegenereerde beelden hebben geen camera-origineel waarop ze kunnen terugvallen. Als de uitvoer te klein is, kan later schalen patronen verzachten, details verschuiven of de manier wijzigen waarop het item wordt weergegeven en gecomprimeerd. + Stel eerst de afmetingen in voor het uiteindelijke gebruik, zoals achtergrond, pictogram, achtergrond, afdruk of overlay. + Gebruik grotere formaten voor texturen die kunnen worden bijgesneden, ingezoomd of hergebruikt in verschillende lay-outs. + Exporteer testversies vóór grote output, omdat procedurele details het gedrag van compressie kunnen veranderen. + Exporteer huidige achtergronden + Haal beschikbare Home-, Lock- en ingebouwde achtergronden op van het apparaat + Bewaar of deel geïnstalleerde achtergronden + Wallpapers Export leest de achtergronden die Android beschikbaar stelt via de wallpaper-API\'s van het systeem. Afhankelijk van het apparaat, de Android-versie, de machtigingen en de buildvariant kunnen Home-, Lock- of ingebouwde achtergronden afzonderlijk beschikbaar zijn of ontbreken. + Open Achtergronden exporteren om de achtergronden te laden die de app door het systeem kan lezen. + Selecteer de beschikbare Home-, Lock- of ingebouwde achtergronditems die u wilt opslaan, kopiëren of delen. + Als een achtergrond ontbreekt of de functie niet beschikbaar is, is dit meestal een systeem-, machtigings- of buildvariantbeperking en geen bewerkingsinstelling. + Hoeken animatie gaspedaal + Kopieer kleur als + HEX + naam + Portretmatmodel voor snelle persoonsuitsnijdingen met zachter haar en randverwerking. + Snelle verbetering bij weinig licht met behulp van Zero-DCE++-curveschatting. + Resstormer beeldherstelmodel voor het verminderen van regenstrepen en artefacten bij nat weer. + Document-unwarping-model dat een coördinatenraster voorspelt en gebogen pagina\'s opnieuw in kaart brengt in een vlakkere scan. + Bewegingsduurschaal + Beheert de animatiesnelheid van de app: 0 schakelt beweging uit, 1 is normaal, hogere waarden vertragen animaties + Toon recente tools + Geef snelle toegang tot recent gebruikte tools op het hoofdscherm weer + Gebruiksstatistieken + Ontdek app-openingen, toollanceringen en uw meest gebruikte tools + App wordt geopend + Gereedschap wordt geopend + Laatste hulpmiddel + Succesvolle reddingen + Activiteitsreeks + Gegevens opgeslagen + Topformaat + Gebruikt gereedschap + %1$d opent • %2$s + Nog geen gebruiksstatistieken + Open een paar tools en ze verschijnen hier automatisch + Uw gebruiksstatistieken worden alleen lokaal op uw apparaat opgeslagen. ImageToolbox gebruikt ze alleen om uw persoonlijke activiteit, favoriete tools en opgeslagen gegevensinzichten te tonen + editor bewerk foto foto retoucheren aanpassen + formaat wijzigen converteren schaal resolutie afmetingen breedte hoogte jpg jpeg png webp comprimeren + comprimeer grootte gewicht bytes mb kb verminder kwaliteit optimaliseren + bijgesneden beeldverhouding bijsnijden + filtereffect kleurcorrectie lut-masker aanpassen + teken penseel potlood annoteer markeringen + cipher coderen decoderen wachtwoord geheim + achtergrondverwijderaar wissen verwijder uitsparing transparante alpha + preview viewer galerij inspecteren open + samenvoegen combineren panorama lange screenshot + URL webdownload internetlink afbeelding + picker pipet monster hex rgb kleur + paletkleurenstalen extraheren dominant + exif metadata privacy verwijder strip schone gps-locatie + vergelijk diff verschil voor na + limiet formaat wijzigen max. min. megapixels afmetingen klem + Hulpmiddelen voor pdf-documentpagina\'s + ocr-tekst herkent extractscan + verloop achtergrondkleur mesh + watermerk logo tekststempel overlay + gif-animatie geanimeerd frames converteren + apng-animatie geanimeerde png frames converteren + zip-archief comprimeren, inpakken, bestanden uitpakken + jxl jpeg xl converteert jpg-afbeeldingsformaat + SVG vectortracering converteert XML + formaat converteren jpg jpeg png webp heic avif jxl bmp + documentscanner scan papier perspectief bijsnijden + QR-barcodecodescanner scannen + stapeloverlay gemiddelde mediaan astrofotografie + gesplitste tegels raster segmentdelen + kleur converteren hex rgb hsl hsv cmyk lab + webp converteert geanimeerd afbeeldingsformaat + ruisgenerator willekeurige textuurkorrel + collage rasterindeling combineren + opmaaklagen annoteren overlay-bewerkingen + base64 codeert decodeert gegevens uri + checksum hash md5 sha sha1 sha256 verifiëren + mesh verloop achtergrond + exif-metadata bewerk gps-datumcamera + snijd gespleten rasterstukken tegels + audioomslag albumhoezen muziekmetagegevens + achtergrond export achtergrond + ascii-tekstkunstkarakters + ai ml neurale verbetering van het luxe segment + kleurenbibliotheek materiaalpalet met de naam kleuren + shader glsl fragmenteffectstudio + pdf samenvoegen combineren samenvoegen + pdf splitsen van afzonderlijke pagina\'s + pdf roteer de paginarichting + pdf herschikken pagina\'s opnieuw rangschikken + Pdf-paginanummers paginering + pdf ocr-tekst herkent doorzoekbaar extract + pdf watermerk stempel logo tekst + pdf handtekening teken tekenen + pdf beveiligen wachtwoord coderen slot + pdf ontgrendelen wachtwoord decoderen verwijder bescherming + pdf comprimeren verkleinen optimaliseren + pdf grijswaarden zwart wit monochroom + pdf reparatie fix herstellen gebroken + pdf-metadata-eigenschappen exif auteurstitel + pdf verwijder verwijder pagina\'s + pdf bijsnijdmarges + pdf annotaties vormen lagen afvlakken + pdf uittreksel afbeeldingen foto\'s + pdf zip-archief comprimeren + pdf-afdrukprinter + pdf preview-viewer lezen + afbeeldingen naar pdf converteren jpg jpeg png-document + pdf naar afbeeldingenpagina\'s exporteren jpg png + pdf annotaties opmerkingen verwijderen schoon + Neutrale variant + Fout + Slagschaduw + Tand Hoogte + Horizontaal tandbereik + Verticaal tandbereik + Bovenrand + Rechterrand + Onderrand + Linkerrand + Gescheurde rand + Gebruiksstatistieken opnieuw instellen + Tool wordt geopend, statistieken, streak, opgeslagen gegevens worden opgeslagen en het topformaat wordt gereset. Het openen van de app blijft ongewijzigd. + Audio + Bestand + Profielen exporteren + Huidig ​​profiel opslaan + Exportprofiel verwijderen + Wilt u het exportprofiel \\"%1$s\\" verwijderen? + Rand tekenen + Toon een dunne rand rond het teken- en wisdoek + Golvend + Afgeronde UI-elementen met een zachte golvende rand + Lepel + Inkeping + Afgeronde hoeken naar binnen gesneden + Getrapte hoeken met een dubbele snede + Tijdelijke aanduiding + Gebruik {filename} om de originele bestandsnaam zonder extensie in te voegen + Gloed + Basisbedrag + Ringbedrag + Ray bedrag + Ringbreedte + Perspectief vervormen + Java-look en feel + Scheer + X-hoek + en hoek + Formaat van uitvoer wijzigen + Waterdruppel + Golflengte + Fase + Hoge pas + Kleur masker + Chroom + Oplossen + Zachtheid + Feedback + Lensvervaging + Lage invoer + Hoge invoer + Lage output + Hoge output + Lichteffecten + Hoogte van de bult + Zachtheid van de bult + Rimpeling + X-amplitude + Y-amplitude + X-golflengte + Y-golflengte + Adaptieve vervaging + Textuur generatie + Genereer verschillende procedurele texturen en sla ze op als afbeeldingen + Textuurtype + Vooroordeel + Tijd + Glans + Dispersie + Monsters + Hoekcoëfficiënt + Gradiëntcoëfficiënt + Rastertype + Afstand macht + Schaal Y + Wazigheid + Basistype + Turbulentiefactor + Schalen + Iteraties + Ringen + Geborsteld metaal + Bijtende stoffen + Mobiel + Schaakbord + fBm + Marmer + Plasma + Dekbed + Hout + willekeurig + Vierkant + Zeshoekig + Achthoekig + Driehoekig + Geribbeld + VL-ruis + SC-ruis + Recent + Nog geen recent gebruikte filters + structuur generator geborsteld metaal bijtende stoffen cellulair schaakbord marmer plasma quilt hout baksteen camouflage cel wolk barst stof gebladerte honingraat ijs lavanevel papier Roest zand rook steen terrein topografie water rimpeling + Baksteen + Camouflage + Cel + Wolk + Scheur + Stof + Gebladerte + Honingraat + Ijs + Lava + Nevel + Papier + Roest + Zand + Rook + Steen + Terrein + Topografie + Waterrimpeling + Geavanceerd hout + Mortel breedte + Onregelmatigheid + Schuine kant + Ruwheid + Eerste drempel + Tweede drempel + Derde drempel + Zachtheid van de randen + Randbreedte + Dekking + Detail + Diepte + Vertakking + Horizontale draden + Verticale draden + Dons + Aderen + Verlichting + Breedte van de scheur + Vorst + Stroom + Korst + Wolkendichtheid + Sterren + Vezeldichtheid + Vezelsterkte + Vlekken + Corrosie + Pitten + Vlokken + Duinfrequentie + Windhoek + Rimpelingen + Bosjes + Ader schaal + Waterniveau + Bergniveau + Erosie + Sneeuwniveau + Aantal lijnen + Lijndikte + Schaduw + Kleur van de poriën + Mortel kleur + Donkere baksteenkleur + Kleur baksteen + Markeer kleur + Donkere kleur + Bos kleur + Aarde kleur + Zand kleur + Achtergrondkleur + Cel kleur + Kleur van de rand + Kleur van de hemel + Schaduw kleur + Lichte kleur + Oppervlaktekleur + Variatie kleur + Kleur van de barst + Warp-kleur + Inslag kleur + Donkere bladkleur + Bladkleur + Randkleur + Honing kleur + Diepe kleur + IJs kleur + Vorst kleur + Kleur van de korst + Kleur wassen + Gloed kleur + Kleur van de ruimte + Violette kleur + Blauwe kleur + Basiskleur + Vezel kleur + Vlek kleur + Metaal kleur + Donkere roestkleur + Roest kleur + Oranje kleur + Rook kleur + Kleur van de ader + Kleur laagland + Waterkleur + Rots kleur + Sneeuw kleur + Lage kleur + Hoge kleur + Lijnkleur + Ondiepe kleur + Gras + Vuil + Leer + Concreet + Asfalt + Mos + Vuur + Aurora + Olievlek + Waterverf + Abstracte stroom + Opaal + Damascus staal + Bliksem + Flueel + Inkt marmering + Holografische folie + Bioluminescentie + Kosmische draaikolk + Lavalamp + Evenementhorizon + Fractale bloei + Chromatische tunnel + Eclipse Corona + Vreemde aantrekker + Ferrofluïde kroon + Supernova + Iris + Pauwenveer + Nautilusschelp + Geringde planeet + Bladdichtheid + Lengte mes + Wind + Fragmentariteit + klontjes + Vocht + Kiezels + Rimpels + Poriën + Totaal + Scheuren + Teer + Dragen + Spikkels + Vezels + Vlam frequentie + Rook + Intensiteit + Linten + Banden + Iridescentie + Duisternis + Bloemen + Pigment + Randen + Papier + Verspreiding + Symmetrie + Scherpte + Kleurenspel + Melkachtigheid + Lagen + Vouwen + Pools + Takken + Richting + Glans + Plooien + Bevedering + Inktbalans + Spectrum + Kreukels + Diffractie + Armen + Twist + Kern gloed + klodders + Schijf kantelen + Horizongrootte + Schijfbreedte + Lenzen + Bloemblaadjes + Krul + Filigraan + Facetten + Kromming + Grootte van de maan + Corona-formaat + Stralen + Diamanten ring + Lobben + Baandichtheid + Dikte + Spieken + Spike lengte + Lichaamsgrootte + Metalen + Schokradius + Schaalbreedte + Uitgegooid + Pupilgrootte + Iris maat + Kleurvariatie + Vanglicht + Grootte van ogen + Weerhaak dichtheid + Bochten + Kamers + Opening + Randen + Parelmoer + Grootte van de planeet + Ringkanteling + Ringbreedte + Sfeer + Vuil kleur + Donkere graskleur + Kleur gras + Tipkleur + Donkere aardekleur + Droge kleur + Kiezel kleur + Kleur leer + Beton kleur + Teer kleur + Asfalt kleur + Steen kleur + Stof kleur + Kleur van de bodem + Donkere moskleur + Mos kleur + Rode kleur + Kern kleur + Groene kleur + Cyaan kleur + Magenta kleur + Gouden kleur + Papierkleur + Pigmentkleur + Secundaire kleur + Eerste kleur + Tweede kleur + Roze kleur + Donkere staalkleur + Kleur staal + Lichte staalkleur + Oxide kleur + Halo-kleur + Kleur van de bout + Fluwelen kleur + Glans kleur + Blauwe inktkleur + Rode inktkleur + Donkere inktkleur + Zilver kleur + Gele kleur + Weefsel kleur + Schijf kleur + Warme kleur + Lenskleur + Kleur buitenkant + Innerlijke kleur + Corona-kleur + Koude kleur + Warme kleur + Kleur van de wolk + Vlam kleur + Kleur van de veren + Kleur van de schaal + Parel kleur + Planeet kleur + Ringkleur + Verwijder originele bestanden na het opslaan + Alleen succesvol verwerkte bronbestanden worden verwijderd + Originele bestanden verwijderd: %1$d + Kan originele bestanden niet verwijderen: %1$d + Originele bestanden verwijderd: %1$d, mislukt: %2$d + Bladgebaren + Sta het slepen van uitgebreide optiebladen toe + Chroma-subsampling + Beetje diepte + Zonder verlies + %1$d-bit + Batch hernoemen + Hernoem meerdere bestanden met behulp van bestandsnaampatronen + batch hernoemen van bestanden bestandsnaam patroonvolgorde datumextensie + Kies bestanden waarvan u de naam wilt wijzigen + Bestandsnaampatroon + Hernoemen + Datumbron + EXIF-datum genomen + Datum van bestandswijziging + Aanmaakdatum bestand + Huidige datum + Handmatige datum + Handmatige datum + Handmatige tijd + De geselecteerde datum is voor sommige bestanden niet beschikbaar. Voor hen wordt de huidige datum gebruikt. + Voer een bestandsnaampatroon in + Het patroon produceert een ongeldige of te lange bestandsnaam + Het patroon produceert dubbele bestandsnamen + Alle bestandsnamen komen al overeen met het patroon + Dit patroon maakt gebruik van tokens die niet beschikbaar zijn voor batchhernoeming + Naam blijft hetzelfde + Bestanden zijn succesvol hernoemd + Sommige bestanden konden niet worden hernoemd + Er werd geen toestemming verleend + Gebruikt de originele bestandsnaam zonder extensie, zodat u de bronidentificatie intact kunt houden. Ondersteunt ook segmenteren met \\o{start:end}, transliteratie met \\o{t} en vervangen door \\o{s/oud/nieuw/}. + Automatisch oplopende teller. Ondersteunt aangepaste opmaak: \\c{padding} (bijvoorbeeld \\c{3} -&gt; 001), \\c{start:step} of \\c{start:step:padding}. + De naam van de bovenliggende map waarin het oorspronkelijke bestand zich bevond. + De grootte van het originele bestand. Ondersteunt eenheden: \\z{b}, \\z{kb}, \\z{mb} of gewoon \\z voor een voor mensen leesbaar formaat. + Genereert een willekeurige Universally Unique Identifier (UUID) om de uniciteit van de bestandsnaam te garanderen. + Het hernoemen van bestanden kan niet ongedaan worden gemaakt. Zorg ervoor dat de nieuwe namen correct zijn voordat u verdergaat. + Bevestig hernoemen + Instellingen zijmenu + Menuschaal + Menu alfa + Automatische witbalans + Knippen + Controleren op verborgen watermerken + Opslag + Cachelimiet + Wis de cache automatisch wanneer deze groter wordt dan deze omvang + Opruimingsinterval + Hoe vaak moet worden gecontroleerd of de cache moet worden gewist + Bij app-lancering + 1 dag + 1 week + 1 maand + Wis de app-cache automatisch volgens de geselecteerde limiet en interval + Verplaatsbaar gewasgebied + Maakt het mogelijk om het hele bijsnijdgebied te verplaatsen door er naar binnen te slepen + GIF samenvoegen + Combineer meerdere GIF-clips in één animatie + GIF-clips bestellen + Achteruit + Speel omgekeerd + Boemerang + Speel vooruit en dan achteruit + Normaliseer de framegrootte + Schaal frames zodat ze in de grootste clip passen; uitschakelen om hun oorspronkelijke schaal te behouden + Vertraging tussen clips (ms) + Map + Selecteer alle afbeeldingen uit een map en de submappen ervan + Dubbele zoeker + Vind exacte kopieën en visueel vergelijkbare afbeeldingen + dubbele zoeker vergelijkbare afbeeldingen exacte kopieën foto\'s opruimen opslag dHash SHA-256 + Kies afbeeldingen om duplicaten te vinden + Gelijkenisgevoeligheid + Exacte kopieën + Soortgelijke afbeeldingen + Selecteer alle exacte duplicaten + Selecteer alles behalve aanbevolen + Geen duplicaten gevonden + Probeer meer afbeeldingen toe te voegen of de gelijkenisgevoeligheid te verhogen + Kon %1$d afbeelding(en) niet lezen + %1$s • %2$s terugvorderbaar + %1$d groepen • %2$s terugvorderbaar + %1$d geselecteerd • %2$s + Dit kan niet ongedaan worden gemaakt. Android kan u vragen om de verwijdering te bevestigen in een systeemdialoog. + Niet gevonden + Verplaats om te beginnen + Verplaats naar einde + \ No newline at end of file diff --git a/core/resources/src/main/res/values-pa/strings.xml b/core/resources/src/main/res/values-pa/strings.xml new file mode 100644 index 0000000..00efad5 --- /dev/null +++ b/core/resources/src/main/res/values-pa/strings.xml @@ -0,0 +1,3739 @@ + + + + ਕੁਝ ਗਲਤ ਹੋ ਗਿਆ: %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 ਪੰਨਿਆਂ ਤੋਂ ਚੁਣੀਆਂ ਗਈਆਂ ਐਨੋਟੇਸ਼ਨ ਕਿਸਮਾਂ ਨੂੰ ਹਟਾਓ ਜਿਵੇਂ ਕਿ ਲਿੰਕ, ਟਿੱਪਣੀਆਂ, ਹਾਈਲਾਈਟਸ, ਆਕਾਰ ਜਾਂ ਫਾਰਮ ਖੇਤਰ + ਹਾਈਪਰਲਿੰਕਸ + ਫਾਈਲ ਅਟੈਚਮੈਂਟਸ + ਲਾਈਨਾਂ + ਪੌਪਅੱਪ + ਸਟਪਸ + ਆਕਾਰ + ਟੈਕਸਟ ਨੋਟਸ + ਟੈਕਸਟ ਮਾਰਕਅੱਪ + ਫਾਰਮ ਖੇਤਰ + ਮਾਰਕਅੱਪ + ਅਗਿਆਤ + ਐਨੋਟੇਸ਼ਨ + ਅਨਗਰੁੱਪ ਕਰੋ + ਸੰਰਚਨਾਯੋਗ ਰੰਗ ਅਤੇ ਆਫਸੈਟਾਂ ਦੇ ਨਾਲ ਪਰਤ ਦੇ ਪਿੱਛੇ ਬਲਰ ਸ਼ੈਡੋ ਸ਼ਾਮਲ ਕਰੋ + ਸਮਗਰੀ ਜਾਗਰੂਕ ਵਿਗਾੜ + ਇਸ ਕਾਰਵਾਈ ਨੂੰ ਪੂਰਾ ਕਰਨ ਲਈ ਲੋੜੀਂਦੀ ਮੈਮੋਰੀ ਨਹੀਂ ਹੈ। ਕਿਰਪਾ ਕਰਕੇ ਇੱਕ ਛੋਟਾ ਚਿੱਤਰ ਵਰਤ ਕੇ, ਹੋਰ ਐਪਾਂ ਨੂੰ ਬੰਦ ਕਰਨ, ਜਾਂ ਐਪ ਨੂੰ ਰੀਸਟਾਰਟ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ। + ਸਮਾਨਾਂਤਰ ਵਰਕਰ + ਇਹ ਚਿੱਤਰ ਸੁਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤੇ ਗਏ ਸਨ ਕਿਉਂਕਿ ਪਰਿਵਰਤਿਤ ਫ਼ਾਈਲਾਂ ਅਸਲ ਨਾਲੋਂ ਵੱਡੀਆਂ ਹੋਣਗੀਆਂ। ਅਸਲ ਚਿੱਤਰਾਂ ਨੂੰ ਮਿਟਾਉਣ ਤੋਂ ਪਹਿਲਾਂ ਇਸ ਸੂਚੀ ਦੀ ਜਾਂਚ ਕਰੋ। + ਫਾਈਲਾਂ ਛੱਡੀਆਂ ਗਈਆਂ: %1$s + ਐਪ ਲੌਗਸ + ਸਮੱਸਿਆਵਾਂ ਦਾ ਪਤਾ ਲਗਾਉਣ ਲਈ ਐਪ ਦੇ ਲੌਗ ਦੇਖੋ + ਸਮੂਹਬੱਧ ਟੂਲਸ ਵਿੱਚ ਮਨਪਸੰਦ + ਜਦੋਂ ਟੂਲਾਂ ਨੂੰ ਕਿਸਮ ਅਨੁਸਾਰ ਸਮੂਹਬੱਧ ਕੀਤਾ ਜਾਂਦਾ ਹੈ ਤਾਂ ਇੱਕ ਟੈਬ ਵਜੋਂ ਮਨਪਸੰਦ ਜੋੜਦਾ ਹੈ + ਆਖਰੀ ਵਾਂਗ ਮਨਪਸੰਦ ਦਿਖਾਓ + ਮਨਪਸੰਦ ਟੂਲ ਟੈਬ ਨੂੰ ਅੰਤ ਵਿੱਚ ਭੇਜਦਾ ਹੈ + ਹਰੀਜੱਟਲ ਵਿੱਥ + ਲੰਬਕਾਰੀ ਵਿੱਥ + ਪਿਛਾਖੜੀ ਊਰਜਾ + ਫਾਰਵਰਡ ਐਨਰਜੀ ਐਲਗੋਰਿਦਮ ਦੀ ਬਜਾਏ ਸਧਾਰਨ ਗਰੇਡੀਐਂਟ ਮੈਗਨੀਟਿਊਡ ਐਨਰਜੀ ਮੈਪ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਨਕਾਬਪੋਸ਼ ਖੇਤਰ ਨੂੰ ਹਟਾਓ + ਸੀਮਾਂ ਨਕਾਬਪੋਸ਼ ਖੇਤਰ ਵਿੱਚੋਂ ਲੰਘਣਗੀਆਂ, ਇਸਦੀ ਸੁਰੱਖਿਆ ਕਰਨ ਦੀ ਬਜਾਏ ਸਿਰਫ ਚੁਣੇ ਹੋਏ ਖੇਤਰ ਨੂੰ ਹਟਾਉਂਦੀਆਂ ਹਨ + VHS NTSC + ਉੱਨਤ NTSC ਸੈਟਿੰਗਾਂ + ਮਜ਼ਬੂਤ ​​VHS ਅਤੇ ਪ੍ਰਸਾਰਣ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਲਈ ਵਾਧੂ ਐਨਾਲਾਗ ਟਿਊਨਿੰਗ + ਕ੍ਰੋਮਾ ਖੂਨ ਨਿਕਲਣਾ + ਟੇਪ ਪਹਿਨਣ + ਟਰੈਕਿੰਗ + ਲੂਮਾ ਸਮੀਅਰ + ਘੰਟੀ ਵੱਜ ਰਹੀ ਹੈ + ਬਰਫ਼ + ਖੇਤਰ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਫਿਲਟਰ ਦੀ ਕਿਸਮ + ਲੂਮਾ ਫਿਲਟਰ ਇਨਪੁਟ ਕਰੋ + ਇਨਪੁਟ ਕਰੋਮਾ ਲੋਪਾਸ + ਕ੍ਰੋਮਾ ਡੀਮੋਡੂਲੇਸ਼ਨ + ਪੜਾਅ ਸ਼ਿਫਟ + ਪੜਾਅ ਆਫਸੈੱਟ + ਸਿਰ ਬਦਲਣਾ + ਸਿਰ ਬਦਲਣ ਦੀ ਉਚਾਈ + ਹੈੱਡ ਸਵਿਚਿੰਗ ਆਫਸੈੱਟ + ਹੈੱਡ ਸਵਿਚਿੰਗ ਸ਼ਿਫਟ + ਮੱਧ-ਲਾਈਨ ਸਥਿਤੀ + ਮਿਡ-ਲਾਈਨ ਝਟਕਾ + ਸ਼ੋਰ ਦੀ ਉਚਾਈ ਨੂੰ ਟਰੈਕ ਕਰਨਾ + ਟਰੈਕਿੰਗ ਵੇਵ + ਬਰਫ਼ ਨੂੰ ਟਰੈਕ ਕਰਨਾ + ਬਰਫ ਦੀ ਐਨੀਸੋਟ੍ਰੋਪੀ ਨੂੰ ਟਰੈਕ ਕਰਨਾ + ਟਰੈਕਿੰਗ ਸ਼ੋਰ + ਸ਼ੋਰ ਦੀ ਤੀਬਰਤਾ ਨੂੰ ਟਰੈਕ ਕਰਨਾ + ਸੰਯੁਕਤ ਸ਼ੋਰ + ਸੰਯੁਕਤ ਸ਼ੋਰ ਬਾਰੰਬਾਰਤਾ + ਮਿਸ਼ਰਿਤ ਸ਼ੋਰ ਦੀ ਤੀਬਰਤਾ + ਸੰਯੁਕਤ ਸ਼ੋਰ ਵੇਰਵੇ + ਘੰਟੀ ਵੱਜਣ ਦੀ ਬਾਰੰਬਾਰਤਾ + ਰਿੰਗਿੰਗ ਪਾਵਰ + ਲੂਮਾ ਰੌਲਾ + ਲੂਮਾ ਸ਼ੋਰ ਬਾਰੰਬਾਰਤਾ + ਲੂਮਾ ਸ਼ੋਰ ਦੀ ਤੀਬਰਤਾ + Luma ਸ਼ੋਰ ਵੇਰਵੇ + ਕ੍ਰੋਮਾ ਰੌਲਾ + ਕ੍ਰੋਮਾ ਸ਼ੋਰ ਬਾਰੰਬਾਰਤਾ + ਕ੍ਰੋਮਾ ਸ਼ੋਰ ਦੀ ਤੀਬਰਤਾ + ਕ੍ਰੋਮਾ ਸ਼ੋਰ ਵੇਰਵੇ + ਬਰਫ਼ ਦੀ ਤੀਬਰਤਾ + ਬਰਫ਼ ਐਨੀਸੋਟ੍ਰੋਪੀ + Chroma ਪੜਾਅ ਸ਼ੋਰ + ਕ੍ਰੋਮਾ ਪੜਾਅ ਗੜਬੜ + ਕ੍ਰੋਮਾ ਦੇਰੀ ਹਰੀਜੱਟਲ + ਕ੍ਰੋਮਾ ਦੇਰੀ ਲੰਬਕਾਰੀ + VHS ਟੇਪ ਦੀ ਗਤੀ + VHS ਕ੍ਰੋਮਾ ਦਾ ਨੁਕਸਾਨ + VHS ਤਿੱਖੀ ਤੀਬਰਤਾ + VHS ਸ਼ਾਰਪਨ ਬਾਰੰਬਾਰਤਾ + VHS ਕਿਨਾਰੇ ਦੀ ਲਹਿਰ ਦੀ ਤੀਬਰਤਾ + VHS ਕਿਨਾਰੇ ਵੇਵ ਸਪੀਡ + VHS ਕਿਨਾਰੇ ਵੇਵ ਬਾਰੰਬਾਰਤਾ + VHS ਕਿਨਾਰੇ ਵੇਵ ਵੇਰਵੇ + ਆਉਟਪੁੱਟ ਕ੍ਰੋਮਾ ਲੋਪਾਸ + ਖਿਤਿਜੀ ਸਕੇਲ + ਲੰਬਕਾਰੀ ਸਕੇਲ + ਸਕੇਲ ਫੈਕਟਰ ਐਕਸ + ਸਕੇਲ ਫੈਕਟਰ Y + ਆਮ ਚਿੱਤਰ ਵਾਟਰਮਾਰਕਸ ਨੂੰ ਖੋਜਦਾ ਹੈ ਅਤੇ ਉਹਨਾਂ ਨੂੰ LaMa ਨਾਲ ਪੇਂਟ ਕਰਦਾ ਹੈ। ਖੋਜ ਅਤੇ ਪੇਂਟਿੰਗ ਮਾਡਲਾਂ ਨੂੰ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਡਾਊਨਲੋਡ ਕਰਦਾ ਹੈ + ਡਿਊਟਰਾਨੋਪੀਆ + ਚਿੱਤਰ ਦਾ ਵਿਸਤਾਰ ਕਰੋ + YOLO v11 ਸੈਗਮੈਂਟੇਸ਼ਨ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹੋਏ ਆਬਜੈਕਟ ਸੈਗਮੈਂਟੇਸ਼ਨ ਆਧਾਰਿਤ ਬੈਕਗ੍ਰਾਊਂਡ ਰਿਮੂਵਰ + ਲਾਈਨ ਕੋਣ ਦਿਖਾਓ + ਡਰਾਇੰਗ ਦੌਰਾਨ ਡਿਗਰੀ ਵਿੱਚ ਮੌਜੂਦਾ ਲਾਈਨ ਰੋਟੇਸ਼ਨ ਦਿਖਾਉਂਦਾ ਹੈ + ਐਨੀਮੇਟਡ ਇਮੋਜੀ + ਉਪਲਬਧ ਇਮੋਜੀ ਨੂੰ ਐਨੀਮੇਸ਼ਨ ਵਜੋਂ ਦਿਖਾਓ + ਮੂਲ ਫੋਲਡਰ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕਰੋ + ਚੁਣੇ ਗਏ ਫੋਲਡਰ ਦੀ ਬਜਾਏ ਮੂਲ ਫਾਈਲ ਦੇ ਅੱਗੇ ਨਵੀਆਂ ਫਾਈਲਾਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ + ਵਾਟਰਮਾਰਕ PDF + ਕਾਫ਼ੀ ਮੈਮੋਰੀ ਨਹੀਂ ਹੈ + ਚਿੱਤਰ ਸੰਭਾਵਤ ਤੌਰ \'ਤੇ ਇਸ ਡਿਵਾਈਸ \'ਤੇ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਲਈ ਬਹੁਤ ਵੱਡਾ ਹੈ, ਜਾਂ ਸਿਸਟਮ ਦੀ ਉਪਲਬਧ ਮੈਮੋਰੀ ਖਤਮ ਹੋ ਗਈ ਹੈ। ਚਿੱਤਰ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਨੂੰ ਘਟਾਉਣ, ਹੋਰ ਐਪਾਂ ਨੂੰ ਬੰਦ ਕਰਨ, ਜਾਂ ਇੱਕ ਛੋਟੀ ਫ਼ਾਈਲ ਚੁਣਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ। + ਡੀਬੱਗ ਮੀਨੂ + ਐਪ ਫੰਕਸ਼ਨਾਂ ਦੀ ਜਾਂਚ ਕਰਨ ਲਈ ਮੀਨੂ, ਇਹ ਉਤਪਾਦਨ ਰੀਲੀਜ਼ ਵਿੱਚ ਦਿਖਾਉਣ ਦਾ ਇਰਾਦਾ ਨਹੀਂ ਹੈ + ਪ੍ਰਦਾਤਾ + PaddleOCR ਨੂੰ ਤੁਹਾਡੀ ਡਿਵਾਈਸ \'ਤੇ ਵਾਧੂ ONNX ਮਾਡਲਾਂ ਦੀ ਲੋੜ ਹੈ। ਕੀ ਤੁਸੀਂ %1$s ਡਾਟਾ ਡਾਊਨਲੋਡ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ? + ਯੂਨੀਵਰਸਲ + ਕੋਰੀਅਨ + ਲਾਤੀਨੀ + ਪੂਰਬੀ ਸਲਾਵਿਕ + ਥਾਈ + ਯੂਨਾਨੀ + ਅੰਗਰੇਜ਼ੀ + ਸਿਰਿਲਿਕ + ਅਰਬੀ + ਦੇਵਨਾਗਰੀ + ਤਾਮਿਲ + ਤੇਲਗੂ + ਸ਼ੈਡਰ + ਸ਼ੈਡਰ ਪ੍ਰੀਸੈੱਟ + ਕੋਈ ਸ਼ੈਡਰ ਨਹੀਂ ਚੁਣਿਆ ਗਿਆ + ਸ਼ੈਡਰ ਸਟੂਡੀਓ + ਕਸਟਮ ਫਰੈਗਮੈਂਟ ਸ਼ੈਡਰ ਬਣਾਓ, ਸੰਪਾਦਿਤ ਕਰੋ, ਪ੍ਰਮਾਣਿਤ ਕਰੋ, ਆਯਾਤ ਕਰੋ ਅਤੇ ਨਿਰਯਾਤ ਕਰੋ + ਸੁਰੱਖਿਅਤ ਕੀਤੇ ਸ਼ੈਡਰ + ਸੁਰੱਖਿਅਤ ਕੀਤੇ ਸ਼ੈਡਰ ਖੋਲ੍ਹੋ, ਡੁਪਲੀਕੇਟ, ਨਿਰਯਾਤ, ਸਾਂਝਾ ਕਰੋ ਜਾਂ ਮਿਟਾਓ + ਨਵਾਂ ਸ਼ੈਡਰ + ਸ਼ੈਡਰ ਫਾਈਲ + .itshader JSON + %1$d ਪਰਮ + ਸ਼ੈਡਰ ਸਰੋਤ + ਸਿਰਫ਼ void main() ਦਾ ਮੁੱਖ ਭਾਗ ਲਿਖੋ। ਯੂਵੀ ਕੋਆਰਡੀਨੇਟਸ ਲਈ ਟੈਕਸਟਚਰ ਕੋਆਰਡੀਨੇਟ ਦੀ ਵਰਤੋਂ ਕਰੋ ਅਤੇ ਸਰੋਤ ਨਮੂਨੇ ਵਜੋਂ ਇਨਪੁਟ ਇਮੇਜ ਟੈਕਸਟ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਫੰਕਸ਼ਨ + ਵਿਕਲਪਿਕ ਸਹਾਇਕ ਫੰਕਸ਼ਨ, ਸਥਿਰਾਂਕ, ਅਤੇ ਸਟ੍ਰਕਟਸ void main() ਤੋਂ ਪਹਿਲਾਂ ਸੰਮਿਲਿਤ ਕੀਤੇ ਗਏ ਹਨ। ਪਰਮਾਂ ਤੋਂ ਵਰਦੀਆਂ ਤਿਆਰ ਕੀਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ। + ਜਦੋਂ ਸ਼ੈਡਰ ਨੂੰ ਸੰਪਾਦਨਯੋਗ ਮੁੱਲਾਂ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ ਤਾਂ ਇੱਥੇ ਵਰਦੀਆਂ ਸ਼ਾਮਲ ਕਰੋ। + ਸ਼ੈਡਰ ਰੀਸੈਟ ਕਰੋ + ਮੌਜੂਦਾ ਸ਼ੈਡਰ ਡਰਾਫਟ ਨੂੰ ਕਲੀਅਰ ਕੀਤਾ ਜਾਵੇਗਾ + ਅਵੈਧ ਸ਼ੇਡਰ + ਅਸਮਰਥਿਤ ਸ਼ੇਡਰ ਸੰਸਕਰਣ %1$d। ਸਮਰਥਿਤ ਸੰਸਕਰਣ: %2$d। + ਸ਼ੈਡਰ ਨਾਮ ਖਾਲੀ ਨਹੀਂ ਹੋਣਾ ਚਾਹੀਦਾ। + ਸ਼ੈਡਰ ਸਰੋਤ ਖਾਲੀ ਨਹੀਂ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ। + ਸ਼ੈਡਰ ਸਰੋਤ ਵਿੱਚ ਅਸਮਰਥਿਤ ਅੱਖਰ ਹਨ: %1$s। ਸਿਰਫ਼ ASCII GLSL ਸਰੋਤ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਪੈਰਾਮੀਟਰ \\\"%1$s\\\" ਨੂੰ ਇੱਕ ਤੋਂ ਵੱਧ ਵਾਰ ਘੋਸ਼ਿਤ ਕੀਤਾ ਗਿਆ ਹੈ। + ਪੈਰਾਮੀਟਰ ਨਾਮ ਖਾਲੀ ਨਹੀਂ ਹੋਣੇ ਚਾਹੀਦੇ। + ਪੈਰਾਮੀਟਰ \\\"%1$s\\\" ਇੱਕ ਰਾਖਵੇਂ GPUI ਚਿੱਤਰ ਨਾਮ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ। + ਪੈਰਾਮੀਟਰ \\\"%1$s\\\" ਇੱਕ ਵੈਧ GLSL ਪਛਾਣਕਰਤਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ। + ਪੈਰਾਮੀਟਰ \\\"%1$s\\\" ਵਿੱਚ %2$s ਮੁੱਲ ਕਿਸਮ \\\"%3$s\\\" ਹੈ, \\\"%4$s\\\" ਦੀ ਉਮੀਦ ਹੈ। + ਪੈਰਾਮੀਟਰ \\\"%1$s\\\" ਬੂਲ ਮੁੱਲਾਂ ਲਈ ਘੱਟੋ-ਘੱਟ ਜਾਂ ਅਧਿਕਤਮ ਨੂੰ ਪਰਿਭਾਸ਼ਿਤ ਨਹੀਂ ਕਰ ਸਕਦਾ ਹੈ। + ਪੈਰਾਮੀਟਰ \\\"%1$s\\\" ਵਿੱਚ ਅਧਿਕਤਮ ਤੋਂ ਘੱਟੋ ਘੱਟ ਹੈ। + ਪੈਰਾਮੀਟਰ \\\"%1$s\\\" ਪੂਰਵ-ਨਿਰਧਾਰਤ ਘੱਟੋ-ਘੱਟ ਤੋਂ ਘੱਟ ਹੈ। + ਪੈਰਾਮੀਟਰ \\\"%1$s\\\" ਪੂਰਵ-ਨਿਰਧਾਰਤ ਅਧਿਕਤਮ ਤੋਂ ਵੱਧ ਹੈ। + ਸ਼ੈਡਰ ਨੂੰ \\\"ਯੂਨੀਫਾਰਮ ਸੈਂਪਲਰ2D %1$s;\\\" ਘੋਸ਼ਿਤ ਕਰਨਾ ਚਾਹੀਦਾ ਹੈ। + ਸ਼ੈਡਰ ਨੂੰ \\\"ਵੱਖ-ਵੱਖ vec2 %1$s;\\\" ਘੋਸ਼ਿਤ ਕਰਨਾ ਚਾਹੀਦਾ ਹੈ। + ਸ਼ੈਡਰ ਨੂੰ \\\"void main()\\\" ਨੂੰ ਪਰਿਭਾਸ਼ਿਤ ਕਰਨਾ ਚਾਹੀਦਾ ਹੈ। + ਸ਼ੈਡਰ ਨੂੰ gl_FragColor ਲਈ ਇੱਕ ਰੰਗ ਲਿਖਣਾ ਚਾਹੀਦਾ ਹੈ। + ShaderToy mainImage shaders ਸਮਰਥਿਤ ਨਹੀਂ ਹਨ। GPUImage ਦੇ void main() ਇਕਰਾਰਨਾਮੇ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਐਨੀਮੇਟਡ ShaderToy ਯੂਨੀਫਾਰਮ \\\"iTime\\\" ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ। + ਐਨੀਮੇਟਡ ShaderToy ਯੂਨੀਫਾਰਮ \\\"iFrame\\\" ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ। + ShaderToy ਯੂਨੀਫਾਰਮ \\\"iResolution\\\" ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ। + ShaderToy iChannel ਟੈਕਸਟ ਸਮਰਥਿਤ ਨਹੀਂ ਹਨ। + ਬਾਹਰੀ ਟੈਕਸਟ ਸਮਰਥਿਤ ਨਹੀਂ ਹਨ। + ਬਾਹਰੀ ਟੈਕਸਟ ਐਕਸਟੈਂਸ਼ਨ ਸਮਰਥਿਤ ਨਹੀਂ ਹਨ। + ਲਿਬਰੇਟਰੋ ਸ਼ੇਡਰ ਪੈਰਾਮੀਟਰ ਸਮਰਥਿਤ ਨਹੀਂ ਹਨ। + ਸਿਰਫ਼ ਇੱਕ ਇੰਪੁੱਟ ਟੈਕਸਟ ਸਮਰਥਿਤ ਹੈ। \\\"ਵਰਦੀ %1$s %2$s;\\\" ਹਟਾਓ। + ਪੈਰਾਮੀਟਰ \\\"%1$s\\\" ਨੂੰ \\\"ਵਰਦੀ %2$s %1$s;\\\" ਵਜੋਂ ਘੋਸ਼ਿਤ ਕੀਤਾ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ। + ਪੈਰਾਮੀਟਰ \\\"%1$s\\\" ਨੂੰ \\\"ਵਰਦੀ %2$s %1$s;\\\" ਵਜੋਂ ਘੋਸ਼ਿਤ ਕੀਤਾ ਗਿਆ ਹੈ, ਉਮੀਦ ਕੀਤੀ ਗਈ \\\"ਵਰਦੀ %3$s %1$s;\\\"। + ਸ਼ੈਡਰ ਫਾਈਲ ਖਾਲੀ ਹੈ। + ਸ਼ੈਡਰ ਫਾਈਲ ਵਰਜਨ, ਨਾਮ ਅਤੇ ਸ਼ੈਡਰ ਖੇਤਰਾਂ ਦੇ ਨਾਲ ਇੱਕ .itshader JSON ਵਸਤੂ ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ। + ਸ਼ੈਡਰ ਫਾਈਲ ਵੈਧ JSON ਨਹੀਂ ਹੈ ਜਾਂ .itshader ਫਾਰਮੈਟ ਨਾਲ ਮੇਲ ਨਹੀਂ ਖਾਂਦੀ ਹੈ। + ਖੇਤਰ \\\"%1$s\\\" ਲੋੜੀਂਦਾ ਹੈ। + %1$s ਲੋੜੀਂਦਾ ਹੈ। + %1$s \\\"%2$s\\\" ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ। ਸਮਰਥਿਤ ਕਿਸਮਾਂ: %3$s। + \\\"%2$s\\\" ਪੈਰਾਮੀਟਰਾਂ ਲਈ %1$s ਦੀ ਲੋੜ ਹੈ। + %1$s ਇੱਕ ਸੀਮਤ ਸੰਖਿਆ ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ। + %1$s ਇੱਕ ਪੂਰਨ ਅੰਕ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ। + %1$s ਸੱਚ ਜਾਂ ਗਲਤ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ। + %1$s ਇੱਕ ਰੰਗ ਸਤਰ, RGB/RGBA ਐਰੇ, ਜਾਂ ਰੰਗ ਵਸਤੂ ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ। + %1$s ਨੂੰ #RRGGBB ਜਾਂ #RRGGBBAA ਫਾਰਮੈਟ ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਚਾਹੀਦੀ ਹੈ। + %1$s ਵਿੱਚ ਵੈਧ ਹੈਕਸਾਡੈਸੀਮਲ ਰੰਗ ਚੈਨਲ ਹੋਣੇ ਚਾਹੀਦੇ ਹਨ। + %1$s ਵਿੱਚ 3 ਜਾਂ 4 ਰੰਗ ਚੈਨਲ ਹੋਣੇ ਚਾਹੀਦੇ ਹਨ। + %1$s 0 ਅਤੇ 255 ਦੇ ਵਿਚਕਾਰ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ। + %1$s ਇੱਕ ਦੋ-ਨੰਬਰ ਐਰੇ ਜਾਂ x ਅਤੇ y ਵਾਲੀ ਵਸਤੂ ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ। + %1$s ਵਿੱਚ ਬਿਲਕੁਲ 2 ਨੰਬਰ ਹੋਣੇ ਚਾਹੀਦੇ ਹਨ। + ਡੁਪਲੀਕੇਟ + ਹਮੇਸ਼ਾ EXIF ​​ਸਾਫ਼ ਕਰੋ + ਸੇਵ \'ਤੇ ਚਿੱਤਰ EXIF ​​ਡੇਟਾ ਨੂੰ ਹਟਾਓ, ਭਾਵੇਂ ਕੋਈ ਟੂਲ ਮੈਟਾਡੇਟਾ ਰੱਖਣ ਦੀ ਬੇਨਤੀ ਕਰਦਾ ਹੈ + ਮਦਦ ਅਤੇ ਸੁਝਾਅ + %1$d ਟਿਊਟੋਰਿਅਲ + ਓਪਨ ਟੂਲ + ਮੁੱਖ ਟੂਲ, ਨਿਰਯਾਤ ਵਿਕਲਪ, PDF ਪ੍ਰਵਾਹ, ਰੰਗ ਉਪਯੋਗਤਾਵਾਂ, ਅਤੇ ਆਮ ਸਮੱਸਿਆਵਾਂ ਲਈ ਹੱਲ ਸਿੱਖੋ + ਸ਼ੁਰੂ ਕਰਨਾ + ਟੂਲ ਚੁਣੋ, ਫਾਈਲਾਂ ਆਯਾਤ ਕਰੋ, ਨਤੀਜੇ ਸੁਰੱਖਿਅਤ ਕਰੋ, ਅਤੇ ਤੇਜ਼ੀ ਨਾਲ ਸੈਟਿੰਗਾਂ ਦੀ ਮੁੜ ਵਰਤੋਂ ਕਰੋ + ਚਿੱਤਰ ਸੰਪਾਦਨ + ਮੁੜ ਆਕਾਰ ਦਿਓ, ਕੱਟੋ, ਫਿਲਟਰ ਕਰੋ, ਪਿਛੋਕੜ ਮਿਟਾਓ, ਡਰਾਅ ਕਰੋ ਅਤੇ ਵਾਟਰਮਾਰਕ ਚਿੱਤਰ + ਫਾਈਲਾਂ ਅਤੇ ਮੈਟਾਡੇਟਾ + ਫਾਰਮੈਟਾਂ ਨੂੰ ਕਨਵਰਟ ਕਰੋ, ਆਉਟਪੁੱਟ ਦੀ ਤੁਲਨਾ ਕਰੋ, ਗੋਪਨੀਯਤਾ ਦੀ ਰੱਖਿਆ ਕਰੋ, ਅਤੇ ਫਾਈਲਨਾਮਾਂ ਨੂੰ ਨਿਯੰਤਰਿਤ ਕਰੋ + PDF ਅਤੇ ਦਸਤਾਵੇਜ਼ + PDF ਬਣਾਓ, ਦਸਤਾਵੇਜ਼ਾਂ ਨੂੰ ਸਕੈਨ ਕਰੋ, OCR ਪੰਨੇ, ਅਤੇ ਸ਼ੇਅਰਿੰਗ ਲਈ ਫਾਈਲਾਂ ਤਿਆਰ ਕਰੋ + ਟੈਕਸਟ, QR, ਅਤੇ ਡਾਟਾ + ਟੈਕਸਟ ਨੂੰ ਪਛਾਣੋ, ਕੋਡ ਸਕੈਨ ਕਰੋ, ਬੇਸ 64 ਨੂੰ ਏਨਕੋਡ ਕਰੋ, ਹੈਸ਼ਾਂ ਦੀ ਜਾਂਚ ਕਰੋ ਅਤੇ ਪੈਕੇਜ ਫਾਈਲਾਂ + ਰੰਗ ਸੰਦ + ਰੰਗ ਚੁਣੋ, ਪੈਲੇਟ ਬਣਾਓ, ਰੰਗ ਲਾਇਬ੍ਰੇਰੀਆਂ ਦੀ ਪੜਚੋਲ ਕਰੋ, ਅਤੇ ਗਰੇਡੀਐਂਟ ਬਣਾਓ + ਰਚਨਾਤਮਕ ਸੰਦ + SVG, ASCII ਕਲਾ, ਸ਼ੋਰ ਟੈਕਸਟ, ਸ਼ੈਡਰ, ਅਤੇ ਪ੍ਰਯੋਗਾਤਮਕ ਵਿਜ਼ੂਅਲ ਤਿਆਰ ਕਰੋ + ਸਮੱਸਿਆ ਨਿਪਟਾਰਾ + ਭਾਰੀ ਫਾਈਲਾਂ, ਪਾਰਦਰਸ਼ਤਾ, ਸ਼ੇਅਰਿੰਗ, ਆਯਾਤ ਅਤੇ ਰਿਪੋਰਟਿੰਗ ਮੁੱਦਿਆਂ ਨੂੰ ਠੀਕ ਕਰੋ + ਸਹੀ ਟੂਲ ਚੁਣੋ + ਸਹੀ ਥਾਂ \'ਤੇ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਸ਼੍ਰੇਣੀਆਂ, ਖੋਜ, ਮਨਪਸੰਦ, ਅਤੇ ਸਾਂਝੀਆਂ ਕਾਰਵਾਈਆਂ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਸਭ ਤੋਂ ਵਧੀਆ ਐਂਟਰੀ ਪੁਆਇੰਟ ਤੋਂ ਸ਼ੁਰੂ ਕਰੋ + ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਵਿੱਚ ਬਹੁਤ ਸਾਰੇ ਫੋਕਸ ਕੀਤੇ ਟੂਲ ਹਨ, ਅਤੇ ਉਹਨਾਂ ਵਿੱਚੋਂ ਬਹੁਤ ਸਾਰੇ ਪਹਿਲੀ ਨਜ਼ਰ ਵਿੱਚ ਓਵਰਲੈਪ ਹੁੰਦੇ ਹਨ। ਉਸ ਕੰਮ ਤੋਂ ਸ਼ੁਰੂ ਕਰੋ ਜਿਸ ਨੂੰ ਤੁਸੀਂ ਪੂਰਾ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ, ਫਿਰ ਉਹ ਟੂਲ ਚੁਣੋ ਜੋ ਨਤੀਜੇ ਦੇ ਸਭ ਤੋਂ ਮਹੱਤਵਪੂਰਨ ਹਿੱਸੇ ਨੂੰ ਨਿਯੰਤਰਿਤ ਕਰਦਾ ਹੈ: ਆਕਾਰ, ਫਾਰਮੈਟ, ਮੈਟਾਡੇਟਾ, ਟੈਕਸਟ, PDF ਪੰਨੇ, ਰੰਗ, ਜਾਂ ਖਾਕਾ। + ਖੋਜ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਤੁਸੀਂ ਕਿਰਿਆ ਦਾ ਨਾਮ ਜਾਣਦੇ ਹੋ, ਜਿਵੇਂ ਕਿ ਮੁੜ ਆਕਾਰ, PDF, EXIF, OCR, QR, ਜਾਂ ਰੰਗ। + ਇੱਕ ਸ਼੍ਰੇਣੀ ਖੋਲ੍ਹੋ ਜਦੋਂ ਤੁਸੀਂ ਸਿਰਫ਼ ਕੰਮ ਦੀ ਕਿਸਮ ਜਾਣਦੇ ਹੋ, ਫਿਰ ਮਨਪਸੰਦ ਦੇ ਤੌਰ \'ਤੇ ਅਕਸਰ ਟੂਲ ਪਿੰਨ ਕਰੋ। + ਜਦੋਂ ਕੋਈ ਹੋਰ ਐਪ ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਵਿੱਚ ਇੱਕ ਚਿੱਤਰ ਨੂੰ ਸਾਂਝਾ ਕਰਦਾ ਹੈ, ਤਾਂ ਸ਼ੇਅਰ ਸ਼ੀਟ ਪ੍ਰਵਾਹ ਵਿੱਚੋਂ ਟੂਲ ਚੁਣੋ। + ਨਤੀਜੇ ਆਯਾਤ ਕਰੋ, ਸੁਰੱਖਿਅਤ ਕਰੋ ਅਤੇ ਸਾਂਝੇ ਕਰੋ + ਇਨਪੁਟ ਚੁੱਕਣ ਤੋਂ ਲੈ ਕੇ ਸੰਪਾਦਿਤ ਫਾਈਲ ਨੂੰ ਨਿਰਯਾਤ ਕਰਨ ਤੱਕ ਦੇ ਆਮ ਪ੍ਰਵਾਹ ਨੂੰ ਸਮਝੋ + ਆਮ ਸੰਪਾਦਨ ਪ੍ਰਵਾਹ ਦੀ ਪਾਲਣਾ ਕਰੋ + ਜ਼ਿਆਦਾਤਰ ਟੂਲ ਇੱਕੋ ਤਾਲ ਦੀ ਪਾਲਣਾ ਕਰਦੇ ਹਨ: ਇਨਪੁਟ ਚੁਣੋ, ਵਿਕਲਪਾਂ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ, ਪੂਰਵਦਰਸ਼ਨ ਕਰੋ, ਫਿਰ ਸੁਰੱਖਿਅਤ ਕਰੋ ਜਾਂ ਸਾਂਝਾ ਕਰੋ। ਮਹੱਤਵਪੂਰਨ ਵੇਰਵੇ ਇਹ ਹੈ ਕਿ ਸੁਰੱਖਿਅਤ ਕਰਨਾ ਇੱਕ ਆਉਟਪੁੱਟ ਫਾਈਲ ਬਣਾਉਂਦਾ ਹੈ, ਜਦੋਂ ਕਿ ਸਾਂਝਾ ਕਰਨਾ ਆਮ ਤੌਰ \'ਤੇ ਕਿਸੇ ਹੋਰ ਐਪ ਨੂੰ ਤੁਰੰਤ ਹੈਂਡਆਫ ਕਰਨ ਲਈ ਸਭ ਤੋਂ ਵਧੀਆ ਹੁੰਦਾ ਹੈ। + ਸਿੰਗਲ-ਇਮੇਜ ਟੂਲਸ ਲਈ ਇੱਕ ਫਾਈਲ ਚੁਣੋ ਜਾਂ ਬੈਚ ਟੂਲਸ ਲਈ ਕਈ ਫਾਈਲਾਂ ਚੁਣੋ ਜਦੋਂ ਚੋਣਕਾਰ ਇਸਦੀ ਇਜਾਜ਼ਤ ਦਿੰਦਾ ਹੈ। + ਸੁਰੱਖਿਅਤ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਪੂਰਵਦਰਸ਼ਨ ਅਤੇ ਆਉਟਪੁੱਟ ਨਿਯੰਤਰਣਾਂ ਦੀ ਜਾਂਚ ਕਰੋ, ਖਾਸ ਤੌਰ \'ਤੇ ਫਾਰਮੈਟ, ਗੁਣਵੱਤਾ ਅਤੇ ਫਾਈਲਨਾਮ ਵਿਕਲਪ। + ਇੱਕ ਤੇਜ਼ ਹੈਂਡਆਫ ਲਈ ਸ਼ੇਅਰ ਦੀ ਵਰਤੋਂ ਕਰੋ, ਜਾਂ ਜਦੋਂ ਤੁਹਾਨੂੰ ਚੁਣੇ ਹੋਏ ਫੋਲਡਰ ਵਿੱਚ ਇੱਕ ਨਿਰੰਤਰ ਕਾਪੀ ਦੀ ਲੋੜ ਹੋਵੇ ਤਾਂ ਸੁਰੱਖਿਅਤ ਕਰੋ। + ਇੱਕੋ ਸਮੇਂ ਬਹੁਤ ਸਾਰੀਆਂ ਤਸਵੀਰਾਂ ਨਾਲ ਕੰਮ ਕਰੋ + ਬੈਚ ਟੂਲ ਵਾਰ-ਵਾਰ ਮੁੜ-ਆਕਾਰ, ਰੂਪਾਂਤਰਨ, ਸੰਕੁਚਨ, ਅਤੇ ਨਾਮਕਰਨ ਕਾਰਜਾਂ ਲਈ ਸਭ ਤੋਂ ਵਧੀਆ ਹਨ + ਇੱਕ ਅਨੁਮਾਨਯੋਗ ਬੈਚ ਤਿਆਰ ਕਰੋ + ਬੈਚ ਪ੍ਰੋਸੈਸਿੰਗ ਸਭ ਤੋਂ ਤੇਜ਼ ਹੁੰਦੀ ਹੈ ਜਦੋਂ ਹਰ ਆਉਟਪੁੱਟ ਨੂੰ ਉਸੇ ਨਿਯਮਾਂ ਦੀ ਪਾਲਣਾ ਕਰਨੀ ਚਾਹੀਦੀ ਹੈ. ਇਹ ਇੱਕ ਵੱਡੀ ਗਲਤੀ ਕਰਨ ਲਈ ਸਭ ਤੋਂ ਆਸਾਨ ਸਥਾਨ ਵੀ ਹੈ, ਇਸਲਈ ਲੰਬੇ ਨਿਰਯਾਤ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਨਾਮਕਰਨ, ਗੁਣਵੱਤਾ, ਮੈਟਾਡੇਟਾ ਅਤੇ ਫੋਲਡਰ ਵਿਵਹਾਰ ਦੀ ਚੋਣ ਕਰੋ। + ਸਾਰੇ ਸੰਬੰਧਿਤ ਚਿੱਤਰਾਂ ਨੂੰ ਇਕੱਠੇ ਚੁਣੋ ਅਤੇ ਕ੍ਰਮ ਨੂੰ ਰੱਖੋ ਜੇਕਰ ਆਉਟਪੁੱਟ ਕ੍ਰਮ \'ਤੇ ਨਿਰਭਰ ਕਰਦਾ ਹੈ। + ਨਿਰਯਾਤ ਸ਼ੁਰੂ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਇੱਕ ਵਾਰ ਮੁੜ ਆਕਾਰ, ਫਾਰਮੈਟ, ਗੁਣਵੱਤਾ, ਮੈਟਾਡੇਟਾ, ਅਤੇ ਫਾਈਲਨਾਮ ਨਿਯਮ ਸੈਟ ਕਰੋ। + ਜਦੋਂ ਸਰੋਤ ਫਾਈਲਾਂ ਵੱਡੀਆਂ ਹੋਣ ਜਾਂ ਜਦੋਂ ਤੁਹਾਡੇ ਲਈ ਟੀਚਾ ਫਾਰਮੈਟ ਨਵਾਂ ਹੋਵੇ ਤਾਂ ਪਹਿਲਾਂ ਇੱਕ ਛੋਟਾ ਟੈਸਟ ਬੈਚ ਚਲਾਓ। + ਤੇਜ਼ ਸਿੰਗਲ ਸੰਪਾਦਨ + ਜਦੋਂ ਤੁਹਾਨੂੰ ਇੱਕ ਚਿੱਤਰ ਵਿੱਚ ਕਈ ਸਧਾਰਨ ਤਬਦੀਲੀਆਂ ਦੀ ਲੋੜ ਹੋਵੇ ਤਾਂ ਆਲ-ਇਨ-ਵਨ ਐਡੀਟਰ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਟੂਲ ਹਾਪਿੰਗ ਤੋਂ ਬਿਨਾਂ ਇੱਕ ਚਿੱਤਰ ਨੂੰ ਪੂਰਾ ਕਰੋ + ਸਿੰਗਲ ਐਡਿਟ ਉਦੋਂ ਲਾਭਦਾਇਕ ਹੁੰਦਾ ਹੈ ਜਦੋਂ ਚਿੱਤਰ ਨੂੰ ਇੱਕ ਵਿਸ਼ੇਸ਼ ਕਾਰਵਾਈ ਦੀ ਬਜਾਏ ਤਬਦੀਲੀਆਂ ਦੀ ਇੱਕ ਛੋਟੀ ਲੜੀ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। ਇਹ ਆਮ ਤੌਰ \'ਤੇ ਇੱਕ ਬੈਚ ਵਰਕਫਲੋ ਨੂੰ ਬਣਾਏ ਬਿਨਾਂ ਇੱਕ ਅੰਤਮ ਕਾਪੀ ਨੂੰ ਤੁਰੰਤ ਸਾਫ਼ ਕਰਨ, ਪੂਰਵਦਰਸ਼ਨ ਕਰਨ ਅਤੇ ਨਿਰਯਾਤ ਕਰਨ ਲਈ ਤੇਜ਼ ਹੁੰਦਾ ਹੈ। + ਇੱਕ ਚਿੱਤਰ ਲਈ ਸਿੰਗਲ ਐਡਿਟ ਖੋਲ੍ਹੋ ਜਿਸ ਨੂੰ ਆਮ ਵਿਵਸਥਾਵਾਂ, ਮਾਰਕਅੱਪ, ਕ੍ਰੌਪ, ਰੋਟੇਸ਼ਨ, ਜਾਂ ਨਿਰਯਾਤ ਤਬਦੀਲੀਆਂ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। + ਸੰਪਾਦਨਾਂ ਨੂੰ ਉਸ ਕ੍ਰਮ ਵਿੱਚ ਲਾਗੂ ਕਰੋ ਜੋ ਪਹਿਲਾਂ ਸਭ ਤੋਂ ਘੱਟ ਪਿਕਸਲ ਨੂੰ ਬਦਲਦਾ ਹੈ, ਜਿਵੇਂ ਕਿ ਫਿਲਟਰਾਂ ਤੋਂ ਪਹਿਲਾਂ ਕੱਟਣਾ ਅਤੇ ਅੰਤਿਮ ਸੰਕੁਚਨ ਤੋਂ ਪਹਿਲਾਂ ਫਿਲਟਰ। + ਜਦੋਂ ਤੁਹਾਨੂੰ ਬੈਚ ਪ੍ਰੋਸੈਸਿੰਗ, ਸਹੀ ਫਾਈਲ-ਆਕਾਰ ਟੀਚਿਆਂ, PDF ਆਉਟਪੁੱਟ, OCR, ਜਾਂ ਮੈਟਾਡੇਟਾ ਕਲੀਨਅੱਪ ਦੀ ਲੋੜ ਹੋਵੇ ਤਾਂ ਇਸਦੀ ਬਜਾਏ ਇੱਕ ਵਿਸ਼ੇਸ਼ ਟੂਲ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਪ੍ਰੀਸੈਟਸ ਅਤੇ ਸੈਟਿੰਗਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਵਾਰ-ਵਾਰ ਚੋਣਾਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ ਅਤੇ ਆਪਣੇ ਪਸੰਦੀਦਾ ਵਰਕਫਲੋ ਲਈ ਐਪ ਨੂੰ ਟਿਊਨ ਕਰੋ + ਇੱਕੋ ਸੈੱਟਅੱਪ ਨੂੰ ਦੁਹਰਾਉਣ ਤੋਂ ਬਚੋ + ਪ੍ਰੀਸੈੱਟ ਅਤੇ ਸੈਟਿੰਗਾਂ ਉਦੋਂ ਉਪਯੋਗੀ ਹੁੰਦੀਆਂ ਹਨ ਜਦੋਂ ਤੁਸੀਂ ਇੱਕੋ ਕਿਸਮ ਦੀ ਫਾਈਲ ਨੂੰ ਅਕਸਰ ਨਿਰਯਾਤ ਕਰਦੇ ਹੋ। ਇੱਕ ਚੰਗਾ ਪ੍ਰੀਸੈਟ ਇੱਕ ਵਾਰ-ਵਾਰ ਮੈਨੂਅਲ ਚੈਕਲਿਸਟ ਨੂੰ ਇੱਕ ਟੈਪ ਵਿੱਚ ਬਦਲਦਾ ਹੈ, ਜਦੋਂ ਕਿ ਗਲੋਬਲ ਸੈਟਿੰਗਾਂ ਡਿਫੌਲਟ ਨੂੰ ਪਰਿਭਾਸ਼ਿਤ ਕਰਦੀਆਂ ਹਨ ਜਿਸ ਨਾਲ ਨਵੇਂ ਟੂਲ ਸ਼ੁਰੂ ਹੁੰਦੇ ਹਨ। + ਆਮ ਆਉਟਪੁੱਟ ਆਕਾਰ, ਫਾਰਮੈਟ, ਗੁਣਵੱਤਾ ਮੁੱਲ, ਜਾਂ ਨਾਮਕਰਨ ਪੈਟਰਨ ਲਈ ਪ੍ਰੀਸੈੱਟ ਬਣਾਓ। + ਡਿਫੌਲਟ ਫਾਰਮੈਟ, ਗੁਣਵੱਤਾ, ਸੇਵ ਫੋਲਡਰ, ਫਾਈਲ ਨਾਮ, ਚੋਣਕਾਰ, ਅਤੇ ਇੰਟਰਫੇਸ ਵਿਵਹਾਰ ਲਈ ਸੈਟਿੰਗਾਂ ਦੀ ਸਮੀਖਿਆ ਕਰੋ। + ਐਪ ਨੂੰ ਮੁੜ ਸਥਾਪਿਤ ਕਰਨ ਜਾਂ ਕਿਸੇ ਹੋਰ ਡਿਵਾਈਸ \'ਤੇ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਸੈਟਿੰਗਾਂ ਦਾ ਬੈਕਅੱਪ ਲਓ। + ਉਪਯੋਗੀ ਪੂਰਵ-ਨਿਰਧਾਰਤ ਸੈੱਟ ਕਰੋ + ਇੱਕ ਵਾਰ ਡਿਫੌਲਟ ਫਾਰਮੈਟ, ਗੁਣਵੱਤਾ, ਸਕੇਲ ਮੋਡ, ਰੀਸਾਈਜ਼ ਕਿਸਮ ਅਤੇ ਰੰਗ ਸਪੇਸ ਚੁਣੋ + ਨਵੇਂ ਸਾਧਨਾਂ ਨੂੰ ਆਪਣੇ ਟੀਚੇ ਦੇ ਨੇੜੇ ਸ਼ੁਰੂ ਕਰੋ + ਡਿਫੌਲਟ ਮੁੱਲ ਸਿਰਫ਼ ਕਾਸਮੈਟਿਕ ਤਰਜੀਹਾਂ ਨਹੀਂ ਹਨ। ਉਹ ਇਹ ਫੈਸਲਾ ਕਰਦੇ ਹਨ ਕਿ ਕਿਹੜਾ ਫਾਰਮੈਟ, ਕੁਆਲਿਟੀ, ਰੀਸਾਈਜ਼ ਵਿਵਹਾਰ, ਸਕੇਲ ਮੋਡ, ਅਤੇ ਰੰਗ ਸਪੇਸ ਬਹੁਤ ਸਾਰੇ ਟੂਲਸ ਵਿੱਚ ਪਹਿਲਾਂ ਦਿਖਾਈ ਦਿੰਦੇ ਹਨ, ਜੋ ਹਰ ਨਿਰਯਾਤ \'ਤੇ ਇੱਕੋ ਵਿਕਲਪ ਨੂੰ ਮੁੜ-ਚੁਣਨ ਤੋਂ ਬਚਣ ਵਿੱਚ ਮਦਦ ਕਰਦਾ ਹੈ। + ਆਪਣੀ ਆਮ ਮੰਜ਼ਿਲ ਨਾਲ ਮੇਲ ਕਰਨ ਲਈ ਡਿਫੌਲਟ ਚਿੱਤਰ ਫਾਰਮੈਟ ਅਤੇ ਗੁਣਵੱਤਾ ਸੈੱਟ ਕਰੋ, ਜਿਵੇਂ ਕਿ ਫੋਟੋਆਂ ਲਈ JPEG ਜਾਂ ਅਲਫ਼ਾ ਲਈ PNG/WebP। + ਡਿਫੌਲਟ ਰੀਸਾਈਜ਼ ਕਿਸਮ ਅਤੇ ਸਕੇਲ ਮੋਡ ਨੂੰ ਟਿਊਨ ਕਰੋ ਜੇਕਰ ਤੁਸੀਂ ਅਕਸਰ ਸਖਤ ਮਾਪਾਂ, ਸਮਾਜਿਕ ਪਲੇਟਫਾਰਮਾਂ, ਜਾਂ ਦਸਤਾਵੇਜ਼ ਪੰਨਿਆਂ ਲਈ ਨਿਰਯਾਤ ਕਰਦੇ ਹੋ। + ਰੰਗ ਸਪੇਸ ਡਿਫੌਲਟ ਸਿਰਫ ਉਦੋਂ ਬਦਲੋ ਜਦੋਂ ਤੁਹਾਡੇ ਵਰਕਫਲੋ ਨੂੰ ਡਿਸਪਲੇ, ਪ੍ਰਿੰਟ, ਜਾਂ ਬਾਹਰੀ ਸੰਪਾਦਕਾਂ ਵਿੱਚ ਇਕਸਾਰ ਰੰਗ ਪ੍ਰਬੰਧਨ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। + ਚੋਣਕਾਰ ਅਤੇ ਲਾਂਚਰ + ਜਦੋਂ ਐਪ ਬਹੁਤ ਸਾਰੇ ਡਾਇਲਾਗ ਖੋਲ੍ਹਦਾ ਹੈ ਜਾਂ ਗਲਤ ਥਾਂ \'ਤੇ ਸ਼ੁਰੂ ਹੁੰਦਾ ਹੈ ਤਾਂ ਚੋਣਕਾਰ ਸੈਟਿੰਗਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਫਾਈਲਾਂ ਖੋਲ੍ਹਣ ਨੂੰ ਆਪਣੀ ਆਦਤ ਨਾਲ ਮੇਲ ਖਾਂਦਾ ਬਣਾਓ + ਚੋਣਕਾਰ ਅਤੇ ਲਾਂਚਰ ਸੈਟਿੰਗਾਂ ਇਹ ਨਿਯੰਤਰਿਤ ਕਰਦੀਆਂ ਹਨ ਕਿ ਕੀ ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਮੁੱਖ ਸਕ੍ਰੀਨ, ਇੱਕ ਟੂਲ, ਜਾਂ ਇੱਕ ਫਾਈਲ ਚੋਣ ਪ੍ਰਵਾਹ ਤੋਂ ਸ਼ੁਰੂ ਹੁੰਦਾ ਹੈ। ਇਹ ਵਿਕਲਪ ਖਾਸ ਤੌਰ \'ਤੇ ਉਦੋਂ ਲਾਭਦਾਇਕ ਹੁੰਦੇ ਹਨ ਜਦੋਂ ਤੁਸੀਂ ਆਮ ਤੌਰ \'ਤੇ Android ਸ਼ੇਅਰ ਸ਼ੀਟ ਤੋਂ ਦਾਖਲ ਹੁੰਦੇ ਹੋ ਜਾਂ ਜਦੋਂ ਤੁਸੀਂ ਚਾਹੁੰਦੇ ਹੋ ਕਿ ਐਪ ਇੱਕ ਟੂਲ ਲਾਂਚਰ ਵਾਂਗ ਮਹਿਸੂਸ ਕਰੇ। + ਜੇਕਰ ਸਿਸਟਮ ਚੋਣਕਾਰ ਫਾਈਲਾਂ ਨੂੰ ਲੁਕਾਉਂਦਾ ਹੈ, ਬਹੁਤ ਸਾਰੀਆਂ ਤਾਜ਼ਾ ਆਈਟਮਾਂ ਦਿਖਾਉਂਦਾ ਹੈ, ਜਾਂ ਕਲਾਉਡ ਫਾਈਲਾਂ ਨੂੰ ਖਰਾਬ ਢੰਗ ਨਾਲ ਹੈਂਡਲ ਕਰਦਾ ਹੈ ਤਾਂ ਫੋਟੋ ਚੋਣਕਾਰ ਸੈਟਿੰਗਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਸਿਰਫ਼ ਉਹਨਾਂ ਟੂਲਸ ਲਈ ਛੱਡਣ ਨੂੰ ਯੋਗ ਬਣਾਓ ਜਿੱਥੇ ਤੁਸੀਂ ਆਮ ਤੌਰ \'ਤੇ ਸ਼ੇਅਰ ਤੋਂ ਦਾਖਲ ਹੁੰਦੇ ਹੋ ਜਾਂ ਸਰੋਤ ਫ਼ਾਈਲ ਨੂੰ ਪਹਿਲਾਂ ਹੀ ਜਾਣਦੇ ਹੋ। + ਲਾਂਚਰ ਮੋਡ ਦੀ ਸਮੀਖਿਆ ਕਰੋ ਜੇਕਰ ਤੁਸੀਂ ਚਾਹੁੰਦੇ ਹੋ ਕਿ ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਇੱਕ ਸਧਾਰਨ ਹੋਮ ਸਕ੍ਰੀਨ ਨਾਲੋਂ ਇੱਕ ਟੂਲ ਚੋਣਕਾਰ ਵਾਂਗ ਵਿਹਾਰ ਕਰੇ। + ਚਿੱਤਰ ਸਰੋਤ + ਉਹ ਚੋਣਕਾਰ ਮੋਡ ਚੁਣੋ ਜੋ ਗੈਲਰੀਆਂ, ਫ਼ਾਈਲ ਪ੍ਰਬੰਧਕਾਂ ਅਤੇ ਕਲਾਊਡ ਫ਼ਾਈਲਾਂ ਨਾਲ ਸਭ ਤੋਂ ਵਧੀਆ ਕੰਮ ਕਰਦਾ ਹੈ + ਸਹੀ ਸਰੋਤ ਤੋਂ ਫਾਈਲਾਂ ਚੁਣੋ + ਚਿੱਤਰ ਸਰੋਤ ਸੈਟਿੰਗਾਂ ਪ੍ਰਭਾਵਿਤ ਕਰਦੀਆਂ ਹਨ ਕਿ ਐਪ ਕਿਵੇਂ ਚਿੱਤਰਾਂ ਲਈ ਐਂਡਰਾਇਡ ਨੂੰ ਪੁੱਛਦਾ ਹੈ। ਸਭ ਤੋਂ ਵਧੀਆ ਵਿਕਲਪ ਇਸ ਗੱਲ \'ਤੇ ਨਿਰਭਰ ਕਰਦਾ ਹੈ ਕਿ ਤੁਹਾਡੀਆਂ ਫਾਈਲਾਂ ਸਿਸਟਮ ਗੈਲਰੀ, ਇੱਕ ਫਾਈਲ ਮੈਨੇਜਰ ਫੋਲਡਰ, ਇੱਕ ਕਲਾਉਡ ਪ੍ਰਦਾਤਾ, ਜਾਂ ਕੋਈ ਹੋਰ ਐਪ ਜੋ ਅਸਥਾਈ ਸਮੱਗਰੀ ਨੂੰ ਸਾਂਝਾ ਕਰਦੀ ਹੈ ਵਿੱਚ ਰਹਿੰਦੀਆਂ ਹਨ ਜਾਂ ਨਹੀਂ। + ਜਦੋਂ ਤੁਸੀਂ ਆਮ ਗੈਲਰੀ ਚਿੱਤਰਾਂ ਲਈ ਸਭ ਤੋਂ ਵੱਧ ਸਿਸਟਮ-ਏਕੀਕ੍ਰਿਤ ਅਨੁਭਵ ਚਾਹੁੰਦੇ ਹੋ ਤਾਂ ਡਿਫੌਲਟ ਚੋਣਕਾਰ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਜੇਕਰ ਤੁਸੀਂ ਉਮੀਦ ਕਰਦੇ ਹੋ ਕਿ ਐਲਬਮਾਂ, ਫੋਲਡਰਾਂ, ਕਲਾਉਡ ਫਾਈਲਾਂ, ਜਾਂ ਗੈਰ-ਮਿਆਰੀ ਫਾਰਮੈਟ ਦਿਖਾਈ ਨਹੀਂ ਦਿੰਦੇ ਹਨ ਤਾਂ ਚੋਣਕਾਰ ਮੋਡ ਬਦਲੋ। + ਜੇਕਰ ਸਰੋਤ ਐਪ ਨੂੰ ਛੱਡਣ ਤੋਂ ਬਾਅਦ ਸਾਂਝੀ ਕੀਤੀ ਗਈ ਫ਼ਾਈਲ ਗਾਇਬ ਹੋ ਜਾਂਦੀ ਹੈ, ਤਾਂ ਇਸਨੂੰ ਇੱਕ ਸਥਾਈ ਚੋਣਕਾਰ ਦੁਆਰਾ ਦੁਬਾਰਾ ਖੋਲ੍ਹੋ ਜਾਂ ਪਹਿਲਾਂ ਇੱਕ ਸਥਾਨਕ ਕਾਪੀ ਸੁਰੱਖਿਅਤ ਕਰੋ। + ਮਨਪਸੰਦ ਅਤੇ ਸ਼ੇਅਰ ਟੂਲ + ਮੁੱਖ ਸਕ੍ਰੀਨ ਨੂੰ ਫੋਕਸ ਰੱਖੋ ਅਤੇ ਉਹਨਾਂ ਸਾਧਨਾਂ ਨੂੰ ਲੁਕਾਓ ਜੋ ਸ਼ੇਅਰ ਵਹਾਅ ਵਿੱਚ ਅਰਥ ਨਹੀਂ ਰੱਖਦੇ + ਟੂਲ ਸੂਚੀ ਸ਼ੋਰ ਨੂੰ ਘਟਾਓ + ਮਨਪਸੰਦ ਅਤੇ ਛੁਪੀਆਂ-ਲਈ-ਸ਼ੇਅਰ ਸੈਟਿੰਗਾਂ ਉਪਯੋਗੀ ਹੁੰਦੀਆਂ ਹਨ ਜਦੋਂ ਤੁਸੀਂ ਹਰ ਰੋਜ਼ ਸਿਰਫ ਕੁਝ ਟੂਲਸ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹੋ। ਉਹ ਟੂਲਸ ਨੂੰ ਲੁਕਾ ਕੇ ਸ਼ੇਅਰ ਪ੍ਰਵਾਹ ਨੂੰ ਵਿਹਾਰਕ ਵੀ ਰੱਖਦੇ ਹਨ ਜੋ ਕਿਸੇ ਹੋਰ ਐਪ ਦੁਆਰਾ ਭੇਜੀ ਗਈ ਫਾਈਲ ਦੀ ਕਿਸਮ ਦੀ ਵਰਤੋਂ ਨਹੀਂ ਕਰ ਸਕਦੇ ਹਨ। + ਵਾਰ-ਵਾਰ ਟੂਲਾਂ ਨੂੰ ਪਿੰਨ ਕਰੋ ਤਾਂ ਕਿ ਟੂਲਾਂ ਨੂੰ ਸ਼੍ਰੇਣੀ ਅਨੁਸਾਰ ਗਰੁੱਪਬੱਧ ਕੀਤੇ ਜਾਣ \'ਤੇ ਵੀ ਉਹਨਾਂ ਤੱਕ ਪਹੁੰਚਣਾ ਆਸਾਨ ਰਹੇ। + ਸ਼ੇਅਰ ਫਲੋ ਤੋਂ ਟੂਲ ਓਹਲੇ ਕਰੋ ਜਦੋਂ ਉਹ ਕਿਸੇ ਹੋਰ ਐਪ ਤੋਂ ਆਉਣ ਵਾਲੀਆਂ ਫਾਈਲਾਂ ਲਈ ਅਪ੍ਰਸੰਗਿਕ ਹੋਣ। + ਮਨਪਸੰਦ ਆਰਡਰਿੰਗ ਸੈਟਿੰਗਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜੇਕਰ ਤੁਸੀਂ ਅੰਤ ਵਿੱਚ ਮਨਪਸੰਦ ਨੂੰ ਤਰਜੀਹ ਦਿੰਦੇ ਹੋ ਜਾਂ ਸੰਬੰਧਿਤ ਸਾਧਨਾਂ ਨਾਲ ਸਮੂਹਬੱਧ ਕਰਦੇ ਹੋ। + ਬੈਕਅੱਪ ਸੈਟਿੰਗਜ਼ + ਪ੍ਰੀਸੈਟਸ, ਤਰਜੀਹਾਂ ਅਤੇ ਐਪ ਵਿਹਾਰ ਨੂੰ ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਕਿਸੇ ਹੋਰ ਇੰਸਟੌਲ \'ਤੇ ਲੈ ਜਾਓ + ਆਪਣੇ ਸੈੱਟਅੱਪ ਨੂੰ ਪੋਰਟੇਬਲ ਰੱਖੋ + ਤੁਹਾਡੇ ਦੁਆਰਾ ਪ੍ਰੀਸੈਟਸ, ਫੋਲਡਰਾਂ, ਫਾਈਲਨਾਮ ਨਿਯਮਾਂ, ਟੂਲ ਆਰਡਰ, ਅਤੇ ਵਿਜ਼ੂਅਲ ਤਰਜੀਹਾਂ ਨੂੰ ਟਿਊਨ ਕਰਨ ਤੋਂ ਬਾਅਦ ਬੈਕਅੱਪ ਅਤੇ ਰੀਸਟੋਰ ਸਭ ਤੋਂ ਮਦਦਗਾਰ ਹੁੰਦਾ ਹੈ। ਇੱਕ ਬੈਕਅੱਪ ਤੁਹਾਨੂੰ ਮੈਮੋਰੀ ਤੋਂ ਤੁਹਾਡੇ ਵਰਕਫਲੋ ਨੂੰ ਦੁਬਾਰਾ ਬਣਾਏ ਬਿਨਾਂ ਸੈਟਿੰਗਾਂ ਨਾਲ ਪ੍ਰਯੋਗ ਕਰਨ ਜਾਂ ਐਪ ਨੂੰ ਮੁੜ ਸਥਾਪਿਤ ਕਰਨ ਦਿੰਦਾ ਹੈ। + ਪ੍ਰੀਸੈਟਸ, ਫਾਈਲਨਾਮ ਵਿਹਾਰ, ਡਿਫੌਲਟ ਫਾਰਮੈਟ, ਜਾਂ ਟੂਲ ਵਿਵਸਥਾ ਨੂੰ ਬਦਲਣ ਤੋਂ ਬਾਅਦ ਇੱਕ ਬੈਕਅੱਪ ਬਣਾਓ। + ਬੈਕਅੱਪ ਨੂੰ ਐਪ ਫੋਲਡਰ ਦੇ ਬਾਹਰ ਕਿਤੇ ਸਟੋਰ ਕਰੋ ਜੇਕਰ ਤੁਸੀਂ ਡਾਟਾ ਕਲੀਅਰ ਕਰਨ, ਰੀਸਟਾਲ ਕਰਨ ਜਾਂ ਡਿਵਾਈਸਾਂ ਨੂੰ ਮੂਵ ਕਰਨ ਦੀ ਯੋਜਨਾ ਬਣਾਉਂਦੇ ਹੋ। + ਮਹੱਤਵਪੂਰਨ ਬੈਚਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਸੈਟਿੰਗਾਂ ਨੂੰ ਰੀਸਟੋਰ ਕਰੋ ਤਾਂ ਜੋ ਡਿਫਾਲਟ ਅਤੇ ਸੇਵ ਵਿਵਹਾਰ ਤੁਹਾਡੇ ਪੁਰਾਣੇ ਸੈੱਟਅੱਪ ਨਾਲ ਮੇਲ ਖਾਂਦਾ ਹੋਵੇ। + ਚਿੱਤਰਾਂ ਦਾ ਆਕਾਰ ਬਦਲੋ ਅਤੇ ਬਦਲੋ + ਇੱਕ ਜਾਂ ਕਈ ਚਿੱਤਰਾਂ ਲਈ ਆਕਾਰ, ਫਾਰਮੈਟ, ਗੁਣਵੱਤਾ ਅਤੇ ਮੈਟਾਡੇਟਾ ਬਦਲੋ + ਮੁੜ ਆਕਾਰ ਵਾਲੀਆਂ ਕਾਪੀਆਂ ਬਣਾਓ + ਰੀਸਾਈਜ਼ ਅਤੇ ਕਨਵਰਟ ਅਨੁਮਾਨਿਤ ਮਾਪਾਂ ਅਤੇ ਹਲਕੇ ਆਉਟਪੁੱਟ ਫਾਈਲਾਂ ਲਈ ਮੁੱਖ ਸਾਧਨ ਹੈ। ਇਸਦੀ ਵਰਤੋਂ ਉਦੋਂ ਕਰੋ ਜਦੋਂ ਚਿੱਤਰ ਦਾ ਆਕਾਰ, ਆਉਟਪੁੱਟ ਫਾਰਮੈਟ, ਅਤੇ ਮੈਟਾਡੇਟਾ ਵਿਵਹਾਰ ਇੱਕੋ ਸਮੇਂ \'ਤੇ ਮਾਇਨੇ ਰੱਖਦਾ ਹੈ। + ਇੱਕ ਜਾਂ ਇੱਕ ਤੋਂ ਵੱਧ ਚਿੱਤਰ ਚੁਣੋ, ਫਿਰ ਚੁਣੋ ਕਿ ਕੀ ਮਾਪ, ਸਕੇਲ, ਜਾਂ ਫਾਈਲ ਦਾ ਆਕਾਰ ਸਭ ਤੋਂ ਮਹੱਤਵਪੂਰਨ ਹੈ। + ਆਉਟਪੁੱਟ ਫਾਰਮੈਟ ਅਤੇ ਗੁਣਵੱਤਾ ਦੀ ਚੋਣ ਕਰੋ; ਜਦੋਂ ਪਾਰਦਰਸ਼ਤਾ ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖਿਆ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ ਤਾਂ PNG ਜਾਂ WebP ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਨਤੀਜੇ ਦੀ ਪੂਰਵਦਰਸ਼ਨ ਕਰੋ, ਫਿਰ ਮੂਲ ਨੂੰ ਬਦਲੇ ਬਿਨਾਂ ਤਿਆਰ ਕੀਤੀਆਂ ਕਾਪੀਆਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਜਾਂ ਸਾਂਝਾ ਕਰੋ। + ਫਾਈਲ ਆਕਾਰ ਦੁਆਰਾ ਮੁੜ ਆਕਾਰ ਦਿਓ + ਜਦੋਂ ਕੋਈ ਵੈਬਸਾਈਟ, ਮੈਸੇਂਜਰ, ਜਾਂ ਫਾਰਮ ਭਾਰੀ ਚਿੱਤਰਾਂ ਨੂੰ ਅਸਵੀਕਾਰ ਕਰਦਾ ਹੈ ਤਾਂ ਇੱਕ ਆਕਾਰ ਸੀਮਾ ਨੂੰ ਨਿਸ਼ਾਨਾ ਬਣਾਓ + ਸਖਤ ਅੱਪਲੋਡ ਸੀਮਾਵਾਂ ਨੂੰ ਫਿੱਟ ਕਰੋ + ਜਦੋਂ ਮੰਜ਼ਿਲ ਸਿਰਫ਼ ਇੱਕ ਖਾਸ ਸੀਮਾ ਤੋਂ ਹੇਠਾਂ ਫਾਈਲਾਂ ਨੂੰ ਸਵੀਕਾਰ ਕਰਦੀ ਹੈ ਤਾਂ ਗੁਣਵੱਤਾ ਮੁੱਲਾਂ ਦਾ ਅੰਦਾਜ਼ਾ ਲਗਾਉਣ ਨਾਲੋਂ ਫਾਈਲ ਆਕਾਰ ਦੁਆਰਾ ਮੁੜ ਆਕਾਰ ਦੇਣਾ ਬਿਹਤਰ ਹੈ। ਟੂਲ ਨਤੀਜੇ ਨੂੰ ਵਰਤੋਂ ਯੋਗ ਰੱਖਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰਦੇ ਹੋਏ ਟੀਚੇ ਤੱਕ ਪਹੁੰਚਣ ਲਈ ਕੰਪਰੈਸ਼ਨ ਅਤੇ ਮਾਪਾਂ ਨੂੰ ਅਨੁਕੂਲ ਕਰ ਸਕਦਾ ਹੈ। + ਮੈਟਾਡੇਟਾ ਅਤੇ ਪ੍ਰਦਾਤਾ ਤਬਦੀਲੀਆਂ ਲਈ ਜਗ੍ਹਾ ਛੱਡਣ ਲਈ ਅਸਲ ਅੱਪਲੋਡ ਸੀਮਾ ਤੋਂ ਥੋੜ੍ਹਾ ਹੇਠਾਂ ਟੀਚਾ ਆਕਾਰ ਦਾਖਲ ਕਰੋ। + ਫੋਟੋਆਂ ਲਈ ਨੁਕਸਾਨਦੇਹ ਫਾਰਮੈਟਾਂ ਨੂੰ ਤਰਜੀਹ ਦਿਓ ਜਦੋਂ ਟੀਚਾ ਛੋਟਾ ਹੋਵੇ; PNG ਰੀਸਾਈਜ਼ ਕਰਨ ਤੋਂ ਬਾਅਦ ਵੀ ਵੱਡਾ ਰਹਿ ਸਕਦਾ ਹੈ। + ਨਿਰਯਾਤ ਕਰਨ ਤੋਂ ਬਾਅਦ ਚਿਹਰਿਆਂ, ਟੈਕਸਟ ਅਤੇ ਕਿਨਾਰਿਆਂ ਦੀ ਜਾਂਚ ਕਰੋ ਕਿਉਂਕਿ ਹਮਲਾਵਰ ਆਕਾਰ ਦੇ ਟੀਚੇ ਦਿਖਾਈ ਦੇਣ ਵਾਲੀਆਂ ਕਲਾਕ੍ਰਿਤੀਆਂ ਨੂੰ ਪੇਸ਼ ਕਰ ਸਕਦੇ ਹਨ। + ਸੀਮਾ ਮੁੜ ਆਕਾਰ + ਸਿਰਫ਼ ਉਹਨਾਂ ਚਿੱਤਰਾਂ ਨੂੰ ਸੁੰਗੜੋ ਜੋ ਤੁਹਾਡੀ ਅਧਿਕਤਮ ਚੌੜਾਈ, ਉਚਾਈ, ਜਾਂ ਫਾਈਲ ਸੀਮਾਵਾਂ ਤੋਂ ਵੱਧ ਹਨ + ਉਹਨਾਂ ਚਿੱਤਰਾਂ ਨੂੰ ਮੁੜ ਆਕਾਰ ਦੇਣ ਤੋਂ ਬਚੋ ਜੋ ਪਹਿਲਾਂ ਹੀ ਠੀਕ ਹਨ + ਲਿਮਿਟ ਰੀਸਾਈਜ਼ ਮਿਕਸਡ ਫੋਲਡਰਾਂ ਲਈ ਲਾਭਦਾਇਕ ਹੈ ਜਿੱਥੇ ਕੁਝ ਚਿੱਤਰ ਬਹੁਤ ਵੱਡੇ ਹਨ ਅਤੇ ਕੁਝ ਪਹਿਲਾਂ ਹੀ ਤਿਆਰ ਹਨ। ਹਰ ਫਾਈਲ ਨੂੰ ਉਸੇ ਰੀਸਾਈਜ਼ ਦੁਆਰਾ ਮਜਬੂਰ ਕਰਨ ਦੀ ਬਜਾਏ, ਇਹ ਸਵੀਕਾਰਯੋਗ ਚਿੱਤਰਾਂ ਨੂੰ ਉਹਨਾਂ ਦੀ ਅਸਲ ਗੁਣਵੱਤਾ ਦੇ ਨੇੜੇ ਰੱਖਦਾ ਹੈ। + ਹਰ ਫਾਈਲ ਨੂੰ ਅੱਖਾਂ ਬੰਦ ਕਰਕੇ ਮੁੜ ਆਕਾਰ ਦੇਣ ਦੀ ਬਜਾਏ ਮੰਜ਼ਿਲ ਦੇ ਆਧਾਰ \'ਤੇ ਵੱਧ ਤੋਂ ਵੱਧ ਮਾਪ ਜਾਂ ਸੀਮਾਵਾਂ ਸੈੱਟ ਕਰੋ। + ਜਦੋਂ ਤੁਸੀਂ ਸਰੋਤਾਂ ਨਾਲੋਂ ਭਾਰੀ ਹੋ ਜਾਣ ਵਾਲੇ ਆਉਟਪੁੱਟ ਤੋਂ ਬਚਣਾ ਚਾਹੁੰਦੇ ਹੋ ਤਾਂ ਛੱਡੋ-ਜੇ-ਵੱਡੇ ਸਟਾਈਲ ਵਿਵਹਾਰ ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ। + ਵੱਖ-ਵੱਖ ਕੈਮਰਿਆਂ, ਸਕ੍ਰੀਨਸ਼ੌਟਸ, ਜਾਂ ਡਾਊਨਲੋਡਾਂ ਦੇ ਬੈਚਾਂ ਲਈ ਇਸਦੀ ਵਰਤੋਂ ਕਰੋ ਜਿੱਥੇ ਸਰੋਤ ਆਕਾਰ ਬਹੁਤ ਬਦਲਦੇ ਹਨ। + ਕੱਟੋ, ਘੁੰਮਾਓ ਅਤੇ ਸਿੱਧਾ ਕਰੋ + ਹੋਰ ਸਾਧਨਾਂ ਵਿੱਚ ਚਿੱਤਰ ਨੂੰ ਨਿਰਯਾਤ ਕਰਨ ਜਾਂ ਵਰਤਣ ਤੋਂ ਪਹਿਲਾਂ ਮਹੱਤਵਪੂਰਨ ਖੇਤਰ ਨੂੰ ਫਰੇਮ ਕਰੋ + ਰਚਨਾ ਨੂੰ ਸਾਫ਼ ਕਰੋ + ਪਹਿਲਾਂ ਕ੍ਰੌਪਿੰਗ ਬਾਅਦ ਵਿੱਚ ਫਿਲਟਰ, OCR, PDF ਪੰਨੇ, ਅਤੇ ਸ਼ੇਅਰਿੰਗ ਨਤੀਜਿਆਂ ਨੂੰ ਕਲੀਨਰ ਬਣਾਉਂਦਾ ਹੈ। + ਕਰੋਪ ਖੋਲ੍ਹੋ ਅਤੇ ਉਹ ਚਿੱਤਰ ਚੁਣੋ ਜਿਸ ਨੂੰ ਤੁਸੀਂ ਅਨੁਕੂਲ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ। + ਜਦੋਂ ਆਉਟਪੁੱਟ ਕਿਸੇ ਪੋਸਟ, ਦਸਤਾਵੇਜ਼, ਜਾਂ ਅਵਤਾਰ ਨਾਲ ਫਿੱਟ ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ ਤਾਂ ਮੁਫਤ ਕ੍ਰੌਪ ਜਾਂ ਆਕਾਰ ਅਨੁਪਾਤ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਸੁਰੱਖਿਅਤ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਘੁੰਮਾਓ ਜਾਂ ਫਲਿੱਪ ਕਰੋ ਤਾਂ ਜੋ ਬਾਅਦ ਵਿੱਚ ਟੂਲ ਸਹੀ ਚਿੱਤਰ ਪ੍ਰਾਪਤ ਕਰ ਸਕਣ। + ਫਿਲਟਰ ਅਤੇ ਐਡਜਸਟਮੈਂਟ ਲਾਗੂ ਕਰੋ + ਇੱਕ ਸੰਪਾਦਨਯੋਗ ਫਿਲਟਰ ਸਟੈਕ ਬਣਾਓ ਅਤੇ ਨਿਰਯਾਤ ਤੋਂ ਪਹਿਲਾਂ ਨਤੀਜੇ ਦੀ ਝਲਕ ਵੇਖੋ + ਚਿੱਤਰ ਦੀ ਦਿੱਖ ਨੂੰ ਟਿਊਨ ਕਰੋ + ਫਿਲਟਰ ਤੇਜ਼ ਸੁਧਾਰਾਂ, ਸਟਾਈਲਾਈਜ਼ਡ ਆਉਟਪੁੱਟ, ਅਤੇ OCR ਜਾਂ ਪ੍ਰਿੰਟਿੰਗ ਲਈ ਚਿੱਤਰ ਤਿਆਰ ਕਰਨ ਲਈ ਉਪਯੋਗੀ ਹਨ। ਵਿਪਰੀਤਤਾ, ਤਿੱਖਾਪਨ ਅਤੇ ਚਮਕ ਵਿੱਚ ਛੋਟੀਆਂ ਤਬਦੀਲੀਆਂ ਭਾਰੀ ਪ੍ਰਭਾਵਾਂ ਤੋਂ ਵੱਧ ਮਾਇਨੇ ਰੱਖ ਸਕਦੀਆਂ ਹਨ ਜਦੋਂ ਚਿੱਤਰ ਵਿੱਚ ਟੈਕਸਟ ਜਾਂ ਵਧੀਆ ਵੇਰਵੇ ਹੁੰਦੇ ਹਨ। + ਇੱਕ-ਇੱਕ ਕਰਕੇ ਫਿਲਟਰ ਜੋੜੋ ਅਤੇ ਹਰੇਕ ਤਬਦੀਲੀ ਤੋਂ ਬਾਅਦ ਪੂਰਵਦਰਸ਼ਨ \'ਤੇ ਨਜ਼ਰ ਰੱਖੋ। + ਕੰਮ ਦੇ ਆਧਾਰ \'ਤੇ ਚਮਕ, ਕੰਟ੍ਰਾਸਟ, ਤਿੱਖਾਪਨ, ਬਲਰ, ਰੰਗ ਜਾਂ ਮਾਸਕ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ। + ਸਿਰਫ਼ ਉਦੋਂ ਹੀ ਨਿਰਯਾਤ ਕਰੋ ਜਦੋਂ ਪ੍ਰੀਵਿਊ ਤੁਹਾਡੇ ਟੀਚੇ ਵਾਲੇ ਡੀਵਾਈਸ, ਦਸਤਾਵੇਜ਼, ਜਾਂ ਸੋਸ਼ਲ ਪਲੇਟਫਾਰਮ ਨਾਲ ਮੇਲ ਖਾਂਦਾ ਹੈ। + ਪਹਿਲਾਂ ਝਲਕ + ਇੱਕ ਭਾਰੀ ਸੰਪਾਦਨ, ਪਰਿਵਰਤਨ, ਜਾਂ ਕਲੀਨਅੱਪ ਟੂਲ ਚੁਣਨ ਤੋਂ ਪਹਿਲਾਂ ਚਿੱਤਰਾਂ ਦੀ ਜਾਂਚ ਕਰੋ + ਜਾਂਚ ਕਰੋ ਕਿ ਤੁਸੀਂ ਅਸਲ ਵਿੱਚ ਕੀ ਪ੍ਰਾਪਤ ਕੀਤਾ ਹੈ + ਚਿੱਤਰ ਪੂਰਵਦਰਸ਼ਨ ਉਦੋਂ ਲਾਭਦਾਇਕ ਹੁੰਦਾ ਹੈ ਜਦੋਂ ਕੋਈ ਫ਼ਾਈਲ ਚੈਟ, ਕਲਾਉਡ ਸਟੋਰੇਜ, ਜਾਂ ਕਿਸੇ ਹੋਰ ਐਪ ਤੋਂ ਆਉਂਦੀ ਹੈ ਅਤੇ ਤੁਸੀਂ ਯਕੀਨੀ ਨਹੀਂ ਹੁੰਦੇ ਕਿ ਇਸ ਵਿੱਚ ਕੀ ਸ਼ਾਮਲ ਹੈ। ਮਾਪ, ਪਾਰਦਰਸ਼ਤਾ, ਸਥਿਤੀ, ਅਤੇ ਵਿਜ਼ੂਅਲ ਕੁਆਲਿਟੀ ਦੀ ਜਾਂਚ ਕਰਨਾ ਪਹਿਲਾਂ ਸਹੀ ਫਾਲੋ-ਅੱਪ ਟੂਲ ਚੁਣਨ ਵਿੱਚ ਮਦਦ ਕਰਦਾ ਹੈ। + ਚਿੱਤਰ ਪੂਰਵਦਰਸ਼ਨ ਖੋਲ੍ਹੋ ਜਦੋਂ ਤੁਹਾਨੂੰ ਇਹ ਫੈਸਲਾ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਕਿ ਉਹਨਾਂ ਨਾਲ ਕੀ ਕਰਨਾ ਹੈ ਕਈ ਚਿੱਤਰਾਂ ਦੀ ਜਾਂਚ ਕਰਨ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। + ਰੋਟੇਸ਼ਨ ਸਮੱਸਿਆਵਾਂ, ਪਾਰਦਰਸ਼ੀ ਖੇਤਰਾਂ, ਕੰਪਰੈਸ਼ਨ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਅਤੇ ਅਚਾਨਕ ਵੱਡੇ ਮਾਪਾਂ ਦੀ ਭਾਲ ਕਰੋ। + ਮੁੜ-ਆਕਾਰ, ਕਟੌਤੀ, ਤੁਲਨਾ, EXIF, ਜਾਂ ਬੈਕਗ੍ਰਾਉਂਡ ਰੀਮੂਵਰ \'ਤੇ ਜਾਣ ਤੋਂ ਬਾਅਦ ਹੀ ਤੁਸੀਂ ਜਾਣਦੇ ਹੋ ਕਿ ਕਿਸ ਨੂੰ ਠੀਕ ਕਰਨ ਦੀ ਜ਼ਰੂਰਤ ਹੈ। + ਬੈਕਗ੍ਰਾਊਂਡ ਨੂੰ ਹਟਾਓ ਜਾਂ ਰੀਸਟੋਰ ਕਰੋ + ਬੈਕਗ੍ਰਾਉਂਡਾਂ ਨੂੰ ਆਪਣੇ ਆਪ ਮਿਟਾਓ ਜਾਂ ਰੀਸਟੋਰ ਟੂਲਸ ਨਾਲ ਹੱਥੀਂ ਕਿਨਾਰਿਆਂ ਨੂੰ ਸੋਧੋ + ਵਿਸ਼ੇ ਨੂੰ ਰੱਖੋ, ਬਾਕੀ ਹਟਾਓ + ਬੈਕਗ੍ਰਾਊਂਡ ਹਟਾਉਣਾ ਸਭ ਤੋਂ ਵਧੀਆ ਕੰਮ ਕਰਦਾ ਹੈ ਜਦੋਂ ਤੁਸੀਂ ਆਟੋਮੈਟਿਕ ਚੋਣ ਨੂੰ ਧਿਆਨ ਨਾਲ ਮੈਨੂਅਲ ਕਲੀਨਅੱਪ ਨਾਲ ਜੋੜਦੇ ਹੋ। ਅੰਤਿਮ ਨਿਰਯਾਤ ਫਾਰਮੈਟ ਮਾਸਕ ਜਿੰਨਾ ਹੀ ਮਾਇਨੇ ਰੱਖਦਾ ਹੈ, ਕਿਉਂਕਿ ਜੇਪੀਈਜੀ ਪਾਰਦਰਸ਼ੀ ਖੇਤਰਾਂ ਨੂੰ ਸਮਤਲ ਕਰੇਗਾ ਭਾਵੇਂ ਪੂਰਵਦਰਸ਼ਨ ਸਹੀ ਦਿਖਾਈ ਦਿੰਦਾ ਹੈ। + ਜੇਕਰ ਵਿਸ਼ੇ ਨੂੰ ਪਿਛੋਕੜ ਤੋਂ ਸਪਸ਼ਟ ਤੌਰ \'ਤੇ ਵੱਖ ਕੀਤਾ ਗਿਆ ਹੈ ਤਾਂ ਆਟੋਮੈਟਿਕ ਹਟਾਉਣ ਨਾਲ ਸ਼ੁਰੂ ਕਰੋ। + ਵਾਲਾਂ, ਕੋਨਿਆਂ, ਸ਼ੈਡੋਜ਼, ਅਤੇ ਛੋਟੇ ਵੇਰਵਿਆਂ ਨੂੰ ਠੀਕ ਕਰਨ ਲਈ ਮਿਟਾਉਣ ਅਤੇ ਰੀਸਟੋਰ ਕਰਨ ਲਈ ਜ਼ੂਮ ਇਨ ਕਰੋ ਅਤੇ ਸਵਿਚ ਕਰੋ। + ਜਦੋਂ ਤੁਹਾਨੂੰ ਅੰਤਿਮ ਫ਼ਾਈਲ ਵਿੱਚ ਪਾਰਦਰਸ਼ਤਾ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ ਤਾਂ PNG ਜਾਂ WebP ਵਜੋਂ ਰੱਖਿਅਤ ਕਰੋ। + ਡਰਾਅ, ਮਾਰਕ ਅੱਪ ਅਤੇ ਵਾਟਰਮਾਰਕ + ਤੀਰ, ਨੋਟਸ, ਹਾਈਲਾਈਟਸ, ਹਸਤਾਖਰ, ਜਾਂ ਵਾਰ-ਵਾਰ ਵਾਟਰਮਾਰਕ ਓਵਰਲੇ ਸ਼ਾਮਲ ਕਰੋ + ਦਿਖਾਈ ਦੇਣ ਵਾਲੀ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਕਰੋ + ਮਾਰਕਅੱਪ ਟੂਲ ਇੱਕ ਚਿੱਤਰ ਨੂੰ ਸਮਝਾਉਣ ਵਿੱਚ ਮਦਦ ਕਰਦੇ ਹਨ, ਜਦੋਂ ਕਿ ਵਾਟਰਮਾਰਕਿੰਗ ਨਿਰਯਾਤ ਕੀਤੀਆਂ ਫਾਈਲਾਂ ਨੂੰ ਬ੍ਰਾਂਡ ਜਾਂ ਸੁਰੱਖਿਅਤ ਕਰਨ ਵਿੱਚ ਮਦਦ ਕਰਦੀ ਹੈ। + ਜਦੋਂ ਤੁਹਾਨੂੰ ਫ੍ਰੀਹੈਂਡ ਨੋਟਸ, ਆਕਾਰ, ਹਾਈਲਾਈਟਿੰਗ, ਜਾਂ ਤੇਜ਼ ਐਨੋਟੇਸ਼ਨਾਂ ਦੀ ਲੋੜ ਹੋਵੇ ਤਾਂ ਡਰਾਅ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਵਾਟਰਮਾਰਕਿੰਗ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਉਹੀ ਟੈਕਸਟ ਜਾਂ ਚਿੱਤਰ ਚਿੰਨ੍ਹ ਆਉਟਪੁੱਟ \'ਤੇ ਲਗਾਤਾਰ ਦਿਖਾਈ ਦੇਵੇ। + ਨਿਰਯਾਤ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਧੁੰਦਲਾਪਨ, ਸਥਿਤੀ ਅਤੇ ਪੈਮਾਨੇ ਦੀ ਜਾਂਚ ਕਰੋ ਤਾਂ ਜੋ ਨਿਸ਼ਾਨ ਦਿਖਾਈ ਦੇਵੇ ਪਰ ਧਿਆਨ ਭਟਕਾਉਣ ਵਾਲਾ ਨਾ ਹੋਵੇ। + ਪੂਰਵ-ਨਿਰਧਾਰਤ ਡਰਾਅ ਕਰੋ + ਤੇਜ਼ ਮਾਰਕਅੱਪ ਲਈ ਲਾਈਨ ਦੀ ਚੌੜਾਈ, ਰੰਗ, ਮਾਰਗ ਮੋਡ, ਸਥਿਤੀ ਲੌਕ, ਅਤੇ ਵੱਡਦਰਸ਼ੀ ਸੈੱਟ ਕਰੋ + ਡਰਾਇੰਗ ਨੂੰ ਅਨੁਮਾਨ ਲਗਾਉਣ ਯੋਗ ਬਣਾਓ + ਜਦੋਂ ਤੁਸੀਂ ਸਕ੍ਰੀਨਸ਼ੌਟਸ ਨੂੰ ਐਨੋਟੇਟ ਕਰਦੇ ਹੋ, ਦਸਤਾਵੇਜ਼ਾਂ \'ਤੇ ਨਿਸ਼ਾਨ ਲਗਾਉਂਦੇ ਹੋ, ਜਾਂ ਚਿੱਤਰਾਂ \'ਤੇ ਅਕਸਰ ਸਾਈਨ ਕਰਦੇ ਹੋ ਤਾਂ ਡਰਾਅ ਸੈਟਿੰਗਾਂ ਮਦਦਗਾਰ ਹੁੰਦੀਆਂ ਹਨ। ਪੂਰਵ-ਨਿਰਧਾਰਤ ਸੈੱਟਅੱਪ ਸਮਾਂ ਘਟਾਉਂਦੇ ਹਨ, ਜਦੋਂ ਕਿ ਵੱਡਦਰਸ਼ੀ ਅਤੇ ਸਥਿਤੀ ਲੌਕ ਛੋਟੀਆਂ ਸਕ੍ਰੀਨਾਂ \'ਤੇ ਸਹੀ ਸੰਪਾਦਨਾਂ ਨੂੰ ਆਸਾਨ ਬਣਾਉਂਦੇ ਹਨ। + ਪੂਰਵ-ਨਿਰਧਾਰਤ ਲਾਈਨ ਚੌੜਾਈ ਸੈਟ ਕਰੋ ਅਤੇ ਉਹਨਾਂ ਮੁੱਲਾਂ ਲਈ ਰੰਗ ਖਿੱਚੋ ਜੋ ਤੁਸੀਂ ਤੀਰਾਂ, ਹਾਈਲਾਈਟਾਂ ਜਾਂ ਦਸਤਖਤਾਂ ਲਈ ਸਭ ਤੋਂ ਵੱਧ ਵਰਤਦੇ ਹੋ। + ਪੂਰਵ-ਨਿਰਧਾਰਤ ਮਾਰਗ ਮੋਡ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਤੁਸੀਂ ਆਮ ਤੌਰ \'ਤੇ ਇੱਕੋ ਕਿਸਮ ਦੇ ਸਟ੍ਰੋਕ, ਆਕਾਰ ਜਾਂ ਮਾਰਕਅੱਪ ਖਿੱਚਦੇ ਹੋ। + ਜਦੋਂ ਫਿੰਗਰ ਇਨਪੁੱਟ ਛੋਟੇ ਵੇਰਵਿਆਂ ਨੂੰ ਸਹੀ ਢੰਗ ਨਾਲ ਰੱਖਣਾ ਔਖਾ ਬਣਾਉਂਦਾ ਹੈ ਤਾਂ ਵੱਡਦਰਸ਼ੀ ਜਾਂ ਸਥਿਤੀ ਲੌਕ ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ। + ਮਲਟੀ-ਚਿੱਤਰ ਲੇਆਉਟ + ਸਕ੍ਰੀਨਸ਼ਾਟ, ਸਕੈਨ, ਜਾਂ ਤੁਲਨਾਤਮਕ ਪੱਟੀਆਂ ਦਾ ਪ੍ਰਬੰਧ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਸਹੀ ਮਲਟੀ-ਇਮੇਜ ਟੂਲ ਚੁਣੋ + ਸਹੀ ਲੇਆਉਟ ਟੂਲ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਇਹ ਟੂਲ ਇੱਕੋ ਜਿਹੇ ਲੱਗਦੇ ਹਨ, ਪਰ ਹਰੇਕ ਨੂੰ ਇੱਕ ਵੱਖਰੀ ਕਿਸਮ ਦੀ ਮਲਟੀ-ਇਮੇਜ ਆਉਟਪੁੱਟ ਲਈ ਟਿਊਨ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਪਹਿਲਾਂ ਸਹੀ ਨੂੰ ਚੁਣਨਾ ਲੇਆਉਟ ਨਿਯੰਤਰਣਾਂ ਨਾਲ ਲੜਨ ਤੋਂ ਬਚਦਾ ਹੈ ਜੋ ਇੱਕ ਵੱਖਰੇ ਨਤੀਜੇ ਲਈ ਤਿਆਰ ਕੀਤੇ ਗਏ ਸਨ। + ਇੱਕ ਰਚਨਾ ਵਿੱਚ ਕਈ ਚਿੱਤਰਾਂ ਵਾਲੇ ਡਿਜ਼ਾਈਨ ਕੀਤੇ ਖਾਕੇ ਲਈ ਕੋਲਾਜ ਮੇਕਰ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਚਿੱਤਰ ਸਿਲਾਈ ਜਾਂ ਸਟੈਕਿੰਗ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਚਿੱਤਰ ਇੱਕ ਲੰਮੀ ਲੰਬਕਾਰੀ ਜਾਂ ਲੇਟਵੀਂ ਪੱਟੀ ਬਣ ਜਾਣ। + ਜਦੋਂ ਇੱਕ ਵੱਡੀ ਤਸਵੀਰ ਨੂੰ ਕਈ ਛੋਟੇ ਟੁਕੜੇ ਬਣਾਉਣ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ ਤਾਂ ਚਿੱਤਰ ਵੰਡਣ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਚਿੱਤਰ ਫਾਰਮੈਟਾਂ ਨੂੰ ਬਦਲੋ + ਹੱਥੀਂ ਪਿਕਸਲ ਸੰਪਾਦਿਤ ਕੀਤੇ ਬਿਨਾਂ JPEG, PNG, WebP, AVIF, JXL ਅਤੇ ਹੋਰ ਕਾਪੀਆਂ ਬਣਾਓ + ਨੌਕਰੀ ਲਈ ਫਾਰਮੈਟ ਚੁਣੋ + ਫੋਟੋਆਂ, ਸਕ੍ਰੀਨਸ਼ੌਟਸ, ਪਾਰਦਰਸ਼ਤਾ, ਕੰਪਰੈਸ਼ਨ, ਜਾਂ ਅਨੁਕੂਲਤਾ ਲਈ ਵੱਖ-ਵੱਖ ਫਾਰਮੈਟ ਬਿਹਤਰ ਹਨ। ਪਰਿਵਰਤਨ ਸਭ ਤੋਂ ਸੁਰੱਖਿਅਤ ਹੁੰਦਾ ਹੈ ਜਦੋਂ ਤੁਸੀਂ ਸਮਝਦੇ ਹੋ ਕਿ ਮੰਜ਼ਿਲ ਐਪ ਕਿਸ ਚੀਜ਼ ਦਾ ਸਮਰਥਨ ਕਰਦੀ ਹੈ ਅਤੇ ਕੀ ਸਰੋਤ ਵਿੱਚ ਅਲਫ਼ਾ, ਐਨੀਮੇਸ਼ਨ, ਜਾਂ ਮੈਟਾਡੇਟਾ ਸ਼ਾਮਲ ਹੈ ਜੋ ਤੁਸੀਂ ਰੱਖਣਾ ਚਾਹੁੰਦੇ ਹੋ। + ਅਨੁਕੂਲ ਫੋਟੋਆਂ ਲਈ JPEG, ਨੁਕਸਾਨ ਰਹਿਤ ਚਿੱਤਰਾਂ ਲਈ PNG ਅਤੇ ਅਲਫ਼ਾ, ਅਤੇ ਸੰਖੇਪ ਸਾਂਝਾਕਰਨ ਲਈ WebP ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਜਦੋਂ ਫਾਰਮੈਟ ਇਸਦਾ ਸਮਰਥਨ ਕਰਦਾ ਹੈ ਤਾਂ ਗੁਣਵੱਤਾ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ; ਹੇਠਲੇ ਮੁੱਲ ਆਕਾਰ ਨੂੰ ਘਟਾਉਂਦੇ ਹਨ ਪਰ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਨੂੰ ਜੋੜ ਸਕਦੇ ਹਨ। + ਮੂਲ ਨੂੰ ਉਦੋਂ ਤੱਕ ਰੱਖੋ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਕਨਵਰਟ ਕੀਤੀਆਂ ਫਾਈਲਾਂ ਨੂੰ ਨਿਸ਼ਾਨਾ ਐਪ ਵਿੱਚ ਸਹੀ ਢੰਗ ਨਾਲ ਖੋਲ੍ਹਣ ਦੀ ਪੁਸ਼ਟੀ ਨਹੀਂ ਕਰਦੇ। + EXIF ਅਤੇ ਗੋਪਨੀਯਤਾ ਦੀ ਜਾਂਚ ਕਰੋ + ਸੰਵੇਦਨਸ਼ੀਲ ਫੋਟੋਆਂ ਨੂੰ ਸਾਂਝਾ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਮੈਟਾਡੇਟਾ ਦੇਖੋ, ਸੰਪਾਦਿਤ ਕਰੋ ਜਾਂ ਹਟਾਓ + ਲੁਕਵੇਂ ਚਿੱਤਰ ਡੇਟਾ ਨੂੰ ਨਿਯੰਤਰਿਤ ਕਰੋ + EXIF ਵਿੱਚ ਕੈਮਰਾ ਡੇਟਾ, ਮਿਤੀਆਂ, ਸਥਾਨ, ਸਥਿਤੀ, ਅਤੇ ਹੋਰ ਵੇਰਵੇ ਸ਼ਾਮਲ ਹੋ ਸਕਦੇ ਹਨ ਜੋ ਚਿੱਤਰ ਵਿੱਚ ਦਿਖਾਈ ਨਹੀਂ ਦਿੰਦੇ ਹਨ। ਜਨਤਕ ਸ਼ੇਅਰਿੰਗ, ਸਮਰਥਨ ਰਿਪੋਰਟਾਂ, ਮਾਰਕੀਟਪਲੇਸ ਸੂਚੀਆਂ, ਜਾਂ ਕੋਈ ਵੀ ਫੋਟੋ ਜੋ ਕਿਸੇ ਨਿੱਜੀ ਸਥਾਨ ਨੂੰ ਪ੍ਰਗਟ ਕਰ ਸਕਦੀ ਹੈ, ਤੋਂ ਪਹਿਲਾਂ ਇਹ ਜਾਂਚ ਕਰਨ ਯੋਗ ਹੈ। + ਫੋਟੋਆਂ ਸਾਂਝੀਆਂ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ EXIF ​​ਟੂਲ ਖੋਲ੍ਹੋ ਜਿਸ ਵਿੱਚ ਟਿਕਾਣਾ ਜਾਂ ਨਿੱਜੀ ਡਿਵਾਈਸ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੋ ਸਕਦੀ ਹੈ। + ਸੰਵੇਦਨਸ਼ੀਲ ਖੇਤਰਾਂ ਨੂੰ ਹਟਾਓ ਜਾਂ ਗਲਤ ਟੈਗ ਸੰਪਾਦਿਤ ਕਰੋ ਜਿਵੇਂ ਕਿ ਮਿਤੀ, ਸਥਿਤੀ, ਲੇਖਕ, ਜਾਂ ਕੈਮਰਾ ਡੇਟਾ। + ਜਦੋਂ ਗੋਪਨੀਯਤਾ ਮਹੱਤਵਪੂਰਨ ਹੋਵੇ ਤਾਂ ਇੱਕ ਨਵੀਂ ਕਾਪੀ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ ਅਤੇ ਅਸਲੀ ਦੀ ਬਜਾਏ ਉਸ ਕਾਪੀ ਨੂੰ ਸਾਂਝਾ ਕਰੋ। + ਆਟੋ EXIF ​​ਸਫਾਈ + ਇਹ ਫੈਸਲਾ ਕਰਦੇ ਹੋਏ ਕਿ ਤਾਰੀਖਾਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖਿਆ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ ਜਾਂ ਨਹੀਂ, ਡਿਫੌਲਟ ਰੂਪ ਵਿੱਚ ਮੈਟਾਡੇਟਾ ਹਟਾਓ + ਗੋਪਨੀਯਤਾ ਨੂੰ ਡਿਫੌਲਟ ਬਣਾਓ + EXIF ਸੈਟਿੰਗਾਂ ਸਮੂਹ ਉਦੋਂ ਲਾਭਦਾਇਕ ਹੁੰਦਾ ਹੈ ਜਦੋਂ ਤੁਸੀਂ ਹਰ ਨਿਰਯਾਤ ਲਈ ਇਸ ਨੂੰ ਯਾਦ ਰੱਖਣ ਦੀ ਬਜਾਏ ਆਪਣੇ ਆਪ ਗੋਪਨੀਯਤਾ ਸਾਫ਼ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ। ਤਾਰੀਖ ਸਮਾਂ ਰੱਖੋ ਇੱਕ ਵੱਖਰਾ ਫੈਸਲਾ ਹੈ ਕਿਉਂਕਿ ਤਾਰੀਖਾਂ ਨੂੰ ਛਾਂਟਣ ਲਈ ਲਾਭਦਾਇਕ ਹੋ ਸਕਦਾ ਹੈ ਪਰ ਸਾਂਝਾ ਕਰਨ ਲਈ ਸੰਵੇਦਨਸ਼ੀਲ ਹੋ ਸਕਦਾ ਹੈ। + ਜਦੋਂ ਤੁਹਾਡੇ ਜ਼ਿਆਦਾਤਰ ਨਿਰਯਾਤ ਜਨਤਕ ਜਾਂ ਅਰਧ-ਜਨਤਕ ਸਾਂਝਾਕਰਨ ਲਈ ਹੁੰਦੇ ਹਨ ਤਾਂ ਹਮੇਸ਼ਾ-ਸਪਸ਼ਟ EXIF ​​ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ। + ਕੈਪਚਰ ਟਾਈਮ ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖਣਾ ਹਰ ਟਾਈਮਸਟੈਂਪ ਨੂੰ ਹਟਾਉਣ ਨਾਲੋਂ ਜ਼ਿਆਦਾ ਮਹੱਤਵਪੂਰਨ ਹੋਣ \'ਤੇ ਹੀ ਤਾਰੀਖ ਸਮਾਂ ਰੱਖੋ ਨੂੰ ਚਾਲੂ ਕਰੋ। + ਇੱਕ-ਬੰਦ ਫਾਈਲਾਂ ਲਈ ਮੈਨੁਅਲ EXIF ​​ਸੰਪਾਦਨ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਿੱਥੇ ਤੁਹਾਨੂੰ ਕੁਝ ਖੇਤਰ ਰੱਖਣ ਅਤੇ ਹੋਰਾਂ ਨੂੰ ਹਟਾਉਣ ਦੀ ਲੋੜ ਹੈ। + ਕੰਟਰੋਲ ਫਾਈਲਨਾਮ + ਅਗੇਤਰ, ਪਿਛੇਤਰ, ਟਾਈਮਸਟੈਂਪ, ਪ੍ਰੀਸੈੱਟ, ਚੈੱਕਸਮ ਅਤੇ ਕ੍ਰਮ ਨੰਬਰਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਨਿਰਯਾਤ ਨੂੰ ਲੱਭਣਾ ਆਸਾਨ ਬਣਾਓ + ਇੱਕ ਚੰਗਾ ਫਾਈਲਨਾਮ ਪੈਟਰਨ ਬੈਚਾਂ ਨੂੰ ਕ੍ਰਮਬੱਧ ਕਰਨ ਵਿੱਚ ਮਦਦ ਕਰਦਾ ਹੈ ਅਤੇ ਦੁਰਘਟਨਾ ਨੂੰ ਓਵਰਰਾਈਟਸ ਨੂੰ ਰੋਕਦਾ ਹੈ। ਫਾਈਲਨਾਮ ਸੈਟਿੰਗਾਂ ਖਾਸ ਤੌਰ \'ਤੇ ਉਪਯੋਗੀ ਹੁੰਦੀਆਂ ਹਨ ਜਦੋਂ ਐਕਸਪੋਰਟ ਸਰੋਤ ਫੋਲਡਰ ਵਿੱਚ ਵਾਪਸ ਜਾਂਦੇ ਹਨ, ਜਿੱਥੇ ਸਮਾਨ ਨਾਮ ਤੇਜ਼ੀ ਨਾਲ ਉਲਝਣ ਵਿੱਚ ਪੈ ਸਕਦੇ ਹਨ। + ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਫਾਈਲ ਨਾਮ ਅਗੇਤਰ, ਪਿਛੇਤਰ, ਟਾਈਮਸਟੈਂਪ, ਅਸਲੀ ਨਾਮ, ਪ੍ਰੀਸੈਟ ਜਾਣਕਾਰੀ, ਜਾਂ ਕ੍ਰਮ ਵਿਕਲਪ ਸੈੱਟ ਕਰੋ। + ਉਹਨਾਂ ਬੈਚਾਂ ਲਈ ਕ੍ਰਮ ਨੰਬਰਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਿੱਥੇ ਆਰਡਰ ਮਾਇਨੇ ਰੱਖਦਾ ਹੈ, ਜਿਵੇਂ ਕਿ ਪੰਨੇ, ਫਰੇਮ ਜਾਂ ਕੋਲਾਜ। + ਓਵਰਰਾਈਟ ਉਦੋਂ ਹੀ ਸਮਰੱਥ ਕਰੋ ਜਦੋਂ ਤੁਸੀਂ ਯਕੀਨੀ ਹੋਵੋ ਕਿ ਮੌਜੂਦਾ ਫੋਲਡਰ ਲਈ ਮੌਜੂਦਾ ਫਾਈਲਾਂ ਨੂੰ ਬਦਲਣਾ ਸੁਰੱਖਿਅਤ ਹੈ। + ਫਾਈਲਾਂ ਨੂੰ ਓਵਰਰਾਈਟ ਕਰੋ + ਆਉਟਪੁੱਟ ਨੂੰ ਮੇਲ ਖਾਂਦੇ ਨਾਮਾਂ ਨਾਲ ਬਦਲੋ ਜਦੋਂ ਤੁਹਾਡਾ ਵਰਕਫਲੋ ਜਾਣਬੁੱਝ ਕੇ ਵਿਨਾਸ਼ਕਾਰੀ ਹੋਵੇ + ਓਵਰਰਾਈਟ ਮੋਡ ਦੀ ਸਾਵਧਾਨੀ ਨਾਲ ਵਰਤੋਂ ਕਰੋ + ਓਵਰਰਾਈਟ ਫਾਈਲਾਂ ਬਦਲਦੀਆਂ ਹਨ ਕਿ ਨਾਮਕਰਨ ਵਿਵਾਦਾਂ ਨੂੰ ਕਿਵੇਂ ਨਜਿੱਠਿਆ ਜਾਂਦਾ ਹੈ। ਇਹ ਉਸੇ ਫੋਲਡਰ ਵਿੱਚ ਦੁਹਰਾਉਣ ਵਾਲੇ ਨਿਰਯਾਤ ਲਈ ਲਾਭਦਾਇਕ ਹੈ, ਪਰ ਇਹ ਪੁਰਾਣੇ ਨਤੀਜਿਆਂ ਨੂੰ ਵੀ ਬਦਲ ਸਕਦਾ ਹੈ ਜੇਕਰ ਤੁਹਾਡਾ ਫਾਈਲਨਾਮ ਪੈਟਰਨ ਉਹੀ ਨਾਮ ਦੁਬਾਰਾ ਬਣਾਉਂਦਾ ਹੈ। + ਸਿਰਫ਼ ਉਹਨਾਂ ਫੋਲਡਰਾਂ ਲਈ ਓਵਰਰਾਈਟ ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ ਜਿੱਥੇ ਪੁਰਾਣੀਆਂ ਤਿਆਰ ਕੀਤੀਆਂ ਫਾਈਲਾਂ ਨੂੰ ਬਦਲਣ ਦੀ ਉਮੀਦ ਕੀਤੀ ਜਾਂਦੀ ਹੈ ਅਤੇ ਮੁੜ ਪ੍ਰਾਪਤ ਕੀਤੀ ਜਾ ਸਕਦੀ ਹੈ। + ਮੂਲ, ਕਲਾਇੰਟ ਫਾਈਲਾਂ, ਵਨ-ਟਾਈਮ ਸਕੈਨ, ਜਾਂ ਬੈਕਅੱਪ ਤੋਂ ਬਿਨਾਂ ਕੁਝ ਵੀ ਨਿਰਯਾਤ ਕਰਦੇ ਸਮੇਂ ਓਵਰਰਾਈਟ ਨੂੰ ਅਯੋਗ ਰੱਖੋ। + ਇੱਕ ਜਾਣਬੁੱਝ ਕੇ ਫਾਈਲ ਨਾਮ ਪੈਟਰਨ ਨਾਲ ਓਵਰਰਾਈਟ ਨੂੰ ਜੋੜੋ ਤਾਂ ਜੋ ਸਿਰਫ ਇੱਛਤ ਆਉਟਪੁੱਟ ਬਦਲੇ ਜਾਣ। + ਫਾਈਲਨਾਮ ਪੈਟਰਨ + ਅਸਲੀ ਨਾਮ, ਅਗੇਤਰ, ਪਿਛੇਤਰ, ਟਾਈਮਸਟੈਂਪ, ਪ੍ਰੀਸੈੱਟ, ਸਕੇਲ ਮੋਡ, ਅਤੇ ਚੈੱਕਸਮ ਨੂੰ ਜੋੜੋ + ਨਾਂ ਬਣਾਓ ਜੋ ਆਉਟਪੁੱਟ ਦੀ ਵਿਆਖਿਆ ਕਰਦੇ ਹਨ + ਫਾਈਲਨਾਮ ਪੈਟਰਨ ਸੈਟਿੰਗਾਂ ਨਿਰਯਾਤ ਕੀਤੀਆਂ ਫਾਈਲਾਂ ਨੂੰ ਉਹਨਾਂ ਦੇ ਨਾਲ ਕੀ ਵਾਪਰਿਆ ਦੇ ਉਪਯੋਗੀ ਇਤਿਹਾਸ ਵਿੱਚ ਬਦਲ ਸਕਦੀਆਂ ਹਨ। ਇਹ ਬੈਚਾਂ ਵਿੱਚ ਮਾਇਨੇ ਰੱਖਦਾ ਹੈ ਕਿਉਂਕਿ ਫੋਲਡਰ ਦ੍ਰਿਸ਼ ਸਿਰਫ ਉਹੀ ਸਥਾਨ ਹੋ ਸਕਦਾ ਹੈ ਜਿੱਥੇ ਤੁਸੀਂ ਅਸਲ ਆਕਾਰ, ਪ੍ਰੀਸੈਟ, ਫਾਰਮੈਟ ਅਤੇ ਪ੍ਰੋਸੈਸਿੰਗ ਆਰਡਰ ਨੂੰ ਵੱਖ ਕਰ ਸਕਦੇ ਹੋ। + ਜਦੋਂ ਤੁਹਾਨੂੰ ਦਸਤੀ ਛਾਂਟੀ ਲਈ ਪੜ੍ਹਨਯੋਗ ਨਾਵਾਂ ਦੀ ਲੋੜ ਹੋਵੇ ਤਾਂ ਅਸਲ ਫਾਈਲ ਨਾਮ, ਅਗੇਤਰ, ਪਿਛੇਤਰ, ਅਤੇ ਟਾਈਮਸਟੈਂਪ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਪ੍ਰੀਸੈਟ, ਸਕੇਲ ਮੋਡ, ਫਾਈਲ ਸਾਈਜ਼, ਜਾਂ ਚੈਕਸਮ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਕਰੋ ਜਦੋਂ ਆਉਟਪੁੱਟਾਂ ਨੂੰ ਦਸਤਾਵੇਜ਼ ਬਣਾਉਣਾ ਚਾਹੀਦਾ ਹੈ ਕਿ ਉਹ ਕਿਵੇਂ ਤਿਆਰ ਕੀਤੇ ਗਏ ਸਨ। + ਵਰਕਫਲੋ ਲਈ ਬੇਤਰਤੀਬ ਨਾਵਾਂ ਤੋਂ ਬਚੋ ਜਿੱਥੇ ਫਾਈਲਾਂ ਨੂੰ ਮੂਲ ਜਾਂ ਪੇਜ ਆਰਡਰ ਨਾਲ ਜੋੜੀ ਰੱਖਣ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। + ਚੁਣੋ ਕਿ ਫ਼ਾਈਲਾਂ ਕਿੱਥੇ ਰੱਖਿਅਤ ਕੀਤੀਆਂ ਜਾਣ + ਫੋਲਡਰ, ਮੂਲ-ਫੋਲਡਰ ਸੇਵਿੰਗ, ਅਤੇ ਵਨ-ਟਾਈਮ ਸੇਵ ਟਿਕਾਣੇ ਸੈੱਟ ਕਰਕੇ ਨਿਰਯਾਤ ਨੂੰ ਗੁਆਉਣ ਤੋਂ ਬਚੋ + ਆਉਟਪੁੱਟ ਨੂੰ ਅਨੁਮਾਨਿਤ ਰੱਖੋ + ਜਦੋਂ ਤੁਸੀਂ ਬਹੁਤ ਸਾਰੀਆਂ ਫਾਈਲਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਦੇ ਹੋ ਜਾਂ ਕਲਾਉਡ ਪ੍ਰਦਾਤਾਵਾਂ ਤੋਂ ਫਾਈਲਾਂ ਸਾਂਝੀਆਂ ਕਰਦੇ ਹੋ ਤਾਂ ਵਿਵਹਾਰ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ। ਫੋਲਡਰ ਸੈਟਿੰਗਾਂ ਇਹ ਫੈਸਲਾ ਕਰਦੀਆਂ ਹਨ ਕਿ ਕੀ ਆਉਟਪੁੱਟ ਇੱਕ ਜਾਣੇ-ਪਛਾਣੇ ਸਥਾਨ \'ਤੇ ਇਕੱਠੇ ਹੁੰਦੇ ਹਨ, ਮੂਲ ਦੇ ਨਾਲ ਦਿਖਾਈ ਦਿੰਦੇ ਹਨ, ਜਾਂ ਕਿਸੇ ਖਾਸ ਕੰਮ ਲਈ ਅਸਥਾਈ ਤੌਰ \'ਤੇ ਕਿਤੇ ਹੋਰ ਜਾਂਦੇ ਹਨ। + ਜੇਕਰ ਤੁਸੀਂ ਹਮੇਸ਼ਾ ਇੱਕ ਥਾਂ \'ਤੇ ਨਿਰਯਾਤ ਚਾਹੁੰਦੇ ਹੋ ਤਾਂ ਇੱਕ ਡਿਫੌਲਟ ਸੇਵਿੰਗ ਫੋਲਡਰ ਸੈੱਟ ਕਰੋ। + ਕਲੀਨਅਪ ਵਰਕਫਲੋ ਲਈ ਸੇਵ-ਟੂ-ਆਰਜੀਨਲ-ਫੋਲਡਰ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਿੱਥੇ ਆਉਟਪੁੱਟ ਸਰੋਤ ਫਾਈਲ ਦੇ ਨੇੜੇ ਰਹਿਣਾ ਚਾਹੀਦਾ ਹੈ। + ਇੱਕ ਵਾਰ ਸੰਭਾਲਣ ਵਾਲੇ ਟਿਕਾਣੇ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਇੱਕ ਇੱਕਲੇ ਕੰਮ ਨੂੰ ਤੁਹਾਡੇ ਡਿਫੌਲਟ ਨੂੰ ਬਦਲੇ ਬਿਨਾਂ ਇੱਕ ਵੱਖਰੀ ਮੰਜ਼ਿਲ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। + ਇੱਕ ਵਾਰ ਸੰਭਾਲਣ ਵਾਲੇ ਫੋਲਡਰ + ਅਗਲੇ ਨਿਰਯਾਤ ਨੂੰ ਆਪਣੀ ਆਮ ਮੰਜ਼ਿਲ ਨੂੰ ਬਦਲੇ ਬਿਨਾਂ ਇੱਕ ਅਸਥਾਈ ਫੋਲਡਰ ਵਿੱਚ ਭੇਜੋ + ਵਿਸ਼ੇਸ਼ ਨੌਕਰੀਆਂ ਨੂੰ ਅਲੱਗ ਰੱਖੋ + ਵਨ-ਟਾਈਮ ਸੇਵ ਟਿਕਾਣਾ ਅਸਥਾਈ ਪ੍ਰੋਜੈਕਟਾਂ, ਕਲਾਇੰਟ ਫੋਲਡਰਾਂ, ਕਲੀਨਅੱਪ ਸੈਸ਼ਨਾਂ, ਜਾਂ ਨਿਰਯਾਤ ਲਈ ਲਾਭਦਾਇਕ ਹੈ ਜੋ ਤੁਹਾਡੇ ਨਿਯਮਤ ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਫੋਲਡਰ ਨਾਲ ਨਹੀਂ ਮਿਲਾਉਣਾ ਚਾਹੀਦਾ। ਇਹ ਤੁਹਾਡੇ ਡਿਫੌਲਟ ਸੈਟਅਪ ਨੂੰ ਦੁਬਾਰਾ ਲਿਖੇ ਬਿਨਾਂ ਇੱਕ ਥੋੜ੍ਹੇ ਸਮੇਂ ਲਈ ਮੰਜ਼ਿਲ ਦਿੰਦਾ ਹੈ। + ਇੱਕ ਕਾਰਜ ਸ਼ੁਰੂ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਇੱਕ ਵਾਰ-ਵਾਰ ਫੋਲਡਰ ਚੁਣੋ ਜੋ ਤੁਹਾਡੇ ਆਮ ਨਿਰਯਾਤ ਸਥਾਨ ਤੋਂ ਬਾਹਰ ਹੈ। + ਇਸਨੂੰ ਛੋਟੇ ਪ੍ਰੋਜੈਕਟਾਂ, ਸਾਂਝੇ ਕੀਤੇ ਫੋਲਡਰਾਂ, ਜਾਂ ਬੈਚਾਂ ਲਈ ਵਰਤੋ ਜਿੱਥੇ ਆਉਟਪੁੱਟਾਂ ਨੂੰ ਇਕੱਠੇ ਸਮੂਹ ਵਿੱਚ ਰੱਖਣਾ ਚਾਹੀਦਾ ਹੈ। + ਨੌਕਰੀ ਤੋਂ ਬਾਅਦ ਡਿਫੌਲਟ ਫੋਲਡਰ \'ਤੇ ਵਾਪਸ ਜਾਓ ਤਾਂ ਜੋ ਬਾਅਦ ਵਿੱਚ ਨਿਰਯਾਤ ਅਚਾਨਕ ਅਸਥਾਈ ਸਥਾਨ \'ਤੇ ਨਾ ਉਤਰੇ। + ਸੰਤੁਲਨ ਗੁਣਵੱਤਾ ਅਤੇ ਫਾਈਲ ਆਕਾਰ + ਇਹ ਸਮਝੋ ਕਿ ਅੰਦਾਜ਼ਾ ਲਗਾਉਣ ਦੀ ਬਜਾਏ ਮਾਪ, ਫਾਰਮੈਟ ਜਾਂ ਗੁਣਵੱਤਾ ਨੂੰ ਕਦੋਂ ਬਦਲਣਾ ਹੈ + ਫਾਈਲਾਂ ਨੂੰ ਜਾਣਬੁੱਝ ਕੇ ਸੁੰਗੜੋ + ਫ਼ਾਈਲ ਦਾ ਆਕਾਰ ਚਿੱਤਰ ਦੇ ਮਾਪ, ਫਾਰਮੈਟ, ਗੁਣਵੱਤਾ, ਪਾਰਦਰਸ਼ਤਾ ਅਤੇ ਸਮੱਗਰੀ ਦੀ ਗੁੰਝਲਤਾ \'ਤੇ ਨਿਰਭਰ ਕਰਦਾ ਹੈ। ਜੇਕਰ ਕੋਈ ਫ਼ਾਈਲ ਹਾਲੇ ਵੀ ਬਹੁਤ ਜ਼ਿਆਦਾ ਭਾਰੀ ਹੈ, ਤਾਂ ਮਾਪ ਬਦਲਣ ਨਾਲ ਗੁਣਵੱਤਾ ਨੂੰ ਵਾਰ-ਵਾਰ ਘਟਾਉਣ ਨਾਲੋਂ ਜ਼ਿਆਦਾ ਮਦਦ ਮਿਲਦੀ ਹੈ। + ਜਦੋਂ ਚਿੱਤਰ ਮੰਜ਼ਿਲ ਤੋਂ ਵੱਡਾ ਹੋਵੇ ਤਾਂ ਪਹਿਲਾਂ ਮਾਪਾਂ ਦਾ ਆਕਾਰ ਬਦਲੋ। + ਸਿਰਫ਼ ਨੁਕਸਾਨਦੇਹ ਫਾਰਮੈਟਾਂ ਲਈ ਨੀਵੀਂ ਕੁਆਲਿਟੀ ਅਤੇ ਨਿਰਯਾਤ ਤੋਂ ਬਾਅਦ ਟੈਕਸਟ, ਕਿਨਾਰਿਆਂ, ਗਰੇਡੀਐਂਟਸ ਅਤੇ ਚਿਹਰਿਆਂ ਦੀ ਜਾਂਚ ਕਰੋ। + ਜਦੋਂ ਗੁਣਵੱਤਾ ਵਿੱਚ ਤਬਦੀਲੀਆਂ ਕਾਫ਼ੀ ਨਾ ਹੋਣ ਤਾਂ ਫਾਰਮੈਟ ਬਦਲੋ, ਉਦਾਹਰਨ ਲਈ ਸਾਂਝਾਕਰਨ ਲਈ WebP ਜਾਂ ਕਰਿਸਪ ਪਾਰਦਰਸ਼ੀ ਸੰਪਤੀਆਂ ਲਈ PNG। + ਐਨੀਮੇਟਡ ਫਾਰਮੈਟਾਂ ਨਾਲ ਕੰਮ ਕਰੋ + ਜਦੋਂ ਇੱਕ ਫਾਈਲ ਵਿੱਚ ਇੱਕ ਬਿੱਟਮੈਪ ਦੀ ਬਜਾਏ ਫ੍ਰੇਮ ਹੋਵੇ ਤਾਂ GIF, APNG, WebP, ਅਤੇ JXL ਟੂਲਸ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਹਰ ਚਿੱਤਰ ਨੂੰ ਸਥਿਰ ਨਾ ਸਮਝੋ + ਐਨੀਮੇਟਡ ਫਾਰਮੈਟਾਂ ਨੂੰ ਫ੍ਰੇਮ, ਮਿਆਦ, ਅਤੇ ਅਨੁਕੂਲਤਾ ਨੂੰ ਸਮਝਣ ਵਾਲੇ ਸਾਧਨਾਂ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। + ਜਦੋਂ ਤੁਹਾਨੂੰ ਉਹਨਾਂ ਫਾਰਮੈਟਾਂ ਲਈ ਫਰੇਮ-ਪੱਧਰ ਦੀਆਂ ਕਾਰਵਾਈਆਂ ਦੀ ਲੋੜ ਹੋਵੇ ਤਾਂ GIF ਜਾਂ APNG ਟੂਲਸ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਜਦੋਂ ਤੁਹਾਨੂੰ ਆਧੁਨਿਕ ਕੰਪਰੈਸ਼ਨ ਜਾਂ ਐਨੀਮੇਟਡ WebP ਆਉਟਪੁੱਟ ਦੀ ਲੋੜ ਹੋਵੇ ਤਾਂ WebP ਟੂਲਸ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਸ਼ੇਅਰ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਐਨੀਮੇਸ਼ਨ ਟਾਈਮਿੰਗ ਦੀ ਪੂਰਵਦਰਸ਼ਨ ਕਰੋ ਕਿਉਂਕਿ ਕੁਝ ਪਲੇਟਫਾਰਮ ਐਨੀਮੇਟਡ ਚਿੱਤਰਾਂ ਨੂੰ ਬਦਲਦੇ ਜਾਂ ਸਮਤਲ ਕਰਦੇ ਹਨ। + ਆਉਟਪੁੱਟ ਦੀ ਤੁਲਨਾ ਅਤੇ ਪੂਰਵਦਰਸ਼ਨ ਕਰੋ + ਨਿਰਯਾਤ ਰੱਖਣ ਤੋਂ ਪਹਿਲਾਂ ਤਬਦੀਲੀਆਂ, ਫਾਈਲ ਆਕਾਰ ਅਤੇ ਵਿਜ਼ੂਅਲ ਅੰਤਰਾਂ ਦੀ ਜਾਂਚ ਕਰੋ + ਸਾਂਝਾ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਪੁਸ਼ਟੀ ਕਰੋ + ਤੁਲਨਾ ਟੂਲ ਕੰਪਰੈਸ਼ਨ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ, ਗਲਤ ਰੰਗਾਂ, ਜਾਂ ਅਣਚਾਹੇ ਸੰਪਾਦਨਾਂ ਨੂੰ ਛੇਤੀ ਫੜਨ ਵਿੱਚ ਮਦਦ ਕਰਦੇ ਹਨ। + ਜਦੋਂ ਤੁਸੀਂ ਨਾਲ-ਨਾਲ ਦੋ ਸੰਸਕਰਣਾਂ ਦੀ ਜਾਂਚ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ ਤਾਂ ਤੁਲਨਾ ਖੋਲ੍ਹੋ। + ਕਿਨਾਰਿਆਂ, ਟੈਕਸਟ, ਗਰੇਡੀਐਂਟਸ, ਅਤੇ ਪਾਰਦਰਸ਼ੀ ਖੇਤਰਾਂ ਵਿੱਚ ਜ਼ੂਮ ਕਰੋ ਜਿੱਥੇ ਸਮੱਸਿਆਵਾਂ ਨੂੰ ਖੁੰਝਾਉਣਾ ਸਭ ਤੋਂ ਆਸਾਨ ਹੈ। + ਜੇਕਰ ਆਉਟਪੁੱਟ ਬਹੁਤ ਭਾਰੀ ਜਾਂ ਧੁੰਦਲੀ ਹੈ, ਤਾਂ ਪਿਛਲੇ ਟੂਲ \'ਤੇ ਵਾਪਸ ਜਾਓ ਅਤੇ ਗੁਣਵੱਤਾ ਜਾਂ ਫਾਰਮੈਟ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ। + PDF ਟੂਲ ਹੱਬ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਪੀਡੀਐਫ ਫਾਈਲਾਂ ਨੂੰ ਮਿਲਾਓ, ਵੰਡੋ, ਘੁੰਮਾਓ, ਪੰਨੇ ਹਟਾਓ, ਸੰਕੁਚਿਤ ਕਰੋ, ਸੁਰੱਖਿਆ ਕਰੋ, OCR ਅਤੇ ਵਾਟਰਮਾਰਕ ਕਰੋ + ਇੱਕ PDF ਓਪਰੇਸ਼ਨ ਚੁਣੋ + PDF ਹੱਬ ਦਸਤਾਵੇਜ਼ ਕਾਰਵਾਈਆਂ ਨੂੰ ਇੱਕ ਐਂਟਰੀ ਪੁਆਇੰਟ ਦੇ ਪਿੱਛੇ ਸਮੂਹ ਕਰਦਾ ਹੈ ਤਾਂ ਜੋ ਤੁਸੀਂ ਸਹੀ ਕਾਰਵਾਈ ਚੁਣ ਸਕੋ। ਜਦੋਂ ਫਾਈਲ ਪਹਿਲਾਂ ਹੀ PDF ਹੋਵੇ ਤਾਂ ਉੱਥੇ ਸ਼ੁਰੂ ਕਰੋ, ਅਤੇ ਜਦੋਂ ਤੁਹਾਡਾ ਸਰੋਤ ਅਜੇ ਵੀ ਚਿੱਤਰਾਂ ਦਾ ਇੱਕ ਸਮੂਹ ਹੈ ਤਾਂ PDF ਜਾਂ ਦਸਤਾਵੇਜ਼ ਸਕੈਨਰ ਲਈ ਚਿੱਤਰਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ। + PDF ਟੂਲ ਖੋਲ੍ਹੋ ਅਤੇ ਓਪਰੇਸ਼ਨ ਚੁਣੋ ਜੋ ਤੁਹਾਡੇ ਟੀਚੇ ਨਾਲ ਮੇਲ ਖਾਂਦਾ ਹੈ। + ਸਰੋਤ PDF ਜਾਂ ਚਿੱਤਰ ਚੁਣੋ, ਫਿਰ ਪੰਨਾ ਕ੍ਰਮ, ਰੋਟੇਸ਼ਨ, ਸੁਰੱਖਿਆ, ਜਾਂ OCR ਵਿਕਲਪਾਂ ਦੀ ਸਮੀਖਿਆ ਕਰੋ। + ਤਿਆਰ ਕੀਤੀ PDF ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ ਅਤੇ ਪੰਨੇ ਦੀ ਗਿਣਤੀ, ਆਰਡਰ ਅਤੇ ਪੜ੍ਹਨਯੋਗਤਾ ਦੀ ਪੁਸ਼ਟੀ ਕਰਨ ਲਈ ਇਸਨੂੰ ਇੱਕ ਵਾਰ ਖੋਲ੍ਹੋ। + ਚਿੱਤਰਾਂ ਨੂੰ PDF ਵਿੱਚ ਬਦਲੋ + ਸਕੈਨ, ਸਕ੍ਰੀਨਸ਼ੌਟਸ, ਰਸੀਦਾਂ, ਜਾਂ ਫੋਟੋਆਂ ਨੂੰ ਇੱਕ ਸਾਂਝਾ ਕਰਨ ਯੋਗ ਦਸਤਾਵੇਜ਼ ਵਿੱਚ ਜੋੜੋ + ਇੱਕ ਸਾਫ਼ ਦਸਤਾਵੇਜ਼ ਬਣਾਓ + ਚਿੱਤਰ ਬਿਹਤਰ ਪੀਡੀਐਫ ਪੰਨੇ ਬਣ ਜਾਂਦੇ ਹਨ ਜਦੋਂ ਉਹਨਾਂ ਨੂੰ ਲਗਾਤਾਰ ਕੱਟਿਆ ਜਾਂਦਾ ਹੈ, ਆਰਡਰ ਕੀਤਾ ਜਾਂਦਾ ਹੈ ਅਤੇ ਆਕਾਰ ਦਿੱਤਾ ਜਾਂਦਾ ਹੈ। ਚਿੱਤਰ ਸੂਚੀ ਨੂੰ ਇੱਕ ਪੰਨੇ ਦੇ ਸਟੈਕ ਵਾਂਗ ਵਰਤੋ: ਹਰ ਰੋਟੇਸ਼ਨ, ਹਾਸ਼ੀਏ, ਅਤੇ ਪੰਨੇ-ਆਕਾਰ ਦੀ ਚੋਣ ਇਸ ਗੱਲ ਨੂੰ ਪ੍ਰਭਾਵਤ ਕਰਦੀ ਹੈ ਕਿ ਅੰਤਿਮ ਦਸਤਾਵੇਜ਼ ਕਿੰਨਾ ਪੜ੍ਹਨਯੋਗ ਮਹਿਸੂਸ ਕਰਦਾ ਹੈ। + ਜਦੋਂ ਪੰਨੇ ਦੇ ਕਿਨਾਰੇ, ਪਰਛਾਵੇਂ, ਜਾਂ ਸਥਿਤੀ ਗਲਤ ਹੋਣ ਤਾਂ ਚਿੱਤਰਾਂ ਨੂੰ ਪਹਿਲਾਂ ਕੱਟੋ ਜਾਂ ਘੁੰਮਾਓ। + ਇਰਾਦੇ ਵਾਲੇ ਪੰਨੇ ਦੇ ਕ੍ਰਮ ਵਿੱਚ ਚਿੱਤਰਾਂ ਦੀ ਚੋਣ ਕਰੋ ਅਤੇ ਪੰਨੇ ਦੇ ਆਕਾਰ ਜਾਂ ਹਾਸ਼ੀਏ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ ਜੇਕਰ ਟੂਲ ਉਹਨਾਂ ਦੀ ਪੇਸ਼ਕਸ਼ ਕਰਦਾ ਹੈ। + PDF ਨੂੰ ਨਿਰਯਾਤ ਕਰੋ, ਫਿਰ ਜਾਂਚ ਕਰੋ ਕਿ ਇਸਨੂੰ ਭੇਜਣ ਤੋਂ ਪਹਿਲਾਂ ਸਾਰੇ ਪੰਨੇ ਪੜ੍ਹਨਯੋਗ ਹਨ। + ਦਸਤਾਵੇਜ਼ਾਂ ਨੂੰ ਸਕੈਨ ਕਰੋ + ਕਾਗਜ਼ ਦੇ ਪੰਨਿਆਂ ਨੂੰ ਕੈਪਚਰ ਕਰੋ ਅਤੇ ਉਹਨਾਂ ਨੂੰ PDF, OCR, ਜਾਂ ਸਾਂਝਾ ਕਰਨ ਲਈ ਤਿਆਰ ਕਰੋ + ਪੜ੍ਹਨਯੋਗ ਪੰਨਿਆਂ ਨੂੰ ਕੈਪਚਰ ਕਰੋ + ਦਸਤਾਵੇਜ਼ ਸਕੈਨਿੰਗ ਫਲੈਟ ਪੰਨਿਆਂ, ਚੰਗੀ ਰੋਸ਼ਨੀ, ਅਤੇ ਸਹੀ ਕੀਤੇ ਦ੍ਰਿਸ਼ਟੀਕੋਣ ਨਾਲ ਵਧੀਆ ਕੰਮ ਕਰਦੀ ਹੈ। OCR ਜਾਂ PDF ਨਿਰਯਾਤ ਤੋਂ ਪਹਿਲਾਂ ਇੱਕ ਸਾਫ਼ ਸਕੈਨ ਸਮੇਂ ਦੀ ਬਚਤ ਕਰਦਾ ਹੈ ਕਿਉਂਕਿ ਟੈਕਸਟ ਪਛਾਣ ਅਤੇ ਸੰਕੁਚਨ ਦੋਵੇਂ ਪੰਨੇ ਦੀ ਗੁਣਵੱਤਾ \'ਤੇ ਨਿਰਭਰ ਕਰਦੇ ਹਨ। + ਦਸਤਾਵੇਜ਼ ਨੂੰ ਇੱਕ ਵਿਪਰੀਤ ਸਤਹ \'ਤੇ ਕਾਫ਼ੀ ਰੋਸ਼ਨੀ ਅਤੇ ਘੱਟੋ-ਘੱਟ ਸ਼ੈਡੋ ਦੇ ਨਾਲ ਰੱਖੋ। + ਖੋਜੇ ਗਏ ਕੋਨਿਆਂ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ ਜਾਂ ਹੱਥੀਂ ਕਾਂਟ-ਛਾਂਟ ਕਰੋ ਤਾਂ ਕਿ ਪੰਨਾ ਫਰੇਮ ਨੂੰ ਸਾਫ਼-ਸਫ਼ਾਈ ਨਾਲ ਭਰ ਸਕੇ। + ਚਿੱਤਰਾਂ ਜਾਂ PDF ਵਜੋਂ ਨਿਰਯਾਤ ਕਰੋ, ਫਿਰ OCR ਚਲਾਓ ਜੇਕਰ ਤੁਹਾਨੂੰ ਚੋਣਯੋਗ ਟੈਕਸਟ ਦੀ ਲੋੜ ਹੈ। + PDF ਫਾਈਲਾਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਜਾਂ ਅਨਲੌਕ ਕਰੋ + ਪਾਸਵਰਡ ਦੀ ਸਾਵਧਾਨੀ ਨਾਲ ਵਰਤੋਂ ਕਰੋ ਅਤੇ ਜਦੋਂ ਵੀ ਸੰਭਵ ਹੋਵੇ ਸੰਪਾਦਨਯੋਗ ਸਰੋਤ ਕਾਪੀ ਰੱਖੋ + PDF ਪਹੁੰਚ ਨੂੰ ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਸੰਭਾਲੋ + ਸੁਰੱਖਿਆ ਟੂਲ ਨਿਯੰਤਰਿਤ ਸ਼ੇਅਰਿੰਗ ਲਈ ਉਪਯੋਗੀ ਹੁੰਦੇ ਹਨ, ਪਰ ਪਾਸਵਰਡ ਗੁਆਉਣੇ ਆਸਾਨ ਅਤੇ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰਨੇ ਔਖੇ ਹੁੰਦੇ ਹਨ। ਕਾਪੀਆਂ ਨੂੰ ਵੰਡਣ ਲਈ ਸੁਰੱਖਿਅਤ ਕਰੋ, ਅਸੁਰੱਖਿਅਤ ਸਰੋਤ ਫਾਈਲਾਂ ਨੂੰ ਵੱਖਰੇ ਤੌਰ \'ਤੇ ਰੱਖੋ, ਅਤੇ ਕੁਝ ਵੀ ਮਿਟਾਉਣ ਤੋਂ ਪਹਿਲਾਂ ਨਤੀਜੇ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ। + PDF ਦੀ ਇੱਕ ਕਾਪੀ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ, ਨਾ ਕਿ ਤੁਹਾਡੇ ਇੱਕੋ ਇੱਕ ਸਰੋਤ ਦਸਤਾਵੇਜ਼। + ਸੁਰੱਖਿਅਤ ਫਾਈਲ ਨੂੰ ਸਾਂਝਾ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਪਾਸਵਰਡ ਨੂੰ ਕਿਤੇ ਸੁਰੱਖਿਅਤ ਰੱਖੋ। + ਸਿਰਫ਼ ਉਹਨਾਂ ਫ਼ਾਈਲਾਂ ਲਈ ਅਨਲੌਕ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਿਨ੍ਹਾਂ ਨੂੰ ਤੁਹਾਨੂੰ ਸੰਪਾਦਿਤ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਹੈ, ਫਿਰ ਪੁਸ਼ਟੀ ਕਰੋ ਕਿ ਅਨਲੌਕ ਕੀਤੀ ਕਾਪੀ ਸਹੀ ਢੰਗ ਨਾਲ ਖੁੱਲ੍ਹਦੀ ਹੈ। + PDF ਪੰਨਿਆਂ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ + ਮਿਲਾਓ, ਵੰਡੋ, ਮੁੜ ਵਿਵਸਥਿਤ ਕਰੋ, ਘੁੰਮਾਓ, ਕੱਟੋ, ਪੰਨਿਆਂ ਨੂੰ ਹਟਾਓ, ਅਤੇ ਜਾਣਬੁੱਝ ਕੇ ਪੰਨਾ ਨੰਬਰ ਸ਼ਾਮਲ ਕਰੋ + ਸਜਾਵਟ ਤੋਂ ਪਹਿਲਾਂ ਦਸਤਾਵੇਜ਼ ਬਣਤਰ ਨੂੰ ਠੀਕ ਕਰੋ + PDF ਪੇਜ ਟੂਲਸ ਨੂੰ ਉਸੇ ਕ੍ਰਮ ਵਿੱਚ ਵਰਤਣਾ ਆਸਾਨ ਹੈ ਜਿਸ ਕ੍ਰਮ ਵਿੱਚ ਤੁਸੀਂ ਇੱਕ ਕਾਗਜ਼ੀ ਦਸਤਾਵੇਜ਼ ਤਿਆਰ ਕਰਦੇ ਹੋ: ਪੰਨਿਆਂ ਨੂੰ ਇਕੱਠਾ ਕਰੋ, ਗਲਤਾਂ ਨੂੰ ਹਟਾਓ, ਉਹਨਾਂ ਨੂੰ ਕ੍ਰਮ ਵਿੱਚ ਰੱਖੋ, ਸਹੀ ਸਥਿਤੀ, ਫਿਰ ਅੰਤਮ ਵੇਰਵੇ ਜਿਵੇਂ ਕਿ ਪੰਨਾ ਨੰਬਰ ਜਾਂ ਵਾਟਰਮਾਰਕ ਸ਼ਾਮਲ ਕਰੋ। + ਜਦੋਂ ਦਸਤਾਵੇਜ਼ ਅਜੇ ਵੀ ਕਈ ਫਾਈਲਾਂ ਵਿੱਚ ਵੰਡਿਆ ਹੋਇਆ ਹੈ ਤਾਂ ਪਹਿਲਾਂ ਵਿਲੀਨ ਜਾਂ ਚਿੱਤਰ-ਟੂ-ਪੀਡੀਐਫ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਪੰਨਾ ਨੰਬਰ, ਦਸਤਖਤ ਜਾਂ ਵਾਟਰਮਾਰਕ ਜੋੜਨ ਤੋਂ ਪਹਿਲਾਂ ਪੰਨਿਆਂ ਨੂੰ ਮੁੜ ਵਿਵਸਥਿਤ ਕਰੋ, ਘੁੰਮਾਓ, ਕੱਟੋ ਜਾਂ ਹਟਾਓ। + ਢਾਂਚਾਗਤ ਸੰਪਾਦਨਾਂ ਤੋਂ ਬਾਅਦ ਅੰਤਮ PDF ਦੀ ਪੂਰਵਦਰਸ਼ਨ ਕਰੋ ਕਿਉਂਕਿ ਇੱਕ ਗੁੰਮ ਜਾਂ ਘੁੰਮਾਇਆ ਗਿਆ ਪੰਨਾ ਬਾਅਦ ਵਿੱਚ ਨੋਟਿਸ ਕਰਨਾ ਔਖਾ ਹੋ ਸਕਦਾ ਹੈ। + PDF ਐਨੋਟੇਸ਼ਨ + ਜਦੋਂ ਦਰਸ਼ਕ ਟਿੱਪਣੀਆਂ ਅਤੇ ਨਿਸ਼ਾਨਾਂ ਨੂੰ ਵੱਖਰੇ ਢੰਗ ਨਾਲ ਦਿਖਾਉਂਦੇ ਹਨ ਤਾਂ ਐਨੋਟੇਸ਼ਨਾਂ ਨੂੰ ਹਟਾਓ ਜਾਂ ਉਹਨਾਂ ਨੂੰ ਸਮਤਲ ਕਰੋ + ਜੋ ਦਿਖਾਈ ਦਿੰਦਾ ਹੈ ਉਸ ਨੂੰ ਕੰਟਰੋਲ ਕਰੋ + ਐਨੋਟੇਸ਼ਨ ਟਿੱਪਣੀਆਂ, ਹਾਈਲਾਈਟਸ, ਡਰਾਇੰਗ, ਫਾਰਮ ਚਿੰਨ੍ਹ, ਜਾਂ ਹੋਰ ਵਾਧੂ PDF ਵਸਤੂਆਂ ਹੋ ਸਕਦੀਆਂ ਹਨ। ਕੁਝ ਦਰਸ਼ਕ ਉਹਨਾਂ ਨੂੰ ਵੱਖਰੇ ਢੰਗ ਨਾਲ ਪ੍ਰਦਰਸ਼ਿਤ ਕਰਦੇ ਹਨ, ਇਸਲਈ ਉਹਨਾਂ ਨੂੰ ਹਟਾਉਣਾ ਜਾਂ ਸਮਤਲ ਕਰਨਾ ਸਾਂਝੇ ਦਸਤਾਵੇਜ਼ਾਂ ਨੂੰ ਵਧੇਰੇ ਅਨੁਮਾਨਯੋਗ ਬਣਾ ਸਕਦਾ ਹੈ। + ਟਿੱਪਣੀਆਂ, ਹਾਈਲਾਈਟਸ, ਜਾਂ ਸਮੀਖਿਆ ਦੇ ਚਿੰਨ੍ਹ ਸਾਂਝੇ ਕਾਪੀ ਵਿੱਚ ਸ਼ਾਮਲ ਨਾ ਕੀਤੇ ਜਾਣ \'ਤੇ ਐਨੋਟੇਸ਼ਨਾਂ ਨੂੰ ਹਟਾਓ। + ਫਲੈਟ ਕਰੋ ਜਦੋਂ ਦਿਖਾਈ ਦੇਣ ਵਾਲੇ ਚਿੰਨ੍ਹ ਪੰਨੇ \'ਤੇ ਰਹਿਣੇ ਚਾਹੀਦੇ ਹਨ ਪਰ ਹੁਣ ਸੰਪਾਦਨਯੋਗ ਐਨੋਟੇਸ਼ਨਾਂ ਵਾਂਗ ਵਿਵਹਾਰ ਨਹੀਂ ਕਰਦੇ। + ਇੱਕ ਅਸਲੀ ਕਾਪੀ ਰੱਖੋ ਕਿਉਂਕਿ ਐਨੋਟੇਸ਼ਨਾਂ ਨੂੰ ਹਟਾਉਣਾ ਜਾਂ ਸਮਤਲ ਕਰਨਾ ਉਲਟਾ ਕਰਨਾ ਮੁਸ਼ਕਲ ਹੋ ਸਕਦਾ ਹੈ। + ਟੈਕਸਟ ਨੂੰ ਪਛਾਣੋ + ਚੋਣਯੋਗ OCR ਇੰਜਣਾਂ ਅਤੇ ਭਾਸ਼ਾਵਾਂ ਵਾਲੇ ਚਿੱਤਰਾਂ ਤੋਂ ਟੈਕਸਟ ਐਕਸਟਰੈਕਟ ਕਰੋ + ਬਿਹਤਰ OCR ਨਤੀਜੇ ਪ੍ਰਾਪਤ ਕਰੋ + OCR ਸ਼ੁੱਧਤਾ ਚਿੱਤਰ ਦੀ ਤਿੱਖਾਪਨ, ਭਾਸ਼ਾ ਡੇਟਾ, ਵਿਪਰੀਤਤਾ, ਅਤੇ ਪੰਨਾ ਸਥਿਤੀ \'ਤੇ ਨਿਰਭਰ ਕਰਦੀ ਹੈ। ਸਭ ਤੋਂ ਵਧੀਆ ਨਤੀਜੇ ਆਮ ਤੌਰ \'ਤੇ ਪਹਿਲਾਂ ਚਿੱਤਰ ਨੂੰ ਤਿਆਰ ਕਰਨ ਤੋਂ ਆਉਂਦੇ ਹਨ: ਰੌਲੇ ਨੂੰ ਦੂਰ ਕਰੋ, ਸਿੱਧਾ ਘੁੰਮਾਓ, ਪੜ੍ਹਨਯੋਗਤਾ ਵਧਾਓ, ਫਿਰ ਟੈਕਸਟ ਨੂੰ ਪਛਾਣੋ। + ਚਿੱਤਰ ਨੂੰ ਟੈਕਸਟ ਖੇਤਰ ਵਿੱਚ ਕੱਟੋ ਅਤੇ ਮਾਨਤਾ ਤੋਂ ਪਹਿਲਾਂ ਇਸਨੂੰ ਸਿੱਧਾ ਘੁੰਮਾਓ। + ਸਹੀ ਭਾਸ਼ਾ ਚੁਣੋ ਅਤੇ ਭਾਸ਼ਾ ਡੇਟਾ ਡਾਊਨਲੋਡ ਕਰੋ ਜੇਕਰ ਐਪ ਇਸਦੀ ਬੇਨਤੀ ਕਰਦਾ ਹੈ। + ਮਾਨਤਾ ਪ੍ਰਾਪਤ ਟੈਕਸਟ ਨੂੰ ਕਾਪੀ ਜਾਂ ਸਾਂਝਾ ਕਰੋ, ਫਿਰ ਹੱਥੀਂ ਨਾਮ, ਨੰਬਰ ਅਤੇ ਵਿਰਾਮ ਚਿੰਨ੍ਹ ਦੀ ਜਾਂਚ ਕਰੋ। + OCR ਭਾਸ਼ਾ ਡੇਟਾ ਚੁਣੋ + ਸਕ੍ਰਿਪਟਾਂ, ਭਾਸ਼ਾਵਾਂ, ਅਤੇ ਡਾਊਨਲੋਡ ਕੀਤੇ OCR ਮਾਡਲਾਂ ਨੂੰ ਮਿਲਾ ਕੇ ਮਾਨਤਾ ਵਿੱਚ ਸੁਧਾਰ ਕਰੋ + OCR ਨੂੰ ਟੈਕਸਟ ਨਾਲ ਮਿਲਾਓ + ਗਲਤ OCR ਭਾਸ਼ਾ ਚੰਗੀਆਂ ਫ਼ੋਟੋਆਂ ਨੂੰ ਮਾੜੀ ਪਛਾਣ ਵਾਂਗ ਬਣਾ ਸਕਦੀ ਹੈ। ਸਕ੍ਰਿਪਟਾਂ, ਵਿਰਾਮ ਚਿੰਨ੍ਹਾਂ, ਨੰਬਰਾਂ ਅਤੇ ਮਿਸ਼ਰਤ ਦਸਤਾਵੇਜ਼ਾਂ ਲਈ ਭਾਸ਼ਾ ਡੇਟਾ ਮਾਅਨੇ ਰੱਖਦਾ ਹੈ, ਇਸਲਈ ਫ਼ੋਨ UI ਦੀ ਭਾਸ਼ਾ ਦੀ ਬਜਾਏ ਚਿੱਤਰ ਵਿੱਚ ਦਿਖਾਈ ਦੇਣ ਵਾਲੀ ਭਾਸ਼ਾ ਚੁਣੋ। + ਚਿੱਤਰ ਵਿੱਚ ਦਿਖਾਈ ਦੇਣ ਵਾਲੀ ਭਾਸ਼ਾ ਜਾਂ ਸਕ੍ਰਿਪਟ ਚੁਣੋ, ਨਾ ਕਿ ਸਿਰਫ਼ ਫ਼ੋਨ ਦੀ ਭਾਸ਼ਾ। + ਔਫਲਾਈਨ ਕੰਮ ਕਰਨ ਜਾਂ ਬੈਚ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਗੁੰਮ ਹੋਏ ਡੇਟਾ ਨੂੰ ਡਾਊਨਲੋਡ ਕਰੋ। + ਮਿਸ਼ਰਤ-ਭਾਸ਼ਾ ਦੇ ਦਸਤਾਵੇਜ਼ਾਂ ਲਈ, ਸਭ ਤੋਂ ਮਹੱਤਵਪੂਰਨ ਭਾਸ਼ਾ ਨੂੰ ਪਹਿਲਾਂ ਚਲਾਓ ਅਤੇ ਹੱਥੀਂ ਨਾਮ ਅਤੇ ਨੰਬਰਾਂ ਦੀ ਜਾਂਚ ਕਰੋ। + ਖੋਜਯੋਗ PDF + OCR ਟੈਕਸਟ ਨੂੰ ਫਾਈਲਾਂ ਵਿੱਚ ਵਾਪਸ ਲਿਖੋ ਤਾਂ ਜੋ ਸਕੈਨ ਕੀਤੇ ਪੰਨਿਆਂ ਨੂੰ ਖੋਜਿਆ ਜਾ ਸਕੇ ਅਤੇ ਕਾਪੀ ਕੀਤਾ ਜਾ ਸਕੇ + ਸਕੈਨ ਨੂੰ ਖੋਜਣਯੋਗ ਦਸਤਾਵੇਜ਼ਾਂ ਵਿੱਚ ਬਦਲੋ + OCR ਟੈਕਸਟ ਨੂੰ ਕਲਿੱਪਬੋਰਡ ਵਿੱਚ ਕਾਪੀ ਕਰਨ ਤੋਂ ਇਲਾਵਾ ਹੋਰ ਵੀ ਬਹੁਤ ਕੁਝ ਕਰ ਸਕਦਾ ਹੈ। ਜਦੋਂ ਤੁਸੀਂ ਕਿਸੇ ਫਾਈਲ ਜਾਂ ਖੋਜਯੋਗ PDF ਵਿੱਚ ਮਾਨਤਾ ਪ੍ਰਾਪਤ ਟੈਕਸਟ ਲਿਖਦੇ ਹੋ, ਤਾਂ ਪੰਨੇ ਦਾ ਚਿੱਤਰ ਦਿਖਾਈ ਦਿੰਦਾ ਹੈ ਜਦੋਂ ਕਿ ਟੈਕਸਟ ਨੂੰ ਖੋਜਣਾ, ਚੁਣਨਾ, ਸੂਚਕਾਂਕ ਜਾਂ ਆਰਕਾਈਵ ਕਰਨਾ ਆਸਾਨ ਹੋ ਜਾਂਦਾ ਹੈ। + ਸਕੈਨ ਕੀਤੇ ਦਸਤਾਵੇਜ਼ਾਂ ਲਈ ਖੋਜਯੋਗ PDF ਆਉਟਪੁੱਟ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜੋ ਅਜੇ ਵੀ ਅਸਲ ਪੰਨਿਆਂ ਵਾਂਗ ਦਿਖਾਈ ਦੇਣੀਆਂ ਚਾਹੀਦੀਆਂ ਹਨ। + ਲਿਖਤ-ਤੋਂ-ਫਾਈਲ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਤੁਹਾਨੂੰ ਸਿਰਫ਼ ਐਕਸਟਰੈਕਟ ਕੀਤੇ ਟੈਕਸਟ ਦੀ ਲੋੜ ਹੋਵੇ ਅਤੇ ਪੰਨੇ ਦੇ ਚਿੱਤਰ ਦੀ ਪਰਵਾਹ ਨਾ ਕਰੋ। + ਮਹੱਤਵਪੂਰਨ ਸੰਖਿਆਵਾਂ, ਨਾਮਾਂ ਅਤੇ ਟੇਬਲਾਂ ਦੀ ਦਸਤੀ ਜਾਂਚ ਕਰੋ ਕਿਉਂਕਿ OCR ਟੈਕਸਟ ਖੋਜਣਯੋਗ ਹੋ ਸਕਦਾ ਹੈ ਪਰ ਫਿਰ ਵੀ ਅਪੂਰਣ ਹੋ ਸਕਦਾ ਹੈ। + QR ਕੋਡ ਸਕੈਨ ਕਰੋ + ਉਪਲਬਧ ਹੋਣ \'ਤੇ ਕੈਮਰਾ ਇਨਪੁਟ ਜਾਂ ਸੁਰੱਖਿਅਤ ਚਿੱਤਰਾਂ ਤੋਂ QR ਸਮੱਗਰੀ ਪੜ੍ਹੋ + ਕੋਡਾਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਪੜ੍ਹੋ + QR ਕੋਡਾਂ ਵਿੱਚ ਲਿੰਕ, Wi-Fi ਡੇਟਾ, ਸੰਪਰਕ, ਟੈਕਸਟ ਜਾਂ ਹੋਰ ਪੇਲੋਡ ਹੋ ਸਕਦੇ ਹਨ। + ਸਕੈਨ QR ਕੋਡ ਖੋਲ੍ਹੋ ਅਤੇ ਕੋਡ ਨੂੰ ਤਿੱਖਾ, ਸਮਤਲ ਅਤੇ ਪੂਰੀ ਤਰ੍ਹਾਂ ਫਰੇਮ ਦੇ ਅੰਦਰ ਰੱਖੋ। + ਲਿੰਕ ਖੋਲ੍ਹਣ ਜਾਂ ਸੰਵੇਦਨਸ਼ੀਲ ਡੇਟਾ ਦੀ ਨਕਲ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਡੀਕੋਡ ਕੀਤੀ ਸਮੱਗਰੀ ਦੀ ਸਮੀਖਿਆ ਕਰੋ। + ਜੇਕਰ ਸਕੈਨਿੰਗ ਅਸਫਲ ਹੋ ਜਾਂਦੀ ਹੈ, ਤਾਂ ਰੋਸ਼ਨੀ ਵਿੱਚ ਸੁਧਾਰ ਕਰੋ, ਕੋਡ ਨੂੰ ਕੱਟੋ, ਜਾਂ ਉੱਚ-ਰੈਜ਼ੋਲੂਸ਼ਨ ਸਰੋਤ ਚਿੱਤਰ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ। + ਬੇਸ 64 ਅਤੇ ਚੈੱਕਸਮ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਚਿੱਤਰਾਂ ਨੂੰ ਟੈਕਸਟ ਦੇ ਤੌਰ \'ਤੇ ਏਨਕੋਡ ਕਰੋ, ਟੈਕਸਟ ਨੂੰ ਚਿੱਤਰਾਂ \'ਤੇ ਵਾਪਸ ਡੀਕੋਡ ਕਰੋ, ਅਤੇ ਫਾਈਲ ਦੀ ਇਕਸਾਰਤਾ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ + ਫਾਈਲਾਂ ਅਤੇ ਟੈਕਸਟ ਵਿਚਕਾਰ ਮੂਵ ਕਰੋ + ਬੇਸ64 ਛੋਟੇ ਚਿੱਤਰ ਪੇਲੋਡਾਂ ਲਈ ਲਾਭਦਾਇਕ ਹੈ, ਜਦੋਂ ਕਿ ਚੈੱਕਸਮ ਇਹ ਪੁਸ਼ਟੀ ਕਰਨ ਵਿੱਚ ਮਦਦ ਕਰਦੇ ਹਨ ਕਿ ਫਾਈਲਾਂ ਨਹੀਂ ਬਦਲੀਆਂ ਹਨ। ਇਹ ਟੂਲ ਤਕਨੀਕੀ ਵਰਕਫਲੋ, ਸਹਾਇਤਾ ਰਿਪੋਰਟਾਂ, ਫਾਈਲ ਟ੍ਰਾਂਸਫਰ, ਅਤੇ ਉਹਨਾਂ ਸਥਾਨਾਂ ਵਿੱਚ ਸਭ ਤੋਂ ਵੱਧ ਮਦਦਗਾਰ ਹੁੰਦੇ ਹਨ ਜਿੱਥੇ ਬਾਈਨਰੀ ਫਾਈਲਾਂ ਨੂੰ ਅਸਥਾਈ ਤੌਰ \'ਤੇ ਟੈਕਸਟ ਬਣਨ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। + ਜਦੋਂ ਇੱਕ ਵਰਕਫਲੋ ਨੂੰ ਬਾਈਨਰੀ ਫਾਈਲ ਦੀ ਬਜਾਏ ਟੈਕਸਟ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ ਤਾਂ ਇੱਕ ਛੋਟੀ ਚਿੱਤਰ ਨੂੰ ਏਨਕੋਡ ਕਰਨ ਲਈ ਬੇਸ64 ਟੂਲਸ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਸਿਰਫ਼ ਭਰੋਸੇਯੋਗ ਸਰੋਤਾਂ ਤੋਂ ਬੇਸ 64 ਨੂੰ ਡੀਕੋਡ ਕਰੋ ਅਤੇ ਸੇਵ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਝਲਕ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ। + ਜਦੋਂ ਤੁਹਾਨੂੰ ਨਿਰਯਾਤ ਕੀਤੀਆਂ ਫ਼ਾਈਲਾਂ ਦੀ ਤੁਲਨਾ ਕਰਨ ਜਾਂ ਅੱਪਲੋਡ/ਡਾਊਨਲੋਡ ਦੀ ਪੁਸ਼ਟੀ ਕਰਨ ਦੀ ਲੋੜ ਹੋਵੇ ਤਾਂ ਚੈੱਕਸਮ ਟੂਲਸ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਪੈਕੇਜ ਜਾਂ ਡਾਟਾ ਸੁਰੱਖਿਅਤ ਕਰੋ + ਫਾਈਲਾਂ ਨੂੰ ਬੰਡਲ ਕਰਨ ਜਾਂ ਟੈਕਸਟ ਡੇਟਾ ਨੂੰ ਬਦਲਣ ਲਈ ZIP ਅਤੇ ਸਾਈਫਰ ਟੂਲਸ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਸੰਬੰਧਿਤ ਫਾਈਲਾਂ ਨੂੰ ਇਕੱਠੇ ਰੱਖੋ + ਪੈਕੇਜਿੰਗ ਟੂਲ ਮਦਦਗਾਰ ਹੁੰਦੇ ਹਨ ਜਦੋਂ ਕਈ ਆਉਟਪੁੱਟਾਂ ਨੂੰ ਇੱਕ ਫਾਈਲ ਦੇ ਰੂਪ ਵਿੱਚ ਯਾਤਰਾ ਕਰਨੀ ਚਾਹੀਦੀ ਹੈ। ਜਦੋਂ ਤੁਹਾਨੂੰ ਇੱਕ ਪੂਰਾ ਨਤੀਜਾ ਸੈੱਟ ਭੇਜਣ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ ਤਾਂ ਉਹ ਤਿਆਰ ਕੀਤੀਆਂ ਤਸਵੀਰਾਂ, PDF, ਲੌਗਸ ਅਤੇ ਮੈਟਾਡੇਟਾ ਨੂੰ ਇਕੱਠੇ ਰੱਖਣ ਲਈ ਵੀ ਵਧੀਆ ਹਨ। + ਜ਼ਿਪ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਤੁਹਾਨੂੰ ਬਹੁਤ ਸਾਰੇ ਨਿਰਯਾਤ ਚਿੱਤਰ ਜਾਂ ਦਸਤਾਵੇਜ਼ ਇਕੱਠੇ ਭੇਜਣ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। + ਸਿਫਰ ਟੂਲ ਦੀ ਵਰਤੋਂ ਉਦੋਂ ਹੀ ਕਰੋ ਜਦੋਂ ਤੁਸੀਂ ਚੁਣੀ ਹੋਈ ਵਿਧੀ ਨੂੰ ਸਮਝਦੇ ਹੋ ਅਤੇ ਲੋੜੀਂਦੀਆਂ ਕੁੰਜੀਆਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਸਟੋਰ ਕਰ ਸਕਦੇ ਹੋ। + ਸਰੋਤ ਫਾਈਲਾਂ ਨੂੰ ਮਿਟਾਉਣ ਤੋਂ ਪਹਿਲਾਂ ਬਣਾਏ ਆਰਕਾਈਵ ਜਾਂ ਪਰਿਵਰਤਿਤ ਟੈਕਸਟ ਦੀ ਜਾਂਚ ਕਰੋ। + ਨਾਵਾਂ ਵਿੱਚ ਚੈੱਕਸਮ + ਚੈਕਸਮ-ਆਧਾਰਿਤ ਫਾਈਲਨਾਮਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਵਿਲੱਖਣਤਾ ਅਤੇ ਇਕਸਾਰਤਾ ਪੜ੍ਹਨਯੋਗਤਾ ਨਾਲੋਂ ਜ਼ਿਆਦਾ ਮਾਇਨੇ ਰੱਖਦੀ ਹੈ + ਸਮੱਗਰੀ ਦੁਆਰਾ ਫਾਈਲਾਂ ਨੂੰ ਨਾਮ ਦਿਓ + ਚੈੱਕਸਮ ਫਾਈਲਨਾਮ ਉਪਯੋਗੀ ਹੁੰਦੇ ਹਨ ਜਦੋਂ ਨਿਰਯਾਤ ਵਿੱਚ ਡੁਪਲੀਕੇਟ ਦਿਖਾਈ ਦੇਣ ਵਾਲੇ ਨਾਮ ਹੋ ਸਕਦੇ ਹਨ ਜਾਂ ਜਦੋਂ ਤੁਹਾਨੂੰ ਇਹ ਸਾਬਤ ਕਰਨ ਦੀ ਜ਼ਰੂਰਤ ਹੁੰਦੀ ਹੈ ਕਿ ਦੋ ਫਾਈਲਾਂ ਇੱਕੋ ਜਿਹੀਆਂ ਹਨ। ਉਹ ਮੈਨੂਅਲ ਬ੍ਰਾਊਜ਼ਿੰਗ ਲਈ ਘੱਟ ਅਨੁਕੂਲ ਹਨ, ਇਸਲਈ ਇਹਨਾਂ ਦੀ ਵਰਤੋਂ ਤਕਨੀਕੀ ਪੁਰਾਲੇਖਾਂ ਅਤੇ ਪੁਸ਼ਟੀਕਰਨ ਵਰਕਫਲੋ ਲਈ ਕਰੋ। + ਜਦੋਂ ਸਮੱਗਰੀ ਦੀ ਪਛਾਣ ਮਨੁੱਖੀ-ਪੜ੍ਹਨ ਯੋਗ ਸਿਰਲੇਖ ਨਾਲੋਂ ਵਧੇਰੇ ਮਹੱਤਵਪੂਰਨ ਹੁੰਦੀ ਹੈ ਤਾਂ ਫਾਈਲ ਨਾਮ ਵਜੋਂ ਚੈੱਕਸਮ ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ। + ਇਸਦੀ ਵਰਤੋਂ ਪੁਰਾਲੇਖਾਂ, ਡੁਪਲੀਕੇਸ਼ਨ, ਦੁਹਰਾਉਣਯੋਗ ਨਿਰਯਾਤ, ਜਾਂ ਉਹਨਾਂ ਫਾਈਲਾਂ ਲਈ ਕਰੋ ਜੋ ਦੁਬਾਰਾ ਅੱਪਲੋਡ ਅਤੇ ਡਾਊਨਲੋਡ ਕੀਤੀਆਂ ਜਾ ਸਕਦੀਆਂ ਹਨ। + ਫੋਲਡਰਾਂ ਜਾਂ ਮੈਟਾਡੇਟਾ ਨਾਲ ਚੈਕਸਮ ਨਾਮਾਂ ਨੂੰ ਜੋੜੋ ਜਦੋਂ ਲੋਕਾਂ ਨੂੰ ਅਜੇ ਵੀ ਇਹ ਸਮਝਣ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ ਕਿ ਫਾਈਲ ਕੀ ਦਰਸਾਉਂਦੀ ਹੈ। + ਚਿੱਤਰਾਂ ਤੋਂ ਰੰਗ ਚੁਣੋ + ਨਮੂਨਾ ਪਿਕਸਲ, ਰੰਗ ਕੋਡ ਕਾਪੀ ਕਰੋ, ਅਤੇ ਪੈਲੇਟ ਜਾਂ ਡਿਜ਼ਾਈਨ ਵਿੱਚ ਰੰਗਾਂ ਦੀ ਮੁੜ ਵਰਤੋਂ ਕਰੋ + ਸਹੀ ਰੰਗ ਦਾ ਨਮੂਨਾ + ਰੰਗ ਚੁਣਨਾ ਇੱਕ ਡਿਜ਼ਾਈਨ ਨਾਲ ਮੇਲ ਕਰਨ, ਬ੍ਰਾਂਡ ਦੇ ਰੰਗਾਂ ਨੂੰ ਕੱਢਣ, ਜਾਂ ਚਿੱਤਰ ਮੁੱਲਾਂ ਦੀ ਜਾਂਚ ਕਰਨ ਲਈ ਉਪਯੋਗੀ ਹੈ। ਜ਼ੂਮ ਕਰਨਾ ਮਹੱਤਵਪੂਰਨ ਹੈ ਕਿਉਂਕਿ ਐਂਟੀਅਲਾਈਜ਼ਿੰਗ, ਸ਼ੈਡੋ ਅਤੇ ਕੰਪਰੈਸ਼ਨ ਗੁਆਂਢੀ ਪਿਕਸਲ ਤੁਹਾਡੇ ਦੁਆਰਾ ਉਮੀਦ ਕੀਤੇ ਰੰਗ ਤੋਂ ਥੋੜ੍ਹਾ ਵੱਖਰਾ ਬਣਾ ਸਕਦੇ ਹਨ। + ਚਿੱਤਰ ਤੋਂ ਰੰਗ ਚੁਣੋ ਖੋਲ੍ਹੋ ਅਤੇ ਤੁਹਾਨੂੰ ਲੋੜੀਂਦੇ ਰੰਗ ਦੇ ਨਾਲ ਇੱਕ ਸਰੋਤ ਚਿੱਤਰ ਚੁਣੋ। + ਛੋਟੇ ਵੇਰਵਿਆਂ ਦਾ ਨਮੂਨਾ ਲੈਣ ਤੋਂ ਪਹਿਲਾਂ ਜ਼ੂਮ ਇਨ ਕਰੋ ਤਾਂ ਕਿ ਨੇੜਲੇ ਪਿਕਸਲ ਨਤੀਜੇ ਨੂੰ ਪ੍ਰਭਾਵਿਤ ਨਾ ਕਰਨ। + ਤੁਹਾਡੀ ਮੰਜ਼ਿਲ ਲਈ ਲੋੜੀਂਦੇ ਫਾਰਮੈਟ ਵਿੱਚ ਰੰਗ ਦੀ ਨਕਲ ਕਰੋ, ਜਿਵੇਂ ਕਿ HEX, RGB, ਜਾਂ HSV। + ਪੈਲੇਟਸ ਬਣਾਓ ਅਤੇ ਕਲਰ ਲਾਇਬ੍ਰੇਰੀ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਪੈਲੇਟਸ ਐਕਸਟਰੈਕਟ ਕਰੋ, ਸੁਰੱਖਿਅਤ ਕੀਤੇ ਰੰਗਾਂ ਨੂੰ ਬ੍ਰਾਊਜ਼ ਕਰੋ, ਅਤੇ ਇਕਸਾਰ ਵਿਜ਼ੂਅਲ ਸਿਸਟਮ ਰੱਖੋ + ਮੁੜ ਵਰਤੋਂ ਯੋਗ ਪੈਲੇਟ ਬਣਾਓ + ਪੈਲੇਟ ਨਿਰਯਾਤ ਗ੍ਰਾਫਿਕਸ, ਵਾਟਰਮਾਰਕਸ, ਅਤੇ ਡਰਾਇੰਗਾਂ ਨੂੰ ਦ੍ਰਿਸ਼ਟੀਗਤ ਤੌਰ \'ਤੇ ਇਕਸਾਰ ਰੱਖਣ ਵਿੱਚ ਮਦਦ ਕਰਦੇ ਹਨ। ਉਹ ਖਾਸ ਤੌਰ \'ਤੇ ਉਦੋਂ ਲਾਭਦਾਇਕ ਹੁੰਦੇ ਹਨ ਜਦੋਂ ਤੁਸੀਂ ਬਾਰ-ਬਾਰ ਸਕ੍ਰੀਨਸ਼ਾਟ ਐਨੋਟੇਟ ਕਰਦੇ ਹੋ, ਬ੍ਰਾਂਡ ਸੰਪਤੀਆਂ ਤਿਆਰ ਕਰਦੇ ਹੋ, ਜਾਂ ਕਈ ਟੂਲਸ ਵਿੱਚ ਇੱਕੋ ਰੰਗ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। + ਜਦੋਂ ਤੁਸੀਂ ਪਹਿਲਾਂ ਹੀ ਮੁੱਲ ਜਾਣਦੇ ਹੋ ਤਾਂ ਚਿੱਤਰ ਤੋਂ ਇੱਕ ਪੈਲੇਟ ਤਿਆਰ ਕਰੋ ਜਾਂ ਹੱਥੀਂ ਰੰਗ ਜੋੜੋ। + ਮਹੱਤਵਪੂਰਨ ਰੰਗਾਂ ਨੂੰ ਨਾਮ ਦਿਓ ਜਾਂ ਵਿਵਸਥਿਤ ਕਰੋ ਤਾਂ ਜੋ ਤੁਸੀਂ ਉਹਨਾਂ ਨੂੰ ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਲੱਭ ਸਕੋ। + ਡਰਾਅ, ਵਾਟਰਮਾਰਕ, ਗਰੇਡੀਐਂਟ, SVG, ਜਾਂ ਬਾਹਰੀ ਡਿਜ਼ਾਈਨ ਟੂਲਸ ਵਿੱਚ ਕਾਪੀ ਕੀਤੇ ਮੁੱਲਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਗਰੇਡੀਐਂਟ ਬਣਾਓ + ਪਿਛੋਕੜ, ਪਲੇਸਹੋਲਡਰ, ਅਤੇ ਵਿਜ਼ੂਅਲ ਪ੍ਰਯੋਗਾਂ ਲਈ ਗਰੇਡੀਐਂਟ ਚਿੱਤਰ ਬਣਾਓ + ਰੰਗ ਪਰਿਵਰਤਨ ਨੂੰ ਕੰਟਰੋਲ ਕਰੋ + ਗਰੇਡੀਐਂਟ ਟੂਲ ਉਦੋਂ ਉਪਯੋਗੀ ਹੁੰਦੇ ਹਨ ਜਦੋਂ ਤੁਹਾਨੂੰ ਮੌਜੂਦਾ ਚਿੱਤਰ ਨੂੰ ਸੰਪਾਦਿਤ ਕਰਨ ਦੀ ਬਜਾਏ ਇੱਕ ਤਿਆਰ ਚਿੱਤਰ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। ਉਹ ਬਾਹਰੀ ਡਿਜ਼ਾਈਨ ਟੂਲਸ ਲਈ ਸਾਫ਼ ਬੈਕਗ੍ਰਾਊਂਡ, ਪਲੇਸਹੋਲਡਰ, ਵਾਲਪੇਪਰ, ਮਾਸਕ ਜਾਂ ਸਧਾਰਨ ਸੰਪਤੀਆਂ ਬਣ ਸਕਦੇ ਹਨ। + ਗਰੇਡੀਐਂਟ ਕਿਸਮ ਚੁਣੋ ਅਤੇ ਪਹਿਲਾਂ ਮਹੱਤਵਪੂਰਨ ਰੰਗ ਸੈੱਟ ਕਰੋ। + ਟੀਚੇ ਦੀ ਵਰਤੋਂ ਦੇ ਕੇਸ ਲਈ ਦਿਸ਼ਾ, ਸਟਾਪ, ਨਿਰਵਿਘਨਤਾ ਅਤੇ ਆਉਟਪੁੱਟ ਆਕਾਰ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ। + ਇੱਕ ਫਾਰਮੈਟ ਵਿੱਚ ਨਿਰਯਾਤ ਕਰੋ ਜੋ ਮੰਜ਼ਿਲ ਨਾਲ ਮੇਲ ਖਾਂਦਾ ਹੈ, ਆਮ ਤੌਰ \'ਤੇ ਸਾਫ਼-ਸੁਥਰੇ ਗ੍ਰਾਫਿਕਸ ਲਈ PNG। + ਰੰਗ ਪਹੁੰਚਯੋਗਤਾ + ਜਦੋਂ UI ਨੂੰ ਪੜ੍ਹਨਾ ਔਖਾ ਹੁੰਦਾ ਹੈ ਤਾਂ ਗਤੀਸ਼ੀਲ ਰੰਗ, ਕੰਟ੍ਰਾਸਟ ਅਤੇ ਰੰਗ-ਅੰਨ੍ਹੇ ਵਿਕਲਪਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਰੰਗਾਂ ਦੀ ਵਰਤੋਂ ਨੂੰ ਆਸਾਨ ਬਣਾਓ + ਰੰਗ ਸੈਟਿੰਗਾਂ ਆਰਾਮ, ਪੜ੍ਹਨਯੋਗਤਾ ਅਤੇ ਤੁਸੀਂ ਕਿੰਨੀ ਜਲਦੀ ਨਿਯੰਤਰਣ ਸਕੈਨ ਕਰ ਸਕਦੇ ਹੋ ਨੂੰ ਪ੍ਰਭਾਵਿਤ ਕਰਦੇ ਹਨ। ਜੇਕਰ ਟੂਲ ਚਿਪਸ, ਸਲਾਈਡਰ, ਜਾਂ ਚੁਣੀਆਂ ਗਈਆਂ ਸਥਿਤੀਆਂ ਨੂੰ ਵੱਖ ਕਰਨਾ ਔਖਾ ਲੱਗਦਾ ਹੈ, ਤਾਂ ਥੀਮ ਅਤੇ ਪਹੁੰਚਯੋਗਤਾ ਵਿਕਲਪ ਸਿਰਫ਼ ਡਾਰਕ ਮੋਡ ਨੂੰ ਬਦਲਣ ਨਾਲੋਂ ਵਧੇਰੇ ਉਪਯੋਗੀ ਹੋ ਸਕਦੇ ਹਨ। + ਜਦੋਂ ਤੁਸੀਂ ਚਾਹੁੰਦੇ ਹੋ ਕਿ ਐਪ ਮੌਜੂਦਾ ਵਾਲਪੇਪਰ ਪੈਲੇਟ ਦਾ ਅਨੁਸਰਣ ਕਰੇ ਤਾਂ ਗਤੀਸ਼ੀਲ ਰੰਗਾਂ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ। + ਜਦੋਂ ਬਟਨ ਅਤੇ ਸਤਹ ਕਾਫ਼ੀ ਵੱਖਰੇ ਨਾ ਹੋਣ ਤਾਂ ਕੰਟ੍ਰਾਸਟ ਵਧਾਓ ਜਾਂ ਸਕੀਮ ਬਦਲੋ। + ਰੰਗ-ਅੰਨ੍ਹੇ ਸਕੀਮ ਵਿਕਲਪਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜੇਕਰ ਕੁਝ ਅਵਸਥਾਵਾਂ ਜਾਂ ਲਹਿਜ਼ੇ ਨੂੰ ਵੱਖ ਕਰਨਾ ਮੁਸ਼ਕਲ ਹੈ। + ਅਲਫ਼ਾ ਪਿਛੋਕੜ + ਪਾਰਦਰਸ਼ੀ PNG, WebP, ਅਤੇ ਸਮਾਨ ਆਉਟਪੁੱਟ ਦੇ ਆਲੇ-ਦੁਆਲੇ ਵਰਤੇ ਜਾਣ ਵਾਲੇ ਪੂਰਵਦਰਸ਼ਨ ਜਾਂ ਭਰਨ ਵਾਲੇ ਰੰਗ ਨੂੰ ਨਿਯੰਤਰਿਤ ਕਰੋ + ਪਾਰਦਰਸ਼ੀ ਝਲਕ ਨੂੰ ਸਮਝੋ + ਅਲਫ਼ਾ ਫਾਰਮੈਟਾਂ ਲਈ ਬੈਕਗ੍ਰਾਉਂਡ ਰੰਗ ਪਾਰਦਰਸ਼ੀ ਚਿੱਤਰਾਂ ਦਾ ਨਿਰੀਖਣ ਕਰਨਾ ਆਸਾਨ ਬਣਾ ਸਕਦਾ ਹੈ, ਪਰ ਇਹ ਜਾਣਨਾ ਮਹੱਤਵਪੂਰਨ ਹੈ ਕਿ ਕੀ ਤੁਸੀਂ ਪ੍ਰੀਵਿਊ/ਫਿਲ ਵਿਵਹਾਰ ਨੂੰ ਬਦਲ ਰਹੇ ਹੋ ਜਾਂ ਅੰਤਿਮ ਨਤੀਜੇ ਨੂੰ ਸਮਤਲ ਕਰ ਰਹੇ ਹੋ। ਇਹ ਸਟਿੱਕਰਾਂ, ਲੋਗੋ ਅਤੇ ਬੈਕਗ੍ਰਾਊਂਡ-ਹਟਾਏ ਚਿੱਤਰਾਂ ਲਈ ਮਹੱਤਵਪੂਰਨ ਹੈ। + ਪਹਿਲਾਂ ਇੱਕ ਅਲਫ਼ਾ-ਸਹਾਇਕ ਫਾਰਮੈਟ ਦੀ ਵਰਤੋਂ ਕਰੋ, ਜਿਵੇਂ ਕਿ PNG ਜਾਂ WebP, ਜਦੋਂ ਪਾਰਦਰਸ਼ੀ ਪਿਕਸਲ ਨਿਰਯਾਤ ਨੂੰ ਬਚਣਾ ਚਾਹੀਦਾ ਹੈ। + ਜਦੋਂ ਪਾਰਦਰਸ਼ੀ ਕਿਨਾਰਿਆਂ ਨੂੰ ਐਪ ਦੀ ਸਤ੍ਹਾ ਦੇ ਵਿਰੁੱਧ ਦੇਖਣਾ ਔਖਾ ਹੁੰਦਾ ਹੈ ਤਾਂ ਬੈਕਗ੍ਰਾਊਂਡ ਰੰਗ ਸੈਟਿੰਗਾਂ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ। + ਇੱਕ ਛੋਟਾ ਟੈਸਟ ਨਿਰਯਾਤ ਕਰੋ ਅਤੇ ਇਸਨੂੰ ਕਿਸੇ ਹੋਰ ਐਪ ਵਿੱਚ ਦੁਬਾਰਾ ਖੋਲ੍ਹੋ ਤਾਂ ਜੋ ਇਹ ਪੁਸ਼ਟੀ ਕੀਤੀ ਜਾ ਸਕੇ ਕਿ ਕੀ ਪਾਰਦਰਸ਼ਤਾ ਜਾਂ ਚੁਣੀ ਹੋਈ ਭਰਾਈ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ ਸੀ। + AI ਮਾਡਲ ਦੀ ਚੋਣ + ਪਹਿਲਾਂ ਸਭ ਤੋਂ ਵੱਡੇ ਨੂੰ ਚੁਣਨ ਦੀ ਬਜਾਏ ਚਿੱਤਰ ਦੀ ਕਿਸਮ ਅਤੇ ਨੁਕਸ ਦੁਆਰਾ ਇੱਕ ਮਾਡਲ ਚੁਣੋ + ਮਾਡਲ ਨੂੰ ਨੁਕਸਾਨ ਨਾਲ ਮੇਲ ਕਰੋ + AI ਟੂਲਸ ਵੱਖ-ਵੱਖ ONNX/ORT ਮਾਡਲਾਂ ਨੂੰ ਡਾਊਨਲੋਡ ਜਾਂ ਆਯਾਤ ਕਰ ਸਕਦੇ ਹਨ, ਅਤੇ ਹਰੇਕ ਮਾਡਲ ਨੂੰ ਕਿਸੇ ਖਾਸ ਕਿਸਮ ਦੀ ਸਮੱਸਿਆ ਲਈ ਸਿਖਲਾਈ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ। ਜੇਪੀਈਜੀ ਬਲੌਕਸ, ਐਨੀਮੇ ਲਾਈਨ ਕਲੀਨਅਪ, ਡੀਨੋਇਜ਼ਿੰਗ, ਬੈਕਗ੍ਰਾਉਂਡ ਹਟਾਉਣ, ਰੰਗੀਕਰਨ, ਜਾਂ ਅਪਸਕੇਲਿੰਗ ਲਈ ਇੱਕ ਮਾਡਲ ਗਲਤ ਚਿੱਤਰ ਕਿਸਮ \'ਤੇ ਵਰਤੇ ਜਾਣ \'ਤੇ ਮਾੜੇ ਨਤੀਜੇ ਪੈਦਾ ਕਰ ਸਕਦੇ ਹਨ। + ਡਾਊਨਲੋਡ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਮਾਡਲ ਦਾ ਵੇਰਵਾ ਪੜ੍ਹੋ; ਉਹ ਮਾਡਲ ਚੁਣੋ ਜੋ ਤੁਹਾਡੀ ਅਸਲ ਸਮੱਸਿਆ ਦਾ ਨਾਮ ਦਿੰਦਾ ਹੈ, ਜਿਵੇਂ ਕਿ ਕੰਪਰੈਸ਼ਨ, ਸ਼ੋਰ, ਹਾਫਟੋਨ, ਬਲਰ, ਜਾਂ ਬੈਕਗ੍ਰਾਊਂਡ ਹਟਾਉਣਾ। + ਇੱਕ ਨਵੀਂ ਚਿੱਤਰ ਕਿਸਮ ਦੀ ਜਾਂਚ ਕਰਦੇ ਸਮੇਂ ਇੱਕ ਛੋਟੇ ਜਾਂ ਵਧੇਰੇ ਖਾਸ ਮਾਡਲ ਨਾਲ ਸ਼ੁਰੂ ਕਰੋ, ਫਿਰ ਭਾਰੀ ਮਾਡਲਾਂ ਨਾਲ ਤੁਲਨਾ ਕਰੋ ਤਾਂ ਹੀ ਜੇਕਰ ਨਤੀਜਾ ਕਾਫ਼ੀ ਚੰਗਾ ਨਾ ਹੋਵੇ। + ਸਿਰਫ਼ ਉਹਨਾਂ ਮਾਡਲਾਂ ਨੂੰ ਰੱਖੋ ਜੋ ਤੁਸੀਂ ਅਸਲ ਵਿੱਚ ਵਰਤਦੇ ਹੋ, ਕਿਉਂਕਿ ਡਾਊਨਲੋਡ ਕੀਤੇ ਅਤੇ ਆਯਾਤ ਕੀਤੇ ਮਾਡਲ ਧਿਆਨ ਦੇਣ ਯੋਗ ਸਟੋਰੇਜ ਸਪੇਸ ਲੈ ਸਕਦੇ ਹਨ। + ਮਾਡਲ ਪਰਿਵਾਰਾਂ ਨੂੰ ਸਮਝੋ + ਮਾਡਲ ਦੇ ਨਾਮ ਅਕਸਰ ਉਹਨਾਂ ਦੇ ਨਿਸ਼ਾਨੇ \'ਤੇ ਸੰਕੇਤ ਦਿੰਦੇ ਹਨ: RealESRGAN-ਸ਼ੈਲੀ ਦੇ ਮਾਡਲ ਅਪਸਕੇਲ, SCUNet ਅਤੇ FBCNN ਮਾਡਲ ਕਲੀਨ ਸ਼ੋਰ ਜਾਂ ਕੰਪਰੈਸ਼ਨ, ਬੈਕਗ੍ਰਾਉਂਡ ਮਾਡਲ ਮਾਸਕ ਬਣਾਉਂਦੇ ਹਨ, ਅਤੇ ਕਲਰਾਈਜ਼ਰ ਗ੍ਰੇਸਕੇਲ ਇਨਪੁਟ ਵਿੱਚ ਰੰਗ ਜੋੜਦੇ ਹਨ। ਆਯਾਤ ਕੀਤੇ ਕਸਟਮ ਮਾਡਲ ਸ਼ਕਤੀਸ਼ਾਲੀ ਹੋ ਸਕਦੇ ਹਨ, ਪਰ ਉਹਨਾਂ ਨੂੰ ਉਮੀਦ ਕੀਤੇ ਇੰਪੁੱਟ/ਆਊਟਪੁੱਟ ਆਕਾਰ ਨਾਲ ਮੇਲ ਖਾਂਦਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ ਅਤੇ ਸਮਰਥਿਤ ONNX ਜਾਂ ORT ਫਾਰਮੈਟ ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਚਾਹੀਦੀ ਹੈ। + ਜਦੋਂ ਤੁਹਾਨੂੰ ਵਧੇਰੇ ਪਿਕਸਲ ਦੀ ਲੋੜ ਹੋਵੇ ਤਾਂ ਅੱਪਸਕੇਲਰ ਦੀ ਵਰਤੋਂ ਕਰੋ, ਜਦੋਂ ਚਿੱਤਰ ਦਾਣੇਦਾਰ ਹੋਵੇ, ਅਤੇ ਆਰਟੀਫੈਕਟ ਰਿਮੂਵਰ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਕੰਪਰੈਸ਼ਨ ਬਲਾਕ ਜਾਂ ਬੈਂਡਿੰਗ ਮੁੱਖ ਮੁੱਦਾ ਹੋਵੇ। + ਬੈਕਗ੍ਰਾਊਂਡ ਮਾਡਲਾਂ ਦੀ ਵਰਤੋਂ ਸਿਰਫ਼ ਵਿਸ਼ੇ ਨੂੰ ਵੱਖ ਕਰਨ ਲਈ ਕਰੋ; ਉਹ ਆਮ ਵਸਤੂ ਨੂੰ ਹਟਾਉਣ ਜਾਂ ਚਿੱਤਰ ਨੂੰ ਵਧਾਉਣ ਦੇ ਸਮਾਨ ਨਹੀਂ ਹਨ। + ਸਿਰਫ਼ ਉਹਨਾਂ ਸਰੋਤਾਂ ਤੋਂ ਕਸਟਮ ਮਾਡਲਾਂ ਨੂੰ ਆਯਾਤ ਕਰੋ ਜਿਨ੍ਹਾਂ \'ਤੇ ਤੁਸੀਂ ਭਰੋਸਾ ਕਰਦੇ ਹੋ ਅਤੇ ਮਹੱਤਵਪੂਰਨ ਫ਼ਾਈਲਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਉਹਨਾਂ ਦੀ ਇੱਕ ਛੋਟੀ ਜਿਹੀ ਤਸਵੀਰ \'ਤੇ ਜਾਂਚ ਕਰੋ। + AI ਪੈਰਾਮੀਟਰ + ਗੁਣਵੱਤਾ, ਗਤੀ ਅਤੇ ਮੈਮੋਰੀ ਨੂੰ ਸੰਤੁਲਿਤ ਕਰਨ ਲਈ ਟੁਕੜੇ ਦੇ ਆਕਾਰ, ਓਵਰਲੈਪ ਅਤੇ ਸਮਾਨਾਂਤਰ ਕਰਮਚਾਰੀਆਂ ਨੂੰ ਟਿਊਨ ਕਰੋ + ਸਥਿਰਤਾ ਦੇ ਨਾਲ ਗਤੀ ਨੂੰ ਸੰਤੁਲਿਤ ਕਰੋ + ਚੰਕ ਦਾ ਆਕਾਰ ਇਹ ਨਿਯੰਤਰਿਤ ਕਰਦਾ ਹੈ ਕਿ ਮਾਡਲ ਦੁਆਰਾ ਸੰਸਾਧਿਤ ਕੀਤੇ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਚਿੱਤਰ ਦੇ ਟੁਕੜੇ ਕਿੰਨੇ ਵੱਡੇ ਹਨ। ਬਾਰਡਰ ਲੁਕਾਉਣ ਲਈ ਓਵਰਲੈਪ ਗੁਆਂਢੀ ਹਿੱਸਿਆਂ ਨੂੰ ਮਿਲਾਉਂਦਾ ਹੈ। ਪੈਰਲਲ ਵਰਕਰ ਪ੍ਰੋਸੈਸਿੰਗ ਨੂੰ ਤੇਜ਼ ਕਰ ਸਕਦੇ ਹਨ, ਪਰ ਹਰੇਕ ਵਰਕਰ ਨੂੰ ਮੈਮੋਰੀ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ, ਇਸਲਈ ਉੱਚ ਮੁੱਲ ਸੀਮਤ RAM ਵਾਲੇ ਫ਼ੋਨਾਂ \'ਤੇ ਵੱਡੀਆਂ ਤਸਵੀਰਾਂ ਨੂੰ ਅਸਥਿਰ ਬਣਾ ਸਕਦੇ ਹਨ। + ਜਦੋਂ ਤੁਹਾਡੀ ਡਿਵਾਈਸ ਮਾਡਲ ਨੂੰ ਭਰੋਸੇਯੋਗ ਢੰਗ ਨਾਲ ਹੈਂਡਲ ਕਰਦੀ ਹੈ ਅਤੇ ਚਿੱਤਰ ਬਹੁਤ ਵੱਡਾ ਨਹੀਂ ਹੁੰਦਾ ਹੈ ਤਾਂ ਨਿਰਵਿਘਨ ਸੰਦਰਭ ਲਈ ਵੱਡੇ ਭਾਗਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਜਦੋਂ ਭਾਗ ਬਾਰਡਰ ਦਿਖਾਈ ਦੇਣ ਤਾਂ ਓਵਰਲੈਪ ਨੂੰ ਵਧਾਓ, ਪਰ ਯਾਦ ਰੱਖੋ ਕਿ ਜ਼ਿਆਦਾ ਓਵਰਲੈਪ ਦਾ ਮਤਲਬ ਹੈ ਜ਼ਿਆਦਾ ਵਾਰ-ਵਾਰ ਕੰਮ ਕਰਨਾ। + ਇੱਕ ਸਥਿਰ ਟੈਸਟ ਦੇ ਬਾਅਦ ਹੀ ਸਮਾਨਾਂਤਰ ਵਰਕਰਾਂ ਨੂੰ ਉਭਾਰੋ; ਜੇਕਰ ਪ੍ਰੋਸੈਸਿੰਗ ਹੌਲੀ ਹੋ ਜਾਂਦੀ ਹੈ, ਡਿਵਾਈਸ ਨੂੰ ਗਰਮ ਕਰਦੀ ਹੈ, ਜਾਂ ਕਰੈਸ਼ ਹੋ ਜਾਂਦੀ ਹੈ, ਤਾਂ ਪਹਿਲਾਂ ਕਰਮਚਾਰੀਆਂ ਨੂੰ ਘਟਾਓ। + ਚੰਕ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਤੋਂ ਬਚੋ + ਚੰਕਿੰਗ ਐਪ ਨੂੰ ਉਹਨਾਂ ਚਿੱਤਰਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਦਿੰਦੀ ਹੈ ਜੋ ਮਾਡਲ ਤੋਂ ਵੱਡੀਆਂ ਹਨ ਜੋ ਇੱਕ ਵਾਰ ਆਰਾਮ ਨਾਲ ਸੰਭਾਲ ਸਕਦੀਆਂ ਹਨ। ਟ੍ਰੇਡਆਫ ਇਹ ਹੈ ਕਿ ਹਰੇਕ ਟਾਈਲ ਦੇ ਆਲੇ-ਦੁਆਲੇ ਦੇ ਸੰਦਰਭ ਘੱਟ ਹੁੰਦੇ ਹਨ, ਇਸਲਈ ਘੱਟ ਓਵਰਲੈਪ ਜਾਂ ਬਹੁਤ-ਛੋਟੇ ਟੁਕੜੇ ਦਿਸਣਯੋਗ ਬਾਰਡਰ, ਟੈਕਸਟਚਰ ਬਦਲਾਅ, ਜਾਂ ਗੁਆਂਢੀ ਖੇਤਰਾਂ ਵਿਚਕਾਰ ਅਸੰਗਤ ਵੇਰਵੇ ਬਣਾ ਸਕਦੇ ਹਨ। + ਓਵਰਲੈਪ ਵਧਾਓ ਜੇਕਰ ਤੁਸੀਂ ਆਇਤਾਕਾਰ ਬਾਰਡਰ, ਵਾਰ-ਵਾਰ ਟੈਕਸਟਚਰ ਬਦਲਾਅ, ਜਾਂ ਪ੍ਰੋਸੈਸ ਕੀਤੇ ਖੇਤਰਾਂ ਦੇ ਵਿਚਕਾਰ ਵੇਰਵੇ ਜੰਪ ਦੇਖਦੇ ਹੋ। + ਜੇ ਆਉਟਪੁੱਟ ਪੈਦਾ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਪ੍ਰਕਿਰਿਆ ਅਸਫਲ ਹੋ ਜਾਂਦੀ ਹੈ, ਖਾਸ ਕਰਕੇ ਵੱਡੇ ਚਿੱਤਰਾਂ ਜਾਂ ਭਾਰੀ ਮਾਡਲਾਂ \'ਤੇ, ਟੁਕੜੇ ਦੇ ਆਕਾਰ ਨੂੰ ਘਟਾਓ। + ਪੂਰੀ ਚਿੱਤਰ ਨੂੰ ਪ੍ਰੋਸੈਸ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਇੱਕ ਛੋਟਾ ਫਸਲ ਟੈਸਟ ਸੁਰੱਖਿਅਤ ਕਰੋ ਤਾਂ ਜੋ ਤੁਸੀਂ ਪੈਰਾਮੀਟਰਾਂ ਦੀ ਤੇਜ਼ੀ ਨਾਲ ਤੁਲਨਾ ਕਰ ਸਕੋ। + AI ਸਥਿਰਤਾ + ਜਦੋਂ AI ਪ੍ਰੋਸੈਸਿੰਗ ਕ੍ਰੈਸ਼ ਜਾਂ ਫ੍ਰੀਜ਼ ਹੋ ਜਾਂਦੀ ਹੈ ਤਾਂ ਹੇਠਲੇ ਹਿੱਸੇ ਦਾ ਆਕਾਰ, ਕਰਮਚਾਰੀ ਅਤੇ ਸਰੋਤ ਰੈਜ਼ੋਲਿਊਸ਼ਨ + ਭਾਰੀ AI ਨੌਕਰੀਆਂ ਤੋਂ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰੋ + ਏਆਈ ਪ੍ਰੋਸੈਸਿੰਗ ਐਪ ਵਿੱਚ ਸਭ ਤੋਂ ਭਾਰੀ ਵਰਕਫਲੋਜ਼ ਵਿੱਚੋਂ ਇੱਕ ਹੈ। ਕ੍ਰੈਸ਼, ਅਸਫਲ ਸੈਸ਼ਨ, ਫ੍ਰੀਜ਼, ਜਾਂ ਅਚਾਨਕ ਐਪ ਰੀਸਟਾਰਟ ਹੋਣ ਦਾ ਆਮ ਤੌਰ \'ਤੇ ਮਤਲਬ ਹੁੰਦਾ ਹੈ ਕਿ ਮਾਡਲ, ਸਰੋਤ ਰੈਜ਼ੋਲਿਊਸ਼ਨ, ਟੁਕੜੇ ਦਾ ਆਕਾਰ, ਜਾਂ ਸਮਾਨਾਂਤਰ ਵਰਕਰਾਂ ਦੀ ਗਿਣਤੀ ਮੌਜੂਦਾ ਡਿਵਾਈਸ ਸਥਿਤੀ ਲਈ ਬਹੁਤ ਜ਼ਿਆਦਾ ਮੰਗ ਹੈ। + ਜੇਕਰ ਐਪ ਕ੍ਰੈਸ਼ ਹੋ ਜਾਂਦੀ ਹੈ ਜਾਂ ਮਾਡਲ ਸੈਸ਼ਨ ਨੂੰ ਖੋਲ੍ਹਣ ਵਿੱਚ ਅਸਫਲ ਹੋ ਜਾਂਦੀ ਹੈ, ਸਮਾਨਾਂਤਰ ਵਰਕਰਾਂ ਅਤੇ ਟੁਕੜੇ ਦਾ ਆਕਾਰ ਘੱਟ ਕਰਦਾ ਹੈ, ਤਾਂ ਉਸੇ ਚਿੱਤਰ ਨੂੰ ਦੁਬਾਰਾ ਅਜ਼ਮਾਓ। + AI ਪ੍ਰੋਸੈਸਿੰਗ ਤੋਂ ਪਹਿਲਾਂ ਬਹੁਤ ਵੱਡੀਆਂ ਤਸਵੀਰਾਂ ਦਾ ਆਕਾਰ ਬਦਲੋ ਜਦੋਂ ਟੀਚੇ ਨੂੰ ਅਸਲ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਦੀ ਲੋੜ ਨਹੀਂ ਹੁੰਦੀ ਹੈ। + ਹੋਰ ਭਾਰੀ ਐਪਾਂ ਨੂੰ ਬੰਦ ਕਰੋ, ਇੱਕ ਛੋਟੇ ਮਾਡਲ ਦੀ ਵਰਤੋਂ ਕਰੋ, ਜਾਂ ਇੱਕ ਵਾਰ ਵਿੱਚ ਘੱਟ ਫਾਈਲਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰੋ ਜਦੋਂ ਮੈਮੋਰੀ ਦਾ ਦਬਾਅ ਵਾਪਸ ਆਉਂਦਾ ਰਹਿੰਦਾ ਹੈ। + ਮਾਡਲ ਅਸਫਲਤਾਵਾਂ ਦਾ ਨਿਦਾਨ ਕਰੋ + ਇੱਕ ਅਸਫਲ AI ਰਨ ਦਾ ਹਮੇਸ਼ਾ ਇਹ ਮਤਲਬ ਨਹੀਂ ਹੁੰਦਾ ਕਿ ਚਿੱਤਰ ਖਰਾਬ ਹੈ। ਮਾਡਲ ਫਾਈਲ ਅਧੂਰੀ, ਅਸਮਰਥਿਤ, ਮੈਮੋਰੀ ਲਈ ਬਹੁਤ ਵੱਡੀ, ਜਾਂ ਓਪਰੇਸ਼ਨ ਨਾਲ ਮੇਲ ਨਹੀਂ ਖਾਂਦੀ ਹੋ ਸਕਦੀ ਹੈ। ਜਦੋਂ ਐਪ ਇੱਕ ਅਸਫਲ ਸੈਸ਼ਨ ਦੀ ਰਿਪੋਰਟ ਕਰਦਾ ਹੈ, ਤਾਂ ਇਸਨੂੰ ਪਹਿਲਾਂ ਮਾਡਲ ਅਤੇ ਪੈਰਾਮੀਟਰਾਂ ਨੂੰ ਸਰਲ ਬਣਾਉਣ ਲਈ ਇੱਕ ਸਿਗਨਲ ਵਜੋਂ ਮੰਨੋ। + ਇੱਕ ਮਾਡਲ ਨੂੰ ਮਿਟਾਓ ਅਤੇ ਦੁਬਾਰਾ ਡਾਊਨਲੋਡ ਕਰੋ ਜੇਕਰ ਇਹ ਡਾਊਨਲੋਡ ਕਰਨ ਤੋਂ ਤੁਰੰਤ ਬਾਅਦ ਅਸਫਲ ਹੋ ਜਾਂਦਾ ਹੈ ਜਾਂ ਇਸ ਤਰ੍ਹਾਂ ਵਿਵਹਾਰ ਕਰਦਾ ਹੈ ਜਿਵੇਂ ਕਿ ਫਾਈਲ ਖਰਾਬ ਹੋ ਗਈ ਹੈ। + ਇੱਕ ਆਯਾਤ ਕੀਤੇ ਕਸਟਮ ਮਾਡਲ ਨੂੰ ਦੋਸ਼ ਦੇਣ ਤੋਂ ਪਹਿਲਾਂ ਇੱਕ ਬਿਲਟ-ਇਨ ਡਾਊਨਲੋਡ ਕਰਨ ਯੋਗ ਮਾਡਲ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ, ਕਿਉਂਕਿ ਆਯਾਤ ਕੀਤੇ ਮਾਡਲਾਂ ਵਿੱਚ ਅਸੰਗਤ ਇਨਪੁਟ ਹੋ ਸਕਦੇ ਹਨ। + ਜੇਕਰ ਤੁਹਾਨੂੰ ਕਿਸੇ ਕਰੈਸ਼ ਜਾਂ ਸੈਸ਼ਨ ਦੀ ਗਲਤੀ ਦੀ ਰਿਪੋਰਟ ਕਰਨ ਦੀ ਲੋੜ ਹੈ ਤਾਂ ਅਸਫਲਤਾ ਨੂੰ ਦੁਬਾਰਾ ਤਿਆਰ ਕਰਨ ਤੋਂ ਬਾਅਦ ਐਪ ਲੌਗਸ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਸ਼ੈਡਰ ਸਟੂਡੀਓ ਵਿੱਚ ਪ੍ਰਯੋਗ ਕਰੋ + ਉੱਨਤ ਚਿੱਤਰ ਪ੍ਰੋਸੈਸਿੰਗ ਅਤੇ ਕਸਟਮ ਦਿੱਖ ਲਈ GPU ਸ਼ੈਡਰ ਪ੍ਰਭਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਸ਼ੇਡਰਾਂ ਨਾਲ ਧਿਆਨ ਨਾਲ ਕੰਮ ਕਰੋ + ਸ਼ੈਡਰ ਸਟੂਡੀਓ ਸ਼ਕਤੀਸ਼ਾਲੀ ਹੈ, ਪਰ ਸ਼ੈਡਰ ਕੋਡ ਅਤੇ ਪੈਰਾਮੀਟਰਾਂ ਨੂੰ ਛੋਟੇ, ਪਰਖਣਯੋਗ ਤਬਦੀਲੀਆਂ ਦੀ ਲੋੜ ਹੈ। ਇਸਨੂੰ ਇੱਕ ਪ੍ਰਯੋਗਾਤਮਕ ਟੂਲ ਵਾਂਗ ਵਰਤੋ: ਇੱਕ ਕਾਰਜਸ਼ੀਲ ਬੇਸਲਾਈਨ ਰੱਖੋ, ਇੱਕ ਸਮੇਂ ਵਿੱਚ ਇੱਕ ਚੀਜ਼ ਬਦਲੋ, ਅਤੇ ਇੱਕ ਪ੍ਰੀਸੈਟ \'ਤੇ ਭਰੋਸਾ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਵੱਖ-ਵੱਖ ਚਿੱਤਰ ਆਕਾਰਾਂ ਨਾਲ ਜਾਂਚ ਕਰੋ। + ਪੈਰਾਮੀਟਰ ਜੋੜਨ ਤੋਂ ਪਹਿਲਾਂ ਕਿਸੇ ਜਾਣੇ-ਪਛਾਣੇ ਕੰਮ ਕਰਨ ਵਾਲੇ ਸ਼ੈਡਰ ਜਾਂ ਇੱਕ ਸਧਾਰਨ ਪ੍ਰਭਾਵ ਤੋਂ ਸ਼ੁਰੂ ਕਰੋ। + ਇੱਕ ਸਮੇਂ ਵਿੱਚ ਇੱਕ ਪੈਰਾਮੀਟਰ ਜਾਂ ਕੋਡ ਬਲਾਕ ਬਦਲੋ ਅਤੇ ਤਰੁੱਟੀਆਂ ਲਈ ਪੂਰਵਦਰਸ਼ਨ ਦੀ ਜਾਂਚ ਕਰੋ। + ਪ੍ਰੀਸੈਟਾਂ ਨੂੰ ਕੁਝ ਵੱਖ-ਵੱਖ ਚਿੱਤਰ ਆਕਾਰਾਂ \'ਤੇ ਟੈਸਟ ਕਰਨ ਤੋਂ ਬਾਅਦ ਹੀ ਨਿਰਯਾਤ ਕਰੋ। + SVG ਜਾਂ ASCII ਕਲਾ ਬਣਾਓ + ਰਚਨਾਤਮਕ ਆਉਟਪੁੱਟ ਲਈ ਵੈਕਟਰ-ਵਰਗੀ ਸੰਪਤੀਆਂ ਜਾਂ ਟੈਕਸਟ-ਅਧਾਰਿਤ ਚਿੱਤਰ ਬਣਾਓ + ਰਚਨਾਤਮਕ ਆਉਟਪੁੱਟ ਕਿਸਮ ਚੁਣੋ + SVG ਅਤੇ ASCII ਟੂਲ ਵੱਖ-ਵੱਖ ਟੀਚਿਆਂ ਦੀ ਸੇਵਾ ਕਰਦੇ ਹਨ: ਸਕੇਲੇਬਲ ਗ੍ਰਾਫਿਕਸ ਜਾਂ ਟੈਕਸਟ-ਸ਼ੈਲੀ ਰੈਂਡਰਿੰਗ। ਦੋਵੇਂ ਰਚਨਾਤਮਕ ਰੂਪਾਂਤਰਨ ਹਨ, ਇਸਲਈ ਅੰਤਮ ਆਕਾਰ \'ਤੇ ਪੂਰਵਦਰਸ਼ਨ ਕਰਨਾ ਇੱਕ ਛੋਟੇ ਥੰਬਨੇਲ ਤੋਂ ਨਤੀਜੇ ਦਾ ਨਿਰਣਾ ਕਰਨ ਨਾਲੋਂ ਵਧੇਰੇ ਮਹੱਤਵਪੂਰਨ ਹੈ। + SVG ਮੇਕਰ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਨਤੀਜਾ ਸਾਫ਼-ਸੁਥਰਾ ਸਕੇਲ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ ਜਾਂ ਡਿਜ਼ਾਈਨ ਵਰਕਫਲੋਜ਼ ਵਿੱਚ ਦੁਬਾਰਾ ਵਰਤਿਆ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ। + ASCII ਕਲਾ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਤੁਸੀਂ ਕਿਸੇ ਚਿੱਤਰ ਦੀ ਇੱਕ ਸ਼ੈਲੀਬੱਧ ਟੈਕਸਟ ਪ੍ਰਤੀਨਿਧਤਾ ਚਾਹੁੰਦੇ ਹੋ। + ਅੰਤਿਮ ਆਕਾਰ \'ਤੇ ਪੂਰਵਦਰਸ਼ਨ ਕਰੋ ਕਿਉਂਕਿ ਛੋਟੇ ਵੇਰਵੇ ਤਿਆਰ ਕੀਤੇ ਆਉਟਪੁੱਟ ਵਿੱਚ ਅਲੋਪ ਹੋ ਸਕਦੇ ਹਨ। + ਸ਼ੋਰ ਟੈਕਸਟ ਤਿਆਰ ਕਰੋ + ਪਿਛੋਕੜ, ਮਾਸਕ, ਓਵਰਲੇਅ, ਜਾਂ ਪ੍ਰਯੋਗਾਂ ਲਈ ਵਿਧੀਗਤ ਟੈਕਸਟ ਬਣਾਓ + ਪ੍ਰਕਿਰਿਆ ਸੰਬੰਧੀ ਆਉਟਪੁੱਟ ਨੂੰ ਟਿਊਨ ਕਰੋ + ਸ਼ੋਰ ਪੈਦਾ ਕਰਨ ਵਾਲੇ ਪੈਰਾਮੀਟਰਾਂ ਤੋਂ ਨਵੇਂ ਚਿੱਤਰ ਬਣਾਉਂਦੇ ਹਨ, ਇਸਲਈ ਛੋਟੀਆਂ ਤਬਦੀਲੀਆਂ ਬਹੁਤ ਵੱਖਰੇ ਨਤੀਜੇ ਪੈਦਾ ਕਰ ਸਕਦੀਆਂ ਹਨ। ਇਹ ਟੈਕਸਟ, ਮਾਸਕ, ਓਵਰਲੇਅ, ਪਲੇਸਹੋਲਡਰ, ਜਾਂ ਵਿਸਤ੍ਰਿਤ ਪੈਟਰਨਾਂ ਦੇ ਨਾਲ ਸੰਕੁਚਨ ਦੀ ਜਾਂਚ ਲਈ ਉਪਯੋਗੀ ਹੈ। + ਵਧੀਆ ਵੇਰਵਿਆਂ ਨੂੰ ਟਿਊਨ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਸ਼ੋਰ ਦੀ ਕਿਸਮ ਅਤੇ ਆਉਟਪੁੱਟ ਆਕਾਰ ਚੁਣੋ। + ਪੈਟਰਨ ਟੀਚੇ ਦੀ ਵਰਤੋਂ ਲਈ ਫਿੱਟ ਹੋਣ ਤੱਕ ਪੈਮਾਨੇ, ਤੀਬਰਤਾ, ​​ਰੰਗ ਜਾਂ ਬੀਜ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ। + ਲਾਭਦਾਇਕ ਨਤੀਜਿਆਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ ਅਤੇ ਪੈਰਾਮੀਟਰਾਂ ਨੂੰ ਲਿਖੋ ਜੇਕਰ ਤੁਹਾਨੂੰ ਬਾਅਦ ਵਿੱਚ ਟੈਕਸਟ ਨੂੰ ਦੁਬਾਰਾ ਬਣਾਉਣ ਦੀ ਲੋੜ ਹੈ। + ਮਾਰਕਅੱਪ ਪ੍ਰੋਜੈਕਟ + ਜਦੋਂ ਐਪ ਨੂੰ ਬੰਦ ਕਰਨ ਤੋਂ ਬਾਅਦ ਐਨੋਟੇਸ਼ਨਾਂ ਨੂੰ ਸੰਪਾਦਨਯੋਗ ਰਹਿਣ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ ਤਾਂ ਪ੍ਰੋਜੈਕਟ ਫਾਈਲਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਪੱਧਰੀ ਸੰਪਾਦਨਾਂ ਨੂੰ ਮੁੜ ਵਰਤੋਂ ਯੋਗ ਰੱਖੋ + ਮਾਰਕਅੱਪ ਪ੍ਰੋਜੈਕਟ ਫਾਈਲਾਂ ਉਦੋਂ ਉਪਯੋਗੀ ਹੁੰਦੀਆਂ ਹਨ ਜਦੋਂ ਇੱਕ ਸਕ੍ਰੀਨਸ਼ੌਟ, ਡਾਇਗ੍ਰਾਮ, ਜਾਂ ਨਿਰਦੇਸ਼ ਚਿੱਤਰ ਨੂੰ ਬਾਅਦ ਵਿੱਚ ਤਬਦੀਲੀਆਂ ਦੀ ਲੋੜ ਹੋ ਸਕਦੀ ਹੈ। ਨਿਰਯਾਤ ਕੀਤੀਆਂ PNG/JPEG ਫਾਈਲਾਂ ਅੰਤਿਮ ਚਿੱਤਰ ਹਨ, ਜਦੋਂ ਕਿ ਪ੍ਰੋਜੈਕਟ ਫਾਈਲਾਂ ਸੰਪਾਦਨਯੋਗ ਪਰਤਾਂ ਅਤੇ ਸੰਪਾਦਨ ਸਥਿਤੀ ਨੂੰ ਭਵਿੱਖ ਦੇ ਸਮਾਯੋਜਨ ਲਈ ਸੁਰੱਖਿਅਤ ਰੱਖਦੀਆਂ ਹਨ। + ਜਦੋਂ ਐਨੋਟੇਸ਼ਨ, ਕਾਲਆਉਟ, ਜਾਂ ਲੇਆਉਟ ਤੱਤ ਸੰਪਾਦਨਯੋਗ ਰਹਿਣੇ ਚਾਹੀਦੇ ਹਨ ਤਾਂ ਮਾਰਕਅੱਪ ਲੇਅਰਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਜੇਕਰ ਤੁਸੀਂ ਹੋਰ ਸੰਸ਼ੋਧਨ ਦੀ ਉਮੀਦ ਕਰਦੇ ਹੋ ਤਾਂ ਅੰਤਿਮ ਚਿੱਤਰ ਨੂੰ ਨਿਰਯਾਤ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਪ੍ਰੋਜੈਕਟ ਫਾਈਲ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ ਜਾਂ ਦੁਬਾਰਾ ਖੋਲ੍ਹੋ। + ਇੱਕ ਸਧਾਰਨ ਚਿੱਤਰ ਨੂੰ ਸਿਰਫ਼ ਉਦੋਂ ਹੀ ਨਿਰਯਾਤ ਕਰੋ ਜਦੋਂ ਤੁਸੀਂ ਇੱਕ ਸਮਤਲ ਅੰਤਿਮ ਸੰਸਕਰਣ ਨੂੰ ਸਾਂਝਾ ਕਰਨ ਲਈ ਤਿਆਰ ਹੋਵੋ। + ਫਾਈਲਾਂ ਨੂੰ ਐਨਕ੍ਰਿਪਟ ਕਰੋ + ਕਿਸੇ ਵੀ ਸਰੋਤ ਕਾਪੀ ਨੂੰ ਮਿਟਾਉਣ ਤੋਂ ਪਹਿਲਾਂ ਫਾਈਲਾਂ ਨੂੰ ਪਾਸਵਰਡ ਨਾਲ ਸੁਰੱਖਿਅਤ ਕਰੋ ਅਤੇ ਡੀਕ੍ਰਿਪਸ਼ਨ ਦੀ ਜਾਂਚ ਕਰੋ + ਏਨਕ੍ਰਿਪਟਡ ਆਉਟਪੁੱਟ ਨੂੰ ਧਿਆਨ ਨਾਲ ਹੈਂਡਲ ਕਰੋ + ਸਾਈਫਰ ਟੂਲ ਪਾਸਵਰਡ-ਅਧਾਰਿਤ ਏਨਕ੍ਰਿਪਸ਼ਨ ਨਾਲ ਫਾਈਲਾਂ ਦੀ ਸੁਰੱਖਿਆ ਕਰ ਸਕਦੇ ਹਨ, ਪਰ ਇਹ ਕੁੰਜੀ ਨੂੰ ਯਾਦ ਰੱਖਣ ਜਾਂ ਬੈਕਅੱਪ ਰੱਖਣ ਲਈ ਬਦਲ ਨਹੀਂ ਹਨ। ਏਨਕ੍ਰਿਪਸ਼ਨ ਟ੍ਰਾਂਸਪੋਰਟ ਅਤੇ ਸਟੋਰੇਜ ਲਈ ਉਪਯੋਗੀ ਹੈ, ਜਦੋਂ ਕਿ ਜ਼ਿਪ ਬਿਹਤਰ ਹੁੰਦਾ ਹੈ ਜਦੋਂ ਮੁੱਖ ਟੀਚਾ ਕਈ ਆਉਟਪੁੱਟਾਂ ਨੂੰ ਬੰਡਲ ਕਰਨਾ ਹੁੰਦਾ ਹੈ। + ਪਹਿਲਾਂ ਇੱਕ ਕਾਪੀ ਨੂੰ ਐਨਕ੍ਰਿਪਟ ਕਰੋ ਅਤੇ ਅਸਲੀ ਨੂੰ ਉਦੋਂ ਤੱਕ ਰੱਖੋ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਇਹ ਜਾਂਚ ਨਹੀਂ ਕਰ ਲੈਂਦੇ ਕਿ ਡੀਕ੍ਰਿਪਸ਼ਨ ਤੁਹਾਡੇ ਦੁਆਰਾ ਸਟੋਰ ਕੀਤੇ ਪਾਸਵਰਡ ਨਾਲ ਕੰਮ ਕਰਦਾ ਹੈ। + ਕੁੰਜੀ ਲਈ ਪਾਸਵਰਡ ਮੈਨੇਜਰ ਜਾਂ ਕਿਸੇ ਹੋਰ ਸੁਰੱਖਿਅਤ ਥਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ; ਐਪ ਤੁਹਾਡੇ ਲਈ ਗੁੰਮ ਹੋਏ ਪਾਸਵਰਡ ਦਾ ਅੰਦਾਜ਼ਾ ਨਹੀਂ ਲਗਾ ਸਕਦੀ। + ਇਨਕ੍ਰਿਪਟਡ ਫਾਈਲਾਂ ਨੂੰ ਸਿਰਫ਼ ਉਹਨਾਂ ਲੋਕਾਂ ਨਾਲ ਸਾਂਝਾ ਕਰੋ ਜਿਨ੍ਹਾਂ ਕੋਲ ਪਾਸਵਰਡ ਵੀ ਹੈ ਅਤੇ ਉਹ ਜਾਣਦੇ ਹਨ ਕਿ ਉਹਨਾਂ ਨੂੰ ਕਿਹੜਾ ਟੂਲ ਜਾਂ ਤਰੀਕਾ ਡੀਕ੍ਰਿਪਟ ਕਰਨਾ ਚਾਹੀਦਾ ਹੈ। + ਸੰਦਾਂ ਦਾ ਪ੍ਰਬੰਧ ਕਰੋ + ਗਰੁੱਪ, ਆਰਡਰ, ਖੋਜ ਅਤੇ ਪਿੰਨ ਟੂਲਸ ਤਾਂ ਕਿ ਮੁੱਖ ਸਕ੍ਰੀਨ ਤੁਹਾਡੇ ਅਸਲ ਵਰਕਫਲੋ ਨਾਲ ਮੇਲ ਖਾਂਦੀ ਹੋਵੇ + ਮੁੱਖ ਸਕ੍ਰੀਨ ਨੂੰ ਤੇਜ਼ ਬਣਾਓ + ਜਦੋਂ ਤੁਸੀਂ ਜਾਣਦੇ ਹੋ ਕਿ ਤੁਸੀਂ ਕਿਹੜੇ ਟੂਲ ਸਭ ਤੋਂ ਵੱਧ ਵਰਤਦੇ ਹੋ ਤਾਂ ਟੂਲ ਵਿਵਸਥਾ ਸੈਟਿੰਗਾਂ ਟਿਊਨਿੰਗ ਦੇ ਯੋਗ ਹਨ। ਗਰੁੱਪਿੰਗ ਖੋਜ ਵਿੱਚ ਮਦਦ ਕਰਦੀ ਹੈ, ਕਸਟਮ ਆਰਡਰ ਮਾਸਪੇਸ਼ੀ ਮੈਮੋਰੀ ਵਿੱਚ ਮਦਦ ਕਰਦਾ ਹੈ, ਅਤੇ ਮਨਪਸੰਦ ਰੋਜ਼ਾਨਾ ਸਾਧਨਾਂ ਨੂੰ ਪਹੁੰਚਯੋਗ ਰੱਖਦੇ ਹਨ ਭਾਵੇਂ ਪੂਰੀ ਸੂਚੀ ਵੱਡੀ ਹੋ ਜਾਂਦੀ ਹੈ। + ਐਪ ਨੂੰ ਸਿੱਖਣ ਵੇਲੇ ਜਾਂ ਜਦੋਂ ਤੁਸੀਂ ਟਾਸਕ ਟਾਈਪ ਦੁਆਰਾ ਸੰਗਠਿਤ ਟੂਲਸ ਨੂੰ ਤਰਜੀਹ ਦਿੰਦੇ ਹੋ ਤਾਂ ਗਰੁੱਪ ਮੋਡ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਕਸਟਮ ਆਰਡਰ \'ਤੇ ਸਵਿਚ ਕਰੋ ਜਦੋਂ ਤੁਸੀਂ ਪਹਿਲਾਂ ਹੀ ਆਪਣੇ ਲਗਾਤਾਰ ਟੂਲ ਜਾਣਦੇ ਹੋ ਅਤੇ ਘੱਟ ਸਕ੍ਰੋਲ ਚਾਹੁੰਦੇ ਹੋ। + ਉਹਨਾਂ ਦੁਰਲੱਭ ਸਾਧਨਾਂ ਲਈ ਖੋਜ ਸੈਟਿੰਗਾਂ ਅਤੇ ਮਨਪਸੰਦ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜੋ ਤੁਹਾਨੂੰ ਨਾਮ ਦੁਆਰਾ ਯਾਦ ਹਨ ਪਰ ਹਰ ਸਮੇਂ ਦਿਖਾਈ ਨਹੀਂ ਦੇਣਾ ਚਾਹੁੰਦੇ। + ਵੱਡੀਆਂ ਫਾਈਲਾਂ ਨੂੰ ਸੰਭਾਲੋ + ਹੌਲੀ ਨਿਰਯਾਤ, ਮੈਮੋਰੀ ਦਬਾਅ, ਅਤੇ ਅਚਾਨਕ ਵੱਡੇ ਆਉਟਪੁੱਟ ਤੋਂ ਬਚੋ + ਨਿਰਯਾਤ ਤੋਂ ਪਹਿਲਾਂ ਕੰਮ ਘਟਾਓ + ਬਹੁਤ ਵੱਡੇ ਚਿੱਤਰ, ਲੰਬੇ ਬੈਚ, ਅਤੇ ਉੱਚ-ਗੁਣਵੱਤਾ ਵਾਲੇ ਫਾਰਮੈਟ ਵਧੇਰੇ ਮੈਮੋਰੀ ਅਤੇ ਸਮਾਂ ਲੈ ਸਕਦੇ ਹਨ। ਸਭ ਤੋਂ ਤੇਜ਼ ਫਿਕਸ ਆਮ ਤੌਰ \'ਤੇ ਸਭ ਤੋਂ ਭਾਰੀ ਓਪਰੇਸ਼ਨ ਤੋਂ ਪਹਿਲਾਂ ਕੰਮ ਦੀ ਮਾਤਰਾ ਨੂੰ ਘਟਾ ਰਿਹਾ ਹੈ, ਉਸੇ ਵੱਡੇ ਆਕਾਰ ਦੇ ਇੰਪੁੱਟ ਨੂੰ ਪੂਰਾ ਕਰਨ ਦੀ ਉਡੀਕ ਨਾ ਕਰਨਾ। + ਭਾਰੀ ਫਿਲਟਰ, OCR, ਜਾਂ PDF ਰੂਪਾਂਤਰਨ ਨੂੰ ਲਾਗੂ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਵੱਡੀਆਂ ਤਸਵੀਰਾਂ ਦਾ ਆਕਾਰ ਬਦਲੋ। + ਸੈਟਿੰਗਾਂ ਦੀ ਪੁਸ਼ਟੀ ਕਰਨ ਲਈ ਪਹਿਲਾਂ ਇੱਕ ਛੋਟੇ ਬੈਚ ਦੀ ਵਰਤੋਂ ਕਰੋ, ਫਿਰ ਉਸੇ ਪ੍ਰੀਸੈਟ ਨਾਲ ਬਾਕੀ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰੋ। + ਜਦੋਂ ਆਉਟਪੁੱਟ ਫਾਈਲ ਉਮੀਦ ਨਾਲੋਂ ਵੱਡੀ ਹੋਵੇ ਤਾਂ ਘੱਟ ਗੁਣਵੱਤਾ ਜਾਂ ਫਾਰਮੈਟ ਬਦਲੋ। + ਕੈਸ਼ ਅਤੇ ਝਲਕ + ਭਾਰੀ ਸੈਸ਼ਨਾਂ ਤੋਂ ਬਾਅਦ ਤਿਆਰ ਕੀਤੇ ਪੂਰਵਦਰਸ਼ਨਾਂ, ਕੈਸ਼ ਕਲੀਨਅੱਪ ਅਤੇ ਸਟੋਰੇਜ ਦੀ ਵਰਤੋਂ ਨੂੰ ਸਮਝੋ + ਸਟੋਰੇਜ ਅਤੇ ਸਪੀਡ ਨੂੰ ਸੰਤੁਲਿਤ ਰੱਖੋ + ਪੂਰਵਦਰਸ਼ਨ ਜਨਰੇਸ਼ਨ ਅਤੇ ਕੈਸ਼ ਵਾਰ-ਵਾਰ ਬ੍ਰਾਊਜ਼ਿੰਗ ਨੂੰ ਤੇਜ਼ ਬਣਾ ਸਕਦੇ ਹਨ, ਪਰ ਭਾਰੀ ਸੰਪਾਦਨ ਸੈਸ਼ਨ ਅਸਥਾਈ ਡੇਟਾ ਨੂੰ ਪਿੱਛੇ ਛੱਡ ਸਕਦੇ ਹਨ। ਕੈਸ਼ ਸੈਟਿੰਗਾਂ ਉਦੋਂ ਮਦਦ ਕਰਦੀਆਂ ਹਨ ਜਦੋਂ ਐਪ ਹੌਲੀ ਮਹਿਸੂਸ ਕਰਦੀ ਹੈ, ਸਟੋਰੇਜ ਤੰਗ ਹੈ, ਜਾਂ ਕਈ ਪ੍ਰਯੋਗਾਂ ਤੋਂ ਬਾਅਦ ਪੂਰਵ-ਝਲਕ ਪੁਰਾਣੇ ਦਿਖਾਈ ਦਿੰਦੇ ਹਨ। + ਕਈ ਚਿੱਤਰਾਂ ਨੂੰ ਬ੍ਰਾਊਜ਼ ਕਰਨ ਵੇਲੇ ਪੂਰਵ-ਝਲਕ ਨੂੰ ਚਾਲੂ ਰੱਖੋ ਅਸਥਾਈ ਸਟੋਰੇਜ ਨੂੰ ਘੱਟ ਤੋਂ ਘੱਟ ਕਰਨ ਨਾਲੋਂ ਜ਼ਿਆਦਾ ਮਹੱਤਵਪੂਰਨ ਹੈ। + ਵੱਡੇ ਬੈਚਾਂ, ਅਸਫਲ ਪ੍ਰਯੋਗਾਂ, ਜਾਂ ਬਹੁਤ ਸਾਰੀਆਂ ਉੱਚ-ਰੈਜ਼ੋਲੂਸ਼ਨ ਫਾਈਲਾਂ ਦੇ ਨਾਲ ਲੰਬੇ ਸੈਸ਼ਨਾਂ ਤੋਂ ਬਾਅਦ ਕੈਸ਼ ਸਾਫ਼ ਕਰੋ। + ਆਟੋਮੈਟਿਕ ਕੈਸ਼ ਕਲੀਅਰਿੰਗ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜੇਕਰ ਤੁਸੀਂ ਲਾਂਚਾਂ ਦੇ ਵਿਚਕਾਰ ਪ੍ਰੀਵਿਊ ਨੂੰ ਗਰਮ ਰੱਖਣ ਨਾਲੋਂ ਸਟੋਰੇਜ ਕਲੀਨਅੱਪ ਨੂੰ ਤਰਜੀਹ ਦਿੰਦੇ ਹੋ। + ਸਕ੍ਰੀਨ ਵਿਵਹਾਰ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ + ਫੋਕਸ ਕੀਤੇ ਕੰਮ ਲਈ ਪੂਰੀ ਸਕਰੀਨ, ਸੁਰੱਖਿਅਤ ਮੋਡ, ਚਮਕ, ਅਤੇ ਸਿਸਟਮ ਬਾਰ ਵਿਕਲਪਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਇੰਟਰਫੇਸ ਨੂੰ ਸਥਿਤੀ ਦੇ ਅਨੁਕੂਲ ਬਣਾਓ + ਜਦੋਂ ਤੁਸੀਂ ਲੈਂਡਸਕੇਪ ਵਿੱਚ ਸੰਪਾਦਿਤ ਕਰਦੇ ਹੋ, ਨਿੱਜੀ ਚਿੱਤਰ ਪੇਸ਼ ਕਰਦੇ ਹੋ, ਜਾਂ ਨਿਰੰਤਰ ਚਮਕ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ ਤਾਂ ਸਕ੍ਰੀਨ ਸੈਟਿੰਗਾਂ ਮਦਦ ਕਰਦੀਆਂ ਹਨ। ਉਹਨਾਂ ਦੀ ਆਮ ਵਰਤੋਂ ਲਈ ਲੋੜ ਨਹੀਂ ਹੁੰਦੀ ਹੈ, ਪਰ ਉਹ ਅਜੀਬ ਸਥਿਤੀਆਂ ਜਿਵੇਂ ਕਿ QR ਨਿਰੀਖਣ, ਨਿੱਜੀ ਝਲਕ, ਜਾਂ ਤੰਗ ਲੈਂਡਸਕੇਪ ਸੰਪਾਦਨ ਨੂੰ ਹੱਲ ਕਰਦੇ ਹਨ। + ਜਦੋਂ ਨੈਵੀਗੇਸ਼ਨ ਕਰੋਮ ਨਾਲੋਂ ਸਪੇਸ ਨੂੰ ਸੰਪਾਦਿਤ ਕਰਨਾ ਵਧੇਰੇ ਮਹੱਤਵਪੂਰਨ ਹੁੰਦਾ ਹੈ ਤਾਂ ਪੂਰੀ ਸਕ੍ਰੀਨ ਜਾਂ ਸਿਸਟਮ ਬਾਰ ਸੈਟਿੰਗਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਸਕਰੀਨਸ਼ਾਟ ਜਾਂ ਐਪ ਪੂਰਵਦਰਸ਼ਨਾਂ ਵਿੱਚ ਨਿੱਜੀ ਚਿੱਤਰ ਦਿਖਾਈ ਨਾ ਦੇਣ \'ਤੇ ਸੁਰੱਖਿਅਤ ਮੋਡ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਉਹਨਾਂ ਸਾਧਨਾਂ ਲਈ ਚਮਕ ਲਾਗੂਕਰਨ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਿੱਥੇ ਹਨੇਰੇ ਚਿੱਤਰਾਂ ਜਾਂ QR ਕੋਡਾਂ ਦੀ ਜਾਂਚ ਕਰਨਾ ਔਖਾ ਹੈ। + ਪਾਰਦਰਸ਼ਤਾ ਬਣਾਈ ਰੱਖੋ + ਪਾਰਦਰਸ਼ੀ ਬੈਕਗ੍ਰਾਊਂਡ ਨੂੰ ਚਿੱਟੇ, ਕਾਲੇ ਜਾਂ ਚਪਟੇ ਹੋਣ ਤੋਂ ਰੋਕੋ + ਅਲਫ਼ਾ-ਜਾਣੂ ਆਉਟਪੁੱਟ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਪਾਰਦਰਸ਼ਤਾ ਉਦੋਂ ਹੀ ਬਚੀ ਰਹਿੰਦੀ ਹੈ ਜਦੋਂ ਚੁਣਿਆ ਟੂਲ ਅਤੇ ਆਉਟਪੁੱਟ ਫਾਰਮੈਟ ਅਲਫ਼ਾ ਦਾ ਸਮਰਥਨ ਕਰਦਾ ਹੈ। ਜੇਕਰ ਇੱਕ ਨਿਰਯਾਤ ਕੀਤਾ ਪਿਛੋਕੜ ਚਿੱਟਾ ਜਾਂ ਕਾਲਾ ਹੋ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਸਮੱਸਿਆ ਆਮ ਤੌਰ \'ਤੇ ਆਉਟਪੁੱਟ ਫਾਰਮੈਟ, ਇੱਕ ਭਰਨ/ਬੈਕਗ੍ਰਾਉਂਡ ਸੈਟਿੰਗ, ਜਾਂ ਇੱਕ ਮੰਜ਼ਿਲ ਐਪ ਹੈ ਜੋ ਚਿੱਤਰ ਨੂੰ ਸਮਤਲ ਕਰਦੀ ਹੈ। + ਬੈਕਗ੍ਰਾਊਂਡ-ਹਟਾਏ ਗਏ ਚਿੱਤਰ, ਸਟਿੱਕਰ, ਲੋਗੋ, ਜਾਂ ਓਵਰਲੇਅ ਨੂੰ ਨਿਰਯਾਤ ਕਰਦੇ ਸਮੇਂ PNG ਜਾਂ WebP ਚੁਣੋ। + ਪਾਰਦਰਸ਼ੀ ਆਉਟਪੁੱਟ ਲਈ JPEG ਤੋਂ ਬਚੋ ਕਿਉਂਕਿ ਇਹ ਅਲਫ਼ਾ ਚੈਨਲ ਨੂੰ ਸਟੋਰ ਨਹੀਂ ਕਰਦਾ ਹੈ। + ਜੇਕਰ ਪੂਰਵਦਰਸ਼ਨ ਇੱਕ ਭਰੀ ਹੋਈ ਬੈਕਗ੍ਰਾਊਂਡ ਦਿਖਾਉਂਦਾ ਹੈ ਤਾਂ ਅਲਫ਼ਾ ਫਾਰਮੈਟਾਂ ਲਈ ਬੈਕਗ੍ਰਾਊਂਡ ਰੰਗ ਨਾਲ ਸੰਬੰਧਿਤ ਸੈਟਿੰਗਾਂ ਦੀ ਜਾਂਚ ਕਰੋ। + ਸ਼ੇਅਰਿੰਗ ਅਤੇ ਆਯਾਤ ਸਮੱਸਿਆਵਾਂ ਨੂੰ ਠੀਕ ਕਰੋ + ਜਦੋਂ ਫਾਈਲਾਂ ਸਹੀ ਢੰਗ ਨਾਲ ਦਿਖਾਈ ਨਹੀਂ ਦਿੰਦੀਆਂ ਜਾਂ ਖੁੱਲ੍ਹਦੀਆਂ ਨਹੀਂ ਹਨ ਤਾਂ ਚੋਣਕਾਰ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸਮਰਥਿਤ ਫਾਰਮੈਟਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਐਪ ਵਿੱਚ ਫਾਈਲ ਪ੍ਰਾਪਤ ਕਰੋ + ਆਯਾਤ ਸਮੱਸਿਆਵਾਂ ਅਕਸਰ ਪ੍ਰਦਾਤਾ ਅਨੁਮਤੀਆਂ, ਅਸਮਰਥਿਤ ਫਾਰਮੈਟਾਂ, ਜਾਂ ਚੋਣਕਾਰ ਵਿਹਾਰ ਕਾਰਨ ਹੁੰਦੀਆਂ ਹਨ। ਕਲਾਉਡ ਐਪਸ ਅਤੇ ਮੈਸੇਂਜਰਾਂ ਤੋਂ ਸਾਂਝੀਆਂ ਕੀਤੀਆਂ ਫਾਈਲਾਂ ਅਸਥਾਈ ਹੋ ਸਕਦੀਆਂ ਹਨ, ਇਸਲਈ ਲੰਬੇ ਸੰਪਾਦਨਾਂ ਲਈ ਇੱਕ ਨਿਰੰਤਰ ਸਰੋਤ ਚੁਣਨਾ ਵਧੇਰੇ ਭਰੋਸੇਯੋਗ ਹੋ ਸਕਦਾ ਹੈ। + ਹਾਲੀਆ-ਫਾਇਲਾਂ ਦੇ ਸ਼ਾਰਟਕੱਟ ਦੀ ਬਜਾਏ ਸਿਸਟਮ ਪਿਕਰ ਰਾਹੀਂ ਫਾਈਲ ਖੋਲ੍ਹਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ। + ਜੇਕਰ ਇੱਕ ਫਾਰਮੈਟ ਇੱਕ ਟੂਲ ਦੁਆਰਾ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ, ਤਾਂ ਇਸਨੂੰ ਪਹਿਲਾਂ ਬਦਲੋ ਜਾਂ ਇੱਕ ਹੋਰ ਖਾਸ ਟੂਲ ਖੋਲ੍ਹੋ। + ਜੇਕਰ ਸ਼ੇਅਰ ਫਲੋਅ ਜਾਂ ਡਾਇਰੈਕਟ ਟੂਲ ਓਪਨਿੰਗ ਉਮੀਦ ਨਾਲੋਂ ਵੱਖਰੇ ਵਿਵਹਾਰ ਕਰਦੇ ਹਨ ਤਾਂ ਚਿੱਤਰ ਚੋਣਕਾਰ ਅਤੇ ਲਾਂਚਰ ਸੈਟਿੰਗਾਂ ਦੀ ਸਮੀਖਿਆ ਕਰੋ। + ਬਾਹਰ ਜਾਣ ਦੀ ਪੁਸ਼ਟੀ + ਅਸੁਰੱਖਿਅਤ ਸੰਪਾਦਨਾਂ ਨੂੰ ਗੁਆਉਣ ਤੋਂ ਬਚੋ ਜਦੋਂ ਇਸ਼ਾਰੇ, ਬੈਕ ਪ੍ਰੈਸ, ਜਾਂ ਟੂਲ ਸਵਿੱਚ ਅਚਾਨਕ ਵਾਪਰਦੇ ਹਨ + ਅਧੂਰੇ ਕੰਮ ਦੀ ਰੱਖਿਆ ਕਰੋ + ਜਦੋਂ ਤੁਸੀਂ ਸੰਕੇਤ ਨੈਵੀਗੇਸ਼ਨ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹੋ, ਵੱਡੀਆਂ ਫ਼ਾਈਲਾਂ ਨੂੰ ਸੰਪਾਦਿਤ ਕਰਦੇ ਹੋ, ਜਾਂ ਅਕਸਰ ਐਪਾਂ ਵਿਚਕਾਰ ਸਵਿਚ ਕਰਦੇ ਹੋ ਤਾਂ ਟੂਲ ਐਗਜ਼ਿਟ ਪੁਸ਼ਟੀਕਰਨ ਮਦਦਗਾਰ ਹੁੰਦਾ ਹੈ। ਇਹ ਇੱਕ ਟੂਲ ਨੂੰ ਛੱਡਣ ਤੋਂ ਪਹਿਲਾਂ ਇੱਕ ਛੋਟਾ ਜਿਹਾ ਵਿਰਾਮ ਜੋੜਦਾ ਹੈ ਤਾਂ ਜੋ ਅਧੂਰੀਆਂ ਸੈਟਿੰਗਾਂ ਅਤੇ ਪੂਰਵਦਰਸ਼ਨ ਅਚਾਨਕ ਗੁਆਚ ਨਾ ਜਾਣ। + ਪੁਸ਼ਟੀਕਰਨ ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ ਜੇਕਰ ਤੁਹਾਡੇ ਦੁਆਰਾ ਨਿਰਯਾਤ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਅਚਾਨਕ ਵਾਪਸ ਦੇ ਇਸ਼ਾਰਿਆਂ ਨੇ ਕਦੇ ਇੱਕ ਟੂਲ ਬੰਦ ਕਰ ਦਿੱਤਾ ਹੈ। + ਇਸ ਨੂੰ ਅਸਮਰੱਥ ਰੱਖੋ ਜੇਕਰ ਤੁਸੀਂ ਜਿਆਦਾਤਰ ਇੱਕ-ਕਦਮ ਵਿੱਚ ਤੇਜ਼ ਰੂਪਾਂਤਰਨ ਕਰਦੇ ਹੋ ਅਤੇ ਤੇਜ਼ ਨੈਵੀਗੇਸ਼ਨ ਨੂੰ ਤਰਜੀਹ ਦਿੰਦੇ ਹੋ। + ਲੰਬੇ ਵਰਕਫਲੋ ਲਈ ਪ੍ਰੀਸੈਟਸ ਦੇ ਨਾਲ ਇਸਦੀ ਵਰਤੋਂ ਕਰੋ ਜਿੱਥੇ ਸੈਟਿੰਗਾਂ ਨੂੰ ਦੁਬਾਰਾ ਬਣਾਉਣਾ ਤੰਗ ਕਰਨ ਵਾਲਾ ਹੋਵੇਗਾ। + ਸਮੱਸਿਆਵਾਂ ਦੀ ਰਿਪੋਰਟ ਕਰਨ ਵੇਲੇ ਲੌਗਸ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਕੋਈ ਮੁੱਦਾ ਖੋਲ੍ਹਣ ਜਾਂ ਡਿਵੈਲਪਰ ਨਾਲ ਸੰਪਰਕ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਉਪਯੋਗੀ ਵੇਰਵੇ ਇਕੱਠੇ ਕਰੋ + ਸੰਦਰਭ ਦੇ ਨਾਲ ਮੁੱਦਿਆਂ ਦੀ ਰਿਪੋਰਟ ਕਰੋ + ਕਦਮ, ਐਪ ਸੰਸਕਰਣ, ਇਨਪੁਟ ਕਿਸਮ, ਅਤੇ ਲੌਗਸ ਦੇ ਨਾਲ ਇੱਕ ਸਪਸ਼ਟ ਰਿਪੋਰਟ ਦਾ ਨਿਦਾਨ ਕਰਨਾ ਬਹੁਤ ਸੌਖਾ ਹੈ। ਲੌਗਸ ਸਮੱਸਿਆ ਨੂੰ ਦੁਬਾਰਾ ਬਣਾਉਣ ਤੋਂ ਬਾਅਦ ਸਭ ਤੋਂ ਲਾਭਦਾਇਕ ਹੁੰਦੇ ਹਨ ਕਿਉਂਕਿ ਉਹ ਆਲੇ ਦੁਆਲੇ ਦੇ ਸੰਦਰਭ ਨੂੰ ਤਾਜ਼ਾ ਰੱਖਦੇ ਹਨ। + ਟੂਲ ਦਾ ਨਾਮ, ਸਹੀ ਕਾਰਵਾਈ, ਅਤੇ ਕੀ ਇਹ ਇੱਕ ਫਾਈਲ ਜਾਂ ਹਰੇਕ ਫਾਈਲ ਨਾਲ ਵਾਪਰਦਾ ਹੈ ਨੋਟ ਕਰੋ। + ਮੁੱਦੇ ਨੂੰ ਦੁਬਾਰਾ ਤਿਆਰ ਕਰਨ ਤੋਂ ਬਾਅਦ ਐਪ ਲੌਗ ਖੋਲ੍ਹੋ ਅਤੇ ਜਦੋਂ ਸੰਭਵ ਹੋਵੇ ਤਾਂ ਸੰਬੰਧਿਤ ਲੌਗ ਆਉਟਪੁੱਟ ਨੂੰ ਸਾਂਝਾ ਕਰੋ। + ਸਕ੍ਰੀਨਸ਼ਾਟ ਜਾਂ ਨਮੂਨਾ ਫਾਈਲਾਂ ਨੂੰ ਸਿਰਫ਼ ਉਦੋਂ ਹੀ ਨੱਥੀ ਕਰੋ ਜਦੋਂ ਉਹਨਾਂ ਵਿੱਚ ਨਿੱਜੀ ਜਾਣਕਾਰੀ ਨਾ ਹੋਵੇ। + ਬੈਕਗ੍ਰਾਊਂਡ ਪ੍ਰੋਸੈਸਿੰਗ ਰੋਕ ਦਿੱਤੀ ਗਈ ਸੀ + ਐਂਡਰਾਇਡ ਨੇ ਬੈਕਗ੍ਰਾਉਂਡ ਪ੍ਰੋਸੈਸਿੰਗ ਨੋਟੀਫਿਕੇਸ਼ਨ ਨੂੰ ਸਮੇਂ ਸਿਰ ਸ਼ੁਰੂ ਨਹੀਂ ਹੋਣ ਦਿੱਤਾ। ਇਹ ਇੱਕ ਸਿਸਟਮ ਸਮੇਂ ਦੀ ਸੀਮਾ ਹੈ ਜਿਸ ਨੂੰ ਭਰੋਸੇਯੋਗ ਢੰਗ ਨਾਲ ਹੱਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ। ਐਪ ਨੂੰ ਰੀਸਟਾਰਟ ਕਰੋ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ; ਐਪ ਨੂੰ ਖੁੱਲ੍ਹਾ ਰੱਖਣਾ ਅਤੇ ਸੂਚਨਾਵਾਂ ਜਾਂ ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਕੰਮ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਦੇਣ ਨਾਲ ਮਦਦ ਮਿਲ ਸਕਦੀ ਹੈ। + ਫਾਈਲ ਚੁਣਨਾ ਛੱਡੋ + ਜਦੋਂ ਇਨਪੁਟ ਆਮ ਤੌਰ \'ਤੇ ਸ਼ੇਅਰ, ਕਲਿੱਪਬੋਰਡ, ਜਾਂ ਪਿਛਲੀ ਸਕ੍ਰੀਨ ਤੋਂ ਆਉਂਦਾ ਹੈ ਤਾਂ ਟੂਲ ਤੇਜ਼ੀ ਨਾਲ ਖੋਲ੍ਹੋ + ਦੁਹਰਾਉਣ ਵਾਲੇ ਟੂਲ ਨੂੰ ਤੇਜ਼ੀ ਨਾਲ ਲਾਂਚ ਕਰੋ + ਫਾਈਲ ਚੁਣਨਾ ਛੱਡੋ ਸਮਰਥਿਤ ਟੂਲਸ ਦੇ ਪਹਿਲੇ ਪੜਾਅ ਨੂੰ ਬਦਲਦਾ ਹੈ। ਇਹ ਉਦੋਂ ਲਾਭਦਾਇਕ ਹੁੰਦਾ ਹੈ ਜਦੋਂ ਤੁਸੀਂ ਆਮ ਤੌਰ \'ਤੇ ਪਹਿਲਾਂ ਤੋਂ ਚੁਣੇ ਹੋਏ ਚਿੱਤਰ ਦੇ ਨਾਲ ਜਾਂ ਐਂਡਰੌਇਡ ਸ਼ੇਅਰ ਐਕਸ਼ਨ ਤੋਂ ਇੱਕ ਟੂਲ ਦਾਖਲ ਕਰਦੇ ਹੋ, ਪਰ ਇਹ ਉਲਝਣ ਵਾਲਾ ਮਹਿਸੂਸ ਕਰ ਸਕਦਾ ਹੈ ਜੇਕਰ ਤੁਸੀਂ ਉਮੀਦ ਕਰਦੇ ਹੋ ਕਿ ਹਰੇਕ ਟੂਲ ਤੁਰੰਤ ਇੱਕ ਫਾਈਲ ਦੀ ਮੰਗ ਕਰੇਗਾ। + ਇਸਨੂੰ ਸਿਰਫ਼ ਉਦੋਂ ਹੀ ਚਾਲੂ ਕਰੋ ਜਦੋਂ ਤੁਹਾਡਾ ਆਮ ਪ੍ਰਵਾਹ ਪਹਿਲਾਂ ਹੀ ਟੂਲ ਦੇ ਖੁੱਲ੍ਹਣ ਤੋਂ ਪਹਿਲਾਂ ਸਰੋਤ ਚਿੱਤਰ ਪ੍ਰਦਾਨ ਕਰਦਾ ਹੈ। + ਐਪ ਨੂੰ ਸਿੱਖਣ ਵੇਲੇ ਇਸਨੂੰ ਅਸਮਰੱਥ ਰੱਖੋ, ਕਿਉਂਕਿ ਸਪਸ਼ਟ ਚੋਣ ਹਰ ਟੂਲ ਦੇ ਪ੍ਰਵਾਹ ਨੂੰ ਸਮਝਣ ਵਿੱਚ ਆਸਾਨ ਬਣਾਉਂਦੀ ਹੈ। + ਜੇਕਰ ਕੋਈ ਟੂਲ ਬਿਨਾਂ ਕਿਸੇ ਸਪੱਸ਼ਟ ਇਨਪੁਟ ਦੇ ਖੁੱਲ੍ਹਦਾ ਹੈ, ਤਾਂ ਇਸ ਸੈਟਿੰਗ ਨੂੰ ਅਸਮਰੱਥ ਕਰੋ ਜਾਂ ਸ਼ੇਅਰ/ਓਪਨ ਵਿਦ ਤੋਂ ਸ਼ੁਰੂ ਕਰੋ ਤਾਂ ਕਿ ਐਂਡਰੌਇਡ ਪਹਿਲਾਂ ਫਾਈਲ ਪਾਸ ਕਰੇ। + ਪਹਿਲਾਂ ਸੰਪਾਦਕ ਖੋਲ੍ਹੋ + ਪੂਰਵਦਰਸ਼ਨ ਨੂੰ ਛੱਡੋ ਜਦੋਂ ਇੱਕ ਸਾਂਝਾ ਚਿੱਤਰ ਸਿੱਧਾ ਸੰਪਾਦਨ ਵਿੱਚ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ + ਪਹਿਲੇ ਸਟਾਪ ਵਜੋਂ ਪੂਰਵਦਰਸ਼ਨ ਜਾਂ ਸੰਪਾਦਨ ਚੁਣੋ + ਪੂਰਵਦਰਸ਼ਨ ਦੀ ਬਜਾਏ ਸੰਪਾਦਨ ਖੋਲ੍ਹੋ ਉਹਨਾਂ ਉਪਭੋਗਤਾਵਾਂ ਲਈ ਹੈ ਜੋ ਪਹਿਲਾਂ ਹੀ ਜਾਣਦੇ ਹਨ ਕਿ ਉਹ ਚਿੱਤਰ ਨੂੰ ਸੋਧਣਾ ਚਾਹੁੰਦੇ ਹਨ। ਜਦੋਂ ਤੁਸੀਂ ਚੈਟ ਜਾਂ ਕਲਾਉਡ ਸਟੋਰੇਜ ਤੋਂ ਫਾਈਲਾਂ ਦੀ ਜਾਂਚ ਕਰਦੇ ਹੋ ਤਾਂ ਪੂਰਵਦਰਸ਼ਨ ਸੁਰੱਖਿਅਤ ਹੁੰਦਾ ਹੈ; ਸਕ੍ਰੀਨਸ਼ੌਟਸ, ਤੇਜ਼ ਫਸਲਾਂ, ਐਨੋਟੇਸ਼ਨਾਂ, ਅਤੇ ਇੱਕ ਵਾਰੀ ਸੁਧਾਰਾਂ ਲਈ ਸਿੱਧਾ ਸੰਪਾਦਨ ਤੇਜ਼ ਹੈ। + ਜੇਕਰ ਜ਼ਿਆਦਾਤਰ ਸਾਂਝੀਆਂ ਕੀਤੀਆਂ ਤਸਵੀਰਾਂ ਨੂੰ ਤੁਰੰਤ ਕੱਟਣ, ਮਾਰਕਅੱਪ, ਫਿਲਟਰਾਂ, ਜਾਂ ਨਿਰਯਾਤ ਤਬਦੀਲੀਆਂ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ ਤਾਂ ਸਿੱਧੇ ਸੰਪਾਦਨ ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ। + ਜਦੋਂ ਤੁਸੀਂ ਫੈਸਲਾ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਅਕਸਰ ਮੈਟਾਡੇਟਾ, ਮਾਪ, ਪਾਰਦਰਸ਼ਤਾ, ਜਾਂ ਚਿੱਤਰ ਗੁਣਵੱਤਾ ਦੀ ਜਾਂਚ ਕਰਦੇ ਹੋ ਤਾਂ ਪਹਿਲਾਂ ਝਲਕ ਛੱਡੋ। + ਇਸ ਨੂੰ ਮਨਪਸੰਦ ਦੇ ਨਾਲ ਵਰਤੋ ਤਾਂ ਜੋ ਸ਼ੇਅਰ ਕੀਤੀਆਂ ਤਸਵੀਰਾਂ ਤੁਹਾਡੇ ਦੁਆਰਾ ਅਸਲ ਵਿੱਚ ਵਰਤੇ ਜਾਣ ਵਾਲੇ ਟੂਲਸ ਦੇ ਨੇੜੇ ਹੋਣ। + ਪ੍ਰੀਸੈਟ ਟੈਕਸਟ ਐਂਟਰੀ + ਨਿਯੰਤਰਣਾਂ ਨੂੰ ਵਾਰ-ਵਾਰ ਐਡਜਸਟ ਕਰਨ ਦੀ ਬਜਾਏ ਸਹੀ ਪ੍ਰੀਸੈਟ ਮੁੱਲ ਦਾਖਲ ਕਰੋ + ਸਲਾਈਡਰ ਹੌਲੀ ਹੋਣ \'ਤੇ ਸਟੀਕ ਮੁੱਲਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਪ੍ਰੀਸੈਟ ਟੈਕਸਟ ਐਂਟਰੀ ਮਦਦ ਕਰਦੀ ਹੈ ਜਦੋਂ ਤੁਹਾਨੂੰ ਦੁਹਰਾਉਣ ਵਾਲੇ ਕੰਮ ਲਈ ਸਹੀ ਸੰਖਿਆਵਾਂ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ: ਸਮਾਜਿਕ ਚਿੱਤਰ ਆਕਾਰ, ਦਸਤਾਵੇਜ਼ ਹਾਸ਼ੀਏ, ਸੰਕੁਚਨ ਟੀਚੇ, ਲਾਈਨ ਚੌੜਾਈ, ਜਾਂ ਹੋਰ ਮੁੱਲ ਜੋ ਇਸ਼ਾਰਿਆਂ ਨਾਲ ਪਹੁੰਚਣ ਲਈ ਤੰਗ ਕਰਦੇ ਹਨ। ਇਹ ਛੋਟੀਆਂ ਸਕ੍ਰੀਨਾਂ \'ਤੇ ਵੀ ਲਾਭਦਾਇਕ ਹੈ ਜਿੱਥੇ ਵਧੀਆ ਸਲਾਈਡਰ ਅੰਦੋਲਨ ਔਖਾ ਹੈ. + ਟੈਕਸਟ ਐਂਟਰੀ ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ ਜੇਕਰ ਤੁਸੀਂ ਅਕਸਰ ਸਹੀ ਆਕਾਰ, ਪ੍ਰਤੀਸ਼ਤ, ਗੁਣਵੱਤਾ ਮੁੱਲ, ਜਾਂ ਟੂਲ ਪੈਰਾਮੀਟਰਾਂ ਦੀ ਮੁੜ ਵਰਤੋਂ ਕਰਦੇ ਹੋ। + ਮੁੱਲ ਨੂੰ ਇੱਕ ਵਾਰ ਟਾਈਪ ਕਰਨ ਤੋਂ ਬਾਅਦ ਇੱਕ ਪ੍ਰੀ-ਸੈੱਟ ਦੇ ਰੂਪ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕਰੋ, ਫਿਰ ਉਸੇ ਸੈੱਟਅੱਪ ਨੂੰ ਦੁਬਾਰਾ ਬਣਾਉਣ ਦੀ ਬਜਾਏ ਇਸਦੀ ਮੁੜ ਵਰਤੋਂ ਕਰੋ। + ਸਖ਼ਤ ਆਉਟਪੁੱਟ ਲੋੜਾਂ ਲਈ ਮੋਟੇ ਵਿਜ਼ੂਅਲ ਟਿਊਨਿੰਗ ਅਤੇ ਟਾਈਪ ਕੀਤੇ ਮੁੱਲਾਂ ਲਈ ਸੰਕੇਤ ਨਿਯੰਤਰਣ ਰੱਖੋ। + ਅਸਲੀ ਦੇ ਨੇੜੇ ਸੰਭਾਲੋ + ਜਦੋਂ ਫੋਲਡਰ ਸੰਦਰਭ ਮਹੱਤਵ ਰੱਖਦਾ ਹੈ ਤਾਂ ਸ੍ਰੋਤ ਚਿੱਤਰਾਂ ਦੇ ਨਾਲ ਤਿਆਰ ਕੀਤੀਆਂ ਫਾਈਲਾਂ ਨੂੰ ਰੱਖੋ + ਆਪਣੇ ਸਰੋਤਾਂ ਨਾਲ ਆਉਟਪੁੱਟ ਰੱਖੋ + ਸੇਵ ਟੂ ਓਰੀਜਨਲ ਫੋਲਡਰ ਕੈਮਰਾ ਫੋਲਡਰਾਂ, ਪ੍ਰੋਜੈਕਟ ਫੋਲਡਰਾਂ, ਡੌਕੂਮੈਂਟ ਸਕੈਨਾਂ, ਅਤੇ ਕਲਾਇੰਟ ਬੈਚਾਂ ਲਈ ਸੌਖਾ ਹੈ ਜਿੱਥੇ ਸੰਪਾਦਿਤ ਕਾਪੀ ਸਰੋਤ ਦੇ ਅੱਗੇ ਰਹਿਣੀ ਚਾਹੀਦੀ ਹੈ। ਇਹ ਇੱਕ ਸਮਰਪਿਤ ਆਉਟਪੁੱਟ ਫੋਲਡਰ ਨਾਲੋਂ ਘੱਟ ਸੁਥਰਾ ਹੋ ਸਕਦਾ ਹੈ, ਇਸਲਈ ਇਸਨੂੰ ਸਪਸ਼ਟ ਫਾਈਲ ਨਾਮ ਪਿਛੇਤਰ ਜਾਂ ਪੈਟਰਨਾਂ ਨਾਲ ਜੋੜੋ। + ਇਸ ਨੂੰ ਉਦੋਂ ਚਾਲੂ ਕਰੋ ਜਦੋਂ ਹਰੇਕ ਸਰੋਤ ਫੋਲਡਰ ਪਹਿਲਾਂ ਹੀ ਅਰਥਪੂਰਨ ਹੋਵੇ ਅਤੇ ਆਉਟਪੁੱਟ ਉੱਥੇ ਸਮੂਹਬੱਧ ਰਹਿਣ। + ਫਾਈਲ ਨਾਮ ਪਿਛੇਤਰ, ਅਗੇਤਰ, ਟਾਈਮਸਟੈਂਪ, ਜਾਂ ਪੈਟਰਨਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ ਤਾਂ ਕਿ ਸੰਪਾਦਿਤ ਕਾਪੀਆਂ ਨੂੰ ਮੂਲ ਤੋਂ ਵੱਖ ਕਰਨਾ ਆਸਾਨ ਹੋਵੇ। + ਜਦੋਂ ਤੁਸੀਂ ਇੱਕ ਨਿਰਯਾਤ ਫੋਲਡਰ ਵਿੱਚ ਸਾਰੀਆਂ ਤਿਆਰ ਕੀਤੀਆਂ ਫਾਈਲਾਂ ਨੂੰ ਇਕੱਠਾ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ ਤਾਂ ਇਸਨੂੰ ਮਿਸ਼ਰਤ ਬੈਚਾਂ ਲਈ ਅਸਮਰੱਥ ਬਣਾਓ। + ਵੱਡਾ ਆਉਟਪੁੱਟ ਛੱਡੋ + ਪਰਿਵਰਤਨਾਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰਨ ਤੋਂ ਬਚੋ ਜੋ ਅਚਾਨਕ ਸਰੋਤ ਨਾਲੋਂ ਭਾਰੀ ਹੋ ਜਾਂਦੇ ਹਨ + ਬੈਚਾਂ ਨੂੰ ਮਾੜੇ ਆਕਾਰ ਦੇ ਹੈਰਾਨੀ ਤੋਂ ਬਚਾਓ + ਕੁਝ ਪਰਿਵਰਤਨ ਫਾਈਲਾਂ ਨੂੰ ਵੱਡਾ ਬਣਾਉਂਦੇ ਹਨ, ਖਾਸ ਤੌਰ \'ਤੇ PNG ਸਕ੍ਰੀਨਸ਼ੌਟਸ, ਪਹਿਲਾਂ ਤੋਂ ਸੰਕੁਚਿਤ ਫੋਟੋਆਂ, ਉੱਚ-ਗੁਣਵੱਤਾ ਵਾਲੇ WebP, ਜਾਂ ਮੈਟਾਡੇਟਾ ਨਾਲ ਸੁਰੱਖਿਅਤ ਚਿੱਤਰ। ਛੱਡਣ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ ਜੇਕਰ ਵੱਡਾ ਉਹਨਾਂ ਆਉਟਪੁੱਟਾਂ ਨੂੰ ਵਰਕਫਲੋ ਵਿੱਚ ਇੱਕ ਛੋਟੇ ਸਰੋਤ ਨੂੰ ਬਦਲਣ ਤੋਂ ਰੋਕਦਾ ਹੈ ਜਿੱਥੇ ਆਕਾਰ ਘਟਾਉਣਾ ਮੁੱਖ ਟੀਚਾ ਹੈ। + ਜਦੋਂ ਨਿਰਯਾਤ ਦਾ ਮਤਲਬ ਅਪਲੋਡ ਸੀਮਾਵਾਂ ਲਈ ਫਾਈਲਾਂ ਨੂੰ ਸੰਕੁਚਿਤ ਕਰਨਾ, ਮੁੜ ਆਕਾਰ ਦੇਣਾ ਜਾਂ ਤਿਆਰ ਕਰਨਾ ਹੈ ਤਾਂ ਇਸਨੂੰ ਸਮਰੱਥ ਬਣਾਓ। + ਇੱਕ ਬੈਚ ਦੇ ਬਾਅਦ ਛੱਡੀਆਂ ਗਈਆਂ ਫਾਈਲਾਂ ਦੀ ਸਮੀਖਿਆ ਕਰੋ; ਉਹ ਪਹਿਲਾਂ ਤੋਂ ਹੀ ਅਨੁਕੂਲਿਤ ਹੋ ਸਕਦੇ ਹਨ ਜਾਂ ਇੱਕ ਵੱਖਰੇ ਫਾਰਮੈਟ ਦੀ ਲੋੜ ਹੋ ਸਕਦੀ ਹੈ। + ਜਦੋਂ ਗੁਣਵੱਤਾ, ਪਾਰਦਰਸ਼ਤਾ, ਜਾਂ ਫਾਰਮੈਟ ਅਨੁਕੂਲਤਾ ਅੰਤਿਮ ਫਾਈਲ ਆਕਾਰ ਤੋਂ ਵੱਧ ਮਹੱਤਵਪੂਰਨ ਹੁੰਦੀ ਹੈ ਤਾਂ ਇਸਨੂੰ ਅਯੋਗ ਕਰੋ। + ਕਲਿੱਪਬੋਰਡ ਅਤੇ ਲਿੰਕ + ਤੇਜ਼ ਟੈਕਸਟ-ਅਧਾਰਿਤ ਟੂਲਸ ਲਈ ਆਟੋਮੈਟਿਕ ਕਲਿੱਪਬੋਰਡ ਇਨਪੁਟ ਅਤੇ ਲਿੰਕ ਪ੍ਰੀਵਿਊ ਨੂੰ ਕੰਟਰੋਲ ਕਰੋ + ਆਟੋਮੈਟਿਕ ਇਨਪੁਟ ਦਾ ਅਨੁਮਾਨ ਲਗਾਉਣ ਯੋਗ ਬਣਾਓ + ਕਲਿੱਪਬੋਰਡ ਅਤੇ ਲਿੰਕ ਸੈਟਿੰਗਾਂ QR, Base64, URL, ਅਤੇ ਟੈਕਸਟ-ਹੈਵੀ ਵਰਕਫਲੋ ਲਈ ਸੁਵਿਧਾਜਨਕ ਹਨ, ਪਰ ਆਟੋਮੈਟਿਕ ਇਨਪੁਟ ਜਾਣਬੁੱਝ ਕੇ ਰਹਿਣਾ ਚਾਹੀਦਾ ਹੈ। ਜੇਕਰ ਐਪ ਆਪਣੇ ਆਪ ਹੀ ਖੇਤਰ ਭਰਦੀ ਜਾਪਦੀ ਹੈ, ਤਾਂ ਕਲਿੱਪਬੋਰਡ ਪੇਸਟ ਵਿਵਹਾਰ ਦੀ ਜਾਂਚ ਕਰੋ; ਜੇਕਰ ਲਿੰਕ ਕਾਰਡ ਸ਼ੋਰ ਜਾਂ ਹੌਲੀ ਮਹਿਸੂਸ ਕਰਦੇ ਹਨ, ਤਾਂ ਲਿੰਕ ਪੂਰਵਦਰਸ਼ਨ ਵਿਵਹਾਰ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ। + ਆਟੋਮੈਟਿਕ ਕਲਿੱਪਬੋਰਡ ਪੇਸਟ ਦੀ ਆਗਿਆ ਦਿਓ ਜਦੋਂ ਤੁਸੀਂ ਅਕਸਰ ਟੈਕਸਟ ਦੀ ਨਕਲ ਕਰਦੇ ਹੋ ਅਤੇ ਤੁਰੰਤ ਇੱਕ ਮੇਲ ਖਾਂਦਾ ਟੂਲ ਖੋਲ੍ਹਦੇ ਹੋ। + ਆਟੋਮੈਟਿਕ ਪੇਸਟ ਨੂੰ ਅਸਮਰੱਥ ਕਰੋ ਜੇਕਰ ਨਿੱਜੀ ਕਲਿੱਪਬੋਰਡ ਸਮੱਗਰੀ ਉਹਨਾਂ ਟੂਲਸ ਵਿੱਚ ਦਿਖਾਈ ਦਿੰਦੀ ਹੈ ਜਿੱਥੇ ਤੁਸੀਂ ਇਸਦੀ ਉਮੀਦ ਨਹੀਂ ਕੀਤੀ ਸੀ। + ਤੁਹਾਡੇ ਵਰਕਫਲੋ ਲਈ ਪੂਰਵਦਰਸ਼ਨ ਪ੍ਰਾਪਤ ਕਰਨਾ ਹੌਲੀ, ਧਿਆਨ ਭਟਕਾਉਣ ਵਾਲਾ, ਜਾਂ ਬੇਲੋੜਾ ਹੋਣ \'ਤੇ ਲਿੰਕ ਪੂਰਵਦਰਸ਼ਨਾਂ ਨੂੰ ਬੰਦ ਕਰੋ। + ਸੰਖੇਪ ਨਿਯੰਤਰਣ + ਸਕ੍ਰੀਨ ਸਪੇਸ ਤੰਗ ਹੋਣ \'ਤੇ ਸੰਘਣੇ ਚੋਣਕਾਰ ਅਤੇ ਤੇਜ਼ ਸੈਟਿੰਗ ਪਲੇਸਮੈਂਟ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਛੋਟੀਆਂ ਸਕ੍ਰੀਨਾਂ \'ਤੇ ਹੋਰ ਨਿਯੰਤਰਣ ਫਿੱਟ ਕਰੋ + ਛੋਟੇ ਫ਼ੋਨਾਂ, ਲੈਂਡਸਕੇਪ ਸੰਪਾਦਨ, ਅਤੇ ਬਹੁਤ ਸਾਰੇ ਮਾਪਦੰਡਾਂ ਵਾਲੇ ਟੂਲਸ \'ਤੇ ਸੰਖੇਪ ਚੋਣਕਾਰ ਅਤੇ ਤੇਜ਼ ਸੈਟਿੰਗ ਪਲੇਸਮੈਂਟ ਮਦਦ ਕਰਦੇ ਹਨ। ਵਪਾਰ ਇਹ ਹੈ ਕਿ ਨਿਯੰਤਰਣ ਸੰਘਣੇ ਮਹਿਸੂਸ ਕਰ ਸਕਦੇ ਹਨ, ਇਸ ਲਈ ਇਹ ਸਭ ਤੋਂ ਵਧੀਆ ਹੈ ਜਦੋਂ ਤੁਸੀਂ ਪਹਿਲਾਂ ਹੀ ਆਮ ਵਿਕਲਪਾਂ ਨੂੰ ਪਛਾਣ ਲੈਂਦੇ ਹੋ। + ਜਦੋਂ ਡਾਇਲਾਗ ਜਾਂ ਵਿਕਲਪ ਸੂਚੀਆਂ ਸਕ੍ਰੀਨ ਲਈ ਬਹੁਤ ਉੱਚੀਆਂ ਮਹਿਸੂਸ ਹੋਣ ਤਾਂ ਸੰਖੇਪ ਚੋਣਕਾਰਾਂ ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ। + ਜੇਕਰ ਡਿਸਪਲੇ ਦੇ ਇੱਕ ਪਾਸੇ ਤੋਂ ਟੂਲ ਨਿਯੰਤਰਣਾਂ ਤੱਕ ਪਹੁੰਚਣਾ ਆਸਾਨ ਹੈ ਤਾਂ ਤੇਜ਼ ਸੈਟਿੰਗਾਂ ਵਾਲੇ ਪਾਸੇ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ। + ਜੇਕਰ ਤੁਸੀਂ ਅਜੇ ਵੀ ਐਪ ਨੂੰ ਸਿੱਖ ਰਹੇ ਹੋ ਜਾਂ ਅਕਸਰ ਮਿਸ-ਟੈਪ ਸੰਘਣੇ ਵਿਕਲਪਾਂ ਨੂੰ ਛੱਡ ਰਹੇ ਹੋ ਤਾਂ ਕਮਰੇ ਵਾਲੇ ਨਿਯੰਤਰਣਾਂ \'ਤੇ ਵਾਪਸ ਜਾਓ। + ਵੈੱਬ ਤੋਂ ਚਿੱਤਰ ਲੋਡ ਕਰੋ + ਇੱਕ ਪੰਨਾ ਜਾਂ ਚਿੱਤਰ URL ਪੇਸਟ ਕਰੋ, ਪਾਰਸ ਕੀਤੇ ਚਿੱਤਰਾਂ ਦਾ ਪੂਰਵਦਰਸ਼ਨ ਕਰੋ, ਫਿਰ ਚੁਣੇ ਗਏ ਨਤੀਜਿਆਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਜਾਂ ਸਾਂਝਾ ਕਰੋ + ਜਾਣਬੁੱਝ ਕੇ ਵੈੱਬ ਚਿੱਤਰਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਵੈੱਬ ਚਿੱਤਰ ਲੋਡਿੰਗ ਪ੍ਰਦਾਨ ਕੀਤੇ ਗਏ URL ਤੋਂ ਚਿੱਤਰ ਸਰੋਤਾਂ ਨੂੰ ਪਾਰਸ ਕਰਦਾ ਹੈ, ਪਹਿਲੇ ਉਪਲਬਧ ਚਿੱਤਰ ਦਾ ਪੂਰਵਦਰਸ਼ਨ ਕਰਦਾ ਹੈ, ਅਤੇ ਤੁਹਾਨੂੰ ਚੁਣੀਆਂ ਗਈਆਂ ਪਾਰਸ ਕੀਤੀਆਂ ਤਸਵੀਰਾਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਜਾਂ ਸਾਂਝਾ ਕਰਨ ਦਿੰਦਾ ਹੈ। ਕੁਝ ਸਾਈਟਾਂ ਅਸਥਾਈ, ਸੁਰੱਖਿਅਤ, ਜਾਂ ਸਕ੍ਰਿਪਟ ਦੁਆਰਾ ਤਿਆਰ ਕੀਤੇ ਲਿੰਕਾਂ ਦੀ ਵਰਤੋਂ ਕਰਦੀਆਂ ਹਨ, ਇਸਲਈ ਇੱਕ ਪੰਨਾ ਜੋ ਇੱਕ ਬ੍ਰਾਊਜ਼ਰ ਵਿੱਚ ਕੰਮ ਕਰਦਾ ਹੈ ਅਜੇ ਵੀ ਕੋਈ ਉਪਯੋਗੀ ਚਿੱਤਰ ਨਹੀਂ ਵਾਪਸ ਕਰ ਸਕਦਾ ਹੈ। + ਇੱਕ ਸਿੱਧਾ ਚਿੱਤਰ URL ਜਾਂ ਇੱਕ ਪੰਨਾ URL ਪੇਸਟ ਕਰੋ ਅਤੇ ਪਾਰਸ ਚਿੱਤਰ ਸੂਚੀ ਦੇ ਦਿਖਾਈ ਦੇਣ ਦੀ ਉਡੀਕ ਕਰੋ। + ਫ੍ਰੇਮ ਜਾਂ ਚੋਣ ਨਿਯੰਤਰਣ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਪੰਨੇ ਵਿੱਚ ਕਈ ਚਿੱਤਰ ਸ਼ਾਮਲ ਹੁੰਦੇ ਹਨ ਅਤੇ ਤੁਹਾਨੂੰ ਉਹਨਾਂ ਵਿੱਚੋਂ ਕੁਝ ਦੀ ਹੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। + ਜੇਕਰ ਕੁਝ ਵੀ ਲੋਡ ਨਹੀਂ ਹੁੰਦਾ ਹੈ, ਤਾਂ ਇੱਕ ਸਿੱਧਾ ਚਿੱਤਰ ਲਿੰਕ ਅਜ਼ਮਾਓ ਜਾਂ ਪਹਿਲਾਂ ਬ੍ਰਾਊਜ਼ਰ ਰਾਹੀਂ ਡਾਊਨਲੋਡ ਕਰੋ; ਸਾਈਟ ਪਾਰਸਿੰਗ ਨੂੰ ਰੋਕ ਸਕਦੀ ਹੈ ਜਾਂ ਨਿੱਜੀ ਲਿੰਕਾਂ ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦੀ ਹੈ। + ਮੁੜ ਆਕਾਰ ਦੀ ਕਿਸਮ ਚੁਣੋ + ਕਿਸੇ ਚਿੱਤਰ ਨੂੰ ਨਵੇਂ ਮਾਪਾਂ ਵਿੱਚ ਮਜਬੂਰ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਸਪਸ਼ਟ, ਲਚਕਦਾਰ, ਕ੍ਰੌਪ ਅਤੇ ਫਿੱਟ ਨੂੰ ਸਮਝੋ + ਆਕਾਰ, ਕੈਨਵਸ, ਅਤੇ ਆਕਾਰ ਅਨੁਪਾਤ ਨੂੰ ਕੰਟਰੋਲ ਕਰੋ + ਮੁੜ-ਆਕਾਰ ਦੀ ਕਿਸਮ ਇਹ ਫੈਸਲਾ ਕਰਦੀ ਹੈ ਕਿ ਕੀ ਹੁੰਦਾ ਹੈ ਜਦੋਂ ਬੇਨਤੀ ਕੀਤੀ ਚੌੜਾਈ ਅਤੇ ਉਚਾਈ ਅਸਲ ਆਕਾਰ ਅਨੁਪਾਤ ਨਾਲ ਮੇਲ ਨਹੀਂ ਖਾਂਦੀ। ਇਹ ਪਿਕਸਲਾਂ ਨੂੰ ਖਿੱਚਣ, ਅਨੁਪਾਤ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰਨ, ਵਾਧੂ ਖੇਤਰ ਕੱਟਣ, ਜਾਂ ਬੈਕਗ੍ਰਾਉਂਡ ਕੈਨਵਸ ਜੋੜਨ ਵਿੱਚ ਅੰਤਰ ਹੈ। + ਅਸ਼ਲੀਲ ਦੀ ਵਰਤੋਂ ਸਿਰਫ਼ ਉਦੋਂ ਕਰੋ ਜਦੋਂ ਵਿਗਾੜ ਸਵੀਕਾਰਯੋਗ ਹੋਵੇ ਜਾਂ ਸਰੋਤ ਦਾ ਪਹਿਲਾਂ ਤੋਂ ਹੀ ਨਿਸ਼ਾਨਾ ਅਨੁਪਾਤ ਸਮਾਨ ਹੋਵੇ। + ਖਾਸ ਤੌਰ \'ਤੇ ਫ਼ੋਟੋਆਂ ਅਤੇ ਮਿਕਸਡ ਬੈਚਾਂ ਲਈ, ਮਹੱਤਵਪੂਰਨ ਪਾਸੇ ਤੋਂ ਆਕਾਰ ਬਦਲ ਕੇ ਅਨੁਪਾਤ ਰੱਖਣ ਲਈ ਲਚਕਦਾਰ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਬਾਰਡਰਾਂ ਤੋਂ ਬਿਨਾਂ ਸਖ਼ਤ ਫਰੇਮਾਂ ਲਈ ਕ੍ਰੌਪ ਦੀ ਵਰਤੋਂ ਕਰੋ, ਜਾਂ ਫਿੱਟ ਕਰੋ ਜਦੋਂ ਪੂਰਾ ਚਿੱਤਰ ਇੱਕ ਸਥਿਰ ਕੈਨਵਸ ਦੇ ਅੰਦਰ ਦਿਖਾਈ ਦੇਣਾ ਚਾਹੀਦਾ ਹੈ। + ਚਿੱਤਰ ਸਕੇਲ ਮੋਡ ਚੁਣੋ + ਰੀਸੈਪਲਿੰਗ ਫਿਲਟਰ ਚੁਣੋ ਜੋ ਤਿੱਖਾਪਨ, ਨਿਰਵਿਘਨਤਾ, ਗਤੀ ਅਤੇ ਕਿਨਾਰੇ ਦੀਆਂ ਕਲਾਕ੍ਰਿਤੀਆਂ ਨੂੰ ਨਿਯੰਤਰਿਤ ਕਰਦਾ ਹੈ + ਅਚਨਚੇਤ ਕੋਮਲਤਾ ਤੋਂ ਬਿਨਾਂ ਮੁੜ ਆਕਾਰ ਦਿਓ + ਚਿੱਤਰ ਸਕੇਲ ਮੋਡ ਨਿਯੰਤਰਿਤ ਕਰਦਾ ਹੈ ਕਿ ਆਕਾਰ ਬਦਲਣ ਦੌਰਾਨ ਨਵੇਂ ਪਿਕਸਲ ਦੀ ਗਣਨਾ ਕਿਵੇਂ ਕੀਤੀ ਜਾਂਦੀ ਹੈ। ਸਧਾਰਨ ਮੋਡ ਤੇਜ਼ ਅਤੇ ਅਨੁਮਾਨਯੋਗ ਹੁੰਦੇ ਹਨ, ਜਦੋਂ ਕਿ ਉੱਨਤ ਫਿਲਟਰ ਵੇਰਵੇ ਨੂੰ ਬਿਹਤਰ ਢੰਗ ਨਾਲ ਸੁਰੱਖਿਅਤ ਕਰ ਸਕਦੇ ਹਨ ਪਰ ਕਿਨਾਰਿਆਂ ਨੂੰ ਤਿੱਖਾ ਕਰ ਸਕਦੇ ਹਨ, ਹਾਲੋਜ਼ ਬਣਾ ਸਕਦੇ ਹਨ, ਜਾਂ ਵੱਡੇ ਚਿੱਤਰਾਂ \'ਤੇ ਜ਼ਿਆਦਾ ਸਮਾਂ ਲੈ ਸਕਦੇ ਹਨ। + ਜਦੋਂ ਨਤੀਜਾ ਸਿਰਫ਼ ਛੋਟਾ ਅਤੇ ਸਾਫ਼ ਹੋਣ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ ਤਾਂ ਰੋਜ਼ਾਨਾ ਤੇਜ਼ ਆਕਾਰ ਦੇਣ ਲਈ ਬੇਸਿਕ ਜਾਂ ਬਿਲੀਨੀਅਰ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਵਧੀਆ ਵੇਰਵੇ, ਟੈਕਸਟ, ਜਾਂ ਉੱਚ-ਗੁਣਵੱਤਾ ਡਾਊਨਸਕੇਲਿੰਗ ਮਾਮਲੇ \'ਤੇ Lanczos, Robidoux, Spline, ਜਾਂ EWA-ਸ਼ੈਲੀ ਮੋਡਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਪਿਕਸਲ ਆਰਟ, ਆਈਕਨਾਂ ਅਤੇ ਸਖ਼ਤ ਗ੍ਰਾਫਿਕਸ ਲਈ ਨਜ਼ਦੀਕੀ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਿੱਥੇ ਧੁੰਦਲੇ ਪਿਕਸਲ ਦਿੱਖ ਨੂੰ ਵਿਗਾੜ ਦਿੰਦੇ ਹਨ। + ਸਕੇਲ ਰੰਗ ਸਪੇਸ + ਇਹ ਫੈਸਲਾ ਕਰੋ ਕਿ ਰੰਗ ਕਿਵੇਂ ਮਿਲਾਏ ਜਾਂਦੇ ਹਨ ਜਦੋਂ ਕਿ ਰੀਸਾਈਜ਼ ਦੌਰਾਨ ਪਿਕਸਲ ਦੁਬਾਰਾ ਨਮੂਨੇ ਕੀਤੇ ਜਾਂਦੇ ਹਨ + ਗਰੇਡੀਐਂਟ ਅਤੇ ਕਿਨਾਰਿਆਂ ਨੂੰ ਕੁਦਰਤੀ ਰੱਖੋ + ਸਕੇਲ ਰੰਗ ਸਪੇਸ ਰੀਸਾਈਜ਼ ਕਰਨ ਵੇਲੇ ਵਰਤੇ ਗਏ ਗਣਿਤ ਨੂੰ ਬਦਲਦਾ ਹੈ। ਜ਼ਿਆਦਾਤਰ ਚਿੱਤਰ ਪੂਰਵ-ਨਿਰਧਾਰਤ ਨਾਲ ਠੀਕ ਹੁੰਦੇ ਹਨ, ਪਰ ਰੇਖਿਕ ਜਾਂ ਅਨੁਭਵੀ ਸਪੇਸ ਗਰੇਡੀਐਂਟ, ਗੂੜ੍ਹੇ ਕਿਨਾਰਿਆਂ, ਅਤੇ ਸੰਤ੍ਰਿਪਤ ਰੰਗਾਂ ਨੂੰ ਮਜ਼ਬੂਤ ​​ਸਕੇਲਿੰਗ ਤੋਂ ਬਾਅਦ ਸਾਫ਼ ਦਿਖ ਸਕਦੇ ਹਨ। + ਪੂਰਵ-ਨਿਰਧਾਰਤ ਛੱਡੋ ਜਦੋਂ ਗਤੀ ਅਤੇ ਅਨੁਕੂਲਤਾ ਛੋਟੇ ਰੰਗ ਦੇ ਅੰਤਰਾਂ ਤੋਂ ਵੱਧ ਮਹੱਤਵ ਰੱਖਦੀ ਹੈ। + ਰੇਖਿਕ ਜਾਂ ਅਨੁਭਵੀ ਮੋਡ ਅਜ਼ਮਾਓ ਜਦੋਂ ਗੂੜ੍ਹੀ ਰੂਪਰੇਖਾ, UI ਸਕ੍ਰੀਨਸ਼ਾਟ, ਗਰੇਡੀਐਂਟ, ਜਾਂ ਰੰਗ ਬੈਂਡ ਮੁੜ ਆਕਾਰ ਦੇਣ ਤੋਂ ਬਾਅਦ ਗਲਤ ਦਿਖਾਈ ਦਿੰਦੇ ਹਨ। + ਅਸਲ ਨਿਸ਼ਾਨਾ ਸਕ੍ਰੀਨ \'ਤੇ ਪਹਿਲਾਂ ਅਤੇ ਬਾਅਦ ਦੀ ਤੁਲਨਾ ਕਰੋ, ਕਿਉਂਕਿ ਰੰਗ-ਸਪੇਸ ਬਦਲਾਅ ਸੂਖਮ ਅਤੇ ਸਮੱਗਰੀ ਨਿਰਭਰ ਹੋ ਸਕਦੇ ਹਨ। + ਰੀਸਾਈਜ਼ ਮੋਡਾਂ ਨੂੰ ਸੀਮਤ ਕਰੋ + ਜਾਣੋ ਕਿ ਫਿੱਟ ਕਰਨ ਲਈ ਓਵਰ-ਸੀਮਾ ਚਿੱਤਰਾਂ ਨੂੰ ਕਦੋਂ ਛੱਡਿਆ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ, ਰੀਕੋਡ ਕੀਤਾ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ ਜਾਂ ਘੱਟ ਕੀਤਾ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ + ਚੁਣੋ ਕਿ ਸੀਮਾ \'ਤੇ ਕੀ ਹੁੰਦਾ ਹੈ + ਸੀਮਾ ਰੀਸਾਈਜ਼ ਨਾ ਸਿਰਫ਼ ਇੱਕ ਛੋਟਾ-ਚਿੱਤਰ ਸੰਦ ਹੈ। ਇਸਦਾ ਮੋਡ ਫੈਸਲਾ ਕਰਦਾ ਹੈ ਕਿ ਐਪ ਕੀ ਕਰਦੀ ਹੈ ਜਦੋਂ ਕੋਈ ਸਰੋਤ ਪਹਿਲਾਂ ਤੋਂ ਹੀ ਤੁਹਾਡੀ ਸੀਮਾ ਦੇ ਅੰਦਰ ਹੁੰਦਾ ਹੈ ਜਾਂ ਜਦੋਂ ਸਿਰਫ ਇੱਕ ਪਾਸੇ ਨਿਯਮ ਤੋੜਦਾ ਹੈ, ਜੋ ਕਿ ਮਿਸ਼ਰਤ ਫੋਲਡਰਾਂ ਅਤੇ ਅਪਲੋਡ ਦੀ ਤਿਆਰੀ ਲਈ ਮਹੱਤਵਪੂਰਨ ਹੈ। + ਛੱਡੋ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਸੀਮਾਵਾਂ ਦੇ ਅੰਦਰ ਚਿੱਤਰਾਂ ਨੂੰ ਛੂਹਿਆ ਨਹੀਂ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ ਅਤੇ ਦੁਬਾਰਾ ਨਿਰਯਾਤ ਨਹੀਂ ਕੀਤਾ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ। + ਜਦੋਂ ਮਾਪ ਸਵੀਕਾਰਯੋਗ ਹੋਵੇ ਤਾਂ ਰੀਕੋਡ ਦੀ ਵਰਤੋਂ ਕਰੋ ਪਰ ਤੁਹਾਨੂੰ ਅਜੇ ਵੀ ਇੱਕ ਨਵੇਂ ਫਾਰਮੈਟ, ਮੈਟਾਡੇਟਾ ਵਿਵਹਾਰ, ਗੁਣਵੱਤਾ, ਜਾਂ ਫਾਈਲ ਨਾਮ ਦੀ ਲੋੜ ਹੈ। + ਜ਼ੂਮ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਸੀਮਾਵਾਂ ਨੂੰ ਤੋੜਨ ਵਾਲੀਆਂ ਤਸਵੀਰਾਂ ਨੂੰ ਅਨੁਪਾਤਕ ਤੌਰ \'ਤੇ ਘੱਟ ਕੀਤਾ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ ਜਦੋਂ ਤੱਕ ਉਹ ਮਨਜ਼ੂਰ ਕੀਤੇ ਬਕਸੇ ਵਿੱਚ ਫਿੱਟ ਨਹੀਂ ਹੁੰਦੇ। + ਆਕਾਰ ਅਨੁਪਾਤ ਟੀਚੇ + ਸਖ਼ਤ ਆਉਟਪੁੱਟ ਆਕਾਰਾਂ ਵਿੱਚ ਖਿੱਚੇ ਹੋਏ ਚਿਹਰੇ, ਕੱਟੇ ਹੋਏ ਕਿਨਾਰਿਆਂ ਅਤੇ ਅਚਾਨਕ ਬਾਰਡਰਾਂ ਤੋਂ ਬਚੋ + ਆਕਾਰ ਬਦਲਣ ਤੋਂ ਪਹਿਲਾਂ ਆਕਾਰ ਨਾਲ ਮੇਲ ਕਰੋ + ਚੌੜਾਈ ਅਤੇ ਉਚਾਈ ਮੁੜ ਆਕਾਰ ਦੇ ਟੀਚੇ ਦਾ ਅੱਧਾ ਹਿੱਸਾ ਹੈ। ਆਕਾਰ ਅਨੁਪਾਤ ਇਹ ਫੈਸਲਾ ਕਰਦਾ ਹੈ ਕਿ ਕੀ ਚਿੱਤਰ ਕੁਦਰਤੀ ਤੌਰ \'ਤੇ ਫਿੱਟ ਹੋ ਸਕਦਾ ਹੈ, ਕੱਟਣ ਦੀ ਲੋੜ ਹੈ, ਜਾਂ ਇਸਦੇ ਆਲੇ ਦੁਆਲੇ ਕੈਨਵਸ ਦੀ ਲੋੜ ਹੈ। ਪਹਿਲਾਂ ਆਕਾਰ ਦੀ ਜਾਂਚ ਕਰਨਾ ਸਭ ਤੋਂ ਹੈਰਾਨੀਜਨਕ ਰੀਸਾਈਜ਼ ਨਤੀਜਿਆਂ ਨੂੰ ਰੋਕਦਾ ਹੈ। + ਸਪਸ਼ਟ, ਕਰੋਪ, ਜਾਂ ਫਿੱਟ ਚੁਣਨ ਤੋਂ ਪਹਿਲਾਂ ਸਰੋਤ ਆਕਾਰ ਦੀ ਟੀਚਾ ਆਕਾਰ ਨਾਲ ਤੁਲਨਾ ਕਰੋ। + ਜਦੋਂ ਵਿਸ਼ਾ ਵਾਧੂ ਬੈਕਗ੍ਰਾਊਂਡ ਗੁਆ ਸਕਦਾ ਹੈ ਅਤੇ ਮੰਜ਼ਿਲ ਨੂੰ ਇੱਕ ਸਖਤ ਫ੍ਰੇਮ ਦੀ ਲੋੜ ਹੈ ਤਾਂ ਪਹਿਲਾਂ ਕੱਟੋ। + ਜਦੋਂ ਅਸਲੀ ਚਿੱਤਰ ਦਾ ਹਰ ਹਿੱਸਾ ਦਿਖਾਈ ਦੇਣਾ ਚਾਹੀਦਾ ਹੈ ਤਾਂ ਫਿੱਟ ਜਾਂ ਲਚਕਦਾਰ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਅੱਪਸਕੇਲ ਜਾਂ ਸਧਾਰਨ ਮੁੜ-ਆਕਾਰ + ਜਾਣੋ ਕਿ ਕਦੋਂ ਪਿਕਸਲ ਜੋੜਨ ਲਈ AI ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ ਅਤੇ ਕਦੋਂ ਸਧਾਰਨ ਸਕੇਲਿੰਗ ਕਾਫੀ ਹੁੰਦੀ ਹੈ + ਵੇਰਵੇ ਦੇ ਨਾਲ ਆਕਾਰ ਨੂੰ ਉਲਝਣ ਨਾ ਕਰੋ + ਸਧਾਰਣ ਰੀਸਾਈਜ਼ ਪਿਕਸਲ ਇੰਟਰਪੋਲੇਟਿੰਗ ਦੁਆਰਾ ਮਾਪ ਬਦਲਦਾ ਹੈ; ਇਹ ਅਸਲ ਵੇਰਵੇ ਦੀ ਕਾਢ ਨਹੀਂ ਕਰ ਸਕਦਾ। AI ਅਪਸਕੇਲ ਤਿੱਖਾ-ਦਿੱਖ ਵਾਲਾ ਵੇਰਵਾ ਬਣਾ ਸਕਦਾ ਹੈ, ਪਰ ਇਹ ਹੌਲੀ ਹੈ ਅਤੇ ਟੈਕਸਟਚਰ ਨੂੰ ਭੁਲੇਖਾ ਪਾ ਸਕਦਾ ਹੈ, ਇਸਲਈ ਸਹੀ ਚੋਣ ਇਸ ਗੱਲ \'ਤੇ ਨਿਰਭਰ ਕਰਦੀ ਹੈ ਕਿ ਕੀ ਤੁਹਾਨੂੰ ਅਨੁਕੂਲਤਾ, ਗਤੀ, ਜਾਂ ਵਿਜ਼ੂਅਲ ਬਹਾਲੀ ਦੀ ਲੋੜ ਹੈ। + ਥੰਬਨੇਲ, ਅੱਪਲੋਡ ਸੀਮਾਵਾਂ, ਸਕ੍ਰੀਨਸ਼ੌਟਸ, ਅਤੇ ਸਧਾਰਨ ਮਾਪ ਤਬਦੀਲੀਆਂ ਲਈ ਸਧਾਰਨ ਰੀਸਾਈਜ਼ ਦੀ ਵਰਤੋਂ ਕਰੋ। + AI ਅੱਪਸਕੇਲ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਇੱਕ ਛੋਟੀ ਜਾਂ ਨਰਮ ਚਿੱਤਰ ਨੂੰ ਵੱਡੇ ਆਕਾਰ ਵਿੱਚ ਬਿਹਤਰ ਦੇਖਣ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। + AI ਅਪਸਕੇਲ ਤੋਂ ਬਾਅਦ ਚਿਹਰਿਆਂ, ਟੈਕਸਟ ਅਤੇ ਪੈਟਰਨਾਂ ਦੀ ਤੁਲਨਾ ਕਰੋ ਕਿਉਂਕਿ ਤਿਆਰ ਕੀਤਾ ਗਿਆ ਵੇਰਵਾ ਯਕੀਨਨ ਦਿਖਾਈ ਦੇ ਸਕਦਾ ਹੈ ਪਰ ਗਲਤ ਹੋ ਸਕਦਾ ਹੈ। + ਫਿਲਟਰ ਆਰਡਰ ਮਹੱਤਵਪੂਰਨ ਹੈ + ਇੱਕ ਜਾਣਬੁੱਝ ਕੇ ਕ੍ਰਮ ਵਿੱਚ ਸਫਾਈ, ਰੰਗ, ਤਿੱਖਾਪਨ, ਅਤੇ ਅੰਤਮ ਸੰਕੁਚਨ ਨੂੰ ਲਾਗੂ ਕਰੋ + ਇੱਕ ਕਲੀਨਰ ਫਿਲਟਰ ਸਟੈਕ ਬਣਾਓ + ਫਿਲਟਰ ਹਮੇਸ਼ਾ ਬਦਲਣਯੋਗ ਨਹੀਂ ਹੁੰਦੇ ਹਨ। ਤਿੱਖਾ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਇੱਕ ਧੱਬਾ, OCR ਤੋਂ ਪਹਿਲਾਂ ਇੱਕ ਕੰਟ੍ਰਾਸਟ ਬੂਸਟ, ਜਾਂ ਕੰਪਰੈਸ਼ਨ ਤੋਂ ਪਹਿਲਾਂ ਇੱਕ ਰੰਗ ਬਦਲਣਾ ਇੱਕੋ ਨਿਯੰਤਰਣ ਤੋਂ ਬਹੁਤ ਵੱਖਰਾ ਆਉਟਪੁੱਟ ਪੈਦਾ ਕਰ ਸਕਦਾ ਹੈ। + ਪਹਿਲਾਂ ਕੱਟੋ ਅਤੇ ਘੁੰਮਾਓ ਤਾਂ ਕਿ ਬਾਅਦ ਵਿੱਚ ਫਿਲਟਰ ਸਿਰਫ਼ ਉਹਨਾਂ ਪਿਕਸਲਾਂ \'ਤੇ ਕੰਮ ਕਰਨ ਜੋ ਬਾਕੀ ਰਹਿਣਗੇ। + ਤਿੱਖਾ ਕਰਨ ਜਾਂ ਸਟਾਈਲਾਈਜ਼ਡ ਪ੍ਰਭਾਵਾਂ ਤੋਂ ਪਹਿਲਾਂ ਐਕਸਪੋਜ਼ਰ, ਸਫੈਦ ਸੰਤੁਲਨ, ਅਤੇ ਕੰਟ੍ਰਾਸਟ ਲਾਗੂ ਕਰੋ। + ਅੰਤਮ ਆਕਾਰ ਅਤੇ ਸੰਕੁਚਨ ਨੂੰ ਅੰਤ ਦੇ ਨੇੜੇ ਰੱਖੋ ਤਾਂ ਜੋ ਪੂਰਵਦਰਸ਼ਨ ਕੀਤੇ ਵੇਰਵੇ ਨਿਰਯਾਤ ਅਨੁਮਾਨਤ ਤੌਰ \'ਤੇ ਬਚੇ ਰਹਿਣ। + ਪਿਛੋਕੜ ਵਾਲੇ ਕਿਨਾਰਿਆਂ ਨੂੰ ਠੀਕ ਕਰੋ + ਆਟੋਮੈਟਿਕ ਹਟਾਉਣ ਤੋਂ ਬਾਅਦ ਹਾਲੋਜ਼, ਬਚੇ ਹੋਏ ਪਰਛਾਵੇਂ, ਵਾਲ, ਛੇਕ ਅਤੇ ਨਰਮ ਰੂਪਰੇਖਾ ਸਾਫ਼ ਕਰੋ + ਕੱਟਆਉਟਸ ਨੂੰ ਜਾਣਬੁੱਝ ਕੇ ਦਿੱਖ ਬਣਾਓ + ਆਟੋਮੈਟਿਕ ਮਾਸਕ ਅਕਸਰ ਇੱਕ ਨਜ਼ਰ ਵਿੱਚ ਚੰਗੇ ਲੱਗਦੇ ਹਨ ਪਰ ਚਮਕਦਾਰ, ਹਨੇਰੇ, ਜਾਂ ਪੈਟਰਨ ਵਾਲੇ ਬੈਕਗ੍ਰਾਊਂਡ \'ਤੇ ਸਮੱਸਿਆਵਾਂ ਨੂੰ ਪ੍ਰਗਟ ਕਰਦੇ ਹਨ। ਕਿਨਾਰੇ ਦੀ ਸਫਾਈ ਇੱਕ ਤੇਜ਼ ਕੱਟਆਊਟ ਅਤੇ ਮੁੜ ਵਰਤੋਂ ਯੋਗ ਸਟਿੱਕਰ, ਲੋਗੋ, ਜਾਂ ਉਤਪਾਦ ਚਿੱਤਰ ਵਿੱਚ ਅੰਤਰ ਹੈ। + ਹੈਲੋਸ ਅਤੇ ਗੁੰਮ ਹੋਏ ਕਿਨਾਰੇ ਦੇ ਵੇਰਵੇ ਨੂੰ ਪ੍ਰਗਟ ਕਰਨ ਲਈ ਵਿਪਰੀਤ ਪਿਛੋਕੜਾਂ ਦੇ ਵਿਰੁੱਧ ਨਤੀਜੇ ਦੀ ਪੂਰਵਦਰਸ਼ਨ ਕਰੋ। + ਆਲੇ-ਦੁਆਲੇ ਦੇ ਬਚੇ ਹੋਏ ਹਿੱਸੇ ਨੂੰ ਮਿਟਾਉਣ ਤੋਂ ਪਹਿਲਾਂ ਵਾਲਾਂ, ਕੋਨਿਆਂ ਅਤੇ ਪਾਰਦਰਸ਼ੀ ਵਸਤੂਆਂ ਲਈ ਰੀਸਟੋਰ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਅੰਤਮ ਬੈਕਗ੍ਰਾਉਂਡ ਰੰਗ \'ਤੇ ਇੱਕ ਛੋਟਾ ਟੈਸਟ ਨਿਰਯਾਤ ਕਰੋ ਜਦੋਂ ਕੱਟਆਉਟ ਨੂੰ ਇੱਕ ਡਿਜ਼ਾਈਨ ਵਿੱਚ ਰੱਖਿਆ ਜਾਵੇਗਾ। + ਪੜ੍ਹਨਯੋਗ ਵਾਟਰਮਾਰਕਸ + ਫੋਟੋ ਨੂੰ ਬਰਬਾਦ ਕੀਤੇ ਬਿਨਾਂ ਚਮਕਦਾਰ, ਹਨੇਰੇ, ਵਿਅਸਤ ਅਤੇ ਕੱਟੇ ਹੋਏ ਚਿੱਤਰਾਂ \'ਤੇ ਨਿਸ਼ਾਨ ਦਿੱਖ ਬਣਾਓ + ਚਿੱਤਰ ਨੂੰ ਲੁਕਾਏ ਬਿਨਾਂ ਸੁਰੱਖਿਅਤ ਕਰੋ + ਇੱਕ ਵਾਟਰਮਾਰਕ ਜੋ ਇੱਕ ਚਿੱਤਰ \'ਤੇ ਵਧੀਆ ਦਿਖਾਈ ਦਿੰਦਾ ਹੈ ਦੂਜੀ \'ਤੇ ਅਲੋਪ ਹੋ ਸਕਦਾ ਹੈ। ਆਕਾਰ, ਧੁੰਦਲਾਪਨ, ਵਿਪਰੀਤਤਾ, ਪਲੇਸਮੈਂਟ, ਅਤੇ ਦੁਹਰਾਓ ਨੂੰ ਪੂਰੇ ਬੈਚ ਲਈ ਚੁਣਿਆ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ, ਨਾ ਸਿਰਫ਼ ਪਹਿਲੇ ਪੂਰਵਦਰਸ਼ਨ ਲਈ। + ਸਾਰੀਆਂ ਫਾਈਲਾਂ ਨੂੰ ਨਿਰਯਾਤ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਬੈਚ ਵਿੱਚ ਸਭ ਤੋਂ ਚਮਕਦਾਰ ਅਤੇ ਹਨੇਰੇ ਚਿੱਤਰਾਂ \'ਤੇ ਨਿਸ਼ਾਨ ਦੀ ਜਾਂਚ ਕਰੋ। + ਵੱਡੇ ਦੁਹਰਾਉਣ ਵਾਲੇ ਨਿਸ਼ਾਨਾਂ ਲਈ ਘੱਟ ਧੁੰਦਲਾਪਨ ਅਤੇ ਛੋਟੇ ਕੋਨੇ ਦੇ ਨਿਸ਼ਾਨਾਂ ਲਈ ਮਜ਼ਬੂਤ ​​ਕੰਟ੍ਰਾਸਟ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਮਹੱਤਵਪੂਰਨ ਚਿਹਰਿਆਂ, ਟੈਕਸਟ ਅਤੇ ਉਤਪਾਦ ਦੇ ਵੇਰਵਿਆਂ ਨੂੰ ਸਾਫ਼ ਰੱਖੋ ਜਦੋਂ ਤੱਕ ਕਿ ਨਿਸ਼ਾਨ ਦੁਬਾਰਾ ਵਰਤੋਂ ਨੂੰ ਰੋਕਣ ਲਈ ਨਾ ਹੋਵੇ। + ਇੱਕ ਚਿੱਤਰ ਨੂੰ ਟਾਈਲਾਂ ਵਿੱਚ ਵੰਡੋ + ਕਤਾਰਾਂ, ਕਾਲਮਾਂ, ਕਸਟਮ ਪ੍ਰਤੀਸ਼ਤ, ਫਾਰਮੈਟ ਅਤੇ ਗੁਣਵੱਤਾ ਦੁਆਰਾ ਇੱਕ ਸਿੰਗਲ ਚਿੱਤਰ ਨੂੰ ਕੱਟੋ + ਗਰਿੱਡ ਅਤੇ ਆਰਡਰ ਕੀਤੇ ਟੁਕੜੇ ਤਿਆਰ ਕਰੋ + ਚਿੱਤਰ ਵੰਡਣਾ ਇੱਕ ਸਰੋਤ ਚਿੱਤਰ ਤੋਂ ਕਈ ਆਉਟਪੁੱਟ ਬਣਾਉਂਦਾ ਹੈ। ਇਹ ਕਤਾਰ ਅਤੇ ਕਾਲਮ ਗਿਣਤੀ ਦੁਆਰਾ ਵੰਡਿਆ ਜਾ ਸਕਦਾ ਹੈ, ਕਤਾਰ ਜਾਂ ਕਾਲਮ ਪ੍ਰਤੀਸ਼ਤਤਾ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰ ਸਕਦਾ ਹੈ, ਅਤੇ ਚੁਣੇ ਹੋਏ ਆਉਟਪੁੱਟ ਫਾਰਮੈਟ ਅਤੇ ਗੁਣਵੱਤਾ ਦੇ ਨਾਲ ਟੁਕੜਿਆਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰ ਸਕਦਾ ਹੈ, ਜੋ ਕਿ ਗਰਿੱਡਾਂ, ਪੰਨਿਆਂ ਦੇ ਟੁਕੜਿਆਂ, ਪੈਨੋਰਾਮਾ ਅਤੇ ਆਰਡਰ ਕੀਤੀਆਂ ਸਮਾਜਿਕ ਪੋਸਟਾਂ ਲਈ ਉਪਯੋਗੀ ਹੈ। + ਸਰੋਤ ਚਿੱਤਰ ਨੂੰ ਚੁਣੋ, ਫਿਰ ਤੁਹਾਨੂੰ ਲੋੜੀਂਦੀਆਂ ਕਤਾਰਾਂ ਅਤੇ ਕਾਲਮਾਂ ਦੀ ਗਿਣਤੀ ਸੈੱਟ ਕਰੋ। + ਕਤਾਰ ਜਾਂ ਕਾਲਮ ਪ੍ਰਤੀਸ਼ਤ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ ਜਦੋਂ ਟੁਕੜੇ ਸਾਰੇ ਬਰਾਬਰ ਆਕਾਰ ਦੇ ਨਾ ਹੋਣ। + ਸੇਵ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਆਉਟਪੁੱਟ ਫਾਰਮੈਟ ਅਤੇ ਗੁਣਵੱਤਾ ਚੁਣੋ ਤਾਂ ਜੋ ਹਰੇਕ ਤਿਆਰ ਕੀਤਾ ਟੁਕੜਾ ਇੱਕੋ ਨਿਰਯਾਤ ਨਿਯਮਾਂ ਦੀ ਵਰਤੋਂ ਕਰੇ। + ਚਿੱਤਰ ਦੀਆਂ ਪੱਟੀਆਂ ਕੱਟੋ + ਖੜ੍ਹਵੇਂ ਜਾਂ ਲੇਟਵੇਂ ਖੇਤਰਾਂ ਨੂੰ ਹਟਾਓ ਅਤੇ ਬਾਕੀ ਬਚੇ ਹਿੱਸਿਆਂ ਨੂੰ ਦੁਬਾਰਾ ਇਕੱਠੇ ਮਿਲਾਓ + ਇੱਕ ਅੰਤਰ ਛੱਡੇ ਬਿਨਾਂ ਇੱਕ ਖੇਤਰ ਨੂੰ ਹਟਾਓ + ਚਿੱਤਰ ਕੱਟਣਾ ਆਮ ਫਸਲ ਨਾਲੋਂ ਵੱਖਰਾ ਹੈ। ਇਹ ਇੱਕ ਚੁਣੀ ਹੋਈ ਲੰਬਕਾਰੀ ਜਾਂ ਲੇਟਵੀਂ ਪੱਟੀ ਨੂੰ ਹਟਾ ਸਕਦਾ ਹੈ, ਫਿਰ ਬਾਕੀ ਬਚੇ ਚਿੱਤਰ ਭਾਗਾਂ ਨੂੰ ਇਕੱਠੇ ਮਿਲ ਸਕਦਾ ਹੈ। ਉਲਟ ਮੋਡ ਇਸ ਦੀ ਬਜਾਏ ਚੁਣੇ ਹੋਏ ਖੇਤਰ ਨੂੰ ਰੱਖਦਾ ਹੈ, ਅਤੇ ਦੋਵੇਂ ਉਲਟ ਦਿਸ਼ਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਨਾਲ ਚੁਣੇ ਹੋਏ ਆਇਤਕਾਰ ਨੂੰ ਰੱਖਿਆ ਜਾਂਦਾ ਹੈ। + ਸਾਈਡ ਖੇਤਰਾਂ, ਕਾਲਮਾਂ ਜਾਂ ਵਿਚਕਾਰਲੀਆਂ ਪੱਟੀਆਂ ਲਈ ਲੰਬਕਾਰੀ ਕੱਟਣ ਦੀ ਵਰਤੋਂ ਕਰੋ; ਬੈਨਰਾਂ, ਗੈਪਾਂ ਜਾਂ ਕਤਾਰਾਂ ਲਈ ਹਰੀਜੱਟਲ ਕਟਿੰਗ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਜਦੋਂ ਤੁਸੀਂ ਚੁਣੇ ਹੋਏ ਹਿੱਸੇ ਨੂੰ ਹਟਾਉਣ ਦੀ ਬਜਾਏ ਇਸ ਨੂੰ ਰੱਖਣਾ ਚਾਹੁੰਦੇ ਹੋ ਤਾਂ ਇੱਕ ਧੁਰੇ \'ਤੇ ਉਲਟ ਨੂੰ ਚਾਲੂ ਕਰੋ। + ਕੱਟਣ ਤੋਂ ਬਾਅਦ ਕਿਨਾਰਿਆਂ ਦੀ ਪੂਰਵਦਰਸ਼ਨ ਕਰੋ ਕਿਉਂਕਿ ਹਟਾਏ ਗਏ ਖੇਤਰ ਮਹੱਤਵਪੂਰਨ ਵੇਰਵਿਆਂ ਨੂੰ ਪਾਰ ਕਰਨ \'ਤੇ ਵਿਲੀਨ ਸਮੱਗਰੀ ਅਚਾਨਕ ਦਿਖਾਈ ਦੇ ਸਕਦੀ ਹੈ। + ਆਉਟਪੁੱਟ ਫਾਰਮੈਟ ਦੀ ਚੋਣ ਕਰੋ + ਸਮੱਗਰੀ ਅਤੇ ਮੰਜ਼ਿਲ ਦੇ ਆਧਾਰ \'ਤੇ JPEG, PNG, WebP, AVIF, JXL, ਜਾਂ PDF ਚੁਣੋ + ਮੰਜ਼ਿਲ ਨੂੰ ਫਾਰਮੈਟ ਦਾ ਫੈਸਲਾ ਕਰਨ ਦਿਓ + ਸਭ ਤੋਂ ਵਧੀਆ ਫਾਰਮੈਟ ਇਸ ਗੱਲ \'ਤੇ ਨਿਰਭਰ ਕਰਦਾ ਹੈ ਕਿ ਚਿੱਤਰ ਵਿੱਚ ਕੀ ਹੈ ਅਤੇ ਇਹ ਕਿੱਥੇ ਵਰਤਿਆ ਜਾਵੇਗਾ। ਫੋਟੋਆਂ, ਸਕ੍ਰੀਨਸ਼ੌਟਸ, ਪਾਰਦਰਸ਼ੀ ਸੰਪਤੀਆਂ, ਦਸਤਾਵੇਜ਼, ਅਤੇ ਐਨੀਮੇਟਡ ਫਾਈਲਾਂ ਸਾਰੇ ਵੱਖ-ਵੱਖ ਤਰੀਕਿਆਂ ਨਾਲ ਅਸਫਲ ਹੋ ਜਾਂਦੀਆਂ ਹਨ ਜਦੋਂ ਗਲਤ ਫਾਰਮੈਟ ਵਿੱਚ ਮਜਬੂਰ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। + ਜਦੋਂ ਪਾਰਦਰਸ਼ਤਾ ਅਤੇ ਸਹੀ ਪਿਕਸਲ ਦੀ ਲੋੜ ਨਾ ਹੋਵੇ ਤਾਂ ਵਿਆਪਕ ਫੋਟੋ ਅਨੁਕੂਲਤਾ ਲਈ JPEG ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਤਿੱਖੇ ਸਕ੍ਰੀਨਸ਼ਾਟ, UI ਕੈਪਚਰ, ਪਾਰਦਰਸ਼ੀ ਗ੍ਰਾਫਿਕਸ, ਅਤੇ ਨੁਕਸਾਨ ਰਹਿਤ ਸੰਪਾਦਨਾਂ ਲਈ PNG ਦੀ ਵਰਤੋਂ ਕਰੋ। + WebP, AVIF, ਜਾਂ JXL ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਸੰਖੇਪ ਆਧੁਨਿਕ ਆਉਟਪੁੱਟ ਮਾਇਨੇ ਰੱਖਦਾ ਹੈ ਅਤੇ ਪ੍ਰਾਪਤ ਕਰਨ ਵਾਲੀ ਐਪ ਇਸਦਾ ਸਮਰਥਨ ਕਰਦੀ ਹੈ। + ਆਡੀਓ ਕਵਰ ਐਕਸਟਰੈਕਟ ਕਰੋ + ਆਡੀਓ ਫਾਈਲਾਂ ਤੋਂ ਏਮਬੈਡਡ ਐਲਬਮ ਆਰਟ ਜਾਂ ਉਪਲਬਧ ਮੀਡੀਆ ਫਰੇਮਾਂ ਨੂੰ PNG ਚਿੱਤਰਾਂ ਵਜੋਂ ਸੁਰੱਖਿਅਤ ਕਰੋ + ਆਰਟਵਰਕ ਨੂੰ ਆਡੀਓ ਫਾਈਲਾਂ ਵਿੱਚੋਂ ਬਾਹਰ ਕੱਢੋ + ਆਡੀਓ ਕਵਰ ਔਡੀਓ ਫਾਈਲਾਂ ਤੋਂ ਏਮਬੈਡਡ ਆਰਟਵਰਕ ਨੂੰ ਪੜ੍ਹਨ ਲਈ ਐਂਡਰਾਇਡ ਮੀਡੀਆ ਮੈਟਾਡੇਟਾ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹਨ। ਜਦੋਂ ਏਮਬੈਡਡ ਆਰਟਵਰਕ ਉਪਲਬਧ ਨਹੀਂ ਹੁੰਦਾ ਹੈ, ਤਾਂ ਰੀਟ੍ਰੀਵਰ ਇੱਕ ਮੀਡੀਆ ਫਰੇਮ ਵਿੱਚ ਵਾਪਸ ਆ ਸਕਦਾ ਹੈ ਜੇਕਰ ਐਂਡਰਾਇਡ ਇੱਕ ਪ੍ਰਦਾਨ ਕਰ ਸਕਦਾ ਹੈ; ਜੇਕਰ ਕੋਈ ਵੀ ਮੌਜੂਦ ਨਹੀਂ ਹੈ, ਤਾਂ ਟੂਲ ਰਿਪੋਰਟ ਕਰਦਾ ਹੈ ਕਿ ਐਕਸਟਰੈਕਟ ਕਰਨ ਲਈ ਕੋਈ ਚਿੱਤਰ ਨਹੀਂ ਹੈ। + ਆਡੀਓ ਕਵਰ ਖੋਲ੍ਹੋ ਅਤੇ ਉਮੀਦ ਕੀਤੀ ਕਵਰ ਆਰਟ ਨਾਲ ਇੱਕ ਜਾਂ ਵੱਧ ਆਡੀਓ ਫਾਈਲਾਂ ਦੀ ਚੋਣ ਕਰੋ। + ਸੁਰੱਖਿਅਤ ਕਰਨ ਜਾਂ ਸਾਂਝਾ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਐਕਸਟਰੈਕਟ ਕੀਤੇ ਕਵਰ ਦੀ ਸਮੀਖਿਆ ਕਰੋ, ਖਾਸ ਕਰਕੇ ਸਟ੍ਰੀਮਿੰਗ ਜਾਂ ਰਿਕਾਰਡਿੰਗ ਐਪਾਂ ਤੋਂ ਫਾਈਲਾਂ ਲਈ। + ਜੇਕਰ ਕੋਈ ਚਿੱਤਰ ਨਹੀਂ ਮਿਲਦਾ ਹੈ, ਤਾਂ ਆਡੀਓ ਫਾਈਲ ਵਿੱਚ ਸੰਭਾਵਤ ਤੌਰ \'ਤੇ ਕੋਈ ਏਮਬੈਡਡ ਕਵਰ ਨਹੀਂ ਹੈ ਜਾਂ Android ਇੱਕ ਉਪਯੋਗੀ ਫ੍ਰੇਮ ਨੂੰ ਉਜਾਗਰ ਨਹੀਂ ਕਰ ਸਕਦਾ ਹੈ। + ਮਿਤੀਆਂ ਅਤੇ ਸਥਾਨ ਮੈਟਾਡੇਟਾ + ਇਹ ਫੈਸਲਾ ਕਰੋ ਕਿ ਜਦੋਂ ਕੈਪਚਰ ਟਾਈਮ ਮਾਅਨੇ ਰੱਖਦਾ ਹੈ ਤਾਂ ਕੀ ਸੁਰੱਖਿਅਤ ਕਰਨਾ ਹੈ ਪਰ GPS ਜਾਂ ਡਿਵਾਈਸ ਡੇਟਾ ਲੀਕ ਨਹੀਂ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ + ਲਾਭਦਾਇਕ ਮੈਟਾਡੇਟਾ ਨੂੰ ਨਿੱਜੀ ਮੈਟਾਡੇਟਾ ਤੋਂ ਵੱਖ ਕਰੋ + ਮੈਟਾਡੇਟਾ ਸਾਰੇ ਬਰਾਬਰ ਜੋਖਮ ਭਰੇ ਨਹੀਂ ਹਨ। ਕੈਪਚਰ ਮਿਤੀਆਂ ਪੁਰਾਲੇਖਾਂ ਅਤੇ ਛਾਂਟਣ ਲਈ ਉਪਯੋਗੀ ਹੋ ਸਕਦੀਆਂ ਹਨ, ਜਦੋਂ ਕਿ GPS, ਡਿਵਾਈਸ ਸੀਰੀਅਲ-ਵਰਗੇ ਖੇਤਰ, ਸੌਫਟਵੇਅਰ ਟੈਗ, ਜਾਂ ਮਾਲਕ ਖੇਤਰ ਇਰਾਦੇ ਤੋਂ ਵੱਧ ਪ੍ਰਗਟ ਕਰ ਸਕਦੇ ਹਨ। + ਜਦੋਂ ਐਲਬਮਾਂ, ਸਕੈਨ ਜਾਂ ਪ੍ਰੋਜੈਕਟ ਇਤਿਹਾਸ ਲਈ ਕਾਲਕ੍ਰਮਿਕ ਕ੍ਰਮ ਮਹੱਤਵਪੂਰਨ ਹੋਵੇ ਤਾਂ ਤਾਰੀਖ ਅਤੇ ਸਮਾਂ ਟੈਗ ਰੱਖੋ। + ਜਨਤਕ ਸਾਂਝਾਕਰਨ ਜਾਂ ਅਜਨਬੀਆਂ ਨੂੰ ਤਸਵੀਰਾਂ ਭੇਜਣ ਤੋਂ ਪਹਿਲਾਂ GPS ਅਤੇ ਡਿਵਾਈਸ-ਪਛਾਣ ਵਾਲੇ ਟੈਗ ਹਟਾਓ। + ਇੱਕ ਨਮੂਨਾ ਨਿਰਯਾਤ ਕਰੋ ਅਤੇ ਗੋਪਨੀਯਤਾ ਲੋੜਾਂ ਸਖ਼ਤ ਹੋਣ \'ਤੇ EXIF ​​ਦੀ ਦੁਬਾਰਾ ਜਾਂਚ ਕਰੋ। + ਫਾਈਲ ਨਾਮ ਦੇ ਟਕਰਾਅ ਤੋਂ ਬਚੋ + ਜਦੋਂ ਬਹੁਤ ਸਾਰੀਆਂ ਫਾਈਲਾਂ image.jpg ਜਾਂ screenshot.png ਵਰਗੇ ਨਾਮ ਸਾਂਝੇ ਕਰਦੀਆਂ ਹਨ ਤਾਂ ਬੈਚ ਆਊਟਪੁੱਟਾਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ + ਹਰੇਕ ਆਉਟਪੁੱਟ ਨਾਮ ਨੂੰ ਵਿਲੱਖਣ ਬਣਾਓ + ਜਦੋਂ ਚਿੱਤਰ ਮੈਸੇਂਜਰਾਂ, ਸਕ੍ਰੀਨਸ਼ਾਟ, ਕਲਾਉਡ ਫੋਲਡਰਾਂ, ਜਾਂ ਮਲਟੀਪਲ ਕੈਮਰਿਆਂ ਤੋਂ ਆਉਂਦੇ ਹਨ ਤਾਂ ਡੁਪਲੀਕੇਟ ਫਾਈਲਨਾਮ ਆਮ ਹੁੰਦੇ ਹਨ। ਇੱਕ ਚੰਗਾ ਪੈਟਰਨ ਦੁਰਘਟਨਾ ਨੂੰ ਬਦਲਣ ਤੋਂ ਰੋਕਦਾ ਹੈ ਅਤੇ ਆਉਟਪੁੱਟ ਨੂੰ ਉਹਨਾਂ ਦੇ ਸਰੋਤਾਂ ਵਿੱਚ ਵਾਪਸ ਟਰੇਸ ਕਰਨ ਯੋਗ ਰੱਖਦਾ ਹੈ। + ਸੰਪਾਦਿਤ ਕਾਪੀ ਪਛਾਣਨਯੋਗ ਹੋਣ \'ਤੇ ਅਸਲੀ ਨਾਮ ਅਤੇ ਪਿਛੇਤਰ ਸ਼ਾਮਲ ਕਰੋ। + ਵੱਡੇ ਮਿਸ਼ਰਤ ਬੈਚਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਦੇ ਸਮੇਂ ਕ੍ਰਮ, ਟਾਈਮਸਟੈਂਪ, ਪ੍ਰੀਸੈੱਟ, ਜਾਂ ਮਾਪ ਸ਼ਾਮਲ ਕਰੋ। + ਓਵਰਰਾਈਟ ਵਰਕਫਲੋ ਤੋਂ ਬਚੋ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਇਹ ਪੁਸ਼ਟੀ ਨਹੀਂ ਕਰ ਲੈਂਦੇ ਕਿ ਨਾਮਕਰਨ ਪੈਟਰਨ ਟਕਰਾ ਨਹੀਂ ਸਕਦਾ। + ਸੁਰੱਖਿਅਤ ਕਰੋ ਜਾਂ ਸਾਂਝਾ ਕਰੋ + ਇੱਕ ਸਥਾਈ ਫਾਈਲ ਅਤੇ ਕਿਸੇ ਹੋਰ ਐਪ ਲਈ ਇੱਕ ਅਸਥਾਈ ਹੈਂਡਆਫ ਵਿੱਚੋਂ ਚੁਣੋ + ਸਹੀ ਇਰਾਦੇ ਨਾਲ ਨਿਰਯਾਤ ਕਰੋ + ਸੇਵ ਅਤੇ ਸ਼ੇਅਰ ਸਮਾਨ ਮਹਿਸੂਸ ਕਰ ਸਕਦੇ ਹਨ, ਪਰ ਉਹ ਵੱਖੋ ਵੱਖਰੀਆਂ ਸਮੱਸਿਆਵਾਂ ਨੂੰ ਹੱਲ ਕਰਦੇ ਹਨ। ਸੰਭਾਲਣ ਨਾਲ ਇੱਕ ਫਾਈਲ ਬਣ ਜਾਂਦੀ ਹੈ ਜੋ ਤੁਸੀਂ ਬਾਅਦ ਵਿੱਚ ਲੱਭ ਸਕਦੇ ਹੋ; ਸ਼ੇਅਰਿੰਗ ਐਂਡਰੌਇਡ ਰਾਹੀਂ ਕਿਸੇ ਹੋਰ ਐਪ ਨੂੰ ਤਿਆਰ ਨਤੀਜਾ ਭੇਜਦੀ ਹੈ ਅਤੇ ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਟਿਕਾਊ ਸਥਾਨਕ ਕਾਪੀ ਨਾ ਛੱਡੇ। + ਸੇਵ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਆਉਟਪੁੱਟ ਨੂੰ ਇੱਕ ਜਾਣੇ-ਪਛਾਣੇ ਫੋਲਡਰ ਵਿੱਚ ਰਹਿਣਾ ਚਾਹੀਦਾ ਹੈ, ਬੈਕਅੱਪ ਲਿਆ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ, ਜਾਂ ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਵਰਤਿਆ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ। + ਤਤਕਾਲ ਸੁਨੇਹਿਆਂ, ਈਮੇਲ ਅਟੈਚਮੈਂਟਾਂ, ਸਮਾਜਿਕ ਪੋਸਟਿੰਗ, ਜਾਂ ਕਿਸੇ ਹੋਰ ਸੰਪਾਦਕ ਨੂੰ ਨਤੀਜਾ ਭੇਜਣ ਲਈ ਸ਼ੇਅਰ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਪਹਿਲਾਂ ਬਚਾਓ ਜਦੋਂ ਪ੍ਰਾਪਤ ਕਰਨ ਵਾਲਾ ਐਪ ਭਰੋਸੇਯੋਗ ਨਹੀਂ ਹੁੰਦਾ ਜਾਂ ਜਦੋਂ ਇੱਕ ਅਸਫਲ ਸ਼ੇਅਰ ਦੁਬਾਰਾ ਬਣਾਉਣ ਲਈ ਤੰਗ ਕਰਨ ਵਾਲਾ ਹੁੰਦਾ ਹੈ। + PDF ਪੰਨੇ ਦਾ ਆਕਾਰ ਅਤੇ ਹਾਸ਼ੀਏ + ਦਸਤਾਵੇਜ਼ ਬਣਾਉਣ ਤੋਂ ਪਹਿਲਾਂ ਕਾਗਜ਼ ਦਾ ਆਕਾਰ, ਫਿੱਟ ਵਿਹਾਰ ਅਤੇ ਸਾਹ ਲੈਣ ਦਾ ਕਮਰਾ ਚੁਣੋ + ਚਿੱਤਰ ਪੰਨਿਆਂ ਨੂੰ ਛਪਣਯੋਗ ਅਤੇ ਪੜ੍ਹਨਯੋਗ ਬਣਾਓ + ਚਿੱਤਰ ਅਜੀਬ PDF ਪੰਨੇ ਬਣ ਸਕਦੇ ਹਨ ਜਦੋਂ ਉਹਨਾਂ ਦਾ ਆਕਾਰ ਚੁਣੇ ਹੋਏ ਕਾਗਜ਼ ਦੇ ਆਕਾਰ ਨਾਲ ਮੇਲ ਨਹੀਂ ਖਾਂਦਾ। ਮਾਰਜਿਨ, ਫਿੱਟ ਮੋਡ, ਅਤੇ ਪੇਜ ਓਰੀਐਂਟੇਸ਼ਨ ਇਹ ਫੈਸਲਾ ਕਰਦੇ ਹਨ ਕਿ ਕੀ ਸਮੱਗਰੀ ਕ੍ਰੌਪ ਕੀਤੀ ਗਈ ਹੈ, ਛੋਟੀ ਹੈ, ਫੈਲੀ ਹੋਈ ਹੈ, ਜਾਂ ਪੜ੍ਹਨ ਲਈ ਆਰਾਮਦਾਇਕ ਹੈ। + ਜਦੋਂ ਪੀਡੀਐਫ ਨੂੰ ਪ੍ਰਿੰਟ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ ਜਾਂ ਫਾਰਮ ਵਿੱਚ ਜਮ੍ਹਾ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ ਤਾਂ ਅਸਲ ਕਾਗਜ਼ ਦੇ ਆਕਾਰ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਸਕੈਨ ਅਤੇ ਸਕ੍ਰੀਨਸ਼ੌਟਸ ਲਈ ਹਾਸ਼ੀਏ ਦੀ ਵਰਤੋਂ ਕਰੋ ਤਾਂ ਕਿ ਟੈਕਸਟ ਪੰਨੇ ਦੇ ਕਿਨਾਰੇ ਦੇ ਵਿਰੁੱਧ ਨਾ ਬੈਠ ਸਕੇ। + ਪੋਰਟਰੇਟ ਅਤੇ ਲੈਂਡਸਕੇਪ ਪੰਨਿਆਂ ਦਾ ਪੂਰਵਦਰਸ਼ਨ ਇਕੱਠੇ ਕਰੋ ਜਦੋਂ ਸਰੋਤ ਚਿੱਤਰਾਂ ਵਿੱਚ ਮਿਸ਼ਰਤ ਸਥਿਤੀ ਹੋਵੇ। + ਸਕੈਨ ਸਾਫ਼ ਕਰੋ + PDF ਜਾਂ OCR ਤੋਂ ਪਹਿਲਾਂ ਕਾਗਜ਼ ਦੇ ਕਿਨਾਰਿਆਂ, ਸ਼ੈਡੋਜ਼, ਕੰਟ੍ਰਾਸਟ, ਰੋਟੇਸ਼ਨ ਅਤੇ ਪੜ੍ਹਨਯੋਗਤਾ ਵਿੱਚ ਸੁਧਾਰ ਕਰੋ + ਆਰਕਾਈਵ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਪੰਨੇ ਤਿਆਰ ਕਰੋ + ਇੱਕ ਸਕੈਨ ਨੂੰ ਤਕਨੀਕੀ ਤੌਰ \'ਤੇ ਕੈਪਚਰ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ ਪਰ ਪੜ੍ਹਨਾ ਅਜੇ ਵੀ ਔਖਾ ਹੈ। PDF ਨਿਰਯਾਤ ਤੋਂ ਪਹਿਲਾਂ ਛੋਟੇ ਸਫ਼ਾਈ ਕਦਮ ਅਕਸਰ ਕੰਪਰੈਸ਼ਨ ਸੈਟਿੰਗਾਂ ਨਾਲੋਂ ਜ਼ਿਆਦਾ ਮਾਇਨੇ ਰੱਖਦੇ ਹਨ, ਖਾਸ ਤੌਰ \'ਤੇ ਰਸੀਦਾਂ, ਹੱਥ ਲਿਖਤ ਨੋਟਸ, ਅਤੇ ਘੱਟ ਰੋਸ਼ਨੀ ਵਾਲੇ ਪੰਨਿਆਂ ਲਈ। + ਸਹੀ ਦ੍ਰਿਸ਼ਟੀਕੋਣ ਅਤੇ ਟੇਬਲ ਦੇ ਕਿਨਾਰਿਆਂ, ਉਂਗਲਾਂ, ਪਰਛਾਵੇਂ ਅਤੇ ਬੈਕਗ੍ਰਾਊਂਡ ਕਲਟਰ ਨੂੰ ਕੱਟੋ। + ਧਿਆਨ ਨਾਲ ਕੰਟ੍ਰਾਸਟ ਵਧਾਓ ਤਾਂ ਕਿ ਸਟੈਂਪ, ਦਸਤਖਤਾਂ ਜਾਂ ਪੈਨਸਿਲ ਦੇ ਨਿਸ਼ਾਨਾਂ ਨੂੰ ਨਸ਼ਟ ਕੀਤੇ ਬਿਨਾਂ ਟੈਕਸਟ ਸਪੱਸ਼ਟ ਹੋ ਜਾਵੇ। + ਪੰਨਾ ਸਿੱਧਾ ਅਤੇ ਪੜ੍ਹਨਯੋਗ ਹੋਣ ਤੋਂ ਬਾਅਦ ਹੀ OCR ਚਲਾਓ, ਫਿਰ ਹੱਥੀਂ ਮਹੱਤਵਪੂਰਨ ਨੰਬਰਾਂ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ। + ਪੀਡੀਐਫ ਨੂੰ ਸੰਕੁਚਿਤ, ਗ੍ਰੇਸਕੇਲ, ਜਾਂ ਮੁਰੰਮਤ ਕਰੋ + ਹਲਕੀ, ਛਪਣਯੋਗ, ਜਾਂ ਮੁੜ-ਬਣਾਈ ਦਸਤਾਵੇਜ਼ ਕਾਪੀਆਂ ਲਈ ਇੱਕ-ਫਾਈਲ PDF ਓਪਰੇਸ਼ਨਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ + PDF ਕਲੀਨਅੱਪ ਓਪਰੇਸ਼ਨ ਚੁਣੋ + PDF ਟੂਲਸ ਵਿੱਚ ਕੰਪਰੈਸ਼ਨ, ਗ੍ਰੇਸਕੇਲ ਪਰਿਵਰਤਨ, ਅਤੇ ਮੁਰੰਮਤ ਲਈ ਵੱਖਰੇ ਓਪਰੇਸ਼ਨ ਸ਼ਾਮਲ ਹਨ। ਕੰਪਰੈਸ਼ਨ ਅਤੇ ਗ੍ਰੇਸਕੇਲ ਮੁੱਖ ਤੌਰ \'ਤੇ ਏਮਬੈਡਡ ਚਿੱਤਰਾਂ ਦੁਆਰਾ ਕੰਮ ਕਰਦੇ ਹਨ, ਜਦੋਂ ਕਿ ਮੁਰੰਮਤ ਪੀਡੀਐਫ ਢਾਂਚੇ ਨੂੰ ਦੁਬਾਰਾ ਬਣਾਉਂਦੀ ਹੈ; ਇਹਨਾਂ ਵਿੱਚੋਂ ਕਿਸੇ ਨੂੰ ਵੀ ਹਰ ਦਸਤਾਵੇਜ਼ ਲਈ ਗਾਰੰਟੀਸ਼ੁਦਾ ਫਿਕਸ ਨਹੀਂ ਮੰਨਿਆ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ। + ਸੰਕੁਚਿਤ PDF ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਦਸਤਾਵੇਜ਼ ਵਿੱਚ ਸ਼ੇਅਰ ਕਰਨ ਲਈ ਚਿੱਤਰ ਅਤੇ ਫਾਈਲ ਆਕਾਰ ਦੇ ਮਾਮਲੇ ਸ਼ਾਮਲ ਹੋਣ। + ਗ੍ਰੇਸਕੇਲ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਦਸਤਾਵੇਜ਼ ਨੂੰ ਛਾਪਣਾ ਆਸਾਨ ਹੋਵੇ ਜਾਂ ਰੰਗ ਚਿੱਤਰਾਂ ਦੀ ਲੋੜ ਨਾ ਹੋਵੇ। + ਜਦੋਂ ਕੋਈ ਦਸਤਾਵੇਜ਼ ਖੋਲ੍ਹਣ ਵਿੱਚ ਅਸਫਲ ਹੁੰਦਾ ਹੈ ਜਾਂ ਅੰਦਰੂਨੀ ਬਣਤਰ ਨੂੰ ਨੁਕਸਾਨ ਪਹੁੰਚਦਾ ਹੈ, ਤਾਂ ਇੱਕ ਕਾਪੀ \'ਤੇ ਮੁਰੰਮਤ ਪੀਡੀਐਫ ਦੀ ਵਰਤੋਂ ਕਰੋ, ਫਿਰ ਦੁਬਾਰਾ ਬਣਾਈ ਗਈ ਫਾਈਲ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ। + PDF ਤੋਂ ਚਿੱਤਰ ਐਕਸਟਰੈਕਟ ਕਰੋ + ਏਮਬੈਡਡ PDF ਚਿੱਤਰਾਂ ਨੂੰ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰੋ ਅਤੇ ਐਕਸਟਰੈਕਟ ਕੀਤੇ ਨਤੀਜਿਆਂ ਨੂੰ ਇੱਕ ਪੁਰਾਲੇਖ ਵਿੱਚ ਪੈਕ ਕਰੋ + ਦਸਤਾਵੇਜ਼ਾਂ ਤੋਂ ਸਰੋਤ ਚਿੱਤਰਾਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ + ਐਬਸਟਰੈਕਟ ਈਮੇਜ਼ ਏਮਬੈਡਡ ਚਿੱਤਰ ਆਬਜੈਕਟ ਲਈ PDF ਨੂੰ ਸਕੈਨ ਕਰਦਾ ਹੈ, ਜਿੱਥੇ ਵੀ ਸੰਭਵ ਹੋਵੇ ਡੁਪਲੀਕੇਟ ਛੱਡਦਾ ਹੈ, ਅਤੇ ਰਿਕਵਰ ਕੀਤੇ ਚਿੱਤਰਾਂ ਨੂੰ ਤਿਆਰ ਕੀਤੇ ਜ਼ਿਪ ਆਰਕਾਈਵ ਵਿੱਚ ਸਟੋਰ ਕਰਦਾ ਹੈ। ਇਹ ਸਿਰਫ਼ ਉਹਨਾਂ ਚਿੱਤਰਾਂ ਨੂੰ ਐਕਸਟਰੈਕਟ ਕਰਦਾ ਹੈ ਜੋ ਅਸਲ ਵਿੱਚ PDF ਵਿੱਚ ਸ਼ਾਮਲ ਹਨ, ਨਾ ਕਿ ਟੈਕਸਟ, ਵੈਕਟਰ ਡਰਾਇੰਗ, ਜਾਂ ਸਕ੍ਰੀਨਸ਼ੌਟ ਦੇ ਰੂਪ ਵਿੱਚ ਹਰ ਦਿਸਣ ਵਾਲੇ ਪੰਨੇ ਨੂੰ। + ਜਦੋਂ ਤੁਹਾਨੂੰ PDF ਦੇ ਅੰਦਰ ਸਟੋਰ ਕੀਤੀਆਂ ਤਸਵੀਰਾਂ ਦੀ ਲੋੜ ਹੋਵੇ, ਜਿਵੇਂ ਕਿ ਕੈਟਾਲਾਗ ਜਾਂ ਸਕੈਨ ਕੀਤੀਆਂ ਸੰਪਤੀਆਂ ਦੀਆਂ ਫੋਟੋਆਂ ਦੀ ਲੋੜ ਹੋਵੇ ਤਾਂ ਐਕਸਟਰੈਕਟ ਚਿੱਤਰਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਜਦੋਂ ਤੁਹਾਨੂੰ ਟੈਕਸਟ ਅਤੇ ਲੇਆਉਟ ਸਮੇਤ ਪੂਰੇ ਪੰਨਿਆਂ ਦੀ ਲੋੜ ਹੋਵੇ ਤਾਂ ਇਸਦੀ ਬਜਾਏ ਚਿੱਤਰਾਂ ਲਈ PDF ਜਾਂ ਪੰਨਾ ਕੱਢਣ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਜੇਕਰ ਟੂਲ ਕੋਈ ਏਮਬੈਡਡ ਚਿੱਤਰਾਂ ਦੀ ਰਿਪੋਰਟ ਨਹੀਂ ਕਰਦਾ ਹੈ, ਤਾਂ ਪੰਨਾ ਟੈਕਸਟ/ਵੈਕਟਰ ਸਮੱਗਰੀ ਹੋ ਸਕਦਾ ਹੈ ਜਾਂ ਚਿੱਤਰਾਂ ਨੂੰ ਕੱਢਣਯੋਗ ਵਸਤੂਆਂ ਵਜੋਂ ਸਟੋਰ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ। + ਮੈਟਾਡੇਟਾ, ਫਲੈਟਿੰਗ, ਅਤੇ ਪ੍ਰਿੰਟ ਆਉਟਪੁੱਟ + ਦਸਤਾਵੇਜ਼ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਸੰਪਾਦਿਤ ਕਰੋ, ਪੰਨਿਆਂ ਨੂੰ ਰਾਸਟਰਾਈਜ਼ ਕਰੋ, ਜਾਂ ਜਾਣਬੁੱਝ ਕੇ ਕਸਟਮ ਪ੍ਰਿੰਟ ਲੇਆਉਟ ਤਿਆਰ ਕਰੋ + ਅੰਤਿਮ-ਦਸਤਾਵੇਜ਼ ਕਾਰਵਾਈ ਦੀ ਚੋਣ ਕਰੋ + PDF ਮੈਟਾਡੇਟਾ, ਫਲੈਟਨਿੰਗ, ਅਤੇ ਪ੍ਰਿੰਟ ਦੀ ਤਿਆਰੀ ਵੱਖ-ਵੱਖ ਅੰਤਮ-ਪੜਾਅ ਦੀਆਂ ਸਮੱਸਿਆਵਾਂ ਨੂੰ ਹੱਲ ਕਰਦੀ ਹੈ। ਮੈਟਾਡੇਟਾ ਦਸਤਾਵੇਜ਼ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਨਿਯੰਤਰਿਤ ਕਰਦਾ ਹੈ, ਫਲੈਟ ਪੀਡੀਐਫ ਪੰਨਿਆਂ ਨੂੰ ਘੱਟ ਸੰਪਾਦਨਯੋਗ ਆਉਟਪੁੱਟ ਵਿੱਚ ਰਾਸਟਰਾਈਜ਼ ਕਰਦਾ ਹੈ, ਅਤੇ ਪ੍ਰਿੰਟ PDF ਪੰਨੇ ਦੇ ਆਕਾਰ, ਸਥਿਤੀ, ਹਾਸ਼ੀਏ, ਅਤੇ ਪੰਨੇ-ਪ੍ਰਤੀ-ਸ਼ੀਟ ਨਿਯੰਤਰਣ ਦੇ ਨਾਲ ਇੱਕ ਨਵਾਂ ਖਾਕਾ ਤਿਆਰ ਕਰਦਾ ਹੈ। + ਜਦੋਂ ਲੇਖਕ, ਸਿਰਲੇਖ, ਕੀਵਰਡ, ਨਿਰਮਾਤਾ, ਜਾਂ ਗੋਪਨੀਯਤਾ ਸਫ਼ਾਈ ਦੇ ਮਾਮਲਿਆਂ ਵਿੱਚ ਮੈਟਾਡੇਟਾ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਫਲੈਟ ਪੀਡੀਐਫ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਦਿਖਾਈ ਦੇਣ ਵਾਲੀ ਪੰਨੇ ਦੀ ਸਮੱਗਰੀ ਨੂੰ ਵੱਖਰੀ PDF ਵਸਤੂਆਂ ਵਜੋਂ ਸੰਪਾਦਿਤ ਕਰਨਾ ਔਖਾ ਹੋ ਜਾਵੇ। + ਪ੍ਰਿੰਟ PDF ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਤੁਹਾਨੂੰ ਕਸਟਮ ਪੰਨੇ ਦੇ ਆਕਾਰ, ਸਥਿਤੀ, ਹਾਸ਼ੀਏ, ਜਾਂ ਪ੍ਰਤੀ ਸ਼ੀਟ ਕਈ ਪੰਨਿਆਂ ਦੀ ਲੋੜ ਹੋਵੇ। + OCR ਲਈ ਚਿੱਤਰ ਤਿਆਰ ਕਰੋ + OCR ਨੂੰ ਟੈਕਸਟ ਪੜ੍ਹਨ ਲਈ ਕਹਿਣ ਤੋਂ ਪਹਿਲਾਂ ਕ੍ਰੌਪ, ਰੋਟੇਸ਼ਨ, ਕੰਟ੍ਰਾਸਟ, ਡੀਨੋਇਜ਼ ਅਤੇ ਸਕੇਲ ਦੀ ਵਰਤੋਂ ਕਰੋ + OCR ਕਲੀਨਰ ਪਿਕਸਲ ਦਿਓ + OCR ਗਲਤੀਆਂ ਅਕਸਰ OCR ਇੰਜਣ ਦੀ ਬਜਾਏ ਇਨਪੁਟ ਚਿੱਤਰ ਤੋਂ ਆਉਂਦੀਆਂ ਹਨ। ਟੇਢੇ ਪੰਨੇ, ਘੱਟ ਕੰਟ੍ਰਾਸਟ, ਧੁੰਦਲਾ, ਪਰਛਾਵੇਂ, ਛੋਟੇ ਟੈਕਸਟ, ਅਤੇ ਮਿਸ਼ਰਤ ਬੈਕਗ੍ਰਾਉਂਡ ਸਾਰੇ ਮਾਨਤਾ ਗੁਣਵੱਤਾ ਨੂੰ ਘਟਾਉਂਦੇ ਹਨ। + ਟੈਕਸਟ ਖੇਤਰ \'ਤੇ ਕੱਟੋ ਅਤੇ ਚਿੱਤਰ ਨੂੰ ਘੁੰਮਾਓ ਤਾਂ ਜੋ ਰੇਖਾਵਾਂ ਮਾਨਤਾ ਤੋਂ ਪਹਿਲਾਂ ਹਰੀਜੱਟਲ ਹੋਣ। + ਕੰਟ੍ਰਾਸਟ ਵਧਾਓ ਜਾਂ ਕਲੀਨਰ ਬਲੈਕ-ਐਂਡ-ਵਾਈਟ ਵਿੱਚ ਬਦਲੋ ਤਾਂ ਹੀ ਜਦੋਂ ਇਹ ਅੱਖਰਾਂ ਨੂੰ ਪੜ੍ਹਨਾ ਆਸਾਨ ਬਣਾਉਂਦਾ ਹੈ। + ਛੋਟੇ ਟੈਕਸਟ ਨੂੰ ਮੱਧਮ ਰੂਪ ਵਿੱਚ ਮੁੜ ਆਕਾਰ ਦਿਓ, ਪਰ ਜਾਅਲੀ ਕਿਨਾਰਿਆਂ ਨੂੰ ਬਣਾਉਣ ਵਾਲੇ ਹਮਲਾਵਰ ਤਿੱਖੇ ਕਰਨ ਤੋਂ ਬਚੋ। + QR ਸਮੱਗਰੀ ਦੀ ਜਾਂਚ ਕਰੋ + ਕੋਡ \'ਤੇ ਭਰੋਸਾ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਲਿੰਕਾਂ, ਵਾਈ-ਫਾਈ ਪ੍ਰਮਾਣ ਪੱਤਰਾਂ, ਸੰਪਰਕਾਂ ਅਤੇ ਕਾਰਵਾਈਆਂ ਦੀ ਸਮੀਖਿਆ ਕਰੋ + ਡੀਕੋਡ ਕੀਤੇ ਟੈਕਸਟ ਨੂੰ ਅਵਿਸ਼ਵਾਸੀ ਇਨਪੁਟ ਵਜੋਂ ਮੰਨੋ + ਇੱਕ QR ਕੋਡ ਇੱਕ URL, ਸੰਪਰਕ ਕਾਰਡ, Wi-Fi ਪਾਸਵਰਡ, ਕੈਲੰਡਰ ਇਵੈਂਟ, ਫ਼ੋਨ ਨੰਬਰ, ਜਾਂ ਸਧਾਰਨ ਟੈਕਸਟ ਨੂੰ ਲੁਕਾ ਸਕਦਾ ਹੈ। ਕੋਡ ਦੇ ਨੇੜੇ ਦਿਖਾਈ ਦੇਣ ਵਾਲਾ ਲੇਬਲ ਅਸਲ ਪੇਲੋਡ ਨਾਲ ਮੇਲ ਨਹੀਂ ਖਾਂਦਾ, ਇਸ ਲਈ ਇਸ \'ਤੇ ਕਾਰਵਾਈ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਡੀਕੋਡ ਕੀਤੀ ਸਮੱਗਰੀ ਦੀ ਸਮੀਖਿਆ ਕਰੋ। + ਇਸ ਨੂੰ ਖੋਲ੍ਹਣ ਤੋਂ ਪਹਿਲਾਂ ਪੂਰੇ ਡੀਕੋਡ ਕੀਤੇ URL ਦੀ ਜਾਂਚ ਕਰੋ, ਖਾਸ ਕਰਕੇ ਛੋਟੇ ਜਾਂ ਅਣਜਾਣ ਡੋਮੇਨ। + ਵਾਈ-ਫਾਈ, ਸੰਪਰਕ, ਕੈਲੰਡਰ, ਫ਼ੋਨ ਅਤੇ SMS ਕੋਡਾਂ ਤੋਂ ਸਾਵਧਾਨ ਰਹੋ ਕਿਉਂਕਿ ਉਹ ਸੰਵੇਦਨਸ਼ੀਲ ਕਾਰਵਾਈਆਂ ਨੂੰ ਚਾਲੂ ਕਰ ਸਕਦੇ ਹਨ। + ਸਮੱਗਰੀ ਨੂੰ ਪਹਿਲਾਂ ਕਾਪੀ ਕਰੋ ਜਦੋਂ ਤੁਹਾਨੂੰ ਇਸਨੂੰ ਤੁਰੰਤ ਖੋਲ੍ਹਣ ਦੀ ਬਜਾਏ ਕਿਤੇ ਹੋਰ ਤਸਦੀਕ ਕਰਨ ਦੀ ਲੋੜ ਹੋਵੇ। + QR ਕੋਡ ਤਿਆਰ ਕਰੋ + ਸਾਦੇ ਟੈਕਸਟ, URL, Wi-Fi, ਸੰਪਰਕ, ਈਮੇਲ, ਫ਼ੋਨ, SMS, ਅਤੇ ਸਥਾਨ ਡੇਟਾ ਤੋਂ ਕੋਡ ਬਣਾਓ + ਸਹੀ QR ਪੇਲੋਡ ਬਣਾਓ + QR ਸਕ੍ਰੀਨ ਦਾਖਲ ਕੀਤੀ ਸਮੱਗਰੀ ਤੋਂ ਕੋਡ ਤਿਆਰ ਕਰ ਸਕਦੀ ਹੈ ਅਤੇ ਨਾਲ ਹੀ ਮੌਜੂਦਾ ਨੂੰ ਸਕੈਨ ਕਰ ਸਕਦੀ ਹੈ। ਸਟ੍ਰਕਚਰਡ QR ਕਿਸਮਾਂ ਹੋਰ ਐਪਾਂ ਨੂੰ ਸਮਝੇ ਜਾਣ ਵਾਲੇ ਫਾਰਮੈਟ ਵਿੱਚ ਡੇਟਾ ਨੂੰ ਏਨਕੋਡ ਕਰਨ ਵਿੱਚ ਮਦਦ ਕਰਦੀਆਂ ਹਨ, ਜਿਵੇਂ ਕਿ Wi-Fi ਲੌਗਇਨ ਵੇਰਵੇ, ਸੰਪਰਕ ਕਾਰਡ, ਈਮੇਲ ਖੇਤਰ, ਫ਼ੋਨ ਨੰਬਰ, SMS ਸਮੱਗਰੀ, URL, ਜਾਂ ਭੂਗੋਲਿਕ ਨਿਰਦੇਸ਼ਾਂਕ। + ਸਧਾਰਨ ਨੋਟਸ ਲਈ ਸਧਾਰਨ ਟੈਕਸਟ ਚੁਣੋ, ਜਾਂ ਜਦੋਂ ਕੋਡ ਨੂੰ Wi-Fi, ਸੰਪਰਕ, URL, ਈਮੇਲ, ਫ਼ੋਨ, SMS, ਜਾਂ ਟਿਕਾਣਾ ਡੇਟਾ ਦੇ ਤੌਰ \'ਤੇ ਖੁੱਲ੍ਹਣਾ ਚਾਹੀਦਾ ਹੈ ਤਾਂ ਇੱਕ ਢਾਂਚਾਗਤ ਕਿਸਮ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਤਿਆਰ ਕੀਤੇ ਕੋਡ ਨੂੰ ਸਾਂਝਾ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਹਰ ਖੇਤਰ ਦੀ ਜਾਂਚ ਕਰੋ ਕਿਉਂਕਿ ਦਿਖਣਯੋਗ ਪੂਰਵਦਰਸ਼ਨ ਸਿਰਫ਼ ਤੁਹਾਡੇ ਦੁਆਰਾ ਦਾਖਲ ਕੀਤੇ ਪੇਲੋਡ ਨੂੰ ਦਰਸਾਉਂਦਾ ਹੈ। + ਸੁਰੱਖਿਅਤ ਕੀਤੇ QR ਕੋਡ ਦੀ ਸਕੈਨਰ ਨਾਲ ਜਾਂਚ ਕਰੋ ਜਦੋਂ ਇਹ ਛਾਪਿਆ ਜਾਵੇਗਾ, ਜਨਤਕ ਤੌਰ \'ਤੇ ਪੋਸਟ ਕੀਤਾ ਜਾਵੇਗਾ, ਜਾਂ ਦੂਜੇ ਲੋਕਾਂ ਦੁਆਰਾ ਵਰਤਿਆ ਜਾਵੇਗਾ। + ਇੱਕ ਚੈਕਸਮ ਮੋਡ ਚੁਣੋ + ਫਾਈਲਾਂ ਜਾਂ ਟੈਕਸਟ ਤੋਂ ਹੈਸ਼ਾਂ ਦੀ ਗਣਨਾ ਕਰੋ, ਇੱਕ ਫਾਈਲ ਦੀ ਤੁਲਨਾ ਕਰੋ, ਜਾਂ ਕਈ ਫਾਈਲਾਂ ਦੀ ਬੈਚ-ਚੈੱਕ ਕਰੋ + ਸਮੱਗਰੀ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ, ਦਿੱਖ ਨਹੀਂ + ਚੈਕਸਮ ਟੂਲਸ ਵਿੱਚ ਫਾਈਲ ਹੈਸ਼ਿੰਗ, ਟੈਕਸਟ ਹੈਸ਼ਿੰਗ, ਕਿਸੇ ਜਾਣੇ-ਪਛਾਣੇ ਹੈਸ਼ ਨਾਲ ਇੱਕ ਫਾਈਲ ਦੀ ਤੁਲਨਾ ਕਰਨ ਅਤੇ ਬੈਚ ਦੀ ਤੁਲਨਾ ਕਰਨ ਲਈ ਵੱਖਰੀਆਂ ਟੈਬਾਂ ਹਨ। ਇੱਕ ਚੈਕਸਮ ਚੁਣੇ ਹੋਏ ਐਲਗੋਰਿਦਮ ਲਈ ਬਾਈਟ-ਫੋਰ-ਬਾਈਟ ਪਛਾਣ ਸਾਬਤ ਕਰਦਾ ਹੈ; ਇਹ ਸਾਬਤ ਨਹੀਂ ਕਰਦਾ ਕਿ ਦੋ ਚਿੱਤਰ ਸਿਰਫ਼ ਇੱਕੋ ਜਿਹੇ ਦਿਖਾਈ ਦਿੰਦੇ ਹਨ। + ਟਾਈਪ ਕੀਤੇ ਜਾਂ ਪੇਸਟ ਕੀਤੇ ਟੈਕਸਟ ਡੇਟਾ ਲਈ ਫਾਈਲਾਂ ਲਈ ਗਣਨਾ ਅਤੇ ਟੈਕਸਟ ਹੈਸ਼ ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਤੁਲਨਾ ਵਰਤੋ ਜਦੋਂ ਕੋਈ ਤੁਹਾਨੂੰ ਇੱਕ ਫਾਈਲ ਲਈ ਇੱਕ ਸੰਭਾਵਿਤ ਚੈੱਕਸਮ ਦਿੰਦਾ ਹੈ। + ਬੈਚ ਤੁਲਨਾ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਦੋਂ ਬਹੁਤ ਸਾਰੀਆਂ ਫਾਈਲਾਂ ਨੂੰ ਇੱਕ ਪਾਸ ਵਿੱਚ ਹੈਸ਼ ਜਾਂ ਪੁਸ਼ਟੀਕਰਨ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ, ਤਾਂ ਚੁਣੇ ਹੋਏ ਐਲਗੋਰਿਦਮ ਨੂੰ ਇਕਸਾਰ ਰੱਖੋ। + ਕਲਿੱਪਬੋਰਡ ਗੋਪਨੀਯਤਾ + ਗਲਤੀ ਨਾਲ ਨਿੱਜੀ ਕਾਪੀ ਕੀਤੇ ਡੇਟਾ ਦਾ ਪਰਦਾਫਾਸ਼ ਕੀਤੇ ਬਿਨਾਂ ਆਟੋਮੈਟਿਕ ਕਲਿੱਪਬੋਰਡ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਨਕਲ ਕੀਤੀ ਸਮੱਗਰੀ ਨੂੰ ਜਾਣਬੁੱਝ ਕੇ ਰੱਖੋ + ਕਲਿੱਪਬੋਰਡ ਆਟੋਮੇਸ਼ਨ ਸੁਵਿਧਾਜਨਕ ਹੈ, ਪਰ ਕਾਪੀ ਕੀਤੇ ਟੈਕਸਟ ਵਿੱਚ ਪਾਸਵਰਡ, ਲਿੰਕ, ਪਤੇ, ਟੋਕਨ, ਜਾਂ ਨਿੱਜੀ ਨੋਟਸ ਹੋ ਸਕਦੇ ਹਨ। ਕਲਿੱਪਬੋਰਡ-ਆਧਾਰਿਤ ਇਨਪੁਟ ਨੂੰ ਇੱਕ ਸਪੀਡ ਵਿਸ਼ੇਸ਼ਤਾ ਦੇ ਰੂਪ ਵਿੱਚ ਸਮਝੋ ਜੋ ਤੁਹਾਡੀਆਂ ਆਦਤਾਂ ਅਤੇ ਗੋਪਨੀਯਤਾ ਦੀਆਂ ਉਮੀਦਾਂ ਨਾਲ ਮੇਲ ਖਾਂਦਾ ਹੈ। + ਜੇਕਰ ਨਿੱਜੀ ਟੈਕਸਟ ਟੂਲਸ ਵਿੱਚ ਅਚਾਨਕ ਦਿਖਾਈ ਦਿੰਦਾ ਹੈ ਤਾਂ ਆਟੋਮੈਟਿਕ ਕਲਿੱਪਬੋਰਡ ਵਰਤੋਂ ਨੂੰ ਅਸਮਰੱਥ ਬਣਾਓ। + ਕਲਿੱਪਬੋਰਡ-ਸਿਰਫ਼ ਸੇਵਿੰਗ ਦੀ ਵਰਤੋਂ ਉਦੋਂ ਹੀ ਕਰੋ ਜਦੋਂ ਤੁਸੀਂ ਜਾਣਬੁੱਝ ਕੇ ਨਤੀਜਾ ਸਟੋਰੇਜ ਤੋਂ ਬਚਣਾ ਚਾਹੁੰਦੇ ਹੋ। + ਸੰਵੇਦਨਸ਼ੀਲ ਟੈਕਸਟ, QR ਡੇਟਾ, ਜਾਂ ਬੇਸ64 ਪੇਲੋਡਸ ਨੂੰ ਸੰਭਾਲਣ ਤੋਂ ਬਾਅਦ ਕਲਿੱਪਬੋਰਡ ਨੂੰ ਸਾਫ਼ ਕਰੋ ਜਾਂ ਬਦਲੋ। + ਰੰਗ ਫਾਰਮੈਟ + ਕਿਤੇ ਹੋਰ ਪੇਸਟ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ HEX, RGB, HSV, ਅਲਫ਼ਾ, ਅਤੇ ਕਾਪੀ ਕੀਤੇ ਰੰਗ ਦੇ ਮੁੱਲਾਂ ਨੂੰ ਸਮਝੋ + ਉਸ ਫਾਰਮੈਟ ਨੂੰ ਕਾਪੀ ਕਰੋ ਜਿਸਦੀ ਟੀਚਾ ਉਮੀਦ ਕਰਦਾ ਹੈ + ਇੱਕੋ ਦਿਸਣ ਵਾਲੇ ਰੰਗ ਨੂੰ ਕਈ ਤਰੀਕਿਆਂ ਨਾਲ ਲਿਖਿਆ ਜਾ ਸਕਦਾ ਹੈ। ਇੱਕ ਡਿਜ਼ਾਈਨ ਟੂਲ, CSS ਖੇਤਰ, Android ਰੰਗ ਮੁੱਲ, ਜਾਂ ਚਿੱਤਰ ਸੰਪਾਦਕ ਇੱਕ ਵੱਖਰੇ ਆਰਡਰ, ਅਲਫ਼ਾ ਹੈਂਡਲਿੰਗ, ਜਾਂ ਰੰਗ ਮਾਡਲ ਦੀ ਉਮੀਦ ਕਰ ਸਕਦਾ ਹੈ। + ਵੈੱਬ, ਡਿਜ਼ਾਈਨ, ਜਾਂ ਥੀਮ ਖੇਤਰਾਂ ਵਿੱਚ ਪੇਸਟ ਕਰਦੇ ਸਮੇਂ HEX ਦੀ ਵਰਤੋਂ ਕਰੋ ਜੋ ਸੰਖੇਪ ਰੰਗ ਕੋਡਾਂ ਦੀ ਉਮੀਦ ਕਰਦੇ ਹਨ। + ਜਦੋਂ ਤੁਹਾਨੂੰ ਚੈਨਲਾਂ, ਚਮਕ, ਸੰਤ੍ਰਿਪਤਾ, ਜਾਂ ਰੰਗ ਨੂੰ ਹੱਥੀਂ ਐਡਜਸਟ ਕਰਨ ਦੀ ਲੋੜ ਹੋਵੇ ਤਾਂ RGB ਜਾਂ HSV ਦੀ ਵਰਤੋਂ ਕਰੋ। + ਜਦੋਂ ਪਾਰਦਰਸ਼ਤਾ ਮਹੱਤਵਪੂਰਨ ਹੁੰਦੀ ਹੈ ਤਾਂ ਅਲਫ਼ਾ ਦੀ ਵੱਖਰੇ ਤੌਰ \'ਤੇ ਜਾਂਚ ਕਰੋ ਕਿਉਂਕਿ ਕੁਝ ਮੰਜ਼ਿਲਾਂ ਇਸ ਨੂੰ ਨਜ਼ਰਅੰਦਾਜ਼ ਕਰਦੀਆਂ ਹਨ ਜਾਂ ਇੱਕ ਵੱਖਰੇ ਆਰਡਰ ਦੀ ਵਰਤੋਂ ਕਰਦੀਆਂ ਹਨ। + ਰੰਗ ਲਾਇਬ੍ਰੇਰੀ ਬ੍ਰਾਊਜ਼ ਕਰੋ + ਨਾਮ ਜਾਂ HEX ਦੁਆਰਾ ਨਾਮਿਤ ਰੰਗਾਂ ਦੀ ਖੋਜ ਕਰੋ ਅਤੇ ਮਨਪਸੰਦ ਨੂੰ ਸਿਖਰ ਦੇ ਨੇੜੇ ਰੱਖੋ + ਮੁੜ ਵਰਤੋਂ ਯੋਗ ਨਾਮ ਵਾਲੇ ਰੰਗ ਲੱਭੋ + ਕਲਰ ਲਾਇਬ੍ਰੇਰੀ ਇੱਕ ਨਾਮ ਦੇ ਰੰਗ ਸੰਗ੍ਰਹਿ ਨੂੰ ਲੋਡ ਕਰਦੀ ਹੈ, ਇਸਨੂੰ ਨਾਮ ਦੁਆਰਾ ਕ੍ਰਮਬੱਧ ਕਰਦੀ ਹੈ, ਰੰਗ ਦੇ ਨਾਮ ਜਾਂ HEX ਮੁੱਲ ਦੁਆਰਾ ਫਿਲਟਰ ਕਰਦੀ ਹੈ, ਅਤੇ ਮਨਪਸੰਦ ਰੰਗਾਂ ਨੂੰ ਸਟੋਰ ਕਰਦੀ ਹੈ ਤਾਂ ਜੋ ਮਹੱਤਵਪੂਰਨ ਇੰਦਰਾਜ਼ਾਂ ਤੱਕ ਪਹੁੰਚਣਾ ਆਸਾਨ ਰਹੇ। ਇਹ ਉਦੋਂ ਲਾਭਦਾਇਕ ਹੁੰਦਾ ਹੈ ਜਦੋਂ ਤੁਹਾਨੂੰ ਚਿੱਤਰ ਤੋਂ ਨਮੂਨਾ ਲੈਣ ਦੀ ਬਜਾਏ ਅਸਲੀ ਨਾਮ ਵਾਲੇ ਰੰਗ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। + ਜਦੋਂ ਤੁਸੀਂ ਪਰਿਵਾਰ ਨੂੰ ਜਾਣਦੇ ਹੋ, ਜਿਵੇਂ ਕਿ ਲਾਲ, ਨੀਲਾ, ਸਲੇਟ ਜਾਂ ਪੁਦੀਨਾ, ਰੰਗ ਦੇ ਨਾਮ ਦੁਆਰਾ ਖੋਜੋ। + HEX ਦੁਆਰਾ ਖੋਜ ਕਰੋ ਜਦੋਂ ਤੁਹਾਡੇ ਕੋਲ ਪਹਿਲਾਂ ਹੀ ਇੱਕ ਸੰਖਿਆਤਮਕ ਰੰਗ ਹੋਵੇ ਅਤੇ ਮੇਲ ਖਾਂਦੀਆਂ ਜਾਂ ਨਜ਼ਦੀਕੀ ਨਾਮ ਵਾਲੀਆਂ ਐਂਟਰੀਆਂ ਨੂੰ ਲੱਭਣਾ ਚਾਹੁੰਦੇ ਹੋ। + ਅਕਸਰ ਰੰਗਾਂ ਨੂੰ ਮਨਪਸੰਦ ਵਜੋਂ ਚਿੰਨ੍ਹਿਤ ਕਰੋ ਤਾਂ ਜੋ ਉਹ ਬ੍ਰਾਊਜ਼ਿੰਗ ਕਰਦੇ ਸਮੇਂ ਆਮ ਲਾਇਬ੍ਰੇਰੀ ਤੋਂ ਅੱਗੇ ਚਲੇ ਜਾਣ। + ਜਾਲ ਦੇ ਗਰੇਡੀਐਂਟ ਸੰਗ੍ਰਹਿ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਉਪਲਬਧ ਮੈਸ਼ ਗਰੇਡੀਐਂਟ ਸਰੋਤਾਂ ਨੂੰ ਡਾਊਨਲੋਡ ਕਰੋ ਅਤੇ ਚੁਣੀਆਂ ਗਈਆਂ ਤਸਵੀਰਾਂ ਨੂੰ ਸਾਂਝਾ ਕਰੋ + ਰਿਮੋਟ ਮੈਸ਼ ਗਰੇਡੀਐਂਟ ਸੰਪਤੀਆਂ ਦੀ ਵਰਤੋਂ ਕਰੋ + ਮੈਸ਼ ਗਰੇਡੀਐਂਟ ਇੱਕ ਰਿਮੋਟ ਸਰੋਤ ਸੰਗ੍ਰਹਿ ਲੋਡ ਕਰ ਸਕਦਾ ਹੈ, ਗੁੰਮ ਗਰੇਡੀਐਂਟ ਚਿੱਤਰਾਂ ਨੂੰ ਡਾਊਨਲੋਡ ਕਰ ਸਕਦਾ ਹੈ, ਡਾਊਨਲੋਡ ਪ੍ਰਗਤੀ ਦਿਖਾ ਸਕਦਾ ਹੈ, ਅਤੇ ਚੁਣੇ ਗਏ ਗਰੇਡੀਐਂਟ ਨੂੰ ਸਾਂਝਾ ਕਰ ਸਕਦਾ ਹੈ। ਉਪਲਬਧਤਾ ਨੈੱਟਵਰਕ ਪਹੁੰਚ ਅਤੇ ਰਿਮੋਟ ਸਰੋਤ ਸਟੋਰ \'ਤੇ ਨਿਰਭਰ ਕਰਦੀ ਹੈ, ਇਸਲਈ ਇੱਕ ਖਾਲੀ ਸੂਚੀ ਦਾ ਆਮ ਤੌਰ \'ਤੇ ਮਤਲਬ ਹੁੰਦਾ ਹੈ ਕਿ ਸਰੋਤ ਅਜੇ ਉਪਲਬਧ ਨਹੀਂ ਸਨ। + ਜਦੋਂ ਤੁਸੀਂ ਰੈਡੀਮੇਡ ਮੈਸ਼ ਗਰੇਡੀਐਂਟ ਚਿੱਤਰ ਚਾਹੁੰਦੇ ਹੋ ਤਾਂ ਗਰੇਡੀਐਂਟ ਟੂਲਸ ਤੋਂ ਮੇਸ਼ ਗਰੇਡੀਐਂਟ ਖੋਲ੍ਹੋ। + ਸੰਗ੍ਰਹਿ ਖਾਲੀ ਹੋਣ ਤੋਂ ਪਹਿਲਾਂ ਸਰੋਤ ਲੋਡ ਹੋਣ ਜਾਂ ਡਾਊਨਲੋਡ ਪ੍ਰਗਤੀ ਦੀ ਉਡੀਕ ਕਰੋ। + ਤੁਹਾਨੂੰ ਲੋੜੀਂਦੇ ਗਰੇਡੀਐਂਟ ਚੁਣੋ ਅਤੇ ਸਾਂਝਾ ਕਰੋ, ਜਾਂ ਜਦੋਂ ਤੁਸੀਂ ਹੱਥੀਂ ਕਸਟਮ ਗਰੇਡੀਐਂਟ ਬਣਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ ਤਾਂ ਗਰੇਡੀਐਂਟ ਮੇਕਰ \'ਤੇ ਵਾਪਸ ਜਾਓ। + ਸੰਪਤੀ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਤਿਆਰ ਕੀਤਾ + ਗਰੇਡੀਐਂਟ, ਸ਼ੋਰ, ਵਾਲਪੇਪਰ, SVG ਵਰਗੀ ਕਲਾ, ਜਾਂ ਟੈਕਸਟ ਬਣਾਉਣ ਤੋਂ ਪਹਿਲਾਂ ਆਉਟਪੁੱਟ ਆਕਾਰ ਚੁਣੋ + ਤੁਹਾਨੂੰ ਲੋੜੀਂਦੇ ਆਕਾਰ \'ਤੇ ਤਿਆਰ ਕਰੋ + ਉਤਪੰਨ ਚਿੱਤਰਾਂ ਵਿੱਚ ਵਾਪਸ ਆਉਣ ਲਈ ਅਸਲ ਕੈਮਰਾ ਨਹੀਂ ਹੈ। ਜੇਕਰ ਆਉਟਪੁੱਟ ਬਹੁਤ ਛੋਟਾ ਹੈ, ਤਾਂ ਬਾਅਦ ਵਿੱਚ ਸਕੇਲਿੰਗ ਪੈਟਰਨਾਂ ਨੂੰ ਨਰਮ ਕਰ ਸਕਦੀ ਹੈ, ਵੇਰਵਿਆਂ ਨੂੰ ਬਦਲ ਸਕਦੀ ਹੈ, ਜਾਂ ਸੰਪਤੀ ਟਾਈਲਾਂ ਅਤੇ ਸੰਕੁਚਿਤ ਕਰਨ ਦੇ ਤਰੀਕੇ ਨੂੰ ਬਦਲ ਸਕਦੀ ਹੈ। + ਪਹਿਲਾਂ ਅੰਤਿਮ ਵਰਤੋਂ ਦੇ ਕੇਸ ਲਈ ਮਾਪ ਸੈੱਟ ਕਰੋ, ਜਿਵੇਂ ਕਿ ਵਾਲਪੇਪਰ, ਆਈਕਨ, ਬੈਕਗ੍ਰਾਊਂਡ, ਪ੍ਰਿੰਟ, ਜਾਂ ਓਵਰਲੇ। + ਟੈਕਸਟ ਲਈ ਵੱਡੇ ਆਕਾਰ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜੋ ਕਈ ਖਾਕਿਆਂ ਵਿੱਚ ਕੱਟੇ, ਜ਼ੂਮ ਕੀਤੇ ਜਾਂ ਦੁਬਾਰਾ ਵਰਤੇ ਜਾ ਸਕਦੇ ਹਨ। + ਵਿਸ਼ਾਲ ਆਉਟਪੁੱਟ ਤੋਂ ਪਹਿਲਾਂ ਟੈਸਟ ਸੰਸਕਰਣਾਂ ਨੂੰ ਨਿਰਯਾਤ ਕਰੋ ਕਿਉਂਕਿ ਪ੍ਰਕਿਰਿਆ ਸੰਬੰਧੀ ਵੇਰਵੇ ਬਦਲ ਸਕਦੇ ਹਨ ਕਿ ਕੰਪਰੈਸ਼ਨ ਕਿਵੇਂ ਵਿਵਹਾਰ ਕਰਦਾ ਹੈ। + ਮੌਜੂਦਾ ਵਾਲਪੇਪਰ ਨਿਰਯਾਤ ਕਰੋ + ਡਿਵਾਈਸ ਤੋਂ ਉਪਲਬਧ ਹੋਮ, ਲਾਕ ਅਤੇ ਬਿਲਟ-ਇਨ ਵਾਲਪੇਪਰ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰੋ + ਇੰਸਟੌਲ ਕੀਤੇ ਵਾਲਪੇਪਰ ਸੁਰੱਖਿਅਤ ਕਰੋ ਜਾਂ ਸਾਂਝੇ ਕਰੋ + ਵਾਲਪੇਪਰ ਐਕਸਪੋਰਟ ਉਹਨਾਂ ਵਾਲਪੇਪਰਾਂ ਨੂੰ ਪੜ੍ਹਦਾ ਹੈ ਜਿਨ੍ਹਾਂ ਨੂੰ ਐਂਡਰਾਇਡ ਸਿਸਟਮ ਵਾਲਪੇਪਰ API ਦੁਆਰਾ ਪ੍ਰਗਟ ਕਰਦਾ ਹੈ। ਡਿਵਾਈਸ, Android ਸੰਸਕਰਣ, ਅਨੁਮਤੀਆਂ ਅਤੇ ਬਿਲਡ ਵੇਰੀਐਂਟ \'ਤੇ ਨਿਰਭਰ ਕਰਦੇ ਹੋਏ, ਹੋਮ, ਲਾਕ, ਜਾਂ ਬਿਲਟ-ਇਨ ਵਾਲਪੇਪਰ ਵੱਖਰੇ ਤੌਰ \'ਤੇ ਉਪਲਬਧ ਹੋ ਸਕਦੇ ਹਨ ਜਾਂ ਗੁੰਮ ਹੋ ਸਕਦੇ ਹਨ। + ਸਿਸਟਮ ਐਪ ਨੂੰ ਪੜ੍ਹਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿੰਦਾ ਹੈ, ਉਹਨਾਂ ਵਾਲਪੇਪਰਾਂ ਨੂੰ ਲੋਡ ਕਰਨ ਲਈ ਵਾਲਪੇਪਰ ਐਕਸਪੋਰਟ ਖੋਲ੍ਹੋ। + ਉਪਲਬਧ ਹੋਮ, ਲਾਕ, ਜਾਂ ਬਿਲਟ-ਇਨ ਵਾਲਪੇਪਰ ਐਂਟਰੀਆਂ ਚੁਣੋ ਜਿਨ੍ਹਾਂ ਨੂੰ ਤੁਸੀਂ ਸੁਰੱਖਿਅਤ ਕਰਨਾ, ਕਾਪੀ ਕਰਨਾ ਜਾਂ ਸਾਂਝਾ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ। + ਜੇਕਰ ਕੋਈ ਵਾਲਪੇਪਰ ਗੁੰਮ ਹੈ ਜਾਂ ਵਿਸ਼ੇਸ਼ਤਾ ਉਪਲਬਧ ਨਹੀਂ ਹੈ, ਤਾਂ ਇਹ ਆਮ ਤੌਰ \'ਤੇ ਇੱਕ ਸੰਪਾਦਨ ਸੈਟਿੰਗ ਦੀ ਬਜਾਏ ਇੱਕ ਸਿਸਟਮ, ਅਨੁਮਤੀ, ਜਾਂ ਬਿਲਡ-ਵੇਰੀਐਂਟ ਸੀਮਾ ਹੁੰਦੀ ਹੈ। + ਕੋਨੇ ਐਨੀਮੇਸ਼ਨ ਥ੍ਰੋਟਲ + ਇਸ ਤਰ੍ਹਾਂ ਰੰਗ ਕਾਪੀ ਕਰੋ + HEX + ਨਾਮ + ਨਰਮ ਵਾਲਾਂ ਅਤੇ ਕਿਨਾਰੇ ਨੂੰ ਸੰਭਾਲਣ ਵਾਲੇ ਤੇਜ਼ ਵਿਅਕਤੀ ਕੱਟਆਊਟ ਲਈ ਪੋਰਟਰੇਟ ਮੈਟਿੰਗ ਮਾਡਲ। + ਜ਼ੀਰੋ-DCE++ ਕਰਵ ਅੰਦਾਜ਼ੇ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹੋਏ ਤੇਜ਼ ਘੱਟ ਰੋਸ਼ਨੀ ਸੁਧਾਰ। + ਬਰਸਾਤ ਦੀਆਂ ਲਕੀਰਾਂ ਅਤੇ ਗਿੱਲੇ-ਮੌਸਮ ਦੀਆਂ ਕਲਾਕ੍ਰਿਤੀਆਂ ਨੂੰ ਘਟਾਉਣ ਲਈ ਰੀਸਟੋਰਮਰ ਚਿੱਤਰ ਬਹਾਲੀ ਦਾ ਮਾਡਲ। + ਦਸਤਾਵੇਜ਼ ਅਣਵਰਪਿੰਗ ਮਾਡਲ ਜੋ ਇੱਕ ਕੋਆਰਡੀਨੇਟ ਗਰਿੱਡ ਦੀ ਭਵਿੱਖਬਾਣੀ ਕਰਦਾ ਹੈ ਅਤੇ ਕਰਵਡ ਪੰਨਿਆਂ ਨੂੰ ਫਲੈਟਰ ਸਕੈਨ ਵਿੱਚ ਰੀਮੈਪ ਕਰਦਾ ਹੈ। + ਮੋਸ਼ਨ ਮਿਆਦ ਦਾ ਪੈਮਾਨਾ + ਐਪ ਐਨੀਮੇਸ਼ਨ ਦੀ ਗਤੀ ਨੂੰ ਨਿਯੰਤਰਿਤ ਕਰਦਾ ਹੈ: 0 ਮੋਸ਼ਨ ਨੂੰ ਅਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ, 1 ਆਮ ਹੈ, ਉੱਚ ਮੁੱਲ ਐਨੀਮੇਸ਼ਨ ਨੂੰ ਹੌਲੀ ਕਰਦੇ ਹਨ + ਹਾਲੀਆ ਟੂਲ ਦਿਖਾਓ + ਮੁੱਖ ਸਕ੍ਰੀਨ \'ਤੇ ਹਾਲ ਹੀ ਵਿੱਚ ਵਰਤੇ ਗਏ ਟੂਲਸ ਤੱਕ ਤੁਰੰਤ ਪਹੁੰਚ ਦਿਖਾਓ + ਵਰਤੋਂ ਦੇ ਅੰਕੜੇ + ਐਪ ਓਪਨ, ਟੂਲ ਲਾਂਚ, ਅਤੇ ਤੁਹਾਡੇ ਸਭ ਤੋਂ ਵੱਧ ਵਰਤੇ ਗਏ ਟੂਲਸ ਦੀ ਪੜਚੋਲ ਕਰੋ + ਐਪ ਖੁੱਲ੍ਹਦਾ ਹੈ + ਟੂਲ ਖੁੱਲ੍ਹਦਾ ਹੈ + ਆਖਰੀ ਸੰਦ + ਸਫਲ ਬਚਤ + ਗਤੀਵਿਧੀ ਲੜੀ + ਡਾਟਾ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ + ਪ੍ਰਮੁੱਖ ਫਾਰਮੈਟ + ਟੂਲ ਵਰਤੇ ਗਏ + %1$d ਖੁੱਲਦਾ ਹੈ • %2$s + ਅਜੇ ਤੱਕ ਵਰਤੋਂ ਦੇ ਕੋਈ ਅੰਕੜੇ ਨਹੀਂ ਹਨ + ਕੁਝ ਟੂਲ ਖੋਲ੍ਹੋ ਅਤੇ ਉਹ ਇੱਥੇ ਆਪਣੇ ਆਪ ਦਿਖਾਈ ਦੇਣਗੇ + ਤੁਹਾਡੇ ਵਰਤੋਂ ਦੇ ਅੰਕੜੇ ਸਿਰਫ਼ ਤੁਹਾਡੀ ਡਿਵਾਈਸ \'ਤੇ ਸਥਾਨਕ ਤੌਰ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਜਾਂਦੇ ਹਨ। ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਉਹਨਾਂ ਦੀ ਵਰਤੋਂ ਸਿਰਫ਼ ਤੁਹਾਡੀ ਨਿੱਜੀ ਗਤੀਵਿਧੀ, ਮਨਪਸੰਦ ਟੂਲ ਅਤੇ ਸੁਰੱਖਿਅਤ ਕੀਤੇ ਡੇਟਾ ਇਨਸਾਈਟਸ ਦਿਖਾਉਣ ਲਈ ਕਰਦਾ ਹੈ + ਆਕਾਰ ਬਦਲੋ ਕਨਵਰਟ ਸਕੇਲ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਮਾਪ ਚੌੜਾਈ ਉਚਾਈ jpg jpeg png webp ਕੰਪਰੈੱਸ + ਸੰਕੁਚਿਤ ਆਕਾਰ ਭਾਰ ਬਾਈਟ mb kb ਗੁਣਵੱਤਾ ਅਨੁਕੂਲਤਾ ਨੂੰ ਘਟਾਓ + ਜਾਲ ਗਰੇਡੀਐਂਟ ਪਿਛੋਕੜ + pdf ਵੱਖਰੇ ਪੰਨਿਆਂ ਨੂੰ ਵੰਡੋ + pdf ਦਸਤਖਤ ਸਾਈਨ ਡਰਾਅ + pdf ਪੰਨਿਆਂ ਨੂੰ ਮਿਟਾਓ + ਪੀਡੀਐਫ ਪ੍ਰੀਵਿਊ ਦਰਸ਼ਕ ਪੜ੍ਹਦਾ ਹੈ + ਨਿਰਪੱਖ ਰੂਪ + ਗਲਤੀ + ਡਰਾਪ ਸ਼ੈਡੋ + ਦੰਦ ਦੀ ਉਚਾਈ + ਹਰੀਜ਼ੱਟਲ ਟੂਥ ਰੇਂਜ + ਵਰਟੀਕਲ ਟੂਥ ਰੇਂਜ + ਸਿਖਰ ਦਾ ਕਿਨਾਰਾ + ਸੱਜਾ ਕਿਨਾਰਾ + ਹੇਠਲਾ ਕਿਨਾਰਾ + ਖੱਬਾ ਕਿਨਾਰਾ + ਫਟੇ ਕਿਨਾਰੇ + ਸੰਪਾਦਕ ਫੋਟੋ ਤਸਵੀਰ ਰੀਟਚ ਐਡਜਸਟ + ਫਸਲ ਟ੍ਰਿਮ ਕੱਟ ਪੱਖ ਅਨੁਪਾਤ + ਫਿਲਟਰ ਪ੍ਰਭਾਵ ਰੰਗ ਸੁਧਾਰ ਲੂਟ ਮਾਸਕ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ + ਪੇਂਟ ਬੁਰਸ਼ ਪੈਨਸਿਲ ਐਨੋਟੇਟ ਮਾਰਕਅੱਪ ਖਿੱਚੋ + ਸਿਫਰ ਐਨਕ੍ਰਿਪਟ ਡੀਕ੍ਰਿਪਟ ਪਾਸਵਰਡ ਗੁਪਤ + ਬੈਕਗ੍ਰਾਊਂਡ ਰਿਮੂਵਰ ਮਿਟਾਓ ਕੱਟਆਊਟ ਪਾਰਦਰਸ਼ੀ ਅਲਫ਼ਾ + ਝਲਕ ਦਰਸ਼ਕ ਗੈਲਰੀ ਦਾ ਨਿਰੀਖਣ ਖੁੱਲ੍ਹਾ + ਸਟੀਚ ਮਰਜ ਕੰਬਾਈਨ ਪੈਨੋਰਾਮਾ ਲੰਬਾ ਸਕ੍ਰੀਨਸ਼ੌਟ + url ਵੈੱਬ ਡਾਊਨਲੋਡ ਇੰਟਰਨੈੱਟ ਲਿੰਕ ਚਿੱਤਰ + ਪਿੱਕਰ ਆਈਡ੍ਰੌਪਰ ਨਮੂਨਾ ਹੈਕਸ ਆਰਜੀਬੀ ਰੰਗ + ਪੈਲੇਟ ਰੰਗਾਂ ਦੇ ਸਵੈਚ ਐਬਸਟਰੈਕਟ ਪ੍ਰਭਾਵੀ ਹਨ + exif ਮੈਟਾਡੇਟਾ ਗੋਪਨੀਯਤਾ ਹਟਾਓ ਪੱਟੀ ਸਾਫ਼ GPS ਸਥਾਨ + ਬਾਅਦ ਤੋਂ ਪਹਿਲਾਂ ਅੰਤਰ ਦੀ ਤੁਲਨਾ ਕਰੋ + ਸੀਮਾ ਰੀਸਾਈਜ਼ ਅਧਿਕਤਮ ਘੱਟੋ-ਘੱਟ ਮੈਗਾਪਿਕਸਲ ਦੇ ਮਾਪ ਕਲੈਂਪ + pdf ਦਸਤਾਵੇਜ਼ ਪੰਨੇ ਟੂਲ + ocr ਟੈਕਸਟ ਪਛਾਣ ਐਕਸਟਰੈਕਟ ਸਕੈਨ + ਗਰੇਡੀਐਂਟ ਬੈਕਗ੍ਰਾਊਂਡ ਰੰਗ ਜਾਲ + ਵਾਟਰਮਾਰਕ ਲੋਗੋ ਟੈਕਸਟ ਸਟੈਂਪ ਓਵਰਲੇਅ + gif ਐਨੀਮੇਸ਼ਨ ਐਨੀਮੇਟਡ ਕਨਵਰਟ ਫਰੇਮ + apng ਐਨੀਮੇਸ਼ਨ ਐਨੀਮੇਟਡ png ਕਨਵਰਟ ਫਰੇਮ + zip ਆਰਕਾਈਵ ਕੰਪਰੈੱਸ ਪੈਕ ਅਨਪੈਕ ਫਾਈਲਾਂ + jxl jpeg xl jpg ਚਿੱਤਰ ਫਾਰਮੈਟ ਵਿੱਚ ਬਦਲੋ + svg ਵੈਕਟਰ ਟਰੇਸ ਕਨਵਰਟ xml + ਫਾਰਮੈਟ ਕਨਵਰਟ jpg jpeg png webp heic avif jxl bmp + ਦਸਤਾਵੇਜ਼ ਸਕੈਨਰ ਸਕੈਨ ਕਾਗਜ਼ ਦ੍ਰਿਸ਼ਟੀਕੋਣ ਫਸਲ + qr ਬਾਰਕੋਡ ਕੋਡ ਸਕੈਨਰ ਸਕੈਨ + ਸਟੈਕਿੰਗ ਓਵਰਲੇ ਔਸਤ ਮੱਧ ਐਸਟ੍ਰੋਫੋਟੋਗ੍ਰਾਫੀ + ਸਪਲਿਟ ਟਾਇਲਸ ਗਰਿੱਡ ਟੁਕੜੇ ਹਿੱਸੇ + ਰੰਗ ਪਰਿਵਰਤਨ hex rgb hsl hsv cmyk ਲੈਬ + webp ਕਨਵਰਟ ਐਨੀਮੇਟਡ ਚਿੱਤਰ ਫਾਰਮੈਟ + ਸ਼ੋਰ ਜਨਰੇਟਰ ਬੇਤਰਤੀਬ ਟੈਕਸਟਚਰ ਅਨਾਜ + ਕੋਲਾਜ ਗਰਿੱਡ ਖਾਕਾ ਜੋੜ + ਮਾਰਕਅੱਪ ਲੇਅਰ ਐਨੋਟੇਟ ਓਵਰਲੇ ਐਡਿਟ + ਬੇਸ 64 ਏਨਕੋਡ ਡੀਕੋਡ ਡੇਟਾ ਯੂਰੀ + ਚੈੱਕਸਮ ਹੈਸ਼ md5 sha sha1 sha256 ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ + exif ਮੈਟਾਡੇਟਾ ਜੀਪੀਐਸ ਮਿਤੀ ਕੈਮਰਾ ਸੰਪਾਦਿਤ ਕਰੋ + ਸਪਲਿਟ ਗਰਿੱਡ ਟੁਕੜੇ ਟਾਇਲ ਕੱਟ + ਆਡੀਓ ਕਵਰ ਐਲਬਮ ਆਰਟ ਸੰਗੀਤ ਮੈਟਾਡੇਟਾ + ਵਾਲਪੇਪਰ ਨਿਰਯਾਤ ਪਿਛੋਕੜ + ascii ਟੈਕਸਟ ਕਲਾ ਅੱਖਰ + ਏਆਈਐਮਐਲ ਨਿਊਰਲ ਐਨਹਾਂਸ ਅਪਸਕੇਲ ਖੰਡ + ਰੰਗ ਲਾਇਬ੍ਰੇਰੀ ਸਮੱਗਰੀ ਪੈਲੇਟ ਨਾਮ ਰੰਗ + ਸ਼ੈਡਰ ਜੀਐਲਐਸਐਲ ਫਰੈਗਮੈਂਟ ਇਫੈਕਟ ਸਟੂਡੀਓ + pdf ਮਰਜ ਕੰਬਾਈਨ join + pdf ਪੰਨਿਆਂ ਦੀ ਸਥਿਤੀ ਨੂੰ ਘੁੰਮਾਓ + pdf ਪੰਨਿਆਂ ਦੀ ਲੜੀ ਨੂੰ ਮੁੜ ਵਿਵਸਥਿਤ ਕਰੋ + pdf ਪੰਨਾ ਨੰਬਰ ਪੰਨਾਬੰਦੀ + pdf ocr ਟੈਕਸਟ ਖੋਜਣਯੋਗ ਐਬਸਟਰੈਕਟ ਨੂੰ ਪਛਾਣਦਾ ਹੈ + pdf ਵਾਟਰਮਾਰਕ ਸਟੈਂਪ ਲੋਗੋ ਟੈਕਸਟ + pdf ਪਾਸਵਰਡ ਇਨਕ੍ਰਿਪਟ ਲੌਕ ਦੀ ਰੱਖਿਆ ਕਰੋ + pdf ਅਨਲੌਕ ਪਾਸਵਰਡ ਡੀਕ੍ਰਿਪਟ ਸੁਰੱਖਿਆ ਹਟਾਓ + pdf ਕੰਪਰੈੱਸ ਘਟਾਓ ਆਕਾਰ ਅਨੁਕੂਲ + ਪੀਡੀਐਫ ਗ੍ਰੇਸਕੇਲ ਕਾਲਾ ਚਿੱਟਾ ਮੋਨੋਕ੍ਰੋਮ + ਪੀਡੀਐਫ ਮੁਰੰਮਤ ਠੀਕ ਰਿਕਵਰੀ ਟੁੱਟ ਗਈ + ਪੀਡੀਐਫ ਮੈਟਾਡੇਟਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ exif ਲੇਖਕ ਸਿਰਲੇਖ + pdf ਕ੍ਰੌਪ ਟ੍ਰਿਮ ਮਾਰਜਿਨ + pdf ਫਲੈਟ ਐਨੋਟੇਸ਼ਨ ਲੇਅਰਾਂ ਬਣਾਉਂਦੀ ਹੈ + ਪੀਡੀਐਫ ਐਕਸਟਰੈਕਟ ਚਿੱਤਰ ਤਸਵੀਰਾਂ + ਪੀਡੀਐਫ ਜ਼ਿਪ ਆਰਕਾਈਵ ਕੰਪਰੈੱਸ + pdf ਪ੍ਰਿੰਟ ਪ੍ਰਿੰਟਰ + ਚਿੱਤਰਾਂ ਨੂੰ ਪੀਡੀਐਫ ਵਿੱਚ jpg jpeg png ਦਸਤਾਵੇਜ਼ ਵਿੱਚ ਬਦਲੋ + ਚਿੱਤਰ ਪੰਨਿਆਂ ਲਈ pdf jpg png ਨੂੰ ਨਿਰਯਾਤ ਕਰੋ + ਪੀਡੀਐਫ ਐਨੋਟੇਸ਼ਨ ਟਿੱਪਣੀਆਂ ਸਾਫ਼ ਹਟਾਉਂਦੀਆਂ ਹਨ + ਵਰਤੋਂ ਦੇ ਅੰਕੜੇ ਰੀਸੈਟ ਕਰੋ + ਟੂਲ ਖੁੱਲ੍ਹਦਾ ਹੈ, ਅੰਕੜਿਆਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ, ਸਟ੍ਰੀਕ, ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਡੇਟਾ, ਅਤੇ ਚੋਟੀ ਦੇ ਫਾਰਮੈਟ ਨੂੰ ਰੀਸੈਟ ਕੀਤਾ ਜਾਵੇਗਾ। ਐਪ ਖੁੱਲ੍ਹਦਾ ਹੈ, ਬਦਲਿਆ ਨਹੀਂ ਜਾਵੇਗਾ। + ਆਡੀਓ + ਫਾਈਲ + ਪ੍ਰੋਫਾਈਲਾਂ ਨੂੰ ਨਿਰਯਾਤ ਕਰੋ + ਮੌਜੂਦਾ ਪ੍ਰੋਫਾਈਲ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ + ਨਿਰਯਾਤ ਪ੍ਰੋਫਾਈਲ ਮਿਟਾਓ + ਕੀ ਤੁਸੀਂ ਨਿਰਯਾਤ ਪ੍ਰੋਫਾਈਲ \\"%1$s\\" ਨੂੰ ਮਿਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ? + ਡਰਾਇੰਗ ਬਾਰਡਰ + ਡਰਾਇੰਗ ਅਤੇ ਮਿਟਾਉਣ ਵਾਲੇ ਕੈਨਵਸ ਦੇ ਦੁਆਲੇ ਇੱਕ ਪਤਲੀ ਬਾਰਡਰ ਦਿਖਾਓ + ਵੇਵੀ + ਨਰਮ ਵੇਵੀ ਕਿਨਾਰੇ ਵਾਲੇ ਗੋਲ UI ਤੱਤ + ਸਕੂਪ + ਨੌਚ + ਗੋਲ ਕੋਨੇ ਅੰਦਰ ਵੱਲ ਉੱਕਰੀ + ਡਬਲ ਕੱਟ ਦੇ ਨਾਲ ਸਟੈਪਡ ਕੋਨੇ + ਪਲੇਸਹੋਲਡਰ + ਬਿਨਾਂ ਐਕਸਟੈਂਸ਼ਨ ਦੇ ਅਸਲੀ ਫਾਈਲ ਨਾਮ ਪਾਉਣ ਲਈ {filename} ਦੀ ਵਰਤੋਂ ਕਰੋ + ਭੜਕਣਾ + ਮੂਲ ਰਕਮ + ਰਿੰਗ ਦੀ ਮਾਤਰਾ + ਰੇ ਦੀ ਰਕਮ + ਰਿੰਗ ਚੌੜਾਈ + ਦ੍ਰਿਸ਼ਟੀਕੋਣ ਨੂੰ ਵਿਗਾੜੋ + ਜਾਵਾ ਲੁੱਕ ਐਂਡ ਫੀਲ + ਸ਼ੀਅਰ + X ਕੋਣ + ਅਤੇ ਕੋਣ + ਆਉਟਪੁੱਟ ਦਾ ਆਕਾਰ ਬਦਲੋ + ਪਾਣੀ ਦੀ ਬੂੰਦ + ਤਰੰਗ ਲੰਬਾਈ + ਪੜਾਅ + ਹਾਈ ਪਾਸ + ਰੰਗ ਮਾਸਕ + ਕਰੋਮ + ਭੰਗ + ਕੋਮਲਤਾ + ਫੀਡਬੈਕ + ਲੈਂਸ ਬਲਰ + ਘੱਟ ਇੰਪੁੱਟ + ਉੱਚ ਇੰਪੁੱਟ + ਘੱਟ ਆਉਟਪੁੱਟ + ਉੱਚ ਆਉਟਪੁੱਟ + ਹਲਕੇ ਪ੍ਰਭਾਵ + ਬੰਪ ਦੀ ਉਚਾਈ + ਬੰਪ ਕੋਮਲਤਾ + ਤਰੰਗ + X ਐਪਲੀਟਿਊਡ + Y ਐਪਲੀਟਿਊਡ + X ਤਰੰਗ-ਲੰਬਾਈ + Y ਤਰੰਗ-ਲੰਬਾਈ + ਅਨੁਕੂਲ ਬਲਰ + ਟੈਕਸਟ ਜਨਰੇਸ਼ਨ + ਵੱਖ-ਵੱਖ ਪ੍ਰਕਿਰਿਆ ਸੰਬੰਧੀ ਟੈਕਸਟ ਤਿਆਰ ਕਰੋ ਅਤੇ ਉਹਨਾਂ ਨੂੰ ਚਿੱਤਰਾਂ ਵਜੋਂ ਸੁਰੱਖਿਅਤ ਕਰੋ + ਟੈਕਸਟ ਦੀ ਕਿਸਮ + ਪੱਖਪਾਤ + ਸਮਾਂ + ਚਮਕ + ਫੈਲਾਅ + ਨਮੂਨੇ + ਕੋਣ ਗੁਣਾਂਕ + ਗਰੇਡੀਐਂਟ ਗੁਣਾਂਕ + ਗਰਿੱਡ ਦੀ ਕਿਸਮ + ਦੂਰੀ ਦੀ ਸ਼ਕਤੀ + ਸਕੇਲ ਵਾਈ + ਧੁੰਦਲਾਪਨ + ਆਧਾਰ ਕਿਸਮ + ਗੜਬੜ ਕਾਰਕ + ਸਕੇਲਿੰਗ + ਦੁਹਰਾਓ + ਰਿੰਗ + ਬੁਰਸ਼ ਧਾਤ + ਕਾਸਟਿਕਸ + ਸੈਲੂਲਰ + ਚੈਕਰਬੋਰਡ + fBm + ਮਾਰਬਲ + ਪਲਾਜ਼ਮਾ + ਰਜਾਈ + ਲੱਕੜ + ਬੇਤਰਤੀਬ + ਵਰਗ + ਹੈਕਸਾਗੋਨਲ + ਅਸ਼ਟਭੁਜ + ਤਿਕੋਣੀ + ਰੱਜਿਆ ਹੋਇਆ + VL ਸ਼ੋਰ + SC ਸ਼ੋਰ + ਹਾਲੀਆ + ਹਾਲੇ ਤੱਕ ਕੋਈ ਹਾਲ ਹੀ ਵਿੱਚ ਵਰਤੇ ਗਏ ਫਿਲਟਰ ਨਹੀਂ ਹਨ + ਟੈਕਸਟਚਰ ਜਨਰੇਟਰ ਬੁਰਸ਼ ਮੈਟਲ ਕਾਸਟਿਕਸ ਸੈਲੂਲਰ ਚੈਕਰਬੋਰਡ ਸੰਗਮਰਮਰ ਪਲਾਜ਼ਮਾ ਰਜਾਈ ਲੱਕੜ ਇੱਟ ਕੈਮੋਫਲੇਜ ਸੈੱਲ ਕਲਾਉਡ ਕ੍ਰੈਕ ਫੈਬਰਿਕ ਫੋਲੀਏਜ ਹਨੀਕੌਂਬ ਆਈਸ ਲਾਵਾ ਨੇਬੂਲਾ ਪੇਪਰ ਜੰਗਾਲ ਰੇਤ ਦਾ ਧੂੰਆਂ ਪੱਥਰ ਭੂਮੀ ਭੂਮੀਗਤ ਪਾਣੀ ਦੀ ਲਹਿਰ + ਇੱਟ + ਕੈਮਫਲੈਜ + ਸੈੱਲ + ਬੱਦਲ + ਕਰੈਕ + ਫੈਬਰਿਕ + ਪੱਤੇ + ਹਨੀਕੋੰਬ + ਬਰਫ਼ + ਲਾਵਾ + ਨੇਬੁਲਾ + ਕਾਗਜ਼ + ਜੰਗਾਲ + ਰੇਤ + ਧੂੰਆਂ + ਪੱਥਰ + ਭੂਮੀ + ਟੌਪੋਗ੍ਰਾਫੀ + ਪਾਣੀ ਦੀ ਲਹਿਰ + ਉੱਨਤ ਲੱਕੜ + ਮੋਰਟਾਰ ਚੌੜਾਈ + ਅਨਿਯਮਿਤਤਾ + ਬੇਵਲ + ਖੁਰਦਰੀ + ਪਹਿਲੀ ਥ੍ਰੈਸ਼ਹੋਲਡ + ਦੂਜੀ ਥ੍ਰੈਸ਼ਹੋਲਡ + ਤੀਜੀ ਥ੍ਰੈਸ਼ਹੋਲਡ + ਕਿਨਾਰੇ ਦੀ ਕੋਮਲਤਾ + ਬਾਰਡਰ ਦੀ ਚੌੜਾਈ + ਕਵਰੇਜ + ਵੇਰਵੇ + ਡੂੰਘਾਈ + ਬ੍ਰਾਂਚਿੰਗ + ਲੇਟਵੇਂ ਥ੍ਰੈੱਡਸ + ਵਰਟੀਕਲ ਥ੍ਰੈੱਡਸ + ਫਜ਼ + ਨਾੜੀਆਂ + ਰੋਸ਼ਨੀ + ਚੀਰ ਦੀ ਚੌੜਾਈ + ਠੰਡ + ਪ੍ਰਵਾਹ + ਛਾਲੇ + ਬੱਦਲ ਦੀ ਘਣਤਾ + ਤਾਰੇ + ਫਾਈਬਰ ਘਣਤਾ + ਫਾਈਬਰ ਦੀ ਤਾਕਤ + ਧੱਬੇ + ਖੋਰ + ਪਿਟਿੰਗ + ਫਲੈਕਸ + ਟਿਊਨ ਬਾਰੰਬਾਰਤਾ + ਹਵਾ ਦਾ ਕੋਣ + ਲਹਿਰਾਂ + ਵਿਸਪਸ + ਨਾੜੀ ਦਾ ਪੈਮਾਨਾ + ਪਾਣੀ ਦਾ ਪੱਧਰ + ਪਹਾੜੀ ਪੱਧਰ + ਕਟਾਵ + ਬਰਫ਼ ਦਾ ਪੱਧਰ + ਲਾਈਨ ਦੀ ਗਿਣਤੀ + ਲਾਈਨ ਮੋਟਾਈ + ਸ਼ੈਡਿੰਗ + ਪੋਰ ਰੰਗ + ਮੋਰਟਾਰ ਰੰਗ + ਗੂੜ੍ਹਾ ਇੱਟ ਦਾ ਰੰਗ + ਇੱਟ ਦਾ ਰੰਗ + ਹਾਈਲਾਈਟ ਰੰਗ + ਗੂੜਾ ਰੰਗ + ਜੰਗਲ ਦਾ ਰੰਗ + ਧਰਤੀ ਦਾ ਰੰਗ + ਰੇਤ ਦਾ ਰੰਗ + ਬੈਕਗ੍ਰਾਊਂਡ ਦਾ ਰੰਗ + ਸੈੱਲ ਦਾ ਰੰਗ + ਕਿਨਾਰੇ ਦਾ ਰੰਗ + ਅਸਮਾਨੀ ਰੰਗ + ਸ਼ੈਡੋ ਰੰਗ + ਹਲਕਾ ਰੰਗ + ਸਤਹ ਦਾ ਰੰਗ + ਪਰਿਵਰਤਨ ਰੰਗ + ਕਰੈਕ ਰੰਗ + ਵਾਰਪ ਰੰਗ + ਵੇਫਟ ਰੰਗ + ਗੂੜ੍ਹੇ ਪੱਤੇ ਦਾ ਰੰਗ + ਪੱਤੇ ਦਾ ਰੰਗ + ਬਾਰਡਰ ਰੰਗ + ਸ਼ਹਿਦ ਦਾ ਰੰਗ + ਡੂੰਘਾ ਰੰਗ + ਬਰਫ਼ ਦਾ ਰੰਗ + ਠੰਡ ਦਾ ਰੰਗ + ਛਾਲੇ ਦਾ ਰੰਗ + ਰੰਗ ਧੋਵੋ + ਚਮਕਦਾਰ ਰੰਗ + ਸਪੇਸ ਰੰਗ + ਵਾਇਲੇਟ ਰੰਗ + ਨੀਲਾ ਰੰਗ + ਬੇਸ ਰੰਗ + ਫਾਈਬਰ ਰੰਗ + ਦਾਗ ਰੰਗ + ਧਾਤੂ ਰੰਗ + ਗੂੜ੍ਹਾ ਜੰਗਾਲ ਰੰਗ + ਜੰਗਾਲ ਰੰਗ + ਸੰਤਰੀ ਰੰਗ + ਧੂੰਏਂ ਦਾ ਰੰਗ + ਨਾੜੀ ਦਾ ਰੰਗ + ਨੀਵਾਂ ਰੰਗ + ਪਾਣੀ ਦਾ ਰੰਗ + ਰੌਕ ਰੰਗ + ਬਰਫ਼ ਦਾ ਰੰਗ + ਘੱਟ ਰੰਗ + ਉੱਚ ਰੰਗ + ਲਾਈਨ ਰੰਗ + ਘਟੀਆ ਰੰਗ + ਘਾਹ + ਮੈਲ + ਚਮੜਾ + ਕੰਕਰੀਟ + ਅਸਫਾਲਟ + ਮੌਸ + ਅੱਗ + ਅਰੋੜਾ + ਤੇਲ ਸਲੀਕ + ਪਾਣੀ ਦਾ ਰੰਗ + ਐਬਸਟਰੈਕਟ ਫਲੋ + ਓਪਲ + ਦਮਿਸ਼ਕ ਸਟੀਲ + ਬਿਜਲੀ + ਮਖਮਲ + ਸਿਆਹੀ ਮਾਰਬਲਿੰਗ + ਹੋਲੋਗ੍ਰਾਫਿਕ ਫੋਇਲ + ਬਾਇਓਲੂਮਿਨਿਸੈਂਸ + ਬ੍ਰਹਿਮੰਡੀ ਵੌਰਟੇਕਸ + ਲਾਵਾ ਲੈਂਪ + ਇਵੈਂਟ ਹੋਰਾਈਜ਼ਨ + ਫ੍ਰੈਕਟਲ ਬਲੂਮ + ਰੰਗੀਨ ਸੁਰੰਗ + ਗ੍ਰਹਿਣ ਕਰੋਨਾ + ਅਜੀਬ ਆਕਰਸ਼ਕ + Ferrofluid ਤਾਜ + ਸੁਪਰਨੋਵਾ + ਆਇਰਿਸ + ਮੋਰ ਦਾ ਖੰਭ + ਨਟੀਲਸ ਸ਼ੈੱਲ + ਰਿੰਗਡ ਪਲੈਨੇਟ + ਬਲੇਡ ਦੀ ਘਣਤਾ + ਬਲੇਡ ਦੀ ਲੰਬਾਈ + ਹਵਾ + ਪੈਚਿਸ + ਝੁੰਡ + ਨਮੀ + ਕੰਕਰ + ਝੁਰੜੀਆਂ + ਪੋਰਸ + ਕੁੱਲ + ਚੀਰ + ਟਾਰ + ਪਹਿਨੋ + ਧੱਬੇ + ਰੇਸ਼ੇ + ਅੱਗ ਦੀ ਬਾਰੰਬਾਰਤਾ + ਧੂੰਆਂ + ਤੀਬਰਤਾ + ਰਿਬਨ + ਬੈਂਡ + iridescence + ਹਨੇਰਾ + ਖਿੜਦਾ ਹੈ + ਰੰਗਦਾਰ + ਕਿਨਾਰੇ + ਕਾਗਜ਼ + ਪ੍ਰਸਾਰ + ਸਮਰੂਪਤਾ + ਤਿੱਖਾਪਨ + ਰੰਗ ਖੇਡ + ਦੁੱਧ + ਪਰਤਾਂ + ਫੋਲਡਿੰਗ + ਪੋਲਿਸ਼ + ਸ਼ਾਖਾਵਾਂ + ਦਿਸ਼ਾ + ਸ਼ੀਨ + ਫੋਲਡ + ਖੰਭ + ਸਿਆਹੀ ਦਾ ਸੰਤੁਲਨ + ਸਪੈਕਟ੍ਰਮ + ਚੀਕਣੀ + ਭਿੰਨਤਾ + ਹਥਿਆਰ + ਮਰੋੜ + ਕੋਰ ਗਲੋ + Blobs + ਡਿਸਕ ਝੁਕਾਓ + ਹੋਰੀਜ਼ਨ ਦਾ ਆਕਾਰ + ਡਿਸਕ ਦੀ ਚੌੜਾਈ + ਲੈਂਸਿੰਗ + ਪੇਟਲ + ਕਰਲ + ਫਿਲਿਗਰੀ + ਪਹਿਲੂ + ਵਕਰਤਾ + ਚੰਦਰਮਾ ਦਾ ਆਕਾਰ + ਕੋਰੋਨਾ ਦਾ ਆਕਾਰ + ਕਿਰਨਾਂ + ਹੀਰੇ ਦੀ ਰਿੰਗ + ਲੋਬਸ + ਔਰਬਿਟ ਘਣਤਾ + ਮੋਟਾਈ + ਸਪਾਈਕਸ + ਸਪਾਈਕ ਦੀ ਲੰਬਾਈ + ਸਰੀਰ ਦਾ ਆਕਾਰ + ਧਾਤੂ + ਸਦਮਾ ਘੇਰਾ + ਸ਼ੈੱਲ ਦੀ ਚੌੜਾਈ + ਬਾਹਰ ਸੁੱਟ ਦਿੱਤਾ + ਵਿਦਿਆਰਥੀ ਦਾ ਆਕਾਰ + ਆਇਰਿਸ ਦਾ ਆਕਾਰ + ਰੰਗ ਪਰਿਵਰਤਨ + ਕੈਚਲਾਈਟ + ਅੱਖ ਦਾ ਆਕਾਰ + ਬਾਰਬ ਘਣਤਾ + ਵਾਰੀ + ਚੈਂਬਰ + ਖੁੱਲ ਰਿਹਾ ਹੈ + ਰਿਜਸ + ਮੋਤੀ + ਗ੍ਰਹਿ ਦਾ ਆਕਾਰ + ਰਿੰਗ ਝੁਕਾਓ + ਰਿੰਗ ਚੌੜਾਈ + ਵਾਯੂਮੰਡਲ + ਗੰਦਗੀ ਦਾ ਰੰਗ + ਗੂੜਾ ਘਾਹ ਦਾ ਰੰਗ + ਘਾਹ ਦਾ ਰੰਗ + ਟਿਪ ਰੰਗ + ਗੂੜ੍ਹਾ ਧਰਤੀ ਦਾ ਰੰਗ + ਸੁੱਕਾ ਰੰਗ + ਕੰਕਰ ਰੰਗ + ਚਮੜੇ ਦਾ ਰੰਗ + ਕੰਕਰੀਟ ਰੰਗ + ਟਾਰ ਰੰਗ + ਅਸਫਾਲਟ ਰੰਗ + ਪੱਥਰ ਦਾ ਰੰਗ + ਧੂੜ ਰੰਗ + ਮਿੱਟੀ ਦਾ ਰੰਗ + ਗੂੜ੍ਹਾ ਮੌਸ ਰੰਗ + ਮੌਸ ਰੰਗ + ਲਾਲ ਰੰਗ + ਕੋਰ ਰੰਗ + ਹਰਾ ਰੰਗ + ਸਿਆਨ ਰੰਗ + ਮੈਜੈਂਟਾ ਰੰਗ + ਸੋਨੇ ਦਾ ਰੰਗ + ਕਾਗਜ਼ ਦਾ ਰੰਗ + ਰੰਗਦਾਰ ਰੰਗ + ਸੈਕੰਡਰੀ ਰੰਗ + ਪਹਿਲਾ ਰੰਗ + ਦੂਜਾ ਰੰਗ + ਗੁਲਾਬੀ ਰੰਗ + ਗੂੜ੍ਹਾ ਸਟੀਲ ਰੰਗ + ਸਟੀਲ ਦਾ ਰੰਗ + ਹਲਕਾ ਸਟੀਲ ਰੰਗ + ਆਕਸਾਈਡ ਰੰਗ + ਹਾਲੋ ਰੰਗ + ਬੋਲਟ ਰੰਗ + ਮਖਮਲ ਰੰਗ + ਚਮਕਦਾਰ ਰੰਗ + ਨੀਲੀ ਸਿਆਹੀ ਰੰਗ + ਲਾਲ ਸਿਆਹੀ ਦਾ ਰੰਗ + ਗੂੜ੍ਹਾ ਸਿਆਹੀ ਰੰਗ + ਸਿਲਵਰ ਰੰਗ + ਪੀਲਾ ਰੰਗ + ਟਿਸ਼ੂ ਦਾ ਰੰਗ + ਡਿਸਕ ਦਾ ਰੰਗ + ਗਰਮ ਰੰਗ + ਲੈਂਸ ਦਾ ਰੰਗ + ਬਾਹਰੀ ਰੰਗ + ਅੰਦਰੂਨੀ ਰੰਗ + ਕਰੋਨਾ ਰੰਗ + ਠੰਡਾ ਰੰਗ + ਗਰਮ ਰੰਗ + ਬੱਦਲ ਦਾ ਰੰਗ + ਲਾਟ ਰੰਗ + ਖੰਭ ਦਾ ਰੰਗ + ਸ਼ੈੱਲ ਰੰਗ + ਮੋਤੀ ਰੰਗ + ਗ੍ਰਹਿ ਰੰਗ + ਰਿੰਗ ਰੰਗ + ਸੁਰੱਖਿਅਤ ਕਰਨ ਤੋਂ ਬਾਅਦ ਅਸਲੀ ਫਾਈਲਾਂ ਨੂੰ ਮਿਟਾਓ + ਸਿਰਫ਼ ਸਫਲਤਾਪੂਰਵਕ ਪ੍ਰੋਸੈਸ ਕੀਤੀਆਂ ਸਰੋਤ ਫ਼ਾਈਲਾਂ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਜਾਵੇਗਾ + ਮੂਲ ਫ਼ਾਈਲਾਂ ਮਿਟਾਈਆਂ ਗਈਆਂ: %1$d + ਅਸਲ ਫ਼ਾਈਲਾਂ ਨੂੰ ਮਿਟਾਉਣ ਵਿੱਚ ਅਸਫਲ: %1$d + ਮੂਲ ਫ਼ਾਈਲਾਂ ਮਿਟਾਈਆਂ ਗਈਆਂ: %1$d, ਅਸਫਲ: %2$d + ਸ਼ੀਟ ਇਸ਼ਾਰੇ + ਵਿਸਤ੍ਰਿਤ ਵਿਕਲਪ ਸ਼ੀਟਾਂ ਨੂੰ ਖਿੱਚਣ ਦੀ ਆਗਿਆ ਦਿਓ + ਕ੍ਰੋਮਾ ਸਬ-ਸੈਪਲਿੰਗ + ਬਿੱਟ ਡੂੰਘਾਈ + ਨੁਕਸਾਨ ਰਹਿਤ + %1$d-ਬਿੱਟ + ਬੈਚ ਦਾ ਨਾਮ ਬਦਲੋ + ਫਾਈਲਨਾਮ ਪੈਟਰਨਾਂ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਕਈ ਫਾਈਲਾਂ ਦਾ ਨਾਮ ਬਦਲੋ + ਬੈਚ ਫਾਈਲਾਂ ਦਾ ਨਾਮ ਬਦਲੋ ਫਾਈਲ ਨਾਮ ਪੈਟਰਨ ਕ੍ਰਮ ਮਿਤੀ ਐਕਸਟੈਂਸ਼ਨ + ਨਾਮ ਬਦਲਣ ਲਈ ਫਾਈਲਾਂ ਚੁਣੋ + ਫਾਈਲ ਨਾਮ ਪੈਟਰਨ + ਨਾਮ ਬਦਲੋ + ਮਿਤੀ ਸਰੋਤ + EXIF ਮਿਤੀ ਲਈ ਗਈ + ਫਾਈਲ ਸੋਧੀ ਗਈ ਮਿਤੀ + ਫਾਈਲ ਬਣਾਉਣ ਦੀ ਮਿਤੀ + ਮੌਜੂਦਾ ਮਿਤੀ + ਦਸਤੀ ਮਿਤੀ + ਦਸਤੀ ਮਿਤੀ + ਦਸਤੀ ਸਮਾਂ + ਚੁਣੀ ਗਈ ਮਿਤੀ ਕੁਝ ਫ਼ਾਈਲਾਂ ਲਈ ਉਪਲਬਧ ਨਹੀਂ ਹੈ। ਉਹਨਾਂ ਲਈ ਵਰਤਮਾਨ ਮਿਤੀ ਦੀ ਵਰਤੋਂ ਕੀਤੀ ਜਾਵੇਗੀ। + ਇੱਕ ਫਾਈਲ ਨਾਮ ਪੈਟਰਨ ਦਰਜ ਕਰੋ + ਪੈਟਰਨ ਇੱਕ ਅਵੈਧ ਜਾਂ ਬਹੁਤ ਜ਼ਿਆਦਾ ਲੰਬਾ ਫਾਈਲ ਨਾਮ ਬਣਾਉਂਦਾ ਹੈ + ਪੈਟਰਨ ਡੁਪਲੀਕੇਟ ਫਾਈਲ ਨਾਮ ਬਣਾਉਂਦਾ ਹੈ + ਸਾਰੇ ਫਾਈਲ ਨਾਮ ਪਹਿਲਾਂ ਹੀ ਪੈਟਰਨ ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਹਨ + ਇਹ ਪੈਟਰਨ ਟੋਕਨਾਂ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ ਜੋ ਬੈਚ ਦੇ ਨਾਮ ਬਦਲਣ ਲਈ ਉਪਲਬਧ ਨਹੀਂ ਹਨ + ਨਾਮ ਉਹੀ ਰਹੇਗਾ + ਫਾਈਲਾਂ ਦਾ ਨਾਮ ਸਫਲਤਾਪੂਰਵਕ ਬਦਲਿਆ ਗਿਆ + ਕੁਝ ਫ਼ਾਈਲਾਂ ਦਾ ਨਾਮ ਬਦਲਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ + ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਦਿੱਤੀ ਗਈ + ਸਰੋਤ ਪਛਾਣ ਨੂੰ ਬਰਕਰਾਰ ਰੱਖਣ ਵਿੱਚ ਤੁਹਾਡੀ ਮਦਦ ਕਰਦੇ ਹੋਏ, ਐਕਸਟੈਂਸ਼ਨ ਦੇ ਬਿਨਾਂ ਮੂਲ ਫਾਈਲ ਨਾਮ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ। \\o{start:end} ਨਾਲ ਕੱਟਣ, \\o{t} ਨਾਲ ਲਿਪੀਅੰਤਰਨ ਅਤੇ \\o{s/old/new/} ਨਾਲ ਬਦਲਣ ਦਾ ਵੀ ਸਮਰਥਨ ਕਰਦਾ ਹੈ। + ਸਵੈ-ਵਧਾਉਣ ਵਾਲਾ ਕਾਊਂਟਰ। ਕਸਟਮ ਫਾਰਮੈਟਿੰਗ ਦਾ ਸਮਰਥਨ ਕਰਦਾ ਹੈ: \\c{ਪੈਡਿੰਗ} (ਉਦਾਹਰਨ ਲਈ \\c{3} -&gt; 001), \\c{start:step} ਜਾਂ \\c{start:step:padding}। + ਮੂਲ ਫੋਲਡਰ ਦਾ ਨਾਮ ਜਿੱਥੇ ਅਸਲ ਫ਼ਾਈਲ ਸਥਿਤ ਸੀ। + ਅਸਲ ਫ਼ਾਈਲ ਦਾ ਆਕਾਰ। ਇਕਾਈਆਂ ਦਾ ਸਮਰਥਨ ਕਰਦਾ ਹੈ: \\z{b}, \\z{kb}, \\z{mb} ਜਾਂ ਮਨੁੱਖੀ ਪੜ੍ਹਨਯੋਗ ਫਾਰਮੈਟ ਲਈ ਸਿਰਫ਼ \\z। + ਫਾਈਲ ਨਾਮ ਦੀ ਵਿਲੱਖਣਤਾ ਨੂੰ ਯਕੀਨੀ ਬਣਾਉਣ ਲਈ ਇੱਕ ਬੇਤਰਤੀਬ ਯੂਨੀਵਰਸਲੀ ਯੂਨੀਕ ਆਈਡੈਂਟੀਫਾਇਰ (UUID) ਤਿਆਰ ਕਰਦਾ ਹੈ। + ਫਾਈਲਾਂ ਦਾ ਨਾਮ ਬਦਲਣਾ ਅਣਕੀਤਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ। ਜਾਰੀ ਰੱਖਣ ਤੋਂ ਪਹਿਲਾਂ ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਨਵੇਂ ਨਾਮ ਸਹੀ ਹਨ। + ਨਾਮ ਬਦਲਣ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ + ਸਾਈਡ ਮੀਨੂ ਸੈਟਿੰਗਾਂ + ਮੀਨੂ ਸਕੇਲ + ਮੀਨੂ ਅਲਫ਼ਾ + ਆਟੋ ਵ੍ਹਾਈਟ ਬੈਲੇਂਸ + ਕਲਿਪਿੰਗ + ਲੁਕੇ ਹੋਏ ਵਾਟਰਮਾਰਕਸ ਦੀ ਜਾਂਚ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ + ਸਟੋਰੇਜ + ਕੈਸ਼ ਸੀਮਾ + ਕੈਸ਼ ਨੂੰ ਆਪਣੇ ਆਪ ਸਾਫ਼ ਕਰੋ ਜਦੋਂ ਇਹ ਇਸ ਆਕਾਰ ਤੋਂ ਵੱਧ ਜਾਂਦਾ ਹੈ + ਸਫਾਈ ਅੰਤਰਾਲ + ਕਿੰਨੀ ਵਾਰ ਜਾਂਚ ਕਰਨੀ ਹੈ ਕਿ ਕੈਸ਼ ਕਲੀਅਰ ਕੀਤਾ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ ਜਾਂ ਨਹੀਂ + ਐਪ ਲਾਂਚ ਹੋਣ \'ਤੇ + 1 ਦਿਨ + 1 ਹਫ਼ਤਾ + 1 ਮਹੀਨਾ + ਚੁਣੀ ਗਈ ਸੀਮਾ ਅਤੇ ਅੰਤਰਾਲ ਦੇ ਅਨੁਸਾਰ ਆਪਣੇ ਆਪ ਐਪ ਕੈਸ਼ ਨੂੰ ਸਾਫ਼ ਕਰੋ + ਚਲਣਯੋਗ ਫਸਲ ਖੇਤਰ + ਇਸ ਦੇ ਅੰਦਰ ਖਿੱਚ ਕੇ ਪੂਰੇ ਫਸਲ ਖੇਤਰ ਨੂੰ ਹਿਲਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ + GIF ਨੂੰ ਮਿਲਾਓ + ਇੱਕ ਐਨੀਮੇਸ਼ਨ ਵਿੱਚ ਕਈ GIF ਕਲਿੱਪਾਂ ਨੂੰ ਜੋੜੋ + GIF ਕਲਿੱਪ ਆਰਡਰ + ਉਲਟਾ + ਉਲਟਾ ਖੇਡੋ + ਬੂਮਰੈਂਗ + ਅੱਗੇ ਅਤੇ ਫਿਰ ਪਿੱਛੇ ਖੇਡੋ + ਫਰੇਮ ਦਾ ਆਕਾਰ ਆਮ ਬਣਾਓ + ਸਭ ਤੋਂ ਵੱਡੀ ਕਲਿੱਪ ਫਿੱਟ ਕਰਨ ਲਈ ਸਕੇਲ ਫਰੇਮ; ਆਪਣੇ ਅਸਲੀ ਸਕੇਲ ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖਣ ਲਈ ਬੰਦ ਕਰੋ + ਕਲਿੱਪਾਂ ਵਿਚਕਾਰ ਦੇਰੀ (ms) + ਫੋਲਡਰ + ਇੱਕ ਫੋਲਡਰ ਅਤੇ ਇਸਦੇ ਸਬਫੋਲਡਰ ਤੋਂ ਸਾਰੀਆਂ ਤਸਵੀਰਾਂ ਚੁਣੋ + ਡੁਪਲੀਕੇਟ ਖੋਜਕ + ਸਟੀਕ ਕਾਪੀਆਂ ਅਤੇ ਦ੍ਰਿਸ਼ਟੀਗਤ ਸਮਾਨ ਚਿੱਤਰ ਲੱਭੋ + ਡੁਪਲੀਕੇਟ ਖੋਜੀ ਸਮਾਨ ਚਿੱਤਰਾਂ ਦੀਆਂ ਸਹੀ ਕਾਪੀਆਂ ਫੋਟੋਆਂ ਦੀ ਸਫਾਈ ਸਟੋਰੇਜ਼ dHash SHA-256 + ਡੁਪਲੀਕੇਟ ਲੱਭਣ ਲਈ ਚਿੱਤਰ ਚੁਣੋ + ਸਮਾਨਤਾ ਸੰਵੇਦਨਸ਼ੀਲਤਾ + ਸਹੀ ਕਾਪੀਆਂ + ਮਿਲਦੇ-ਜੁਲਦੇ ਚਿੱਤਰ + ਸਾਰੇ ਸਹੀ ਡੁਪਲੀਕੇਟ ਚੁਣੋ + ਸਿਫ਼ਾਰਸ਼ ਕੀਤੇ ਨੂੰ ਛੱਡ ਕੇ ਸਭ ਨੂੰ ਚੁਣੋ + ਕੋਈ ਡੁਪਲੀਕੇਟ ਨਹੀਂ ਮਿਲੇ + ਹੋਰ ਚਿੱਤਰ ਜੋੜਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਜਾਂ ਸਮਾਨਤਾ ਦੀ ਸੰਵੇਦਨਸ਼ੀਲਤਾ ਵਧਾਉਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ + %1$d ਚਿੱਤਰ(ਚਿੱਤਰਾਂ) ਨੂੰ ਪੜ੍ਹਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ + %1$s • %2$s ਮੁੜ ਦਾਅਵਾ ਕਰਨ ਯੋਗ + %1$d ਸਮੂਹ • %2$s ਮੁੜ ਦਾਅਵਾ ਕਰਨ ਯੋਗ + %1$d ਚੁਣਿਆ ਗਿਆ • %2$s + ਇਸ ਨੂੰ ਅਣਕੀਤਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ। Android ਤੁਹਾਨੂੰ ਸਿਸਟਮ ਡਾਇਲਾਗ ਵਿੱਚ ਮਿਟਾਉਣ ਦੀ ਪੁਸ਼ਟੀ ਕਰਨ ਲਈ ਕਹਿ ਸਕਦਾ ਹੈ। + ਨਹੀਂ ਲਭਿਆ + ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਮੂਵ ਕਰੋ + ਅੰਤ ਵਿੱਚ ਭੇਜੋ + \ No newline at end of file diff --git a/core/resources/src/main/res/values-pl/strings.xml b/core/resources/src/main/res/values-pl/strings.xml new file mode 100644 index 0000000..666ccc8 --- /dev/null +++ b/core/resources/src/main/res/values-pl/strings.xml @@ -0,0 +1,3741 @@ + + + + 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 + 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 + 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. + 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. + 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 + Zniekształcenie świadome treści + Za mało pamięci, aby ukończyć tę czynność. Spróbuj użyć mniejszego obrazu, zamknij inne aplikacje lub uruchom ponownie aplikację. + Pracownicy równoległi + Obrazy te nie zostały zapisane, ponieważ przekonwertowane pliki byłyby większe niż oryginały. Sprawdź tę listę przed usunięciem oryginalnych obrazów. + Pominięte pliki: %1$s + Dzienniki aplikacji + Wyświetl dzienniki aplikacji, aby wykryć problemy + Ulubione w pogrupowanych narzędziach + Dodaje ulubione jako kartę, gdy narzędzia są pogrupowane według typu + Pokaż ulubione jako ostatnie + Przenosi zakładkę ulubionych narzędzi na koniec + Odstępy poziome + Odstęp pionowy + Energia wsteczna + Użyj prostej mapy energii o gradiencie wielkości zamiast algorytmu energii w przód + Usuń zamaskowany obszar + Szwy przejdą przez zamaskowany obszar, usuwając tylko wybrany obszar, zamiast go chronić + VHS-NTSC + Zaawansowane ustawienia NTSC + Dodatkowe strojenie analogowe w celu uzyskania silniejszych artefaktów VHS i transmisji + Krwawienie chromu + Zużycie taśmy + Śledzenie + Rozmaz Lumy + Dzwonienie + Śnieg + Użyj pola + Typ filtra + Filtr wejściowy luminacji + Dolnoprzepustowy kolor wejściowy + Demodulacja chrominancji + Przesunięcie fazowe + Przesunięcie fazowe + Przełączanie głowicy + Wysokość przełączania głowicy + Przesunięcie przełączania głowicy + Zmiana przełączania głowicy + Pozycja w środkowej linii + Jitter w środkowej linii + Śledzenie wysokości hałasu + Fala śledzenia + Śledzenie śniegu + Śledzenie anizotropii śniegu + Szum śledzenia + Śledzenie intensywności hałasu + Hałas złożony + Złożona częstotliwość szumu + Złożone natężenie hałasu + Szczegóły szumu złożonego + Częstotliwość dzwonienia + Moc dzwonienia + Hałas Lumy + Częstotliwość szumu Lumy + Intensywność hałasu Lumy + Szczegóły szumu Lumy + Szum chromowy + Częstotliwość szumu chrominancyjnego + Intensywność szumu chrominancyjnego + Szczegóły szumu chroma + Intensywność śniegu + Anizotropia śniegu + Szum fazowy chrominancji + Błąd fazy chrominancji + Poziome opóźnienie chrominancji + Opóźnienie chrominancji w pionie + Szybkość taśmy VHS + Utrata chrominancji VHS + Intensywność wyostrzania VHS + Częstotliwość wyostrzania VHS + Natężenie fali krawędziowej VHS + Prędkość fali krawędziowej VHS + Częstotliwość fali krawędziowej VHS + Szczegóły fali krawędziowej VHS + Wyjściowa dolnoprzepustowa barwa + Skala pozioma + Skala pionowa + Współczynnik skali X + Współczynnik skali Y + Wykrywa popularne znaki wodne w obrazach i maluje je za pomocą LaMa. Automatyczne pobieranie modeli wykrywania i malowania + Deuteranopia + Rozwiń obraz + Narzędzie do usuwania tła oparte na segmentacji obiektów przy użyciu segmentacji YOLO v11 + Animowane emotikonki + Pokazuj dostępne emotikonki jako animacje + Pokaż kąt linii + Pokazuje bieżący obrót linii w stopniach podczas rysowania + Zapisz w oryginalnym folderze + Zapisz nowe pliki obok oryginalnego pliku zamiast w wybranym folderze + Znak wodny w formacie PDF + Za mało pamięci + Obraz jest prawdopodobnie zbyt duży, aby go przetworzyć na tym urządzeniu lub w systemie zabrakło dostępnej pamięci. Spróbuj zmniejszyć rozdzielczość obrazu, zamknij inne aplikacje lub wybierz mniejszy plik. + Menu debugowania + Menu do testowania funkcji aplikacji. Nie powinno pojawiać się w wersji produkcyjnej + Dostawca + PaddleOCR wymaga dodatkowych modeli ONNX na Twoim urządzeniu. Czy chcesz pobrać dane %1$s? + Uniwersalny + koreański + łacina + Wschodniosłowiańskie + tajski + grecki + angielski + Cyrylica + arabski + Dewanagari + Tamil + telugu + Moduł cieniujący + Wstępnie ustawione moduły cieniujące + Nie wybrano modułu cieniującego + Studio shaderów + Twórz, edytuj, sprawdzaj, importuj i eksportuj niestandardowe moduły cieniujące fragmentów + Zapisane shadery + Otwórz zapisane shadery, duplikuj, eksportuj, udostępniaj lub usuwaj + Nowy shader + Plik shadera + .itsshader JSON + %1$d parametry + Źródło shaderów + Zapisz tylko treść void main(). Użyj teksturyKoordynator dla współrzędnych UV i inputImageTexture jako próbnika źródłowego. + Funkcje + Opcjonalne funkcje pomocnicze, stałe i struktury wstawione przed void main(). Mundury są generowane z parametrów. + Dodaj uniformy tutaj, gdy moduł cieniujący potrzebuje edytowalnych wartości. + Zresetuj moduł cieniujący + Bieżąca wersja robocza modułu cieniującego zostanie wyczyszczona + Nieprawidłowy moduł cieniujący + Nieobsługiwana wersja modułu cieniującego %1$d. Obsługiwana wersja: %2$d. + Nazwa modułu cieniującego nie może być pusta. + Źródło modułu cieniującego nie może być puste. + Źródło modułu cieniującego zawiera nieobsługiwane znaki: %1$s. Używaj wyłącznie źródła ASCII GLSL. + Parametr „%1$s” został zadeklarowany więcej niż raz. + Nazwy parametrów nie mogą być puste. + Parametr „%1$s” wykorzystuje zastrzeżoną nazwę GPUImage. + Parametr „%1$s” musi być prawidłowym identyfikatorem GLSL. + Parametr „%1$s” ma %2$s typ wartości „%3$s”, oczekiwany jest „%4$s”. + Parametr „%1$s” nie może definiować wartości minimalnej ani maksymalnej dla wartości typu bool. + Parametr „%1$s” ma wartość minimalną większą niż maks. + Parametr „%1$s” domyślnie jest niższy niż min. + Parametr „%1$s” domyślnie jest większy niż maks. + Moduł cieniujący musi zadeklarować „jednolity próbnik2D %1$s;”. + Moduł cieniujący musi zadeklarować „zmienny vec2 %1$s;”. + Shader musi zdefiniować `void main()\\\". + Shader musi zapisać kolor do gl_FragColor. + ShaderToy mainImage shadery nie są obsługiwane. Użyj umowy void main() GPUImage. + Animowany uniform ShaderToy „iTime” nie jest obsługiwany. + Animowany uniform ShaderToy „iFrame” nie jest obsługiwany. + ShaderToy uniform „iResolution” nie jest obsługiwany. + Tekstury ShaderToy iChannel nie są obsługiwane. + Tekstury zewnętrzne nie są obsługiwane. + Zewnętrzne rozszerzenia tekstur nie są obsługiwane. + Parametry modułu cieniującego Libretro nie są obsługiwane. + Obsługiwana jest tylko jedna tekstura wejściowa. Usuń „mundur %1$s %2$s;”. + Parametr „%1$s” musi być zadeklarowany jako „jednolity %2$s %1$s;”. + Parametr „%1$s” jest zadeklarowany jako „jednolity %2$s %1$s;”, oczekiwany jako „jednolity %3$s %1$s;”. + Plik modułu cieniującego jest pusty. + Plik modułu cieniującego musi być obiektem JSON .itshader z polami wersji, nazwy i modułu cieniującego. + Plik modułu cieniującego nie jest prawidłowym formatem JSON lub nie pasuje do formatu .itshader. + Pole „%1$s” jest wymagane. + %1$s jest wymagany. + %1$s „%2$s” nie jest obsługiwany. Obsługiwane typy: %3$s. + Dla parametrów %2$s wymagany jest %1$s. + %1$s musi być liczbą skończoną. + %1$s musi być liczbą całkowitą. + %1$s musi być prawdą lub fałszem. + %1$s musi być ciągiem kolorów, tablicą RGB/RGBA lub obiektem koloru. + %1$s musi używać formatu #RRGGBB lub #RRGGBBAA. + %1$s musi zawierać prawidłowe szesnastkowe kanały kolorów. + %1$s musi zawierać 3 lub 4 kanały kolorów. + %1$s musi należeć do zakresu od 0 do 255. + %1$s musi być tablicą dwucyfrową lub obiektem z x i y. + %1$s musi zawierać dokładnie 2 cyfry. + Duplikat + Zawsze czyść EXIF + Usuń dane EXIF ​​​​obrazu podczas zapisywania, nawet jeśli narzędzie żąda zachowania metadanych + Pomoc i wskazówki + %1$d tutoriale + Otwórz narzędzie + Poznaj główne narzędzia, opcje eksportu, przepływy plików PDF, narzędzia do kolorowania i rozwiązania typowych problemów + Rozpoczęcie + Wybieraj narzędzia, importuj pliki, zapisuj wyniki i szybciej wykorzystuj ustawienia ponownie + Edycja obrazu + Zmieniaj rozmiar, przycinaj, filtruj, usuwaj tło, rysuj i znakuj obrazy wodne + Pliki i metadane + Konwertuj formaty, porównuj dane wyjściowe, chroń prywatność i kontroluj nazwy plików + PDF i dokumenty + Twórz pliki PDF, skanuj dokumenty, strony OCR i przygotowuj pliki do udostępnienia + Tekst, kod QR i dane + Rozpoznawaj tekst, skanuj kody, koduj Base64, sprawdzaj skróty i pakuj pliki + Narzędzia koloru + Wybieraj kolory, twórz palety, przeglądaj biblioteki kolorów i twórz gradienty + Kreatywne narzędzia + Generuj grafikę SVG, ASCII, tekstury szumu, shadery i eksperymentalne wizualizacje + Rozwiązywanie problemów + Napraw problemy z dużymi plikami, przejrzystością, udostępnianiem, importem i raportowaniem + Wybierz odpowiednie narzędzie + Użyj kategorii, wyszukiwania, ulubionych i akcji udostępniania, aby zacząć we właściwym miejscu + Zacznij od najlepszego punktu wejścia + Image Toolbox zawiera wiele wyspecjalizowanych narzędzi, a wiele z nich na pierwszy rzut oka pokrywa się. Zacznij od zadania, które chcesz zakończyć, a następnie wybierz narzędzie sterujące najważniejszą częścią wyniku: rozmiarem, formatem, metadanymi, tekstem, stronami PDF, kolorami lub układem. + Użyj wyszukiwania, jeśli znasz nazwę akcji, taką jak zmiana rozmiaru, PDF, EXIF, OCR, QR lub kolor. + Otwórz kategorię, gdy znasz tylko typ zadania, a następnie przypnij często używane narzędzia jako ulubione. + Gdy inna aplikacja udostępnia obraz w Przyborniku obrazu, wybierz narzędzie z arkusza udostępniania. + Importuj, zapisuj i udostępniaj wyniki + Zrozum typowy przebieg od pobrania danych wejściowych do eksportu edytowanego pliku + Postępuj zgodnie ze zwykłym procesem edycji + Większość narzędzi działa według tego samego rytmu: wybierz dane wejściowe, dostosuj opcje, wyświetl podgląd, a następnie zapisz lub udostępnij. Ważnym szczegółem jest to, że zapisanie powoduje utworzenie pliku wyjściowego, natomiast udostępnianie jest zwykle najlepsze w celu szybkiego przekazania do innej aplikacji. + Wybierz jeden plik dla narzędzi zawierających pojedynczy obraz lub kilka plików dla narzędzi wsadowych, jeśli selektor na to pozwala. + Przed zapisaniem sprawdź opcje podglądu i wyjścia, zwłaszcza opcje formatu, jakości i nazwy pliku. + Użyj udostępniania, aby szybko przekazać dane, lub zapisz, gdy potrzebujesz trwałej kopii w wybranym folderze. + Pracuj z wieloma obrazami jednocześnie + Narzędzia wsadowe najlepiej sprawdzają się w przypadku powtarzających się zadań związanych ze zmianą rozmiaru, konwersją, kompresją i nazewnictwem + Przygotuj przewidywalną partię + Przetwarzanie wsadowe jest najszybsze, gdy każdy wynik powinien podlegać tym samym regułom. Jest to również najłatwiejsze miejsce na popełnienie dużego błędu, dlatego przed rozpoczęciem długiego eksportu wybierz nazewnictwo, jakość, metadane i zachowanie folderów. + Wybierz wszystkie powiązane obrazy razem i zachowaj kolejność, jeśli wynik zależy od sekwencji. + Przed rozpoczęciem eksportu ustaw reguły zmiany rozmiaru, formatu, jakości, metadanych i nazw plików. + Uruchom najpierw małą partię testową, gdy pliki źródłowe są duże lub gdy format docelowy jest dla Ciebie nowy. + Szybka edycja pojedyncza + Użyj edytora „wszystko w jednym”, jeśli potrzebujesz kilku prostych zmian na jednym obrazie + Zakończ jeden obraz bez przeskakiwania narzędzi + Pojedyncza edycja jest przydatna, gdy obraz wymaga małego łańcucha zmian, a nie jednej wyspecjalizowanej operacji. Zwykle szybsze jest szybkie czyszczenie, przeglądanie i eksportowanie ostatniej kopii bez tworzenia wsadowego przepływu pracy. + Otwórz opcję Pojedyncza edycja dla jednego obrazu, który wymaga typowych dostosowań, znaczników, przycięcia, obrotu lub eksportu. + Zastosuj zmiany w kolejności, w której najpierw zmienia się najmniejsza liczba pikseli, np. przytnij przed filtrami i filtry przed końcową kompresją. + Zamiast tego użyj specjalistycznego narzędzia, jeśli potrzebujesz przetwarzania wsadowego, dokładnego docelowego rozmiaru pliku, wydruku PDF, OCR lub czyszczenia metadanych. + Użyj gotowych ustawień i ustawień + Zapisz powtarzające się wybory i dostosuj aplikację do preferowanego przepływu pracy + Unikaj powtarzania tej samej konfiguracji + Presety i ustawienia są przydatne, gdy często eksportujesz ten sam rodzaj pliku. Dobre ustawienie wstępne zamienia powtarzaną ręczną listę kontrolną w jedno kliknięcie, podczas gdy ustawienia globalne definiują ustawienia domyślne, od których zaczynają się nowe narzędzia. + Twórz ustawienia wstępne dla typowych rozmiarów wyjściowych, formatów, wartości jakości lub wzorców nazewnictwa. + Przejrzyj ustawienia domyślnego formatu, jakości, folderu zapisu, nazwy pliku, selektora i zachowania interfejsu. + Utwórz kopię zapasową ustawień przed ponowną instalacją aplikacji lub przejściem na inne urządzenie. + Ustaw przydatne ustawienia domyślne + Wybierz raz domyślny format, jakość, tryb skali, rodzaj zmiany rozmiaru i przestrzeń kolorów + Spraw, aby nowe narzędzia zaczynały się bliżej Twojego celu + Wartości domyślne to nie tylko preferencje kosmetyczne. Decydują o tym, jaki format, jakość, sposób zmiany rozmiaru, tryb skali i przestrzeń kolorów pojawiają się jako pierwsze w wielu narzędziach, co pozwala uniknąć ponownego wybierania tych samych opcji przy każdym eksporcie. + Ustaw domyślny format i jakość obrazu, aby odpowiadały zwykłemu miejscu docelowemu, np. JPEG w przypadku zdjęć lub PNG/WebP w przypadku wersji alfa. + Dostosuj domyślny typ zmiany rozmiaru i tryb skalowania, jeśli często eksportujesz w celu uzyskania ścisłych wymiarów, platform społecznościowych lub stron dokumentów. + Zmień domyślne ustawienia przestrzeni kolorów tylko wtedy, gdy przepływ pracy wymaga spójnej obsługi kolorów na wyświetlaczach, drukarkach lub w zewnętrznych edytorach. + Picker i launcher + Użyj ustawień selektora, gdy aplikacja otwiera zbyt wiele okien dialogowych lub uruchamia się w niewłaściwym miejscu + Dostosuj otwieranie plików do swojego nawyku + Ustawienia selektora i programu uruchamiającego kontrolują, czy Przybornik obrazu ma być uruchamiany z ekranu głównego, narzędzia, czy z procesu wyboru pliku. Opcje te są szczególnie przydatne, gdy zwykle wchodzisz z arkusza udostępniania Androida lub gdy chcesz, aby aplikacja przypominała bardziej program uruchamiający narzędzia. + Użyj ustawień selektora zdjęć, jeśli selektor systemowy ukrywa pliki, pokazuje zbyt wiele ostatnich elementów lub źle obsługuje pliki w chmurze. + Włącz opcję pomijania pobierania tylko dla narzędzi, do których zwykle wchodzisz z udziału lub znasz już plik źródłowy. + Przejrzyj tryb uruchamiania, jeśli chcesz, aby Image Toolbox zachowywał się bardziej jak selektor narzędzi niż normalny ekran główny. + Źródło obrazu + Wybierz tryb wyboru, który najlepiej sprawdza się w przypadku galerii, menedżerów plików i plików w chmurze + Wybierz pliki z odpowiedniego źródła + Ustawienia źródła obrazu wpływają na sposób, w jaki aplikacja prosi Androida o obrazy. Najlepszy wybór zależy od tego, czy Twoje pliki znajdują się w galerii systemowej, folderze menedżera plików, u dostawcy usług w chmurze czy w innej aplikacji udostępniającej zawartość tymczasową. + Użyj domyślnego selektora, jeśli chcesz uzyskać najbardziej zintegrowaną z systemem obsługę typowych obrazów z galerii. + Przełącz tryb selektora, jeśli albumy, foldery, pliki w chmurze lub niestandardowe formaty nie pojawiają się tam, gdzie się spodziewasz. + Jeśli udostępniony plik zniknie po opuszczeniu aplikacji źródłowej, otwórz go ponownie za pomocą trwałego selektora lub najpierw zapisz kopię lokalną. + Ulubione i udostępniaj narzędzia + Skoncentruj ekran główny i ukryj narzędzia, które nie mają sensu w przepływach udostępniania + Zmniejsz szum listy narzędzi + Ustawienia ulubionych i ukrytych do udostępniania przydadzą się, jeśli na co dzień korzystasz tylko z kilku narzędzi. Zapewniają także praktyczny przepływ udostępniania, ukrywając narzędzia, które nie mogą korzystać z typu pliku wysyłanego przez inną aplikację. + Przypinaj często używane narzędzia, aby były łatwo dostępne, nawet jeśli narzędzia są pogrupowane według kategorii. + Ukryj narzędzia przed przepływami udostępniania, gdy nie mają one znaczenia dla plików pochodzących z innej aplikacji. + Użyj ustawień kolejności ulubionych, jeśli wolisz ulubione na końcu lub pogrupowane za pomocą powiązanych narzędzi. + Utwórz kopię zapasową ustawień + Bezpiecznie przenieś ustawienia wstępne, preferencje i zachowanie aplikacji do innej instalacji + Przechowuj swoją konfigurację jako przenośną + Tworzenie kopii zapasowych i przywracanie jest najbardziej przydatne po dostosowaniu ustawień wstępnych, folderów, reguł dotyczących nazw plików, kolejności narzędzi i preferencji wizualnych. Kopia zapasowa umożliwia eksperymentowanie z ustawieniami lub ponowną instalację aplikacji bez konieczności odbudowywania przepływu pracy z pamięci. + Utwórz kopię zapasową po zmianie ustawień wstępnych, zachowania nazw plików, domyślnych formatów lub układu narzędzi. + Jeśli planujesz wyczyścić dane, ponownie zainstalować lub przenieść urządzenia, przechowuj kopię zapasową poza folderem aplikacji. + Przywróć ustawienia przed przetworzeniem ważnych partii, aby ustawienia domyślne i zachowanie zapisu odpowiadały starej konfiguracji. + Zmień rozmiar i konwertuj obrazy + Zmień rozmiar, format, jakość i metadane jednego lub wielu obrazów + Twórz kopie o zmienionym rozmiarze + Zmień rozmiar i konwertuj to główne narzędzie umożliwiające przewidywalne wymiary i lżejsze pliki wyjściowe. Użyj go, gdy rozmiar obrazu, format wyjściowy i zachowanie metadanych mają znaczenie jednocześnie. + Wybierz jeden lub więcej obrazów, a następnie zdecyduj, czy najważniejsze są wymiary, skala czy rozmiar pliku. + Wybierz format wyjściowy i jakość; użyj formatu PNG lub WebP, gdy konieczne jest zachowanie przezroczystości. + Wyświetl podgląd wyniku, a następnie zapisz lub udostępnij wygenerowane kopie bez zmiany oryginałów. + Zmień rozmiar według rozmiaru pliku + Ustaw limit rozmiaru, gdy witryna internetowa, komunikator lub formularz odrzucają ciężkie obrazy + Dopasuj rygorystyczne limity przesyłania + Zmiana rozmiaru według rozmiaru pliku jest lepsza niż zgadywanie wartości jakości, gdy miejsce docelowe akceptuje tylko pliki poniżej określonego limitu. Narzędzie może dostosować kompresję i wymiary, aby osiągnąć cel, starając się jednocześnie zachować użyteczność wyniku. + Wprowadź docelowy rozmiar nieco poniżej rzeczywistego limitu przesyłania, aby pozostawić miejsce na metadane i zmiany dostawcy. + Preferuj formaty stratne dla zdjęć, gdy cel jest mały; PNG może pozostać duży nawet po zmianie rozmiaru. + Po wyeksportowaniu sprawdź twarze, tekst i krawędzie, ponieważ obiekty o dużych rozmiarach mogą wprowadzić widoczne artefakty. + Ogranicz zmianę rozmiaru + Zmniejsz tylko obrazy, które przekraczają maksymalną szerokość, wysokość lub ograniczenia pliku + Unikaj zmiany rozmiaru obrazów, które już są w porządku + Limit zmiany rozmiaru jest przydatny w przypadku folderów mieszanych, w których niektóre obrazy są ogromne, a inne są już przygotowane. Zamiast wymuszać tę samą zmianę rozmiaru każdego pliku, akceptowalne obrazy są bliższe ich oryginalnej jakości. + Ustaw maksymalne wymiary lub limity w oparciu o miejsce docelowe, zamiast zmieniać na ślepo rozmiar każdego pliku. + Włącz zachowanie stylu „Pomiń, jeśli jest większy”, jeśli chcesz uniknąć wyników, które stają się cięższe niż źródła. + Użyj tej opcji w przypadku partii z różnych kamer, zrzutów ekranu lub plików do pobrania, w przypadku których rozmiary źródeł znacznie się różnią. + Przycinaj, obracaj i prostuj + Wykadruj ważny obszar przed eksportowaniem lub użyciem obrazu w innych narzędziach + Oczyść kompozycję + Przycięcie w pierwszej kolejności sprawia, że ​​późniejsze filtry, OCR, strony PDF i udostępnianie wyników są czystsze. + Otwórz Przytnij i wybierz obraz, który chcesz dostosować. + Użyj swobodnego kadrowania lub współczynnika proporcji, jeśli wynik musi pasować do posta, dokumentu lub awatara. + Obróć lub odwróć przed zapisaniem, aby późniejsze narzędzia otrzymały poprawiony obraz. + Zastosuj filtry i dostosowania + Zbuduj edytowalny stos filtrów i wyświetl podgląd wyniku przed eksportem + Dostosuj wygląd obrazu + Filtry są przydatne do szybkich poprawek, stylizowania wydruków i przygotowywania obrazów do OCR lub drukowania. Niewielkie zmiany kontrastu, ostrości i jasności mogą mieć większe znaczenie niż intensywne efekty, gdy obraz zawiera tekst lub drobne szczegóły. + Dodawaj filtry jeden po drugim i obserwuj podgląd po każdej zmianie. + Dostosuj jasność, kontrast, ostrość, rozmycie, kolor lub maski w zależności od zadania. + Eksportuj tylko wtedy, gdy podgląd pasuje do Twojego urządzenia docelowego, dokumentu lub platformy społecznościowej. + Najpierw podgląd + Zanim wybierzesz bardziej zaawansowane narzędzie do edycji, konwersji lub czyszczenia, sprawdź obrazy + Sprawdź, co faktycznie otrzymałeś + Podgląd obrazu jest przydatny, gdy plik pochodzi z czatu, magazynu w chmurze lub innej aplikacji i nie masz pewności, co zawiera. Sprawdzenie wymiarów, przezroczystości, orientacji i jakości wizualnej pomaga w wyborze odpowiedniego narzędzia uzupełniającego. + Otwórz podgląd obrazu, jeśli chcesz sprawdzić kilka obrazów przed podjęciem decyzji, co z nimi zrobić. + Poszukaj problemów z rotacją, przezroczystych obszarów, artefaktów kompresji i nieoczekiwanie dużych wymiarów. + Przejdź do opcji Zmień rozmiar, Przytnij, Porównaj, EXIF ​​lub Usuwanie tła dopiero wtedy, gdy wiesz, co wymaga naprawy. + Usuń lub przywróć tło + Automatycznie usuwaj tła lub ręcznie poprawiaj krawędzie za pomocą narzędzi przywracania + Zachowaj temat, usuń resztę + Usuwanie tła działa najlepiej, gdy połączysz automatyczny wybór z dokładnym ręcznym czyszczeniem. Ostateczny format eksportu ma znaczenie tak samo jak maska, ponieważ JPEG spłaszcza przezroczyste obszary, nawet jeśli podgląd wygląda poprawnie. + Zacznij od automatycznego usunięcia, jeśli obiekt jest wyraźnie oddzielony od tła. + Powiększ i przełączaj między usuwaniem a przywracaniem, aby naprawić włosy, narożniki, cienie i drobne szczegóły. + Jeśli potrzebujesz przezroczystości w pliku końcowym, zapisz jako PNG lub WebP. + Rysuj, zaznaczaj i znak wodny + Dodaj strzałki, notatki, wyróżnienia, podpisy lub powtarzające się nakładki znaków wodnych + Dodaj widoczne informacje + Narzędzia do oznaczania pomagają wyjaśnić obraz, a znak wodny pomaga promować markę lub chronić eksportowane pliki. + Użyj opcji Rysuj, jeśli potrzebujesz odręcznych notatek, kształtów, wyróżnień lub szybkich adnotacji. + Użyj znaku wodnego, jeśli ten sam znak tekstowy lub obrazowy powinien pojawiać się konsekwentnie na wydrukach. + Przed eksportem sprawdź krycie, położenie i skalę, aby znak był widoczny, ale nie rozpraszał. + Narysuj wartości domyślne + Ustaw szerokość linii, kolor, tryb ścieżki, blokadę orientacji i lupę, aby przyspieszyć oznaczanie + Spraw, aby rysunek był przewidywalny + Ustawienia rysowania są przydatne, gdy często dodajesz adnotacje do zrzutów ekranu, zaznaczasz dokumenty lub podpisujesz obrazy. Ustawienia domyślne skracają czas konfiguracji, a lupa i blokada orientacji ułatwiają precyzyjną edycję na małych ekranach. + Ustaw domyślną szerokość linii i narysuj kolor zgodnie z wartościami, których najczęściej używasz w przypadku strzałek, wyróżnień i podpisów. + Użyj domyślnego trybu ścieżki, jeśli zwykle rysujesz tego samego rodzaju obrysy, kształty lub znaczniki. + Włącz lupę lub blokadę orientacji, gdy wprowadzanie palcem utrudnia dokładne umieszczenie drobnych szczegółów. + Układy wieloobrazowe + Wybierz odpowiednie narzędzie do tworzenia wielu obrazów przed ułożeniem zrzutów ekranu, skanów lub pasków porównawczych + Użyj odpowiedniego narzędzia do układania + Narzędzia te brzmią podobnie, ale każde z nich jest dostrojone do innego rodzaju tworzenia wielu obrazów. Wybierając najpierw właściwy, unikniesz walki z układem sterowania, który został zaprojektowany z myślą o innym rezultacie. + Użyj Kreatora kolaży do zaprojektowanych układów z kilkoma obrazami w jednej kompozycji. + Użyj opcji Łączenie lub układanie obrazu, jeśli obrazy powinny stać się jednym długim pionowym lub poziomym paskiem. + Użyj podziału obrazu, gdy jeden duży obraz musi zostać podzielony na kilka mniejszych części. + Konwertuj formaty obrazów + Twórz kopie w formacie JPEG, PNG, WebP, AVIF, JXL i inne bez ręcznej edycji pikseli + Wybierz format pracy + Różne formaty są lepsze w przypadku zdjęć, zrzutów ekranu, przejrzystości, kompresji i zgodności. Konwersja jest najbezpieczniejsza, gdy wiesz, co obsługuje aplikacja docelowa i czy źródło zawiera wersję alfa, animację lub metadane, które chcesz zachować. + Użyj formatu JPEG do zgodnych zdjęć, PNG do obrazów bezstratnych i alfa oraz WebP do kompaktowego udostępniania. + Dostosuj jakość, jeśli format ją obsługuje; niższe wartości zmniejszają rozmiar, ale mogą powodować dodanie artefaktów. + Zachowaj oryginały do ​​czasu sprawdzenia, czy przekonwertowane pliki otwierają się prawidłowo w aplikacji docelowej. + Sprawdź EXIF ​​i prywatność + Wyświetl, edytuj lub usuń metadane przed udostępnieniem poufnych zdjęć + Kontroluj ukryte dane obrazu + EXIF może zawierać dane aparatu, daty, lokalizację, orientację i inne szczegóły niewidoczne na obrazie. Warto to sprawdzić przed udostępnieniem publicznym, raportami wsparcia, wpisami na rynku lub jakimkolwiek zdjęciem, które może ujawnić miejsce prywatne. + Otwórz narzędzia EXIF ​​​​przed udostępnieniem zdjęć, które mogą zawierać informacje o lokalizacji lub urządzeniu prywatnym. + Usuń wrażliwe pola lub edytuj nieprawidłowe tagi, takie jak data, orientacja, autor lub dane aparatu. + Zapisz nową kopię i udostępnij ją zamiast oryginału, gdy liczy się prywatność. + Automatyczne czyszczenie EXIF + Domyślnie usuń metadane przy podejmowaniu decyzji, czy daty powinny zostać zachowane + Ustaw prywatność jako domyślną + Grupa ustawień EXIF ​​jest przydatna, gdy chcesz, aby czyszczenie prywatności odbywało się automatycznie, zamiast zapamiętywać to przy każdym eksporcie. Zachowanie daty i godziny to osobna decyzja, ponieważ daty mogą być przydatne do sortowania, ale wrażliwe na udostępnianie. + Włącz zawsze czysty EXIF, gdy większość eksportów jest przeznaczona do udostępniania publicznego lub półpublicznego. + Włącz opcję Zachowaj datę i godzinę tylko wtedy, gdy zachowanie czasu przechwytywania jest ważniejsze niż usuwanie każdego znacznika czasu. + Użyj ręcznej edycji EXIF ​​​​w przypadku plików jednorazowych, w których musisz zachować niektóre pola i usunąć inne. + Kontroluj nazwy plików + Używaj przedrostków, przyrostków, znaczników czasu, ustawień wstępnych, sum kontrolnych i numerów sekwencyjnych + Ułatw znalezienie eksportu + Dobry wzorzec nazwy pliku pomaga sortować partie i zapobiega przypadkowemu nadpisaniu. Ustawienia nazw plików są szczególnie przydatne, gdy eksporty wracają do folderu źródłowego, gdzie podobne nazwy mogą szybko stać się mylące. + Ustaw prefiks, sufiks, znacznik czasu, oryginalną nazwę, informacje o ustawieniach lub opcje sekwencji w Ustawieniach. + Używaj numerów kolejnych w przypadku partii, w których kolejność ma znaczenie, takich jak strony, ramki lub kolaże. + Włącz nadpisywanie tylko wtedy, gdy masz pewność, że zastąpienie istniejących plików jest bezpieczne dla bieżącego folderu. + Zastąp pliki + Zastąp dane wyjściowe pasującymi nazwami tylko wtedy, gdy przepływ pracy jest celowo destrukcyjny + Używaj trybu nadpisywania ostrożnie + Nadpisywanie plików zmienia sposób obsługi konfliktów nazewnictwa. Jest to przydatne w przypadku wielokrotnego eksportu do tego samego folderu, ale może również zastąpić wcześniejsze wyniki, jeśli wzorzec nazwy pliku ponownie wygeneruje tę samą nazwę. + Włącz nadpisywanie tylko dla folderów, w których oczekiwane jest zastępowanie starszych wygenerowanych plików i które można odzyskać. + Zachowaj opcję zastępowania wyłączoną podczas eksportowania oryginałów, plików klienta, jednorazowych skanów lub czegokolwiek bez kopii zapasowej. + Połącz nadpisywanie z celowym wzorcem nazwy pliku, aby zastąpić tylko zamierzone dane wyjściowe. + Wzorce nazw plików + Łącz oryginalne nazwy, przedrostki, przyrostki, znaczniki czasu, ustawienia wstępne, tryb skali i sumy kontrolne + Kompiluj nazwy wyjaśniające dane wyjściowe + Ustawienia wzorców nazw plików mogą zamienić wyeksportowane pliki w przydatną historię tego, co się z nimi stało. Ma to znaczenie w przypadku partii, ponieważ widok folderu może być jedynym miejscem, w którym można rozróżnić rozmiar oryginału, ustawienie wstępne, format i kolejność przetwarzania. + Jeśli potrzebujesz czytelnych nazw do ręcznego sortowania, użyj oryginalnej nazwy pliku, przedrostka, sufiksu i sygnatury czasowej. + Dodaj ustawienie wstępne, tryb skali, rozmiar pliku lub sumę kontrolną, gdy dane wyjściowe muszą dokumentować sposób, w jaki zostały utworzone. + Unikaj przypadkowych nazw procesów, w których pliki muszą być sparowane z oryginałami lub kolejnością stron. + Wybierz miejsce zapisywania plików + Uniknij utraty eksportu, ustawiając foldery, zapisując oryginalne foldery i jednorazowe lokalizacje + Zachowaj przewidywalność wyników + Zachowanie oszczędzania ma największe znaczenie, gdy przetwarzasz wiele plików lub udostępniasz pliki od dostawców usług w chmurze. Ustawienia folderów decydują, czy dane wyjściowe gromadzą się w jednym znanym miejscu, pojawiają się obok oryginałów, czy też tymczasowo trafiają gdzie indziej w celu wykonania określonego zadania. + Ustaw domyślny folder zapisywania, jeśli chcesz zawsze eksportować w jednym miejscu. + Użyj folderu zapisu do oryginalnego folderu w przypadku procesów czyszczenia, w których dane wyjściowe powinny pozostać w pobliżu pliku źródłowego. + Użyj lokalizacji jednorazowego zapisu, gdy pojedyncze zadanie wymaga innego miejsca docelowego, bez zmiany lokalizacji domyślnej. + Foldery jednorazowego zapisu + Wyślij następny eksport do folderu tymczasowego bez zmiany normalnego miejsca docelowego + Oddziel zadania specjalne + Lokalizacja jednorazowego zapisu jest przydatna w przypadku projektów tymczasowych, folderów klientów, sesji czyszczenia lub eksportów, które nie powinny mieszać się ze zwykłym folderem Image Toolbox. Daje krótkotrwałe miejsce docelowe bez konieczności przepisywania domyślnej konfiguracji. + Wybierz folder jednorazowy przed rozpoczęciem zadania, które należy do innej lokalizacji eksportu. + Używaj go do krótkich projektów, folderów współdzielonych lub partii, w których wyniki muszą pozostać zgrupowane razem. + Po zakończeniu zadania wróć do folderu domyślnego, aby późniejsze eksporty nie trafiały nieoczekiwanie do lokalizacji tymczasowej. + Zrównoważ jakość i rozmiar pliku + Dowiedz się, kiedy zmienić wymiary, format lub jakość, zamiast zgadywać + Celowo zmniejszaj pliki + Rozmiar pliku zależy od wymiarów obrazu, formatu, jakości, przezroczystości i złożoności treści. Jeśli plik jest nadal zbyt ciężki, zmiana wymiarów zwykle pomaga bardziej niż wielokrotne obniżanie jakości. + Zmień rozmiar wymiarów najpierw, gdy obraz jest większy niż może wyświetlić miejsce docelowe. + Niższa jakość tylko w przypadku formatów stratnych. Po wyeksportowaniu sprawdź tekst, krawędzie, gradienty i powierzchnie. + Zmień format, gdy zmiany jakości nie wystarczą, na przykład WebP do udostępniania lub PNG, aby uzyskać wyraźne, przezroczyste zasoby. + Pracuj z animowanymi formatami + Użyj narzędzi GIF, APNG, WebP i JXL, gdy plik zawiera ramki zamiast jednej bitmapy + Nie traktuj każdego obrazu jako nieruchomego + Animowane formaty wymagają narzędzi obsługujących klatki, czas trwania i zgodność. + Jeśli potrzebujesz operacji na poziomie klatki dla tych formatów, użyj narzędzi GIF lub APNG. + Użyj narzędzi WebP, jeśli potrzebujesz nowoczesnej kompresji lub animowanych wyników WebP. + Przed udostępnieniem sprawdź czas animacji, ponieważ niektóre platformy konwertują lub spłaszczają animowane obrazy. + Porównaj i wyświetl podgląd wyników + Przed zachowaniem eksportu sprawdź zmiany, rozmiar pliku i różnice wizualne + Sprawdź przed udostępnieniem + Narzędzia porównawcze pomagają wcześnie wykryć artefakty kompresji, nieprawidłowe kolory lub niechciane zmiany. + Otwórz opcję Porównaj, jeśli chcesz sprawdzić dwie wersje obok siebie. + Powiększ krawędzie, tekst, gradienty i przezroczyste obszary, w których najłatwiej przeoczyć problemy. + Jeśli wydruk jest zbyt ciężki lub rozmazany, wróć do poprzedniego narzędzia i dostosuj jakość lub format. + Skorzystaj z centrum narzędzi PDF + Łącz, dziel, obracaj, usuwaj strony, kompresuj, chroń, OCR i znak wodny plików PDF + Wybierz operację PDF + Centrum PDF grupuje działania związane z dokumentami w jednym punkcie wejścia, dzięki czemu możesz wybrać konkretną operację. Zacznij od tego, gdy plik jest już plikiem PDF i użyj opcji Obrazy do pliku PDF lub Skanera dokumentów, gdy źródłem jest nadal zestaw obrazów. + Otwórz Narzędzia PDF i wybierz operację odpowiadającą Twojemu celowi. + Wybierz źródłowy plik PDF lub obrazy, a następnie sprawdź kolejność stron, obrót, ochronę lub opcje OCR. + Zapisz wygenerowany plik PDF i otwórz go raz, aby potwierdzić liczbę stron, kolejność i czytelność. + Zamień obrazy w plik PDF + Połącz skany, zrzuty ekranu, rachunki lub zdjęcia w jeden dokument, który można udostępniać + Stwórz czysty dokument + Obrazy stają się lepszymi stronami PDF, jeśli są przycięte, uporządkowane i mają spójny rozmiar. Traktuj listę obrazów jak stos stron: każdy obrót, margines i wybór rozmiaru strony wpływają na czytelność końcowego dokumentu. + Najpierw przytnij lub obróć obrazy, jeśli krawędzie strony, cienie lub orientacja są nieprawidłowe. + Wybierz obrazy w zamierzonej kolejności stron i dostosuj rozmiar strony lub marginesy, jeśli narzędzie je oferuje. + Wyeksportuj plik PDF, a następnie przed wysłaniem sprawdź, czy wszystkie strony są czytelne. + Skanuj dokumenty + Przechwytuj strony papierowe i przygotuj je do pliku PDF, OCR lub udostępnienia + Przechwytuj czytelne strony + Skanowanie dokumentów działa najlepiej w przypadku płaskich stron, dobrego oświetlenia i skorygowanej perspektywy. Czyste skanowanie przed eksportem OCR lub PDF oszczędza czas, ponieważ rozpoznawanie i kompresja tekstu zależą od jakości strony. + Umieść dokument na kontrastowej powierzchni z wystarczającą ilością światła i minimalną ilością cieni. + Dostosuj wykryte rogi lub przytnij ręcznie, aby strona prawidłowo wypełniała ramkę. + Eksportuj jako obrazy lub plik PDF, a następnie uruchom OCR, jeśli potrzebujesz tekstu do zaznaczenia. + Chroń lub odblokowuj pliki PDF + Używaj haseł ostrożnie i, jeśli to możliwe, zachowaj edytowalną kopię źródłową + Bezpiecznie obsługuj dostęp do plików PDF + Narzędzia zabezpieczające są przydatne do kontrolowanego udostępniania, ale hasła można łatwo stracić i trudno odzyskać. Chroń kopie do dystrybucji, przechowuj niechronione pliki źródłowe osobno i sprawdzaj wynik przed usunięciem czegokolwiek. + Chroń kopię pliku PDF, a nie jedyny dokument źródłowy. + Przed udostępnieniem chronionego pliku przechowuj hasło w bezpiecznym miejscu. + Użyj opcji odblokowania tylko w przypadku plików, które możesz edytować, a następnie sprawdź, czy odblokowana kopia otwiera się prawidłowo. + Organizuj strony PDF + Łącz, dziel, zmieniaj kolejność, obracaj, przycinaj, usuwaj strony i celowo dodawaj numery stron + Napraw strukturę dokumentu przed dekoracją + Narzędzia do tworzenia stron w formacie PDF są najłatwiejsze w użyciu w tej samej kolejności, w jakiej przygotowuje się dokument papierowy: zbierz strony, usuń niewłaściwe, ułóż je w odpowiedniej kolejności, popraw orientację, a następnie dodaj ostatnie szczegóły, takie jak numery stron lub znaki wodne. + Jeśli dokument jest nadal podzielony na kilka plików, użyj najpierw scalania lub konwersji obrazów do pliku PDF. + Zmień układ, obróć, przytnij lub usuń strony przed dodaniem numerów stron, podpisów lub znaków wodnych. + Wyświetl podgląd ostatecznego pliku PDF po wprowadzeniu zmian strukturalnych, ponieważ brakująca lub obrócona strona może być później trudna do zauważenia. + Adnotacje w formacie PDF + Usuń adnotacje lub spłaszcz je, gdy widzowie inaczej wyświetlają komentarze i oceny + Kontroluj to, co pozostaje widoczne + Adnotacjami mogą być komentarze, wyróżnienia, rysunki, znaki formularzy lub inne dodatkowe obiekty PDF. Niektórzy przeglądarki wyświetlają je inaczej, więc ich usunięcie lub spłaszczenie może sprawić, że udostępniane dokumenty będą bardziej przewidywalne. + Usuń adnotacje, jeśli komentarze, wyróżnienia lub oceny nie powinny być zawarte w udostępnianej kopii. + Spłaszcz, gdy widoczne znaczniki powinny pozostać na stronie, ale nie powinny już zachowywać się jak edytowalne adnotacje. + Zachowaj oryginalną kopię, ponieważ usunięcie lub spłaszczenie adnotacji może być trudne do odwrócenia. + Rozpoznaj tekst + Wyodrębnij tekst z obrazów za pomocą wybranych silników i języków OCR + Uzyskaj lepsze wyniki OCR + Dokładność rozpoznawania OCR zależy od ostrości obrazu, danych językowych, kontrastu i orientacji strony. Najlepsze rezultaty zwykle daje się najpierw przygotować obraz: wytnij szum, obróć go w pionie, zwiększ czytelność, a następnie rozpoznaj tekst. + Przytnij obraz do obszaru tekstowego i obróć go w pionie przed rozpoznaniem. + Wybierz właściwy język i pobierz dane językowe, jeśli aplikacja o to poprosi. + Skopiuj lub udostępnij rozpoznany tekst, a następnie ręcznie sprawdź nazwy, cyfry i znaki interpunkcyjne. + Wybierz dane języka OCR + Popraw rozpoznawanie, dopasowując skrypty, języki i pobrane modele OCR + Dopasuj OCR do tekstu + Niewłaściwy język OCR może sprawić, że dobre zdjęcia będą wyglądać jak złe rozpoznanie. Dane językowe mają znaczenie w przypadku skryptów, znaków interpunkcyjnych, liczb i dokumentów mieszanych, dlatego wybierz język wyświetlany na obrazku, a nie język interfejsu telefonu. + Wybierz język lub skrypt wyświetlany na obrazku, a nie tylko język telefonu. + Pobierz brakujące dane przed pracą w trybie offline lub przetwarzaniem partii. + W przypadku dokumentów wielojęzycznych uruchom najpierw najważniejszy język i ręcznie sprawdź nazwy i numery. + Przeszukiwalne pliki PDF + Zapisz tekst OCR z powrotem do plików, aby można było przeszukiwać i kopiować zeskanowane strony + Zamień skany w dokumenty z możliwością przeszukiwania + OCR może zrobić więcej niż tylko skopiować tekst do schowka. Kiedy piszesz rozpoznany tekst do pliku lub pliku PDF z możliwością przeszukiwania, obraz strony pozostaje widoczny, a wyszukiwanie, zaznaczanie, indeksowanie i archiwizowanie tekstu staje się łatwiejsze. + Użyj przeszukiwalnego pliku PDF w przypadku zeskanowanych dokumentów, które powinny nadal wyglądać jak oryginalne strony. + Użyj zapisu do pliku, jeśli potrzebujesz tylko wyodrębnionego tekstu i nie przejmujesz się obrazem strony. + Sprawdź ręcznie ważne liczby, nazwiska i tabele, ponieważ tekst OCR może być przeszukiwany, ale nadal niedoskonały. + Zeskanuj kody QR + Czytaj zawartość QR z wejścia aparatu lub zapisanych obrazów, jeśli są dostępne + Bezpiecznie czytaj kody + Kody QR mogą zawierać łącza, dane Wi-Fi, kontakty, tekst lub inne ładunki. + Otwórz Zeskanuj kod QR i trzymaj kod ostry, płaski i całkowicie mieszczący się w ramce. + Przejrzyj zdekodowaną treść przed otwarciem linków lub skopiowaniem wrażliwych danych. + Jeśli skanowanie się nie powiedzie, popraw oświetlenie, przytnij kod lub wypróbuj obraz źródłowy o wyższej rozdzielczości. + Użyj Base64 i sum kontrolnych + Zakoduj obrazy jako tekst, dekoduj tekst z powrotem do obrazów i sprawdź integralność plików + Poruszaj się pomiędzy plikami i tekstem + Base64 jest przydatny w przypadku małych ładunków obrazu, podczas gdy sumy kontrolne pomagają potwierdzić, że pliki nie uległy zmianie. Narzędzia te są najbardziej przydatne w przepływach pracy technicznych, raportach pomocy technicznej, przesyłaniu plików i miejscach, w których pliki binarne muszą tymczasowo stać się tekstem. + Użyj narzędzi Base64, aby zakodować mały obraz, gdy przepływ pracy wymaga tekstu zamiast pliku binarnego. + Odkoduj Base64 tylko z zaufanych źródeł i sprawdź podgląd przed zapisaniem. + Użyj narzędzi sum kontrolnych, jeśli chcesz porównać wyeksportowane pliki lub potwierdzić przesłanie/pobranie. + Pakuj lub chroń dane + Użyj narzędzi ZIP i szyfrowania do łączenia plików lub przekształcania danych tekstowych + Trzymaj powiązane pliki razem + Narzędzia do pakowania są pomocne, gdy kilka wyników powinno być przesyłanych w jednym pliku. Nadają się również do przechowywania wygenerowanych obrazów, plików PDF, dzienników i metadanych razem, gdy trzeba wysłać kompletny zestaw wyników. + Użyj ZIP, jeśli chcesz wysłać razem wiele wyeksportowanych obrazów lub dokumentów. + Używaj narzędzi szyfrujących tylko wtedy, gdy rozumiesz wybraną metodę i potrafisz bezpiecznie przechowywać wymagane klucze. + Przetestuj utworzone archiwum lub przekształcony tekst przed usunięciem plików źródłowych. + Sumy kontrolne w nazwach + Używaj nazw plików opartych na sumie kontrolnej, gdy unikalność i integralność są ważniejsze niż czytelność + Nazwij pliki według zawartości + Nazwy plików z sumą kontrolną są przydatne, gdy eksport może mieć zduplikowane widoczne nazwy lub gdy trzeba udowodnić, że dwa pliki są identyczne. Są mniej przyjazne w przypadku ręcznego przeglądania, więc używaj ich do archiwów technicznych i procesów weryfikacji. + Włącz sumę kontrolną jako nazwę pliku, gdy tożsamość treści jest ważniejsza niż tytuł czytelny dla człowieka. + Używaj go do archiwizacji, deduplikacji, powtarzalnych eksportów lub plików, które można ponownie przesłać i pobrać. + Łącz nazwy sum kontrolnych z folderami lub metadanymi, gdy ludzie nadal muszą zrozumieć, co reprezentuje plik. + Wybierz kolory z obrazów + Próbuj piksele, kopiuj kody kolorów i ponownie wykorzystuj kolory w paletach lub projektach + Próbka dokładnego koloru + Wybieranie kolorów jest przydatne przy dopasowywaniu projektu, wyodrębnianiu kolorów marki lub sprawdzaniu wartości obrazu. Powiększanie ma znaczenie, ponieważ wygładzanie, cienie i kompresja mogą sprawić, że sąsiednie piksele będą nieznacznie różnić się od oczekiwanego koloru. + Otwórz opcję Wybierz kolor z obrazu i wybierz obraz źródłowy o żądanym kolorze. + Przed próbkowaniem małych szczegółów powiększ obraz, aby pobliskie piksele nie miały wpływu na wynik. + Skopiuj kolor w formacie wymaganym przez miejsce docelowe, np. HEX, RGB lub HSV. + Twórz palety i korzystaj z biblioteki kolorów + Wyodrębniaj palety, przeglądaj zapisane kolory i zachowaj spójność systemów wizualnych + Zbuduj paletę wielokrotnego użytku + Palety pomagają zachować spójność wizualną eksportowanej grafiki, znaków wodnych i rysunków. Są szczególnie przydatne, gdy wielokrotnie dodajesz adnotacje do zrzutów ekranu, przygotowujesz zasoby marki lub potrzebujesz tych samych kolorów w kilku narzędziach. + Wygeneruj paletę z obrazu lub dodaj kolory ręcznie, gdy znasz już wartości. + Nazwij lub uporządkuj ważne kolory, aby móc je później znaleźć. + Użyj skopiowanych wartości w narzędziach Draw, znaku wodnego, gradientu, SVG lub zewnętrznych narzędziach do projektowania. + Twórz gradienty + Twórz obrazy gradientowe na potrzeby tła, obiektów zastępczych i eksperymentów wizualnych + Kontroluj przejścia kolorów + Narzędzia gradientowe są przydatne, gdy potrzebujesz wygenerowanego obrazu zamiast edytować istniejący. Mogą stać się czystym tłem, obiektami zastępczymi, tapetami, maskami lub prostymi zasobami dla zewnętrznych narzędzi projektowych. + Wybierz typ gradientu i najpierw ustaw ważne kolory. + Dostosuj kierunek, zatrzymania, płynność i rozmiar wyjściowy dla docelowego przypadku użycia. + Eksportuj w formacie pasującym do miejsca docelowego, zwykle PNG, aby uzyskać czystą wygenerowaną grafikę. + Dostępność kolorów + Używaj dynamicznych kolorów, kontrastu i opcji daltonizmu, gdy interfejs użytkownika jest trudny do odczytania + Ułatw sobie używanie kolorów + Ustawienia kolorów wpływają na wygodę, czytelność i szybkość skanowania elementów sterujących. Jeśli trudno rozróżnić elementy narzędzi, suwaki lub wybrane stany, opcje motywu i ułatwień dostępu mogą być bardziej przydatne niż zwykła zmiana trybu ciemnego. + Wypróbuj dynamiczne kolory, jeśli chcesz, aby aplikacja dopasowywała się do aktualnej palety tapet. + Zwiększ kontrast lub zmień schemat, gdy przyciski i powierzchnie nie wyróżniają się wystarczająco. + Użyj opcji schematu dla daltonistów, jeśli niektóre stany lub akcenty są trudne do rozróżnienia. + Tło alfa + Kontroluj kolor podglądu lub wypełnienia używany wokół przezroczystych plików PNG, WebP i podobnych + Poznaj przejrzyste podglądy + Kolor tła w formatach alfa może ułatwić kontrolę przezroczystych obrazów, ale ważne jest, aby wiedzieć, czy zmieniasz zachowanie podglądu/wypełnienia, czy też spłaszczasz wynik końcowy. Ma to znaczenie w przypadku naklejek, logo i obrazów z usuniętym tłem. + Jeśli przezroczyste piksele muszą przetrwać eksport, użyj najpierw formatu obsługującego alfa, takiego jak PNG lub WebP. + Dostosuj ustawienia koloru tła, gdy przezroczyste krawędzie są słabo widoczne na powierzchni aplikacji. + Wyeksportuj mały test i otwórz go ponownie w innej aplikacji, aby sprawdzić, czy przezroczystość lub wybrane wypełnienie zostały zapisane. + Wybór modelu AI + Zamiast wybierać najpierw największy, wybierz model według typu obrazu i wady + Dopasuj model do uszkodzenia + Narzędzia AI mogą pobierać lub importować różne modele ONNX/ORT, a każdy model jest szkolony pod kątem określonego rodzaju problemu. Model bloków JPEG, czyszczenie linii anime, odszumianie, usuwanie tła, kolorowanie lub skalowanie w górę może dawać gorsze wyniki, jeśli zostanie użyty do niewłaściwego typu obrazu. + Przeczytaj opis modelu przed pobraniem; wybierz model, który opisuje rzeczywisty problem, taki jak kompresja, szum, półtony, rozmycie lub usuwanie tła. + Testując nowy typ obrazu, zacznij od mniejszego lub bardziej szczegółowego modelu, a następnie porównaj z cięższymi modelami tylko wtedy, gdy wynik nie jest wystarczająco dobry. + Przechowuj tylko te modele, których naprawdę używasz, ponieważ pobrane i zaimportowane modele mogą zająć zauważalnie miejsce na dysku. + Zrozumienie rodzin modeli + Nazwy modeli często wskazują na cel: ekskluzywne modele w stylu RealESRGAN, modele SCUNet i FBCNN usuwają szum lub kompresję, modele tła tworzą maski, a moduły koloryzujące dodają kolor do sygnału wejściowego w skali szarości. Zaimportowane modele niestandardowe mogą być potężne, ale powinny odpowiadać oczekiwanemu kształtowi wejścia/wyjścia i używać obsługiwanego formatu ONNX lub ORT. + Używaj modułów zwiększających skalę, gdy potrzebujesz większej liczby pikseli, filtrów odszumiających, gdy obraz jest ziarnisty, i narzędzi do usuwania artefaktów, gdy głównym problemem są bloki kompresji lub pasy. + Używaj modeli tła tylko do oddzielania obiektów; nie są one tożsame z ogólnym usuwaniem obiektów lub ulepszaniem obrazu. + Importuj niestandardowe modele tylko z zaufanych źródeł i testuj je na małym obrazie przed użyciem ważnych plików. + Parametry AI + Dostosuj rozmiar porcji, nakładanie się i równoległe procesy robocze, aby zrównoważyć jakość, szybkość i pamięć + Zrównoważ prędkość ze stabilnością + Rozmiar fragmentu określa wielkość fragmentów obrazu przed ich przetworzeniem przez model. Nakładanie łączy sąsiednie fragmenty, aby ukryć granice. Równoległe procesy robocze mogą przyspieszyć przetwarzanie, ale każdy proces roboczy potrzebuje pamięci, dlatego wysokie wartości mogą powodować niestabilność dużych obrazów w telefonach z ograniczoną ilością pamięci RAM. + Użyj większych fragmentów, aby uzyskać płynniejszy kontekst, gdy Twoje urządzenie niezawodnie obsługuje model, a obraz nie jest duży. + Zwiększ nakładanie się, gdy granice fragmentów staną się widoczne, ale pamiętaj, że większe nakładanie się oznacza większą powtarzalność pracy. + Wychowaj pracowników równoległych dopiero po stabilnym teście; jeśli przetwarzanie spowalnia, nagrzewa urządzenie lub ulega awarii, najpierw zmniejsz liczbę pracowników. + Unikaj artefaktów fragmentarycznych + Dzielenie na kawałki pozwala aplikacji przetwarzać obrazy, które są większe, niż model może wygodnie obsłużyć na raz. Wadą jest to, że każda płytka ma mniej otaczającego kontekstu, więc małe nakładanie się lub zbyt małe fragmenty mogą powodować widoczne obramowania, zmiany tekstury lub niespójne szczegóły pomiędzy sąsiednimi obszarami. + Zwiększ nakładanie się, jeśli widzisz prostokątne obramowania, powtarzające się zmiany tekstur lub przeskoki szczegółów pomiędzy przetworzonymi regionami. + Jeśli proces zakończy się niepowodzeniem, zmniejsz rozmiar fragmentu przed utworzeniem wydruku, szczególnie w przypadku dużych obrazów lub ciężkich modeli. + Zapisz test małego przycięcia przed przetworzeniem pełnego obrazu, aby móc szybko porównać parametry. + Stabilność sztucznej inteligencji + Mniejszy rozmiar porcji, procesy robocze i rozdzielczość źródła w przypadku awarii lub zawieszenia przetwarzania AI + Odzyskaj siły po ciężkich zadaniach AI + Przetwarzanie AI to jeden z najcięższych procesów w aplikacji. Awarie, nieudane sesje, zawieszanie się lub nagłe ponowne uruchamianie aplikacji zwykle oznaczają, że model, rozdzielczość źródła, rozmiar fragmentu lub liczba równoległych procesów roboczych są zbyt wymagające w stosunku do bieżącego stanu urządzenia. + Jeśli aplikacja ulegnie awarii lub nie uda się otworzyć sesji modelu, zmniejsz równoległe procesy robocze i rozmiar porcji, a następnie spróbuj ponownie wykonać ten sam obraz. + Zmień rozmiar bardzo dużych obrazów przed przetwarzaniem AI, gdy cel nie wymaga oryginalnej rozdzielczości. + Zamknij inne wymagające aplikacji, użyj mniejszego modelu lub przetwarzaj mniej plików na raz, gdy obciążenie pamięci będzie powracać. + Diagnozuj awarie modelu + Nieudane uruchomienie AI nie zawsze oznacza, że ​​obraz jest zły. Plik modelu może być niekompletny, nieobsługiwany, za duży dla pamięci lub niedopasowany do operacji. Gdy aplikacja zgłosi nieudaną sesję, potraktuj to jako sygnał, aby najpierw uprościć model i parametry. + Usuń i pobierz ponownie model, jeśli nie powiedzie się natychmiast po pobraniu lub zachowuje się tak, jakby plik był uszkodzony. + Wypróbuj wbudowany model do pobrania, zanim zrzucisz winę na zaimportowany model niestandardowy, ponieważ zaimportowane modele mogą mieć niezgodne dane wejściowe. + Użyj dzienników aplikacji po odtworzeniu błędu, jeśli chcesz zgłosić awarię lub błąd sesji. + Eksperymentuj w Shader Studio + Użyj efektów cieniowania GPU, aby uzyskać zaawansowane przetwarzanie obrazu i niestandardowy wygląd + Pracuj ostrożnie z shaderami + Shader Studio jest potężne, ale kod i parametry modułu cieniującego wymagają niewielkich, dających się przetestować zmian. Traktuj to jak narzędzie eksperymentalne: zachowaj działający punkt odniesienia, zmieniaj jedną rzecz na raz i testuj z różnymi rozmiarami obrazów, zanim zaufasz gotowemu rozwiązaniu. + Zacznij od znanego działającego modułu cieniującego lub prostego efektu przed dodaniem parametrów. + Zmieniaj po jednym parametrze lub bloku kodu i sprawdzaj podgląd pod kątem błędów. + Eksportuj gotowe ustawienia dopiero po przetestowaniu ich na kilku różnych rozmiarach obrazu. + Utwórz grafikę SVG lub ASCII + Generuj zasoby wektorowe lub obrazy tekstowe w celu uzyskania kreatywnych wyników + Wybierz typ wyniku kreacji + Narzędzia SVG i ASCII służą różnym celom: skalowalnej grafice lub renderowaniu w stylu tekstu. Obie są konwersjami kreatywnymi, więc podgląd w ostatecznym rozmiarze jest ważniejszy niż ocenianie wyniku na podstawie małej miniatury. + Użyj narzędzia SVG Maker, jeśli wynik powinien być skalowany w sposób przejrzysty lub można go ponownie wykorzystać w procesach projektowania. + Użyj grafiki ASCII, jeśli chcesz stylizowanej reprezentacji tekstowej obrazu. + Podgląd w ostatecznym rozmiarze, ponieważ drobne szczegóły mogą zniknąć z wygenerowanego wyniku. + Generuj tekstury szumu + Twórz tekstury proceduralne dla tła, masek, nakładek lub eksperymentów + Dostrój wyjście proceduralne + Generowanie szumu tworzy nowe obrazy na podstawie parametrów, więc niewielkie zmiany mogą dawać bardzo różne wyniki. Jest przydatny w przypadku tekstur, masek, nakładek, obiektów zastępczych lub testowania kompresji ze szczegółowymi wzorami. + Wybierz typ szumu i rozmiar wyjściowy przed dostrojeniem drobnych szczegółów. + Dostosuj skalę, intensywność, kolory lub materiał siewny, aż wzór będzie pasował do docelowego zastosowania. + Zapisz przydatne wyniki i zapisz parametry, jeśli chcesz później odtworzyć teksturę. + Projekty znaczników + Użyj plików projektu, jeśli adnotacje muszą pozostać edytowalne po zamknięciu aplikacji + Zachowaj możliwość wielokrotnego wykorzystania warstwowych edycji + Pliki projektów znaczników są przydatne, gdy zrzut ekranu, diagram lub obraz instrukcji może wymagać późniejszych zmian. Wyeksportowane pliki PNG/JPEG są ostatecznymi obrazami, podczas gdy pliki projektu zachowują edytowalne warstwy i stan edycji na potrzeby przyszłych poprawek. + Użyj warstw znaczników, jeśli adnotacje, objaśnienia lub elementy układu powinny pozostać edytowalne. + Zapisz lub otwórz ponownie plik projektu przed wyeksportowaniem ostatecznego obrazu, jeśli spodziewasz się większej liczby poprawek. + Eksportuj normalny obraz dopiero wtedy, gdy będziesz gotowy udostępnić spłaszczoną wersję ostateczną. + Szyfruj pliki + Chroń pliki hasłem i przetestuj odszyfrowanie przed usunięciem jakiejkolwiek kopii źródłowej + Ostrożnie obchodź się z zaszyfrowanymi danymi wyjściowymi + Narzędzia szyfrujące mogą chronić pliki za pomocą szyfrowania opartego na hasłach, ale nie zastępują one zapamiętywania klucza ani przechowywania kopii zapasowych. Szyfrowanie jest przydatne do transportu i przechowywania, natomiast ZIP jest lepszy, gdy głównym celem jest połączenie kilku wyników. + Najpierw zaszyfruj kopię i zachowaj oryginał do czasu sprawdzenia, czy odszyfrowanie działa z zapisanym hasłem. + Użyj menedżera haseł lub innego bezpiecznego miejsca na klucz; aplikacja nie może odgadnąć za Ciebie utraconego hasła. + Udostępniaj zaszyfrowane pliki tylko osobom, które również posiadają hasło i wiedzą, jakim narzędziem lub metodą należy je odszyfrować. + Rozmieść narzędzia + Grupuj, porządkuj, wyszukuj i przypinaj narzędzia, aby ekran główny odpowiadał Twojemu rzeczywistemu procesowi pracy + Przyspiesz ekran główny + Warto dostosować ustawienia rozmieszczenia narzędzi, gdy już wiesz, jakich narzędzi używasz najczęściej. Grupowanie ułatwia eksplorację, niestandardowa kolejność pomaga zapamiętywać mięśnie, a ulubione zapewniają dostęp do codziennych narzędzi, nawet gdy pełna lista staje się duża. + Korzystaj z trybu grupowego podczas nauki aplikacji lub gdy wolisz narzędzia uporządkowane według typu zadania. + Przejdź na zamówienie niestandardowe, jeśli znasz już często używane narzędzia i chcesz mieć mniej zwojów. + Użyj jednocześnie ustawień wyszukiwania i ulubionych, aby uzyskać rzadkie narzędzia, które pamiętasz z nazwy, ale nie chcesz, aby były cały czas widoczne. + Obsługuj duże pliki + Unikaj powolnego eksportu, wykorzystania pamięci i nieoczekiwanie dużych wyników + Ogranicz pracę przed eksportem + Bardzo duże obrazy, długie partie i formaty o wysokiej jakości mogą wymagać więcej pamięci i czasu. Najszybszym rozwiązaniem jest zwykle zmniejszenie ilości pracy przed najcięższą operacją, zamiast czekania na zakończenie tego samego, ponadwymiarowego wejścia. + Zmień rozmiar dużych obrazów przed zastosowaniem ciężkich filtrów, OCR lub konwersji PDF. + Najpierw użyj mniejszej partii, aby potwierdzić ustawienia, a następnie przetwórz resztę z tym samym ustawieniem wstępnym. + Obniż jakość lub zmień format, gdy plik wyjściowy jest większy niż oczekiwano. + Pamięć podręczna i podglądy + Poznaj wygenerowane podglądy, czyszczenie pamięci podręcznej i wykorzystanie pamięci po intensywnych sesjach + Zachowaj równowagę pamięci i szybkości + Generowanie podglądu i pamięć podręczna mogą przyspieszyć wielokrotne przeglądanie, ale intensywne sesje edycyjne mogą pozostawić tymczasowe dane. Ustawienia pamięci podręcznej są pomocne, gdy aplikacja działa wolno, brakuje miejsca na dane lub podglądy wydają się nieaktualne po wielu eksperymentach. + Włączanie podglądu podczas przeglądania wielu obrazów jest ważniejsze niż minimalizowanie przechowywania tymczasowego. + Wyczyść pamięć podręczną po dużych partiach, nieudanych eksperymentach lub długich sesjach z wieloma plikami o wysokiej rozdzielczości. + Użyj automatycznego czyszczenia pamięci podręcznej, jeśli wolisz czyszczenie pamięci niż utrzymywanie podglądu pomiędzy uruchomieniami. + Dostosuj zachowanie ekranu + Korzystaj z opcji pełnego ekranu, trybu bezpiecznego, jasności i paska systemowego, aby skoncentrować się na pracy + Dopasuj interfejs do sytuacji + Ustawienia ekranu są pomocne podczas edycji w orientacji poziomej, prezentowania prywatnych zdjęć lub gdy potrzebujesz stałej jasności. Nie są one wymagane do normalnego użytkowania, ale rozwiązują niezręczne sytuacje, takie jak kontrola QR, prywatny podgląd lub ciasna edycja pozioma. + Użyj ustawień pełnego ekranu lub paska systemowego, gdy przestrzeń do edycji jest ważniejsza niż chrom nawigacji. + Użyj trybu bezpiecznego, jeśli prywatne obrazy nie powinny pojawiać się na zrzutach ekranu ani podglądach aplikacji. + Użyj wymuszania jasności w narzędziach, w których trudno jest sprawdzić ciemne obrazy lub kody QR. + Zachowaj przejrzystość + Zapobiegaj zmianie koloru przezroczystego tła na biały, czarny lub spłaszczony + Użyj danych wyjściowych obsługujących alfa + Przezroczystość zostanie zachowana tylko wtedy, gdy wybrane narzędzie i format wyjściowy obsługują wersję alfa. Jeśli wyeksportowane tło zmieni kolor na biały lub czarny, problemem jest zwykle format wyjściowy, ustawienie wypełnienia/tła lub aplikacja docelowa, która spłaszczyła obraz. + Wybierz PNG lub WebP podczas eksportowania obrazów, naklejek, logo lub nakładek z usuniętym tłem. + Unikaj formatu JPEG w przypadku przezroczystych wyników, ponieważ nie przechowuje on kanału alfa. + Sprawdź ustawienia związane z kolorem tła dla formatów alfa, jeśli podgląd pokazuje wypełnione tło. + Napraw problemy z udostępnianiem i importowaniem + Użyj ustawień selektora i obsługiwanych formatów, gdy pliki nie pojawiają się lub nie otwierają się poprawnie + Pobierz plik do aplikacji + Problemy z importem są często spowodowane uprawnieniami dostawcy, nieobsługiwanymi formatami lub zachowaniem selektora. Pliki udostępniane z aplikacji w chmurze i komunikatorów mogą być tymczasowe, więc wybranie stałego źródła może być bezpieczniejsze w przypadku dłuższych edycji. + Spróbuj otworzyć plik za pomocą selektora systemowego zamiast skrótu do ostatnich plików. + Jeśli jedno narzędzie nie obsługuje formatu, najpierw go przekonwertuj lub otwórz bardziej szczegółowe narzędzie. + Przejrzyj ustawienia selektora obrazów i programu uruchamiającego, jeśli przepływy udostępniania lub bezpośrednie otwieranie narzędzi zachowują się inaczej niż oczekiwano. + Potwierdzenie wyjścia + Unikaj utraty niezapisanych zmian w przypadku przypadkowych gestów, naciśnięć wstecz lub przełączeń narzędzi + Chroń niedokończoną pracę + Potwierdzenie wyjścia narzędzia jest przydatne, gdy korzystasz z nawigacji gestami, edytujesz duże pliki lub często przełączasz się między aplikacjami. Dodaje małą pauzę przed opuszczeniem narzędzia, dzięki czemu niedokończone ustawienia i podglądy nie zostaną przypadkowo utracone. + Włącz potwierdzenie, jeśli przypadkowe gesty wstecz kiedykolwiek zamknęły narzędzie przed eksportem. + Pozostaw tę opcję wyłączoną, jeśli głównie wykonujesz szybkie jednoetapowe konwersje i wolisz szybszą nawigację. + Używaj go razem z ustawieniami wstępnymi w przypadku dłuższych przepływów pracy, w których przebudowa ustawień byłaby denerwująca. + Podczas zgłaszania problemów korzystaj z dzienników + Zbierz przydatne informacje przed otwarciem problemu lub skontaktowaniem się z programistą + Zgłaszaj problemy z kontekstem + Przejrzysty raport zawierający kroki, wersję aplikacji, typ danych wejściowych i dzienniki jest znacznie łatwiejszy do zdiagnozowania. Dzienniki są najbardziej przydatne zaraz po odtworzeniu problemu, ponieważ utrzymują świeży kontekst. + Zanotuj nazwę narzędzia, dokładną akcję i to, czy dzieje się to z jednym plikiem, czy z każdym plikiem. + Otwórz dzienniki aplikacji po odtworzeniu problemu i udostępnij odpowiednie wyniki dziennika, jeśli to możliwe. + Dołącz zrzuty ekranu lub przykładowe pliki tylko wtedy, gdy nie zawierają informacji prywatnych. + Przetwarzanie w tle zostało zatrzymane + Android nie pozwolił na terminowe rozpoczęcie powiadomienia o przetwarzaniu w tle. Jest to ograniczenie czasowe systemu, którego nie można wiarygodnie naprawić. Uruchom ponownie aplikację i spróbuj ponownie; Pomocne może być pozostawienie aplikacji otwartej i zezwolenie na powiadomienia lub pracę w tle. + Pomiń wybieranie plików + Otwieraj narzędzia szybciej, gdy dane wejściowe zwykle pochodzą z udziału, schowka lub poprzedniego ekranu + Spraw, aby wielokrotne uruchamianie narzędzi było szybsze + Pomiń wybieranie plików zmienia pierwszy krok obsługiwanych narzędzi. Jest to przydatne, gdy zwykle wchodzisz do narzędzia z już wybranym obrazem lub z akcji udostępniania Androida, ale może to być mylące, jeśli oczekujesz, że każde narzędzie natychmiast poprosi o plik. + Włącz tę opcję tylko wtedy, gdy zwykły przepływ zapewnia już obraz źródłowy przed otwarciem narzędzia. + Pozostaw tę opcję wyłączoną podczas nauki aplikacji, ponieważ wyraźne wybieranie ułatwia zrozumienie przepływu każdego narzędzia. + Jeśli narzędzie otworzy się bez oczywistych danych wejściowych, wyłącz to ustawienie lub zacznij od Udostępnij/Otwórz za pomocą, aby system Android przekazał plik jako pierwszy. + Najpierw otwórz edytor + Pomiń podgląd, gdy udostępniony obraz powinien przejść bezpośrednio do edycji + Jako pierwszy przystanek wybierz podgląd lub edycję + Otwórz opcję Edytuj zamiast podglądu jest przeznaczona dla użytkowników, którzy już wiedzą, że chcą zmodyfikować obraz. Podgląd jest bezpieczniejszy, gdy sprawdzasz pliki z czatu lub chmury; edycja bezpośrednia jest szybsza w przypadku zrzutów ekranu, szybkiego kadrowania, adnotacji i jednorazowych poprawek. + Włącz bezpośrednią edycję, jeśli większość udostępnionych obrazów wymaga natychmiastowego przycięcia, znaczników, filtrów lub wyeksportowania zmian. + Jeśli często sprawdzasz metadane, wymiary, przezroczystość lub jakość obrazu, przed podjęciem decyzji pozostaw podgląd jako pierwszy. + Używaj tego razem z ulubionymi, aby udostępnione zdjęcia znajdowały się blisko narzędzi, których faktycznie używasz. + Wstępnie ustawione wprowadzanie tekstu + Wprowadź dokładne, wstępnie ustawione wartości, zamiast wielokrotnie regulować elementy sterujące + Używaj dokładnych wartości, gdy suwaki działają wolno + Wstępnie ustawione wprowadzanie tekstu pomaga, gdy potrzebne są dokładne liczby do powtarzalnej pracy: rozmiary obrazów społecznościowych, marginesy dokumentów, cele kompresji, szerokości linii lub inne wartości, które irytują w obsłudze za pomocą gestów. Jest to również przydatne na małych ekranach, gdzie precyzyjny ruch suwaka jest trudniejszy. + Włącz wprowadzanie tekstu, jeśli często używasz dokładnych rozmiarów, wartości procentowych, wartości jakości lub parametrów narzędzi. + Zapisz wartość jako ustawienie wstępne po jednokrotnym wpisaniu, a następnie użyj jej ponownie zamiast odbudowywać tę samą konfigurację. + Zachowaj kontrolę gestów w celu wstępnego dostrojenia wizualnego i wpisywania wartości w celu spełnienia rygorystycznych wymagań wyjściowych. + Zapisz blisko oryginału + Jeśli kontekst folderu ma znaczenie, umieść wygenerowane pliki obok obrazów źródłowych + Zachowaj wyniki wraz ze źródłami + Zapisz w oryginalnym folderze jest przydatny w przypadku folderów aparatu, folderów projektów, skanów dokumentów i partii klientów, gdzie edytowana kopia powinna pozostać obok źródła. Może być mniej uporządkowany niż dedykowany folder wyjściowy, dlatego połącz go z wyraźnymi przyrostkami lub wzorami nazw plików. + Włącz tę opcję, gdy każdy folder źródłowy ma już znaczenie i dane wyjściowe powinny pozostać tam zgrupowane. + Używaj przyrostków, przedrostków, znaczników czasu lub wzorców nazw plików, aby edytowane kopie można było łatwo oddzielić od oryginałów. + Wyłącz tę opcję dla partii mieszanych, jeśli chcesz, aby wszystkie wygenerowane pliki były gromadzone w jednym folderze eksportu. + Pomiń większą moc wyjściową + Unikaj zapisywania konwersji, które nieoczekiwanie stają się cięższe niż źródło + Chroń partie przed niespodziankami o złym rozmiarze + Niektóre konwersje powodują powiększenie plików, zwłaszcza zrzutów ekranu w formacie PNG, już skompresowanych zdjęć, wysokiej jakości plików WebP lub obrazów z zachowanymi metadanymi. Zezwalaj na pomijanie, jeśli większe uniemożliwia zastąpienie mniejszych źródeł przez te wyjścia w przepływach pracy, w których głównym celem jest zmniejszenie rozmiaru. + Włącz tę opcję, jeśli eksport ma na celu skompresowanie, zmianę rozmiaru lub przygotowanie plików do limitów przesyłania. + Przejrzyj pominięte pliki po partii; mogą być już zoptymalizowane lub mogą wymagać innego formatu. + Wyłącz tę opcję, jeśli jakość, przezroczystość lub zgodność formatu są ważniejsze niż ostateczny rozmiar pliku. + Schowek i linki + Kontroluj automatyczne wprowadzanie danych w schowku i podgląd linków, aby uzyskać szybsze narzędzia tekstowe + Zapewnij przewidywalność automatycznego wprowadzania danych + Ustawienia schowka i łączy są wygodne w przypadku przepływów pracy zawierających kody QR, Base64, adresy URL i duże ilości tekstu, ale automatyczne wprowadzanie powinno pozostać zamierzone. Jeśli aplikacja sama wypełnia pola, sprawdź zachowanie wklejania w schowku; jeśli karty łączy wydają się głośne lub powolne, dostosuj zachowanie podglądu łącza. + Zezwalaj na automatyczne wklejanie do schowka, gdy często kopiujesz tekst, i natychmiast otwieraj pasujące narzędzie. + Wyłącz automatyczne wklejanie, jeśli prywatna zawartość schowka pojawia się w narzędziach, gdzie się tego nie spodziewałeś. + Wyłącz podgląd linków, gdy pobieranie podglądu jest powolne, rozpraszające lub niepotrzebne w przepływie pracy. + Kompaktowe sterowanie + Używaj gęstszych selektorów i szybkiego umieszczania ustawień, gdy na ekranie jest mało miejsca + Dopasuj więcej elementów sterujących na małych ekranach + Kompaktowe selektory i szybkie rozmieszczanie ustawień pomagają w małych telefonach, edycji poziomej i narzędziach z wieloma parametrami. Kompromis polega na tym, że sterowanie może wydawać się gęstsze, więc najlepiej jest to zrobić, gdy już rozpoznasz typowe opcje. + Włącz kompaktowe selektory, gdy okna dialogowe lub listy opcji wydają się zbyt wysokie na ekranie. + Dostosuj stronę szybkich ustawień, jeśli dostęp do elementów sterujących narzędziami jest łatwiejszy z jednej strony wyświetlacza. + Wróć do bardziej przestronnych elementów sterujących, jeśli nadal uczysz się aplikacji lub często pomijasz gęste opcje. + Załaduj obrazy z Internetu + Wklej adres URL strony lub obrazu, wyświetl podgląd przeanalizowanych obrazów, a następnie zapisz lub udostępnij wybrane wyniki + Celowo używaj obrazów internetowych + Web Image Loading analizuje źródła obrazów z podanego adresu URL, wyświetla podgląd pierwszego dostępnego obrazu i umożliwia zapisanie lub udostępnienie wybranych przeanalizowanych obrazów. Niektóre witryny korzystają z łączy tymczasowych, chronionych lub generowanych przez skrypty, więc strona działająca w przeglądarce może nadal nie zwracać żadnych użytecznych obrazów. + Wklej bezpośredni adres URL obrazu lub adres URL strony i poczekaj, aż pojawi się lista przeanalizowanych obrazów. + Użyj elementów sterujących ramką lub zaznaczeniem, gdy strona zawiera kilka obrazów, a potrzebujesz tylko niektórych z nich. + Jeśli nic się nie ładuje, wypróbuj bezpośredni link do obrazu lub najpierw pobierz go przez przeglądarkę; witryna może blokować parsowanie lub korzystać z linków prywatnych. + Wybierz typ zmiany rozmiaru + Zapoznaj się z Jasnymi, Elastycznymi, Przytnij i Dopasuj, zanim narzucisz obrazowi nowe wymiary + Kontroluj kształt, płótno i proporcje + Typ zmiany rozmiaru decyduje, co się stanie, gdy żądana szerokość i wysokość nie będą odpowiadać oryginalnym współczynnikom proporcji. Jest to różnica między rozciąganiem pikseli, zachowaniem proporcji, przycięciem dodatkowego obszaru lub dodaniem tła. + Użyj opcji Jasny tylko wtedy, gdy zniekształcenie jest akceptowalne lub źródło ma już ten sam współczynnik proporcji co obiekt docelowy. + Użyj opcji Elastyczny, aby zachować proporcje poprzez zmianę rozmiaru od najważniejszej strony, szczególnie w przypadku zdjęć i partii mieszanych. + Użyj opcji Przytnij, aby uzyskać ścisłe ramki bez obramowania, lub Dopasuj, gdy cały obraz musi pozostać widoczny na stałym obszarze roboczym. + Wybierz tryb skali obrazu + Wybierz filtr ponownego próbkowania, który kontroluje ostrość, gładkość, szybkość i artefakty krawędziowe + Zmień rozmiar bez przypadkowej miękkości + Tryb skali obrazu kontroluje sposób obliczania nowych pikseli podczas zmiany rozmiaru. Proste tryby są szybkie i przewidywalne, natomiast zaawansowane filtry mogą lepiej zachować szczegóły, ale mogą wyostrzyć krawędzie, stworzyć aureolę lub zająć więcej czasu w przypadku dużych obrazów. + Użyj opcji Podstawowy lub Dwuliniowy, aby szybko zmieniać rozmiar codziennie, gdy wynik ma być jedynie mniejszy i czysty. + Użyj trybów w stylu Lanczos, Robidoux, Spline lub EWA, gdy liczą się drobne szczegóły, tekst lub wysokiej jakości zmniejszanie skali. + Użyj opcji Najbliższe w przypadku grafik pikselowych, ikon i grafik o ostrych krawędziach, w przypadku których rozmyte piksele mogłyby zepsuć wygląd. + Skaluj przestrzeń kolorów + Zdecyduj, w jaki sposób kolory będą mieszane podczas ponownego próbkowania pikseli podczas zmiany rozmiaru + Zachowaj naturalne gradienty i krawędzie + Skaluj przestrzeń kolorów zmienia matematykę używaną podczas zmiany rozmiaru. Większość obrazów jest w porządku przy ustawieniach domyślnych, ale przestrzenie liniowe lub percepcyjne mogą sprawić, że gradienty, ciemne krawędzie i nasycone kolory będą wyglądać na czystsze po mocnym skalowaniu. + Pozostaw ustawienie domyślne, gdy szybkość i kompatybilność mają większe znaczenie niż drobne różnice w kolorach. + Wypróbuj tryby liniowy lub percepcyjny, gdy ciemne kontury, zrzuty ekranu interfejsu użytkownika, gradienty lub paski kolorów wyglądają nieprawidłowo po zmianie rozmiaru. + Porównaj przed i po na prawdziwym ekranie docelowym, ponieważ zmiany przestrzeni kolorów mogą być subtelne i zależne od treści. + Ogranicz tryby zmiany rozmiaru + Wiedz, kiedy należy pominąć, przekodować lub zmniejszyć obrazy przekraczające limity, aby je dopasować + Wybierz, co dzieje się na limicie + Limit Resize to nie tylko narzędzie do zmniejszania rozmiaru obrazu. Jego tryb decyduje o tym, co aplikacja zrobi, gdy źródło znajdzie się już w Twoich granicach lub gdy tylko jedna strona złamie regułę, co jest ważne w przypadku mieszanych folderów i przygotowania do przesyłania. + Użyj opcji Pomiń, jeśli obrazy znajdujące się w granicach powinny pozostać nietknięte i nie powinny być ponownie eksportowane. + Użyj opcji Rekoduj, jeśli wymiary są akceptowalne, ale nadal potrzebujesz nowego formatu, zachowania metadanych, jakości lub nazwy pliku. + Użyj Zoomu, gdy obrazy przekraczające granice powinny zostać proporcjonalnie zmniejszone, aż zmieszczą się w dozwolonym polu. + Docelowe proporcje obrazu + Unikaj rozciągniętych twarzy, przyciętych krawędzi i nieoczekiwanych obramowań w przypadku ściśle określonych rozmiarów wyjściowych + Dopasuj kształt przed zmianą rozmiaru + Szerokość i wysokość to tylko połowa docelowej zmiany rozmiaru. Proporcje obrazu decydują o tym, czy obraz może się naturalnie zmieścić, czy wymaga przycięcia, czy też wymaga wokół niego płótna. Sprawdzenie najpierw kształtu zapobiega najbardziej zaskakującym efektom zmiany rozmiaru. + Porównaj kształt źródłowy z kształtem docelowym, zanim wybierzesz opcję Wyraźny, Przytnij lub Dopasuj. + Przytnij najpierw, gdy obiekt może stracić dodatkowe tło, a miejsce docelowe wymaga ścisłej ramki. + Użyj opcji Dopasuj lub Elastyczny, jeśli każda część oryginalnego obrazu musi pozostać widoczna. + Ekskluzywna lub normalna zmiana rozmiaru + Wiedz, kiedy dodanie pikseli wymaga sztucznej inteligencji, a kiedy wystarczy proste skalowanie + Nie myl rozmiaru ze szczegółami + Normalna zmiana rozmiaru zmienia wymiary poprzez interpolację pikseli; nie może wymyślać prawdziwych szczegółów. Ekskluzywna sztuczna inteligencja może tworzyć ostrzejsze szczegóły, ale jest wolniejsza i może powodować halucynacje tekstur, więc właściwy wybór zależy od tego, czy potrzebujesz kompatybilności, szybkości czy przywrócenia wizualnego. + Używaj normalnej zmiany rozmiaru miniatur, limitów przesyłania, zrzutów ekranu i prostych zmian wymiarów. + Skorzystaj ze skalowania AI, gdy mały lub miękki obraz musi wyglądać lepiej w większym rozmiarze. + Porównaj twarze, tekst i wzory po zwiększeniu poziomu AI, ponieważ wygenerowane szczegóły mogą wyglądać przekonująco, ale niepoprawnie. + Kolejność filtrów ma znaczenie + Zastosuj czyszczenie, kolor, ostrość i końcową kompresję w zamierzonej kolejności + Zbuduj czystszy stos filtrów + Filtry nie zawsze są wymienne. Rozmycie przed wyostrzeniem, zwiększenie kontrastu przed OCR lub zmiana koloru przed kompresją może dać bardzo różne wyniki przy tych samych elementach sterujących. + Najpierw przytnij i obróć, aby później filtry działały tylko na pikselach, które pozostaną. + Przed wyostrzeniem lub stylizacją zastosuj ekspozycję, balans bieli i kontrast. + Zachowaj ostateczną zmianę rozmiaru i kompresję pod koniec, aby podglądane szczegóły przetrwały eksport w przewidywalny sposób. + Napraw krawędzie tła + Oczyść aureole, resztki cieni, włosy, dziury i miękkie kontury po automatycznym usunięciu + Spraw, aby wycięcia wyglądały celowo + Automatyczne maski często dobrze wyglądają na pierwszy rzut oka, ale ujawniają problemy na jasnym, ciemnym lub wzorzystym tle. Oczyszczanie krawędzi to różnica między szybkim wycięciem a naklejką, logo lub obrazem produktu wielokrotnego użytku. + Wyświetl podgląd wyniku na kontrastowym tle, aby odsłonić aureole i brakujące szczegóły krawędzi. + Użyj przywracania włosów, narożników i przezroczystych obiektów przed usunięciem otaczających resztek. + Wyeksportuj mały test ostatecznego koloru tła, gdy wycinek zostanie umieszczony w projekcie. + Czytelne znaki wodne + Spraw, aby ślady były widoczne na jasnych, ciemnych, zajętych i przyciętych obrazach, nie niszcząc zdjęcia + Chroń obraz, nie ukrywając go + Znak wodny, który dobrze wygląda na jednym obrazie, może zniknąć na innym. Rozmiar, krycie, kontrast, rozmieszczenie i powtarzalność należy wybrać dla całej partii, a nie tylko pierwszego podglądu. + Przed eksportowaniem wszystkich plików przetestuj znak na najjaśniejszych i najciemniejszych obrazach w partii. + Użyj mniejszego krycia w przypadku dużych, powtarzających się znaków i większego kontrastu w przypadku małych znaków w narożnikach. + Ważne twarze, tekst i szczegóły produktu powinny być wyraźne, chyba że znak ma zapobiegać ponownemu użyciu. + Podziel jeden obraz na kafelki + Wytnij pojedynczy obraz według wierszy, kolumn, niestandardowych wartości procentowych, formatu i jakości + Przygotuj siatki i zamówione elementy + Podział obrazu tworzy wiele wyników z jednego obrazu źródłowego. Może dzielić według liczby wierszy i kolumn, dostosowywać wartości procentowe wierszy lub kolumn oraz zapisywać fragmenty z wybranym formatem wyjściowym i jakością, co jest przydatne w przypadku siatek, wycinków stron, panoram i uporządkowanych postów społecznościowych. + Wybierz obraz źródłowy, a następnie ustaw liczbę potrzebnych wierszy i kolumn. + Dostosuj wartości procentowe wierszy lub kolumn, jeśli nie wszystkie elementy powinny być tej samej wielkości. + Wybierz format wyjściowy i jakość przed zapisaniem, aby każdy wygenerowany element korzystał z tych samych reguł eksportu. + Wytnij paski obrazu + Usuń obszary pionowe lub poziome i połącz pozostałe części z powrotem + Usuń region bez pozostawiania przerwy + Cięcie obrazu różni się od normalnego kadrowania. Może usunąć wybrany pionowy lub poziomy pasek, a następnie połączyć pozostałe części obrazu w całość. Zamiast tego tryb odwrotny zachowuje wybrany region, a użycie obu odwrotnych kierunków zachowuje wybrany prostokąt. + Użyj cięcia pionowego w przypadku obszarów bocznych, kolumn lub pasów środkowych; użyj cięcia poziomego w przypadku banerów, przerw lub rzędów. + Włącz odwrotność osi, jeśli chcesz zachować wybraną część zamiast ją usuwać. + Podgląd krawędzi po wycięciu, ponieważ scalona zawartość może wyglądać gwałtownie, gdy usunięty obszar przecina ważne szczegóły. + Wybierz format wyjściowy + Wybierz JPEG, PNG, WebP, AVIF, JXL lub PDF na podstawie zawartości i miejsca docelowego + Pozwól, aby miejsce docelowe zdecydowało o formacie + Najlepszy format zależy od tego, co zawiera obraz i gdzie będzie używany. Zdjęcia, zrzuty ekranu, przezroczyste zasoby, dokumenty i pliki animowane – wszystko to zawodzi na różne sposoby, gdy zostanie nadane w niewłaściwym formacie. + Użyj formatu JPEG, aby zapewnić szeroką kompatybilność zdjęć, gdy nie jest wymagana przezroczystość i dokładne piksele. + Użyj formatu PNG, aby uzyskać ostre zrzuty ekranu, przechwytywania interfejsu użytkownika, przejrzystą grafikę i bezstratne edycje. + Użyj WebP, AVIF lub JXL, gdy liczy się kompaktowy, nowoczesny wydruk i obsługuje go aplikacja odbierająca. + Wyodrębnij okładki audio + Zapisz osadzone okładki albumów lub dostępne ramki multimedialne z plików audio jako obrazy PNG + Wyciągnij grafikę z plików audio + Audio Covers wykorzystuje metadane multimedialne systemu Android do odczytywania osadzonej grafiki z plików audio. Gdy osadzona grafika jest niedostępna, aporter może powrócić do ramki multimedialnej, jeśli system Android może ją zapewnić; jeśli żaden z nich nie istnieje, narzędzie zgłasza, że ​​nie ma obrazu do wyodrębnienia. + Otwórz Okładki audio i wybierz jeden lub więcej plików audio z oczekiwaną okładką. + Przejrzyj wyodrębnioną okładkę przed zapisaniem lub udostępnieniem, szczególnie w przypadku plików z aplikacji do przesyłania strumieniowego lub nagrywania. + Jeśli nie zostanie znaleziony żaden obraz, plik audio prawdopodobnie nie ma osadzonej okładki lub system Android nie może wyświetlić użytecznej ramki. + Metadane dat i lokalizacji + Zdecyduj, co zachować, gdy liczy się czas przechwytywania, ale dane GPS lub urządzenia nie powinny wyciekać + Oddziel przydatne metadane od prywatnych metadanych + Nie wszystkie metadane są równie ryzykowne. Daty przechwycenia mogą być przydatne do archiwizowania i sortowania, podczas gdy GPS, pola przypominające numer seryjny urządzenia, znaczniki oprogramowania lub pola właściciela mogą ujawnić więcej, niż było to zamierzone. + Zachowaj znaczniki daty i godziny, gdy w przypadku albumów, skanów lub historii projektu liczy się porządek chronologiczny. + Usuń znaczniki GPS i urządzenia identyfikujące urządzenia przed publicznym udostępnieniem lub wysłaniem zdjęć nieznajomym. + Wyeksportuj próbkę i ponownie sprawdź EXIF, gdy wymagania dotyczące prywatności są rygorystyczne. + Unikaj kolizji nazw plików + Chroń wyniki wsadowe, gdy wiele plików ma takie same nazwy, jak obraz.jpg lub zrzut ekranu.png + Spraw, aby każda nazwa wyjściowa była unikalna + Zduplikowane nazwy plików są powszechne, gdy obrazy pochodzą z komunikatorów, zrzutów ekranu, folderów w chmurze lub wielu kamer. Dobry wzorzec zapobiega przypadkowej wymianie i zapewnia identyfikowalność wyników aż do ich źródeł. + Jeśli edytowana kopia powinna być rozpoznawalna, dołącz oryginalną nazwę i przyrostek. + Dodaj sekwencję, znacznik czasu, ustawienie wstępne lub wymiary podczas przetwarzania dużych partii mieszanych. + Unikaj zastępowania przepływów pracy, dopóki nie upewnisz się, że wzorzec nazewnictwa nie może kolidować. + Zapisz lub udostępnij + Wybierz pomiędzy plikiem trwałym a tymczasowym przekazaniem do innej aplikacji + Eksportuj z właściwym zamiarem + Funkcja Zapisz i udostępnij może wydawać się podobna, ale rozwiązuje różne problemy. Zapisanie tworzy plik, który możesz później znaleźć; udostępnianie wysyła wygenerowany wynik za pośrednictwem Androida do innej aplikacji i może nie pozostawiać trwałej kopii lokalnej. + Użyj opcji Zapisz, jeśli dane wyjściowe muszą pozostać w znanym folderze, utworzyć kopię zapasową lub zostać ponownie wykorzystane później. + Użyj opcji Udostępnij, aby szybko wysyłać wiadomości, załączniki do wiadomości e-mail, publikować wpisy w mediach społecznościowych lub wysyłać wyniki do innego redaktora. + Zapisz najpierw, jeśli aplikacja odbierająca nie jest wiarygodna lub gdy odtworzenie nieudanego udziału byłoby denerwujące. + Rozmiar strony PDF i marginesy + Przed utworzeniem dokumentu wybierz rozmiar papieru, zachowanie dopasowania i chwilę wytchnienia + Spraw, aby strony obrazów mogły być drukowane i czytelne + Obrazy mogą stać się niewygodnymi stronami PDF, jeśli ich kształt nie pasuje do wybranego rozmiaru papieru. Marginesy, tryb dopasowania i orientacja strony decydują o tym, czy treść jest przycięta, niewielka, rozciągnięta czy wygodna w czytaniu. + Jeśli plik PDF może zostać wydrukowany lub przesłany do formularza, użyj rzeczywistego rozmiaru papieru. + W przypadku skanów i zrzutów ekranu używaj marginesów, aby tekst nie przylegał do krawędzi strony. + Wyświetl podgląd stron w orientacji pionowej i poziomej, jeśli obrazy źródłowe mają mieszaną orientację. + Wyczyść skany + Popraw krawędzie papieru, cienie, kontrast, obrót i czytelność przed PDF lub OCR + Przygotuj strony przed archiwizacją + Skan może zostać przechwycony technicznie, ale nadal trudny do odczytania. Małe kroki porządkowe przed eksportem do pliku PDF często mają większe znaczenie niż ustawienia kompresji, szczególnie w przypadku paragonów, odręcznych notatek i stron przy słabym oświetleniu. + Popraw perspektywę i przytnij krawędzie stołu, palce, cienie i bałagan w tle. + Ostrożnie zwiększaj kontrast, aby tekst stał się wyraźniejszy, nie niszcząc pieczątek, podpisów ani śladów ołówka. + Uruchom OCR dopiero, gdy strona jest wyprostowana i czytelna, a następnie ręcznie zweryfikuj ważne liczby. + Kompresuj, skaluj szarość lub naprawiaj pliki PDF + Użyj operacji PDF na jednym pliku, aby uzyskać lżejsze, nadające się do wydruku lub przebudowane kopie dokumentów + Wybierz operację czyszczenia pliku PDF + Narzędzia PDF obejmują oddzielne operacje kompresji, konwersji skali szarości i naprawy. Kompresja i skala szarości działają głównie poprzez osadzone obrazy, podczas gdy naprawa odbudowuje strukturę pliku PDF; żadnego z nich nie należy traktować jako gwarantowanej poprawki dla każdego dokumentu. + Użyj opcji Kompresuj PDF, jeśli dokument zawiera obrazy, a rozmiar pliku ma znaczenie przy udostępnianiu. + Użyj skali szarości, jeśli dokument powinien być łatwiejszy do wydrukowania lub nie wymaga kolorowych obrazów. + Użyj opcji Napraw plik PDF na kopii, jeśli dokument nie otwiera się lub ma uszkodzoną strukturę wewnętrzną, a następnie sprawdź odbudowany plik. + Wyodrębnij obrazy z plików PDF + Odzyskaj osadzone obrazy PDF i spakuj wyodrębnione wyniki do archiwum + Zapisuj obrazy źródłowe z dokumentów + Wyodrębnij obrazy skanuje plik PDF w poszukiwaniu osadzonych obiektów obrazów, pomija duplikaty, jeśli to możliwe, i przechowuje odzyskane obrazy w wygenerowanym archiwum ZIP. Wyodrębnia tylko obrazy faktycznie osadzone w pliku PDF, a nie tekst, rysunki wektorowe lub każdą widoczną stronę w postaci zrzutu ekranu. + Użyj opcji Wyodrębnij obrazy, jeśli potrzebujesz obrazów przechowywanych w pliku PDF, takich jak zdjęcia z katalogu lub zeskanowane zasoby. + Zamiast tego użyj pliku PDF do obrazów lub wyodrębnienia stron, jeśli potrzebujesz całych stron, łącznie z tekstem i układem. + Jeśli narzędzie nie zgłasza żadnych osadzonych obrazów, strona może zawierać treść tekstową/wektorową lub obrazy mogą nie być przechowywane jako obiekty, które można wyodrębnić. + Metadane, spłaszczanie i wydruki + Edytuj właściwości dokumentu, rasteryzuj strony lub celowo przygotowuj niestandardowe układy wydruku + Wybierz akcję końcową dokumentu + Metadane PDF, spłaszczanie i przygotowanie do druku rozwiązują różne problemy na ostatnim etapie. Metadane kontrolują właściwości dokumentu, Flatten PDF rasteryzuje strony w mniej edytowalny sposób, a Print PDF przygotowuje nowy układ z elementami sterującymi rozmiarem strony, orientacją, marginesem i liczbą stron na arkusz. + Używaj metadanych, gdy liczy się autor, tytuł, słowa kluczowe, producent lub czyszczenie prywatności. + Użyj opcji Spłaszcz PDF, jeśli widoczna zawartość strony powinna być trudniejsza do edycji jako oddzielnych obiektów PDF. + Użyj opcji Drukuj PDF, jeśli potrzebujesz niestandardowego rozmiaru strony, orientacji, marginesów lub wielu stron na arkuszu. + Przygotuj obrazy do OCR + Użyj przycinania, obracania, kontrastu, usuwania szumów i skalowania, zanim poprosisz OCR o przeczytanie tekstu + Daj czystsze piksele OCR + Błędy OCR często wynikają z obrazu wejściowego, a nie z silnika OCR. Krzywe strony, niski kontrast, rozmycie, cienie, drobny tekst i mieszane tła pogarszają jakość rozpoznawania. + Przytnij do obszaru tekstowego i obróć obraz, aby przed rozpoznaniem linie były poziome. + Zwiększ kontrast lub zmień kolor na czystszy czarno-biały tylko wtedy, gdy ułatwi to czytanie liter. + Zmień rozmiar drobnego tekstu w umiarkowany sposób w górę, ale unikaj agresywnego wyostrzania, które tworzy fałszywe krawędzie. + Sprawdź zawartość QR + Zanim zaufasz kodowi, przejrzyj łącza, dane uwierzytelniające Wi-Fi, kontakty i działania + Traktuj zdekodowany tekst jako niezaufane dane wejściowe + Kod QR może ukryć adres URL, kartę kontaktową, hasło Wi-Fi, wydarzenie w kalendarzu, numer telefonu lub zwykły tekst. Widoczna etykieta obok kodu może nie odpowiadać rzeczywistemu ładunkowi, dlatego przed podjęciem działań sprawdź zdekodowaną treść. + Sprawdź pełny zdekodowany adres URL przed jego otwarciem, szczególnie w przypadku skróconych lub nieznanych domen. + Zachowaj ostrożność w przypadku kodów Wi-Fi, kontaktów, kalendarza, telefonu i SMS-ów, ponieważ mogą one wywołać wrażliwe działania. + Skopiuj zawartość jako pierwszą, jeśli chcesz ją zweryfikować w innym miejscu, zamiast otwierać ją natychmiast. + Generuj kody QR + Twórz kody na podstawie zwykłego tekstu, adresów URL, Wi-Fi, kontaktów, e-maili, telefonów, SMS-ów i danych lokalizacyjnych + Zbuduj odpowiedni ładunek QR + Ekran QR może generować kody z wprowadzonych treści, a także skanować istniejące. Ustrukturyzowane typy QR pomagają kodować dane w formacie zrozumiałym dla innych aplikacji, takim jak dane logowania do Wi-Fi, karty kontaktowe, pola e-mail, numery telefonów, treść SMS-ów, adresy URL lub współrzędne geograficzne. + Wybierz zwykły tekst w przypadku prostych notatek lub użyj typu strukturalnego, gdy kod powinien otwierać się jako dane Wi-Fi, kontakt, adres URL, e-mail, telefon, SMS lub dane o lokalizacji. + Sprawdź każde pole przed udostępnieniem wygenerowanego kodu, ponieważ widoczny podgląd odzwierciedla tylko wprowadzony ładunek. + Przetestuj zapisany kod QR za pomocą skanera, zanim zostanie wydrukowany, opublikowany publicznie lub wykorzystany przez inne osoby. + Wybierz tryb sumy kontrolnej + Oblicz skróty z plików lub tekstu, porównaj jeden plik lub sprawdź wsadowo wiele plików + Sprawdzaj treść, a nie wygląd + Narzędzia sum kontrolnych mają osobne zakładki do mieszania plików, mieszania tekstu, porównywania pliku ze znanym skrótem i porównywania wsadowego. Suma kontrolna potwierdza tożsamość bajt po bajcie dla wybranego algorytmu; nie dowodzi, że dwa obrazy jedynie wyglądają podobnie. + Użyj Oblicz dla plików i Text Hash dla wpisanych lub wklejonych danych tekstowych. + Użyj opcji Porównaj, gdy ktoś poda oczekiwaną sumę kontrolną dla jednego pliku. + Użyj Batch Compare, gdy wiele plików wymaga skrótów lub weryfikacji w jednym przebiegu, a następnie zachowaj spójność wybranego algorytmu. + Prywatność schowka + Korzystaj z automatycznych funkcji schowka bez przypadkowego ujawniania prywatnych skopiowanych danych + Zachowaj celowość skopiowanych treści + Automatyzacja schowka jest wygodna, ale skopiowany tekst może zawierać hasła, łącza, adresy, tokeny lub prywatne notatki. Traktuj wprowadzanie ze schowka jako funkcję szybkości, która powinna odpowiadać Twoim przyzwyczajeniom i oczekiwaniom dotyczącym prywatności. + Wyłącz automatyczne używanie schowka, jeśli w narzędziach nieoczekiwanie pojawi się prywatny tekst. + Używaj zapisywania wyłącznie w schowku tylko wtedy, gdy celowo chcesz, aby wynik nie był przechowywany. + Wyczyść lub zamień schowek po obsłudze poufnego tekstu, danych QR lub ładunków Base64. + Formaty kolorów + Zapoznaj się z wartościami kolorów HEX, RGB, HSV, alfa i skopiowanymi przed wklejeniem w innym miejscu + Skopiuj format, jakiego oczekuje obiekt docelowy + Ten sam widoczny kolor można zapisać na kilka sposobów. Narzędzie do projektowania, pole CSS, wartość koloru Androida lub edytor obrazów mogą wymagać innej kolejności, obsługi alfa lub modelu kolorów. + Użyj HEX podczas wklejania do pól internetowych, projektów lub motywów, które wymagają kompaktowych kodów kolorów. + Użyj RGB lub HSV, jeśli chcesz ręcznie dostosować kanały, jasność, nasycenie lub odcień. + Sprawdź alfa oddzielnie, gdy przejrzystość ma znaczenie, ponieważ niektóre miejsca docelowe ją ignorują lub stosują inną kolejność. + Przeglądaj bibliotekę kolorów + Szukaj nazwanych kolorów według nazwy lub HEX i przechowuj ulubione na górze + Znajdź nazwane kolory wielokrotnego użytku + Biblioteka kolorów ładuje nazwaną kolekcję kolorów, sortuje ją według nazwy, filtruje według nazwy koloru lub wartości szesnastkowej i przechowuje ulubione kolory, dzięki czemu ważne wpisy są łatwiej dostępne. Jest to przydatne, gdy zamiast próbkowania z obrazu potrzebny jest prawdziwy nazwany kolor. + Szukaj według nazwy koloru, jeśli znasz rodzinę, na przykład czerwonego, niebieskiego, łupkowego lub miętowego. + Szukaj według szesnastkowego, jeśli masz już kolor numeryczny i chcesz znaleźć pasujące lub pobliskie nazwane wpisy. + Oznacz często używane kolory jako ulubione, aby podczas przeglądania wyprzedzały ogólną bibliotekę. + Użyj kolekcji gradientów siatki + Pobierz dostępne zasoby gradientów siatki i udostępnij wybrane obrazy + Użyj zdalnych zasobów gradientu siatki + Gradienty siatkowe mogą ładować zdalny zbiór zasobów, pobierać brakujące obrazy gradientów, pokazywać postęp pobierania i udostępniać wybrane gradienty. Dostępność zależy od dostępu do sieci i zdalnego magazynu zasobów, więc pusta lista zwykle oznacza, że ​​zasoby nie były jeszcze dostępne. + Otwórz opcję Gradienty siatki w narzędziach gradientów, jeśli potrzebujesz gotowych obrazów gradientów siatki. + Poczekaj na ładowanie zasobu lub postęp pobierania, zanim założysz, że kolekcja jest pusta. + Wybierz i udostępnij potrzebne gradienty lub wróć do Kreatora gradientów, jeśli chcesz ręcznie utworzyć niestandardowy gradient. + Wygenerowana rozdzielczość zasobów + Wybierz rozmiar wyjściowy przed wygenerowaniem gradientów, szumu, tapet, grafiki w stylu SVG lub tekstur + Wygeneruj w wymaganym rozmiarze + Wygenerowane obrazy nie mają oryginału z aparatu, na którym można by się oprzeć. Jeśli wynik jest zbyt mały, późniejsze skalowanie może złagodzić wzorki, przesunąć szczegóły lub zmienić sposób kafelkowania zasobów i ich kompresji. + Najpierw ustaw wymiary dla ostatecznego przypadku użycia, takiego jak tapeta, ikona, tło, wydruk lub nakładka. + W przypadku tekstur, które można przyciąć, powiększyć lub ponownie wykorzystać w kilku układach, należy używać większych rozmiarów. + Eksportuj wersje testowe przed ogromnymi wynikami, ponieważ szczegóły proceduralne mogą zmienić zachowanie kompresji. + Eksportuj bieżące tapety + Pobierz dostępne tapety Home, Lock i wbudowane z urządzenia + Zapisz lub udostępnij zainstalowane tapety + Eksport tapet odczytuje tapety wyświetlane przez Androida za pośrednictwem systemowych interfejsów API tapet. W zależności od urządzenia, wersji Androida, uprawnień i wariantu kompilacji tapety Home, Lock lub wbudowane mogą być dostępne osobno lub mogą ich brakować. + Otwórz Eksport tapet, aby załadować tapety, które system pozwala aplikacji odczytać. + Wybierz dostępne wpisy ekranu głównego, blokady lub wbudowanej tapety, które chcesz zapisać, skopiować lub udostępnić. + Jeśli brakuje tapety lub funkcja jest niedostępna, jest to zwykle ograniczenie systemu, uprawnień lub wariantu kompilacji, a nie ustawienie edycji. + Przepustnica animacji zakrętów + Skopiuj kolor jako + HEX + nazwa + Model matowy do portretów do szybkich wycinanek, z delikatniejszymi włosami i lepszymi krawędziami. + Szybka poprawa przy słabym oświetleniu za pomocą estymacji krzywej Zero-DCE++. + Model przywracania obrazu Restormer w celu ograniczenia smug deszczu i artefaktów spowodowanych deszczową pogodą. + Model usuwania wypaczeń dokumentu, który przewiduje siatkę współrzędnych i przekształca zakrzywione strony w bardziej płaski skan. + Skala czasu trwania ruchu + Reguluje prędkość animacji w aplikacji: 0 wyłącza ruch, 1 oznacza normalną prędkość, wyższe wartości spowalniają animacje + Pokaż ostatnio używane narzędzia + Wyświetl na ekranie głównym szybki dostęp do ostatnio używanych narzędzi + Statystyki użytkowania + Przeglądaj otwieranie aplikacji, uruchamianie narzędzi i najczęściej używane narzędzia + Aplikacja zostanie otwarta + Narzędzie zostanie otwarte + Ostatnie narzędzie + Pomyślne oszczędności + Pasmo aktywności + Dane zapisane + Najlepszy format + Używane narzędzia + %1$d otwiera • %2$s + Nie ma jeszcze statystyk użytkowania + Otwórz kilka narzędzi, a pojawią się one tutaj automatycznie + Twoje statystyki użytkowania są przechowywane wyłącznie lokalnie na Twoim urządzeniu. ImageToolbox używa ich wyłącznie do pokazywania Twojej osobistej aktywności, ulubionych narzędzi i zapisanych statystyk + edytor edytuj zdjęcie retusz obrazu dostosuj + zmień rozmiar konwertuj skalę rozdzielczość wymiary szerokość wysokość jpg jpeg png webp kompresuj + skompresuj rozmiar waga bajty mb kb zmniejsz jakość optymalizuj + przycięcie, przycięcie, współczynnik proporcji + efekt filtra korekcja kolorów dostosuj maskę lutową + narysuj pędzel ołówkiem opisz znaczniki + szyfrowanie, szyfrowanie, odszyfrowywanie tajnego hasła + usuwanie tła usuń usuń wycięcie przezroczystą alfa + podgląd przeglądarki galeria sprawdź otwórz + ścieg scalaj połącz panoramę długi zrzut ekranu + adres URL pobierz obraz łącza internetowego + próbka kroplomierza próbka szesnastkowego koloru rgb + paleta kolorów próbki wyodrębnia dominujący + Prywatność metadanych exif usuń czystą lokalizację GPS + porównaj różnice różnica przed i po + limit zmień rozmiar max min megapiksele wymiary zacisk + narzędzia stron dokumentów PDF + oc tekst rozpoznaje skanowanie ekstraktu + gradientowa siatka kolorów tła + nakładka tekstowa z logo znaku wodnego + animacja gif animowana konwersja klatek + animacja apng animowana png konwersja klatek + zip archiwum skompresuj rozpakuj pliki + jxl jpeg xl konwertuj format obrazu jpg + ślad wektora svg, konwersja xml + format konwertuj jpg jpeg png webp heic avif jxl bmp + skaner dokumentów skanuj kadr z perspektywy papieru + skanowanie skanera kodów kreskowych qr + układanie nakładki średnia mediana astrofotografii + podzielone płytki siatki części plasterków + konwersja kolorów hex rgb hsl hsv cmyk laboratorium + webp konwertuje format obrazu animowanego + generator szumu losowe ziarno tekstury + układ siatki kolażu łączy + warstwy znaczników adnotacja edycja nakładki + kodowanie base64 i dekodowanie danych uri + suma kontrolna hash md5 sha sha1 sha256 sprawdź + tło gradientowe siatki + metadane exif edytuj aparat daty GPS + wyciąć podzielone kawałki siatki płytek + metadane muzyczne okładek audio okładek albumów + tło eksportu tapet + znaki graficzne tekstu ascii + ai ml neuronowy segment ekskluzywny + paleta materiałów biblioteki kolorów o nazwie kolory + studio efektów fragmentacyjnych shader glsl + pdf połącz połącz połącz + pdf podzielone na osobne strony + pdf obracaj orientację stron + pdf zmień kolejność stron sortuj + numery stron pdf, paginacja + PDF oc tekst rozpoznaje ekstrakt z możliwością przeszukiwania + tekst logo pieczęci znaku wodnego w formacie PDF + rysowanie znaku podpisu w formacie pdf + pdf chroń hasło szyfruj blokadę + pdf odblokuj hasło odszyfruj usuń ochronę + pdf kompresuj, zmniejszaj rozmiar, optymalizuj + pdf skala szarości czarny biały monochromatyczny + naprawa pliku pdf naprawa zepsuta + właściwości metadanych pdf exif autor tytuł + pdf usuń usuń strony + pdf przytnij marginesy + pdf spłaszczone adnotacje tworzą warstwy + pdf wyodrębnij obrazy zdjęcia + kompres archiwum pdf zip + drukarka do druku PDF + przeglądarka podglądu PDF przeczytana + obrazy do formatu PDF przekonwertuj dokument jpg jpeg png + pdf do obrazów strony eksportuj jpg png + adnotacje pdf komentarze usuwają clean + Wariant neutralny + Błąd + Rzuć cień + Wysokość zęba + Poziomy zakres zębów + Pionowy zakres zębów + Górna krawędź + Prawa krawędź + Dolna krawędź + Lewa krawędź + Rozdarta krawędź + Zresetuj statystyki użytkowania + Narzędzie zostanie otwarte, zapisz statystyki, smugi, zapisane dane i najwyższy format zostaną zresetowane. Otwarcia aplikacji pozostaną niezmienione. + Audio + Plik + Eksportuj profile + Zapisz bieżący profil + Usuń profil eksportu + Czy chcesz usunąć profil eksportu „%1$s”? + Granica rysunku + Pokaż cienką ramkę wokół płótna do rysowania i wymazywania + Falisty + Zaokrąglone elementy interfejsu użytkownika z miękką falistą krawędzią + Szufelka + Karb + Zaokrąglone rogi rzeźbione do wewnątrz + Narożniki schodkowe z podwójnym ścięciem + Symbol zastępczy + Użyj {filename}, aby wstawić oryginalną nazwę pliku bez rozszerzenia + Migotać + Kwota podstawowa + Ilość pierścionka + Ilość promieni + Szerokość pierścionka + Zniekształca perspektywę + Wygląd i działanie Java + Ścinanie + Kąt X + i kąt + Zmień rozmiar wydruku + Kropla wody + Długość fali + Faza + Wysoka Przełęcz + Maska koloru + Chrom + Rozwiązać + Miękkość + Informacja zwrotna + Rozmycie obiektywu + Niskie wejście + Wysokie wejście + Niska wydajność + Wysoka wydajność + Efekty świetlne + Wysokość guza + Miękkość uderzenia + Marszczyć + Amplituda X + Amplituda Y + Długość fali X + Długość fali Y + Rozmycie adaptacyjne + Generowanie tekstur + Generuj różne tekstury proceduralne i zapisuj je jako obrazy + Typ tekstury + Stronniczość + Czas + Świecić + Dyspersja + Próbki + Współczynnik kąta + Współczynnik gradientu + Typ siatki + Moc na odległość + Skala Y + Brak wyrazistości + Typ podstawowy + Współczynnik turbulencji + Ułuskowienie + Iteracje + Pierścionki + Szczotkowany metal + Kaustyka + Komórkowy + Szachownica + fBm + Marmur + Osocze + Kołdra + Drewno + losowy + Kwadrat + Sześciokątny + Ośmioboczny + Trójkątny + Prążkowany + Hałas VL + Hałas SC + Ostatni + Brak jeszcze ostatnio używanych filtrów + generator tekstur szczotkowany metal kaustyka komórkowa szachownica marmur plazma kołdra drewno cegła kamuflaż komórka chmura pęknięcie tkanina liście plaster miodu lód lawa mgławica papier rdza piasek dym kamień topografia woda falowanie + Cegła + Kamuflaż + Komórka + Chmura + Pękać + Tkanina + Listowie + Plaster miodu + Lód + Lawa + Mgławica + Papier + Rdza + Piasek + Dym + Kamień + Teren + Topografia + Falowanie Wody + Zaawansowane drewno + Szerokość zaprawy + Nieprawidłowość + Ukos + Chropowatość + Pierwszy próg + Drugi próg + Trzeci próg + Miękkość krawędzi + Szerokość obramowania + Zasięg + Szczegół + Głębokość + Rozgałęzianie + Gwinty poziome + Gwinty pionowe + Kędziory + Słojowanie + Oświetlenie + Szerokość pęknięcia + Mróz + Przepływ + Skorupa + Gęstość chmur + Gwiazdy + Gęstość włókien + Siła włókien + Plamy + Korozja + Wżery + Płatki + Częstotliwość wydm + Kąt wiatru + Fale + Ogniki + Skala żył + Poziom wody + Poziom górski + Erozja + Poziom śniegu + Liczba linii + Grubość linii + Zacienienie + Kolor porów + Kolor zaprawy + Kolor ciemnej cegły + Kolor ceglany + Zaznacz kolor + Ciemny kolor + Kolor lasu + Kolor ziemi + Kolor piaskowy + Kolor tła + Kolor komórki + Kolor krawędzi + Kolor nieba + Kolor cienia + Jasny kolor + Kolor powierzchni + Kolor odmiany + Kolor pęknięcia + Kolor osnowy + Kolor wątku + Ciemny kolor liści + Kolor liścia + Kolor obramowania + Kolor miodowy + Głęboki kolor + Kolor lodu + Kolor mrozu + Kolor skorupy + Umyj kolor + Kolor blasku + Kosmiczny kolor + Kolor fioletowy + Kolor niebieski + Kolor bazowy + Kolor włókna + Kolor plamy + Kolor metalu + Kolor ciemnej rdzy + Kolor rdzy + Kolor pomarańczowy + Kolor dymu + Kolor żył + Kolor nizinny + Kolor wody + Rockowy kolor + Kolor śniegu + Niski kolor + Wysoki kolor + Kolor linii + Płytki kolor + Trawa + Brud + Skóra + Beton + Asfalt + Mech + Ogień + Zorza polarna + Plama oleju + Akwarela + Streszczenie przepływu + Opal + Stal damasceńska + Błyskawica + Aksamit + Marmurkowatość atramentu + Folia holograficzna + Bioluminescencja + Kosmiczny wir + Lampa lawowa + Horyzont Zdarzeń + Fraktalny rozkwit + Tunel chromatyczny + Korona zaćmienia + Dziwny atraktor + Korona ferrofluidu + Supernowa + Irys + Pawie Pióro + Skorupa Nautilusa + Pierścieniowa Planeta + Gęstość ostrza + Długość ostrza + Wiatr + Niejednolitość + Zlepki + Wilgoć + Kamyczki + Fałdowanie + Pory + Agregat + Spękanie + Smoła + Nosić + Plamy + Włókna + Częstotliwość płomienia + Dym + Intensywność + Wstążki + Kołnierz sutanny i togi + Opalizowanie + Ciemność + Kwitnie + Pigment + Krawędzie + Papier + Dyfuzja + Symetria + Ostrość + Zabawa kolorami + Mleczność + Warstwy + Składanie + Polski + Gałęzie + Kierunek + Połysk + Marszczenie + Upierzenie + Bilans atramentu + Widmo + Zmarszczki + Dyfrakcja + Herb + Twist + Blask rdzenia + Plamy + Pochylenie dysku + Rozmiar horyzontu + Szerokość dysku + Soczewkowanie + Płatki + Kędzior + Filigran + Aspekty + Krzywizna + Rozmiar księżyca + Rozmiar korony + Promienie + Pierścionek z brylantem + Płaty + Gęstość orbity + Grubość + Kolce + Długość kolca + Rozmiar ciała + Metaliczny + Promień uderzenia + Szerokość skorupy + Wyrzucony + Rozmiar źrenicy + Rozmiar tęczówki + Zmiana koloru + Światło przyciągające + Rozmiar oczu + Gęstość zadziorów + Zakręty + Kancelaria + Otwór + Grzbiety + Perłowość + Rozmiar planety + Pochylenie pierścienia + Szerokość pierścionka + Atmosfera + Kolor brudu + Kolor ciemnej trawy + Kolor trawy + Kolor końcówki + Kolor ciemnej ziemi + Kolor suchy + Kolor żwirkowy + Kolor skóry + Kolor betonu + Kolor smoły + Kolor asfaltu + Kolor kamienia + Kolor kurzu + Kolor gleby + Kolor ciemnego mchu + Kolor mchu + Kolor czerwony + Kolor rdzenia + Kolor zielony + Kolor cyjan + Kolor magenty + Kolor złoty + Kolor papieru + Kolor pigmentu + Kolor wtórny + Pierwszy kolor + Drugi kolor + Kolor różowy + Kolor ciemnej stali + Kolor stali + Kolor jasnej stali + Kolor tlenku + Kolor halo + Kolor śruby + Aksamitny kolor + Kolor połysku + Kolor atramentu niebieski + Kolor atramentu czerwony + Kolor atramentu ciemny + Kolor srebrny + Kolor żółty + Kolor tkanki + Kolor dysku + Gorący kolor + Kolor soczewki + Kolor zewnętrzny + Kolor wewnętrzny + Kolor korony + Kolor zimny + Ciepły kolor + Kolor chmury + Kolor płomienia + Kolor piór + Kolor powłoki + Kolor perłowy + Kolor planety + Kolor pierścionka + Usuń oryginalne pliki po zapisaniu + Tylko pomyślnie przetworzone pliki źródłowe zostaną usunięte + Oryginalne pliki usunięte: %1$d + Nie udało się usunąć oryginalnych plików: %1$d + Oryginalne pliki usunięte: %1$d, błąd: %2$d + Gesty arkusza + Zezwalaj na przeciąganie rozwiniętych arkuszy opcji + Podpróbkowanie chrominancji + Głębia bitowa + Bezstratny + %1$d-bit + Zmień nazwę partii + Zmień nazwę wielu plików, używając wzorców nazw plików + wsadowa zmiana nazw plików wzorzec nazw plików rozszerzenie daty + Wybierz pliki, których nazwę chcesz zmienić + Wzór nazwy pliku + Przemianować + Źródło daty + Pobrano datę EXIF + Data modyfikacji pliku + Data utworzenia pliku + Aktualna data + Ręczna data + Ręczna data + Czas ręczny + Wybrana data jest niedostępna dla niektórych plików. Dla nich zostanie użyta bieżąca data. + Wprowadź wzór nazwy pliku + Wzorzec generuje nieprawidłową lub zbyt długą nazwę pliku + Wzorzec generuje zduplikowane nazwy plików + Wszystkie nazwy plików już pasują do wzorca + W tym wzorcu używane są tokeny, które nie są dostępne w przypadku zbiorczej zmiany nazwy + Nazwa pozostanie taka sama + Pomyślnie zmieniono nazwy plików + Nie udało się zmienić nazwy niektórych plików + Pozwolenie nie zostało wydane + Używa oryginalnej nazwy pliku bez rozszerzenia, pomagając zachować nienaruszoną identyfikację źródła. Obsługuje także cięcie za pomocą \\o{start:end}, transliterację za pomocą \\o{t} i zastępowanie przez \\o{s/stare/nowe}. + Licznik automatycznie zwiększający się. Obsługuje niestandardowe formatowanie: \\c{padding} (np. \\c{3} -&gt; 001), \\c{start:step} lub \\c{start:step:padding}. + Nazwa folderu nadrzędnego, w którym znajdował się oryginalny plik. + Rozmiar oryginalnego pliku. Obsługuje jednostki: \\z{b}, \\z{kb}, \\z{mb} lub po prostu \\z w formacie czytelnym dla człowieka. + Generuje losowy uniwersalny unikalny identyfikator (UUID), aby zapewnić niepowtarzalność nazwy pliku. + Zmiany nazw plików nie można cofnąć. Przed kontynuowaniem upewnij się, że nowe nazwy są poprawne. + Potwierdź zmianę nazwy + Ustawienia menu bocznego + Skala menu + Menu alfa + Automatyczny balans bieli + Obrzynek + Sprawdzanie ukrytych znaków wodnych + Składowanie + Limit pamięci podręcznej + Wyczyść pamięć podręczną automatycznie, gdy przekroczy ten rozmiar + Interwał czyszczenia + Jak często sprawdzać, czy pamięć podręczna powinna zostać wyczyszczona + Podczas uruchamiania aplikacji + 1 dzień + 1 tydzień + 1 miesiąc + Wyczyść pamięć podręczną aplikacji automatycznie zgodnie z wybranym limitem i interwałem + Ruchomy obszar upraw + Umożliwia przesuwanie całego obszaru kadrowania poprzez przeciąganie wewnątrz niego + Połącz GIF + Połącz wiele klipów GIF w jedną animację + Kolejność klipów GIF + Odwracać + Graj w odwrotnej kolejności + Bumerang + Graj do przodu, a następnie do tyłu + Normalizuj rozmiar ramki + Skaluj klatki, aby dopasować je do największego klipu; wyłączyć, aby zachować oryginalną skalę + Opóźnienie między klipami (ms) + Falcówka + Wybierz wszystkie obrazy z folderu i jego podfolderów + Wyszukiwarka duplikatów + Znajdź dokładne kopie i wizualnie podobne obrazy + wyszukiwarka duplikatów podobne obrazy dokładne kopie zdjęć czyszczenie pamięci dHash SHA-256 + Wybierz obrazy, aby znaleźć duplikaty + Wrażliwość na podobieństwo + Dokładne kopie + Podobne obrazy + Wybierz wszystkie dokładne duplikaty + Wybierz wszystkie oprócz zalecanych + Nie znaleziono duplikatów + Spróbuj dodać więcej obrazów lub zwiększyć czułość podobieństwa + Nie można odczytać obrazów %1$d + %1$s • %2$s podlega recyklingowi + grupy %1$d • %2$s podlegają recyklingowi + %1$d wybrane • %2$s + Tego nie można cofnąć. Android może poprosić Cię o potwierdzenie usunięcia w oknie dialogowym systemu. + Nie znaleziono + Przesuń, aby rozpocząć + Przejdź do końca + \ No newline at end of file diff --git a/core/resources/src/main/res/values-pt-rBR/strings.xml b/core/resources/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000..6571881 --- /dev/null +++ b/core/resources/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,3739 @@ + + + + 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 + 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 + 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. + 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. + 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 + Distorção sensível ao conteúdo + Não há memória suficiente para concluir esta ação. Tente usar uma imagem menor, fechar outros aplicativos ou reiniciar o aplicativo. + Trabalhadores paralelos + Essas imagens não foram salvas porque os arquivos convertidos seriam maiores que os originais. Verifique esta lista antes de excluir imagens originais. + Arquivos ignorados: %1$s + Registros de aplicativos + Veja os registros do aplicativo para identificar os problemas + Favoritos em ferramentas agrupadas + Adiciona favoritos como uma guia quando as ferramentas são agrupadas por tipo + Mostrar favorito como último + Move a aba de ferramentas favoritas para o final + Espaçamento horizontal + Espaçamento vertical + Energia retrógrada + Use mapa de energia de magnitude de gradiente simples em vez de algoritmo de energia direta + Remover área mascarada + As costuras passarão pela região mascarada, removendo apenas a área selecionada em vez de protegê-la + VHS NTSC + Configurações avançadas de NTSC + Ajuste analógico extra para VHS mais fortes e artefatos de transmissão + Sangramento cromático + Desgaste da fita + Monitorando + Esfregaço de Luma + Toque + Neve + Usar campo + Tipo de filtro + Filtro luma de entrada + Passagem baixa de croma de entrada + Demodulação cromática + Mudança de fase + Deslocamento de fase + Troca de cabeça + Altura de comutação da cabeça + Deslocamento de troca de cabeçote + Mudança de troca de cabeça + Posição na linha média + Tremulação da linha média + Altura do ruído de rastreamento + Onda de rastreamento + Rastreando neve + Rastreando a anisotropia da neve + Ruído de rastreamento + Rastreando a intensidade do ruído + Ruído composto + Frequência de ruído composto + Intensidade de ruído composto + Detalhe de ruído composto + Frequência de toque + Poder de toque + Ruído de luma + Frequência de ruído Luma + Intensidade de ruído Luma + Detalhe de ruído Luma + Ruído cromático + Frequência de ruído cromático + Intensidade de ruído cromático + Detalhe de ruído cromático + Intensidade da neve + Anisotropia da neve + Ruído de fase cromática + Erro de fase cromática + Atraso de croma horizontal + Atraso de croma vertical + Velocidade da fita VHS + Perda de croma VHS + VHS aumenta a intensidade + Frequência de nitidez VHS + Intensidade de onda de borda VHS + Velocidade de onda de borda VHS + Frequência de onda de borda VHS + Detalhe da onda da borda VHS + Passagem baixa de croma de saída + Escala horizontal + Escala vertical + Fator de escala X + Fator de escala Y + Detecta marcas d’água comuns em imagens e as pinta com LaMa. Baixa modelos de detecção e pintura interna automaticamente + Deuteranopia + Expandir imagem + Removedor de fundo baseado em segmentação de objetos usando segmentação YOLO v11 + Emojis Animados + Mostrar emojis disponíveis como animações + Mostrar ângulo da linha + Mostra a rotação atual da linha em graus enquanto desenha + Salvar na pasta original + Salve novos arquivos ao lado do arquivo original em vez da pasta selecionada + PDF de marca d’água + Memória insuficiente + A imagem provavelmente é muito grande para ser processada neste dispositivo ou o sistema ficou sem memória disponível. Tente reduzir a resolução da imagem, fechar outros aplicativos ou escolher um arquivo menor. + Menu de depuração + Menu para testar funções do aplicativo. Não se destina a aparecer na versão de produção + Provedor + PaddleOCR requer modelos ONNX adicionais em seu dispositivo. Deseja fazer download de dados de %1$s? + Universal + coreano + Latim + Eslavo Oriental + Tailandês + grego + Inglês + cirílico + árabe + Devanágari + tâmil + Telugu + Sombreador + Predefinição de sombreador + Nenhum sombreador selecionado + Estúdio de sombreamento + Crie, edite, valide, importe e exporte shaders de fragmentos personalizados + Sombreadores salvos + Abra shaders salvos, duplique, exporte, compartilhe ou exclua + Novo sombreador + Arquivo de sombreador + .itshader JSON + %1$d parâmetros + Fonte do sombreador + Escreva apenas o corpo de void main(). Use TextureCoorden para coordenadas UV e inputImageTexture como amostrador de origem. + Funções + Funções auxiliares opcionais, constantes e estruturas inseridas antes de void main(). Uniformes são gerados a partir de parâmetros. + Adicione uniformes aqui quando o shader precisar de valores editáveis. + Redefinir sombreador + O rascunho do shader atual será apagado + Sombreador inválido + Versão do shader não suportada %1$d. Versão suportada: %2$d. + O nome do shader não deve ficar em branco. + A origem do sombreador não deve estar em branco. + A origem do shader contém caracteres não suportados: %1$s. Use apenas fonte ASCII GLSL. + O parâmetro \\\"%1$s\\\" é declarado mais de uma vez. + Os nomes dos parâmetros não devem ficar em branco. + O parâmetro \\\"%1$s\\\" usa um nome GPUImage reservado. + O parâmetro \\\"%1$s\\\" deve ser um identificador GLSL válido. + O parâmetro \\\"%1$s\\\" tem o tipo de valor %2$s \\\"%3$s\\\", esperado \\\"%4$s\\\". + O parâmetro \\\"%1$s\\\" não pode definir mínimo ou máximo para valores bool. + O parâmetro \\\"%1$s\\\" tem min maior que max. + O padrão do parâmetro \\\"%1$s\\\" é menor que min. + O padrão do parâmetro \\\"%1$s\\\" é maior que o máximo. + O shader deve declarar \"uniform sampler2D %1$s;\\\". + O shader deve declarar \"vec2 variável %1$s;\\\". + O shader deve definir \"void main()\". + O shader deve escrever uma cor em gl_FragColor. + Os shaders ShaderToy mainImage não são suportados. Use o contrato void main() do GPUImage. + O uniforme animado do ShaderToy \"iTime\" não é compatível. + O uniforme animado do ShaderToy \"iFrame\" não é compatível. + O uniforme ShaderToy \"iResolution\" não é suportado. + Texturas ShaderToy iChannel não são suportadas. + Texturas externas não são suportadas. + Extensões de textura externas não são suportadas. + Os parâmetros do shader Libretro não são suportados. + Apenas uma textura de entrada é suportada. Remova \\\"uniforme %1$s %2$s;\\\". + O parâmetro \\\"%1$s\\\" deve ser declarado como \\\"uniforme %2$s %1$s;\\\". + O parâmetro \\\"%1$s\\\" é declarado como \\\"uniforme %2$s %1$s;\\\", esperado \\\"uniforme %3$s %1$s;\\\". + O arquivo do shader está vazio. + O arquivo shader deve ser um objeto JSON .itshader com versão, nome e campos de shader. + O arquivo Shader não é JSON válido ou não corresponde ao formato .itshader. + O campo \\\"%1$s\\\" é obrigatório. + %1$s é obrigatório. + %1$s \\\"%2$s\\\" não é compatível. Tipos suportados: %3$s. + %1$s é necessário para parâmetros \\\"%2$s\\\". + %1$s deve ser um número finito. + %1$s deve ser um número inteiro. + %1$s deve ser verdadeiro ou falso. + %1$s deve ser uma sequência de cores, matriz RGB/RGBA ou objeto de cor. + %1$s deve usar o formato #RRGGBB ou #RRGGBBAA. + %1$s deve conter canais de cores hexadecimais válidos. + %1$s deve conter 3 ou 4 canais de cores. + %1$s deve estar entre 0 e 255. + %1$s deve ser uma matriz de dois números ou um objeto com x e y. + %1$s deve conter exatamente 2 números. + Duplicado + Sempre limpar EXIF + Remova os dados EXIF ​​da imagem ao salvar, mesmo quando uma ferramenta solicitar a manutenção de metadados + Ajuda e dicas + %1$d tutoriais + Ferramenta aberta + Aprenda as principais ferramentas, opções de exportação, fluxos de PDF, utilitários de cores e soluções para problemas comuns + Começando + Escolha ferramentas, importe arquivos, salve resultados e reutilize configurações com mais rapidez + Edição de imagem + Redimensione, corte, filtre, apague fundos, desenhe e coloque marcas d\'água em imagens + Arquivos e metadados + Converta formatos, compare resultados, proteja a privacidade e controle nomes de arquivos + PDF e documentos + Crie PDFs, digitalize documentos, páginas OCR e prepare arquivos para compartilhamento + Texto, QR e dados + Reconheça texto, escaneie códigos, codifique Base64, verifique hashes e arquivos de pacote + Ferramentas de cores + Escolha cores, crie paletas, explore bibliotecas de cores e crie gradientes + Ferramentas criativas + Gere arte SVG, ASCII, texturas de ruído, shaders e visuais experimentais + Solução de problemas + Corrija problemas de arquivos pesados, transparência, compartilhamento, importações e relatórios + Escolha a ferramenta certa + Use categorias, pesquisa, favoritos e ações de compartilhamento para começar no lugar certo + Comece do melhor ponto de entrada + O Image Toolbox possui muitas ferramentas específicas e muitas delas se sobrepõem à primeira vista. Comece pela tarefa que deseja finalizar e escolha a ferramenta que controla a parte mais importante do resultado: tamanho, formato, metadados, texto, páginas PDF, cores ou layout. + Use a pesquisa quando souber o nome da ação, como redimensionar, PDF, EXIF, OCR, QR ou cor. + Abra uma categoria quando você souber apenas o tipo de tarefa e fixe ferramentas frequentes como favoritas. + Quando outro aplicativo compartilhar uma imagem no Image Toolbox, escolha a ferramenta no fluxo da planilha de compartilhamento. + Importe, salve e compartilhe resultados + Entenda o fluxo normal desde a seleção da entrada até a exportação do arquivo editado + Siga o fluxo de edição comum + A maioria das ferramentas segue o mesmo ritmo: escolha a entrada, ajuste as opções, visualize e salve ou compartilhe. O detalhe importante é que salvar cria um arquivo de saída, enquanto o compartilhamento geralmente é melhor para transferência rápida para outro aplicativo. + Escolha um arquivo para ferramentas de imagem única ou vários arquivos para ferramentas em lote quando o seletor permitir. + Verifique os controles de visualização e saída antes de salvar, especialmente opções de formato, qualidade e nome de arquivo. + Use compartilhar para uma transferência rápida ou salve quando precisar de uma cópia persistente na pasta selecionada. + Trabalhe com muitas imagens ao mesmo tempo + As ferramentas em lote são melhores para tarefas repetidas de redimensionamento, conversão, compactação e nomeação + Prepare um lote previsível + O processamento em lote é mais rápido quando cada saída deve seguir as mesmas regras. É também o lugar mais fácil para cometer um grande erro, então escolha o nome, a qualidade, os metadados e o comportamento da pasta antes de iniciar uma exportação longa. + Selecione todas as imagens relacionadas juntas e mantenha a ordem se a saída depender da sequência. + Defina regras de redimensionamento, formato, qualidade, metadados e nome de arquivo uma vez antes de iniciar a exportação. + Execute primeiro um pequeno lote de teste quando os arquivos de origem forem grandes ou quando o formato de destino for novo para você. + Edição única rápida + Use o editor multifuncional quando precisar de várias alterações simples em uma imagem + Conclua uma imagem sem pular ferramentas + A Edição Única é útil quando a imagem precisa de uma pequena cadeia de alterações em vez de uma operação especializada. Geralmente é mais rápido para limpeza rápida, visualização e exportação de uma cópia final sem criar um fluxo de trabalho em lote. + Abra a Edição única para uma imagem que precisa de ajustes comuns, marcação, corte, rotação ou alterações de exportação. + Aplique edições na ordem que altera o menor número de pixels primeiro, como cortar antes dos filtros e filtros antes da compactação final. + Use uma ferramenta especializada quando precisar de processamento em lote, destinos de tamanho exato de arquivo, saída de PDF, OCR ou limpeza de metadados. + Use predefinições e configurações + Salve escolhas repetidas e ajuste o aplicativo para seu fluxo de trabalho preferido + Evite repetir a mesma configuração + As predefinições e configurações são úteis quando você exporta frequentemente o mesmo tipo de arquivo. Uma boa predefinição transforma uma lista de verificação manual repetida em um toque, enquanto as configurações globais definem os padrões com os quais as novas ferramentas começam. + Crie predefinições para tamanhos de saída, formatos, valores de qualidade ou padrões de nomenclatura comuns. + Revise as configurações de formato padrão, qualidade, pasta para salvar, nome de arquivo, seletor e comportamento da interface. + Faça backup das configurações antes de reinstalar o aplicativo ou mudar para outro dispositivo. + Defina padrões úteis + Escolha formato padrão, qualidade, modo de escala, tipo de redimensionamento e espaço de cores uma vez + Faça com que novas ferramentas comecem mais perto do seu objetivo + Os valores padrão não são apenas preferências cosméticas. Eles decidem qual formato, qualidade, comportamento de redimensionamento, modo de escala e espaço de cores aparecem primeiro em muitas ferramentas, o que ajuda a evitar a seleção novamente das mesmas opções em cada exportação. + Defina o formato e a qualidade da imagem padrão para corresponder ao seu destino habitual, como JPEG para fotos ou PNG/WebP para alfa. + Ajuste o tipo de redimensionamento padrão e o modo de escala se você exporta frequentemente para dimensões estritas, plataformas sociais ou páginas de documentos. + Altere os padrões do espaço de cores somente quando seu fluxo de trabalho precisar de manipulação consistente de cores em monitores, impressão ou editores externos. + Seletor e lançador + Use as configurações do seletor quando o aplicativo abrir muitas caixas de diálogo ou iniciar no lugar errado + Faça com que a abertura de arquivos corresponda ao seu hábito + As configurações do seletor e do iniciador controlam se o Image Toolbox inicia na tela principal, em uma ferramenta ou em um fluxo de seleção de arquivo. Essas opções são especialmente úteis quando você costuma entrar na planilha de compartilhamento do Android ou quando deseja que o aplicativo pareça mais um iniciador de ferramentas. + Use as configurações do seletor de fotos se o seletor do sistema ocultar arquivos, mostrar muitos itens recentes ou lidar mal com os arquivos da nuvem. + Habilite skip picking apenas para ferramentas onde você costuma entrar no compartilhamento ou já conhece o arquivo de origem. + Revise o modo de inicialização se desejar que o Image Toolbox se comporte mais como um seletor de ferramentas do que como uma tela inicial normal. + Fonte da imagem + Escolha o modo de seleção que funciona melhor com galerias, gerenciadores de arquivos e arquivos na nuvem + Escolha arquivos da fonte certa + As configurações da fonte da imagem afetam como o aplicativo solicita imagens ao Android. A melhor escolha depende se seus arquivos estão na galeria do sistema, em uma pasta do gerenciador de arquivos, em um provedor de nuvem ou em outro aplicativo que compartilha conteúdo temporário. + Use o seletor padrão quando desejar a experiência mais integrada ao sistema para imagens comuns de galerias. + Alterne o modo seletor se álbuns, pastas, arquivos na nuvem ou formatos fora do padrão não aparecerem onde você espera. + Se um arquivo compartilhado desaparecer após sair do aplicativo de origem, reabra-o por meio de um seletor persistente ou salve primeiro uma cópia local. + Favoritos e ferramentas de compartilhamento + Mantenha a tela principal focada e oculte ferramentas que não fazem sentido nos fluxos de compartilhamento + Reduza o ruído da lista de ferramentas + Favoritos e configurações ocultas para compartilhamento são úteis quando você usa apenas algumas ferramentas todos os dias. Eles também mantêm o fluxo de compartilhamento prático, ocultando ferramentas que não podem usar o tipo de arquivo enviado por outro aplicativo. + Fixe ferramentas frequentes para que fiquem fáceis de alcançar, mesmo quando as ferramentas são agrupadas por categoria. + Oculte ferramentas de fluxos de compartilhamento quando elas forem irrelevantes para arquivos provenientes de outro aplicativo. + Use as configurações de ordenação de favoritos se preferir favoritos no final ou agrupados com ferramentas relacionadas. + Fazer backup das configurações + Mova predefinições, preferências e comportamento do aplicativo para outra instalação com segurança + Mantenha sua configuração portátil + Backup e restauração são mais úteis depois de ajustar predefinições, pastas, regras de nome de arquivo, ordem de ferramentas e preferências visuais. Um backup permite que você experimente as configurações ou reinstale o aplicativo sem reconstruir seu fluxo de trabalho da memória. + Crie um backup após alterar predefinições, comportamento de nome de arquivo, formatos padrão ou organização de ferramentas. + Armazene o backup em algum lugar fora da pasta do aplicativo se você planeja limpar dados, reinstalar ou mover dispositivos. + Restaure as configurações antes de processar lotes importantes para que os padrões e o comportamento de salvamento correspondam à sua configuração antiga. + Redimensionar e converter imagens + Altere tamanho, formato, qualidade e metadados de uma ou mais imagens + Crie cópias redimensionadas + Redimensionar e converter é a principal ferramenta para dimensões previsíveis e arquivos de saída mais leves. Use-o quando o tamanho da imagem, o formato de saída e o comportamento dos metadados forem importantes ao mesmo tempo. + Escolha uma ou mais imagens e escolha se as dimensões, a escala ou o tamanho do arquivo são mais importantes. + Selecione o formato e a qualidade de saída; use PNG ou WebP quando a transparência precisar ser preservada. + Visualize o resultado e salve ou compartilhe as cópias geradas sem alterar os originais. + Redimensionar por tamanho de arquivo + Defina um limite de tamanho quando um site, mensageiro ou formulário rejeitar imagens pesadas + Ajustar limites rígidos de upload + Redimensionar por tamanho de arquivo é melhor do que adivinhar valores de qualidade quando o destino aceita apenas arquivos abaixo de um limite específico. A ferramenta pode ajustar a compactação e as dimensões para atingir o objetivo enquanto tenta manter o resultado utilizável. + Insira o tamanho desejado um pouco abaixo do limite real de upload para deixar espaço para metadados e alterações de provedor. + Prefira formatos com perdas para fotos quando o alvo for pequeno; O PNG pode permanecer grande mesmo após o redimensionamento. + Inspecione faces, texto e arestas após a exportação, pois alvos de tamanho agressivo podem introduzir artefatos visíveis. + Limitar redimensionamento + Reduza apenas imagens que excedam a largura, altura máxima ou restrições de arquivo + Evite redimensionar imagens que já estão boas + Limit Resize é útil para pastas mistas onde algumas imagens são enormes e outras já estão preparadas. Em vez de forçar o mesmo redimensionamento de todos os arquivos, ele mantém as imagens aceitáveis ​​mais próximas de sua qualidade original. + Defina dimensões ou limites máximos com base no destino, em vez de redimensionar cada arquivo às cegas. + Ative o comportamento do estilo ignorar se for maior quando quiser evitar saídas que se tornem mais pesadas que as fontes. + Use isto para lotes de diferentes câmeras, capturas de tela ou downloads onde os tamanhos das fontes variam muito. + Cortar, girar e endireitar + Enquadre a área importante antes de exportar ou usar a imagem em outras ferramentas + Limpe a composição + Cortar primeiro torna os filtros posteriores, o OCR, as páginas PDF e o compartilhamento de resultados mais limpos. + Abra Cortar e escolha a imagem que deseja ajustar. + Use corte livre ou proporção quando a saída precisar caber em uma postagem, documento ou avatar. + Gire ou vire antes de salvar para que as ferramentas posteriores recebam a imagem corrigida. + Aplicar filtros e ajustes + Crie uma pilha de filtros editável e visualize o resultado antes de exportar + Ajustar a aparência da imagem + Os filtros são úteis para correções rápidas, saída estilizada e preparação de imagens para OCR ou impressão. Pequenas alterações no contraste, nitidez e brilho podem ser mais importantes do que efeitos pesados ​​quando a imagem contém texto ou detalhes finos. + Adicione filtros um por um e fique de olho na visualização após cada alteração. + Ajuste brilho, contraste, nitidez, desfoque, cor ou máscaras dependendo da tarefa. + Exporte somente quando a visualização corresponder ao seu dispositivo, documento ou plataforma social de destino. + Pré-visualizar primeiro + Inspecione as imagens antes de escolher uma ferramenta mais pesada de edição, conversão ou limpeza + Verifique o que você realmente recebeu + A visualização da imagem é útil quando um arquivo vem de um bate-papo, armazenamento em nuvem ou outro aplicativo e você não tem certeza do que ele contém. Verificar primeiro as dimensões, a transparência, a orientação e a qualidade visual ajuda a escolher a ferramenta de acompanhamento correta. + Abra a Visualização de imagens quando precisar inspecionar várias imagens antes de decidir o que fazer com elas. + Procure problemas de rotação, áreas transparentes, artefatos de compactação e dimensões inesperadamente grandes. + Mude para Redimensionar, Cortar, Comparar, EXIF ​​ou Removedor de Fundo somente depois de saber o que precisa ser corrigido. + Remover ou restaurar um plano de fundo + Apague fundos automaticamente ou refine bordas manualmente com ferramentas de restauração + Mantenha o assunto, remova o resto + A remoção do fundo funciona melhor quando você combina a seleção automática com uma limpeza manual cuidadosa. O formato de exportação final é tão importante quanto a máscara, porque o JPEG achatará as áreas transparentes mesmo que a visualização pareça correta. + Comece com a remoção automática se o assunto estiver claramente separado do fundo. + Aumente o zoom e alterne entre apagar e restaurar para corrigir cabelos, cantos, sombras e pequenos detalhes. + Salve como PNG ou WebP quando precisar de transparência no arquivo final. + Desenhar, marcar e colocar marca d\'água + Adicione setas, notas, destaques, assinaturas ou sobreposições repetidas de marcas d’água + Adicione informações visíveis + As ferramentas de marcação ajudam a explicar uma imagem, enquanto a marca d\'água ajuda a marcar ou proteger os arquivos exportados. + Use o Draw quando precisar de notas à mão livre, formas, destaques ou anotações rápidas. + Use marca d’água quando o mesmo texto ou marca de imagem precisar aparecer de forma consistente nas saídas. + Verifique a opacidade, a posição e a escala antes de exportar para que a marca fique visível, mas não distraia. + Desenhar padrões + Defina a largura da linha, a cor, o modo de caminho, o bloqueio de orientação e a lupa para uma marcação mais rápida + Faça com que o desenho pareça previsível + As configurações de desenho são úteis quando você anota capturas de tela, marca documentos ou assina imagens com frequência. Os padrões reduzem o tempo de configuração, enquanto a lupa e o bloqueio de orientação facilitam edições precisas em telas pequenas. + Defina a largura da linha padrão e desenhe a cor com os valores que você mais usa para setas, realces ou assinaturas. + Use o modo de caminho padrão quando você normalmente desenha o mesmo tipo de traços, formas ou marcações. + Ative a lupa ou o bloqueio de orientação quando a entrada do dedo dificultar a localização precisa de pequenos detalhes. + Layouts de múltiplas imagens + Escolha a ferramenta multi-imagem certa antes de organizar capturas de tela, digitalizações ou faixas de comparação + Use a ferramenta de layout certa + Essas ferramentas parecem semelhantes, mas cada uma é ajustada para um tipo diferente de saída de múltiplas imagens. Escolher o caminho certo primeiro evita lutar contra controles de layout que foram projetados para um resultado diferente. + Use o Collage Maker para layouts projetados com várias imagens em uma composição. + Use Costura ou Empilhamento de imagens quando as imagens se tornarem uma longa faixa vertical ou horizontal. + Use a divisão de imagens quando uma imagem grande precisar se transformar em vários pedaços menores. + Converta formatos de imagem + Crie JPEG, PNG, WebP, AVIF, JXL e outras cópias sem editar pixels manualmente + Escolha o formato do trabalho + Diferentes formatos são melhores para fotos, capturas de tela, transparência, compactação ou compatibilidade. A conversão é mais segura quando você entende o que o aplicativo de destino oferece suporte e se a origem contém alfa, animação ou metadados que você deseja manter. + Use JPEG para fotos compatíveis, PNG para imagens sem perdas e alfa e WebP para compartilhamento compacto. + Ajuste a qualidade quando o formato suportar; valores mais baixos reduzem o tamanho, mas podem adicionar artefatos. + Mantenha os originais até verificar se os arquivos convertidos estão abertos corretamente no aplicativo de destino. + Verifique EXIF ​​e privacidade + Visualize, edite ou remova metadados antes de compartilhar fotos confidenciais + Controle dados de imagem ocultos + EXIF pode conter dados da câmera, datas, localização, orientação e outros detalhes não visíveis na imagem. Vale a pena conferir antes de compartilhar publicamente, relatórios de suporte, listagens de marketplace ou qualquer foto que possa revelar um local privado. + Abra as ferramentas EXIF ​​antes de compartilhar fotos que possam conter informações de localização ou privadas do dispositivo. + Remova campos confidenciais ou edite tags incorretas, como data, orientação, autor ou dados da câmera. + Salve uma nova cópia e compartilhe-a em vez do original quando a privacidade for importante. + Limpeza EXIF ​​automática + Remova metadados por padrão ao decidir se as datas devem ser preservadas + Torne a privacidade o padrão + O grupo de configurações EXIF ​​é útil quando você deseja que a limpeza de privacidade aconteça automaticamente, em vez de lembrá-la a cada exportação. Manter data e hora é uma decisão separada porque as datas podem ser úteis para classificação, mas sensíveis para compartilhamento. + Ative EXIF ​​sempre claro quando a maioria de suas exportações for destinada ao compartilhamento público ou semipúblico. + Habilite Manter data e hora somente quando preservar o tempo de captura for mais importante do que remover todos os carimbos de data e hora. + Use a edição EXIF ​​manual para arquivos únicos onde você precisa manter alguns campos e remover outros. + Controlar nomes de arquivos + Use prefixos, sufixos, carimbos de data/hora, predefinições, somas de verificação e números de sequência + Torne as exportações fáceis de encontrar + Um bom padrão de nome de arquivo ajuda a classificar lotes e evita substituições acidentais. As configurações de nome de arquivo são especialmente úteis quando as exportações voltam para a pasta de origem, onde nomes semelhantes podem se tornar confusos rapidamente. + Defina o prefixo, sufixo, carimbo de data e hora do nome do arquivo, nome original, informações predefinidas ou opções de sequência em Configurações. + Use números de sequência para lotes em que a ordem é importante, como páginas, molduras ou colagens. + Habilite a substituição somente quando tiver certeza de que a substituição dos arquivos existentes é segura para a pasta atual. + Substituir arquivos + Substitua as saídas por nomes correspondentes somente quando seu fluxo de trabalho for intencionalmente destrutivo + Use o modo de substituição com cuidado + Substituir arquivos altera a forma como os conflitos de nomenclatura são tratados. É útil para repetir exportações para a mesma pasta, mas também pode substituir resultados anteriores se o padrão de nome de arquivo produzir o mesmo nome novamente. + Habilite a substituição apenas para pastas onde a substituição de arquivos gerados mais antigos é esperada e recuperável. + Mantenha a substituição desativada ao exportar originais, arquivos de cliente, digitalizações únicas ou qualquer coisa sem backup. + Combine a substituição com um padrão de nome de arquivo deliberado para que apenas as saídas pretendidas sejam substituídas. + Padrões de nome de arquivo + Combine nomes originais, prefixos, sufixos, carimbos de data/hora, predefinições, modo de escala e somas de verificação + Crie nomes que expliquem a saída + As configurações de padrão de nome de arquivo podem transformar arquivos exportados em um histórico útil do que aconteceu com eles. Isso é importante em lotes porque a visualização da pasta pode ser o único lugar onde você pode distinguir tamanho original, predefinição, formato e ordem de processamento. + Use nome de arquivo, prefixo, sufixo e carimbo de data/hora originais quando precisar de nomes legíveis para classificação manual. + Adicione informações de predefinição, modo de escala, tamanho de arquivo ou soma de verificação quando as saídas precisarem documentar como foram produzidas. + Evite nomes aleatórios para fluxos de trabalho em que os arquivos precisam ficar emparelhados com os originais ou com a ordem das páginas. + Escolha onde os arquivos serão salvos + Evite perder exportações definindo pastas, salvando a pasta original e salvando locais únicos + Mantenha os resultados previsíveis + O comportamento de salvamento é mais importante quando você processa muitos arquivos ou compartilha arquivos de provedores de nuvem. As configurações de pasta decidem se as saídas são coletadas em um local conhecido, aparecem ao lado dos originais ou vão temporariamente para outro lugar para uma tarefa específica. + Defina uma pasta de salvamento padrão se quiser sempre exportar em um só lugar. + Use salvar na pasta original para fluxos de trabalho de limpeza em que a saída deve ficar próxima ao arquivo de origem. + Use o local de salvamento único quando uma única tarefa precisar de um destino diferente sem alterar seu padrão. + Salvar pastas uma única vez + Envie as próximas exportações para uma pasta temporária sem alterar seu destino normal + Mantenha os trabalhos especiais separados + O local de salvamento único é útil para projetos temporários, pastas de clientes, sessões de limpeza ou exportações que não devem ser misturadas com sua pasta normal do Image Toolbox. Fornece um destino de curta duração sem reescrever sua configuração padrão. + Escolha uma pasta descartável antes de iniciar uma tarefa que pertença fora do seu local normal de exportação. + Use-o para projetos curtos, pastas compartilhadas ou lotes onde as saídas devem permanecer agrupadas. + Retorne à pasta padrão após o trabalho para que as exportações posteriores não cheguem inesperadamente ao local temporário. + Equilibre qualidade e tamanho do arquivo + Entenda quando alterar dimensões, formato ou qualidade em vez de adivinhar + Encolher arquivos intencionalmente + O tamanho do arquivo depende das dimensões, formato, qualidade, transparência e complexidade do conteúdo da imagem. Se um arquivo ainda for muito pesado, alterar as dimensões geralmente ajuda mais do que reduzir repetidamente a qualidade. + Redimensione as dimensões primeiro quando a imagem for maior do que o destino pode exibir. + Qualidade inferior apenas para formatos com perdas e verificação de texto, bordas, gradientes e faces após a exportação. + Alterne o formato quando as alterações de qualidade não forem suficientes, por exemplo, WebP para compartilhamento ou PNG para ativos transparentes e nítidos. + Trabalhe com formatos animados + Use ferramentas GIF, APNG, WebP e JXL quando um arquivo tiver quadros em vez de um bitmap + Não trate todas as imagens como estáticas + Os formatos animados precisam de ferramentas que entendam os frames, a duração e a compatibilidade. + Use ferramentas GIF ou APNG quando precisar de operações em nível de quadro para esses formatos. + Use ferramentas WebP quando precisar de compactação moderna ou saída WebP animada. + Visualize o tempo da animação antes de compartilhar porque algumas plataformas convertem ou nivelam imagens animadas. + Compare e visualize o resultado + Inspecione as alterações, o tamanho do arquivo e as diferenças visuais antes de manter uma exportação + Verifique antes de compartilhar + As ferramentas de comparação ajudam a detectar artefatos de compactação, cores erradas ou edições indesejadas antecipadamente. + Abra Comparar quando quiser inspecionar duas versões lado a lado. + Amplie bordas, texto, gradientes e regiões transparentes onde os problemas são mais fáceis de ignorar. + Se a saída estiver muito pesada ou desfocada, retorne à ferramenta anterior e ajuste a qualidade ou o formato. + Use o hub de ferramentas PDF + Mesclar, dividir, girar, remover páginas, compactar, proteger, OCR e marcar arquivos PDF com marca d\'água + Escolha uma operação PDF + O hub PDF agrupa ações de documentos atrás de um ponto de entrada para que você possa escolher a operação exata. Comece aí quando o arquivo já for um PDF e use Imagens para PDF ou Scanner de Documentos quando sua fonte ainda for um conjunto de imagens. + Abra Ferramentas PDF e escolha a operação que corresponde ao seu objetivo. + Selecione o PDF ou as imagens de origem e revise a ordem das páginas, rotação, proteção ou opções de OCR. + Salve o PDF gerado e abra-o uma vez para confirmar a contagem, ordem e legibilidade das páginas. + Transforme imagens em PDF + Combine digitalizações, capturas de tela, recibos ou fotos em um documento compartilhável + Crie um documento limpo + As imagens tornam-se páginas PDF melhores quando são cortadas, ordenadas e dimensionadas de forma consistente. Trate a lista de imagens como uma pilha de páginas: cada rotação, margem e escolha de tamanho de página afetam a legibilidade do documento final. + Corte ou gire as imagens primeiro quando as bordas, sombras ou orientação da página estiverem incorretas. + Selecione as imagens na ordem de página pretendida e ajuste o tamanho ou as margens da página, se a ferramenta oferecer. + Exporte o PDF e verifique se todas as páginas estão legíveis antes de enviá-lo. + Digitalizar documentos + Capture páginas em papel e prepare-as para PDF, OCR ou compartilhamento + Capture páginas legíveis + A digitalização de documentos funciona melhor com páginas planas, boa iluminação e perspectiva corrigida. Uma digitalização limpa antes da exportação de OCR ou PDF economiza tempo porque o reconhecimento e a compactação de texto dependem da qualidade da página. + Coloque o documento em uma superfície contrastante com luz suficiente e sombras mínimas. + Ajuste os cantos detectados ou corte manualmente para que a página preencha o quadro de maneira limpa. + Exporte como imagens ou PDF e execute o OCR se precisar de texto selecionável. + Proteja ou desbloqueie arquivos PDF + Use senhas com cuidado e mantenha uma cópia da fonte editável quando possível + Lide com o acesso ao PDF com segurança + As ferramentas de proteção são úteis para compartilhamento controlado, mas as senhas são fáceis de perder e difíceis de recuperar. Proteja as cópias para distribuição, mantenha os arquivos de origem desprotegidos separadamente e verifique o resultado antes de excluir qualquer coisa. + Proteja uma cópia do PDF, não o seu único documento de origem. + Armazene a senha em algum lugar seguro antes de compartilhar o arquivo protegido. + Use o desbloqueio apenas para arquivos que você tem permissão para editar e verifique se a cópia desbloqueada abre corretamente. + Organizar páginas PDF + Mesclar, dividir, reorganizar, girar, cortar, remover páginas e adicionar números de páginas deliberadamente + Corrija a estrutura do documento antes da decoração + As ferramentas de páginas PDF são mais fáceis de usar na mesma ordem em que você prepararia um documento em papel: colete as páginas, remova as erradas, coloque-as em ordem, corrija a orientação e adicione detalhes finais, como números de páginas ou marcas d\'água. + Use mesclar ou imagens para PDF primeiro quando o documento ainda estiver dividido em vários arquivos. + Reorganize, gire, corte ou remova páginas antes de adicionar números de páginas, assinaturas ou marcas d’água. + Visualize o PDF final após edições estruturais, pois uma página faltante ou girada pode ser difícil de ser notada posteriormente. + Anotações em PDF + Remova anotações ou nivele-as quando os espectadores mostrarem comentários e marcações de forma diferente + Controle o que permanece visível + As anotações podem ser comentários, destaques, desenhos, marcas de formulário ou outros objetos PDF extras. Alguns visualizadores os exibem de maneira diferente, portanto, removê-los ou nivelá-los pode tornar os documentos compartilhados mais previsíveis. + Remova anotações quando comentários, destaques ou marcas de revisão não devem ser incluídos na cópia compartilhada. + Achatar quando as marcas visíveis devem permanecer na página, mas não se comportam mais como anotações editáveis. + Mantenha uma cópia original porque pode ser difícil reverter a remoção ou nivelamento de anotações. + Reconhecer texto + Extraia texto de imagens com mecanismos e idiomas de OCR selecionáveis + Obtenha melhores resultados de OCR + A precisão do OCR depende da nitidez da imagem, dos dados de idioma, do contraste e da orientação da página. Os melhores resultados geralmente vêm da preparação inicial da imagem: eliminar o ruído, girar na vertical, aumentar a legibilidade e, em seguida, reconhecer o texto. + Corte a imagem na área de texto e gire-a na vertical antes do reconhecimento. + Escolha o idioma correto e baixe os dados do idioma se o aplicativo solicitar. + Copie ou compartilhe o texto reconhecido e verifique manualmente nomes, números e pontuação. + Escolha os dados do idioma OCR + Melhore o reconhecimento combinando scripts, idiomas e modelos de OCR baixados + Combine o OCR com o texto + A linguagem de OCR errada pode fazer com que boas fotos pareçam um reconhecimento ruim. Os dados de idioma são importantes para scripts, pontuação, números e documentos mistos; portanto, escolha o idioma que aparece na imagem em vez do idioma da IU do telefone. + Escolha o idioma ou script que aparece na imagem, não apenas o idioma do telefone. + Baixe os dados ausentes antes de trabalhar off-line ou processar um lote. + Para documentos com idiomas mistos, execute primeiro o idioma mais importante e verifique manualmente os nomes e números. + PDFs pesquisáveis + Escreva o texto OCR de volta em arquivos para que as páginas digitalizadas possam ser pesquisadas e copiadas + Transforme digitalizações em documentos pesquisáveis + OCR pode fazer mais do que copiar texto para a área de transferência. Quando você escreve texto reconhecido em um arquivo ou PDF pesquisável, a imagem da página permanece visível enquanto o texto fica mais fácil de pesquisar, selecionar, indexar ou arquivar. + Use a saída PDF pesquisável para documentos digitalizados que ainda devem se parecer com as páginas originais. + Use write-to-file quando precisar apenas de texto extraído e não se importar com a imagem da página. + Verifique manualmente números, nomes e tabelas importantes porque o texto OCR pode ser pesquisável, mas ainda assim imperfeito. + Digitalize códigos QR + Leia o conteúdo QR da entrada da câmera ou imagens salvas, quando disponíveis + Leia códigos com segurança + Os códigos QR podem conter links, dados de Wi-Fi, contatos, texto ou outras cargas úteis. + Abra o Scan QR Code e mantenha o código nítido, plano e totalmente dentro da moldura. + Revise o conteúdo decodificado antes de abrir links ou copiar dados confidenciais. + Se a digitalização falhar, melhore a iluminação, corte o código ou tente uma imagem de origem de resolução mais alta. + Use Base64 e somas de verificação + Codifique imagens como texto, decodifique o texto em imagens e verifique a integridade do arquivo + Mova-se entre arquivos e texto + Base64 é útil para pequenas cargas de imagens, enquanto as somas de verificação ajudam a confirmar que os arquivos não foram alterados. Essas ferramentas são mais úteis em fluxos de trabalho técnicos, relatórios de suporte, transferências de arquivos e locais onde os arquivos binários precisam se tornar texto temporariamente. + Use ferramentas Base64 para codificar uma imagem pequena quando um fluxo de trabalho precisar de texto em vez de um arquivo binário. + Decodifique Base64 apenas de fontes confiáveis ​​e verifique a visualização antes de salvar. + Use ferramentas de soma de verificação quando precisar comparar arquivos exportados ou confirmar um upload/download. + Empacote ou proteja dados + Use ferramentas ZIP e de criptografia para agrupar arquivos ou transformar dados de texto + Mantenha os arquivos relacionados juntos + As ferramentas de empacotamento são úteis quando várias saídas devem viajar como um arquivo. Eles também são bons para manter imagens, PDFs, logs e metadados gerados juntos quando você precisa enviar um conjunto completo de resultados. + Use ZIP quando precisar enviar muitas imagens ou documentos exportados juntos. + Use ferramentas de criptografia somente quando você compreender o método selecionado e puder armazenar as chaves necessárias com segurança. + Teste o arquivo criado ou o texto transformado antes de excluir os arquivos de origem. + Somas de verificação em nomes + Use nomes de arquivos baseados em soma de verificação quando a exclusividade e a integridade forem mais importantes do que a legibilidade + Nomeie os arquivos por conteúdo + Os nomes de arquivos de soma de verificação são úteis quando as exportações podem ter nomes visíveis duplicados ou quando você precisa provar que dois arquivos são idênticos. Eles são menos amigáveis ​​para navegação manual, portanto, use-os para arquivos técnicos e fluxos de trabalho de verificação. + Habilite a soma de verificação como nome de arquivo quando a identidade do conteúdo for mais importante do que um título legível por humanos. + Use-o para arquivamentos, desduplicação, exportações repetíveis ou arquivos que podem ser carregados e baixados novamente. + Combine nomes de somas de verificação com pastas ou metadados quando as pessoas ainda precisarem entender o que o arquivo representa. + Escolha cores das imagens + Amostra de pixels, copie códigos de cores e reutilize cores em paletas ou designs + Experimente a cor exata + A seleção de cores é útil para combinar um design, extrair cores de marcas ou verificar valores de imagens. O zoom é importante porque o antialiasing, as sombras e a compactação podem tornar os pixels vizinhos ligeiramente diferentes da cor esperada. + Abra Escolher cor da imagem e escolha uma imagem de origem com a cor necessária. + Aumente o zoom antes de amostrar pequenos detalhes para que os pixels próximos não afetem o resultado. + Copie a cor no formato exigido pelo seu destino, como HEX, RGB ou HSV. + Crie paletas e use a biblioteca de cores + Extraia paletas, navegue pelas cores salvas e mantenha sistemas visuais consistentes + Crie uma paleta reutilizável + As paletas ajudam a manter gráficos, marcas d’água e desenhos exportados visualmente consistentes. Eles são especialmente úteis quando você anota repetidamente capturas de tela, prepara ativos de marca ou precisa das mesmas cores em várias ferramentas. + Gere uma paleta a partir de uma imagem ou adicione cores manualmente quando já souber os valores. + Nomeie ou organize cores importantes para que você possa encontrá-las novamente mais tarde. + Use valores copiados em ferramentas de desenho, marca d\'água, gradiente, SVG ou design externo. + Crie gradientes + Crie imagens gradientes para planos de fundo, espaços reservados e experimentos visuais + Controlar transições de cores + As ferramentas de gradiente são úteis quando você precisa de uma imagem gerada em vez de editar uma existente. Eles podem se tornar fundos limpos, espaços reservados, papéis de parede, máscaras ou ativos simples para ferramentas de design externas. + Escolha o tipo de gradiente e defina primeiro as cores importantes. + Ajuste a direção, as paradas, a suavidade e o tamanho da saída para o caso de uso de destino. + Exporte em um formato que corresponda ao destino, geralmente PNG para gráficos gerados limpos. + Acessibilidade de cores + Use cores dinâmicas, contraste e opções para daltônicos quando a IU for difícil de ler + Torne as cores mais fáceis de usar + As configurações de cores afetam o conforto, a legibilidade e a rapidez com que você pode digitalizar os controles. Se for difícil distinguir chips de ferramentas, controles deslizantes ou estados selecionados, as opções de tema e acessibilidade podem ser mais úteis do que simplesmente alterar o modo escuro. + Experimente cores dinâmicas quando quiser que o aplicativo siga a paleta de papel de parede atual. + Aumente o contraste ou altere o esquema quando os botões e superfícies não se destacam o suficiente. + Use opções de esquema para daltônicos se alguns estados ou acentos forem difíceis de distinguir. + Fundo alfa + Controle a visualização ou a cor de preenchimento usada em PNG transparente, WebP e saídas semelhantes + Entenda as visualizações transparentes + A cor de fundo para formatos alfa pode facilitar a inspeção de imagens transparentes, mas é importante saber se você está alterando um comportamento de visualização/preenchimento ou nivelando o resultado final. Isso é importante para adesivos, logotipos e imagens com fundo removido. + Use primeiro um formato com suporte alfa, como PNG ou WebP, quando pixels transparentes precisarem sobreviver à exportação. + Ajuste as configurações de cor de fundo quando as bordas transparentes forem difíceis de ver na superfície do aplicativo. + Exporte um pequeno teste e reabra-o em outro aplicativo para confirmar se a transparência ou o preenchimento escolhido foi salvo. + Escolha do modelo de IA + Escolha um modelo por tipo de imagem e defeito em vez de escolher primeiro o maior + Combine o modelo com o dano + As ferramentas AI podem baixar ou importar diferentes modelos ONNX/ORT, e cada modelo é treinado para um tipo específico de problema. Um modelo para blocos JPEG, limpeza de linha de anime, remoção de ruído, remoção de fundo, colorização ou aumento de escala pode produzir resultados piores quando usado no tipo de imagem errado. + Leia a descrição do modelo antes de baixar; escolha o modelo que nomeia seu problema real, como compactação, ruído, meio-tom, desfoque ou remoção de fundo. + Comece com um modelo menor ou mais específico ao testar um novo tipo de imagem e compare com modelos mais pesados ​​somente se o resultado não for bom o suficiente. + Mantenha apenas os modelos que você realmente usa, pois os modelos baixados e importados podem ocupar um espaço de armazenamento considerável. + Compreenda as famílias modelo + Os nomes dos modelos geralmente sugerem seu alvo: modelos de estilo RealESRGAN sofisticados, modelos SCUNet e FBCNN limpam ruído ou compactação, modelos de fundo criam máscaras e colorizadores adicionam cor à entrada em escala de cinza. Os modelos personalizados importados podem ser poderosos, mas devem corresponder ao formato de entrada/saída esperado e usar o formato ONNX ou ORT compatível. + Use upscalers quando precisar de mais pixels, denoisers quando a imagem estiver granulada e removedores de artefatos quando blocos de compactação ou faixas forem o problema principal. + Use modelos de fundo apenas para separação de assuntos; eles não são iguais à remoção geral de objetos ou ao aprimoramento de imagens. + Importe modelos personalizados apenas de fontes confiáveis ​​e teste-os em uma imagem pequena antes de usar arquivos importantes. + Parâmetros de IA + Ajuste o tamanho do bloco, a sobreposição e os trabalhadores paralelos para equilibrar qualidade, velocidade e memória + Velocidade de equilíbrio com estabilidade + O tamanho do pedaço controla o tamanho dos pedaços de imagem antes de serem processados ​​pelo modelo. A sobreposição combina pedaços vizinhos para ocultar as bordas. Trabalhadores paralelos podem acelerar o processamento, mas cada trabalhador precisa de memória, portanto, valores altos podem tornar imagens grandes instáveis ​​em telefones com RAM limitada. + Use pedaços maiores para um contexto mais suave quando seu dispositivo lidar com o modelo de maneira confiável e a imagem não for enorme. + Aumente a sobreposição quando as bordas dos pedaços ficarem visíveis, mas lembre-se de que mais sobreposição significa mais trabalho repetido. + Levante trabalhadores paralelos somente após um teste estável; se o processamento ficar lento, aquecer o dispositivo ou travar, reduza os trabalhadores primeiro. + Evite artefatos de pedaços + O chunking permite que o aplicativo processe imagens maiores do que o modelo pode manipular confortavelmente de uma só vez. A desvantagem é que cada bloco tem menos contexto circundante, portanto, uma sobreposição baixa ou pedaços muito pequenos podem criar bordas visíveis, alterações de textura ou detalhes inconsistentes entre áreas vizinhas. + Aumente a sobreposição se você observar bordas retangulares, alterações repetidas de textura ou saltos de detalhes entre regiões processadas. + Reduza o tamanho do bloco se o processo falhar antes de produzir a saída, especialmente em imagens grandes ou modelos pesados. + Salve um pequeno teste de corte antes de processar a imagem completa para poder comparar os parâmetros rapidamente. + Estabilidade de IA + Reduza o tamanho do bloco, os trabalhadores e a resolução de origem quando o processamento de IA falha ou congela + Recupere-se de trabalhos pesados ​​de IA + O processamento de IA é um dos fluxos de trabalho mais pesados ​​do aplicativo. Falhas, sessões com falha, congelamentos ou reinicializações repentinas de aplicativos geralmente significam que o modelo, a resolução da fonte, o tamanho do bloco ou a contagem de trabalhadores paralelos são muito exigentes para o estado atual do dispositivo. + Se o aplicativo travar ou não conseguir abrir uma sessão de modelo, reduza os trabalhadores paralelos e o tamanho do bloco e tente a mesma imagem novamente. + Redimensione imagens muito grandes antes do processamento de IA quando o objetivo não exigir a resolução original. + Feche outros aplicativos pesados, use um modelo menor ou processe menos arquivos de uma vez quando a pressão da memória continuar retornando. + Diagnosticar falhas de modelo + Uma falha na execução da IA ​​nem sempre significa que a imagem está ruim. O arquivo de modelo pode estar incompleto, não ser compatível, ser muito grande para a memória ou não corresponder à operação. Quando o aplicativo relatar uma sessão com falha, trate isso como um sinal para simplificar primeiro o modelo e os parâmetros. + Exclua e baixe novamente um modelo se ele falhar imediatamente após o download ou se comportar como se o arquivo estivesse corrompido. + Experimente um modelo integrado para download antes de culpar um modelo personalizado importado, porque os modelos importados podem ter entradas incompatíveis. + Use os logs de aplicativos depois de reproduzir a falha se precisar relatar uma falha ou erro de sessão. + Experimente no Shader Studio + Use efeitos de sombreamento de GPU para processamento avançado de imagens e aparência personalizada + Trabalhe cuidadosamente com shaders + O Shader Studio é poderoso, mas o código e os parâmetros do shader precisam de pequenas alterações testáveis. Trate-o como uma ferramenta experimental: mantenha uma linha de base funcional, mude uma coisa de cada vez e teste com diferentes tamanhos de imagem antes de confiar em uma predefinição. + Comece com um shader funcional conhecido ou um efeito simples antes de adicionar parâmetros. + Altere um parâmetro ou bloco de código por vez e verifique se há erros na visualização. + Exporte predefinições somente depois de testá-las em alguns tamanhos de imagem diferentes. + Faça arte SVG ou ASCII + Gere ativos semelhantes a vetores ou imagens baseadas em texto para produção criativa + Escolha o tipo de saída criativa + As ferramentas SVG e ASCII atendem a diferentes objetivos: gráficos escalonáveis ​​ou renderização em estilo de texto. Ambas são conversões criativas, portanto, visualizar o tamanho final é mais importante do que julgar o resultado a partir de uma pequena miniatura. + Use o SVG Maker quando o resultado precisar ser dimensionado de forma limpa ou ser reutilizado em fluxos de trabalho de design. + Use Arte ASCII quando desejar uma representação de texto estilizado de uma imagem. + Visualize no tamanho final porque pequenos detalhes podem desaparecer na saída gerada. + Gerar texturas de ruído + Crie texturas procedurais para planos de fundo, máscaras, sobreposições ou experimentos + Ajustar a saída processual + A geração de ruído cria novas imagens a partir de parâmetros, portanto pequenas alterações podem produzir resultados muito diferentes. É útil para texturas, máscaras, sobreposições, espaços reservados ou teste de compactação com padrões detalhados. + Escolha um tipo de ruído e tamanho de saída antes de ajustar os detalhes. + Ajuste a escala, a intensidade, as cores ou a semente até que o padrão se ajuste ao uso pretendido. + Salve resultados úteis e anote os parâmetros se precisar recriar a textura posteriormente. + Projetos de marcação + Use arquivos de projeto quando as anotações precisarem permanecer editáveis ​​após fechar o aplicativo + Mantenha as edições em camadas reutilizáveis + Os arquivos de projeto de marcação são úteis quando uma captura de tela, um diagrama ou uma imagem de instrução podem precisar de alterações posteriormente. Os arquivos PNG/JPEG exportados são imagens finais, enquanto os arquivos do projeto preservam as camadas editáveis ​​e o estado de edição para ajustes futuros. + Use camadas de marcação quando anotações, textos explicativos ou elementos de layout permanecerem editáveis. + Salve ou reabra o arquivo do projeto antes de exportar uma imagem final se desejar mais revisões. + Exporte uma imagem normal somente quando estiver pronto para compartilhar uma versão final nivelada. + Criptografar arquivos + Proteja os arquivos com uma senha e teste a descriptografia antes de excluir qualquer cópia de origem + Lide com saídas criptografadas com cuidado + As ferramentas de criptografia podem proteger arquivos com criptografia baseada em senha, mas não substituem a memorização da chave ou a manutenção de backups. A criptografia é útil para transporte e armazenamento, enquanto o ZIP é melhor quando o objetivo principal é agrupar várias saídas. + Criptografe uma cópia primeiro e guarde o original até testar se a descriptografia funciona com a senha armazenada. + Utilize um gerenciador de senhas ou outro local seguro para a chave; o aplicativo não consegue adivinhar uma senha perdida para você. + Compartilhe arquivos criptografados apenas com pessoas que também tenham a senha e saibam qual ferramenta ou método deve descriptografá-los. + Organizar ferramentas + Ferramentas de agrupar, solicitar, pesquisar e fixar para que a tela principal corresponda ao seu fluxo de trabalho real + Torne a tela principal mais rápida + Vale a pena ajustar as configurações de disposição das ferramentas quando você souber quais ferramentas você mais usa. O agrupamento ajuda na exploração, a ordem personalizada ajuda a memória muscular e os favoritos mantêm as ferramentas diárias acessíveis mesmo quando a lista completa fica grande. + Use o modo agrupado enquanto aprende o aplicativo ou quando preferir ferramentas organizadas por tipo de tarefa. + Mude para a ordem personalizada quando você já conhece suas ferramentas frequentes e deseja menos rolagens. + Use as configurações de pesquisa e os favoritos juntos para ferramentas raras que você lembra pelo nome, mas não deseja que fiquem visíveis o tempo todo. + Lidar com arquivos grandes + Evite exportações lentas, pressão de memória e resultados inesperadamente grandes + Reduza o trabalho antes da exportação + Imagens muito grandes, lotes longos e formatos de alta qualidade podem consumir mais memória e tempo. A solução mais rápida geralmente é reduzir a quantidade de trabalho antes da operação mais pesada, sem esperar que a mesma entrada superdimensionada termine. + Redimensione imagens enormes antes de aplicar filtros pesados, OCR ou conversão de PDF. + Use primeiro um lote menor para confirmar as configurações e depois processe o restante com a mesma predefinição. + Reduza a qualidade ou altere o formato quando o arquivo de saída for maior que o esperado. + Cache e visualizações + Entenda as visualizações geradas, a limpeza do cache e o uso do armazenamento após sessões intensas + Mantenha o armazenamento e a velocidade equilibrados + A geração de visualização e o cache podem tornar a navegação repetida mais rápida, mas sessões intensas de edição podem deixar dados temporários para trás. As configurações de cache ajudam quando o aplicativo parece lento, o armazenamento é limitado ou as visualizações parecem obsoletas após muitos experimentos. + Manter as visualizações ativadas ao navegar por muitas imagens é mais importante do que minimizar o armazenamento temporário. + Limpe o cache após lotes grandes, experimentos malsucedidos ou sessões longas com muitos arquivos de alta resolução. + Use a limpeza automática do cache se preferir a limpeza do armazenamento em vez de manter as visualizações quentes entre as inicializações. + Ajustar o comportamento da tela + Use opções de tela cheia, modo seguro, brilho e barra do sistema para um trabalho focado + Faça a interface se adequar à situação + As configurações da tela ajudam quando você edita em paisagem, apresenta imagens privadas ou precisa de brilho consistente. Eles não são necessários para uso normal, mas resolvem situações embaraçosas, como inspeção de QR, visualizações privadas ou edição de paisagem restrita. + Use as configurações de tela cheia ou da barra do sistema quando a edição do espaço for mais importante do que o cromo de navegação. + Use o modo seguro quando imagens privadas não aparecerem em capturas de tela ou visualizações de aplicativos. + Use a aplicação de brilho para ferramentas onde imagens escuras ou códigos QR são difíceis de inspecionar. + Mantenha a transparência + Impedir que fundos transparentes fiquem brancos, pretos ou achatados + Use saída com reconhecimento de alfa + A transparência sobrevive somente quando a ferramenta selecionada e o formato de saída suportam alfa. Se um plano de fundo exportado ficar branco ou preto, o problema geralmente é o formato de saída, uma configuração de preenchimento/plano de fundo ou um aplicativo de destino que achatou a imagem. + Escolha PNG ou WebP ao exportar imagens, adesivos, logotipos ou sobreposições com fundo removido. + Evite JPEG para saída transparente porque ele não armazena um canal alfa. + Verifique as configurações relacionadas à cor de fundo para formatos alfa se a visualização mostrar um fundo preenchido. + Corrija problemas de compartilhamento e importação + Use as configurações do seletor e os formatos suportados quando os arquivos não aparecem ou não abrem corretamente + Coloque o arquivo no aplicativo + Os problemas de importação geralmente são causados ​​por permissões do provedor, formatos não suportados ou comportamento do seletor. Os arquivos compartilhados de aplicativos em nuvem e mensageiros podem ser temporários, portanto, escolher uma fonte persistente pode ser mais confiável para edições mais longas. + Tente abrir o arquivo por meio do seletor de sistema em vez de um atalho de arquivos recentes. + Se um formato não for compatível com uma ferramenta, converta-o primeiro ou abra uma ferramenta mais específica. + Revise as configurações do seletor de imagens e do iniciador se os fluxos de compartilhamento ou a abertura direta da ferramenta se comportarem de maneira diferente do esperado. + Confirmação de saída + Evite perder edições não salvas quando gestos, toques para trás ou trocas de ferramentas acontecem acidentalmente + Proteja o trabalho inacabado + A confirmação de saída da ferramenta é útil quando você usa a navegação por gestos, edita arquivos grandes ou alterna frequentemente entre aplicativos. Ele adiciona uma pequena pausa antes de sair de uma ferramenta para que configurações e visualizações inacabadas não sejam perdidas acidentalmente. + Ative a confirmação se gestos acidentais para trás fecharem uma ferramenta antes da exportação. + Mantenha-o desativado se você faz conversões rápidas em uma única etapa e prefere uma navegação mais rápida. + Use-o junto com predefinições para fluxos de trabalho mais longos, onde reconstruir as configurações seria irritante. + Use logs ao relatar problemas + Colete detalhes úteis antes de abrir um problema ou entrar em contato com o desenvolvedor + Relatar problemas com contexto + Um relatório claro com etapas, versão do aplicativo, tipo de entrada e registros é muito mais fácil de diagnosticar. Os logs são mais úteis logo após a reprodução de um problema porque mantêm o contexto circundante atualizado. + Anote o nome da ferramenta, a ação exata e se isso acontece com um arquivo ou com todos os arquivos. + Abra os logs de aplicativos depois de reproduzir o problema e compartilhe a saída de log relevante quando possível. + Anexe capturas de tela ou arquivos de amostra somente quando não contiverem informações privadas. + O processamento em segundo plano foi interrompido + O Android não permitiu que a notificação de processamento em segundo plano iniciasse a tempo. Esta é uma limitação de tempo do sistema que não pode ser corrigida de forma confiável. Reinicie o aplicativo e tente novamente; manter o aplicativo aberto e permitir notificações ou trabalho em segundo plano pode ajudar. + Ignorar seleção de arquivos + Abra ferramentas mais rapidamente quando a entrada geralmente vem do compartilhamento, da área de transferência ou de uma tela anterior + Torne os lançamentos repetidos de ferramentas mais rápidos + Ignorar seleção de arquivos altera a primeira etapa das ferramentas suportadas. É útil quando você normalmente entra em uma ferramenta com uma imagem já selecionada ou a partir de ações de compartilhamento do Android, mas pode parecer confuso se você espera que cada ferramenta solicite um arquivo imediatamente. + Habilite-o somente quando seu fluxo normal já fornecer a imagem de origem antes da ferramenta ser aberta. + Mantenha-o desativado enquanto aprende o aplicativo, pois a seleção explícita torna cada fluxo de ferramenta mais fácil de entender. + Se uma ferramenta abrir sem nenhuma entrada óbvia, desative esta configuração ou inicie em Compartilhar/Abrir com para que o Android passe o arquivo primeiro. + Abra o editor primeiro + Ignorar a visualização quando uma imagem compartilhada deve ir direto para edição + Escolha visualizar ou editar como primeira parada + Abrir edição em vez de visualização é para usuários que já sabem que desejam modificar a imagem. A visualização é mais segura quando você inspeciona arquivos do bate-papo ou do armazenamento em nuvem; a edição direta é mais rápida para capturas de tela, cortes rápidos, anotações e correções únicas. + Ative a edição direta se a maioria das imagens compartilhadas precisar imediatamente de cortes, marcações, filtros ou alterações de exportação. + Deixe a visualização primeiro quando você inspeciona metadados, dimensões, transparência ou qualidade de imagem com frequência antes de decidir. + Use-o junto com os favoritos para que as imagens compartilhadas cheguem perto das ferramentas que você realmente usa. + Entrada de texto predefinida + Insira valores predefinidos exatos em vez de ajustar os controles repetidamente + Use valores precisos quando os controles deslizantes estiverem lentos + A entrada de texto predefinido ajuda quando você precisa de números exatos para trabalhos repetidos: tamanhos de imagens sociais, margens de documentos, alvos de compactação, larguras de linha ou outros valores que são incômodos de serem alcançados com gestos. Também é útil em telas pequenas onde o movimento fino do controle deslizante é mais difícil. + Ative a entrada de texto se você reutiliza frequentemente tamanhos exatos, porcentagens, valores de qualidade ou parâmetros de ferramentas. + Salve o valor como uma predefinição após digitá-lo uma vez e reutilize-o em vez de reconstruir a mesma configuração. + Mantenha os controles de gestos para ajuste visual aproximado e valores digitados para requisitos de saída rigorosos. + Salvar próximo ao original + Coloque os arquivos gerados ao lado das imagens de origem quando o contexto da pasta for importante + Mantenha as saídas com suas fontes + Salvar na pasta original é útil para pastas de câmeras, pastas de projetos, digitalizações de documentos e lotes de clientes onde a cópia editada deve ficar próxima à fonte. Pode ser menos organizado do que uma pasta de saída dedicada, então combine-a com sufixos ou padrões claros de nome de arquivo. + Habilite-o quando cada pasta de origem já for significativa e as saídas deverão permanecer agrupadas lá. + Use sufixos, prefixos, carimbos de data/hora ou padrões de nome de arquivo para que as cópias editadas sejam fáceis de separar dos originais. + Desative-o para lotes mistos quando desejar que todos os arquivos gerados sejam coletados em uma pasta de exportação. + Ignorar saída maior + Evite salvar conversões que inesperadamente se tornem mais pesadas que a fonte + Proteja lotes contra surpresas de tamanho ruim + Algumas conversões tornam os arquivos maiores, especialmente capturas de tela PNG, fotos já compactadas, WebP de alta qualidade ou imagens com metadados preservados. Permitir Ignorar se Maior evita que essas saídas substituam uma fonte menor em fluxos de trabalho onde a redução do tamanho é o objetivo principal. + Ative-o quando a exportação for destinada a compactar, redimensionar ou preparar arquivos para limites de upload. + Revise os arquivos ignorados após um lote; eles podem já estar otimizados ou precisar de um formato diferente. + Desative-o quando a qualidade, a transparência ou a compatibilidade de formato forem mais importantes do que o tamanho final do arquivo. + Área de transferência e links + Controle a entrada automática da área de transferência e visualizações de links para ferramentas baseadas em texto mais rápidas + Torne a entrada automática previsível + As configurações da área de transferência e do link são convenientes para QR, Base64, URL e fluxos de trabalho com muito texto, mas a entrada automática deve permanecer intencional. Se o aplicativo parecer preencher os campos sozinho, verifique o comportamento de colar na área de transferência; se os cartões de link parecerem barulhentos ou lentos, ajuste o comportamento de visualização do link. + Permita a colagem automática da área de transferência quando você copia texto com frequência e abre imediatamente uma ferramenta correspondente. + Desative a colagem automática se o conteúdo privado da área de transferência aparecer em ferramentas onde você não esperava. + Desative as visualizações de link quando a busca de visualização for lenta, perturbadora ou desnecessária para o seu fluxo de trabalho. + Controles compactos + Use seletores mais densos e posicionamento rápido de configurações quando o espaço da tela for apertado + Coloque mais controles em telas pequenas + Seletores compactos e posicionamento rápido de configurações ajudam em telefones pequenos, edição de paisagem e ferramentas com muitos parâmetros. A desvantagem é que os controles podem parecer mais densos, então é melhor fazer isso depois que você já reconhecer as opções comuns. + Ative seletores compactos quando as caixas de diálogo ou listas de opções parecerem muito altas para a tela. + Ajuste o lado das configurações rápidas se os controles da ferramenta forem mais fáceis de alcançar de um lado da tela. + Retorne aos controles mais espaçosos se você ainda estiver aprendendo o aplicativo ou se errar frequentemente em opções densas. + Carregar imagens da web + Cole o URL de uma página ou imagem, visualize as imagens analisadas e salve ou compartilhe os resultados selecionados + Use imagens da web deliberadamente + O Carregamento de imagens da Web analisa fontes de imagens do URL fornecido, visualiza a primeira imagem disponível e permite salvar ou compartilhar imagens analisadas selecionadas. Alguns sites usam links temporários, protegidos ou gerados por script, portanto, uma página que funciona em um navegador ainda pode não retornar imagens utilizáveis. + Cole um URL de imagem direto ou um URL de página e aguarde até que a lista de imagens analisadas apareça. + Use os controles de quadro ou seleção quando a página contém várias imagens e você só precisa de algumas delas. + Se nada carregar, tente primeiro um link direto da imagem ou faça o download pelo navegador; o site pode bloquear a análise ou usar links privados. + Escolha o tipo de redimensionamento + Entenda Explícito, Flexível, Corte e Ajuste antes de forçar uma imagem em novas dimensões + Controle a forma, a tela e a proporção + O tipo de redimensionamento decide o que acontece quando a largura e a altura solicitadas não correspondem à proporção original. É a diferença entre esticar pixels, preservar proporções, cortar áreas extras ou adicionar uma tela de fundo. + Use Explícito somente quando a distorção for aceitável ou a fonte já tiver a mesma proporção do destino. + Use Flexível para manter as proporções redimensionando do lado importante, especialmente para fotos e lotes mistos. + Use Cortar para quadros rígidos sem bordas ou Ajustar quando toda a imagem precisar permanecer visível dentro de uma tela fixa. + Escolha o modo de escala de imagem + Escolha o filtro de reamostragem que controla nitidez, suavidade, velocidade e artefatos de borda + Redimensione sem suavidade acidental + O modo de escala de imagem controla como os novos pixels são calculados durante o redimensionamento. Os modos simples são rápidos e previsíveis, enquanto os filtros avançados podem preservar melhor os detalhes, mas podem tornar as bordas mais nítidas, criar halos ou demorar mais em imagens grandes. + Use Básico ou Bilinear para redimensionar rapidamente todos os dias quando o resultado só precisa ser menor e limpo. + Use os modos Lanczos, Robidoux, Spline ou EWA quando detalhes finos, texto ou redução de escala de alta qualidade forem importantes. + Use Mais próximo para pixel art, ícones e gráficos com bordas rígidas, onde pixels desfocados arruinariam a aparência. + Dimensionar o espaço de cores + Decida como as cores são mescladas enquanto os pixels são reamostrados durante o redimensionamento + Mantenha gradientes e bordas naturais + A escala do espaço de cores altera a matemática usada durante o redimensionamento. A maioria das imagens funciona bem com o padrão, mas espaços lineares ou perceptivos podem fazer gradientes, bordas escuras e cores saturadas parecerem mais limpas após um dimensionamento forte. + Deixe o padrão quando a velocidade e a compatibilidade forem mais importantes do que pequenas diferenças de cores. + Experimente os modos Linear ou Perceptivo quando contornos escuros, capturas de tela da interface do usuário, gradientes ou faixas coloridas parecerem errados após o redimensionamento. + Compare o antes e o depois na tela de destino real, pois as alterações no espaço de cores podem ser sutis e dependentes do conteúdo. + Limitar modos de redimensionamento + Saiba quando imagens acima do limite devem ser ignoradas, recodificadas ou reduzidas para caber + Escolha o que acontece no limite + Limit Resize não é apenas uma ferramenta para imagens menores. Seu modo decide o que o app faz quando uma fonte já está dentro dos seus limites ou quando apenas um lado infringe a regra, o que é importante para pastas mistas e preparação de upload. + Use Ignorar quando as imagens dentro dos limites não forem alteradas e não forem exportadas novamente. + Use Recode quando as dimensões forem aceitáveis, mas você ainda precisar de um novo formato, comportamento de metadados, qualidade ou nome de arquivo. + Use o Zoom quando as imagens que ultrapassam os limites devem ser reduzidas proporcionalmente até caberem na caixa permitida. + Alvos de proporção + Evite faces esticadas, bordas cortadas e bordas inesperadas em tamanhos de saída rígidos + Combine a forma antes de redimensionar + A largura e a altura são apenas metade de um alvo de redimensionamento. A proporção decide se a imagem pode caber naturalmente, precisa de corte ou de uma tela ao redor. Verificar primeiro a forma evita resultados de redimensionamento mais surpreendentes. + Compare a forma de origem com a forma de destino antes de escolher Explícito, Cortar ou Ajustar. + Corte primeiro quando o assunto puder perder fundo extra e o destino precisar de um enquadramento rígido. + Use Ajustar ou Flexível quando todas as partes da imagem original precisarem permanecer visíveis. + Redimensionamento sofisticado ou normal + Saiba quando adicionar pixels precisa de IA e quando o dimensionamento simples é suficiente + Não confunda tamanho com detalhe + O redimensionamento normal altera as dimensões interpolando pixels; não pode inventar detalhes reais. O upscale de IA pode criar detalhes com aparência mais nítida, mas é mais lento e pode alucinar texturas, então a escolha certa depende se você precisa de compatibilidade, velocidade ou restauração visual. + Use o redimensionamento normal para miniaturas, limites de upload, capturas de tela e alterações simples de dimensão. + Use o aprimoramento de IA quando uma imagem pequena ou suave precisar ficar melhor em um tamanho maior. + Compare rostos, textos e padrões após o aprimoramento da IA, pois os detalhes gerados podem parecer convincentes, mas incorretos. + A ordem do filtro é importante + Aplique limpeza, cor, nitidez e compactação final em uma sequência intencional + Construa uma pilha de filtros mais limpa + Os filtros nem sempre são intercambiáveis. Um desfoque antes da nitidez, um aumento de contraste antes do OCR ou uma mudança de cor antes da compactação podem produzir resultados muito diferentes dos mesmos controles. + Corte e gire primeiro para que os filtros posteriores funcionem apenas nos pixels que permanecerão. + Aplique exposição, equilíbrio de branco e contraste antes de efeitos de nitidez ou estilizados. + Mantenha o redimensionamento e a compactação finais perto do fim para que os detalhes visualizados sobrevivam à exportação de maneira previsível. + Corrija as bordas do fundo + Limpe halos, sobras de sombras, cabelos, buracos e contornos suaves após a remoção automática + Faça os recortes parecerem intencionais + As máscaras automáticas geralmente ficam bem à primeira vista, mas revelam problemas em fundos claros, escuros ou padronizados. A limpeza de bordas é a diferença entre um recorte rápido e um adesivo, logotipo ou imagem de produto reutilizável. + Visualize o resultado em fundos contrastantes para revelar halos e detalhes ausentes nas bordas. + Use a restauração para cabelos, cantos e objetos transparentes antes de apagar as sobras ao redor. + Exporte um pequeno teste na cor de fundo final quando o recorte for colocado em um desenho. + Marcas d’água legíveis + Torne as marcas visíveis em imagens claras, escuras, ocupadas e cortadas sem estragar a foto + Proteja a imagem sem ocultá-la + Uma marca d\'água que fica bem em uma imagem pode desaparecer em outra. Tamanho, opacidade, contraste, posicionamento e repetição devem ser escolhidos para todo o lote, não apenas para a primeira visualização. + Teste a marca nas imagens mais claras e mais escuras do lote antes de exportar todos os arquivos. + Use opacidade mais baixa para marcas grandes e repetidas e contraste mais forte para marcas pequenas nos cantos. + Mantenha rostos, textos e detalhes importantes do produto claros, a menos que a marca tenha como objetivo impedir a reutilização. + Divida uma imagem em blocos + Corte uma única imagem por linhas, colunas, porcentagens personalizadas, formato e qualidade + Prepare grades e peças encomendadas + A divisão de imagens cria múltiplas saídas de uma imagem de origem. Ele pode dividir por contagens de linhas e colunas, ajustar porcentagens de linhas ou colunas e salvar partes com o formato e qualidade de saída selecionados, o que é útil para grades, fatias de páginas, panoramas e postagens sociais ordenadas. + Escolha a imagem de origem e defina o número de linhas e colunas necessárias. + Ajuste as porcentagens de linha ou coluna quando as peças não devem ter o mesmo tamanho. + Escolha o formato e a qualidade de saída antes de salvar, para que cada peça gerada use as mesmas regras de exportação. + Recorte tiras de imagem + Remova regiões verticais ou horizontais e mescle as partes restantes novamente + Remova uma região sem deixar espaço + O corte de imagem é diferente do corte normal. Ele pode remover uma faixa vertical ou horizontal selecionada e depois mesclar as partes restantes da imagem. O modo inverso mantém a região selecionada e o uso de ambas as direções inversas mantém o retângulo selecionado. + Utilize corte vertical para regiões laterais, colunas ou faixas intermediárias; use corte horizontal para banners, lacunas ou linhas. + Habilite a inversão em um eixo quando quiser manter a peça selecionada em vez de removê-la. + Visualize as bordas após o corte porque o conteúdo mesclado pode parecer abrupto quando a área removida cruza detalhes importantes. + Escolha o formato de saída + Escolha JPEG, PNG, WebP, AVIF, JXL ou PDF com base no conteúdo e destino + Deixe o destino decidir o formato + O melhor formato depende do que a imagem contém e onde será utilizada. Fotos, capturas de tela, ativos transparentes, documentos e arquivos animados falham de maneiras diferentes quando forçados ao formato errado. + Use JPEG para ampla compatibilidade fotográfica quando transparência e pixels exatos não forem necessários. + Use PNG para capturas de tela nítidas, capturas de interface do usuário, gráficos transparentes e edições sem perdas. + Use WebP, AVIF ou JXL quando a saída compacta e moderna for importante e o aplicativo de recebimento for compatível. + Extraia capas de áudio + Salve a arte do álbum incorporada ou os quadros de mídia disponíveis de arquivos de áudio como imagens PNG + Extraia a arte dos arquivos de áudio + Audio Covers usa metadados de mídia Android para ler obras de arte incorporadas em arquivos de áudio. Quando a arte incorporada não estiver disponível, o recuperador poderá recorrer a um quadro de mídia se o Android puder fornecer um; se não existir, a ferramenta informará que não há imagem para extrair. + Abra Audio Covers e selecione um ou mais arquivos de áudio com a capa esperada. + Revise a capa extraída antes de salvar ou compartilhar, especialmente para arquivos de aplicativos de streaming ou gravação. + Se nenhuma imagem for encontrada, o arquivo de áudio provavelmente não tem capa incorporada ou o Android não conseguiu expor um quadro utilizável. + Datas e metadados de localização + Decida o que preservar quando o tempo de captura for importante, mas os dados do GPS ou do dispositivo não devem vazar + Separe os metadados úteis dos metadados privados + Os metadados não são todos igualmente arriscados. As datas de captura podem ser úteis para arquivamento e classificação, enquanto o GPS, os campos do tipo serial do dispositivo, as tags de software ou os campos do proprietário podem revelar mais do que o pretendido. + Mantenha etiquetas de data e hora quando a ordem cronológica for importante para álbuns, digitalizações ou histórico de projetos. + Remova o GPS e as etiquetas de identificação do dispositivo antes de compartilhar publicamente ou enviar imagens para estranhos. + Exporte uma amostra e inspecione o EXIF ​​novamente quando os requisitos de privacidade forem rigorosos. + Evite colisões de nomes de arquivos + Proteja as saídas em lote quando muitos arquivos compartilham nomes como image.jpg ou screenshot.png + Torne cada nome de saída único + Nomes de arquivos duplicados são comuns quando as imagens vêm de mensageiros, capturas de tela, pastas na nuvem ou múltiplas câmeras. Um bom padrão evita a substituição acidental e mantém as saídas rastreáveis ​​até suas fontes. + Inclua o nome original mais um sufixo quando a cópia editada for reconhecível. + Adicione sequência, carimbo de data/hora, predefinição ou dimensões ao processar grandes lotes mistos. + Evite substituir fluxos de trabalho até verificar que o padrão de nomenclatura não pode colidir. + Salve ou compartilhe + Escolha entre um arquivo persistente e uma transferência temporária para outro aplicativo + Exporte com a intenção certa + Salvar e compartilhar podem parecer semelhantes, mas resolvem problemas diferentes. Salvar cria um arquivo que você pode encontrar mais tarde; o compartilhamento envia um resultado gerado pelo Android para outro aplicativo e pode não deixar uma cópia local durável. + Use Salvar quando a saída precisar permanecer em uma pasta conhecida, fazer backup ou ser reutilizada posteriormente. + Use Compartilhar para mensagens rápidas, anexos de e-mail, postagens sociais ou envio do resultado para outro editor. + Salve primeiro quando o aplicativo receptor não for confiável ou quando for difícil recriar um compartilhamento com falha. + Tamanho e margens da página PDF + Escolha o tamanho do papel, o comportamento adequado e o espaço para respirar antes de criar um documento + Torne as páginas de imagens imprimíveis e legíveis + As imagens podem se tornar páginas PDF estranhas quando seu formato não corresponde ao tamanho de papel escolhido. As margens, o modo de ajuste e a orientação da página decidem se o conteúdo é cortado, pequeno, esticado ou confortável de ler. + Use um tamanho de papel real quando o PDF puder ser impresso ou enviado para um formulário. + Use margens para digitalizações e capturas de tela para que o texto não fique encostado na borda da página. + Visualize páginas retrato e paisagem juntas quando as imagens de origem tiverem orientação mista. + Limpar verificações + Melhore as bordas, sombras, contraste, rotação e legibilidade do papel antes do PDF ou OCR + Prepare as páginas antes de arquivar + Uma digitalização pode ser tecnicamente capturada, mas ainda assim difícil de ler. Pequenas etapas de limpeza antes da exportação de PDF geralmente são mais importantes do que configurações de compactação, especialmente para recibos, notas manuscritas e páginas com pouca luz. + Corrija a perspectiva e corte bordas da mesa, dedos, sombras e desordem de fundo. + Aumente o contraste com cuidado para que o texto fique mais claro sem destruir carimbos, assinaturas ou marcas de lápis. + Execute o OCR somente depois que a página estiver na posição vertical e legível e, em seguida, verifique os números importantes manualmente. + Compactar, escalar cinza ou reparar PDFs + Use operações de PDF de um arquivo para cópias de documentos mais leves, imprimíveis ou reconstruídas + Escolha a operação de limpeza de PDF + PDF Tools inclui operações separadas para compactação, conversão em escala de cinza e reparo. A compactação e a escala de cinza funcionam principalmente por meio de imagens incorporadas, enquanto o reparo reconstrói a estrutura do PDF; nada disso deve ser tratado como uma solução garantida para todos os documentos. + Use Compactar PDF quando o documento contiver imagens e o tamanho do arquivo for importante para compartilhamento. + Use Escala de cinza quando o documento for mais fácil de imprimir ou não precisar de imagens coloridas. + Use Reparar PDF em uma cópia quando um documento não abre ou danifica a estrutura interna e, em seguida, verifique o arquivo reconstruído. + Extraia imagens de PDFs + Recupere imagens PDF incorporadas e empacote os resultados extraídos em um arquivo + Salve imagens de origem de documentos + Extrair Imagens verifica o PDF em busca de objetos de imagem incorporados, ignora duplicatas sempre que possível e armazena imagens recuperadas em um arquivo ZIP gerado. Ele extrai apenas imagens que estão realmente incorporadas no PDF, não texto, desenhos vetoriais ou todas as páginas visíveis como uma captura de tela. + Use Extrair imagens quando precisar de imagens armazenadas em um PDF, como fotos de um catálogo ou ativos digitalizados. + Use PDF para imagens ou extração de páginas quando precisar de páginas inteiras, incluindo texto e layout. + Se a ferramenta não reportar imagens incorporadas, a página pode ter conteúdo de texto/vetor ou as imagens podem não ser armazenadas como objetos extraíveis. + Metadados, nivelamento e saída de impressão + Edite propriedades de documentos, rasterize páginas ou prepare layouts de impressão personalizados intencionalmente + Escolha a ação do documento final + Metadados de PDF, nivelamento e preparação de impressão resolvem diferentes problemas de estágio final. Os metadados controlam as propriedades do documento, o Flatten PDF rasteriza as páginas em uma saída menos editável e o Print PDF prepara um novo layout com controles de tamanho de página, orientação, margem e páginas por folha. + Use metadados quando o autor, o título, as palavras-chave, o produtor ou a limpeza de privacidade forem importantes. + Use Flatten PDF quando o conteúdo visível da página se tornar mais difícil de editar como objetos PDF separados. + Use Imprimir PDF quando precisar de tamanho de página personalizado, orientação, margens ou várias páginas por folha. + Preparar imagens para OCR + Use corte, rotação, contraste, redução de ruído e escala antes de solicitar ao OCR para ler o texto + Dê pixels mais limpos ao OCR + Os erros de OCR geralmente vêm da imagem de entrada e não do mecanismo de OCR. Páginas tortas, baixo contraste, desfoque, sombras, texto minúsculo e fundos mistos reduzem a qualidade do reconhecimento. + Corte na área de texto e gire a imagem para que as linhas fiquem horizontais antes do reconhecimento. + Aumente o contraste ou converta para preto e branco mais limpo somente quando isso facilitar a leitura das letras. + Redimensione moderadamente o texto minúsculo para cima, mas evite nitidez agressiva que cria bordas falsas. + Verifique o conteúdo do QR + Revise links, credenciais de Wi-Fi, contatos e ações antes de confiar em um código + Trate o texto decodificado como entrada não confiável + Um código QR pode ocultar um URL, cartão de contato, senha de Wi-Fi, evento de calendário, número de telefone ou texto simples. O rótulo visível próximo ao código pode não corresponder à carga real, portanto, revise o conteúdo decodificado antes de agir. + Inspecione o URL decodificado completo antes de abri-lo, especialmente domínios abreviados ou desconhecidos. + Tenha cuidado com códigos de Wi-Fi, contato, calendário, telefone e SMS, pois eles podem desencadear ações confidenciais. + Copie o conteúdo primeiro quando precisar verificá-lo em outro lugar, em vez de abri-lo imediatamente. + Gerar códigos QR + Crie códigos a partir de texto simples, URLs, Wi-Fi, contatos, e-mail, telefone, SMS e dados de localização + Crie a carga QR certa + A tela QR pode gerar códigos a partir do conteúdo inserido, bem como digitalizar os existentes. Os tipos QR estruturados ajudam a codificar dados em um formato que outros aplicativos entendem, como detalhes de login de Wi-Fi, cartões de contato, campos de e-mail, números de telefone, conteúdo de SMS, URLs ou coordenadas geográficas. + Escolha texto simples para notas simples ou use um tipo estruturado quando o código deve abrir como Wi-Fi, contato, URL, e-mail, telefone, SMS ou dados de localização. + Verifique todos os campos antes de compartilhar o código gerado porque a visualização visível reflete apenas a carga inserida. + Teste o código QR salvo com um scanner quando ele for impresso, publicado publicamente ou usado por outras pessoas. + Escolha um modo de soma de verificação + Calcule hashes de arquivos ou texto, compare um arquivo ou verifique vários arquivos em lote + Verifique o conteúdo, não a aparência + Checksum Tools possui guias separadas para hash de arquivo, hash de texto, comparação de um arquivo com um hash conhecido e comparação de lote. Uma soma de verificação prova a identidade byte por byte do algoritmo selecionado; isso não prova que duas imagens apenas pareçam semelhantes. + Use Calcular para arquivos e Hash de texto para dados de texto digitados ou colados. + Use Comparar quando alguém fornecer uma soma de verificação esperada para um arquivo. + Use a comparação em lote quando muitos arquivos precisarem de hashes ou verificação em uma única passagem e mantenha o algoritmo selecionado consistente. + Privacidade da área de transferência + Use recursos automáticos da área de transferência sem expor acidentalmente dados privados copiados + Mantenha o conteúdo copiado intencionalmente + A automação da área de transferência é conveniente, mas o texto copiado pode conter senhas, links, endereços, tokens ou notas privadas. Trate a entrada baseada na área de transferência como um recurso de velocidade que deve corresponder aos seus hábitos e expectativas de privacidade. + Desative o uso automático da área de transferência se texto privado aparecer inesperadamente nas ferramentas. + Use o salvamento somente na área de transferência somente quando desejar deliberadamente que o resultado evite o armazenamento. + Limpe ou substitua a área de transferência após lidar com texto confidencial, dados QR ou cargas Base64. + Formatos de cores + Entenda os valores HEX, RGB, HSV, alfa e de cores copiadas antes de colar em outro lugar + Copie o formato que o destino espera + A mesma cor visível pode ser escrita de diversas maneiras. Uma ferramenta de design, campo CSS, valor de cor do Android ou editor de imagem pode esperar uma ordem, tratamento alfa ou modelo de cor diferente. + Use HEX ao colar em campos de web, design ou tema que exigem códigos de cores compactos. + Use RGB ou HSV quando precisar ajustar canais, brilho, saturação ou matiz manualmente. + Marque alfa separadamente quando a transparência for importante porque alguns destinos a ignoram ou usam uma ordem diferente. + Navegue pela biblioteca de cores + Pesquise cores nomeadas por nome ou HEX e mantenha as favoritas perto do topo + Encontre cores nomeadas reutilizáveis + A Biblioteca de Cores carrega uma coleção de cores nomeada, classifica-a por nome, filtra por nome de cor ou valor HEX e armazena cores favoritas para que entradas importantes fiquem mais fáceis de alcançar. É útil quando você precisa de uma cor com nome real em vez de amostrar uma imagem. + Pesquise pelo nome de uma cor quando conhecer a família, como vermelho, azul, ardósia ou menta. + Pesquise por HEX quando você já tiver uma cor numérica e quiser encontrar entradas nomeadas correspondentes ou próximas. + Marque cores frequentes como favoritas para que elas avancem na biblioteca geral durante a navegação. + Use coleções de gradiente de malha + Baixe recursos de gradiente de malha disponíveis e compartilhe imagens selecionadas + Use recursos de gradiente de malha remota + Mesh Gradients pode carregar uma coleção de recursos remotos, baixar imagens de gradiente ausentes, mostrar o progresso do download e compartilhar gradientes selecionados. A disponibilidade depende do acesso à rede e do armazenamento remoto de recursos, portanto, uma lista vazia geralmente significa que os recursos ainda não estavam disponíveis. + Abra Gradientes de malha nas ferramentas de gradiente quando desejar imagens de gradiente de malha prontas. + Aguarde o carregamento do recurso ou o progresso do download antes de assumir que a coleção está vazia. + Selecione e compartilhe os gradientes necessários ou retorne ao Gradient Maker quando quiser criar um gradiente personalizado manualmente. + Resolução de ativos gerados + Escolha o tamanho da saída antes de gerar gradientes, ruídos, papéis de parede, arte semelhante a SVG ou texturas + Gere no tamanho que você precisa + As imagens geradas não possuem um original da câmera para recorrer. Se a saída for muito pequena, o dimensionamento posterior poderá suavizar padrões, alterar detalhes ou alterar a forma como o ativo é agrupado e compactado. + Defina primeiro as dimensões para o caso de uso final, como papel de parede, ícone, plano de fundo, impressão ou sobreposição. + Use tamanhos maiores para texturas que podem ser cortadas, ampliadas ou reutilizadas em vários layouts. + Exporte versões de teste antes de grandes resultados porque os detalhes do procedimento podem alterar o comportamento da compactação. + Exportar papéis de parede atuais + Recuperar tela inicial, bloqueio e papéis de parede integrados disponíveis no dispositivo + Salve ou compartilhe papéis de parede instalados + O Wallpapers Export lê os papéis de parede que o Android expõe por meio das APIs de papéis de parede do sistema. Dependendo do dispositivo, versão do Android, permissões e variante de construção, Home, Lock ou papéis de parede integrados podem estar disponíveis separadamente ou podem estar ausentes. + Abra Exportação de papéis de parede para carregar os papéis de parede que o sistema permite que o aplicativo leia. + Selecione as entradas disponíveis de Início, Bloqueio ou papel de parede integrado que deseja salvar, copiar ou compartilhar. + Se um papel de parede estiver faltando ou o recurso não estiver disponível, geralmente é uma limitação do sistema, permissão ou variante de construção, em vez de uma configuração de edição. + Acelerador de animação de cantos + Copiar cor como + HEX + nome + Modelo de fosqueamento retrato para recortes rápidos de pessoas com cabelos mais macios e manuseio de bordas. + Aprimoramento rápido em condições de pouca luz usando estimativa de curva Zero-DCE++. + Modelo de restauração de imagem Restormer para reduzir riscos de chuva e artefatos de clima úmido. + Modelo de documento sem distorções que prevê uma grade de coordenadas e remapeia páginas curvas em uma digitalização mais plana. + Escala de Duração do Movimento + Controla a velocidade da animação do aplicativo: 0 desativa o movimento, 1 é o padrão, valores maiores diminuem a velocidade das animações + Mostrar Ferramentas Recentes + Exibir acesso rápido às ferramentas usadas recentemente na tela principal + Estatísticas de uso + Explore aberturas de aplicativos, lançamentos de ferramentas e suas ferramentas mais usadas + O aplicativo abre + A ferramenta é aberta + Última ferramenta + Salvamentos bem-sucedidos + Sequência de atividades + Dados salvos + Formato superior + Ferramentas usadas + %1$d abre • %2$s + Ainda não há estatísticas de uso + Abra algumas ferramentas e elas aparecerão aqui automaticamente + Suas estatísticas de uso são armazenadas apenas localmente no seu dispositivo. ImageToolbox os usa apenas para mostrar sua atividade pessoal, ferramentas favoritas e informações de dados salvos + editor editar foto retocar ajustar + redimensionar converter escala resolução dimensões largura altura jpg jpeg png webp compactar + comprimir tamanho peso bytes mb kb reduzir qualidade otimizar + proporção de aspecto de corte de corte + filtro efeito correção de cor ajustar máscara lut + desenhar pincel lápis anotar marcação + cifrar criptografar descriptografar senha segredo + removedor de fundo apagar remover recorte alfa transparente + visualizar visualizador galeria inspecionar abrir + costurar mesclar combinar panorama captura de tela longa + url web download internet link imagem + selecionador conta-gotas amostra hex rgb cor + paleta cores amostras extrair dominante + privacidade de metadados exif remover localização GPS limpa + compare a diferença antes depois + limite redimensionar max min megapixels dimensões braçadeira + ferramentas de páginas de documentos pdf + texto ocr reconhecer extrair digitalização + malha de cor de fundo gradiente + sobreposição de carimbo de texto de logotipo de marca d\'água + animação gif animado converter quadros + apng animação animado png converter quadros + arquivo zip compactar pacote descompactar arquivos + jxl jpeg xl converter formato de imagem jpg + rastreamento de vetor svg converter xml + formato converter jpg jpeg png webp heic avif jxl bmp + documento scanner digitalizar papel perspectiva colheita + varredura de scanner de código de barras qr + empilhamento sobreposição média mediana astrofotografia + dividir peças de fatia de grade de telhas + conversão de cores hex rgb hsl hsv cmyk lab + webp converter formato de imagem animada + gerador de ruído grão de textura aleatória + layout de grade de colagem combinar + camadas de marcação anotar edição de sobreposição + codificação base64 decodificação de dados uri + hash de soma de verificação md5 sha1 sha256 verificar + fundo gradiente de malha + exif metadados editar gps data câmera + cortar peças de grade divididas + áudio capa arte do álbum metadados de música + fundo de exportação de papel de parede + personagens de arte de texto ascii + ai ml neural aprimora segmento sofisticado + paleta de materiais da biblioteca de cores denominada cores + estúdio de efeito de fragmento shader glsl + mesclar pdf combinar juntar + pdf dividir páginas separadas + pdf girar orientação de páginas + pdf reorganizar reordenar classificação de páginas + paginação de números de página pdf + pdf ocr texto reconhecer extrato pesquisável + texto do logotipo do carimbo da marca d\'água em pdf + desenho de assinatura em pdf + pdf proteger senha criptografar bloqueio + pdf desbloquear senha descriptografar remover proteção + compactação de pdf reduzir tamanho otimizar + pdf em escala de cinza preto branco monocromático + pdf reparar consertar recuperar quebrado + propriedades de metadados pdf exif título do autor + pdf remover excluir páginas + margens de corte de corte em pdf + pdf achatar anotações forma camadas + pdf extrair imagens fotos + compactação de arquivo zip pdf + impressora de impressão pdf + visualizador de visualização de pdf lido + imagens para pdf converter documento jpg jpeg png + pdf para páginas de imagens exportar jpg png + anotações em pdf comentários remover limpar + Variante Neutra + Erro + Sombra projetada + Altura do dente + Faixa de dentes horizontais + Faixa de dentes verticais + Borda superior + Borda Direita + Borda inferior + Margem Esquerda + Borda rasgada + Redefinir estatísticas de uso + A ferramenta é aberta, as estatísticas salvas, a sequência, os dados salvos e o formato superior serão redefinidos. As aberturas de aplicativos permanecerão inalteradas. + Áudio + Arquivo + Exportar perfis + Salvar perfil atual + Excluir perfil de exportação + Deseja excluir o perfil de exportação \\"%1$s\\"? + Desenho de borda + Mostrar uma borda fina ao redor da tela de desenho e apagamento + Ondulado + Elementos de UI arredondados com bordas suaves e onduladas + Colher + Entalhe + Cantos arredondados esculpidos para dentro + Cantos escalonados com corte duplo + Espaço reservado + Use {filename} para inserir o nome do arquivo original sem extensão + Sinalizador + Valor base + Valor do anel + Quantidade de raios + Largura do anel + Distorcer Perspectiva + Aparência e comportamento do Java + Cisalhamento + Ângulo X + e ângulo + Redimensionar saída + Gota d\'água + Comprimento de onda + Fase + Passa alta + Máscara colorida + Cromo + Dissolver + Suavidade + Opinião + Desfoque de lente + Entrada baixa + Entrada alta + Baixa produção + Alto rendimento + Efeitos de luz + Altura do impacto + Suavidade de colisão + Ondulação + Amplitude X + Amplitude Y + Comprimento de onda X + Comprimento de onda Y + Desfoque adaptativo + Geração de Textura + Gere várias texturas procedurais e salve-as como imagens + Tipo de textura + Viés + Tempo + Brilhar + Dispersão + Amostras + Coeficiente de ângulo + Coeficiente de gradiente + Tipo de grade + Poder de distância + Escala Y + Flocosidade + Tipo de base + Fator de turbulência + Dimensionamento + Iterações + Anéis + Metal escovado + Cáusticos + Celular + Tabuleiro de damas + fBm + Mármore + Plasma + Colcha + Madeira + aleatório + Quadrado + Hexagonal + Octogonal + Triangular + Ridged + Ruído VL + Ruído SC + Recente + Ainda não há filtros usados ​​recentemente + gerador textura gerador metal escovado cáusticos celular xadrez mármore plasma colcha madeira tijolo camuflagem célula nuvem rachadura tecido folhagem favo de mel gelo lava nebulosa papel ferrugem areia fumaça pedra terreno topografia ondulação água + Tijolo + Camuflar + Célula + Nuvem + Rachadura + Tecido + Folhagem + Favo de mel + Gelo + Lava + Nebulosa + Papel + Ferrugem + Areia + Fumaça + Pedra + Terreno + Topografia + Ondulação da água + Madeira Avançada + Largura da argamassa + Irregularidade + Bisel + Rugosidade + Primeiro limite + Segundo limite + Terceiro limite + Suavidade nas bordas + Largura da borda + Cobertura + Detalhe + Profundidade + Ramificação + Roscas horizontais + Roscas verticais + Fuzz + Veias + Iluminação + Largura da fissura + Geada + Fluxo + Crosta + Densidade da nuvem + Estrelas + Densidade de fibra + Resistência da fibra + Manchas + Corrosão + Pitting + Flocos + Frequência das dunas + Ângulo do vento + Ondulações + Fogos-fátuos + Escala de veia + Nível de água + Nível da montanha + Erosão + Nível de neve + Contagem de linhas + Espessura da linha + Sombreamento + Cor dos poros + Cor da argamassa + Cor tijolo escuro + Cor de tijolo + Cor de destaque + Cor escura + Cor da floresta + Cor da terra + Cor areia + Cor de fundo + Cor da célula + Cor da borda + Cor do céu + Cor da sombra + Cor clara + Cor da superfície + Cor da variação + Cor da rachadura + Cor de urdidura + Cor da trama + Cor escura da folha + Cor da folha + Cor da borda + Cor mel + Cor profunda + Cor do gelo + Cor geada + Cor da crosta + Lavar cor + Cor brilhante + Cor do espaço + Cor violeta + Cor azul + Cor básica + Cor da fibra + Cor da mancha + Cor metálica + Cor ferrugem escura + Cor ferrugem + Cor laranja + Cor de fumaça + Cor da veia + Cor da planície + Cor da água + Cor da rocha + Cor da neve + Cor baixa + Cor alta + Cor da linha + Cor rasa + Grama + Sujeira + Couro + Concreto + Asfalto + Musgo + Fogo + aurora + Mancha de óleo + Aquarela + Fluxo abstrato + Opala + Aço Damasco + Raio + Veludo + Marmoreio de tinta + Folha holográfica + Bioluminescência + Vórtice Cósmico + Lâmpada de lava + Horizonte de eventos + Flor Fractal + Túnel Cromático + Eclipse Corona + Atrator Estranho + Coroa Ferrofluida + Supernova + Íris + Pena de pavão + Concha do Nautilus + Planeta Anelado + Densidade da lâmina + Comprimento da lâmina + Vento + Irregularidade + Aglomerados + Umidade + Seixos + Rugas + Poros + Agregar + Rachaduras + Alcatrão + Vestir + Manchas + Fibras + Frequência da chama + Fumaça + Intensidade + Fitas + Bandas + Iridescência + Escuridão + Floresce + Pigmento + Bordas + Papel + Difusão + Simetria + Nitidez + Jogo de cores + Leitoso + Camadas + Dobrável + polonês + Galhos + Direção + Brilho + Dobras + Penas + Balanço de tinta + Espectro + Rugas + Difração + Braços + Torção + Brilho central + Bolhas + Inclinação do disco + Tamanho do horizonte + Largura do disco + Lentes + Pétalas + Enrolar + Filigrana + Facetas + Curvatura + Tamanho da lua + Tamanho da coroa + Raios + Anel de diamante + Lóbulos + Densidade de órbita + Grossura + Espinhos + Comprimento do pico + Tamanho do corpo + Metálico + Raio de choque + Largura da casca + Jogado fora + Tamanho da pupila + Tamanho da íris + Variação de cor + Lanterna + Tamanho dos olhos + Densidade de farpa + Voltas + Câmaras + Abertura + Cumes + Perolescência + Tamanho do planeta + Inclinação do anel + Largura do anel + Atmosfera + Cor de sujeira + Cor de grama escura + Cor da grama + Cor da ponta + Cor terra escura + Cor seca + Cor seixo + Cor de couro + Cor concreta + Cor alcatrão + Cor asfáltica + Cor de pedra + Cor da poeira + Cor do solo + Cor musgo escuro + Cor musgo + Cor vermelha + Cor central + Cor verde + Cor ciano + Cor magenta + Cor dourada + Cor do papel + Cor do pigmento + Cor secundária + Primeira cor + Segunda cor + Cor rosa + Cor aço escuro + Cor de aço + Cor aço claro + Cor óxido + Cor do halo + Cor do parafuso + Cor veludo + Cor brilhante + Cor de tinta azul + Cor de tinta vermelha + Cor de tinta escura + Cor prateada + Cor amarela + Cor do tecido + Cor do disco + Cor quente + Cor da lente + Cor externa + Cor interna + Cor da coroa + Cor fria + Cor quente + Cor da nuvem + Cor da chama + Cor da pena + Cor da casca + Cor pérola + Cor do planeta + Cor do anel + Exclua os arquivos originais após salvar + Somente arquivos de origem processados ​​com sucesso serão excluídos + Arquivos originais excluídos: %1$d + Falha ao excluir arquivos originais: %1$d + Arquivos originais excluídos: %1$d, falha: %2$d + Gestos de planilha + Permitir arrastar folhas de opções expandidas + Subamostragem de croma + Profundidade de bits + Sem perdas + %1$d bits + Renomear lote + Renomeie vários arquivos usando padrões de nome de arquivo + renomear arquivos em lote nome do arquivo padrão sequência data extensão + Escolha arquivos para renomear + Padrão de nome de arquivo + Renomear + Fonte de data + Data EXIF ​​tirada + Data de modificação do arquivo + Data de criação do arquivo + Data atual + Data manual + Data manual + Hora manual + A data selecionada não está disponível para alguns arquivos. A data atual será usada para eles. + Insira um padrão de nome de arquivo + O padrão produz um nome de arquivo inválido ou muito longo + O padrão produz nomes de arquivos duplicados + Todos os nomes de arquivos já correspondem ao padrão + Este padrão usa tokens que não estão disponíveis para renomeação em lote + O nome permanecerá o mesmo + Arquivos renomeados com sucesso + Alguns arquivos não puderam ser renomeados + A permissão não foi concedida + Usa o nome do arquivo original sem extensão, ajudando a manter intacta a identificação da fonte. Também suporta fatiamento com \\o{start:end}, transliteração com \\o{t} e substituição por \\o{s/old/new/}. + Contador de incremento automático. Suporta formatação personalizada: \\c{padding} (por exemplo, \\c{3} -&gt; 001), \\c{start:step} ou \\c{start:step:padding}. + O nome da pasta pai onde o arquivo original estava localizado. + O tamanho do arquivo original. Suporta unidades: \\z{b}, \\z{kb}, \\z{mb} ou apenas \\z para formato legível por humanos. + Gera um identificador universalmente exclusivo (UUID) aleatório para garantir a exclusividade do nome do arquivo. + A renomeação de arquivos não pode ser desfeita. Certifique-se de que os novos nomes estejam corretos antes de continuar. + Confirmar renomeação + Configurações do menu lateral + Escala do menu + Menu alfa + Balanço de branco automático + Recorte + Verificando marcas d’água ocultas + Armazenar + Limite de cache + Limpe o cache automaticamente quando ele ultrapassar esse tamanho + Intervalo de limpeza + Com que frequência verificar se o cache deve ser limpo + No lançamento do aplicativo + 1 dia + 1 semana + 1 mês + Limpe o cache do aplicativo automaticamente de acordo com o limite e intervalo selecionados + Área de cultivo móvel + Permite mover toda a área de corte arrastando dentro dela + Mesclar GIF + Combine vários clipes GIF em uma animação + Ordem dos clipes GIF + Reverter + Jogue como invertido + Bumerangue + Jogue para frente e depois para trás + Normalizar o tamanho do quadro + Dimensione os quadros para caber no clipe maior; desligue para preservar sua escala original + Atraso entre clipes (ms) + Pasta + Selecione todas as imagens de uma pasta e suas subpastas + Localizador de duplicatas + Encontre cópias exatas e imagens visualmente semelhantes + localizador de duplicatas imagens semelhantes cópias exatas limpeza de fotos armazenamento dHash SHA-256 + Escolha imagens para encontrar duplicatas + Sensibilidade de similaridade + Cópias exatas + Imagens semelhantes + Selecione todas as duplicatas exatas + Selecione tudo, exceto recomendado + Nenhuma duplicata encontrada + Tente adicionar mais imagens ou aumentar a sensibilidade de similaridade + Não foi possível ler %1$d imagens + %1$s • %2$s recuperável + %1$d grupos • %2$s recuperáveis + %1$d selecionado • %2$s + Isto não pode ser desfeito. O Android pode solicitar que você confirme a exclusão em uma caixa de diálogo do sistema. + Não encontrado + Mova para começar + Mover para o fim + \ No newline at end of file diff --git a/core/resources/src/main/res/values-ro/strings.xml b/core/resources/src/main/res/values-ro/strings.xml new file mode 100644 index 0000000..6be86b2 --- /dev/null +++ b/core/resources/src/main/res/values-ro/strings.xml @@ -0,0 +1,3741 @@ + + + + + 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 + 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 + 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. + 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. + 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 + Distorsiuni în funcție de conținut + Nu este suficientă memorie pentru a finaliza această acțiune. Încercați să utilizați o imagine mai mică, să închideți alte aplicații sau să reporniți aplicația. + Muncitori paraleli + Aceste imagini nu au fost salvate deoarece fișierele convertite ar fi mai mari decât cele originale. Verificați această listă înainte de a șterge imaginile originale. + Fișiere ignorate: %1$s + Jurnalele aplicației + Vizualizați jurnalele aplicației pentru a identifica problemele + Favorite în instrumente grupate + Adaugă favorite ca filă atunci când instrumentele sunt grupate după tip + Afișează preferatul ca ultimul + Mută ​​fila instrumente preferate până la sfârșit + Spațiere orizontală + Spațiere verticală + Energie înapoi + Utilizați o hartă simplă a energiei cu magnitudinea gradientului în loc de algoritmul de energie directă + Îndepărtați zona mascată + Cusăturile vor trece prin regiunea mascată, eliminând doar zona selectată în loc să o protejeze + VHS NTSC + Setări NTSC avansate + Reglare analogică suplimentară pentru VHS și artefacte de difuzare mai puternice + Sângerare cromatică + Uzura benzii + Urmărire + Frotiu Luma + Sună + Zăpadă + Utilizați câmpul + Tip filtru + Filtru lumina de intrare + Intrare chroma lowpass + Demodularea cromatică + Schimbarea de fază + Decalaj de fază + Schimbarea capului + Înălțimea de comutare a capului + Decalaj de comutare a capului + Schimbarea capului + Poziția la mijloc + Jitter la mijlocul liniei + Urmărirea înălțimii zgomotului + Val de urmărire + Urmărirea zăpezii + Urmărirea anizotropiei zăpezii + Zgomot de urmărire + Urmărirea intensității zgomotului + Zgomot compus + Frecvența compozită a zgomotului + Intensitatea zgomotului compus + Detaliu de zgomot compus + Frecvența de apel + Puterea de sonerie + Zgomot Luma + Frecvența zgomotului Luma + Intensitatea zgomotului Luma + Detaliu zgomot Luma + Zgomot cromatic + Frecvența zgomotului cromatic + Intensitatea zgomotului cromatic + Detaliu zgomot cromatic + Intensitatea zăpezii + Anizotropia zăpezii + Zgomot de fază cromatică + Eroare de fază cromatică + Întârzierea cromatică orizontală + Întârzierea cromatică verticală + Viteza benzii VHS + Pierderea cromatică VHS + VHS ascuți intensitatea + Frecvența de ascuțire a VHS + Intensitatea undei de margine VHS + Viteza undei de margine VHS + Frecvența undelor de margine VHS + Detaliu VHS edge wave + Ieșire chroma lowpass + Scară orizontală + Scară verticală + Factorul de scară X + Factorul de scară Y + Detectează filigranele comune ale imaginii și le pictează cu LaMa. Descărcă automat modelele de detectare și de inpainting + Deuteranopia + Extinde imaginea + Eliminator de fundal bazat pe segmentarea obiectelor folosind YOLO v11 Segmentation + Arată unghiul liniei + Afișează rotația curentă a liniei în grade în timpul desenului + Emoji animate + Afișați emoji-urile disponibile ca animații + Salvați în dosarul original + Salvați fișierele noi lângă fișierul original în loc de folderul selectat + Filigran PDF + Nu este suficientă memorie + Imaginea este probabil prea mare pentru a fi procesată pe acest dispozitiv sau sistemul a rămas fără memorie disponibilă. Încercați să reduceți rezoluția imaginii, să închideți alte aplicații sau să alegeți un fișier mai mic. + Meniul Depanare + Meniu pentru a testa funcțiile aplicației, acesta nu este destinat să apară în versiunea de producție + Furnizor + PaddleOCR necesită modele ONNX suplimentare pe dispozitivul dvs. Doriți să descărcați %1$s date? + Universal + coreean + latin + slava de est + thailandez + greacă + engleză + chirilic + arabic + Devanagari + tamil + Telugu + Shader + Shader presetat + Nu a fost selectat niciun shader + Shader Studio + Creați, editați, validați, importați și exportați shadere de fragmente personalizate + Nuanțe salvate + Deschideți shaderele salvate, duplicați, exportați, partajați sau ștergeți + Shader nou + Fișier Shader + .itshader JSON + %1$d parametri + Sursa de umbrire + Scrieți numai corpul lui void main(). Utilizați textureCoordinate pentru coordonatele UV și inputImageTexture ca eșantionare sursă. + Funcții + Funcții de ajutor opționale, constante și structuri inserate înainte de void main(). Uniformele sunt generate din parametri. + Adăugați uniforme aici când shader-ul are nevoie de valori editabile. + Resetați shaderul + Schița actuală a shaderului va fi ștearsă + Shader nevalid + Versiunea shader neacceptată %1$d. Versiunea acceptată: %2$d. + Numele shaderului nu trebuie să fie necompletat. + Sursa shader nu trebuie să fie goală. + Sursa shader conține caractere neacceptate: %1$s. Utilizați numai sursa ASCII GLSL. + Parametrul \\\"%1$s\\\" este declarat de mai multe ori. + Numele parametrilor nu trebuie să fie necompletate. + Parametrul „%1$s” folosește un nume GPUImage rezervat. + Parametrul „%1$s” trebuie să fie un identificator GLSL valid. + Parametrul \\\"%1$s\\\" are %2$s tip de valoare \\\"%3$s\\\", așteptat \\\"%4$s\\\". + Parametrul \\\"%1$s\\\" nu poate defini min sau maxim pentru valorile bool. + Parametrul „%1$s” are min mai mare decât max. + Parametrul „%1$s” prestabilit este mai mic decât min. + Parametrul „%1$s” prestabilit este mai mare decât max. + Shader trebuie să declare „uniform sampler2D %1$s;. + Shader trebuie să declare „Vec2 variabil %1$s;. + Shader trebuie să definească „void main()”. + Shader trebuie să scrie o culoare în gl_FragColor. + ShaderToy mainImage nu sunt acceptate. Utilizați contractul principal () nul al lui GPUImage. + Uniforma animată ShaderToy \\\"iTime\\\" nu este acceptată. + Uniforma animată ShaderToy „iFrame” nu este acceptată. + Uniforma ShaderToy \\\"iResolution\\\" nu este acceptată. + Texturile ShaderToy iChannel nu sunt acceptate. + Texturile externe nu sunt acceptate. + Extensiile de texturi externe nu sunt acceptate. + Parametrii de shader Libretro nu sunt acceptați. + Este acceptată o singură textură de intrare. Eliminați „uniforma %1$s %2$s;. + Parametrul \\\"%1$s\\\" trebuie declarat ca \\\"uniform %2$s %1$s;\\\". + Parametrul \\\"%1$s\\\" este declarat ca \\\"uniform %2$s %1$s;\\\", așteptat \\\"uniform %3$s %1$s;\\\". + Fișierul Shader este gol. + Fișierul Shader trebuie să fie un obiect JSON .itshader cu câmpuri versiune, nume și shader. + Fișierul Shader nu este JSON valid sau nu se potrivește cu formatul .itshader. + Câmpul \\\"%1$s\\\" este obligatoriu. + %1$s este obligatoriu. + %1$s \\\"%2$s\\\" nu este acceptat. Tipuri acceptate: %3$s. + %1$s este necesar pentru parametrii \\\"%2$s\\\". + %1$s trebuie să fie un număr finit. + %1$s trebuie să fie un număr întreg. + %1$s trebuie să fie adevărat sau fals. + %1$s trebuie să fie un șir de culoare, o matrice RGB/RGBA sau un obiect de culoare. + %1$s trebuie să utilizeze formatul #RRGGBB sau #RRGGBBAA. + %1$s trebuie să conțină canale de culoare hexazecimale valide. + %1$s trebuie să conțină 3 sau 4 canale de culoare. + %1$s trebuie să fie între 0 și 255. + %1$s trebuie să fie o matrice cu două numere sau un obiect cu x și y. + %1$s trebuie să conțină exact 2 numere. + Duplicat + Ștergeți întotdeauna EXIF + Eliminați datele EXIF ​​ale imaginii la salvare, chiar și atunci când un instrument solicită păstrarea metadatelor + Ajutor și sfaturi + %1$d tutoriale + Deschide instrumentul + Aflați principalele instrumente, opțiuni de export, fluxuri PDF, utilitare de culoare și remedieri pentru probleme comune + Noțiuni de bază + Alegeți instrumente, importați fișiere, salvați rezultatele și reutilizați setările mai rapid + Editarea imaginilor + Redimensionați, decupați, filtrați, ștergeți fundalurile, desenați și imaginile filigran + Fișiere și metadate + Convertiți formate, comparați rezultatele, protejați confidențialitatea și controlați numele fișierelor + PDF și documente + Creați PDF-uri, scanați documente, pagini OCR și pregătiți fișiere pentru partajare + Text, QR și date + Recunoașteți textul, scanați codurile, codificați Base64, verificați hashurile și pachetele fișierelor + Instrumente de culoare + Alegeți culori, construiți palete, explorați bibliotecile de culori și creați degrade + Instrumente creative + Generați SVG, artă ASCII, texturi de zgomot, shadere și imagini experimentale + Depanare + Remediați problemele de fișiere grele, transparență, partajare, importuri și raportare + Alegeți instrumentul potrivit + Utilizați categorii, căutare, favorite și acțiuni de distribuire pentru a începe din locul potrivit + Începeți de la cel mai bun punct de intrare + Image Toolbox are multe instrumente concentrate și multe dintre ele se suprapun la prima vedere. Începeți de la sarcina pe care doriți să o finalizați, apoi alegeți instrumentul care controlează cea mai importantă parte a rezultatului: dimensiune, format, metadate, text, pagini PDF, culori sau aspect. + Utilizați căutarea atunci când cunoașteți numele acțiunii, cum ar fi redimensionare, PDF, EXIF, OCR, QR sau culoare. + Deschideți o categorie atunci când știți doar tipul de sarcină, apoi fixați instrumentele frecvente ca favorite. + Când o altă aplicație partajează o imagine în Image Toolbox, alegeți instrumentul din fluxul foii de partajare. + Importați, salvați și partajați rezultatele + Înțelegeți fluxul obișnuit de la alegerea intrării la exportul fișierului editat + Urmați fluxul de editare comun + Majoritatea instrumentelor urmează același ritm: alegeți intrarea, ajustați opțiunile, previzualizați, apoi salvați sau distribuiți. Detaliul important este că salvarea creează un fișier de ieșire, în timp ce partajarea este de obicei cea mai bună pentru transferul rapid către o altă aplicație. + Alegeți un fișier pentru instrumentele cu o singură imagine sau mai multe fișiere pentru instrumentele lot, atunci când selectorul permite acest lucru. + Verificați controalele de previzualizare și de ieșire înainte de a salva, în special opțiunile de format, calitate și nume de fișier. + Utilizați partajarea pentru o transferare rapidă sau salvați atunci când aveți nevoie de o copie persistentă în folderul selectat. + Lucrați cu mai multe imagini simultan + Instrumentele batch sunt cele mai bune pentru sarcini repetate de redimensionare, conversie, compresie și denumire + Pregătiți un lot previzibil + Procesarea în lot este cea mai rapidă atunci când fiecare ieșire ar trebui să urmeze aceleași reguli. Este, de asemenea, cel mai ușor loc pentru a face o greșeală mare, așa că alegeți denumirea, calitatea, metadatele și comportamentul folderului înainte de a începe un export lung. + Selectați împreună toate imaginile asociate și păstrați ordinea dacă rezultatul depinde de secvență. + Setați regulile de redimensionare, format, calitate, metadate și nume de fișier o dată înainte de a începe exportul. + Rulați mai întâi un mic lot de test atunci când fișierele sursă sunt mari sau când formatul țintă este nou pentru dvs. + Editare rapidă unică + Utilizați editorul all-in-one atunci când aveți nevoie de mai multe modificări simple pe o singură imagine + Finalizați o imagine fără săriți cu instrumente + Editarea unică este utilă atunci când imaginea are nevoie de un lanț mic de modificări, mai degrabă decât de o operație specializată. De obicei, este mai rapid pentru curățarea rapidă, previzualizarea și exportul unei copii finale fără a construi un flux de lucru în lot. + Deschide Editare unică pentru o imagine care necesită ajustări comune, marcare, decupare, rotație sau modificări de export. + Aplicați editările în ordinea în care se modifică mai întâi cei mai puțini pixeli, cum ar fi decuparea înainte de filtre și filtre înainte de comprimarea finală. + Utilizați în schimb un instrument specializat atunci când aveți nevoie de procesare în lot, ținte exacte de dimensiunea fișierului, ieșire PDF, OCR sau curățarea metadatelor. + Utilizați presetări și setări + Salvați opțiunile repetate și reglați aplicația pentru fluxul de lucru preferat + Evitați să repetați aceeași configurație + Presetările și setările sunt utile atunci când exportați adesea același tip de fișier. O presetare bună transformă o listă de verificare manuală repetată într-o singură atingere, în timp ce setările globale definesc valorile implicite cu care încep noile instrumente. + Creați presetări pentru dimensiuni comune de ieșire, formate, valori de calitate sau modele de denumire. + Examinați setările pentru formatul implicit, calitatea, folderul de salvare, numele fișierului, selectorul și comportamentul interfeței. + Faceți o copie de rezervă a setărilor înainte de a reinstala aplicația sau de a trece pe alt dispozitiv. + Setați valori implicite utile + Alegeți formatul implicit, calitatea, modul de scară, tipul de redimensionare și spațiul de culoare o dată + Faceți ca instrumentele noi să înceapă mai aproape de obiectivul dvs + Valorile implicite nu sunt doar preferințe cosmetice. Aceștia decid ce format, calitatea, comportamentul de redimensionare, modul de scalare și spațiul de culoare apar mai întâi în multe instrumente, ceea ce ajută la evitarea reselectării acelorași opțiuni la fiecare export. + Setați formatul și calitatea imaginii implicite pentru a se potrivi cu destinația dvs. obișnuită, cum ar fi JPEG pentru fotografii sau PNG/WebP pentru alfa. + Ajustați tipul implicit de redimensionare și modul de scalare dacă exportați adesea pentru dimensiuni stricte, platforme sociale sau pagini de document. + Modificați valorile implicite ale spațiului de culoare numai atunci când fluxul dvs. de lucru necesită o gestionare consecventă a culorilor pe ecrane, imprimare sau editori externi. + Selector și lansator + Utilizați setările selectorului atunci când aplicația deschide prea multe dialoguri sau pornește în locul greșit + Faceți ca deschiderea fișierelor să se potrivească cu obiceiul dvs + Setările selectorului și lansatorului controlează dacă Image Toolbox pornește de la ecranul principal, un instrument sau un flux de selecție a fișierelor. Aceste opțiuni sunt utile în special atunci când intri de obicei din foaia de partajare Android sau când vrei ca aplicația să se simtă mai mult ca un lansator de instrumente. + Utilizați setările selectorului de fotografii dacă selectorul de sistem ascunde fișiere, arată prea multe articole recente sau gestionează prost fișierele din cloud. + Activați ignorarea alegerii numai pentru instrumentele în care introduceți de obicei din partajare sau cunoașteți deja fișierul sursă. + Examinați modul de lansare dacă doriți ca Image Toolbox să se comporte mai mult ca un selector de instrumente decât un ecran de pornire normal. + Sursa imaginii + Alegeți modul de selecție care funcționează cel mai bine cu galerii, manageri de fișiere și fișiere cloud + Alegeți fișierele din sursa potrivită + Setările pentru Sursa imaginii afectează modul în care aplicația solicită Android imagini. Cea mai bună alegere depinde dacă fișierele dvs. se află în galeria de sistem, într-un folder manager de fișiere, într-un furnizor de cloud sau în altă aplicație care partajează conținut temporar. + Utilizați selectorul implicit atunci când doriți cea mai integrată experiență în sistem pentru imaginile comune ale galeriei. + Comutați modul selector dacă albumele, folderele, fișierele cloud sau formatele nestandard nu apar acolo unde vă așteptați. + Dacă un fișier partajat dispare după ce părăsiți aplicația sursă, redeschideți-l printr-un selector persistent sau salvați mai întâi o copie locală. + Favorite și instrumente de partajare + Păstrați ecranul principal concentrat și ascundeți instrumentele care nu au sens în fluxurile de distribuire + Reduceți zgomotul listei de scule + Setările favorite și ascunse pentru partajare sunt utile atunci când utilizați doar câteva instrumente în fiecare zi. De asemenea, mențin fluxul de partajare practic, ascunzând instrumente care nu pot folosi tipul de fișier pe care îl trimite o altă aplicație. + Fixați instrumentele frecvente, astfel încât acestea să rămână ușor accesibile chiar și atunci când instrumentele sunt grupate pe categorii. + Ascundeți instrumentele din fluxurile de partajare atunci când sunt irelevante pentru fișierele care provin dintr-o altă aplicație. + Utilizați setările de comandă favorite dacă preferați favoritele la sfârșit sau grupate cu instrumente aferente. + Faceți backup pentru setări + Mută ​​în siguranță setările, preferințele și comportamentul aplicației la o altă instalare + Păstrați-vă configurația portabilă + Backup and Restore este cel mai util după ce ați reglat presetările, folderele, regulile de nume de fișiere, ordinea instrumentelor și preferințele vizuale. O copie de rezervă vă permite să experimentați cu setările sau să reinstalați aplicația fără a vă reconstrui fluxul de lucru din memorie. + Creați o copie de rezervă după modificarea presetărilor, comportamentul numelui fișierelor, formatele implicite sau aranjarea instrumentelor. + Stocați copia de rezervă undeva în afara dosarului aplicației dacă intenționați să ștergeți datele, să reinstalați sau să mutați dispozitive. + Restabiliți setările înainte de a procesa loturi importante, astfel încât valorile implicite și comportamentul de salvare să se potrivească cu vechea configurație. + Redimensionați și convertiți imaginile + Schimbați dimensiunea, formatul, calitatea și metadatele pentru una sau mai multe imagini + Creați copii redimensionate + Redimensionare și conversie este instrumentul principal pentru dimensiuni previzibile și fișiere de ieșire mai ușoare. Folosiți-l atunci când dimensiunea imaginii, formatul de ieșire și comportamentul metadatelor contează în același timp. + Alegeți una sau mai multe imagini, apoi alegeți dacă dimensiunile, scara sau dimensiunea fișierului contează cel mai mult. + Selectați formatul de ieșire și calitatea; utilizați PNG sau WebP atunci când transparența trebuie păstrată. + Previzualizați rezultatul, apoi salvați sau partajați copiile generate fără a modifica originalele. + Redimensionați după dimensiunea fișierului + Vizați o limită de dimensiune atunci când un site web, un messenger sau un formular respinge imagini grele + Respectați limite stricte de încărcare + Redimensionarea după dimensiunea fișierului este mai bună decât ghicirea valorilor de calitate atunci când destinația acceptă numai fișiere sub o anumită limită. Instrumentul poate ajusta compresia și dimensiunile pentru a ajunge la țintă în timp ce încearcă să păstreze rezultatul utilizabil. + Introduceți dimensiunea țintă puțin sub limita reală de încărcare pentru a lăsa loc pentru modificările metadatelor și ale furnizorului. + Preferați formatele cu pierderi pentru fotografii atunci când ținta este mică; PNG poate rămâne mare chiar și după redimensionare. + Inspectați fețele, textul și marginile după export, deoarece dimensiunile ținte agresive pot introduce artefacte vizibile. + Limitați redimensionarea + Reduceți numai imaginile care depășesc limitele maxime de lățime, înălțime sau fișiere + Evitați redimensionarea imaginilor care sunt deja bune + Limit Resize este util pentru folderele mixte în care unele imagini sunt uriașe, iar altele sunt deja pregătite. În loc să forțeze fiecare fișier prin aceeași redimensionare, păstrează imaginile acceptabile mai aproape de calitatea lor originală. + Setați dimensiunile sau limitele maxime în funcție de destinație, mai degrabă decât să redimensionați orbește fiecare fișier. + Activați comportamentul de stil skip-if-mai mare atunci când doriți să evitați ieșirile care devin mai grele decât sursele. + Utilizați acest lucru pentru loturi de la diferite camere, capturi de ecran sau descărcări în care dimensiunile sursei variază foarte mult. + Decupați, rotiți și îndreptați + Încadrați zona importantă înainte de a exporta sau de a utiliza imaginea în alte instrumente + Curățați compoziția + Decuparea mai întâi face filtrele ulterioare, paginile OCR, PDF și partajarea rezultatelor mai curate. + Deschideți Decupare și alegeți imaginea pe care doriți să o ajustați. + Utilizați decuparea gratuită sau un raport de aspect atunci când rezultatul trebuie să se potrivească cu o postare, un document sau un avatar. + Rotiți sau răsturnați înainte de a salva, astfel încât instrumentele ulterioare să primească imaginea corectată. + Aplicați filtre și ajustări + Creați o stivă de filtre editabilă și previzualizați rezultatul înainte de export + Reglați aspectul imaginii + Filtrele sunt utile pentru corecții rapide, rezultate stilizate și pregătirea imaginilor pentru OCR sau imprimare. Micile modificări ale contrastului, clarității și luminozității pot conta mai mult decât efectele grele atunci când imaginea conține text sau detalii fine. + Adăugați filtre unul câte unul și urmăriți previzualizarea după fiecare modificare. + Reglați luminozitatea, contrastul, claritatea, neclaritatea, culoarea sau măștile în funcție de sarcină. + Exportați numai atunci când previzualizarea se potrivește cu dispozitivul, documentul sau platforma socială țintă. + Previzualizează mai întâi + Inspectați imaginile înainte de a alege un instrument mai greu de editare, conversie sau curățare + Verificați ce ați primit de fapt + Previzualizarea imaginii este utilă atunci când un fișier provine din chat, stocare în cloud sau altă aplicație și nu sunteți sigur ce conține. Verificarea dimensiunilor, transparenței, orientării și calității vizuale vă ajută mai întâi să alegeți instrumentul de urmărire corect. + Deschideți Imagine Preview atunci când trebuie să inspectați mai multe imagini înainte de a decide ce să faceți cu ele. + Căutați probleme de rotație, zone transparente, artefacte de compresie și dimensiuni neașteptat de mari. + Treceți la Redimensionare, Decupare, Comparare, EXIF ​​sau Eliminare fundal numai după ce știți ce trebuie remediat. + Eliminați sau restaurați un fundal + Ștergeți fundalurile automat sau rafinați manual marginile cu instrumente de restaurare + Păstrați subiectul, eliminați restul + Eliminarea fundalului funcționează cel mai bine atunci când combinați selecția automată cu curățarea manuală atentă. Formatul final de export contează la fel de mult ca și masca, deoarece JPEG va aplatiza zonele transparente chiar dacă previzualizarea pare corectă. + Începeți cu eliminarea automată dacă subiectul este separat clar de fundal. + Măriți și comutați între ștergere și restaurare pentru a repara părul, colțurile, umbrele și detaliile mici. + Salvați ca PNG sau WebP când aveți nevoie de transparență în fișierul final. + Desenați, marcați și filigranați + Adăugați săgeți, note, evidențieri, semnături sau suprapuneri repetate de filigran + Adăugați informații vizibile + Instrumentele de marcare ajută la explicarea unei imagini, în timp ce filigranul ajută la marcarea sau protejarea fișierelor exportate. + Utilizați Draw atunci când aveți nevoie de note, forme, evidențieri sau adnotări rapide. + Utilizați Watermarking atunci când același text sau imagine trebuie să apară în mod consecvent pe ieșiri. + Verificați opacitatea, poziția și scala înainte de a exporta, astfel încât marcajul să fie vizibil, dar să nu distragă atenția. + Desenați valorile implicite + Setați lățimea liniei, culoarea, modul cale, blocarea orientării și lupa pentru o marcare mai rapidă + Faceți desenul să pară previzibil + Setările de desen sunt utile atunci când adnotați capturi de ecran, marcați documente sau semnați des imagini. Setările implicite reduc timpul de configurare, în timp ce lupa și blocarea orientării facilitează editările precise pe ecranele mici. + Setați lățimea implicită a liniei și desenați culoarea la valorile pe care le utilizați cel mai mult pentru săgeți, evidențieri sau semnături. + Utilizați modul cale implicită atunci când desenați de obicei același tip de linii, forme sau marcaje. + Activați lupa sau blocarea orientării atunci când introducerea cu degetul face ca detaliile mici să fie greu de plasat cu precizie. + Aspecte cu mai multe imagini + Alegeți instrumentul potrivit pentru mai multe imagini înainte de a aranja capturi de ecran, scanări sau benzi de comparație + Utilizați instrumentul de aspect potrivit + Aceste instrumente sună similar, dar fiecare este reglat pentru un tip diferit de ieșire cu mai multe imagini. Alegerea mai întâi pe cea potrivită evită lupta împotriva controalelor de aspect care au fost concepute pentru un rezultat diferit. + Utilizați Collage Maker pentru machete proiectate cu mai multe imagini într-o singură compoziție. + Utilizați Imagine Stitching sau Stacking atunci când imaginile ar trebui să devină o bandă lungă verticală sau orizontală. + Utilizați Divizarea imaginii când o imagine mare trebuie să devină mai multe bucăți mai mici. + Convertiți formate de imagine + Creați JPEG, PNG, WebP, AVIF, JXL și alte copii fără a edita manual pixelii + Alegeți formatul pentru job + Diferitele formate sunt mai bune pentru fotografii, capturi de ecran, transparență, compresie sau compatibilitate. Conversia este cea mai sigură atunci când înțelegeți ce acceptă aplicația de destinație și dacă sursa conține alfa, animație sau metadate pe care doriți să le păstrați. + Utilizați JPEG pentru fotografii compatibile, PNG pentru imagini fără pierderi și alfa și WebP pentru partajare compactă. + Reglați calitatea atunci când formatul o acceptă; valorile mai mici reduc dimensiunea, dar pot adăuga artefacte. + Păstrați originalele până când verificați că fișierele convertite sunt deschise corect în aplicația țintă. + Verificați EXIF ​​și confidențialitate + Vizualizați, editați sau eliminați metadatele înainte de a partaja fotografii sensibile + Controlați datele de imagine ascunse + EXIF poate conține date despre cameră, date, locație, orientare și alte detalii care nu sunt vizibile în imagine. Merită verificat înainte de distribuirea publică, rapoartele de asistență, listele de pe piață sau orice fotografie care ar putea dezvălui un loc privat. + Deschideți instrumentele EXIF ​​înainte de a partaja fotografii care pot conține informații despre locație sau despre dispozitivul privat. + Eliminați câmpurile sensibile sau editați etichete incorecte, cum ar fi data, orientarea, autorul sau datele camerei. + Salvați o copie nouă și partajați acea copie în loc de originală atunci când confidențialitatea contează. + Curățare automată EXIF + Eliminați metadatele în mod prestabilit în timp ce decideți dacă datele ar trebui păstrate + Faceți confidențialitatea implicită + Grupul de setări EXIF ​​este util atunci când doriți ca curățarea confidențialității să aibă loc automat, în loc să-l amintiți pentru fiecare export. Păstrarea datei și orei este o decizie separată, deoarece datele pot fi utile pentru sortare, dar sensibile pentru partajare. + Activați EXIF ​​întotdeauna clar atunci când majoritatea exporturilor dvs. sunt destinate partajării publice sau semi-publice. + Activați Păstrarea datei și orei numai atunci când păstrarea timpului de captare este mai importantă decât eliminarea oricărei mărci temporale. + Utilizați editarea manuală EXIF ​​pentru fișiere unice în care trebuie să păstrați unele câmpuri și să eliminați altele. + Controlați numele fișierelor + Utilizați prefixe, sufixe, marcaje temporale, presetări, sume de verificare și numere de secvență + Faceți exporturile ușor de găsit + Un model bun de nume de fișier ajută la sortarea loturilor și previne suprascrierile accidentale. Setările numelor de fișiere sunt utile în special atunci când exporturile revin în folderul sursă, unde nume similare pot deveni rapid confuze. + Setați prefixul numelui de fișier, sufixul, marca temporală, numele original, informațiile prestabilite sau opțiunile de secvență în Setări. + Utilizați numere de secvență pentru loturile în care ordinea contează, cum ar fi paginile, cadrele sau colajele. + Activați suprascrierea numai atunci când sunteți sigur că înlocuirea fișierelor existente este sigură pentru folderul curent. + Suprascrie fișierele + Înlocuiți ieșirile cu nume care se potrivesc numai atunci când fluxul de lucru este distructiv în mod intenționat + Utilizați cu atenție modul de suprascriere + Suprascrierea fișierelor modifică modul în care sunt gestionate conflictele de denumire. Este util pentru exporturi repetate în același folder, dar poate înlocui și rezultatele anterioare dacă modelul de nume de fișier produce din nou același nume. + Activați suprascrierea numai pentru folderele în care se așteaptă înlocuirea fișierelor mai vechi generate și poate fi recuperată. + Păstrați suprascrierea dezactivată atunci când exportați originale, fișiere client, scanări unice sau orice altceva fără o copie de rezervă. + Combinați suprascrierea cu un model de nume de fișier deliberat, astfel încât numai ieșirile dorite să fie înlocuite. + Modele de nume de fișiere + Combinați numele originale, prefixele, sufixele, marcajele de timp, presetările, modul de scară și sumele de verificare + Creați nume care explică rezultatul + Setările modelului de nume de fișiere pot transforma fișierele exportate într-un istoric util a ceea ce li sa întâmplat. Acest lucru contează în loturi, deoarece vizualizarea folderului poate fi singurul loc în care puteți distinge dimensiunea originală, presetarea, formatul și ordinea de procesare. + Folosiți numele de fișier original, prefixul, sufixul și marcajul de timp atunci când aveți nevoie de nume care pot fi citite pentru sortarea manuală. + Adăugați informații despre presetare, modul de scalare, dimensiunea fișierului sau sumă de control atunci când ieșirile trebuie să documenteze modul în care au fost produse. + Evitați nume aleatorii pentru fluxurile de lucru în care fișierele trebuie să rămână asociate cu originalele sau cu ordinea paginilor. + Alegeți unde sunt salvate fișierele + Evitați pierderea exporturilor setând foldere, salvarea folderului original și locații de salvare unică + Păstrați rezultatele previzibile + Comportamentul de salvare contează cel mai mult atunci când procesați multe fișiere sau partajați fișiere de la furnizorii de cloud. Setările folderului decid dacă ieșirile se colectează într-un loc cunoscut, apar lângă originale sau merg temporar în altă parte pentru o anumită sarcină. + Setați un folder de salvare implicit dacă doriți întotdeauna exporturi într-un singur loc. + Utilizați salvarea în folderul original pentru fluxurile de lucru de curățare în care rezultatul ar trebui să rămână lângă fișierul sursă. + Utilizați locația de salvare unică atunci când o singură sarcină are nevoie de o destinație diferită, fără a vă schimba implicit. + Dosare de salvare unică + Trimiteți următoarele exporturi într-un folder temporar fără a vă schimba destinația normală + Păstrați locurile de muncă speciale separate + Locația de salvare unică este utilă pentru proiecte temporare, foldere client, sesiuni de curățare sau exporturi care nu ar trebui să se amestece cu folderul obișnuit Image Toolbox. Oferă o destinație de scurtă durată fără a rescrie configurația implicită. + Alegeți un folder unic înainte de a începe o sarcină care aparține în afara locației dvs. obișnuite de export. + Utilizați-l pentru proiecte scurte, foldere partajate sau loturi în care rezultatele trebuie să rămână grupate împreună. + Reveniți la folderul implicit după lucrare, astfel încât exporturile ulterioare să nu ajungă în mod neașteptat în locația temporară. + Echilibrează calitatea și dimensiunea fișierului + Înțelegeți când să schimbați dimensiunile, formatul sau calitatea în loc să ghiciți + Reduceți fișierele în mod intenționat + Dimensiunea fișierului depinde de dimensiunile imaginii, format, calitate, transparență și complexitatea conținutului. Dacă un fișier este încă prea greu, modificarea dimensiunilor ajută de obicei mai mult decât la scăderea în mod repetat a calității. + Redimensionați mai întâi dimensiunile când imaginea este mai mare decât poate afișa destinația. + Calitate mai scăzută numai pentru formatele cu pierderi și verificați textul, marginile, degradeurile și fețele după export. + Schimbați formatul atunci când modificările calității nu sunt suficiente, de exemplu WebP pentru partajare sau PNG pentru elemente transparente clare. + Lucrați cu formate animate + Utilizați instrumentele GIF, APNG, WebP și JXL atunci când un fișier are cadre în loc de un bitmap + Nu tratați fiecare imagine ca fiind fixă + Formatele animate au nevoie de instrumente care să înțeleagă cadrele, durata și compatibilitatea. + Utilizați instrumente GIF sau APNG atunci când aveți nevoie de operații la nivel de cadru pentru aceste formate. + Utilizați instrumentele WebP atunci când aveți nevoie de compresie modernă sau de ieșire WebP animată. + Previzualizează sincronizarea animației înainte de partajare, deoarece unele platforme convertesc sau aplatizează imaginile animate. + Comparați și previzualizați rezultatul + Inspectați modificările, dimensiunea fișierului și diferențele vizuale înainte de a păstra un export + Verificați înainte de a distribui + Instrumentele de comparare ajută la identificarea artefactelor de compresie, a culorilor greșite sau a modificărilor nedorite din timp. + Deschideți Compara când doriți să inspectați două versiuni una lângă alta. + Măriți la margini, text, degrade și regiuni transparente unde problemele sunt cel mai ușor de ratat. + Dacă rezultatul este prea greu sau neclar, reveniți la instrumentul anterior și ajustați calitatea sau formatul. + Utilizați centrul de instrumente PDF + Îmbinați, împărțiți, rotiți, eliminați pagini, comprimați, protejați, OCR și fișiere PDF cu filigran + Alegeți o operație PDF + Centrul PDF grupează acțiunile documentului în spatele unui singur punct de intrare, astfel încât să puteți alege operația exactă. Începeți de acolo când fișierul este deja un PDF și utilizați Imagini în PDF sau Document Scanner când sursa este încă un set de imagini. + Deschideți PDF Tools și alegeți operația care se potrivește cu obiectivul dvs. + Selectați PDF-ul sau imaginile sursă, apoi examinați ordinea paginilor, rotație, protecție sau opțiunile OCR. + Salvați PDF-ul generat și deschideți-l o dată pentru a confirma numărul de pagini, ordinea și lizibilitatea. + Transformați imaginile într-un PDF + Combinați scanări, capturi de ecran, chitanțe sau fotografii într-un singur document care poate fi partajat + Creați un document curat + Imaginile devin pagini PDF mai bune atunci când sunt decupate, ordonate și dimensionate în mod constant. Tratați lista de imagini ca pe o stivă de pagini: fiecare alegere de rotație, margine și dimensiune a paginii afectează cât de lizibil se simte documentul final. + Decupați sau rotiți mai întâi imaginile când marginile paginii, umbrele sau orientarea sunt incorecte. + Selectați imaginile în ordinea dorită a paginilor și ajustați dimensiunea paginii sau marginile dacă instrumentul le oferă. + Exportați PDF-ul, apoi verificați dacă toate paginile pot fi citite înainte de a-l trimite. + Scanați documente + Capturați pagini de hârtie și pregătiți-le pentru PDF, OCR sau partajare + Capturați pagini care pot fi citite + Scanarea documentelor funcționează cel mai bine cu pagini plate, iluminare bună și perspectivă corectată. O scanare curată înainte de exportul OCR sau PDF economisește timp, deoarece recunoașterea textului și compresia depind ambele de calitatea paginii. + Așezați documentul pe o suprafață contrastantă cu suficientă lumină și umbre minime. + Ajustați colțurile detectate sau decupați manual, astfel încât pagina să umple cadrul curat. + Exportați ca imagini sau PDF, apoi executați OCR dacă aveți nevoie de text selectabil. + Protejați sau deblocați fișierele PDF + Folosiți parolele cu atenție și păstrați o copie sursă editabilă atunci când este posibil + Gestionați accesul PDF în siguranță + Instrumentele de protecție sunt utile pentru partajarea controlată, dar parolele sunt ușor de pierdut și greu de recuperat. Protejați copiile pentru distribuire, păstrați separat fișierele sursă neprotejate și verificați rezultatul înainte de a șterge orice. + Protejați o copie a PDF-ului, nu singurul dvs. document sursă. + Păstrați parola într-un loc sigur înainte de a partaja fișierul protejat. + Utilizați deblocarea numai pentru fișierele pe care aveți permisiunea să le editați, apoi verificați că copia deblocată se deschide corect. + Organizați pagini PDF + Îmbinați, împărțiți, rearanjați, rotiți, decupați, eliminați pagini și adăugați numere de pagini în mod deliberat + Fixați structura documentului înainte de decorare + Instrumentele pentru pagini PDF sunt cel mai ușor de utilizat în aceeași ordine în care ați pregăti un document pe hârtie: colectați paginile, eliminați-le pe cele greșite, puneți-le în ordine, orientarea corectă, apoi adăugați detalii finale, cum ar fi numerele paginilor sau filigranele. + Utilizați mai întâi îmbinare sau imagini în PDF atunci când documentul este încă împărțit în mai multe fișiere. + Rearanjați, rotiți, decupați sau eliminați pagini înainte de a adăuga numere de pagină, semnături sau filigrane. + Previzualizează PDF-ul final după modificările structurale, deoarece o pagină lipsă sau rotită poate fi greu de observat mai târziu. + Adnotări PDF + Eliminați adnotările sau aplatizați-le atunci când spectatorii arată comentariile și marcajele diferit + Controlează ceea ce rămâne vizibil + Adnotările pot fi comentarii, evidențieri, desene, semne de formular sau alte obiecte PDF suplimentare. Unii vizualizatori le afișează diferit, astfel încât eliminarea sau aplatizarea lor poate face documentele partajate mai previzibile. + Eliminați adnotările atunci când comentariile, evidențierile sau semnele de recenzie nu ar trebui incluse în copia partajată. + Aplatizați atunci când semnele vizibile ar trebui să rămână pe pagină, dar nu se mai comportă ca niște adnotări editabile. + Păstrați o copie originală, deoarece eliminarea sau aplatizarea adnotărilor poate fi dificil de inversat. + Recunoașteți textul + Extrageți text din imagini cu motoare OCR și limbi selectabile + Obțineți rezultate OCR mai bune + Precizia OCR depinde de claritatea imaginii, datele de limbă, contrastul și orientarea paginii. Cele mai bune rezultate vin, de obicei, din pregătirea mai întâi a imaginii: tăiați zgomotul, rotiți în poziție verticală, creșteți lizibilitatea, apoi recunoașteți textul. + Decupați imaginea în zona de text și rotiți-o în poziție verticală înainte de recunoaștere. + Alegeți limba corectă și descărcați datele despre limbă dacă aplicația o solicită. + Copiați sau partajați textul recunoscut, apoi verificați manual numele, numerele și semnele de punctuație. + Alegeți datele în limba OCR + Îmbunătățiți recunoașterea prin potrivirea scripturilor, limbilor și modelelor OCR descărcate + Potriviți OCR cu textul + Limba OCR greșită poate face ca fotografiile bune să pară o recunoaștere proastă. Datele de limbă contează pentru scripturi, punctuație, numere și documente mixte, așa că alegeți limba care apare în imagine și nu limba interfeței de utilizare a telefonului. + Alegeți limba sau scriptul care apare în imagine, nu doar limba telefonului. + Descărcați datele lipsă înainte de a lucra offline sau de a procesa un lot. + Pentru documentele în diferite limbi, rulați mai întâi cea mai importantă limbă și verificați manual numele și numerele. + PDF-uri care pot fi căutate + Scrieți textul OCR înapoi în fișiere, astfel încât paginile scanate să poată fi căutate și copiate + Transformați scanările în documente care pot fi căutate + OCR poate face mai mult decât copia text în clipboard. Când scrieți text recunoscut într-un fișier sau PDF care poate fi căutat, imaginea paginii rămâne vizibilă, în timp ce textul devine mai ușor de căutat, selectat, indexat sau arhivat. + Utilizați rezultatul PDF care poate fi căutat pentru documentele scanate care ar trebui să arate în continuare ca paginile originale. + Utilizați scrierea în fișier atunci când aveți nevoie doar de text extras și nu vă pasă de imaginea paginii. + Verificați manual numerele, numele și tabelele importante deoarece textul OCR poate fi căutat, dar totuși imperfect. + Scanați coduri QR + Citiți conținutul QR de la intrarea camerei sau imaginile salvate atunci când sunt disponibile + Citiți codurile în siguranță + Codurile QR pot conține linkuri, date Wi-Fi, contacte, text sau alte încărcături utile. + Deschideți Scanează codul QR și păstrați codul clar, plat și complet în interiorul cadrului. + Examinați conținutul decodat înainte de a deschide link-uri sau de a copia date sensibile. + Dacă scanarea eșuează, îmbunătățiți iluminarea, decupați codul sau încercați o imagine sursă cu rezoluție mai mare. + Utilizați Base64 și sume de control + Codificați imaginile ca text, decodați textul înapoi în imagini și verificați integritatea fișierului + Deplasați-vă între fișiere și text + Base64 este utilă pentru încărcături mici de imagini, în timp ce sumele de verificare ajută la confirmarea faptului că fișierele nu s-au modificat. Aceste instrumente sunt cele mai utile în fluxurile de lucru tehnice, rapoartele de asistență, transferurile de fișiere și locurile în care fișierele binare trebuie să devină text temporar. + Utilizați instrumentele Base64 pentru a codifica o imagine mică atunci când un flux de lucru are nevoie de text în loc de un fișier binar. + Decodați Base64 numai din surse de încredere și verificați previzualizarea înainte de a salva. + Utilizați instrumentele de sumă de control atunci când trebuie să comparați fișierele exportate sau să confirmați o încărcare/descărcare. + Împachetați sau protejați datele + Utilizați instrumente ZIP și de criptare pentru gruparea fișierelor sau transformarea datelor text + Păstrați fișierele asociate împreună + Instrumentele de ambalare sunt utile atunci când mai multe ieșiri ar trebui să călătorească ca un singur fișier. De asemenea, sunt bune pentru a păstra împreună imaginile generate, PDF-urile, jurnalele și metadatele atunci când trebuie să trimiteți un set complet de rezultate. + Utilizați ZIP atunci când trebuie să trimiteți mai multe imagini sau documente exportate împreună. + Utilizați instrumentele de cifrare numai atunci când înțelegeți metoda selectată și puteți stoca în siguranță cheile necesare. + Testați arhiva creată sau textul transformat înainte de a șterge fișierele sursă. + Sume de control în nume + Utilizați nume de fișiere bazate pe sumă de control atunci când unicitatea și integritatea contează mai mult decât lizibilitatea + Denumiți fișierele după conținut + Numele de fișiere de sumă de control sunt utile atunci când exporturile pot avea nume vizibile duplicat sau când trebuie să dovediți că două fișiere sunt identice. Sunt mai puțin prietenoși pentru navigarea manuală, așa că folosiți-le pentru arhive tehnice și fluxuri de lucru de verificare. + Activați suma de control ca nume de fișier atunci când identitatea conținutului este mai importantă decât un titlu care poate fi citit de om. + Utilizați-l pentru arhive, deduplicare, exporturi repetabile sau fișiere care pot fi încărcate și descărcate din nou. + Asociați numele sumelor de verificare cu dosare sau metadate atunci când oamenii încă mai trebuie să înțeleagă ce reprezintă fișierul. + Alege culorile din imagini + Eșantionați pixeli, copiați coduri de culoare și reutilizați culorile în palete sau modele + Eșantionați culoarea exactă + Alegerea culorilor este utilă pentru potrivirea unui design, extragerea culorilor mărcii sau verificarea valorilor imaginii. Mișcarea contează deoarece antialiasing, umbrele și compresia pot face pixelii vecini să fie ușor diferiți de culoarea pe care o așteptați. + Deschideți Alegeți culoarea din imagine și alegeți o imagine sursă cu culoarea de care aveți nevoie. + Măriți înainte de a eșantiona detalii mici, astfel încât pixelii din apropiere să nu afecteze rezultatul. + Copiați culoarea în formatul cerut de destinație, cum ar fi HEX, RGB sau HSV. + Creați palete și utilizați biblioteca de culori + Extrage paletele, răsfoiește culorile salvate și păstrează sisteme vizuale consistente + Construiește o paletă reutilizabilă + Paletele ajută la menținerea coerentei vizuale a graficelor, filigranelor și desenelor exportate. Sunt utile în special atunci când adnotați în mod repetat capturi de ecran, pregătiți elemente de marcă sau aveți nevoie de aceleași culori în mai multe instrumente. + Generați o paletă dintr-o imagine sau adăugați manual culori atunci când știți deja valorile. + Denumiți sau organizați culorile importante pentru a le putea găsi din nou mai târziu. + Utilizați valorile copiate în Draw, filigran, gradient, SVG sau instrumente de proiectare externe. + Creați degrade + Creați imagini în gradient pentru fundaluri, substituenți și experimente vizuale + Controlați tranzițiile de culoare + Instrumentele de gradient sunt utile atunci când aveți nevoie de o imagine generată în loc să editați una existentă. Acestea pot deveni fundaluri curate, substituenți, imagini de fundal, măști sau simple active pentru instrumente de design externe. + Alegeți tipul de gradient și setați mai întâi culorile importante. + Reglați direcția, opririle, netezimea și dimensiunea de ieșire pentru cazul de utilizare țintă. + Exportați într-un format care se potrivește cu destinația, de obicei PNG pentru grafica generată curată. + Accesibilitatea culorilor + Folosiți culori dinamice, contrast și opțiuni de daltonism atunci când interfața de utilizare este greu de citit + Faceți culorile mai ușor de utilizat + Setările de culoare afectează confortul, lizibilitatea și cât de repede puteți scana comenzile. Dacă cipurile de instrumente, glisoarele sau stările selectate par greu de distins, opțiunile de tema și de accesibilitate pot fi mai utile decât simpla schimbare a modului întunecat. + Încercați culori dinamice când doriți ca aplicația să urmeze paleta actuală de imagini de fundal. + Măriți contrastul sau schimbați schema atunci când butoanele și suprafețele nu ies suficient în evidență. + Folosiți opțiunile de schema daltonism dacă unele stări sau accente sunt greu de distins. + Fundal alfa + Controlați culoarea de previzualizare sau de umplere utilizată în jurul PNG transparent, WebP și ieșiri similare + Înțelegeți previzualizările transparente + Culoarea de fundal pentru formatele alfa poate face imaginile transparente mai ușor de inspectat, dar este important să știți dacă modificați un comportament de previzualizare/umplere sau aplatizați rezultatul final. Acest lucru contează pentru autocolante, logo-uri și imaginile de fundal eliminate. + Utilizați mai întâi un format care acceptă alfa, cum ar fi PNG sau WebP, atunci când pixelii transparenți trebuie să supraviețuiască exportului. + Ajustați setările de culoare de fundal atunci când marginile transparente sunt greu de văzut pe suprafața aplicației. + Exportați un mic test și redeschideți-l într-o altă aplicație pentru a confirma dacă a fost salvată transparența sau completarea aleasă. + Alegerea modelului AI + Alegeți un model după tipul de imagine și defect, în loc să îl alegeți mai întâi pe cel mai mare + Potriviți modelul cu deteriorarea + AI Tools poate descărca sau importa diferite modele ONNX/ORT, iar fiecare model este instruit pentru un anumit tip de problemă. Un model pentru blocuri JPEG, curățarea liniilor anime, eliminarea zgomotului, eliminarea fundalului, colorarea sau scalarea poate produce rezultate mai proaste atunci când este utilizat pe un tip de imagine greșit. + Citiți descrierea modelului înainte de descărcare; alegeți modelul care denumește problema dvs. reală, cum ar fi compresia, zgomotul, semitonul, estomparea sau eliminarea fundalului. + Începeți cu un model mai mic sau mai specific când testați un nou tip de imagine, apoi comparați cu modele mai grele numai dacă rezultatul nu este suficient de bun. + Păstrați doar modelele pe care le utilizați cu adevărat, deoarece modelele descărcate și importate pot ocupa spațiu de stocare vizibil. + Înțelegeți familiile model + Numele modelelor sugerează adesea ținta lor: modelele în stil RealESRGAN la nivel superior, modelele SCUNet și FBCNN curăță zgomotul sau compresia, modelele de fundal creează măști, iar coloratoarele adaugă culoare intrării în tonuri de gri. Modelele personalizate importate pot fi puternice, dar ar trebui să se potrivească cu forma de intrare/ieșire așteptată și să utilizeze formatul ONNX sau ORT acceptat. + Folosiți aplicații de upscalare atunci când aveți nevoie de mai mulți pixeli, dezgomozători atunci când imaginea este granulată și dispozitive de îndepărtare a artefactelor atunci când blocurile de compresie sau benzile sunt problema principală. + Utilizați modele de fundal numai pentru separarea subiectelor; nu sunt la fel cu eliminarea generală a obiectelor sau îmbunătățirea imaginii. + Importați modele personalizate numai din surse în care aveți încredere și testați-le pe o imagine mică înainte de a utiliza fișiere importante. + Parametrii AI + Reglați dimensiunea bucăților, suprapunerea și lucrătorii paraleli pentru a echilibra calitatea, viteza și memoria + Echilibrează viteza cu stabilitatea + Dimensiunea fragmentelor controlează cât de mari sunt piesele de imagine înainte de a fi procesate de model. Suprapunerea combină bucățile învecinate pentru a ascunde chenarele. Lucrătorii în paralel pot accelera procesarea, dar fiecare lucrător are nevoie de memorie, astfel încât valorile mari pot face instabile imaginile mari pe telefoanele cu RAM limitată. + Folosiți bucăți mai mari pentru un context mai fluid atunci când dispozitivul gestionează modelul în mod fiabil și imaginea nu este uriașă. + Creșteți suprapunerea atunci când marginile bucăților devin vizibile, dar amintiți-vă că o suprapunere mai mare înseamnă mai multă muncă repetată. + Ridicați lucrători paraleli numai după un test stabil; dacă procesarea încetinește, încălzește dispozitivul sau se blochează, reduceți mai întâi lucrătorii. + Evitați artefactele în bucăți + Chunking permite aplicației să proceseze imagini care sunt mai mari decât le poate gestiona confortabil modelul simultan. Compensația este că fiecare țiglă are mai puțin context înconjurător, astfel încât suprapunerea redusă sau bucățile prea mici pot crea margini vizibile, modificări ale texturii sau detalii inconsecvente între zonele învecinate. + Măriți suprapunerea dacă vedeți chenare dreptunghiulare, modificări repetate ale texturii sau salturi de detalii între regiunile procesate. + Reduceți dimensiunea bucăților dacă procesul eșuează înainte de a produce rezultate, în special pe imagini mari sau modele grele. + Salvați un mic test de decupare înainte de a procesa imaginea completă, astfel încât să puteți compara rapid parametrii. + Stabilitatea AI + Dimensiunea fragmentelor, lucrătorii și rezoluția sursei mai mici atunci când procesarea AI se blochează sau se blochează + Recuperați-vă după sarcini grele de AI + Procesarea AI este unul dintre cele mai grele fluxuri de lucru din aplicație. Blocările, sesiunile eșuate, înghețarile sau repornirile bruște ale aplicației înseamnă, de obicei, că modelul, rezoluția sursei, dimensiunea fragmentelor sau numărul de lucrători paraleli este prea solicitant pentru starea actuală a dispozitivului. + Dacă aplicația se blochează sau nu reușește să deschidă o sesiune de model, reduceți lucrătorii paraleli și dimensiunea bucăților, apoi încercați din nou aceeași imagine. + Redimensionați imaginile foarte mari înainte de procesarea AI când obiectivul nu necesită rezoluția originală. + Închideți alte aplicații grele, utilizați un model mai mic sau procesați mai puține fișiere simultan când presiunea memoriei revine în continuare. + Diagnosticați defecțiunile modelului + O rulare AI eșuată nu înseamnă întotdeauna că imaginea este proastă. Fișierul model poate fi incomplet, neacceptat, prea mare pentru memorie sau nepotrivit cu operația. Când aplicația raportează o sesiune eșuată, tratați-o ca pe un semnal pentru a simplifica mai întâi modelul și parametrii. + Ștergeți și redescărcați un model dacă acesta eșuează imediat după descărcare sau se comportă ca și cum fișierul ar fi corupt. + Încercați un model descărcabil încorporat înainte de a da vina pe un model personalizat importat, deoarece modelele importate pot avea intrări incompatibile. + Utilizați jurnalele de aplicații după reproducerea eșecului dacă trebuie să raportați o eroare de blocare sau de sesiune. + Experimentați în Shader Studio + Utilizați efectele de umbrire GPU pentru procesarea avansată a imaginii și aspectul personalizat + Lucrați cu atenție cu shaders + Shader Studio este puternic, dar codul shader și parametrii necesită modificări mici, testabile. Tratați-l ca pe un instrument experimental: păstrați o linie de bază funcțională, schimbați câte un lucru și testați cu diferite dimensiuni de imagine înainte de a avea încredere într-o presetare. + Începeți de la un shader de lucru cunoscut sau un efect simplu înainte de a adăuga parametri. + Schimbați câte un parametru sau un bloc de cod la un moment dat și verificați previzualizarea pentru erori. + Exportați presetări numai după ce le-ați testat pe câteva dimensiuni diferite de imagine. + Realizați artă SVG sau ASCII + Generați elemente de tip vector sau imagini bazate pe text pentru rezultate creative + Alegeți tipul de ieșire a reclamei + Instrumentele SVG și ASCII servesc diferite ținte: grafică scalabilă sau redare în stil text. Ambele sunt conversii creative, așa că previzualizarea la dimensiunea finală este mai importantă decât evaluarea rezultatului dintr-o miniatură mică. + Utilizați SVG Maker atunci când rezultatul ar trebui să fie scalat în mod curat sau să fie reutilizat în fluxurile de lucru de proiectare. + Utilizați ASCII Art când doriți o reprezentare stilizată a unei imagini cu text. + Previzualizare la dimensiunea finală, deoarece detaliile mici pot dispărea în rezultatul generat. + Generați texturi de zgomot + Creați texturi procedurale pentru fundaluri, măști, suprapuneri sau experimente + Reglați ieșirea procedurală + Generarea de zgomot creează noi imagini din parametri, astfel încât modificările mici pot produce rezultate foarte diferite. Este util pentru texturi, măști, suprapuneri, substituenți sau pentru testarea compresiei cu modele detaliate. + Alegeți un tip de zgomot și o dimensiune de ieșire înainte de a regla detaliile fine. + Reglați scara, intensitatea, culorile sau semințele până când modelul se potrivește utilizării țintă. + Salvați rezultatele utile și notați parametrii dacă trebuie să recreați textura mai târziu. + Proiecte de markup + Utilizați fișierele de proiect atunci când adnotările trebuie să rămână editabile după închiderea aplicației + Păstrați editările stratificate reutilizabile + Fișierele de proiect de marcare sunt utile atunci când o captură de ecran, o diagramă sau o imagine cu instrucțiuni poate necesita modificări ulterioare. Fișierele PNG/JPEG exportate sunt imagini finale, în timp ce fișierele de proiect păstrează straturile editabile și starea de editare pentru ajustări viitoare. + Utilizați Straturi de marcare atunci când adnotările, înștiințările sau elementele de aspect ar trebui să rămână editabile. + Salvați sau redeschideți fișierul de proiect înainte de a exporta o imagine finală dacă vă așteptați la mai multe revizuiri. + Exportați o imagine normală numai atunci când sunteți gata să partajați o versiune finală aplatizată. + Criptați fișierele + Protejați fișierele cu o parolă și testați decriptarea înainte de a șterge orice copie sursă + Gestionați cu atenție ieșirile criptate + Instrumentele de cifrare pot proteja fișierele cu criptare bazată pe parolă, dar nu înlocuiesc reținerea cheii sau păstrarea copiilor de rezervă. Criptarea este utilă pentru transport și stocare, în timp ce ZIP este mai bună atunci când scopul principal este gruparea mai multor rezultate. + Criptați mai întâi o copie și păstrați originalul până când ați testat că decriptarea funcționează cu parola pe care ați stocat-o. + Utilizați un manager de parole sau un alt loc sigur pentru cheie; aplicația nu poate ghici o parolă pierdută pentru tine. + Partajați fișierele criptate numai cu persoane care au și parola și știu ce instrument sau metodă ar trebui să le decripteze. + Aranjați instrumentele + Grupați, comandați, căutați și fixați instrumentele astfel încât ecranul principal să se potrivească cu fluxul dvs. de lucru real + Faceți ecranul principal mai rapid + Setările de aranjare a instrumentelor merită ajustate odată ce știți ce instrumente folosiți cel mai mult. Gruparea ajută la explorare, ordinea personalizată ajută memoria musculară, iar favoritele mențin instrumentele zilnice accesibile chiar și atunci când lista completă devine mare. + Utilizați modul grupat în timp ce învățați aplicația sau când preferați instrumente organizate după tipul de activitate. + Treceți la comanda personalizată atunci când vă cunoașteți deja instrumentele frecvente și doriți mai puține defilări. + Utilizați setările de căutare și favoritele împreună pentru instrumentele rare pe care le amintiți după nume, dar nu doriți să fie vizibile tot timpul. + Gestionați fișiere mari + Evitați exporturile lente, presiunea memoriei și producția neașteptat de mare + Reduceți munca înainte de export + Imaginile foarte mari, loturile lungi și formatele de înaltă calitate pot necesita mai multă memorie și timp. Cea mai rapidă remediere este de obicei reducerea cantității de lucru înainte de operațiunea cea mai grea, fără a aștepta ca aceeași intrare supradimensionată să se termine. + Redimensionați imagini uriașe înainte de a aplica filtre grele, OCR sau conversie PDF. + Utilizați mai întâi un lot mai mic pentru a confirma setările, apoi procesați restul cu aceeași presetare. + Calitate mai scăzută sau modificarea formatului atunci când fișierul de ieșire este mai mare decât se aștepta. + Cache și previzualizări + Înțelegeți previzualizările generate, curățarea memoriei cache și utilizarea stocării după sesiuni intense + Mențineți stocarea și viteza echilibrate + Generarea previzualizării și memoria cache pot face navigarea repetată mai rapidă, dar sesiunile de editare intense pot lăsa date temporare în urmă. Setările de cache ajută atunci când aplicația pare lentă, spațiul de stocare limitat sau previzualizările par învechite după multe experimente. + Păstrarea previzualizărilor activate atunci când răsfoiți multe imagini este mai importantă decât minimizarea stocării temporare. + Goliți memoria cache după loturi mari, experimente eșuate sau sesiuni lungi cu multe fișiere de înaltă rezoluție. + Utilizați ștergerea automată a memoriei cache dacă preferați curățarea spațiului de stocare decât menținerea previzualizărilor la cald între lansări. + Ajustați comportamentul ecranului + Folosiți opțiunile pentru ecran complet, modul securizat, luminozitate și bara de sistem pentru o muncă concentrată + Faceți ca interfața să se potrivească situației + Setările ecranului vă ajută atunci când editați în peisaj, prezentați imagini private sau aveți nevoie de luminozitate constantă. Ele nu sunt necesare pentru utilizarea normală, dar rezolvă situații incomode, cum ar fi inspecția QR, previzualizările private sau editarea peisajului înghesuit. + Utilizați setările pentru ecran complet sau bara de sistem atunci când editarea spațiului este mai importantă decât navigarea Chrome. + Utilizați modul securizat atunci când imaginile private nu ar trebui să apară în capturi de ecran sau previzualizări ale aplicațiilor. + Utilizați aplicarea luminozității pentru instrumentele în care imaginile întunecate sau codurile QR sunt greu de inspectat. + Păstrați transparența + Preveniți fundalurile transparente să devină albe, negre sau aplatizate + Utilizați ieșire conștientă de alfa + Transparența supraviețuiește numai atunci când instrumentul selectat și formatul de ieșire acceptă alpha. Dacă un fundal exportat devine alb sau negru, problema este de obicei formatul de ieșire, o setare de umplere/fond sau o aplicație de destinație care a aplatizat imaginea. + Alegeți PNG sau WebP când exportați imagini, autocolante, logo-uri sau suprapuneri eliminate de fundal. + Evitați JPEG pentru ieșire transparentă, deoarece nu stochează un canal alfa. + Verificați setările legate de culoarea de fundal pentru formatele alfa dacă previzualizarea arată un fundal plin. + Remediați problemele de partajare și de import + Utilizați setările selectorului și formatele acceptate atunci când fișierele nu apar sau nu se deschid corect + Introduceți fișierul în aplicație + Problemele de import sunt adesea cauzate de permisiunile furnizorului, formatele neacceptate sau comportamentul selectorului. Fișierele partajate din aplicații cloud și mesagerie pot fi temporare, așa că alegerea unei surse persistente poate fi mai fiabilă pentru editări mai lungi. + Încercați să deschideți fișierul prin selectorul de sistem în loc de o comandă rapidă pentru fișiere recente. + Dacă un format nu este acceptat de un instrument, convertiți-l mai întâi sau deschideți un instrument mai specific. + Examinați setările selectorului de imagini și ale lansatorului dacă fluxurile de partajare sau deschiderea directă a instrumentului se comportă diferit decât era de așteptat. + Confirmare de ieșire + Evitați să pierdeți editările nesalvate atunci când gesturi, apăsări înapoi sau comutări de instrumente au loc accidental + Protejați lucrările neterminate + Confirmarea de ieșire din instrument este utilă atunci când utilizați navigarea prin gesturi, editați fișiere mari sau comutați adesea între aplicații. Adaugă o mică pauză înainte de a părăsi un instrument, astfel încât setările și previzualizările neterminate să nu se piardă accidental. + Activați confirmarea dacă gesturile accidentale înapoi au închis vreodată un instrument înainte de a-l exporta. + Păstrați-l dezactivat dacă faceți mai ales conversii rapide într-un singur pas și preferați o navigare mai rapidă. + Folosiți-l împreună cu setările prestabilite pentru fluxuri de lucru mai lungi, unde reconstruirea setărilor ar fi enervantă. + Utilizați jurnalele atunci când raportați probleme + Colectați detalii utile înainte de a deschide o problemă sau de a contacta dezvoltatorul + Raportați probleme cu contextul + Un raport clar cu pași, versiunea aplicației, tipul de intrare și jurnalele este mult mai ușor de diagnosticat. Jurnalele sunt cele mai utile imediat după reproducerea unei probleme, deoarece păstrează proaspăt contextul din jur. + Notați numele instrumentului, acțiunea exactă și dacă se întâmplă cu un fișier sau cu fiecare fișier. + Deschideți jurnalele de aplicații după reproducerea problemei și partajați rezultatul jurnalului relevant atunci când este posibil. + Atașați capturi de ecran sau fișiere mostre numai atunci când acestea nu conțin informații private. + Procesarea în fundal a fost oprită + Android nu a lăsat notificarea de procesare în fundal să înceapă la timp. Aceasta este o limitare de sincronizare a sistemului care nu poate fi remediată în mod fiabil. Reporniți aplicația și încercați din nou; menținerea aplicației deschise și permiterea notificărilor sau a lucrărilor în fundal poate ajuta. + Omiteți alegerea fișierelor + Deschideți instrumentele mai rapid atunci când introducerea provine de obicei din partajare, clipboard sau un ecran anterior + Faceți lansări repetate de instrumente mai rapide + Omiteți alegerea fișierelor modifică primul pas al instrumentelor acceptate. Este util atunci când introduceți de obicei un instrument cu o imagine deja selectată sau din acțiuni de partajare Android, dar poate fi confuz dacă vă așteptați ca fiecare instrument să solicite imediat un fișier. + Activați-l numai atunci când fluxul dvs. obișnuit oferă deja imaginea sursă înainte de deschiderea instrumentului. + Păstrați-l dezactivat în timp ce învățați aplicația, deoarece alegerea explicită face ca fiecare instrument să fie mai ușor de înțeles. + Dacă se deschide un instrument fără nicio introducere evidentă, dezactivați această setare sau începeți de la Share/Open With, astfel încât Android să treacă primul fișierul. + Deschideți mai întâi editorul + Omite previzualizarea când o imagine partajată ar trebui să intre direct în editare + Alegeți previzualizarea sau editarea ca primă oprire + Deschideți Editare în loc de previzualizare este pentru utilizatorii care știu deja că doresc să modifice imaginea. Previzualizarea este mai sigură atunci când inspectați fișierele din chat sau stocare în cloud; editarea directă este mai rapidă pentru capturi de ecran, decupări rapide, adnotări și corecții unice. + Activați editarea directă dacă majoritatea imaginilor partajate necesită imediat modificări de decupare, marcare, filtre sau export. + Părăsiți mai întâi previzualizarea când inspectați adesea metadatele, dimensiunile, transparența sau calitatea imaginii înainte de a decide. + Utilizați acest lucru împreună cu favoritele, astfel încât imaginile partajate să ajungă aproape de instrumentele pe care le utilizați de fapt. + Introducerea textului presetat + Introduceți valori prestabilite exacte în loc să reglați comenzile în mod repetat + Utilizați valori precise când glisoarele sunt lente + Introducerea textului presetat vă ajută atunci când aveți nevoie de numere exacte pentru lucrul repetat: dimensiunile imaginilor sociale, marginile documentelor, ținte de compresie, lățimi de linii sau alte valori care sunt enervant de atins cu gesturi. Este util și pe ecranele mici, unde mișcarea fină a glisorului este mai dificilă. + Activați introducerea textului dacă reutilizați adesea dimensiunile exacte, procentele, valorile calității sau parametrii instrumentului. + Salvați valoarea ca presetare după ce ați tastat-o ​​o dată, apoi reutilizați-o în loc să reconstruiți aceeași configurație. + Păstrați comenzile prin gesturi pentru reglarea vizuală brută și valorile tastate pentru cerințe stricte de ieșire. + Salvați aproape de original + Puneți fișierele generate lângă imaginile sursă atunci când contextul folderului contează + Păstrați ieșirile cu sursele lor + Salvare în dosarul original este util pentru folderele camerei, dosarele de proiect, scanările de documente și loturile de clienți în care copia editată ar trebui să rămână lângă sursă. Poate fi mai puțin ordonat decât un folder de ieșire dedicat, așa că combinați-l cu sufixe sau modele clare de nume de fișier. + Activați-l atunci când fiecare folder sursă este deja semnificativ și ieșirile ar trebui să rămână grupate acolo. + Utilizați sufixe de nume de fișiere, prefixe, marcaje temporale sau modele, astfel încât copiile editate să fie ușor de separat de originale. + Dezactivați-l pentru loturi mixte atunci când doriți ca toate fișierele generate să fie colectate într-un folder de export. + Omite o ieșire mai mare + Evitați salvarea conversiilor care devin în mod neașteptat mai grele decât sursa + Protejați loturile de surprize de dimensiuni proaste + Unele conversii fac fișierele mai mari, în special capturile de ecran PNG, fotografiile deja comprimate, WebP de înaltă calitate sau imaginile cu metadate păstrate. Allow Skip If Larger împiedică acele ieșiri să înlocuiască o sursă mai mică în fluxurile de lucru în care reducerea dimensiunii este obiectivul principal. + Activați-l atunci când exportul este menit să comprima, să redimensioneze sau să pregătească fișierele pentru limitele de încărcare. + Examinați fișierele omise după un lot; acestea pot fi deja optimizate sau pot avea nevoie de un alt format. + Dezactivați-l atunci când calitatea, transparența sau compatibilitatea formatelor contează mai mult decât dimensiunea finală a fișierului. + Clipboard și linkuri + Controlați introducerea automată în clipboard și previzualizările linkurilor pentru instrumente mai rapide bazate pe text + Faceți previzibilă introducerea automată + Setările pentru clipboard și linkuri sunt convenabile pentru fluxurile de lucru QR, Base64, URL și text, dar introducerea automată ar trebui să rămână intenționată. Dacă aplicația pare să umple câmpurile de la sine, verificați comportamentul lipirii clipboard-ului; dacă cardurile de link se simt zgomotoase sau lente, ajustați comportamentul de previzualizare a linkului. + Permiteți lipirea automată a clipboard-ului atunci când copiați adesea text și deschideți imediat un instrument de potrivire. + Dezactivați lipirea automată dacă conținutul clipboardului privat apare în instrumente unde nu v-ați așteptat. + Dezactivați previzualizările linkurilor atunci când preluarea previzualizării este lentă, distrage atenția sau nu este necesară pentru fluxul dvs. de lucru. + Comenzi compacte + Utilizați selectoare mai dense și plasare rapidă a setărilor atunci când spațiul pe ecran este redus + Potriviți mai multe comenzi pe ecrane mici + Selectoarele compacte și plasarea rapidă a setărilor ajută pe telefoanele mici, editarea peisajului și instrumentele cu mulți parametri. Compensația este că controalele se pot simți mai dense, așa că acest lucru este cel mai bine după ce recunoașteți deja opțiunile comune. + Activați selectoarele compacte atunci când casetele de dialog sau listele de opțiuni par prea înalte pentru ecran. + Reglați setările rapide dacă comenzile instrumentului sunt mai ușor de accesat dintr-o parte a afișajului. + Reveniți la comenzi mai spațioase dacă încă învățați aplicația sau dacă nu atingeți frecvent opțiunile dense. + Încărcați imagini de pe web + Inserați o pagină sau adresa URL a unei imagini, previzualizați imaginile analizate, apoi salvați sau partajați rezultatele selectate + Utilizați imaginile web în mod deliberat + Încărcarea imaginilor web analizează sursele de imagini de la adresa URL furnizată, previzualizează prima imagine disponibilă și vă permite să salvați sau să partajați imaginile analizate selectate. Unele site-uri folosesc linkuri temporare, protejate sau generate de scripturi, astfel încât o pagină care funcționează într-un browser poate să nu returneze imagini utilizabile. + Lipiți o adresă URL directă a imaginii sau adresa URL a unei pagini și așteptați să apară lista de imagini analizate. + Utilizați comenzile de cadru sau de selecție atunci când pagina conține mai multe imagini și aveți nevoie doar de unele dintre ele. + Dacă nu se încarcă nimic, încercați mai întâi un link direct de imagine sau descărcați prin browser; site-ul poate bloca analizarea sau poate folosi linkuri private. + Alegeți tipul de redimensionare + Înțelegeți explicit, flexibil, decupare și potrivire înainte de a forța o imagine în dimensiuni noi + Controlați forma, pânza și raportul de aspect + Tipul de redimensionare decide ce se întâmplă atunci când lățimea și înălțimea solicitate nu se potrivesc cu raportul de aspect original. Este diferența dintre întinderea pixelilor, păstrarea proporțiilor, decuparea suprafeței suplimentare sau adăugarea unei pânze de fundal. + Utilizați explicit numai atunci când distorsiunea este acceptabilă sau sursa are deja același raport de aspect ca și ținta. + Utilizați Flexibil pentru a păstra proporțiile prin redimensionarea din partea importantă, în special pentru fotografii și loturi mixte. + Utilizați Decupare pentru cadre stricte fără margini sau Potriviți atunci când întreaga imagine trebuie să rămână vizibilă în interiorul unei pânze fixe. + Alegeți modul de scalare a imaginii + Alegeți filtrul de reeșantionare care controlează claritatea, netezimea, viteza și artefactele de margine + Redimensionați fără catifelare accidentală + Modul de scară a imaginii controlează modul în care sunt calculați noii pixeli în timpul redimensionării. Modurile simple sunt rapide și previzibile, în timp ce filtrele avansate pot păstra mai bine detaliile, dar pot ascuți marginile, pot crea halouri sau pot dura mai mult pentru imaginile mari. + Utilizați Basic sau Bilinear pentru redimensionarea rapidă de zi cu zi atunci când rezultatul trebuie doar să fie mai mic și curat. + Utilizați modurile Lanczos, Robidoux, Spline sau EWA atunci când detaliile fine, textul sau reducerea de înaltă calitate contează. + Utilizați Nearest pentru pixel art, pictograme și grafică cu margini dure, unde pixelii neclari ar strica aspectul. + Scala spațiul de culoare + Decideți cum sunt amestecate culorile în timp ce pixelii sunt reeșantionați în timpul redimensionării + Păstrați degradeurile și marginile naturale + Scala spațiul de culoare modifică matematica utilizată în timpul redimensionării. Cele mai multe imagini sunt în regulă cu valoarea implicită, dar spațiile liniare sau perceptuale pot face ca gradienții, marginile întunecate și culorile saturate să pară mai curate după o scalare puternică. + Lăsați valoarea implicită atunci când viteza și compatibilitatea contează mai mult decât micile diferențe de culoare. + Încercați modurile Liniare sau perceptuale când contururile întunecate, capturile de ecran cu interfața de utilizare, gradienții sau benzile de culoare arată greșit după redimensionare. + Comparați înainte și după pe ecranul țintă real, deoarece modificările spațiului de culoare pot fi subtile și pot depinde de conținut. + Limitați modurile de redimensionare + Aflați când imaginile depășite limitele trebuie sărite, recodate sau reduse pentru a se potrivi + Alege ce se întâmplă la limită + Limit Resize nu este doar un instrument pentru imagini mai mici. Modul său decide ce face aplicația atunci când o sursă se află deja în limitele tale sau când doar o parte încalcă regula, ceea ce este important pentru dosare mixte și pregătirea încărcării. + Utilizați Skip atunci când imaginile din interiorul limitelor ar trebui lăsate neatinse și nu trebuie exportate din nou. + Utilizați Recode atunci când dimensiunile sunt acceptabile, dar aveți nevoie de un nou format, comportament al metadatelor, calitate sau nume de fișier. + Utilizați Zoom atunci când imaginile care depășesc limitele trebuie reduse proporțional până se potrivesc cu caseta permisă. + Ținte de raport de aspect + Evitați fețele întinse, marginile tăiate și chenarele neașteptate în dimensiuni stricte de ieșire + Potriviți forma înainte de a redimensiona + Lățimea și înălțimea sunt doar jumătate dintr-o țintă de redimensionare. Raportul de aspect decide dacă imaginea se poate potrivi natural, necesită tăiere sau are nevoie de o pânză în jurul ei. Verificarea mai întâi a formei previne cele mai surprinzătoare rezultate de redimensionare. + Comparați forma sursă cu forma țintă înainte de a alege Explicit, Decupare sau Potrivire. + Decupați mai întâi atunci când subiectul poate pierde fundalul suplimentar și destinația are nevoie de un cadru strict. + Utilizați Fit sau Flexible atunci când fiecare parte a imaginii originale trebuie să rămână vizibilă. + Redimensionare sau normală + Aflați când adăugați pixeli necesită inteligență artificială și când scalarea simplă este suficientă + Nu confundați dimensiunea cu detaliile + Redimensionarea normală modifică dimensiunile prin interpolarea pixelilor; nu poate inventa detalii reale. Upscale AI poate crea detalii cu aspect mai clar, dar este mai lent și poate halucina textură, așa că alegerea potrivită depinde dacă aveți nevoie de compatibilitate, viteză sau restaurare vizuală. + Utilizați redimensionarea normală pentru miniaturi, limite de încărcare, capturi de ecran și modificări simple ale dimensiunilor. + Utilizați AI upscale atunci când o imagine mică sau moale trebuie să arate mai bine la o dimensiune mai mare. + Comparați fețele, textul și modelele după extinderea AI, deoarece detaliile generate pot părea convingătoare, dar incorecte. + Ordinea filtrelor contează + Aplicați curățarea, culoarea, claritatea și compresia finală într-o secvență intenționată + Construiți o stivă de filtre mai curată + Filtrele nu sunt întotdeauna interschimbabile. O neclaritate înainte de clarificare, o creștere a contrastului înainte de OCR sau o schimbare de culoare înainte de compresie pot produce rezultate foarte diferite de la aceleași comenzi. + Decupați și rotiți mai întâi, astfel încât mai târziu filtrele să funcționeze numai pe pixelii care vor rămâne. + Aplicați expunerea, balansul de alb și contrastul înainte de apariția efectelor de claritate sau stilizate. + Păstrați redimensionarea și compresia finală aproape de sfârșit, astfel încât detaliile previzualizate să supraviețuiască exportului în mod previzibil. + Remediați marginile de fundal + Curăță halourile, umbrele rămase, părul, găurile și contururile moi după îndepărtarea automată + Faceți decupajele să pară intenționate + Măștile automate arată adesea bine dintr-o privire, dar dezvăluie probleme pe fundaluri luminoase, întunecate sau cu modele. Curățarea marginilor este diferența dintre o decupare rapidă și un autocolant reutilizabil, logo sau imagine de produs. + Previzualizează rezultatul pe fundaluri contrastante pentru a dezvălui halouri și detaliile marginilor lipsă. + Utilizați restaurare pentru păr, colțuri și obiecte transparente înainte de a șterge resturile din jur. + Exportați un mic test pe culoarea finală de fundal atunci când decupajul va fi plasat într-un design. + Filigrane care pot fi citite + Faceți semnele vizibile pe imagini luminoase, întunecate, ocupate și tăiate, fără a distruge fotografia + Protejați imaginea fără a o ascunde + Un filigran care arată bine pe o imagine poate dispărea pe alta. Mărimea, opacitatea, contrastul, plasarea și repetarea trebuie alese pentru întregul lot, nu numai pentru prima previzualizare. + Testați marcajul pe cele mai luminoase și mai întunecate imagini din lot înainte de a exporta toate fișierele. + Utilizați o opacitate mai scăzută pentru semne repetate mari și un contrast mai puternic pentru semne de colț mici. + Păstrați clare fețele importante, textul și detaliile despre produs, cu excepția cazului în care marca este menită să împiedice reutilizarea. + Împărțiți o imagine în plăci + Tăiați o singură imagine după rânduri, coloane, procente personalizate, format și calitate + Pregătiți grile și piesele comandate + Împărțirea imaginii creează mai multe ieșiri dintr-o singură imagine sursă. Poate împărți în funcție de numărul de rânduri și coloane, poate ajusta procentele de rânduri sau coloane și poate salva bucăți cu formatul și calitatea de ieșire selectate, ceea ce este util pentru grile, secțiuni de pagină, panorame și postări sociale ordonate. + Alegeți imaginea sursă, apoi setați numărul de rânduri și coloane de care aveți nevoie. + Ajustați procentele rândurilor sau coloanelor atunci când piesele nu ar trebui să aibă toate dimensiunile egale. + Alegeți formatul și calitatea de ieșire înainte de a salva, astfel încât fiecare piesă generată să folosească aceleași reguli de export. + Tăiați benzi de imagine + Îndepărtați regiunile verticale sau orizontale și îmbinați părțile rămase înapoi împreună + Eliminați o regiune fără a lăsa un gol + Tăierea imaginii este diferită de decuparea normală. Poate elimina o bandă verticală sau orizontală selectată, apoi poate îmbina părțile rămase ale imaginii. Modul invers păstrează regiunea selectată, iar utilizarea ambelor direcții inverse păstrează dreptunghiul selectat. + Utilizați tăierea verticală pentru regiunile laterale, coloanele sau benzile din mijloc; utilizați tăierea orizontală pentru bannere, goluri sau rânduri. + Activați inversul pe o axă atunci când doriți să păstrați piesa selectată în loc să o eliminați. + Previzualizează marginile după tăiere, deoarece conținutul îmbinat poate părea brusc atunci când zona eliminată traversează detalii importante. + Alegeți formatul de ieșire + Alegeți JPEG, PNG, WebP, AVIF, JXL sau PDF în funcție de conținut și destinație + Lăsați destinația să decidă formatul + Cel mai bun format depinde de ce conține imaginea și de unde va fi folosită. Fotografiile, capturile de ecran, elementele transparente, documentele și fișierele animate toate eșuează în moduri diferite când sunt forțate în format greșit. + Utilizați JPEG pentru o compatibilitate largă a fotografiilor atunci când nu sunt necesari transparența și pixelii exacti. + Utilizați PNG pentru capturi de ecran clare, capturi de interfață de utilizare, grafică transparentă și editări fără pierderi. + Utilizați WebP, AVIF sau JXL atunci când ieșirea compactă modernă contează și aplicația de recepție o acceptă. + Extrage coperți audio + Salvați grafica de album încorporată sau cadrele media disponibile din fișierele audio ca imagini PNG + Scoateți opera de artă din fișierele audio + Audio Covers folosește metadatele media Android pentru a citi lucrările de artă încorporate din fișierele audio. Când opera de artă încorporată nu este disponibilă, retriever-ul poate reveni la un cadru media dacă Android poate oferi unul; dacă niciunul nu există, instrumentul raportează că nu există nicio imagine de extras. + Deschide Audio Covers și selectează unul sau mai multe fișiere audio cu coperta așteptată. + Examinați coperta extrasă înainte de a salva sau de a partaja, în special pentru fișierele din aplicațiile de streaming sau de înregistrare. + Dacă nu se găsește nicio imagine, este posibil ca fișierul audio să nu aibă copertă încorporată sau Android nu ar putea expune un cadru utilizabil. + Date și metadatele locației + Decideți ce să păstrați atunci când timpul de captare contează, dar datele GPS sau ale dispozitivului nu ar trebui să curgă + Separați metadatele utile de metadatele private + Metadatele nu sunt toate la fel de riscante. Datele de captare pot fi utile pentru arhivare și sortare, în timp ce GPS-ul, câmpurile asemănătoare dispozitivului, etichetele software sau câmpurile proprietarului pot dezvălui mai mult decât s-a dorit. + Păstrați etichetele de dată și oră atunci când ordinea cronologică contează pentru albume, scanări sau istoricul proiectelor. + Eliminați GPS-ul și etichetele de identificare a dispozitivului înainte de a partaja public sau de a trimite imagini către străini. + Exportați un eșantion și inspectați din nou EXIF ​​atunci când cerințele de confidențialitate sunt stricte. + Evitați coliziunile de nume de fișiere + Protejați ieșirile în lot atunci când multe fișiere partajează nume precum imagine.jpg sau screenshot.png + Faceți fiecare nume de ieșire unic + Numele de fișiere duplicat sunt frecvente atunci când imaginile provin de la mesageri, capturi de ecran, foldere cloud sau mai multe camere. Un model bun previne înlocuirea accidentală și menține ieșirile urmăribile până la sursele lor. + Includeți numele originalului plus un sufix atunci când copia editată ar trebui să fie recunoscută. + Adăugați secvență, marca temporală, presetare sau dimensiuni atunci când procesați loturi mari mixte. + Evitați suprascrieți fluxurile de lucru până când ați verificat că modelul de denumire nu se poate ciocni. + Salvați sau distribuiți + Alegeți între un fișier persistent și un transfer temporar către o altă aplicație + Exportați cu intenția corectă + Salvați și distribuiți pot simți similar, dar rezolvă probleme diferite. Salvarea creează un fișier pe care îl puteți găsi mai târziu; partajarea trimite un rezultat generat prin Android către o altă aplicație și nu poate lăsa o copie locală durabilă. + Utilizați Salvare atunci când rezultatul trebuie să rămână într-un folder cunoscut, să fie copiat de rezervă sau să fie reutilizat ulterior. + Utilizați Partajare pentru mesaje rapide, atașamente de e-mail, postări pe rețelele sociale sau trimiterea rezultatului către alt editor. + Salvați mai întâi atunci când aplicația de primire nu este de încredere sau când o partajare eșuată ar fi enervant de recreat. + Dimensiunea paginii PDF și marginile + Alegeți dimensiunea hârtiei, comportamentul de potrivire și spațiul de respirație înainte de a crea un document + Faceți paginile cu imagini imprimabile și lizibile + Imaginile pot deveni pagini PDF incomode atunci când forma lor nu se potrivește cu dimensiunea de hârtie aleasă. Marginile, modul de potrivire și orientarea paginii decid dacă conținutul este decupat, mic, întins sau confortabil de citit. + Utilizați o dimensiune reală a hârtiei atunci când PDF-ul poate fi tipărit sau trimis la un formular. + Utilizați marginile pentru scanări și capturi de ecran, astfel încât textul să nu se așeze pe marginea paginii. + Previzualizează paginile portret și peisaj împreună când imaginile sursă au o orientare mixtă. + Curățați scanările + Îmbunătățiți marginile hârtiei, umbrele, contrastul, rotația și lizibilitatea înainte de PDF sau OCR + Pregătiți paginile înainte de arhivare + O scanare poate fi capturată tehnic, dar totuși greu de citit. Pașii mici de curățare înainte de exportul PDF contează adesea mai mult decât setările de compresie, în special pentru chitanțe, note scrise de mână și pagini cu lumină scăzută. + Corectați perspectiva și tăiați marginile mesei, degetele, umbrele și dezordinea de fundal. + Măriți cu atenție contrastul, astfel încât textul să devină mai clar fără a distruge ștampile, semnăturile sau urmele de creion. + Rulați OCR numai după ce pagina este dreaptă și lizibilă, apoi verificați manual numerele importante. + Comprimați, în tonuri de gri sau reparați PDF-uri + Utilizați operațiuni PDF cu un singur fișier pentru copii de documente mai ușoare, imprimabile sau reconstruite + Alegeți operația de curățare PDF + PDF Tools include operații separate pentru compresie, conversie în tonuri de gri și reparare. Compresia și tonurile de gri funcționează în principal prin imagini încorporate, în timp ce repararea reconstruiește structura PDF; niciunul dintre acestea nu ar trebui tratat ca o remediere garantată pentru fiecare document. + Utilizați Comprimare PDF atunci când documentul conține imagini și dimensiunea fișierului contează pentru partajare. + Utilizați tonuri de gri atunci când documentul ar trebui să fie mai ușor de imprimat sau nu necesită imagini color. + Utilizați Reparați PDF pe o copie atunci când un document nu se deschide sau are structura internă deteriorată, apoi verificați fișierul reconstruit. + Extrageți imagini din PDF-uri + Recuperați imaginile PDF încorporate și împachetați rezultatele extrase într-o arhivă + Salvați imaginile sursă din documente + Extract Images scanează PDF-ul pentru obiecte de imagine încorporate, omite duplicatele acolo unde este posibil și stochează imaginile recuperate într-o arhivă ZIP generată. Extrage doar imagini care sunt de fapt încorporate în PDF, nu text, desene vectoriale sau fiecare pagină vizibilă ca captură de ecran. + Utilizați Extrageți imagini când aveți nevoie de imagini stocate într-un PDF, cum ar fi fotografii dintr-un catalog sau materiale scanate. + Folosiți PDF în imagini sau extragerea paginii atunci când aveți nevoie de pagini întregi, inclusiv text și aspect. + Dacă instrumentul nu raportează nicio imagine încorporată, pagina poate fi conținut text/vector sau imaginile ar putea să nu fie stocate ca obiecte extractibile. + Metadate, aplatizare și imprimare + Editați proprietățile documentului, rasterizați paginile sau pregătiți în mod intenționat machete de imprimare personalizate + Alegeți acțiunea finală a documentului + Metadatele PDF, aplatizarea și pregătirea pentru imprimare rezolvă diferite probleme din etapa finală. Metadatele controlează proprietățile documentului, Flatten PDF rasterizează paginile în rezultate mai puțin editabile, iar Print PDF pregătește un nou aspect cu controale pentru dimensiunea paginii, orientarea, marginea și pagini-pe-coală. + Folosiți metadatele atunci când contează autorul, titlul, cuvintele cheie, producătorul sau curățarea confidențialității. + Utilizați Aplatizare PDF atunci când conținutul paginii vizibil ar trebui să devină mai greu de editat ca obiecte PDF separate. + Utilizați Print PDF atunci când aveți nevoie de dimensiune personalizată a paginii, orientare, margini sau mai multe pagini pe coală. + Pregătiți imagini pentru OCR + Utilizați decuparea, rotația, contrastul, reducerea zgomotului și scalarea înainte de a cere OCR să citească text + Oferă pixeli OCR mai curați + Greșelile OCR provin adesea din imaginea de intrare, mai degrabă decât din motorul OCR. Paginile strâmbe, contrastul scăzut, neclaritatea, umbrele, textul mic și fundalurile mixte reduc toate calitatea recunoașterii. + Decupați zona de text și rotiți imaginea astfel încât liniile să fie orizontale înainte de recunoaștere. + Măriți contrastul sau convertiți-l în alb-negru mai curat numai atunci când face literele mai ușor de citit. + Redimensionați textul mic în sus moderat, dar evitați clarificarea agresivă care creează margini false. + Verificați conținutul QR + Examinați linkurile, acreditările Wi-Fi, contactele și acțiunile înainte de a avea încredere într-un cod + Tratează textul decodat ca intrări nesigure + Un cod QR poate ascunde o adresă URL, o cartelă de contact, o parolă Wi-Fi, un eveniment din calendar, un număr de telefon sau un text simplu. Este posibil ca eticheta vizibilă din apropierea codului să nu se potrivească cu sarcina utilă reală, așa că examinați conținutul decodat înainte de a acționa asupra acestuia. + Inspectați adresa URL completă decodificată înainte de a o deschide, în special domeniile scurtate sau necunoscute. + Fiți atenți la codurile Wi-Fi, de contact, calendar, telefon și SMS, deoarece acestea pot declanșa acțiuni sensibile. + Copiați mai întâi conținutul când trebuie să îl verificați în altă parte, în loc să îl deschideți imediat. + Generați coduri QR + Creați coduri din text simplu, adrese URL, Wi-Fi, contacte, e-mail, telefon, SMS și date despre locație + Creați sarcina utilă QR corectă + Ecranul QR poate genera coduri din conținutul introdus, precum și le poate scana pe cele existente. Tipurile QR structurate ajută la codificarea datelor într-un format pe care alte aplicații îl înțeleg, cum ar fi detaliile de conectare la Wi-Fi, cărțile de contact, câmpurile de e-mail, numerele de telefon, conținutul SMS, adresele URL sau coordonatele geografice. + Alegeți text simplu pentru note simple sau utilizați un tip structurat atunci când codul ar trebui să se deschidă ca Wi-Fi, contact, URL, e-mail, telefon, SMS sau date de locație. + Verificați fiecare câmp înainte de a partaja codul generat, deoarece previzualizarea vizibilă reflectă doar sarcina utilă pe care ați introdus-o. + Testați codul QR salvat cu un scaner când va fi tipărit, postat public sau folosit de alte persoane. + Alegeți un mod de sumă de control + Calculați hash-uri din fișiere sau text, comparați un fișier sau verificați mai multe fișiere + Verificați conținutul, nu aspectul + Checksum Tools are file separate pentru hashing fișiere, hashing text, compararea unui fișier cu un hash cunoscut și compararea loturilor. O sumă de control dovedește identitatea octet pentru octet pentru algoritmul selectat; nu demonstrează că două imagini doar arată similare. + Utilizați Calculate pentru fișiere și Text Hash pentru datele text tastate sau lipite. + Utilizați Comparați când cineva vă oferă o sumă de verificare așteptată pentru un fișier. + Utilizați compararea loturilor atunci când multe fișiere necesită hash-uri sau verificare într-o singură trecere, apoi păstrați consecvența algoritmului selectat. + Confidențialitate din clipboard + Utilizați funcțiile automate de clipboard fără a expune accidental datele copiate private + Păstrați conținutul copiat intenționat + Automatizarea clipboard-ului este convenabilă, dar textul copiat poate conține parole, linkuri, adrese, jetoane sau note private. Tratați intrarea bazată pe clipboard ca pe o funcție de viteză care ar trebui să se potrivească cu obiceiurile și așteptările dvs. de confidențialitate. + Dezactivați utilizarea automată a clipboard-ului dacă textul privat apare în mod neașteptat în instrumente. + Utilizați salvarea doar prin clipboard numai atunci când doriți ca rezultatul să evite stocarea. + Ștergeți sau înlocuiți clipboard-ul după ce ați manipulat text sensibil, date QR sau încărcături utile Base64. + Formate de culoare + Înțelegeți valorile HEX, RGB, HSV, alfa și ale culorilor copiate înainte de a lipi în altă parte + Copiați formatul așteptat de țintă + Aceeași culoare vizibilă poate fi scrisă în mai multe moduri. Un instrument de proiectare, un câmp CSS, o valoare a culorii Android sau un editor de imagini se pot aștepta la o ordine diferită, o manipulare alfa sau un model de culoare diferit. + Utilizați HEX când inserați în câmpuri web, design sau tematice care se așteaptă la coduri de culoare compacte. + Utilizați RGB sau HSV atunci când trebuie să reglați manual canalele, luminozitatea, saturația sau nuanța. + Verificați alpha separat când transparența contează, deoarece unele destinații o ignoră sau folosesc o altă ordine. + Răsfoiți biblioteca de culori + Căutați culorile numite după nume sau HEX și păstrați favoritele în partea de sus + Găsiți culori denumite reutilizabile + Biblioteca de culori încarcă o colecție de culori cu nume, o sortează după nume, filtrează după numele culorii sau valoarea HEX și stochează culorile preferate, astfel încât intrările importante să rămână mai ușor de accesat. Este util atunci când aveți nevoie de o culoare cu nume reală în loc de eșantionare dintr-o imagine. + Căutați după un nume de culoare când cunoașteți familia, cum ar fi roșu, albastru, ardezie sau mentă. + Căutați după HEX când aveți deja o culoare numerică și doriți să găsiți intrări denumite care se potrivesc sau din apropiere. + Marcați culorile frecvente ca favorite, astfel încât acestea să treacă înaintea bibliotecii generale în timpul navigării. + Utilizați colecții de gradient de plasă + Descărcați resursele de gradient de plasă disponibile și partajați imaginile selectate + Utilizați elemente de gradient de plasă de la distanță + Mesh Gradients poate încărca o colecție de resurse de la distanță, poate descărca imagini cu gradient lipsă, poate afișa progresul descărcării și poate partaja gradienții selectați. Disponibilitatea depinde de accesul la rețea și de depozitul de resurse la distanță, așa că o listă goală înseamnă, de obicei, că resursele nu erau încă disponibile. + Deschideți Gradiente de plasă din instrumentele de gradient atunci când doriți imagini cu gradient de plasă gata făcute. + Așteptați încărcarea resurselor sau progresul descărcării înainte de a presupune că colecția este goală. + Selectați și partajați degradeurile de care aveți nevoie sau reveniți la Gradient Maker când doriți să construiți manual un gradient personalizat. + Rezolvarea activelor generate + Alegeți dimensiunea de ieșire înainte de a genera degrade, zgomot, imagini de fundal, artă SVG sau texturi + Generați la dimensiunea de care aveți nevoie + Imaginile generate nu au un original al camerei pe care să recurgă. Dacă rezultatul este prea mic, scalarea ulterioară poate înmuia modelele, poate modifica detaliile sau poate modifica modul în care se comprimă și se comprimă elementele. + Setați mai întâi dimensiunile pentru cazul final de utilizare, cum ar fi tapet, pictogramă, fundal, imprimare sau suprapunere. + Utilizați dimensiuni mai mari pentru texturi care pot fi decupate, mărite sau reutilizate în mai multe aspecte. + Exportați versiunile de testare înaintea rezultatelor uriașe, deoarece detaliile procedurale pot schimba modul în care se comportă compresia. + Exportați imagini de fundal curente + Preluați imaginile de fundal disponibile pentru Acasă, Blocare și încorporate de pe dispozitiv + Salvați sau partajați imagini de fundal instalate + Imagini de fundal Export citește imaginile de fundal pe care Android le expune prin intermediul API-urilor de fundal ale sistemului. În funcție de dispozitiv, versiunea Android, permisiuni și varianta de construcție, imaginile de fundal Acasă, Blocare sau încorporate pot fi disponibile separat sau pot lipsi. + Deschideți Wallpapers Export pentru a încărca imaginile de fundal pe care sistemul le permite aplicației să le citească. + Selectați intrările disponibile Acasă, Blocare sau tapet încorporat pe care doriți să le salvați, să le copiați sau să le partajați. + Dacă lipsește un tapet sau funcția nu este disponibilă, este de obicei o limitare de sistem, o permisiune sau o variantă de construcție, mai degrabă decât o setare de editare. + Corners Animation Throttle + Copiați culoarea ca + HEX + nume + Model de covoraș portret pentru decupaje rapide de persoane, cu păr mai moale și manevrare a marginilor. + Îmbunătățire rapidă în condiții de lumină scăzută folosind estimarea curbei Zero-DCE++. + Model de restaurare a imaginii Restormer pentru reducerea dungilor de ploaie și a artefactelor pe vreme umedă. + Model de deformare a documentelor care prezice o grilă de coordonate și remapează paginile curbe într-o scanare mai plată. + Scala de durată a mișcării + Controlează viteza animației aplicației: 0 dezactivează mișcarea, 1 este normal, valorile mai mari încetinesc animațiile + Afișați instrumentele recente + Afișați acces rapid la instrumentele utilizate recent pe ecranul principal + Statistici de utilizare + Explorați se deschide aplicația, se lansează instrumente și cele mai utilizate instrumente + Se deschide aplicația + Instrumentul se deschide + Ultimul instrument + Salvări reușite + Urma de activitate + Datele salvate + Format de top + Instrumente folosite + %1$d se deschide • %2$s + Nu există încă statistici de utilizare + Deschideți câteva instrumente și acestea vor apărea aici automat + Statisticile dvs. de utilizare sunt stocate numai local pe dispozitivul dvs. ImageToolbox le folosește numai pentru a vă afișa activitatea personală, instrumentele preferate și informațiile despre datele salvate + Eroare + Varianta neutră + Drop Shadow + Înălțimea dintelui + Gama de dinte orizontală + Gama de dinte verticală + Marginea de sus + Marginea dreaptă + Marginea de jos + Marginea din stânga + Marginea ruptă + editor edita fotografie retușare imagine ajusta + redimensionare converti scala rezolutie dimensiuni latime inaltime jpg jpeg png webp comprima + comprima dimensiunea greutate octeți mb kb reduce calitatea optimiza + decuparea decuparea raportului de aspect + efectul de filtru corecția culorii reglați masca lut + trage pensula creion adnotare marcaj + cifrare criptare decriptare parola secretă + dispozitiv de îndepărtare a fundalului șterge șterge decupaj alfa transparent + vizualizare de previzualizare galerie inspectare deschisă + îmbinare cusături combina panoramă captură de ecran lungă + url web descărcare imagine link internet + picker eyedropper eșantion hex culoare rgb + paleta de culori eșantioane extras dominant + confidențialitatea metadatelor exif eliminați banda curată locația gps + comparați diferența înainte și după + limita redimensionare max min megapixeli dimensiuni clema + instrumente pentru paginile documentului pdf + ocr text recunoaște extras scanare + plasă de culoare de fundal cu gradient + suprapunere a ștampilei textului logoului filigranului + animație gif conversie cadre animate + animație apng animate PNG converti cadre + arhiva zip compress pack despachetează fișierele + jxl jpeg xl convertește formatul de imagine jpg + svg vector trace convert xml + format convert jpg jpeg png webp heic avif jxl bmp + scaner de documente scanare decupare perspectiva hârtiei + scaner cod de bare qr scaner + stivuire suprapunere medie astrofotografie mediană + împărțit plăci grilă felie părți + culoare convert hex rgb hsl hsv cmyk lab + webp convert format de imagine animată + generator de zgomot granul de textură aleatoare + combină aspectul grilei de colaj + straturile de marcare adnotă editarea suprapunerii + base64 encode decode data uri + checksum hash md5 sha sha1 sha256 verifica + fundal gradient de plasă + metadate exif editați camera de date GPS + tăiați plăci bucăți de grilă împărțite + metadate muzicale coperta audio coperta albumului + fundal export tapet + caractere de artă text ascii + ai ml neural sporesc segmentul de lux + paleta de materiale ale bibliotecii de culori numită culori + shader glsl fragment effect studio + pdf merge combine join + pdf împărțiți pagini separate + pdf rotiți orientarea paginilor + pdf rearanja reordonează sortarea paginilor + paginarea numerelor paginilor pdf + textul pdf ocr recunoaște extrasul care poate fi căutat + pdf text logo ștampilă filigran + pdf semnătură semnă desen + pdf proteja parola criptare blocare + pdf deblocare parola decriptare elimina protectie + pdf comprima reduce dimensiunea optimiza + pdf tonuri de gri negru alb monocrom + pdf reparare reparare recuperare stricat + proprietățile metadatelor pdf titlul autorului exif + pdf elimina șterge pagini + pdf tăiați marginile de tăiere + pdf aplatiza adnotările formează straturi + pdf extrage imagini imagini + pdf comprimare arhivă zip + imprimanta de imprimare pdf + vizualizator de previzualizare pdf citit + imagini în pdf converti document jpg jpeg png + pdf în pagini de imagini export jpg png + adnotări pdf comentarii elimina curat + Resetați statisticile de utilizare + Instrumentul se deschide, salvează statisticile, seria, datele salvate și formatul de top vor fi resetate. Deschiderile aplicației vor rămâne neschimbate. + Audio + Fişier + Exportați profiluri + Salvați profilul curent + Ștergeți profilul de export + Doriți să ștergeți profilul de export \\"%1$s\\"? + Chenar de desen + Afișați un chenar subțire în jurul desenului și ștergeți pânza + Ondulat + Elemente UI rotunjite cu o margine ondulată moale + Chiuretă + Crestătură + Colțuri rotunjite sculptate spre interior + Colțuri trepte cu tăietură dublă + Substituent + Utilizați {filename} pentru a insera numele fișierului original fără extensie + Flare + Suma de bază + Suma inelului + Cantitatea de raze + Lățimea inelului + Distorsionează perspectiva + Aspect și simțire Java + Forfecare + Unghiul X + și unghi + Redimensionați ieșirea + Picătură de apă + Lungime de undă + Fază + High Pass + Mască de culoare + Chrome + Dizolva + Moliciune + Feedback + Încețoșarea obiectivului + Intrare scăzută + Intrare mare + Putere scăzută + Randament ridicat + Efecte de lumină + Înălțimea denivelării + Moliciunea bumpului + Clipoci + X amplitudine + Amplitudinea Y + lungime de undă X + lungime de undă Y + Blur adaptiv + Generarea texturii + Generați diverse texturi procedurale și salvați-le ca imagini + Tip textura + Părtinire + Timp + Strălucire + Dispersia + Mostre + Coeficientul unghiului + Coeficient de gradient + Tip grilă + Putere la distanță + Scara Y + Neclaritate + Tipul de bază + Factorul de turbulență + Scalare + Iterații + Inele + Metal periat + Caustice + Celular + Tablă de șah + fBm + Marmură + Plasma + Pilota + Lemn + aleatoriu + Pătrat + Hexagonal + Octogonal + Triunghiular + Crestată + VL Zgomot + SC Zgomot + Recent + Nu există încă filtre utilizate recent + generator de textură metal periat caustică celular tablă de șah marmură plasmă pilota lemn cărămidă camuflaj nor de celule crăpătură țesătură frunziș fagure de miere gheață lavă nebuloasă hârtie rugină nisip fum piatră teren topografie apă ondulare + Cărămidă + Camuflaj + Celulă + Nor + Sparge + Țesătură + Frunziş + Fagure de miere + Gheaţă + Lavă + Nebuloasă + Hârtie + Rugini + Nisip + Fum + Piatră + Teren + Topografie + Undă de apă + Lemn avansat + Lățimea mortarului + Neregularitate + Teşit + Rugozitate + Primul prag + Al doilea prag + Al treilea prag + Moliciunea marginilor + Lățimea chenarului + Acoperire + Detaliu + Adâncime + Ramificare + Fire orizontale + Fire verticale + Puf + Venele + Iluminat + Latimea fisurii + Îngheţ + Flux + Crustă + Densitatea norilor + Stele + Densitatea fibrelor + Rezistența fibrelor + Petele + Coroziune + Pitting + Fulgi + Frecvența dunelor + Unghiul vântului + Ondulări + Suciuri + Scala venelor + Nivelul apei + Nivelul muntelui + Eroziune + Nivelul de zăpadă + Număr de linii + Grosimea liniei + Umbrire + Culoarea porilor + Culoarea mortarului + Culoare cărămidă închisă + Culoare caramida + Evidențiați culoarea + Culoare închisă + Culoarea pădurii + Culoarea pământului + Culoarea nisipului + Culoare de fundal + Culoarea celulei + Culoarea marginilor + Culoarea cerului + Culoarea umbrei + Culoare deschisă + Culoarea suprafeței + Varianta de culoare + Culoare crapată + Culoare urzeală + Culoarea bătăturii + Culoare închisă a frunzei + Culoarea frunzelor + Culoarea chenarului + Culoarea mierii + Culoare profundă + Culoare gheață + Culoarea înghețului + Culoarea crustei + Spălați culoarea + Culoare strălucitoare + Culoarea spațiului + Culoare violet + Culoare albastră + Culoare de bază + Culoarea fibrei + Culoarea petelor + Culoare metal + Culoare ruginie închisă + Culoare rugină + Culoare portocalie + Culoarea fumului + Culoarea venelor + Culoare de câmpie + Culoarea apei + Culoare rock + Culoarea zăpezii + Culoare scăzută + Culoare mare + Culoarea liniei + Culoare superficială + Iarbă + Murdărie + Piele + Beton + Asfalt + Muşchi + Foc + Aurora + Acuarelă + Opal + Fulger + Catifea + Supernova + Iris + Vânt + Umiditate + Pietricele + Agregat + Gudron + Purta + Fum + Intensitate + Irizare + Întuneric + Pigment + Hârtie + Simetrie + Claritate + Aspect lăptos + Pliere + Lustrui + Ramuri + Direcţie + Luciu + Penaj + Spectru + Difracţie + Arme + Twist + Răsuci + Filigran + Curbură + Grosime + Metalic + Deschidere + Atmosferă + Culoare aurie + Culoare roz + Culoare oțel + Culoare perla + Slick de ulei + Flux abstract + Oțel Damasc + Marbling cu cerneală + Folie holografică + Bioluminiscență + Vortexul cosmic + Lampă de lavă + Orizontul evenimentelor + Fractal Bloom + Tunelul cromatic + Eclipsa Corona + Atractor ciudat + Coroana de ferrofluid + Pene de păun + Nautilus Shell + Planeta inelata + Densitatea lamei + Lungimea lamei + Petice + Aglomerări + Riduri + Porii + Crăpături + Petele + Fibre + Frecvența flăcării + Panglici + Benzi + Înflorește + Margini + Difuzie + Joc de culori + Straturi + Pliuri + Echilibru de cerneală + Încreți + Stralucirea miezului + Blobs + Înclinarea discului + Dimensiunea orizontului + Lățimea discului + Lentile + Petale + Fațete + Dimensiunea lunii + Dimensiunea Corona + Raze + Inel cu diamante + Lobii + Densitatea orbitei + Spikes + Lungimea vârfului + Dimensiunea corpului + Raza de soc + Lățimea cochiliei + Aruncat afară + Dimensiunea pupilei + Dimensiunea irisului + Variație de culoare + Catchlight + Dimensiunea ochilor + Densitatea barbului + Se întoarce + Camerele + Crestele + Pearlescență + Dimensiunea planetei + Înclinarea inelului + Lățimea inelului + Culoarea murdăriei + Culoare închisă a ierbii + Culoarea ierbii + Culoarea vârfului + Culoarea pământului închisă + Culoare uscată + Culoare pietriș + Culoare piele + Culoare beton + Culoarea gudronului + Culoare asfalt + Culoare piatra + Culoare praf + Culoarea solului + Culoare de muşchi închis + Culoarea mușchiului + Culoare roșie + Culoarea miezului + Culoare verde + Culoare cyan + Culoare magenta + Culoare hârtie + Culoarea pigmentului + Culoare secundară + Prima culoare + A doua culoare + Culoare închisă de oțel + Culoare deschisă din oțel + Culoarea oxidului + Culoare halo + Culoarea șuruburilor + Culoare catifea + Culoare strălucitoare + Culoare cerneală albastră + Culoare roșie de cerneală + Culoare de cerneală închisă + Culoare argintie + Culoare galbenă + Culoarea țesuturilor + Culoarea discului + Culoare fierbinte + Culoarea lentilelor + Culoarea exterioară + Culoare interioară + Culoare corona + Culoare rece + Culoare caldă + Culoarea norului + Culoarea flăcării + Culoare pene + Culoarea carcasei + Culoarea planetei + Culoarea inelului + Ștergeți fișierele originale după salvare + Doar fișierele sursă procesate cu succes vor fi șterse + Fișiere originale șterse: %1$d + Nu s-au șters fișierele originale: %1$d + Fișiere originale șterse: %1$d, eșuate: %2$d + Gesturi de foi + Permite tragerea foilor de opțiuni extinse + Subeșantionarea cromatică + Adâncime de biți + Fără pierderi + %1$d-bit + Redenumire lot + Redenumiți mai multe fișiere folosind modele de nume de fișiere + redenumirea loturilor fișierelor nume de fișier model secvență extensie de dată + Alegeți fișierele pentru a redenumi + Model de nume de fișier + Redenumiți + Sursa datei + Data EXIF ​​luată + Data modificării fișierului + Data creării fișierului + Data curentă + Data manuală + Data manuală + Timp manual + Data selectată nu este disponibilă pentru unele fișiere. Data curentă va fi folosită pentru ele. + Introduceți un model de nume de fișier + Modelul produce un nume de fișier invalid sau prea lung + Modelul produce nume de fișiere duplicat + Toate numele de fișiere se potrivesc deja cu modelul + Acest model folosește jetoane care nu sunt disponibile pentru redenumirea loturilor + Numele va rămâne același + Fișierele au fost redenumite cu succes + Unele fișiere nu au putut fi redenumite + Permisiunea nu a fost acordată + Folosește numele fișierului original fără extensie, ajutându-vă să păstrați intactă identificarea sursei. De asemenea, acceptă tăierea cu \\o{start:end}, transliterarea cu \\o{t} și înlocuirea cu \\o{s/old/new/}. + Contor cu incrementare automată. Acceptă formatarea personalizată: \\c{padding} (de exemplu, \\c{3} -&gt; 001), \\c{start:step} sau \\c{start:step:padding}. + Numele folderului părinte în care a fost localizat fișierul original. + Dimensiunea fișierului original. Acceptă unități: \\z{b}, \\z{kb}, \\z{mb} sau doar \\z pentru un format care poate fi citit de om. + Generează un identificator unic universal (UUID) aleatoriu pentru a asigura unicitatea numelui de fișier. + Redenumirea fișierelor nu poate fi anulată. Asigurați-vă că noile nume sunt corecte înainte de a continua. + Confirmați redenumirea + Setări din meniul lateral + Scara meniului + Meniu alfa + Balans de alb automat + Tăiere + Verificarea filigranelor ascunse + Depozitare + Limită cache + Goliți automat memoria cache atunci când crește peste această dimensiune + Interval de curățare + Cât de des să verificați dacă ar trebui șters memoria cache + La lansarea aplicației + 1 zi + 1 saptamana + 1 luna + Ștergeți automat memoria cache a aplicației în funcție de limita și intervalul selectat + Zona de cultură mobilă + Permite mutarea întregii zone de decupare prin tragerea în interiorul acesteia + Îmbinați GIF + Combinați mai multe clipuri GIF într-o singură animație + Comandă clipuri GIF + Verso + Joacă invers + Bumerang + Redați înainte și apoi înapoi + Normalizați dimensiunea cadrului + Scala rame pentru a se potrivi celui mai mare clip; opriți pentru a-și păstra scara inițială + Întârziere între clipuri (ms) + Pliant + Selectați toate imaginile dintr-un folder și subdosarele acestuia + Finder duplicat + Găsiți copii exacte și imagini similare vizual + duplicat finder imagini similare copii exacte fotografii curatare stocare dHash SHA-256 + Alegeți imagini pentru a găsi duplicate + Sensibilitate la similaritate + Copii exacte + Imagini similare + Selectați toate duplicatele exacte + Selectați toate, cu excepția celor recomandate + Nu s-au găsit duplicate + Încercați să adăugați mai multe imagini sau să creșteți sensibilitatea la similaritate + Nu s-au putut citi %1$d imagini + %1$s • %2$s recuperabil + %1$d grupuri • %2$s recuperabile + %1$d selectat • %2$s + Acest lucru nu poate fi anulat. Android vă poate cere să confirmați ștergerea într-un dialog de sistem. + Nu a fost găsit + Mutați pentru a începe + Mutați la final + \ No newline at end of file diff --git a/core/resources/src/main/res/values-ru/strings.xml b/core/resources/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000..3e85260 --- /dev/null +++ b/core/resources/src/main/res/values-ru/strings.xml @@ -0,0 +1,3742 @@ + + + + + Что-то пошло не так: %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, где я публикую бета-версии и объявления + Получайте уведомления о новых версиях приложения и читайте объявления + Пользовательские параметры + Параметры следует вводить по следующему шаблону: \"--{название_опции} {значение}\" + Автоматическая обрезка + Свободные углы + Приведение точек к границам изображения + Восстанавливающая кисть + Использовать ядро круга + Открытие + Закрытие + Морфологический градиент + Цилиндр + Черная шляпа + Вписывает изображение в заданный размер и добавляет блюр/цвет на задний фон + Расположение инструментов + Группировать инструменты по типу + Группирует инструменты на главном экране на основе их типа вместо пользовательского расположения + Избранное при группировке + Show favorite as last + Перемещает вкладку избранных инструментов в конец + Значения по умолчанию + Видимость системных панелей + Показывать системные панели по свайпу + Включает возможность показать скрытые системные панели по свайпу + Авто + Скрыть все + Показать все + Скрыть панель навигации + Скрыть панель уведомлений + Генерация шума + Создание различных шумов, таких как 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 + Искажайте изображение, рисуя пальцами + Деформация + Твердость + Режим деформации + Сила исчезновения + Верхняя обрезка + Нижняя обрезка + Левая обрезка + Правая обрезка + Загрузка + Гладкие формы + Используйте суперэллипсы вместо стандартных закругленных прямоугольников для получения более плавных и естественных форм. + Тип формы + Обрезанная + Закругленная + Гладкая + Острые края без закруглений + Классические закругленные углы. + Тип фигуры + Размер углов + Задержка анимации углов + Сквиркл + Элегантные закругленные элементы пользовательского интерфейса + Волнистая + Закругленные элементы интерфейса с мягким волнистым краем + Формат имени файла + Пользовательский текст, размещаемый в самом начале имени файла, идеально подходит для названий проектов, брендов или личных тегов. + Использует исходное имя файла без расширения, что помогает сохранить идентификацию источника без изменений. Также поддерживает срезы синтаксисом \\o{start:end}, транслитерацию \\o{t} и замену \\o{s/old/new/}. + Ширина изображения в пикселях, полезная для отслеживания изменений разрешения или масштабирования результатов. + Высота изображения в пикселях, полезная при работе с пропорциями или экспорте. + Генерирует случайные цифры, чтобы гарантировать уникальные имена файлов; добавьте больше цифр для дополнительной защиты от дубликатов. + Вставляет имя примененной предустановки в имя файла, чтобы вы могли легко запомнить, как было обработано изображение. + Имя родительской папки, в которой находился исходный файл. + Размер исходного файла. Поддерживает единицы измерения: \\z{b}, \\z{kb}, \\z{mb} или просто \\z для удобного формата. + Генерирует случайный уникальный идентификатор (UUID) для обеспечения уникальности имени файла. + Автоинкрементный счетчик. Поддерживает форматирование: \\c{padding} (напр. \\c{3} -> 001), \\c{start:step} или \\c{start:step:padding}. + Отображает режим масштабирования изображения, используемый во время обработки, помогая различать изображения с измененным размером, обрезанные или подогнанные. + Пользовательский текст, помещаемый в конце имени файла, полезный для управления версиями, например _v2, _edited или _final. + Расширение файла (png, jpg, webp и т. д.) автоматически соответствует фактическому сохраненному формату. + Настраиваемая временная метка, позволяющая определить собственный формат по спецификации Java для идеальной сортировки. + Тип броска + Масштаб длительности анимаций + Управляет скоростью анимаций приложения: 0 отключает движение, 1 — обычная скорость, большие значения замедляют анимации + 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. + Всегда очищать EXIF + Удалять EXIF данные изображения при сохранении, даже если инструмент запрашивает сохранение метаданных + Цвет фона для альфа-форматов + Добавляет возможность устанавливать цвет фона для каждого формата изображения с поддержкой альфа-канала, при отключении это доступно только для не-альфа-форматов. + Открыть проект + Продолжить редактирование ранее сохраненного проекта Image Toolbox + Невозможно открыть проект Image Toolbox + В проекте Image Toolbox отсутствуют данные проекта + Проект Image Toolbox поврежден. + Неподдерживаемая версия проекта Image Toolbox: %1$d. + Сохранить проект + Храните слои, фон и историю редактирования в редактируемом файле проекта. + Не удалось открыть + Запись в PDF с возможностью поиска + Распознавайте текст из пакета изображений и сохраняйте PDF с возможностью поиска с изображением и выбираемым текстовым слоем. + Прозрачность слоя + Горизонтальный флип + Вертикальный флип + Замок + Добавить тень + Цвет тени + Текстовая геометрия + Растяните или наклоните текст для более четкой стилизации. + Масштаб X + Наклон X + Удаление аннотаций + Удаление выбранных типов аннотаций, таких как ссылки, комментарии, выделение, фигуры или поля форм, со страниц PDF. + Гиперссылки + Вложения файлов + Линии + Всплывающие окна + Марки + Формы + Текстовые заметки + Текстовая разметка + Поля формы + Разметка + Неизвестный + Аннотации + Разгруппировать + Добавьте размытую тень за слоем с настраиваемым цветом и смещениями. + Контентное искажение + Недостаточно памяти для выполнения этого действия. Попробуйте использовать изображение меньшего размера, закройте другие приложения или перезапустите приложение. + Эти изображения не были сохранены, поскольку преобразованные файлы были бы больше оригиналов. Проверьте этот список перед удалением исходных изображений. + Пропущено файлов: %1$s + Логи приложения + Просмотрите логи приложения, чтобы выявить проблемы. + Обратная энергия + Использовать простую карту энергии на основе градиента вместо алгоритма прямой энергии + Удалить выделенную область + Швы будут проходить через замаскированную область, удаляя только выделенное вместо защиты + Добавляет избранное в виде вкладки, когда инструменты сгруппированы по типу. + Горизонтальный интервал + Вертикальный интервал + VHS NTSC + Расширенные настройки NTSC + Дополнительная аналоговая настройка для более сильного VHS и артефактов вещания + Цветность кровотечение + Лента износа + Отслеживание + мазок Люмы + Звонок + Снег + Использовать поле + Тип фильтра + Входной фильтр яркости + Входной фильтр нижних частот цветности + Демодуляция цветности + Фазовый сдвиг + Смещение фазы + Переключение головы + Высота переключения головки + Смещение переключения головки + Переключение головы + Положение средней линии + Джиттер средней линии + Отслеживание высоты шума + Отслеживание волны + Отслеживание снега + Отслеживание анизотропии снега + Отслеживание шума + Отслеживание интенсивности шума + Композитный шум + Частота составного шума + Интенсивность совокупного шума + Детализация составного шума + Частота звонка + Мощность звонка + Яркостный шум + Частота шума яркости + Интенсивность шума яркости + Детализация шума яркости + Цветовой шум + Частота цветного шума + Интенсивность цветного шума + Детализация цветного шума + Интенсивность снега + Анизотропия снега + Фазовый шум цветности + Ошибка фазы цветности + Задержка цветности по горизонтали + Задержка цветности по вертикали + Скорость ленты VHS + Потеря цветности VHS + VHS резкость интенсивности + Частота резкости VHS + Интенсивность краевой волны VHS + Скорость краевой волны VHS + Частота краевой волны VHS + Детали краевой волны VHS + Выходная цветность нижних частот + Масштабировать по горизонтали + Масштабировать по вертикали + Масштабный коэффициент X + Масштабный коэффициент Y + Обнаруживает распространенные водяные знаки изображений и закрашивает их с помощью LaMa. Автоматически загружает модели обнаружения и удаления + Дейтеранопия + Развернуть изображение + Удаление фона на основе сегментации объектов с использованием сегментации YOLO v11 + Показать угол линии + Показывает текущий поворот линии в градусах во время рисования. + Сохранить в исходную папку + Сохраняйте новые файлы рядом с исходным файлом вместо выбранной папки. + Водяной знак PDF + Не хватило памяти + Скорее всего, изображение слишком большое для обработки на этом устройстве, либо в системе закончилась доступная память. Попробуйте уменьшить разрешение изображения, закрыть другие приложения или выбрать файл поменьше. + Фоновая обработка была остановлена + Android не дал вовремя запустить уведомление фоновой обработки. Это системное ограничение по времени, которое нельзя надежно исправить. Перезапустите приложение и попробуйте снова; также может помочь не закрывать приложение и разрешить уведомления или фоновую работу. + Меню отладки + Меню для проверки функций приложения. Оно не должно отображаться в рабочей версии. + Шейдер + Пресет шейдера + Шейдер не выбран + Студия шейдеров + Создание, редактирование, проверка, импорт и экспорт фрагментных шейдеров + Сохраненные шейдеры + Открывайте сохраненные шейдеры, дублируйте, экспортируйте, делитесь или удаляйте их + Новый шейдер + Файл шейдера + JSON .itshader + Параметров: %1$d + Исходный код шейдера + Пишите только тело void main(). Используйте textureCoordinate для UV-координат и inputImageTexture как исходный sampler. + Функции + Необязательные helper-функции, константы и structs, которые вставляются перед void main(). Uniforms генерируются из параметров. + Добавьте uniforms здесь, если шейдеру нужны настраиваемые значения. + Сбросить шейдер + Текущий черновик шейдера будет очищен + Некорректный шейдер + Неподдерживаемая версия шейдера: %1$d. Поддерживаемая версия: %2$d. + Название шейдера не должно быть пустым. + Исходный код шейдера не должен быть пустым. + Исходный код шейдера содержит неподдерживаемые символы: %1$s. Используйте только ASCII GLSL. + Параметр \"%1$s\" объявлен больше одного раза. + Названия параметров не должны быть пустыми. + Параметр \"%1$s\" использует зарезервированное имя GPUImage. + Параметр \"%1$s\" должен быть корректным GLSL-идентификатором. + У параметра \"%1$s\" значение %2$s имеет тип \"%3$s\", ожидался \"%4$s\". + Параметр \"%1$s\" не может задавать min или max для bool-значений. + У параметра \"%1$s\" min больше max. + Значение по умолчанию параметра \"%1$s\" меньше min. + Значение по умолчанию параметра \"%1$s\" больше max. + Шейдер должен объявлять \"uniform sampler2D %1$s;\". + Шейдер должен объявлять \"varying vec2 %1$s;\". + Шейдер должен определять \"void main()\". + Шейдер должен записывать цвет в gl_FragColor. + Шейдеры ShaderToy с mainImage не поддерживаются. Используйте контракт GPUImage с void main(). + Анимированный uniform ShaderToy \"iTime\" не поддерживается. + Анимированный uniform ShaderToy \"iFrame\" не поддерживается. + Uniform ShaderToy \"iResolution\" не поддерживается. + Текстуры ShaderToy iChannel не поддерживаются. + Внешние текстуры не поддерживаются. + Расширения внешних текстур не поддерживаются. + Параметры шейдеров Libretro не поддерживаются. + Поддерживается только одна входная текстура. Удалите \"uniform %1$s %2$s;\". + Параметр \"%1$s\" должен быть объявлен как \"uniform %2$s %1$s;\". + Параметр \"%1$s\" объявлен как \"uniform %2$s %1$s;\", ожидалось \"uniform %3$s %1$s;\". + Файл шейдера пуст. + Файл шейдера должен быть JSON-объектом .itshader с полями version, name и shader. + Файл шейдера содержит некорректный JSON или не соответствует формату .itshader. + Поле \"%1$s\" обязательно. + %1$s обязательно. + %1$s \"%2$s\" не поддерживается. Поддерживаемые типы: %3$s. + %1$s обязательно для параметров \"%2$s\". + %1$s должно быть конечным числом. + %1$s должно быть целым числом. + %1$s должно быть true или false. + %1$s должно быть строкой цвета, массивом RGB/RGBA или объектом цвета. + %1$s должно использовать формат #RRGGBB или #RRGGBBAA. + %1$s должно содержать корректные шестнадцатеричные каналы цвета. + %1$s должно содержать 3 или 4 цветовых канала. + %1$s должно быть между 0 и 255. + %1$s должно быть массивом из двух чисел или объектом с x и y. + %1$s должно содержать ровно 2 числа. + Поставщик + PaddleOCR требует наличия на вашем устройстве дополнительных моделей ONNX. Вы хотите загрузить данные %1$s? + Универсальный + корейский + латинский + Восточнославянский + тайский + Греческий + Английский + кириллица + арабский + Деванагари + тамильский + телугу + Дублировать + Помощь и советы + %1$d учебных пособий + Открыть инструмент + Изучите основные инструменты, параметры экспорта, PDF-сценарии, цветовые утилиты и решения частых проблем. + С чего начать + Выбирайте нужные инструменты, импортируйте файлы, сохраняйте результаты и быстрее настраивайте рабочий процесс. + Редактирование изображений + Меняйте размер, обрезайте, применяйте фильтры, стирайте фон, рисуйте и добавляйте водяные знаки. + Файлы и метаданные + Конвертируйте форматы, сравнивайте результаты, защищайте приватность и управляйте именами файлов. + PDF и документы + Создавайте PDF, сканируйте документы, распознавайте текст на страницах и готовьте файлы к отправке. + Текст, QR-код и данные + Распознавайте текст, сканируйте коды, кодируйте Base64, проверяйте хэши и упаковывайте файлы. + Инструменты цвета + Выбирайте цвета, создавайте палитры, изучайте библиотеки цветов и создавайте градиенты. + Творческие инструменты + Создавайте изображения в формате SVG, ASCII, текстуры шума, шейдеры и экспериментальные визуальные эффекты. + Решение проблем + Разбирайтесь с тяжелыми файлами, прозрачностью, импортом, шарингом и отчетами об ошибках. + Выберите правильный инструмент + Используйте категории, поиск, избранное и действия «Поделиться», чтобы начать с нужного места. + Начните с лучшей точки входа + Image Toolbox имеет множество специализированных инструментов, и многие из них на первый взгляд совпадают. Начните с задачи, которую хотите завершить, а затем выберите инструмент, который контролирует наиболее важную часть результата: размер, формат, метаданные, текст, страницы PDF, цвета или макет. + Используйте поиск, если вам известно имя действия, например изменение размера, PDF, EXIF, OCR, QR или цвет. + Откройте категорию, когда вам известен только тип задачи, а затем закрепите часто используемые инструменты в избранном. + Когда другое приложение отправляет изображение в Image Toolbox, выберите подходящий инструмент в Android-меню «Поделиться». + Импортируйте, сохраняйте и делитесь результатами + Разберитесь в обычном пути: выбрать файл, настроить инструмент, сохранить или отправить результат. + Следуйте общему процессу редактирования. + Большинство инструментов работают по одной схеме: выберите файл, настройте параметры, проверьте предпросмотр, затем сохраните или отправьте результат. Сохранение создает файл, который можно найти позже, а «Поделиться» удобнее для быстрой передачи в другое приложение. + Выберите один файл для одиночных инструментов или несколько файлов для пакетных, если пикер это поддерживает. + Перед сохранением проверьте предпросмотр и параметры результата, особенно формат, качество и имя файла. + Используйте «Поделиться» для быстрой отправки или «Сохранить», если нужна постоянная копия в выбранной папке. + Работайте со многими изображениями одновременно + Пакетные инструменты лучше всего подходят для повторяющихся задач изменения размера, преобразования, сжатия и именования. + Подготовьте предсказуемую партию + Пакетная обработка быстрее всего работает, когда все результаты должны подчиняться одним правилам. Здесь же проще всего ошибиться сразу на множестве файлов, поэтому перед долгим экспортом заранее проверьте имя, качество, метаданные и папку сохранения. + Выберите все связанные изображения вместе и сохраните порядок, если результат зависит от последовательности. + Перед началом экспорта задайте правила изменения размера, формата, качества, метаданных и имен файлов. + Сначала запустите небольшой тестовый пакет, если исходные файлы большие или если целевой формат для вас новый. + Быстрое одиночное редактирование + Используйте универсальный редактор, если вам нужно несколько простых изменений в одном изображении. + Завершите одно изображение без смены инструментов + Одиночное редактирование удобно, когда изображению нужна небольшая цепочка правок, а не одна специализированная операция. Обычно это быстрее для быстрой очистки, предпросмотра и экспорта одной финальной копии без настройки пакетной обработки. + Откройте одиночное редактирование для одного изображения, которое требует общих настроек, разметки, обрезки, поворота или экспорта изменений. + Применяйте изменения в том порядке, в котором сначала изменяется наименьшее количество пикселей, например обрезка перед фильтрами и фильтры перед окончательным сжатием. + Вместо этого используйте специализированный инструмент, когда вам нужна пакетная обработка, точный целевой размер файла, вывод PDF, распознавание текста или очистка метаданных. + Используйте пресеты и настройки + Сохраняйте повторяющиеся параметры и подстраивайте приложение под свой рабочий процесс. + Избегайте повторения одной и той же настройки + Пресеты и настройки полезны, если вы часто экспортируете файлы одного типа. Хороший пресет заменяет повторяющийся ручной чеклист одним нажатием, а глобальные настройки задают значения, с которых новые инструменты стартуют по умолчанию. + Создавайте пресеты для частых размеров, форматов, значений качества или шаблонов именования. + Просмотрите настройки формата по умолчанию, качества, папки сохранения, имени файла, средства выбора и поведения интерфейса. + Создайте резервную копию настроек перед переустановкой приложения или переходом на другое устройство. + Установите полезные настройки по умолчанию + Выберите формат по умолчанию, качество, режим масштабирования, тип изменения размера и цветовое пространство один раз. + Сделайте новые инструменты ближе к вашей цели + Значения по умолчанию — это не просто косметические настройки. Они решают, какой формат, качество, поведение при изменении размера, режим масштабирования и цветовое пространство появляются первыми во многих инструментах, что помогает избежать повторного выбора одних и тех же вариантов при каждом экспорте. + Установите формат и качество изображения по умолчанию в соответствии с вашим обычным назначением, например JPEG для фотографий или PNG/WebP для альфа-канала. + Настройте тип изменения размера и режим масштабирования по умолчанию, если вы часто экспортируете файлы со строгими размерами, для социальных платформ или страниц документов. + Изменяйте настройки цветового пространства по умолчанию только в том случае, если ваш рабочий процесс требует единообразной обработки цвета на дисплеях, при печати или во внешних редакторах. + Пикер и лаунчер + Используйте настройки выбора, когда приложение открывает слишком много диалоговых окон или запускается не в том месте. + Сделайте так, чтобы открытие файлов соответствовало вашей привычке + Настройки пикера и запуска определяют, открывается ли Image Toolbox с главного экрана, сразу в инструменте или из сценария выбора файла. Они особенно полезны, если вы часто заходите через Android-меню «Поделиться» или хотите, чтобы приложение работало скорее как лаунчер инструментов. + Используйте настройки выбора фото, если системный пикер скрывает файлы, показывает слишком много недавнего или плохо работает с облачными файлами. + Включайте пропуск выбора только для инструментов, которые обычно открываете уже с переданным файлом или заранее выбранным источником. + Проверьте режим запуска, если хотите, чтобы Image Toolbox больше походил на быстрый выбор инструментов, а не на обычный главный экран. + Источник изображения + Выберите режим выбора, который лучше всего работает с галереями, файловыми менеджерами и облачными файлами. + Выбирайте файлы из правильного источника + Настройки источника изображения влияют на то, как приложение запрашивает файлы у Android. Лучший вариант зависит от того, где лежат изображения: в системной галерее, папке файлового менеджера, облаке или другом приложении, которое выдает временные файлы. + Используйте средство выбора по умолчанию, если вам нужен наиболее интегрированный в систему интерфейс для распространенных изображений галереи. + Переключите режим выбора, если альбомы, папки, облачные файлы или нестандартные форматы не отображаются там, где вы ожидаете. + Если общий файл исчезает после выхода из исходного приложения, снова откройте его с помощью постоянного средства выбора или сначала сохраните локальную копию. + Избранное и инструменты обмена + Держите главный экран чище и скрывайте инструменты, которые не нужны в сценариях «Поделиться». + Уменьшите шум в списке инструментов + Избранное и скрытие инструментов для «Поделиться» помогают, если каждый день вы используете только несколько экранов. Так список становится короче, а в сценариях шаринга не появляются инструменты, которым не подходит переданный тип файла. + Закрепите часто используемые инструменты, чтобы они были легко доступны, даже если инструменты сгруппированы по категориям. + Скрывайте инструменты из меню «Поделиться», если они не подходят для файлов, приходящих из другого приложения. + Используйте настройки порядка избранных, если вы предпочитаете, чтобы избранное располагалось в конце или сгруппировано со связанными инструментами. + Резервное копирование настроек + Безопасное перемещение пресетов, настроек и поведения приложения в другую установку. + Сохраняйте свою настройку переносимой + Резервное копирование и восстановление наиболее полезно после того, как вы настроили пресеты, папки, правила имен файлов, порядок инструментов и визуальные предпочтения. Резервная копия позволяет поэкспериментировать с настройками или переустановить приложение, не перестраивая рабочий процесс из памяти. + Создайте резервную копию после изменения настроек, поведения имени файла, форматов по умолчанию или расположения инструментов. + Сохраните резервную копию где-нибудь за пределами папки приложения, если вы планируете очистить данные, переустановить или переместить устройства. + Восстановите настройки перед обработкой важных пакетов, чтобы значения по умолчанию и поведение при сохранении соответствовали вашим старым настройкам. + Изменение размера и конвертирование изображений + Изменение размера, формата, качества и метаданных для одного или нескольких изображений. + Создание копий с измененным размером + Resize and Convert — основной инструмент для предсказуемых размеров и более легких файлов. Используйте его, когда одновременно важны размер изображения, выходной формат и поведение метаданных. + Выберите одно или несколько изображений, а затем выберите, какие размеры, масштаб или размер файла имеют наибольшее значение. + Выберите формат и качество; используйте PNG или WebP, если нужно сохранить прозрачность. + Просмотрите результат, затем сохраните или поделитесь созданными копиями, не меняя оригиналы. + Изменение размера по размеру файла + Установите ограничение размера, если веб-сайт, мессенджер или форма отклоняют тяжелые изображения. + Соблюдайте строгие ограничения на загрузку + Изменение размера файла по размеру лучше, чем угадывание значений качества, когда место назначения принимает только файлы ниже определенного предела. Инструмент может регулировать сжатие и размеры для достижения цели, стараясь при этом сохранить пригодный для использования результат. + Введите целевой размер немного ниже реального предела загрузки, чтобы оставить место для изменений метаданных и поставщиков. + Для фотографий выбирайте форматы с потерями, если нужен маленький файл; PNG может оставаться большим даже после изменения размера. + Проверяйте грани, текст и края после экспорта, поскольку агрессивные размеры могут привести к появлению видимых артефактов. + Ограничить изменение размера + Сжимайте только те изображения, которые превышают максимальную ширину, высоту или ограничения файла. + Избегайте изменения размера изображений, которые и так хороши. + Limit Resize полезен для смешанных папок, где часть изображений огромная, а часть уже готова. Вместо принудительного изменения размера каждого файла он оставляет подходящие изображения ближе к исходному качеству. + Установите максимальные размеры или ограничения в зависимости от места назначения, а не изменяйте размер каждого файла вслепую. + Включайте пропуск, если не хотите получать файлы тяжелее исходных. + Используйте это для пакетов с разных камер, снимков экрана или загрузок, где исходные размеры сильно различаются. + Обрезка, поворот и выпрямление + Создайте рамку важной области перед экспортом или использованием изображения в других инструментах. + Очистите состав. + Обрезка в первую очередь делает последующие фильтры, оптическое распознавание текста, страницы PDF и обмен результатами более чистыми. + Откройте «Обрезать» и выберите изображение, которое хотите настроить. + Используйте свободное кадрирование или соотношение сторон, если результат должен соответствовать сообщению, документу или аватару. + Поверните или переверните перед сохранением, чтобы последующие инструменты получили исправленное изображение. + Применяйте фильтры и корректировки + Создайте редактируемый стек фильтров и просмотрите результат перед экспортом. + Настройка внешнего вида изображения + Фильтры полезны для быстрой коррекции, стилизации вывода и подготовки изображений к распознаванию или печати. Небольшие изменения контрастности, резкости и яркости могут иметь большее значение, чем серьезные эффекты, если изображение содержит текст или мелкие детали. + Добавляйте фильтры один за другим и следите за предварительным просмотром после каждого изменения. + Регулируйте яркость, контрастность, резкость, размытие, цвет или маски в зависимости от задачи. + Экспортируйте только в том случае, если предварительный просмотр соответствует вашему целевому устройству, документу или социальной платформе. + Сначала предварительный просмотр + Проверьте изображения, прежде чем выбирать более тяжелый инструмент редактирования, преобразования или очистки. + Проверьте, что вы на самом деле получили + Предварительный просмотр изображения полезен, когда файл поступает из чата, облачного хранилища или другого приложения, и вы не уверены, что он содержит. Проверка размеров, прозрачности, ориентации и визуального качества в первую очередь помогает выбрать правильный инструмент для последующих действий. + Откройте «Предварительный просмотр изображения», если вам нужно просмотреть несколько изображений, прежде чем решить, что с ними делать. + Ищите проблемы с вращением, прозрачные области, артефакты сжатия и неожиданно большие размеры. + Переходите к параметрам «Изменить размер», «Обрезать», «Сравнить», «EXIF» или «Удалить фон» только после того, как вы узнаете, что нужно исправить. + Удаление или восстановление фона + Автоматически стирайте фон или улучшайте края вручную с помощью инструментов восстановления. + Оставьте тему, остальное удалите + Удаление фона работает лучше всего, когда вы сочетаете автоматический выбор с тщательной очисткой вручную. Окончательный формат экспорта имеет такое же значение, как и маска, поскольку JPEG сглаживает прозрачные области, даже если предварительный просмотр выглядит правильно. + Начните с автоматического удаления, если объект четко отделен от фона. + Увеличьте масштаб и переключайтесь между режимами стирания и восстановления, чтобы исправить волосы, углы, тени и мелкие детали. + Сохраните в формате PNG или WebP, если вам нужна прозрачность в конечном файле. + Рисование, разметка и установка водяных знаков + Добавляйте стрелки, заметки, выделение, подписи или повторяющиеся наложения водяных знаков. + Добавьте видимую информацию + Инструменты разметки помогают объяснить изображение, а водяные знаки помогают маркировать или защищать экспортированные файлы. + Используйте Draw, когда вам нужны рукописные заметки, фигуры, выделение или быстрые аннотации. + Используйте водяные знаки, если один и тот же текст или знак должен стабильно появляться на результатах. + Перед экспортом проверьте непрозрачность, положение и масштаб, чтобы метка была видна, но не отвлекала внимание. + Настройки рисования по умолчанию + Установите ширину линии, цвет, режим пути, блокировку ориентации и лупу для более быстрой разметки. + Сделайте рисунок предсказуемым + Настройки рисования полезны, когда вы часто комментируете снимки экрана, отмечаете документы или подписываете изображения. Значения по умолчанию сокращают время настройки, а лупа и блокировка ориентации упрощают точное редактирование на маленьких экранах. + Установите толщину линии и цвет по умолчанию для стрелок, выделений или подписей. + Используйте режим пути по умолчанию, если вы обычно рисуете одни и те же штрихи, фигуры или разметку. + Включите лупу или блокировку ориентации, если при вводе пальцем трудно точно разместить мелкие детали. + Макеты с несколькими изображениями + Выберите правильный инструмент для работы с несколькими изображениями, прежде чем упорядочивать снимки экрана, сканы или сравнительные полосы. + Используйте правильный инструмент макета + Эти инструменты звучат одинаково, но каждый из них настроен для своего типа вывода нескольких изображений. Выбор правильного варианта позволяет избежать борьбы с элементами управления макетом, которые были разработаны для другого результата. + Используйте Collage Maker для создания макетов с несколькими изображениями в одной композиции. + Используйте сшивку или наложение изображений, когда изображения должны стать одной длинной вертикальной или горизонтальной полосой. + Используйте разделение изображений, когда одно большое изображение нужно разделить на несколько частей меньшего размера. + Конвертируйте форматы изображений + Создавайте копии JPEG, PNG, WebP, AVIF, JXL и других файлов без редактирования пикселей вручную. + Выберите формат работы + Разные форматы лучше подходят для фотографий, снимков экрана, прозрачности, сжатия или совместимости. Преобразование будет наиболее безопасным, если вы понимаете, что поддерживает целевое приложение и содержит ли источник альфа-версию, анимацию или метаданные, которые вы хотите сохранить. + Используйте JPEG для совместимых фотографий, PNG для изображений без потерь и альфа-версий, а также WebP для компактного обмена. + Отрегулируйте качество, если формат поддерживает это; более низкие значения уменьшают размер, но могут добавить артефакты. + Сохраняйте оригиналы до тех пор, пока не убедитесь, что преобразованные файлы правильно открываются в целевом приложении. + Проверьте EXIF ​​и конфиденциальность + Просматривайте, редактируйте или удаляйте метаданные, прежде чем делиться конфиденциальными фотографиями. + Управление скрытыми данными изображения + EXIF может содержать данные камеры, даты, местоположение, ориентацию и другие детали, не видимые на изображении. Это стоит проверить, прежде чем публиковать в открытом доступе, отчеты о поддержке, списки на торговых площадках или любые фотографии, которые могут указывать на частное место. + Откройте инструменты EXIF, прежде чем делиться фотографиями, которые могут содержать информацию о местоположении или личном устройстве. + Удалите конфиденциальные поля или отредактируйте неверные теги, такие как дата, ориентация, автор или данные камеры. + Сохраните новую копию и поделитесь ею вместо оригинала, если конфиденциальность имеет значение. + Автоматическая очистка EXIF + Удаление метаданных по умолчанию при принятии решения о сохранении дат. + Сделайте конфиденциальность значением по умолчанию + Настройки EXIF полезны, если вы хотите удалять приватные метаданные автоматически, а не вспоминать об этом при каждом экспорте. Сохранение даты и времени — отдельное решение: даты помогают сортировать файлы, но тоже могут быть чувствительными. + Включите всегда чистый EXIF, если большая часть вашего экспорта предназначена для публичного или полупубличного обмена. + Включайте сохранение даты и времени только в том случае, если сохранение времени захвата более важно, чем удаление каждой временной метки. + Используйте ручное редактирование EXIF ​​для одноразовых файлов, в которых вам нужно сохранить некоторые поля и удалить другие. + Управление именами файлов + Используйте префиксы, суффиксы, временные метки, пресеты, checksum и порядковые номера. + Облегчите поиск экспорта + Хороший шаблон имени файла помогает сортировать пакетные результаты и предотвращает случайную перезапись. Это особенно полезно, когда экспорт сохраняется в исходную папку, где похожие имена быстро путают. + Настройте префикс, суффикс, метку времени, исходное имя, информацию о пресете или порядковые номера. + Используйте порядковые номера для пакетов, порядок которых имеет значение, например страниц, рамок или коллажей. + Включайте перезапись только в том случае, если вы уверены, что замена существующих файлов безопасна для текущей папки. + Перезаписать файлы + Заменяйте файлы с совпадающими именами только если это действительно нужно вашему процессу. + Используйте режим перезаписи осторожно + Overwrite Files меняет обработку конфликтов имен. Это удобно для повторного экспорта в одну и ту же папку, но может заменить старые результаты, если шаблон снова создает то же имя. + Включите перезапись только для папок, в которых ожидается замена старых сгенерированных файлов, которые можно восстановить. + Не отключайте перезапись при экспорте оригиналов, клиентских файлов, однократного сканирования или чего-либо еще без резервной копии. + Комбинируйте перезапись с продуманным шаблоном имени файла, чтобы заменялись только нужные результаты. + Шаблоны имен файлов + Объедините оригинальные имена, префиксы, суффиксы, временные метки, пресеты, режим масштабирования и контрольные суммы. + Создавайте имена, которые объясняют результат + Шаблоны имен файлов могут превратить экспорт в понятную историю обработки. Это особенно важно в пакетах, где папка может быть единственным местом, по которому видно исходный размер, пресет, формат и порядок файлов. + Используйте исходное имя файла, префикс, суффикс и метку времени, если вам нужны читаемые имена для ручной сортировки. + Добавьте пресет, режим масштабирования, размер файла или checksum, если имя должно показывать, как файл был создан. + Избегайте случайных имен для рабочих процессов, в которых файлы должны оставаться в паре с оригиналами или порядком страниц. + Выберите, где сохранять файлы + Избегайте потери экспорта, устанавливая папки, сохраняя исходную папку и места однократного сохранения. + Обеспечьте предсказуемость результатов + Поведение сохранения особенно важно при пакетной обработке и файлах из облака. Настройки папки решают, будут ли результаты складываться в одно место, появляться рядом с оригиналами или временно сохраняться в другую папку для конкретной задачи. + Установите папку сохранения по умолчанию, если вы хотите, чтобы экспорт всегда был в одном месте. + Используйте сохранение в исходную папку для очистки и правок, где результат должен лежать рядом с исходным файлом. + Используйте одноразовую папку сохранения, когда одной задаче нужна другая папка без изменения значения по умолчанию. + Папки для одноразового сохранения + Отправьте следующий экспорт во временную папку, не меняя обычное место сохранения. + Держите специальные задания отдельно + Одноразовая папка сохранения полезна для временных проектов, клиентских папок, сессий очистки или экспортов, которые не нужно смешивать с обычной папкой Image Toolbox. Так можно разово выбрать другую папку без изменения настроек по умолчанию. + Перед запуском задачи выберите одноразовую папку, которая находится за пределами обычного места экспорта. + Используйте его для коротких проектов, общих папок или пакетов, где результаты должны оставаться сгруппированными. + После задачи вернитесь к папке по умолчанию, чтобы следующие экспорты случайно не попадали во временное место. + Баланс качества и размера файла + Поймите, когда следует изменить размеры, формат или качество, вместо того, чтобы гадать + Сжимайте файлы намеренно + Размер файла зависит от разрешения, формата, качества, прозрачности и сложности изображения. Если файл все еще слишком тяжелый, уменьшение разрешения обычно помогает сильнее, чем многократное снижение качества. + Сначала уменьшайте размеры, если изображение больше, чем нужно месту публикации или приложению-получателю. + Снижайте качество только у форматов с потерями и после экспорта проверяйте текст, края, градиенты и лица. + Переключите формат, если изменений качества недостаточно, например WebP для совместного использования или PNG для четких прозрачных ресурсов. + Работа с анимированными форматами + Используйте инструменты GIF, APNG, WebP и JXL, если в файле есть кадры вместо одного растрового изображения. + Не рассматривайте каждое изображение как неподвижное + Для анимационных форматов нужны инструменты, которые понимают кадры, продолжительность и совместимость. + Используйте инструменты GIF или APNG, если вам нужны операции на уровне кадров для этих форматов. + Используйте инструменты WebP, если вам нужно современное сжатие или анимированный вывод WebP. + Проверьте анимацию перед публикацией: некоторые платформы конвертируют или превращают анимированные изображения в статичные. + Сравнение и предварительный просмотр вывода + Прежде чем сохранить экспорт, проверьте изменения, размер файла и визуальные различия. + Проверьте перед отправкой + Инструменты сравнения помогают заранее обнаружить артефакты сжатия, неправильные цвета или нежелательные изменения. + Откройте «Сравнение», если вы хотите проверить две версии рядом. + Увеличьте масштаб краев, текста, градиентов и прозрачных областей, где проблемы легче всего не заметить. + Если результат слишком насыщенный или размытый, вернитесь к предыдущему инструменту и отрегулируйте качество или формат. + Используйте центр инструментов PDF + Объединяйте, разделяйте, поворачивайте, удаляйте страницы, сжимайте, защищайте, распознавайте текст и добавляйте водяные знаки. + Выберите операцию PDF + PDF Tools собирает операции с документами в одном месте, чтобы можно было выбрать конкретное действие. Начинайте отсюда, если файл уже PDF; если исходник — набор изображений, используйте «Изображения в PDF» или «Сканер документов». + Откройте PDF Tools и выберите операцию, соответствующую вашей цели. + Выберите исходный PDF или изображения, затем проверьте порядок страниц, поворот, защиту или параметры OCR. + Сохраните созданный PDF-файл и откройте его один раз, чтобы проверить количество страниц, порядок и читаемость. + Превратите изображения в PDF + Объедините сканы, снимки экрана, квитанции или фотографии в один общий документ. + Создайте чистый документ + Изображения становятся более качественными страницами PDF, если их обрезать, упорядочивать и изменять их размер одинаково. Относитесь к списку изображений как к стопке страниц: каждый выбор поворота, поля и размера страницы влияет на то, насколько читаемым будет окончательный документ. + Сначала обрезайте или поворачивайте изображения, если края страницы, тени или ориентация неправильны. + Выберите изображения в заданном порядке страниц и отрегулируйте размер страницы или поля, если инструмент предлагает их. + Экспортируйте PDF-файл, а затем перед отправкой убедитесь, что все страницы читаемы. + Сканировать документы + Сохраняйте бумажные страницы и подготавливайте их для PDF, OCR или совместного использования. + Захват читаемых страниц + Сканирование документов лучше всего работает с плоскими страницами, хорошим освещением и исправленной перспективой. Чистое сканирование перед распознаванием текста или экспортом в PDF экономит время, поскольку распознавание и сжатие текста зависят от качества страницы. + Поместите документ на контрастную поверхность с достаточным количеством света и минимальным количеством теней. + Отрегулируйте обнаруженные углы или обрежьте вручную, чтобы страница ровно заполняла рамку. + Экспортируйте как изображения или PDF, а затем запустите OCR, если вам нужен текст, который можно выделить. + Защитите или разблокируйте PDF-файлы + Используйте пароли осторожно и по возможности сохраняйте редактируемую исходную копию. + Безопасное управление доступом к PDF-файлам + Инструменты защиты полезны для контролируемой отправки документов, но пароли легко потерять и трудно восстановить. Защищайте копии для распространения, храните исходные файлы отдельно и проверяйте результат перед удалением чего-либо. + Защитите копию PDF-файла, а не единственный исходный документ. + Прежде чем делиться защищенным файлом, сохраните пароль в безопасном месте. + Используйте разблокировку только для файлов, которые вам разрешено редактировать, а затем убедитесь, что разблокированная копия открывается правильно. + Организация страниц PDF + Объединяйте, разделяйте, переставляйте, поворачивайте, обрезайте, удаляйте страницы и намеренно добавляйте номера страниц. + Исправление структуры документа перед оформлением + Инструменты для страниц PDF проще всего использовать в том же порядке, в котором вы готовите бумажный документ: соберите страницы, удалите неправильные, упорядочите их, исправьте ориентацию, а затем добавьте окончательные детали, такие как номера страниц или водяные знаки. + Сначала используйте слияние или преобразование изображений в PDF, если документ все еще разделен на несколько файлов. + Переставляйте, поворачивайте, обрезайте или удаляйте страницы перед добавлением номеров страниц, подписей или водяных знаков. + Предварительный просмотр окончательного PDF-файла после структурных изменений, поскольку одну отсутствующую или повернутую страницу позже будет трудно заметить. + PDF-аннотации + Удаляйте или сводите аннотации, если разные просмотрщики показывают комментарии и пометки по-разному. + Контролируйте то, что остается видимым + Аннотации могут представлять собой комментарии, выделение, рисунки, пометки формы или другие дополнительные объекты PDF. Некоторые программы просмотра отображают их по-разному, поэтому их удаление или объединение может сделать общие документы более предсказуемыми. + Удалите аннотации, если комментарии, выделения или отметки обзора не должны быть включены в общую копию. + Сводите аннотации, когда видимые пометки должны остаться на странице, но больше не должны редактироваться как отдельные объекты. + Сохраните оригинал, потому что удаление или сведение аннотаций сложно откатить. + Распознать текст + Извлекайте текст из изображений с помощью выбираемых механизмов оптического распознавания символов и языков. + Получите лучшие результаты оптического распознавания символов + Точность распознавания зависит от резкости изображения, языковых данных, контрастности и ориентации страницы. Наилучшие результаты обычно достигаются, если сначала подготовить изображение: убрать шум, повернуть вертикально, повысить читаемость, а затем распознать текст. + Обрежьте изображение до текстовой области и поверните его вертикально перед распознаванием. + Выберите правильный язык и загрузите языковые данные, если приложение этого запросит. + Скопируйте или поделитесь распознанным текстом, а затем вручную проверьте имена, цифры и пунктуацию. + Выберите данные языка OCR + Улучшите распознавание, сопоставляя сценарии, языки и загруженные модели OCR. + Сопоставьте OCR с текстом + Из-за неправильного языка распознавания хорошие фотографии могут выглядеть как плохое распознавание. Данные о языке важны для написания букв, знаков препинания, цифр и смешанных документов, поэтому выбирайте язык, который отображается на изображении, а не язык пользовательского интерфейса телефона. + Выберите язык или алфавит, который отображается на изображении, а не только язык телефона. + Загрузите недостающие данные перед работой в автономном режиме или обработкой пакета. + Для документов на разных языках сначала запустите наиболее важный язык и вручную проверьте имена и номера. + PDF-файлы с возможностью поиска + Запишите текст OCR обратно в файлы, чтобы можно было искать и копировать отсканированные страницы. + Превратите сканы в документы с возможностью поиска + OCR может не только копировать текст в буфер обмена. Когда вы записываете распознанный текст в файл или PDF-файл с возможностью поиска, изображение страницы остается видимым, а текст становится легче искать, выбирать, индексировать или архивировать. + Используйте PDF-файл с возможностью поиска для отсканированных документов, которые по-прежнему должны выглядеть как исходные страницы. + Используйте запись в файл, когда вам нужен только извлеченный текст и не важно изображение страницы. + Проверяйте важные номера, имена и таблицы вручную, поскольку текст OCR может быть доступен для поиска, но все равно несовершенен. + Сканировать QR-коды + Чтение QR-содержимого со входа камеры или сохраненных изображений, если они доступны. + Читайте коды безопасно + QR-коды могут содержать ссылки, данные Wi-Fi, контакты, текст и другие данные. + Откройте «Сканировать QR-код» и сохраните код четким, плоским и полностью внутри рамки. + Прежде чем открывать ссылки или копировать приватные данные, проверьте расшифрованное содержимое. + Если сканирование не удалось, улучшите освещение, обрежьте код или попробуйте исходное изображение с более высоким разрешением. + Используйте Base64 и контрольные суммы + Кодируйте изображения как текст, декодируйте текст обратно в изображения и проверяйте целостность файла. + Перемещение между файлами и текстом + Base64 полезен для небольших изображений, которые нужно временно превратить в текст, а checksum помогает подтвердить, что файл не изменился. Эти инструменты пригодятся в технических сценариях, отчетах об ошибках и передаче файлов. + Используйте инструменты Base64 для кодирования небольшого изображения, когда рабочему процессу требуется текст, а не двоичный файл. + Декодируйте Base64 только из надежных источников и проверяйте предварительный просмотр перед сохранением. + Используйте инструменты контрольной суммы, когда вам нужно сравнить экспортированные файлы или подтвердить загрузку/загрузку. + Упакуйте или защитите данные + Используйте инструменты ZIP и шифрования для объединения файлов или преобразования текстовых данных. + Храните связанные файлы вместе + Упаковка полезна, когда несколько результатов должны передаваться одним файлом. Так удобно держать вместе сгенерированные изображения, PDF, логи и метаданные, если нужно отправить полный набор. + Используйте ZIP, когда вам нужно отправить много экспортированных изображений или документов вместе. + Используйте инструменты шифрования только в том случае, если вы понимаете выбранный метод и можете безопасно хранить необходимые ключи. + Протестируйте созданный архив или преобразованный текст перед удалением исходных файлов. + Контрольные суммы в именах + Используйте имена файлов на основе контрольной суммы, когда уникальность и целостность важнее читаемости. + Называйте файлы по содержимому + Checksum в имени полезен, когда у экспортов могут повторяться обычные названия или нужно доказать, что два файла идентичны. Такие имена хуже читаются человеком, поэтому лучше подходят для технических архивов и проверки. + Включите контрольную сумму в качестве имени файла, если идентичность содержимого важнее, чем удобочитаемый заголовок. + Используйте его для архивов, дедупликации, повторяемого экспорта или файлов, которые можно загружать и скачивать повторно. + Сочетайте имена контрольных сумм с папками или метаданными, когда людям все еще нужно понять, что представляет собой файл. + Выбирайте цвета из изображений + Пробуйте пиксели, копируйте цветовые коды и повторно используйте цвета в палитрах или проектах. + Образец точного цвета + Выбор цвета полезен для подбора дизайна, извлечения цветов бренда или проверки значений изображения. Масштабирование имеет значение, поскольку сглаживание, тени и сжатие могут привести к тому, что соседние пиксели будут немного отличаться от ожидаемого цвета. + Откройте «Выбрать цвет из изображения» и выберите исходное изображение с нужным вам цветом. + Увеличьте масштаб перед выборкой мелких деталей, чтобы близлежащие пиксели не повлияли на результат. + Скопируйте цвет в формате, требуемом местом назначения, например HEX, RGB или HSV. + Создавайте палитры и используйте библиотеку цветов. + Извлекайте палитры, просматривайте сохраненные цвета и сохраняйте согласованность визуальных систем. + Создайте многоразовую палитру + Палитры помогают сохранять визуальное единообразие графики, водяных знаков и рисунков. Они особенно полезны, когда вы часто размечаете скриншоты, готовите брендовые материалы или используете одни и те же цвета в разных инструментах. + Создайте палитру из изображения или добавьте цвета вручную, если значения уже известны. + Назовите или систематизируйте важные цвета, чтобы вы могли найти их позже. + Используйте скопированные значения в Draw, водяных знаках, градиентах, SVG или внешних инструментах дизайна. + Создание градиентов + Создавайте градиентные изображения для фона, заполнителей и визуальных экспериментов. + Контролируйте цветовые переходы + Градиенты полезны, когда нужно создать новое изображение, а не редактировать существующее. Их можно использовать как фон, плейсхолдер, обои, маску или простой материал для внешних дизайн-инструментов. + Выберите тип градиента и сначала установите важные цвета. + Отрегулируйте направление, остановки, плавность и размер вывода для целевого варианта использования. + Экспортируйте в формат, соответствующий месту назначения, обычно PNG для чистой сгенерированной графики. + Доступность цвета + Используйте динамические цвета, контрастность и параметры дальтонизма, когда пользовательский интерфейс трудно читать. + Упростите использование цветов + Настройки цвета влияют на комфорт, читаемость и скорость сканирования элементов управления. Если элементы инструментов, ползунки или выбранные состояния трудно различить, параметры темы и специальных возможностей могут оказаться более полезными, чем простое изменение темного режима. + Попробуйте динамические цвета, если хотите, чтобы приложение соответствовало текущей палитре обоев. + Увеличьте контрастность или измените схему, если кнопки и поверхности недостаточно выделяются. + Используйте варианты схемы для дальтоников, если некоторые состояния или акценты трудно различить. + Альфа-фон + Управляйте предпросмотром или цветом заливки для прозрачных PNG, WebP и похожих результатов. + Понимание прозрачного предварительного просмотра + Цвет фона для альфа-форматов может облегчить проверку прозрачных изображений, но важно знать, меняете ли вы поведение предварительного просмотра/заливки или выравниваете конечный результат. Это актуально для наклеек, логотипов и изображений с удаленным фоном. + Если прозрачные пиксели должны сохраниться при экспорте, сначала используйте формат с поддержкой альфа-канала, например PNG или WebP. + Отрегулируйте настройки цвета фона, если прозрачные края плохо различимы на поверхности приложения. + Экспортируйте небольшой тест и снова откройте его в другом приложении, чтобы убедиться, что прозрачность или выбранная заливка сохранены. + Выбор модели ИИ + Выбирайте модель по типу изображения и дефекту вместо того, чтобы сначала выбирать самую большую модель. + Сопоставьте модель с повреждением + AI Tools может загружать или импортировать различные модели ONNX/ORT, и каждая модель подготовлена ​​для решения определенного типа проблем. Модель для блоков JPEG, очистка строк аниме, шумоподавление, удаление фона, раскрашивание или масштабирование могут привести к худшим результатам при использовании с неправильным типом изображения. + Перед загрузкой прочтите описание модели; выберите модель, которая описывает вашу реальную проблему, например сжатие, шум, полутона, размытие или удаление фона. + При тестировании нового типа изображения начните с меньшей или более конкретной модели, а затем сравнивайте ее с более тяжелыми моделями, только если результат окажется недостаточно хорошим. + Сохраняйте только те модели, которые вы действительно используете, поскольку загруженные и импортированные модели могут занимать заметное место на диске. + Понимание модельных семейств + Названия моделей часто подсказывают назначение: RealESRGAN-подобные модели увеличивают изображение, SCUNet и FBCNN чистят шум или сжатие, background-модели создают маски, а colorizer добавляет цвет к grayscale-изображениям. Импортированные модели могут быть мощными, но должны совпадать с ожидаемым входом/выходом и использовать поддерживаемый ONNX или ORT. + Используйте апскейлеры, когда вам нужно больше пикселей, шумоподавители, когда изображение зернистое, и средства удаления артефактов, когда основной проблемой являются блоки сжатия или полосы. + Используйте фоновые модели только для разделения предметов; это не то же самое, что обычное удаление объектов или улучшение изображения. + Импортируйте пользовательские модели только из источников, которым вы доверяете, и проверяйте их на небольшом изображении, прежде чем использовать важные файлы. + Параметры ИИ + Настройте размер фрагмента, перекрытие и параллельные рабочие процессы, чтобы сбалансировать качество, скорость и объем памяти. + Баланс скорости и стабильности + Размер фрагмента определяет, насколько велики фрагменты изображения до их обработки моделью. Перекрытие смешивает соседние фрагменты, чтобы скрыть границы. Параллельные рабочие процессы могут ускорить обработку, но каждому рабочему процессу требуется память, поэтому высокие значения могут привести к нестабильной работе больших изображений на телефонах с ограниченной оперативной памятью. + Используйте более крупные фрагменты для более гладкого контекста, когда ваше устройство надежно обрабатывает модель и изображение невелико. + Увеличьте перекрытие, когда границы фрагментов станут видимыми, но помните, что большее перекрытие означает более повторяющуюся работу. + Поднимайте параллельные воркеры только после стабильного теста; если обработка замедляется, нагревает устройство или происходит сбой, сначала сократите число рабочих процессов. + Избегайте артефактов фрагментов + Чанкинг позволяет приложению обрабатывать изображения, которые больше, чем модель может с легкостью обработать одновременно. Компромисс заключается в том, что каждая плитка имеет меньше окружающего контекста, поэтому низкое перекрытие или слишком маленькие фрагменты могут создавать видимые границы, изменения текстуры или противоречивые детали между соседними областями. + Увеличьте перекрытие, если вы видите прямоугольные границы, повторяющиеся изменения текстуры или переходы деталей между обработанными областями. + Уменьшите размер фрагмента, если процесс завершается сбоем, прежде чем создавать выходные данные, особенно для больших изображений или тяжелых моделей. + Сохраните небольшой тест обрезки перед обработкой полного изображения, чтобы можно было быстро сравнить параметры. + Стабильность ИИ + Уменьшение размера фрагмента, рабочих процессов и разрешения источника при сбое или зависании обработки ИИ. + Восстановление после тяжелой работы ИИ + Обработка ИИ — один из самых тяжелых рабочих процессов в приложении. Сбои, неудачные сеансы, зависания или внезапные перезапуски приложений обычно означают, что модель, разрешение источника, размер фрагмента или количество параллельных рабочих процессов слишком требовательны к текущему состоянию устройства. + Если приложение выходит из строя или не удается открыть сеанс модели, уменьшите количество параллельных рабочих процессов и размер фрагмента, а затем повторите попытку того же изображения. + Измените размер очень больших изображений перед обработкой ИИ, если цель не требует исходного разрешения. + Закройте другие тяжелые приложения, используйте модель меньшего размера или обрабатывайте меньше файлов одновременно, когда нехватка памяти продолжает возвращаться. + Диагностика сбоев модели + Неудачный запуск ИИ не всегда означает, что изображение плохое. Файл модели может быть неполным, неподдерживаемым, слишком большим для памяти или не соответствовать операции. Когда приложение сообщает о сбое сеанса, воспринимайте это как сигнал, чтобы сначала упростить модель и параметры. + Удалите и повторно загрузите модель, если она дает сбой сразу после загрузки или ведет себя так, как будто файл поврежден. + Прежде чем обвинять импортированную пользовательскую модель, попробуйте встроенную загружаемую модель, поскольку импортированные модели могут иметь несовместимые входные данные. + Используйте журналы приложений после воспроизведения сбоя, если вам нужно сообщить о сбое или ошибке сеанса. + Экспериментируйте в Shader Studio. + Используйте эффекты шейдеров графического процессора для расширенной обработки изображений и индивидуального внешнего вида. + Аккуратно работайте с шейдерами + Shader Studio — это мощный инструмент, но код и параметры шейдера требуют небольших, проверяемых изменений. Относитесь к нему как к экспериментальному инструменту: сохраняйте рабочую базовую линию, меняйте что-то одно за раз и тестируйте с изображениями разных размеров, прежде чем доверять предустановке. + Прежде чем добавлять параметры, начните с известного рабочего шейдера или простого эффекта. + Изменяйте по одному параметру или блоку кода и проверяйте предварительный просмотр на наличие ошибок. + Экспортируйте пресеты только после их тестирования на изображениях нескольких разных размеров. + Создайте изображение в формате SVG или ASCII. + Создавайте векторные ресурсы или текстовые изображения для творческих результатов. + Выберите тип вывода креатива + Инструменты SVG и ASCII служат разным целям: масштабируемая графика или рендеринг в текстовом стиле. Оба являются творческими преобразованиями, поэтому предварительный просмотр в окончательном размере более важен, чем оценка результата по крошечному миниатюре. + Используйте SVG Maker, когда результат необходимо масштабировать или повторно использовать в рабочих процессах проектирования. + Используйте ASCII Art, если вам нужно стилизованное текстовое представление изображения. + Предварительный просмотр в окончательном размере, поскольку мелкие детали могут исчезнуть в сгенерированном выводе. + Генерация текстур шума + Создавайте процедурные текстуры для фона, масок, наложений или экспериментов. + Настройка процедурного вывода + Генерация шума создает новые изображения на основе параметров, поэтому небольшие изменения могут привести к совершенно другим результатам. Это полезно для текстур, масок, наложений, заполнителей или тестирования сжатия с подробными шаблонами. + Прежде чем настраивать мелкие детали, выберите тип шума и выходной размер. + Отрегулируйте масштаб, интенсивность, цвета или начальный размер, пока узор не будет соответствовать целевому использованию. + Сохраните полезные результаты и запишите параметры, если вам понадобится воссоздать текстуру позже. + Разметка проектов + Используйте файлы проекта, если аннотации должны оставаться доступными для редактирования после закрытия приложения. + Обеспечьте возможность повторного использования многоуровневых изменений + Файлы проекта разметки полезны, когда снимок экрана, диаграмма или изображение инструкции могут потребоваться изменения позже. Экспортированные файлы PNG/JPEG являются окончательными изображениями, а файлы проекта сохраняют редактируемые слои и состояние редактирования для будущих корректировок. + Используйте слои разметки, когда аннотации, выноски или элементы макета должны оставаться редактируемыми. + Сохраните или повторно откройте файл проекта перед экспортом окончательного изображения, если вы ожидаете дополнительных изменений. + Экспортируйте обычное изображение только тогда, когда вы готовы поделиться сведенной окончательной версией. + Шифровать файлы + Защитите файлы паролем и проверьте расшифровку перед удалением исходной копии. + Осторожно обращайтесь с зашифрованными файлами + Инструменты шифрования могут защитить файлы паролем, но не заменяют хранение ключа и резервных копий. Шифрование полезно для передачи и хранения, а ZIP лучше подходит, когда главная цель — просто объединить несколько результатов. + Сначала зашифруйте копию и сохраните оригинал, пока не проверите, работает ли расшифровка с сохраненным паролем. + Используйте менеджер паролей или другое безопасное место для ключа; приложение не сможет угадать за вас потерянный пароль. + Делитесь зашифрованными файлами только с людьми, у которых также есть пароль и которые знают, какой инструмент или метод должен их расшифровать. + Расположите инструменты + Группируйте, упорядочивайте, ищите и закрепляйте инструменты, чтобы главный экран соответствовал вашему реальному рабочему процессу. + Сделайте главный экран быстрее + Настройки расположения инструментов стоит настроить, если вы знаете, какие инструменты вы используете чаще всего. Группировка помогает исследовать, индивидуальный порядок помогает мышечной памяти, а избранное сохраняет доступ к ежедневным инструментам, даже когда полный список становится большим. + Используйте сгруппированный режим при изучении приложения или если вы предпочитаете инструменты, организованные по типу задач. + Перейдите на свой порядок, когда уже знаете частые инструменты и хотите меньше прокрутки. + Используйте настройки поиска и избранное вместе для редких инструментов, которые вы помните по названию, но не хотите, чтобы они были видны постоянно. + Обработка больших файлов + Избегайте медленного экспорта, нехватки памяти и неожиданно большого объема вывода. + Сокращение работы перед экспортом + Очень большие изображения, длинные пакеты и форматы с высоким качеством требуют больше памяти и времени. Самое быстрое решение обычно в том, чтобы уменьшить объем работы перед тяжелой операцией, а не ждать завершения той же слишком большой задачи. + Измените размер огромных изображений перед применением тяжелых фильтров, оптического распознавания символов или преобразования PDF. + Сначала используйте меньшую партию для подтверждения настроек, а затем обработайте остальные с той же предустановкой. + Уменьшите качество или измените формат, если выходной файл больше ожидаемого. + Кэш и превью + Понимание созданных предварительных просмотров, очистки кэша и использования хранилища после тяжелых сеансов. + Сохраняйте баланс между объемом памяти и скоростью + Генерация предварительного просмотра и кэш могут ускорить повторный просмотр, но тяжелые сеансы редактирования могут оставить временные данные. Настройки кэша помогают, когда приложение работает медленно, не хватает памяти или предварительный просмотр выглядит устаревшим после многих экспериментов. + Включите предварительный просмотр, когда просмотр большого количества изображений более важен, чем минимизация временного хранилища. + Очистите кеш после больших пакетов, неудачных экспериментов или длительных сеансов с большим количеством файлов с высоким разрешением. + Используйте автоматическую очистку кэша, если вы предпочитаете очистку хранилища, а не сохранение предварительного просмотра между запусками. + Настройка поведения экрана + Используйте полноэкранный режим, безопасный режим, яркость и параметры системной панели для сосредоточенной работы. + Сделайте интерфейс соответствующим ситуации + Настройки экрана помогают при редактировании в альбомной ориентации, работе с приватными изображениями или необходимости фиксированной яркости. Обычно они не нужны, но выручают при проверке QR-кодов, приватном предпросмотре и тесном редактировании в landscape. + Используйте полноэкранный режим или настройки системных панелей, когда рабочее пространство важнее навигационных элементов. + Используйте безопасный режим, когда личные изображения не должны появляться на снимках экрана или в предварительном просмотре приложений. + Используйте принудительное регулирование яркости для инструментов, где темные изображения или QR-коды трудно проверить. + Сохраняйте прозрачность + Предотвращает превращение прозрачного фона в белый, черный или сплющенный. + Использовать вывод с поддержкой альфа-канала + Прозрачность сохраняется только в том случае, если выбранный инструмент и выходной формат поддерживают альфа-канал. Если экспортированный фон становится белым или черным, проблема обычно заключается в выходном формате, настройке заливки/фона или целевом приложении, которое сгладило изображение. + Выбирайте PNG или WebP при экспорте изображений, наклеек, логотипов или наложений с удаленным фоном. + Избегайте JPEG для прозрачного вывода, поскольку он не сохраняет альфа-канал. + Проверьте настройки, связанные с цветом фона для альфа-форматов, если при предварительном просмотре фон отображается с заливкой. + Устранение проблем с общим доступом и импортом + Используйте настройки средства выбора и поддерживаемые форматы, если файлы не отображаются или не открываются правильно. + Загрузите файл в приложение + Проблемы с импортом часто возникают из-за разрешений поставщика, неподдерживаемых форматов или поведения средства выбора. Файлы, предоставленные облачными приложениями и мессенджерами, могут быть временными, поэтому выбор постоянного источника может быть более надежным при длительном редактировании. + Попробуйте открыть файл через средство выбора системы вместо ярлыка недавних файлов. + Если формат не поддерживается одним инструментом, сначала преобразуйте его или откройте более конкретный инструмент. + Проверьте настройки выбора изображений и запуска, если шаринг или прямое открытие инструмента работают не так, как ожидалось. + Подтверждение выхода + Не теряйте несохраненные изменения при случайных жестах, нажатиях назад или переключении инструментов. + Защитите незавершенную работу + Подтверждение выхода полезно, если вы используете жестовую навигацию, редактируете большие файлы или часто переключаетесь между приложениями. Оно добавляет короткую паузу перед закрытием инструмента, чтобы незавершенные настройки и предпросмотр не потерялись случайно. + Включите подтверждение, если случайные жесты назад когда-либо закрывали инструмент перед экспортом. + Оставьте его отключенным, если вы в основном выполняете быстрые одношаговые преобразования и предпочитаете более быструю навигацию. + Используйте его вместе с пресетами для более длительных рабочих процессов, когда перестроение настроек может раздражать. + Используйте журналы при сообщении о проблемах + Собирайте полезную информацию, прежде чем открывать проблему или обращаться к разработчику. + Сообщать о проблемах с контекстом + Четкий отчет с указанием шагов, версии приложения, типа ввода и журналов значительно упрощает диагностику. Журналы наиболее полезны сразу после воспроизведения проблемы, поскольку они сохраняют свежий контекст. + Запишите имя инструмента, точное действие и то, происходит ли оно с одним файлом или с каждым файлом. + Откройте журналы приложений после воспроизведения проблемы и по возможности поделитесь соответствующими выводами журнала. + Прикрепляйте скриншоты или файлы примеров только в том случае, если они не содержат личной информации. + Пропустить выбор файла + Открывайте инструменты быстрее, если файл обычно приходит из «Поделиться», буфера обмена или предыдущего экрана. + Ускорьте повторный запуск инструментов + Skip File Picking меняет первый шаг в поддерживаемых инструментах. Это полезно, когда инструмент обычно открывается уже с выбранным изображением или через Android «Поделиться», но может сбивать с толку, если вы ждете, что каждый инструмент сразу спросит файл. + Включайте это только если ваш обычный сценарий уже передает исходное изображение до открытия инструмента. + Держите его отключенным во время изучения приложения, поскольку явный выбор упрощает понимание каждого процесса работы с инструментом. + Если инструмент открывается без очевидного ввода, отключите этот параметр или начните с «Поделиться/Открыть с помощью», чтобы Android сначала передавал файл. + Сначала откройте редактор + Пропустить предварительный просмотр, когда общее изображение должно сразу перейти к редактированию. + Выберите предварительный просмотр или редактирование в качестве первой остановки + Функция «Открыть редактирование вместо предварительного просмотра» предназначена для пользователей, которые уже знают, что хотят изменить изображение. Предварительный просмотр безопаснее, если вы проверяете файлы из чата или облачного хранилища; Прямое редактирование снимков экрана, быстрой обрезки, аннотаций и разовых исправлений выполняется быстрее. + Включите прямое редактирование, если для большинства общих изображений необходимо немедленно обрезать, разметить, отфильтровать или экспортировать изменения. + Если вы часто проверяете метаданные, размеры, прозрачность или качество изображения, прежде чем принять решение, сначала оставьте предварительный просмотр. + Используйте это вместе с избранным, чтобы общие изображения находились рядом с инструментами, которые вы действительно используете. + Ввод значений пресетов + Вводите точные значения пресетов вместо долгой настройки ползунками. + Используйте точные значения, когда ползунки работают медленно + Ввод значений пресетов помогает, когда нужны точные числа для повторяющейся работы: размеры изображений для соцсетей, поля документа, целевой размер сжатия, ширина линий или другие параметры, до которых неудобно добираться жестами. На маленьких экранах это особенно полезно. + Включите ввод текста, если вы часто повторно используете точные размеры, проценты, значения качества или параметры инструмента. + Сохраните значение как пресет после первого ввода и переиспользуйте его вместо повторной настройки. + Оставьте жесты для грубой визуальной настройки, а точный ввод — для строгих требований к результату. + Сохранить близко к оригиналу + Размещайте сгенерированные файлы рядом с исходными изображениями, если контекст папки имеет значение. + Храните результаты вместе с их источниками + Сохранить в исходную папку удобно для папок камеры, папок проектов, сканированных документов и клиентских пакетов, где отредактированная копия должна оставаться рядом с исходным файлом. Это может быть менее аккуратно, чем выделенная выходная папка, поэтому комбинируйте ее с четкими суффиксами или шаблонами имен файлов. + Включите его, когда каждая исходная папка уже имеет смысл и выходные данные должны оставаться там сгруппированными. + Используйте суффиксы, префиксы, временные метки или шаблоны имен файлов, чтобы отредактированные копии можно было легко отличить от оригиналов. + Отключите его для смешанных пакетов, если вы хотите, чтобы все сгенерированные файлы были собраны в одной папке экспорта. + Пропускать файлы больше исходника + Не сохраняйте результаты, которые неожиданно становятся тяжелее исходных файлов. + Защитите партии от неприятных сюрпризов, связанных с размером + Некоторые преобразования увеличивают файлы, особенно PNG-скриншоты, уже сжатые фото, WebP с высоким качеством или изображения с сохраненными метаданными. Skip If Larger не даст заменить маленький исходник более тяжелым результатом, когда главная цель — уменьшить размер. + Включите его, если экспорт предназначен для сжатия, изменения размера или подготовки файлов к ограничениям на загрузку. + Просмотр пропущенных файлов после пакета; возможно, они уже оптимизированы или им может потребоваться другой формат. + Отключите его, если качество, прозрачность или совместимость форматов важнее конечного размера файла. + Буфер обмена и ссылки + Управляйте автоматической вставкой из буфера обмена и предпросмотром ссылок для быстрых текстовых инструментов. + Сделайте автоматическую вставку предсказуемой + Настройки буфера обмена и ссылок удобны для QR, Base64, URL и других текстовых сценариев, но автоматическая вставка должна оставаться ожидаемой. Если приложение будто само заполняет поля, проверьте поведение буфера обмена; если карточки ссылок мешают или тормозят, настройте предпросмотр ссылок. + Разрешите автоматическую вставку из буфера обмена, если вы часто копируете текст, и сразу же откройте соответствующий инструмент. + Отключите автоматическую вставку, если личное содержимое буфера обмена появляется в инструментах там, где вы этого не ожидали. + Отключите предварительный просмотр ссылок, если предварительный просмотр медленный, отвлекающий или ненужный для вашего рабочего процесса. + Компактное управление + Используйте более плотные селекторы и быстрое размещение настроек, когда места на экране мало. + Разместите больше элементов управления на маленьких экранах + Компактные селекторы и быстрое размещение настроек помогают на небольших телефонах, редактировании ландшафта и инструментах с множеством параметров. Компромисс заключается в том, что элементы управления могут показаться более плотными, поэтому лучше всего это делать после того, как вы уже узнаете общие параметры. + Включите компактные селекторы, если диалоговые окна или списки параметров кажутся слишком высокими для экрана. + Отрегулируйте сторону быстрых настроек, если элементы управления инструментом легче доступны с одной стороны дисплея. + Вернитесь к более просторным элементам управления, если вы еще изучаете приложение или часто промахиваетесь по плотным опциям. + Загрузка изображений из Интернета + Вставьте ссылку на страницу или изображение, посмотрите найденные картинки и сохраните нужные. + Используйте веб-изображения сознательно + Загрузка изображений из интернета разбирает указанную ссылку, показывает первое найденное изображение и позволяет сохранить или отправить выбранные картинки. Некоторые сайты используют временные, защищенные или созданные скриптами ссылки, поэтому страница может открываться в браузере, но не отдавать приложению подходящие изображения. + Вставьте прямую ссылку на изображение или страницу и дождитесь списка найденных картинок. + Используйте выбор изображений, если на странице найдено несколько картинок, а нужны только некоторые. + Если ничего не загружается, попробуйте сначала использовать прямую ссылку на изображение или загрузить его через браузер; сайт может блокировать парсинг или использовать приватные ссылки. + Выберите тип изменения размера + Прежде чем переводить изображение в новые измерения, разберитесь с функциями «Явное», «Гибкое», «Обрезка» и «Подгонка». + Управление формой, холстом и соотношением сторон + Тип изменения размера определяет, что произойдет, если запрошенные ширина и высота не соответствуют исходному соотношению сторон. В этом разница между растягиванием пикселей, сохранением пропорций, обрезкой дополнительной области или добавлением фонового холста. + Используйте Explicit только в том случае, если искажение приемлемо или источник уже имеет то же соотношение сторон, что и целевой объект. + Используйте «Гибкий», чтобы сохранить пропорции, изменяя размер с важной стороны, особенно для фотографий и смешанных партий. + Используйте «Обрезать» для строгих рамок без границ или «Подогнать», если все изображение должно оставаться видимым внутри фиксированного холста. + Выберите режим масштабирования изображения + Выберите фильтр передискретизации, который контролирует резкость, плавность, скорость и краевые артефакты. + Изменение размера без случайной мягкости + Режим масштабирования изображения управляет тем, как рассчитываются новые пиксели при изменении размера. Простые режимы работают быстро и предсказуемо, а расширенные фильтры могут лучше сохранять детали, но могут повысить резкость краев, создать ореолы или занять больше времени на больших изображениях. + Используйте Basic или Bilinear для быстрого ежедневного изменения размера, когда результат должен быть только меньшим и чистым. + Используйте режимы Lanczos, Robidoux, Spline или EWA, когда важны мелкие детали, текст или высококачественное уменьшение масштаба. + Используйте «Ближайший» для пиксельной графики, значков и графики с резкими краями, где размытые пиксели могут испортить внешний вид. + Масштабировать цветовое пространство + Решите, как смешиваются цвета при повторной выборке пикселей во время изменения размера. + Сохраняйте градиенты и края естественными + Масштабирование цветового пространства меняет математические методы, используемые при изменении размера. Для большинства изображений подходят значения по умолчанию, но линейные или перцептивные пространства могут привести к тому, что градиенты, темные края и насыщенные цвета будут выглядеть чище после сильного масштабирования. + Оставьте значение по умолчанию, если скорость и совместимость имеют большее значение, чем незначительные различия в цвете. + Попробуйте линейный или перцепционный режимы, если темные контуры, снимки экрана пользовательского интерфейса, градиенты или цветные полосы после изменения размера выглядят неправильно. + Сравните «до» и «после» на реальном целевом экране, поскольку изменения цветового пространства могут быть незаметными и зависеть от содержимого. + Ограничить режимы изменения размера + Знайте, когда изображения с превышением лимита следует пропустить, перекодировать или уменьшить до нужного размера. + Выберите, что произойдет на пределе + Limit Resize — это не только инструмент для уменьшения изображений. Его режим определяет, что будет делать приложение, когда источник уже находится в ваших пределах или когда только одна сторона нарушает правило, что важно для смешанных папок и подготовки к загрузке. + Используйте «Пропустить», если изображения, находящиеся в пределах ограничений, следует оставить нетронутыми и больше не экспортировать. + Используйте Recode, если размеры приемлемы, но вам все еще нужен новый формат, поведение метаданных, качество или имя файла. + Используйте масштабирование, когда изображения, выходящие за пределы ограничений, необходимо пропорционально уменьшать до тех пор, пока они не впишутся в разрешенный размер. + Целевые соотношения сторон + Избегайте растянутых граней, обрезанных краев и неожиданных границ при выводе со строгими размерами. + Сопоставьте форму перед изменением размера + Ширина и высота составляют лишь половину целевого размера. Соотношение сторон определяет, будет ли изображение естественным образом вписываться, необходимо его обрезать или вокруг него потребуется холст. Предварительная проверка формы предотвращает самые неожиданные результаты изменения размера. + Сравните исходную фигуру с целевой фигурой, прежде чем выбирать «Явное», «Обрезать» или «Подогнать». + Обрезайте сначала, когда объект может потерять дополнительный фон, а место назначения требует строгого кадра. + Используйте «Подогнать» или «Гибкий», если каждая часть исходного изображения должна оставаться видимой. + Масштабирование или нормальное изменение размера + Знайте, когда для добавления пикселей требуется искусственный интеллект, а когда достаточно простого масштабирования. + Не путайте размер с деталями + Обычное изменение размера изменяет размеры путем интерполяции пикселей; он не может изобрести реальные детали. Высококачественный искусственный интеллект может создавать более четкие детали, но он медленнее и может искажать текстуру, поэтому правильный выбор зависит от того, нужна ли вам совместимость, скорость или визуальное восстановление. + Используйте обычное изменение размера для миниатюр, ограничений на загрузку, снимков экрана и простых изменений размеров. + Используйте масштабирование AI, когда маленькое или мягкое изображение должно выглядеть лучше в большем размере. + Сравните лица, текст и узоры после масштабирования с помощью ИИ, поскольку сгенерированные детали могут выглядеть убедительно, но некорректно. + Порядок фильтров имеет значение + Применяйте очистку, цвет, резкость и окончательное сжатие в намеренной последовательности. + Создайте более чистый стек фильтров + Фильтры не всегда взаимозаменяемы. Размытие перед повышением резкости, повышение контрастности перед распознаванием текста или изменение цвета перед сжатием могут привести к совершенно разным результатам при использовании одних и тех же элементов управления. + Сначала обрежьте и поверните, чтобы последующие фильтры работали только с оставшимися пикселями. + Примените экспозицию, баланс белого и контрастность перед повышением резкости или стилизацией эффектов. + Окончательное изменение размера и сжатие сохраняйте ближе к концу, чтобы предварительно просмотренные детали предсказуемо сохранялись при экспорте. + Исправление краев фона + Очистите ореолы, оставшиеся тени, волосы, дыры и мягкие контуры после автоматического удаления. + Сделайте вырезы намеренными + Автоматические маски часто выглядят хорошо на первый взгляд, но выявляют проблемы на ярком, темном или узорчатом фоне. Очистка краев — это разница между быстрым вырезом и многоразовой наклейкой, логотипом или изображением продукта. + Предварительно просмотрите результат на контрастном фоне, чтобы выявить ореолы и недостающие детали по краям. + Используйте восстановление для волос, углов и прозрачных объектов, прежде чем стирать окружающие остатки. + Экспортируйте небольшой тест окончательного цвета фона, когда вырез будет помещен в дизайн. + Читабельные водяные знаки + Сделайте метки видимыми на ярких, темных, насыщенных и обрезанных изображениях, не испортив фотографию. + Защитите изображение, не скрывая его + Водяной знак, который хорошо смотрится на одном изображении, может исчезнуть на другом. Размер, непрозрачность, контрастность, размещение и повторение следует выбирать для всей партии, а не только для первого предварительного просмотра. + Прежде чем экспортировать все файлы, проверьте отметку на самых ярких и самых темных изображениях в пакете. + Используйте меньшую непрозрачность для крупных повторяющихся отметок и более сильный контраст для небольших угловых отметок. + Сохраняйте важные лица, текст и сведения о продукте на виду, если только знак не предназначен для предотвращения повторного использования. + Разделить изображение на части + Разрежьте одно изображение по строкам, столбцам, процентам, формату и качеству. + Подготовьте сетку и упорядоченные фрагменты + Image Splitting создает несколько файлов из одного изображения. Можно задать количество строк и столбцов, настроить проценты для строк или колонок и сохранить все части с выбранным форматом и качеством. Это удобно для сеток, нарезки страниц, панорам и последовательных публикаций. + Выберите исходное изображение, затем установите необходимое количество строк и столбцов. + Измените проценты строк или столбцов, если части должны быть разного размера. + Перед сохранением выберите формат и качество, чтобы все части экспортировались по одним правилам. + Вырезать полосы из изображения + Удаляйте вертикальные или горизонтальные области и склеивайте оставшиеся части. + Удалить область без пустого промежутка + Image Cutting отличается от обычной обрезки. Он может удалить выбранную вертикальную или горизонтальную полосу, а затем склеить оставшиеся части изображения. Инверсия, наоборот, оставляет выбранную область; если включить инверсию по обеим осям, останется выбранный прямоугольник. + Используйте вертикальное вырезание для боковых областей, колонок или полос посередине; горизонтальное — для баннеров, зазоров или строк. + Включите инверсию оси, если хотите сохранить выделенную часть, а не удалить ее. + Проверьте края после вырезания: склейка может выглядеть резкой, если удаленная область пересекала важные детали. + Выберите выходной формат + Выберите JPEG, PNG, WebP, AVIF, JXL или PDF в зависимости от содержимого и места назначения. + Выбирайте формат под место использования + Лучший формат зависит от того, что содержит изображение и где оно будет использоваться. Фотографии, снимки экрана, прозрачные ресурсы, документы и анимированные файлы по-разному выходят из строя, если их принудительно перевести в неправильный формат. + Используйте JPEG для широкой совместимости фотографий, когда прозрачность и точные пиксели не требуются. + Используйте PNG для четких снимков экрана, снимков пользовательского интерфейса, прозрачной графики и редактирования без потерь. + Используйте WebP, AVIF или JXL, когда важен компактный современный вывод и принимающее приложение его поддерживает. + Извлечь обложки из аудио + Сохраняйте встроенные обложки альбомов или доступные кадры из аудиофайлов в PNG. + Извлеките обложку из аудиофайлов + Audio Covers читает встроенную обложку через Android MediaMetadataRetriever. Если обложки нет, Android иногда может отдать кадр из медиа; если нет ни того ни другого, инструмент честно покажет, что изображения для извлечения нет. + Откройте Audio Covers и выберите один или несколько аудиофайлов, где ожидается обложка. + Проверьте извлеченную обложку перед сохранением или отправкой, особенно у файлов из стриминговых или записывающих приложений. + Если изображение не найдено, в файле, скорее всего, нет встроенной обложки или Android не смог получить подходящий кадр. + Метаданные даты и местоположения + Решите, что сохранить, если время съемки имеет значение, но данные GPS или устройства не должны утечь. + Отделяйте полезные метаданные от приватных + Не все метаданные одинаково рискованны. Дата съемки может быть полезна для архива и сортировки, а GPS, поля устройства, теги программы или владелец могут раскрывать лишнее. + Сохраняйте метки даты и времени, если хронологический порядок важен для альбомов, сканирований или истории проекта. + Удалите GPS и идентификационные метки устройства, прежде чем публиковать их или отправлять изображения незнакомцам. + Экспортируйте образец и еще раз проверьте EXIF, если требования к конфиденциальности строгие. + Избегайте конфликтов имен файлов + Защитите пакетный вывод, если многие файлы имеют одинаковые имена, например image.jpg или screen.png. + Делайте каждое имя результата уникальным + Дублирующиеся имена файлов часто встречаются, когда изображения поступают из мессенджеров, снимков экрана, облачных папок или с нескольких камер. Хороший шаблон предотвращает случайную замену и обеспечивает возможность отслеживания результатов до их источников. + Включите исходное имя и суффикс, если отредактированная копия должна быть узнаваема. + Добавляйте номер, временную метку, пресет или размеры при обработке больших смешанных пакетов. + Избегайте перезаписи рабочих процессов, пока не убедитесь, что шаблон именования не может конфликтовать. + Сохраните или поделитесь + Выбирайте между постоянным файлом и временной передачей другому приложению. + Экспортируйте с правильным намерением + «Сохранить и поделиться» может показаться похожим, но они решают разные проблемы. При сохранении создается файл, который вы сможете найти позже; Совместное использование отправляет сгенерированный результат через Android в другое приложение и может не оставить надежную локальную копию. + Используйте «Сохранить», если результат должен остаться в известной папке, попасть в бэкап или пригодиться позже. + Используйте «Поделиться» для быстрых сообщений, вложений к электронной почте, публикаций в социальных сетях или отправки результатов другому редактору. + Сначала сохраните файл, если принимающее приложение ненадежно или если воссоздание отказавшего общего ресурса раздражает. + Размер и поля PDF-страницы + Выберите размер бумаги, параметры соответствия и передышку перед созданием документа. + Сделайте страницы изображений пригодными для печати и читабельными + Изображения могут плохо лечь на PDF-страницы, если их пропорции не совпадают с выбранным размером бумаги. Поля, режим подгонки и ориентация решают, будет ли содержимое обрезано, уменьшено, растянуто или удобно для чтения. + Используйте реальный размер бумаги, если PDF-файл можно распечатать или отправить в форму. + Используйте поля для сканов и снимков экрана, чтобы текст не прилегал к краю страницы. + Предварительный просмотр страниц с книжной и альбомной ориентацией вместе, если исходные изображения имеют смешанную ориентацию. + Очистка сканов + Улучшите края бумаги, тени, контрастность, поворот и читаемость перед PDF или OCR. + Подготовьте страницы перед архивированием + Технически скан можно зафиксировать, но его все равно трудно прочитать. Небольшие шаги по очистке перед экспортом в PDF часто имеют большее значение, чем настройки сжатия, особенно для квитанций, рукописных заметок и страниц при слабом освещении. + Исправьте перспективу и обрежьте края стола, пальцы, тени и фоновый мусор. + Аккуратно увеличивайте контраст, чтобы текст стал более четким, не разрушая штампы, подписи или карандашные пометки. + Запускайте OCR только после того, как страница станет вертикальной и читаемой, а затем проверьте важные цифры вручную. + Сжать, обесцветить или восстановить PDF + Используйте отдельные операции для более легких, печатных или заново собранных копий документа. + Выберите нужную PDF-операцию + В PDF Tools сжатие, перевод в оттенки серого и восстановление — разные операции. Сжатие и grayscale в основном работают со встроенными изображениями, а восстановление пересобирает структуру PDF. Это полезно, но не гарантирует исправление любого документа. + Используйте «Сжатие PDF», если документ содержит изображения и размер файла имеет значение для совместного использования. + Используйте оттенки серого, если документ должен проще печататься или цветные изображения не нужны. + Используйте «Восстановить PDF» на копии, если документ не открывается или имеет поврежденную внутреннюю структуру, а затем проверьте восстановленный файл. + Извлечение изображений из PDF-файлов + Достаньте встроенные изображения из PDF и упакуйте результат в архив. + Сохранить исходные изображения из документа + Extract Images ищет в PDF встроенные объекты изображений, по возможности пропускает дубликаты и сохраняет найденные картинки в ZIP-архив. Он извлекает именно изображения внутри PDF, а не текст, векторную графику или каждую видимую страницу как скриншот. + Используйте «Извлечение изображений», если вам нужны изображения, хранящиеся в PDF-файле, например фотографии из каталога или отсканированные ресурсы. + Используйте PDF to Images или извлечение страниц, если нужны полные страницы вместе с текстом и макетом. + Если встроенных изображений нет, страница может состоять из текста, векторной графики или объектов, которые нельзя извлечь как картинки. + Метаданные, сведение и печать + Редактируйте свойства документа, растрируйте страницы или готовьте макеты для печати. + Выберите финальное действие с документом + Метаданные, сведение и подготовка к печати решают разные задачи. Metadata меняет свойства документа, Flatten PDF растрирует страницы и делает их менее редактируемыми, а Print PDF готовит новый макет с размером страницы, ориентацией, полями и количеством страниц на листе. + Используйте Metadata, когда важны автор, заголовок, ключевые слова, producer или очистка приватной информации. + Используйте Flatten PDF, когда видимое содержимое страницы должно стать сложнее редактировать как отдельные PDF-объекты. + Используйте «Печать PDF», если вам нужен нестандартный размер страницы, ориентация, поля или несколько страниц на листе. + Подготовьте изображения для OCR + Используйте обрезку, вращение, контрастность, шумоподавление и масштабирование, прежде чем просить OCR прочитать текст. + Дайте OCR более чистые пиксели + Ошибки OCR часто идут от самого изображения, а не от движка распознавания. Перекошенные страницы, низкий контраст, размытие, тени, мелкий текст и шумный фон снижают качество результата. + Обрежьте текстовую область и поверните изображение так, чтобы линии были горизонтальными перед распознаванием. + Увеличивайте контрастность или преобразуйте изображение в более чистое черно-белое изображение только в том случае, если это облегчит чтение букв. + Умеренно увеличивайте размер крошечного текста, но избегайте агрессивного повышения резкости, которое создает ложные края. + Проверьте QR-содержимое + Прежде чем доверять коду, проверьте ссылки, учетные данные Wi-Fi, контакты и действия. + Не доверяйте QR-содержимому автоматически + QR-код может содержать ссылку, контакт, пароль Wi-Fi, событие календаря, номер телефона или обычный текст. Подпись рядом с кодом не обязана совпадать с реальным содержимым, поэтому сначала проверьте расшифрованные данные. + Проверьте полный декодированный URL-адрес перед его открытием, особенно если это сокращенные или незнакомые домены. + Будьте осторожны с Wi-Fi, контактами, календарем, телефоном и SMS: такие коды могут запускать чувствительные действия. + Если нужно проверить данные отдельно, сначала скопируйте содержимое, а не открывайте его сразу. + Создавать QR-коды + Создавайте коды из текста, ссылок, Wi-Fi, контактов, email, телефона, SMS и геоданных. + Соберите правильное содержимое QR + Экран QR умеет не только сканировать, но и создавать коды из введенных данных. Структурированные типы помогают записать данные в понятном другим приложениям формате: Wi-Fi, контакт, email, телефон, SMS, ссылка или координаты. + Выберите обычный текст для простых заметок или структурированный тип, если код должен открываться как Wi-Fi, контакт, ссылка, email, телефон, SMS или геоданные. + Перед отправкой QR-кода проверьте каждое поле: предпросмотр отражает только то, что вы ввели. + Проверьте сохраненный QR-код с помощью сканера, когда он будет распечатан, опубликован публично или использован другими людьми. + Выберите режим checksum + Считайте хэши файлов или текста, сравнивайте один файл или проверяйте много файлов сразу. + Проверяйте содержимое, а не внешний вид + Checksum Tools разделен на вкладки: хэш файла, хэш текста, сравнение файла с известным хэшем и пакетная проверка. Checksum подтверждает побайтовое совпадение для выбранного алгоритма, но не доказывает, что два изображения просто визуально похожи. + Используйте «Рассчитать» для файлов и «Текстовый хэш» для введенных или вставленных текстовых данных. + Используйте «Сравнить», когда у вас есть ожидаемый хэш для одного файла. + Используйте пакетное сравнение, когда нужно посчитать или проверить хэши для многих файлов за один проход; алгоритм держите одним и тем же. + Конфиденциальность буфера обмена + Используйте функции автоматического буфера обмена, не допуская случайного раскрытия личных скопированных данных. + Держите скопированные данные под контролем + Автоматизация буфера обмена удобна, но скопированный текст может содержать пароли, ссылки, адреса, токены или личные заметки. Включайте такие функции только если они совпадают с вашими привычками и ожиданиями приватности. + Отключите автоматическое использование буфера обмена, если в инструментах неожиданно появляется личный текст. + Используйте сохранение только в буфер обмена, если действительно не хотите создавать файл. + Очистите или замените буфер обмена после работы с приватным текстом, QR-данными или Base64. + Цветовые форматы + Разберитесь с HEX, RGB, HSV, alpha и копируемыми значениями перед вставкой в другое приложение. + Скопируйте формат, который нужен получателю + Один и тот же видимый цвет можно записать несколькими способами. Инструмент дизайна, поле CSS, значение цвета Android или редактор изображений могут ожидать другого порядка, обработки альфа-канала или цветовой модели. + Используйте HEX при вставке в поля веб-сайтов, дизайна или темы, для которых требуются компактные цветовые коды. + Используйте RGB или HSV, когда вам нужно вручную настроить каналы, яркость, насыщенность или оттенок. + Проверьте alpha отдельно, если нужна прозрачность: некоторые приложения ее игнорируют или ждут другой порядок каналов. + Открыть библиотеку цветов + Ищите именованные цвета по названию или HEX и держите избранные ближе. + Найдите повторно используемые цвета + Color Library загружает коллекцию именованных цветов, сортирует ее по названию, ищет по названию или HEX и хранит избранные цвета, чтобы важные варианты было проще найти. Это полезно, когда нужен готовый именованный цвет, а не цвет, взятый пипеткой с изображения. + Ищите по названию, если знаете цветовую семью: red, blue, slate, mint и т.п. + Ищите по HEX, если у вас уже есть числовой цвет и нужно найти совпадающие или близкие именованные варианты. + Добавляйте частые цвета в избранное, чтобы они поднимались выше общей библиотеки. + Использовать коллекцию Mesh Gradients + Загружайте доступные mesh gradient ресурсы и отправляйте выбранные изображения. + Используйте удаленную коллекцию mesh gradients + Mesh Gradients может загрузить удаленную коллекцию ресурсов, скачать недостающие изображения, показать прогресс и отправить выбранные градиенты. Доступность зависит от сети и хранилища remote resources, поэтому пустой список обычно означает, что ресурсы пока не загрузились или недоступны. + Откройте Mesh Gradients из инструментов градиента, если нужны готовые mesh gradient изображения. + Дождитесь загрузки ресурсов или прогресса скачивания, прежде чем считать коллекцию пустой. + Выберите и отправьте нужные градиенты или вернитесь в Gradient Maker, если хотите собрать свой градиент вручную. + Разрешение сгенерированных изображений + Выберите размер перед созданием градиентов, шума, обоев, SVG-подобной графики или текстур. + Генерируйте сразу в нужном размере + У сгенерированных изображений нет исходной фотографии, к которой можно вернуться. Если результат слишком маленький, последующее масштабирование может смягчить паттерны, сдвинуть детали или изменить поведение тайлинга и сжатия. + Сначала задайте размеры под конечную задачу: обои, значок, фон, печать или оверлей. + Используйте больший размер для текстур, которые могут обрезаться, масштабироваться или повторно использоваться в разных макетах. + Перед огромными результатами экспортируйте тестовые версии: процедурные детали могут заметно менять поведение сжатия. + Экспортировать текущие обои + Получите доступные обои «Домой», «Блокировка» и встроенные обои с устройства. + Сохраните или поделитесь установленными обоями + Экспорт обоев считывает обои, которые Android предоставляет через системные API обоев. В зависимости от устройства, версии Android, разрешений и варианта сборки обои «Домой», «Блокировка» или встроенные обои могут быть доступны отдельно или отсутствовать. + Откройте «Экспорт обоев», чтобы загрузить обои, которые система позволяет приложению считывать. + Выберите доступные записи «Домой», «Блокировка» или встроенные обои, которые вы хотите сохранить, скопировать или поделиться. + Если обои отсутствуют или функция недоступна, обычно это связано с ограничением системы, разрешения или варианта сборки, а не с настройкой редактирования. + Скопировать цвет как + Шестнадцатеричный + имя + Модель портретного коврика для быстрой стрижки людей с более мягкими волосами и обработкой краев. + Быстрое улучшение при слабом освещении с использованием оценки кривой Zero-DCE++. + Модель восстановления изображений Restormer для уменьшения полос дождя и артефактов влажной погоды. + Модель развертки документа, которая прогнозирует координатную сетку и преобразует изогнутые страницы в более плоские сканы. + Дней подряд + Данных сохранено + Топ формат + Показать последние инструменты + Отображение быстрого доступа к недавно использованным инструментам на главном экране. + Статистика использования + Изучите открытия приложений, запуски инструментов и наиболее часто используемые вами инструменты. + Открытий приложения + Открытий инструментов + Последний инструмент + Успешные сохранения + Используемые инструменты + %1$d открыто • %2$s + Статистики использования пока нет + Откройте несколько инструментов, и они появятся здесь автоматически. + Статистика использования хранится только локально на вашем устройстве. ImageToolbox использует их только для отображения вашей личной активности, любимых инструментов и сохраненных данных. + Сбросить статистику использования + Открытия инструментов, статистика сохранений, серия активности, сохраненные данные и топ формат будут сброшены. Количество открытий приложения не изменится. + редактор редактировать фотографию ретушь изображения настроить + изменить размер конвертировать масштаб разрешение размеры ширина высота jpg jpeg png webp сжатие + сжать размер вес байты мб КБ уменьшить качество оптимизировать + обрезка, обрезка, соотношение сторон обрезки + Эффект фильтра Цветокоррекция Настройка маски яркости + нарисовать кисть карандаш аннотировать разметку + шифровать шифровать расшифровать пароль секрет + удаление фона стереть удалить вырез прозрачный альфа + предварительный просмотр просмотрщик галерея проверка открыть + сшить объединить объединить панораму длинный скриншот + URL-адрес загрузки Интернет-ссылки изображения + сборщик образцов пипетки шестнадцатеричный цвет RGB + палитра цветов, образцы, извлечение доминанты + конфиденциальность метаданных exif удалить полосу очистить местоположение GPS + сравнить разницу до и после + ограничение изменение размера макс. мин. мегапиксели размеры закрепить + инструменты для страниц PDF-документа + распознавание текста ocr, сканирование извлечения + Цветная сетка градиентного фона + наложение водяного знака логотипа текста штампа + gif анимация анимированные конвертировать кадры + apng анимация анимированный png конвертировать кадры + zip архив сжать пакет распаковать файлы + jxl jpeg xl конвертировать изображения в формат jpg + Преобразование векторной трассировки SVG в XML + конвертировать формат jpg jpeg png webp heic avif jxl bmp + сканер документов сканирование бумаги перспектива обрезка + сканирование сканера штрих-кода qr + наложение суммирования средней медианной астрофотографии + части фрагмента сетки разделенных плиток + преобразование цветов hex rgb hsl hsv cmyk lab + WebP конвертирует формат анимированных изображений + Генератор шума, случайное зерно текстуры + объединение макета сетки коллажа + слои разметки, аннотации, наложение, редактирование + Base64 кодирует декодирование данных URI + Хэш контрольной суммы md5 sha sha1 sha256 проверить + сетка градиентный фон + Редактировать метаданные exif, камера с датой, GPS + разрезать плитку на кусочки сетки + аудио обложка обложки альбома музыка метаданные + фон экспорта обоев + ascii текстовые художественные персонажи + высококлассный сегмент нейронного улучшения AIML + палитра материалов библиотеки цветов с именами цветов + Студия эффекта фрагмента шейдера glsl + pdf объединить объединить присоединиться + PDF разделить на отдельные страницы + pdf повернуть ориентацию страниц + pdf переупорядочить изменить порядок страниц сортировать + нумерация страниц в формате pdf + pdf ocr текст распознавать экстракт с возможностью поиска + pdf водяной знак штамп логотип текст + рисунок подписи в формате pdf + pdf защита паролем шифрование блокировка + pdf разблокировать пароль расшифровать снять защиту + Сжать PDF-файл уменьшить размер оптимизировать + PDF оттенки серого черный белый монохромный + pdf ремонт исправить восстановить сломанный + Свойства метаданных PDF, exif, должность автора + pdf удалить удалить страницы + обрезка полей в формате pdf + PDF-сглаживание аннотаций образует слои + pdf извлечь изображения картинки + сжатие zip-архива PDF + PDF-принтер для печати + просмотрщик PDF-файлов читать + изображения в PDF конвертировать документ jpg jpeg png + PDF в изображения, экспорт страниц jpg png + pdf аннотации комментарии удалить очистить + Нейтральный вариант + Ошибка + Аудио + Файл + Профили экспорта + Сохранить текущий профиль + Удалить профиль экспорта + Вы хотите удалить профиль экспорта \"%1$s\"? + Рисование границы + Показывает тонкую рамку вокруг изображения в рисовании и удалении фона + Совок + Нотч + Закругленные углы, вырезанные внутрь + Ступенчатые углы с двойным срезом + Заполнитель + Используйте {filename}, чтобы вставить исходное имя файла без расширения. + Вспышка + Базовая сумма + Сумма звонка + Количество лучей + Ширина кольца + Искажение перспективы + Внешний вид Java + Сдвиг + Угол X + Угол Y + Изменить размер вывода + Капля воды + Длина волны + Фаза + Высокий проход + Цветовая маска + Хром + Растворить + Мягкость + Обратная связь + Размытие объектива + Низкий входной сигнал + Высокий вход + Низкая мощность + Высокая производительность + Световые эффекты + Высота выступа + Мягкость удара + Пульсация + X-амплитуда + Амплитуда Y + длина волны X + Длина волны Y + Адаптивное размытие + Генерация текстур + Создавайте различные процедурные текстуры и сохраняйте их как изображения. + Тип текстуры + Предвзятость + Время + Светить + Дисперсия + Образцы + Угловой коэффициент + Коэффициент градиента + Тип сетки + Дистанционная мощность + Масштаб Y + Нечеткость + Тип основы + Фактор турбулентности + Масштабирование + Итерации + Кольца + Матовый металл + Каустика + Сотовая связь + Шахматная доска + ФБМ + Мрамор + Плазма + Одеяло + Древесина + Случайный + Квадрат + Шестиугольный + Восьмиугольный + Треугольный + Ребристый + ВЛ Шум + СК Шум + Недавние + Недавно использованных фильтров пока нет. + генератор текстур матовый металл каустика сотовая шахматная доска мрамор плазма лоскутное одеяло дерево кирпич камуфляж ячейка облако трещина ткань листва соты лед лава туманность бумага ржавчина песок дым местность топография вода рябь + Кирпич + Камуфляж + Клетка + Облако + Трескаться + Ткань + Листва + Соты + Лед + Лава + Туманность + Бумага + Ржавчина + Песок + Дым + Камень + Местность + Топография + Водная рябь + Продвинутая древесина + Ширина раствора + Неравномерность + Фаска + Шероховатость + Первый порог + Второй порог + Третий порог + Мягкость края + Ширина границы + Покрытие + Деталь + Глубина + Ветвление + Горизонтальные нити + Вертикальные резьбы + Фазз + Вены + Освещение + Ширина трещины + Мороз + Поток + Корочка + Плотность облаков + Звезды + Плотность волокна + Прочность волокна + Пятна + Коррозия + Питтинг + Хлопья + Частота дюн + Угол ветра + Рябь + огоньки + Шкала вен + Уровень воды + Уровень горы + Эрозия + Уровень снега + Количество строк + Толщина линии + Затенение + Цвет пор + Цвет раствора + Цвет темного кирпича + Кирпичный цвет + Цвет выделения + Темный цвет + Цвет леса + цвет земли + Песочный цвет + Цвет фона + Цвет ячейки + Цвет края + Цвет неба + Цвет тени + Светлый цвет + Цвет поверхности + Цвет вариации + Цвет трещины + Цвет деформации + Цвет утка + Темный цвет листьев + Цвет листьев + Цвет границы + Медовый цвет + Глубокий цвет + Цвет льда + Морозный цвет + Цвет корочки + Цвет стирки + Цвет свечения + Космический цвет + Фиолетовый цвет + Синий цвет + Базовый цвет + Цвет волокна + Цвет пятна + Цвет металла + Цвет темной ржавчины + Цвет ржавчины + Оранжевый цвет + Цвет дыма + Цвет вен + Цвет низменности + Цвет воды + Цвет камня + Цвет снега + Низкий цвет + Высокий цвет + Цвет линии + Мелкий цвет + Трава + Грязь + Кожа + Конкретный + Асфальт + Мох + Огонь + Аврора + Нефтяное пятно + Акварель + Абстрактный поток + Опал + Дамасская сталь + Молния + Бархат + Мраморные чернила + Голографическая фольга + Биолюминесценция + Космический вихрь + Лавовая лампа + Горизонт событий + Фрактальный Блум + Хроматический туннель + Затмение Корона + Странный аттрактор + Феррожидкостная корона + Сверхновая + Глаз + Павлинье перо + Наутилус Шелл + Кольцевая планета + Плотность лезвия + Длина лезвия + Ветер + неоднородность + Комки + Влага + Камешки + Морщины + Поры + Совокупный + Трещины + Деготь + Носить + Пятнышки + Волокна + Частота пламени + Дым + Интенсивность + Ленты + Группы + Переливчатость + Тьма + Цветет + Пигмент + Края + Бумага + Диффузия + Симметрия + Резкость + Игра цвета + Молочность + Слои + Складной + Польский + Филиалы + Направление + Шин + Складки + Растушевка + Баланс чернил + Спектр + Морщины + Дифракция + Оружие + Крутить + Свечение ядра + Капли + Наклон диска + Размер горизонта + Ширина диска + Линзирование + Лепестки + Завиток + Филигрань + Фасеты + Кривизна + Размер Луны + Размер короны + Лучи + Кольцо с бриллиантом + Доли + Плотность орбиты + Толщина + Шипы + Длина шипа + Размер тела + Металлик + Радиус удара + Ширина корпуса + Выброшен + Размер зрачка + Размер радужной оболочки + Цветовая вариация + Блик + Размер глаз + Плотность бородки + Повороты + Чемберс + Открытие + Хребты + Перламутровый блеск + Размер планеты + Наклон кольца + Ширина кольца + Атмосфера + Цвет грязи + Цвет темной травы + Цвет травы + Цвет наконечника + Цвет темной земли + Сухой цвет + Цвет гальки + Цвет кожи + Цвет бетона + Цвет смолы + Цвет асфальта + Цвет камня + Цвет пыли + Цвет почвы + Цвет темного мха + Цвет мха + Красный цвет + Основной цвет + Зеленый цвет + Голубой цвет + Пурпурный цвет + Золотой цвет + Цвет бумаги + Цвет пигмента + Вторичный цвет + Первый цвет + Второй цвет + Розовый цвет + Темно-стальной цвет + Стальной цвет + Светлый стальной цвет + Цвет оксида + Цвет ореола + Цвет болта + Бархатный цвет + Цвет блеска + Синий цвет чернил + Красный цвет чернил + Темный цвет чернил + Серебряный цвет + Желтый цвет + Цвет ткани + Цвет диска + Горячий цвет + Цвет линзы + Внешний цвет + Внутренний цвет + Цвет короны + Холодный цвет + Теплый цвет + Цвет облаков + Цвет пламени + Цвет пера + Цвет корпуса + Жемчужный цвет + Цвет планеты + Цвет кольца + Удалить исходные файлы после сохранения + Будут удалены только успешно обработанные исходные файлы. + Исходные файлы удалены: %1$d + Не удалось удалить исходные файлы: %1$d. + Исходные файлы удалены: %1$d, ошибка: %2$d. + Жесты шторок + Разрешить перетаскивание развёрнутых шторок с опциями + Подвыборка цветности + Разрядность + Без потерь + %1$d-бит + Пакетное переименование + Переименование нескольких файлов по шаблону имени + пакетное переименование файлов шаблон имени нумерация дата расширение + Выберите файлы для переименования + Шаблон имени файла + Переименовать + Источник даты + Дата съёмки из EXIF + Дата изменения файла + Дата создания файла + Текущая дата + Дата вручную + Дата вручную + Время вручную + Для некоторых файлов выбранная дата недоступна. Для них будет использована текущая дата. + Введите шаблон имени файла + Шаблон создаёт недопустимое или слишком длинное имя файла + Шаблон создаёт одинаковые имена файлов + Все имена файлов уже соответствуют шаблону + Шаблон содержит токены, недоступные для пакетного переименования + Имя не изменится + Файлы успешно переименованы + Не удалось переименовать некоторые файлы + Разрешение не предоставлено + Переименование файлов невозможно отменить. Прежде чем продолжить, убедитесь, что новые имена верны. + Подтвердить переименование + Настройки бокового меню + Масштаб меню + Меню альфа + Автоматический баланс белого + Обрезка + Проверка скрытых водяных знаков + Хранилище + Лимит кэша + Автоматически очищать кэш, когда он превысит этот размер + Интервал очистки + Как часто проверять необходимость очистки кэша + При запуске приложения + 1 день + 1 неделя + 1 месяц + Очищайте кеш приложения автоматически в соответствии с выбранным лимитом и интервалом. + Передвижная зона обрезки + Позволяет перемещать всю область обрезки, перетаскивая ее внутри + Объединить GIF + Объединение нескольких GIF-фрагментов в одну анимацию + Порядок GIF-фрагментов + Реверс + Бумеранг + Нормализовать размер кадров + Масштабировать кадры по самому большому фрагменту; выключите, чтобы сохранить исходный масштаб + Задержка между фрагментами (мс) + Играть наоборот + Играйте вперед, а затем назад + Папка + Выберите все изображения из папки и ее подпапок. + Поиск дубликатов + Найдите точные копии и визуально похожие изображения + Поиск дубликатов похожих изображений точные копии фотографий очистка хранилища dHash SHA-256 + Выбирайте изображения, чтобы найти дубликаты + Чувствительность к сходству + Точные копии + Похожие изображения + Выбрать все точные дубликаты + Выбрать все, кроме рекомендованных + Дубликатов не обнаружено + Попробуйте добавить больше изображений или повысить чувствительность к сходству. + Не удалось прочитать %1$d изображений. + %1$s • %2$s подлежит возврату + %1$d групп • %2$s можно вернуть + %1$d выбрано • %2$s + Это невозможно отменить. Android может попросить вас подтвердить удаление в системном диалоговом окне. + Не найдено + Переместить в начало + Переместить в конец + \ No newline at end of file diff --git a/core/resources/src/main/res/values-si/strings.xml b/core/resources/src/main/res/values-si/strings.xml new file mode 100644 index 0000000..3cd9ef3 --- /dev/null +++ b/core/resources/src/main/res/values-si/strings.xml @@ -0,0 +1,3739 @@ + + + + ගැටලුවක් පවතී: %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 පිටුවලින් සබැඳි, අදහස්, උද්දීපනය, හැඩතල, හෝ පෝරම ක්ෂේත්‍ර වැනි තෝරාගත් විවරණ වර්ග ඉවත් කරන්න + අධි සබැඳි + ගොනු ඇමුණුම් + රේඛා + උත්පතන + මුද්දර + හැඩතල + පෙළ සටහන් + පෙළ සලකුණු කිරීම + ආකෘති ක්ෂේත්ර + සලකුණු කිරීම + නොදන්නා + විවරණ + සමූහ ඉවත් කරන්න + වින්‍යාසගත කළ හැකි වර්ණ සහ ඕෆ්සෙට් සහිත ස්ථරය පිටුපස බොඳ සෙවනැල්ල එක් කරන්න + අන්තර්ගතය දැනුවත් විකෘති කිරීම + මෙම ක්‍රියාව සම්පූර්ණ කිරීමට ප්‍රමාණවත් මතකයක් නොමැත. කරුණාකර කුඩා රූපයක් භාවිතා කිරීමට, වෙනත් යෙදුම් වැසීමට හෝ යෙදුම නැවත ආරම්භ කිරීමට උත්සාහ කරන්න. + සමාන්තර කම්කරුවන් + පරිවර්තනය කරන ලද ගොනු මුල් පිටපත්වලට වඩා විශාල වන නිසා මෙම පින්තූර සුරැකුණේ නැත. මුල් පින්තූර මකා දැමීමට පෙර මෙම ලැයිස්තුව පරීක්ෂා කරන්න. + ගොනු මඟ හැරිණි: %1$s + යෙදුම් ලොග + ගැටළු හඳුනා ගැනීමට යෙදුමේ ලොග් බලන්න + සමූහගත මෙවලම්වල ප්‍රියතමයන් + මෙවලම් වර්ගය අනුව කාණ්ඩ කළ විට ටැබ් එකක් ලෙස ප්‍රියතමයන් එක් කරයි + අන්තිම ලෙස ප්රියතම පෙන්වන්න + ප්‍රියතම මෙවලම් පටිත්ත අවසානය දක්වා ගෙන යයි + තිරස් පරතරය + සිරස් පරතරය + පසුගාමී ශක්තිය + ඉදිරි ශක්ති ඇල්ගොරිතම වෙනුවට සරල ශ්‍රේණියේ විශාලත්වය බලශක්ති සිතියම භාවිතා කරන්න + ආවරණ සහිත ප්රදේශය ඉවත් කරන්න + මැහුම් ආවරණ කලාපය හරහා ගමන් කරනු ඇත, එය ආරක්ෂා කිරීම වෙනුවට තෝරාගත් ප්රදේශය පමණක් ඉවත් කරනු ඇත + VHS NTSC + උසස් NTSC සැකසුම් + ශක්තිමත් VHS සහ විකාශන පුරාවස්තු සඳහා අමතර ඇනලොග් සුසර කිරීම + ක්රෝමා ලේ ගැලීම + ටේප් ඇඳීම + ලුහුබැඳීම + ලුමා ස්මියර් + නාද වෙනවා + හිම + ක්ෂේත්‍රය භාවිතා කරන්න + පෙරහන් වර්ගය + ලුමා පෙරහන ඇතුල් කරන්න + ක්‍රෝමා ලෝපාස් ආදානය කරන්න + ක්‍රෝමා ඩිමොඩියුලේෂන් + අදියර මාරුව + අදියර ඕෆ්සෙට් + හිස මාරු කිරීම + හිස මාරු කිරීමේ උස + හිස මාරු කිරීම ඕෆ්සෙට් + හිස මාරු කිරීමේ මාරුව + මැද රේඛාවේ පිහිටීම + මැද පේළියේ කම්පනය + ශබ්ද උස නිරීක්ෂණය කිරීම + ලුහුබැඳීමේ තරංගය + හිම ලුහුබැඳීම + හිම ඇනිසොට්‍රොපි නිරීක්ෂණය කිරීම + ලුහුබැඳීමේ ශබ්දය + ශබ්ද තීව්‍රතාව නිරීක්ෂණය කිරීම + සංයුක්ත ශබ්දය + සංයුක්ත ශබ්ද සංඛ්යාතය + සංයුක්ත ශබ්ද තීව්රතාව + සංයුක්ත ශබ්ද විස්තරය + නාද කිරීමේ වාර ගණන + නාද බලය + ලුමා ශබ්දය + ලුමා ශබ්ද සංඛ්යාතය + ලුමා ශබ්ද තීව්රතාව + ලුමා ශබ්ද විස්තරය + ක්‍රෝමා ශබ්දය + ක්‍රෝමා ශබ්ද සංඛ්‍යාතය + ක්‍රෝමා ශබ්ද තීව්‍රතාවය + ක්‍රෝමා ශබ්ද විස්තරය + හිම තීව්රතාවය + හිම ඇනිසොට්‍රොපි + ක්‍රෝමා ෆේස් ශබ්දය + ක්‍රෝමා ෆේස් දෝෂය + ක්‍රෝමා ප්‍රමාදය තිරස් + ක්‍රෝමා ප්‍රමාදය සිරස් අතට + VHS ටේප් වේගය + VHS ක්‍රෝමා අලාභය + VHS තීව්‍රතාවය තියුණු කරයි + VHS තියුනු සංඛ්යාතය + VHS දාර තරංග තීව්‍රතාවය + VHS දාර තරංග වේගය + VHS දාර තරංග සංඛ්යාතය + VHS එජ් තරංග විස්තරය + ප්‍රතිදානය ක්‍රෝමා ලෝපාස් + තිරස් පරිමාණය + පරිමාණය සිරස් අතට + පරිමාණ සාධකය X + පරිමාණ සාධකය Y + පොදු රූප ජල සලකුණු හඳුනාගෙන ඒවා ලාමා සමඟ තීන්ත ආලේප කරයි. හඳුනාගැනීම් සහ පින්තාරු කිරීමේ ආකෘති ස්වයංක්‍රීයව බාගත කරයි + ඩියුටරනෝපියාව + රූපය දිග හරින්න + YOLO v11 Segmentation භාවිතයෙන් වස්තු ඛණ්ඩනය පදනම් කරගත් පසුබිම් ඉවත් කරන්නා + රේඛා කෝණය පෙන්වන්න + ඇඳීම අතරතුර වත්මන් රේඛා භ්‍රමණය අංශක වලින් පෙන්වයි + සජීවිකරණ ඉමෝජි + පවතින ඉමොජි සජීවිකරණ ලෙස පෙන්වන්න + මුල් ෆෝල්ඩරයට සුරකින්න + තෝරාගත් ෆෝල්ඩරය වෙනුවට මුල් ගොනුව අසල නව ගොනු සුරකින්න + ජල සලකුණ PDF + මතකය මදි + මෙම උපාංගයේ සැකසීමට රූපය විශාල වැඩිය, නැතහොත් පද්ධතිය පවතින මතකය අවසන් වී ඇත. පින්තූර විභේදනය අඩු කිරීමට, වෙනත් යෙදුම් වැසීමට හෝ කුඩා ගොනුවක් තෝරා ගැනීමට උත්සාහ කරන්න. + දෝශ නිරාකරණ මෙනුව + යෙදුම් කාර්යයන් පරීක්ෂා කිරීමට මෙනුව, මෙය නිෂ්පාදන නිකුතුවේ පෙන්වීමට අදහස් නොකෙරේ + සපයන්නා + PaddleOCR හට ඔබගේ උපාංගයේ අමතර ONNX මාදිලි අවශ්‍ය වේ. ඔබට %1$s දත්ත බාගැනීමට අවශ්‍යද? + විශ්වීය + කොරියානු + ලතින් + නැගෙනහිර ස්ලාවික් + තායි + ග්රීක + ඉංග්රීසි + සිරිලික් + අරාබි + දේවනාගරි + දෙමළ + තෙළිඟු + ෂේඩර් + ෂේඩර් පෙරසිටුවයි + සෙවනක් තෝරා නැත + Shader Studio + අභිරුචි ඛණ්ඩක සෙවන සාදන්න, සංස්කරණය කරන්න, වලංගු කරන්න, ආනයනය කරන්න සහ අපනයනය කරන්න + සුරැකි සෙවන + සුරකින ලද සෙවනැලි විවෘත කරන්න, අනුපිටපත් කරන්න, අපනයනය කරන්න, බෙදාගන්න, හෝ මකන්න + නව සෙවන + Shader ගොනුව + .itshader JSON + %1$d පරාමිති + සෙවන මූලාශ්රය + හිස් ප්‍රධාන() හි සිරුර පමණක් ලියන්න. UV ඛණ්ඩාංක සඳහා textureCoordinate සහ මූලාශ්‍ර සාම්පලයක් ලෙස inputImageTexture භාවිතා කරන්න. + කාර්යයන් + විකල්ප සහායක ශ්‍රිත, නියතයන්, සහ ව්‍යුහයන් හිස් ප්‍රධාන() ට පෙර ඇතුළත් කර ඇත. නිල ඇඳුම් නිර්මාණය වන්නේ පරාමිති වලින්. + සෙවනට සංස්කරණය කළ හැකි අගයන් අවශ්‍ය විට නිල ඇඳුම් මෙහි එක් කරන්න. + සෙවන නැවත සකසන්න + වත්මන් සෙවන කෙටුම්පත හිස් කරනු ඇත + වලංගු නොවන සෙවන + සහය නොදක්වන සෙවන අනුවාදය %1$d. සහාය දක්වන අනුවාදය: %2$d. + සෙවන නම හිස් නොවිය යුතුය. + සෙවන මූලාශ්‍රය හිස් නොවිය යුතුය. + Shader මූලාශ්‍රයේ සහය නොදක්වන අක්ෂර අඩංගු වේ: %1$s. ASCII GLSL මූලාශ්‍රය පමණක් භාවිතා කරන්න. + \\\"%1$s\\\" පරාමිතිය එක් වරකට වඩා ප්‍රකාශ කර ඇත. + පරාමිති නම් හිස් නොවිය යුතුය. + \\\"%1$s\\\" පරාමිතිය වෙන් කළ GPUImage නමක් භාවිතා කරයි. + \\\"%1$s\\\" පරාමිතිය වලංගු GLSL හැඳුනුම්කාරකයක් විය යුතුය. + \\\"%1$s\\\" පරාමිතිය %2$s අගය වර්ගය \\\"%3$s\\\" ඇත, අපේක්ෂිත \\\"%4$s\\\". + \\\"%1$s\\\" පරාමිතියට bool අගයන් සඳහා min හෝ max නිර්වචනය කළ නොහැක. + \\\"%1$s\\\" පරාමිතිය උපරිමයට වඩා මිනි වැඩිය. + \\\"%1$s\\\" පරාමිතිය පෙරනිමිය විනාඩියට වඩා අඩුය. + පරාමිතිය \\\"%1$s\\\" පෙරනිමිය උපරිමයට වඩා වැඩිය. + Shader විසින් \\\"ඒකාකාර සාම්පල2D %1$s;\\\" ප්‍රකාශ කළ යුතුය. + Shader විසින් \\\"වෙනස් vec2 %1$s;\\\" ප්‍රකාශ කළ යුතුය. + Shader විසින් \\\"void main()\\\" අර්ථ දැක්විය යුතුය. + Shader gl_FragColor වෙත වර්ණයක් ලිවිය යුතුය. + ShaderToy mainImage shaders සඳහා සහය නොදක්වයි. GPUImage හි අවලංගු ප්‍රධාන() ගිවිසුම භාවිතා කරන්න. + සජීවිකරණ ShaderToy නිල ඇඳුම \\\"iTime\\\" සඳහා සහය නොදක්වයි. + සජීවිකරණ ShaderToy නිල ඇඳුම \\\"iFrame\\\" සඳහා සහය නොදක්වයි. + ShaderToy නිල ඇඳුම \\\"iResolution\\\" සඳහා සහය නොදක්වයි. + ShaderToy iChannel වයනය සඳහා සහය නොදක්වයි. + බාහිර වයනය සඳහා සහය නොදක්වයි. + බාහිර වයනය දිගු සඳහා සහය නොදක්වයි. + Libretro සෙවන පරාමිතීන් සඳහා සහය නොදක්වයි. + එක් ආදාන වයනයකට පමණක් සහය දක්වයි. \\\"නිල ඇඳුම %1$s %2$s;\\\" ඉවත් කරන්න. + \\\"%1$s\\\" පරාමිතිය \\\"ඒකාකාර %2$s %1$s;\\\" ලෙස ප්‍රකාශ කළ යුතුය. + \\\"%1$s\\\" පරාමිතිය \\\"ඒකාකාර %2$s %1$s;\\\" ලෙස ප්‍රකාශ කර ඇත, අපේක්ෂිත \\\"ඒකාකාර %3$s %1$s;\\\". + Shader ගොනුව හිස්ය. + Shader ගොනුව අනුවාදය, නම සහ සෙවන ක්ෂේත්‍ර සහිත .itshader JSON වස්තුවක් විය යුතුය. + Shader ගොනුව JSON වලංගු නැත හෝ .itshader ආකෘතියට නොගැලපේ. + ක්ෂේත්‍රය \\\"%1$s\\\" අවශ්‍යයි. + %1$s අවශ්‍යයි. + %1$s \\\"%2$s\\\" සහාය නොදක්වයි. සහාය දක්වන වර්ග: %3$s. + %1$s \\\"%2$s\\\" පරාමිති සඳහා අවශ්‍ය වේ. + %1$s පරිමිත අංකයක් විය යුතුය. + %1$s නිඛිලයක් විය යුතුය. + %1$s සත්‍ය හෝ අසත්‍ය විය යුතුය. + %1$s වර්ණ තන්තුවක්, RGB/RGBA අරාවක් හෝ වර්ණ වස්තුවක් විය යුතුය. + %1$s #RRGGBB හෝ #RRGGBBAA ආකෘතිය භාවිතා කළ යුතුය. + %1$s හි වලංගු ෂඩ් දශම වර්ණ නාලිකා අඩංගු විය යුතුය. + %1$s හි වර්ණ නාලිකා 3ක් හෝ 4ක් අඩංගු විය යුතුය. + %1$s 0 සහ 255 අතර විය යුතුය. + %1$s ඉලක්කම් දෙකක අරාවක් හෝ x සහ y සහිත වස්තුවක් විය යුතුය. + %1$s හි හරියටම අංක 2ක් අඩංගු විය යුතුය. + අනුපිටපත් කරන්න + සෑම විටම EXIF ​​ඉවත් කරන්න + මෙවලමක් පාර-දත්ත තබා ගැනීමට ඉල්ලා සිටින විට පවා, සුරැකීමේදී රූප EXIF ​​දත්ත ඉවත් කරන්න + උදව් සහ ඉඟි + %1$d නිබන්ධන + විවෘත මෙවලම + ප්‍රධාන මෙවලම්, අපනයන විකල්ප, PDF ප්‍රවාහ, වර්ණ උපයෝගිතා සහ පොදු ගැටළු සඳහා විසඳුම් ඉගෙන ගන්න + ඇරඹේ + මෙවලම් තෝරන්න, ගොනු ආයාත කරන්න, ප්‍රතිඵල සුරකින්න, සහ සැකසීම් ඉක්මනින් නැවත භාවිත කරන්න + රූප සංස්කරණය + රූප ප්‍රතිප්‍රමාණ කරන්න, කප්පාදු කරන්න, පෙරහන් කරන්න, පසුබිම් මකා දමන්න, චිත්‍ර අඳින්න, සහ ජල සලකුණු කරන්න + ගොනු සහ පාර-දත්ත + ආකෘති පරිවර්තනය කරන්න, ප්රතිදානය සංසන්දනය කරන්න, පෞද්ගලිකත්වය ආරක්ෂා කරන්න, සහ ගොනු නාම පාලනය කරන්න + PDF සහ ලේඛන + PDF සාදන්න, ලේඛන පරිලෝකනය කරන්න, OCR පිටු, සහ බෙදාගැනීම සඳහා ගොනු සකස් කරන්න + පෙළ, QR, සහ දත්ත + පෙළ හඳුනා ගන්න, කේත ස්කෑන් කරන්න, Base64 කේතනය කරන්න, හැෂ් පරීක්ෂා කරන්න, සහ පැකේජ ගොනු + වර්ණ මෙවලම් + වර්ණ තෝරන්න, පැලට් ගොඩනඟන්න, වර්ණ පුස්තකාල ගවේෂණය කරන්න, සහ අනුක්‍රම සාදන්න + නිර්මාණාත්මක මෙවලම් + SVG, ASCII කලාව, ශබ්ද වයනය, සෙවනැලි, සහ පර්යේෂණාත්මක දර්ශන උත්පාදනය කරන්න + දෝෂගවේෂණය + බර ගොනු, විනිවිදභාවය, බෙදාගැනීම්, ආයාත කිරීම් සහ වාර්තා කිරීමේ ගැටළු නිරාකරණය කරන්න + නිවැරදි මෙවලම තෝරන්න + නියම තැනින් ආරම්භ කිරීමට ප්‍රවර්ග, සෙවීම, ප්‍රියතමයන්, සහ බෙදාගැනීමේ ක්‍රියා භාවිත කරන්න + හොඳම පිවිසුම් ස්ථානයෙන් ආරම්භ කරන්න + රූප මෙවලම් පෙට්ටියේ බොහෝ නාභිගත මෙවලම් ඇති අතර, ඒවායින් බොහොමයක් බැලූ බැල්මට අතිච්ඡාදනය වේ. ඔබට අවසන් කිරීමට අවශ්‍ය කාර්යයෙන් ආරම්භ කරන්න, ප්‍රතිඵලයේ වැදගත්ම කොටස පාලනය කරන මෙවලම තෝරන්න: ප්‍රමාණය, ආකෘතිය, පාර-දත්ත, පෙළ, PDF පිටු, වර්ණ, හෝ පිරිසැලසුම. + ප්‍රමාණය වෙනස් කිරීම, PDF, EXIF, OCR, QR, හෝ වර්ණය වැනි ක්‍රියා නාමය ඔබ දන්නා විට සෙවීම භාවිත කරන්න. + ඔබ කාර්ය වර්ගය පමණක් දන්නා විට ප්‍රවර්ගයක් විවෘත කරන්න, ඉන්පසු නිතර මෙවලම් ප්‍රියතමයන් ලෙස අමුණන්න. + වෙනත් යෙදුමක් රූප මෙවලම් පෙට්ටියට පින්තූරයක් බෙදා ගන්නා විට, බෙදාගැනීමේ පත්‍ර ප්‍රවාහයෙන් මෙවලම තෝරන්න. + ප්‍රතිඵල ආයාත කරන්න, සුරකින්න, සහ බෙදාගන්න + ආදානය තෝරා ගැනීමේ සිට සංස්කරණය කළ ගොනුව අපනයනය කිරීම දක්වා සුපුරුදු ප්‍රවාහය තේරුම් ගන්න + පොදු සංස්කරණ ප්‍රවාහය අනුගමනය කරන්න + බොහෝ මෙවලම් එකම රිද්මයක් අනුගමනය කරයි: ආදානය තෝරන්න, විකල්ප සීරුමාරු කරන්න, පෙරදසුන, පසුව සුරකින්න හෝ බෙදාගන්න. වැදගත් විස්තරය නම් සුරැකීම ප්‍රතිදාන ගොනුවක් නිර්මාණය කරන අතර බෙදා ගැනීම සාමාන්‍යයෙන් වෙනත් යෙදුමකට ඉක්මන් භාරදීම සඳහා හොඳම වේ. + පිකර් ඉඩ දෙන විට තනි රූප මෙවලම් සඳහා එක් ගොනුවක් හෝ කණ්ඩායම් මෙවලම් සඳහා ගොනු කිහිපයක් තෝරන්න. + සුරැකීමට පෙර පෙරදසුන සහ ප්‍රතිදාන පාලන පරීක්ෂා කරන්න, විශේෂයෙන් ආකෘතිය, ගුණාත්මකභාවය සහ ගොනු නාම විකල්ප. + ඉක්මන් භාරදීමක් සඳහා බෙදාගැනීම භාවිතා කරන්න, නැතහොත් ඔබට තෝරාගත් ෆෝල්ඩරය තුළ ස්ථිර පිටපතක් අවශ්‍ය වූ විට සුරකින්න. + බොහෝ පින්තූර සමඟ එකවර වැඩ කරන්න + කණ්ඩායම් මෙවලම් නැවත නැවත ප්‍රමාණය වෙනස් කිරීම, පරිවර්තනය කිරීම, සම්පීඩනය සහ නම් කිරීමේ කාර්යයන් සඳහා හොඳම වේ + පුරෝකථනය කළ හැකි කණ්ඩායමක් සූදානම් කරන්න + සෑම නිමැවුමක්ම එකම නීති අනුගමනය කළ යුතු විට කණ්ඩායම් සැකසීම වේගවත් වේ. එය විශාල වැරැද්දක් කිරීමට පහසුම ස්ථානය ද වේ, එබැවින් දිගු අපනයනයක් ආරම්භ කිරීමට පෙර නම් කිරීම, ගුණාත්මකභාවය, පාර-දත්ත සහ ෆෝල්ඩර හැසිරීම තෝරන්න. + ප්‍රතිදානය අනුපිළිවෙල මත රඳා පවතී නම්, සම්බන්ධිත සියලුම පින්තූර එකට තෝරා අනුපිළිවෙල තබා ගන්න. + අපනයනය ආරම්භ කිරීමට පෙර ප්‍රමාණය වෙනස් කිරීම, ආකෘතිය, ගුණාත්මකභාවය, පාර-දත්ත සහ ගොනු නාම රීති එක් වරක් සකසන්න. + මූලාශ්‍ර ගොනු විශාල වන විට හෝ ඉලක්ක ආකෘතිය ඔබට අලුත් වූ විට පළමුව කුඩා පරීක්ෂණ කණ්ඩායමක් ධාවනය කරන්න. + ඉක්මන් තනි සංස්කරණය + ඔබට එක් රූපයක් මත සරල වෙනස්කම් කිහිපයක් අවශ්‍ය වූ විට සියල්ලෙන් එක සංස්කාරකය භාවිතා කරන්න + මෙවලම් පැනීමකින් තොරව එක් රූපයක් අවසන් කරන්න + එක් විශේෂිත මෙහෙයුමකට වඩා රූපයට කුඩා වෙනස්කම් දාමයක් අවශ්‍ය වූ විට තනි සංස්කරණය ප්‍රයෝජනවත් වේ. කණ්ඩායම් කාර්ය ප්‍රවාහයක් ගොඩනැගීමකින් තොරව ඉක්මන් පිරිසිදු කිරීම, පෙරදසුන් කිරීම සහ එක් අවසාන පිටපතක් අපනයනය කිරීම සඳහා සාමාන්‍යයෙන් වේගවත් වේ. + පොදු ගැලපීම්, සලකුණු කිරීම, කැපීම, භ්‍රමණය, හෝ අපනයන වෙනස්කම් අවශ්‍ය එක් රූපයක් සඳහා තනි සංස්කරණයක් විවෘත කරන්න. + පෙරහන් වලට පෙර කප්පාදු කිරීම සහ අවසාන සම්පීඩනයට පෙර පෙරහන් වැනි අඩුම පික්සල පළමුව වෙනස් කරන අනුපිළිවෙලෙහි සංස්කරණයන් යොදන්න. + ඔබට කණ්ඩායම් සැකසීම, නිශ්චිත ගොනු ප්‍රමාණයේ ඉලක්ක, PDF ප්‍රතිදානය, OCR, හෝ පාරදත්ත පිරිසිදු කිරීම අවශ්‍ය වූ විට ඒ වෙනුවට විශේෂිත මෙවලමක් භාවිතා කරන්න. + පෙරසිටුවීම් සහ සැකසුම් භාවිතා කරන්න + නැවත නැවත තේරීම් සුරකින්න සහ ඔබ කැමති කාර්ය ප්‍රවාහය සඳහා යෙදුම සුසර කරන්න + එකම සැකසුම නැවත නැවත කිරීමෙන් වළකින්න + ඔබ එකම ආකාරයේ ගොනුවක් නිතර අපනයනය කරන විට පෙරසිටුවීම් සහ සැකසුම් ප්‍රයෝජනවත් වේ. හොඳ පෙරසිටුවක් පුනරාවර්තන අත්පොත පිරික්සුම් ලැයිස්තුවක් එක් ටැප් එකක් බවට පත් කරන අතර ගෝලීය සැකසුම් නව මෙවලම් ආරම්භ කරන පෙරනිමියන් නිර්වචනය කරයි. + පොදු ප්‍රතිදාන ප්‍රමාණ, ආකෘති, තත්ත්ව අගයන් හෝ නම් කිරීමේ රටා සඳහා පෙරසිටහන් සාදන්න. + පෙරනිමි ආකෘතිය, ගුණාත්මකභාවය, සුරකින්න ෆෝල්ඩරය, ගොනු නාමය, පිකර්, සහ අතුරු මුහුණත හැසිරීම සඳහා සැකසීම් සමාලෝචනය කරන්න. + යෙදුම නැවත ස්ථාපනය කිරීමට හෝ වෙනත් උපාංගයකට යාමට පෙර සිටුවම් උපස්ථ කරන්න. + ප්රයෝජනවත් පෙරනිමි සකසන්න + පෙරනිමි ආකෘතිය, ගුණාත්මකභාවය, පරිමාණ මාදිලිය, වර්ගය ප්‍රමාණය වෙනස් කිරීම සහ වර්ණ අවකාශය වරක් තෝරන්න + නව මෙවලම් ඔබේ ඉලක්කයට ආසන්නව ආරම්භ කරන්න + පෙරනිමි අගයන් රූපලාවණ්‍ය මනාපයන් පමණක් නොවේ. සෑම අපනයනයකම එකම තේරීම් නැවත තෝරා ගැනීම වළක්වා ගැනීමට උපකාර වන බොහෝ මෙවලම්වල පළමුව දිස්වන ආකෘතිය, ගුණාත්මකභාවය, ප්‍රමාණය වෙනස් කිරීමේ හැසිරීම, පරිමාණ මාදිලිය සහ වර්ණ අවකාශය ඔවුන් තීරණය කරයි. + ඡායාරූප සඳහා JPEG හෝ ඇල්ෆා සඳහා PNG/WebP වැනි ඔබේ සුපුරුදු ගමනාන්තයට ගැළපෙන පරිදි පෙරනිමි රූප ආකෘතිය සහ ගුණත්වය සකසන්න. + ඔබ බොහෝ විට දැඩි මානයන්, සමාජ වේදිකා, හෝ ලේඛන පිටු සඳහා අපනයනය කරන්නේ නම් පෙරනිමි ප්‍රතිප්‍රමාණයේ වර්ගය සහ පරිමාණ මාදිලිය සුසර කරන්න. + ඔබේ වැඩ ප්‍රවාහයට සංදර්ශක, මුද්‍රණ හෝ බාහිර සංස්කාරක හරහා ස්ථාවර වර්ණ හැසිරවීමක් අවශ්‍ය වූ විට පමණක් වර්ණ අවකාශයේ පෙරනිමි වෙනස් කරන්න. + පිකර් සහ දියත් කිරීම + යෙදුම බොහෝ සංවාද විවෘත කරන විට හෝ වැරදි තැනකින් ආරම්භ වන විට පිකර් සැකසීම් භාවිත කරන්න + විවෘත කරන ගොනු ඔබේ පුරුද්දට ගැලපෙන ලෙස සකස් කරන්න + පිකර් සහ දියත් කිරීමේ සිටුවම් මඟින් රූප මෙවලම් පෙට්ටිය ප්‍රධාන තිරයෙන්, මෙවලමකින් හෝ ගොනු තේරීමේ ප්‍රවාහයෙන් ආරම්භ වන්නේද යන්න පාලනය කරයි. ඔබ සාමාන්‍යයෙන් ඇන්ඩ්‍රොයිඩ් කොටස් පත්‍රයෙන් ඇතුළු වන විට හෝ යෙදුම මෙවලම් දියත් කිරීමක් ලෙස දැනෙන්නට අවශ්‍ය විට මෙම විකල්ප විශේෂයෙන් ප්‍රයෝජනවත් වේ. + පද්ධති පිකර් ගොනු සඟවන්නේ නම්, මෑත කාලීන අයිතම බොහොමයක් පෙන්වන්නේ නම් හෝ වලාකුළු ගොනු දුර්වල ලෙස හසුරුවන්නේ නම් ඡායාරූප පිකර් සැකසීම් භාවිත කරන්න. + ඔබ සාමාන්‍යයෙන් බෙදාගැනීමෙන් ඇතුළු වන හෝ දැනටමත් මූලාශ්‍ර ගොනුව දන්නා මෙවලම් සඳහා පමණක් skip picking සබල කරන්න. + ඔබට රූප මෙවලම් පෙට්ටිය සාමාන්‍ය මුල් තිරයකට වඩා මෙවලම් පිකර් එකක් ලෙස හැසිරීමට අවශ්‍ය නම් දියත් කිරීමේ මාදිලිය සමාලෝචනය කරන්න. + රූප මූලාශ්රය + ගැලරි, ගොනු කළමනාකරුවන් සහ වලාකුළු ගොනු සමඟ වඩාත් හොඳින් ක්‍රියා කරන පිකර් මාදිලිය තෝරන්න + නිවැරදි මූලාශ්‍රයෙන් ගොනු තෝරන්න + රූප මූලාශ්‍ර සැකසීම් යෙදුම ඇන්ඩ්‍රොයිඩ් වෙතින් පින්තූර ඉල්ලන ආකාරය බලපායි. හොඳම තේරීම රඳා පවතින්නේ ඔබේ ගොනු පද්ධති ගැලරිය, ගොනු කළමනාකරු ෆෝල්ඩරය, වලාකුළු සපයන්නා හෝ තාවකාලික අන්තර්ගතය බෙදා ගන්නා වෙනත් යෙදුමක ජීවත් වන්නේද යන්න මතය. + ඔබට පොදු ගැලරි රූප සඳහා වඩාත්ම පද්ධති ඒකාබද්ධ අත්දැකීමක් අවශ්‍ය විට පෙරනිමි පිකර් භාවිතා කරන්න. + ඇල්බම, ෆෝල්ඩර, වලාකුළු ගොනු, හෝ සම්මත නොවන ආකෘති ඔබ බලාපොරොත්තු වන ස්ථානයේ නොපෙන්වන්නේ නම් පිකර් මාදිලිය මාරු කරන්න. + මූලාශ්‍ර යෙදුමෙන් ඉවත් වූ පසු බෙදා ගත් ගොනුවක් අතුරුදහන් වුවහොත්, එය නොනැසී පවතින පිකර් හරහා නැවත විවෘත කරන්න නැතහොත් පළමුව දේශීය පිටපතක් සුරකින්න. + ප්රියතම සහ හුවමාරු මෙවලම් + ප්‍රධාන තිරය නාභිගත කර තබාගෙන බෙදාගැනීම් ප්‍රවාහවල තේරුමක් නැති මෙවලම් සඟවන්න + මෙවලම් ලැයිස්තුවේ ශබ්දය අඩු කරන්න + ඔබ දිනපතා මෙවලම් කිහිපයක් පමණක් භාවිතා කරන විට ප්‍රියතමයන් සහ බෙදාගැනීම සඳහා සැඟවුණු සැකසීම් ප්‍රයෝජනවත් වේ. වෙනත් යෙදුමක් යවන ගොනු වර්ගය භාවිත කළ නොහැකි මෙවලම් සැඟවීමෙන් ඔවුන් බෙදාගැනීමේ ප්‍රවාහය ප්‍රායෝගිකව තබා ගනී. + ප්‍රවර්ග අනුව මෙවලම් කාණ්ඩ කළ විට පවා පහසුවෙන් ළඟා වීමට හැකි වන පරිදි නිතර මෙවලම් අමුණන්න. + වෙනත් යෙදුමකින් එන ගොනු සඳහා මෙවලම් අදාළ නොවන විට බෙදාගැනීම් ප්‍රවාහවලින් සඟවන්න. + ඔබ අවසානයේ ප්‍රියතමයන් කැමති නම් හෝ අදාළ මෙවලම් සමඟ සමූහගත කරන්නේ නම් ප්‍රියතම ඇණවුම් සැකසීම් භාවිත කරන්න. + උපස්ථ සැකසුම් + පෙරසිටුවීම්, මනාප සහ යෙදුම් හැසිරීම ආරක්ෂිතව වෙනත් ස්ථාපනයකට ගෙන යන්න + ඔබගේ සැකසුම අතේ ගෙන යා හැකි ලෙස තබා ගන්න + ඔබ පෙරසිටුවීම්, ෆෝල්ඩර, ගොනු නාම රීති, මෙවලම් අනුපිළිවෙල සහ දෘශ්‍ය මනාපයන් සුසර කළ පසු උපස්ථ සහ ප්‍රතිසාධනය වඩාත් ප්‍රයෝජනවත් වේ. උපස්ථයක් මඟින් ඔබට සැකසීම් සමඟ අත්හදා බැලීමට හෝ මතකයෙන් ඔබේ කාර්ය ප්‍රවාහය ප්‍රතිනිර්මාණය නොකර යෙදුම නැවත ස්ථාපනය කිරීමට ඉඩ සලසයි. + පෙරසිටුවීම්, ගොනු නාම හැසිරීම, පෙරනිමි ආකෘති හෝ මෙවලම් සැකැස්ම වෙනස් කිරීමෙන් පසු උපස්ථයක් සාදන්න. + ඔබ දත්ත ඉවත් කිරීමට, නැවත ස්ථාපනය කිරීමට හෝ උපාංග ගෙන යාමට අදහස් කරන්නේ නම් යෙදුම් ෆෝල්ඩරයෙන් පිටත කොහේ හරි උපස්ථය ගබඩා කරන්න. + වැදගත් කාණ්ඩ සැකසීමට පෙර සිටුවම් ප්‍රතිසාධනය කරන්න එවිට පෙරනිමිය සහ හැසිරීම සුරකින්න ඔබේ පැරණි සැකසුමට ගැලපේ. + පින්තූර ප්‍රතිප්‍රමාණ කර පරිවර්තනය කරන්න + පින්තූර එකක් හෝ කිහිපයක් සඳහා ප්‍රමාණය, ආකෘතිය, ගුණාත්මකභාවය සහ පාර-දත්ත වෙනස් කරන්න + ප්‍රමාණය වෙනස් කළ පිටපත් සාදන්න + ප්‍රතිප්‍රමාණ කිරීම සහ පරිවර්තනය යනු පුරෝකථනය කළ හැකි මානයන් සහ සැහැල්ලු ප්‍රතිදාන ගොනු සඳහා වන ප්‍රධාන මෙවලමයි. රූපයේ ප්‍රමාණය, ප්‍රතිදාන ආකෘතිය සහ පාරදත්ත හැසිරීම යන සියල්ල එකවර වැදගත් වන විට එය භාවිතා කරන්න. + පින්තූර එකක් හෝ කිහිපයක් තෝරන්න, පසුව මානයන්, පරිමාණය හෝ ගොනු ප්‍රමාණය වඩාත් වැදගත් වන්නේද යන්න තෝරන්න. + ප්රතිදාන ආකෘතිය සහ ගුණාත්මකභාවය තෝරන්න; විනිවිදභාවය සුරැකිය යුතු විට PNG හෝ WebP භාවිතා කරන්න. + ප්‍රතිඵලය පෙරදසුන් කරන්න, ඉන්පසු මුල් පිටපත් වෙනස් නොකර උත්පාදනය කරන ලද පිටපත් සුරකින්න හෝ බෙදාගන්න. + ගොනු ප්‍රමාණය අනුව ප්‍රමාණය වෙනස් කරන්න + වෙබ් අඩවියක්, පණිවිඩකරුවෙකු හෝ පෝරමයක් බර රූප ප්‍රතික්ෂේප කරන විට ප්‍රමාණ සීමාවක් ඉලක්ක කරන්න + දැඩි උඩුගත කිරීමේ සීමාවන්ට ගැලපේ + ගමනාන්තය නිශ්චිත සීමාවකට වඩා අඩු ගොනු පමණක් පිළිගන්නා විට ගුණාත්මක අගයන් අනුමාන කිරීමට වඩා ගොනු ප්‍රමාණය අනුව ප්‍රමාණය වෙනස් කිරීම වඩා හොඳය. මෙවලම භාවිතා කළ හැකි ප්‍රතිඵලය තබා ගැනීමට උත්සාහ කරන අතරම ඉලක්කයට ළඟා වීමට සම්පීඩනය සහ මානයන් සකස් කළ හැක. + පාර-දත්ත සහ සැපයුම්කරුවන්ගේ වෙනස්කම් සඳහා ඉඩ තැබීමට සැබෑ උඩුගත කිරීමේ සීමාවට වඩා මඳක් පහළින් ඉලක්ක ප්‍රමාණය ඇතුළු කරන්න. + ඉලක්කය කුඩා වන විට ඡායාරූප සඳහා අහිමි ආකෘති වලට කැමති වන්න; ප්‍රමාණය වෙනස් කිරීමෙන් පසුව පවා PNG හට විශාලව පැවතිය හැක. + ආක්‍රමණශීලී ප්‍රමාණයේ ඉලක්කවලට දෘශ්‍යමාන කෞතුක වස්තු හඳුන්වා දිය හැකි බැවින් අපනයනයෙන් පසු මුහුණු, පෙළ සහ දාර පරීක්ෂා කරන්න. + ප්‍රමාණය සීමා කරන්න + ඔබගේ උපරිම පළල, උස, හෝ ගොනු සීමාවන් ඉක්මවන පින්තූර පමණක් හැකිලීම + දැනටමත් හොඳින් ඇති පින්තූර ප්‍රමාණය වෙනස් කිරීමෙන් වළකින්න + සමහර පින්තූර විශාල වන අතර අනෙක් ඒවා දැනටමත් සූදානම් කර ඇති මිශ්‍ර ෆෝල්ඩර සඳහා ප්‍රමාණය සීමා කිරීම ප්‍රයෝජනවත් වේ. සෑම ගොනුවක්ම එකම ප්‍රමාණය වෙනස් කිරීම හරහා බල කිරීම වෙනුවට, එය පිළිගත හැකි පින්තූර ඒවායේ මුල් ගුණාත්මක භාවයට සමීප කරයි. + සෑම ගොනුවක්ම අන්ධ ලෙස වෙනස් කරනවාට වඩා ගමනාන්තය මත පදනම්ව උපරිම මානයන් හෝ සීමාවන් සකසන්න. + මූලාශ්‍රවලට වඩා බරින් වැඩි ප්‍රතිදානයන් වළක්වා ගැනීමට ඔබට අවශ්‍ය වූ විට, විශාල නම්-විශාල විලාසිතාවේ හැසිරීම සක්‍රීය කරන්න. + මූලාශ්‍ර ප්‍රමාණයන් බොහෝ සෙයින් වෙනස් වන විවිධ කැමරා, තිර රූ, හෝ බාගැනීම් වලින් කණ්ඩායම් සඳහා මෙය භාවිතා කරන්න. + කපන්න, කරකවන්න, සහ කෙළින් කරන්න + වෙනත් මෙවලම්වල රූපය අපනයනය කිරීමට හෝ භාවිතා කිරීමට පෙර වැදගත් ප්‍රදේශය රාමු කරන්න + සංයුතිය පිරිසිදු කරන්න + පළමුව කප්පාදු කිරීම පසු පෙරහන්, OCR, PDF පිටු, සහ බෙදාගැනීමේ ප්‍රතිඵල පිරිසිදු කරයි. + කැපීම විවෘත කර ඔබට සකස් කිරීමට අවශ්‍ය රූපය තෝරන්න. + ප්‍රතිදානය පළ කිරීමකට, ලේඛනයකට හෝ අවතාරයකට ගැළපෙන විට නිදහස් බෝග හෝ දර්ශන අනුපාතයක් භාවිත කරන්න. + සුරැකීමට පෙර කරකවන්න හෝ පෙරළන්න එවිට පසුව මෙවලම් නිවැරදි කළ රූපය ලබා ගනී. + පෙරහන් සහ ගැලපීම් යොදන්න + සංස්කරණය කළ හැකි පෙරහන් තොගයක් ගොඩනඟා අපනයනය කිරීමට පෙර ප්‍රතිඵලය පෙරදසුන් කරන්න + රූපයේ පෙනුම සුසර කරන්න + ඉක්මන් නිවැරදි කිරීම්, ශෛලීගත ප්‍රතිදානය සහ OCR හෝ මුද්‍රණය සඳහා රූප සකස් කිරීම සඳහා පෙරහන් ප්‍රයෝජනවත් වේ. රූපයේ පෙළ හෝ සියුම් විස්තර අඩංගු වන විට ප්‍රතිවිරෝධය, තියුණු බව සහ දීප්තිය සඳහා කුඩා වෙනස්කම් බර බලපෑම්වලට වඩා වැදගත් විය හැක. + පෙරහන් එකින් එක එකතු කර එක් එක් වෙනස් කිරීමෙන් පසු පෙරදසුන දෙස අවධානයෙන් සිටින්න. + කාර්යය මත පදනම්ව දීප්තිය, වෙනස, තියුණු බව, නොපැහැදිලි බව, වර්ණය හෝ වෙස් මුහුණු සකසන්න. + පෙරදසුන ඔබගේ ඉලක්ක උපාංගය, ලේඛනය, හෝ සමාජ වේදිකාවට ගැලපෙන විට පමණක් අපනයනය කරන්න. + පළමුව පෙරදසුන් කරන්න + බර සංස්කරණයක්, පරිවර්තනයක්, හෝ පිරිසිදු කිරීමේ මෙවලමක් තෝරා ගැනීමට පෙර පින්තූර පරීක්ෂා කරන්න + ඔබට ඇත්ත වශයෙන්ම ලැබුණු දේ පරීක්ෂා කරන්න + ගොනුවක් කතාබස්, වලාකුළු ආචයනය හෝ වෙනත් යෙදුමකින් පැමිණෙන විට රූප පෙරදසුන ප්‍රයෝජනවත් වන අතර එහි අඩංගු දේ ඔබට විශ්වාස නැත. මානයන්, විනිවිදභාවය, දිශානතිය සහ දෘශ්‍ය ගුණාත්මකභාවය පරීක්ෂා කිරීම නිවැරදි පසු විපරම් මෙවලම තෝරා ගැනීමට උපකාරී වේ. + ඔබට ඒවා සමඟ කළ යුතු දේ තීරණය කිරීමට පෙර පින්තූර කිහිපයක් පරීක්ෂා කිරීමට අවශ්‍ය වූ විට රූප පෙරදසුන විවෘත කරන්න. + භ්‍රමණ ගැටළු, විනිවිද පෙනෙන ප්‍රදේශ, සම්පීඩන කෞතුක වස්තු සහ අනපේක්ෂිත ලෙස විශාල මානයන් සොයන්න. + ප්‍රතිප්‍රමාණ කිරීම, කප්පාදු කිරීම, සංසන්දනය කිරීම, EXIF ​​හෝ පසුබිම් ඉවත් කරන්නා වෙත යන්න ඔබ නිවැරදි කිරීමට අවශ්‍ය දේ දැනගත් පසුව පමණි. + පසුබිමක් ඉවත් කරන්න හෝ ප්‍රතිසාධන කරන්න + පසුබිම් ස්වයංක්‍රීයව මකන්න හෝ ප්‍රතිසාධන මෙවලම් සමඟින් දාර අතින් පිරිපහදු කරන්න + විෂය තබා ගන්න, ඉතිරිය ඉවත් කරන්න + ඔබ ප්‍රවේශමෙන් අතින් පිරිසිදු කිරීම සමඟ ස්වයංක්‍රීය තේරීම ඒකාබද්ධ කරන විට පසුබිම ඉවත් කිරීම වඩාත් හොඳින් ක්‍රියා කරයි. අවසාන අපනයන ආකෘතිය වෙස්මුහුණ තරම් වැදගත් වේ, මන්ද JPEG පෙරදසුන නිවැරදිව පෙනුනද විනිවිද පෙනෙන ප්‍රදේශ සමතලා කරයි. + විෂයය පසුබිමෙන් පැහැදිලිව වෙන් කර ඇත්නම් ස්වයංක්‍රීයව ඉවත් කිරීම ආරම්භ කරන්න. + හිසකෙස්, කොන්, සෙවනැලි සහ කුඩා විස්තර සවි කිරීමට විශාලනය කර මැකීම සහ ප්‍රතිසාධනය අතර මාරු වන්න. + ඔබට අවසාන ගොනුවේ විනිවිදභාවය අවශ්‍ය වූ විට PNG හෝ WebP ලෙස සුරකින්න. + ඇඳීම, සලකුණු කිරීම සහ දිය සලකුණ + ඊතල, සටහන්, උද්දීපන, අත්සන්, හෝ නැවත නැවත දිය සලකුණු උඩැතිරි එක් කරන්න + දෘශ්‍ය තොරතුරු එක් කරන්න + සලකුණු මෙවලම් රූපයක් පැහැදිලි කිරීමට උපකාරී වන අතර ජල සලකුණු කිරීම අපනයනය කළ ගොනු සන්නාමයට හෝ ආරක්ෂා කිරීමට උපකාරී වේ. + ඔබට සින්නක්කර සටහන්, හැඩතල, උද්දීපනය, හෝ ඉක්මන් විවරණ අවශ්‍ය විට Draw භාවිත කරන්න. + නිමැවුම් මත එකම පෙළ හෝ රූප සලකුණ අඛණ්ඩව දිස්විය යුතු විට දිය සලකුණු භාවිත කරන්න. + අපනයනය කිරීමට පෙර පාරාන්ධතාව, පිහිටීම සහ පරිමාණය පරීක්ෂා කරන්න එවිට ලකුණ පෙනෙන නමුත් අවධානය වෙනතකට යොමු නොකරයි. + පෙරනිමි අඳින්න + වේගවත් සලකුණු කිරීම සඳහා රේඛා පළල, වර්ණය, මාර්ග මාදිලිය, දිශානති අගුල සහ විශාලනය සකසන්න + ඇඳීම පුරෝකථනය කළ හැකි හැඟීමක් ඇති කරන්න + ඔබ තිර රූ සටහන් කරන විට, ලේඛන සලකුණු කරන විට හෝ පින්තූර නිතර අත්සන් කරන විට ඇඳීම් සැකසීම් ප්‍රයෝජනවත් වේ. පෙරනිමි සැකසුම් කාලය අඩු කරයි, විශාලනය සහ දිශානති අගුල කුඩා තිරවල නිවැරදි සංස්කරණයන් පහසු කරයි. + පෙරනිමි රේඛා පළල සකසා ඊතල, උද්දීපන හෝ අත්සන් සඳහා ඔබ වැඩිපුරම භාවිතා කරන අගයන් වෙත වර්ණ අඳින්න. + ඔබ සාමාන්‍යයෙන් එකම ආකාරයේ පහරවල්, හැඩතල, හෝ සලකුණු කරන විට පෙරනිමි මාර්ග ප්‍රකාරය භාවිතා කරන්න. + ඇඟිලි ආදානය කුඩා විස්තර නිවැරදිව තැබීමට අපහසු වන විට විශාලනය හෝ දිශානති අගුල සබල කරන්න. + බහු රූප පිරිසැලසුම් + තිරපිටපත්, ස්කෑන්, හෝ සංසන්දනාත්මක තීරු සැකසීමට පෙර නිවැරදි බහු-රූප මෙවලම තෝරන්න + නිවැරදි පිරිසැලසුම් මෙවලම භාවිතා කරන්න + මෙම මෙවලම් සමාන ශබ්දයක් ඇත, නමුත් ඒ සෑම එකක්ම විවිධ ආකාරයේ බහු-පින්තූර ප්‍රතිදානය සඳහා සුසර කර ඇත. නිවැරදි එක තෝරා ගැනීම පළමුව වෙනස් ප්‍රතිඵලයක් සඳහා නිර්මාණය කර ඇති පිරිසැලසුම් පාලන සමඟ සටන් වැදීම වළක්වයි. + එක් සංයුතියක රූප කිහිපයක් සහිත සැලසුම් කළ පිරිසැලසුම් සඳහා Collage Maker භාවිතා කරන්න. + රූප එක් දිගු සිරස් හෝ තිරස් තීරුවක් බවට පත් විය යුතු විට රූප මැසීම හෝ ගොඩගැසීම භාවිත කරන්න. + එක් විශාල රූපයක් කුඩා කැබලි කිහිපයක් වීමට අවශ්‍ය වූ විට Image Splitting භාවිතා කරන්න. + රූප ආකෘති පරිවර්තනය කරන්න + පික්සල අතින් සංස්කරණය නොකර JPEG, PNG, WebP, AVIF, JXL, සහ අනෙකුත් පිටපත් සාදන්න + කාර්යය සඳහා ආකෘතිය තෝරන්න + ඡායාරූප, තිරපිටපත්, විනිවිදභාවය, සම්පීඩනය හෝ ගැළපුම සඳහා විවිධ ආකෘති වඩා හොඳය. ගමනාන්ත යෙදුම සහය දක්වන්නේ කුමක් ද යන්න සහ මූලාශ්‍රයේ ඔබට තබා ගැනීමට අවශ්‍ය ඇල්ෆා, සජීවිකරණය හෝ පාර-දත්ත තිබේ දැයි ඔබ තේරුම් ගත් විට පරිවර්තනය වඩාත් ආරක්ෂිත වේ. + ගැළපෙන ඡායාරූප සඳහා JPEG, අහිමි නොවන රූප සහ ඇල්ෆා සඳහා PNG, සහ සංයුක්ත බෙදාගැනීම සඳහා WebP භාවිතා කරන්න. + ආකෘතිය එයට සහය දක්වන විට ගුණාත්මකභාවය සකසන්න; අඩු අගයන් ප්රමාණය අඩු කරන නමුත් කෞතුක වස්තු එකතු කළ හැක. + ඔබ පරිවර්තනය කරන ලද ගොනු නිවැරදිව ඉලක්ක යෙදුම තුළ විවෘත කර ඇති බව තහවුරු කරන තෙක් මුල් පිටපත් තබා ගන්න. + EXIF සහ පෞද්ගලිකත්වය පරීක්ෂා කරන්න + සංවේදී ඡායාරූප බෙදා ගැනීමට පෙර පාරදත්ත බලන්න, සංස්කරණය කරන්න, හෝ ඉවත් කරන්න + සැඟවුණු රූප දත්ත පාලනය කරන්න + EXIF හි කැමරා දත්ත, දිනයන්, ස්ථානය, දිශානතිය සහ රූපයේ නොපෙනෙන අනෙකුත් විස්තර අඩංගු විය හැක. පොදු බෙදාගැනීම්, ආධාරක වාර්තා, වෙළඳපල ලැයිස්තුගත කිරීම් හෝ පුද්ගලික ස්ථානයක් හෙළිදරව් කළ හැකි ඕනෑම ඡායාරූපයකට පෙර එය පරීක්ෂා කිරීම වටී. + ස්ථානය හෝ පුද්ගලික උපාංග තොරතුරු අඩංගු විය හැකි ඡායාරූප බෙදා ගැනීමට පෙර EXIF ​​මෙවලම් විවෘත කරන්න. + සංවේදී ක්ෂේත්‍ර ඉවත් කරන්න හෝ දිනය, දිශානතිය, කර්තෘ, හෝ කැමරා දත්ත වැනි වැරදි ටැග් සංස්කරණය කරන්න. + පුද්ගලිකත්වය වැදගත් වන විට මුල් පිටපත වෙනුවට නව පිටපතක් සුරකින්න සහ එම පිටපත බෙදා ගන්න. + ස්වයංක්‍රීය EXIF ​​පිරිසිදු කිරීම + දින සංරක්ෂණය කළ යුතුද යන්න තීරණය කිරීමේදී පාරදත්ත පෙරනිමියෙන් ඉවත් කරන්න + පුද්ගලිකත්වය පෙරනිමිය කරන්න + සෑම අපනයනයක් සඳහාම එය මතක තබා ගැනීම වෙනුවට පුද්ගලිකත්වය පිරිසිදු කිරීම ස්වයංක්‍රීයව සිදු වීමට ඔබට අවශ්‍ය වූ විට EXIF ​​සැකසුම් සමූහය ප්‍රයෝජනවත් වේ. දින වෙන් කිරීම සඳහා ප්‍රයෝජනවත් විය හැකි නමුත් බෙදා ගැනීමට සංවේදී බැවින් දිනය තබා ගැනීම වෙනම තීරණයකි. + ඔබගේ බොහෝ අපනයනයන් පොදු හෝ අර්ධ පොදු බෙදාගැනීම සඳහා අදහස් කරන විට සැමවිටම-පැහැදිලි EXIF ​​සක්‍රීය කරන්න. + සෑම වේලා මුද්දරයක්ම ඉවත් කරනවාට වඩා ග්‍රහණ කාලය සංරක්ෂණය කිරීම වැදගත් වන විට පමණක් Keep Date Time සබල කරන්න. + ඔබට සමහර ක්ෂේත්‍ර තබා ගැනීමට සහ අනෙක් ඒවා ඉවත් කිරීමට අවශ්‍ය වන එක් වර ගොනු සඳහා අතින් EXIF ​​සංස්කරණය භාවිතා කරන්න. + ගොනු නාම පාලනය කරන්න + උපසර්ග, උපසර්ග, වේලා මුද්දර, පෙරසිටුවීම්, චෙක්සම් සහ අනුක්‍රමික අංක භාවිත කරන්න + අපනයන සොයා ගැනීමට පහසු කරන්න + හොඳ ගොනු නාම රටාවක් කණ්ඩායම් වර්ග කිරීමට සහ අහම්බෙන් උඩින් ලිවීම වළක්වයි. අපනයනයන් ප්‍රභව ෆෝල්ඩරය වෙත ආපසු යන විට ගොනු නාම සැකසීම් විශේෂයෙන් ප්‍රයෝජනවත් වේ, එහිදී සමාන නම් ඉක්මනින් ව්‍යාකූල විය හැක. + සැකසීම් තුළ ගොනු නාම උපසර්ගය, උපසර්ගය, වේලා මුද්‍රාව, මුල් නම, පෙර සැකසූ තොරතුරු, හෝ අනුක්‍රමික විකල්ප සකසන්න. + පිටු, රාමු, හෝ කොලෙජ් වැනි ඇණවුම වැදගත් වන කණ්ඩායම් සඳහා අනුක්‍රමික අංක භාවිතා කරන්න. + පවතින ගොනු ප්‍රතිස්ථාපනය කිරීම වත්මන් ෆෝල්ඩරය සඳහා ආරක්ෂිත බව ඔබට විශ්වාස නම් පමණක් උඩින් ලිවීම සබල කරන්න. + ගොනු උඩින් ලියන්න + ඔබේ කාර්ය ප්‍රවාහය හිතාමතාම විනාශකාරී වන විට පමණක් ප්‍රතිදානයන් ගැළපෙන නම් සමඟ ප්‍රතිස්ථාපනය කරන්න + උඩින් ලියන ආකාරය ප්‍රවේශමෙන් භාවිතා කරන්න + උඩින් ලියන ගොනු නම් කිරීමේ ගැටුම් හසුරුවන ආකාරය වෙනස් කරයි. එය එකම ෆෝල්ඩරය වෙත නැවත නිර්යාත කිරීම සඳහා ප්‍රයෝජනවත් වේ, නමුත් ඔබේ ගොනු නාම රටාව නැවත එම නමම නිපදවන්නේ නම් එයට පෙර ප්‍රතිඵල ප්‍රතිස්ථාපනය කළ හැක. + පැරණි ජනනය කළ ගොනු ප්‍රතිස්ථාපනය කිරීමට බලාපොරොත්තු වන සහ ප්‍රතිසාධනය කළ හැකි ෆෝල්ඩර සඳහා පමණක් උඩින් ලිවීම සබල කරන්න. + මුල් පිටපත්, සේවාදායක ගොනු, එක් වරක් ස්කෑන් කිරීම හෝ උපස්ථයක් නොමැතිව ඕනෑම දෙයක් අපනයනය කරන විට උඩින් ලිවීම අබල කර තබා ගන්න. + හිතාමතා ගොනු නාම රටාවක් සමඟ උඩින් ලිවීම ඒකාබද්ධ කරන්න එවිට අපේක්ෂිත ප්‍රතිදානයන් පමණක් ප්‍රතිස්ථාපනය වේ. + ගොනු නාම රටා + මුල් නම්, උපසර්ග, උපසර්ග, වේලා මුද්දර, පෙරසිටුවීම්, පරිමාණ මාදිලිය සහ චෙක්සම් ඒකාබද්ධ කරන්න + ප්‍රතිදානය පැහැදිලි කරන නම් සාදන්න + ගොනු නාම රටා සැකසීම් මඟින් නිර්යාත කළ ගොනු ඒවාට සිදු වූ දේ පිළිබඳ ප්‍රයෝජනවත් ඉතිහාසයක් බවට පත් කළ හැක. ඔබට මුල් ප්‍රමාණය, පෙර සැකසුම, ආකෘතිය සහ සැකසුම් අනුපිළිවෙල වෙන්කර හඳුනාගත හැකි එකම ස්ථානය ෆෝල්ඩර දර්ශනය විය හැකි බැවින් මෙය කාණ්ඩ වශයෙන් වැදගත් වේ. + අතින් වර්ග කිරීම සඳහා ඔබට කියවිය හැකි නම් අවශ්‍ය වූ විට මුල් ගොනු නාමය, උපසර්ගය, උපසර්ගය සහ වේලා මුද්‍රාව භාවිත කරන්න. + ප්‍රතිදානයන් ඒවා නිෂ්පාදනය කළ ආකාරය ලේඛනගත කළ යුතු විට පෙරසිටුවීම, පරිමාණ මාදිලිය, ගොනු ප්‍රමාණය, හෝ චෙක්සම් තොරතුරු එක් කරන්න. + ලිපිගොනු මුල් පිටපත් හෝ පිටු අනුපිළිවෙල සමඟ යුගල කිරීමට අවශ්‍ය වැඩ ප්‍රවාහ සඳහා අහඹු නම්වලින් වළකින්න. + ගොනු සුරකින ස්ථානය තෝරන්න + ෆෝල්ඩර සැකසීම, මුල් ෆෝල්ඩර සුරැකීම සහ එක් වරක් සුරැකීමේ ස්ථාන සැකසීමෙන් අපනයන අහිමි වීමෙන් වළකින්න + නිමැවුම් පුරෝකථනය කළ හැකි ලෙස තබා ගන්න + ඔබ බොහෝ ගොනු සකසන විට හෝ වලාකුළු සපයන්නන්ගෙන් ගොනු බෙදා ගන්නා විට හැසිරීම සුරැකීම වැදගත් වේ. ෆෝල්ඩර සැකසීම් තීරණය කරන්නේ ප්‍රතිදානයන් එක් දන්නා ස්ථානයක රැස් කරන්නේද, මුල් පිටපත අසල දිස්වන්නේද, නැතහොත් නිශ්චිත කාර්යයක් සඳහා තාවකාලිකව වෙනත් ස්ථානයකට යනවාද යන්නයි. + ඔබට සෑම විටම එක් ස්ථානයක අපනයනය කිරීමට අවශ්‍ය නම් පෙරනිමි සුරැකුම් ෆෝල්ඩරයක් සකසන්න. + ප්‍රභව ගොනුව අසල ප්‍රතිදානය පැවතිය යුතු පිරිසිදු කිරීමේ කාර්ය ප්‍රවාහ සඳහා save-to-original-ෆෝල්ඩරය භාවිතා කරන්න. + ඔබේ පෙරනිමිය වෙනස් නොකර තනි කාර්යයකට වෙනත් ගමනාන්තයක් අවශ්‍ය වූ විට එක් වරක් සුරැකීමේ ස්ථානය භාවිත කරන්න. + එක් වරක් සුරැකීමේ ෆෝල්ඩර + ඔබගේ සාමාන්‍ය ගමනාන්තය වෙනස් නොකර තාවකාලික ෆෝල්ඩරයකට ඊළඟ නිර්යාත යවන්න + විශේෂ රැකියා වෙනම තබා ගන්න + ඔබේ සාමාන්‍ය රූප මෙවලම් පෙට්ටිය සමඟ මිශ්‍ර නොකළ යුතු තාවකාලික ව්‍යාපෘති, සේවාදායක ෆෝල්ඩර, පිරිසිදු කිරීමේ සැසි, හෝ අපනයන සඳහා එක් වරක් සුරැකීමේ ස්ථානය ප්‍රයෝජනවත් වේ. එය ඔබගේ පෙරනිමි සැකසුම නැවත ලිවීමෙන් තොරව කෙටි කාලීන ගමනාන්තයක් ලබා දෙයි. + ඔබගේ සාමාන්‍ය අපනයන ස්ථානයෙන් පිටත කාර්යයක් ආරම්භ කිරීමට පෙර එක් වරක් ෆෝල්ඩරයක් තෝරන්න. + කෙටි ව්‍යාපෘති, හවුල් ෆෝල්ඩර, හෝ ප්‍රතිදානයන් එකට සමූහගතව තිබිය යුතු කණ්ඩායම් සඳහා එය භාවිතා කරන්න. + රැකියාවෙන් පසු පෙරනිමි ෆෝල්ඩරය වෙත ආපසු යන්න, එවිට අපනයන තාවකාලික ස්ථානයට අනපේක්ෂිත ලෙස ගොඩ නොගැසෙනු ඇත. + ශේෂයේ ගුණාත්මකභාවය සහ ගොනු ප්රමාණය + අනුමාන කිරීම වෙනුවට මානයන්, ආකෘතිය හෝ ගුණාත්මකභාවය වෙනස් කළ යුත්තේ කවදාදැයි තේරුම් ගන්න + හිතාමතාම ගොනු හැකිලීම + ගොනු විශාලත්වය රූපයේ මානයන්, ආකෘතිය, ගුණාත්මකභාවය, විනිවිදභාවය සහ අන්තර්ගත සංකීර්ණත්වය මත රඳා පවතී. ගොනුවක් තවමත් බර වැඩි නම්, මානයන් වෙනස් කිරීම සාමාන්‍යයෙන් නැවත නැවතත් ගුණාත්මකභාවය අඩු කිරීමට වඩා උපකාරී වේ. + ගමනාන්තය පෙන්විය හැකි ප්‍රමාණයට වඩා රූපය විශාල වූ විට පළමුව මානයන් ප්‍රතිප්‍රමාණ කරන්න. + අලාභ ආකෘති සඳහා පමණක් අඩු ගුණාත්මක භාවය සහ අපනයනයෙන් පසු පෙළ, දාර, අනුක්‍රමණය සහ මුහුණු පරීක්ෂා කරන්න. + ගුණාත්මක වෙනස්කම් ප්‍රමාණවත් නොවන විට ආකෘතිය මාරු කරන්න, උදාහරණයක් ලෙස බෙදාගැනීම සඳහා WebP හෝ හැපෙනසුළු විනිවිද පෙනෙන වත්කම් සඳහා PNG. + සජීවිකරණ ආකෘති සමඟ වැඩ කරන්න + ගොනුවක එක් බිට්මැප් එකක් වෙනුවට රාමු ඇති විට GIF, APNG, WebP, සහ JXL මෙවලම් භාවිතා කරන්න + සෑම රූපයක්ම නිශ්චල ලෙස සලකන්න එපා + සජීවිකරණ ආකෘතිවලට රාමු, කාලසීමාව සහ ගැළපුම තේරුම් ගත හැකි මෙවලම් අවශ්‍ය වේ. + ඔබට එම ආකෘති සඳහා රාමු මට්ටමේ මෙහෙයුම් අවශ්‍ය විට GIF හෝ APNG මෙවලම් භාවිතා කරන්න. + ඔබට නවීන සම්පීඩනය හෝ සජීවිකරණ WebP ප්‍රතිදානය අවශ්‍ය විට WebP මෙවලම් භාවිතා කරන්න. + සමහර වේදිකා සජීවිකරණ රූප පරිවර්තනය කිරීම හෝ සමතලා කරන නිසා බෙදා ගැනීමට පෙර සජීවිකරණ වේලාව පෙරදසුන් කරන්න. + ප්රතිදානය සංසන්දනය කර පෙරදසුන් කරන්න + අපනයනයක් තබා ගැනීමට පෙර වෙනස්කම්, ගොනු විශාලත්වය සහ දෘශ්‍ය වෙනස්කම් පරීක්ෂා කරන්න + බෙදා ගැනීමට පෙර තහවුරු කරන්න + සම්පීඩන කෞතුක වස්තු, වැරදි වර්ණ, හෝ අනවශ්‍ය සංස්කරණ කලින් අල්ලා ගැනීමට සංසන්දන මෙවලම් උදවු කරයි. + ඔබට අනුවාද දෙකක් පැත්තකින් පරීක්ෂා කිරීමට අවශ්‍ය විට සංසන්දනය විවෘත කරන්න. + ගැටළු මඟ හැරීමට පහසු වන දාර, පෙළ, අනුක්‍රමික සහ විනිවිද පෙනෙන කලාප වෙත විශාලනය කරන්න. + ප්‍රතිදානය ඉතා බර හෝ නොපැහැදිලි නම්, පෙර මෙවලම වෙත ආපසු ගොස් ගුණාත්මකභාවය හෝ ආකෘතිය සීරුමාරු කරන්න. + PDF මෙවලම් කේන්ද්‍රය භාවිතා කරන්න + PDF ගොනු ඒකාබද්ධ කරන්න, බෙදන්න, කරකවන්න, පිටු ඉවත් කරන්න, සම්පීඩනය කරන්න, ආරක්ෂා කරන්න, OCR, සහ ජල සලකුණු කරන්න + PDF මෙහෙයුමක් තෝරන්න + PDF හබ් එක ඇතුල් වීමේ ස්ථානයක් පිටුපස ක්‍රියා ලේඛනගත කරයි, එවිට ඔබට නිවැරදි ක්‍රියාව තෝරාගත හැක. ගොනුව දැනටමත් PDF එකක් වන විට එහි ආරම්භ කරන්න, සහ ඔබේ මූලාශ්‍රය තවමත් පින්තූර කට්ටලයක් වන විට PDF වෙත පින්තූර හෝ ලේඛන ස්කෑනරය භාවිතා කරන්න. + PDF මෙවලම් විවෘත කර ඔබගේ ඉලක්කයට ගැලපෙන මෙහෙයුම තෝරන්න. + මූලාශ්‍ර PDF හෝ පින්තූර තෝරන්න, ඉන්පසු පිටු අනුපිළිවෙල, භ්‍රමණය, ආරක්ෂාව, හෝ OCR විකල්ප සමාලෝචනය කරන්න. + ජනනය කරන ලද PDF සුරකින්න සහ පිටු ගණන, ඇණවුම් සහ කියවීමේ හැකියාව තහවුරු කිරීමට එය වරක් විවෘත කරන්න. + පින්තූර PDF බවට පත් කරන්න + ස්කෑන්, තිරපිටපත්, රිසිට්පත්, හෝ ඡායාරූප එක් බෙදාගත හැකි ලේඛනයකට ඒකාබද්ධ කරන්න + පිරිසිදු ලියවිල්ලක් සාදන්න + රූප කපා, ඇනවුම් කර, සහ ප්‍රමාණයට අනුගත වූ විට වඩා හොඳ PDF පිටු බවට පත් වේ. පින්තූර ලැයිස්තුව පිටු තොගයක් මෙන් සලකන්න: සෑම භ්‍රමණයක්, ආන්තිකය සහ පිටු ප්‍රමාණයේ තේරීම අවසාන ලේඛනය කියවිය හැකි ආකාරය කෙරෙහි බලපායි. + පිටු දාර, සෙවණැලි, හෝ දිශානතිය වැරදි වූ විට පළමුව පින්තූර කපන්න හෝ කරකවන්න. + අපේක්ෂිත පිටු අනුපිළිවෙලෙහි පින්තූර තෝරන්න සහ මෙවලම ඒවා ලබා දෙන්නේ නම් පිටු ප්‍රමාණය හෝ මායිම් සීරුමාරු කරන්න. + PDF අපනයනය කරන්න, පසුව එය යැවීමට පෙර සියලුම පිටු කියවිය හැකිදැයි පරීක්ෂා කරන්න. + ලේඛන පරිලෝකනය කරන්න + කඩදාසි පිටු ග්‍රහණය කර ඒවා PDF, OCR හෝ බෙදාගැනීම සඳහා සූදානම් කරන්න + කියවිය හැකි පිටු ග්‍රහණය කරන්න + ලේඛන පරිලෝකනය පැතලි පිටු, හොඳ ආලෝකය සහ නිවැරදි ඉදිරිදර්ශනය සමඟින් වඩාත් හොඳින් ක්‍රියා කරයි. OCR හෝ PDF අපනයනය කිරීමට පෙර පිරිසිදු ස්කෑන් කිරීම කාලය ඉතිරි කරයි, මන්ද පෙළ හඳුනාගැනීම සහ සම්පීඩනය යන දෙකම පිටු ගුණත්වය මත රඳා පවතී. + ප්‍රමාණවත් ආලෝකයක් සහ අවම සෙවනැලි සහිත ප්‍රතිවිරුද්ධ මතුපිටක් මත ලේඛනය තබන්න. + සොයාගත් කොන් සීරුමාරු කරන්න හෝ අතින් කැපීම කරන්න එවිට පිටුව රාමුව පිරිසිදුව පුරවන්න. + පින්තූර හෝ PDF ලෙස නිර්යාත කරන්න, ඔබට තෝරාගත හැකි පෙළ අවශ්‍ය නම් OCR ධාවනය කරන්න. + PDF ගොනු ආරක්ෂා කිරීම හෝ අගුළු හැරීම + මුරපද ප්‍රවේශමෙන් භාවිත කරන්න සහ හැකි විට සංස්කරණය කළ හැකි මූලාශ්‍ර පිටපතක් තබා ගන්න + PDF ප්‍රවේශය ආරක්ෂිතව හසුරුවන්න + පාලිත බෙදාගැනීම සඳහා ආරක්ෂණ මෙවලම් ප්‍රයෝජනවත් වේ, නමුත් මුරපද නැති වීමට පහසු වන අතර ප්‍රතිසාධනය කිරීමට අපහසු වේ. බෙදා හැරීම සඳහා පිටපත් ආරක්ෂා කරන්න, අනාරක්ෂිත මූලාශ්‍ර ගොනු වෙන වෙනම තබා ගන්න, සහ කිසිවක් මකා දැමීමට පෙර ප්‍රතිඵලය තහවුරු කරන්න. + PDF පිටපතක් ආරක්ෂා කරන්න, ඔබේ එකම මූලාශ්‍ර ලේඛනය නොවේ. + ආරක්ෂිත ගොනුව බෙදා ගැනීමට පෙර මුරපදය ආරක්ෂිත ස්ථානයක ගබඩා කරන්න. + ඔබට සංස්කරණය කිරීමට අවසර දී ඇති ගොනු සඳහා පමණක් අගුළු ඇරීම භාවිතා කරන්න, ඉන්පසු අගුළු දැමූ පිටපත නිවැරදිව විවෘත වන බව තහවුරු කරන්න. + PDF පිටු සංවිධානය කරන්න + ඒකාබද්ධ කරන්න, බෙදන්න, නැවත සකස් කරන්න, කරකවන්න, කප්පාදු කරන්න, පිටු ඉවත් කරන්න, සහ හිතාමතාම පිටු අංක එකතු කරන්න + සැරසිලි කිරීමට පෙර ලේඛන ව්යුහය සවි කරන්න + PDF පිටු මෙවලම් ඔබ කඩදාසි ලේඛනයක් සකස් කරන අනුපිළිවෙලෙහිම භාවිතා කිරීමට පහසු වේ: පිටු එකතු කිරීම, වැරදි ඒවා ඉවත් කිරීම, ඒවා පිළිවෙලට තැබීම, නිවැරදි දිශානතිය, පසුව පිටු අංක හෝ ජල සලකුණු වැනි අවසාන විස්තර එක් කරන්න. + ලේඛනය තවමත් ගොනු කිහිපයක් හරහා බෙදී ඇති විට පළමුව PDF වෙත ඒකාබද්ධ කිරීම හෝ පින්තූර භාවිතා කරන්න. + පිටු අංක, අත්සන්, හෝ දිය සලකුණු එකතු කිරීමට පෙර පිටු නැවත සකස් කරන්න, කරකවන්න, කප්පාදු කරන්න, හෝ ඉවත් කරන්න. + එක් අස්ථානගත වූ හෝ කරකවන ලද පිටුවක් පසුව දැකීමට අපහසු විය හැකි බැවින් ව්‍යුහාත්මක සංස්කරණවලින් පසු අවසන් PDF පෙරදසුන් කරන්න. + PDF විවරණ + නරඹන්නන් අදහස් සහ ලකුණු වෙනස් ලෙස පෙන්වන විට විවරණ ඉවත් කරන්න හෝ ඒවා සමතලා කරන්න + දෘශ්‍යමානව පවතින දේ පාලනය කරන්න + විවරණ අදහස්, උද්දීපනය, ඇඳීම්, ආකෘති ලකුණු, හෝ වෙනත් අමතර PDF වස්තු විය හැක. සමහර නරඹන්නන් ඒවා වෙනස් ලෙස පෙන්වයි, එබැවින් ඒවා ඉවත් කිරීම හෝ සමතලා කිරීම හවුල් ලේඛන වඩාත් පුරෝකථනය කළ හැකිය. + බෙදාගත් පිටපතෙහි අදහස්, උද්දීපනය හෝ සමාලෝචන ලකුණු ඇතුළත් නොකළ යුතු විට විවරණ ඉවත් කරන්න. + දෘශ්‍ය ලකුණු පිටුවේ පැවතිය යුතු විට සමතලා කරන්න, නමුත් තවදුරටත් සංස්කරණය කළ හැකි විවරණ මෙන් නොහැසිරෙන්න. + විවරණ ඉවත් කිරීම හෝ සමතලා කිරීම ආපසු හැරවීමට අපහසු විය හැකි නිසා මුල් පිටපතක් තබා ගන්න. + පෙළ හඳුනා ගන්න + තෝරාගත හැකි OCR එන්ජින් සහ භාෂා සහිත රූපවලින් පෙළ උපුටා ගන්න + වඩා හොඳ OCR ප්රතිඵල ලබා ගන්න + OCR නිරවද්‍යතාවය රූපයේ තියුණු බව, භාෂා දත්ත, ප්‍රතිවිරුද්ධතාව සහ පිටු දිශානතිය මත රඳා පවතී. හොඳම ප්‍රතිඵල සාමාන්‍යයෙන් ලැබෙන්නේ පළමුව රූපය සැකසීමෙනි: ශබ්දය කපා දමන්න, කෙළින් කරකවන්න, කියවීමේ හැකියාව වැඩි කරන්න, පසුව පෙළ හඳුනා ගන්න. + රූපය පෙළ ප්‍රදේශයට කපා හඳුනා ගැනීමට පෙර එය කෙළින් කරකවන්න. + යෙදුම එය ඉල්ලන්නේ නම් නිවැරදි භාෂාව තෝරන්න සහ භාෂා දත්ත බාගන්න. + පිළිගත් පෙළ පිටපත් කරන්න හෝ බෙදාගන්න, පසුව නම්, අංක සහ විරාම ලකුණු හස්තීයව පරීක්ෂා කරන්න. + OCR භාෂා දත්ත තෝරන්න + ස්ක්‍රිප්ට්, භාෂා, සහ බාගත කළ OCR ආකෘති ගැළපීමෙන් හඳුනාගැනීම වැඩි දියුණු කරන්න + OCR පෙළට ගළපන්න + වැරදි OCR භාෂාව හොඳ ඡායාරූප නරක හඳුනාගැනීමක් ලෙස පෙනෙනු ඇත. භාෂා දත්ත ස්ක්‍රිප්ට්, විරාම ලකුණු, අංක සහ මිශ්‍ර ලේඛන සඳහා වැදගත් වේ, එබැවින් දුරකථනයේ UI භාෂාවට වඩා රූපයේ දිස්වන භාෂාව තෝරන්න. + දුරකථනයේ භාෂාව පමණක් නොව රූපයේ දිස්වන භාෂාව හෝ පිටපත තෝරන්න. + නොබැඳිව වැඩ කිරීමට හෝ කණ්ඩායමක් සැකසීමට පෙර නැතිවූ දත්ත බාගන්න. + මිශ්‍ර භාෂා ලේඛන සඳහා, පළමුව වඩාත් වැදගත් භාෂාව ධාවනය කර නම් සහ අංක හස්තීයව පරීක්ෂා කරන්න. + සෙවිය හැකි PDF + ස්කෑන් කළ පිටු සෙවීමට සහ පිටපත් කිරීමට හැකි වන පරිදි OCR පෙළ නැවත ගොනුවලට ලියන්න + ස්කෑන් සෙවිය හැකි ලේඛන බවට පත් කරන්න + OCR හට පසුරු පුවරුවට පෙළ පිටපත් කිරීමට වඩා වැඩි යමක් කළ හැක. ඔබ හඳුනාගත් පෙළ ගොනුවකට හෝ සෙවිය හැකි PDF වෙත ලියන විට, පිටුවේ රූපය දෘශ්‍යමානව පවතින අතර, පෙළ සෙවීමට, තේරීමට, සුචිගත කිරීමට හෝ ලේඛනගත කිරීමට පහසු වේ. + ස්කෑන් කරන ලද ලේඛන සඳහා සෙවිය හැකි PDF ප්‍රතිදානය භාවිතා කරන්න, එය තවමත් මුල් පිටු මෙන් විය යුතුය. + ඔබට උපුටා ගත් පෙළ පමණක් අවශ්‍ය වන විට සහ පිටු රූපය ගැන තැකීමක් නොකරන විට ලිවීමට ගොනුව භාවිත කරන්න. + OCR පෙළ සෙවිය හැකි නමුත් තවමත් අසම්පූර්ණ බැවින් වැදගත් අංක, නම් සහ වගු හස්තීයව පරීක්ෂා කරන්න. + QR කේත පරිලෝකනය කරන්න + පවතින විට කැමරා ආදානය හෝ සුරැකි රූප වලින් QR අන්තර්ගතය කියවන්න + ආරක්ෂිතව කේත කියවන්න + QR කේතවල සබැඳි, Wi-Fi දත්ත, සම්බන්ධතා, පෙළ, හෝ වෙනත් ගෙවීම් අඩංගු විය හැක. + ස්කෑන් QR කේතය විවෘත කර කේතය තියුණු, පැතලි සහ සම්පූර්ණයෙන්ම රාමුව තුළ තබා ගන්න. + සබැඳි විවෘත කිරීමට හෝ සංවේදී දත්ත පිටපත් කිරීමට පෙර විකේතනය කළ අන්තර්ගතය සමාලෝචනය කරන්න. + ස්කෑන් කිරීම අසාර්ථක වුවහොත්, ආලෝකය වැඩි දියුණු කරන්න, කේතය කප්පාදු කරන්න, නැතහොත් ඉහළ විභේදන මූලාශ්‍ර රූපයක් උත්සාහ කරන්න. + Base64 සහ චෙක්සම් භාවිතා කරන්න + පින්තූර පෙළ ලෙස සංකේතනය කරන්න, පෙළ නැවත රූපවලට විකේතනය කරන්න, සහ ගොනු අඛණ්ඩතාව තහවුරු කරන්න + ලිපිගොනු සහ පෙළ අතර ගමන් කරන්න + Base64 කුඩා පින්තූර ගෙවීම සඳහා ප්‍රයෝජනවත් වන අතර, ගොනු වෙනස් වී නැති බව තහවුරු කිරීමට චෙක්සම් උපකාර කරයි. මෙම මෙවලම් තාක්ෂණික කාර්ය ප්‍රවාහයන්, ආධාරක වාර්තා, ගොනු මාරු කිරීම් සහ ද්විමය ගොනු තාවකාලිකව පෙළ බවට පත් කිරීමට අවශ්‍ය ස්ථාන සඳහා වඩාත් උපකාරී වේ. + ද්විමය ගොනුවක් වෙනුවට වැඩ ප්‍රවාහයකට පෙළ අවශ්‍ය වූ විට කුඩා රූපයක් කේතනය කිරීමට Base64 මෙවලම් භාවිතා කරන්න. + විශ්වාසදායක මූලාශ්‍රවලින් පමණක් Base64 විකේතනය කර සුරැකීමට පෙර පෙරදසුන සත්‍යාපනය කරන්න. + ඔබට අපනයනය කළ ගොනු සංසන්දනය කිරීමට හෝ උඩුගත කිරීමක්/බාගැනීමක් තහවුරු කිරීමට අවශ්‍ය වූ විට චෙක්සම් මෙවලම් භාවිත කරන්න. + දත්ත ඇසුරුම් කිරීම හෝ ආරක්ෂා කිරීම + ගොනු එකතු කිරීම හෝ පෙළ දත්ත පරිවර්තනය කිරීම සඳහා ZIP සහ කේතාංක මෙවලම් භාවිතා කරන්න + අදාළ ගොනු එකට තබා ගන්න + නිමැවුම් කිහිපයක් එක් ගොනුවක් ලෙස ගමන් කළ යුතු විට ඇසුරුම් මෙවලම් උපකාරී වේ. ඔබට සම්පූර්ණ ප්‍රතිඵල කට්ටලයක් යැවීමට අවශ්‍ය වූ විට ජනනය කරන ලද පින්තූර, PDF, ලඝු-සටහන් සහ පාර-දත්ත එකට තබා ගැනීමටද ඒවා හොඳ වේ. + ඔබට බොහෝ අපනයනය කළ පින්තූර හෝ ලේඛන එකට යැවීමට අවශ්‍ය වූ විට ZIP භාවිත කරන්න. + ඔබ තෝරාගත් ක්‍රමය තේරුම් ගත් විට සහ අවශ්‍ය යතුරු ආරක්ෂිතව ගබඩා කළ හැකි විට පමණක් කේතාංක මෙවලම් භාවිතා කරන්න. + මූලාශ්‍ර ගොනු මකා දැමීමට පෙර සාදන ලද සංරක්ෂිතය හෝ පරිවර්තනය කළ පෙළ පරීක්ෂා කරන්න. + නම්වල චෙක්සම් + කියවීමට වඩා සුවිශේෂත්වය සහ අඛණ්ඩතාව වැදගත් වන විට චෙක්සම් මත පදනම් වූ ගොනු නාම භාවිතා කරන්න + අන්තර්ගතය අනුව ගොනු නම් කරන්න + අපනයනවල අනුපිටපත් දෘශ්‍ය නම් තිබිය හැකි විට හෝ ඔබට ගොනු දෙකක් සමාන බව ඔප්පු කිරීමට අවශ්‍ය වූ විට චෙක්සම් ගොනු නාම ප්‍රයෝජනවත් වේ. ඒවා අතින් පිරික්සීම සඳහා අඩු මිත්‍රශීලී වේ, එබැවින් තාක්ෂණික ලේඛනාගාර සහ සත්‍යාපන කාර්ය ප්‍රවාහ සඳහා ඒවා භාවිතා කරන්න. + මිනිසුන්ට කියවිය හැකි මාතෘකාවකට වඩා අන්තර්ගත අනන්‍යතාවය වැදගත් වන විට චෙක්සම් ගොනු නාමය ලෙස සබල කරන්න. + එය ලේඛනාගාර, අඩුකිරීම්, පුනරාවර්තනය කළ හැකි අපනයන, හෝ උඩුගත කර නැවත බාගත කළ හැකි ගොනු සඳහා භාවිත කරන්න. + මිනිසුන්ට තවමත් ගොනුව නියෝජනය කරන්නේ කුමක්ද යන්න තේරුම් ගැනීමට අවශ්‍ය වූ විට ෆෝල්ඩර හෝ පාර-දත්ත සමඟ චෙක්සම් නම් යුගල කරන්න. + පින්තූර වලින් වර්ණ තෝරන්න + නියැදි පික්සල, වර්ණ කේත පිටපත් කරන්න, සහ තලවල හෝ මෝස්තරවල වර්ණ නැවත භාවිත කරන්න + නිශ්චිත වර්ණය සාම්පල කරන්න + මෝස්තරයකට ගැලපීම, සන්නාම වර්ණ උකහා ගැනීම හෝ රූප අගයන් පරීක්ෂා කිරීම සඳහා වර්ණ තෝරා ගැනීම ප්‍රයෝජනවත් වේ. විශාලනය කිරීම වැදගත් වන්නේ ප්‍රතිවිකුණුම්කරණය, සෙවනැලි සහ සම්පීඩනය අසල්වැසි පික්සල ඔබ අපේක්ෂා කරන වර්ණයට වඩා තරමක් වෙනස් කළ හැකි බැවිනි. + පින්තූරයෙන් වර්ණ තෝරන්න විවෘත කර ඔබට අවශ්‍ය වර්ණය සහිත ප්‍රභව රූපයක් තෝරන්න. + අවට පික්සල ප්‍රතිඵලයට බලපාන්නේ නැති නිසා කුඩා විස්තර නියැදීමට පෙර විශාලනය කරන්න. + HEX, RGB, හෝ HSV වැනි ඔබේ ගමනාන්තයට අවශ්‍ය ආකෘතියෙන් වර්ණය පිටපත් කරන්න. + palettes සාදා වර්ණ පුස්තකාලය භාවිතා කරන්න + පැලට් උපුටා ගන්න, සුරකින ලද වර්ණ බ්‍රවුස් කරන්න, සහ ස්ථාවර දෘශ්‍ය පද්ධති තබා ගන්න + නැවත භාවිතා කළ හැකි පැලට් එකක් සාදන්න + අපනයනය කරන ලද ග්‍රැෆික්ස්, ජල සලකුණු සහ චිත්‍ර දෘශ්‍යමය වශයෙන් අනුකූලව තබා ගැනීමට පැලට් උදවු කරයි. ඔබ තිරපිටපත් නැවත නැවත සටහන් කරන විට, සන්නාම වත්කම් සකස් කරන විට හෝ මෙවලම් කිහිපයක් හරහා එකම වර්ණ අවශ්‍ය වූ විට ඒවා විශේෂයෙන් ප්‍රයෝජනවත් වේ. + ඔබ දැනටමත් අගයන් දන්නා විට රූපයකින් palette ජනනය කරන්න හෝ අතින් වර්ණ එකතු කරන්න. + ඔබට පසුව ඒවා නැවත සොයා ගත හැකි වන පරිදි වැදගත් වර්ණ නම් කරන්න හෝ සංවිධානය කරන්න. + Draw, watermark, gradient, SVG, හෝ බාහිර නිර්මාණ මෙවලම්වල පිටපත් කළ අගයන් භාවිත කරන්න. + අනුක්‍රම සාදන්න + පසුබිම්, ස්ථාන දරන්නන්, සහ දෘශ්‍ය අත්හදා බැලීම් සඳහා අනුක්‍රමණ රූප සාදන්න + වර්ණ සංක්‍රාන්ති පාලනය කරන්න + ඔබට පවතින රූපයක් සංස්කරණය කරනවා වෙනුවට ජනනය කළ රූපයක් අවශ්‍ය වූ විට Gradient මෙවලම් ප්‍රයෝජනවත් වේ. ඒවා පිරිසිදු පසුබිම්, ස්ථාන රඳවනයන්, බිතුපත්, වෙස් මුහුණු හෝ බාහිර නිර්මාණ මෙවලම් සඳහා සරල වත්කම් බවට පත් විය හැක. + Gradient වර්ගය තෝරන්න සහ වැදගත් වර්ණ පළමුව සකසන්න. + ඉලක්ක භාවිත අවස්ථාව සඳහා දිශාව, නැවතුම්, සුමට බව සහ ප්‍රතිදාන ප්‍රමාණය සකසන්න. + ගමනාන්තයට ගැළපෙන ආකෘතියකින් අපනයනය කරන්න, සාමාන්‍යයෙන් පිරිසිදු ජනනය කරන ලද චිත්‍රක සඳහා PNG. + වර්ණ ප්රවේශ්යතාව + UI කියවීමට අපහසු විට ගතික වර්ණ, ප්‍රතිවිරෝධතා සහ වර්ණ අන්ධ විකල්ප භාවිතා කරන්න + වර්ණ භාවිතා කිරීමට පහසු කරන්න + වර්ණ සැකසීම් සුවපහසුව, කියවීමේ හැකියාව සහ ඔබට කෙතරම් ඉක්මනින් පාලන ස්කෑන් කළ හැකිද යන්න බලපායි. මෙවලම් චිප්ස්, ස්ලයිඩර් හෝ තෝරාගත් තත්වයන් වෙන්කර හඳුනා ගැනීමට අපහසු නම්, තේමා සහ ප්‍රවේශ්‍යතා විකල්පයන් අඳුරු මාදිලිය වෙනස් කිරීමට වඩා ප්‍රයෝජනවත් විය හැක. + යෙදුම වත්මන් බිතුපත් තලය අනුගමනය කිරීමට ඔබට අවශ්‍ය විට ගතික වර්ණ උත්සාහ කරන්න. + බොත්තම් සහ මතුපිට ප්‍රමාණවත් නොවන විට ප්‍රතිවිරුද්ධතාව වැඩි කරන්න හෝ යෝජනා ක්‍රමය වෙනස් කරන්න. + සමහර ප්‍රාන්ත හෝ උච්චාරණ වෙන්කර හඳුනා ගැනීමට අපහසු නම් වර්ණ අන්ධ යෝජනා ක්‍රම භාවිතා කරන්න. + ඇල්ෆා පසුබිම + විනිවිද පෙනෙන PNG, WebP, සහ ඒ හා සමාන ප්‍රතිදානයන් වටා භාවිතා කරන පෙරදසුන හෝ පිරවුම් වර්ණය පාලනය කරන්න + විනිවිද පෙනෙන පෙරදසුන් තේරුම් ගන්න + ඇල්ෆා ආකෘති සඳහා පසුබිම් වර්ණය විනිවිද පෙනෙන රූප පරීක්ෂා කිරීම පහසු කරයි, නමුත් ඔබ පෙරදසුනක්/පිරවීමේ හැසිරීමක් වෙනස් කරන්නේද නැතහොත් අවසාන ප්‍රතිඵලය සමතලා කරනවාද යන්න දැනගැනීම වැදගත් වේ. ස්ටිකර්, ලාංඡන සහ පසුබිමෙන් ඉවත් කළ පින්තූර සඳහා මෙය වැදගත් වේ. + විනිවිද පෙනෙන පික්සල අපනයනයෙන් නොනැසී පැවතිය යුතු විට PNG හෝ WebP වැනි ඇල්ෆා ආධාරක ආකෘතියක් පළමුව භාවිතා කරන්න. + යෙදුම් මතුපිටට එරෙහිව විනිවිද පෙනෙන දාර දැකීමට අපහසු වූ විට පසුබිම් වර්ණ සැකසීම් සකසන්න. + විනිවිදභාවය හෝ තෝරාගත් පිරවුම සුරකින ලද්දේ දැයි තහවුරු කිරීමට කුඩා පරීක්ෂණයක් නිර්යාත කර එය වෙනත් යෙදුමකින් නැවත විවෘත කරන්න. + AI මාදිලි තේරීම + පළමුව විශාලතම එක තෝරා ගැනීම වෙනුවට රූපයේ වර්ගය සහ දෝෂය අනුව ආකෘතියක් තෝරන්න + හානියට ආකෘතිය ගලපන්න + AI මෙවලම්වලට විවිධ ONNX/ORT මාදිලි බාගැනීමට හෝ ආයාත කිරීමට හැකි අතර, එක් එක් මාදිලිය විශේෂිත ආකාරයේ ගැටළු සඳහා පුහුණු කර ඇත. JPEG බ්ලොක් සඳහා ආකෘතියක්, ඇනිමෙ රේඛා පිරිසිදු කිරීම, denoising, පසුබිම ඉවත් කිරීම, වර්ණ ගැන්වීම, හෝ ඉහළ නැංවීම වැරදි රූප වර්ගය මත භාවිතා කරන විට වඩාත් නරක ප්‍රතිඵල ඇති කළ හැක. + බාගත කිරීමට පෙර ආදර්ශ විස්තරය කියවන්න; සම්පීඩනය, ශබ්දය, අර්ධ ස්වරය, නොපැහැදිලි, හෝ පසුබිම ඉවත් කිරීම වැනි ඔබේ සැබෑ ගැටලුව නම් කරන ආකෘතිය තෝරන්න. + නව රූප වර්ගයක් පරීක්ෂා කිරීමේදී කුඩා හෝ වඩාත් නිශ්චිත ආකෘතියකින් ආරම්භ කරන්න, ප්‍රති result ලය ප්‍රමාණවත් නොවේ නම් පමණක් බර මාදිලි සමඟ සසඳන්න. + ඔබ සැබවින්ම භාවිතා කරන මාදිලි පමණක් තබා ගන්න, මන්ද බාගත් සහ ආනයනය කරන ලද ආකෘති සැලකිය යුතු ගබඩා ඉඩක් ගත හැක. + ආදර්ශ පවුල් තේරුම් ගන්න + ආකෘති නම් බොහෝ විට ඔවුන්ගේ ඉලක්කයට ඉඟි කරයි: RealESRGAN-විලාසයේ ආකෘති ඉහළ මට්ටමේ, SCUNet සහ FBCNN මාදිලි පිරිසිදු ශබ්දය හෝ සම්පීඩනය, පසුබිම් ආකෘති වෙස් මුහුණු නිර්මාණය කරයි, සහ වර්ණක අළුපැහැය ආදානයට වර්ණ එක් කරයි. ආනයනය කරන ලද අභිරුචි ආකෘති බලවත් විය හැකි නමුත් ඒවා අපේක්ෂිත ආදාන/ප්‍රතිදාන හැඩයට ගැළපෙන අතර සහාය දක්වන ONNX හෝ ORT ආකෘතිය භාවිත කළ යුතුය. + ඔබට වැඩිපුර පික්සල අවශ්‍ය විට ඉහළ පරිමාණයන් ද, රූපය ධාන්‍ය සහිත වූ විට ඩෙනොයිසර් ද, සම්පීඩන බ්ලොක් හෝ බැන්ඩිං ප්‍රධාන ගැටලුව වන විට කෞතුක වස්තු ඉවත් කරන්නන් ද භාවිත කරන්න. + විෂය වෙන් කිරීම සඳහා පමණක් පසුබිම් ආකෘති භාවිතා කරන්න; ඒවා සාමාන්‍ය වස්තු ඉවත් කිරීම හෝ රූපය වැඩි දියුණු කිරීම වැනි නොවේ. + ඔබ විශ්වාස කරන මූලාශ්‍රවලින් පමණක් අභිරුචි ආකෘති ආයාත කර වැදගත් ගොනු භාවිත කිරීමට පෙර කුඩා රූපයක් මත ඒවා පරීක්ෂා කරන්න. + AI පරාමිතීන් + ගුණාත්මකභාවය, වේගය සහ මතකය සමතුලිත කිරීමට කුට්ටි ප්‍රමාණය, අතිච්ඡාදනය සහ සමාන්තර කම්කරුවන් සුසර කරන්න + ස්ථාවරත්වය සමඟ වේගය සමතුලිත කරන්න + කුට්ටි ප්‍රමාණය ආකෘතිය මඟින් සැකසීමට පෙර රූප කොටස් කොතරම් විශාලද යන්න පාලනය කරයි. මායිම් සැඟවීමට අසල්වැසි කුට්ටි අතිච්ඡාදනය කරයි. සමාන්තර සේවකයින්ට සැකසීම වේගවත් කළ හැක, නමුත් සෑම සේවකයෙකුටම මතකය අවශ්‍ය වේ, එබැවින් ඉහළ අගයන් සීමිත RAM සහිත දුරකථනවල විශාල රූප අස්ථායී කළ හැක. + ඔබගේ උපාංගය ආකෘතිය විශ්වාසදායක ලෙස හසුරුවන විට සහ රූපය විශාල නොවන විට සුමට සන්දර්භය සඳහා විශාල කුට්ටි භාවිතා කරන්න. + කුට්ටි මායිම් දෘශ්‍යමාන වන විට අතිච්ඡාදනය වැඩි කරන්න, නමුත් වැඩිපුර අතිච්ඡාදනය වීම යනු නැවත නැවත වැඩ කිරීම බව මතක තබා ගන්න. + ස්ථාවර පරීක්ෂණයකින් පසුව පමණක් සමාන්තර සේවකයින් නැංවීම; පිරිසැකසුම් කිරීම මන්දගාමී වුවහොත්, උපාංගය රත් කරන්නේ නම් හෝ බිඳ වැටෙන්නේ නම්, පළමුව සේවකයින් අඩු කරන්න. + කුට්ටි කෞතුක වස්තු වලින් වළකින්න + Chunking මඟින් යෙදුමට එකවර පහසුවෙන් හැසිරවිය හැකි ආකෘතියට වඩා විශාල රූප සැකසීමට ඉඩ දෙයි. හුවමාරුව නම් සෑම ටයිල් එකක්ම අවට සන්දර්භය අඩු බැවින් අඩු අතිච්ඡාදනය හෝ ඉතා කුඩා කැබලි අසල්වැසි ප්‍රදේශ අතර දෘශ්‍ය මායිම්, වයනය වෙනස්වීම් හෝ නොගැලපෙන විස්තර නිර්මාණය කළ හැකිය. + ඔබ සෘජුකෝණාස්‍රාකාර මායිම්, නැවත නැවත වයනය වෙනස්වීම්, හෝ සැකසූ කලාප අතර විස්තර පැනීම් දුටුවහොත් අතිච්ඡාදනය වැඩි කරන්න. + ප්‍රතිදානය නිපදවීමට පෙර ක්‍රියාවලිය අසාර්ථක වුවහොත්, විශේෂයෙන් විශාල රූප හෝ බර ආකෘති මත කුට්ටි ප්‍රමාණය අඩු කරන්න. + සම්පූර්ණ රූපය සැකසීමට පෙර කුඩා බෝග පරීක්ෂණයක් සුරකින්න එවිට ඔබට ඉක්මනින් පරාමිති සංසන්දනය කළ හැක. + AI ස්ථාවරත්වය + AI සැකසීම බිඳවැටෙන විට හෝ කැටි වූ විට අඩු කුට්ටි ප්‍රමාණය, කම්කරුවන් සහ මූලාශ්‍ර විභේදනය + බර AI රැකියා වලින් ප්‍රකෘතිමත් වන්න + AI සැකසීම යෙදුමේ ඇති බරම වැඩ ප්‍රවාහයන්ගෙන් එකකි. බිඳවැටීම්, අසාර්ථක සැසි, කැටි කිරීම්, හෝ හදිසි යෙදුම් නැවත ආරම්භ කිරීම සාමාන්‍යයෙන් අදහස් වන්නේ ආකෘතිය, මූලාශ්‍ර විභේදනය, කුට්ටි ප්‍රමාණය, හෝ සමාන්තර සේවක සංඛ්‍යාව වත්මන් උපාංග තත්ත්වය සඳහා වැඩි ඉල්ලුමක් ඇති බවයි. + යෙදුම බිඳ වැටුණහොත් හෝ ආදර්ශ සැසියක් විවෘත කිරීමට අපොහොසත් වුවහොත්, සමාන්තර කම්කරුවන් සහ කුට්ටි ප්‍රමාණය අඩු කළහොත්, එම රූපය නැවත උත්සාහ කරන්න. + ඉලක්කයට මුල් විභේදනය අවශ්‍ය නොවන විට AI සැකසීමට පෙර ඉතා විශාල රූප ප්‍රතිප්‍රමාණ කරන්න. + වෙනත් බර යෙදුම් වසා දමන්න, කුඩා ආකෘතියක් භාවිතා කරන්න, හෝ මතක පීඩනය නැවත පැමිණෙන විට එකවර ගොනු කිහිපයක් සකසන්න. + ආකෘති අසාර්ථකත්වය හඳුනා ගන්න + අසාර්ථක AI ධාවනය සෑම විටම රූපය නරක බව අදහස් නොවේ. ආදර්ශ ගොනුව අසම්පූර්ණ, සහය නොදක්වන, මතකයට වඩා විශාල හෝ මෙහෙයුම සමඟ නොගැලපේ. යෙදුම අසාර්ථක සැසියක් වාර්තා කරන විට, එය මුලින්ම ආකෘතිය සහ පරාමිතීන් සරල කිරීමට සංඥාවක් ලෙස සලකන්න. + ආකෘතියක් බාගත කළ වහාම අසාර්ථක වුවහොත් හෝ ගොනුව දූෂිත වී ඇති ආකාරයට හැසිරෙන්නේ නම් එය මකා නැවත බාගන්න. + ආනයනය කරන ලද අභිරුචි ආකෘතියකට දොස් පැවරීමට පෙර ගොඩනඟා බාගත කළ හැකි ආකෘතියක් උත්සාහ කරන්න, මන්ද ආනයනික මාදිලිවල නොගැලපෙන ආදාන තිබිය හැක. + ඔබට බිඳවැටීමක් හෝ සැසි දෝෂයක් වාර්තා කිරීමට අවශ්‍ය නම් අසාර්ථකත්වය ප්‍රතිනිෂ්පාදනය කිරීමෙන් පසු යෙදුම් ලොග් භාවිතා කරන්න. + Shader Studio හි අත්හදා බැලීම + උසස් රූප සැකසීම සහ අභිරුචි පෙනුම සඳහා GPU සෙවන බලපෑම් භාවිතා කරන්න + සෙවනැලි සමඟ ප්රවේශමෙන් වැඩ කරන්න + Shader Studio බලවත්, නමුත් සෙවන කේතය සහ පරාමිතීන් සඳහා කුඩා, පරීක්ෂා කළ හැකි වෙනස්කම් අවශ්‍ය වේ. එය පර්යේෂණාත්මක මෙවලමක් ලෙස සලකන්න: ක්‍රියාකාරී පදනමක් තබා ගන්න, වරකට එක දෙයක් වෙනස් කරන්න, සහ පෙරසිටුවක් විශ්වාස කිරීමට පෙර විවිධ රූප ප්‍රමාණවලින් පරීක්ෂා කරන්න. + පරාමිති එකතු කිරීමට පෙර දන්නා වැඩ කරන සෙවනකින් හෝ සරල ආචරණයකින් ආරම්භ කරන්න. + වරකට එක් පරාමිතියක් හෝ කේත බ්ලොක් එකක් වෙනස් කර දෝෂ සඳහා පෙරදසුන පරීක්ෂා කරන්න. + පෙරසිටුවීම් විවිධ ප්‍රමාණවලින් ඒවා පරීක්ෂා කිරීමෙන් පසුව පමණක් අපනයනය කරන්න. + SVG හෝ ASCII කලාව සාදන්න + නිර්මාණාත්මක ප්‍රතිදානය සඳහා දෛශික වැනි වත්කම් හෝ පෙළ-පාදක රූප ජනනය කරන්න + නිර්මාණාත්මක නිමැවුම් වර්ගය තෝරන්න + SVG සහ ASCII මෙවලම් විවිධ ඉලක්ක සඳහා සේවය කරයි: පරිමාණය කළ හැකි චිත්‍රක හෝ පෙළ-විලාස විදැහුම්කරණය. දෙකම නිර්මාණාත්මක පරිවර්තන වේ, එබැවින් කුඩා සිඟිති රුවකින් ප්‍රතිඵලය විනිශ්චය කිරීමට වඩා අවසාන ප්‍රමාණයෙන් පෙරදසුන් කිරීම වැදගත් වේ. + ප්‍රතිඵලය පිරිසිදුව පරිමාණය කළ යුතු හෝ සැලසුම් කාර්ය ප්‍රවාහවල නැවත භාවිත කළ යුතු විට SVG Maker භාවිත කරන්න. + ඔබට රූපයක ශෛලීගත පෙළ නිරූපණයක් අවශ්‍ය විට ASCII කලාව භාවිත කරන්න. + ජනනය කරන ලද ප්‍රතිදානයේදී කුඩා විස්තර අතුරුදහන් විය හැකි බැවින් අවසාන ප්‍රමාණයෙන් පෙරදසුන් කරන්න. + ශබ්ද වයනය උත්පාදනය කරන්න + පසුබිම්, වෙස් මුහුණු, ආවරණ, හෝ අත්හදා බැලීම් සඳහා ක්‍රියා පටිපාටි වයනය සාදන්න + පටිපාටි ප්‍රතිදානය සුසර කරන්න + ශබ්ද උත්පාදනය පරාමිති වලින් නව රූප නිර්මාණය කරයි, එබැවින් කුඩා වෙනස්කම් ඉතා වෙනස් ප්රතිඵල ලබා ගත හැක. එය වයනය, ආවරණ, ආවරණ, ස්ථාන රඳවනයන් හෝ සවිස්තරාත්මක රටා සමඟ සම්පීඩනය පරීක්ෂා කිරීම සඳහා ප්‍රයෝජනවත් වේ. + සියුම් විස්තර සුසර කිරීමට පෙර ශබ්ද වර්ගය සහ ප්‍රතිදාන ප්‍රමාණය තෝරන්න. + රටාව ඉලක්ක භාවිතයට ගැලපෙන තෙක් පරිමාණය, තීව්‍රතාවය, වර්ණ හෝ බීජ සකසන්න. + ඔබට පසුව වයනය නැවත නිර්මාණය කිරීමට අවශ්‍ය නම් ප්‍රයෝජනවත් ප්‍රතිඵල සුරකින්න සහ පරාමිති ලියන්න. + සලකුණු කිරීමේ ව්යාපෘති + යෙදුම වැසීමෙන් පසු විවරණ සංස්කරණය කළ හැකිව තිබිය යුතු විට ව්‍යාපෘති ගොනු භාවිත කරන්න + ස්ථර සංස්කරණ නැවත භාවිත කළ හැකි ලෙස තබා ගන්න + තිර රුවක්, රූප සටහනක් හෝ උපදෙස් රූපයකට පසුව වෙනස්කම් අවශ්‍ය වූ විට සලකුණු කිරීමේ ව්‍යාපෘති ගොනු ප්‍රයෝජනවත් වේ. අපනයනය කරන ලද PNG/JPEG ගොනු අවසාන රූප වන අතර, ව්‍යාපෘති ගොනු අනාගත ගැලපීම් සඳහා සංස්කරණය කළ හැකි ස්ථර සහ සංස්කරණ තත්ත්වය ආරක්ෂා කරයි. + විවරණ, ඇමතුම් හෝ පිරිසැලසුම් මූලද්‍රව්‍ය සංස්කරණය කළ හැකිව තිබිය යුතු විට සලකුණු ස්ථර භාවිත කරන්න. + ඔබ තවත් සංශෝධන බලාපොරොත්තු වන්නේ නම් අවසාන රූපයක් නිර්යාත කිරීමට පෙර ව්‍යාපෘති ගොනුව සුරකින්න හෝ නැවත විවෘත කරන්න. + සාමාන්‍ය රූපයක් නිර්යාත කරන්න ඔබ පැතලි අවසන් අනුවාදයක් බෙදා ගැනීමට සූදානම් වූ විට පමණි. + ගොනු සංකේතනය කරන්න + කිසියම් මූලාශ්‍ර පිටපතක් මකා දැමීමට පෙර මුරපදයකින් ගොනු ආරක්ෂා කර විකේතනය පරීක්ෂා කරන්න + සංකේතාත්මක ප්‍රතිදානයන් ප්‍රවේශමෙන් හසුරුවන්න + කේතාංක මෙවලම් සඳහා මුරපද මත පදනම් වූ සංකේතනයකින් ගොනු ආරක්ෂා කළ හැක, නමුත් ඒවා යතුර මතක තබා ගැනීම හෝ උපස්ථ තබා ගැනීම සඳහා ආදේශකයක් නොවේ. ප්‍රවාහනය සහ ගබඩා කිරීම සඳහා සංකේතනය ප්‍රයෝජනවත් වන අතර ප්‍රධාන ඉලක්කය ප්‍රතිදානයන් කිහිපයක් එකතු කරන විට ZIP වඩා හොඳය. + පළමුව පිටපතක් සංකේතනය කර ඔබ ගබඩා කර ඇති මුරපදය සමඟ විකේතනය ක්‍රියා කරන බව පරීක්ෂා කරන තෙක් මුල් පිටපත තබා ගන්න. + යතුර සඳහා මුරපද කළමනාකරු හෝ වෙනත් ආරක්ෂිත ස්ථානයක් භාවිතා කරන්න; යෙදුමට ඔබ සඳහා නැතිවූ මුරපදයක් අනුමාන කළ නොහැක. + සංකේතාත්මක ගොනු බෙදාගන්න මුරපදය ඇති සහ ඒවා විකේතනය කළ යුත්තේ කුමන මෙවලම හෝ ක්‍රමයදැයි දන්නා පුද්ගලයින් සමඟ පමණි. + මෙවලම් සකස් කරන්න + ප්‍රධාන තිරය ඔබේ සැබෑ කාර්ය ප්‍රවාහයට ගැළපෙන පරිදි කණ්ඩායම්, ඇණවුම, සෙවීම සහ පින් මෙවලම් + ප්‍රධාන තිරය වේගවත් කරන්න + ඔබ වැඩිපුරම භාවිතා කරන්නේ කුමන මෙවලම්දැයි දැනගත් පසු මෙවලම් සැකසීම් සැකසීම් සුසර කිරීම වටී. සමූහගත කිරීම ගවේෂණයට උපකාරී වේ, අභිරුචි අනුපිළිවෙල මාංශ පේශි මතකයට උපකාරී වේ, සහ ප්‍රියතමයන් සම්පූර්ණ ලැයිස්තුව විශාල වන විට පවා දෛනික මෙවලම් වෙත ළඟා විය හැකි ලෙස තබා ගනී. + යෙදුම ඉගෙන ගන්නා අතරතුර හෝ ඔබ කාර්ය වර්ගය අනුව සංවිධානය කරන ලද මෙවලම් කැමති විට සමූහගත මාදිලිය භාවිතා කරන්න. + ඔබ දැනටමත් ඔබේ නිරන්තර මෙවලම් දන්නා විට සහ අඩු අනුචලන අවශ්‍ය විට අභිරුචි අනුපිළිවෙලට මාරු වන්න. + ඔබට නමින් මතක ඇති නමුත් සෑම විටම දෘශ්‍යමාන වීමට අවශ්‍ය නැති දුර්ලභ මෙවලම් සඳහා සෙවුම් සැකසීම් සහ ප්‍රියතමයන් භාවිතා කරන්න. + විශාල ගොනු හසුරුවන්න + මන්දගාමී අපනයන, මතක පීඩනය සහ අනපේක්ෂිත ලෙස විශාල ප්‍රතිදානයෙන් වළකින්න + අපනයනය කිරීමට පෙර වැඩ අඩු කරන්න + ඉතා විශාල රූප, දිගු කාණ්ඩ සහ උසස් තත්ත්වයේ ආකෘති සඳහා වැඩි මතකයක් සහ කාලයක් ගත විය හැක. වේගවත්ම විසඳුම සාමාන්‍යයෙන් බරම මෙහෙයුමට පෙර වැඩ ප්‍රමාණය අඩු කිරීමයි, එකම විශාල ආදානය අවසන් වන තෙක් බලා සිටීම නොවේ. + බර පෙරහන්, OCR, හෝ PDF පරිවර්තනය යෙදීමට පෙර විශාල පින්තූර ප්‍රතිප්‍රමාණ කරන්න. + සැකසීම් තහවුරු කිරීමට පළමුව කුඩා කණ්ඩායමක් භාවිතා කරන්න, පසුව ඉතිරිය පෙර සැකසුම සමඟ සකසන්න. + ප්‍රතිදාන ගොනුව බලාපොරොත්තු වූවාට වඩා විශාල වන විට අඩු ගුණත්වය හෝ ආකෘතිය වෙනස් කරන්න. + හැඹිලි සහ පෙරදසුන් + අධික සැසිවලින් පසු ජනනය කරන ලද පෙරදසුන්, හැඹිලි පිරිසිදු කිරීම සහ ගබඩා භාවිතය තේරුම් ගන්න + ගබඩාව සහ වේගය සමතුලිතව තබා ගන්න + පෙරදසුන් උත්පාදනය සහ හැඹිලිය නැවත නැවත බ්‍රවුස් කිරීම වේගවත් කළ හැකි නමුත් අධික සංස්කරණ සැසි තාවකාලික දත්ත ඉතිරි කළ හැක. යෙදුම මන්දගාමී බවක් දැනෙන විට, ගබඩාව තද වී ඇති විට, හෝ බොහෝ අත්හදා බැලීම්වලින් පසු පෙරදසුන් යල් පැන ගිය විට හැඹිලි සැකසීම් උදවු කරයි. + බොහෝ පින්තූර බ්‍රවුස් කරන විට පෙරදසුන් සක්‍රීය කර තබා ගැනීම තාවකාලික ආචයනය අවම කිරීමට වඩා වැදගත් වේ. + විශාල කණ්ඩායම්, අසාර්ථක අත්හදා බැලීම් හෝ බොහෝ අධි-විභේදන ගොනු සහිත දිගු සැසිවලින් පසු හැඹිලිය හිස් කරන්න. + දියත් කිරීම් අතර පෙරදසුන් උණුසුම්ව තබා ගැනීමට වඩා ගබඩා පිරිසිදු කිරීමට ඔබ කැමති නම් ස්වයංක්‍රීය හැඹිලි නිෂ්කාශනය භාවිතා කරන්න. + තිර හැසිරීම් සකසන්න + නාභිගත වැඩ සඳහා පූර්ණ තිරය, ආරක්ෂිත මාදිලිය, දීප්තිය සහ පද්ධති තීරු විකල්ප භාවිතා කරන්න + අතුරු මුහුණත තත්වයට ගැලපෙන පරිදි සකස් කරන්න + ඔබ භූ දර්ශනයේ සංස්කරණය කරන විට, පුද්ගලික රූප ඉදිරිපත් කරන විට, හෝ ස්ථාවර දීප්තිය අවශ්‍ය විට තිර සැකසීම් උදවු කරයි. ඒවා සාමාන්‍ය භාවිතය සඳහා අවශ්‍ය නොවේ, නමුත් ඒවා QR පරීක්ෂාව, පුද්ගලික පෙරදසුන්, හෝ අවහිර වූ භූ දර්ශන සංස්කරණය වැනි අපහසු තත්වයන් විසඳයි. + සංචාලන ක්‍රෝම් වලට වඩා අවකාශය සංස්කරණය කිරීම වැදගත් වන විට පූර්ණ තිර හෝ පද්ධති තීරු සැකසුම් භාවිතා කරන්න. + පුද්ගලික රූප තිරපිටපත්වල හෝ යෙදුම් පෙරදසුන්වල නොපෙන්විය යුතු විට ආරක්ෂිත මාදිලිය භාවිත කරන්න. + අඳුරු රූප හෝ QR කේත පරීක්ෂා කිරීමට අපහසු මෙවලම් සඳහා දීප්තිය බලාත්මක කිරීම භාවිත කරන්න. + විනිවිදභාවය තබා ගන්න + විනිවිද පෙනෙන පසුබිම් සුදු, කළු හෝ සමතලා වීමෙන් වළක්වන්න + ඇල්ෆා දැනුවත් ප්‍රතිදානය භාවිතා කරන්න + පාරදෘශ්‍යභාවය පවතින්නේ තෝරාගත් මෙවලම සහ ප්‍රතිදාන ආකෘතිය ඇල්ෆා සහාය දක්වන විට පමණි. නිර්යාත කළ පසුබිමක් සුදු හෝ කළු පැහැයට හැරේ නම්, ගැටළුව සාමාන්‍යයෙන් ප්‍රතිදාන ආකෘතිය, පිරවුම්/පසුබිම් සැකසුම හෝ රූපය සමතලා කළ ගමනාන්ත යෙදුමකි. + පසුබිමෙන් ඉවත් කළ රූප, ස්ටිකර්, ලාංඡන, හෝ උඩින් නිර්යාත කරන විට PNG හෝ WebP තෝරන්න. + එය ඇල්ෆා නාලිකාවක් ගබඩා නොකරන නිසා විනිවිද පෙනෙන ප්රතිදානය සඳහා JPEG වළකින්න. + පෙරදසුන පිරවූ පසුබිමක් පෙන්නුම් කරන්නේ නම් ඇල්ෆා ආකෘති සඳහා පසුබිම් වර්ණයට අදාළ සැකසුම් පරීක්ෂා කරන්න. + බෙදාගැනීමේ සහ ආනයන ගැටළු නිරාකරණය කරන්න + ගොනු නොපෙනෙන විට හෝ නිවැරදිව විවෘත නොවන විට පිකර් සැකසුම් සහ සහාය දක්වන ආකෘති භාවිතා කරන්න + යෙදුම තුළට ගොනුව ලබා ගන්න + ආයාත ගැටළු බොහෝ විට සපයන්නන්ගේ අවසර, සහය නොදක්වන ආකෘති, හෝ පිකර් හැසිරීම් නිසා ඇතිවේ. වලාකුළු යෙදුම් සහ පණිවිඩකරුවන් වෙතින් බෙදා ගත් ගොනු තාවකාලික විය හැක, එබැවින් දිගු සංස්කරණ සඳහා ස්ථීර මූලාශ්‍රයක් තෝරා ගැනීම වඩාත් විශ්වාසදායක විය හැක. + මෑත-ගොනු කෙටිමඟක් වෙනුවට පද්ධති පිකර් හරහා ගොනුව විවෘත කිරීමට උත්සාහ කරන්න. + ආකෘතියක් එක් මෙවලමකින් සහාය නොදක්වන්නේ නම්, එය පළමුව පරිවර්තනය කරන්න හෝ වඩාත් නිශ්චිත මෙවලමක් විවෘත කරන්න. + බෙදාගැනීම් ප්‍රවාහයන් හෝ සෘජු මෙවලම් විවෘත කිරීම බලාපොරොත්තු වූවාට වඩා වෙනස් ලෙස හැසිරෙන්නේ නම් රූප පිකර් සහ දියත් කිරීමේ සැකසුම් සමාලෝචනය කරන්න. + පිටවීම තහවුරු කිරීම + අභිනයන්, පසුපස එබීම් හෝ මෙවලම් ස්විචයන් අහම්බෙන් සිදු වූ විට නොසුරකින ලද සංස්කරණ අහිමි වීමෙන් වළකින්න + නිම නොකළ වැඩ ආරක්ෂා කරන්න + ඔබ ඉංගිත සංචාලනය භාවිතා කරන විට, විශාල ගොනු සංස්කරණය කරන විට, හෝ බොහෝ විට යෙදුම් අතර මාරු වන විට මෙවලම් පිටවීම තහවුරු කිරීම ප්‍රයෝජනවත් වේ. එය මෙවලමක් හැර යාමට පෙර කුඩා විරාමයක් එක් කරයි, එබැවින් නිම නොකළ සැකසුම් සහ පෙරදසුන් අහම්බෙන් නැති නොවේ. + ඔබ නිර්යාත කිරීමට පෙර අහම්බෙන් පසුපස අභිනයන් මෙවලමක් වසා ඇත්නම් තහවුරු කිරීම සබල කරන්න. + ඔබ බොහෝ විට ඉක්මන් එක්-පියවර පරිවර්තන සිදු කරන්නේ නම් සහ වේගවත් සංචාලනයකට කැමති නම් එය අබල කර තබන්න. + නැවත ගොඩනැඟීමේ සිටුවම් කරදරකාරී වන දිගු කාර්ය ප්‍රවාහ සඳහා පෙරසිටුවීම් සමඟ එය භාවිත කරන්න. + ගැටළු වාර්තා කිරීමේදී ලඝු-සටහන් භාවිතා කරන්න + ගැටලුවක් විවෘත කිරීමට හෝ සංවර්ධකයා සම්බන්ධ කර ගැනීමට පෙර ප්‍රයෝජනවත් තොරතුරු රැස් කරන්න + සන්දර්භය සමඟ ගැටළු වාර්තා කරන්න + පියවර, යෙදුම් අනුවාදය, ආදාන වර්ගය සහ ලඝු-සටහන් සහිත පැහැදිලි වාර්තාවක් රෝග විනිශ්චය කිරීමට වඩා පහසුය. ගැටලුවක් ප්‍රතිනිෂ්පාදනය කිරීමෙන් පසු ලඝු-සටහන් වඩාත් ප්‍රයෝජනවත් වන්නේ ඒවා අවට සන්දර්භය නැවුම්ව තබා ගන්නා බැවිනි. + මෙවලමෙහි නම, නිශ්චිත ක්‍රියාව සහ එය එක් ගොනුවක් හෝ සෑම ගොනුවක් සමඟම සිදුවේද යන්න සටහන් කරන්න. + ගැටලුව ප්‍රතිනිෂ්පාදනය කිරීමෙන් පසු යෙදුම් ලොග් විවෘත කර හැකි විට අදාළ ලොග් ප්‍රතිදානය බෙදා ගන්න. + තිරපිටපත් හෝ නියැදි ගොනු අමුණන්න ඒවායේ පුද්ගලික තොරතුරු නොමැති විට පමණි. + පසුබිම සැකසීම නතර කරන ලදී + පසුබිම සැකසීමේ දැනුම්දීම නියමිත වේලාවට ආරම්භ කිරීමට Android ඉඩ දුන්නේ නැත. මෙය විශ්වාසදායක ලෙස ස්ථාවර කළ නොහැකි පද්ධති කාල සීමාවකි. යෙදුම නැවත ආරම්භ කර නැවත උත්සාහ කරන්න; යෙදුම විවෘතව තබාගැනීම සහ දැනුම්දීම් හෝ පසුබිම් වැඩ කිරීමට ඉඩ දීම උපකාර විය හැක. + ගොනු තේරීම මඟ හරින්න + සාමාන්‍යයෙන් කොටස්, ක්ලිප්බෝඩ් හෝ පෙර තිරයකින් ආදානය ලැබෙන විට මෙවලම් වේගයෙන් විවෘත කරන්න + නැවත නැවතත් මෙවලම් දියත් කිරීම වේගවත් කරන්න + Skip File Picking සහාය දක්වන මෙවලම්වල පළමු පියවර වෙනස් කරයි. ඔබ සාමාන්‍යයෙන් දැනටමත් තෝරාගත් රූපයක් හෝ Android බෙදාගැනීමේ ක්‍රියා වලින් මෙවලමක් ඇතුළු කරන විට එය ප්‍රයෝජනවත් වේ, නමුත් සෑම මෙවලමක්ම වහාම ගොනුවක් ඉල්ලා සිටිනු ඇතැයි ඔබ අපේක්ෂා කරන්නේ නම් එය ව්‍යාකූල විය හැක. + මෙවලම විවෘත කිරීමට පෙර ඔබේ සුපුරුදු ප්‍රවාහය දැනටමත් මූලාශ්‍ර රූපය සපයන විට පමණක් එය සබල කරන්න. + යෙදුම ඉගෙන ගන්නා අතරතුර එය අක්‍රිය කර තබන්න, මන්ද පැහැදිලි තෝරා ගැනීම සෑම මෙවලම් ප්‍රවාහයක්ම තේරුම් ගැනීමට පහසු කරයි. + කිසිදු පැහැදිලි ආදානයක් නොමැතිව මෙවලමක් විවෘත වන්නේ නම්, මෙම සැකසුම අක්‍රිය කරන්න හෝ Share/Open With වෙතින් ආරම්භ කරන්න එවිට Android පළමුව ගොනුව පසු කරයි. + පළමුව සංස්කාරකය විවෘත කරන්න + බෙදා ගත් රූපයක් කෙලින්ම සංස්කරණයට යා යුතු විට පෙරදසුන මඟ හරින්න + පළමු නැවතුම ලෙස පෙරදසුන හෝ සංස්කරණය කරන්න + පෙරදසුන වෙනුවට විවෘත සංස්කරණය යනු රූපය වෙනස් කිරීමට අවශ්‍ය බව දැනටමත් දන්නා පරිශීලකයින් සඳහා වේ. ඔබ කතාබස් හෝ වලාකුළු ආචයනයෙන් ගොනු පරීක්ෂා කරන විට පෙරදසුන ආරක්ෂිත වේ; තිරපිටපත්, ඉක්මන් බෝග, විවරණ, සහ එක වර නිවැරදි කිරීම් සඳහා සෘජු සංස්කරණය වේගවත් වේ. + බොහෝ බෙදාගත් පින්තූරවලට වහාම කැපීම, සලකුණු කිරීම, පෙරහන් හෝ අපනයන වෙනස්කම් අවශ්‍ය නම් සෘජු සංස්කරණය සක්‍රීය කරන්න. + තීරණය කිරීමට පෙර ඔබ බොහෝ විට පාරදත්ත, මානයන්, විනිවිදභාවය හෝ රූපයේ ගුණත්වය පරීක්ෂා කරන විට පළමුව පෙරදසුන තබන්න. + ප්‍රියතමයන් සමඟින් මෙය භාවිතා කරන්න, එවිට බෙදාගත් පින්තූර ඔබ සැබවින්ම භාවිත කරන මෙවලම්වලට සමීප වේ. + පෙර සැකසූ පෙළ ඇතුළත් කිරීම + පාලනයන් නැවත නැවත සීරුමාරු කිරීම වෙනුවට නිශ්චිත පෙරසිටුවීම් අගයන් ඇතුළත් කරන්න + ස්ලයිඩර් මන්දගාමී වන විට නිශ්චිත අගයන් භාවිතා කරන්න + ඔබට නැවත නැවත වැඩ කිරීම සඳහා නිශ්චිත සංඛ්‍යා අවශ්‍ය වූ විට පෙර සැකසූ පෙළ ඇතුළත් කිරීම උපකාරී වේ: සමාජ රූප ප්‍රමාණ, ලේඛන මායිම්, සම්පීඩන ඉලක්ක, රේඛා පළල, හෝ අභිනයන් සමඟ ළඟා වීමට කරදරකාරී වෙනත් අගයන්. සිහින් ස්ලයිඩර චලනය දුෂ්කර වන කුඩා තිරවල ද එය ප්රයෝජනවත් වේ. + ඔබ බොහෝ විට නිවැරදි ප්‍රමාණ, ප්‍රතිශත, තත්ත්ව අගයන්, හෝ මෙවලම් පරාමිති නැවත භාවිත කරන්නේ නම් පෙළ ඇතුළත් කිරීම සබල කරන්න. + අගය එක් වරක් ටයිප් කිරීමෙන් පසු පෙරසිටුවක් ලෙස සුරකින්න, පසුව එම සැකසුම නැවත ගොඩනැගීම වෙනුවට එය නැවත භාවිතා කරන්න. + රළු දෘශ්‍ය සුසර කිරීම සඳහා අභින පාලනයන් සහ දැඩි ප්‍රතිදාන අවශ්‍යතා සඳහා ටයිප් කළ අගයන් තබා ගන්න. + මුල් පිටපත අසල සුරකින්න + ෆෝල්ඩර සන්දර්භය වැදගත් වන විට උත්පාදනය කරන ලද ගොනු මූලාශ්‍ර රූප අසල තබන්න + ඒවායේ මූලාශ්‍ර සමඟ ප්‍රතිදාන තබා ගන්න + මුල් ෆෝල්ඩරයට සුරකින්න කැමරා ෆෝල්ඩර, ව්‍යාපෘති ෆෝල්ඩර, ලේඛන ස්කෑන්, සහ ප්‍රභවයට යාබදව සංස්කරණය කළ පිටපත රැඳී සිටිය යුතු සේවාදායක කණ්ඩායම් සඳහා පහසු වේ. එය කැප වූ ප්‍රතිදාන ෆෝල්ඩරයකට වඩා අඩු පිළිවෙලක් විය හැක, එබැවින් එය පැහැදිලි ගොනු නාම උපසර්ග හෝ රටා සමඟ ඒකාබද්ධ කරන්න. + එක් එක් මූලාශ්‍ර ෆෝල්ඩරය දැනටමත් අර්ථවත් වන විට සහ ප්‍රතිදානයන් එහි සමූහගතව තිබිය යුතු විට එය සබල කරන්න. + ගොනු නාම උපසර්ග, උපසර්ග, වේලා මුද්දර, හෝ රටා භාවිතා කරන්න එවිට සංස්කරණය කළ පිටපත් මුල් පිටපත් වලින් වෙන් කිරීමට පහසු වේ. + ඔබට ජනනය කරන ලද සියලුම ගොනු එක් අපනයන ෆෝල්ඩරයක එකතු කිරීමට අවශ්‍ය විට මිශ්‍ර කණ්ඩායම් සඳහා එය අක්‍රීය කරන්න. + විශාල ප්‍රතිදානය මඟ හරින්න + අනපේක්ෂිත ලෙස මූලාශ්‍රයට වඩා බර බවට පත්වන පරිවර්තන සුරැකීමෙන් වළකින්න + නරක ප්‍රමාණයේ විස්මයන්ගෙන් කණ්ඩායම් ආරක්ෂා කරන්න + සමහර පරිවර්තන ගොනු විශාල කරයි, විශේෂයෙන් PNG තිරපිටපත්, දැනටමත් සම්පීඩිත ඡායාරූප, උසස් තත්ත්වයේ WebP, හෝ පාර-දත්ත සංරක්ෂණය කර ඇති රූප. ප්‍රමාණය අඩු කිරීම ප්‍රධාන ඉලක්කය වන වැඩ ප්‍රවාහවල කුඩා ප්‍රභවයක් ප්‍රතිස්ථාපනය කිරීමෙන් එම ප්‍රතිදානයන් විශාල වුවහොත් මඟ හැරීමට ඉඩ දෙන්න. + නිර්යාත කිරීම උඩුගත කිරීමේ සීමාවන් සඳහා ගොනු සම්පීඩනය කිරීමට, ප්‍රමාණය වෙනස් කිරීමට හෝ සකස් කිරීමට අදහස් කරන විට එය සබල කරන්න. + කණ්ඩායමකට පසු මඟ හැරුණු ගොනු සමාලෝචනය කරන්න; ඒවා දැනටමත් ප්‍රශස්ත කර ඇති හෝ වෙනත් ආකෘතියක් අවශ්‍ය විය හැක. + අවසාන ගොනු ප්‍රමාණයට වඩා ගුණාත්මකභාවය, විනිවිදභාවය හෝ ආකෘති ගැළපුම වැදගත් වන විට එය අබල කරන්න. + ක්ලිප්බෝඩ් සහ සබැඳි + වේගවත් පෙළ-පාදක මෙවලම් සඳහා ස්වයංක්‍රීය ක්ලිප්බෝඩ් ආදානය සහ සබැඳි පෙරදසුන් පාලනය කරන්න + ස්වයංක්‍රීය ආදානය පුරෝකථනය කළ හැකි කරන්න + QR, Base64, URL, සහ පෙළ බර වැඩ ප්‍රවාහ සඳහා ක්ලිප්බෝඩ් සහ සබැඳි සැකසීම් පහසු වේ, නමුත් ස්වයංක්‍රීය ආදානය හිතාමතාම පැවතිය යුතුය. යෙදුම තනිවම ක්ෂේත්‍ර පුරවන බව පෙනේ නම්, ක්ලිප්බෝඩ් ඇලවීමේ හැසිරීම පරීක්ෂා කරන්න; සබැඳි කාඩ්පත් ඝෝෂාකාරී හෝ මන්දගාමී බවක් දැනේ නම්, සබැඳි පෙරදසුන් හැසිරීම් සීරුමාරු කරන්න. + ඔබ බොහෝ විට පෙළ පිටපත් කර වහාම ගැළපෙන මෙවලමක් විවෘත කරන විට ස්වයංක්‍රීය ක්ලිප්බෝඩ් ඇලවීමට ඉඩ දෙන්න. + පුද්ගලික ක්ලිප්බෝඩ් අන්තර්ගතය ඔබ බලාපොරොත්තු නොවූ මෙවලම්වල දිස්වන්නේ නම් ස්වයංක්‍රීය ඇලවීම අක්‍රීය කරන්න. + පෙරදසුන් ලබා ගැනීම මන්දගාමී, අවධානය වෙනතකට යොමු කරන, හෝ ඔබේ කාර්ය ප්‍රවාහයට අනවශ්‍ය වූ විට සබැඳි පෙරදසුන් ක්‍රියා විරහිත කරන්න. + සංයුක්ත පාලනයන් + තිර ඉඩ තද වූ විට ඝන තේරීම් සහ වේගවත් සැකසුම් ස්ථානගත කිරීම භාවිතා කරන්න + කුඩා තිර මත තවත් පාලන සවි කරන්න + කුඩා දුරකථන, භූ දර්ශන සංස්කරණය සහ බොහෝ පරාමිති සහිත මෙවලම් සඳහා සංයුක්ත තේරීම් සහ වේගවත් සැකසුම් ස්ථානගත කිරීම උපකාරී වේ. හුවමාරුව නම්, පාලනයට ඝනත්වය දැනිය හැකි නිසා, ඔබ දැනටමත් පොදු විකල්ප හඳුනා ගැනීමෙන් පසුව මෙය වඩාත් සුදුසුය. + සංවාද හෝ විකල්ප ලැයිස්තු තිරයට උස වැඩි යැයි හැඟෙන විට සංයුක්ත තේරීම් සක්‍රීය කරන්න. + සංදර්ශකයේ එක් පැත්තකින් මෙවලම් පාලනයට ළඟා වීමට පහසු නම් වේගවත් සැකසුම් පැත්ත සකසන්න. + ඔබ තවමත් යෙදුම ඉගෙන ගන්නේ නම් හෝ නිතර නිතර තද විකල්ප අතපසු කරන්නේ නම් ඉඩකඩ සහිත පාලන වෙත ආපසු යන්න. + වෙබයෙන් පින්තූර පූරණය කරන්න + පිටුවක් හෝ රූප URL එකක් අලවන්න, විග්‍රහ කළ පින්තූර පෙරදසුන් කරන්න, ඉන්පසු තෝරාගත් ප්‍රතිඵල සුරකින්න හෝ බෙදාගන්න + වෙබ් පින්තූර හිතාමතාම භාවිතා කරන්න + Web Image Loading මඟින් සපයා ඇති URL වෙතින් රූප මූලාශ්‍ර විග්‍රහ කරයි, පවතින පළමු රූපය පෙරදසුන් කරයි, සහ තෝරාගත් විග්‍රහ කළ පින්තූර සුරැකීමට හෝ බෙදා ගැනීමට ඔබට ඉඩ සලසයි. සමහර අඩවි තාවකාලික, ආරක්ෂිත හෝ ස්ක්‍රිප්ට්-උත්පාදිත සබැඳි භාවිතා කරයි, එබැවින් බ්‍රවුසරයක ක්‍රියා කරන පිටුවක් තවමත් භාවිත කළ හැකි රූප කිසිවක් ලබා නොදිය හැක. + සෘජු රූප URL එකක් හෝ පිටු URL එකක් අලවා විග්‍රහ කළ රූප ලැයිස්තුව දිස්වන තෙක් රැඳී සිටින්න. + පිටුවේ පින්තූර කිහිපයක් අඩංගු වන විට සහ ඔබට ඒවායින් කිහිපයක් පමණක් අවශ්‍ය වන විට රාමුව හෝ තේරීම් පාලන භාවිතා කරන්න. + කිසිවක් පූරණය නොවන්නේ නම්, සෘජු රූප සබැඳියක් උත්සාහ කරන්න හෝ බ්‍රවුසරය හරහා බාගත කරන්න; වෙබ් අඩවිය විග්‍රහ කිරීම අවහිර කිරීමට හෝ පුද්ගලික සබැඳි භාවිත කිරීමට ඉඩ ඇත. + ප්‍රමාණය වෙනස් කිරීමේ වර්ගය තෝරන්න + රූපයක් නව මානයන් වෙත බල කිරීමට පෙර පැහැදිලි, නම්‍යශීලී, බෝග සහ යෝග්‍යතා තේරුම් ගන්න + හැඩය, කැන්වසය සහ දර්ශන අනුපාතය පාලනය කරන්න + ප්‍රතිප්‍රමාණ කිරීමේ වර්ගය ඉල්ලන ලද පළල සහ උස මුල් දර්ශන අනුපාතයට නොගැලපෙන විට කුමක් සිදුවේද යන්න තීරණය කරයි. එය පික්සෙල් දිගු කිරීම, සමානුපාතිකයන් ආරක්ෂා කිරීම, අමතර ප්‍රදේශයක් කැපීම හෝ පසුබිම් කැන්වසයක් එකතු කිරීම අතර වෙනසයි. + විකෘති කිරීම පිළිගත හැකි විට හෝ මූලාශ්‍රයට දැනටමත් ඉලක්කයට සමාන දර්ශන අනුපාතයක් ඇති විට පමණක් පැහැදිලි භාවිත කරන්න. + විශේෂයෙන් ඡායාරූප සහ මිශ්‍ර කණ්ඩායම් සඳහා වැදගත් පැත්තේ සිට ප්‍රමාණය වෙනස් කිරීමෙන් සමානුපාතය තබා ගැනීමට නම්‍යශීලී භාවිත කරන්න. + මායිම් නොමැතිව දැඩි රාමු සඳහා Crop භාවිත කරන්න, නැතහොත් සම්පූර්ණ රූපය ස්ථාවර කැන්වසයක් තුළ දෘශ්‍යමාන විය යුතු විට Fit කරන්න. + රූප පරිමාණ මාදිලිය තෝරන්න + තියුණු බව, සුමට බව, වේගය සහ දාර කෞතුක වස්තු පාලනය කරන නැවත සාම්පල පෙරහන තෝරන්න + අහම්බෙන් මෘදු බවකින් තොරව ප්‍රමාණය වෙනස් කරන්න + රූප පරිමාණ ප්‍රකාරය ප්‍රතිප්‍රමාණ කිරීමේදී නව පික්සල ගණනය කරන ආකාරය පාලනය කරයි. සරල මාතයන් වේගවත් සහ පුරෝකථනය කළ හැකි අතර, උසස් පෙරහන් වලට වඩා හොඳින් විස්තර සංරක්ෂණය කළ හැකි නමුත් දාර මුවහත් කිරීමට, හලෝස් සෑදීමට හෝ විශාල රූප මත වැඩි කාලයක් ගත විය හැක. + ප්‍රතිඵලය කුඩා සහ පිරිසිදු වීමට පමණක් අවශ්‍ය වූ විට වේගවත් දෛනික ප්‍රමාණය වෙනස් කිරීම සඳහා Basic හෝ Bilinear භාවිතා කරන්න. + සියුම් විස්තර, පෙළ, හෝ උසස් තත්ත්වයේ පහත හෙලීම වැදගත් වන විට Lanczos, Robidoux, Spline, හෝ EWA-විලාසයේ මාතයන් භාවිතා කරන්න. + නොපැහැදිලි පික්සල පෙනුම විනාශ කරන පික්සල් චිත්‍ර, අයිකන සහ දෘඩ දාර ග්‍රැෆික්ස් සඳහා ළඟම භාවිතා කරන්න. + වර්ණ අවකාශය පරිමාණය කරන්න + ප්‍රතිප්‍රමාණ කිරීමේදී පික්සල නැවත සකස් කරන අතරතුර වර්ණ මිශ්‍ර වන්නේ කෙසේදැයි තීරණය කරන්න + ශ්‍රේණි සහ දාර ස්වභාවිකව තබා ගන්න + පරිමාණ වර්ණ අවකාශය ප්‍රමාණය වෙනස් කිරීමේදී භාවිතා කරන ගණිතය වෙනස් කරයි. බොහෝ රූප පෙරනිමියෙන් හොඳයි, නමුත් රේඛීය හෝ ප්‍රත්‍යක්ෂ අවකාශයන් ශක්තිමත් පරිමාණයෙන් පසු ශ්‍රේණි, අඳුරු දාර සහ සංතෘප්ත වර්ණ පිරිසිදු පෙනුමක් ඇති කළ හැක. + කුඩා වර්ණ වෙනස්කම් වලට වඩා වේගය සහ ගැළපුම වැදගත් වන විට පෙරනිමිය තබන්න. + ප්‍රමාණය වෙනස් කිරීමෙන් පසු අඳුරු දළ සටහන්, UI තිරපිටපත්, අනුක්‍රම හෝ වර්ණ පටි වැරදි ලෙස පෙනෙන විට රේඛීය හෝ ප්‍රත්‍යක්ෂ මාතයන් උත්සාහ කරන්න. + සැබෑ ඉලක්ක තිරයේ පෙර සහ පසු සසඳන්න, මන්ද වර්ණ-අවකාශ වෙනස්කම් සියුම් සහ අන්තර්ගතය මත රඳා පවතින බැවිනි. + Resize modes සීමා කරන්න + සීමාව ඉක්මවා ඇති පින්තූර මඟ හැරිය යුතු, නැවත කේතනය කළ යුතු හෝ ගැළපෙන පරිදි පරිමාණය කළ යුත්තේ කවදාදැයි දැන ගන්න + සීමාවෙන් සිදුවන්නේ කුමක්ද යන්න තෝරන්න + Limit Resize යනු කුඩා රූප මෙවලමක් පමණක් නොවේ. මූලාශ්‍රයක් දැනටමත් ඔබගේ සීමාවන් තුළ ඇති විට හෝ මිශ්‍ර ෆෝල්ඩර සහ උඩුගත කිරීම සඳහා වැදගත් වන රීතිය එක් පැත්තක් පමණක් කඩ කරන විට යෙදුම කුමක් කරන්නේ දැයි එහි මාදිලිය තීරණය කරයි. + සීමාවන් තුළ ඇති පින්තූර ස්පර්ශ නොකළ යුතු අතර නැවත අපනයනය නොකළ යුතු විට Skip භාවිත කරන්න. + මානයන් පිළිගත හැකි විට Recode භාවිතා කරන්න නමුත් ඔබට තවමත් නව ආකෘතියක්, පාරදත්ත හැසිරීම්, ගුණාත්මක භාවය හෝ ගොනු නාමයක් අවශ්‍ය වේ. + සීමාවන් කඩන පින්තූර ඉඩ ඇති කොටුවට ගැලපෙන තෙක් සමානුපාතිකව පරිමාණය කළ යුතු විට විශාලනය භාවිතා කරන්න. + දර්ශන අනුපාත ඉලක්ක + දිගු වූ මුහුණු, කැපුම් දාර සහ දැඩි නිමැවුම් ප්‍රමාණවලින් අනපේක්ෂිත මායිම් වළක්වා ගන්න + ප්‍රමාණය වෙනස් කිරීමට පෙර හැඩය ගලපන්න + පළල සහ උස ප්‍රමාණය වෙනස් කිරීමේ ඉලක්කයෙන් අඩක් පමණි. දර්ශන අනුපාතය තීරණය කරන්නේ රූපය ස්වභාවිකව ගැළපෙනවාද, කප්පාදු කිරීම අවශ්‍යද, නැතහොත් එය වටා කැන්වසයක් අවශ්‍යද යන්නයි. පළමුව හැඩය පරීක්ෂා කිරීම පුදුම සහගත ප්‍රතිප්‍රමාණ ප්‍රතිඵල වළක්වයි. + පැහැදිලි, බෝග, හෝ සුදුසු තේරීමට පෙර ඉලක්ක හැඩය සමඟ මූලාශ්‍ර හැඩය සසඳන්න. + විෂයට අමතර පසුබිමක් අහිමි විය හැකි විට සහ ගමනාන්තයට දැඩි රාමුවක් අවශ්‍ය වූ විට පළමුව කප්පාදු කරන්න. + මුල් රූපයේ සෑම කොටසක්ම දෘශ්‍යමානව පැවතිය යුතු විට Fit හෝ Flexible භාවිතා කරන්න. + ඉහළ මට්ටමේ හෝ සාමාන්‍ය ප්‍රමාණය වෙනස් කිරීම + පික්සල එකතු කිරීමේදී AI අවශ්‍ය වන විට සහ සරල පරිමාණය ප්‍රමාණවත් වන්නේ කවදාදැයි දැන ගන්න + ප්‍රමාණය විස්තර සමඟ පටලවා නොගන්න + සාමාන්‍ය ප්‍රමාණය වෙනස් කිරීම පික්සල අන්තර් පොලිත කිරීම මගින් මානයන් වෙනස් කරයි; එය සැබෑ විස්තර සොයා ගත නොහැක. AI ඉහළට වඩා තියුණු පෙනුමක් ඇති විස්තර නිර්මාණය කළ හැක, නමුත් එය මන්දගාමී වන අතර වයනය මායාවට පත් කළ හැකිය, එබැවින් නිවැරදි තේරීම ඔබට ගැළපුම, වේගය හෝ දෘශ්‍ය ප්‍රතිසාධනය අවශ්‍යද යන්න මත රඳා පවතී. + සිඟිති රූ, උඩුගත කිරීමේ සීමාවන්, තිරපිටපත් සහ සරල මාන වෙනස්කම් සඳහා සාමාන්‍ය ප්‍රමාණය වෙනස් කරන්න. + කුඩා හෝ මෘදු රූපයක් විශාල ප්‍රමාණයකින් වඩා හොඳ පෙනුමක් අවශ්‍ය වූ විට AI ඉහළ පරිමාණය භාවිතා කරන්න. + උත්පාදනය කරන ලද විස්තර ඒත්තු ගැන්විය හැකි නමුත් වැරදි ලෙස පෙනෙන නිසා AI ඉහළ මට්ටමෙන් පසු මුහුණු, පෙළ සහ රටා සසඳන්න. + පෙරහන් ඇණවුම වැදගත් වේ + පිරිසිදු කිරීම, වර්ණය, තියුණු බව සහ අවසාන සම්පීඩනය හිතාමතා අනුපිළිවෙලක් යොදන්න + පිරිසිදු පෙරහන් තොගයක් සාදන්න + පෙරහන් සෑම විටම එකිනෙකට හුවමාරු නොවේ. මුවහත් කිරීමට පෙර නොපැහැදිලි වීමක්, OCR ට පෙර ප්‍රතිවිරෝධතා වැඩි කිරීමක් හෝ සම්පීඩනයට පෙර වර්ණ වෙනස් කිරීමක් එකම පාලනයන්ගෙන් බෙහෙවින් වෙනස් ප්‍රතිදානයක් නිපදවිය හැකිය. + පළමුව කප්පාදු කර කරකවන්න එවිට පසුව පෙරහන් ක්‍රියා කරන්නේ ඉතිරිව ඇති පික්සල මත පමණි. + තියුණු කිරීමට හෝ ශෛලීගත බලපෑම් කිරීමට පෙර නිරාවරණය, සුදු සමබරතාවය සහ ප්‍රතිවිරුද්ධතාව යොදන්න. + අවසාන ප්‍රතිප්‍රමාණය සහ සම්පීඩනය අවසානයට ආසන්නව තබා ගන්න, එවිට පෙරදසුන් කළ විස්තර අපනයනය අනාවැකි කළ හැකි ලෙස පවතිනු ඇත. + පසුබිම් දාර සවි කරන්න + ස්වයංක්‍රීයව ඉවත් කිරීමෙන් පසු හැලෝස්, ඉතිරි වූ සෙවනැලි, හිසකෙස්, සිදුරු සහ මෘදු දළ සටහන් පිරිසිදු කරන්න + කටවුට් හිතාමතාම පෙනෙන්න + ස්වයංක්‍රීය වෙස් මුහුණු බොහෝ විට බැලූ බැල්මට හොඳ පෙනුමක් ඇති නමුත් දීප්තිමත්, අඳුරු හෝ රටා සහිත පසුබිම්වල ගැටලු හෙළි කරයි. Edge cleanup යනු ඉක්මන් කටවුට් එකක් සහ නැවත භාවිත කළ හැකි ස්ටිකරයක්, ලාංඡනයක් හෝ නිෂ්පාදන රූපයක් අතර වෙනසයි. + හලෝස් සහ නැතිවූ දාර විස්තර හෙළි කිරීමට ප්‍රතිවිරෝධතා පසුබිම්වලට එරෙහිව ප්‍රතිඵලය පෙරදසුන් කරන්න. + අවට ඉතිරි කොටස් මකා දැමීමට පෙර හිසකෙස්, කොන් සහ විනිවිද පෙනෙන වස්තූන් සඳහා ප්‍රතිසාධනය භාවිතා කරන්න. + මෝස්තරයකට කටවුට් තබන විට අවසාන පසුබිම් වර්ණය පිළිබඳ කුඩා පරීක්ෂණයක් අපනයනය කරන්න. + කියවිය හැකි දිය සලකුණු + ඡායාරූපය විනාශ නොකර දීප්තිමත්, අඳුරු, කාර්යබහුල සහ කපන ලද පින්තූර මත ලකුණු දෘශ්‍යමාන කරන්න + රූපය සඟවා නොගෙන එය ආරක්ෂා කරන්න + එක් රූපයක හොඳින් පෙනෙන දිය සලකුණක් තවත් රූපයක අතුරුදහන් විය හැකිය. ප්‍රමාණය, පාරාන්ධතාව, වෙනස, ස්ථානගත කිරීම සහ පුනරාවර්තනය පළමු පෙරදසුන පමණක් නොව මුළු කණ්ඩායම සඳහාම තෝරා ගත යුතුය. + සියලුම ගොනු අපනයනය කිරීමට පෙර කණ්ඩායමේ දීප්තිමත්ම සහ අඳුරුතම රූපවල සලකුණ පරීක්ෂා කරන්න. + විශාල පුනරාවර්තන ලකුණු සඳහා අඩු පාරාන්ධතාව සහ කුඩා කෙළවරේ ලකුණු සඳහා ප්‍රබල වෙනස භාවිතා කරන්න. + නැවත භාවිතය වැලැක්වීමට ලකුණ අදහස් කරන්නේ නම් මිස වැදගත් මුහුණු, පෙළ සහ නිෂ්පාදන විස්තර පැහැදිලිව තබා ගන්න. + එක් රූපයක් ටයිල් වලට බෙදන්න + පේළි, තීරු, අභිරුචි ප්‍රතිශත, ආකෘතිය සහ ගුණාත්මකභාවය අනුව තනි රූපයක් කපන්න + ජාලක සහ ඇණවුම් කළ කෑලි සකස් කරන්න + පින්තූර බෙදීම එක් මූලාශ්‍ර රූපයකින් බහු ප්‍රතිදානයන් නිර්මාණය කරයි. එයට පේළි සහ තීරු ගණන අනුව බෙදීමට, පේළි හෝ තීරු ප්‍රතිශත සකස් කිරීමට සහ තෝරාගත් ප්‍රතිදාන ආකෘතිය සහ ගුණාත්මකභාවය සමඟ කෑලි සුරැකීමට හැකිය, එය ජාලක, පිටු පෙති, පරිදර්ශන සහ ඇණවුම් කළ සමාජ පළ කිරීම් සඳහා ප්‍රයෝජනවත් වේ. + මූලාශ්‍ර රූපය තෝරන්න, පසුව ඔබට අවශ්‍ය පේළි සහ තීරු ගණන සකසන්න. + කෑලි සියල්ල සමාන ප්‍රමාණයේ නොවිය යුතු විට පේළි හෝ තීරු ප්‍රතිශතය සීරුමාරු කරන්න. + සුරැකීමට පෙර ප්‍රතිදාන ආකෘතිය සහ ගුණාත්මකභාවය තෝරන්න, එවිට සෑම ජනනය කරන ලද කැබැල්ලක්ම එකම අපනයන නීති භාවිතා කරයි. + රූප තීරු කපා + සිරස් හෝ තිරස් කලාප ඉවත් කර ඉතිරි කොටස් නැවත එකට ඒකාබද්ධ කරන්න + පරතරයක් ඉතිරි නොකර කලාපයක් ඉවත් කරන්න + රූප කැපීම සාමාන්‍ය වගාවට වඩා වෙනස් ය. එය තෝරාගත් සිරස් හෝ තිරස් තීරුවක් ඉවත් කළ හැකිය, ඉන්පසු ඉතිරි රූප කොටස් එකට ඒකාබද්ධ කරන්න. ප්‍රතිලෝම මාදිලිය වෙනුවට තෝරාගත් කලාපය තබා ගන්නා අතර ප්‍රතිලෝම දිශා දෙකම භාවිතා කිරීමෙන් තෝරාගත් සෘජුකෝණාස්‍රය තබා ගනී. + පැති කලාප, තීරු හෝ මැද තීරු සඳහා සිරස් කැපීම භාවිතා කරන්න; බැනර්, හිඩැස් හෝ පේළි සඳහා තිරස් කැපීම භාවිතා කරන්න. + ඔබට එය ඉවත් කිරීම වෙනුවට තෝරාගත් කොටස තබා ගැනීමට අවශ්‍ය වූ විට අක්ෂයක් මත ප්‍රතිලෝම සක්‍රීය කරන්න. + ඉවත් කළ ප්‍රදේශය වැදගත් තොරතුරු හරහා ගිය විට ඒකාබද්ධ කළ අන්තර්ගතය හදිසියේ දිස්විය හැකි බැවින් කැපීමෙන් පසු දාර පෙරදසුන් කරන්න. + ප්රතිදාන ආකෘතිය තෝරන්න + අන්තර්ගතය සහ ගමනාන්තය මත පදනම්ව JPEG, PNG, WebP, AVIF, JXL, හෝ PDF තෝරන්න + ගමනාන්තයට ආකෘතිය තීරණය කිරීමට ඉඩ දෙන්න + හොඳම ආකෘතිය රඳා පවතින්නේ රූපයේ අඩංගු දේ සහ එය භාවිතා කරන්නේ කොතැනද යන්න මතය. ඡායාරූප, තිරපිටපත්, විනිවිද පෙනෙන වත්කම්, ලේඛන සහ සජීවිකරණ ගොනු වැරදි ආකෘතියට බල කරන විට විවිධ ආකාරවලින් අසාර්ථක වේ. + විනිවිදභාවය සහ නිශ්චිත පික්සල අවශ්‍ය නොවන විට පුළුල් ඡායාරූප ගැළපුම සඳහා JPEG භාවිතා කරන්න. + තියුණු තිරපිටපත්, UI ග්‍රහණ, විනිවිද පෙනෙන ග්‍රැෆික්ස් සහ පාඩු රහිත සංස්කරණ සඳහා PNG භාවිත කරන්න. + සංයුක්ත නවීන ප්‍රතිදානය වැදගත් වන විට සහ ලැබෙන යෙදුම එයට සහාය දක්වන විට WebP, AVIF, හෝ JXL භාවිත කරන්න. + ශ්‍රව්‍ය ආවරණ උපුටා ගන්න + කාවැද්දූ ඇල්බම කලා හෝ ලබා ගත හැකි මාධ්‍ය රාමු ශ්‍රව්‍ය ගොනු වලින් PNG රූප ලෙස සුරකින්න + ශ්‍රව්‍ය ගොනු වලින් කලා කෘති අදින්න + ශ්‍රව්‍ය ආවරණ ශ්‍රව්‍ය ගොනු වලින් කාවැද්දූ කලා කෘති කියවීමට ඇන්ඩ්‍රොයිඩ් මාධ්‍ය පාර-දත්ත භාවිත කරයි. කාවැද්දූ කලා කෘති නොමැති විට, ඇන්ඩ්‍රොයිඩ් එකක් සැපයිය හැකි නම්, රිට්‍රීවර් මාධ්‍ය රාමුවකට ආපසු යා හැක; කිසිවක් නොමැති නම්, උපුටා ගැනීමට රූපයක් නොමැති බව මෙවලම වාර්තා කරයි. + ශ්‍රව්‍ය ආවරණ විවෘත කර අපේක්ෂිත ආවරණ කලාව සහිත ශ්‍රව්‍ය ගොනු එකක් හෝ කිහිපයක් තෝරන්න. + සුරැකීමට හෝ බෙදා ගැනීමට පෙර උපුටා ගත් කවරය සමාලෝචනය කරන්න, විශේෂයෙන් ප්‍රවාහයෙන් හෝ පටිගත කරන යෙදුම්වලින් ගොනු සඳහා. + රූපයක් හමු නොවුනේ නම්, ශ්‍රව්‍ය ගොනුවට කාවැද්දූ ආවරණයක් නොමැති හෝ Android භාවිතා කළ හැකි රාමුවක් නිරාවරණය කළ නොහැක. + දින සහ ස්ථාන පාර-දත්ත + ග්‍රහණ කාලය වැදගත් වන විට සංරක්ෂණය කළ යුතු දේ තීරණය කරන්න, නමුත් GPS හෝ උපාංග දත්ත කාන්දු නොවිය යුතුය + ප්‍රයෝජනවත් පාරදත්ත පුද්ගලික පාරදත්ත වලින් වෙන් කරන්න + පාරදත්ත සියල්ල එකසේ අවදානම් නොවේ. ග්‍රහණ දිනයන් ලේඛනාගාර සහ වර්ග කිරීම සඳහා ප්‍රයෝජනවත් විය හැකි අතර, GPS, උපාංග අනුක්‍රමික වැනි ක්ෂේත්‍ර, මෘදුකාංග ටැග්, හෝ හිමිකාර ක්ෂේත්‍රවලට අපේක්ෂිත ප්‍රමාණයට වඩා වැඩි යමක් අනාවරණය කළ හැක. + ඇල්බම, ස්කෑන්, හෝ ව්‍යාපෘති ඉතිහාසය සඳහා කාලානුක්‍රමික අනුපිළිවෙල වැදගත් වන විට දිනය සහ වේලාව ටැග් තබා ගන්න. + පොදු බෙදාගැනීම හෝ නාඳුනන පුද්ගලයින්ට පින්තූර යැවීමට පෙර GPS සහ උපාංග හඳුනාගැනීමේ ටැග් ඉවත් කරන්න. + පුද්ගලිකත්ව අවශ්‍යතා දැඩි වූ විට නියැදියක් අපනයනය කර EXIF ​​නැවත පරීක්ෂා කරන්න. + ගොනු නාම ගැටීමෙන් වළකින්න + බොහෝ ගොනු image.jpg හෝ screenshot.png වැනි නම් බෙදා ගන්නා විට කණ්ඩායම් ප්‍රතිදාන ආරක්ෂා කරන්න + සෑම නිමැවුම් නාමයක්ම අද්විතීය කරන්න + රූප පණිවිඩකරුවන්, තිරපිටපත්, වලාකුළු ෆෝල්ඩර, හෝ බහු කැමරාවලින් ලැබෙන විට අනුපිටපත් ගොනු නාම පොදු වේ. හොඳ රටාවක් අහම්බෙන් ආදේශ කිරීම වළක්වන අතර ප්‍රතිදානයන් ඒවායේ ප්‍රභවයන් වෙත නැවත සොයා ගත හැකි ලෙස තබා ගනී. + සංස්කරණය කළ පිටපත හඳුනාගත හැකි විට මුල් නම සහ උපසර්ගයක් ඇතුළත් කරන්න. + විශාල මිශ්‍ර කාණ්ඩ සකසන විට අනුපිළිවෙල, වේලා මුද්‍රාව, පෙරසිටුවීම හෝ මානයන් එක් කරන්න. + ඔබ නම් කිරීමේ රටාව ගැටිය නොහැකි බව තහවුරු කරන තෙක් වැඩ ප්‍රවාහ උඩින් ලිවීමෙන් වළකින්න. + සුරකින්න හෝ බෙදාගන්න + ස්ථීර ගොනුවක් සහ වෙනත් යෙදුමකට තාවකාලික භාරදීමක් අතර තෝරන්න + නිවැරදි චේතනාවෙන් අපනයනය කරන්න + සුරැකීම සහ බෙදාගැනීම සමාන බවක් දැනිය හැකි නමුත් ඒවා විවිධ ගැටළු විසඳයි. සුරැකීම ඔබට පසුව සොයා ගත හැකි ගොනුවක් නිර්මාණය කරයි; බෙදාගැනීම ඇන්ඩ්‍රොයිඩ් හරහා ජනනය කරන ලද ප්‍රතිඵලයක් වෙනත් යෙදුමකට යවන අතර කල් පවතින දේශීය පිටපතක් ඉතිරි නොකළ හැකිය. + ප්‍රතිදානය දන්නා ෆෝල්ඩරයක තිබිය යුතු, උපස්ථ කළ යුතු හෝ පසුව නැවත භාවිත කළ යුතු විට සුරකින්න භාවිත කරන්න. + ඉක්මන් පණිවිඩ, විද්‍යුත් තැපැල් ඇමුණුම්, සමාජ පළ කිරීම හෝ ප්‍රතිඵලය වෙනත් සංස්කාරකයකට යැවීම සඳහා Share භාවිත කරන්න. + ලැබෙන යෙදුම විශ්වාස කළ නොහැකි වූ විට හෝ අසාර්ථක වූ බෙදාගැනීමක් ප්‍රතිනිර්මාණය කිරීමට කරදරයක් වන විට පළමුව සුරකින්න. + PDF පිටු ප්‍රමාණය සහ මායිම් + ලේඛනයක් සෑදීමට පෙර කඩදාසි ප්‍රමාණය, සුදුසු හැසිරීම සහ හුස්ම ගැනීමේ කාමරය තෝරන්න + පින්තූර පිටු මුද්‍රණය කළ හැකි සහ කියවිය හැකි බවට පත් කරන්න + ඒවායේ හැඩය තෝරාගත් කඩදාසි ප්‍රමාණයට නොගැලපෙන විට පින්තූර අපහසු PDF පිටු බවට පත්විය හැක. මායිම්, යෝග්‍යතා ප්‍රකාරය සහ පිටු දිශානතිය මඟින් අන්තර්ගතය කප්පාදු කර තිබේද, කුඩාද, දිගු කර තිබේද, හෝ කියවීමට පහසුද යන්න තීරණය කරයි. + PDF මුද්‍රණය කළ හැකි හෝ පෝරමයකට ඉදිරිපත් කළ හැකි විට සැබෑ කඩදාසි ප්‍රමාණය භාවිතා කරන්න. + ස්කෑන් සහ තිරපිටපත් සඳහා මායිම් භාවිතා කරන්න, එවිට පෙළ පිටු කෙළවරට විරුද්ධ නොවේ. + මූලාශ්‍ර රූප මිශ්‍ර දිශානතියක් ඇති විට ප්‍රතිමූර්තිය සහ භූ දර්ශන පිටු එකට පෙරදසුන් කරන්න. + ස්කෑන් පිරිසිදු කරන්න + PDF හෝ OCR වලට පෙර කඩදාසි දාර, සෙවනැලි, වෙනස, භ්‍රමණය සහ කියවීමේ හැකියාව වැඩි දියුණු කරන්න + සංරක්ෂණය කිරීමට පෙර පිටු සකස් කරන්න + ස්කෑන් එකක් තාක්ෂණිකව ග්‍රහණය කර ගත හැකි නමුත් කියවීමට අපහසුය. PDF අපනයනය කිරීමට පෙර කුඩා පිරිසිදු කිරීමේ පියවර බොහෝ විට සම්පීඩන සැකසුම් වලට වඩා වැදගත් වේ, විශේෂයෙන් රිසිට්පත්, අතින් ලියන ලද සටහන් සහ අඩු ආලෝක පිටු සඳහා. + නිවැරදි ඉදිරිදර්ශනය සහ මේස දාර, ඇඟිලි, සෙවනැලි සහ පසුබිම් අවුල් ඉවත් කරන්න. + මුද්දර, අත්සන්, හෝ පැන්සල් ලකුණු විනාශ නොකර පෙළ පැහැදිලි වන පරිදි ප්‍රවේශමෙන් ප්‍රතිවිරුද්ධතාව වැඩි කරන්න. + පිටුව කෙළින් සහ කියවිය හැකි වූ පසුව පමණක් OCR ධාවනය කරන්න, පසුව වැදගත් අංක අතින් සත්‍යාපනය කරන්න. + PDF සම්පීඩනය, අළු පරිමාණය හෝ අලුත්වැඩියා කරන්න + සැහැල්ලු, මුද්‍රණය කළ හැකි හෝ නැවත ගොඩනඟන ලද ලේඛන පිටපත් සඳහා එක් ගොනු PDF මෙහෙයුම් භාවිත කරන්න + PDF පිරිසිදු කිරීමේ මෙහෙයුම තෝරන්න + PDF මෙවලම් සම්පීඩනය, අළු පරිමාණ පරිවර්තනය සහ අලුත්වැඩියා කිරීම සඳහා වෙනම මෙහෙයුම් ඇතුළත් වේ. සම්පීඩනය සහ අළුපැහැ ගැන්වීම ප්‍රධාන වශයෙන් කාවැද්දූ රූප හරහා ක්‍රියා කරන අතර අලුත්වැඩියා කිරීම PDF ව්‍යුහය නැවත ගොඩනඟයි; මේ කිසිවක් සෑම ලේඛනයක් සඳහාම සහතික කළ විසඳුමක් ලෙස නොසැලකිය යුතුය. + ලේඛනයේ පින්තූර සහ බෙදාගැනීම සඳහා ගොනු ප්‍රමාණයේ කරුණු අඩංගු වන විට Compress PDF භාවිත කරන්න. + ලේඛනය මුද්‍රණය කිරීමට පහසු හෝ වර්ණ රූප අවශ්‍ය නොවන විට Grayscale භාවිතා කරන්න. + ලේඛනයක් විවෘත කිරීමට අපොහොසත් වූ විට හෝ අභ්‍යන්තර ව්‍යුහයට හානි වූ විට පිටපතක් මත අළුත්වැඩියා PDF භාවිතා කරන්න, පසුව නැවත ගොඩනඟන ලද ගොනුව සත්‍යාපනය කරන්න. + PDF වලින් පින්තූර උපුටා ගන්න + කාවැද්දූ PDF පින්තූර ප්‍රතිසාධනය කර උපුටා ගත් ප්‍රතිඵල ලේඛනාගාරයකට අසුරන්න + ලේඛන වලින් මූලාශ්‍ර පින්තූර සුරකින්න + Extract Images කාවැද්දූ රූප වස්තු සඳහා PDF පරිලෝකනය කරයි, හැකි සෑම විටම අනුපිටපත් මඟහරියි, සහ ප්‍රතිසාධනය කරන ලද පින්තූර ජනනය කරන ලද ZIP සංරක්ෂිතයක ගබඩා කරයි. එය උපුටා ගන්නේ PDF තුළ සත්‍ය වශයෙන්ම කාවැදී ඇති රූප පමණක් මිස පෙළ, දෛශික ඇඳීම් හෝ තිර රුවක් ලෙස පෙනෙන සෑම පිටුවක්ම නොවේ. + නාමාවලියකින් හෝ පරිලෝකනය කළ වත්කම්වල ඡායාරූප වැනි PDF එකක් තුළ ගබඩා කර ඇති පින්තූර ඔබට අවශ්‍ය වූ විට Extract Images භාවිත කරන්න. + ඔබට පෙළ සහ පිරිසැලසුම ඇතුළුව සම්පූර්ණ පිටු අවශ්‍ය වූ විට ඒ වෙනුවට පින්තූර වෙත PDF හෝ පිටු උපුටා ගැනීම භාවිත කරන්න. + මෙවලම කාවැද්දූ රූප කිසිවක් වාර්තා නොකරන්නේ නම්, පිටුව පෙළ/දෛශික අන්තර්ගතය විය හැකිය හෝ පින්තූර උපුටා ගත හැකි වස්තු ලෙස ගබඩා නොකළ හැකිය. + පාරදත්ත, සමතලා කිරීම සහ මුද්‍රණ ප්‍රතිදානය + ලේඛන ගුණාංග සංස්කරණය කරන්න, පිටු rasterize, හෝ අභිරුචි මුද්‍රණ පිරිසැලසුම් හිතාමතාම සකස් කරන්න + අවසාන ලේඛන ක්‍රියාව තෝරන්න + PDF පාර-දත්ත, සමතලා කිරීම සහ මුද්‍රණය සකස් කිරීම විවිධ අවසන් අදියර ගැටලු විසඳයි. පාර-දත්ත ලේඛන ගුණාංග පාලනය කරයි, PDF මඟින් පිටු අඩු සංස්කරණය කළ හැකි ප්‍රතිදානයකට rasterizes කරයි, සහ PDF PDF මඟින් පිටු ප්‍රමාණය, දිශානතිය, ආන්තිකය සහ පිටු-පත්‍ර පාලනයන් සමඟින් නව පිරිසැලසුමක් සකස් කරයි. + කර්තෘ, මාතෘකාව, මූල පද, නිෂ්පාදක, හෝ පෞද්ගලිකත්වය පිරිසිදු කිරීම වැදගත් වන විට පාරදත්ත භාවිත කරන්න. + දෘශ්‍ය පිටු අන්තර්ගතය වෙනම PDF වස්තු ලෙස සංස්කරණය කිරීමට අපහසු වන විට පැතලි PDF භාවිතා කරන්න. + ඔබට අභිරුචි පිටු ප්‍රමාණය, දිශානතිය, මායිම්, හෝ පත්‍රයකට පිටු කිහිපයක් අවශ්‍ය වූ විට මුද්‍රණ PDF භාවිත කරන්න. + OCR සඳහා පින්තූර සූදානම් කරන්න + OCR වෙතින් පෙළ කියවීමට ඉල්ලා සිටීමට පෙර කප්පාදුව, භ්‍රමණය, ප්‍රතිවිරුද්ධතාව, denoise, සහ පරිමාණය භාවිත කරන්න + OCR ක්ලීනර් පික්සල දෙන්න + OCR වැරදි බොහෝ විට OCR එන්ජිමට වඩා ආදාන රූපයෙන් පැමිණේ. වංක පිටු, අඩු වෙනස, නොපැහැදිලි, සෙවනැලි, කුඩා පෙළ සහ මිශ්‍ර පසුබිම් සියල්ල හඳුනාගැනීමේ ගුණත්වය අඩු කරයි. + හඳුනා ගැනීමට පෙර රේඛා තිරස් වන පරිදි පෙළ ප්‍රදේශයට කපා රූපය කරකවන්න. + අකුරු කියවීමට පහසු වන විට පමණක් ප්‍රතිවිරුද්ධතාව වැඩි කරන්න හෝ පිරිසිදු කළු-සුදු බවට පරිවර්තනය කරන්න. + කුඩා අකුරු මධ්‍යස්ථව ඉහළට ප්‍රතිප්‍රමාණ කරන්න, නමුත් ව්‍යාජ දාර ඇති කරන ආක්‍රමණශීලී තියුණු කිරීම වළක්වන්න. + QR අන්තර්ගතය පරීක්ෂා කරන්න + කේතයක් විශ්වාස කිරීමට පෙර සබැඳි, Wi-Fi අක්තපත්‍ර, සම්බන්ධතා සහ ක්‍රියා සමාලෝචනය කරන්න + විකේතනය කළ පෙළ විශ්වාස නොකළ ආදානයක් ලෙස සලකන්න + QR කේතයකට URL එකක්, සම්බන්ධතා කාඩ්පතක්, Wi-Fi මුරපදයක්, දින දර්ශන සිදුවීමක්, දුරකථන අංකයක් හෝ සරල පෙළක් සැඟවිය හැක. කේතය අසල ඇති දෘශ්‍ය ලේබලය සැබෑ ගෙවීමට නොගැලපේ, එබැවින් එය මත ක්‍රියා කිරීමට පෙර විකේතනය කළ අන්තර්ගතය සමාලෝචනය කරන්න. + එය විවෘත කිරීමට පෙර සම්පූර්ණ විකේතනය කරන ලද URL පරීක්ෂා කරන්න, විශේෂයෙන් කෙටි කළ හෝ නුහුරු නුපුරුදු වසම්. + Wi-Fi, සම්බන්ධතා, දින දර්ශනය, දුරකථන සහ SMS කේත සමඟ ප්‍රවේශම් වන්න, මන්ද ඒවා සංවේදී ක්‍රියාවන් අවුලුවාලිය හැකිය. + ඔබට එය වහාම විවෘත කිරීම වෙනුවට වෙනත් තැනක සත්‍යාපනය කිරීමට අවශ්‍ය වූ විට පළමුව අන්තර්ගතය පිටපත් කරන්න. + QR කේත ජනනය කරන්න + සරල පෙළ, URL, Wi-Fi, සම්බන්ධතා, විද්‍යුත් තැපෑල, දුරකථන, SMS සහ ස්ථාන දත්ත වලින් කේත සාදන්න + නිවැරදි QR පේලෝඩ් එක ගොඩනඟන්න + QR තිරයට ඇතුළත් කළ අන්තර්ගතයෙන් කේත ජනනය කිරීමට මෙන්ම පවතින ඒවා පරිලෝකනය කළ හැක. ව්‍යුහගත QR වර්ග Wi-Fi පිවිසුම් විස්තර, සම්බන්ධතා කාඩ්පත්, විද්‍යුත් තැපැල් ක්ෂේත්‍ර, දුරකථන අංක, SMS අන්තර්ගතය, URL, හෝ භූගෝලීය ඛණ්ඩාංක වැනි වෙනත් යෙදුම් තේරුම් ගන්නා ආකෘතියකින් දත්ත සංකේතනය කිරීමට උදවු කරයි. + සරල සටහන් සඳහා සරල පෙළ තෝරන්න, නැතහොත් කේතය Wi-Fi, සම්බන්ධතා, URL, ඊමේල්, දුරකථන, SMS, හෝ ස්ථාන දත්ත ලෙස විවෘත කළ යුතු විට ව්‍යුහගත වර්ගයක් භාවිතා කරන්න. + දෘශ්‍ය පෙරදසුන ඔබ ඇතුළත් කළ ගෙවීම පමණක් පිළිබිඹු කරන බැවින් ජනනය කරන ලද කේතය බෙදා ගැනීමට පෙර සෑම ක්ෂේත්‍රයක්ම පරීක්ෂා කරන්න. + සුරකින ලද QR කේතය මුද්‍රණය කරන විට, ප්‍රසිද්ධියේ පළ කරන විට, හෝ වෙනත් පුද්ගලයින් විසින් භාවිත කරන විට ස්කෑනරයක් සමඟ පරීක්ෂා කරන්න. + චෙක්සම් මාදිලියක් තෝරන්න + ගොනු හෝ පෙළ වලින් හැෂ් ගණනය කරන්න, එක් ගොනුවක් සංසන්දනය කරන්න, නැතහොත් බොහෝ ගොනු කණ්ඩායම් පරීක්ෂා කරන්න + පෙනුම නොව අන්තර්ගතය තහවුරු කරන්න + චෙක්සම් මෙවලම් සතුව ගොනු හැෂිං, පෙළ හැෂිං, දන්නා හෑෂ් එකකට එරෙහිව ගොනුවක් සංසන්දනය කිරීම සහ කණ්ඩායම් සංසන්දනය සඳහා වෙනම ටැබ් ඇත. චෙක්සම් එකක් තෝරාගත් ඇල්ගොරිතම සඳහා බයිට් සඳහා බයිට් අනන්‍යතාවය සනාථ කරයි; රූප දෙකක් හුදෙක් සමාන බව ඔප්පු නොවේ. + ගොනු සඳහා ගණනය කරන්න සහ ටයිප් කළ හෝ ඇලවූ පෙළ දත්ත සඳහා Text Hash භාවිතා කරන්න. + යමෙකු ඔබට එක් ගොනුවක් සඳහා අපේක්ෂිත චෙක්සම් ලබා දෙන විට සංසන්දනය කරන්න. + බොහෝ ගොනු වලට හැෂ් හෝ සත්‍යාපනය අවශ්‍ය වූ විට Batch Compare භාවිතා කරන්න, ඉන්පසු තෝරාගත් ඇල්ගොරිතම ස්ථාවරව තබා ගන්න. + ක්ලිප්බෝඩ් පෞද්ගලිකත්වය + පුද්ගලික පිටපත් කළ දත්ත අහම්බෙන් හෙලිදරව් නොකර ස්වයංක්‍රීය ක්ලිප්බෝඩ් විශේෂාංග භාවිතා කරන්න + පිටපත් කළ අන්තර්ගතය හිතාමතාම තබා ගන්න + ක්ලිප්බෝඩ් ස්වයංක්‍රීයකරණය පහසු වේ, නමුත් පිටපත් කළ පෙළ මුරපද, සබැඳි, ලිපින, ටෝකන හෝ පුද්ගලික සටහන් අඩංගු විය හැක. ක්ලිප්බෝඩ් මත පදනම් වූ ආදානය ඔබේ පුරුදු සහ රහස්‍යතා අපේක්ෂාවන්ට ගැළපෙන වේග විශේෂාංගයක් ලෙස සලකන්න. + පුද්ගලික පෙළ අනපේක්ෂිත ලෙස මෙවලම්වල දිස්වන්නේ නම් ස්වයංක්‍රීය ක්ලිප්බෝඩ් භාවිතය අබල කරන්න. + ඔබට හිතාමතාම ප්‍රතිඵලය ආචයනය වැලැක්වීමට අවශ්‍ය විට පමණක් පසුරු පුවරුව-පමණක් ඉතිරි කිරීම භාවිත කරන්න. + සංවේදී පෙළ, QR දත්ත, හෝ Base64 ගෙවුම් පැටවීම් හැසිරවීමෙන් පසු පසුරු පුවරුව හිස් කරන්න හෝ ප්‍රතිස්ථාපනය කරන්න. + වර්ණ ආකෘති + වෙනත් තැනක ඇලවීමට පෙර HEX, RGB, HSV, alpha, සහ පිටපත් කළ වර්ණ අගයන් තේරුම් ගන්න + ඉලක්කය අපේක්ෂා කරන ආකෘතිය පිටපත් කරන්න + එකම දෘශ්ය වර්ණය ක්රම කිහිපයකින් ලිවිය හැකිය. නිර්මාණ මෙවලමක්, CSS ක්ෂේත්‍රයක්, Android වර්ණ අගයක්, හෝ රූප සංස්කාරකයක් වෙනත් අනුපිළිවෙලක්, ඇල්ෆා හැසිරවීමක් හෝ වර්ණ ආකෘතියක් බලාපොරොත්තු විය හැක. + සංයුක්ත වර්ණ කේත අපේක්ෂා කරන වෙබ්, නිර්මාණ හෝ තේමා ක්ෂේත්‍රවලට ඇලවීමේදී HEX භාවිත කරන්න. + ඔබට නාලිකා, දීප්තිය, සන්තෘප්තිය, හෝ පැහැය අතින් සකස් කිරීමට අවශ්‍ය වූ විට RGB හෝ HSV භාවිත කරන්න. + සමහර ගමනාන්ත එය නොසලකා හරින නිසා හෝ වෙනත් අනුපිළිවෙලක් භාවිතා කරන නිසා විනිවිදභාවය වැදගත් වන විට ඇල්ෆා වෙන වෙනම පරීක්ෂා කරන්න. + වර්ණ පුස්තකාලය පිරික්සන්න + නම හෝ HEX අනුව නම් කරන ලද වර්ණ සොයන්න සහ ප්‍රියතමයන් ඉහළින් තබා ගන්න + නැවත භාවිතා කළ හැකි නම් වර්ණ සොයන්න + වර්ණ පුස්තකාලය නම් කළ වර්ණ එකතුවක් පූරණය කරයි, එය නම අනුව වර්ග කරයි, වර්ණ නාමයෙන් හෝ HEX අගය අනුව පෙරහන් කරයි, සහ ප්‍රියතම වර්ණ ගබඩා කරයි, එවිට වැදගත් ඇතුළත් කිරීම් පහසුවෙන් ළඟා විය හැක. රූපයකින් නියැදීම වෙනුවට ඔබට සැබෑ නම් කළ වර්ණයක් අවශ්‍ය වූ විට එය ප්‍රයෝජනවත් වේ. + ඔබ පවුල දන්නා විට රතු, නිල්, ස්ලයිට් හෝ මින්ට් වැනි වර්ණ නාමයකින් සොයන්න. + ඔබට දැනටමත් සංඛ්‍යාත්මක වර්ණයක් ඇති විට සහ ගැළපෙන හෝ ආසන්න නම් ඇතුළත් කිරීම් සොයා ගැනීමට අවශ්‍ය විට HEX මගින් සොයන්න. + බ්‍රවුස් කරන අතරතුර සාමාන්‍ය පුස්තකාලයට වඩා ඉදිරියට යන නිසා නිතර වර්ණ ප්‍රියතමයන් ලෙස සලකුණු කරන්න. + දැල් අනුක්‍රමණ එකතු භාවිතා කරන්න + පවතින දැල් අනුක්‍රමණ සම්පත් බාගත කර තෝරාගත් පින්තූර බෙදා ගන්න + දුරස්ථ දැල් අනුක්‍රමණ වත්කම් භාවිතා කරන්න + Mesh Gradients හට දුරස්ථ සම්පත් එකතුවක් පූරණය කිරීමට, නැතිවූ අනුක්‍රමණ රූප බාගැනීමට, බාගැනීම් ප්‍රගතිය පෙන්වීමට සහ තෝරාගත් අනුක්‍රමික බෙදා ගැනීමට හැකිය. ලබා ගැනීම ජාල ප්‍රවේශය සහ දුරස්ථ සම්පත් ගබඩාව මත රඳා පවතී, එබැවින් හිස් ලැයිස්තුවක් සාමාන්‍යයෙන් අදහස් වන්නේ සම්පත් තවමත් නොතිබූ බවයි. + ඔබට සූදානම් කළ දැල් අනුක්‍රමණ රූප අවශ්‍ය විට අනුක්‍රමණ මෙවලම්වලින් Mesh Gradients විවෘත කරන්න. + එකතුව හිස් යැයි උපකල්පනය කිරීමට පෙර සම්පත් පැටවීම හෝ බාගැනීම් ප්‍රගතිය සඳහා රැඳී සිටින්න. + ඔබට අවශ්‍ය අනුක්‍රමණය තෝරන්න සහ බෙදා ගන්න, නැතහොත් ඔබට අභිරුචි අනුක්‍රමයක් හස්තීයව ගොඩනගා ගැනීමට අවශ්‍ය වූ විට Gradient Maker වෙත ආපසු යන්න. + ජනනය කරන ලද වත්කම් විභේදනය + අනුක්‍රමණය, ශබ්දය, බිතුපත්, SVG-වැනි කලාව, හෝ වයනය ජනනය කිරීමට පෙර ප්‍රතිදාන ප්‍රමාණය තෝරන්න + ඔබට අවශ්‍ය ප්‍රමාණයෙන් නිපදවන්න + ජනනය කරන ලද රූපවල නැවත වැටීමට මුල් කැමරාවක් නොමැත. ප්‍රතිදානය ඉතා කුඩා නම්, පසුව පරිමාණය මඟින් රටා මෘදු කිරීමට, විස්තර මාරු කිරීමට හෝ වත්කම් ටයිල් සහ සම්පීඩනය කරන ආකාරය වෙනස් කිරීමට හැකිය. + බිතුපත, නිරූපකය, පසුබිම, මුද්‍රණය හෝ උඩැතිරිය වැනි අවසාන භාවිත අවස්ථාව සඳහා ප්‍රථමයෙන් මානයන් සකසන්න. + පිරිසැලසුම් කිහිපයක කප්පාදු කළ, විශාලනය කළ හෝ නැවත භාවිත කළ හැකි වයනය සඳහා විශාල ප්‍රමාණ භාවිත කරන්න. + පරිපාටිමය විස්තර මගින් සම්පීඩනය හැසිරෙන ආකාරය වෙනස් කළ හැකි නිසා විශාල ප්‍රතිදානයකට පෙර පරීක්ෂණ අනුවාද අපනයනය කරන්න. + වත්මන් බිතුපත් අපනයනය කරන්න + උපාංගයෙන් ලබා ගත හැකි නිවස, අගුළු සහ බිල්පත් ලබා ගන්න + ස්ථාපිත බිතුපත් සුරකින්න හෝ බෙදාගන්න + Wallpapers Export මඟින් පද්ධති Wallpaper API හරහා Android නිරාවරණය කරන බිතුපත් කියවයි. උපාංගය, ඇන්ඩ්‍රොයිඩ් අනුවාදය, අවසර සහ ගොඩනැගීමේ ප්‍රභේදය මත පදනම්ව, නිවස, අගුළු, හෝ ගොඩනඟන ලද බිතුපත් වෙන වෙනම ලබා ගත හැකි හෝ නැතිවී තිබිය හැක. + පද්ධතිය යෙදුමට කියවීමට ඉඩ දෙන බිතුපත් පැටවීමට Wallpapers Export විවෘත කරන්න. + ඔබට සුරැකීමට, පිටපත් කිරීමට හෝ බෙදා ගැනීමට අවශ්‍ය පවතින නිවස, අගුල, හෝ බිල්පත් ඇතුළත් කිරීම් තෝරන්න. + බිතුපතක් අස්ථානගත වී ඇත්නම් හෝ විශේෂාංගය නොමැති නම්, එය සාමාන්‍යයෙන් සංස්කරණ සැකසීමකට වඩා පද්ධතියක්, අවසරයක් හෝ ගොඩනැගීමේ ප්‍රභේද සීමාවකි. + Corners Animation Throttle + ලෙස වර්ණය පිටපත් කරන්න + HEX + නම + මෘදු හිසකෙස් සහ දාර හැසිරවීම සහිත වේගවත් පුද්ගල කටවුට් සඳහා පෝට්රේට් මැටිං ආකෘතිය. + Zero-DCE++ වක්‍ර ඇස්තමේන්තුව භාවිතයෙන් වේගවත් අඩු ආලෝකය වැඩි දියුණු කිරීම. + වැසි ඉරි සහ තෙත් කාලගුණ කෞතුක වස්තු අඩු කිරීම සඳහා ප්‍රතිස්ථාපන රූප ප්‍රතිසාධන ආකෘතිය. + ඛණ්ඩාංක ජාලයක් පුරෝකථනය කරන සහ වක්‍ර පිටු පැතලි ස්කෑන් එකකට නැවත සකස් කරන ලේඛන ඉවත් කිරීමේ ආකෘතිය. + චලන කාල සීමාව + යෙදුම් සජීවිකරණ වේගය පාලනය කරයි: 0 චලනය අක්‍රීය කරයි, 1 සාමාන්‍ය වේ, ඉහළ අගයන් සජීවිකරණ මන්දගාමී වේ + මෑත කාලීන මෙවලම් පෙන්වන්න + ප්‍රධාන තිරයේ මෑතකදී භාවිත කළ මෙවලම් වෙත ඉක්මන් ප්‍රවේශය සංදර්ශන කරන්න + භාවිත සංඛ්යා ලේඛන + යෙදුම විවෘත කිරීම්, මෙවලම් දියත් කිරීම් සහ ඔබ වැඩිපුරම භාවිතා කරන මෙවලම් ගවේෂණය කරන්න + යෙදුම විවෘත වේ + මෙවලම විවෘත වේ + අවසාන මෙවලම + සාර්ථක ඉතිරි කිරීම් + ක්‍රියාකාරකම් මාලාව + දත්ත සුරකින ලදී + ඉහළම ආකෘතිය + භාවිතා කරන මෙවලම් + %1$d විවෘත වේ • %2$s + තවමත් භාවිත සංඛ්‍යාලේඛන නොමැත + මෙවලම් කිහිපයක් විවෘත කරන්න, ඒවා ස්වයංක්‍රීයව මෙහි දිස්වනු ඇත + ඔබගේ භාවිත සංඛ්‍යාලේඛන ඔබගේ උපාංගයේ ස්ථානීයව පමණක් ගබඩා කෙරේ. ImageToolbox ඒවා භාවිතා කරන්නේ ඔබගේ පුද්ගලික ක්‍රියාකාරකම්, ප්‍රියතම මෙවලම් සහ සුරැකි දත්ත විදසුන් පෙන්වීමට පමණි + සංස්කාරකය සංස්කරණය ඡායාරූප පින්තූර retouch සකස් කිරීම + ප්‍රමාණය වෙනස් කරන්න පරිමාණ විභේදනය මානයන් පළල උස jpg jpeg png webp සම්පීඩනය + සම්පීඩන ප්‍රමාණයේ බර බයිට් mb kb ගුණාත්මක ප්‍රශස්තකරණය අඩු කරන්න + කප්පාදුව කැපූ දර්ශන අනුපාතය + පෙරහන් ආචරණය වර්ණ නිවැරදි කිරීම lut මාස්ක් සකස් කරන්න + තීන්ත බුරුසු පැන්සල් විවරණ සලකුණු කිරීම + cipher encrypt decrypt password රහස + පසුබිම් ඉවත් කරන්නා මකන්න කැපුම් විනිවිද පෙනෙන ඇල්ෆා ඉවත් කරන්න + පෙරදසුන් නරඹන්නන්ගේ ගැලරිය පරීක්ෂාව විවෘතයි + stitch merge combine panorama දිගු තිර රුවක් + url වෙබ් බාගත අන්තර්ජාල සබැඳි රූපය + picker eyedropper සාම්පල hex rgb වර්ණය + exif පාරදත්ත පුද්ගලිකත්වය තීරු පිරිසිදු gps ස්ථානය ඉවත් කරන්න + සීමාව ප්‍රතිප්‍රමාණය උපරිම මිනි මෙගාපික්සල් මාන කලම්ප + ocr පෙළ හඳුනාගැනීමේ සාරය ස්කෑන් කිරීම + අනුක්‍රමණ පසුබිම් වර්ණ දැලක් + දිය සලකුණ ලාංඡනය පෙළ මුද්දර ආවරණයක් + gif සජීවිකරණ සජීවිකරණ පරිවර්තන රාමු + apng සජීවිකරණ සජීවිකරණ png පරිවර්තනය රාමු + jxl jpeg xl jpg රූප ආකෘතිය පරිවර්තනය කරන්න + ආකෘතිය පරිවර්තනය jpg jpeg png webp heic avif jxl bmp + ලේඛන ස්කෑනරය ස්කෑන් කඩදාසි ඉදිරිදර්ශන බෝගය + වර්ණ පරිවර්තනය hex rgb hsl hsv cmyk රසායනාගාරය + කොලෙජ් ජාල සැකැස්ම ඒකාබද්ධ කරන්න + checksum hash md5 sha sha1 sha256 සත්‍යාපනය කරන්න + ශ්‍රව්‍ය ආවරණ ඇල්බම කලා සංගීත පාරදත්ත + ascii පෙළ කලා චරිත + pdf පිටු අනුපිළිවෙල නැවත සකස් කරන්න + pdf ocr පෙළ සෙවිය හැකි උපුටා ගැනීම හඳුනා ගනී + pdf මුරපද සංකේතාංකන අගුල ආරක්ෂා කරයි + pdf අලුත්වැඩියාව නැවත යථා තත්ත්වයට පත් කිරීම කැඩී ඇත + pdf උපුටා ගැනීම පින්තූර පින්තූර + මධ්යස්ථ ප්රභේදය + දෝෂයකි + සෙවනැල්ල හෙළන්න + දත් උස + තිරස් දත් පරාසය + සිරස් දත් පරාසය + ඉහළ කෙළවර + දකුණු කෙළවර + පහළ කෙළවර + වම් කෙළවර + ඉරා දැමූ දාරය + palette colours swatches extract dominant + පසු වෙනස පෙර සසඳන්න + pdf ලේඛන පිටු මෙවලම් + zip archive compress pack ගොනු අසුරන්න + svg vector trace convert xml + qr තීරු කේත ස්කෑනර් ස්කෑන් + සාමාන්‍ය මධ්‍ය තාරකා ඡායාරූපකරණය උඩින් තැබීම + split tiles grid පෙත්ත කොටස් + webp සජීවිකරණ රූප ආකෘතිය පරිවර්තනය කරන්න + ශබ්ද උත්පාදක අහඹු වයනය ධාන්ය + මාර්ක්අප් ස්තර විවරණ උඩැතිරි සංස්කරණය + base64 දත්ත විකේතනය කිරීම uri + දැල් අනුක්‍රමණ පසුබිම + exif පාරදත්ත සංස්කරණය gps දිනය කැමරාව + බෙදුණු ජාලක කැබලි ටයිල් කපා + බිතුපත් අපනයන පසුබිම + AI ml ස්නායු වැඩි දියුණු කිරීම ඉහළ මට්ටමේ කොටස + වර්ණ පුස්තකාල ද්‍රව්‍ය තලය නම් වර්ණ + shader glsl fragment effect studio + pdf merge combine join + pdf වෙනම පිටු බෙදන්න + pdf භ්‍රමණය පිටු දිශානතිය + pdf පිටු අංක පේජිනේෂන් + pdf දිය සලකුණ මුද්දර ලාංඡන පෙළ + pdf අත්සන් අත්සන් ඇඳීම + pdf unlock password decrypt remove protection + pdf සම්පීඩනය ප්‍රමාණය ප්‍රශස්ත කිරීම අඩු කරන්න + pdf අළු පරිමාණ කළු සුදු ඒකවර්ණ + pdf පාරදත්ත ගුණාංග exif කර්තෘ මාතෘකාව + pdf පිටු මකා දමන්න + pdf බෝග සීමා මායිම් + pdf සමතලා විවරණ ස්තර සාදයි + pdf zip සංරක්ෂිත සම්පීඩනය + pdf මුද්‍රණ මුද්‍රණ යන්ත්‍රය + pdf පෙරදසුන් නරඹන්නා කියවා ඇත + පින්තූර pdf බවට පරිවර්තනය jpg jpeg png ලේඛනය + pdf සිට පින්තූර පිටු වෙත jpg png අපනයනය කරන්න + pdf annotations comments remove clean + භාවිත සංඛ්‍යාලේඛන යළි පිහිටුවන්න + මෙවලම විවෘත වේ, සංඛ්‍යාලේඛන සුරකින්න, ස්ට්‍රීක්, සුරකින ලද දත්ත, සහ ඉහළ ආකෘතිය යළි පිහිටුවනු ඇත. යෙදුම් විවෘත කිරීම් නොවෙනස්ව පවතිනු ඇත. + ශ්රව්ය උපකරණ + ගොනුව + පැතිකඩ අපනයනය කරන්න + වත්මන් පැතිකඩ සුරකින්න + අපනයන පැතිකඩ මකන්න + ඔබට \\"%1$s\\" නිර්යාත පැතිකඩ මැකීමට අවශ්‍යද? + මායිම් ඇඳීම + ඇඳීම සහ මැකීමේ කැන්වසය වටා තුනී මායිමක් පෙන්වන්න + රැලි සහිත + මෘදු රැලි සහිත දාරයක් සහිත වටකුරු UI මූලද්‍රව්‍ය + Scoop + නොච් + වටකුරු කොන් ඇතුලට කැටයම් කර ඇත + ද්විත්ව කප්පාදුවක් සහිත පියවර සහිත කොන් + ස්ථාන දරන්නා + දිගුවකින් තොරව මුල් ගොනු නාමය ඇතුළත් කිරීමට {filename} භාවිතා කරන්න + දැල්ල + මූලික මුදල + මුද්ද මුදල + කිරණ ප්රමාණය + වළල්ලේ පළල + ඉදිරිදර්ශනය විකෘති කරන්න + ජාවා පෙනුම සහ හැඟීම + ෂියර් + X කෝණය + සහ කෝණය + ප්‍රතිදානය ප්‍රතිප්‍රමාණ කරන්න + ජල බිංදුව + තරංග ආයාමය + අදියර + උසස් සමත් + වර්ණ මාස්ක් + Chrome + විසුරුවා හරින්න + මෘදු බව + ප්රතිපෝෂණ + කාච බොඳවීම + අඩු ආදානය + ඉහළ ආදානය + අඩු ප්රතිදානය + ඉහළ ප්රතිදානය + සැහැල්ලු බලපෑම් + ගැටිති උස + ගැටිති මෘදු බව + Ripple + X විස්තාරය + Y විස්තාරය + X තරංග ආයාමය + Y තරංග ආයාමය + අනුවර්තන බොඳවීම + Texture Generation + විවිධ ක්‍රියා පටිපාටි වයනය ජනනය කර ඒවා රූප ලෙස සුරකින්න + වයනය වර්ගය + පක්ෂග්රාහී + කාලය + බැබළෙන්න + විසුරුම + සාම්පල + කෝණ සංගුණකය + අනුක්‍රමණ සංගුණකය + ජාල වර්ගය + දුරස්ථ බලය + පරිමාණ Y + නොපැහැදිලි බව + මූලික වර්ගය + කැළඹිලි සාධකය + පරිමාණ කිරීම + පුනරාවර්තන + වළලු + බුරුසු ලෝහ + කෝස්ටික් + සෛලීය + පිරික්සුම් පුවරුව + fBm + කිරිගරුඬ + ප්ලාස්මා + ඇඳ ඇතිරිලි + ලී + අහඹු + චතුරස්රය + ෂඩාස්රාකාර + අෂ්ටාංගික + ත්රිකෝණාකාර + රිජ්ඩ් + VL ශබ්දය + SC ශබ්දය + මෑත + මෑතදී භාවිත කළ පෙරහන් තවමත් නැත + වයනය උත්පාදක බ්‍රෂ් කරන ලද ලෝහ කෝස්ටික් සෙලියුලර් චෙක්බෝඩ් කිරිගරුඬ ප්ලාස්මා ක්විල්ට් ලී ගඩොල් කැමෆ්ලැජ් සෛල වලාකුළු ඉරිතැලි රෙදි පත්‍ර පැණි වද අයිස් ලාවා නිහාරිකාව කඩදාසි මලකඩ වැලි දුම් ගල් භූමි භූ විෂමතාව ජල රැල්ල + ගඩොල් + වසං කිරීම + සෛලය + වලාකුළු + ඉරිතැලීම + රෙදි + ශාක පත්ර + මී පැණි + අයිස් + ලාවා + නිහාරිකාව + කඩදාසි + මලකඩ + වැලි + දුම් + ගල් + භූමිය + භූ විෂමතාව + ජල රැල්ල + උසස් ලී + මෝටාර් පළල + අක්රමිකතා + Bevel + රළු බව + පළමු එළිපත්ත + දෙවන එළිපත්ත + තුන්වන එළිපත්ත + දාර මෘදු බව + මායිම් පළල + ආවරණය + විස්තර + ගැඹුර + අතු බෙදීම + තිරස් නූල් + සිරස් නූල් + අවුල් + නහර + ආලෝකකරණය + ඉරිතැලීම් පළල + ෆ්රොස්ට් + ප්රවාහය + කබොල + වලාකුළු ඝනත්වය + තරු + තන්තු ඝනත්වය + තන්තු ශක්තිය + පැල්ලම් + විඛාදනය + වලවල් දැමීම + පෙති + ඩූන් සංඛ්යාතය + සුළං කෝණය + රැලි + විස්ප්ස් + ශිරා පරිමාණය + ජල මට්ටම + කඳු මට්ටම + ඛාදනය + හිම මට්ටම + රේඛා ගණන + රේඛා ඝණකම + සෙවන + සිදුරු වර්ණය + මෝටාර් වර්ණය + තද ගඩොල් වර්ණය + ගඩොල් වර්ණය + වර්ණ උද්දීපනය කරන්න + අඳුරු වර්ණය + වනාන්තර වර්ණය + පෘථිවි වර්ණය + වැලි වර්ණය + පසුබිම් වර්ණය + සෛල වර්ණය + දාර වර්ණය + අහස් වර්ණය + සෙවනැලි වර්ණය + සැහැල්ලු වර්ණය + මතුපිට වර්ණය + විවිධ වර්ණ + ඉරිතැලීම් වර්ණය + විකෘති වර්ණය + Weft වර්ණය + තද කොළ වර්ණය + කොළ පාට + මායිම් වර්ණය + මී පැණි වර්ණය + ගැඹුරු වර්ණය + අයිස් වර්ණය + තුහින වර්ණය + කබොල වර්ණය + සෝදන වර්ණය + දිදුලන වර්ණය + අවකාශයේ වර්ණය + වයලට් වර්ණය + නිල් වර්ණය + මූලික වර්ණය + තන්තු වර්ණය + පැල්ලම් වර්ණය + ලෝහ වර්ණය + අඳුරු මලකඩ වර්ණය + මලකඩ වර්ණය + තැඹිලි පාට + දුම් වර්ණය + නහර වර්ණය + පහතරට වර්ණය + ජල වර්ණය + පාෂාණ වර්ණය + හිම වර්ණය + අඩු වර්ණය + ඉහළ වර්ණය + රේඛා වර්ණය + නොගැඹුරු වර්ණය + තණකොළ + අපිරිසිදු + සම් + කොන්ක්රීට් + ඇස්ෆල්ට් + පාසි + ගිනි + අරෝරා + තෙල් ස්ලික් + ජල වර්ණ + වියුක්ත ප්රවාහය + ඔපල් + දමස්කස් වානේ + අකුණු + වෙල්වට් + තීන්ත Marbling + හොලෝග්රැෆික් තීරු + ෛජව දීප්තිය + කොස්මික් වෝටෙක්ස් + ලාවා ලාම්පුව + ඉවෙන්ට් ක්ෂිතිජය + ෆ්රැක්ටල් බ්ලූම් + වර්ණ උමග + Eclipse Corona + අමුතු ආකර්ශකය + Ferrofluid ඔටුන්න + සුපර්නෝවා + අයිරිස් + මොනර පිහාටුව + Nautilus Shell + මුදු ග්රහලෝකය + බ්ලේඩ් ඝනත්වය + තල දිග + සුළඟ + Patchiness + පොකුරු + තෙතමනය + ගල් කැට + රැලි + සිදුරු + එකතු කරන්න + ඉරිතැලීම් + තාර + අඳින්න + පැල්ලම් + කෙඳි + ගිනිදැල් වාර ගණන + දුම් + තීව්රතාව + රිබන් + සංගීත කණ්ඩායම් + Iridescence + අන්ධකාරය + බ්ලූම්ස් + වර්ණකය + දාර + කඩදාසි + විසරණය + සමමිතිය + තියුණු බව + වර්ණ සෙල්ලම් + කිරිබත් බව + ස්ථර + නැමීම + පෝලන්ත + ශාඛා + දිශාව + ෂීන් + නැමීම් + පිහාටු + තීන්ත ශේෂය + වර්ණාවලිය + රැලි වැටෙනවා + විවර්තනය + ආයුධ + Twist + මූලික දීප්තිය + බ්ලබ්ස් + තැටිය ඇලවීම + ක්ෂිතිජ ප්රමාණය + තැටියේ පළල + කාච + පෙති + Curl + ෆිලීග්‍රී + මුහුණුවර + වක්රය + චන්ද්ර ප්රමාණය + කොරෝනා ප්‍රමාණය + කිරණ + දියමන්ති මුද්ද + lobes + කක්ෂ ඝනත්වය + ඝනකම + කරල් + කරල් දිග + සිරුරේ ප්රමාණය + ලෝහමය + කම්පන අරය + ෂෙල් පළල + එළියට විසි කළා + ශිෂ්ය ප්රමාණය + අයිරිස් ප්රමාණය + වර්ණ විචලනය + Catchlight + අක්ෂි ප්රමාණය + බාර්බ් ඝනත්වය + හැරෙනවා + කුටි + විවෘත කිරීම + කඳු වැටි + මුතු ඇටය + ග්රහලෝක ප්රමාණය + මුදු ඇලවීම + වළල්ලේ පළල + වායුගෝලය + අපිරිසිදු වර්ණය + තද තණකොළ වර්ණය + තණකොළ වර්ණය + ඉඟි වර්ණය + අඳුරු පෘථිවි වර්ණය + වියළි වර්ණය + ගල් කැට වර්ණය + සම් වර්ණය + කොන්ක්රීට් වර්ණය + තාර වර්ණය + ඇස්ෆල්ට් වර්ණය + ගල් වර්ණය + දූවිලි වර්ණය + පාංශු වර්ණය + අඳුරු පාසි වර්ණය + පාසි වර්ණය + රතු පාටයි + මූලික වර්ණය + කොළ පාට + සයන් වර්ණය + මැජෙන්ටා වර්ණය + රන් වර්ණය + කඩදාසි වර්ණය + වර්ණක වර්ණය + ද්විතියික වර්ණය + පළමු වර්ණය + දෙවන වර්ණය + රෝස පාට + තද වානේ වර්ණය + වානේ වර්ණය + සැහැල්ලු වානේ වර්ණය + ඔක්සයිඩ් වර්ණය + හැලෝ වර්ණය + බෝල්ට් වර්ණය + වෙල්වට් වර්ණය + දීප්තිමත් වර්ණය + නිල් තීන්ත වර්ණය + රතු තීන්ත වර්ණය + තද තීන්ත වර්ණය + රිදී වර්ණය + කහ වර්ණය + පටක වර්ණය + තැටි වර්ණය + උණුසුම් වර්ණය + කාච වර්ණය + පිටත වර්ණය + අභ්යන්තර වර්ණය + කොරෝනා වර්ණය + සීතල වර්ණය + උණුසුම් වර්ණය + වලාකුළු වර්ණය + ගිනිදැල් වර්ණය + පිහාටු වර්ණය + ෂෙල් වර්ණය + මුතු වර්ණය + ග්රහලෝක වර්ණය + මුදු වර්ණය + සුරැකීමෙන් පසු මුල් ගොනු මකන්න + සාර්ථකව සකසන ලද මූලාශ්‍ර ගොනු පමණක් මකනු ලැබේ + මුල් ගොනු මකා ඇත: %1$d + මුල් ගොනු මැකීමට අසමත් විය: %1$d + මුල් ගොනු මකා ඇත: %1$d, අසාර්ථක විය: %2$d + පත්ර අභිනයන් + පුළුල් කළ විකල්ප පත්‍ර ඇදගෙන යාමට ඉඩ දෙන්න + ක්‍රෝමා උප නියැදීම + ටිකක් ගැඹුර + පාඩු නැති + %1$d-බිට් + කණ්ඩායම නැවත නම් කරන්න + ගොනු නාම රටා භාවිතයෙන් ගොනු කිහිපයක් නැවත නම් කරන්න + batch rename files filename pattern අනුපිළිවෙල දින දිගුව + නැවත නම් කිරීමට ගොනු තෝරන්න + ගොනු නාම රටාව + නැවත නම් කරන්න + දින මූලාශ්රය + EXIF දිනය ගන්නා ලදී + ගොනුව වෙනස් කළ දිනය + ගොනුව නිර්මාණය කළ දිනය + වත්මන් දිනය + අත්පොත දිනය + අත්පොත දිනය + අතින් කාලය + තෝරාගත් දිනය සමහර ගොනු සඳහා නොමැත. වත්මන් දිනය ඔවුන් සඳහා භාවිතා කරනු ඇත. + ගොනු නාම රටාවක් ඇතුළත් කරන්න + රටාව වලංගු නොවන හෝ ඉතා දිගු ගොනු නාමයක් නිෂ්පාදනය කරයි + රටාව අනුපිටපත් ගොනු නාම නිෂ්පාදනය කරයි + සියලුම ගොනු නාම දැනටමත් රටාවට ගැලපේ + මෙම රටාව කණ්ඩායම් නැවත නම් කිරීම සඳහා නොමැති ටෝකන භාවිතා කරයි + නම එලෙසම පවතිනු ඇත + ගොනු සාර්ථකව නැවත නම් කරන ලදී + සමහර ගොනු නැවත නම් කළ නොහැක + අවසර ලැබුණේ නැහැ + මුල් ගොනු නාමය දිගුවකින් තොරව භාවිත කරයි, මූලාශ්‍ර හඳුනාගැනීම නොවෙනස්ව තබා ගැනීමට ඔබට උදවු කරයි. එසේම \\o{start:end} සමඟ පෙති කැපීම, \\o{t} සමඟ අක්ෂර පරිවර්තනය සහ \\o{s/old/new/} සමඟ ප්‍රතිස්ථාපනය කිරීමට සහය දක්වයි. + ස්වයංක්‍රීය වර්ධක කවුන්ටරය. අභිරුචි හැඩතල ගැන්වීම සඳහා සහය දක්වයි: \\c{padding} (උදා. \\c{3} -&gt; 001), \\c{start:step} හෝ \\c{start:step:padding}. + මුල් ගොනුව තිබූ මව් ෆෝල්ඩරයේ නම. + මුල් ගොනුවේ විශාලත්වය. සහායක ඒකක: \\z{b}, \\z{kb}, \\z{mb} හෝ මිනිසුන්ට කියවිය හැකි ආකෘතිය සඳහා \\z. + ගොනු නාමයේ සුවිශේෂත්වය සහතික කිරීම සඳහා අහඹු විශ්වීය අනන්‍ය හඳුනාගැනීමක් (UUID) ජනනය කරයි. + ගොනු නැවත නම් කිරීම පසුගමනය කළ නොහැක. ඉදිරියට යාමට පෙර නව නම් නිවැරදි බව තහවුරු කර ගන්න. + නැවත නම් කිරීම තහවුරු කරන්න + පැති මෙනු සැකසුම් + මෙනු පරිමාණය + ඇල්ෆා මෙනුව + ස්වයංක්‍රීය සුදු ශේෂය + කපා හැරීම + සැඟවුණු ජල සලකුණු සඳහා පරීක්ෂා කිරීම + ගබඩා කිරීම + හැඹිලි සීමාව + මෙම ප්‍රමාණයෙන් ඔබ්බට වැඩෙන විට හැඹිලිය ස්වයංක්‍රීයව හිස් කරන්න + පිරිසිදු කිරීමේ පරතරය + හැඹිලිය ඉවත් කළ යුතුද යන්න පරීක්ෂා කිරීමට කොපමණ වාරයක් + යෙදුම දියත් කිරීමේදී + දින 1 යි + සති 1 යි + මාස 1 යි + තෝරාගත් සීමාව සහ පරතරය අනුව යෙදුම් හැඹිලිය ස්වයංක්‍රීයව හිස් කරන්න + චංචල භෝග ප්රදේශය + එය තුළට ඇදගෙන යාමෙන් සම්පූර්ණ බෝග ප්‍රදේශය චලනය කිරීමට ඉඩ සලසයි + GIF ඒකාබද්ධ කරන්න + GIF ක්ලිප් කිහිපයක් එක් සජීවිකරණයකට ඒකාබද්ධ කරන්න + GIF ක්ලිප් ඇණවුම + ආපසු හැරවීම + ප්‍රතිලෝම ලෙස ක්‍රීඩා කරන්න + බූමරන්ග් + ඉදිරියට සහ පසුව පසුපසට සෙල්ලම් කරන්න + රාමු ප්රමාණය සාමාන්ය කරන්න + විශාලතම ක්ලිප් එකට ගැලපෙන පරිදි පරිමාණ රාමු; ඔවුන්ගේ මුල් පරිමාණය ආරක්ෂා කර ගැනීම සඳහා අක්රිය කරන්න + ක්ලිප් අතර ප්‍රමාදය (ms) + ෆෝල්ඩරය + ෆෝල්ඩරයකින් සහ එහි උප ෆෝල්ඩරවලින් සියලුම පින්තූර තෝරන්න + අනුපිටපත් සොයන්නා + නිවැරදි පිටපත් සහ දෘශ්‍යමය වශයෙන් සමාන රූප සොයන්න + අනුපිටපත් සොයන්නා සමාන පින්තූර නියම පිටපත් ඡායාරූප පිරිසිදු කිරීමේ ගබඩාව dHash SHA-256 + අනුපිටපත් සොයා ගැනීමට පින්තූර තෝරන්න + සමානතා සංවේදීතාව + නියම පිටපත් + සමාන රූප + සියලුම නිවැරදි අනුපිටපත් තෝරන්න + නිර්දේශිත හැර සියල්ල තෝරන්න + අනුපිටපත් හමු නොවීය + තවත් පින්තූර එකතු කිරීමට හෝ සමානතා සංවේදීතාව වැඩි කිරීමට උත්සාහ කරන්න + %1$d පින්තුර(ය) කියවීමට නොහැකි විය + %1$s • %2$s නැවත ලබාගත හැක + %1$d කණ්ඩායම් • %2$s නැවත ලබාගත හැකි + %1$d තෝරාගත් • %2$s + මෙය ආපසු හැරවිය නොහැක. පද්ධති සංවාදයක මකාදැමීම තහවුරු කිරීමට Android ඔබෙන් ඉල්ලා සිටිය හැක. + හමු වුණේ නැහැ + ආරම්භයට ගෙන යන්න + අවසානය දක්වා ගමන් කරන්න + \ No newline at end of file diff --git a/core/resources/src/main/res/values-sk/strings.xml b/core/resources/src/main/res/values-sk/strings.xml new file mode 100644 index 0000000..cb58324 --- /dev/null +++ b/core/resources/src/main/res/values-sk/strings.xml @@ -0,0 +1,3741 @@ + + + + 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 + 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 + 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 farba + %1$s farieb + %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. + Ší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. + 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 + Content Aware Distortion + Nedostatok pamäte na dokončenie tejto akcie. Skúste použiť menší obrázok, zatvorte ostatné aplikácie alebo reštartujte aplikáciu. + Paralelní pracovníci + Tieto obrázky sa neuložili, pretože skonvertované súbory by boli väčšie ako originály. Pred odstránením originálnych obrázkov skontrolujte tento zoznam. + Preskočené súbory: %1$s + Denníky aplikácií + Pozrite si denníky aplikácie, aby ste zistili problémy + Obľúbené v zoskupených nástrojoch + Pridá obľúbené položky ako kartu, keď sú nástroje zoskupené podľa typu + Zobraziť obľúbené ako posledné + Presunie kartu obľúbených nástrojov na koniec + Horizontálne rozostupy + Vertikálne rozostupy + Spätná energia + Namiesto dopredného energetického algoritmu použite jednoduchú energetickú mapu gradientu + Odstráňte maskovanú oblasť + Švy budú prechádzať cez maskovanú oblasť, pričom odstránia iba vybranú oblasť namiesto jej ochrany + VHS NTSC + Pokročilé nastavenia NTSC + Extra analógové ladenie pre silnejšie VHS a vysielacie artefakty + Chroma krvácanie + Opotrebenie pásky + Sledovanie + Luma náter + Zvonenie + Sneh + Použiť pole + Typ filtra + Vstupný luma filter + Vstupná farebná dolná priepustnosť + Chroma demodulácia + Fázový posun + Fázový posun + Prepínanie hlavy + Výška prepínania hlavy + Offset prepínania hlavy + Prepínanie hlavy + Stredová pozícia + Jitter v strednej čiare + Sledovanie výšky hluku + Sledovacia vlna + Sledovanie snehu + Sledovanie anizotropie snehu + Hluk sledovania + Sledovanie intenzity hluku + Zložený hluk + Frekvencia kompozitného šumu + Intenzita kompozitného hluku + Detail kompozitného šumu + Frekvencia zvonenia + Sila zvonenia + Luma hluk + Frekvencia šumu Luma + Luma intenzita hluku + Detail šumu Luma + Chroma šum + Frekvencia chromatického šumu + Intenzita chromatického šumu + Detail chromatického šumu + Intenzita snehu + Anizotropia snehu + Chroma fázový šum + Chyba chromatickej fázy + Horizontálne oneskorenie chromatickosti + Vertikálne oneskorenie chromatickosti + Rýchlosť pásky VHS + Strata sýtosti VHS + VHS zostrenie intenzity + VHS zaostrenie frekvencie + Intenzita okrajovej vlny VHS + Rýchlosť okrajovej vlny VHS + Frekvencia okrajových vĺn VHS + Detail okrajovej vlny VHS + Výstupná farebná dolná priepustnosť + Mierka horizontálna + Vertikálna mierka + Mierkový faktor X + Mierkový faktor Y + Detekuje bežné vodoznaky obrázkov a vyfarbuje ich pomocou LaMa. Automaticky stiahne modely detekcie a maľovania + Deuteranopia + Rozbaliť obrázok + Odstraňovač pozadia založený na segmentácii objektov pomocou YOLO v11 Segmentation + Zobraziť uhol čiary + Počas kreslenia zobrazuje aktuálne otočenie čiary v stupňoch + Animované emotikony + Zobraziť dostupné emotikony ako animácie + Uložiť do pôvodného priečinka + Uložte nové súbory vedľa pôvodného súboru namiesto vybratého priečinka + Vodoznak vo formáte PDF + Nedostatok pamäte + Obraz je pravdepodobne príliš veľký na spracovanie na tomto zariadení, alebo sa v systéme minula dostupná pamäť. Skúste znížiť rozlíšenie obrázka, zatvorte ostatné aplikácie alebo vyberte menší súbor. + Ponuka ladenia + Ponuka na testovanie funkcií aplikácie, toto nie je určené na zobrazenie v produkčnom vydaní + Poskytovateľ + PaddleOCR vyžaduje na vašom zariadení ďalšie modely ONNX. Chcete stiahnuť %1$s údaje? + Univerzálny + kórejský + latinčina + východoslovanský + thajčina + grécky + angličtina + azbuka + arabčina + dévanágarí + tamilčina + telugčina + Shader + Prednastavený shader + Nie je vybratý žiadny shader + Shader Studio + Vytvárajte, upravujte, overujte, importujte a exportujte vlastné shadery fragmentov + Uložené shadery + Otvárajte uložené shadery, duplikujte, exportujte, zdieľajte alebo odstráňte + Nový shader + Shader súbor + .itshader JSON + %1$d parametrov + Shader zdroj + Napíšte iba telo funkcie void main(). Použite textureCoordinate pre UV súradnice a zadajteImageTexture ako zdrojový vzorkovač. + Funkcie + Voliteľné pomocné funkcie, konštanty a štruktúry vložené pred void main(). Uniformy sú generované z parametrov. + Tu pridajte uniformy, keď shader potrebuje upraviteľné hodnoty. + Resetovať shader + Aktuálny koncept shadera bude vymazaný + Neplatný shader + Nepodporovaná verzia shadera %1$d. Podporovaná verzia: %2$d. + Názov tieňovača nesmie byť prázdny. + Zdroj tieňovania nesmie byť prázdny. + Shader zdroj obsahuje nepodporované znaky: %1$s. Používajte len zdroj ASCII GLSL. + Parameter \\\"%1$s\\\" je deklarovaný viac ako raz. + Názvy parametrov nesmú byť prázdne. + Parameter \\\"%1$s\\\" používa vyhradený názov GPUImage. + Parameter \\\"%1$s\\\" musí byť platným identifikátorom GLSL. + Parameter \\\"%1$s\\\" má %2$s typ hodnoty \\\"%3$s\\\", očakávaný \\\"%4$s\\\". + Parameter \\\"%1$s\\\" nemôže definovať minimálnu alebo maximálnu hodnotu boolov. + Parameter \\\"%1$s\\\" má min. väčší ako max. + Predvolený parameter \\\"%1$s\\\" je nižší ako min. + Predvolený parameter \\\"%1$s\\\" je väčší ako max. + Shader musí deklarovať \\\"uniform sampler2D %1$s;\\\". + Shader musí deklarovať \\\"varying vec2 %1$s;\\\". + Shader musí definovať \\\"void main()\\\". + Shader musí zapísať farbu do gl_FragColor. + ShaderToy mainImage shadery nie sú podporované. Použite neplatnú zmluvu main() GPUImage. + Animovaná uniforma ShaderToy \\\"iTime\\\" nie je podporovaná. + Animovaná uniforma ShaderToy \\\"iFrame\\\" nie je podporovaná. + Uniforma ShaderToy \\\"iResolution\\\" nie je podporovaná. + Textúry ShaderToy iChannel nie sú podporované. + Vonkajšie textúry nie sú podporované. + Externé rozšírenia textúr nie sú podporované. + Parametre shadera Libretro nie sú podporované. + Podporovaná je iba jedna vstupná textúra. Odstrániť \\\"uniform %1$s %2$s;\\\". + Parameter \\\"%1$s\\\" musí byť deklarovaný ako \\\"uniform %2$s %1$s;\\\". + Parameter \\\"%1$s\\\" je deklarovaný ako \\\"jednotný %2$s %1$s;\\\", očakávaný \\\"jednotný %3$s %1$s;\\\". + Shader súbor je prázdny. + Shader súbor musí byť objekt .itshader JSON s poliami verzie, názvu a shadera. + Shader súbor nie je platný JSON alebo sa nezhoduje s formátom .itshader. + Pole \\\"%1$s\\\" je povinné. + Vyžaduje sa %1$s. + %1$s \\\"%2$s\\\" nie je podporované. Podporované typy: %3$s. + Pre parametre \\\"%2$s\\\" sa vyžaduje %1$s. + %1$s musí byť konečné číslo. + %1$s musí byť celé číslo. + %1$s musí byť pravdivé alebo nepravdivé. + %1$s musí byť farebný reťazec, pole RGB/RGBA alebo farebný objekt. + %1$s musí používať formát #RRGGBB alebo #RRGGBBAA. + %1$s musí obsahovať platné hexadecimálne farebné kanály. + %1$s musí obsahovať 3 alebo 4 farebné kanály. + %1$s musí byť medzi 0 a 255. + %1$s musí byť pole dvoch čísel alebo objekt s x a y. + %1$s musí obsahovať presne 2 čísla. + Duplicitné + Vždy vymažte EXIF + Odstráňte EXIF ​​údaje obrázka pri uložení, aj keď nástroj vyžaduje uchovanie metadát + Pomocník a tipy + %1$d návodov + Otvorte nástroj + Naučte sa hlavné nástroje, možnosti exportu, toky PDF, nástroje pre farby a opravy bežných problémov + Začíname + Rýchlejšie si vyberte nástroje, importujte súbory, uložte výsledky a znova použite nastavenia + Úprava obrázkov + Zmena veľkosti, orezanie, filtrovanie, vymazanie pozadia, kreslenie a vodoznak obrázkov + Súbory a metadáta + Konvertujte formáty, porovnávajte výstupy, chráňte súkromie a kontrolujte názvy súborov + PDF a dokumenty + Vytvárajte súbory PDF, skenujte dokumenty, stránky OCR a pripravujte súbory na zdieľanie + Text, QR kód a dáta + Rozpoznávajte text, skenujte kódy, kódujte Base64, kontrolujte hodnoty hash a balíky súborov + Farebné nástroje + Vyberte si farby, vytvorte palety, preskúmajte knižnice farieb a vytvorte prechody + Kreatívne nástroje + Generujte SVG, ASCII art, šumové textúry, shadery a experimentálne vizuály + Riešenie problémov + Opravte ťažké súbory, transparentnosť, zdieľanie, importy a problémy s prehľadmi + Vyberte si správny nástroj + Ak chcete začať na správnom mieste, použite kategórie, vyhľadávanie, obľúbené položky a akcie zdieľania + Začnite od najlepšieho vstupného bodu + Image Toolbox má veľa zameraných nástrojov a mnohé z nich sa na prvý pohľad prekrývajú. Začnite od úlohy, ktorú chcete dokončiť, a potom vyberte nástroj, ktorý riadi najdôležitejšiu časť výsledku: veľkosť, formát, metadáta, text, strany PDF, farby alebo rozloženie. + Vyhľadávanie použite, keď poznáte názov akcie, ako je zmena veľkosti, PDF, EXIF, OCR, QR alebo farba. + Otvorte kategóriu, keď poznáte iba typ úlohy, a potom pripnite časté nástroje medzi obľúbené. + Keď iná aplikácia zdieľa obrázok do Image Toolbox, vyberte nástroj z toku zdieľania hárka. + Importujte, ukladajte a zdieľajte výsledky + Pochopte obvyklý postup od výberu vstupu po export upraveného súboru + Postupujte podľa bežného postupu úprav + Väčšina nástrojov sa riadi rovnakým rytmom: vyberte vstup, upravte možnosti, ukážte a potom uložte alebo zdieľajte. Dôležitým detailom je, že uložením sa vytvorí výstupný súbor, zatiaľ čo zdieľanie je zvyčajne najlepšie na rýchle odovzdanie inej aplikácii. + Vyberte jeden súbor pre nástroje s jedným obrázkom alebo niekoľko súborov pre dávkové nástroje, keď to výber umožňuje. + Pred uložením skontrolujte ovládacie prvky náhľadu a výstupu, najmä možnosti formátu, kvality a názvu súboru. + Použite zdieľanie na rýchle odovzdanie alebo uložte, keď potrebujete trvalú kópiu vo vybranom priečinku. + Pracujte s mnohými obrázkami naraz + Dávkové nástroje sú najlepšie na opakované úlohy zmeny veľkosti, konverzie, kompresie a pomenovania + Pripravte predvídateľnú dávku + Dávkové spracovanie je najrýchlejšie, keď by sa každý výstup mal riadiť rovnakými pravidlami. Je to tiež najjednoduchšie miesto, kde môžete urobiť veľkú chybu, takže pred spustením dlhého exportu vyberte názov, kvalitu, metadáta a správanie priečinka. + Vyberte všetky súvisiace obrázky spolu a zachovajte poradie, ak výstup závisí od sekvencie. + Pred spustením exportu nastavte pravidlá zmeny veľkosti, formátu, kvality, metadát a názvu súboru. + Najprv spustite malú testovaciu dávku, keď sú zdrojové súbory veľké alebo keď je pre vás cieľový formát nový. + Rýchla jednotlivá úprava + Použite all-in-one editor, keď potrebujete niekoľko jednoduchých zmien na jednom obrázku + Dokončite jeden obrázok bez preskakovania nástroja + Jednoduchá úprava je užitočná, keď obrázok vyžaduje malý reťazec zmien namiesto jednej špecializovanej operácie. Zvyčajne je rýchlejší na rýchle čistenie, zobrazenie ukážky a export jednej konečnej kópie bez vytvárania dávkového pracovného postupu. + Otvorte položku Single Edit pre jeden obrázok, ktorý vyžaduje bežné úpravy, označenie, orezanie, otočenie alebo exportovanie. + Úpravy aplikujte v poradí, v ktorom sa najskôr zmení najmenej pixelov, napríklad orezanie pred filtrami a filtre pred konečnou kompresiou. + Ak potrebujete dávkové spracovanie, presné ciele veľkosti súboru, výstup PDF, OCR alebo čistenie metadát, použite namiesto toho špecializovaný nástroj. + Použite predvoľby a nastavenia + Uložte si opakované voľby a vylaďte aplikáciu pre svoj preferovaný pracovný postup + Vyhnite sa opakovaniu rovnakého nastavenia + Predvoľby a nastavenia sú užitočné, keď často exportujete rovnaký druh súboru. Dobrá predvoľba premení opakovaný manuálny kontrolný zoznam na jedno klepnutie, zatiaľ čo globálne nastavenia definujú predvolené hodnoty, s ktorými začínajú nové nástroje. + Vytvorte predvoľby pre bežné výstupné veľkosti, formáty, hodnoty kvality alebo vzory názvov. + Skontrolujte nastavenia, kde nájdete predvolený formát, kvalitu, priečinok na uloženie, názov súboru, výber a správanie rozhrania. + Pred preinštalovaním aplikácie alebo presunutím na iné zariadenie si zálohujte nastavenia. + Nastavte užitočné predvolené hodnoty + Raz vyberte predvolený formát, kvalitu, režim mierky, typ zmeny veľkosti a farebný priestor + Začnite s novými nástrojmi bližšie k vášmu cieľu + Predvolené hodnoty nie sú len kozmetické preferencie. Rozhodujú o tom, aký formát, kvalita, správanie pri zmene veľkosti, režim mierky a farebný priestor sa v mnohých nástrojoch zobrazia ako prvé, čo pomáha vyhnúť sa opätovnému výberu rovnakých možností pri každom exporte. + Nastavte predvolený formát a kvalitu obrázka tak, aby zodpovedali vášmu obvyklému cieľu, napríklad JPEG pre fotografie alebo PNG/WebP pre alfa. + Vylaďte predvolený typ zmeny veľkosti a režim mierky, ak často exportujete pre prísne rozmery, sociálne platformy alebo stránky dokumentov. + Predvolené hodnoty farebného priestoru zmeňte iba vtedy, keď váš pracovný postup vyžaduje konzistentné spracovanie farieb na obrazovkách, tlači alebo externých editoroch. + Výber a spúšťač + Nastavenia výberu použite, keď aplikácia otvorí príliš veľa dialógových okien alebo sa spustí na nesprávnom mieste + Prispôsobte otváranie súborov vášmu zvyku + Nastavenia výberu a spúšťača ovládajú, či sa Image Toolbox spustí z hlavnej obrazovky, nástroja alebo toku výberu súboru. Tieto možnosti sú užitočné najmä vtedy, keď zvyčajne zadávate z hárku zdieľania systému Android alebo keď chcete, aby aplikácia vyzerala skôr ako spúšťač nástrojov. + Nastavenia výberu fotografií použite, ak nástroj na výber systému skrýva súbory, zobrazuje príliš veľa nedávnych položiek alebo zle spracováva cloudové súbory. + Povoľte preskakovanie iba pre nástroje, do ktorých zvyčajne zadávate zo zdieľania alebo už poznáte zdrojový súbor. + Skontrolujte režim spúšťača, ak chcete, aby sa nástroj Image Toolbox správal viac ako nástroj na výber nástrojov než ako bežná domovská obrazovka. + Zdroj obrázka + Vyberte režim výberu, ktorý najlepšie funguje s galériami, správcami súborov a cloudovými súbormi + Vyberte súbory zo správneho zdroja + Nastavenia zdroja obrázkov ovplyvňujú, ako aplikácia žiada Android o obrázky. Najlepšia voľba závisí od toho, či sa vaše súbory nachádzajú v systémovej galérii, priečinku správcu súborov, poskytovateľovi cloudu alebo inej aplikácii, ktorá zdieľa dočasný obsah. + Predvolený nástroj na výber použite, ak chcete pre bežné obrázky galérií čo najviac integrované prostredie. + Prepnite režim výberu, ak sa albumy, priečinky, cloudové súbory alebo neštandardné formáty nezobrazujú tam, kde očakávate. + Ak zdieľaný súbor zmizne po opustení zdrojovej aplikácie, znova ho otvorte pomocou trvalého výberu alebo najskôr uložte lokálnu kópiu. + Obľúbené nástroje a nástroje na zdieľanie + Udržujte hlavnú obrazovku zameranú a skryte nástroje, ktoré v zdieľaných tokoch nedávajú zmysel + Znížte šum zoznamu nástrojov + Obľúbené a skryté nastavenia na zdieľanie sú užitočné, keď každý deň používate len niekoľko nástrojov. Udržiavajú tiež praktický tok zdieľania tým, že skrývajú nástroje, ktoré nemôžu používať typ súboru, ktorý odosiela iná aplikácia. + Pripnite si časté nástroje, aby zostali ľahko dostupné, aj keď sú nástroje zoskupené podľa kategórie. + Skryť nástroje pred zdieľaním, keď nie sú relevantné pre súbory pochádzajúce z inej aplikácie. + Ak uprednostňujete obľúbené položky na konci alebo zoskupené so súvisiacimi nástrojmi, použite obľúbené nastavenia zoradenia. + Zálohujte nastavenia + Bezpečne presuňte predvoľby, preferencie a správanie aplikácie do inej inštalácie + Udržujte svoje nastavenie prenosné + Zálohovanie a obnovenie je najužitočnejšie po vyladení predvolieb, priečinkov, pravidiel názvov, poradia nástrojov a vizuálnych preferencií. Záloha vám umožňuje experimentovať s nastaveniami alebo preinštalovať aplikáciu bez toho, aby ste museli znova zostavovať pracovný postup z pamäte. + Vytvorte zálohu po zmene predvolieb, správania názvu súboru, predvolených formátov alebo usporiadania nástrojov. + Ak plánujete vymazať údaje, preinštalovať alebo presunúť zariadenia, uložte zálohu niekde mimo priečinka aplikácie. + Pred spracovaním dôležitých dávok obnovte nastavenia, aby sa predvolené nastavenia a správanie pri ukladaní zhodovali s vašim starým nastavením. + Zmena veľkosti a konverzia obrázkov + Zmeňte veľkosť, formát, kvalitu a metadáta pre jeden alebo viacero obrázkov + Vytvárajte kópie so zmenenou veľkosťou + Resize and Convert je hlavným nástrojom pre predvídateľné rozmery a ľahšie výstupné súbory. Použite ho, keď na veľkosti obrázka, výstupnom formáte a správaní metadát záleží súčasne. + Vyberte jeden alebo viac obrázkov a potom vyberte, či sú najdôležitejšie rozmery, mierka alebo veľkosť súboru. + Vyberte výstupný formát a kvalitu; použite PNG alebo WebP, keď musí byť zachovaná priehľadnosť. + Ukážte výsledok a potom uložte alebo zdieľajte vygenerované kópie bez zmeny originálov. + Zmeniť veľkosť podľa veľkosti súboru + Zamerajte sa na limit veľkosti, keď webová stránka, messenger alebo formulár odmieta ťažké obrázky + Dodržujte prísne limity nahrávania + Zmena veľkosti podľa veľkosti súboru je lepšia ako hádanie hodnôt kvality, keď cieľ akceptuje iba súbory pod určitým limitom. Nástroj môže upraviť kompresiu a rozmery, aby dosiahol cieľ, pričom sa snaží zachovať použiteľnosť výsledku. + Zadajte cieľovú veľkosť mierne pod skutočný limit nahrávania, aby ste ponechali priestor na zmeny metadát a poskytovateľov. + Uprednostňujte stratové formáty fotografií, keď je cieľ malý; PNG môže zostať veľký aj po zmene veľkosti. + Po exporte skontrolujte tváre, text a okraje, pretože ciele s agresívnou veľkosťou môžu spôsobiť viditeľné artefakty. + Obmedzte zmenu veľkosti + Zmenšiť iba obrázky, ktoré presahujú maximálnu šírku, výšku alebo obmedzenia súboru + Vyhnite sa zmene veľkosti obrázkov, ktoré sú už v poriadku + Limit Resize je užitočný pre zmiešané priečinky, kde sú niektoré obrázky veľké a iné sú už pripravené. Namiesto toho, aby každý súbor musel zmeniť veľkosť, zachováva prijateľné obrázky bližšie k ich pôvodnej kvalite. + Namiesto slepej zmeny veľkosti každého súboru nastavte maximálne rozmery alebo limity na základe cieľa. + Ak sa chcete vyhnúť výstupom, ktoré sú ťažšie ako zdroje, povoľte správanie typu skip-if-large. + Použite to pre dávky z rôznych fotoaparátov, snímky obrazovky alebo stiahnuté súbory, kde sa veľkosť zdrojov veľmi líši. + Orežte, otočte a narovnajte + Pred exportovaním alebo použitím obrázka v iných nástrojoch orámujte dôležitú oblasť + Vyčistite kompozíciu + Orezanie najskôr vyčistí neskoršie filtre, OCR, stránky PDF a výsledky zdieľania. + Otvorte Orezať a vyberte obrázok, ktorý chcete upraviť. + Ak sa výstup musí prispôsobiť príspevku, dokumentu alebo avataru, použite voľné orezanie alebo pomer strán. + Pred uložením otočte alebo otočte, aby neskoršie nástroje dostali opravený obrázok. + Použite filtre a úpravy + Pred exportom vytvorte upraviteľný zásobník filtrov a ukážte výsledok + Vylaďte vzhľad obrazu + Filtre sú užitočné na rýchle opravy, štylizovaný výstup a prípravu obrázkov na OCR alebo tlač. Malé zmeny kontrastu, ostrosti a jasu môžu mať väčší význam ako výrazné efekty, keď obrázok obsahuje text alebo jemné detaily. + Pridávajte filtre jeden po druhom a po každej zmene sledujte náhľad. + Upravte jas, kontrast, ostrosť, rozmazanie, farbu alebo masky v závislosti od úlohy. + Exportujte iba vtedy, keď sa ukážka zhoduje s cieľovým zariadením, dokumentom alebo sociálnou platformou. + Najprv ukážte + Pred výberom náročnejšieho nástroja na úpravu, konverziu alebo čistenie skontrolujte obrázky + Skontrolujte, čo ste skutočne dostali + Ukážka obrázka je užitočná, keď súbor pochádza z chatu, cloudového úložiska alebo inej aplikácie a nie ste si istí, čo obsahuje. Kontrola rozmerov, priehľadnosti, orientácie a vizuálnej kvality najprv pomôže vybrať správny nadväzujúci nástroj. + Otvorte Ukážku obrázka, keď potrebujete skontrolovať niekoľko obrázkov predtým, ako sa rozhodnete, čo s nimi urobiť. + Hľadajte problémy s otáčaním, priehľadné oblasti, kompresné artefakty a neočakávane veľké rozmery. + Presuňte sa na Zmeniť veľkosť, Orezať, Porovnať, EXIF ​​alebo odstraňovač pozadia až potom, čo viete, čo je potrebné opraviť. + Odstráňte alebo obnovte pozadie + Vymažte pozadie automaticky alebo upravte okraje manuálne pomocou nástrojov na obnovenie + Ponechajte predmet, zvyšok odstráňte + Odstránenie pozadia funguje najlepšie, keď skombinujete automatický výber so starostlivým manuálnym čistením. Na konečnom formáte exportu záleží rovnako ako na maske, pretože JPEG vyrovná priehľadné oblasti, aj keď ukážka vyzerá správne. + Ak je objekt jasne oddelený od pozadia, začnite s automatickým odstránením. + Priblížte a prepínajte medzi vymazaním a obnovením, aby ste opravili vlasy, rohy, tiene a malé detaily. + Uložte ako PNG alebo WebP, keď potrebujete priehľadnosť v konečnom súbore. + Kreslenie, označenie a vodoznak + Pridajte šípky, poznámky, zvýraznenia, podpisy alebo opakované prekrytia vodotlačou + Pridajte viditeľné informácie + Nástroje na označovanie pomáhajú vysvetliť obrázok, zatiaľ čo vodoznak pomáha označiť alebo chrániť exportované súbory. + Kresliť použite, keď potrebujete poznámky, tvary, zvýraznenie alebo rýchle poznámky od ruky. + Vodotlač použite vtedy, keď by sa na výstupoch mala konzistentne zobrazovať rovnaká textová alebo obrazová značka. + Pred exportom skontrolujte nepriehľadnosť, polohu a mierku, aby bola značka viditeľná, ale nerušila. + Predvolené nastavenia kreslenia + Nastavte šírku čiary, farbu, režim cesty, zámok orientácie a lupu pre rýchlejšie označovanie + Nechajte kreslenie pôsobiť predvídateľne + Nastavenia kreslenia sú užitočné, keď často anotujete snímky obrazovky, označujete dokumenty alebo podpisujete obrázky. Predvolené nastavenia skracujú čas nastavenia, zatiaľ čo lupa a zámok orientácie uľahčujú presné úpravy na malých obrazovkách. + Nastavte predvolenú šírku čiary a farbu kreslenia na hodnoty, ktoré najčastejšie používate pre šípky, zvýraznenia alebo podpisy. + Ak zvyčajne kreslíte rovnaký druh ťahov, tvarov alebo značiek, použite predvolený režim cesty. + Povoľte lupu alebo uzamknutie orientácie, keď vstup prstom sťažuje presné umiestnenie malých detailov. + Rozloženie viacerých obrázkov + Pred usporiadaním snímok obrazovky, skenov alebo porovnávacích prúžkov si vyberte správny nástroj na viacero obrázkov + Použite správny nástroj na rozloženie + Tieto nástroje znejú podobne, ale každý z nich je vyladený pre iný druh výstupu viacerých obrázkov. Prvým výberom toho správneho sa vyhnete boju s ovládacími prvkami rozloženia, ktoré boli navrhnuté pre iný výsledok. + Použite Collage Maker pre navrhnuté rozloženia s niekoľkými obrázkami v jednej kompozícii. + Ak by sa obrázky mali stať jedným dlhým vertikálnym alebo horizontálnym pásom, použite funkciu Spájanie obrázkov alebo Stohovanie. + Rozdelenie obrázka použite, keď sa z jedného veľkého obrázka musí stať niekoľko menších častí. + Konvertovať obrazové formáty + Vytvárajte kópie JPEG, PNG, WebP, AVIF, JXL a ďalšie kópie bez ručnej úpravy pixelov + Vyberte formát úlohy + Rôzne formáty sú lepšie pre fotografie, snímky obrazovky, priehľadnosť, kompresiu alebo kompatibilitu. Konverzia je najbezpečnejšia, keď pochopíte, čo cieľová aplikácia podporuje a či zdroj obsahuje alfa, animáciu alebo metadáta, ktoré si chcete ponechať. + Použite JPEG pre kompatibilné fotografie, PNG pre bezstratové obrázky a alfa a WebP pre kompaktné zdieľanie. + Upravte kvalitu, keď to formát podporuje; nižšie hodnoty zmenšia veľkosť, ale môžu pridať artefakty. + Originály si ponechajte, kým neoveríte, že konvertované súbory sa správne otvorili v cieľovej aplikácii. + Skontrolujte EXIF ​​a súkromie + Zobrazte, upravte alebo odstráňte metadáta pred zdieľaním citlivých fotografií + Ovládajte skryté obrazové údaje + EXIF môže obsahovať údaje fotoaparátu, dátumy, polohu, orientáciu a ďalšie podrobnosti, ktoré nie sú viditeľné na obrázku. Pred verejným zdieľaním, správami podpory, zoznamami na trhu alebo akoukoľvek fotkou, ktorá môže odhaliť súkromné ​​miesto, sa oplatí skontrolovať. + Pred zdieľaním fotografií, ktoré môžu obsahovať informácie o polohe alebo súkromnom zariadení, otvorte nástroje EXIF. + Odstráňte citlivé polia alebo upravte nesprávne značky, ako sú dátum, orientácia, autor alebo údaje fotoaparátu. + Uložte si novú kópiu a zdieľajte túto kópiu namiesto originálu, keď na ochrane osobných údajov záleží. + Automatické čistenie EXIF + Predvolene odstráňte metadáta pri rozhodovaní, či sa majú dátumy zachovať + Nastavte súkromie ako predvolené + Skupina nastavení EXIF ​​je užitočná, keď chcete, aby čistenie súkromia prebiehalo automaticky namiesto toho, aby ste si ho pamätali pri každom exporte. Ponechať dátum a čas je samostatné rozhodnutie, pretože dátumy môžu byť užitočné na triedenie, ale citlivé na zdieľanie. + Povoľte vždy jasné EXIF, keď je väčšina vašich exportov určená na verejné alebo poloverejné zdieľanie. + Zachovať čas dátumu povoľte iba vtedy, keď je zachovanie času snímania dôležitejšie ako odstránenie každej časovej pečiatky. + Použite manuálnu úpravu EXIF ​​pre jednorazové súbory, kde potrebujete niektoré polia ponechať a iné odstrániť. + Ovládanie názvov súborov + Používajte predpony, prípony, časové pečiatky, predvoľby, kontrolné súčty a poradové čísla + Uľahčite vyhľadávanie exportov + Dobrý vzor názvu súboru pomáha triediť dávky a zabraňuje náhodnému prepísaniu. Nastavenia názvu súboru sú užitočné najmä vtedy, keď sa exporty vrátia späť do zdrojového priečinka, kde sa podobné názvy môžu rýchlo stať mätúcimi. + Nastavte predponu názvu, príponu, časovú pečiatku, pôvodný názov, prednastavené informácie alebo možnosti sekvencie v Nastaveniach. + Použite poradové čísla pre dávky, kde záleží na poradí, ako sú strany, rámy alebo koláže. + Povoľte prepisovanie iba vtedy, keď ste si istí, že nahradenie existujúcich súborov je pre aktuálny priečinok bezpečné. + Prepísať súbory + Nahraďte výstupy zodpovedajúcimi názvami iba vtedy, keď je váš pracovný postup zámerne deštruktívny + Režim prepisovania používajte opatrne + Prepísať súbory mení spôsob riešenia konfliktov názvov. Je to užitočné pre opakované exporty do rovnakého priečinka, ale môže tiež nahradiť predchádzajúce výsledky, ak váš vzor názvu súboru znova vytvorí rovnaký názov. + Povoliť prepisovanie len pre priečinky, kde sa očakáva nahradenie starších vygenerovaných súborov a dá sa obnoviť. + Ponechajte prepisovanie zakázané pri exporte originálov, klientskych súborov, jednorazových skenov alebo čohokoľvek bez zálohy. + Skombinujte prepísanie so zámerným vzorom názvu súboru, aby sa nahradili iba zamýšľané výstupy. + Vzory názvov súborov + Kombinujte pôvodné názvy, predpony, prípony, časové pečiatky, predvoľby, režim mierky a kontrolné súčty + Zostavte názvy, ktoré vysvetľujú výstup + Nastavenia vzoru názvu súboru môžu zmeniť exportované súbory na užitočnú históriu toho, čo sa s nimi stalo. V dávkach je to dôležité, pretože zobrazenie priečinkov môže byť jediným miestom, kde môžete rozlíšiť veľkosť originálu, predvoľbu, formát a poradie spracovania. + Ak potrebujete čitateľné názvy na manuálne triedenie, použite pôvodný názov súboru, predponu, príponu a časovú pečiatku. + Pridajte informácie o predvoľbe, režime mierky, veľkosti súboru alebo kontrolnom súčte, keď musia výstupy dokumentovať, ako boli vyrobené. + Vyhnite sa náhodným názvom pre pracovné postupy, kde súbory musia zostať spárované s originálmi alebo poradím strán. + Vyberte, kam sa majú ukladať súbory + Zabráňte strate exportov nastavením priečinkov, uložením pôvodného priečinka a umiestnením na jednorazové uloženie + Udržujte výstupy predvídateľné + Správanie pri ukladaní je najdôležitejšie, keď spracovávate veľa súborov alebo zdieľate súbory od poskytovateľov cloudu. Nastavenia priečinka rozhodujú o tom, či sa výstupy zhromažďujú na jednom známom mieste, či sa objavia vedľa originálov alebo dočasne prejdú niekam inam pre konkrétnu úlohu. + Ak chcete exportovať vždy na jednom mieste, nastavte predvolený priečinok na ukladanie. + Použite uloženie do pôvodného priečinka pre pracovné postupy čistenia, kde by mal výstup zostať blízko zdrojového súboru. + Použite jednorazové miesto uloženia, keď jedna úloha vyžaduje iný cieľ bez zmeny predvoleného nastavenia. + Priečinky na jednorazové uloženie + Pošlite ďalšie exporty do dočasného priečinka bez zmeny vášho normálneho cieľa + Udržujte špeciálne úlohy oddelené + Umiestnenie na jednorazové uloženie je užitočné pre dočasné projekty, klientske priečinky, relácie čistenia alebo exporty, ktoré by sa nemali miešať s bežným priečinkom Image Toolbox. Poskytuje krátkodobý cieľ bez prepisovania predvoleného nastavenia. + Pred spustením úlohy vyberte jednorazový priečinok, ktorý nepatrí do vášho normálneho umiestnenia exportu. + Použite ho pre krátke projekty, zdieľané priečinky alebo dávky, kde výstupy musia zostať zoskupené. + Po vykonaní úlohy sa vráťte do predvoleného priečinka, aby sa neskoršie exporty neočakávane nedostali do dočasného umiestnenia. + Vyvážte kvalitu a veľkosť súboru + Pochopte, kedy zmeniť rozmery, formát alebo kvalitu namiesto hádania + Súbory zmenšujte zámerne + Veľkosť súboru závisí od rozmerov obrázka, formátu, kvality, priehľadnosti a zložitosti obsahu. Ak je súbor stále príliš ťažký, zmena rozmerov zvyčajne pomáha viac ako opakované znižovanie kvality. + Najprv zmeňte veľkosť, keď je obrázok väčší, ako dokáže zobraziť cieľ. + Nižšia kvalita len pre stratové formáty a po exporte skontrolujte text, okraje, prechody a plochy. + Prepnite formát, keď zmeny kvality nestačia, napríklad WebP na zdieľanie alebo PNG na ostré transparentné aktíva. + Práca s animovanými formátmi + Ak má súbor snímky namiesto jednej bitovej mapy, použite nástroje GIF, APNG, WebP a JXL + Nepovažujte každý obrázok za nehybný + Animované formáty potrebujú nástroje, ktoré rozumejú snímkam, trvaniu a kompatibilite. + Ak pre tieto formáty potrebujete operácie na úrovni rámca, použite nástroje GIF alebo APNG. + Použite nástroje WebP, keď potrebujete modernú kompresiu alebo animovaný výstup WebP. + Pred zdieľaním si zobrazte načasovanie animácie, pretože niektoré platformy animované obrázky konvertujú alebo zlúčia. + Porovnajte a ukážte výstup + Pred uložením exportu skontrolujte zmeny, veľkosť súboru a vizuálne rozdiely + Pred zdieľaním overte + Porovnávacie nástroje pomáhajú včas zachytiť kompresné artefakty, nesprávne farby alebo nechcené úpravy. + Ak chcete skontrolovať dve verzie vedľa seba, otvorte položku Porovnať. + Priblížte okraje, text, prechody a priehľadné oblasti, kde sa problémy dajú najľahšie prehliadnuť. + Ak je výstup príliš ťažký alebo rozmazaný, vráťte sa k predchádzajúcemu nástroju a upravte kvalitu alebo formát. + Použite centrum nástrojov PDF + Zlúčiť, rozdeliť, otáčať, odstraňovať strany, komprimovať, chrániť, OCR a vodoznakové súbory PDF + Vyberte operáciu PDF + Centrum PDF zoskupuje akcie dokumentu za jeden vstupný bod, takže si môžete vybrať presnú operáciu. Začnite tam, keď je súbor už vo formáte PDF, a použite Obrázky do PDF alebo skener dokumentov, keď je vaším zdrojom stále sada obrázkov. + Otvorte nástroje PDF a vyberte operáciu, ktorá zodpovedá vášmu cieľu. + Vyberte zdrojové PDF alebo obrázky a potom skontrolujte poradie strán, otočenie, ochranu alebo možnosti OCR. + Uložte vygenerovaný súbor PDF a raz ho otvorte, aby ste potvrdili počet strán, poradie a čitateľnosť. + Premeňte obrázky na PDF + Skombinujte skeny, snímky obrazovky, účtenky alebo fotografie do jedného zdieľaného dokumentu + Vytvorte čistý dokument + Obrázky sa stanú lepšími stránkami PDF, keď sú orezané, usporiadané a majú konzistentnú veľkosť. So zoznamom obrázkov zaobchádzajte ako so stohom strán: každé otočenie, okraj a veľkosť strany ovplyvňujú čitateľnosť výsledného dokumentu. + Ak sú okraje, tiene alebo orientácia strany nesprávne, najskôr orezajte alebo otočte obrázky. + Vyberte obrázky v zamýšľanom poradí strán a upravte veľkosť strany alebo okraje, ak ich nástroj ponúka. + Exportujte PDF a pred odoslaním skontrolujte, či sú všetky strany čitateľné. + Skenujte dokumenty + Zachyťte papierové strany a pripravte ich na PDF, OCR alebo zdieľanie + Zachyťte čitateľné stránky + Skenovanie dokumentov funguje najlepšie s plochými stranami, dobrým osvetlením a upravenou perspektívou. Čisté skenovanie pred OCR alebo exportom PDF šetrí čas, pretože rozpoznávanie textu aj kompresia závisia od kvality stránky. + Dokument umiestnite na kontrastný povrch s dostatkom svetla a minimálnymi tieňmi. + Upravte zistené rohy alebo orežte manuálne tak, aby stránka čisto vyplnila rám. + Exportujte ako obrázky alebo PDF a potom spustite OCR, ak potrebujete voliteľný text. + Chráňte alebo odomknite súbory PDF + Používajte heslá opatrne a uchovávajte si upraviteľnú zdrojovú kópiu, ak je to možné + Zaobchádzajte s prístupom k PDF bezpečne + Ochranné nástroje sú užitočné na kontrolované zdieľanie, no heslá sa ľahko stratia a ťažko sa obnovujú. Chráňte kópie na distribúciu, uchovávajte nechránené zdrojové súbory oddelene a pred odstránením čohokoľvek skontrolujte výsledok. + Chráňte kópiu PDF, nie váš jediný zdrojový dokument. + Pred zdieľaním chráneného súboru si heslo uložte na bezpečné miesto. + Odomknutie použite iba pre súbory, ktoré môžete upravovať, a potom skontrolujte, či sa odomknutá kópia správne otvára. + Usporiadajte stránky PDF + Zlúčiť, rozdeliť, preusporiadať, otočiť, orezať, odstrániť strany a pridať čísla strán zámerne + Pred dekoráciou opravte štruktúru dokumentu + Nástroje stránok PDF sa najjednoduchšie používajú v rovnakom poradí, v akom by ste pripravovali papierový dokument: zbierajte strany, odstráňte nesprávne, usporiadajte ich, upravte orientáciu a potom pridajte posledné podrobnosti, ako sú čísla strán alebo vodoznaky. + Ak je dokument stále rozdelený do niekoľkých súborov, použite najskôr zlúčenie alebo prevod obrázkov do PDF. + Pred pridaním čísiel strán, podpisov alebo vodoznakov preusporiadajte, otočte, orežte alebo odstráňte strany. + Po štrukturálnych úpravách si zobrazte ukážku konečného PDF, pretože jednu chýbajúcu alebo otočenú stranu si neskôr ťažko všimnúť. + anotácie PDF + Odstráňte anotácie alebo ich zjednoťte, keď diváci zobrazujú komentáre a značky inak + Ovládajte, čo zostáva viditeľné + Anotácie môžu byť komentáre, zvýraznenia, kresby, značky formulárov alebo iné ďalšie objekty PDF. Niektorí používatelia ich zobrazujú odlišne, takže ich odstránením alebo sploštením môžu byť zdieľané dokumenty predvídateľnejšie. + Ak zdieľaná kópia nemá obsahovať komentáre, zvýraznenia alebo značky recenzie, odstráňte anotácie. + Vyrovnať, keď by viditeľné značky mali zostať na stránke, ale už sa nesprávajú ako upraviteľné anotácie. + Ponechajte si originálnu kópiu, pretože odstránenie alebo sploštenie anotácií môže byť ťažké vrátiť späť. + Rozpoznať text + Extrahujte text z obrázkov pomocou voliteľných nástrojov OCR a jazykov + Získajte lepšie výsledky OCR + Presnosť rozpoznávania OCR závisí od ostrosti obrazu, jazykových údajov, kontrastu a orientácie strany. Najlepšie výsledky sa zvyčajne dosiahnu tak, že obrázok najskôr pripravíte: orezajte šum, otočte ho do zvislej polohy, zvýšte čitateľnosť a potom rozpoznajte text. + Pred rozpoznaním orežte obrázok do textovej oblasti a otočte ho vzpriamene. + Vyberte správny jazyk a stiahnite si jazykové údaje, ak to aplikácia vyžaduje. + Skopírujte alebo zdieľajte rozpoznaný text a potom manuálne skontrolujte mená, čísla a interpunkciu. + Vyberte jazykové údaje OCR + Zlepšite rozpoznávanie priraďovaním skriptov, jazykov a stiahnutých modelov OCR + Priraďte OCR k textu + Nesprávny jazyk OCR môže spôsobiť, že dobré fotografie budú vyzerať ako zlé rozpoznanie. Jazykové údaje sú dôležité pre písma, interpunkciu, čísla a zmiešané dokumenty, preto si namiesto jazyka používateľského rozhrania telefónu vyberte jazyk, ktorý sa zobrazuje na obrázku. + Vyberte jazyk alebo písmo, ktoré sa zobrazí na obrázku, nielen jazyk telefónu. + Pred prácou v režime offline alebo spracovaním dávky si stiahnite chýbajúce údaje. + V prípade dokumentov v rôznych jazykoch spustite najskôr najdôležitejší jazyk a manuálne skontrolujte mená a čísla. + Prehľadávateľné súbory PDF + Zapíšte text OCR späť do súborov, aby bolo možné naskenované strany vyhľadávať a kopírovať + Premeňte skeny na dokumenty s možnosťou vyhľadávania + OCR dokáže viac než len kopírovať text do schránky. Keď zapíšete rozpoznaný text do súboru alebo prehľadávateľného PDF, obrázok strany zostane viditeľný, zatiaľ čo text sa bude ľahšie vyhľadávať, vyberať, indexovať alebo archivovať. + Použite výstup PDF s možnosťou vyhľadávania pre naskenované dokumenty, ktoré by mali stále vyzerať ako pôvodné strany. + Zápis do súboru použite vtedy, keď potrebujete iba extrahovaný text a nestaráte sa o obrázok stránky. + Dôležité čísla, mená a tabuľky skontrolujte manuálne, pretože text OCR sa dá vyhľadávať, ale stále je nedokonalý. + Naskenujte QR kódy + Prečítajte si obsah QR zo vstupu fotoaparátu alebo uložených obrázkov, ak sú k dispozícii + Čítajte kódy bezpečne + QR kódy môžu obsahovať odkazy, údaje Wi-Fi, kontakty, text alebo iné užitočné údaje. + Otvorte Skenovať QR kód a ponechajte kód ostrý, plochý a úplne vnútri rámu. + Pred otvorením odkazov alebo kopírovaním citlivých údajov skontrolujte dekódovaný obsah. + Ak skenovanie zlyhá, zlepšite osvetlenie, orežte kód alebo skúste zdrojový obrázok s vyšším rozlíšením. + Použite Base64 a kontrolné súčty + Kódujte obrázky ako text, dekódujte text späť na obrázky a overte integritu súboru + Prechádzajte medzi súbormi a textom + Base64 je užitočný pre malé množstvo obrázkov, zatiaľ čo kontrolné súčty pomáhajú potvrdiť, že sa súbory nezmenili. Tieto nástroje sú najužitočnejšie v technických pracovných postupoch, podporných správach, prenosoch súborov a na miestach, kde sa binárne súbory musia dočasne zmeniť na text. + Použite nástroje Base64 na kódovanie malého obrázka, keď pracovný postup potrebuje text namiesto binárneho súboru. + Dekódujte Base64 iba z dôveryhodných zdrojov a pred uložením si overte náhľad. + Nástroje kontrolného súčtu použite, keď potrebujete porovnať exportované súbory alebo potvrdiť nahrávanie/sťahovanie. + Zabaľte alebo chráňte údaje + Použite ZIP a šifrovacie nástroje na spájanie súborov alebo transformáciu textových údajov + Uchovávajte súvisiace súbory spolu + Nástroje na balenie sú užitočné, keď by niekoľko výstupov malo cestovať ako jeden súbor. Sú tiež dobré na uchovávanie vygenerovaných obrázkov, súborov PDF, denníkov a metadát pohromade, keď potrebujete odoslať kompletnú sadu výsledkov. + Použite ZIP, keď potrebujete odoslať veľa exportovaných obrázkov alebo dokumentov spolu. + Šifrovacie nástroje používajte iba vtedy, keď rozumiete vybranej metóde a viete si požadované kľúče bezpečne uložiť. + Pred odstránením zdrojových súborov otestujte vytvorený archív alebo transformovaný text. + Kontrolné súčty v menách + Použite názvy súborov založené na kontrolnom súčte, keď na jedinečnosti a integrite záleží viac ako na čitateľnosti + Pomenujte súbory podľa obsahu + Názvy súborov s kontrolným súčtom sú užitočné, keď exporty môžu mať duplicitné viditeľné názvy alebo keď potrebujete dokázať, že dva súbory sú identické. Sú menej priateľské na manuálne prehliadanie, preto ich používajte na technické archívy a overovacie pracovné postupy. + Povoľte kontrolný súčet ako názov súboru, keď je identita obsahu dôležitejšia ako človekom čitateľný názov. + Použite ho na archívy, deduplikáciu, opakovateľné exporty alebo súbory, ktoré možno znova nahrať a stiahnuť. + Spárujte názvy kontrolných súčtov s priečinkami alebo metadátami, keď ľudia stále potrebujú pochopiť, čo súbor predstavuje. + Vyberte farby z obrázkov + Vzorkujte pixely, skopírujte farebné kódy a znova použite farby v paletách alebo dizajnoch + Odoberte presnú farbu + Výber farieb je užitočný na zladenie dizajnu, extrahovanie farieb značky alebo kontrolu hodnôt obrázkov. Priblíženie je dôležité, pretože antialiasing, tiene a kompresia môžu spôsobiť, že susedné pixely budú mierne odlišné od farby, ktorú očakávate. + Otvorte položku Vybrať farbu z obrázka a vyberte zdrojový obrázok s farbou, ktorú potrebujete. + Pred vzorkovaním malých detailov priblížte, aby blízke pixely neovplyvnili výsledok. + Skopírujte farbu vo formáte, ktorý vyžaduje váš cieľ, napríklad HEX, RGB alebo HSV. + Vytvorte palety a použite knižnicu farieb + Extrahujte palety, prehliadajte uložené farby a udržujte konzistentné vizuálne systémy + Vytvorte opakovane použiteľnú paletu + Palety pomáhajú udržiavať exportovanú grafiku, vodoznaky a kresby vizuálne konzistentné. Sú užitočné najmä vtedy, keď opakovane anotujete snímky obrazovky, pripravujete prvky značky alebo potrebujete rovnaké farby v niekoľkých nástrojoch. + Vygenerujte paletu z obrázka alebo pridajte farby ručne, keď už poznáte hodnoty. + Pomenujte alebo usporiadajte dôležité farby, aby ste ich neskôr znova našli. + Použite skopírované hodnoty v nástrojoch kreslenia, vodoznaku, prechodu, SVG alebo externých návrhových nástrojov. + Vytvorte prechody + Vytvárajte obrázky s prechodmi pre pozadia, zástupné symboly a vizuálne experimenty + Ovládajte farebné prechody + Nástroje prechodu sú užitočné, keď potrebujete vygenerovaný obrázok namiesto úpravy existujúceho. Môžu sa stať čistými pozadiami, zástupnými symbolmi, tapetami, maskami alebo jednoduchými prvkami pre externé dizajnové nástroje. + Vyberte typ prechodu a najskôr nastavte dôležité farby. + Upravte smer, zarážky, plynulosť a veľkosť výstupu pre cieľový prípad použitia. + Exportujte vo formáte, ktorý sa zhoduje s cieľom, zvyčajne PNG pre čistú vygenerovanú grafiku. + Farebná dostupnosť + Ak je používateľské rozhranie ťažko čitateľné, použite dynamické farby, kontrast a možnosti farbosleposti + Uľahčite používanie farieb + Nastavenia farieb ovplyvňujú pohodlie, čitateľnosť a rýchlosť skenovania ovládacích prvkov. Ak je ťažké rozlíšiť čipy nástrojov, posúvače alebo vybrané stavy, možnosti témy a dostupnosti môžu byť užitočnejšie ako jednoduchá zmena tmavého režimu. + Vyskúšajte dynamické farby, keď chcete, aby aplikácia sledovala aktuálnu paletu tapiet. + Zvýšte kontrast alebo zmeňte schému, keď tlačidlá a povrchy dostatočne nevystupujú. + Ak je ťažké rozlíšiť niektoré stavy alebo akcenty, použite možnosti farebne slepej schémy. + Pozadie alfa + Ovládajte farbu náhľadu alebo výplne používanú okolo priehľadných PNG, WebP a podobných výstupov + Pochopte priehľadné náhľady + Farba pozadia pre alfa formáty môže uľahčiť kontrolu priehľadných obrázkov, ale je dôležité vedieť, či meníte správanie náhľadu/výplne alebo sploštujete konečný výsledok. To je dôležité pre nálepky, logá a obrázky odstránené z pozadia. + Ak musia priehľadné pixely prežiť export, použite najskôr formát podporujúci alfa verziu, ako je PNG alebo WebP. + Upravte nastavenia farby pozadia, keď sú priehľadné okraje na povrchu aplikácie ťažko viditeľné. + Exportujte malý test a znova ho otvorte v inej aplikácii, aby ste potvrdili, či bola uložená priehľadnosť alebo zvolená výplň. + Výber modelu AI + Vyberte si model podľa typu obrázka a chyby namiesto toho, aby ste najskôr vybrali ten najväčší + Priraďte model k poškodeniu + Nástroje AI dokážu stiahnuť alebo importovať rôzne modely ONNX/ORT a každý model je vyškolený na konkrétny druh problému. Model pre bloky JPEG, vyčistenie línií anime, odstránenie šumu, odstránenie pozadia, zafarbenie alebo prevzorkovanie môže priniesť horšie výsledky, ak sa použije na nesprávnom type obrázka. + Pred stiahnutím si prečítajte popis modelu; vyberte model, ktorý pomenúva váš skutočný problém, ako je kompresia, šum, poltón, rozmazanie alebo odstránenie pozadia. + Pri testovaní nového typu obrázka začnite s menším alebo špecifickejším modelom a potom porovnávajte s ťažšími modelmi iba v prípade, že výsledok nie je dostatočne dobrý. + Ponechajte si iba modely, ktoré skutočne používate, pretože stiahnuté a importované modely môžu zaberať značné množstvo úložného priestoru. + Pochopte modelové rodiny + Názvy modelov často napovedajú o ich cieli: modely v štýle RealESRGAN upscale, modely SCUNet a FBCNN vyčistia šum alebo kompresiu, modely na pozadí vytvárajú masky a kolorizéry pridávajú farbu vstupu v odtieňoch sivej. Importované vlastné modely môžu byť výkonné, ale mali by zodpovedať očakávanému tvaru vstupu/výstupu a používať podporovaný formát ONNX alebo ORT. + Použite upscalers, keď potrebujete viac pixelov, potlačenie šumu, keď je obraz zrnitý, a odstraňovače artefaktov, keď sú hlavným problémom kompresné bloky alebo pruhy. + Modely pozadia používajte len na oddelenie objektov; nie sú rovnaké ako všeobecné odstránenie objektu alebo vylepšenie obrazu. + Importujte vlastné modely iba zo zdrojov, ktorým dôverujete, a pred použitím dôležitých súborov ich otestujte na malom obrázku. + parametre AI + Vylaďte veľkosť bloku, prekrytie a paralelných pracovníkov, aby ste dosiahli rovnováhu medzi kvalitou, rýchlosťou a pamäťou + Vyvážte rýchlosť so stabilitou + Veľkosť bloku určuje, aké veľké sú časti obrazu pred ich spracovaním modelom. Prekrytie spája susedné kusy, aby sa skryli okraje. Paralelní pracovníci môžu zrýchliť spracovanie, ale každý pracovník potrebuje pamäť, takže vysoké hodnoty môžu spôsobiť nestabilitu veľkých obrázkov na telefónoch s obmedzenou pamäťou RAM. + Použite väčšie kusy na plynulejší kontext, keď vaše zariadenie zvládne model spoľahlivo a obraz nie je veľký. + Zväčšite prekrytie, keď sa okraje kúskov stanú viditeľnými, ale nezabudnite, že väčšie prekrytie znamená viac opakovanej práce. + Vychovávajte paralelných pracovníkov až po stabilnom teste; ak sa spracovanie spomalí, zariadenie sa zahrieva alebo havaruje, najprv znížte počet pracovníkov. + Vyhnite sa kusovým artefaktom + Chunking umožňuje aplikácii spracovať obrázky, ktoré sú väčšie, než dokáže model pohodlne spracovať naraz. Kompromisom je, že každá dlaždica má menej okolitého kontextu, takže nízke prekrytie alebo príliš malé kúsky môžu vytvárať viditeľné okraje, zmeny textúry alebo nekonzistentné detaily medzi susednými oblasťami. + Zvýšte prekrytie, ak vidíte obdĺžnikové okraje, opakované zmeny textúry alebo preskakovanie detailov medzi spracovanými oblasťami. + Znížte veľkosť bloku, ak proces zlyhá pred produkciou výstupu, najmä pri veľkých obrázkoch alebo ťažkých modeloch. + Pred spracovaním celého obrázka si uložte malý test orezania, aby ste mohli rýchlo porovnávať parametre. + Stabilita AI + Znížte veľkosť bloku, pracovníkov a rozlíšenie zdroja, keď spracovanie AI zlyhá alebo zamrzne + Zotavte sa z ťažkých úloh AI + Spracovanie AI je jedným z najťažších pracovných postupov v aplikácii. Zlyhania, neúspešné relácie, zamrznutia alebo náhle reštarty aplikácie zvyčajne znamenajú, že model, rozlíšenie zdroja, veľkosť bloku alebo počet paralelných pracovníkov sú pre aktuálny stav zariadenia príliš náročné. + Ak aplikácia zlyhá alebo sa nepodarí otvoriť reláciu modelu, znížte paralelných pracovníkov a veľkosť bloku, potom skúste rovnaký obrázok znova. + Zmeňte veľkosť veľmi veľkých obrázkov pred spracovaním AI, keď cieľ nevyžaduje pôvodné rozlíšenie. + Keď sa tlak pamäte neustále vracia, zatvorte ostatné náročné aplikácie, použite menší model alebo spracujte menej súborov naraz. + Diagnostikujte poruchy modelu + Neúspešné spustenie AI nemusí vždy znamenať, že obraz je zlý. Súbor modelu môže byť neúplný, nepodporovaný, príliš veľký pre pamäť alebo sa nezhoduje s operáciou. Keď aplikácia nahlási neúspešnú reláciu, považujte to za signál na zjednodušenie modelu a parametrov. + Odstráňte a znova stiahnite model, ak zlyhá ihneď po stiahnutí alebo sa správa, ako keby bol súbor poškodený. + Pred obviňovaním importovaného vlastného modelu vyskúšajte vstavaný model na stiahnutie, pretože importované modely môžu mať nekompatibilné vstupy. + Ak potrebujete nahlásiť zlyhanie alebo chybu relácie, použite protokoly aplikácií po zopakovaní zlyhania. + Experimentujte v Shader Studio + Použite efekty shadera GPU na pokročilé spracovanie obrazu a vlastný vzhľad + So shadermi pracujte opatrne + Shader Studio je výkonné, ale kód a parametre shadera vyžadujú malé, testovateľné zmeny. Zaobchádzajte s tým ako s experimentálnym nástrojom: udržujte pracovný základ, zmeňte jednu vec po druhej a vyskúšajte s rôznymi veľkosťami obrázkov predtým, ako budete dôverovať predvoľbe. + Pred pridaním parametrov začnite od známeho pracovného shadera alebo jednoduchého efektu. + Zmeňte jeden parameter alebo blok kódu naraz a skontrolujte, či v náhľade nie sú chyby. + Exportujte predvoľby až po ich otestovaní na niekoľkých rôznych veľkostiach obrázkov. + Urobte umenie SVG alebo ASCII + Vytvárajte prvky podobné vektorom alebo textové obrázky pre kreatívny výstup + Vyberte typ výstupu kreatívy + Nástroje SVG a ASCII slúžia rôznym cieľom: škálovateľná grafika alebo vykresľovanie v štýle textu. Obidve sú kreatívne konverzie, takže náhľad v konečnej veľkosti je dôležitejší ako posudzovanie výsledku podľa malej miniatúry. + SVG Maker použite vtedy, keď by sa mal výsledok čisto škálovať alebo by sa mal opätovne použiť v pracovných postupoch návrhu. + ASCII Art použite, ak chcete obrázok reprezentovať štylizovaným textom. + Ukážka v konečnej veľkosti, pretože malé detaily môžu zmiznúť vo vygenerovanom výstupe. + Vytvorte šumové textúry + Vytvorte procedurálne textúry pre pozadia, masky, prekrytia alebo experimenty + Vylaďte procedurálny výstup + Generovanie šumu vytvára nové obrázky z parametrov, takže malé zmeny môžu priniesť veľmi odlišné výsledky. Je to užitočné pre textúry, masky, prekrytia, zástupné symboly alebo testovanie kompresie s detailnými vzormi. + Pred doladením jemných detailov vyberte typ šumu a veľkosť výstupu. + Upravte mierku, intenzitu, farby alebo semená, kým vzor nebude zodpovedať cieľovému použitiu. + Uložte si užitočné výsledky a zapíšte si parametre, ak budete neskôr potrebovať znova vytvoriť textúru. + Značkovacie projekty + Ak je potrebné, aby anotácie zostali po zatvorení aplikácie upraviteľné, použite súbory projektu + Udržujte vrstvené úpravy znovu použiteľné + Značkovacie projektové súbory sú užitočné, keď môže byť potrebné neskôr zmeniť snímku obrazovky, diagram alebo inštrukciu. Exportované súbory PNG/JPEG sú konečné obrázky, zatiaľ čo súbory projektu zachovávajú upraviteľné vrstvy a stav úprav pre budúce úpravy. + Ak by anotácie, popisy alebo prvky rozloženia mali zostať upraviteľné, použite vrstvy značiek. + Ak očakávate ďalšie revízie, uložte alebo znovu otvorte súbor projektu pred exportovaním konečného obrázka. + Normálny obrázok exportujte len vtedy, keď ste pripravení zdieľať sploštenú konečnú verziu. + Šifrovať súbory + Pred odstránením akejkoľvek zdrojovej kópie chráňte súbory heslom a otestujte dešifrovanie + So šifrovanými výstupmi zaobchádzajte opatrne + Šifrovacie nástroje môžu chrániť súbory pomocou šifrovania založeného na heslách, ale nenahrádzajú zapamätanie si kľúča alebo zálohovanie. Šifrovanie je užitočné pri preprave a ukladaní, zatiaľ čo ZIP je lepší, keď je hlavným cieľom spájať niekoľko výstupov. + Najprv zašifrujte kópiu a ponechajte si originál, kým neotestujete, že dešifrovanie funguje s heslom, ktoré ste si uložili. + Použite správcu hesiel alebo iné bezpečné miesto pre kľúč; aplikácia za vás nemôže uhádnuť stratené heslo. + Zdieľajte zašifrované súbory iba s ľuďmi, ktorí majú aj heslo a vedia, ktorý nástroj alebo metóda ich má dešifrovať. + Usporiadajte nástroje + Zoskupujte, objednávajte, vyhľadávajte a pripínajte nástroje tak, aby hlavná obrazovka zodpovedala vášmu skutočnému pracovnému postupu + Zrýchlite hlavnú obrazovku + Nastavenia usporiadania nástrojov sa oplatí vyladiť, keď viete, ktoré nástroje používate najčastejšie. Zoskupovanie pomáha skúmať, vlastné poradie pomáha svalovej pamäti a obľúbené položky udržujú každodenné nástroje dostupné, aj keď sa celý zoznam zväčší. + Použite skupinový režim pri učení sa aplikácie alebo keď uprednostňujete nástroje usporiadané podľa typu úlohy. + Prepnite na vlastné poradie, keď už poznáte svoje časté nástroje a chcete menej zvitkov. + Nastavenia vyhľadávania a obľúbené položky používajte spolu pre zriedkavé nástroje, ktoré si pamätáte podľa názvu, ale nechcete, aby boli stále viditeľné. + Manipulujte s veľkými súbormi + Vyhnite sa pomalému exportu, zaťaženiu pamäte a neočakávane veľkému výstupu + Znížte prácu pred vývozom + Veľmi veľké obrázky, dlhé dávky a vysokokvalitné formáty môžu vyžadovať viac pamäte a času. Najrýchlejšou opravou je zvyčajne zníženie množstva práce pred najťažšou operáciou a nečakanie na dokončenie rovnakého nadrozmerného vstupu. + Zmeňte veľkosť veľkých obrázkov pred použitím ťažkých filtrov, OCR alebo konverzie PDF. + Najprv použite menšiu dávku na potvrdenie nastavení a potom spracujte zvyšok s rovnakou predvoľbou. + Nižšia kvalita alebo zmena formátu, keď je výstupný súbor väčší, ako sa očakávalo. + Cache a náhľady + Pochopte generované ukážky, čistenie vyrovnávacej pamäte a využitie úložiska po náročných reláciách + Udržujte úložný priestor a rýchlosť v rovnováhe + Generovanie ukážky a vyrovnávacia pamäť môžu zrýchliť opakované prehliadanie, ale náročné úpravy môžu zanechať dočasné údaje. Nastavenia vyrovnávacej pamäte pomáhajú, keď je aplikácia pomalá, úložný priestor je obmedzený alebo ukážky vyzerajú zastarané po mnohých experimentoch. + Pri prehliadaní veľkého množstva obrázkov je dôležitejšie ako minimalizácia dočasného úložiska ponechať náhľady povolené. + Vymažte vyrovnávaciu pamäť po veľkých dávkach, neúspešných experimentoch alebo dlhých reláciách s mnohými súbormi vo vysokom rozlíšení. + Ak uprednostňujete čistenie úložiska pred udržiavaním teplých ukážok medzi spustením, použite automatické vymazanie vyrovnávacej pamäte. + Upravte správanie obrazovky + Na sústredenú prácu používajte možnosti celej obrazovky, zabezpečeného režimu, jasu a systémovej lišty + Prispôsobte rozhranie situácii + Nastavenia obrazovky pomáhajú, keď upravujete na šírku, prezentujete súkromné ​​obrázky alebo potrebujete konzistentný jas. Na bežné používanie nie sú potrebné, ale riešia nepríjemné situácie ako kontrola QR, súkromné ​​náhľady, či stiesnené úpravy krajiny. + Ak je úprava priestoru dôležitejšia ako navigačný prehliadač, použite nastavenia na celú obrazovku alebo systémový panel. + Ak sa na snímkach obrazovky alebo ukážkach aplikácií nemajú zobrazovať súkromné ​​obrázky, použite zabezpečený režim. + Použite vynútenie jasu pre nástroje, kde je ťažké skontrolovať tmavé obrázky alebo QR kódy. + Udržujte transparentnosť + Zabráňte tomu, aby sa priehľadné pozadie zmenilo na biele, čierne alebo sploštené + Použite alfa výstup + Transparentnosť prežije len vtedy, keď vybraný nástroj a výstupný formát podporujú alfa. Ak sa exportované pozadie zmení na biele alebo čierne, problémom je zvyčajne výstupný formát, nastavenie výplne/pozadia alebo cieľová aplikácia, ktorá obrázok vyrovnala. + Pri exporte obrázkov, nálepiek, log alebo prekrytí s odstráneným pozadím vyberte PNG alebo WebP. + Vyhnite sa JPEG pre transparentný výstup, pretože neukladá alfa kanál. + Ak náhľad zobrazuje vyplnené pozadie, skontrolujte nastavenia súvisiace s farbou pozadia pre formáty alfa. + Opravte problémy so zdieľaním a importom + Ak sa súbory nezobrazujú alebo neotvárajú správne, použite nastavenia výberu a podporované formáty + Získajte súbor do aplikácie + Problémy s importom sú často spôsobené povoleniami poskytovateľa, nepodporovanými formátmi alebo správaním výberu. Súbory zdieľané z cloudových aplikácií a messengerov môžu byť dočasné, takže výber trvalého zdroja môže byť pri dlhších úpravách spoľahlivejší. + Skúste súbor otvoriť pomocou nástroja na výber systému namiesto odkazu na posledné súbory. + Ak formát nepodporuje jeden nástroj, najprv ho skonvertujte alebo otvorte špecifickejší nástroj. + Skontrolujte nastavenia nástroja na výber obrázkov a spúšťača, ak sa toky zdieľania alebo priame otváranie nástrojov správajú inak, ako sa očakávalo. + Potvrdenie odchodu + Vyhnite sa strate neuložených úprav, keď náhodou dôjde k gestám, stlačeniu chrbta alebo prepnutiu nástrojov + Chráňte nedokončenú prácu + Potvrdenie ukončenia nástroja je užitočné, keď používate navigáciu gestami, upravujete veľké súbory alebo často prepínate medzi aplikáciami. Pred opustením nástroja pridáva malú pauzu, aby sa nedokončené nastavenia a náhľady náhodou nestratili. + Povoľte potvrdenie, ak náhodné gestá späť niekedy zatvorili nástroj pred exportom. + Ak väčšinou robíte rýchle jednokrokové konverzie a dávate prednosť rýchlejšej navigácii, ponechajte ju zakázanú. + Používajte ho spolu s predvoľbami na dlhšie pracovné postupy, kde by prestavba nastavení bola otravná. + Pri nahlasovaní problémov používajte protokoly + Pred otvorením problému alebo kontaktovaním vývojára zozbierajte užitočné podrobnosti + Nahláste problémy s kontextom + Prehľadná správa s krokmi, verziou aplikácie, typom vstupu a protokolmi je oveľa jednoduchšia na diagnostiku. Protokoly sú najužitočnejšie hneď po reprodukovaní problému, pretože udržiavajú okolitý kontext čerstvý. + Poznamenajte si názov nástroja, presnú akciu a to, či sa to stane s jedným súborom alebo každým súborom. + Po zopakovaní problému otvorte denníky aplikácií a zdieľajte príslušný výstup denníka, keď je to možné. + Snímky obrazovky alebo vzorové súbory pripájajte iba vtedy, keď neobsahujú súkromné ​​informácie. + Spracovanie na pozadí bolo zastavené + Android nenechal včas spustiť upozornenie na spracovanie na pozadí. Toto je časové obmedzenie systému, ktoré nemožno spoľahlivo opraviť. Reštartujte aplikáciu a skúste to znova; môže pomôcť ponechanie aplikácie otvorenej a povolenie upozornení alebo práce na pozadí. + Preskočiť výber súboru + Otvárajte nástroje rýchlejšie, keď vstup zvyčajne pochádza zo zdieľania, schránky alebo predchádzajúcej obrazovky + Zrýchlite opakované spúšťanie nástrojov + Preskočiť výber súboru zmení prvý krok podporovaných nástrojov. Je to užitočné, keď zvyčajne zadávate nástroj s už vybratým obrázkom alebo z akcií zdieľania Androidu, ale môže to byť mätúce, ak očakávate, že každý nástroj si okamžite vyžiada súbor. + Povoľte ho iba vtedy, keď váš obvyklý postup už poskytuje zdrojový obrázok pred otvorením nástroja. + Počas učenia sa aplikácie ju nechajte deaktivovanú, pretože explicitné vyberanie uľahčuje pochopenie každého toku nástroja. + Ak sa nástroj otvorí bez zjavného vstupu, deaktivujte toto nastavenie alebo začnite v časti Zdieľať/Otvoriť pomocou, aby Android odovzdal súbor ako prvý. + Najprv otvorte editor + Preskočiť ukážku, keď má zdieľaný obrázok prejsť priamo do úprav + Ako prvú zastávku vyberte ukážku alebo úpravu + Otvoriť Upraviť namiesto ukážky je pre používateľov, ktorí už vedia, že chcú upraviť obrázok. Náhľad je bezpečnejší, keď kontrolujete súbory z chatu alebo cloudového úložiska; priama úprava je rýchlejšia pre snímky obrazovky, rýchle orezanie, anotácie a jednorazové opravy. + Povoľte priame úpravy, ak väčšina zdieľaných obrázkov okamžite vyžaduje orezanie, označenie, filtre alebo exportovanie. + Keď často kontrolujete metadáta, rozmery, priehľadnosť alebo kvalitu obrázka, ponechajte ukážku ako prvú. + Použite to spolu s obľúbenými položkami, aby sa zdieľané obrázky priblížili nástrojom, ktoré skutočne používate. + Prednastavené zadávanie textu + Namiesto opakovaného nastavovania ovládacích prvkov zadajte presné prednastavené hodnoty + Keď sú posuvníky pomalé, použite presné hodnoty + Prednastavené zadávanie textu pomáha, keď potrebujete presné čísla pre opakovanú prácu: veľkosti sociálnych obrázkov, okraje dokumentov, ciele kompresie, šírky čiar alebo iné hodnoty, ktoré je nepríjemné dosiahnuť pomocou gest. Je to užitočné aj na malých obrazovkách, kde je pohyb jemného posúvača ťažší. + Povoľte zadávanie textu, ak často opakovane používate presné veľkosti, percentá, hodnoty kvality alebo parametre nástroja. + Po prvom zadaní hodnoty uložte ako predvoľbu a potom ju znova použite namiesto toho, aby ste znova vytvorili rovnaké nastavenie. + Ponechajte ovládacie prvky gest pre hrubé vizuálne ladenie a zadané hodnoty pre prísne požiadavky na výstup. + Uložiť blízko originálu + Umiestnite vygenerované súbory vedľa zdrojových obrázkov, keď na kontexte priečinka záleží + Uchovávajte výstupy s ich zdrojmi + Uložiť do pôvodného priečinka je užitočné pre priečinky fotoaparátu, priečinky projektov, skeny dokumentov a dávky klienta, kde by upravená kópia mala zostať vedľa zdroja. Môže byť menej uprataný ako vyhradený výstupný priečinok, preto ho skombinujte s jasnými príponami názvov alebo vzormi. + Povoľte ho, keď už má každý zdrojový priečinok zmysel a výstupy by tam mali zostať zoskupené. + Používajte prípony názvov, predpony, časové pečiatky alebo vzory, aby sa upravené kópie dali ľahko oddeliť od originálov. + Vypnite ho pre zmiešané dávky, ak chcete, aby sa všetky vygenerované súbory zhromaždili v jednom exportnom priečinku. + Preskočte väčší výstup + Vyhnite sa ukladaniu konverzií, ktoré sa neočakávane stanú ťažšími ako zdroj + Chráňte šarže pred prekvapeniami zlej veľkosti + Niektoré konverzie zväčšia súbory, najmä snímky obrazovky PNG, už komprimované fotografie, vysokokvalitné WebP alebo obrázky so zachovanými metadátami. Povoliť preskočiť, ak je väčšie zabráni týmto výstupom nahradiť menší zdroj v pracovných tokoch, kde je hlavným cieľom zmenšenie veľkosti. + Povoľte ju, keď je export určený na kompresiu, zmenu veľkosti alebo prípravu súborov na limity nahrávania. + Skontrolujte vynechané súbory po dávke; môžu byť už optimalizované alebo môžu potrebovať iný formát. + Zakážte ho, keď na kvalite, priehľadnosti alebo kompatibilite formátu záleží viac ako na konečnej veľkosti súboru. + Schránka a odkazy + Ovládajte automatický vstup do schránky a ukážky odkazov pre rýchlejšie textové nástroje + Urobte automatické zadávanie predvídateľným + Nastavenia schránky a odkazov sú vhodné pre pracovné postupy QR, Base64, URL a text, ale automatické zadávanie by malo zostať zámerné. Ak sa zdá, že aplikácia vypĺňa polia sama, skontrolujte správanie pri prilepovaní do schránky; ak sú karty odkazov hlučné alebo pomalé, upravte správanie ukážky odkazu. + Povoľte automatické prilepenie do schránky, keď často kopírujete text, a okamžite otvorte zodpovedajúci nástroj. + Zakážte automatické prilepenie, ak sa obsah súkromnej schránky objaví v nástrojoch tam, kde ste ho neočakávali. + Vypnite ukážky odkazov, keď je načítanie ukážky pomalé, rušivé alebo zbytočné pre váš pracovný postup. + Kompaktné ovládacie prvky + Keď je na obrazovke málo miesta, použite hustejšie voliče a rýchle umiestnenie nastavení + Umiestnite viac ovládacích prvkov na malé obrazovky + Kompaktné voliče a rýchle umiestnenie nastavení pomáhajú na malých telefónoch, úprave na šírku a nástrojom s mnohými parametrami. Kompromisom je, že ovládacie prvky môžu byť hustejšie, takže je to najlepšie, keď už poznáte bežné možnosti. + Povoľte kompaktné selektory, keď sa vám dialógy alebo zoznamy možností zdajú príliš vysoké pre obrazovku. + Upravte stranu rýchlych nastavení, ak sú ovládacie prvky nástroja ľahšie dostupné z jednej strany displeja. + Vráťte sa k priestrannejším ovládacím prvkom, ak sa aplikáciu stále učíte alebo často vynechávate hustejšie možnosti. + Načítajte obrázky z webu + Prilepte webovú adresu stránky alebo obrázka, ukážte analyzované obrázky a potom uložte alebo zdieľajte vybrané výsledky + Webové obrázky používajte zámerne + Web Image Loading analyzuje zdroje obrázkov z poskytnutej adresy URL, zobrazí ukážku prvého dostupného obrázka a umožní vám uložiť alebo zdieľať vybrané analyzované obrázky. Niektoré stránky používajú dočasné, chránené odkazy alebo odkazy vygenerované skriptom, takže stránka, ktorá funguje v prehliadači, stále nemusí vracať žiadne použiteľné obrázky. + Prilepte priamu webovú adresu obrázka alebo webovú adresu stránky a počkajte, kým sa zobrazí zoznam analyzovaných obrázkov. + Ak stránka obsahuje niekoľko obrázkov a potrebujete len niektoré, použite ovládacie prvky rámčeka alebo výberu. + Ak sa nič nenačíta, vyskúšajte priamy odkaz na obrázok alebo si ho najprv stiahnite cez prehliadač; stránka môže blokovať analýzu alebo používať súkromné ​​odkazy. + Vyberte typ zmeny veľkosti + Pochopte explicitné, flexibilné, orezanie a prispôsobenie predtým, ako vnútite obrázok do nových rozmerov + Ovládajte tvar, plátno a pomer strán + Typ zmeny veľkosti rozhoduje o tom, čo sa stane, keď sa požadovaná šírka a výška nezhodujú s pôvodným pomerom strán. Je to rozdiel medzi natiahnutím pixelov, zachovaním proporcií, orezaním extra oblasti alebo pridaním pozadia na plátno. + Explicitné použite len vtedy, keď je skreslenie prijateľné alebo keď zdroj už má rovnaký pomer strán ako cieľ. + Použite Flexibilné na zachovanie proporcií zmenou veľkosti z dôležitej strany, najmä pre fotografie a zmiešané dávky. + Použite Orezať pre prísne rámy bez okrajov alebo Prispôsobiť, keď celý obrázok musí zostať viditeľný na pevnom plátne. + Vyberte režim mierky obrázka + Vyberte filter prevzorkovania, ktorý riadi ostrosť, plynulosť, rýchlosť a okrajové artefakty + Zmena veľkosti bez náhodnej mäkkosti + Režim mierky obrazu riadi spôsob výpočtu nových pixelov počas zmeny veľkosti. Jednoduché režimy sú rýchle a predvídateľné, zatiaľ čo pokročilé filtre dokážu lepšie zachovať detaily, ale môžu zaostriť okraje, vytvárať haluze alebo trvať dlhšie na veľkých obrázkoch. + Použite Basic alebo Bilinear na rýchlu každodennú zmenu veľkosti, keď výsledok potrebuje byť len menší a čistý. + Ak záleží na jemných detailoch, texte alebo vysokokvalitnom zmenšení, použite režimy Lanczos, Robidoux, Spline alebo EWA. + Použite Nearest pre pixel art, ikony a grafiku s pevnými okrajmi, kde by rozmazané pixely zničili vzhľad. + Mierka farebného priestoru + Rozhodnite, ako sa farby zmiešajú pri prevzorkovaní pixelov počas zmeny veľkosti + Udržujte prechody a okraje prirodzené + Mierka farebného priestoru mení matematiku použitú pri zmene veľkosti. Väčšina obrázkov je v predvolenom nastavení v poriadku, ale lineárne alebo percepčné medzery môžu spôsobiť, že prechody, tmavé okraje a nasýtené farby budú po silnej zmene mierky čistejšie. + Ponechajte predvolené, keď na rýchlosti a kompatibilite záleží viac ako na malých farebných rozdieloch. + Keď tmavé obrysy, snímky obrazovky používateľského rozhrania, prechody alebo farebné pásy po zmene veľkosti vyzerajú nesprávne, vyskúšajte lineárny alebo percepčný režim. + Porovnajte pred a po na skutočnej cieľovej obrazovke, pretože zmeny farebného priestoru môžu byť jemné a závislé od obsahu. + Obmedzte režimy zmeny veľkosti + Zistite, kedy je potrebné preskočiť, prekódovať alebo zmenšiť nadlimitné obrázky, aby sa zmestili + Vyberte, čo sa stane pri limite + Limit Resize nie je len nástroj na zmenšenie obrázka. Jeho režim rozhoduje o tom, čo aplikácia urobí, keď je zdroj už v rámci vašich limitov alebo keď iba jedna strana poruší pravidlo, čo je dôležité pre zmiešané priečinky a prípravu nahrávania. + Preskočiť použite, ak by sa snímky v rámci limitov mali ponechať nedotknuté a nemali by sa znova exportovať. + Použite Recode, keď sú rozmery prijateľné, ale stále potrebujete nový formát, správanie metadát, kvalitu alebo názov súboru. + Priblíženie použite, ak by sa obrázky, ktoré porušujú limity, mali proporcionálne zmenšiť, kým sa nezmestia do povoleného rámčeka. + Cieľový pomer strán + Vyhnite sa natiahnutým plochám, odrezaným okrajom a neočakávaným okrajom pri striktných výstupných veľkostiach + Pred zmenou veľkosti prispôsobte tvar + Šírka a výška sú len polovicou cieľovej veľkosti. Pomer strán rozhoduje o tom, či sa obrázok zmestí prirodzene, či je potrebné ho orezať alebo či je okolo neho potrebné plátno. Prvá kontrola tvaru zabráni najprekvapivejším výsledkom zmeny veľkosti. + Porovnajte zdrojový tvar s cieľovým tvarom pred výberom Explicit, Crop alebo Fit. + Najprv orežte, keď objekt môže stratiť ďalšie pozadie a cieľ si vyžaduje presný rám. + Použite možnosť Prispôsobiť alebo Flexibilná, ak každá časť pôvodného obrázka musí zostať viditeľná. + Upscale alebo normálna zmena veľkosti + Zistite, kedy pridávanie pixelov potrebuje AI a kedy stačí jednoduché škálovanie + Nezamieňajte veľkosť s detailom + Normálna zmena veľkosti mení rozmery interpoláciou pixelov; nemôže vymyslieť skutočný detail. Upscale AI môže vytvoriť ostrejšie vyzerajúce detaily, ale je pomalší a môže spôsobiť halucinácie textúry, takže správna voľba závisí od toho, či potrebujete kompatibilitu, rýchlosť alebo vizuálnu obnovu. + Použite normálnu zmenu veľkosti pre miniatúry, limity nahrávania, snímky obrazovky a jednoduché zmeny rozmerov. + Použite upscale AI, keď malý alebo mäkký obrázok potrebuje vyzerať lepšie pri väčšej veľkosti. + Po vylepšení AI porovnajte tváre, text a vzory, pretože vygenerované detaily môžu vyzerať presvedčivo, ale nesprávne. + Na poradí filtrov záleží + Aplikujte čistenie, farbu, ostrosť a konečnú kompresiu v zámernom poradí + Vybudujte čistejšiu sadu filtrov + Filtre nie sú vždy zameniteľné. Rozmazanie pred zaostrením, zvýšenie kontrastu pred OCR alebo zmena farby pred kompresiou môžu produkovať veľmi odlišný výstup z tých istých ovládacích prvkov. + Najprv orežte a otočte, aby neskôr filtre fungovali iba na pixeloch, ktoré zostanú. + Pred zaostrením alebo štylizovanými efektmi použite expozíciu, vyváženie bielej a kontrast. + Udržujte konečnú zmenu veľkosti a kompresiu blízko konca, aby podrobnosti v náhľade prežili export predvídateľne. + Opravte okraje pozadia + Po automatickom odstránení vyčistite haló, zvyšky tieňov, vlasy, diery a jemné obrysy + Aby výrezy vyzerali zámerne + Automatické masky často vyzerajú dobre na prvý pohľad, ale odhaľujú problémy na svetlom, tmavom alebo vzorovanom pozadí. Čistenie okrajov je rozdiel medzi rýchlym výrezom a opakovane použiteľnou nálepkou, logom alebo obrázkom produktu. + Ukážte výsledok na kontrastnom pozadí, aby ste odhalili haló a chýbajúce detaily okrajov. + Pred vymazaním okolitých zvyškov použite obnovenie na vlasy, rohy a priehľadné predmety. + Exportujte malý test na finálnej farbe pozadia, keď bude výrez umiestnený do dizajnu. + Čitateľné vodoznaky + Zviditeľnite značky na jasných, tmavých, rušných a orezaných obrázkoch bez toho, aby ste zničili fotografiu + Chráňte obrázok bez toho, aby ste ho skryli + Vodoznak, ktorý vyzerá dobre na jednom obrázku, môže na inom zmiznúť. Veľkosť, nepriehľadnosť, kontrast, umiestnenie a opakovanie by ste mali zvoliť pre celú dávku, nielen pre prvý náhľad. + Pred exportovaním všetkých súborov otestujte značku na najjasnejších a najtmavších obrázkoch v dávke. + Pre veľké opakujúce sa značky použite nižšiu nepriehľadnosť a pre malé rohové značky použite silnejší kontrast. + Udržujte dôležité tváre, text a podrobnosti o produkte jasné, pokiaľ značka nemá brániť opätovnému použitiu. + Rozdeľte jeden obrázok na dlaždice + Vystrihnite jeden obrázok podľa riadkov, stĺpcov, vlastných percent, formátu a kvality + Pripravte si mriežky a objednané kusy + Rozdelenie obrazu vytvára viacero výstupov z jedného zdrojového obrazu. Dokáže rozdeliť podľa počtu riadkov a stĺpcov, upraviť percentá riadkov alebo stĺpcov a ukladať kusy s vybratým výstupným formátom a kvalitou, čo je užitočné pre mriežky, rezy strán, panorámy a usporiadané sociálne príspevky. + Vyberte zdrojový obrázok a potom nastavte počet riadkov a stĺpcov, ktoré potrebujete. + Upravte percentá riadkov alebo stĺpcov, keď by kusy nemali mať rovnakú veľkosť. + Pred uložením vyberte výstupný formát a kvalitu, aby každý vygenerovaný kus používal rovnaké pravidlá exportu. + Vystrihnite pásy obrázkov + Odstráňte zvislé alebo vodorovné oblasti a zvyšné časti spojte späť + Odstráňte oblasť bez zanechania medzery + Orezanie obrázka sa líši od bežného orezania. Môže odstrániť vybraný vertikálny alebo horizontálny pás a potom zlúčiť zostávajúce časti obrázka dohromady. Inverzný režim namiesto toho zachová vybratú oblasť a použitie oboch inverzných smerov zachová vybratý obdĺžnik. + Použite vertikálne rezanie pre bočné oblasti, stĺpy alebo stredné pásy; použite horizontálne rezanie pre bannery, medzery alebo riadky. + Povoľte inverziu na osi, keď chcete ponechať vybratú časť namiesto jej odstránenia. + Ukážka okrajov po orezaní, pretože zlúčený obsah môže vyzerať náhle, keď odstránená oblasť pretína dôležité detaily. + Vyberte výstupný formát + Vyberte JPEG, PNG, WebP, AVIF, JXL alebo PDF na základe obsahu a cieľa + Nechajte cieľ rozhodnúť o formáte + Najlepší formát závisí od toho, čo obrázok obsahuje a kde sa bude používať. Fotografie, snímky obrazovky, priehľadné prvky, dokumenty a animované súbory zlyhajú rôznymi spôsobmi, keď sú nútené do nesprávneho formátu. + Ak sa nevyžaduje priehľadnosť a presné pixely, použite JPEG pre širokú kompatibilitu fotografií. + Použite PNG na ostré snímky obrazovky, zachytenie používateľského rozhrania, priehľadnú grafiku a bezstratové úpravy. + Použite WebP, AVIF alebo JXL, keď záleží na kompaktnom modernom výstupe a prijímajúca aplikácia ho podporuje. + Extrahujte zvukové kryty + Uložte vložený obal albumu alebo dostupné mediálne snímky zo zvukových súborov ako obrázky PNG + Vytiahnite umelecké diela zo zvukových súborov + Audio Covers používa metadáta médií Android na čítanie vložených umeleckých diel zo zvukových súborov. Keď vložená kresba nie je k dispozícii, retriever sa môže vrátiť späť k mediálnemu rámu, ak ho môže poskytnúť Android; ak neexistuje ani jeden, nástroj oznámi, že neexistuje žiadny obrázok na extrahovanie. + Otvorte zvukové obaly a vyberte jeden alebo viac zvukových súborov s očakávaným obalom. + Pred uložením alebo zdieľaním skontrolujte extrahovaný obal, najmä pokiaľ ide o súbory z aplikácií na streamovanie alebo nahrávanie. + Ak sa nenájde žiadny obrázok, zvukový súbor pravdepodobne nemá vložený obal alebo Android nemohol odhaliť použiteľný rám. + Metadáta dátumov a polohy + Rozhodnite sa, čo zachovať, keď záleží na čase snímania, ale údaje GPS alebo zariadenia by nemali uniknúť + Oddeľte užitočné metadáta od súkromných metadát + Metaúdaje nie sú všetky rovnako rizikové. Dátumy zachytenia môžu byť užitočné pri archívoch a triedení, zatiaľ čo GPS, sériové polia zariadenia, softvérové ​​značky alebo polia vlastníka môžu odhaliť viac, ako bolo zamýšľané. + Uchovávajte značky dátumu a času, keď záleží na chronologickom poradí albumov, skenov alebo histórie projektov. + Pred verejným zdieľaním alebo odosielaním obrázkov neznámym osobám odstráňte značky GPS a zariadenia na identifikáciu. + Exportujte vzorku a znova skontrolujte EXIF, keď sú požiadavky na ochranu osobných údajov prísne. + Vyhnite sa kolíziám súborov + Chráňte dávkové výstupy, keď veľa súborov zdieľa názvy ako image.jpg alebo screenshot.png + Urobte každý názov výstupu jedinečný + Duplicitné názvy súborov sú bežné, keď obrázky pochádzajú z messengerov, snímok obrazovky, cloudových priečinkov alebo viacerých kamier. Dobrý vzor zabraňuje náhodnej výmene a udržuje výstupy sledovateľné späť k ich zdrojom. + Ak má byť upravená kópia rozpoznateľná, uveďte pôvodný názov a príponu. + Pri spracovaní veľkých zmiešaných dávok pridajte sekvenciu, časovú pečiatku, predvoľbu alebo rozmery. + Vyhnite sa prepisovaniu pracovných postupov, kým neoveríte, že vzor pomenovania nemôže kolidovať. + Uložiť alebo zdieľať + Vyberte si medzi trvalým súborom a dočasným odovzdaním do inej aplikácie + Exportujte so správnym zámerom + Uložiť a zdieľať môže vyzerať podobne, ale riešia iné problémy. Uložením sa vytvorí súbor, ktorý môžete nájsť neskôr; zdieľanie odošle vygenerovaný výsledok cez Android do inej aplikácie a nemusí zanechať trvalú lokálnu kópiu. + Použite možnosť Uložiť, keď výstup musí zostať v známom priečinku, musí byť zálohovaný alebo opätovne použitý neskôr. + Použite Zdieľať na rýchle správy, e-mailové prílohy, uverejňovanie na sociálnych sieťach alebo odosielanie výsledku inému editorovi. + Najprv uložte, keď je prijímajúca aplikácia nespoľahlivá alebo keď by bolo nepríjemné znovu vytvoriť neúspešné zdieľanie. + Veľkosť a okraje stránky PDF + Pred vytvorením dokumentu vyberte veľkosť papiera, prispôsobenie a priestor na dýchanie + Urobte stránky s obrázkami, ktoré sa dajú vytlačiť a budú čitateľné + Z obrázkov sa môžu stať nepríjemné stránky PDF, ak ich tvar nezodpovedá zvolenej veľkosti papiera. Okraje, režim prispôsobenia a orientácia strany rozhodujú o tom, či bude obsah orezaný, malý, roztiahnutý alebo či sa bude pohodlne čítať. + Ak je možné PDF vytlačiť alebo odoslať do formulára, použite skutočnú veľkosť papiera. + Na skenovanie a snímky obrazovky používajte okraje, aby text nepriliehal k okraju strany. + Ak majú zdrojové obrázky zmiešanú orientáciu, ukážte spolu strany na výšku a na šírku. + Vyčistite skeny + Zlepšite okraje papiera, tiene, kontrast, otočenie a čitateľnosť pred PDF alebo OCR + Pred archiváciou pripravte strany + Skenovanie možno technicky zachytiť, ale stále je ťažké ho prečítať. Malé kroky čistenia pred exportom PDF sú často dôležitejšie ako nastavenia kompresie, najmä v prípade potvrdení, rukou písaných poznámok a strán so slabým osvetlením. + Opravte perspektívu a odrežte okraje stola, prsty, tiene a neporiadok na pozadí. + Opatrne zvyšujte kontrast, aby bol text jasnejší bez toho, aby ste zničili pečiatky, podpisy alebo značky ceruzkou. + OCR spustite až vtedy, keď je strana vzpriamená a čitateľná, potom manuálne overte dôležité čísla. + Komprimujte, odfarbujte alebo opravujte súbory PDF + Použite operácie PDF s jedným súborom na ľahšie, tlačiteľné alebo prerobené kópie dokumentov + Vyberte operáciu čistenia PDF + Nástroje PDF obsahujú samostatné operácie na kompresiu, prevod v odtieňoch sivej a opravu. Kompresia a odtiene šedej fungujú hlavne prostredníctvom vložených obrázkov, zatiaľ čo oprava prestavuje štruktúru PDF; žiadna z nich by sa nemala považovať za zaručenú opravu pre každý dokument. + Komprimovať PDF použite, keď dokument obsahuje obrázky a veľkosť súboru záleží na zdieľaní. + Odtiene sivej použite vtedy, keď by sa mal dokument ľahšie tlačiť alebo ak nepotrebujete farebné obrázky. + Keď sa dokument neotvorí alebo má poškodenú vnútornú štruktúru, použite možnosť Opraviť PDF na kópii a potom skontrolujte prestavaný súbor. + Extrahujte obrázky z PDF + Obnovte vložené obrázky PDF a zabaľte extrahované výsledky do archívu + Uložte zdrojové obrázky z dokumentov + Extrahovať obrázky skenuje PDF, či neobsahuje vložené obrazové objekty, preskakuje duplikáty, ak je to možné, a ukladá obnovené obrázky do vygenerovaného archívu ZIP. Extrahuje iba obrázky, ktoré sú skutočne vložené do PDF, nie text, vektorové kresby alebo každú viditeľnú stranu ako snímku obrazovky. + Použite Extrahovať obrázky, keď potrebujete obrázky uložené v PDF, ako sú fotografie z katalógu alebo naskenované aktíva. + Ak potrebujete celé strany, vrátane textu a rozloženia, použite namiesto toho PDF do obrázkov alebo extrakciu strán. + Ak nástroj nehlási žiadne vložené obrázky, stránka môže obsahovať textový/vektorový obsah alebo obrázky nemusia byť uložené ako extrahovateľné objekty. + Metadáta, sploštenie a tlačový výstup + Úmyselne upravte vlastnosti dokumentu, rastrujte strany alebo pripravte vlastné rozloženia tlače + Vyberte akciu posledného dokumentu + Metadáta PDF, sploštenie a príprava tlače riešia rôzne problémy v záverečnej fáze. Metadáta ovládajú vlastnosti dokumentu, Flatten PDF rastruje strany na menej upraviteľný výstup a Print PDF pripraví nové rozloženie s veľkosťou strany, orientáciou, okrajom a ovládacími prvkami pre počet strán na hárok. + Metadáta použite vtedy, keď záleží na autorovi, názve, kľúčových slovách, producentovi alebo vyčistení súkromia. + Zlúčiť PDF použite vtedy, keď by malo byť ťažšie upravovať viditeľný obsah stránky ako samostatné objekty PDF. + Použite Print PDF, keď potrebujete vlastnú veľkosť strany, orientáciu, okraje alebo viac strán na hárok. + Pripravte obrázky na OCR + Pred požiadaním OCR na čítanie textu použite orezanie, otočenie, kontrast, odšumenie a mierku + Dajte OCR čistejšie pixely + Chyby OCR často pochádzajú zo vstupného obrazu a nie zo stroja OCR. Krivé stránky, nízky kontrast, rozmazanie, tiene, drobný text a zmiešané pozadie znižujú kvalitu rozpoznávania. + Orežte textovú oblasť a otočte obrázok tak, aby boli čiary pred rozpoznaním vodorovné. + Zvýšte kontrast alebo preveďte na čistejšiu čiernobielu len vtedy, keď sa písmená dajú ľahšie čítať. + Mierne zmeňte veľkosť drobného textu smerom nahor, ale vyhnite sa agresívnemu doostrovaniu, ktoré vytvára falošné okraje. + Skontrolujte obsah QR + Skôr ako dôverujete kódu, skontrolujte odkazy, poverenia Wi-Fi, kontakty a akcie + S dekódovaným textom zaobchádzajte ako s nedôveryhodným vstupom + QR kód môže skrývať adresu URL, kartu kontaktu, heslo siete Wi-Fi, udalosť v kalendári, telefónne číslo alebo obyčajný text. Viditeľný štítok v blízkosti kódu sa nemusí zhodovať so skutočným užitočným zaťažením, preto si predtým, ako naň zareagujete, skontrolujte dekódovaný obsah. + Pred otvorením skontrolujte celú dekódovanú adresu URL, najmä skrátené alebo neznáme domény. + Buďte opatrní s kódmi Wi-Fi, kontaktmi, kalendárom, telefónom a SMS, pretože môžu spustiť citlivé akcie. + Skopírujte obsah najskôr, keď ho potrebujete overiť inde, namiesto toho, aby ste ho okamžite otvorili. + Generujte QR kódy + Vytvorte kódy z obyčajného textu, adries URL, Wi-Fi, kontaktov, e-mailu, telefónu, SMS a údajov o polohe + Vytvorte správne užitočné zaťaženie QR + QR obrazovka dokáže generovať kódy zo zadaného obsahu, ako aj skenovať existujúce. Štruktúrované typy QR pomáhajú kódovať údaje vo formáte, ktorému rozumejú iné aplikácie, ako sú prihlasovacie údaje Wi-Fi, kontaktné karty, e-mailové polia, telefónne čísla, obsah SMS, adresy URL alebo geografické súradnice. + Vyberte si obyčajný text pre jednoduché poznámky alebo použite štruktúrovaný typ, keď sa má kód otvoriť, ako Wi-Fi, kontakt, adresa URL, e-mail, telefón, SMS alebo údaje o polohe. + Pred zdieľaním vygenerovaného kódu skontrolujte každé pole, pretože viditeľná ukážka odráža iba zadané užitočné zaťaženie. + Otestujte uložený QR kód skenerom, keď bude vytlačený, zverejnený alebo použitý inými ľuďmi. + Vyberte režim kontrolného súčtu + Vypočítajte hash zo súborov alebo textu, porovnajte jeden súbor alebo hromadne skontrolujte veľa súborov + Overujte obsah, nie vzhľad + Nástroje kontrolného súčtu majú samostatné karty na hašovanie súborov, hašovanie textu, porovnávanie súboru so známym hašovaním a porovnávanie dávok. Kontrolný súčet dokazuje identitu bajtu za bajtom pre vybraný algoritmus; nedokazuje to, že dva obrázky vyzerajú len podobne. + Použite Vypočítať pre súbory a Text Hash pre zadané alebo prilepené textové údaje. + Použite Porovnať, keď vám niekto poskytne očakávaný kontrolný súčet pre jeden súbor. + Použite dávkové porovnanie, keď veľa súborov potrebuje hash alebo overenie v jednom prechode, a potom udržujte vybraný algoritmus konzistentný. + Súkromie schránky + Používajte funkcie automatickej schránky bez náhodného odhalenia súkromných skopírovaných údajov + Ponechajte skopírovaný obsah zámerne + Automatizácia schránky je pohodlná, ale skopírovaný text môže obsahovať heslá, odkazy, adresy, tokeny alebo súkromné ​​poznámky. Zadávanie zo schránky považujte za funkciu rýchlosti, ktorá by mala zodpovedať vašim zvykom a očakávaniam ochrany súkromia. + Zakážte automatické používanie schránky, ak sa v nástrojoch neočakávane objaví súkromný text. + Ukladanie len do schránky použite iba vtedy, ak chcete, aby sa výsledok neukladal. + Po manipulácii s citlivým textom, údajmi QR alebo dátami Base64 vymažte alebo vymeňte schránku. + Farebné formáty + Pred vložením inam pochopte hodnoty HEX, RGB, HSV, alfa a skopírované hodnoty farieb + Skopírujte formát, ktorý cieľ očakáva + Rovnakú viditeľnú farbu je možné napísať niekoľkými spôsobmi. Nástroj na návrh, pole CSS, hodnota farby pre Android alebo editor obrázkov môžu očakávať iné poradie, spracovanie alfa alebo farebný model. + HEX použite pri vkladaní do polí webu, dizajnu alebo tém, ktoré očakávajú kompaktné farebné kódy. + Ak potrebujete manuálne upraviť kanály, jas, sýtosť alebo odtieň, použite RGB alebo HSV. + Ak na transparentnosti záleží, skontrolujte alfa kanál, pretože niektoré ciele ho ignorujú alebo používajú iné poradie. + Prehľadávať knižnicu farieb + Vyhľadávajte pomenované farby podľa názvu alebo HEX a udržujte obľúbené v hornej časti + Nájdite opätovne použiteľné pomenované farby + Knižnica farieb načíta pomenovanú kolekciu farieb, triedi ju podľa názvu, filtruje podľa názvu farby alebo HEX hodnoty a ukladá obľúbené farby, takže dôležité položky sú ľahšie dostupné. Je to užitočné, keď potrebujete skutočnú pomenovanú farbu namiesto vzorkovania z obrázka. + Vyhľadávajte podľa názvu farby, keď poznáte rodinu, ako je červená, modrá, bridlicová alebo mätová. + Hľadajte podľa HEX, keď už máte číselnú farbu a chcete nájsť zodpovedajúce alebo blízke pomenované položky. + Označte časté farby ako obľúbené, aby sa pri prehliadaní posunuli pred všeobecnú knižnicu. + Použite sieťové kolekcie gradientov + Stiahnite si dostupné zdroje sieťového prechodu a zdieľajte vybrané obrázky + Použite vzdialené prvky prechodu siete + Mesh Gradients dokáže načítať vzdialenú kolekciu zdrojov, stiahnuť chýbajúce obrázky prechodov, zobraziť priebeh sťahovania a zdieľať vybrané prechody. Dostupnosť závisí od sieťového prístupu a vzdialeného úložiska zdrojov, takže prázdny zoznam zvyčajne znamená, že zdroje ešte neboli dostupné. + Ak chcete mať hotové obrázky s prechodmi zo siete, otvorte prechody siete z nástrojov prechodu. + Počkajte na načítanie zdroja alebo priebeh sťahovania a až potom predpokladajte, že kolekcia je prázdna. + Vyberte a zdieľajte prechody, ktoré potrebujete, alebo sa vráťte do aplikácie Gradient Maker, keď chcete manuálne vytvoriť vlastný prechod. + Rozlíšenie generovaného majetku + Pred generovaním prechodov, šumu, tapiet, umenia podobného SVG alebo textúr vyberte veľkosť výstupu + Vygenerujte vo veľkosti, ktorú potrebujete + Vygenerované obrázky nemajú originál fotoaparátu, na ktorý by sa dalo vrátiť. Ak je výstup príliš malý, neskoršie škálovanie môže zjemniť vzory, posunúť detaily alebo zmeniť spôsob rozloženia a kompresie aktív. + Najprv nastavte rozmery pre konečný prípad použitia, ako je tapeta, ikona, pozadie, tlač alebo prekrytie. + Väčšie veľkosti použite pre textúry, ktoré možno orezať, priblížiť alebo znova použiť v niekoľkých rozloženiach. + Exportujte testovacie verzie pred obrovskými výstupmi, pretože procedurálne detaily môžu zmeniť správanie kompresie. + Exportovať aktuálne tapety + Získajte dostupné tapety Domov, Zámok a vstavané tapety zo zariadenia + Uložte alebo zdieľajte nainštalované tapety + Tapety Export číta tapety, ktoré Android vystavuje prostredníctvom systémových rozhraní API pre tapety. V závislosti od zariadenia, verzie Androidu, povolení a variantu zostavy môžu byť plochy, zámky alebo vstavané tapety k dispozícii samostatne alebo môžu chýbať. + Otvorte Tapety Exportovať a načítajte tapety, ktoré systém umožňuje aplikácii čítať. + Vyberte dostupné položky Domov, Zámok alebo vstavané tapety, ktoré chcete uložiť, skopírovať alebo zdieľať. + Ak tapeta chýba alebo funkcia nie je k dispozícii, je to zvyčajne obmedzenie systému, povolenia alebo variantu zostavenia, a nie nastavenie úprav. + Rohy animácie plynu + Kopírovať farbu ako + HEX + meno + Model rohože na výšku pre rýchle vystrihovanie osôb s jemnejším vlasom a hranami. + Rýchle vylepšenie pri slabom osvetlení pomocou odhadu krivky Zero-DCE++. + Restormer model obnovy obrazu na zníženie dažďových pruhov a artefaktov z vlhkého počasia. + Model nedeformovania dokumentu, ktorý predpovedá súradnicovú mriežku a premapuje zakrivené strany na plochejšie skenovanie. + Mierka trvania pohybu + Ovláda rýchlosť animácie aplikácie: 0 deaktivuje pohyb, 1 je normálna, vyššie hodnoty spomaľujú animácie + Zobraziť najnovšie nástroje + Zobrazte rýchly prístup k naposledy použitým nástrojom na hlavnej obrazovke + Štatistiky používania + Preskúmajte spustenia aplikácií, spustenia nástrojov a najpoužívanejšie nástroje + Otvorí sa aplikácia + Nástroj sa otvorí + Posledný nástroj + Úspešné uloženia + Séria aktivít + Údaje uložené + Špičkový formát + Použité nástroje + %1$d sa otvorí • %2$s + Zatiaľ žiadne štatistiky používania + Otvorte niekoľko nástrojov a automaticky sa tu zobrazia + Štatistiky používania sú uložené iba lokálne vo vašom zariadení. ImageToolbox ich používa iba na zobrazenie vašej osobnej aktivity, obľúbených nástrojov a prehľadov uložených údajov + editor upraviť fotografiu obrázok retuš upraviť + zmeniť veľkosť konvertovať mierka rozlíšenie rozmery šírka výška jpg jpeg png webp komprimovať + komprimovať veľkosť hmotnosť bajtov mb kb znížiť kvalitu optimalizovať + crop trim cut pomer strán + efekt filtra korekcia farieb upraviť lut maska + kresliť štetec ceruzka anotovať značku + šifra šifrovať dešifrovať heslo tajné + odstraňovač pozadia vymazať odstrániť výrez transparentný alfa + náhľad prehliadača galéria skontrolovať otvoriť + steh zlúčenie kombinovať panoráma dlhá snímka obrazovky + url web na stiahnutie obrázok internetového odkazu + zberač kvapkadlo vzorka hex rgb farba + vzorkovník farieb palety extrakt dominantný + exif metadata privacy remove strip clean gps location + porovnať rozdiel rozdielu pred po + limit resize max min megapixely rozmery svorka + nástroje na stránky dokumentov pdf + ocr text rozpoznať extrahovať skenovanie + gradient farby pozadia + prekrytie textu pečiatky loga vodoznaku + animované obrázky na konverziu gif animácií + apng animácia animované png konvertované snímky + zip archív komprimovať rozbaliť súbory + jxl jpeg xl previesť obrazový formát jpg + svg vector trace convert xml + format convert jpg jpeg png webp heic avif jxl bmp + skener dokumentov skenovanie papiera perspektívny výrez + skenovanie qr skenerom čiarových kódov + stacking overlay average medián astrofotografie + rozdelené dlaždice mriežka slice časti + color convert hex rgb hsl hsv cmyk lab + webp previesť formát animovaného obrázka + generátor šumu náhodné zrno textúry + rozloženie mriežky koláže kombinovať + vrstvy značiek anotujú úpravu prekrytia + base64 kódovať dekódovať údaje uri + kontrolný súčet hash md5 sha sha1 sha256 overiť + sieťový gradient na pozadí + úprava exif metadát gps dátumová kamera + rezané mriežkové kusy dlaždíc + audio obal albumu hudobné metadáta + pozadie exportu tapety + znaky ascii text art + ai ml neurálny vylepšený segment + paleta materiálov knižnice farieb s názvom farby + shader glsl fragment efektové štúdio + pdf zlúčiť spojiť sa + pdf rozdelené na samostatné strany + pdf otočenie orientácie strán + pdf zmeniť usporiadanie zmeniť poradie stránok triediť + pdf stránkovanie s číslami strán + pdf ocr text rozpoznávať vyhľadávateľný extrakt + pdf text loga vodoznaku + pdf podpis podpisu kreslenie + pdf chrániť heslom šifrovať zámok + pdf odomknúť heslo dešifrovať odstrániť ochranu + pdf komprimovať znížiť veľkosť optimalizovať + pdf čiernobiele jednofarebné v odtieňoch sivej + pdf opraviť opraviť obnoviť rozbité + Vlastnosti metadát pdf Názov autora exif + pdf odstrániť odstrániť stránky + pdf okraje orezania + pdf vyrovnať anotácie formuje vrstvy + pdf extrahovať obrázky obrázky + komprimovať archív zip vo formáte pdf + tlačiareň na tlač pdf + pdf náhľad prehliadač čítať + obrázky do pdf previesť jpg jpeg png dokument + pdf do obrázkov exportovať stránky jpg png + pdf anotácie komentáre odstrániť čisté + Neutrálny variant + Chyba + Vržený tieň + Výška zubov + Horizontálny rozsah zubov + Vertikálny rozsah zubov + Horný okraj + Pravý okraj + Spodný okraj + Ľavý okraj + Roztrhaný okraj + Obnoviť štatistiky používania + Nástroj sa otvorí, uložia sa štatistiky, vynuluje sa pruh, uložené údaje a najvyšší formát. Otvorenia aplikácie zostanú nezmenené. + Zvuk + Súbor + Exportovať profily + Uložiť aktuálny profil + Odstrániť exportný profil + Chcete odstrániť exportný profil \\"%1$s\\"? + Hranica kreslenia + Zobrazte tenký okraj okolo kresliaceho a mazacieho plátna + Vlnitý + Zaoblené prvky používateľského rozhrania s jemným zvlneným okrajom + Naberačka + Zárez + Zaoblené rohy vyrezané dovnútra + Stupňovité rohy s dvojitým rezom + Zástupný symbol + Použite {filename} na vloženie pôvodného súboru bez prípony + Vzplanutie + Základná suma + Množstvo prsteňa + Množstvo lúča + Šírka prsteňa + Skreslená perspektíva + Vzhľad a dojem Java + Strih + X uhol + a uhol + Zmeňte veľkosť výstupu + Kvapka vody + Vlnová dĺžka + Fáza + Vysoký priesmyk + Farebná maska + Chrome + Rozpustiť + Mäkkosť + Spätná väzba + Rozostrenie objektívu + Nízky vstup + Vysoký vstup + Nízky výkon + Vysoký výkon + Svetelné efekty + Výška nárazu + Nárazová mäkkosť + Zvlnenie + X amplitúda + Y amplitúda + X vlnová dĺžka + Y vlnová dĺžka + Adaptívne rozostrenie + Generovanie textúry + Vytvorte rôzne procedurálne textúry a uložte ich ako obrázky + Typ textúry + Zaujatosť + Čas + Lesknite sa + Disperzia + Vzorky + Koeficient uhla + Gradientový koeficient + Typ mriežky + Výkon na diaľku + Mierka Y + Fuzziness + Typ základu + Faktor turbulencie + Škálovanie + Iterácie + Prstene + Brúsený kov + Žieraviny + Bunkový + Šachovnica + fBm + Mramor + Plazma + Prikrývka + Drevo + náhodný + Štvorcový + Šesťhranné + Osemhranné + Trojuholníkový + Ridged + Hluk VL + SC Hluk + Nedávne + Zatiaľ žiadne nedávno použité filtre + generátor textúr kartáčovaný kov žieraviny bunkový šachovnica mramor plazma deka drevo tehla maskovanie bunka oblak prasklina tkanina lístie voštinový ľad láva hmlovina papier hrdza piesok dym kameň terén topografia vlnenie vody + Tehla + Kamufláž + Cell + Cloud + Crack + Tkanina + Lístie + Voštinový + Ľad + Láva + Hmlovina + Papier + Hrdza + Piesok + Dym + Kameň + Terén + Topografia + Water Ripple + Pokročilé drevo + Šírka malty + Nepravidelnosť + Skosenie + Drsnosť + Prvý prah + Druhý prah + Tretí prah + Mäkkosť okrajov + Šírka okraja + Pokrytie + Detail + Hĺbka + Vetvenie + Horizontálne závity + Vertikálne vlákna + Fuzz + Žily + Osvetlenie + Šírka trhliny + Mráz + Prietok + Kôrka + Hustota oblačnosti + hviezdy + Hustota vlákna + Pevnosť vlákna + Škvrny + Korózia + Pitting + Vločky + Frekvencia duny + Uhol vetra + Vlnky + Wisps + Váha žíl + Hladina vody + Horská úroveň + Erózia + Úroveň snehu + Počet riadkov + Hrúbka čiary + Tieňovanie + Farba pórov + Farba malty + Tmavá tehlová farba + Farba tehla + Farba zvýraznenia + Tmavá farba + Lesná farba + Farba zeme + Piesková farba + Farba pozadia + Farba bunky + Farba okraja + Farba oblohy + Farba tieňa + Svetlá farba + Farba povrchu + Farba variácie + Farba praskliny + Farba osnovy + Farba útku + Tmavá farba listov + Farba listu + Farba okraja + Medová farba + Sýta farba + Farba ľadu + Mrazivá farba + Farba kôry + Umyť farbu + Žiarivá farba + Farba priestoru + Fialová farba + Modrá farba + Základná farba + Farba vlákna + Farba škvrny + Farba kovu + Tmavá hrdzavá farba + Farba hrdze + Oranžová farba + Farba dymu + Farba žíl + Nížinatá farba + Vodová farba + Skalná farba + Farba snehu + Nízka farba + Vysoká farba + Farba čiary + Plytká farba + Tráva + Špina + Kožené + Betón + Asfalt + Moss + Oheň + Aurora + Ropná škvrna + Akvarel + Abstraktný tok + Opál + Damašková oceľ + Blesk + Zamatová + Atramentové mramorovanie + Holografická fólia + Bioluminiscencia + Kozmický vír + Lávová lampa + Horizont udalostí + Fraktálny kvet + Chromatický tunel + Eclipse Corona + Divný priťahovač + Ferrofluid Crown + Supernova + Iris + Pávie pierko + Nautilus Shell + Prstencová planéta + Hustota čepele + Dĺžka čepele + Vietor + Patchiness + Zhluky + Vlhkosť + kamienky + Vrásky + Póry + Agregátne + Trhliny + Tar + Opotrebenie + Špeky + Vlákna + Frekvencia plameňa + Dym + Intenzita + Stuhy + kapely + Iridescence + Tma + Kvitne + Pigment + Hrany + Papier + Difúzia + Symetria + Ostrosť + Hra farieb + Mliečnosť + Vrstvy + Skladanie + poľský + Pobočky + Smer + Sheen + Záhyby + Operenie + Rovnováha atramentu + Spektrum + Crinkles + Difrakcia + Arms + Twist + Žiarenie jadra + guličky + Naklonenie disku + Veľkosť horizontu + Šírka disku + Lensing + Okvetné lístky + Curl + Filigránsky + Fazety + Zakrivenie + Veľkosť Mesiaca + Veľkosť koróny + Lúče + Diamantový prsteň + Lobes + Hustota obežnej dráhy + Hrúbka + Hroty + Dĺžka hrotu + Veľkosť tela + Kovové + Polomer nárazu + Šírka plášťa + Vyhodené + Veľkosť zrenice + Veľkosť dúhovky + Farebná variácia + Catchlight + Veľkosť oka + Hustota ostňov + Zákruty + komory + Otvorenie + Ridges + Perleť + Veľkosť planéty + Naklonenie krúžku + Šírka prsteňa + Atmosféra + Farba špiny + Tmavá farba trávy + Farba trávy + Farba hrotu + Tmavá farba zeme + Suchá farba + Farba kamienková + Farba kože + Farba betónu + Farba dechtu + Farba asfaltu + Farba kameňa + Farba prachu + Farba pôdy + Tmavá machová farba + Farba machu + Červená farba + Farba jadra + Zelená farba + Azúrová farba + Purpurová farba + Zlatá farba + Farba papiera + Pigmentová farba + Sekundárna farba + Prvá farba + Druhá farba + Ružová farba + Farba tmavej ocele + Farba ocele + Farba svetlá oceľ + Oxidová farba + Halo farba + Farba skrutky + Zamatová farba + Farba lesku + Modrá farba atramentu + Farba červeného atramentu + Tmavá farba atramentu + Strieborná farba + Žltá farba + Farba tkaniva + Farba disku + Horúca farba + Farba šošovky + Vonkajšia farba + Vnútorná farba + Korónová farba + Studená farba + Teplá farba + Farba mrakov + Farba plameňa + Farba peria + Farba škrupiny + Perleťová farba + Farba planéty + Farba prsteňa + Po uložení odstráňte pôvodné súbory + Vymažú sa iba úspešne spracované zdrojové súbory + Pôvodné súbory boli odstránené: %1$d + Pôvodné súbory sa nepodarilo odstrániť: %1$d + Pôvodné súbory boli odstránené: %1$d, zlyhalo: %2$d + Listové gestá + Povoliť presúvanie hárkov rozbalených možností + Chroma subsampling + Bitová hĺbka + Bezstratový + %1$d-bit + Dávkové premenovanie + Premenujte viacero súborov pomocou vzorov názvov súborov + dávkové premenovanie súborov názov súboru vzor sekvencia dátum prípona + Vyberte súbory na premenovanie + Vzor názvu súboru + Premenovať + Zdroj dátumu + EXIF dátum zhotovenia + Dátum úpravy súboru + Dátum vytvorenia súboru + Aktuálny dátum + Manuálny dátum + Manuálny dátum + Manuálny čas + Vybratý dátum je pre niektoré súbory nedostupný. Použije sa na ne aktuálny dátum. + Zadajte vzor názvu súboru + Vzor vytvára neplatný alebo príliš dlhý názov súboru + Vzor vytvára duplicitné názvy súborov + Všetky názvy súborov sa už zhodujú so vzorom + Tento vzor používa tokeny, ktoré nie sú dostupné na dávkové premenovanie + Meno zostane rovnaké + Súbory boli úspešne premenované + Niektoré súbory sa nepodarilo premenovať + Povolenie nebolo udelené + Používa pôvodný názov súboru bez prípony, čo vám pomôže zachovať identifikáciu zdroja neporušenú. Podporuje tiež rezanie pomocou \\o{start:end}, prepis pomocou \\o{t} a nahradenie pomocou \\o{s/old/new/}. + Automatické inkrementačné počítadlo. Podporuje vlastné formátovanie: \\c{padding} (napr. \\c{3} -&gt; 001), \\c{start:step} alebo \\c{start:step:padding}. + Názov nadradeného priečinka, v ktorom sa nachádzal pôvodný súbor. + Veľkosť pôvodného súboru. Podporuje jednotky: \\z{b}, \\z{kb}, \\z{mb} alebo len \\z pre ľudsky čitateľný formát. + Generuje náhodný univerzálny jedinečný identifikátor (UUID), aby sa zabezpečila jedinečnosť súboru. + Premenovanie súborov nie je možné vrátiť späť. Pred pokračovaním sa uistite, že sú nové názvy správne. + Potvrďte premenovanie + Nastavenia bočnej ponuky + Menu mierka + Menu alfa + Automatické vyváženie bielej + Výstrižok + Kontrola skrytých vodoznakov + Skladovanie + Limit vyrovnávacej pamäte + Vymažte vyrovnávaciu pamäť automaticky, keď presiahne túto veľkosť + Interval čistenia + Ako často kontrolovať, či sa má vyrovnávacia pamäť vymazať + Pri spustení aplikácie + 1 deň + 1 týždeň + 1 mesiac + Vymažte vyrovnávaciu pamäť aplikácie automaticky podľa zvoleného limitu a intervalu + Pohyblivá plocha obilia + Umožňuje presunúť celú oblasť orezania potiahnutím dovnútra + Zlúčiť GIF + Skombinujte viacero GIF klipov do jednej animácie + poradie GIF klipov + Obrátené + Hrajte naopak + bumerang + Hrajte dopredu a potom dozadu + Normalizujte veľkosť rámu + Prispôsobte rámy tak, aby sa zmestili na najväčšiu sponu; vypnite, aby sa zachovala ich pôvodná mierka + Oneskorenie medzi klipmi (ms) + Priečinok + Vyberte všetky obrázky z priečinka a jeho podpriečinkov + Duplicate Finder + Nájdite presné kópie a vizuálne podobné obrázky + vyhľadávač duplikátov podobné obrázky presné kópie fotografie čistenie úložisko dHash SHA-256 + Vyberte obrázky a nájdite duplikáty + Citlivosť na podobnosť + Presné kópie + Podobné obrázky + Vyberte všetky presné duplikáty + Vyberte všetky okrem odporúčaných + Nenašli sa žiadne duplikáty + Skúste pridať viac obrázkov alebo zvýšiť citlivosť na podobnosť + Nepodarilo sa prečítať %1$d obrázky + %1$s • %2$s regenerovateľné + %1$d skupín • %2$s je možné získať späť + %1$d vybraté • %2$s + Toto sa nedá vrátiť späť. Android vás môže požiadať o potvrdenie odstránenia v dialógovom okne systému. + Nenájdené + Začnite pohybom + Presun na koniec + \ No newline at end of file diff --git a/core/resources/src/main/res/values-sr/strings.xml b/core/resources/src/main/res/values-sr/strings.xml new file mode 100644 index 0000000..ca79fea --- /dev/null +++ b/core/resources/src/main/res/values-sr/strings.xml @@ -0,0 +1,3741 @@ + + + + + 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 - најбрже, ово подешавање може повећати величину излазне слике + Сортирање + Датум + Датум (обрнуто) + Име + Име (обрнуто) + Конфигурација канала + данас + Јучер + Уграђени бирач + Бирач слика Имаге Тоолбок-а + Нема дозвола + Захтев + Изаберите више медија + Изаберите Сингле Медиа + Пицк + Покушајте поново + Прикажи подешавања у пејзажу + Ако је ово онемогућено, у пејзажном режиму подешавања ће бити отворена на дугмету на горњој траци апликација као и увек, уместо трајно видљиве опције + Подешавања целог екрана + Омогућите га и страница са подешавањима ће се увек отварати на целом екрану уместо листа фиоке који се може померати + Тип прекидача + Цомпосе + Јетпацк Цомпосе Материјал који мењате + Материјал који мењате + Макс + Промени величину сидра + Пикел + Течно + Прекидач заснован на систему дизајна \"Флуент\". + Цупертино + Прекидач заснован на систему дизајна \"Цупертино\". + Слике у СВГ + Пратите дате слике у СВГ слике + Користите узорковану палету + Палета квантизације ће бити узоркована ако је ова опција омогућена + Патх Изостави + Употреба овог алата за праћење великих слика без смањења скалирања се не препоручује, може изазвати пад и продужити време обраде + Смањење слике + Слика ће бити смањена на мање димензије пре обраде, што помаже алату да ради брже и безбедније + Минимални однос боја + Линије Праг + Куадратиц Тхресхолд + Толеранција заокруживања координата + Патх Сцале + Ресетујте својства + Сва својства ће бити постављена на подразумеване вредности, приметите да се ова радња не може опозвати + Детаљно + Подразумевана ширина линије + Енгине Моде + Легаци + ЛСТМ мрежа + Легаци & ЛСТМ + Цонверт + Конвертујте групе слика у дати формат + Додај нову фасциклу + Битс пер Сампле + Компресија + Пхотометриц Интерпретатион + Узорци по пикселу + Планарна конфигурација + И Цб Цр Подузорковање + И Цб Цр Позиционирање + Кс Резолуција + И Ресолутион + Ресолутион Унит + Стрип Оффсетс + Редови по траци + Стрип Бите Цоунтс + Формат за размену ЈПЕГ + ЈПЕГ Интерцханге Формат Дужина + Трансфер Функција + Вхите Поинт + Примари Цхроматицитиес + И Цб Цр коефицијенти + Референца Блацк Вхите + Датум и време + Опис слике + Маке + Модел + софтвер + Уметник + Цопиригхт + Екиф верзија + Фласхпик верзија + Цолор Спаце + Гама + Пикел Кс димензија + Пикел И димензија + Компримовани битови по пикселу + Напомена произвођача + Коментар корисника + Повезана звучна датотека + Датум Време Оригинал + Датум и време дигитализовано + Оффсет Тиме + Оффсет Тиме Оригинал + Оффсет Тиме Дигитализед + Суб Сец Тиме + Суб Сец Време Оригинал + Подсек Време Дигитализовано + Време излагања + Ф број + Програм експозиције + Спектрална осетљивост + Пхотограпхиц Сенситивити + Оецф + Тип осетљивости + Стандардна излазна осетљивост + Препоручени индекс изложености + ИСО брзина + ИСО брзина Латитуде иии + ИСО брзина Латитуде ззз + Вредност брзине затварача + Апертуре Валуе + Бригхтнесс Валуе + Вредност пристраности експозиције + Максимална вредност отвора бленде + Субјецт Дистанце + Режим мерења + Фласх + Предметна област + Фоцал Ленгтх + Фласх Енерги + Просторни фреквентни одзив + Резолуција фокусне равни Кс + Резолуција жаришне равни И + Јединица резолуције фокусне равни + Локација предмета + Индекс експозиције + Сенсинг Метход + Извор датотеке + ЦФА Паттерн + Цустом Рендеред + Режим експозиције + Баланс белог + Однос дигиталног зума + Фокална дужина у филму од 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Нета за брже уклањање позадине уз мању употребу меморије. + Пун модел ДДЦолор пружа висококвалитетну колоризацију за опште слике са минималним артефактима. Најбољи избор од свих модела колоризације. + ДДЦолор Обучени и приватни уметнички скупови података; производи разноврсне и уметничке резултате колоризације са мање нереалних артефаката у боји. + Лагани БиРефНет модел заснован на Свин Трансформеру за прецизно уклањање позадине. + Висококвалитетно уклањање позадине са оштрим ивицама и одличним очувањем детаља, посебно на сложеним објектима и лукавим позадинама. + Модел уклањања позадине који производи прецизне маске са глатким ивицама, погодне за опште објекте и умерено очување детаља. + Модел је већ преузет + Модел је успешно увезен + Тип + Кључна реч + Врло брзо + Нормално + Споро + Веома Споро + Израчунај проценте + Минимална вредност је %1$s + Искривите слику цртањем прстима + Варп + Тврдоћа + Варп Моде + Помери се + Расте + Смањи се + Свирл ЦВ + Свирл ЦЦВ + Фаде Стренгтх + Топ Дроп + Боттом Дроп + Старт Дроп + Енд Дроп + Преузимање + Смоотх Схапес + Користите суперелипсе уместо стандардних заобљених правоугаоника за глаткије, природније облике + Облик облика + Цут + Заобљен + Смоотх + Оштре ивице без заобљења + Класични заобљени углови + Схапес Типе + Цорнерс Сизе + Скуирцле + Елегантни заобљени елементи корисничког интерфејса + Формат имена датотеке + Прилагођени текст постављен на самом почетку назива датотеке, савршен за називе пројеката, брендове или личне ознаке. + Ширина слике у пикселима, корисна за праћење промена резолуције или скалирања резултата. + Висина слике у пикселима, корисна када радите са пропорцијама или извозима. + Генерише насумичне цифре да гарантује јединствена имена датотека; додајте још цифара за додатну сигурност од дупликата. + Умеће примењено унапред подешено име у назив датотеке тако да можете лако да запамтите како је слика обрађена. + Приказује режим скалирања слике који се користи током обраде, помажући у разликовању слика промењене величине, исечених или уклопљених слика. + Прилагођени текст постављен на крај назива датотеке, користан за верзионисање као што је _в2, _едитед или _финал. + Екстензија датотеке (пнг, јпг, вебп, итд.), аутоматски се подудара са стварним сачуваним форматом. + Прилагодљива временска ознака која вам омогућава да дефинишете сопствени формат помоћу Јава спецификације за савршено сортирање. + Флинг Типе + Андроид изворни + иОС стил + Смоотх Цурве + Куицк Стоп + Боунци + Флоати + Снаппи + Ултра Смоотх + Адаптиве + Аццессибилити Аваре + Редуцед Мотион + Природна физика Андроид скроловања за основно поређење + Уравнотежено, глатко померање за општу употребу + Понашање померања попут иОС-а са већим трењем + Јединствена сплине крива за јасан осећај померања + Прецизно померање са брзим заустављањем + Разиграни, покретни померајући се померај + Дуги, клизећи скроловање за прегледање садржаја + Брзо и брзо померање за интерактивни кориснички интерфејс + Врхунско глатко померање са продуженим замахом + Подешава физику на основу брзине бацања + Поштује подешавања приступачности система + Минимално кретање за потребе приступачности + Примари Линес + Додаје дебљу линију сваке пете линије + Филл Цолор + Скривени алати + Алати скривени за дељење + Библиотека боја + Прегледајте огромну колекцију боја + Изоштрава и уклања замућење са слика уз задржавање природних детаља, идеално за поправљање фотографија које нису у фокусу. + Интелигентно враћа слике којима је претходно промењена величина, враћајући изгубљене детаље и текстуре. + Оптимизовано за садржај уживо, смањује артефакте компресије и побољшава фине детаље у кадровима филмова/ТВ емисија. + Конвертује снимке ВХС квалитета у ХД, уклањајући шум траке и побољшавајући резолуцију уз очување винтаге осећаја. + Специјализован за слике и снимке екрана са тешким текстом, изоштрава знакове и побољшава читљивост. + Напредно повећање величине обучено на различитим скуповима података, одлично за побољшање фотографија опште намене. + Оптимизован за веб компресоване фотографије, уклања ЈПЕГ артефакте и враћа природан изглед. + Побољшана верзија за веб фотографије са бољим очувањем текстуре и смањењем артефаката. + 2к повећање помоћу технологије Дуал Аггрегатион Трансформер, одржава оштрину и природне детаље. + 3к повећање помоћу напредне трансформаторске архитектуре, идеално за умерене потребе повећања. + 4к висококвалитетно повећање са најсавременијом мрежом трансформатора, чува фине детаље у већим размерама. + Уклања замућење/шум и подрхтавање фотографија. Опште намене, али најбоље на фотографијама. + Враћа слике ниског квалитета помоћу Свин2СР трансформатора, оптимизованог за БСРГАН деградацију. Одлично за поправљање артефаката тешке компресије и побољшање детаља на скали од 4к. + 4к повећање са СвинИР трансформатором обученим за БСРГАН деградацију. Користи ГАН за оштрије текстуре и природније детаље на фотографијама и сложеним сценама. + Пут + Споји ПДФ + Комбинујте више ПДФ датотека у један документ + Филес Ордер + стр. + Сплит ПДФ + Извуците одређене странице из ПДФ документа + Ротирајте ПДФ + Трајно поправи оријентацију странице + Пагес + Преуредите ПДФ + Превуците и отпустите странице да бисте их променили + Задржите и превуците странице + Бројеви страница + Аутоматски додајте нумерацију у своје документе + Лабел Формат + ПДФ у текст (ОЦР) + Извуците обичан текст из ваших ПДФ докумената + Преклапање прилагођеног текста за брендирање или безбедност + Потпис + Додајте свој електронски потпис на било који документ + Ово ће се користити као потпис + Откључај ПДФ + Уклоните лозинке из заштићених датотека + Заштитите ПДФ + Осигурајте своје документе снажном енкрипцијом + Успех + ПДФ је откључан, можете га сачувати или поделити + Поправи ПДФ + Покушајте да поправите оштећене или нечитљиве документе + Граисцале + Претворите све слике уграђене у документе у сивим тоновима + Цомпресс ПДФ + Оптимизујте величину датотеке документа за лакше дељење + ИмагеТоолбок поново гради интерну табелу унакрсних референци и регенерише структуру датотеке од нуле. Ово може да врати приступ многим датотекама које се „не могу отворити“ + Овај алат претвара све слике документа у сиве тонове. Најбоље за штампање и смањење величине датотеке + Метаподаци + Уредите својства документа ради боље приватности + Ознаке + Произвођач + Аутор + Кључне речи + Креатор + Приватност Дееп Цлеан + Обришите све доступне метаподатке за овај документ + Страница + Дубоки ОЦР + Извуците текст из документа и сачувајте га у једној текстуалној датотеци користећи Тессерацт енгине + Није могуће уклонити све странице + Уклоните ПДФ странице + Уклоните одређене странице из ПДФ документа + Додирните За уклањање + Ручно + Изрежите ПДФ + Опсеците странице документа на било коју границу + Изравнајте ПДФ + Учините ПДФ непромењивим растером страница документа + Није могуће покренути камеру. Проверите дозволе и уверите се да је не користи друга апликација. + Ектрацт Имагес + Извуците слике уграђене у ПДФ-ове у њиховој оригиналној резолуцији + Ова ПДФ датотека не садржи уграђене слике + Овај алат скенира сваку страницу и враћа изворне слике пуног квалитета — савршено за чување оригинала из докумената + Драв Сигнатуре + Пен Парамс + Користите сопствени потпис као слику која се поставља на документе + Зип ПДФ + Поделите документ са датим интервалом и спакујте нове документе у зип архиву + Интервал + Штампај ПДФ + Припремите документ за штампање са прилагођеном величином странице + Странице по листу + Оријентација + Величина странице + Маргина + Блоом + Софт Кнее + Оптимизовано за аниме и цртане филмове. Брзо повећање са побољшаним природним бојама и мање артефаката + Стил попут Самсунг Оне УИ 7 + Унесите основне математичке симболе овде да бисте израчунали жељену вредност (нпр. (5+5)*10) + Математички израз + Покупите до %1$s слика + Задржите датум и време + Увек сачувајте екиф ознаке повезане са датумом и временом, ради независно од опције Кееп екиф + Боја позадине за алфа формате + Додаје могућност постављања боје позадине за сваки формат слике са алфа подршком, када је онемогућена ово је доступно само за оне које нису алфа + Отворени пројекат + Наставите са уређивањем претходно сачуваног пројекта Имаге Тоолбок + Није могуће отворити пројекат Имаге Тоолбок + Пројекту Имаге Тоолбок недостају подаци о пројекту + Пројекат Имаге Тоолбок је оштећен + Неподржана верзија пројекта Имаге Тоолбок: %1$s + Сачувај пројекат + Чувајте слојеве, позадину и историју уређивања у датотеци пројекта који се може уређивати + Отварање није успело + Врите То Сеарцхабле ПДФ + Препознајте текст из групе слика и сачувајте претраживи ПДФ са сликом и слојем текста који се може изабрати + Слој алфа + Хоризонтал Флип + Вертицал Флип + Закључај + Додај сенку + Схадов Цолор + Тект Геометри + Растегните или искосите текст за оштрију стилизацију + Скала Кс + Скев Кс + Уклоните напомене + Уклоните изабране типове напомена као што су везе, коментари, истакнути делови, облици или поља обрасца са ПДФ страница + Хипервезе + Филе Аттацхментс + Линије + Попупс + Марке + Облици + Тект Нотес + Означавање текста + Форм Фиелдс + Маркуп + Непознато + Напомене + Разгрупиши + Додајте замућену сенку иза слоја са подесивом бојом и офсетима + Дисторзија свесног садржаја + Нема довољно меморије за довршетак ове радње. Покушајте да користите мању слику, затворите друге апликације или поново покренете апликацију. + Параллел Воркерс + Ове слике нису сачуване јер би конвертоване датотеке биле веће од оригинала. Проверите ову листу пре него што избришете оригиналне слике. + Фајлови су прескочени: __ПХ_0__ + Апп Логс + Погледајте евиденцију апликације да бисте уочили проблеме + Фаворити у груписаним алатима + Додаје фаворите као картицу када су алати груписани по типу + Прикажи омиљене као последње + Помера картицу омиљених алатки до краја + Хоризонтални размак + Вертикални размак + Заостала енергија + Користите једноставну енергетску мапу магнитуде градијента уместо алгоритма напредне енергије + Уклоните маскирано подручје + Шавови ће проћи кроз маскирани регион, уклањајући само изабрану област уместо да је штите + ВХС НТСЦ + Напредна НТСЦ подешавања + Додатно аналогно подешавање за јачи ВХС и артефакте емитовања + Цхрома блеед + Ношење траке + Праћење + Лума размаз + Звоно + Снег + Користи поље + Тип филтера + Филтер улазне светлости + Инпут цхрома ловпасс + Цхрома демодулатион + Фазни помак + Пхасе оффсет + Пребацивање главе + Висина пребацивања главе + Оффсет преклапања главе + Промена главе + Положај средње линије + Треперење средње линије + Праћење висине буке + Праћење таласа + Праћење снега + Праћење анизотропије снега + Праћење буке + Праћење интензитета буке + Композитни шум + Композитна фреквенција шума + Интензитет композитног шума + Детаљ композитног шума + Фреквенција звона + Снага звона + Лума бука + Фреквенција лума шума + Интензитет лума буке + Лума буке детаљ + Цхрома ноисе + Фреквенција шума боје + Интензитет шума боје + Детаљ Цхрома буке + Интензитет снега + Анизотропија снега + Шум хроматске фазе + Грешка у фази боје + Хоризонтално кашњење боје + Вертикално кашњење боје + Брзина ВХС траке + ВХС губитак боје + Интензитет изоштравања ВХС-а + ВХС фреквенција изоштравања + Интензитет ивичног таласа ВХС + ВХС ивична брзина таласа + ВХС ивична таласна фреквенција + ВХС ивични таласни детаљ + Излазни хрома нископропусни + Хоризонтална скала + Скала вертикала + Фактор скале Кс + Фактор скале И + Детектује уобичајене водене жигове на слици и осликава их ЛаМа. Аутоматски преузима моделе за откривање и сликање + Деутеранопиа + Прошири слику + Уклањање позадине засновано на сегментацији објеката помоћу ИОЛО в11 сегментације + Прикажи угао линије + Приказује тренутну ротацију линије у степенима током цртања + Анимирани емоџији + Прикажи доступне емоџије као анимације + Сачувај у оригиналну фасциклу + Сачувајте нове датотеке поред оригиналне датотеке уместо изабране фасцикле + Водени жиг ПДФ + Нема довољно меморије + Слика је вероватно превелика за обраду на овом уређају или је систему понестало доступне меморије. Покушајте да смањите резолуцију слике, затворите друге апликације или одаберете мању датотеку. + Мени за отклањање грешака + Мени за тестирање функција апликације, ово није намењено да се прикаже у производном издању + Провајдер + ПаддлеОЦР захтева додатне ОННКС моделе на вашем уређају. Да ли желите да преузмете __ПХ_0__ податке? + Универсал + Кореан + латиница + источнословенски + тајландски + грчки + енглески + ћирилица + арапски + Деванагари + тамилски + телугу + Схадер + Шадер унапред подешен + Није изабран ниједан схадер + Схадер Студио + Креирајте, уредите, потврдите, увезите и извезите прилагођене схадере фрагмената + Сачувани сејдери + Отворите сачуване шејдере, дуплирајте, извезите, делите или избришите + Нови схадер + Схадер фајл + .итсхадер ЈСОН + __ПХ_0__ парамс + Извор схадера + Напишите само тело воид маин(). Користите тектуреЦоординате за УВ координате и инпутИмагеТектуре као изворни узоркивач. + Функције + Опционе помоћне функције, константе и структуре уметнуте пре воид маин(). Униформе се генеришу из парама. + Додајте униформе овде када су схадеру потребне вредности које се могу уређивати. + Ресетујте шејдер + Тренутни нацрт схадера ће бити обрисан + Неважећи схадер + Неподржана верзија шејдера __ПХ_0__. Подржана верзија: __ПХ_1__. + Име схадера не сме бити празно. + Поље за извор схадера не сме бити празно. + Извор схадера садржи неподржане знакове: __ПХ_0__. Користите само АСЦИИ ГЛСЛ извор. + Параметар \\\"__ПХ_0__\\\" је декларисан више пута. + Називи параметара не смеју да буду празни. + Параметар \\\"__ПХ_0__\\\" користи резервисано име ГПУИ слике. + Параметар \\\"__ПХ_0__\\\" мора да буде важећи ГЛСЛ идентификатор. + Параметар \\\"__ПХ_0__\\\" има __ПХ_1__ тип вредности \\\"__ПХ_2__\\\", очекивано \\\"__ПХ_3__\\\". + Параметар \\\"__ПХ_0__\\\" не може да дефинише мин или максимум за боол вредности. + Параметар \\\"__ПХ_0__\\\" има мин већи од макс. + Подразумевани параметар \\\"__ПХ_0__\\\" је мањи од мин. + Подразумевани параметар \\\"__ПХ_0__\\\" је већи од макс. + Схадер мора декларисати \\\"униформ самплер2Д __ПХ_0__;\\\". + Шејдер мора да декларише \\\"варијантно вец2 __ПХ_0__;\\\". + Схадер мора да дефинише \\\"воид маин()\\\". + Схадер мора уписати боју у гл_ФрагЦолор. + СхадерТои маинИмаге схадери нису подржани. Користите воид маин() уговор ГПУИмаге-а. + Анимирана СхадерТои униформа \\\"иТиме\\\" није подржана. + Анимирана СхадерТои униформа \\\"иФраме\\\" није подржана. + СхадерТои униформа \\\"иРесолутион\\\" није подржана. + СхадерТои иЦханнел текстуре нису подржане. + Спољне текстуре нису подржане. + Екстензије за екстерне текстуре нису подржане. + Либретро параметри схадера нису подржани. + Подржана је само једна улазна текстура. Уклоните \\\"униформа __ПХ_0__ __ПХ_1__;\\\". + Параметар \\\"__ПХ_0__\\\" мора бити декларисан као \\\"униформ __ПХ_1__ __ПХ_2__;\\\". + Параметар \\\"__ПХ_0__\\\" је декларисан као \\\"униформ __ПХ_1__ __ПХ_2__;\\\", очекивано \\\"униформ __ПХ_3__ __ПХ_4__;\\\". + Схадер датотека је празна. + Схадер датотека мора да буде .итсхадер ЈСОН објекат са верзијом, именом и пољима за шедер. + Схадер датотека није важећа ЈСОН или се не подудара са .итсхадер форматом. + Поље \\\"__ПХ_0__\\\" је обавезно. + __ПХ_0__ је обавезан. + __ПХ_0__ \\\"__ПХ_1__\\\" није подржано. Подржани типови: __ПХ_2__. + __ПХ_0__ је неопходан за параметре \\\"__ПХ_1__\\\". + __ПХ_0__ мора бити коначан број. + __ПХ_0__ мора бити цео број. + __ПХ_0__ мора бити тачно или нетачно. + __ПХ_0__ мора да буде низ боја, РГБ/РГБА низ или објекат у боји. + __ПХ_0__ мора да користи формат #РРГГББ или #РРГГББАА. + __ПХ_0__ мора да садржи важеће хексадецималне канале боја. + __ПХ_0__ мора да садржи 3 или 4 канала у боји. + __ПХ_0__ мора бити између 0 и 255. + __ПХ_0__ мора бити низ од два броја или објекат са к и и. + __ПХ_0__ мора да садржи тачно 2 броја. + Дупликат + Увек обришите ЕКСИФ + Уклоните ЕКСИФ податке слике приликом чувања, чак и када алатка захтева чување метаподатака + Помоћ и савети + __ПХ_0__ упутства + Отвори алат + Научите главне алате, опције извоза, ПДФ токове, услужне програме за боје и исправке за уобичајене проблеме + Почетак рада + Бирајте алате, увозите датотеке, сачувајте резултате и брже користите подешавања + Уређивање слика + Промените величину, исеците, филтрирајте, обришите позадину, цртајте слике и водени жиг + Датотеке и метаподаци + Конвертујте формате, упоредите излаз, заштитите приватност и контролишите називе датотека + ПДФ и документи + Креирајте ПДФ-ове, скенирајте документе, ОЦР странице и припремите датотеке за дељење + Текст, КР и подаци + Препознајте текст, скенирајте кодове, кодирајте Басе64, проверите хешове и датотеке пакета + Алати у боји + Бирајте боје, правите палете, истражујте библиотеке боја и креирајте градијенте + Креативни алати + Генеришите СВГ, АСЦИИ уметност, текстуре шума, сејдере и експерименталне визуелне елементе + Решавање проблема + Решите проблеме са тешким датотекама, транспарентношћу, дељењем, увозом и извештавањем + Изаберите прави алат + Користите категорије, претрагу, фаворите и акције дељења да бисте започели на правом месту + Почните од најбоље улазне тачке + Имаге Тоолбок има много фокусираних алата, а многи од њих се на први поглед преклапају. Почните од задатка који желите да завршите, а затим изаберите алатку која контролише најважнији део резултата: величину, формат, метаподатке, текст, ПДФ странице, боје или изглед. + Користите претрагу када знате назив радње, као што је промена величине, ПДФ, ЕКСИФ, ОЦР, КР или боја. + Отворите категорију када знате само тип задатка, а затим закачите честе алате као омиљене. + Када друга апликација дели слику у Имаге Тоолбок-у, изаберите алатку из тока дељења листа. + Увезите, сачувајте и делите резултате + Разумети уобичајени ток од одабира уноса до извоза уређене датотеке + Пратите уобичајени ток уређивања + Већина алата прати исти ритам: изаберите унос, прилагодите опције, прегледајте, затим сачувајте или делите. Важан детаљ је да чување ствара излазну датотеку, док је дељење обично најбоље за брзо пребацивање у другу апликацију. + Изаберите једну датотеку за алатке са једном сликом или неколико датотека за групне алате када бирач то дозвољава. + Проверите контроле прегледа и излаза пре чувања, посебно опције формата, квалитета и назива датотеке. + Користите дељење за брзу примопредају или сачувајте када вам је потребна трајна копија у изабраном фолдеру. + Радите са више слика одједном + Батцх алати су најбољи за понављање задатака промене величине, конверзије, компресије и именовања + Припремите предвидљиву серију + Пакетна обрада је најбржа када сваки излаз треба да прати иста правила. То је такође најлакше место да направите велику грешку, па изаберите именовање, квалитет, метаподатке и понашање фасцикле пре него што започнете дуги извоз. + Изаберите све повезане слике заједно и задржите редослед ако излаз зависи од секвенце. + Поставите правила за промену величине, формата, квалитета, метаподатака и имена датотеке једном пре него што почнете да извозите. + Прво покрените малу пробну групу када су изворне датотеке велике или када је циљни формат нов за вас. + Брза појединачна измена + Користите све-у-једном уређивач када вам је потребно неколико једноставних промена на једној слици + Завршите једну слику без скакања алата + Појединачно уређивање је корисно када је слици потребан мали ланац промена уместо једне специјализоване операције. Обично је брже за брзо чишћење, преглед и извоз једне коначне копије без прављења скупног тока посла. + Отворите Појединачно уређивање за једну слику којој су потребна уобичајена подешавања, ознаке, исецање, ротирање или промене за извоз. + Примените измене редоследом којим се прво мења најмањи број пиксела, као што је исецање пре филтера и филтери пре коначног компресије. + Уместо тога, користите специјализовани алат када вам је потребна групна обрада, тачне циљеве величине датотеке, ПДФ излаз, ОЦР или чишћење метаподатака. + Користите унапред подешене поставке и подешавања + Сачувајте поновљене изборе и подесите апликацију за жељени ток посла + Избегавајте понављање истог подешавања + Унапред подешене поставке и подешавања су корисни када често извозите исту врсту датотеке. Добра унапред подешена подешавања претварају поновљене ручне контролне листе у један додир, док глобална подешавања дефинишу подразумеване вредности са којима нови алати почињу. + Креирајте унапред подешене вредности за уобичајене излазне величине, формате, вредности квалитета или обрасце именовања. + Прегледајте подешавања за подразумевани формат, квалитет, фасциклу за чување, име датотеке, бирач и понашање интерфејса. + Направите резервну копију подешавања пре поновног инсталирања апликације или преласка на други уређај. + Подесите корисне подразумеване вредности + Једном одаберите подразумевани формат, квалитет, режим размере, тип промене величине и простор боја + Нека нови алати почну ближе вашем циљу + Подразумеване вредности нису само козметичке поставке. Они одлучују који се формат, квалитет, понашање при промени величине, режим скалирања и простор боја први појављују у многим алаткама, што помаже да се избегне поновни избор истих избора при сваком извозу. + Подесите подразумевани формат и квалитет слике тако да одговарају вашем уобичајеном одредишту, као што је ЈПЕГ за фотографије или ПНГ/ВебП за алфа. + Подесите подразумевани тип промене величине и режим размере ако често извозите за строге димензије, друштвене платформе или странице докумената. + Промените подразумеване вредности простора боја само када је вашем току посла потребно доследно руковање бојама на екранима, штампаним или спољним уређивачима. + Бирач и покретач + Користите подешавања бирача када апликација отвори превише дијалога или се покрене на погрешном месту + Нека отварање датотека одговара вашој навици + Подешавања бирача и покретача контролишу да ли Имаге Тоолбок почиње са главног екрана, алата или тока избора датотеке. Ове опције су посебно корисне када обично улазите са Андроид листа за дељење или када желите да апликација делује више као покретач алата. + Користите подешавања бирача фотографија ако системски бирач сакрива датотеке, приказује превише недавних ставки или лоше обрађује датотеке у облаку. + Омогућите прескакање само за алатке у које обично улазите из дељења или већ знате изворну датотеку. + Прегледајте режим покретача ако желите да се Имаге Тоолбок понаша више као бирач алата него као нормалан почетни екран. + Извор слике + Изаберите режим бирача који најбоље функционише са галеријама, менаџерима датотека и датотекама у облаку + Изаберите датотеке са правог извора + Подешавања извора слике утичу на то како апликација тражи слике од Андроид-а. Најбољи избор зависи од тога да ли ваше датотеке живе у системској галерији, директоријуму за управљање датотекама, добављачу облака или другој апликацији која дели привремени садржај. + Користите подразумевани бирач када желите системски интегрисано искуство за уобичајене слике галерије. + Пребаците режим бирача ако се албуми, фасцикле, датотеке у облаку или нестандардни формати не појављују тамо где очекујете. + Ако дељена датотека нестане након што изађете из изворне апликације, поново је отворите помоћу сталног бирача или прво сачувајте локалну копију. + Фаворити и алатке за дељење + Држите главни екран фокусираним и сакријте алате који немају смисла у токовима дељења + Смањите шум листе алата + Фаворити и подешавања скривених за дељење корисни су када користите само неколико алата сваки дан. Они такође одржавају ток дељења практичним тако што скривају алате који не могу да користе тип датотеке коју шаље друга апликација. + Закачите честе алате тако да буду лако доступни чак и када су алати груписани по категоријама. + Сакријте алате из токова дељења када су ирелевантни за датотеке које долазе из друге апликације. + Користите подешавања омиљеног наручивања ако више волите фаворите на крају или груписане са сродним алатима. + Направите резервну копију подешавања + Безбедно преместите унапред подешене поставке, подешавања и понашање апликације у другу инсталацију + Нека ваше подешавање буде преносиво + Прављење резервних копија и враћање је од највеће помоћи након што подесите унапред подешене поставке, фасцикле, правила за називе датотека, редослед алата и визуелне поставке. Резервна копија вам омогућава да експериментишете са подешавањима или поново инсталирате апликацију без обнављања тока посла из меморије. + Направите резервну копију након промене унапред подешених вредности, понашања имена датотеке, подразумеваних формата или распореда алата. + Чувајте резервну копију негде ван фасцикле апликације ако планирате да обришете податке, поново инсталирате или преместите уређаје. + Вратите подешавања пре обраде важних група тако да подразумеване вредности и понашање при чувању одговарају вашем старом подешавању. + Промените величину и конвертујте слике + Промените величину, формат, квалитет и метаподатке за једну или више слика + Направите копије промењене величине + Промена величине и конверзија је главни алат за предвидљиве димензије и лакше излазне датотеке. Користите га када су величина слике, излазни формат и понашање метаподатака важни у исто време. + Изаберите једну или више слика, а затим изаберите да ли су димензије, размера или величина датотеке најважнији. + Изаберите излазни формат и квалитет; користите ПНГ или ВебП када се мора сачувати транспарентност. + Прегледајте резултат, а затим сачувајте или поделите генерисане копије без промене оригинала. + Промена величине према величини датотеке + Циљајте ограничење величине када веб-сајт, месинџер или образац одбијају тешке слике + Прилагодите строга ограничења отпремања + Промена величине према величини датотеке је боља од нагађања вредности квалитета када одредиште прихвата само датотеке испод одређеног ограничења. Алат може да прилагоди компресију и димензије да би достигао циљ док покушава да задржи резултат употребљивим. + Унесите циљну величину мало испод стварног ограничења за отпремање да бисте оставили простора за метаподатке и промене добављача. + Преферирајте формате са губицима за фотографије када је циљ мали; ПНГ може остати велики чак и након промене величине. + Прегледајте лица, текст и ивице након извоза јер мете агресивне величине могу унети видљиве артефакте. + Ограничите промену величине + Смањите само слике које премашују максималну ширину, висину или ограничења датотеке + Избегавајте промену величине слика које су већ у реду + Ограничење величине је корисно за мешовите фасцикле где су неке слике огромне, а друге већ припремљене. Уместо да се сваки фајл наметне истом променом величине, он држи прихватљиве слике ближе њиховом оригиналном квалитету. + Поставите максималне димензије или ограничења на основу одредишта, а не на слепо мењање величине сваке датотеке. + Омогућите понашање стила прескочи ако је већи када желите да избегнете излазе који постају тежи од извора. + Користите ово за групе са различитих камера, снимака екрана или преузимања где се величине извора доста разликују. + Изрежите, ротирајте и исправите + Уоквирите важну област пре извоза или коришћења слике у другим алатима + Очистите композицију + Прво исецање чини касније филтере, ОЦР, ПДФ странице и резултате дељења чишћима. + Отворите Опсецање и изаберите слику коју желите да прилагодите. + Користите бесплатно исецање или пропорцију када излаз мора да одговара објави, документу или аватару. + Ротирајте или окрените пре чувања како би каснији алати добили исправљену слику. + Примените филтере и подешавања + Направите скуп филтера који се може уређивати и прегледајте резултат пре извоза + Подесите изглед слике + Филтери су корисни за брзе исправке, стилизовани излаз и припрему слика за ОЦР или штампање. Мале промене контраста, оштрине и осветљености могу бити важније од јаких ефеката када слика садржи текст или фине детаље. + Додајте филтере један по један и пазите на преглед након сваке промене. + Подесите осветљеност, контраст, оштрину, замућење, боју или маске у зависности од задатка. + Извезите само када се преглед подудара са вашим циљним уређајем, документом или друштвеном платформом. + Прво прегледајте + Прегледајте слике пре него што одаберете тежи алат за уређивање, конверзију или чишћење + Проверите шта сте заправо добили + Преглед слике је користан када датотека долази из ћаскања, складишта у облаку или друге апликације и нисте сигурни шта садржи. Провера димензија, транспарентности, оријентације и визуелног квалитета прво помаже у одабиру одговарајућег алата за праћење. + Отворите преглед слика када треба да прегледате неколико слика пре него што одлучите шта да радите са њима. + Потражите проблеме са ротацијом, транспарентне области, артефакте компресије и неочекивано велике димензије. + Пређите на опцију Промена величине, исецање, упоређивање, ЕКСИФ или уклањање позадине тек када знате шта треба да поправите. + Уклоните или вратите позадину + Аутоматски обришите позадину или ручно прецизирајте ивице помоћу алата за враћање + Задржите тему, уклоните остатак + Уклањање позадине најбоље функционише када комбинујете аутоматски избор са пажљивим ручним чишћењем. Коначни формат извоза је важан колико и маска, јер ће ЈПЕГ изравнати прозирна подручја чак и ако преглед изгледа исправно. + Почните са аутоматским уклањањем ако је субјект јасно одвојен од позадине. + Увећајте и прелазите између брисања и враћања да бисте поправили косу, углове, сенке и мале детаље. + Сачувајте као ПНГ или ВебП када вам је потребна транспарентност у коначној датотеци. + Нацртајте, означите и водени жиг + Додајте стрелице, белешке, истакнуте ставке, потписе или поновљене слојеве воденог жига + Додајте видљиве информације + Алати за означавање помажу у објашњењу слике, док водени жиг помаже брендирању или заштити извезених датотека. + Користите Цртање када су вам потребне белешке, облици, истицање или брзе белешке. + Користите водени жиг када исти текст или ознака слике треба да се доследно појављују на излазима. + Проверите непрозирност, позицију и размеру пре извоза тако да ознака буде видљива, али не одвлачи пажњу. + Подразумеване вредности цртања + Подесите ширину линије, боју, режим путање, закључавање оријентације и лупу за брже означавање + Учините да се цртеж осећа предвидљивим + Подешавања цртања су корисна када често стављате коментаре на снимке екрана, означавате документе или потписујете слике. Подразумеване вредности смањују време подешавања, док лупа и закључавање оријентације олакшавају прецизне измене на малим екранима. + Подесите подразумевану ширину линије и нацртајте боју на вредности које најчешће користите за стрелице, истакнуте делове или потписе. + Користите подразумевани режим путање када обично цртате исту врсту потеза, облика или ознака. + Омогућите лупу или закључавање оријентације када унос прстом отежава прецизно постављање малих детаља. + Распореди са више слика + Изаберите прави алат за више слика пре него што организујете снимке екрана, скенирања или упоредне траке + Користите прави алат за распоред + Ови алати звуче слично, али сваки од њих је подешен за различиту врсту излаза више слика. Одабиром правог прво избегавате борбу против контрола распореда које су дизајниране за другачији резултат. + Користите Цоллаге Макер за дизајниране распореде са неколико слика у једној композицији. + Користите спајање или слагање слика када слике треба да постану једна дуга вертикална или хоризонтална трака. + Користите раздвајање слика када једна велика слика треба да постане неколико мањих делова. + Конвертујте формате слика + Креирајте ЈПЕГ, ПНГ, ВебП, АВИФ, ЈКСЛ и друге копије без ручног уређивања пиксела + Изаберите формат за посао + Различити формати су бољи за фотографије, снимке екрана, транспарентност, компресију или компатибилност. Конверзија је најбезбеднија када разумете шта одредишна апликација подржава и да ли извор садржи алфа, анимацију или метаподатке које желите да задржите. + Користите ЈПЕГ за компатибилне фотографије, ПНГ за слике без губитака и алфа и ВебП за компактно дељење. + Подесите квалитет када га формат подржава; ниже вредности смањују величину, али могу додати артефакте. + Чувајте оригинале док не потврдите да су конвертоване датотеке исправно отворене у циљној апликацији. + Проверите ЕКСИФ и приватност + Прегледајте, измените или уклоните метаподатке пре него што делите осетљиве фотографије + Контролишите скривене податке о слици + ЕКСИФ може да садржи податке о камери, датуме, локацију, оријентацију и друге детаље који нису видљиви на слици. Вреди проверити пре јавног дељења, извештаја подршке, листе тржишта или било које фотографије која може открити приватно место. + Отворите ЕКСИФ алате пре него што делите фотографије које могу да садрже информације о локацији или приватном уређају. + Уклоните осетљива поља или уредите нетачне ознаке као што су датум, оријентација, аутор или подаци о камери. + Сачувајте нову копију и делите ту копију уместо оригинала када је приватност важна. + Аутоматско чишћење ЕКСИФ-а + Уклоните метаподатке подразумевано док одлучујете да ли треба сачувати датуме + Нека приватност буде подразумевана + Група ЕКСИФ подешавања је корисна када желите да се чишћење приватности одвија аутоматски уместо да га памти за сваки извоз. Задржи датум и време је посебна одлука јер датуми могу бити корисни за сортирање, али осетљиви за дељење. + Омогућите увек чист ЕКСИФ када је већина ваших извоза намењена за јавно или полујавно дељење. + Омогућите Кееп Дате Тиме само када је очување времена снимања важније од уклањања сваке временске ознаке. + Користите ручно уређивање ЕКСИФ-а за једнократне датотеке где треба да задржите нека поља и уклоните друга. + Контролна имена датотека + Користите префиксе, суфиксе, временске ознаке, унапред подешене вредности, контролне суме и бројеве секвенце + Учините извоз лаким за проналажење + Добар образац назива датотеке помаже у сортирању група и спречава случајно преписивање. Подешавања назива датотеке су посебно корисна када се извоз врати у изворну фасциклу, где слична имена могу брзо постати збуњујућа. + Подесите префикс имена датотеке, суфикс, временску ознаку, оригинално име, унапред подешене информације или опције секвенце у подешавањима. + Користите бројеве секвенце за серије где је редослед битан, као што су странице, оквири или колажи. + Омогућите преписивање само када сте сигурни да је замена постојећих датотека безбедна за тренутну фасциклу. + Препиши датотеке + Замените излазе одговарајућим именима само када је ваш ток посла намерно деструктиван + Пажљиво користите режим преписивања + Оверврите Филес мења начин на који се решавају сукоби имена. Користан је за поновљене извозе у исту фасциклу, али такође може заменити раније резултате ако ваш образац назива датотеке поново производи исто име. + Омогућите преписивање само за фасцикле у којима се очекује замена старијих генерисаних датотека и које се могу опоравити. + Нека преписивање буде онемогућено када извозите оригинале, клијентске датотеке, једнократна скенирања или било шта без резервне копије. + Комбинујте преписивање са намерним шаблоном назива датотеке тако да се замене само предвиђени излази. + Обрасци назива датотеке + Комбинујте оригинална имена, префиксе, суфиксе, временске ознаке, унапред подешене вредности, режим скале и контролне суме + Направите имена која објашњавају излаз + Подешавања обрасца назива датотеке могу претворити извезене датотеке у корисну историју онога што им се догодило. Ово је важно у групама јер приказ фасцикле може бити једино место где можете разликовати оригиналну величину, унапред подешену вредност, формат и редослед обраде. + Користите оригинално име датотеке, префикс, суфикс и временску ознаку када су вам потребна читљива имена за ручно сортирање. + Додајте унапред подешене вредности, режим размере, величину датотеке или информације о контролној суми када резултати морају да документују како су произведени. + Избегавајте насумична имена за токове посла где датотеке морају да остану упарене са оригиналима или редоследом страница. + Изаберите где се датотеке чувају + Избегните губитак извоза постављањем фасцикли, чувања оригиналне фасцикле и локација за једнократно чување + Нека резултати буду предвидљиви + Понашање чувања је најважније када обрађујете много датотека или делите датотеке од добављача у облаку. Подешавања фасцикле одлучују да ли се резултати прикупљају на једном познатом месту, појављују се поред оригинала или привремено иду негде другде за одређени задатак. + Подесите подразумевану фасциклу за чување ако увек желите да извоз буде на једном месту. + Користите фасциклу за чување у оригиналу за токове рада чишћења где излаз треба да остане близу изворне датотеке. + Користите локацију за једнократно чување када је за један задатак потребно друго одредиште без промене подразумеване. + Једнократне фасцикле за чување + Пошаљите следеће извозе у привремену фасциклу без промене уобичајеног одредишта + Одвојите посебне послове + Локација за једнократно чување је корисна за привремене пројекте, клијентске фасцикле, сесије чишћења или извозе који не би требало да се мешају са вашом уобичајеном фасциклом Имаге Тоолбок. Даје краткотрајну дестинацију без поновног писања вашег подразумеваног подешавања. + Изаберите једнократну фасциклу пре него што започнете задатак који припада изван ваше уобичајене локације за извоз. + Користите га за кратке пројекте, дељене фасцикле или групе где излази морају остати груписани заједно. + Вратите се у подразумевану фасциклу након посла како каснији извози не би неочекивано доспели на привремену локацију. + Уравнотежите квалитет и величину датотеке + Схватите када да промените димензије, формат или квалитет уместо да погађате + Намерно смањите датотеке + Величина датотеке зависи од димензија слике, формата, квалитета, транспарентности и сложености садржаја. Ако је датотека и даље претешка, промена димензија обично помаже више од сталног снижавања квалитета. + Прво промените величину када је слика већа него што одредиште може да прикаже. + Нижи квалитет само за формате са губицима и проверите текст, ивице, градијенте и лица након извоза. + Промените формат када промене квалитета нису довољне, на пример ВебП за дељење или ПНГ за јасне транспарентне материјале. + Радите са анимираним форматима + Користите ГИФ, АПНГ, ВебП и ЈКСЛ алатке када датотека има оквире уместо једне битмапе + Не третирајте сваку слику као мирну + Анимираним форматима су потребни алати који разумеју оквире, трајање и компатибилност. + Користите ГИФ или АПНГ алатке када су вам потребне операције на нивоу оквира за те формате. + Користите ВебП алате када вам је потребна модерна компресија или анимирани ВебП излаз. + Прегледајте време анимације пре дељења јер неке платформе претварају или изравнавају анимиране слике. + Упоредите и прегледајте излаз + Прегледајте промене, величину датотеке и визуелне разлике пре него што задржите извоз + Проверите пре дељења + Алати за поређење помажу да се рано открију артефакти компресије, погрешне боје или нежељене измене. + Отворите Поређење када желите да прегледате две верзије једну поред друге. + Зумирајте ивице, текст, градијенте и провидне области где је проблеме најлакше пропустити. + Ако је резултат превише тежак или мутан, вратите се на претходну алатку и подесите квалитет или формат. + Користите чвориште ПДФ алата + Спојите, поделите, ротирајте, уклоните странице, компримујте, заштитите, ОЦР и ПДФ датотеке са воденим жигом + Изаберите ПДФ операцију + ПДФ чвориште групише радње докумената иза једне улазне тачке тако да можете изабрати тачну операцију. Почните тамо када је датотека већ ПДФ и користите Имагес то ПДФ или Доцумент Сцаннер када је ваш извор још увек скуп слика. + Отворите ПДФ алатке и изаберите операцију која одговара вашем циљу. + Изаберите изворни ПДФ или слике, а затим прегледајте редослед страница, ротацију, заштиту или опције ОЦР. + Сачувајте генерисани ПДФ и отворите га једном да бисте потврдили број страница, редослед и читљивост. + Претворите слике у ПДФ + Комбинујте скениране слике, снимке екрана, признанице или фотографије у један документ за дељење + Направите чист документ + Слике постају боље ПДФ странице када су исечене, поређане и величине доследно. Третирајте листу слика као стог страница: свака ротација, маргина и избор величине странице утичу на то колико је читљив коначни документ. + Прво изрежите или ротирајте слике када су ивице странице, сенке или оријентација нетачни. + Изаберите слике у жељеном редоследу страница и прилагодите величину странице или маргине ако их алатка нуди. + Извезите ПДФ, а затим проверите да ли су све странице читљиве пре него што га пошаљете. + Скенирајте документе + Снимите папирне странице и припремите их за ПДФ, ОЦР или дељење + Снимите читљиве странице + Скенирање докумената најбоље функционише са равним страницама, добрим осветљењем и исправљеном перспективом. Чисто скенирање пре ОЦР или ПДФ извоза штеди време јер и препознавање текста и компресија зависе од квалитета странице. + Поставите документ на контрастну површину са довољно светла и минималним сенкама. + Подесите откривене углове или исеците ручно тако да страница чисто испуњава оквир. + Извезите као слике или ПДФ, а затим покрените ОЦР ако вам је потребан текст који може да се изабере. + Заштитите или откључајте ПДФ датотеке + Пажљиво користите лозинке и чувајте изворну копију која се може уређивати када је то могуће + Безбедно рукујте приступом ПДФ-у + Алати за заштиту су корисни за контролисано дељење, али лозинке је лако изгубити и тешко их је опоравити. Заштитите копије за дистрибуцију, чувајте незаштићене изворне датотеке одвојено и проверите резултат пре него што било шта избришете. + Заштитите копију ПДФ-а, а не ваш једини изворни документ. + Сачувајте лозинку негде на сигурном пре него што делите заштићену датотеку. + Користите откључавање само за датотеке које можете да уређујете, а затим проверите да ли се откључана копија исправно отвара. + Организујте ПДФ странице + Спојите, поделите, преуредите, ротирајте, исеците, уклоните странице и намерно додајте бројеве страница + Поправите структуру документа пре декорације + Алати за ПДФ странице се најлакше користе истим редоследом којим бисте припремили папирни документ: сакупите странице, уклоните погрешне, ставите их у ред, исправите оријентацију, а затим додајте коначне детаље као што су бројеви страница или водени жигови. + Прво користите спајање или сликање у ПДФ када је документ још увек подељен на неколико датотека. + Преуредите, ротирајте, исеците или уклоните странице пре додавања бројева страница, потписа или водених жигова. + Прегледајте коначни ПДФ након структурних измена јер једна страница која недостаје или је ротирана касније може бити тешко приметити. + ПДФ напомене + Уклоните напомене или их поравнајте када гледаоци другачије приказују коментаре и ознаке + Контролишите оно што остаје видљиво + Напомене могу бити коментари, истакнути делови, цртежи, ознаке обрасца или други додатни ПДФ објекти. Неки гледаоци их приказују другачије, тако да њихово уклањање или изравнавање може учинити дељене документе предвидљивијим. + Уклоните напомене када коментари, истакнути делови или ознаке рецензије не треба да буду укључени у дељену копију. + Поравнајте када су видљиве ознаке треба да остану на страници, али се више не понашају као белешке које се могу уређивати. + Задржите оригиналну копију јер уклањање или изравнавање напомена може бити тешко поништити. + Препознајте текст + Издвојите текст из слика помоћу ОЦР механизама и језика који се могу изабрати + Добијте боље ОЦР резултате + Прецизност ОЦР зависи од оштрине слике, језичких података, контраста и оријентације странице. Најбољи резултати обично долазе ако прво припремите слику: исеците шум, ротирајте усправно, повећајте читљивост, а затим препознајте текст. + Исеците слику до области текста и ротирајте је усправно пре препознавања. + Изаберите тачан језик и преузмите податке о језику ако апликација то захтева. + Копирајте или делите препознати текст, а затим ручно проверите имена, бројеве и знаке интерпункције. + Изаберите податке о ОЦР језику + Побољшајте препознавање усклађивањем скрипти, језика и преузетих ОЦР модела + Ускладите ОЦР са текстом + Погрешан ОЦР језик може учинити да добре фотографије изгледају као лоше препознавање. Подаци о језику су важни за писма, интерпункцију, бројеве и мешовите документе, па изаберите језик који се појављује на слици, а не језик корисничког интерфејса телефона. + Изаберите језик или писмо који се појављује на слици, а не само језик телефона. + Преузмите податке који недостају пре рада ван мреже или обраде серије. + За документе на мешовитим језицима, прво покрените најважнији језик и ручно проверите имена и бројеве. + ПДФ-ови који се могу претраживати + Напишите ОЦР текст назад у датотеке тако да се скениране странице могу претраживати и копирати + Претворите скениране документе у документе који се могу претраживати + ОЦР може учинити више од копирања текста у међуспремник. Када упишете препознати текст у датотеку или претраживи ПДФ, слика странице остаје видљива док текст постаје лакши за претраживање, одабир, индексирање или архивирање. + Користите ПДФ излаз који се може претраживати за скениране документе који би и даље требали изгледати као оригиналне странице. + Користите писање у датотеку када вам је потребан само издвојени текст и није вам стало до слике странице. + Ручно проверите важне бројеве, имена и табеле јер ОЦР текст може да се претражи, али је још увек несавршен. + Скенирајте КР кодове + Читајте КР садржај са улаза камере или сачуваних слика када су доступни + Читајте кодове безбедно + КР кодови могу да садрже везе, Ви-Фи податке, контакте, текст или друге корисне податке. + Отворите Сцан КР Цоде и држите код оштар, раван и потпуно унутар оквира. + Прегледајте декодирани садржај пре отварања линкова или копирања осетљивих података. + Ако скенирање не успе, побољшајте осветљење, исеците код или покушајте са изворном сликом веће резолуције. + Користите Басе64 и контролне суме + Кодирајте слике као текст, декодирајте текст назад у слике и проверите интегритет датотеке + Крећите се између датотека и текста + Басе64 је користан за мала оптерећења слика, док контролни суми помажу да се потврди да се датотеке нису промениле. Ови алати су најкориснији у техничким токовима посла, извештајима подршке, трансферима датотека и местима где бинарне датотеке морају привремено да постану текст. + Користите алатке Басе64 за кодирање мале слике када је за ток посла потребан текст уместо бинарне датотеке. + Декодирајте Басе64 само из поузданих извора и проверите преглед пре чувања. + Користите алатке за проверу када треба да упоредите извезене датотеке или потврдите отпремање/преузимање. + Пакујте или заштитите податке + Користите ЗИП и алате за шифровање за груписање датотека или трансформацију текстуалних података + Држите повезане датотеке заједно + Алати за паковање су корисни када неколико излаза треба да путују као једна датотека. Такође су добри за чување генерисаних слика, ПДФ-ова, евиденција и метаподатака заједно када треба да пошаљете комплетан скуп резултата. + Користите ЗИП када треба да пошаљете много извезених слика или докумената заједно. + Користите алате за шифровање само када разумете изабрани метод и можете безбедно да складиштите потребне кључеве. + Тестирајте креирану архиву или трансформисани текст пре брисања изворних датотека. + Контролне суме у именима + Користите имена датотека заснована на контролној суми када су јединственост и интегритет важнији од читљивости + Именујте датотеке према садржају + Имена датотека контролне суме су корисна када извози могу имати дуплирана видљива имена или када треба да докажете да су две датотеке идентичне. Они су мање пријатни за ручно прегледање, па их користите за техничке архиве и верификационе токове. + Омогућите контролни збир као име датотеке када је идентитет садржаја важнији од наслова читљивог људима. + Користите га за архиве, уклањање дупликата, поновљиве извозе или датотеке које се могу поново отпремати и преузимати. + Упарите имена контролних сума са фасциклама или метаподацима када људи још увек морају да разумеју шта датотека представља. + Изаберите боје са слика + Узорак пиксела, копирајте кодове боја и поново користите боје у палетама или дизајну + Узорак тачне боје + Бирање боја је корисно за усклађивање дизајна, издвајање боја бренда или проверу вредности слике. Зумирање је важно јер антиалиасинг, сенке и компресија могу учинити да се суседни пиксели мало разликују од боје коју очекујете. + Отворите Изаберите боју са слике и изаберите изворну слику са бојом која вам је потребна. + Увећајте пре узорковања малих детаља тако да оближњи пиксели не утичу на резултат. + Копирајте боју у формату који захтева ваше одредиште, као што је ХЕКС, РГБ или ХСВ. + Креирајте палете и користите библиотеку боја + Издвајајте палете, претражујте сачуване боје и одржавајте доследне визуелне системе + Направите палету за вишекратну употребу + Палете помажу да извезена графика, водени жигови и цртежи буду визуелно доследни. Посебно су корисни када више пута стављате коментаре на снимке екрана, припремате средства бренда или вам требају исте боје у неколико алата. + Генеришите палету са слике или додајте боје ручно када већ знате вредности. + Именујте или организујте важне боје тако да их касније можете поново пронаћи. + Користите копиране вредности у алатима за цртање, водени жиг, градијент, СВГ или спољне алатке за дизајн. + Креирајте градијенте + Направите градијентне слике за позадину, чуваре места и визуелне експерименте + Контролишите прелазе боја + Алатке за градијент су корисне када вам је потребна генерисана слика уместо да уређујете постојећу. Они могу постати чисте позадине, чувари места, позадине, маске или једноставна средства за спољне алате за дизајн. + Изаберите тип градијента и прво поставите важне боје. + Подесите правац, заустављања, глаткоћу и излазну величину за циљни случај употребе. + Извезите у формату који одговара одредишту, обично ПНГ за чисту генерисану графику. + Приступачност боја + Користите опције динамичких боја, контраста и далтониста када је кориснички интерфејс тешко читљив + Олакшајте употребу боја + Подешавања боја утичу на удобност, читљивост и брзину скенирања контрола. Ако вам је тешко разликовати чипове алата, клизаче или изабрана стања, опције теме и приступачности могу бити корисније од једноставне промене тамног режима. + Испробајте динамичке боје када желите да апликација прати тренутну палету позадина. + Повећајте контраст или промените шему када се дугмад и површине не истичу довољно. + Користите опције шеме за слепе боје ако је тешко разликовати нека стања или акценте. + Алфа позадина + Контролишите преглед или боју попуне која се користи око транспарентних ПНГ, ВебП и сличних излаза + Разумети транспарентне прегледе + Боја позадине за алфа формате може олакшати преглед прозирних слика, али је важно знати да ли мењате понашање прегледа/испуњавања или изравнавате коначни резултат. Ово је важно за налепнице, логотипе и слике уклоњене из позадине. + Прво користите формат који подржава алфа, као што је ПНГ или ВебП, када провидни пиксели морају да преживе извоз. + Подесите поставке боје позадине када је тешко видети прозирне ивице на површини апликације. + Извезите мали тест и поново га отворите у другој апликацији да бисте потврдили да ли су транспарентност или изабрана испуна сачувани. + Избор АИ модела + Изаберите модел према типу слике и дефекту уместо да прво изаберете највећи + Успоредите модел са оштећењем + АИ алати могу да преузимају или увозе различите ОННКС/ОРТ моделе, а сваки модел је обучен за одређену врсту проблема. Модел за ЈПЕГ блокове, чишћење аниме линија, уклањање шума, уклањање позадине, колоризацију или повећање величине може да произведе лошије резултате када се користи на погрешном типу слике. + Прочитајте опис модела пре преузимања; изаберите модел који именује ваш стварни проблем, као што је компресија, шум, полутон, замућење или уклањање позадине. + Почните са мањим или специфичнијим моделом када тестирате нову врсту слике, а затим упоредите са тежим моделима само ако резултат није довољно добар. + Задржите само моделе које заиста користите, јер преузети и увезени модели могу заузети значајан простор за складиштење. + Разумети породице модела + Називи модела често наговештавају њихову мету: модели у стилу РеалЕСРГАН-а унапређују, СЦУНет и ФБЦНН модели чисте шум или компресију, позадински модели креирају маске, а колоризери додају боју уносу у нијансама сиве. Увезени прилагођени модели могу бити моћни, али би требало да одговарају очекиваном улазно/излазном облику и да користе подржани ОННКС или ОРТ формат. + Користите алате за повећање нивоа када вам треба више пиксела, деноисере када је слика зрнаста и средства за уклањање артефаката када су блокови компресије или траке главни проблем. + Користите позадинске моделе само за одвајање субјеката; нису исто што и опште уклањање објеката или побољшање слике. + Увезите прилагођене моделе само из извора којима верујете и тестирајте их на малој слици пре употребе важних датотека. + АИ параметри + Подесите величину комада, преклапање и паралелне раднике да бисте уравнотежили квалитет, брзину и меморију + Уравнотежите брзину са стабилношћу + Величина комада контролише колико су велики делови слике пре него што их модел обради. Преклапање спаја суседне делове да би се сакриле границе. Паралелни радници могу убрзати обраду, али сваком раднику је потребна меморија, тако да високе вредности могу учинити велике слике нестабилним на телефонима са ограниченим РАМ-ом. + Користите веће комаде за глаткији контекст када ваш уређај поуздано рукује моделом и слика није велика. + Повећајте преклапање када границе комада постану видљиве, али запамтите да више преклапања значи више понављања рада. + Подигните паралелне раднике тек након стабилног теста; ако се обрада успорава, загрева уређај или се сруши, прво смањите број радника. + Избегавајте комаде артефаката + Комадовање омогућава апликацији да обрађује слике које су веће него што модел може удобно да обради одједном. Компромис је у томе што свака плочица има мање околног контекста, тако да ниско преклапање или сувише мали комади могу створити видљиве границе, промене текстуре или недоследне детаље између суседних области. + Повећајте преклапање ако видите правоугаоне ивице, поновљене промене текстуре или скокове детаља између обрађених региона. + Смањите величину комада ако процес не успе пре него што произведе излаз, посебно на великим сликама или тешким моделима. + Сачувајте мали тест исецања пре обраде целе слике да бисте могли брзо да упоредите параметре. + АИ стабилност + Мања величина комада, радници и резолуција извора када се АИ обрада сруши или замрзне + Опорави се од тешких АИ послова + АИ обрада је један од најтежих токова посла у апликацији. Падови, неуспеле сесије, замрзавање или изненадна рестартовања апликације обично значе да је модел, резолуција извора, величина комада или број паралелних радника превише захтеван за тренутно стање уређаја. + Ако апликација откаже или не успе да отвори сесију модела, смањите паралелне раднике и величину комада, покушајте поново са истом сликом. + Промените величину веома великих слика пре АИ обраде када циљ не захтева оригиналну резолуцију. + Затворите друге тешке апликације, користите мањи модел или обрадите мање датотека одједном када се притисак на меморију стално враћа. + Дијагностицирајте кварове модела + Неуспешно покретање АИ не значи увек да је слика лоша. Датотека модела може бити непотпуна, неподржана, превелика за меморију или неусклађена са операцијом. Када апликација пријави неуспелу сесију, третирајте то као сигнал да прво поједноставите модел и параметре. + Избришите и поново преузмите модел ако не успе одмах након преузимања или се понаша као да је датотека оштећена. + Испробајте уграђени модел за преузимање пре него што окривите увезени прилагођени модел, јер увезени модели могу имати некомпатибилне улазе. + Користите евиденције апликација након репродуковања грешке ако треба да пријавите грешку у паду или сесији. + Експериментишите у Схадер студију + Користите ефекте ГПУ схадера за напредну обраду слика и прилагођени изглед + Пажљиво радите са схадерима + Схадер Студио је моћан, али код и параметри схадера захтевају мале промене које се могу тестирати. Третирајте га као експерименталну алатку: одржавајте радну основну линију, мењајте једну по једну ствар и тестирајте са различитим величинама слике пре него што верујете унапред подешеној вредности. + Почните од познатог радног схадера или једноставног ефекта пре додавања параметара. + Промените један по један параметар или блок кода и проверите да ли у прегледу има грешака. + Извезите унапред подешене поставке тек након што их тестирате на неколико различитих величина слике. + Направите СВГ или АСЦИИ арт + Генеришите векторска средства или слике засноване на тексту за креативни излаз + Изаберите тип креативног излаза + СВГ и АСЦИИ алати служе различитим циљевима: скалабилна графика или рендеровање у стилу текста. Обе су креативне конверзије, тако да је преглед у коначној величини важнији од процене резултата на основу мале сличице. + Користите СВГ Макер када би резултат требало да буде чисто или да се поново користи у токовима рада дизајна. + Користите АСЦИИ Арт када желите стилизовани текстуални приказ слике. + Прегледајте у коначној величини јер мали детаљи могу нестати у генерисаном излазу. + Генеришите текстуре буке + Креирајте процедуралне текстуре за позадине, маске, преклапања или експерименте + Подесите процедурални излаз + Генерисање шума ствара нове слике из параметара, тако да мале промене могу произвести веома различите резултате. Користан је за текстуре, маске, преклапања, чуваре места или тестирање компресије са детаљним обрасцима. + Одаберите врсту шума и излазну величину пре подешавања финих детаља. + Подесите размеру, интензитет, боје или семе све док узорак не одговара циљној употреби. + Сачувајте корисне резултате и запишите параметре ако касније треба да поново направите текстуру. + Пројекти за означавање + Користите датотеке пројекта када напомене треба да остану уређиване након затварања апликације + Нека слојевите измене буду вишекратне + Датотеке пројекта за означавање су корисне када снимку екрана, дијаграму или инструкцији можда буду потребне промене касније. Извезене ПНГ/ЈПЕГ датотеке су коначне слике, док пројектне датотеке чувају слојеве који се могу уређивати и стање за уређивање за будућа прилагођавања. + Користите слојеве за означавање када би напомене, облачићи или елементи изгледа требало да остану да се уређују. + Сачувајте или поново отворите датотеку пројекта пре извоза коначне слике ако очекујете више ревизија. + Извезите нормалну слику само када сте спремни да делите спљоштену коначну верзију. + Шифрујте датотеке + Заштитите датотеке лозинком и тестирајте дешифровање пре него што избришете било коју изворну копију + Пажљиво рукујте шифрованим излазима + Алати за шифровање могу заштитити датотеке шифровањем заснованим на лозинки, али нису замена за памћење кључа или чување резервних копија. Шифровање је корисно за транспорт и складиштење, док је ЗИП бољи када је главни циљ спајање неколико излаза. + Прво шифрујте копију и чувајте оригинал док не тестирате да дешифровање функционише са лозинком коју сте сачували. + Користите менаџер лозинки или друго безбедно место за кључ; апликација не може да погоди изгубљену лозинку за вас. + Делите шифроване датотеке само са људима који такође имају лозинку и знају који алат или метод треба да их дешифрују. + Распоредите алате + Групирајте, наручите, претражите и закачите алате тако да главни екран одговара вашем стварном току рада + Убрзајте главни екран + Подешавања распореда алата су вредна подешавања када знате које алате највише користите. Груписање помаже у истраживању, прилагођени редослед помаже мишићној меморији, а фаворити одржавају дневне алате доступним чак и када цела листа постане велика. + Користите груписани режим док учите апликацију или када више волите алате организоване по типу задатка. + Пређите на прилагођени редослед када већ знате своје честе алате и желите мање померања. + Користите подешавања претраге и фаворите заједно за ретке алатке које памтите по имену, али не желите да буду видљиве све време. + Руковати великим датотекама + Избегавајте спор извоз, притисак на меморију и неочекивано велики излаз + Смањите посао пре извоза + Веома велике слике, дугачке серије и висококвалитетни формати могу заузети више меморије и времена. Најбрже решење је обично смањење количине посла пре најтеже операције, не чекање да се заврши исти превелики унос. + Промените величину огромних слика пре него што примените тешке филтере, ОЦР или ПДФ конверзију. + Прво користите мању серију да потврдите подешавања, а затим обрадите остатак са истим унапред подешеним. + Нижи квалитет или промените формат када је излазна датотека већа од очекиваног. + Кеш меморија и прегледи + Схватите генерисане прегледе, чишћење кеша и коришћење складишта након тешких сесија + Нека складиште и брзина буду уравнотежени + Генерисање прегледа и кеш меморија могу да убрзају поновљено прегледање, али тешке сесије уређивања могу оставити привремене податке за собом. Подешавања кеша помажу када је апликација спора, меморијски простор је мали или прегледи изгледају устајало након многих експеримената. + Нека прегледи буду омогућени када прегледате много слика важније је од минимизирања привременог складишта. + Обришите кеш меморију након великих серија, неуспешних експеримената или дугих сесија са много датотека високе резолуције. + Користите аутоматско брисање кеша ако више волите чишћење складишта него одржавање топлих прегледа између покретања. + Прилагодите понашање екрана + Користите опције преко целог екрана, безбедног режима, осветљености и системске траке за фокусиран рад + Нека интерфејс одговара ситуацији + Подешавања екрана помажу када уређујете у пејзажу, представљате приватне слике или вам је потребна доследна осветљеност. Нису потребни за нормалну употребу, али решавају незгодне ситуације као што су КР инспекција, приватни прегледи или скучено уређивање пејзажа. + Користите подешавања целог екрана или системске траке када је уређивање простора важније од навигације у Цхроме-у. + Користите безбедан режим када приватне слике не би требало да се појављују на снимцима екрана или прегледима апликација. + Користите примену осветљења за алате где је тешко прегледати тамне слике или КР кодове. + Задржите транспарентност + Спречите да провидне позадине постану беле, црне или спљоштене + Користите алфа-свесни излаз + Транспарентност опстаје само када изабрани алат и излазни формат подржавају алфа. Ако извезена позадина постане бела или црна, проблем је обично излазни формат, поставка испуне/позадине или одредишна апликација која је изравнала слику. + Изаберите ПНГ или ВебП када извозите слике, налепнице, логотипе или слојеве уклоњене из позадине. + Избегавајте ЈПЕГ за транспарентан излаз јер не чува алфа канал. + Проверите подешавања која се односе на боју позадине за алфа формате ако преглед приказује попуњену позадину. + Решите проблеме са дељењем и увозом + Користите подешавања бирача и подржане формате када се датотеке не појављују или не отварају исправно + Убаците датотеку у апликацију + Проблеми са увозом су често узроковани дозволама добављача, неподржаним форматима или понашањем бирача. Датотеке које се деле из апликација у облаку и месинџера могу бити привремене, тако да одабир трајног извора може бити поузданији за дуже измене. + Покушајте да отворите датотеку преко бирача система уместо пречице за недавне датотеке. + Ако један алат не подржава формат, прво га конвертујте или отворите одређенији алат. + Прегледајте подешавања бирача слика и покретача ако се токови дељења или директно отварање алата понашају другачије од очекиваног. + Потврда изласка + Избегавајте губитак несачуваних измена када се покрети, притисци уназад или промене алатки случајно догоде + Заштитите недовршени посао + Потврда излаза из алатке је корисна када користите навигацију покретима, уређујете велике датотеке или често прелазите са једне апликације на другу. Додаје малу паузу пре напуштања алатке тако да се недовршена подешавања и прегледи не изгубе случајно. + Омогућите потврду ако су случајни покрети уназад затворили алатку пре него што сте извезли. + Нека буде онемогућено ако углавном обављате брзе конверзије у једном кораку и преферирате бржу навигацију. + Користите га заједно са унапред подешеним вредностима за дуже токове рада где би обнављање подешавања било досадно. + Користите евиденције када пријављујете проблеме + Прикупите корисне детаље пре него што отворите проблем или контактирате програмера + Пријавите проблеме са контекстом + Јасан извештај са корацима, верзијом апликације, типом уноса и евиденцијама је много лакше дијагностиковати. Дневници су најкориснији одмах након репродукције проблема јер одржавају околни контекст свежим. + Забележите име алатке, тачну радњу и да ли се то дешава са једном или сваком датотеком. + Отворите евиденције апликација након што репродукујете проблем и делите релевантне излазне евиденције када је то могуће. + Приложите снимке екрана или узорке датотека само када не садрже приватне информације. + Обрада у позадини је заустављена + Андроид није дозволио да обавештење о обради у позадини почне на време. Ово је временско ограничење система које се не може поуздано поправити. Поново покрените апликацију и покушајте поново; Одржавање апликације отвореном и омогућавање обавештења или рада у позадини може помоћи. + Прескочи бирање датотека + Отварајте алатке брже када унос обично долази са дељења, међуспремника или претходног екрана + Учините поновљено покретање алата брже + Прескочи избор датотека мења први корак подржаних алата. Корисно је када обично унесете алатку са већ изабраном сликом или из Андроид акција дељења, али може бити збуњујуће ако очекујете да ће сваки алат одмах затражити датотеку. + Омогућите га само када ваш уобичајени ток већ пружа изворну слику пре него што се алатка отвори. + Нека буде онемогућено док учите апликацију, јер експлицитно бирање чини сваки ток алата лакшим за разумевање. + Ако се алатка отвори без очигледног уноса, онемогућите ову поставку или почните са Дели/Отвори са тако да Андроид прво проследи датотеку. + Прво отворите едитор + Прескочите преглед када би дељена слика требало да иде директно у уређивање + Изаберите преглед или измену као прву станицу + Опен Едит Инстеад Оф Превиев је за кориснике који већ знају да желе да измене слику. Преглед је безбеднији када прегледате датотеке из ћаскања или складишта у облаку; директна измена је бржа за снимке екрана, брза усецања, белешке и једнократне исправке. + Омогућите директну измену ако већини дељених слика одмах буду потребне измене исецања, означавања, филтера или извоза. + Прво оставите преглед када често прегледате метаподатке, димензије, транспарентност или квалитет слике пре него што одлучите. + Користите ово заједно са омиљеним тако да дељене слике буду близу алата које заправо користите. + Унапред подешени унос текста + Унесите тачне унапред подешене вредности уместо да више пута подешавате контроле + Користите прецизне вредности када су клизачи спори + Унапред подешени унос текста помаже када су вам потребни тачни бројеви за поновљени рад: величине друштвених слика, маргине документа, циљеви компресије, ширине линија или друге вредности до којих је досадно доћи покретима. Такође је корисно на малим екранима где је теже померање финог клизача. + Омогућите унос текста ако често поново користите тачне величине, проценте, вредности квалитета или параметре алата. + Сачувајте вредност као унапред подешену вредност након што је једном унесете, а затим је поново употребите уместо да поново правите исту поставку. + Задржите контроле покретима за грубо визуелно подешавање и откуцане вредности за строге излазне захтеве. + Сачувај близу оригинала + Ставите генерисане датотеке поред изворних слика када је контекст фасцикле битан + Чувајте излазе са њиховим изворима + Сачувај у оригиналну фасциклу је згодан за фасцикле камере, фасцикле пројекта, скениране документе и групе клијената где уређена копија треба да остане поред извора. Може бити мање уредан од наменске излазне фасцикле, па је комбинујте са јасним суфиксима или обрасцима назива датотеке. + Омогућите га када је сваки изворни фолдер већ смислен и излази треба да остану груписани тамо. + Користите суфиксе назива датотеке, префиксе, временске ознаке или обрасце како би се уређене копије лако одвојиле од оригинала. + Онемогућите га за мешовите групе када желите да се све генерисане датотеке сакупе у једној фасцикли за извоз. + Прескочите већи излаз + Избегавајте чување конверзија које неочекивано постану теже од извора + Заштитите серије од лоших изненађења величине + Неке конверзије чине датотеке већим, посебно ПНГ снимке екрана, већ компримоване фотографије, висококвалитетни ВебП или слике са сачуваним метаподацима. Дозволи прескакање ако је већи спречава те излазе да замене мањи извор у токовима посла где је смањење величине главни циљ. + Омогућите га када је извоз намењен компримовању, промени величине или припреми датотека за ограничења за отпремање. + Прегледајте прескочене датотеке након серије; можда су већ оптимизовани или ће можда требати другачији формат. + Онемогућите га када су квалитет, транспарентност или компатибилност формата важнији од коначне величине датотеке. + Међуспремник и везе + Контролишите аутоматски унос међумеморије и прегледе линкова за брже алатке засноване на тексту + Учините аутоматски унос предвидљивим + Подешавања међуспремника и линкова су згодна за КР, Басе64, УРЛ и токове посла са великим бројем текстова, али аутоматски унос треба да остане намеран. Ако се чини да апликација сама попуњава поља, проверите понашање лепљења у међуспремнику; ако су картице са везама бучне или споре, прилагодите понашање прегледа везе. + Дозволите аутоматско лепљење у међуспремник када често копирате текст и одмах отворите одговарајући алат. + Онемогућите аутоматско лепљење ако се приватни садржај међумеморије појављује у алатима где то нисте очекивали. + Искључите прегледе веза када је преузимање прегледа споро, ометајуће или непотребно за ваш ток посла. + Компактне контроле + Користите гушће бираче и брзо постављање подешавања када је простора на екрану мало + Поставите више контрола на мале екране + Компактни бирачи и брзо постављање подешавања помажу на малим телефонима, пејзажном уређивању и алатима са много параметара. Компромис је у томе што контроле могу бити гушће, тако да је ово најбоље након што већ препознате уобичајене опције. + Омогућите компактне бираче када вам дијалози или листе опција буду превисоки за екран. + Подесите страну брзих подешавања ако су контроле алата лакше доступне са једне стране екрана. + Вратите се на пространије контроле ако још увек учите апликацију или често пропуштате опције густих додира. + Учитајте слике са веба + Налепите УРЛ странице или слике, прегледајте рашчлањене слике, а затим сачувајте или поделите изабране резултате + Намерно користите веб слике + Веб Имаге Лоадинг анализира изворе слика са датог УРЛ-а, прегледа прву доступну слику и омогућава вам да сачувате или делите изабране рашчлањене слике. Неки сајтови користе привремене, заштићене везе или везе генерисане скриптом, тако да страница која ради у прегледачу можда и даље не приказује употребљиве слике. + Налепите директну УРЛ адресу слике или УРЛ странице и сачекајте да се појави рашчлањена листа слика. + Користите контроле оквира или селекције када страница садржи неколико слика и потребне су вам само неке од њих. + Ако се ништа не учита, покушајте прво са директном везом слике или преузмите преко претраживача; сајт може блокирати рашчлањивање или користити приватне везе. + Изаберите тип промене величине + Разумите експлицитно, флексибилно, исецање и уклапање пре него што наметнете слику у нове димензије + Контролишите облик, платно и однос страница + Тип промене величине одлучује шта ће се десити када тражена ширина и висина не одговарају оригиналном односу ширине и висине. То је разлика између растезања пиксела, очувања пропорција, исецања додатне површине или додавања позадинског платна. + Користите Експлицитно само када је изобличење прихватљиво или извор већ има исти однос ширине и висине као циљни. + Користите Флексибилно да задржите пропорције тако што ћете променити величину са важне стране, посебно за фотографије и мешовите серије. + Користите Опсецање за строге оквире без ивица или Уклопи када цела слика мора остати видљива унутар фиксног платна. + Изаберите режим скале слике + Одаберите филтер за поновно узорковање који контролише оштрину, глаткоћу, брзину и артефакте ивица + Промените величину без случајне мекоће + Режим размере слике контролише како се нови пиксели израчунавају током промене величине. Једноставни режими су брзи и предвидљиви, док напредни филтери могу боље да сачувају детаље, али могу да изоштре ивице, створе ореоле или потрају дуже на великим сликама. + Користите Басиц или Билинеар за брзо свакодневно мењање величине када резултат треба само да буде мањи и чист. + Користите режиме Ланцзос, Робидоук, Сплине или ЕВА у стилу када су важни ситни детаљи, текст или висококвалитетно смањење. + Користите Најближе за слику пиксела, иконе и графику са чврстим ивицама где би замућени пиксели уништили изглед. + Скала простора боја + Одлучите како се боје мешају док се пиксели поново узоркују током промене величине + Држите градијенте и ивице природним + Скалирање простора боја мења математику која се користи приликом промене величине. Већина слика је у реду са подразумеваним, али линеарни или перцептивни простори могу учинити да градијенти, тамне ивице и засићене боје изгледају чистије након снажног скалирања. + Оставите подразумевану вредност када су брзина и компатибилност важнији од малих разлика у боји. + Испробајте линеарне или перцептуалне режиме када тамни обриси, снимци екрана корисничког интерфејса, градијенти или траке боја изгледају погрешно након промене величине. + Упоредите пре и после на стварном циљном екрану, јер промене у простору боја могу бити суптилне и зависне од садржаја. + Ограничите режиме промене величине + Знајте када слике са прекораченим ограничењем треба прескочити, поново кодирати или смањити како би се уклопиле + Изаберите шта се дешава на граници + Лимит Ресизе није само алатка за мање слике. Његов режим одлучује шта апликација ради када је извор већ унутар ваших граница или када само једна страна прекрши правило, што је важно за мешане фасцикле и припрему за отпремање. + Користите Прескочи када слике унутар граница треба да буду нетакнуте и да се не извозе поново. + Користите Рецоде када су димензије прихватљиве, али вам је и даље потребан нови формат, понашање метаподатака, квалитет или назив датотеке. + Користите зумирање када слике које прелазе границе треба да буду пропорционално смањене док се не уклапају у дозвољени оквир. + Циљани однос ширине и висине + Избегавајте растегнута лица, одсечене ивице и неочекиване ивице у строгим излазним величинама + Ускладите облик пре промене величине + Ширина и висина су само половина циља за промену величине. Однос ширине и висине одређује да ли слика може да стане природно, да ли је потребно исецање или је потребно платно око ње. Прво провера облика спречава најизненађујуће резултате промене величине. + Упоредите изворни облик са циљним обликом пре него што изаберете Експлицитно, Изрежи или Уклопи. + Прво исеците када субјекат може да изгуби додатну позадину и дестинацији је потребан строг оквир. + Користите Фит или Флексибилно када сваки део оригиналне слике мора остати видљив. + Упсцале или нормална промена величине + Знајте када је за додавање пиксела потребна АИ и када је једноставно скалирање довољно + Не мешајте величину са детаљима + Нормална промена величине мења димензије интерполацијом пиксела; не може да измисли праве детаље. Висока интелигенција може да створи детаље оштријег изгледа, али је спорија и може да халуцинира текстуру, тако да прави избор зависи од тога да ли вам је потребна компатибилност, брзина или визуелна рестаурација. + Користите нормалну промену величине за сличице, ограничења отпремања, снимке екрана и једноставне промене димензија. + Користите АИ повећање када мала или мека слика треба да изгледа боље у већој величини. + Упоредите лица, текст и обрасце након побољшања АИ јер генерисани детаљи могу изгледати убедљиво, али нетачно. + Редослед филтера је важан + Примените чишћење, боју, оштрину и коначну компресију у намерном низу + Направите чистији сноп филтера + Филтери нису увек заменљиви. Замућење пре изоштравања, појачање контраста пре ОЦР-а или промена боје пре компресије могу да произведу веома различите резултате истих контрола. + Прво исеците и ротирајте како би каснији филтери радили само на пикселима који ће остати. + Примените експозицију, баланс белог и контраст пре изоштравања или стилизованих ефеката. + Задржите коначну промену величине и компресију при крају, тако да прегледани детаљи преживе извоз на предвидљив начин. + Поправите ивице позадине + Очистите ореоле, преостале сенке, косу, рупе и меке обрисе након аутоматског уклањања + Нека изрези изгледају намерно + Аутоматске маске често изгледају добро на први поглед, али откривају проблеме на светлим, тамним или шареним позадинама. Чишћење ивица је разлика између брзог изрезивања и вишекратне налепнице, логотипа или слике производа. + Прегледајте резултат на контрастној позадини да бисте открили ореоле и детаље ивица које недостају. + Користите рестаурацију за косу, углове и провидне објекте пре него што избришете околне остатке. + Извезите мали тест за коначну боју позадине када ће изрез бити постављен у дизајн. + Читљиви водени жигови + Нека ознаке буду видљиве на светлим, тамним, заузетим и исеченим сликама, а да притом не уништите фотографију + Заштитите слику без скривања + Водени жиг који добро изгледа на једној слици може нестати на другој. Величина, непрозирност, контраст, положај и понављање треба да буду изабрани за целу групу, а не само за први преглед. + Тестирајте ознаку на најсветлијим и најтамнијим сликама у групи пре него што извезете све датотеке. + Користите нижу непрозирност за велике поновљене ознаке и јачи контраст за мале углове. + Нека важна лица, текст и детаљи о производу буду јасни, осим ако ознака није намењена да спречи поновну употребу. + Поделите једну слику на плочице + Исеците једну слику по редовима, колонама, прилагођеним процентима, формату и квалитету + Припремите решетке и наручене комаде + Подела слике ствара више излаза из једне изворне слике. Може да подели по броју редова и колона, да прилагоди проценте редова или колона и да сачува делове са изабраним излазним форматом и квалитетом, што је корисно за мреже, делове странице, панораме и наручене објаве на друштвеним мрежама. + Изаберите изворну слику, а затим подесите број редова и колона који су вам потребни. + Подесите проценте редова или колона када делови не би требало да буду једнаке величине. + Изаберите излазни формат и квалитет пре чувања тако да сваки генерисани комад користи иста правила за извоз. + Изрежите траке са сликама + Уклоните вертикалне или хоризонталне регионе и спојите преостале делове заједно + Уклоните регион без остављања празнине + Имаге Сечење се разликује од нормалног усева. Може да уклони одабрану вертикалну или хоризонталну траку, а затим споји преостале делове слике. Уместо тога, инверзни режим задржава изабрани регион, а коришћење оба инверзна смера задржава изабрани правоугаоник. + Користите вертикално сечење за бочне регионе, стубове или средње траке; користите хоризонтално сечење за банере, празнине или редове. + Омогућите инверзију на оси када желите да задржите изабрани део уместо да га уклоните. + Прегледајте ивице након сечења јер спојени садржај може изгледати нагло када уклоњена област пређе важне детаље. + Изаберите излазни формат + Изаберите ЈПЕГ, ПНГ, ВебП, АВИФ, ЈКСЛ или ПДФ на основу садржаја и одредишта + Нека одредиште одлучи о формату + Најбољи формат зависи од тога шта слика садржи и где ће се користити. Фотографије, снимци екрана, провидна средства, документи и анимиране датотеке не успевају на различите начине када се наметну у погрешан формат. + Користите ЈПЕГ за широку компатибилност фотографија када нису потребни транспарентност и тачни пиксели. + Користите ПНГ за оштре снимке екрана, снимање корисничког интерфејса, транспарентну графику и измене без губитака. + Користите ВебП, АВИФ или ЈКСЛ када је компактан модеран излаз важан и апликација за пријем то подржава. + Извуците аудио омоте + Сачувајте уграђене омоте албума или доступне медијске оквире из аудио датотека као ПНГ слике + Извуците уметничко дело из аудио датотека + Аудио Цоверс користи Андроид медијске метаподатке за читање уграђених уметничких дела из аудио датотека. Када уграђена илустрација није доступна, ретривер се може вратити на медијски оквир ако га Андроид може обезбедити; ако ниједно не постоји, алатка пријављује да нема слике за издвајање. + Отворите Аудио Цоверс и изаберите једну или више аудио датотека са очекиваном омотом. + Прегледајте извучени омот пре чувања или дељења, посебно за датотеке из апликација за стримовање или снимање. + Ако слика није пронађена, аудио датотека вероватно нема уграђену омотницу или Андроид не може да открије употребљив оквир. + Датуми и метаподаци о локацији + Одлучите шта ћете сачувати када је време снимања важно, али ГПС или подаци уређаја не би требало да процуре + Одвојите корисне метаподатке од приватних метаподатака + Метаподаци нису сви подједнако ризични. Датуми снимања могу бити корисни за архивирање и сортирање, док ГПС, поља налик на серијски уређај, софтверске ознаке или поља власника могу открити више од предвиђеног. + Чувајте ознаке датума и времена када је хронолошки ред битан за албуме, скенирања или историју пројекта. + Уклоните ГПС и ознаке које идентификују уређај пре јавног дељења или слања слика странцима. + Извезите узорак и поново проверите ЕКСИФ када су захтеви за приватност строги. + Избегавајте колизије имена датотеке + Заштитите групне излазе када многе датотеке деле имена као што су имаге.јпг или сцреенсхот.пнг + Нека свако име излаза буде јединствено + Дупликати имена датотека су уобичајени када слике долазе из гласника, снимака екрана, фасцикли у облаку или више камера. Добар образац спречава случајну замену и омогућава праћење резултата до њихових извора. + Укључите оригинално име плус суфикс када би уређена копија требало да буде препознатљива. + Додајте секвенцу, временску ознаку, унапред подешене вредности или димензије када обрађујете велике мешовите серије. + Избегавајте преписивање токова посла док не потврдите да образац именовања не може да се сукоби. + Сачувајте или делите + Изаберите између трајне датотеке и привременог преноса на другу апликацију + Извезите са правом намером + Сачувај и дели могу да се осећају слично, али решавају различите проблеме. Чување ствара датотеку коју можете пронаћи касније; дељење шаље генерисани резултат преко Андроид-а у другу апликацију и можда неће оставити трајну локалну копију. + Користите Сачувај када излаз мора да остане у познатој фасцикли, да се направи резервна копија или да се касније поново користи. + Користите Дели за брзе поруке, прилоге е-поште, објављивање на друштвеним мрежама или слање резултата другом уређивачу. + Сачувајте прво када је апликација која прима непоуздана или када би неуспело дељење било досадно за поновно креирање. + Величина ПДФ странице и маргине + Одаберите величину папира, понашање и простор за дисање пре креирања документа + Учините странице са сликама за штампање и читање + Слике могу постати незгодне ПДФ странице када њихов облик не одговара одабраној величини папира. Маргине, режим уклапања и оријентација странице одлучују да ли ће садржај бити изрезан, сићушан, растегнут или удобан за читање. + Користите праву величину папира када се ПДФ може одштампати или послати у образац. + Користите маргине за скенирање и снимке екрана тако да текст не лежи уз ивицу странице. + Прегледајте усправне и водоравне странице заједно када изворне слике имају мешовиту оријентацију. + Очистите скенирања + Побољшајте ивице папира, сенке, контраст, ротацију и читљивост пре ПДФ-а или ОЦР-а + Припремите странице пре архивирања + Скенирање се може технички снимити, али је и даље тешко читати. Мали кораци чишћења пре извоза ПДФ-а често су важнији од подешавања компресије, посебно за признанице, руком писане белешке и странице при слабом осветљењу. + Исправите перспективу и исеците ивице стола, прсте, сенке и неред у позадини. + Пажљиво повећавајте контраст како би текст постао јаснији без уништавања печата, потписа или трагова оловком. + Покрените ОЦР само након што је страница усправна и читљива, а затим ручно проверите важне бројеве. + Компримујте, сиве боје или поправите ПДФ-ове + Користите ПДФ операције са једном датотеком за лакше копије докумената, за штампање или преправљене копије докумената + Изаберите операцију чишћења ПДФ-а + ПДФ Алати укључују одвојене операције за компресију, конверзију сивих тонова и поправку. Компресија и сиве нијансе раде углавном преко уграђених слика, док поправка обнавља структуру ПДФ-а; ништа од овога не треба третирати као гарантовано решење за сваки документ. + Користите Цомпресс ПДФ када документ садржи слике и величина датотеке је битна за дељење. + Користите нијансе сиве када би документ требало да буде лакши за штампање или му нису потребне слике у боји. + Користите Поправи ПДФ на копији када се документ не отвори или има оштећену унутрашњу структуру, а затим проверите поново направљену датотеку. + Извуците слике из ПДФ-ова + Опоравите уграђене ПДФ слике и спакујте екстраховане резултате у архиву + Сачувајте изворне слике из докумената + Ектрацт Имагес скенира ПДФ у потрази за уграђеним објектима слике, прескаче дупликате где је то могуће и чува опорављене слике у генерисаној ЗИП архиви. Извлачи само слике које су заправо уграђене у ПДФ, а не текст, векторске цртеже или сваку видљиву страницу као снимак екрана. + Користите Ектрацт Имагес када су вам потребне слике ускладиштене у ПДФ-у, као што су фотографије из каталога или скенирана средства. + Користите ПДФ за слике или екстракцију страница уместо тога када су вам потребне целе странице, укључујући текст и изглед. + Ако алатка не пријави да нема уграђених слика, страница може бити текстуални/векторски садржај или слике можда неће бити ускладиштене као објекти који се могу издвојити. + Метаподаци, изравнавање и штампање + Уредите својства документа, растеризујте странице или намерно припремите прилагођене распореде штампања + Изаберите радњу завршног документа + ПДФ метаподаци, изравнавање и припрема за штампу решавају различите проблеме у завршној фази. Метаподаци контролишу својства документа, Флаттен ПДФ растеризује странице у излаз који се мање може уређивати, а Принт ПДФ припрема нови изглед са величином странице, оријентацијом, маргинама и контролама страница по листу. + Користите метаподатке када су аутор, наслов, кључне речи, произвођач или чишћење приватности важни. + Користите Флаттен ПДФ када би видљиви садржај странице требало да буде теже уређивати као засебне ПДФ објекте. + Користите Принт ПДФ када вам је потребна прилагођена величина странице, оријентација, маргине или више страница по листу. + Припремите слике за ОЦР + Користите исецање, ротацију, контраст, смањење шума и размеру пре него што затражите од ОЦР-а да прочита текст + Дајте ОЦР чисте пикселе + ОЦР грешке често потичу из улазне слике, а не из ОЦР механизма. Искривљене странице, низак контраст, замућење, сенке, сићушан текст и мешана позадина смањују квалитет препознавања. + Исеците област за текст и ротирајте слику тако да линије буду хоризонталне пре препознавања. + Повећајте контраст или претворите у чистију црно-белу само када то олакшава читање слова. + Умерено промените величину малог текста нагоре, али избегавајте агресивно изоштравање које ствара лажне ивице. + Проверите КР садржај + Прегледајте везе, Ви-Фи акредитиве, контакте и радње пре него што верујете коду + Третирајте декодирани текст као непоуздани унос + КР код може да сакрије УРЛ, контакт картицу, лозинку за Ви-Фи, догађај у календару, број телефона или обичан текст. Видљива ознака у близини кода можда се не подудара са стварном носивошћу, па прегледајте декодирани садржај пре него што реагујете на њега. + Прегледајте пуну декодирану УРЛ адресу пре него што је отворите, посебно скраћене или непознате домене. + Будите опрезни са Ви-Фи, контактима, календаром, телефоном и СМС кодовима јер они могу покренути осетљиве радње. + Прво копирајте садржај када треба да га верификујете на другом месту уместо да га одмах отворите. + Генеришите КР кодове + Креирајте кодове од обичног текста, УРЛ адреса, Ви-Фи, контаката, е-поште, телефона, СМС-а и података о локацији + Направите прави КР терет + КР екран може да генерише кодове од унетог садржаја као и да скенира постојеће. Структурирани КР типови помажу код кодирања података у формату који друге апликације разумеју, као што су детаљи за пријаву на Ви-Фи, контакт картице, поља е-поште, бројеви телефона, СМС садржај, УРЛ адресе или географске координате. + Изаберите обичан текст за једноставне белешке или користите структурирани тип када код треба да се отвори као Ви-Фи, контакт, УРЛ, имејл, телефон, СМС или подаци о локацији. + Проверите свако поље пре него што поделите генерисани код јер видљиви преглед одражава само оптерећење које сте унели. + Тестирајте сачувани КР код помоћу скенера када ће бити одштампан, јавно објављен или када ће га други људи користити. + Изаберите режим контролне суме + Израчунајте хешове из датотека или текста, упоредите једну датотеку или групно проверите више датотека + Проверите садржај, а не изглед + Алатке за проверу имају засебне картице за хеширање датотека, хеширање текста, поређење датотеке са познатим хешом и групно поређење. Контролна сума доказује бајт за бајт идентитет за изабрани алгоритам; то не доказује да две слике само изгледају слично. + Користите Цалцулате за датотеке и Тект Хасх за укуцане или налепљене текстуалне податке. + Користите Упореди када вам неко да очекивани контролни збир за једну датотеку. + Користите групно поређење када је многим датотекама потребно хеширање или верификација у једном пролазу, а затим одржавајте изабрани алгоритам доследним. + Приватност међуспремника + Користите аутоматске функције међуспремника без случајног излагања приватних копираних података + Држите копирани садржај намерно + Аутоматизација међуспремника је згодна, али копирани текст може да садржи лозинке, везе, адресе, токене или приватне белешке. Третирајте унос заснован на међуспремнику као функцију брзине која треба да одговара вашим навикама и очекивањима приватности. + Онемогућите аутоматско коришћење међуспремника ако се приватни текст неочекивано појави у алатима. + Користите чување само у међуспремнику само када намерно желите да резултат избегнете складиштење. + Обришите или замените међуспремник након руковања осетљивим текстом, КР подацима или Басе64 корисним учитавањем. + Формати боја + Разумети ХЕКС, РГБ, ХСВ, алфа и копиране вредности боја пре него што их налепите на друго место + Копирајте формат који циљ очекује + Иста видљива боја може се написати на неколико начина. Алат за дизајн, ЦСС поље, Андроид вредност боје или уређивач слика могу очекивати другачији редослед, алфа руковање или модел боја. + Користите ХЕКС када лепите у веб, дизајн или поља теме која очекују компактне кодове боја. + Користите РГБ или ХСВ када требате ручно да подесите канале, осветљеност, засићеност или нијансу. + Проверите алфа одвојено када је транспарентност важна јер је нека одредишта игноришу или користе другачији редослед. + Прегледајте библиотеку боја + Претражујте именоване боје по имену или ХЕКС и држите омиљене при врху + Пронађите именоване боје за вишекратну употребу + Библиотека боја учитава именовану колекцију боја, сортира је по имену, филтрира према називу боје или ХЕКС вредности и складишти омиљене боје како би важни уноси били лакше доступни. Корисно је када вам је потребна права именована боја уместо узорковања са слике. + Претражујте по имену боје када знате породицу, као што је црвена, плава, шкриљаста или мента. + Претражујте по ХЕКС-у када већ имате нумеричку боју и желите да пронађете одговарајуће или оближње именоване уносе. + Означите честе боје као омиљене тако да се померају испред опште библиотеке током прегледања. + Користите колекције градијената мреже + Преузмите доступне ресурсе мрежног градијента и делите изабране слике + Користите средства даљинског градијента мреже + Мрежни градијенти могу да учитавају удаљену колекцију ресурса, преузимају слике прелива који недостају, приказују напредак преузимања и деле изабране градијенте. Доступност зависи од приступа мрежи и удаљеног складишта ресурса, тако да празна листа обично значи да ресурси још нису били доступни. + Отворите Месх Градијент из алата за градијент када желите готове слике прелива мреже. + Сачекајте учитавање ресурса или напредак преузимања пре него што претпоставите да је колекција празна. + Изаберите и делите градијенте који су вам потребни или се вратите у Градиент Макер када желите да ручно направите прилагођени градијент. + Резолуција генерисане имовине + Изаберите величину излаза пре него што генеришете градијенте, шум, позадине, уметност налик СВГ-у или текстуре + Генеришите у величини која вам је потребна + Генерисане слике немају оригиналну камеру на коју би се могли вратити. Ако је излаз премали, касније скалирање може омекшати обрасце, померити детаље или променити начин на који се плочице и компримују. + Прво подесите димензије за крајњи случај употребе, као што су позадина, икона, позадина, штампа или преклапање. + Користите веће величине за текстуре које се могу изрезати, зумирати или поново користити у неколико изгледа. + Извезите тестне верзије пре огромних излаза јер детаљи процедуре могу променити начин на који се компресија понаша. + Извезите тренутне позадине + Преузмите доступне позадине Почетна, Закључај и уграђене позадине са уређаја + Сачувајте или делите инсталиране позадине + Извоз позадина чита позадине које Андроид излаже преко системских АПИ-ја за позадину. У зависности од уређаја, верзије Андроид-а, дозвола и варијанте израде, Хоме, Лоцк или уграђене позадине могу бити доступне засебно или могу недостајати. + Отворите извоз позадина да бисте учитали позадине које систем дозвољава апликацији да чита. + Изаберите доступне ставке Почетна, Закључај или уграђене позадине које желите да сачувате, копирате или делите. + Ако позадина недостаје или је функција недоступна, то је обично ограничење система, дозволе или варијанте израде, а не поставка за уређивање. + Цорнерс Аниматион Тхроттле + Копирај боју као + ХЕКС + име + Портретни модел подлоге за брзо сечење лица са мекшом косом и руковањем ивицама. + Брзо побољшање при слабом осветљењу помоћу процене криве Зеро-ДЦЕ++. + Рестормер модел рестаурације слике за смањење трагова кише и артефаката по влажном времену. + Модел одмотавања документа који предвиђа координатну мрежу и пресликава закривљене странице у равније скенирање. + Скала трајања покрета + Контролише брзину анимације апликације: 0 онемогућава кретање, 1 је нормално, веће вредности успоравају анимације + Прикажи недавне алатке + Прикажите брзи приступ недавно коришћеним алатима на главном екрану + Статистика коришћења + Истражите отварање апликације, покретање алата и најчешће коришћене алате + Апликација се отвара + Алат се отвара + Последњи алат + Успешно чување + Низ активности + Подаци су сачувани + Топ формат + Коришћени алати + __ПХ_0__ отвара • __ПХ_1__ + Још увек нема статистике коришћења + Отворите неколико алата и они ће се аутоматски појавити овде + Ваша статистика коришћења се чува само локално на вашем уређају. ИмагеТоолбок их користи само за приказ ваших личних активности, омиљених алата и увида у сачуване податке + уређивач уређивати слику ретуширати подесити + промени величину претворити размеру резолуцију димензије ширина висина јпг јпег пнг вебп компрес + компримирати величина тежина бајтова мб кб смањити квалитет оптимизирати + обрезивање исецање размера ширине и висине + филтер ефекат корекција боје подешавање лут маске + цртање четкицом оловком означавање коментара + шифра шифровање дешифровање шифра тајна + уклањање позадине брисање уклонити изрез транспарентан алфа + прегледајте галерију прегледајте отворите + спајање спојева комбинује панораму дугачак снимак екрана + урл веб слика за преузимање интернет везе + пицкер узорак капаљке хек ргб боја + Палета боја узорци издвајају доминантне + екиф метаподаци приватност уклонити стрип цлеан ГПС локацију + упореди разлику разлика пре после + лимит ресизе мак мин мегапиксели димензије стезаљка + пдф алати за странице докумената + оцр текст препознаје скенирање екстракта + градијентна мрежа боја позадине + водени жиг лого текст печат преклапање + гиф анимација анимирани оквири за претварање + апнг анимација анимирани пнг претворити оквире + зип архива компримовати пакет распаковати датотеке + јкл јпег кл претворити јпг формат слике + свг вецтор траце цонверт кмл + формат претворити јпг јпег пнг вебп хеиц авиф јкл бмп + скенер докумената скенирање папира у перспективи изрезивање + кр скенирање скенера бар кода + слагање преклапање просечне средње астрофотографије + подељене плочице решеткасти делови + цолор цонверт хек ргб хсл хсв цмик лаб + вебп конвертује формат анимиране слике + генератор буке насумична текстура зрна + комбинација распореда колаж мреже + слојеви за означавање означавају уређивање преклапања + басе64 енцоде децоде дата ури + хеш контролне суме мд5 сха сха1 сха256 провери + мрежаста градијентна позадина + екиф метаподаци уређивати ГПС камеру за датуме + исећи плочице са подељеном мрежом + аудио омот албума музички метаподаци + позадину за извоз тапета + асции текстуални ликови + аи мл неурална побољшања врхунског сегмента + палета материјала библиотеке боја под називом боје + схадер глсл фрагмент ефекат студио + пдф спајање комбинује спајање + пдф поделити засебне странице + пдф ротирати оријентацију страница + пдф преуредити преуредити странице сортирати + пдф бројеви страница пагинација + пдф оцр текст препознаје претраживи екстракт + пдф текст логотипа воденог жига + пдф потпис знак извлачење + пдф заштита шифровање лозинком закључавање + пдф дешифровање лозинке за откључавање уклања заштиту + пдф компримирати смањити величину оптимизирати + пдф црно-бело црно-бело једнобојно + пдф поправка поправка опоравити сломљена + пдф својства метаподатака екиф наслов аутора + пдф уклонити брисање страница + пдф обрезивање маргина + пдф изравнати напомене формира слојеве + пдф екстракт слике слика + компресија пдф зип архиве + пдф штампач + читач прегледа пдф-а + слике у пдф претворити јпг јпег пнг документ + пдф у слике страница извоз јпг пнг + пдф напомене коментари уклонити чисте + Неутрал Вариант + Грешка + Дроп Схадов + Висина зуба + Хоризонтални опсег зуба + Вертикални опсег зуба + Топ Едге + Десна ивица + Боттом Едге + Лева ивица + Торн Едге + Ресетујте статистику коришћења + Алат се отвара, чува статистику, низ, сачувани подаци и горњи формат ће бити ресетовани. Отварања апликације ће остати непромењена. + Аудио + Филе + Извези профиле + Сачувајте тренутни профил + Избришите профил за извоз + Да ли желите да избришете профил за извоз \\"__ПХ_0__\\"? + Цртање границе + Покажите танку ивицу око платна за цртање и брисање + Таласаста + Заобљени елементи корисничког интерфејса са меком таласастом ивицом + Сцооп + Нотцх + Заобљени углови урезани према унутра + Степени углови са двоструким резом + Чувар места + Користите {филенаме} да бисте уметнули оригинално име датотеке без екстензије + Фларе + Основни износ + Количина прстена + Количина зрака + Ширина прстена + Дисторт Перспецтиве + Јава изглед и осећај + Смицање + Кс угао + и угао + Промените величину излаза + Ватер Дроп + Таласна дужина + Фаза + Хигх Пасс + Цолор Маск + Цхроме + Растворити се + Мекоћа + Повратне информације + Ленс Блур + Лов инпут + Хигх инпут + Низак излаз + Хигх оутпут + Лигхт Еффецтс + Висина ударца + Бумп софтнесс + Риппле + Кс амплитуда + И амплитуда + Кс таласна дужина + И таласна дужина + Адаптиве Блур + Генерисање текстура + Генеришите различите процедуралне текстуре и сачувајте их као слике + Тип текстуре + Биас + Време + Сјај + Дисперзија + Узорци + Коефицијент угла + Коефицијент градијента + Тип мреже + Дистанце повер + Скала И + Фуззинесс + Тип основе + Фактор турбуленције + Скалирање + Итерације + Прстенови + Брусхед Метал + Цаустицс + Целлулар + Цхецкербоард + фБм + Мермер + Плазма + Јорган + Дрво + насумично + Скуаре + Хекагонал + Оцтагонал + Триангулар + Ридгед + ВЛ Ноисе + СЦ Ноисе + Недавно + генератор текстуре брушени метал цаустицс ћелија шаховница мермер плазма јорган дрво цигла камуфлажа ћелија облак пукотина тканина лишће саће лед лава маглина папир рђа песак дим камен терен топографија воде таласање + Још увек нема недавно коришћених филтера + Брицк + Камуфлажа + Целл + Цлоуд + Црацк + Фабриц + Лишће + Хонеицомб + Ице + Лава + Небула + Папир + Руст + Песак + Смоке + Стоне + Терен + Топографија + Ватер Риппле + Адванцед Воод + Ширина малтера + Неправилност + Бевел + Храпавост + Први праг + Други праг + Трећи праг + Мекоћа ивице + Ширина ивице + Покривеност + Детаљ + Дубина + Гранање + Хоризонталне нити + Вертикалне нити + Фузз + Вене + Осветљење + Ширина пукотине + Фрост + Флов + Цруст + Густина облака + Звезде + Густина влакана + Снага влакана + Мрље + Корозија + Питтинг + Пахуљице + Дина фреквенција + Угао ветра + Рипплес + Виспс + Скала вена + Ниво воде + Планински ниво + Ерозија + Ниво снега + Број линија + Дебљина линије + Сенчење + Боја пора + Боја малтера + Тамна боја цигле + Боја цигле + Истакните боју + Тамна боја + Боја шуме + Боја земље + Боја песка + Боја позадине + Боја ћелије + Боја ивице + Боја неба + Боја сенке + Светла боја + Боја површине + Боја варијације + Боја пукотина + Варп боја + Боја потке + Тамна боја листова + Боја листова + Боја ивице + Боја меда + Дубока боја + Боја леда + Боја мраза + Боја коре + Оперите боју + Боја сјаја + Боја свемира + Љубичаста боја + Плава боја + Основна боја + Боја влакана + Боја мрље + Метална боја + Тамна боја рђе + Боја рђе + Наранџаста боја + Боја дима + Боја вена + Боја низије + Водена боја + Боја камена + Боја снега + Ниска боја + Висока боја + Боја линије + Плитка боја + Трава + Прљавштина + Кожа + Бетон + Аспхалт + Мосс + Ватра + Аурора + Оил Слицк + Ватерцолор + Абстрацт Флов + Опал + Дамасцус Стеел + Муња + Велвет + Инк Марблинг + Холографска фолија + Биолуминисценција + Цосмиц Вортек + Лава Ламп + Хоризонт догађаја + Фрацтал Блоом + Цхроматиц Туннел + Ецлипсе Цорона + Странге Аттрацтор + Феррофлуид Цровн + Супернова + Ирис + Пеацоцк Феатхер + Наутилус Схелл + Прстенаста планета + Густина оштрице + Дужина сечива + Ветар + Патцхнесс + Цлумпс + Влага + Шљунак + Боре + Поре + Агрегат + Пукотине + Тар + Носите + Пеге + Влакна + Фреквенција пламена + Смоке + Интензитет + Траке + Бандс + Иридесценција + Тама + Блоомс + Пигмент + Ивице + Папир + Дифузија + Симетрија + Оштрина + Игра боја + Млечност + Слојеви + Фолдинг + пољски + Огранци + Правац + Схеен + Преклопи + Перје + Баланс мастила + Спецтрум + Цринклес + Дифракција + Оружје + Твист + Цоре сјај + Блобс + Дисц тилт + Величина хоризонта + Ширина диска + Ленсинг + Петалс + Цурл + Филигрански + Фацети + Закривљеност + Величина месеца + Величина короне + Раис + Дијамантски прстен + Лобес + Густина орбите + Дебљина + Шиљци + Дужина шиљака + Величина тела + Металик + Радијус ударца + Ширина шкољке + Избачен + Величина зенице + Величина шаренице + Варијација боје + Цатцхлигхт + Величина очију + Густина шипка + Окреће се + Цхамберс + Отварање + Ридгес + Пеарлесценце + Величина планете + Нагиб прстена + Ширина прстена + Атмосфера + Боја прљавштине + Тамна боја траве + Боја траве + Боја врха + Тамна боја земље + Сува боја + Боја шљунка + Боја коже + Боја бетона + Боја катрана + Боја асфалта + Боја камена + Боја прашине + Боја тла + Боја тамне маховине + Боја маховине + Црвена боја + Цоре цолор + Зелена боја + Цијан боја + Магента боја + Златна боја + Боја папира + Боја пигмента + Секундарна боја + Прва боја + Друга боја + Пинк цолор + Тамна челична боја + Боја челика + Светла челична боја + Боја оксида + Хало боја + Боја вијака + Баршунаста боја + Схеен цолор + Плава боја мастила + Црвена боја мастила + Тамна боја мастила + Сребрна боја + Жута боја + Боја ткива + Боја диска + Врућа боја + Боја сочива + Спољна боја + Унутрашња боја + Цорона боја + Хладна боја + Топла боја + Боја облака + Боја пламена + Боја перја + Боја шкољке + Бисерна боја + Боја планете + Боја прстена + Избришите оригиналне датотеке након чувања + Само успешно обрађене изворне датотеке ће бити избрисане + Оригиналне датотеке су избрисане: __ПХ_0__ + Брисање оригиналних датотека није успело: __ПХ_0__ + Оригиналне датотеке избрисане: __ПХ_0__, неуспешно: __ПХ_1__ + Покрети са листова + Дозволите превлачење проширених листова опција + Подузорковање боје + Дубина бита + Без губитака + __ПХ_0__-бит + Батцх Ренаме + Преименујте више датотека користећи обрасце имена датотека + пакетно преименовање фајлова име датотеке образац секвенце екстензија датума + Изаберите датотеке за преименовање + Образац назива датотеке + Преименуј + Извор датума + ЕКСИФ датум је снимљен + Датум измене датотеке + Датум креирања датотеке + Тренутни датум + Ручни датум + Ручни датум + Ручно време + Изабрани датум је недоступан за неке датотеке. За њих ће се користити тренутни датум. + Унесите образац назива датотеке + Образац производи неважеће или предугачко име датотеке + Шаблон производи дупле називе датотека + Сва имена датотека се већ подударају са шаблоном + Овај образац користи токене који нису доступни за групно преименовање + Име ће остати исто + Датотеке су успешно преименоване + Неке датотеке се не могу преименовати + Дозвола није дата + Користи оригинално име датотеке без екстензије, помажући вам да идентификацију извора сачувате нетакнутом. Такође подржава сечење са \\о{старт:енд}, транслитерацију са \\о{т} и замену са \\о{с/олд/нев/}. + Бројач са аутоматским повећањем. Подржава прилагођено форматирање: \\ц{паддинг} (нпр. \\ц{3} -&gt; 001), \\ц{старт:степ} или \\ц{старт:степ:паддинг}. + Име надређене фасцикле у којој се налазила оригинална датотека. + Величина оригиналне датотеке. Подржава јединице: \\з{б}, \\з{кб}, \\з{мб} или само \\з за људски читљив формат. + Генерише насумични универзално јединствени идентификатор (УУИД) да би се обезбедила јединственост имена датотеке. + Преименовање датотека се не може опозвати. Уверите се да су нова имена тачна пре него што наставите. + Потврдите преименовање + Подешавања бочног менија + Скала менија + Мени алфа + Аутоматски баланс белог + Цлиппинг + Провера скривених водених жигова + Складиштење + Ограничење кеша + Аутоматски обришите кеш када пређе ову величину + Интервал чишћења + Колико често треба проверити да ли кеш треба обрисати + Приликом покретања апликације + 1 дан + 1 недеља + 1 месец + Аутоматски обришите кеш апликације у складу са изабраним ограничењем и интервалом + Покретна површина усева + Омогућава померање целе области усева превлачењем унутар ње + Споји ГИФ + Комбинујте више ГИФ клипова у једну анимацију + Ред ГИФ клипова + Реверсе + Играјте обрнуто + Бумеранг + Играјте унапред, а затим уназад + Нормализујте величину оквира + Скалирајте оквире тако да одговарају највећем клипу; искључите да бисте сачували оригиналну размеру + Кашњење између клипова (мс) + Фолдер + Изаберите све слике из фасцикле и њених потфасцикли + Дуплицате Финдер + Пронађите тачне копије и визуелно сличне слике + проналазач дупликата сличних слика тачне копије фотографија чишћење складиштење дХасх СХА-256 + Изаберите слике да бисте пронашли дупликате + Осетљивост на сличност + Тачне копије + Сличне слике + Изаберите све тачне дупликате + Изаберите све осим препоручених + Није пронађен ниједан дупликат + Покушајте да додате више слика или повећате осетљивост на сличност + Није могуће прочитати __ПХ_0__ слике + __ПХ_0__ • __ПХ_1__ може се повратити + __ПХ_0__ групе • __ПХ_1__ које се могу повратити + __ПХ_0__ изабрано • __ПХ_1__ + Ово се не може поништити. Андроид може од вас тражити да потврдите брисање у системском дијалогу. + Није пронађено + Померите се за почетак + Пређите до краја + \ No newline at end of file diff --git a/core/resources/src/main/res/values-sv/strings.xml b/core/resources/src/main/res/values-sv/strings.xml new file mode 100644 index 0000000..a1e6827 --- /dev/null +++ b/core/resources/src/main/res/values-sv/strings.xml @@ -0,0 +1,3739 @@ + + + + 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 + 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 & 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 + 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 & 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 & 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. + 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. + 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 + Innehållsmedveten distorsion + Inte tillräckligt med minne för att slutföra den här åtgärden. Försök att använda en mindre bild, stänga andra appar eller starta om appen. + Parallellarbetare + Dessa bilder sparades inte eftersom de konverterade filerna skulle vara större än originalen. Kontrollera den här listan innan du tar bort originalbilder. + Överhoppade filer: %1$s + Apploggar + Visa loggar för appen för att upptäcka problemen + Favoriter i grupperade verktyg + Lägger till favoriter som en flik när verktyg är grupperade efter typ + Visa favorit som sist + Flyttar fliken favoritverktyg till slutet + Horisontellt avstånd + Vertikalt avstånd + Bakåt energi + Använd enkel gradient magnitud energikarta istället för framåt energialgoritm + Ta bort det maskerade området + Sömmar kommer att passera genom det maskerade området och tar bara bort det valda området istället för att skydda det + VHS NTSC + Avancerade NTSC-inställningar + Extra analog inställning för starkare VHS och sändningsartefakter + Kromblödning + Tejp slitage + Spårning + Luma utstryk + Ringande + Snö + Använd fält + Filtertyp + Ingång luma filter + Ingång kroma lågpass + Kromademodulering + Fasförskjutning + Fasförskjutning + Huvudbyte + Huvudbytehöjd + Huvudomkopplingsförskjutning + Huvudväxling + Mittlinjeläge + Mittlinjejitter + Spårning av bullerhöjd + Spårningsvåg + Spåra snö + Spåra snöanisotropi + Spårningsljud + Spåra brusintensitet + Kompositbrus + Kompositbrusfrekvens + Kompositbrusintensitet + Kompositbrusdetalj + Ringningsfrekvens + Ringande kraft + Luma brus + Luma brusfrekvens + Luma brusintensitet + Luma brusdetalj + Chroma brus + Chroma brus frekvens + Chroma brusintensitet + Chroma brusdetalj + Snöintensitet + Snönisotropi + Kromfasbrus + Chroma-fasfel + Chroma delay horisontell + Chroma delay vertikal + VHS-bandhastighet + VHS-kromförlust + VHS skärpa intensitet + VHS skärpa frekvens + VHS-kantvågsintensitet + VHS kantvågshastighet + VHS kantvågsfrekvens + VHS-kantvågsdetalj + Utgång kroma lågpass + Skala horisontellt + Skala vertikalt + Skalfaktor X + Skalfaktor Y + Upptäcker vanliga bildvattenstämplar och målar dem med LaMa. Laddar ned detekterings- och målningsmodeller automatiskt + Deuteranopia + Expandera bild + Objektsegmenteringsbaserad bakgrundsborttagning med YOLO v11 Segmentation + Animerade emojis + Visa tillgängliga emoji som animationer + Visa linjevinkel + Visar den aktuella linjens rotation i grader medan du ritar + Spara i originalmapp + Spara nya filer bredvid originalfilen istället för den valda mappen + Vattenstämpel pdf + Inte tillräckligt med minne + Bilden är sannolikt för stor för att bearbetas på den här enheten, eller så har systemet slut på tillgängligt minne. Försök att minska bildupplösningen, stänga andra appar eller välja en mindre fil. + Felsökningsmeny + Meny för att testa appfunktioner, detta är inte avsett att visas i produktionsversionen + Leverantör + PaddleOCR kräver ytterligare ONNX-modeller på din enhet. Vill du ladda ner %1$s data? + Universell + koreanska + latin + östslaviska + Thai + grekiska + engelska + Kyrillisk + arabiska + Devanagari + Tamil + Telugu + Shader + Shader förinställd + Ingen skuggning har valts + Shader Studio + Skapa, redigera, validera, importera och exportera anpassade fragmentskuggningar + Sparade shaders + Öppna sparade skuggningar, duplicera, exportera, dela eller ta bort + Ny shader + Shader-fil + .itshader JSON + %1$d parametrar + Shader källa + Skriv bara texten i void main(). Använd textureCoordinate för UV-koordinater och inputImageTexture som källprovtagare. + Funktioner + Valfria hjälpfunktioner, konstanter och strukturer infogade före void main(). Uniformer genereras från params. + Lägg till uniformer här när skuggningen behöver redigerbara värden. + Återställ shader + Aktuellt skuggutkast kommer att rensas + Ogiltig skuggning + Skuggningsversion %1$d som inte stöds. Version som stöds: %2$d. + Shadernamn får inte vara tomt. + Shader-källa får inte vara tom. + Shader-källan innehåller tecken som inte stöds: %1$s. Använd endast ASCII GLSL-källa. + Parametern \\\"%1$s\\\" deklareras mer än en gång. + Parameternamn får inte vara tomma. + Parametern \\\"%1$s\\\" använder ett reserverat GPUIimage-namn. + Parametern \\\"%1$s\\\" måste vara en giltig GLSL-identifierare. + Parametern \\\"%1$s\\\" har %2$s värdetypen \\\"%3$s\\\", förväntad \\\"%4$s\\\". + Parametern \\\"%1$s\\\" kan inte definiera min eller max för boolvärden. + Parametern \\\"%1$s\\\" har min större än max. + Parameter \\\"%1$s\\\" standard är lägre än min. + Parametern \\\"%1$s\\\" standard är större än max. + Shader måste deklarera \\\"uniform sampler2D %1$s;\\\". + Shader måste deklarera \\\"varierande vec2 %1$s;\\\". + Shader måste definiera \\\"void main()\\\". + Shader måste skriva en färg till gl_FragColor. + ShaderToy mainImage shaders stöds inte. Använd GPUImages void main()-kontrakt. + Animerad ShaderToy-uniform \\\"iTime\\\" stöds inte. + Animerad ShaderToy-uniform \\\"iFrame\\\" stöds inte. + ShaderToy uniform \\\"iResolution\\\" stöds inte. + ShaderToy iChannel-texturer stöds inte. + Externa texturer stöds inte. + Externa texturtillägg stöds inte. + Libretro-skuggningsparametrar stöds inte. + Endast en inmatningstextur stöds. Ta bort \\\"uniform %1$s %2$s;\\\". + Parametern \\\"%1$s\\\" måste deklareras som \\\"uniform %2$s %1$s;\\\". + Parametern \\\"%1$s\\\" deklareras som \\\"uniform %2$s %1$s;\\\", förväntad \\\"uniform %3$s %1$s;\\\". + Shader-filen är tom. + Shader-filen måste vara ett .itshader JSON-objekt med version, namn och shader-fält. + Shader-filen är inte giltig JSON eller matchar inte .itshader-formatet. + Fält \\\"%1$s\\\" är obligatoriskt. + %1$s krävs. + %1$s \\\"%2$s\\\" stöds inte. Typer som stöds: %3$s. + %1$s krävs för parametrarna \\\"%2$s\\\". + %1$s måste vara ett ändligt tal. + %1$s måste vara ett heltal. + %1$s måste vara sant eller falskt. + %1$s måste vara en färgsträng, RGB/RGBA-matris eller färgobjekt. + %1$s måste använda formatet #RRGGBB eller #RRGGBBAA. + %1$s måste innehålla giltiga hexadecimala färgkanaler. + %1$s måste innehålla 3 eller 4 färgkanaler. + %1$s måste vara mellan 0 och 255. + %1$s måste vara en tvåsiffrig matris eller ett objekt med x och y. + %1$s måste innehålla exakt två siffror. + Duplicera + Rensa alltid EXIF + Ta bort bild EXIF-data vid lagring, även när ett verktyg begär att behålla metadata + Hjälp & Tips + %1$d självstudier + Öppna verktyget + Lär dig de viktigaste verktygen, exportalternativ, PDF-flöden, färgverktyg och korrigeringar för vanliga problem + Komma igång + Välj verktyg, importera filer, spara resultat och återanvänd inställningar snabbare + Bildredigering + Ändra storlek, beskära, filtrera, radera bakgrunder, rita och vattenstämpla bilder + Filer och metadata + Konvertera format, jämför utdata, skydda integriteten och kontrollera filnamn + PDF och dokument + Skapa PDF-filer, skanna dokument, OCR-sidor och förbered filer för delning + Text, QR och data + Känn igen text, skanna koder, koda Base64, kontrollera hash och paketfiler + Färgverktyg + Välj färger, bygg paletter, utforska färgbibliotek och skapa övertoningar + Kreativa verktyg + Generera SVG, ASCII-konst, brusstrukturer, shaders och experimentella bilder + Felsökning + Åtgärda problem med tunga filer, transparens, delning, import och rapportering + Välj rätt verktyg + Använd kategorier, sökning, favoriter och dela åtgärder för att börja på rätt plats + Börja från den bästa ingångspunkten + Image Toolbox har många fokuserade verktyg, och många av dem överlappar varandra vid första anblicken. Börja från uppgiften du vill slutföra och välj sedan verktyget som styr den viktigaste delen av resultatet: storlek, format, metadata, text, PDF-sidor, färger eller layout. + Använd sökning när du känner till åtgärdens namn, till exempel ändra storlek, PDF, EXIF, OCR, QR eller färg. + Öppna en kategori när du bara känner till uppgiftstypen och fäst sedan vanliga verktyg som favoriter. + När en annan app delar en bild i Image Toolbox väljer du verktyget från flödet för delningsark. + Importera, spara och dela resultat + Förstå det vanliga flödet från att plocka indata till att exportera den redigerade filen + Följ det vanliga redigeringsflödet + De flesta verktyg följer samma rytm: välj inmatning, justera alternativ, förhandsgranska och spara eller dela sedan. Den viktiga detaljen är att sparandet skapar en utdatafil, medan delning vanligtvis är bäst för snabb överlämning till en annan app. + Välj en fil för enbildsverktyg eller flera filer för batchverktyg när väljaren tillåter det. + Kontrollera förhandsgranskningen och utdatakontrollerna innan du sparar, särskilt alternativ för format, kvalitet och filnamn. + Använd dela för en snabb handoff, eller spara när du behöver en beständig kopia i den valda mappen. + Arbeta med många bilder samtidigt + Batch-verktyg är bäst för upprepade uppgifter om storleksändring, konvertering, komprimering och namngivning + Förbered en förutsägbar batch + Batchbearbetning är snabbast när varje utdata ska följa samma regler. Det är också det enklaste stället att göra ett stort misstag, så välj namn, kvalitet, metadata och mappbeteende innan du påbörjar en lång export. + Välj alla relaterade bilder tillsammans och behåll ordningen om resultatet beror på sekvensen. + Ställ in regler för storlek, format, kvalitet, metadata och filnamn en gång innan du startar exporten. + Kör en liten testbatch först när källfilerna är stora eller när målformatet är nytt för dig. + Snabb singelredigering + Använd allt-i-ett-redigeraren när du behöver flera enkla ändringar på en bild + Avsluta en bild utan verktygshoppning + Single Edit är användbart när bilden behöver en liten kedja av ändringar snarare än en specialiserad operation. Det är vanligtvis snabbare för snabb rensning, förhandsgranskning och export av en slutlig kopia utan att bygga ett batch-arbetsflöde. + Öppna Single Edit för en bild som behöver vanliga justeringar, markeringar, beskärning, rotation eller exportändringar. + Använd redigeringar i den ordning som ändrar minst pixlar först, till exempel beskär före filter och filter före slutlig komprimering. + Använd ett specialiserat verktyg istället när du behöver batchbearbetning, exakta filstorleksmål, PDF-utdata, OCR eller rensning av metadata. + Använd förinställningar och inställningar + Spara upprepade val och ställ in appen för ditt föredragna arbetsflöde + Undvik att upprepa samma inställning + Förinställningar och inställningar är användbara när du exporterar samma typ av fil ofta. En bra förinställning förvandlar en upprepad manuell checklista till ett tryck, medan globala inställningar definierar standardinställningarna som nya verktyg börjar med. + Skapa förinställningar för vanliga utdatastorlekar, format, kvalitetsvärden eller namnmönster. + Granska inställningarna för standardformat, kvalitet, sparamapp, filnamn, väljare och gränssnittsbeteende. + Säkerhetskopiera inställningarna innan du installerar om appen eller flyttar till en annan enhet. + Ställ in användbara standardinställningar + Välj standardformat, kvalitet, skalningsläge, storleksändringstyp och färgrymd en gång + Få nya verktyg att börja närmare ditt mål + Standardvärden är inte bara kosmetiska preferenser. De bestämmer vilket format, kvalitet, storleksändringsbeteende, skalningsläge och färgrymd som visas först i många verktyg, vilket hjälper till att undvika att välja om samma val vid varje export. + Ställ in standardbildformat och -kvalitet för att matcha din vanliga destination, till exempel JPEG för foton eller PNG/WebP för alfa. + Justera standardstorlekstyp och skalningsläge om du ofta exporterar för strikta dimensioner, sociala plattformar eller dokumentsidor. + Ändra standardinställningarna för färgrymd endast när ditt arbetsflöde behöver konsekvent färghantering på skärmar, tryckta eller externa redigerare. + Picker och launcher + Använd väljarinställningar när appen öppnar för många dialogrutor eller startar på fel ställe + Låt öppna filer matcha din vana + Väljar- och startinställningar styr om Image Toolbox startar från huvudskärmen, ett verktyg eller ett filvalsflöde. Dessa alternativ är särskilt användbara när du vanligtvis går in från Android-delarket eller när du vill att appen ska kännas mer som en verktygsstartare. + Använd inställningar för fotoväljare om systemväljaren döljer filer, visar för många senaste objekt eller hanterar molnfiler dåligt. + Aktivera hoppa över plockning endast för verktyg där du vanligtvis anger från delningen eller redan känner till källfilen. + Granska startläge om du vill att Image Toolbox ska bete sig mer som en verktygsväljare än en vanlig startskärm. + Bildkälla + Välj det väljarläge som fungerar bäst med gallerier, filhanterare och molnfiler + Välj filer från rätt källa + Inställningar för bildkälla påverkar hur appen ber Android om bilder. Det bästa valet beror på om dina filer finns i systemgalleriet, en filhanterarmapp, en molnleverantör eller en annan app som delar tillfälligt innehåll. + Använd standardväljaren när du vill ha den mest systemintegrerade upplevelsen för vanliga galleribilder. + Byt väljarläge om album, mappar, molnfiler eller icke-standardformat inte visas som du förväntar dig. + Om en delad fil försvinner efter att ha lämnat källappen öppnar du den igen genom en beständig väljare eller sparar en lokal kopia först. + Favoriter och dela verktyg + Håll huvudskärmen fokuserad och dölj verktyg som inte är vettiga i aktieflöden + Minska bullret från verktygslistan + Favoriter och inställningar för dolda för dela är användbara när du bara använder ett fåtal verktyg varje dag. De håller också delningsflödet praktiskt genom att dölja verktyg som inte kan använda den typ av fil som en annan app skickar. + Nåla fast verktyg så att de är lätta att nå även när verktyg är grupperade efter kategori. + Dölj verktyg från delningsflöden när de är irrelevanta för filer som kommer från en annan app. + Använd favoritbeställningsinställningarna om du föredrar favoriter i slutet eller grupperade med relaterade verktyg. + Säkerhetskopiera inställningar + Flytta förinställningar, inställningar och appbeteende till en annan installation på ett säkert sätt + Håll din installation portabel + Säkerhetskopiering och återställning är mest användbart efter att du har ställt in förinställningar, mappar, filnamnsregler, verktygsordning och visuella inställningar. En säkerhetskopia låter dig experimentera med inställningar eller installera om appen utan att bygga om ditt arbetsflöde från minnet. + Skapa en säkerhetskopia efter att ha ändrat förinställningar, filnamnsbeteende, standardformat eller verktygsarrangemang. + Förvara säkerhetskopian någonstans utanför appmappen om du planerar att rensa data, installera om eller flytta enheter. + Återställ inställningarna innan du bearbetar viktiga partier så att standardinställningar och sparbeteende matchar din gamla konfiguration. + Ändra storlek och konvertera bilder + Ändra storlek, format, kvalitet och metadata för en eller flera bilder + Skapa kopior med ändrad storlek + Ändra storlek och konvertera är huvudverktyget för förutsägbara dimensioner och lättare utdatafiler. Använd den när bildens storlek, utdataformatet och metadatabeteendet har betydelse samtidigt. + Välj en eller flera bilder och välj sedan om dimensioner, skala eller filstorlek har störst betydelse. + Välj utdataformat och kvalitet; använd PNG eller WebP när transparens måste bevaras. + Förhandsgranska resultatet och spara eller dela sedan de genererade kopiorna utan att ändra originalen. + Ändra storlek efter filstorlek + Rikta in en storleksgräns när en webbplats, budbärare eller formulär avvisar tunga bilder + Passa strikta uppladdningsgränser + Ändra storlek efter filstorlek är bättre än att gissa kvalitetsvärden när destinationen endast accepterar filer under en specifik gräns. Verktyget kan justera komprimering och dimensioner för att nå målet samtidigt som det försöker hålla resultatet användbart. + Ange målstorleken något under den verkliga uppladdningsgränsen för att lämna utrymme för metadata och leverantörsändringar. + Föredrar förlustformat för foton när målet är litet; PNG kan förbli stor även efter storleksändring. + Inspektera ytor, text och kanter efter export eftersom aggressiva storleksmål kan introducera synliga artefakter. + Begränsa storleksändring + Förminska endast bilder som överskrider dina maximala bredd-, höjd- eller filbegränsningar + Undvik att ändra storlek på bilder som redan är bra + Limit Resize är användbart för blandade mappar där vissa bilder är enorma och andra redan är förberedda. Istället för att tvinga varje fil igenom samma storleksändring, håller den acceptabla bilder närmare sin ursprungliga kvalitet. + Ställ in maximala dimensioner eller gränser baserat på destinationen istället för att ändra storlek på varje fil i blindo. + Aktivera stilbeteende för skip-om-larger när du vill undvika utdata som blir tyngre än källor. + Använd detta för batcher från olika kameror, skärmdumpar eller nedladdningar där källstorlekarna varierar mycket. + Beskär, rotera och räta ut + Rama in det viktiga området innan du exporterar eller använder bilden i andra verktyg + Rensa upp kompositionen + Beskärning först gör senare filter, OCR, PDF-sidor och delningsresultat renare. + Öppna Beskär och välj den bild du vill justera. + Använd fri beskärning eller ett bildförhållande när resultatet måste passa ett inlägg, dokument eller avatar. + Rotera eller vänd innan du sparar så att senare verktyg får den korrigerade bilden. + Använd filter och justeringar + Bygg en redigerbar filterstack och förhandsgranska resultatet före export + Justera bildens utseende + Filter är användbara för snabba korrigeringar, stiliserad utskrift och förberedelse av bilder för OCR eller utskrift. Små förändringar av kontrast, skärpa och ljusstyrka kan ha större betydelse än tunga effekter när bilden innehåller text eller fina detaljer. + Lägg till filter ett efter ett och håll ett öga på förhandsgranskningen efter varje ändring. + Justera ljusstyrka, kontrast, skärpa, oskärpa, färg eller masker beroende på uppgiften. + Exportera endast när förhandsgranskningen matchar din målenhet, dokument eller sociala plattform. + Förhandsgranska först + Inspektera bilder innan du väljer ett tyngre verktyg för redigering, konvertering eller rensning + Kolla vad du faktiskt fått + Bildförhandsgranskning är användbar när en fil kommer från chatt, molnlagring eller en annan app och du inte är säker på vad den innehåller. Att först kontrollera dimensioner, transparens, orientering och visuell kvalitet hjälper till att välja rätt uppföljningsverktyg. + Öppna Bildförhandsgranskning när du behöver inspektera flera bilder innan du bestämmer dig för vad du ska göra med dem. + Leta efter rotationsproblem, genomskinliga områden, kompressionsartefakter och oväntat stora dimensioner. + Flytta till Ändra storlek, Beskär, Jämför, EXIF ​​eller Background Remover först när du vet vad som behöver åtgärdas. + Ta bort eller återställ en bakgrund + Radera bakgrunder automatiskt eller förfina kanterna manuellt med återställningsverktyg + Behåll ämnet, ta bort resten + Bakgrundsborttagning fungerar bäst när du kombinerar automatiskt val med noggrann manuell rengöring. Det slutliga exportformatet spelar lika stor roll som masken, eftersom JPEG kommer att platta till genomskinliga områden även om förhandsgranskningen ser korrekt ut. + Börja med automatisk borttagning om motivet är tydligt skilt från bakgrunden. + Zooma in och växla mellan radera och återställa för att fixa hår, hörn, skuggor och små detaljer. + Spara som PNG eller WebP när du behöver transparens i den slutliga filen. + Rita, markera och vattenstämpla + Lägg till pilar, anteckningar, markeringar, signaturer eller upprepade vattenstämplar + Lägg till synlig information + Markeringsverktyg hjälper till att förklara en bild, medan vattenmärken hjälper till att varumärket eller skydda exporterade filer. + Använd Rita när du behöver frihandsanteckningar, former, markering eller snabba anteckningar. + Använd vattenmärkning när samma text- eller bildmärke ska visas konsekvent på utdata. + Kontrollera opacitet, position och skala innan du exporterar så att märket är synligt men inte distraherande. + Rita standardvärden + Ställ in linjebredd, färg, banläge, orienteringslås och förstoringsglas för snabbare markering + Få ritningen att kännas förutsägbar + Draw-inställningar är användbara när du kommenterar skärmdumpar, markerar dokument eller signerar bilder ofta. Standardinställningarna minskar inställningstiden, medan förstoringsglaset och orienteringslåset gör exakta redigeringar enklare på små skärmar. + Ställ in standardlinjebredd och rita färg till de värden du använder mest för pilar, markeringar eller signaturer. + Använd standardbanan när du vanligtvis ritar samma typ av linjer, former eller markeringar. + Aktivera förstoringsglas eller orienteringslås när fingerinmatning gör små detaljer svåra att placera exakt. + Flerbildslayouter + Välj rätt flerbildsverktyg innan du ordnar skärmdumpar, skanningar eller jämförelseremsor + Använd rätt layoutverktyg + Dessa verktyg låter likadana, men var och en är inställd för en annan typ av multibildsutmatning. Genom att välja rätt först undviks layoutkontroller som är designade för ett annat resultat. + Använd Collage Maker för designade layouter med flera bilder i en komposition. + Använd bildsömnad eller stapling när bilderna ska bli en lång vertikal eller horisontell remsa. + Använd bilddelning när en stor bild behöver bli flera mindre bitar. + Konvertera bildformat + Skapa JPEG, PNG, WebP, AVIF, JXL och andra kopior utan att redigera pixlar manuellt + Välj format för jobbet + Olika format är bättre för foton, skärmdumpar, transparens, komprimering eller kompatibilitet. Konvertering är säkrast när du förstår vad destinationsappen stöder och om källan innehåller alfa, animation eller metadata som du vill behålla. + Använd JPEG för kompatibla foton, PNG för förlustfria bilder och alfa och WebP för kompakt delning. + Justera kvaliteten när formatet stöder det; lägre värden minskar storleken men kan lägga till artefakter. + Behåll originalen tills du verifierar att de konverterade filerna öppnas korrekt i målappen. + Kontrollera EXIF ​​och sekretess + Visa, redigera eller ta bort metadata innan du delar känsliga foton + Styr dolda bilddata + EXIF kan innehålla kameradata, datum, plats, orientering och andra detaljer som inte syns i bilden. Det är värt att kontrollera innan offentlig delning, supportrapporter, marknadsplatslistor eller andra bilder som kan avslöja en privat plats. + Öppna EXIF-verktyg innan du delar bilder som kan innehålla platsinformation eller privat enhetsinformation. + Ta bort känsliga fält eller redigera felaktiga taggar som datum, orientering, författare eller kameradata. + Spara en ny kopia och dela den kopian istället för originalet när sekretess är viktigt. + Automatisk EXIF-rensning + Ta bort metadata som standard medan du bestämmer om datum ska bevaras + Gör integritet till standard + EXIF-inställningsgruppen är användbar när du vill att sekretessrensning ska ske automatiskt istället för att komma ihåg den för varje export. Behåll datum Tid är ett separat beslut eftersom datum kan vara användbara för sortering men känsliga för delning. + Aktivera alltid rensa EXIF ​​när de flesta av dina exporter är avsedda för offentlig eller halvoffentlig delning. + Aktivera Behåll datumtid endast när det är viktigare att bevara inspelningstiden än att ta bort varje tidsstämpel. + Använd manuell EXIF-redigering för engångsfiler där du behöver behålla vissa fält och ta bort andra. + Styr filnamn + Använd prefix, suffix, tidsstämplar, förinställningar, kontrollsummor och sekvensnummer + Gör exporter lätta att hitta + Ett bra filnamnsmönster hjälper till att sortera batcher och förhindrar oavsiktliga överskrivningar. Filnamnsinställningar är särskilt användbara när exporter går tillbaka till källmappen, där liknande namn snabbt kan bli förvirrande. + Ställ in filnamnsprefix, suffix, tidsstämpel, originalnamn, förinställd information eller sekvensalternativ i Inställningar. + Använd sekvensnummer för batcher där ordningen är viktig, till exempel sidor, ramar eller collage. + Aktivera överskrivning endast när du är säker på att det är säkert att ersätta befintliga filer för den aktuella mappen. + Skriv över filer + Ersätt endast utdata med matchande namn när ditt arbetsflöde är avsiktligt destruktivt + Använd överskrivningsläget försiktigt + Skriv över filer ändrar hur namnkonflikter hanteras. Det är användbart för upprepade exporter till samma mapp, men det kan också ersätta tidigare resultat om ditt filnamnsmönster ger samma namn igen. + Aktivera överskrivning endast för mappar där ersättning av äldre genererade filer förväntas och kan återställas. + Håll överskrivning avaktiverad när du exporterar original, klientfiler, engångsskanningar eller något annat utan säkerhetskopia. + Kombinera överskrivning med ett avsiktligt filnamnsmönster så att endast de avsedda utdata ersätts. + Filnamnsmönster + Kombinera originalnamn, prefix, suffix, tidsstämplar, förinställningar, skalläge och kontrollsummor + Bygg namn som förklarar resultatet + Inställningar för filnamnsmönster kan göra exporterade filer till en användbar historik över vad som hände med dem. Detta är viktigt i omgångar eftersom mappvyn kan vara det enda stället där du kan urskilja originalstorlek, förinställning, format och bearbetningsordning. + Använd originalfilnamn, prefix, suffix och tidsstämpel när du behöver läsbara namn för manuell sortering. + Lägg till förinställning, skalläge, filstorlek eller kontrollsummainformation när utdata måste dokumentera hur de producerades. + Undvik slumpmässiga namn för arbetsflöden där filer måste förbli parade med original eller sidordning. + Välj var filerna ska sparas + Undvik att förlora exporter genom att ställa in mappar, spara originalmappar och engångssparplatser + Håll utgångar förutsägbara + Sparbeteendet är viktigast när du bearbetar många filer eller delar filer från molnleverantörer. Mappinställningar avgör om utdata samlas på ett känt ställe, visas bredvid original eller tillfälligt går någon annanstans för en specifik uppgift. + Ställ in en standardmapp för att spara om du alltid vill ha exporter på ett ställe. + Använd save-to-original-mappen för rengöringsarbetsflöden där utdata ska stanna nära källfilen. + Använd engångslagringsplats när en enskild uppgift behöver en annan destination utan att ändra din standard. + Engångssparmappar + Skicka nästa export till en tillfällig mapp utan att ändra din normala destination + Håll speciella jobb åtskilda + Engångslagringsplats är användbar för tillfälliga projekt, klientmappar, rensningssessioner eller exporter som inte ska blandas med din vanliga Image Toolbox-mapp. Det ger en kortlivad destination utan att skriva om din standardinställning. + Välj en engångsmapp innan du startar en uppgift som hör hemma utanför din normala exportplats. + Använd den för korta projekt, delade mappar eller partier där utdata måste förbli grupperade. + Återgå till standardmappen efter jobbet så att senare exporter inte oväntat hamnar på den tillfälliga platsen. + Balansera kvalitet och filstorlek + Förstå när du ska ändra mått, format eller kvalitet istället för att gissa + Förminska filer avsiktligt + Filstorleken beror på bildens dimensioner, format, kvalitet, transparens och innehållskomplexitet. Om en fil fortfarande är för tung hjälper ändrade dimensioner vanligtvis mer än att upprepade gånger sänka kvaliteten. + Ändra storlek först när bilden är större än vad destinationen kan visa. + Lägre kvalitet endast för förlustformat och kontrollera text, kanter, övertoningar och ansikten efter export. + Byt format när kvalitetsförändringar inte räcker, till exempel WebP för delning eller PNG för skarpa transparenta tillgångar. + Arbeta med animerade format + Använd verktygen GIF, APNG, WebP och JXL när en fil har ramar istället för en bitmapp + Behandla inte varje bild som stillastående + Animerade format behöver verktyg som förstår ramar, varaktighet och kompatibilitet. + Använd GIF- eller APNG-verktyg när du behöver operationer på ramnivå för dessa format. + Använd WebP-verktyg när du behöver modern komprimering eller animerad WebP-utdata. + Förhandsgranska animeringstid innan delning eftersom vissa plattformar konverterar eller plattar till animerade bilder. + Jämför och förhandsgranska utdata + Inspektera ändringar, filstorlek och visuella skillnader innan du behåller en export + Verifiera innan du delar + Jämförelseverktyg hjälper till att fånga komprimeringsartefakter, felaktiga färger eller oönskade redigeringar tidigt. + Öppna Jämför när du vill inspektera två versioner sida vid sida. + Zooma in i kanter, text, övertoningar och genomskinliga områden där problemen är lättast att missa. + Om utskriften är för tung eller suddig, gå tillbaka till föregående verktyg och justera kvalitet eller format. + Använd PDF-verktygshubben + Slå samman, dela, rotera, ta bort sidor, komprimera, skydda, OCR och vattenstämpla PDF-filer + Välj en PDF-operation + PDF-hubben grupperar dokumentåtgärder bakom en ingångspunkt så att du kan välja den exakta operationen. Börja där när filen redan är en PDF, och använd Bilder till PDF eller Dokumentskanner när din källa fortfarande är en uppsättning bilder. + Öppna PDF-verktyg och välj den operation som matchar ditt mål. + Välj käll-PDF eller bilder och granska sedan sidordning, rotation, skydd eller OCR-alternativ. + Spara den genererade PDF-filen och öppna den en gång för att bekräfta sidantal, ordning och läsbarhet. + Förvandla bilder till en PDF + Kombinera skanningar, skärmdumpar, kvitton eller foton till ett delbart dokument + Skapa ett rent dokument + Bilder blir bättre PDF-sidor när de beskärs, ordnas och formateras konsekvent. Behandla bildlistan som en sidbunt: varje val av rotation, marginal och sidstorlek påverkar hur läsbart det slutliga dokumentet känns. + Beskär eller rotera bilder först när sidans kanter, skuggor eller orientering är felaktiga. + Välj bilder i den avsedda sidordningen och justera sidstorlek eller marginaler om verktyget erbjuder dem. + Exportera PDF:en och kontrollera sedan att alla sidor är läsbara innan du skickar den. + Skanna dokument + Fånga papperssidor och förbered dem för PDF, OCR eller delning + Fånga läsbara sidor + Dokumentskanning fungerar bäst med platta sidor, bra belysning och korrigerat perspektiv. En ren skanning före OCR- eller PDF-export sparar tid eftersom textigenkänning och komprimering båda beror på sidkvaliteten. + Placera dokumentet på en kontrasterande yta med tillräckligt med ljus och minimalt med skuggor. + Justera upptäckta hörn eller beskära manuellt så att sidan fyller ramen rent. + Exportera som bilder eller PDF, kör sedan OCR om du behöver valbar text. + Skydda eller lås upp PDF-filer + Använd lösenord noggrant och behåll en redigerbar källkopia när det är möjligt + Hantera PDF-åtkomst på ett säkert sätt + Skyddsverktyg är användbara för kontrollerad delning, men lösenord är lätta att förlora och svåra att återställa. Skydda kopior för distribution, behåll oskyddade källfiler separat och verifiera resultatet innan du tar bort något. + Skydda en kopia av PDF:en, inte ditt enda källdokument. + Förvara lösenordet på ett säkert ställe innan du delar den skyddade filen. + Använd endast upplåsning för filer som du har tillåtelse att redigera och kontrollera sedan att den olåsta kopian öppnas korrekt. + Organisera PDF-sidor + Slå samman, dela, ordna om, rotera, beskära, ta bort sidor och lägg till sidnummer medvetet + Fixa dokumentstrukturen före dekoration + PDF-sidverktyg är enklast att använda i samma ordning som du skulle förbereda ett pappersdokument: samla sidor, ta bort fel, ställ dem i ordning, rätt orientering, lägg sedan till sista detaljer som sidnummer eller vattenstämplar. + Använd sammanfoga eller bilder-till-PDF först när dokumentet fortfarande är uppdelat på flera filer. + Ordna om, rotera, beskära eller ta bort sidor innan du lägger till sidnummer, signaturer eller vattenstämplar. + Förhandsgranska den slutliga PDF-filen efter strukturella redigeringar eftersom en saknad eller roterad sida kan vara svår att lägga märke till senare. + PDF-anteckningar + Ta bort kommentarer eller platta till dem när tittare visar kommentarer och markeringar på olika sätt + Kontrollera vad som förblir synligt + Anteckningar kan vara kommentarer, markeringar, ritningar, formulärmärken eller andra extra PDF-objekt. Vissa tittare visar dem på olika sätt, så att ta bort eller platta till dem kan göra delade dokument mer förutsägbara. + Ta bort kommentarer när kommentarer, markeringar eller recensionsmarkeringar inte ska inkluderas i den delade kopian. + Platta till när synliga märken ska stanna kvar på sidan men inte längre bete sig som redigerbara kommentarer. + Behåll en originalkopia eftersom det kan vara svårt att ta bort eller platta till kommentarer. + Känner igen text + Extrahera text från bilder med valbara OCR-motorer och språk + Få bättre OCR-resultat + OCR-noggrannheten beror på bildens skärpa, språkdata, kontrast och sidorientering. Det bästa resultatet kommer vanligtvis om du förbereder bilden först: beskära bort brus, rotera upprätt, öka läsbarheten och sedan känna igen text. + Beskär bilden till textområdet och rotera den upprätt före igenkänning. + Välj rätt språk och ladda ner språkdata om appen begär det. + Kopiera eller dela den igenkända texten och kontrollera sedan namn, siffror och skiljetecken manuellt. + Välj OCR-språkdata + Förbättra igenkänningen genom att matcha skript, språk och nedladdade OCR-modeller + Matcha OCR till texten + Fel OCR-språk kan få bra bilder att se ut som dålig igenkänning. Språkdata är viktiga för skript, skiljetecken, siffror och blandade dokument, så välj språket som visas i bilden istället för språket för telefonens gränssnitt. + Välj språket eller skriptet som visas i bilden, inte bara telefonspråket. + Ladda ner saknad data innan du arbetar offline eller bearbetar en batch. + För dokument med blandade språk, kör det viktigaste språket först och kontrollera namn och nummer manuellt. + Sökbara PDF-filer + Skriv tillbaka OCR-text till filer så att skannade sidor kan sökas och kopieras + Förvandla skannade dokument till sökbara dokument + OCR kan göra mer än att kopiera text till urklipp. När du skriver igenkänd text till en fil eller sökbar PDF förblir bilden av sidan synlig medan text blir lättare att söka, välja, indexera eller arkivera. + Använd sökbar PDF-utdata för skannade dokument som fortfarande ska se ut som originalsidorna. + Använd skriv till fil när du bara behöver extraherad text och inte bryr dig om sidbilden. + Kontrollera viktiga nummer, namn och tabeller manuellt eftersom OCR-text kan vara sökbar men fortfarande ofullkomlig. + Skanna QR-koder + Läs QR-innehåll från kameraingång eller sparade bilder när det är tillgängligt + Läs koder säkert + QR-koder kan innehålla länkar, Wi-Fi-data, kontakter, text eller annan nyttolast. + Öppna Scan QR Code och håll koden skarp, platt och helt inuti ramen. + Granska det avkodade innehållet innan du öppnar länkar eller kopierar känslig data. + Om skanningen misslyckas, förbättra belysningen, beskära koden eller prova en källbild med högre upplösning. + Använd Base64 och kontrollsummor + Koda bilder som text, avkoda text tillbaka till bilder och verifiera filens integritet + Flytta mellan filer och text + Base64 är användbart för små nyttolaster, medan kontrollsummor hjälper till att bekräfta att filerna inte har ändrats. Dessa verktyg är mest användbara i tekniska arbetsflöden, supportrapporter, filöverföringar och platser där binära filer tillfälligt måste bli text. + Använd Base64-verktyg för att koda en liten bild när ett arbetsflöde behöver text istället för en binär fil. + Avkoda Base64 endast från betrodda källor och verifiera förhandsgranskningen innan du sparar. + Använd kontrollsummaverktyg när du behöver jämföra exporterade filer eller bekräfta en uppladdning/nedladdning. + Paketera eller skydda data + Använd ZIP- och chifferverktyg för att bunta filer eller omvandla textdata + Håll relaterade filer tillsammans + Förpackningsverktyg är användbara när flera utgångar ska resa som en fil. De är också bra för att hålla ihop genererade bilder, PDF-filer, loggar och metadata när du behöver skicka en komplett resultatuppsättning. + Använd ZIP när du behöver skicka många exporterade bilder eller dokument tillsammans. + Använd chifferverktyg endast när du förstår den valda metoden och kan lagra nödvändiga nycklar på ett säkert sätt. + Testa det skapade arkivet eller den transformerade texten innan du tar bort källfiler. + Kontrollsummor i namn + Använd kontrollsummabaserade filnamn när unikhet och integritet är viktigare än läsbarhet + Namnge filer efter innehåll + Kontrollsumma filnamn är användbara när exporter kan ha dubbla synliga namn eller när du behöver bevisa att två filer är identiska. De är mindre vänliga för manuell surfning, så använd dem för tekniska arkiv och verifieringsarbetsflöden. + Aktivera kontrollsumma som filnamn när innehållsidentitet är viktigare än en läsbar titel. + Använd den för arkiv, deduplicering, repeterbara exporter eller filer som kan laddas upp och laddas ner igen. + Para ihop kontrollsummanamn med mappar eller metadata när folk fortfarande behöver förstå vad filen representerar. + Välj färger från bilder + Ta prov på pixlar, kopiera färgkoder och återanvänd färger i paletter eller mönster + Prova den exakta färgen + Färgval är användbart för att matcha en design, extrahera varumärkesfärger eller kontrollera bildvärden. Zoomning är viktig eftersom kantutjämning, skuggor och komprimering kan göra att närliggande pixlar skiljer sig något från den färg du förväntar dig. + Öppna Välj färg från bild och välj en källbild med den färg du behöver. + Zooma in innan du samplar små detaljer så att närliggande pixlar inte påverkar resultatet. + Kopiera färgen i det format som krävs av din destination, till exempel HEX, RGB eller HSV. + Skapa paletter och använd färgbiblioteket + Extrahera paletter, bläddra i sparade färger och behåll konsekventa visuella system + Bygg en återanvändbar palett + Paletter hjälper till att hålla exporterad grafik, vattenstämplar och ritningar visuellt konsekventa. De är särskilt användbara när du upprepade gånger kommenterar skärmdumpar, förbereder varumärkestillgångar eller behöver samma färger i flera verktyg. + Skapa en palett från en bild eller lägg till färger manuellt när du redan känner till värdena. + Namnge eller organisera viktiga färger så att du kan hitta dem igen senare. + Använd kopierade värden i Draw, vattenstämpel, gradient, SVG eller externa designverktyg. + Skapa gradienter + Bygg gradientbilder för bakgrunder, platshållare och visuella experiment + Kontrollera färgövergångar + Gradientverktyg är användbara när du behöver en genererad bild istället för att redigera en befintlig. De kan bli rena bakgrunder, platshållare, bakgrundsbilder, masker eller enkla tillgångar för externa designverktyg. + Välj gradienttyp och ställ in de viktiga färgerna först. + Justera riktning, stopp, jämnhet och utdatastorlek för målanvändningsfallet. + Exportera i ett format som matchar destinationen, vanligtvis PNG för ren genererad grafik. + Färgtillgänglighet + Använd dynamiska färger, kontraster och färgblinda alternativ när användargränssnittet är svårt att läsa + Gör färgerna lättare att använda + Färginställningar påverkar komfort, läsbarhet och hur snabbt du kan skanna kontroller. Om verktygsmarker, skjutreglage eller valda tillstånd känns svåra att urskilja, kan tema- och tillgänglighetsalternativ vara mer användbara än att bara ändra mörkt läge. + Prova dynamiska färger när du vill att appen ska följa den aktuella tapetpaletten. + Öka kontrasten eller ändra schema när knappar och ytor inte sticker ut tillräckligt. + Använd färgblinda schemaalternativ om vissa tillstånd eller accenter är svåra att urskilja. + Alfa bakgrund + Kontrollera förhandsgranskningen eller fyllningsfärgen som används runt transparenta PNG, WebP och liknande utdata + Förstå transparenta förhandsvisningar + Bakgrundsfärg för alfaformat kan göra transparenta bilder lättare att inspektera, men det är viktigt att veta om du ändrar ett förhandsgransknings-/fyllningsbeteende eller plattar till slutresultatet. Detta är viktigt för klistermärken, logotyper och bilder som tagits bort från bakgrunden. + Använd först ett alfastödjande format, som PNG eller WebP, när transparenta pixlar måste överleva exporten. + Justera inställningar för bakgrundsfärg när det är svårt att se genomskinliga kanter mot appens yta. + Exportera ett litet test och öppna det igen i en annan app för att bekräfta om transparensen eller den valda fyllningen sparades. + Val av AI-modell + Välj en modell efter bildtyp och defekt istället för att välja den största först + Matcha modellen med skadan + AI Tools kan ladda ner eller importera olika ONNX/ORT-modeller, och varje modell är utbildad för en viss typ av problem. En modell för JPEG-block, rensning av animelinjer, försvagning, borttagning av bakgrund, färgläggning eller uppskalning kan ge sämre resultat när den används på fel bildtyp. + Läs modellbeskrivningen innan du laddar ner; välj den modell som namnger ditt faktiska problem, som komprimering, brus, halvton, oskärpa eller borttagning av bakgrund. + Börja med en mindre eller mer specifik modell när du testar en ny bildtyp, jämför sedan med tyngre modeller bara om resultatet inte är tillräckligt bra. + Behåll bara modeller du verkligen använder, eftersom nedladdade och importerade modeller kan ta märkbart lagringsutrymme. + Förstå modellfamiljer + Modellnamn antyder ofta sitt mål: exklusiva modeller i RealESRGAN-stil, SCUNet- och FBCNN-modeller rensar brus eller komprimering, bakgrundsmodeller skapar masker och färgsättare lägger till färg till gråskaleinmatning. Importerade anpassade modeller kan vara kraftfulla, men de bör matcha den förväntade in-/utdataformen och använda ONNX- eller ORT-format som stöds. + Använd uppskalare när du behöver fler pixlar, denoisers när bilden är kornig och artefaktborttagare när komprimeringsblock eller banding är huvudproblemet. + Använd endast bakgrundsmodeller för att separera motiv; de är inte samma sak som allmän borttagning av objekt eller bildförbättring. + Importera anpassade modeller endast från källor du litar på och testa dem på en liten bild innan du använder viktiga filer. + AI-parametrar + Justera chunkstorlek, överlappning och parallella arbetare för att balansera kvalitet, hastighet och minne + Balansera hastighet med stabilitet + Chunk size styr hur stora bildbitar är innan de bearbetas av modellen. Överlappning blandar intilliggande bitar för att dölja kanter. Parallella arbetare kan påskynda bearbetningen, men varje arbetare behöver minne, så höga värden kan göra stora bilder instabila på telefoner med begränsat RAM-minne. + Använd större bitar för smidigare sammanhang när din enhet hanterar modellen på ett tillförlitligt sätt och bilden inte är enorm. + Öka överlappningen när bitkanter blir synliga, men kom ihåg att mer överlappning innebär mer upprepat arbete. + Lyft upp parallellarbetare endast efter ett stabilt test; Om bearbetningen saktar ner, värmer enheten eller kraschar, minska antalet anställda först. + Undvik chunk artefakter + Chunking låter appen bearbeta bilder som är större än vad modellen bekvämt kan hantera på en gång. Avvägningen är att varje bricka har mindre omgivande kontext, så låg överlappning eller för små bitar kan skapa synliga kanter, texturförändringar eller inkonsekventa detaljer mellan närliggande områden. + Öka överlappningen om du ser rektangulära kanter, upprepade texturändringar eller detaljhopp mellan bearbetade områden. + Minska bitstorleken om processen misslyckas innan utdata produceras, särskilt på stora bilder eller tunga modeller. + Spara ett litet beskärningstest innan du bearbetar hela bilden så att du snabbt kan jämföra parametrar. + AI stabilitet + Lägre bitstorlek, arbetare och källupplösning när AI-bearbetning kraschar eller fryser + Återhämta dig från tunga AI-jobb + AI-bearbetning är ett av de tyngsta arbetsflödena i appen. Krascher, misslyckade sessioner, frysningar eller plötsliga omstarter av appar betyder vanligtvis att modellen, källupplösningen, bitstorleken eller antalet parallella arbetare är för krävande för det aktuella enhetens tillstånd. + Om appen kraschar eller misslyckas med att öppna en modellsession, sänk parallella arbetare och chunkstorlek och försök sedan samma bild igen. + Ändra storlek på mycket stora bilder innan AI-bearbetning när målet inte kräver den ursprungliga upplösningen. + Stäng andra tunga appar, använd en mindre modell eller bearbeta färre filer samtidigt när minnestrycket återkommer. + Diagnostisera modellfel + En misslyckad AI-körning betyder inte alltid att bilden är dålig. Modellfilen kan vara ofullständig, stöds inte, för stor för minne eller inte matchar åtgärden. När appen rapporterar en misslyckad session, behandla det som en signal för att förenkla modellen och parametrarna först. + Ta bort och ladda ner en modell om den misslyckas direkt efter nedladdningen eller beter sig som om filen är skadad. + Prova en inbyggd nedladdningsbar modell innan du skyller på en importerad anpassad modell, eftersom importerade modeller kan ha inkompatibla indata. + Använd apploggar efter att ha återskapat felet om du behöver rapportera en krasch eller ett sessionsfel. + Experimentera i Shader Studio + Använd GPU-skuggningseffekter för avancerad bildbehandling och anpassade utseenden + Arbeta noggrant med shaders + Shader Studio är kraftfullt, men shader-kod och parametrar kräver små, testbara ändringar. Behandla det som ett experimentellt verktyg: håll en fungerande baslinje, ändra en sak i taget och testa med olika bildstorlekar innan du litar på en förinställning. + Börja från en känd fungerande shader eller en enkel effekt innan du lägger till parametrar. + Ändra en parameter eller kodblock åt gången och kontrollera förhandsgranskningen efter fel. + Exportera förinställningar endast efter att ha testat dem på några olika bildstorlekar. + Gör SVG- eller ASCII-konst + Generera vektorliknande tillgångar eller textbaserade bilder för kreativ produktion + Välj typ av annonsmaterial + SVG- och ASCII-verktyg tjänar olika mål: skalbar grafik eller rendering i textstil. Båda är kreativa omvandlingar, så förhandsgranskning i den slutliga storleken är viktigare än att bedöma resultatet utifrån en liten miniatyrbild. + Använd SVG Maker när resultatet ska skalas rent eller återanvändas i designarbetsflöden. + Använd ASCII Art när du vill ha en stiliserad textrepresentation av en bild. + Förhandsgranska i den slutliga storleken eftersom små detaljer kan försvinna i genererad utdata. + Generera brusstrukturer + Skapa procedurstrukturer för bakgrunder, masker, överlägg eller experiment + Ställ in procedurutgången + Brusgenerering skapar nya bilder från parametrar, så små förändringar kan ge mycket olika resultat. Det är användbart för texturer, masker, överlägg, platshållare eller testning av komprimering med detaljerade mönster. + Välj en brustyp och utdatastorlek innan du ställer in fina detaljer. + Justera skala, intensitet, färger eller frö tills mönstret passar målanvändningen. + Spara användbara resultat och skriv ner parametrarna om du behöver återskapa texturen senare. + Markup-projekt + Använd projektfiler när kommentarer behöver förbli redigerbara efter att du har stängt appen + Håll lagerredigeringar återanvändbara + Markup-projektfiler är användbara när en skärmdump, diagram eller instruktionsbild kan behöva ändras senare. Exporterade PNG/JPEG-filer är slutliga bilder, medan projektfiler bevarar redigerbara lager och redigeringstillstånd för framtida justeringar. + Använd Markup Layers när kommentarer, bildtexter eller layoutelement ska kunna redigeras. + Spara eller öppna projektfilen igen innan du exporterar en slutlig bild om du förväntar dig fler ändringar. + Exportera en normal bild först när du är redo att dela en tillplattad slutversion. + Kryptera filer + Skydda filer med ett lösenord och testa dekryptering innan du tar bort någon källkopia + Hantera krypterade utdata försiktigt + Chifferverktyg kan skydda filer med lösenordsbaserad kryptering, men de är inte en ersättning för att komma ihåg nyckeln eller behålla säkerhetskopior. Kryptering är användbart för transport och lagring, medan ZIP är bättre när huvudmålet är att bunta ihop flera utgångar. + Kryptera en kopia först och behåll originalet tills du har testat att dekrypteringen fungerar med lösenordet du sparat. + Använd en lösenordshanterare eller annan säker plats för nyckeln; appen kan inte gissa ett förlorat lösenord åt dig. + Dela endast krypterade filer med personer som också har lösenordet och vet vilket verktyg eller metod som ska dekryptera dem. + Ordna verktyg + Gruppera, beställ, sök och fäst verktyg så att huvudskärmen matchar ditt verkliga arbetsflöde + Gör huvudskärmen snabbare + Inställningar för verktygsarrangemang är värda att justera när du vet vilka verktyg du använder mest. Gruppering hjälper till att utforska, anpassad ordning hjälper muskelminnet och favoriter håller dagliga verktyg tillgängliga även när hela listan blir stor. + Använd grupperat läge när du lär dig appen eller när du föredrar verktyg ordnade efter uppgiftstyp. + Byt till anpassad ordning när du redan kan dina vanliga verktyg och vill ha färre rullningar. + Använd sökinställningar och favoriter tillsammans för sällsynta verktyg du kommer ihåg med namn men inte vill vara synliga hela tiden. + Hantera stora filer + Undvik långsam export, minnespress och oväntat stor produktion + Minska arbetet före export + Mycket stora bilder, långa partier och högkvalitativa format kan ta mer minne och tid. Den snabbaste lösningen är vanligtvis att minska mängden arbete före den tyngsta operationen, inte vänta på att samma överdimensionerade inmatning ska slutföras. + Ändra storlek på stora bilder innan du använder tunga filter, OCR eller PDF-konvertering. + Använd en mindre batch först för att bekräfta inställningarna, bearbeta sedan resten med samma förinställning. + Lägre kvalitet eller ändra format när utdatafilen är större än förväntat. + Cache och förhandsvisningar + Förstå genererade förhandsvisningar, cacherensning och lagringsanvändning efter tunga sessioner + Håll lagring och hastighet balanserad + Generering av förhandsgranskningar och cache kan göra upprepad surfning snabbare, men tunga redigeringssessioner kan lämna tillfälliga data bakom sig. Cacheinställningarna hjälper när appen känns långsam, lagringen är trång eller förhandsgranskningar ser inaktuella ut efter många experiment. + Att hålla förhandsvisningar aktiverade när du bläddrar i många bilder är viktigare än att minimera tillfällig lagring. + Rensa cacheminnet efter stora partier, misslyckade experiment eller långa sessioner med många högupplösta filer. + Använd automatisk cacheminne om du föredrar lagringsrensning framför att hålla förhandsvisningar varma mellan lanseringarna. + Justera skärmens beteende + Använd alternativ för helskärm, säkert läge, ljusstyrka och systemfält för fokuserat arbete + Få gränssnittet att passa situationen + Skärminställningar hjälper när du redigerar i liggande format, presenterar privata bilder eller behöver konsekvent ljusstyrka. De krävs inte för normal användning, men de löser besvärliga situationer som QR-inspektion, privata förhandsvisningar eller trånga landskapsredigering. + Använd helskärms- eller systemfältsinställningar när redigeringsutrymme är viktigare än navigering i krom. + Använd säkert läge när privata bilder inte ska visas i skärmdumpar eller förhandsvisningar av appar. + Använd ljusstyrka för verktyg där mörka bilder eller QR-koder är svåra att inspektera. + Behåll transparens + Förhindra att genomskinliga bakgrunder blir vita, svarta eller tillplattade + Använd alfamedveten utdata + Transparens överlever endast när det valda verktyget och utdataformatet stöder alfa. Om en exporterad bakgrund blir vit eller svart är problemet vanligtvis utdataformatet, en fyllnings-/bakgrundsinställning eller en målapp som plattade ut bilden. + Välj PNG eller WebP när du exporterar bakgrundsborttagna bilder, klistermärken, logotyper eller överlägg. + Undvik JPEG för transparent utdata eftersom den inte lagrar en alfakanal. + Kontrollera inställningar relaterade till bakgrundsfärg för alfaformat om förhandsgranskningen visar en fylld bakgrund. + Åtgärda delnings- och importproblem + Använd väljarinställningar och format som stöds när filer inte visas eller öppnas korrekt + Hämta filen i appen + Importproblem orsakas ofta av leverantörsbehörigheter, format som inte stöds eller väljarbeteende. Filer som delas från molnappar och meddelanden kan vara tillfälliga, så att välja en beständig källa kan vara mer tillförlitlig för längre redigeringar. + Försök att öppna filen via systemväljaren istället för en genväg till de senaste filerna. + Om ett format inte stöds av ett verktyg, konvertera det först eller öppna ett mer specifikt verktyg. + Granska inställningarna för bildväljaren och startprogrammet om delningsflöden eller direkt öppning av verktyg beter sig annorlunda än förväntat. + Avsluta bekräftelse + Undvik att förlora osparade redigeringar när gester, bakåttryckningar eller verktygsväxlar inträffar av misstag + Skydda oavslutat arbete + Bekräftelse av att avsluta verktyget är användbart när du använder gestnavigering, redigerar stora filer eller ofta växlar mellan appar. Den lägger till en liten paus innan du lämnar ett verktyg så att oavslutade inställningar och förhandsvisningar inte går förlorade av misstag. + Aktivera bekräftelse om oavsiktliga bakåtgester någonsin har stängt ett verktyg innan du exporterade. + Håll det inaktiverat om du oftast gör snabba enstegskonverteringar och föredrar snabbare navigering. + Använd den tillsammans med förinställningar för längre arbetsflöden där ombyggnadsinställningar skulle vara irriterande. + Använd loggar när du rapporterar problem + Samla in användbar information innan du öppnar ett problem eller kontaktar utvecklaren + Rapportera problem med sammanhang + En tydlig rapport med steg, appversion, inmatningstyp och loggar är mycket lättare att diagnostisera. Loggar är mest användbara direkt efter att ett problem har reproducerats eftersom de håller det omgivande sammanhanget fräscht. + Notera verktygets namn, den exakta åtgärden och om det händer med en fil eller varje fil. + Öppna apploggar efter att du har reproducerat problemet och dela relevant loggutdata när det är möjligt. + Bifoga skärmdumpar eller exempelfiler endast när de inte innehåller privat information. + Bakgrundsbearbetningen stoppades + Android lät inte bakgrundsbearbetningsmeddelandet starta i tid. Detta är en systemtidsbegränsning som inte kan fixas tillförlitligt. Starta om appen och försök igen; att hålla appen öppen och tillåta aviseringar eller bakgrundsarbete kan hjälpa. + Hoppa över filplockning + Öppna verktyg snabbare när input vanligtvis kommer från delning, urklipp eller en tidigare skärm + Gör upprepade verktygsstarter snabbare + Hoppa över filval ändrar det första steget av verktyg som stöds. Det är användbart när du vanligtvis anger ett verktyg med en redan vald bild eller från Android-delningsåtgärder, men det kan kännas förvirrande om du förväntar dig att varje verktyg frågar efter en fil omedelbart. + Aktivera det endast när ditt vanliga flöde redan tillhandahåller källbilden innan verktyget öppnas. + Håll den inaktiverad medan du lär dig appen, eftersom explicit val gör varje verktygsflöde lättare att förstå. + Om ett verktyg öppnas utan uppenbar inmatning, inaktivera den här inställningen eller starta från Dela/Öppna med så att Android skickar filen först. + Öppna editorn först + Hoppa över förhandsgranskningen när en delad bild ska gå direkt till redigering + Välj förhandsgranska eller redigera som första anhalt + Öppna redigera istället för förhandsgranskning är för användare som redan vet att de vill ändra bilden. Förhandsgranskning är säkrare när du inspekterar filer från chatt- eller molnlagring; direktredigering är snabbare för skärmdumpar, snabbbeskärningar, anteckningar och engångskorrigeringar. + Aktivera direktredigering om de flesta delade bilderna omedelbart behöver beskäras, markeras, filtreras eller exporteras. + Lämna förhandsgranskningen först när du ofta inspekterar metadata, dimensioner, transparens eller bildkvalitet innan du bestämmer dig. + Använd detta tillsammans med favoriter så att delade bilder hamnar nära de verktyg du faktiskt använder. + Förinställd textinmatning + Ange exakta förinställda värden istället för att justera kontrollerna upprepade gånger + Använd exakta värden när reglagen är långsamma + Förinställd textinmatning hjälper när du behöver exakta siffror för upprepat arbete: sociala bildstorlekar, dokumentmarginaler, komprimeringsmål, linjebredder eller andra värden som är irriterande att nå med gester. Det är också användbart på små skärmar där det är svårare att röra sig med skjutreglaget. + Aktivera textinmatning om du ofta återanvänder exakta storlekar, procentsatser, kvalitetsvärden eller verktygsparametrar. + Spara värdet som en förinställning efter att ha skrivit det en gång och återanvänd det istället för att bygga om samma inställning. + Behåll gestkontroller för grov visuell inställning och inskrivna värden för strikta utdatakrav. + Spara nära original + Lägg genererade filer bredvid källbilder när mappkontexten är viktig + Håll utdata med sina källor + Spara till originalmapp är praktiskt för kameramappar, projektmappar, dokumentskanningar och klientbatcher där den redigerade kopian ska stå bredvid källan. Det kan vara mindre snyggt än en dedikerad utdatamapp, så kombinera den med tydliga filnamnssuffix eller mönster. + Aktivera det när varje källmapp redan är meningsfull och utdata ska förbli grupperade där. + Använd filnamnssuffix, prefix, tidsstämplar eller mönster så att redigerade kopior är lätta att separera från original. + Inaktivera det för blandade partier när du vill att alla genererade filer ska samlas i en exportmapp. + Hoppa över större produktion + Undvik att spara konverteringar som oväntat blir tyngre än källan + Skydda partier från överraskningar av dålig storlek + Vissa konverteringar gör filerna större, särskilt PNG-skärmdumpar, redan komprimerade foton, högkvalitativ WebP eller bilder med bevarad metadata. Tillåt Skip If Larger förhindrar dessa utdata från att ersätta en mindre källa i arbetsflöden där att minska storleken är huvudmålet. + Aktivera det när exporten är avsedd att komprimera, ändra storlek på eller förbereda filer för uppladdningsgränser. + Granska överhoppade filer efter en batch; de kanske redan är optimerade eller kan behöva ett annat format. + Inaktivera det när kvalitet, transparens eller formatkompatibilitet är viktigare än den slutliga filstorleken. + Urklipp och länkar + Kontrollera automatisk urklippsinmatning och länkförhandsvisningar för snabbare textbaserade verktyg + Gör automatisk inmatning förutsägbar + Urklipp och länkinställningar är praktiska för QR, Base64, URL och texttunga arbetsflöden, men automatisk inmatning bör förbli avsiktlig. Om appen verkar fylla fält av sig själv, kontrollera klippbordsklistra beteende; om länkkort känns bullriga eller långsamma, justera beteendet för länkförhandsgranskningen. + Tillåt automatisk inklistring i urklipp när du ofta kopierar text och öppnar omedelbart ett matchningsverktyg. + Inaktivera automatisk inklistring om privat urklippsinnehåll visas i verktyg där du inte förväntade dig det. + Inaktivera länkförhandsvisningar när förhandshämtning är långsam, distraherande eller onödig för ditt arbetsflöde. + Kompakta kontroller + Använd tätare väljare och snabb inställningsplacering när skärmutrymmet är trångt + Passa in fler kontroller på små skärmar + Kompakta väljare och snabb inställningsplacering hjälper till på små telefoner, landskapsredigering och verktyg med många parametrar. Avvägningen är att kontroller kan kännas tätare, så detta är bäst efter att du redan känner igen de vanliga alternativen. + Aktivera kompakta väljare när dialoger eller alternativlistor känns för höga för skärmen. + Justera snabbinställningssidan om verktygskontrollerna är lättare att nå från ena sidan av displayen. + Återgå till rymligare kontroller om du fortfarande lär dig appen eller ofta missar att trycka täta alternativ. + Ladda bilder från webben + Klistra in webbadressen till en sida eller bild, förhandsgranska analyserade bilder och spara eller dela sedan valda resultat + Använd webbbilder medvetet + Webbbildsladdning analyserar bildkällor från den angivna URL:en, förhandsgranskar den första tillgängliga bilden och låter dig spara eller dela utvalda analyserade bilder. Vissa webbplatser använder tillfälliga, skyddade eller skriptgenererade länkar, så en sida som fungerar i en webbläsare kan fortfarande inte returnera några användbara bilder. + Klistra in en direkt bild-URL eller en sid-URL och vänta tills listan med analyserade bilder visas. + Använd ram- eller urvalskontrollerna när sidan innehåller flera bilder och du bara behöver några av dem. + Om inget laddas, prova en direkt bildlänk eller ladda ner via webbläsaren först; webbplatsen kan blockera analys eller använda privata länkar. + Välj storleksändringstyp + Förstå Explicit, Flexible, Crop och Fit innan du tvingar en bild till nya dimensioner + Styr form, duk och bildförhållande + Ändra storlekstyp avgör vad som händer när den begärda bredden och höjden inte matchar det ursprungliga bildförhållandet. Det är skillnaden mellan att sträcka ut pixlar, bevara proportioner, beskära extra område eller lägga till en bakgrundsduk. + Använd Explicit endast när distorsion är acceptabel eller källan redan har samma bildförhållande som målet. + Använd Flexible för att behålla proportioner genom att ändra storlek från den viktiga sidan, speciellt för foton och blandade partier. + Använd Beskär för strikta ramar utan ramar, eller Anpassa när hela bilden måste förbli synlig inuti en fast duk. + Välj bildskalningsläge + Välj omsamplingsfiltret som kontrollerar skärpa, jämnhet, hastighet och kantartefakter + Ändra storlek utan oavsiktlig mjukhet + Bildskalningsläge styr hur nya pixlar beräknas under storleksändring. Enkla lägen är snabba och förutsägbara, medan avancerade filter kan bevara detaljer bättre men kan skärpa kanter, skapa glorier eller ta längre tid på stora bilder. + Använd Basic eller Bilinear för snabb vardaglig storleksändring när resultatet bara behöver bli mindre och rent. + Använd lägen i Lanczos-, Robidoux-, Spline- eller EWA-stil när fina detaljer, text eller högkvalitativ nedskalning är viktig. + Använd Närmaste för pixelkonst, ikoner och grafik med hårda kanter där suddiga pixlar skulle förstöra utseendet. + Skala färgrymd + Bestäm hur färger blandas medan pixlar samplas om under storleksändring + Håll lutningar och kanter naturliga + Skala färgrymd ändrar den matematik som används när du ändrar storlek. De flesta bilder är bra med standardinställningen, men linjära eller perceptuella utrymmen kan få övertoningar, mörka kanter och mättade färger att se renare ut efter kraftig skalning. + Lämna standard när hastighet och kompatibilitet betyder mer än små färgskillnader. + Prova linjära eller perceptuella lägen när mörka konturer, gränssnittsskärmdumpar, övertoningar eller färgband ser fel ut efter storleksändring. + Jämför före och efter på den verkliga målskärmen, eftersom färgrymdsförändringar kan vara subtila och innehållsberoende. + Begränsa storleksändringslägen + Ta reda på när övergränsade bilder bör hoppas över, kodas om eller skalas ner för att passa + Välj vad som händer vid gränsen + Limit Resize är inte bara ett verktyg för mindre bilder. Dess läge avgör vad appen gör när en källa redan är inom dina gränser eller när bara en sida bryter mot regeln, vilket är viktigt för blandade mappar och förberedelser för uppladdning. + Använd Hoppa över när bilder inom gränserna ska lämnas orörda och inte exporteras igen. + Använd Recode när dimensionerna är acceptabla men du fortfarande behöver ett nytt format, metadatabeteende, kvalitet eller filnamn. + Använd Zoom när bilder som bryter mot gränserna ska skalas ned proportionellt tills de passar den tillåtna rutan. + Mål för bildförhållande + Undvik sträckta ytor, avskurna kanter och oväntade kanter i strikta utskriftsstorlekar + Matcha formen innan du ändrar storlek + Bredd och höjd är bara hälften av ett mål för att ändra storlek. Bildförhållandet avgör om bilden kan passa naturligt, behöver beskäras eller behöver en duk runt den. Att kontrollera formen först förhindrar de mest överraskande storleksändringsresultaten. + Jämför källformen med målformen innan du väljer Explicit, Beskär eller Anpassa. + Beskär först när motivet kan tappa extra bakgrund och destinationen behöver en strikt ram. + Använd Anpassa eller Flexibel när varje del av originalbilden måste vara synlig. + Uppskala eller normal storleksändring + Vet när du lägger till pixlar behöver AI och när enkel skalning räcker + Blanda inte ihop storlek med detaljer + Normal storleksändring ändrar dimensioner genom att interpolera pixlar; den kan inte uppfinna riktiga detaljer. AI-uppskalning kan skapa skarpare detaljer, men det är långsammare och kan hallucinera textur, så det rätta valet beror på om du behöver kompatibilitet, hastighet eller visuell restaurering. + Använd normal storleksändring för miniatyrer, uppladdningsgränser, skärmdumpar och enkla dimensionsändringar. + Använd AI-uppskalning när en liten eller mjuk bild behöver se bättre ut i en större storlek. + Jämför ansikten, text och mönster efter AI-uppskalning eftersom genererade detaljer kan se övertygande men felaktiga ut. + Filterordningen är viktig + Använd rengöring, färg, skärpa och slutlig komprimering i en avsiktlig sekvens + Bygg en renare filterstapel + Filter är inte alltid utbytbara. En oskärpa före skärpning, en kontrastökning före OCR eller en färgförändring före komprimering kan ge mycket olika utdata från samma kontroller. + Beskär och rotera först så att senare filter bara fungerar på de pixlar som finns kvar. + Använd exponering, vitbalans och kontrast innan skärpning eller stiliserade effekter. + Håll slutlig storleksändring och komprimering nära slutet så att förhandsgranskade detaljer överlever exporten på ett förutsägbart sätt. + Fixa bakgrundskanter + Rengör glorier, överblivna skuggor, hår, hål och mjuka konturer efter automatisk borttagning + Få utskärningar att se avsiktliga ut + Automatiska masker ser ofta bra ut med ett ögonkast men avslöjar problem på ljusa, mörka eller mönstrade bakgrunder. Kantrensning är skillnaden mellan en snabb utskärning och en återanvändbar klistermärke, logotyp eller produktbild. + Förhandsgranska resultatet mot kontrasterande bakgrunder för att avslöja glorier och saknade kantdetaljer. + Använd restore för hår, hörn och genomskinliga föremål innan du raderar omgivande rester. + Exportera ett litet test på den slutliga bakgrundsfärgen när utklippet kommer att placeras i en design. + Läsbara vattenstämplar + Gör märken synliga på ljusa, mörka, upptagna och beskurna bilder utan att förstöra fotot + Skydda bilden utan att dölja den + En vattenstämpel som ser bra ut på en bild kan försvinna på en annan. Storlek, opacitet, kontrast, placering och upprepning bör väljas för hela batchen, inte bara den första förhandsvisningen. + Testa märket på de ljusaste och mörkaste bilderna i partiet innan du exporterar alla filer. + Använd lägre opacitet för stora upprepade märken och starkare kontrast för små hörnmärken. + Håll viktiga ansikten, text och produktdetaljer tydliga om inte märket är avsett att förhindra återanvändning. + Dela en bild i brickor + Klipp ut en enskild bild efter rader, kolumner, anpassade procentsatser, format och kvalitet + Förbered rutnät och beställda bitar + Bilddelning skapar flera utdata från en källbild. Den kan dela upp efter antal rader och kolumner, justera rad- eller kolumnprocentsatser och spara bitar med valt utdataformat och kvalitet, vilket är användbart för rutnät, sidsnitt, panoramabilder och ordnade sociala inlägg. + Välj källbild och ställ sedan in antalet rader och kolumner du behöver. + Justera rad- eller kolumnprocentsatser när bitarna inte alla ska vara lika stora. + Välj utdataformat och kvalitet innan du sparar så att varje genererad bit använder samma exportregler. + Klipp ut bildremsor + Ta bort vertikala eller horisontella områden och slå samman de återstående delarna igen + Ta bort en region utan att lämna en lucka + Bildskärning skiljer sig från normal beskärning. Den kan ta bort en vald vertikal eller horisontell remsa och sedan slå samman de återstående bilddelarna. Omvänt läge behåller det valda området istället, och om du använder båda omvända riktningarna behålls den valda rektangeln. + Använd vertikal skärning för sidoområden, pelare eller mittremsor; använd horisontell skärning för banderoller, luckor eller rader. + Aktivera invers på en axel när du vill behålla den valda delen istället för att ta bort den. + Förhandsgranska kanter efter klippning eftersom sammanfogat innehåll kan se abrupt ut när det borttagna området korsar viktiga detaljer. + Välj utdataformat + Välj JPEG, PNG, WebP, AVIF, JXL eller PDF baserat på innehåll och destination + Låt destinationen bestämma formatet + Det bästa formatet beror på vad bilden innehåller och var den ska användas. Foton, skärmdumpar, transparenta tillgångar, dokument och animerade filer misslyckas alla på olika sätt när de tvingas in i fel format. + Använd JPEG för bred fotokompatibilitet när transparens och exakta pixlar inte krävs. + Använd PNG för skarpa skärmdumpar, UI-inspelningar, transparent grafik och förlustfria redigeringar. + Använd WebP, AVIF eller JXL när kompakt modern utdata är viktig och den mottagande appen stöder det. + Extrahera ljudomslag + Spara inbäddade albumbilder eller tillgängliga mediaramar från ljudfiler som PNG-bilder + Dra ut konstverk från ljudfiler + Audio Covers använder Android-mediametadata för att läsa inbäddade bilder från ljudfiler. När inbäddade teckningar inte är tillgängliga kan hämtaren falla tillbaka till en mediaram om Android kan tillhandahålla en; om ingetdera finns rapporterar verktyget att det inte finns någon bild att extrahera. + Öppna Audio Covers och välj en eller flera ljudfiler med förväntad omslagsbild. + Granska det extraherade omslaget innan du sparar eller delar, särskilt för filer från streaming- eller inspelningsappar. + Om ingen bild hittas har ljudfilen sannolikt inget inbäddat omslag eller så kunde inte Android exponera en användbar ram. + Datum och platsmetadata + Bestäm vad som ska sparas när fångsttiden är viktig men GPS eller enhetsdata inte ska läcka + Separera användbar metadata från privat metadata + Metadata är inte alla lika riskabla. Fångstdatum kan vara användbara för arkiv och sortering, medan GPS, enhetsserieliknande fält, programvarutaggar eller ägarfält kan avslöja mer än vad som är tänkt. + Behåll datum- och tidstaggar när kronologisk ordning är viktig för album, skanningar eller projekthistorik. + Ta bort GPS och enhetsidentifierande taggar innan du delar eller skickar bilder till främlingar offentligt. + Exportera ett prov och inspektera EXIF ​​igen när integritetskraven är strikta. + Undvik filnamnskollisioner + Skydda batchutgångar när många filer delar namn som image.jpg eller screenshot.png + Gör varje utdatanamn unikt + Dubblettfilnamn är vanliga när bilder kommer från budbärare, skärmdumpar, molnmappar eller flera kameror. Ett bra mönster förhindrar oavsiktlig utbyte och håller utdata spårbara tillbaka till sina källor. + Inkludera originalnamn plus ett suffix när den redigerade kopian ska kunna kännas igen. + Lägg till sekvens, tidsstämpel, förinställning eller dimensioner när du bearbetar stora blandade batcher. + Undvik att skriva över arbetsflöden tills du har verifierat att namnmönstret inte kan kollidera. + Spara eller dela + Välj mellan en beständig fil och en tillfällig handoff till en annan app + Exportera med rätt avsikt + Spara och dela kan kännas lika, men de löser olika problem. Att spara skapar en fil som du kan hitta senare; delning skickar ett genererat resultat via Android till en annan app och kanske inte lämnar en hållbar lokal kopia. + Använd Spara när utdata måste stanna i en känd mapp, säkerhetskopieras eller återanvändas senare. + Använd Dela för snabbmeddelanden, e-postbilagor, sociala inlägg eller skicka resultatet till en annan redaktör. + Spara först när den mottagande appen är opålitlig eller när en misslyckad delning skulle vara irriterande att återskapa. + PDF-sidstorlek och marginaler + Välj pappersstorlek, passform och andningsrum innan du skapar ett dokument + Gör bildsidor utskrivbara och läsbara + Bilder kan bli besvärliga PDF-sidor när deras form inte matchar den valda pappersstorleken. Marginaler, passningsläge och sidorientering avgör om innehållet är beskuret, litet, utsträckt eller bekvämt att läsa. + Använd en riktig pappersstorlek när PDF:en kan skrivas ut eller skickas till ett formulär. + Använd marginaler för skanningar och skärmdumpar så att texten inte ligger mot sidans kant. + Förhandsgranska stående och liggande sidor tillsammans när källbilderna har blandad orientering. + Rensa upp skanningar + Förbättra papperskanter, skuggor, kontrast, rotation och läsbarhet före PDF eller OCR + Förbered sidor innan arkivering + En skanning kan vara tekniskt fångad men fortfarande svår att läsa. Små saneringssteg före PDF-export är ofta viktigare än komprimeringsinställningar, särskilt för kvitton, handskrivna anteckningar och sidor med svag belysning. + Korrigera perspektiv och beskära bort bordskanter, fingrar, skuggor och bakgrundsröra. + Öka kontrasten försiktigt så att texten blir tydligare utan att förstöra stämplar, signaturer eller pennmärken. + Kör OCR först efter att sidan är upprätt och läsbar, verifiera sedan viktiga siffror manuellt. + Komprimera, gråskala eller reparera PDF-filer + Använd PDF-operationer med en fil för lättare, utskrivbara eller ombyggda dokumentkopior + Välj PDF-rensningsåtgärden + PDF-verktyg innehåller separata operationer för komprimering, gråskalekonvertering och reparation. Komprimering och gråskala fungerar huvudsakligen genom inbäddade bilder, medan reparation bygger om PDF-strukturen; ingen av dessa bör behandlas som en garanterad fix för varje dokument. + Använd Komprimera PDF när dokumentet innehåller bilder och filstorleken har betydelse för delning. + Använd gråskala när dokumentet ska vara lättare att skriva ut eller inte behöver färgbilder. + Använd Reparera PDF på en kopia när ett dokument inte öppnas eller har skadad intern struktur, verifiera sedan den återuppbyggda filen. + Extrahera bilder från PDF-filer + Återställ inbäddade PDF-bilder och packa de extraherade resultaten i ett arkiv + Spara källbilder från dokument + Extrahera bilder skannar PDF:en efter inbäddade bildobjekt, hoppar över dubbletter där det är möjligt och lagrar återställda bilder i ett genererat ZIP-arkiv. Det extraherar bara bilder som faktiskt är inbäddade i PDF:en, inte text, vektorritningar eller varje synlig sida som en skärmdump. + Använd Extrahera bilder när du behöver bilder lagrade i en PDF, till exempel foton från en katalog eller skannade tillgångar. + Använd PDF till bilder eller sidextraktion istället när du behöver helsidor, inklusive text och layout. + Om verktyget inte rapporterar några inbäddade bilder kan sidan vara text/vektorinnehåll eller så kan bilderna inte lagras som extraherbara objekt. + Metadata, utjämning och utskrift + Redigera dokumentegenskaper, rastrera sidor eller förbered anpassade utskriftslayouter avsiktligt + Välj den slutliga dokumentåtgärden + PDF-metadata, förplattning och utskriftsförberedelse löser olika problem i slutskedet. Metadata styr dokumentegenskaperna, Flatten PDF rastrar sidor till mindre redigerbara utdata och Print PDF förbereder en ny layout med sidstorlek, orientering, marginal och sidor per ark. + Använd metadata när författare, titel, nyckelord, producent eller integritetsrensning är viktiga. + Använd Platta PDF när synligt sidinnehåll borde bli svårare att redigera som separata PDF-objekt. + Använd Print PDF när du behöver anpassad sidstorlek, orientering, marginaler eller flera sidor per ark. + Förbered bilder för OCR + Använd beskärning, rotation, kontrast, denoise och skala innan du ber OCR att läsa text + Ge OCR-renare pixlar + OCR-fel kommer ofta från inmatningsbilden snarare än OCR-motorn. Krokiga sidor, låg kontrast, oskärpa, skuggor, liten text och blandade bakgrunder minskar igenkänningskvaliteten. + Beskär till textområdet och rotera bilden så att linjerna är horisontella innan de känns igen. + Öka kontrasten eller konvertera till renare svart-vitt endast när det gör bokstäver lättare att läsa. + Ändra storlek på liten text uppåt måttligt, men undvik aggressiv skärpa som skapar falska kanter. + Kontrollera QR-innehåll + Granska länkar, Wi-Fi-uppgifter, kontakter och åtgärder innan du litar på en kod + Behandla avkodad text som opålitlig indata + En QR-kod kan dölja en URL, kontaktkort, Wi-Fi-lösenord, kalenderhändelse, telefonnummer eller vanlig text. Den synliga etiketten nära koden kanske inte matchar den faktiska nyttolasten, så granska det avkodade innehållet innan du agerar på det. + Inspektera den fullständiga avkodade webbadressen innan du öppnar den, särskilt förkortade eller okända domäner. + Var försiktig med Wi-Fi-, kontakt-, kalender-, telefon- och SMS-koder eftersom de kan utlösa känsliga åtgärder. + Kopiera innehållet först när du behöver verifiera det någon annanstans istället för att öppna det direkt. + Generera QR-koder + Skapa koder från vanlig text, webbadresser, Wi-Fi, kontakter, e-post, telefon, SMS och platsdata + Bygg rätt QR-nyttolast + QR-skärmen kan generera koder från inmatat innehåll samt skanna befintliga. Strukturerade QR-typer hjälper till att koda data i ett format som andra appar förstår, till exempel Wi-Fi-inloggningsdetaljer, kontaktkort, e-postfält, telefonnummer, SMS-innehåll, webbadresser eller geografiska koordinater. + Välj vanlig text för enkla anteckningar, eller använd en strukturerad typ när koden ska öppnas som Wi-Fi, kontakt, URL, e-post, telefon, SMS eller platsdata. + Kontrollera varje fält innan du delar den genererade koden eftersom den synliga förhandsgranskningen endast återspeglar nyttolasten du angav. + Testa den sparade QR-koden med en skanner när den kommer att skrivas ut, publiceras offentligt eller användas av andra. + Välj ett kontrollsummaläge + Beräkna hash från filer eller text, jämför en fil eller batchchecka många filer + Verifiera innehåll, inte utseende + Checksum Tools har separata flikar för filhashning, texthashning, jämförelse av en fil mot en känd hash och batchjämförelse. En kontrollsumma bevisar byte-för-byte-identitet för den valda algoritmen; det bevisar inte att två bilder bara ser likadana ut. + Använd Beräkna för filer och Text Hash för skrivna eller inklistrade textdata. + Använd Jämför när någon ger dig en förväntad kontrollsumma för en fil. + Använd Batch Compare när många filer behöver hash eller verifiering i ett pass, håll sedan den valda algoritmen konsekvent. + Urklipp sekretess + Använd automatiska urklippsfunktioner utan att av misstag exponera privat kopierad data + Håll kopierat innehåll avsiktligt + Urklippsautomatisering är bekvämt, men kopierad text kan innehålla lösenord, länkar, adresser, tokens eller privata anteckningar. Behandla urklippsbaserad inmatning som en hastighetsfunktion som bör matcha dina vanor och förväntningar på integritet. + Inaktivera automatisk användning av urklipp om privat text dyker upp i verktyg oväntat. + Spara endast urklipp när du medvetet vill att resultatet ska undvika lagring. + Rensa eller byt ut urklippet efter att ha hanterat känslig text, QR-data eller Base64-nyttolaster. + Färgformat + Förstå HEX-, RGB-, HSV-, alfa- och kopierade färgvärden innan du klistrar in någon annanstans + Kopiera formatet som målet förväntar sig + Samma synliga färg kan skrivas på flera sätt. Ett designverktyg, CSS-fält, Android-färgvärde eller bildredigerare kan förvänta sig en annan ordning, alfahantering eller färgmodell. + Använd HEX när du klistrar in i webb-, design- eller temafält som förväntar sig kompakta färgkoder. + Använd RGB eller HSV när du behöver justera kanaler, ljusstyrka, mättnad eller nyans manuellt. + Kontrollera alfa separat när transparens är viktig eftersom vissa destinationer ignorerar det eller använder en annan ordning. + Bläddra i färgbiblioteket + Sök namngivna färger efter namn eller HEX och håll favoriter nära toppen + Hitta återanvändbara namngivna färger + Color Library laddar en namngiven färgsamling, sorterar den efter namn, filtrerar efter färgnamn eller HEX-värde och lagrar favoritfärger så att viktiga poster blir lättare att nå. Det är användbart när du behöver en riktig namngiven färg istället för att ta prov från en bild. + Sök efter ett färgnamn när du känner till familjen, till exempel röd, blå, skiffer eller mint. + Sök med HEX när du redan har en numerisk färg och vill hitta matchande eller närliggande namngivna poster. + Markera vanliga färger som favoriter så att de går före det allmänna biblioteket medan du surfar. + Använd mesh-gradientsamlingar + Ladda ner tillgängliga mesh-gradientresurser och dela utvalda bilder + Använd externa mesh-gradienttillgångar + Mesh Gradients kan ladda en fjärrresurssamling, ladda ner saknade gradientbilder, visa nedladdningsförlopp och dela valda gradienter. Tillgänglighet beror på nätverksåtkomst och fjärrresursarkivet, så en tom lista betyder vanligtvis att resurser inte var tillgängliga ännu. + Öppna Mesh Gradients från gradientverktygen när du vill ha färdiga mesh-gradientbilder. + Vänta på resursladdning eller nedladdningsförlopp innan du antar att samlingen är tom. + Välj och dela de gradienter du behöver, eller gå tillbaka till Gradient Maker när du vill bygga en anpassad gradient manuellt. + Genererad tillgångsupplösning + Välj utdatastorlek innan du genererar gradienter, brus, bakgrundsbilder, SVG-liknande konst eller texturer + Generera i den storlek du behöver + Genererade bilder har inget kameraoriginal att falla tillbaka på. Om utmatningen är för liten kan senare skalning mjuka upp mönster, förskjuta detaljer eller ändra hur tillgången beläggs och komprimeras. + Ställ in mått för det slutliga användningsfallet först, som tapeter, ikon, bakgrund, tryck eller överlägg. + Använd större storlekar för texturer som kan beskäras, zoomas eller återanvändas i flera layouter. + Exportera testversioner före enorma utdata eftersom procedurdetaljer kan ändra hur komprimering beter sig. + Exportera aktuella bakgrundsbilder + Hämta tillgängliga hem-, lås- och inbyggda bakgrundsbilder från enheten + Spara eller dela installerade bakgrundsbilder + Bakgrundsexport läser bakgrundsbilderna som Android exponerar genom systembakgrunds-API:erna. Beroende på enhet, Android-version, behörigheter och byggvariant kan hem, lås eller inbyggda bakgrunder vara tillgängliga separat eller saknas. + Öppna Bakgrundsexport för att ladda bakgrunderna som systemet låter appen läsa. + Välj de tillgängliga hem-, lås- eller inbyggda bakgrundsposter som du vill spara, kopiera eller dela. + Om en bakgrundsbild saknas eller funktionen inte är tillgänglig är det vanligtvis en system-, behörighets- eller byggvariantbegränsning snarare än en redigeringsinställning. + Corners Animation Throttle + Kopiera färg som + HEX + namn + Porträttmattmodell för snabba personklippningar med mjukare hår och kanthantering. + Snabb förbättring i svagt ljus med Zero-DCE++ kurvuppskattning. + Restormer-bildrestaureringsmodell för att minska regnstråk och artefakter i vått väder. + Dokumentera modell som förutsäger ett koordinatnät och mappar om böjda sidor till en plattare skanning. + Rörelsevaraktighetskala + Styr appens animationshastighet: 0 inaktiverar rörelse, 1 är normalt, högre värden saktar ner animationerna + Visa senaste verktyg + Visa snabb åtkomst till senast använda verktyg på huvudskärmen + Användningsstatistik + Utforska appöppningar, verktygslanseringar och dina mest använda verktyg + Appen öppnas + Verktyget öppnas + Sista verktyget + Lyckade räddningar + Aktivitetsserie + Data sparas + Toppformat + Använda verktyg + %1$d öppnas • %2$s + Ingen användningsstatistik ännu + Öppna några verktyg så visas de här automatiskt + Din användningsstatistik lagras endast lokalt på din enhet. ImageToolbox använder dem endast för att visa din personliga aktivitet, favoritverktyg och sparade datainsikter + redigerare redigera foto bild retuschera justera + ändra storlek konvertera skala upplösning dimensioner bredd höjd jpg jpeg png webp komprimera + komprimera storlek vikt byte mb kb minska kvaliteten optimera + beskär trim skär bildförhållande + filtereffekt färgkorrigering justera lutmask + rita pensel pennanteckning markering + chiffer kryptera dekryptera lösenordshemlighet + bakgrundsborttagare radera ta bort utskärning genomskinlig alfa + förhandsgranska tittargalleri inspektera öppet + stitch merge kombinera panorama lång skärmdump + url webb ladda ner internetlänk bild + plockare pipett prov hex rgb färg + palett färger swatches extrahera dominant + exif metadata sekretess ta bort strip ren gps-plats + jämför skillnaden före efter + gräns ändra storlek max min megapixel mått klämma + Verktyg för pdf-dokumentsidor + ocr text känner igen extraktavsökning + lutning bakgrundsfärg mesh + vattenstämpel logotyp textstämpel överlagring + gif animation animerade konvertera ramar + apng animation animerad png konvertera ramar + zip-arkiv komprimera pack packa upp filer + jxl jpeg xl konvertera jpg-bildformat + svg vektor spår konvertera xml + format konvertera jpg jpeg png webp heic avif jxl bmp + dokumentskanner skanna pappersperspektivbeskärning + qr streckkodsläsare skanning + stapling overlay genomsnittlig median astrofotografering + delade kakel rutnät skiva delar + färgkonvertera hex rgb hsl hsv cmyk lab + webp konvertera animerade bildformat + brusgenerator slumpmässig textur korn + collage rutnät layout kombinera + uppmärkningslager kommentera överläggsredigering + base64 kodar avkoda data-uri + checksum hash md5 sha sha1 sha256 verifiera + mesh gradient bakgrund + exif metadata redigera gps date kamera + skär delade rutnätsbitar brickor + ljudomslag albumomslag musik metadata + tapet export bakgrund + ascii text art tecken + ai ml neural förbättra exklusivt segment + färgbiblioteksmaterialpalett med namnet färger + shader glsl fragment effekt studio + pdf sammanfoga kombinera gå med + pdf dela separata sidor + Orientering av pdf rotera sidor + pdf ordna om sortera sidor + Sidnummer pdf sidnumrering + pdf ocr text känner igen sökbart utdrag + Pdf vattenstämpel logotyp text + Pdf signatur tecken ritning + pdf skydda lösenord kryptera lås + pdf låsa upp lösenord dekryptera ta bort skydd + Pdf komprimera minska storlek optimera + pdf gråskala svart vit monokrom + Pdf reparation fix återställa trasig + pdf-metadataegenskaper exif författarens titel + pdf ta bort radera sidor + Beskärningsmarginaler för pdf + Pdf platta kommentarer bildar lager + Pdf extrahera bilder bilder + Komprimera pdf-zip-arkiv + PDF-skrivare + Läsa pdf-förhandsvisning + bilder till pdf konvertera jpg jpeg png-dokument + pdf till bildsidor exportera jpg png + pdf-anteckningar kommentarer ta bort ren + Neutral variant + Fel + Drop Shadow + Tandhöjd + Horisontellt tandområde + Vertikal tandområde + Top Edge + Höger kant + Nedre kant + Vänster kant + Torn Edge + Återställ användningsstatistik + Verktyget öppnas, spara statistik, streak, sparad data och toppformat kommer att återställas. Appöppningar förblir oförändrade. + Audio + Fil + Exportera profiler + Spara aktuell profil + Ta bort exportprofil + Vill du ta bort exportprofilen \\"%1$s\\"? + Rita kant + Visa en tunn ram runt ritnings- och raderingsduken + Vågig + Rundade UI-element med en mjuk vågig kant + Skopa + Hack + Rundade hörn ristade inåt + Avtrappade hörn med dubbelt snitt + Platshållare + Använd {filnamn} för att infoga det ursprungliga filnamnet utan förlängning + Blossa + Basbelopp + Ringbelopp + Strålmängd + Ringens bredd + Förvränga perspektiv + Java utseende och känsla + Klippa + X vinkel + och vinkel + Ändra storlek på utdata + Vattendroppe + Våglängd + Fas + Högpass + Färgmask + Krom + Upplösa + Mjukhet + Feed-back + Linsoskärpa + Låg ingång + Hög input + Låg effekt + Hög effekt + Ljuseffekter + Bumphöjd + Bump mjukhet + Krusning + X amplitud + Y amplitud + X våglängd + Y våglängd + Adaptiv oskärpa + Texturgenerering + Skapa olika procedurstrukturer och spara dem som bilder + Textur typ + Bias + Tid + Glans + Dispersion + Prover + Vinkelkoefficient + Gradientkoefficient + Rutnätstyp + Avståndskraft + Skala Y + Luddighet + Bastyp + Turbulensfaktor + Skalning + Iterationer + Ringar + Borstad metall + Frätande + Cellulär + Schackbräde + fBm + Marmor + Plasma + Täcke + Trä + slumpmässig + Fyrkant + Hexagonal + Åttkantig + Triangulär + Kantad + VL Buller + SC Buller + Ny + Inga nyligen använda filter ännu + textur generator borstad metall kaustik cellulärt schackbräde marmor plasma täcke trä tegel kamouflage cell moln spricka tyg lövverk bikaka is lava nebulosa papper rost sand rök sten terräng topografi vatten krusning + Tegel + Kamouflage + Cell + Moln + Spricka + Tyg + Lövverk + Vaxkaka + Is + Lava + Nebulosa + Papper + Rost + Sand + Rök + Sten + Terräng + Topografi + Vatten Ripple + Avancerat trä + Murbrukets bredd + Oegentlighet + Fasa + Grovhet + Första tröskeln + Andra tröskeln + Tredje tröskeln + Kantmjukhet + Kantbredd + Rapportering + Detalj + Djup + Förgrening + Horisontella trådar + Vertikala trådar + Ludd + Vener + Belysning + Sprickbredd + Glasera + Flöde + Skorpa + Molndensitet + Stjärnor + Fiberdensitet + Fiberstyrka + Fläckar + Korrosion + Pitting + Flingor + Dynfrekvens + Vindvinkel + Ripples + Wisps + Venskala + Vattennivå + Bergsnivå + Erosion + Snönivå + Antal rader + Linjetjocklek + Skuggning + Porfärg + Murbruk färg + Mörk tegelfärg + Tegelfärg + Markera färg + Mörk färg + Skogsfärg + Jordfärg + Sand färg + Bakgrundsfärg + Cellfärg + Kantfärg + Himmel färg + Skuggfärg + Ljus färg + Ytfärg + Variation färg + Sprickfärg + Varp färg + Inslagsfärg + Mörk bladfärg + Bladfärg + Kantfärg + Honungsfärg + Djup färg + Isfärg + Frost färg + Skorpa färg + Tvätta färg + Glow färg + Space färg + Violett färg + Blå färg + Basfärg + Fiberfärg + Fläckfärg + Metallfärg + Mörk rostfärg + Rost färg + Orange färg + Rök färg + Åderfärg + Lågland färg + Vattenfärg + Rock färg + Snö färg + Låg färg + Hög färg + Linjefärg + Grund färg + Gräs + Smuts + Läder + Betong + Asfalt + Mossa + Brand + Aurora + Oljefläck + Vattenfärg + Abstrakt flöde + Opal + Damaskus stål + Blixt + Sammet + Bläckmarmorering + Holografisk folie + Bioluminescens + Kosmisk Vortex + Lava lampa + Händelsehorisont + Fractal Bloom + Kromatisk tunnel + Eclipse Corona + Konstig attraktion + Ferrofluid Crown + Supernova + Iris + Påfågelfjäder + Nautilus Shell + Ringad planet + Bladdensitet + Bladets längd + Vind + Flapphet + Klumpar + Fukt + Småsten + Rynkor + Porer + Aggregat + Sprickor + Tjära + Bära + Fläckar + Fibrer + Flamfrekvens + Rök + Intensitet + Band + Band + Regnbågsskimmer + Mörker + Blommar + Pigment + Kanter + Papper + Diffusion + Symmetri + Skärpa + Färgspel + Mjölkighet + Lager + Hopfällbar + polska + Grenar + Riktning + Glans + Vik + Fjädrar + Bläckbalans + Spektrum + Crinkles + Diffraktion + Vapen + Vrida + Kärnglöd + Blobbar + Skivlutning + Horisontstorlek + Diskens bredd + Linsning + Kronblad + Ringla + Filigran + Fasetter + Krökning + Månens storlek + Corona storlek + Strålar + Diamantring + Lober + Bantäthet + Tjocklek + Spikar + Pigglängd + Kroppsstorlek + Metallisk + Stötradie + Skalets bredd + Kastas ut + Pupillstorlek + Iris storlek + Färgvariation + Catchlight + Ögonstorlek + Hullingstäthet + vänder + Kammare + Öppning + Åsar + Pearlescence + Planetstorlek + Ringlutning + Ringens bredd + Atmosfär + Smuts färg + Mörk gräsfärg + Gräs färg + Spets färg + Mörk jordfärg + Torr färg + Pebble färg + Läder färg + Betongfärg + Tjära färg + Asfalt färg + Sten färg + Dammfärg + Jordfärg + Mörk mossa färg + Mossa färg + Röd färg + Kärnfärg + Grön färg + Cyan färg + Magenta färg + Guld färg + Pappersfärg + Pigmentfärg + Sekundär färg + Första färgen + Andra färgen + Rosa färg + Mörk stålfärg + Stålfärg + Ljus stålfärg + Oxidfärg + Halo färg + Bultfärg + Sammetsfärg + Glans färg + Blått bläckfärg + Röd bläckfärg + Mörk bläckfärg + Silver färg + Gul färg + Vävnadsfärg + Diskfärg + Het färg + Linsfärg + Ytterfärg + Inre färg + Corona färg + Kall färg + Varm färg + Moln färg + Flamfärg + Fjäderfärg + Skalfärg + Pärlfärg + Planet färg + Ring färg + Ta bort originalfiler efter att du har sparat + Endast framgångsrikt bearbetade källfiler kommer att raderas + Originalfiler raderade: %1$d + Det gick inte att ta bort originalfilerna: %1$d + Originalfiler raderade: %1$d, misslyckades: %2$d + Ark gester + Tillåt att utökade tillvalsark dras + Chroma subsampling + Lite djup + Förlustfri + %1$d-bitar + Byt namn på batch + Byt namn på flera filer med hjälp av filnamnsmönster + batch byta namn på filer filnamn mönster sekvens datumtillägg + Välj filer att byta namn på + Filnamnsmönster + Döpa om + Datumkälla + EXIF-datum taget + Filens ändringsdatum + Skapat datum för filen + Aktuellt datum + Manuellt datum + Manuellt datum + Manuell tid + Det valda datumet är inte tillgängligt för vissa filer. Det aktuella datumet kommer att användas för dem. + Ange ett filnamnsmönster + Mönstret ger ett ogiltigt eller för långt filnamn + Mönstret producerar dubbletter av filnamn + Alla filnamn matchar redan mönstret + Det här mönstret använder tokens som inte är tillgängliga för batchbyte + Namnet förblir detsamma + Filer har bytt namn + Vissa filer kunde inte döpas om + Tillstånd gavs inte + Använder det ursprungliga filnamnet utan förlängning, vilket hjälper dig att hålla källidentifieringen intakt. Stöder även skivning med \\o{start:slut}, translitteration med \\o{t} och ersätt med \\o{s/gammal/ny/}. + Auto-inkrementerande räknare. Stöder anpassad formatering: \\c{utfyllnad} (t.ex. \\c{3} -&gt; 001), \\c{start:steg} eller \\c{start:steg:utfyllnad}. + Namnet på den överordnade mappen där den ursprungliga filen fanns. + Storleken på originalfilen. Stöder enheter: \\z{b}, \\z{kb}, \\z{mb} eller bara \\z för läsbart format. + Genererar en slumpmässig Universally Unique Identifier (UUID) för att säkerställa filnamnets unikhet. + Att byta namn på filer kan inte ångras. Se till att de nya namnen är korrekta innan du fortsätter. + Bekräfta byta namn + Sidomenyinställningar + Meny skala + Meny alfa + Automatisk vitbalans + Klippning + Söker efter dolda vattenstämplar + Lagring + Cachegräns + Rensa cachen automatiskt när den växer över denna storlek + Rengöringsintervall + Hur ofta ska man kontrollera om cachen ska rensas + Vid appstart + 1 dag + 1 vecka + 1 månad + Rensa appens cache automatiskt enligt den valda gränsen och intervallet + Rörlig odlingsyta + Tillåter att hela beskärningsområdet flyttas genom att dra inuti det + Slå ihop GIF + Kombinera flera GIF-klipp till en animation + Beställning av GIF-klipp + Motsatt + Spela omvänt + Bumerang + Spela framåt och sedan bakåt + Normalisera ramstorlek + Skala ramar för att passa det största klippet; stäng av för att bevara sin ursprungliga skala + Fördröjning mellan klipp (ms) + Mapp + Välj alla bilder från en mapp och dess undermappar + Duplicate Finder + Hitta exakta kopior och visuellt liknande bilder + duplicate finder liknande bilder exakta kopior bilder sanering lagring dHash SHA-256 + Välj bilder för att hitta dubbletter + Likhetskänslighet + Exakta kopior + Liknande bilder + Välj alla exakta dubbletter + Välj alla utom rekommenderade + Inga dubbletter hittades + Försök att lägga till fler bilder eller öka likhetskänsligheten + Kunde inte läsa %1$d bild(er) + %1$s • %2$s kan återvinnas + %1$d grupper • %2$s kan återvinnas + %1$d har valts • %2$s + Detta kan inte ångras. Android kan be dig bekräfta borttagningen i en systemdialogruta. + Hittade inte + Flytta för att starta + Flytta till slutet + \ No newline at end of file diff --git a/core/resources/src/main/res/values-ta/strings.xml b/core/resources/src/main/res/values-ta/strings.xml new file mode 100644 index 0000000..3aa058f --- /dev/null +++ b/core/resources/src/main/res/values-ta/strings.xml @@ -0,0 +1,3739 @@ + + + + தொடங்குவதற்கு படத்தைத் தேர்ந்தெடுக்கவும் + நெகிழ்வான + இருங்கள் + படத்தை மீட்டமைக்கவும் + ரத்து செய் + வண்ணம் நகலெடுக்கப்பட்டது + நீலம் + பகிர் + வேகமான மங்கல் + வரை + மறைக்குறியீடு + வகையின்படி குழு விருப்பங்கள் + ஈமோஜியை இயக்கு + முயற்சி + மாற்ற வேண்டிய வண்ணம் + லாக் டிரா நோக்குநிலை + தட்டு பாணி + வானவில் + பழ சாலட் + கவனம் + எளிய 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 பக்கங்களிலிருந்து இணைப்புகள், கருத்துகள், சிறப்பம்சங்கள், வடிவங்கள் அல்லது படிவப் புலங்கள் போன்ற தேர்ந்தெடுக்கப்பட்ட சிறுகுறிப்பு வகைகளை அகற்றவும் + ஹைப்பர்லிங்க்கள் + கோப்பு இணைப்புகள் + கோடுகள் + பாப்அப்கள் + முத்திரைகள் + வடிவங்கள் + உரை குறிப்புகள் + உரை மார்க்அப் + படிவ புலங்கள் + மார்க்அப் + தெரியவில்லை + சிறுகுறிப்புகள் + குழுவிலக்கு + கட்டமைக்கக்கூடிய வண்ணம் மற்றும் ஆஃப்செட்களுடன் லேயருக்குப் பின்னால் மங்கலான நிழலைச் சேர்க்கவும் + உள்ளடக்க விழிப்புணர்வு சிதைவு + இந்த செயலை முடிக்க போதுமான நினைவகம் இல்லை. சிறிய படத்தைப் பயன்படுத்தவும், பிற பயன்பாடுகளை மூடவும் அல்லது பயன்பாட்டை மறுதொடக்கம் செய்யவும். + இணையான தொழிலாளர்கள் + மாற்றப்பட்ட கோப்புகள் அசல் படங்களை விட பெரியதாக இருக்கும் என்பதால், இந்தப் படங்கள் சேமிக்கப்படவில்லை. அசல் படங்களை நீக்கும் முன் இந்தப் பட்டியலைச் சரிபார்க்கவும். + கோப்புகள் தவிர்க்கப்பட்டன: %1$s + பயன்பாட்டு பதிவுகள் + சிக்கல்களைக் கண்டறிய, பயன்பாட்டின் பதிவுகளைப் பார்க்கவும் + குழுவாக்கப்பட்ட கருவிகளில் பிடித்தவை + கருவிகள் வகையின்படி குழுவாக்கப்படும் போது பிடித்தவைகளை தாவலாக சேர்க்கிறது + பிடித்ததை கடைசியாகக் காட்டு + பிடித்த கருவிகள் தாவலை இறுதிவரை நகர்த்துகிறது + கிடைமட்ட இடைவெளி + செங்குத்து இடைவெளி + பின்தங்கிய ஆற்றல் + முன்னோக்கி ஆற்றல் அல்காரிதத்திற்குப் பதிலாக எளிய சாய்வு அளவு ஆற்றல் வரைபடத்தைப் பயன்படுத்தவும் + முகமூடியை அகற்றவும் + முகமூடி அணிந்த பகுதி வழியாக சீம்கள் கடந்து செல்லும், அதை பாதுகாப்பதற்கு பதிலாக தேர்ந்தெடுக்கப்பட்ட பகுதியை மட்டும் அகற்றும் + VHS NTSC + மேம்பட்ட NTSC அமைப்புகள் + வலுவான VHS மற்றும் ஒளிபரப்பு கலைப்பொருட்களுக்கான கூடுதல் அனலாக் டியூனிங் + குரோமா இரத்தப்போக்கு + டேப் உடைகள் + கண்காணிப்பு + லுமா ஸ்மியர் + ஒலிக்கிறது + பனி + புலத்தைப் பயன்படுத்தவும் + வடிகட்டி வகை + உள்ளீடு லுமா வடிகட்டி + குரோமா லோபாஸ் உள்ளீடு + குரோமா டிமாடுலேஷன் + கட்ட மாற்றம் + கட்டம் ஆஃப்செட் + தலை மாறுதல் + தலை மாறுதல் உயரம் + தலை மாறுதல் ஆஃப்செட் + தலை மாறுதல் + நடு வரி நிலை + நடு வரி நடுக்கம் + சத்தத்தின் உயரத்தைக் கண்காணித்தல் + கண்காணிப்பு அலை + பனி கண்காணிப்பு + பனி அனிசோட்ரோபியைக் கண்காணித்தல் + கண்காணிப்பு சத்தம் + இரைச்சல் தீவிரத்தை கண்காணித்தல் + கூட்டு சத்தம் + கூட்டு இரைச்சல் அதிர்வெண் + கூட்டு இரைச்சல் தீவிரம் + கூட்டு இரைச்சல் விவரம் + ஒலிக்கும் அதிர்வெண் + ஒலிக்கும் சக்தி + லூமா சத்தம் + லூமா இரைச்சல் அதிர்வெண் + லூமா இரைச்சல் தீவிரம் + லுமா சத்தம் விவரம் + குரோமா சத்தம் + குரோமா இரைச்சல் அதிர்வெண் + குரோமா இரைச்சல் தீவிரம் + குரோமா இரைச்சல் விவரம் + பனி தீவிரம் + பனி அனிசோட்ரோபி + குரோமா கட்ட இரைச்சல் + குரோமா கட்டப் பிழை + குரோமா தாமதம் கிடைமட்டமானது + குரோமா தாமதம் செங்குத்து + VHS டேப் வேகம் + VHS குரோமா இழப்பு + VHS தீவிரத்தை கூர்மைப்படுத்துகிறது + VHS அதிர்வெண்ணைக் கூர்மைப்படுத்துகிறது + VHS விளிம்பு அலை தீவிரம் + VHS விளிம்பு அலை வேகம் + VHS விளிம்பு அலை அதிர்வெண் + VHS விளிம்பு அலை விவரம் + வெளியீடு குரோமா லோபாஸ் + கிடைமட்ட அளவு + செங்குத்து அளவு + அளவு காரணி X + அளவு காரணி ஒய் + பொதுவான பட வாட்டர்மார்க்ஸைக் கண்டறிந்து, அவற்றை லாமாவுடன் பூசுகிறது. கண்டறிதல் மற்றும் வண்ணப்பூச்சு மாதிரிகளை தானாக பதிவிறக்குகிறது + டியூட்டரனோபியா + படத்தை விரிவாக்கு + YOLO v11 பிரிவைப் பயன்படுத்தி ஆப்ஜெக்ட் பிரிவு அடிப்படையிலான பின்னணி நீக்கி + கோடு கோணத்தைக் காட்டு + வரையும்போது தற்போதைய வரி சுழற்சியை டிகிரிகளில் காட்டுகிறது + அனிமேஷன் செய்யப்பட்ட எமோஜிகள் + கிடைக்கும் ஈமோஜியை அனிமேஷன்களாகக் காட்டு + அசல் கோப்புறையில் சேமிக்கவும் + தேர்ந்தெடுக்கப்பட்ட கோப்புறைக்குப் பதிலாக அசல் கோப்பின் அடுத்த புதிய கோப்புகளைச் சேமிக்கவும் + வாட்டர்மார்க் PDF + போதிய நினைவாற்றல் இல்லை + இந்தச் சாதனத்தில் செயலாக்க முடியாத அளவுக்கு படம் பெரிதாக இருக்கலாம் அல்லது கணினியில் உள்ள நினைவகம் தீர்ந்துவிட்டது. படத்தின் தெளிவுத்திறனைக் குறைக்கவும், பிற பயன்பாடுகளை மூடவும் அல்லது சிறிய கோப்பைத் தேர்ந்தெடுக்கவும். + பிழைத்திருத்த மெனு + பயன்பாட்டுச் செயல்பாடுகளைச் சோதிப்பதற்கான மெனு, இது தயாரிப்பு வெளியீட்டில் காண்பிக்கப்படுவதை நோக்கமாகக் கொண்டிருக்கவில்லை + வழங்குபவர் + PaddleOCRக்கு உங்கள் சாதனத்தில் கூடுதல் ONNX மாதிரிகள் தேவை. %1$s தரவைப் பதிவிறக்க விரும்புகிறீர்களா? + உலகளாவிய + கொரியன் + லத்தீன் + கிழக்கு ஸ்லாவிக் + தாய் + கிரேக்கம் + ஆங்கிலம் + சிரிலிக் + அரபு + தேவநாகரி + தமிழ் + தெலுங்கு + ஷேடர் + ஷேடர் முன்னமைவு + ஷேடர் எதுவும் தேர்ந்தெடுக்கப்படவில்லை + ஷேடர் ஸ்டுடியோ + தனிப்பயன் துண்டு ஷேடர்களை உருவாக்கவும், திருத்தவும், சரிபார்க்கவும், இறக்குமதி செய்யவும் மற்றும் ஏற்றுமதி செய்யவும் + சேமித்த ஷேடர்கள் + சேமித்த ஷேடர்களைத் திறக்கவும், நகல் செய்யவும், ஏற்றுமதி செய்யவும், பகிரவும் அல்லது நீக்கவும் + புதிய ஷேடர் + ஷேடர் கோப்பு + .itshader JSON + %1$d அளவுருக்கள் + ஷேடர் ஆதாரம் + முக்கிய() வெற்றிடத்தின் உடலை மட்டும் எழுதவும். UV ஆயத்தொலைவுகளுக்கு textureCoordinate மற்றும் மூல மாதிரியாக inputImageTexture ஐப் பயன்படுத்தவும். + செயல்பாடுகள் + விருப்ப உதவி செயல்பாடுகள், மாறிலிகள் மற்றும் கட்டமைப்புகள் வெற்றிடத்திற்கு முன் செருகப்பட்டன. சீருடைகள் பாராக்களிலிருந்து உருவாக்கப்படுகின்றன. + ஷேடருக்கு திருத்தக்கூடிய மதிப்புகள் தேவைப்படும்போது சீருடைகளை இங்கே சேர்க்கவும். + ஷேடரை மீட்டமைக்கவும் + தற்போதைய ஷேடர் வரைவு அழிக்கப்படும் + தவறான ஷேடர் + ஆதரிக்கப்படாத ஷேடர் பதிப்பு %1$d. ஆதரிக்கப்படும் பதிப்பு: %2$d. + ஷேடர் பெயர் காலியாக இருக்கக்கூடாது. + ஷேடர் ஆதாரம் காலியாக இருக்கக்கூடாது. + ஷேடர் மூலத்தில் ஆதரிக்கப்படாத எழுத்துகள் உள்ளன: %1$s. ASCII GLSL மூலத்தை மட்டும் பயன்படுத்தவும். + அளவுரு \\\"%1$s\\\" ஒன்றுக்கு மேற்பட்ட முறை அறிவிக்கப்பட்டது. + அளவுரு பெயர்கள் காலியாக இருக்கக்கூடாது. + அளவுரு \\\"%1$s\\\" ஒதுக்கப்பட்ட GPUImage பெயரைப் பயன்படுத்துகிறது. + அளவுரு \\\"%1$s\\\" சரியான GLSL அடையாளங்காட்டியாக இருக்க வேண்டும். + அளவுரு \\\"%1$s\\\" %2$s மதிப்பு வகை \\\"%3$s\\\", எதிர்பார்க்கப்படுகிறது \\\"%4$s\\\". + அளவுரு \\\"%1$s\\\" ஆனது பூல் மதிப்புகளுக்கான நிமிடம் அல்லது அதிகபட்சத்தை வரையறுக்க முடியாது. + அளவுரு \\\"%1$s\\\" அதிகபட்சத்தை விட நிமிடம் அதிகமாக உள்ளது. + அளவுரு \\\"%1$s\\\" இயல்புநிலை நிமிடத்தை விட குறைவாக உள்ளது. + அளவுரு \\\"%1$s\\\" இயல்புநிலை அதிகபட்சத்தை விட அதிகமாக உள்ளது. + ஷேடர் கண்டிப்பாக \\\"சீரான மாதிரி2D %1$s;\\\" என்று அறிவிக்க வேண்டும். + ஷேடர் கண்டிப்பாக \\\"மாறுபடும் vec2 %1$s;\\\" என்று அறிவிக்க வேண்டும். + ஷேடர் கண்டிப்பாக \\\" void main()\\\" ஐ வரையறுக்க வேண்டும். + ஷேடர் gl_FragColor க்கு வண்ணத்தை எழுத வேண்டும். + ShaderToy மெயின்இமேஜ் ஷேடர்கள் ஆதரிக்கப்படவில்லை. GPUImage\'s void main() ஒப்பந்தத்தைப் பயன்படுத்தவும். + அனிமேஷன் செய்யப்பட்ட ShaderToy சீருடை \\\"iTime\\\" ஆதரிக்கப்படவில்லை. + அனிமேஷன் செய்யப்பட்ட ShaderToy சீருடை \\\"iFrame\\\" ஆதரிக்கப்படவில்லை. + ShaderToy சீருடை \\\"iResolution\\\" ஆதரிக்கப்படவில்லை. + ShaderToy iChannel இழைமங்கள் ஆதரிக்கப்படவில்லை. + வெளிப்புற இழைமங்கள் ஆதரிக்கப்படவில்லை. + வெளிப்புற அமைப்பு நீட்டிப்புகள் ஆதரிக்கப்படவில்லை. + லிப்ரெட்ரோ ஷேடர் அளவுருக்கள் ஆதரிக்கப்படவில்லை. + ஒரே ஒரு உள்ளீட்டு அமைப்பு மட்டுமே ஆதரிக்கப்படுகிறது. \\\"சீருடை %1$s %2$s;\\\" அகற்றவும். + அளவுரு \\\"%1$s\\\" கண்டிப்பாக \\\"சீரான %2$s %1$s;\\\" என அறிவிக்க வேண்டும். + அளவுரு \\\"%1$s\\\" \\\"சீரான %2$s %1$s;\\\" என அறிவிக்கப்பட்டது, எதிர்பார்க்கப்படும் \\\"சீரான %3$s %1$s;\\\". + ஷேடர் கோப்பு காலியாக உள்ளது. + ஷேடர் கோப்பு பதிப்பு, பெயர் மற்றும் ஷேடர் புலங்களைக் கொண்ட .itshader JSON பொருளாக இருக்க வேண்டும். + ஷேடர் கோப்பு தவறான JSON அல்லது .itshader வடிவத்துடன் பொருந்தவில்லை. + புலம் \\\"%1$s\\\" தேவை. + %1$s தேவை. + %1$s \\\"%2$s\\\" ஆதரிக்கப்படவில்லை. ஆதரிக்கப்படும் வகைகள்: %3$s. + \\\"%2$s\\\" அளவுருக்களுக்கு %1$s தேவை. + %1$s கண்டிப்பாக வரையறுக்கப்பட்ட எண்ணாக இருக்க வேண்டும். + %1$s ஒரு முழு எண்ணாக இருக்க வேண்டும். + %1$s உண்மையாகவோ அல்லது தவறாகவோ இருக்க வேண்டும். + %1$s என்பது வண்ணச் சரம், RGB/RGBA வரிசை அல்லது வண்ணப் பொருளாக இருக்க வேண்டும். + %1$s கண்டிப்பாக #RRGGBB அல்லது #RRGGBBAA வடிவமைப்பைப் பயன்படுத்த வேண்டும். + %1$s செல்லுபடியாகும் ஹெக்ஸாடெசிமல் வண்ண சேனல்களைக் கொண்டிருக்க வேண்டும். + %1$s 3 அல்லது 4 வண்ண சேனல்களைக் கொண்டிருக்க வேண்டும். + %1$s 0 மற்றும் 255 க்கு இடையில் இருக்க வேண்டும். + %1$s இரண்டு-எண் அணிவரிசையாக இருக்க வேண்டும் அல்லது x மற்றும் y கொண்ட ஒரு பொருளாக இருக்க வேண்டும். + %1$s சரியாக 2 எண்களைக் கொண்டிருக்க வேண்டும். + நகல் + எப்பொழுதும் EXIFஐ அழிக்கவும் + ஒரு கருவி மெட்டாடேட்டாவை வைத்திருக்கும் போது கூட, பட EXIF ​​தரவை சேமிப்பதில் அகற்றவும் + உதவி & குறிப்புகள் + %1$d பயிற்சிகள் + திறந்த கருவி + முக்கிய கருவிகள், ஏற்றுமதி விருப்பங்கள், PDF ஓட்டங்கள், வண்ண பயன்பாடுகள் மற்றும் பொதுவான சிக்கல்களுக்கான தீர்வுகள் ஆகியவற்றைக் கற்றுக்கொள்ளுங்கள் + தொடங்குதல் + கருவிகளைத் தேர்வு செய்யவும், கோப்புகளை இறக்குமதி செய்யவும், முடிவுகளைச் சேமிக்கவும், அமைப்புகளை விரைவாகப் பயன்படுத்தவும் + பட எடிட்டிங் + அளவை மாற்றவும், செதுக்கவும், வடிகட்டவும், பின்னணியை அழிக்கவும், வரையவும் மற்றும் வாட்டர்மார்க் செய்யவும் + கோப்புகள் மற்றும் மெட்டாடேட்டா + வடிவங்களை மாற்றவும், வெளியீட்டை ஒப்பிடவும், தனியுரிமையைப் பாதுகாக்கவும் மற்றும் கோப்புப் பெயர்களைக் கட்டுப்படுத்தவும் + PDF மற்றும் ஆவணங்கள் + PDFகளை உருவாக்கவும், ஆவணங்களை ஸ்கேன் செய்யவும், OCR பக்கங்களை ஸ்கேன் செய்யவும் மற்றும் பகிர்வதற்காக கோப்புகளை தயார் செய்யவும் + உரை, QR மற்றும் தரவு + உரையை அங்கீகரிக்கவும், குறியீடுகளை ஸ்கேன் செய்யவும், Base64 ஐ குறியாக்கவும், ஹாஷ்களை சரிபார்க்கவும் மற்றும் தொகுப்பு கோப்புகளை சரிபார்க்கவும் + வண்ண கருவிகள் + வண்ணங்களைத் தேர்ந்தெடுக்கவும், தட்டுகளை உருவாக்கவும், வண்ண நூலகங்களை ஆராயவும் மற்றும் சாய்வுகளை உருவாக்கவும் + ஆக்கப்பூர்வமான கருவிகள் + SVG, ASCII கலை, இரைச்சல் அமைப்பு, ஷேடர்கள் மற்றும் சோதனை காட்சிகளை உருவாக்கவும் + சரிசெய்தல் + கனமான கோப்புகள், வெளிப்படைத்தன்மை, பகிர்வு, இறக்குமதி மற்றும் புகாரளிக்கும் சிக்கல்களை சரிசெய்யவும் + சரியான கருவியைத் தேர்ந்தெடுக்கவும் + சரியான இடத்தில் தொடங்க வகைகளைப் பயன்படுத்தவும், தேடல், பிடித்தவை மற்றும் செயல்களைப் பகிரவும் + சிறந்த நுழைவுப் புள்ளியிலிருந்து தொடங்குங்கள் + படக் கருவிப்பெட்டியில் பல மையப்படுத்தப்பட்ட கருவிகள் உள்ளன, அவற்றில் பல முதல் பார்வையில் ஒன்றுடன் ஒன்று. நீங்கள் முடிக்க விரும்பும் பணியிலிருந்து தொடங்கவும், பின்னர் முடிவின் மிக முக்கியமான பகுதியைக் கட்டுப்படுத்தும் கருவியைத் தேர்வு செய்யவும்: அளவு, வடிவம், மெட்டாடேட்டா, உரை, PDF பக்கங்கள், வண்ணங்கள் அல்லது தளவமைப்பு. + மறுஅளவிடுதல், PDF, EXIF, OCR, QR அல்லது வண்ணம் போன்ற செயல் பெயர் உங்களுக்குத் தெரிந்தால் தேடலைப் பயன்படுத்தவும். + பணி வகை மட்டும் உங்களுக்குத் தெரிந்தால் ஒரு வகையைத் திறக்கவும், பிறகு அடிக்கடி கருவிகளைப் பிடித்தவையாகப் பின் செய்யவும். + மற்றொரு ஆப்ஸ் பட கருவிப்பெட்டியில் படத்தைப் பகிரும்போது, ​​ஷேர் ஷீட் ஃப்ளோவிலிருந்து கருவியைத் தேர்ந்தெடுக்கவும். + முடிவுகளை இறக்குமதி செய்யவும், சேமிக்கவும் மற்றும் பகிரவும் + உள்ளீட்டைத் தேர்ந்தெடுப்பது முதல் திருத்தப்பட்ட கோப்பை ஏற்றுமதி செய்வது வரை வழக்கமான ஓட்டத்தைப் புரிந்து கொள்ளுங்கள் + பொதுவான எடிட்டிங் ஓட்டத்தைப் பின்பற்றவும் + பெரும்பாலான கருவிகள் ஒரே தாளத்தைப் பின்பற்றுகின்றன: உள்ளீட்டைத் தேர்வுசெய்க, விருப்பங்களைச் சரிசெய்தல், முன்னோட்டம், பின்னர் சேமித்தல் அல்லது பகிர்தல். முக்கியமான விவரம் என்னவென்றால், சேமிப்பது ஒரு வெளியீட்டு கோப்பை உருவாக்குகிறது, அதே சமயம் பகிர்தல் பொதுவாக மற்றொரு பயன்பாட்டிற்கு விரைவாக ஒப்படைக்க சிறந்தது. + ஒற்றைப் படக் கருவிகளுக்கு ஒரு கோப்பைத் தேர்ந்தெடுக்கவும் அல்லது பிக்கர் அனுமதிக்கும் போது தொகுதிக் கருவிகளுக்குப் பல கோப்புகளைத் தேர்ந்தெடுக்கவும். + சேமிப்பதற்கு முன் முன்னோட்டம் மற்றும் வெளியீட்டு கட்டுப்பாடுகளைச் சரிபார்க்கவும், குறிப்பாக வடிவம், தரம் மற்றும் கோப்பு பெயர் விருப்பங்கள். + விரைவான ஒப்படைப்புக்கு பகிர்வைப் பயன்படுத்தவும் அல்லது தேர்ந்தெடுக்கப்பட்ட கோப்புறையில் நிலையான நகல் தேவைப்படும்போது சேமிக்கவும். + ஒரே நேரத்தில் பல படங்களுடன் வேலை செய்யுங்கள் + மீண்டும் மீண்டும் மறுஅளவிடுதல், மாற்றுதல், சுருக்குதல் மற்றும் பெயரிடுதல் பணிகளுக்கு தொகுதி கருவிகள் சிறந்தவை + யூகிக்கக்கூடிய தொகுதியைத் தயார் செய்யவும் + ஒவ்வொரு வெளியீடும் ஒரே விதிகளைப் பின்பற்றும்போது தொகுதி செயலாக்கம் வேகமாக இருக்கும். இது ஒரு பெரிய தவறு செய்ய எளிதான இடமாகும், எனவே நீண்ட ஏற்றுமதியைத் தொடங்குவதற்கு முன் பெயரிடுதல், தரம், மெட்டாடேட்டா மற்றும் கோப்புறை நடத்தை ஆகியவற்றைத் தேர்ந்தெடுக்கவும். + அனைத்து தொடர்புடைய படங்களையும் ஒன்றாகத் தேர்ந்தெடுத்து, வெளியீடு வரிசையைப் பொறுத்தது என்றால் வரிசையை வைத்திருங்கள். + ஏற்றுமதியைத் தொடங்குவதற்கு முன் மறுஅளவிடுதல், வடிவம், தரம், மெட்டாடேட்டா மற்றும் கோப்புப்பெயர் விதிகளை ஒருமுறை அமைக்கவும். + மூலக் கோப்புகள் பெரியதாக இருக்கும்போது அல்லது இலக்கு வடிவம் உங்களுக்குப் புதியதாக இருக்கும்போது முதலில் ஒரு சிறிய சோதனைத் தொகுப்பை இயக்கவும். + விரைவான ஒற்றை திருத்தம் + ஒரு படத்தில் பல எளிய மாற்றங்கள் தேவைப்படும்போது ஆல் இன் ஒன் எடிட்டரைப் பயன்படுத்தவும் + டூல் ஹாப்பிங் இல்லாமல் ஒரு படத்தை முடிக்கவும் + படத்திற்கு ஒரு சிறப்புச் செயல்பாட்டிற்குப் பதிலாக சிறிய அளவிலான மாற்றங்கள் தேவைப்படும்போது ஒற்றைத் திருத்தம் பயனுள்ளதாக இருக்கும். இது பொதுவாக விரைவான சுத்தம், முன்னோட்டம் மற்றும் ஒரு இறுதி நகலை ஒரு தொகுதி பணிப்பாய்வு உருவாக்காமல் ஏற்றுமதி செய்ய வேகமானது. + பொதுவான மாற்றங்கள், மார்க்அப், செதுக்குதல், சுழற்சி அல்லது ஏற்றுமதி மாற்றங்கள் தேவைப்படும் ஒரு படத்திற்கு ஒற்றைத் திருத்தத்தைத் திறக்கவும். + வடிப்பான்களுக்கு முன் செதுக்குதல் மற்றும் இறுதி சுருக்கத்திற்கு முன் வடிப்பான்கள் போன்ற குறைந்த பிக்சல்களை முதலில் மாற்றும் வரிசையில் திருத்தங்களைப் பயன்படுத்தவும். + உங்களுக்கு தொகுதி செயலாக்கம், சரியான கோப்பு அளவு இலக்குகள், PDF வெளியீடு, OCR அல்லது மெட்டாடேட்டா சுத்தம் தேவைப்படும்போது அதற்குப் பதிலாக ஒரு சிறப்புக் கருவியைப் பயன்படுத்தவும். + முன்னமைவுகள் மற்றும் அமைப்புகளைப் பயன்படுத்தவும் + மீண்டும் மீண்டும் தேர்வுகளைச் சேமித்து, உங்களுக்கு விருப்பமான பணிப்பாய்வுக்கு பயன்பாட்டை டியூன் செய்யவும் + ஒரே அமைப்பை மீண்டும் செய்வதைத் தவிர்க்கவும் + ஒரே மாதிரியான கோப்பை அடிக்கடி ஏற்றுமதி செய்யும் போது முன்னமைவுகளும் அமைப்புகளும் பயனுள்ளதாக இருக்கும். ஒரு நல்ல முன்னமைவு, மீண்டும் மீண்டும் கையேடு சரிபார்ப்பு பட்டியலை ஒரே தட்டாக மாற்றுகிறது, அதே நேரத்தில் உலகளாவிய அமைப்புகள் புதிய கருவிகள் தொடங்கும் இயல்புநிலைகளை வரையறுக்கின்றன. + பொதுவான வெளியீட்டு அளவுகள், வடிவங்கள், தர மதிப்புகள் அல்லது பெயரிடும் வடிவங்களுக்கான முன்னமைவுகளை உருவாக்கவும். + இயல்புநிலை வடிவம், தரம், கோப்புறையைச் சேமித்தல், கோப்புப்பெயர், பிக்கர் மற்றும் இடைமுக நடத்தைக்கான அமைப்புகளை மதிப்பாய்வு செய்யவும். + பயன்பாட்டை மீண்டும் நிறுவும் முன் அல்லது மற்றொரு சாதனத்திற்குச் செல்வதற்கு முன் அமைப்புகளை காப்புப் பிரதி எடுக்கவும். + பயனுள்ள இயல்புநிலைகளை அமைக்கவும் + இயல்புநிலை வடிவம், தரம், அளவிலான பயன்முறை, மறுஅளவிடுதல் வகை மற்றும் வண்ண இடத்தை ஒரு முறை தேர்வு செய்யவும் + புதிய கருவிகளை உங்கள் இலக்குக்கு அருகில் தொடங்குங்கள் + இயல்புநிலை மதிப்புகள் வெறும் ஒப்பனை விருப்பத்தேர்வுகள் அல்ல. பல கருவிகளில் முதலில் என்ன வடிவம், தரம், மறுஅளவிடுதல் நடத்தை, அளவீட்டு முறை மற்றும் வண்ண இடம் ஆகியவற்றை அவர்கள் தீர்மானிக்கிறார்கள், இது ஒவ்வொரு ஏற்றுமதியிலும் ஒரே மாதிரியான தேர்வுகளை மீண்டும் தேர்ந்தெடுப்பதைத் தவிர்க்க உதவுகிறது. + புகைப்படங்களுக்கான JPEG அல்லது ஆல்பாவிற்கு PNG/WebP போன்ற உங்கள் வழக்கமான இலக்குடன் பொருந்துமாறு இயல்புநிலை பட வடிவம் மற்றும் தரத்தை அமைக்கவும். + கடுமையான பரிமாணங்கள், சமூக தளங்கள் அல்லது ஆவணப் பக்கங்களுக்கு நீங்கள் அடிக்கடி ஏற்றுமதி செய்தால், இயல்புநிலை மறுஅளவிடல் வகை மற்றும் அளவுப் பயன்முறையை டியூன் செய்யவும். + காட்சிகள், அச்சு அல்லது வெளிப்புற எடிட்டர்கள் முழுவதும் உங்கள் பணிப்பாய்வுக்கு நிலையான வண்ணக் கையாளுதல் தேவைப்படும்போது மட்டுமே வண்ண இட இயல்புநிலைகளை மாற்றவும். + பிக்கர் மற்றும் லாஞ்சர் + பயன்பாடு அதிகமான உரையாடல்களைத் திறக்கும் போது அல்லது தவறான இடத்தில் தொடங்கும் போது பிக்கர் அமைப்புகளைப் பயன்படுத்தவும் + திறக்கும் கோப்புகளை உங்கள் பழக்கத்திற்கு ஏற்றவாறு செய்யுங்கள் + பிக்கர் மற்றும் லாஞ்சர் அமைப்புகள் படக் கருவிப்பெட்டி முதன்மைத் திரை, கருவி அல்லது கோப்புத் தேர்வு ஓட்டத்திலிருந்து தொடங்குகிறதா என்பதைக் கட்டுப்படுத்துகிறது. நீங்கள் வழக்கமாக ஆண்ட்ராய்ட் ஷேர் ஷீட்டில் இருந்து உள்ளிடும்போது அல்லது ஆப்ஸ் ஒரு டூல் லாஞ்சரைப் போல் உணர வேண்டும் என நீங்கள் விரும்பும் போது இந்த விருப்பங்கள் மிகவும் பயனுள்ளதாக இருக்கும். + சிஸ்டம் பிக்கர் கோப்புகளை மறைத்தால், மிக அதிகமான சமீபத்திய உருப்படிகளைக் காட்டினால் அல்லது கிளவுட் கோப்புகளை மோசமாகக் கையாளினால், புகைப்படத் தேர்வு அமைப்புகளைப் பயன்படுத்தவும். + நீங்கள் வழக்கமாக பகிர்விலிருந்து உள்ளிடும் அல்லது ஏற்கனவே மூலக் கோப்பை அறிந்த கருவிகளுக்கு மட்டும் ஸ்கிப் பிக்கிங்கை இயக்கவும். + இமேஜ் டூல்பாக்ஸ் சாதாரண முகப்புத் திரையை விட டூல் பிக்கரைப் போல் செயல்பட வேண்டுமெனில், லாஞ்சர் பயன்முறையை மதிப்பாய்வு செய்யவும். + படத்தின் ஆதாரம் + கேலரிகள், கோப்பு மேலாளர்கள் மற்றும் கிளவுட் கோப்புகளுடன் சிறப்பாகச் செயல்படும் பிக்கர் பயன்முறையைத் தேர்வுசெய்யவும் + சரியான மூலத்திலிருந்து கோப்புகளைத் தேர்ந்தெடுக்கவும் + ஆண்ட்ராய்டிடம் படங்களை எவ்வாறு ஆப்ஸ் கேட்கிறது என்பதைப் பட மூல அமைப்புகள் பாதிக்கின்றன. உங்கள் கோப்புகள் சிஸ்டம் கேலரி, கோப்பு மேலாளர் கோப்புறை, கிளவுட் வழங்குநர் அல்லது தற்காலிக உள்ளடக்கத்தைப் பகிரும் மற்றொரு பயன்பாட்டில் உள்ளதா என்பதைப் பொறுத்து சிறந்த தேர்வு அமையும். + பொதுவான கேலரி படங்களுக்கு மிகவும் கணினி-ஒருங்கிணைந்த அனுபவத்தை நீங்கள் விரும்பும் போது, ​​இயல்புநிலை தேர்வியைப் பயன்படுத்தவும். + ஆல்பங்கள், கோப்புறைகள், கிளவுட் கோப்புகள் அல்லது தரமற்ற வடிவங்கள் நீங்கள் எதிர்பார்க்கும் இடத்தில் தோன்றவில்லை என்றால் பிக்கர் பயன்முறையை மாற்றவும். + மூலப் பயன்பாட்டிலிருந்து வெளியேறிய பிறகு பகிரப்பட்ட கோப்பு மறைந்துவிட்டால், அதைத் தொடர்ந்து தேர்ந்தெடுக்கும் கருவி மூலம் மீண்டும் திறக்கவும் அல்லது முதலில் உள்ளூர் நகலைச் சேமிக்கவும். + பிடித்தவை மற்றும் பகிர்வு கருவிகள் + முதன்மைத் திரையை மையமாக வைத்து, பங்கு ஓட்டங்களில் அர்த்தமில்லாத கருவிகளை மறைக்கவும் + கருவி பட்டியல் இரைச்சலைக் குறைக்கவும் + நீங்கள் ஒவ்வொரு நாளும் ஒரு சில கருவிகளை மட்டுமே பயன்படுத்தும் போது பிடித்தவை மற்றும் பகிர்வுக்கான மறைக்கப்பட்ட அமைப்புகள் பயனுள்ளதாக இருக்கும். மற்றொரு ஆப்ஸ் அனுப்பும் கோப்பு வகையைப் பயன்படுத்த முடியாத கருவிகளை மறைப்பதன் மூலம் அவர்கள் பங்கு ஓட்டத்தை நடைமுறைப்படுத்துகிறார்கள். + அடிக்கடி கருவிகளைப் பின் செய்யவும், அதனால் கருவிகள் வகை வாரியாகத் தொகுக்கப்பட்டாலும் எளிதில் சென்றடையும். + மற்றொரு பயன்பாட்டிலிருந்து வரும் கோப்புகளுக்குப் பொருத்தமற்றதாக இருக்கும் போது, ​​பகிர்வுப் பரிமாற்றங்களிலிருந்து கருவிகளை மறைக்கவும். + முடிவில் பிடித்தவைகளை விரும்பினால் அல்லது தொடர்புடைய கருவிகளுடன் குழுவாக இருந்தால் பிடித்த ஆர்டர் அமைப்புகளைப் பயன்படுத்தவும். + அமைப்புகளை காப்புப் பிரதி எடுக்கவும் + முன்னமைவுகள், விருப்பத்தேர்வுகள் மற்றும் பயன்பாட்டு நடத்தை ஆகியவற்றைப் பாதுகாப்பாக மற்றொரு நிறுவலுக்கு நகர்த்தவும் + உங்கள் அமைப்பை சிறியதாக வைத்திருங்கள் + நீங்கள் முன்னமைவுகள், கோப்புறைகள், கோப்புப்பெயர் விதிகள், கருவி வரிசை மற்றும் காட்சி விருப்பத்தேர்வுகளை டியூன் செய்த பிறகு காப்புப்பிரதி மற்றும் மீட்டமை மிகவும் உதவியாக இருக்கும். ஒரு காப்புப்பிரதியானது, நினைவகத்திலிருந்து உங்கள் பணிப்பாய்வுகளை மீண்டும் உருவாக்காமல், அமைப்புகளுடன் பரிசோதனை செய்ய அல்லது பயன்பாட்டை மீண்டும் நிறுவ அனுமதிக்கிறது. + முன்னமைவுகள், கோப்பு பெயர் நடத்தை, இயல்புநிலை வடிவங்கள் அல்லது கருவி ஏற்பாட்டை மாற்றிய பின் காப்புப்பிரதியை உருவாக்கவும். + டேட்டாவை அழிக்கவோ, மீண்டும் நிறுவவோ அல்லது சாதனங்களை நகர்த்தவோ நீங்கள் திட்டமிட்டால், ஆப்ஸ் கோப்புறைக்கு வெளியே எங்காவது காப்புப்பிரதியைச் சேமிக்கவும். + முக்கியமான தொகுதிகளைச் செயலாக்குவதற்கு முன் அமைப்புகளை மீட்டெடுக்கவும், எனவே இயல்புநிலை மற்றும் உங்கள் பழைய அமைப்புடன் பொருந்தக்கூடிய நடத்தையைச் சேமிக்கவும். + படங்களை மாற்றவும் மற்றும் மாற்றவும் + ஒன்று அல்லது பல படங்களுக்கான அளவு, வடிவம், தரம் மற்றும் மெட்டாடேட்டாவை மாற்றவும் + மறுஅளவிடப்பட்ட நகல்களை உருவாக்கவும் + மறுஅளவிடுதல் மற்றும் மாற்று என்பது கணிக்கக்கூடிய பரிமாணங்கள் மற்றும் இலகுவான வெளியீட்டு கோப்புகளுக்கான முக்கிய கருவியாகும். படத்தின் அளவு, வெளியீட்டு வடிவம் மற்றும் மெட்டாடேட்டா நடத்தை ஆகியவை ஒரே நேரத்தில் முக்கியமானதாக இருக்கும்போது அதைப் பயன்படுத்தவும். + ஒன்று அல்லது அதற்கு மேற்பட்ட படங்களைத் தேர்ந்தெடுத்து, பரிமாணங்கள், அளவுகள் அல்லது கோப்பு அளவு மிகவும் முக்கியமானதா என்பதைத் தேர்ந்தெடுக்கவும். + வெளியீட்டு வடிவம் மற்றும் தரத்தைத் தேர்ந்தெடுக்கவும்; வெளிப்படைத்தன்மை பாதுகாக்கப்பட வேண்டும் என்றால் PNG அல்லது WebP ஐப் பயன்படுத்தவும். + முடிவை முன்னோட்டமிடவும், பின்னர் அசல்களை மாற்றாமல் உருவாக்கப்பட்ட நகல்களைச் சேமிக்கவும் அல்லது பகிரவும். + கோப்பு அளவு மூலம் அளவை மாற்றவும் + ஒரு இணையதளம், தூதுவர் அல்லது படிவம் கனமான படங்களை நிராகரிக்கும் போது அளவு வரம்பை குறிவைக்கவும் + கடுமையான பதிவேற்ற வரம்புகளைப் பொருத்தவும் + ஒரு குறிப்பிட்ட வரம்பிற்குக் கீழே உள்ள கோப்புகளை மட்டுமே இலக்கு ஏற்கும் போது, ​​தர மதிப்புகளை யூகிப்பதை விட, கோப்பு அளவின் அடிப்படையில் அளவை மாற்றுவது சிறந்தது. கருவியானது சுருக்கம் மற்றும் பரிமாணங்களைச் சரிசெய்து, முடிவைப் பயன்படுத்தக்கூடியதாக வைத்திருக்க முயற்சிக்கும்போது இலக்கை அடையலாம். + மெட்டாடேட்டா மற்றும் வழங்குநர் மாற்றங்களுக்கு இடமளிக்க, உண்மையான பதிவேற்ற வரம்பிற்கு சற்றுக் கீழே இலக்கு அளவை உள்ளிடவும். + இலக்கு சிறியதாக இருக்கும் போது புகைப்படங்களுக்கான நஷ்டமான வடிவங்களை விரும்பு; மறுஅளவிற்குப் பிறகும் PNG பெரிதாக இருக்கும். + ஏற்றுமதிக்குப் பிறகு முகங்கள், உரை மற்றும் விளிம்புகளை ஆய்வு செய்யுங்கள், ஏனெனில் ஆக்கிரமிப்பு அளவு இலக்குகள் புலப்படும் கலைப்பொருட்களை அறிமுகப்படுத்தலாம். + வரம்பு அளவு + உங்கள் அதிகபட்ச அகலம், உயரம் அல்லது கோப்புக் கட்டுப்பாடுகளை மீறும் படங்களை மட்டும் சுருக்கவும் + ஏற்கனவே நன்றாக இருக்கும் படங்களின் அளவை மாற்றுவதை தவிர்க்கவும் + சில படங்கள் பெரியதாகவும் மற்றவை ஏற்கனவே தயாராகவும் இருக்கும் கலப்பு கோப்புறைகளுக்கு வரம்பு மறுஅளவாக்கம் பயனுள்ளதாக இருக்கும். ஒவ்வொரு கோப்பையும் ஒரே மாதிரியான அளவை மாற்றுவதற்குப் பதிலாக, ஏற்றுக்கொள்ளக்கூடிய படங்களை அவற்றின் அசல் தரத்திற்கு நெருக்கமாக வைத்திருக்கும். + ஒவ்வொரு கோப்பையும் கண்மூடித்தனமாக மறுஅளவிடுவதற்குப் பதிலாக இலக்கின் அடிப்படையில் அதிகபட்ச பரிமாணங்கள் அல்லது வரம்புகளை அமைக்கவும். + மூலங்களை விட கனமான வெளியீடுகளைத் தவிர்க்க விரும்பும் போது, ​​பெரியதாக இருந்தால், நடை நடத்தையைத் தவிர்க்கவும். + வெவ்வேறு கேமராக்கள், ஸ்கிரீன் ஷாட்கள் அல்லது மூல அளவுகள் மிகவும் மாறுபடும் பதிவிறக்கங்களின் தொகுப்புகளுக்கு இதைப் பயன்படுத்தவும். + செதுக்கி, சுழற்றி, நேராக்குங்கள் + படத்தை ஏற்றுமதி செய்வதற்கு முன் அல்லது பிற கருவிகளில் பயன்படுத்துவதற்கு முன் முக்கியமான பகுதியை வடிவமைக்கவும் + கலவையை சுத்தம் செய்யவும் + முதலில் செதுக்குவது பிந்தைய வடிப்பான்கள், OCR, PDF பக்கங்கள் மற்றும் முடிவுகளைப் பகிர்வதைத் தூய்மைப்படுத்துகிறது. + செதுக்குதலைத் திறந்து, நீங்கள் சரிசெய்ய விரும்பும் படத்தைத் தேர்ந்தெடுக்கவும். + வெளியீடு ஒரு இடுகை, ஆவணம் அல்லது அவதாரத்துடன் பொருந்த வேண்டும் என்றால் இலவச பயிர் அல்லது விகிதத்தைப் பயன்படுத்தவும். + சேமிப்பதற்கு முன் சுழற்றவும் அல்லது புரட்டவும், அதனால் பின்னர் கருவிகள் திருத்தப்பட்ட படத்தைப் பெறும். + வடிப்பான்கள் மற்றும் சரிசெய்தல்களைப் பயன்படுத்தவும் + திருத்தக்கூடிய வடிகட்டி அடுக்கை உருவாக்கி, ஏற்றுமதிக்கு முன் முடிவை முன்னோட்டமிடுங்கள் + படத்தின் தோற்றத்தை டியூன் செய்யவும் + வடிப்பான்கள் விரைவான திருத்தங்கள், பகட்டான வெளியீடு மற்றும் OCR அல்லது அச்சிடுவதற்கு படங்களைத் தயாரிப்பதற்கு பயனுள்ளதாக இருக்கும். படத்தில் உரை அல்லது நுண்ணிய விவரங்கள் இருக்கும் போது, ​​கடுமையான விளைவுகளை விட, மாறுபாடு, கூர்மை மற்றும் பிரகாசம் ஆகியவற்றில் சிறிய மாற்றங்கள் முக்கியமானதாக இருக்கும். + வடிப்பான்களை ஒவ்வொன்றாகச் சேர்த்து, ஒவ்வொரு மாற்றத்திற்குப் பிறகும் முன்னோட்டத்தைக் கண்காணிக்கவும். + பணியைப் பொறுத்து பிரகாசம், மாறுபாடு, கூர்மை, தெளிவின்மை, நிறம் அல்லது முகமூடிகளை சரிசெய்யவும். + முன்னோட்டம் உங்கள் இலக்கு சாதனம், ஆவணம் அல்லது சமூக தளத்துடன் பொருந்தினால் மட்டுமே ஏற்றுமதி செய்யவும். + முதலில் முன்னோட்டம் + கனமான எடிட், கன்வெர்ஷன் அல்லது க்ளீனப் டூலைத் தேர்ந்தெடுக்கும் முன் படங்களைச் சரிபார்க்கவும் + நீங்கள் உண்மையில் பெற்றதைச் சரிபார்க்கவும் + அரட்டை, கிளவுட் ஸ்டோரேஜ் அல்லது வேறொரு பயன்பாட்டிலிருந்து கோப்பு வரும்போது பட முன்னோட்டம் பயனுள்ளதாக இருக்கும், மேலும் அதில் என்ன இருக்கிறது என்று உங்களுக்குத் தெரியவில்லை. பரிமாணங்கள், வெளிப்படைத்தன்மை, நோக்குநிலை மற்றும் காட்சித் தரத்தை முதலில் சரிபார்ப்பது சரியான பின்தொடர்தல் கருவியைத் தேர்வுசெய்ய உதவுகிறது. + நீங்கள் பல படங்களை ஆய்வு செய்ய வேண்டியிருக்கும் போது, ​​அவற்றை என்ன செய்வது என்று முடிவு செய்வதற்கு முன் பட முன்னோட்டத்தைத் திறக்கவும். + சுழற்சி சிக்கல்கள், வெளிப்படையான பகுதிகள், சுருக்க கலைப்பொருட்கள் மற்றும் எதிர்பாராத பெரிய பரிமாணங்களைத் தேடுங்கள். + எதைச் சரிசெய்ய வேண்டும் என்பதை நீங்கள் அறிந்த பின்னரே அளவை மாற்றவும், செதுக்கவும், ஒப்பிடவும், EXIF ​​​​அல்லது பின்னணி நீக்கிக்கு நகர்த்தவும். + பின்னணியை அகற்றவும் அல்லது மீட்டெடுக்கவும் + மீட்டெடுப்பு கருவிகள் மூலம் பின்னணியை தானாக அழிக்கவும் அல்லது விளிம்புகளை கைமுறையாக செம்மைப்படுத்தவும் + பொருளை வைத்திருங்கள், மீதமுள்ளவற்றை அகற்றவும் + தானியங்கு தேர்வை கவனமாக கைமுறையாக சுத்தம் செய்யும் போது பின்னணி அகற்றுதல் சிறப்பாக செயல்படும். முகமூடியைப் போலவே இறுதி ஏற்றுமதி வடிவமும் முக்கியமானது, ஏனெனில் முன்னோட்டம் சரியாகத் தெரிந்தாலும் JPEG வெளிப்படையான பகுதிகளைத் தட்டையாக்கும். + பின்னணியில் இருந்து பொருள் தெளிவாகப் பிரிக்கப்பட்டிருந்தால், தானாகவே அகற்றுவதைத் தொடங்கவும். + முடி, மூலைகள், நிழல்கள் மற்றும் சிறிய விவரங்களை சரிசெய்ய, பெரிதாக்கி, அழிக்க மற்றும் மீட்டமைக்க இடையே மாறவும். + இறுதி கோப்பில் வெளிப்படைத்தன்மை தேவைப்படும் போது PNG அல்லது WebP ஆக சேமிக்கவும். + வரையவும், குறிக்கவும் மற்றும் வாட்டர்மார்க் செய்யவும் + அம்புகள், குறிப்புகள், சிறப்பம்சங்கள், கையொப்பங்கள் அல்லது மீண்டும் மீண்டும் வாட்டர்மார்க் மேலடுக்குகளைச் சேர்க்கவும் + காணக்கூடிய தகவலைச் சேர்க்கவும் + மார்க்அப் கருவிகள் படத்தை விளக்க உதவுகின்றன, அதே சமயம் வாட்டர்மார்க்கிங் பிராண்ட் அல்லது ஏற்றுமதி செய்யப்பட்ட கோப்புகளைப் பாதுகாக்க உதவுகிறது. + ஃப்ரீஹேண்ட் குறிப்புகள், வடிவங்கள், சிறப்பம்சங்கள் அல்லது விரைவான சிறுகுறிப்புகள் தேவைப்படும்போது வரையவும். + வெளியீடுகளில் ஒரே உரை அல்லது படக் குறி தொடர்ந்து தோன்றும் போது வாட்டர்மார்க்கிங்கைப் பயன்படுத்தவும். + ஏற்றுமதி செய்வதற்கு முன் ஒளிபுகாநிலை, நிலை மற்றும் அளவைச் சரிபார்க்கவும், இதனால் குறி தெரியும் ஆனால் கவனத்தை சிதறடிக்காது. + இயல்புநிலைகளை வரையவும் + கோட்டின் அகலம், நிறம், பாதை முறை, நோக்குநிலைப் பூட்டு மற்றும் உருப்பெருக்கி ஆகியவற்றை வேகமாக மார்க்அப் செய்ய அமைக்கவும் + வரைதல் யூகிக்கக்கூடியதாக இருக்கும் + ஸ்கிரீன் ஷாட்களைக் குறிப்பிடும்போது, ​​ஆவணங்களைக் குறிக்கும்போது அல்லது படங்களில் அடிக்கடி கையொப்பமிடும்போது வரைதல் அமைப்புகள் உதவியாக இருக்கும். இயல்புநிலை அமைவு நேரத்தைக் குறைக்கிறது, அதே நேரத்தில் உருப்பெருக்கி மற்றும் நோக்குநிலைப் பூட்டு சிறிய திரைகளில் துல்லியமான திருத்தங்களை எளிதாக்குகிறது. + இயல்புநிலை வரி அகலத்தை அமைத்து, அம்புகள், சிறப்பம்சங்கள் அல்லது கையொப்பங்களுக்கு நீங்கள் அதிகம் பயன்படுத்தும் மதிப்புகளுக்கு வண்ணத்தை வரையவும். + நீங்கள் வழக்கமாக ஒரே மாதிரியான ஸ்ட்ரோக்குகள், வடிவங்கள் அல்லது மார்க்அப்களை வரையும்போது இயல்புநிலை பாதை பயன்முறையைப் பயன்படுத்தவும். + விரல் உள்ளீடு சிறிய விவரங்களை துல்லியமாக வைப்பதை கடினமாக்கும் போது உருப்பெருக்கி அல்லது நோக்குநிலை பூட்டை இயக்கவும். + பல பட தளவமைப்புகள் + ஸ்கிரீன்ஷாட்கள், ஸ்கேன்கள் அல்லது ஒப்பீட்டு கீற்றுகளை ஏற்பாடு செய்வதற்கு முன், சரியான பல படக் கருவியைத் தேர்ந்தெடுக்கவும் + சரியான தளவமைப்பு கருவியைப் பயன்படுத்தவும் + இந்தக் கருவிகள் ஒரே மாதிரியாக ஒலிக்கின்றன, ஆனால் ஒவ்வொன்றும் வெவ்வேறு வகையான பல-பட வெளியீட்டிற்காக டியூன் செய்யப்படுகின்றன. முதலில் சரியானதைத் தேர்ந்தெடுப்பது, வேறுபட்ட முடிவுக்காக வடிவமைக்கப்பட்ட லேஅவுட் கட்டுப்பாடுகளை எதிர்த்துப் போராடுவதைத் தவிர்க்கிறது. + ஒரு கலவையில் பல படங்களுடன் வடிவமைக்கப்பட்ட தளவமைப்புகளுக்கு Collage Maker ஐப் பயன்படுத்தவும். + படங்கள் ஒரு நீண்ட செங்குத்து அல்லது கிடைமட்ட துண்டுகளாக மாறும் போது, ​​பட தையல் அல்லது குவியலைப் பயன்படுத்தவும். + ஒரு பெரிய படம் பல சிறிய துண்டுகளாக மாற வேண்டியிருக்கும் போது படத்தைப் பிரிப்பதைப் பயன்படுத்தவும். + பட வடிவங்களை மாற்றவும் + பிக்சல்களை கைமுறையாகத் திருத்தாமல் JPEG, PNG, WebP, AVIF, JXL மற்றும் பிற நகல்களை உருவாக்கவும் + வேலைக்கான வடிவமைப்பைத் தேர்ந்தெடுக்கவும் + புகைப்படங்கள், ஸ்கிரீன்ஷாட்கள், வெளிப்படைத்தன்மை, சுருக்கம் அல்லது இணக்கத்தன்மைக்கு வெவ்வேறு வடிவங்கள் சிறந்தவை. இலக்கு பயன்பாடு எதை ஆதரிக்கிறது என்பதையும், நீங்கள் வைத்திருக்க விரும்பும் ஆல்பா, அனிமேஷன் அல்லது மெட்டாடேட்டா மூலத்தில் உள்ளதா என்பதையும் நீங்கள் புரிந்துகொண்டால், மாற்றம் பாதுகாப்பானது. + இணக்கமான படங்களுக்கு JPEG, இழப்பற்ற படங்கள் மற்றும் ஆல்பாவிற்கு PNG மற்றும் சிறிய பகிர்வுக்கு WebP ஐப் பயன்படுத்தவும். + வடிவம் ஆதரிக்கும் போது தரத்தை சரிசெய்யவும்; குறைந்த மதிப்புகள் அளவைக் குறைக்கின்றன, ஆனால் கலைப்பொருட்களைச் சேர்க்கலாம். + மாற்றப்பட்ட கோப்புகள் இலக்கு பயன்பாட்டில் சரியாகத் திறக்கப்பட்டுள்ளன என்பதைச் சரிபார்க்கும் வரை அசல்களை வைத்திருங்கள். + EXIF மற்றும் தனியுரிமையை சரிபார்க்கவும் + முக்கியமான புகைப்படங்களைப் பகிரும் முன் மெட்டாடேட்டாவைப் பார்க்கவும், திருத்தவும் அல்லது அகற்றவும் + மறைக்கப்பட்ட படத் தரவைக் கட்டுப்படுத்தவும் + EXIF ஆனது கேமரா தரவு, தேதிகள், இருப்பிடம், நோக்குநிலை மற்றும் படத்தில் தெரியாத பிற விவரங்களைக் கொண்டிருக்கலாம். பொதுப் பகிர்வு, ஆதரவு அறிக்கைகள், சந்தைப் பட்டியல்கள் அல்லது தனிப்பட்ட இடத்தை வெளிப்படுத்தும் எந்தப் புகைப்படமும் முன் சரிபார்ப்பது மதிப்பு. + இருப்பிடம் அல்லது தனிப்பட்ட சாதனத் தகவலைக் கொண்ட படங்களைப் பகிர்வதற்கு முன் EXIF ​​டூல்களைத் திறக்கவும். + முக்கியமான புலங்களை அகற்றவும் அல்லது தேதி, நோக்குநிலை, ஆசிரியர் அல்லது கேமரா தரவு போன்ற தவறான குறிச்சொற்களைத் திருத்தவும். + புதிய நகலைச் சேமித்து, தனியுரிமை முக்கியமானதாக இருக்கும் போது, ​​அசல் பிரதிக்குப் பதிலாக அந்தப் நகலைப் பகிரவும். + ஆட்டோ எக்சிஃப் சுத்தம் + தேதிகள் பாதுகாக்கப்பட வேண்டுமா என்பதை தீர்மானிக்கும் போது மெட்டாடேட்டாவை இயல்பாக அகற்றவும் + தனியுரிமையை இயல்புநிலையாக ஆக்குங்கள் + ஒவ்வொரு ஏற்றுமதிக்கும் தனியுரிமையை நினைவில் வைத்துக்கொள்வதற்குப் பதிலாக, தனியுரிமைச் சுத்திகரிப்பு தானாகவே நடக்க வேண்டும் என நீங்கள் விரும்பினால், EXIF ​​அமைப்புகள் குழு பயனுள்ளதாக இருக்கும். தேதி நேரத்தை வைத்திருத்தல் என்பது ஒரு தனி முடிவாகும், ஏனெனில் தேதிகள் வரிசைப்படுத்துவதற்கு பயனுள்ளதாக இருக்கும், ஆனால் பகிர்வதற்கு உணர்திறன். + உங்கள் ஏற்றுமதிகளில் பெரும்பாலானவை பொது அல்லது அரை-பொது பகிர்வுக்கானதாக இருக்கும் போது எப்போதும் தெளிவான EXIF ​​ஐ இயக்கவும். + ஒவ்வொரு நேர முத்திரையையும் அகற்றுவதை விட, பிடிப்பு நேரத்தைப் பாதுகாப்பது மிகவும் முக்கியமானதாக இருக்கும் போது மட்டும், Keep தேதி நேரத்தை இயக்கவும். + சில புலங்களை வைத்து மற்றவற்றை அகற்ற வேண்டிய ஒருமுறை கோப்புகளுக்கு கையேடு EXIF ​​​​எடிட்டிங் பயன்படுத்தவும். + கோப்புப் பெயர்களைக் கட்டுப்படுத்தவும் + முன்னொட்டுகள், பின்னொட்டுகள், நேர முத்திரைகள், முன்னமைவுகள், செக்சம்கள் மற்றும் வரிசை எண்களைப் பயன்படுத்தவும் + ஏற்றுமதியை எளிதாகக் கண்டறியலாம் + ஒரு நல்ல கோப்புப்பெயர் அமைப்பு தொகுப்புகளை வரிசைப்படுத்த உதவுகிறது மற்றும் தற்செயலான மேலெழுதலைத் தடுக்கிறது. ஏற்றுமதிகள் மூலக் கோப்புறைக்குச் செல்லும் போது கோப்புப்பெயர் அமைப்புகள் மிகவும் பயனுள்ளதாக இருக்கும், அங்கு ஒரே மாதிரியான பெயர்கள் விரைவாக குழப்பமடையலாம். + கோப்புப்பெயர் முன்னொட்டு, பின்னொட்டு, நேரமுத்திரை, அசல் பெயர், முன்னமைக்கப்பட்ட தகவல் அல்லது வரிசை விருப்பங்களை அமைப்புகளில் அமைக்கவும். + பக்கங்கள், பிரேம்கள் அல்லது படத்தொகுப்புகள் போன்ற ஆர்டர் முக்கியத்துவம் வாய்ந்த தொகுதிகளுக்கு வரிசை எண்களைப் பயன்படுத்தவும். + ஏற்கனவே உள்ள கோப்புகளை மாற்றுவது தற்போதைய கோப்புறைக்கு பாதுகாப்பானது என்பதை நீங்கள் உறுதியாக நம்பினால் மட்டுமே மேலெழுதலை இயக்கவும். + கோப்புகளை மேலெழுதவும் + உங்கள் பணிப்பாய்வு வேண்டுமென்றே அழிவை ஏற்படுத்தும் போது மட்டுமே பொருத்தமான பெயர்களுடன் வெளியீடுகளை மாற்றவும் + மேலெழுதும் பயன்முறையை கவனமாகப் பயன்படுத்தவும் + பெயரிடும் முரண்பாடுகள் எவ்வாறு கையாளப்படுகின்றன என்பதை மேலெழுதும் கோப்புகள் மாற்றுகிறது. ஒரே கோப்புறையில் மீண்டும் ஏற்றுமதி செய்வதற்கு இது பயனுள்ளதாக இருக்கும், ஆனால் உங்கள் கோப்புப் பெயரானது மீண்டும் அதே பெயரை உருவாக்கினால் முந்தைய முடிவுகளையும் மாற்றலாம். + பழைய உருவாக்கப்பட்ட கோப்புகளை மாற்றுவது எதிர்பார்க்கப்படும் மற்றும் மீட்டெடுக்கக்கூடிய கோப்புறைகளுக்கு மட்டும் மேலெழுதலை இயக்கவும். + அசல், கிளையன்ட் கோப்புகள், ஒருமுறை ஸ்கேன் செய்தல் அல்லது காப்புப்பிரதி இல்லாமல் எதையும் ஏற்றுமதி செய்யும் போது மேலெழுதுவதை முடக்கி வைக்கவும். + வேண்டுமென்றே கோப்புப்பெயர் வடிவத்துடன் மேலெழுதலை இணைக்கவும், அதனால் உத்தேசிக்கப்பட்ட வெளியீடுகள் மட்டுமே மாற்றப்படும். + கோப்பு பெயர் வடிவங்கள் + அசல் பெயர்கள், முன்னொட்டுகள், பின்னொட்டுகள், நேர முத்திரைகள், முன்னமைவுகள், அளவு முறை மற்றும் செக்சம்கள் ஆகியவற்றை இணைக்கவும் + வெளியீட்டை விளக்கும் பெயர்களை உருவாக்கவும் + கோப்புப்பெயர் அமைப்பு அமைப்புகள் ஏற்றுமதி செய்யப்பட்ட கோப்புகளுக்கு என்ன நடந்தது என்பதற்கான பயனுள்ள வரலாற்றாக மாற்றும். அசல் அளவு, முன்னமைவு, வடிவம் மற்றும் செயலாக்க வரிசை ஆகியவற்றை நீங்கள் வேறுபடுத்தக்கூடிய ஒரே இடம் கோப்புறை காட்சியாக இருக்கலாம் என்பதால் இது தொகுதிகளில் முக்கியமானது. + கைமுறையாக வரிசைப்படுத்துவதற்குப் படிக்கக்கூடிய பெயர்கள் தேவைப்படும்போது அசல் கோப்புப்பெயர், முன்னொட்டு, பின்னொட்டு மற்றும் நேர முத்திரையைப் பயன்படுத்தவும். + வெளியீடுகள் எவ்வாறு தயாரிக்கப்பட்டன என்பதை ஆவணப்படுத்த வேண்டும் என்றால், முன்னமைவு, அளவு முறை, கோப்பு அளவு அல்லது செக்சம் தகவலைச் சேர்க்கவும். + அசல் அல்லது பக்க வரிசையுடன் கோப்புகள் இணைக்கப்பட வேண்டிய பணிப்பாய்வுகளுக்கான சீரற்ற பெயர்களைத் தவிர்க்கவும். + கோப்புகள் எங்கு சேமிக்கப்படுகின்றன என்பதைத் தேர்ந்தெடுக்கவும் + கோப்புறைகள், அசல் கோப்புறை சேமிப்பு மற்றும் ஒரு முறை சேமிக்கும் இடங்களை அமைப்பதன் மூலம் ஏற்றுமதியை இழப்பதைத் தவிர்க்கவும் + வெளியீடுகளை கணிக்கக்கூடியதாக வைத்திருங்கள் + நீங்கள் பல கோப்புகளைச் செயலாக்கும்போது அல்லது கிளவுட் வழங்குநர்களிடமிருந்து கோப்புகளைப் பகிரும்போது நடத்தையைச் சேமிப்பது மிகவும் முக்கியமானது. அவுட்புட்கள் ஒரு அறியப்பட்ட இடத்தில் சேகரிக்கப்படுகிறதா, அசல்களுக்கு அருகில் தோன்றுகிறதா அல்லது ஒரு குறிப்பிட்ட பணிக்காக தற்காலிகமாக வேறு எங்காவது செல்ல வேண்டுமா என்பதை கோப்புறை அமைப்புகள் தீர்மானிக்கின்றன. + நீங்கள் எப்போதும் ஒரே இடத்தில் ஏற்றுமதி செய்ய விரும்பினால், இயல்புநிலை சேமிப்பு கோப்புறையை அமைக்கவும். + க்ளீனப் பணிப்பாய்வுகளுக்கு சேவ்-டு-ஒரிஜினல்-ஃபோல்டரைப் பயன்படுத்தவும், அங்கு வெளியீடு மூலக் கோப்பின் அருகில் இருக்க வேண்டும். + உங்கள் இயல்புநிலையை மாற்றாமல் ஒரு பணிக்கு வேறு இலக்கு தேவைப்படும்போது ஒரு முறை சேமிக்கும் இடத்தைப் பயன்படுத்தவும். + ஒரு முறை சேமிக்கும் கோப்புறைகள் + உங்கள் வழக்கமான இலக்கை மாற்றாமல், அடுத்த ஏற்றுமதிகளை தற்காலிக கோப்புறைக்கு அனுப்பவும் + சிறப்பு வேலைகளை தனித்தனியாக வைத்திருங்கள் + உங்கள் வழக்கமான பட கருவிப்பெட்டி கோப்புறையுடன் கலக்காத தற்காலிக திட்டங்கள், கிளையன்ட் கோப்புறைகள், சுத்தம் செய்யும் அமர்வுகள் அல்லது ஏற்றுமதிகளுக்கு ஒரு முறை சேமிக்கும் இடம் பயனுள்ளதாக இருக்கும். இது உங்கள் இயல்புநிலை அமைப்பை மீண்டும் எழுதாமல் குறுகிய கால இலக்கை வழங்குகிறது. + உங்கள் வழக்கமான ஏற்றுமதி இருப்பிடத்திற்கு வெளியே ஒரு பணியைத் தொடங்கும் முன் ஒரு முறை கோப்புறையைத் தேர்வு செய்யவும். + குறுகிய திட்டப்பணிகள், பகிரப்பட்ட கோப்புறைகள் அல்லது வெளியீடுகள் ஒன்றாகக் குழுவாக இருக்க வேண்டிய தொகுதிகளுக்கு இதைப் பயன்படுத்தவும். + வேலைக்குப் பிறகு இயல்புநிலை கோப்புறைக்குத் திரும்பவும், இதனால் பின்னர் ஏற்றுமதிகள் எதிர்பாராத விதமாக தற்காலிக இடத்தில் இறங்காது. + சமநிலை தரம் மற்றும் கோப்பு அளவு + யூகிப்பதற்குப் பதிலாக பரிமாணங்கள், வடிவம் அல்லது தரத்தை எப்போது மாற்றுவது என்பதைப் புரிந்து கொள்ளுங்கள் + வேண்டுமென்றே கோப்புகளை சுருக்கவும் + கோப்பின் அளவு படத்தின் பரிமாணங்கள், வடிவம், தரம், வெளிப்படைத்தன்மை மற்றும் உள்ளடக்க சிக்கலான தன்மையைப் பொறுத்தது. ஒரு கோப்பு இன்னும் கனமாக இருந்தால், பரிமாணங்களை மாற்றுவது பொதுவாக தரத்தை மீண்டும் மீண்டும் குறைப்பதை விட உதவுகிறது. + இலக்கு காட்டக்கூடியதை விட படம் பெரிதாக இருக்கும்போது முதலில் பரிமாணங்களை மறுஅளவிடவும். + நஷ்டமான வடிவங்களுக்கு மட்டுமே தரம் குறைவு மற்றும் ஏற்றுமதிக்குப் பிறகு உரை, விளிம்புகள், சாய்வுகள் மற்றும் முகங்களைச் சரிபார்க்கவும். + தர மாற்றங்கள் போதுமானதாக இல்லாதபோது வடிவமைப்பை மாற்றவும், எடுத்துக்காட்டாக பகிர்வதற்கான WebP அல்லது மிருதுவான வெளிப்படையான சொத்துக்களுக்கு PNG. + அனிமேஷன் வடிவங்களுடன் வேலை செய்யுங்கள் + கோப்பில் ஒரு பிட்மேப்பிற்குப் பதிலாக ஃப்ரேம்கள் இருக்கும்போது GIF, APNG, WebP மற்றும் JXL கருவிகளைப் பயன்படுத்தவும் + ஒவ்வொரு படத்தையும் அசையாமல் இருக்க வேண்டாம் + அனிமேஷன் வடிவங்களுக்கு பிரேம்கள், கால அளவு மற்றும் இணக்கத்தன்மையைப் புரிந்துகொள்ளும் கருவிகள் தேவை. + அந்த வடிவங்களுக்கான சட்ட-நிலை செயல்பாடுகள் தேவைப்படும்போது GIF அல்லது APNG கருவிகளைப் பயன்படுத்தவும். + உங்களுக்கு நவீன சுருக்க அல்லது அனிமேஷன் செய்யப்பட்ட WebP வெளியீடு தேவைப்படும்போது WebP கருவிகளைப் பயன்படுத்தவும். + பகிர்வதற்கு முன் அனிமேஷன் நேரத்தை முன்னோட்டமிடுங்கள், ஏனெனில் சில இயங்குதளங்கள் அனிமேஷன் படங்களை மாற்றுகின்றன அல்லது சமன் செய்கின்றன. + வெளியீட்டை ஒப்பிட்டு முன்னோட்டமிடவும் + ஏற்றுமதி செய்யும் முன் மாற்றங்கள், கோப்பு அளவு மற்றும் காட்சி வேறுபாடுகளை ஆய்வு செய்யவும் + பகிர்வதற்கு முன் சரிபார்க்கவும் + ஒப்பீட்டு கருவிகள் சுருக்க கலைப்பொருட்கள், தவறான வண்ணங்கள் அல்லது தேவையற்ற திருத்தங்களை முன்கூட்டியே பிடிக்க உதவும். + நீங்கள் இரண்டு பதிப்புகளை அருகருகே ஆய்வு செய்ய விரும்பும் போது ஒப்பிடு என்பதைத் திறக்கவும். + சிக்கல்களை எளிதில் தவறவிடக்கூடிய விளிம்புகள், உரை, சாய்வுகள் மற்றும் வெளிப்படையான பகுதிகளில் பெரிதாக்கவும். + வெளியீடு மிகவும் கனமாகவோ அல்லது மங்கலாகவோ இருந்தால், முந்தைய கருவிக்குத் திரும்பி, தரம் அல்லது வடிவமைப்பைச் சரிசெய்யவும். + PDF கருவிகள் மையத்தைப் பயன்படுத்தவும் + PDF கோப்புகளை ஒன்றிணைக்கவும், பிரிக்கவும், சுழற்றவும், பக்கங்களை அகற்றவும், சுருக்கவும், பாதுகாக்கவும், OCR மற்றும் வாட்டர்மார்க் செய்யவும் + PDF செயல்பாட்டைத் தேர்ந்தெடுக்கவும் + PDF ஹப் ஒரு நுழைவுப் புள்ளியின் பின்னால் உள்ள செயல்களை ஆவணப்படுத்துகிறது, எனவே நீங்கள் சரியான செயல்பாட்டைத் தேர்ந்தெடுக்கலாம். கோப்பு ஏற்கனவே PDF ஆக இருக்கும் போது அங்கு தொடங்கவும், மேலும் உங்கள் ஆதாரம் இன்னும் படங்களின் தொகுப்பாக இருக்கும்போது படங்களை PDF அல்லது ஆவண ஸ்கேனருக்குப் பயன்படுத்தவும். + PDF கருவிகளைத் திறந்து, உங்கள் இலக்குடன் பொருந்தக்கூடிய செயல்பாட்டைத் தேர்ந்தெடுக்கவும். + மூல PDF அல்லது படங்களைத் தேர்ந்தெடுத்து, பக்க வரிசை, சுழற்சி, பாதுகாப்பு அல்லது OCR விருப்பங்களை மதிப்பாய்வு செய்யவும். + உருவாக்கப்பட்ட PDF ஐ சேமித்து, பக்க எண்ணிக்கை, வரிசை மற்றும் வாசிப்புத்திறனை உறுதிப்படுத்த ஒருமுறை திறக்கவும். + படங்களை PDF ஆக மாற்றவும் + ஸ்கேன்கள், ஸ்கிரீன்ஷாட்கள், ரசீதுகள் அல்லது புகைப்படங்களை ஒரு பகிரக்கூடிய ஆவணமாக இணைக்கவும் + சுத்தமான ஆவணத்தை உருவாக்கவும் + படங்கள் செதுக்கப்படும், வரிசைப்படுத்தப்பட்டு, தொடர்ந்து அளவீடு செய்யப்படும்போது அவை சிறந்த PDF பக்கங்களாக மாறும். படப் பட்டியலை ஒரு பக்க அடுக்காகக் கையாளவும்: ஒவ்வொரு சுழற்சி, விளிம்பு மற்றும் பக்க அளவு தேர்வு ஆகியவை இறுதி ஆவணம் எவ்வளவு படிக்கக்கூடியதாக உணர்கிறது என்பதைப் பாதிக்கிறது. + பக்க விளிம்புகள், நிழல்கள் அல்லது நோக்குநிலை தவறாக இருக்கும் போது முதலில் படங்களை செதுக்கவும் அல்லது சுழற்றவும். + உத்தேசிக்கப்பட்ட பக்க வரிசையில் படங்களைத் தேர்ந்தெடுத்து, கருவி அவற்றை வழங்கினால் பக்க அளவு அல்லது விளிம்புகளைச் சரிசெய்யவும். + PDF ஐ ஏற்றுமதி செய்து, அனுப்பும் முன் அனைத்து பக்கங்களும் படிக்கக்கூடியதா என சரிபார்க்கவும். + ஆவணங்களை ஸ்கேன் செய்யவும் + காகிதப் பக்கங்களைப் படம்பிடித்து, PDF, OCR அல்லது பகிர்வதற்காக அவற்றைத் தயாரிக்கவும் + படிக்கக்கூடிய பக்கங்களைப் பிடிக்கவும் + தட்டையான பக்கங்கள், நல்ல வெளிச்சம் மற்றும் திருத்தப்பட்ட முன்னோக்கு ஆகியவற்றுடன் ஆவண ஸ்கேனிங் சிறப்பாகச் செயல்படுகிறது. OCR அல்லது PDF ஏற்றுமதிக்கு முன் ஒரு சுத்தமான ஸ்கேன் நேரத்தைச் சேமிக்கிறது, ஏனெனில் உரை அங்கீகாரம் மற்றும் சுருக்க இரண்டும் பக்கத்தின் தரத்தைப் பொறுத்தது. + போதுமான ஒளி மற்றும் குறைந்த நிழல்கள் கொண்ட ஒரு மாறுபட்ட மேற்பரப்பில் ஆவணத்தை வைக்கவும். + கண்டறியப்பட்ட மூலைகளைச் சரிசெய்யவும் அல்லது கைமுறையாக செதுக்கவும், இதனால் பக்கம் சட்டத்தை சுத்தமாக நிரப்பும். + படங்கள் அல்லது PDF ஆக ஏற்றுமதி செய்து, தேர்ந்தெடுக்கக்கூடிய உரை தேவைப்பட்டால் OCR ஐ இயக்கவும். + PDF கோப்புகளைப் பாதுகாக்கவும் அல்லது திறக்கவும் + கடவுச்சொற்களை கவனமாகப் பயன்படுத்தவும் மற்றும் முடிந்தால் திருத்தக்கூடிய மூல நகலை வைத்திருக்கவும் + PDF அணுகலைப் பாதுகாப்பாகக் கையாளவும் + பாதுகாப்புக் கருவிகள் கட்டுப்படுத்தப்பட்ட பகிர்வுக்கு பயனுள்ளதாக இருக்கும், ஆனால் கடவுச்சொற்களை இழப்பது எளிது மற்றும் மீட்டெடுப்பது கடினம். விநியோகத்திற்கான நகல்களைப் பாதுகாக்கவும், பாதுகாப்பற்ற மூலக் கோப்புகளைத் தனித்தனியாக வைத்திருக்கவும், எதையும் நீக்கும் முன் முடிவைச் சரிபார்க்கவும். + PDF இன் நகலைப் பாதுகாக்கவும், உங்கள் ஒரே ஆதார ஆவணம் அல்ல. + பாதுகாக்கப்பட்ட கோப்பைப் பகிர்வதற்கு முன் கடவுச்சொல்லை பாதுகாப்பான இடத்தில் சேமிக்கவும். + நீங்கள் திருத்த அனுமதிக்கப்படும் கோப்புகளுக்கு மட்டும் திறத்தல் என்பதைப் பயன்படுத்தவும், பின்னர் திறக்கப்பட்ட நகல் சரியாகத் திறக்கப்பட்டுள்ளதா என்பதைச் சரிபார்க்கவும். + PDF பக்கங்களை ஒழுங்கமைக்கவும் + ஒன்றிணைக்கவும், பிரிக்கவும், மறுசீரமைக்கவும், சுழற்றவும், செதுக்கவும், பக்கங்களை அகற்றவும் மற்றும் பக்க எண்களை வேண்டுமென்றே சேர்க்கவும் + அலங்காரத்திற்கு முன் ஆவண கட்டமைப்பை சரிசெய்யவும் + PDF பக்கக் கருவிகள் நீங்கள் காகித ஆவணத்தைத் தயாரிக்கும் அதே வரிசையில் பயன்படுத்த எளிதானது: பக்கங்களைச் சேகரிக்கவும், தவறானவற்றை அகற்றவும், அவற்றை வரிசைப்படுத்தவும், சரியான நோக்குநிலை, பின்னர் பக்க எண்கள் அல்லது வாட்டர்மார்க்ஸ் போன்ற இறுதி விவரங்களைச் சேர்க்கவும். + ஆவணம் இன்னும் பல கோப்புகளில் பிரிக்கப்பட்டிருக்கும் போது முதலில் ஒன்றிணைத்தல் அல்லது படங்களை PDFக்கு பயன்படுத்தவும். + பக்க எண்கள், கையொப்பங்கள் அல்லது வாட்டர்மார்க்களைச் சேர்ப்பதற்கு முன் பக்கங்களை மறுசீரமைக்கவும், சுழற்றவும், செதுக்கவும் அல்லது அகற்றவும். + ஒரு விடுபட்ட அல்லது சுழற்றப்பட்ட பக்கம் பின்னர் கவனிக்க கடினமாக இருக்கும் என்பதால், கட்டமைப்புத் திருத்தங்களுக்குப் பிறகு இறுதி PDFஐ முன்னோட்டமிடுங்கள். + PDF குறிப்புகள் + பார்வையாளர்கள் கருத்துகளையும் மதிப்பெண்களையும் வித்தியாசமாகக் காட்டும்போது சிறுகுறிப்புகளை அகற்றவும் அல்லது சமன் செய்யவும் + காணக்கூடியதைக் கட்டுப்படுத்தவும் + சிறுகுறிப்புகள் கருத்துகள், சிறப்பம்சங்கள், வரைபடங்கள், படிவக் குறிகள் அல்லது பிற கூடுதல் PDF பொருள்களாக இருக்கலாம். சில பார்வையாளர்கள் அவற்றை வித்தியாசமாகக் காட்டுகிறார்கள், எனவே அவற்றை அகற்றுவது அல்லது சமன் செய்வது பகிரப்பட்ட ஆவணங்களை மேலும் கணிக்கக்கூடியதாக மாற்றும். + பகிரப்பட்ட நகலில் கருத்துகள், சிறப்பம்சங்கள் அல்லது மதிப்பாய்வு மதிப்பெண்கள் சேர்க்கப்படாதபோது சிறுகுறிப்புகளை அகற்றவும். + கண்ணுக்குத் தெரியும் மதிப்பெண்கள் பக்கத்தில் இருக்க வேண்டும், ஆனால் இனி திருத்தக்கூடிய சிறுகுறிப்புகளைப் போல் செயல்படாது. + ஒரு அசல் நகலை வைத்திருங்கள், ஏனெனில் சிறுகுறிப்புகளை அகற்றுவது அல்லது தட்டையாக்குவது தலைகீழாக மாற்றுவது கடினம். + உரையை அங்கீகரிக்கவும் + தேர்ந்தெடுக்கக்கூடிய OCR இன்ஜின்கள் மற்றும் மொழிகளுடன் படங்களிலிருந்து உரையைப் பிரித்தெடுக்கவும் + சிறந்த OCR முடிவுகளைப் பெறுங்கள் + OCR துல்லியம் படத்தின் கூர்மை, மொழி தரவு, மாறுபாடு மற்றும் பக்க நோக்குநிலை ஆகியவற்றைப் பொறுத்தது. சிறந்த முடிவுகள் பொதுவாக முதலில் படத்தை தயாரிப்பதில் இருந்து வரும்: சத்தத்தை குறைக்கவும், நிமிர்ந்து சுழற்றவும், படிக்கக்கூடிய தன்மையை அதிகரிக்கவும், பின்னர் உரையை அடையாளம் காணவும். + படத்தை உரை பகுதிக்கு செதுக்கி, அங்கீகாரத்திற்கு முன் அதை நிமிர்ந்து சுழற்றவும். + ஆப்ஸ் கோரினால், சரியான மொழியைத் தேர்ந்தெடுத்து, மொழித் தரவைப் பதிவிறக்கவும். + அங்கீகரிக்கப்பட்ட உரையை நகலெடுக்கவும் அல்லது பகிரவும், பின்னர் பெயர்கள், எண்கள் மற்றும் நிறுத்தற்குறிகளை கைமுறையாக சரிபார்க்கவும். + OCR மொழித் தரவைத் தேர்ந்தெடுக்கவும் + ஸ்கிரிப்டுகள், மொழிகள் மற்றும் பதிவிறக்கம் செய்யப்பட்ட OCR மாதிரிகளைப் பொருத்துவதன் மூலம் அங்கீகாரத்தை மேம்படுத்தவும் + உரையுடன் OCR ஐ பொருத்தவும் + தவறான OCR மொழி நல்ல புகைப்படங்கள் மோசமான அங்கீகாரம் போல் தோற்றமளிக்கும். ஸ்கிரிப்டுகள், நிறுத்தற்குறிகள், எண்கள் மற்றும் கலப்பு ஆவணங்களுக்கு மொழித் தரவு முக்கியமானது, எனவே ஃபோன் UI இன் மொழியைக் காட்டிலும் படத்தில் தோன்றும் மொழியைத் தேர்ந்தெடுக்கவும். + தொலைபேசி மொழி மட்டுமல்ல, படத்தில் தோன்றும் மொழி அல்லது ஸ்கிரிப்டைத் தேர்வு செய்யவும். + ஆஃப்லைனில் பணிபுரியும் முன் அல்லது தொகுப்பைச் செயலாக்கும் முன் விடுபட்ட தரவைப் பதிவிறக்கவும். + கலப்பு மொழி ஆவணங்களுக்கு, மிக முக்கியமான மொழியை முதலில் இயக்கவும் மற்றும் பெயர்கள் மற்றும் எண்களை கைமுறையாக சரிபார்க்கவும். + தேடக்கூடிய PDFகள் + OCR உரையை மீண்டும் கோப்புகளில் எழுதுங்கள், அதனால் ஸ்கேன் செய்யப்பட்ட பக்கங்களைத் தேடலாம் மற்றும் நகலெடுக்கலாம் + ஸ்கேன்களை தேடக்கூடிய ஆவணங்களாக மாற்றவும் + கிளிப்போர்டுக்கு உரையை நகலெடுப்பதை விட OCR பலவற்றைச் செய்ய முடியும். நீங்கள் அங்கீகரிக்கப்பட்ட உரையை கோப்பு அல்லது தேடக்கூடிய PDF இல் எழுதும்போது, ​​பக்கத்தின் படம் தெரியும் அதே சமயம் உரையை தேடுவது, தேர்ந்தெடுப்பது, அட்டவணைப்படுத்துவது அல்லது காப்பகப்படுத்துவது எளிதாக இருக்கும். + ஸ்கேன் செய்யப்பட்ட ஆவணங்களுக்கு தேடக்கூடிய PDF வெளியீட்டைப் பயன்படுத்தவும், அவை அசல் பக்கங்களைப் போலவே இருக்கும். + பிரித்தெடுக்கப்பட்ட உரை மட்டுமே உங்களுக்குத் தேவைப்படும்போது மற்றும் பக்கப் படத்தைப் பற்றி கவலைப்படாமல் எழுதுவதற்கு-கோப்புக்குப் பயன்படுத்தவும். + முக்கியமான எண்கள், பெயர்கள் மற்றும் அட்டவணைகளை கைமுறையாகச் சரிபார்க்கவும், ஏனெனில் OCR உரை தேடக்கூடியதாக இருந்தாலும் இன்னும் அபூரணமாக இருக்கலாம். + QR குறியீடுகளை ஸ்கேன் செய்யவும் + கேமரா உள்ளீடு அல்லது சேமிக்கப்பட்ட படங்கள் கிடைக்கும்போது QR உள்ளடக்கத்தைப் படிக்கவும் + குறியீடுகளைப் பாதுகாப்பாகப் படிக்கவும் + QR குறியீடுகளில் இணைப்புகள், Wi-Fi தரவு, தொடர்புகள், உரை அல்லது பிற பேலோடுகள் இருக்கலாம். + ஸ்கேன் QR குறியீட்டைத் திறந்து, குறியீட்டை கூர்மையாகவும், தட்டையாகவும், முழுமையாகவும் சட்டகத்தின் உள்ளே வைக்கவும். + இணைப்புகளைத் திறப்பதற்கு முன் அல்லது முக்கியத் தரவை நகலெடுக்கும் முன் டிகோட் செய்யப்பட்ட உள்ளடக்கத்தை மதிப்பாய்வு செய்யவும். + ஸ்கேனிங் தோல்வியுற்றால், விளக்குகளை மேம்படுத்தவும், குறியீட்டை செதுக்கவும் அல்லது அதிக தெளிவுத்திறன் கொண்ட மூலப் படத்தை முயற்சிக்கவும். + Base64 மற்றும் செக்சம்களைப் பயன்படுத்தவும் + படங்களை உரையாக குறியாக்கம் செய்யவும், உரையை மீண்டும் படங்களுக்கு குறியாக்கம் செய்யவும் மற்றும் கோப்பு ஒருமைப்பாட்டை சரிபார்க்கவும் + கோப்புகளுக்கும் உரைக்கும் இடையில் நகர்த்தவும் + சிறிய பட பேலோடுகளுக்கு Base64 பயனுள்ளதாக இருக்கும், அதே சமயம் செக்சம்கள் கோப்புகள் மாறவில்லை என்பதை உறுதிப்படுத்த உதவுகிறது. இந்த கருவிகள் தொழில்நுட்ப பணிப்பாய்வுகள், ஆதரவு அறிக்கைகள், கோப்பு பரிமாற்றங்கள் மற்றும் பைனரி கோப்புகள் தற்காலிகமாக உரையாக மாற வேண்டிய இடங்களில் மிகவும் உதவியாக இருக்கும். + பைனரி கோப்பிற்கு பதிலாக ஒரு பணிப்பாய்வுக்கு உரை தேவைப்படும்போது சிறிய படத்தை குறியாக்க Base64 கருவிகளைப் பயன்படுத்தவும். + நம்பகமான ஆதாரங்களில் இருந்து Base64 ஐ டிகோட் செய்து, சேமிப்பதற்கு முன் மாதிரிக்காட்சியை சரிபார்க்கவும். + நீங்கள் ஏற்றுமதி செய்யப்பட்ட கோப்புகளை ஒப்பிடும்போது அல்லது பதிவேற்றம்/பதிவிறக்கத்தை உறுதிசெய்யும் போது செக்சம் கருவிகளைப் பயன்படுத்தவும். + தரவைத் தொகுக்கவும் அல்லது பாதுகாக்கவும் + கோப்புகளைத் தொகுக்க அல்லது உரைத் தரவை மாற்ற ஜிப் மற்றும் சைபர் கருவிகளைப் பயன்படுத்தவும் + தொடர்புடைய கோப்புகளை ஒன்றாக வைத்திருங்கள் + பல வெளியீடுகள் ஒரு கோப்பாக பயணிக்கும்போது பேக்கேஜிங் கருவிகள் உதவியாக இருக்கும். நீங்கள் முழுமையான முடிவுத் தொகுப்பை அனுப்ப வேண்டியிருக்கும் போது, ​​உருவாக்கப்பட்ட படங்கள், PDFகள், பதிவுகள் மற்றும் மெட்டாடேட்டாவை ஒன்றாகச் சேர்ப்பதற்கும் அவை சிறந்தவை. + ஏற்றுமதி செய்யப்பட்ட பல படங்கள் அல்லது ஆவணங்களை ஒன்றாக அனுப்ப வேண்டியிருக்கும் போது ZIP ஐப் பயன்படுத்தவும். + தேர்ந்தெடுக்கப்பட்ட முறையைப் புரிந்துகொண்டு, தேவையான விசைகளைப் பாதுகாப்பாகச் சேமிக்கும் போது மட்டுமே சைஃபர் கருவிகளைப் பயன்படுத்தவும். + மூலக் கோப்புகளை நீக்கும் முன் உருவாக்கப்பட்ட காப்பகத்தை அல்லது உருமாறிய உரையை சோதிக்கவும். + பெயர்களில் செக்சம்கள் + தனித்துவமும் ஒருமைப்பாடும் வாசிப்புத்திறனை விட முக்கியமானதாக இருக்கும்போது செக்சம் அடிப்படையிலான கோப்புப் பெயர்களைப் பயன்படுத்தவும் + உள்ளடக்கத்தின் அடிப்படையில் கோப்புகளை பெயரிடவும் + செக்சம் கோப்புப் பெயர்கள் ஏற்றுமதியில் நகல் காணக்கூடிய பெயர்கள் இருக்கலாம் அல்லது இரண்டு கோப்புகள் ஒரே மாதிரியானவை என்பதை நீங்கள் நிரூபிக்க வேண்டியிருக்கும் போது பயனுள்ளதாக இருக்கும். அவை கைமுறையாக உலாவுவதற்கு குறைவான நட்புடன் உள்ளன, எனவே தொழில்நுட்ப காப்பகங்கள் மற்றும் சரிபார்ப்பு பணிப்பாய்வுகளுக்கு அவற்றைப் பயன்படுத்தவும். + மனிதர்கள் படிக்கக்கூடிய தலைப்பை விட உள்ளடக்க அடையாளம் முக்கியமானதாக இருக்கும்போது செக்ஸமை கோப்புப் பெயராக இயக்கவும். + காப்பகங்கள், துப்பறிதல், மீண்டும் செய்யக்கூடிய ஏற்றுமதிகள் அல்லது பதிவேற்றப்பட்டு மீண்டும் பதிவிறக்கம் செய்யக்கூடிய கோப்புகளுக்கு இதைப் பயன்படுத்தவும். + கோப்பு எதைக் குறிக்கிறது என்பதை மக்கள் இன்னும் புரிந்துகொள்ள வேண்டியிருக்கும் போது, ​​செக்சம் பெயர்களை கோப்புறைகள் அல்லது மெட்டாடேட்டாவுடன் இணைக்கவும். + படங்களிலிருந்து வண்ணங்களைத் தேர்ந்தெடுக்கவும் + மாதிரி பிக்சல்கள், வண்ணக் குறியீடுகளை நகலெடுக்கவும் மற்றும் தட்டுகள் அல்லது வடிவமைப்புகளில் வண்ணங்களை மீண்டும் பயன்படுத்தவும் + சரியான நிறத்தின் மாதிரி + வடிவமைப்பைப் பொருத்துவதற்கு, பிராண்ட் வண்ணங்களைப் பிரித்தெடுப்பதற்கு அல்லது பட மதிப்புகளைச் சரிபார்க்க வண்ணத் தேர்வு பயனுள்ளதாக இருக்கும். பெரிதாக்குவது முக்கியமானது, ஏனெனில் ஆன்டிலியாசிங், நிழல்கள் மற்றும் சுருக்கம் ஆகியவை அண்டை பிக்சல்களை நீங்கள் எதிர்பார்க்கும் நிறத்தில் இருந்து சற்று வித்தியாசப்படுத்தலாம். + படத்திலிருந்து வண்ணத்தைத் தேர்ந்தெடுத்து, உங்களுக்குத் தேவையான வண்ணத்துடன் ஒரு மூலப் படத்தைத் தேர்ந்தெடுக்கவும். + சிறிய விவரங்களை மாதிரியாக்குவதற்கு முன் பெரிதாக்கவும், இதன் மூலம் அருகிலுள்ள பிக்சல்கள் முடிவைப் பாதிக்காது. + HEX, RGB அல்லது HSV போன்ற உங்கள் இலக்குக்குத் தேவையான வடிவமைப்பில் வண்ணத்தை நகலெடுக்கவும். + தட்டுகளை உருவாக்கி, வண்ண நூலகத்தைப் பயன்படுத்தவும் + தட்டுகளைப் பிரித்தெடுக்கவும், சேமித்த வண்ணங்களை உலாவவும், சீரான காட்சி அமைப்புகளை வைத்திருக்கவும் + மீண்டும் பயன்படுத்தக்கூடிய தட்டு ஒன்றை உருவாக்கவும் + ஏற்றுமதி செய்யப்பட்ட கிராபிக்ஸ், வாட்டர்மார்க்ஸ் மற்றும் வரைபடங்களை பார்வைக்கு சீராக வைத்திருக்க தட்டுகள் உதவுகின்றன. நீங்கள் ஸ்கிரீன்ஷாட்களை மீண்டும் மீண்டும் குறிப்பிடும்போது, ​​பிராண்ட் சொத்துகளைத் தயாரிக்கும்போது அல்லது பல கருவிகளில் ஒரே வண்ணங்கள் தேவைப்படும்போது அவை மிகவும் பயனுள்ளதாக இருக்கும். + ஒரு படத்திலிருந்து ஒரு தட்டு உருவாக்கவும் அல்லது மதிப்புகளை நீங்கள் ஏற்கனவே அறிந்திருக்கும் போது கைமுறையாக வண்ணங்களைச் சேர்க்கவும். + முக்கியமான வண்ணங்களுக்குப் பெயரிடவும் அல்லது ஒழுங்கமைக்கவும், பின்னர் அவற்றை மீண்டும் கண்டறியலாம். + வரைதல், வாட்டர்மார்க், சாய்வு, SVG அல்லது வெளிப்புற வடிவமைப்பு கருவிகளில் நகலெடுக்கப்பட்ட மதிப்புகளைப் பயன்படுத்தவும். + சாய்வுகளை உருவாக்கவும் + பின்னணிகள், ப்ளாஸ்ஹோல்டர்கள் மற்றும் காட்சிப் பரிசோதனைகளுக்கான சாய்வுப் படங்களை உருவாக்கவும் + வண்ண மாற்றங்களைக் கட்டுப்படுத்தவும் + ஏற்கனவே உள்ள படத்தைத் திருத்துவதற்குப் பதிலாக உருவாக்கப்பட்ட படம் தேவைப்படும்போது சாய்வு கருவிகள் பயனுள்ளதாக இருக்கும். அவை சுத்தமான பின்னணிகள், ப்ளாஸ்ஹோல்டர்கள், வால்பேப்பர்கள், முகமூடிகள் அல்லது வெளிப்புற வடிவமைப்புக் கருவிகளுக்கான எளிய சொத்துகளாக மாறலாம். + சாய்வு வகையைத் தேர்ந்தெடுத்து, முக்கியமான வண்ணங்களை முதலில் அமைக்கவும். + இலக்கு பயன்பாட்டு வழக்குக்கான திசை, நிறுத்தங்கள், மென்மை மற்றும் வெளியீட்டு அளவை சரிசெய்யவும். + இலக்குடன் பொருந்தக்கூடிய வடிவத்தில் ஏற்றுமதி செய்யுங்கள், பொதுவாக சுத்தமான உருவாக்கப்படும் கிராபிக்ஸ் PNG. + வண்ண அணுகல் + UI படிக்க கடினமாக இருக்கும்போது மாறும் வண்ணங்கள், மாறுபாடு மற்றும் வண்ண-குருட்டு விருப்பங்களைப் பயன்படுத்தவும் + வண்ணங்களைப் பயன்படுத்துவதை எளிதாக்குங்கள் + வண்ண அமைப்புகள் ஆறுதல், வாசிப்புத்திறன் மற்றும் எவ்வளவு விரைவாக நீங்கள் கட்டுப்பாடுகளை ஸ்கேன் செய்யலாம். கருவி சில்லுகள், ஸ்லைடர்கள் அல்லது தேர்ந்தெடுக்கப்பட்ட நிலைகளை வேறுபடுத்துவது கடினமாக இருந்தால், கருப் பயன்முறையை மாற்றுவதை விட தீம் மற்றும் அணுகல்தன்மை விருப்பங்கள் மிகவும் பயனுள்ளதாக இருக்கும். + ஆப்ஸ் தற்போதைய வால்பேப்பர் பேலட்டைப் பின்பற்ற வேண்டும் என நீங்கள் விரும்பினால், டைனமிக் வண்ணங்களை முயற்சிக்கவும். + பொத்தான்கள் மற்றும் மேற்பரப்புகள் போதுமான அளவு தனித்து நிற்காதபோது மாறுபாட்டை அதிகரிக்கவும் அல்லது திட்டத்தை மாற்றவும். + சில நிலைகள் அல்லது உச்சரிப்புகள் வேறுபடுத்துவது கடினமாக இருந்தால், வண்ண-குருட்டுத் திட்ட விருப்பங்களைப் பயன்படுத்தவும். + ஆல்பா பின்னணி + முன்னோட்டத்தைக் கட்டுப்படுத்தவும் அல்லது வெளிப்படையான PNG, WebP மற்றும் ஒத்த வெளியீடுகளில் பயன்படுத்தப்படும் வண்ணத்தை நிரப்பவும் + வெளிப்படையான முன்னோட்டங்களைப் புரிந்து கொள்ளுங்கள் + ஆல்பா வடிவங்களுக்கான பின்னணி வண்ணம், வெளிப்படையான படங்களை ஆய்வு செய்வதை எளிதாக்கும், ஆனால் நீங்கள் முன்னோட்டம்/நிரப்பு நடத்தையை மாற்றுகிறீர்களா அல்லது இறுதி முடிவைத் தட்டையாக்குகிறீர்களா என்பதைத் தெரிந்துகொள்வது அவசியம். ஸ்டிக்கர்கள், லோகோக்கள் மற்றும் பின்புலத்தில் அகற்றப்பட்ட படங்களுக்கு இது முக்கியமானது. + வெளிப்படையான பிக்சல்கள் ஏற்றுமதியைத் தக்கவைக்க வேண்டும் என்றால், PNG அல்லது WebP போன்ற ஆல்பா-ஆதரவு வடிவமைப்பை முதலில் பயன்படுத்தவும். + பயன்பாட்டின் மேற்பரப்பில் வெளிப்படையான விளிம்புகள் கடினமாக இருக்கும்போது பின்னணி வண்ண அமைப்புகளைச் சரிசெய்யவும். + ஒரு சிறிய சோதனையை ஏற்றுமதி செய்து, வெளிப்படைத்தன்மை அல்லது தேர்ந்தெடுக்கப்பட்ட நிரப்புதல் சேமிக்கப்பட்டதா என்பதை உறுதிப்படுத்த மற்றொரு பயன்பாட்டில் மீண்டும் திறக்கவும். + AI மாதிரி தேர்வு + முதலில் பெரியதைத் தேர்ந்தெடுப்பதற்குப் பதிலாக படத்தின் வகை மற்றும் குறைபாட்டின் அடிப்படையில் ஒரு மாதிரியைத் தேர்ந்தெடுக்கவும் + மாதிரியை சேதத்துடன் பொருத்தவும் + AI கருவிகள் வெவ்வேறு ONNX/ORT மாடல்களைப் பதிவிறக்கம் செய்யலாம் அல்லது இறக்குமதி செய்யலாம், மேலும் ஒவ்வொரு மாடலும் ஒரு குறிப்பிட்ட வகையான சிக்கலுக்குப் பயிற்சியளிக்கப்படுகிறது. JPEG தொகுதிகள், அனிம் லைன் க்ளீனப், டெனோயிசிங், பின்னணி நீக்கம், வண்ணமயமாக்கல் அல்லது மேம்பாடு ஆகியவற்றுக்கான மாதிரியானது தவறான பட வகைகளில் பயன்படுத்தப்படும்போது மோசமான முடிவுகளைத் தரக்கூடும். + பதிவிறக்குவதற்கு முன் மாதிரி விளக்கத்தைப் படிக்கவும்; சுருக்கம், சத்தம், ஹால்ஃபோன், மங்கல் அல்லது பின்னணி அகற்றுதல் போன்ற உங்கள் உண்மையான பிரச்சனைக்கு பெயரிடும் மாதிரியைத் தேர்வுசெய்யவும். + புதிய பட வகையைச் சோதிக்கும் போது சிறிய அல்லது அதிக குறிப்பிட்ட மாதிரியுடன் தொடங்கவும், அதன் முடிவு போதுமானதாக இல்லாவிட்டால் மட்டுமே கனமான மாடல்களுடன் ஒப்பிடவும். + நீங்கள் உண்மையில் பயன்படுத்தும் மாடல்களை மட்டும் வைத்திருங்கள், ஏனெனில் பதிவிறக்கம் செய்யப்பட்ட மற்றும் இறக்குமதி செய்யப்பட்ட மாடல்கள் குறிப்பிடத்தக்க சேமிப்பிடத்தை எடுத்துக் கொள்ளலாம். + மாதிரி குடும்பங்களைப் புரிந்து கொள்ளுங்கள் + மாடல் பெயர்கள் பெரும்பாலும் அவற்றின் இலக்கை சுட்டிக்காட்டுகின்றன: RealESRGAN-பாணி மாதிரிகள் உயர்நிலை, SCUNet மற்றும் FBCNN மாதிரிகள் சத்தம் அல்லது சுருக்கத்தை சுத்தம் செய்கின்றன, பின்னணி மாதிரிகள் முகமூடிகளை உருவாக்குகின்றன, மேலும் வண்ணமயமாக்கிகள் கிரேஸ்கேல் உள்ளீட்டிற்கு வண்ணத்தை சேர்க்கின்றன. இறக்குமதி செய்யப்பட்ட தனிப்பயன் மாதிரிகள் சக்திவாய்ந்ததாக இருக்கலாம், ஆனால் அவை எதிர்பார்க்கப்படும் உள்ளீடு/வெளியீட்டு வடிவத்துடன் பொருந்த வேண்டும் மற்றும் ஆதரிக்கப்படும் ONNX அல்லது ORT வடிவமைப்பைப் பயன்படுத்த வேண்டும். + உங்களுக்கு அதிக பிக்சல்கள் தேவைப்படும்போது அப்ஸ்கேலர்களையும், படம் தானியமாக இருக்கும்போது டெனாய்சர்களையும், கம்ப்ரஷன் பிளாக்ஸ் அல்லது பேண்டிங் முக்கிய பிரச்சினையாக இருக்கும்போது ஆர்ட்டிஃபாக்ட் ரிமூவர்களையும் பயன்படுத்தவும். + பொருள் பிரிப்பிற்கு மட்டுமே பின்னணி மாதிரிகளைப் பயன்படுத்தவும்; அவை பொதுவான பொருள் அகற்றுதல் அல்லது படத்தை மேம்படுத்துதல் போன்றவை அல்ல. + நீங்கள் நம்பும் மூலங்களிலிருந்து மட்டும் தனிப்பயன் மாதிரிகளை இறக்குமதி செய்து, முக்கியமான கோப்புகளைப் பயன்படுத்தும் முன் அவற்றை சிறிய படத்தில் சோதிக்கவும். + AI அளவுருக்கள் + தரம், வேகம் மற்றும் நினைவகத்தை சமநிலைப்படுத்த துண்டின் அளவு, ஒன்றுடன் ஒன்று மற்றும் இணையான வேலையாட்களை டியூன் செய்யவும் + நிலைத்தன்மையுடன் சமநிலை வேகம் + மாதிரியால் செயலாக்கப்படுவதற்கு முன், துண்டு அளவு எவ்வளவு பெரிய படத் துண்டுகளைக் கட்டுப்படுத்துகிறது. எல்லைகளை மறைக்க அண்டை பகுதிகளை ஒன்றுடன் ஒன்று இணைக்கிறது. இணையான பணியாளர்கள் செயலாக்கத்தை விரைவுபடுத்த முடியும், ஆனால் ஒவ்வொரு பணியாளருக்கும் நினைவகம் தேவை, எனவே அதிக மதிப்புகள் குறைந்த ரேம் கொண்ட தொலைபேசிகளில் பெரிய படங்களை நிலையற்றதாக மாற்றும். + உங்கள் சாதனம் மாதிரியை நம்பகத்தன்மையுடன் கையாளும் போது மற்றும் படம் பெரிதாக இல்லாதபோது மென்மையான சூழலுக்கு பெரிய துண்டுகளைப் பயன்படுத்தவும். + துண்டின் பார்டர்கள் தெரியும் போது ஒன்றுடன் ஒன்று அதிகரிக்கும், ஆனால் அதிக ஒன்றுடன் ஒன்று மீண்டும் மீண்டும் வேலை செய்யும் என்பதை நினைவில் கொள்ளுங்கள். + ஒரு நிலையான சோதனைக்குப் பிறகுதான் இணையான தொழிலாளர்களை உயர்த்தவும்; செயலாக்கம் குறைந்து, சாதனத்தை வெப்பப்படுத்தினால் அல்லது செயலிழந்தால், முதலில் தொழிலாளர்களைக் குறைக்கவும். + துண்டிக்கப்பட்ட கலைப்பொருட்களைத் தவிர்க்கவும் + ஒரே நேரத்தில் வசதியாகக் கையாளக்கூடிய மாடலை விடப் பெரிய படங்களைச் செயலாக்க பயன்பாட்டை Chunking அனுமதிக்கிறது. பரிமாற்றம் என்னவென்றால், ஒவ்வொரு ஓடுகளும் குறைவான சுற்றுப்புற சூழலைக் கொண்டிருக்கின்றன, எனவே குறைந்த ஒன்றுடன் ஒன்று அல்லது மிகவும் சிறிய துண்டுகள் காணக்கூடிய எல்லைகள், அமைப்பு மாற்றங்கள் அல்லது அண்டை பகுதிகளுக்கு இடையே சீரற்ற விவரங்களை உருவாக்கலாம். + செவ்வக பார்டர்கள், மீண்டும் மீண்டும் அமைப்பு மாற்றங்கள் அல்லது செயலாக்கப்பட்ட பகுதிகளுக்கு இடையே விவரம் தாண்டுதல் போன்றவற்றை நீங்கள் கண்டால், மேல்படிப்பை அதிகரிக்கவும். + வெளியீட்டை உருவாக்கும் முன் செயல்முறை தோல்வியுற்றால், குறிப்பாக பெரிய படங்கள் அல்லது கனமான மாடல்களில் துண்டின் அளவைக் குறைக்கவும். + முழுப் படத்தைச் செயலாக்கும் முன் சிறிய பயிர்ச் சோதனையைச் சேமிக்கவும், இதன் மூலம் நீங்கள் அளவுருக்களை விரைவாக ஒப்பிடலாம். + AI நிலைத்தன்மை + AI செயலாக்கம் செயலிழக்கும்போது அல்லது உறையும்போது குறைந்த துகள் அளவு, வேலையாட்கள் மற்றும் மூலத் தீர்மானம் + கனரக AI வேலைகளில் இருந்து மீண்டு வரவும் + AI செயலாக்கம் என்பது பயன்பாட்டில் உள்ள மிகப்பெரிய பணிப்பாய்வுகளில் ஒன்றாகும். செயலிழப்புகள், செயலிழந்த அமர்வுகள், செயலிழப்புகள் அல்லது திடீர் ஆப்ஸை மறுதொடக்கம் செய்தல் என்பது மாதிரி, மூலத் தெளிவுத்திறன், துண்டின் அளவு அல்லது இணையான பணியாளர் எண்ணிக்கை ஆகியவை தற்போதைய சாதன நிலைக்கு மிகவும் தேவைப்படுகின்றன. + ஆப்ஸ் செயலிழந்தால் அல்லது மாதிரி அமர்வைத் திறக்கத் தவறினால், இணையான வேலையாட்கள் மற்றும் துண்டின் அளவைக் குறைத்து, அதே படத்தை மீண்டும் முயற்சிக்கவும். + இலக்கிற்கு அசல் தெளிவுத்திறன் தேவைப்படாதபோது, ​​AI செயலாக்கத்திற்கு முன் மிகப் பெரிய படங்களை மறுஅளவிடவும். + மற்ற கனமான பயன்பாடுகளை மூடவும், சிறிய மாடலைப் பயன்படுத்தவும் அல்லது நினைவக அழுத்தம் திரும்பும் போது ஒரே நேரத்தில் குறைவான கோப்புகளை செயலாக்கவும். + மாதிரி தோல்விகளைக் கண்டறியவும் + ஒரு தோல்வியுற்ற AI ரன் எப்போதும் படம் மோசமாக உள்ளது என்று அர்த்தம் இல்லை. மாதிரி கோப்பு முழுமையடையாமல் இருக்கலாம், ஆதரிக்கப்படாமல் இருக்கலாம், நினைவகத்திற்கு மிகவும் பெரியதாக இருக்கலாம் அல்லது செயல்பாட்டுடன் பொருந்தாமல் இருக்கலாம். பயன்பாடு தோல்வியுற்ற அமர்வைப் புகாரளிக்கும் போது, ​​முதலில் மாதிரி மற்றும் அளவுருக்களை எளிதாக்குவதற்கான சமிக்ஞையாகக் கருதுங்கள். + பதிவிறக்கம் செய்யப்பட்ட உடனேயே மாடல் தோல்வியுற்றாலோ அல்லது கோப்பு சிதைந்திருப்பது போல் செயல்பட்டாலோ அதை நீக்கிவிட்டு மீண்டும் பதிவிறக்கவும். + இறக்குமதி செய்யப்பட்ட தனிப்பயன் மாதிரியைக் குறை கூறுவதற்கு முன், உள்ளமைக்கப்பட்ட தரவிறக்கம் செய்யக்கூடிய மாதிரியை முயற்சிக்கவும், ஏனெனில் இறக்குமதி செய்யப்பட்ட மாதிரிகள் பொருந்தாத உள்ளீடுகளைக் கொண்டிருக்கலாம். + செயலிழப்பு அல்லது அமர்வுப் பிழையைப் புகாரளிக்க வேண்டும் என்றால், தோல்வியை மீண்டும் உருவாக்கிய பிறகு பயன்பாட்டுப் பதிவுகளைப் பயன்படுத்தவும். + ஷேடர் ஸ்டுடியோவில் பரிசோதனை + மேம்பட்ட பட செயலாக்கத்திற்கும் தனிப்பயன் தோற்றத்திற்கும் GPU ஷேடர் விளைவுகளைப் பயன்படுத்தவும் + ஷேடர்களுடன் கவனமாக வேலை செய்யுங்கள் + ஷேடர் ஸ்டுடியோ சக்தி வாய்ந்தது, ஆனால் ஷேடர் குறியீடு மற்றும் அளவுருக்களுக்கு சிறிய, சோதிக்கக்கூடிய மாற்றங்கள் தேவை. இதை ஒரு சோதனைக் கருவியாகக் கருதுங்கள்: வேலை செய்யும் அடிப்படையை வைத்திருங்கள், ஒரு நேரத்தில் ஒன்றை மாற்றவும் மற்றும் முன்னமைவை நம்புவதற்கு முன் வெவ்வேறு பட அளவுகளுடன் சோதிக்கவும். + அளவுருக்களைச் சேர்ப்பதற்கு முன், தெரிந்த வேலை செய்யும் ஷேடரில் இருந்து தொடங்கவும் அல்லது எளிய விளைவு. + ஒரு நேரத்தில் ஒரு அளவுரு அல்லது குறியீடு தொகுதியை மாற்றவும் மற்றும் பிழைகளுக்கான மாதிரிக்காட்சியை சரிபார்க்கவும். + சில வெவ்வேறு பட அளவுகளில் சோதனை செய்த பின்னரே முன்னமைவுகளை ஏற்றுமதி செய்யவும். + SVG அல்லது ASCII கலையை உருவாக்கவும் + கிரியேட்டிவ் அவுட்புட்டுக்காக திசையன் போன்ற சொத்துக்கள் அல்லது உரை அடிப்படையிலான படங்களை உருவாக்கவும் + படைப்பு வெளியீட்டு வகையைத் தேர்ந்தெடுக்கவும் + SVG மற்றும் ASCII கருவிகள் வெவ்வேறு இலக்குகளுக்கு சேவை செய்கின்றன: அளவிடக்கூடிய கிராபிக்ஸ் அல்லது உரை-பாணி ரெண்டரிங். இரண்டுமே ஆக்கப்பூர்வமான மாற்றங்களாகும், எனவே ஒரு சிறிய சிறுபடத்திலிருந்து முடிவை மதிப்பிடுவதை விட இறுதி அளவில் முன்னோட்டமிடுவது மிகவும் முக்கியமானது. + SVG Makerஐப் பயன்படுத்தவும் + ஒரு படத்தின் பகட்டான உரை பிரதிநிதித்துவத்தை நீங்கள் விரும்பினால் ASCII கலையைப் பயன்படுத்தவும். + உருவாக்கப்படும் வெளியீட்டில் சிறிய விவரங்கள் மறைந்துவிடும் என்பதால் இறுதி அளவில் முன்னோட்டம். + இரைச்சல் அமைப்புகளை உருவாக்கவும் + பின்புலங்கள், முகமூடிகள், மேலடுக்குகள் அல்லது சோதனைகளுக்கான நடைமுறை அமைப்புகளை உருவாக்கவும் + டியூன் செயல்முறை வெளியீடு + சத்தம் உருவாக்கம் அளவுருக்களிலிருந்து புதிய படங்களை உருவாக்குகிறது, எனவே சிறிய மாற்றங்கள் மிகவும் வித்தியாசமான முடிவுகளைத் தரும். இது இழைமங்கள், முகமூடிகள், மேலடுக்குகள், ப்ளாஸ்ஹோல்டர்கள் அல்லது விரிவான வடிவங்களுடன் சுருக்கத்தை சோதிக்க பயனுள்ளதாக இருக்கும். + சிறந்த விவரங்களைச் சரிசெய்வதற்கு முன் இரைச்சல் வகை மற்றும் வெளியீட்டு அளவைத் தேர்வு செய்யவும். + இலக்கு பயன்பாட்டிற்கு பொருந்தும் வரை அளவு, தீவிரம், வண்ணங்கள் அல்லது விதைகளை சரிசெய்யவும். + பயனுள்ள முடிவுகளைச் சேமித்து, பின்னர் நீங்கள் அமைப்பை மீண்டும் உருவாக்க வேண்டும் என்றால் அளவுருக்களை எழுதுங்கள். + மார்க்அப் திட்டங்கள் + பயன்பாட்டை மூடிய பிறகு சிறுகுறிப்புகள் திருத்தக்கூடியதாக இருக்கும் போது திட்டக் கோப்புகளைப் பயன்படுத்தவும் + அடுக்கு திருத்தங்களை மீண்டும் பயன்படுத்தக்கூடியதாக வைத்திருங்கள் + ஸ்கிரீன்ஷாட், வரைபடம் அல்லது அறிவுறுத்தல் படத்திற்கு பின்னர் மாற்றங்கள் தேவைப்படும்போது மார்க்அப் திட்டக் கோப்புகள் பயனுள்ளதாக இருக்கும். ஏற்றுமதி செய்யப்பட்ட PNG/JPEG கோப்புகள் இறுதிப் படங்களாகும், அதே சமயம் திட்டக் கோப்புகள் எடிட் செய்யக்கூடிய லேயர்களையும், எடிட்டிங் நிலையையும் எதிர்கால மாற்றங்களுக்காகப் பாதுகாக்கின்றன. + குறிப்புகள், அழைப்புகள் அல்லது தளவமைப்பு கூறுகள் திருத்தக்கூடியதாக இருக்கும் போது மார்க்அப் லேயர்களைப் பயன்படுத்தவும். + கூடுதல் திருத்தங்களை நீங்கள் எதிர்பார்த்தால், இறுதிப் படத்தை ஏற்றுமதி செய்வதற்கு முன் திட்டக் கோப்பைச் சேமிக்கவும் அல்லது மீண்டும் திறக்கவும். + நீங்கள் தட்டையான இறுதிப் பதிப்பைப் பகிரத் தயாராக இருக்கும்போது மட்டுமே இயல்பான படத்தை ஏற்றுமதி செய்யவும். + கோப்புகளை என்க்ரிப்ட் செய்யவும் + எந்த மூல நகலையும் நீக்கும் முன், கடவுச்சொல் மூலம் கோப்புகளைப் பாதுகாத்து, மறைகுறியாக்கத்தை சோதிக்கவும் + மறைகுறியாக்கப்பட்ட வெளியீடுகளை கவனமாகக் கையாளவும் + மறைக்குறியீடு கருவிகள் கடவுச்சொல் அடிப்படையிலான குறியாக்கத்துடன் கோப்புகளைப் பாதுகாக்க முடியும், ஆனால் அவை விசையை நினைவில் வைப்பதற்கு அல்லது காப்புப்பிரதிகளை வைத்திருப்பதற்கு மாற்றாக இல்லை. குறியாக்கம் போக்குவரத்து மற்றும் சேமிப்பிற்கு பயனுள்ளதாக இருக்கும், அதே சமயம் ஜிப் பல வெளியீடுகளை முக்கிய இலக்காக இணைக்கும் போது சிறப்பாக இருக்கும். + முதலில் ஒரு நகலை என்க்ரிப்ட் செய்து, நீங்கள் சேமித்த கடவுச்சொல்லுடன் டிக்ரிப்ஷன் செயல்படுகிறதா என்று சோதிக்கும் வரை அசலை வைத்திருங்கள். + கடவுச்சொல் நிர்வாகி அல்லது விசைக்கு மற்றொரு பாதுகாப்பான இடத்தைப் பயன்படுத்தவும்; உங்களுக்கான கடவுச்சொல்லை பயன்பாட்டினால் யூகிக்க முடியாது. + மறைகுறியாக்கப்பட்ட கோப்புகளை கடவுச்சொல்லை உள்ளவர்களுடன் மட்டும் பகிரவும் மற்றும் எந்த கருவி அல்லது முறை அவற்றை மறைகுறியாக்க வேண்டும் என்பதை அறிந்திருக்க வேண்டும். + கருவிகளை ஏற்பாடு செய்யுங்கள் + குழு, ஆர்டர், தேடல் மற்றும் பின் கருவிகள், எனவே பிரதான திரை உங்கள் உண்மையான பணிப்பாய்வுக்கு பொருந்தும் + பிரதான திரையை வேகமாக்குங்கள் + நீங்கள் எந்தக் கருவிகளை அதிகமாகப் பயன்படுத்துகிறீர்கள் என்பதை அறிந்தவுடன், கருவி ஏற்பாட்டு அமைப்புகளைச் சரிசெய்வது மதிப்பு. குழுவாக்கம் ஆய்வுக்கு உதவுகிறது, தனிப்பயன் வரிசை தசை நினைவகத்திற்கு உதவுகிறது, மேலும் பிடித்தவைகள் முழு பட்டியல் பெரியதாக இருந்தாலும் கூட தினசரி கருவிகளை அணுகக்கூடியதாக இருக்கும். + பயன்பாட்டைக் கற்கும் போது அல்லது பணி வகையின்படி ஒழுங்கமைக்கப்பட்ட கருவிகளை நீங்கள் விரும்பும் போது குழுவான பயன்முறையைப் பயன்படுத்தவும். + நீங்கள் அடிக்கடி பயன்படுத்தும் கருவிகளை நீங்கள் ஏற்கனவே அறிந்திருந்தால் மற்றும் குறைவான ஸ்க்ரோல்களை விரும்பினால் தனிப்பயன் வரிசைக்கு மாறவும். + பெயர் மூலம் நீங்கள் நினைவில் வைத்திருக்கும் ஆனால் எல்லா நேரத்திலும் பார்க்க விரும்பாத அரிய கருவிகளுக்கு தேடல் அமைப்புகளையும் விருப்பங்களையும் ஒன்றாகப் பயன்படுத்தவும். + பெரிய கோப்புகளை கையாளவும் + மெதுவான ஏற்றுமதி, நினைவக அழுத்தம் மற்றும் எதிர்பாராத பெரிய வெளியீடு ஆகியவற்றைத் தவிர்க்கவும் + ஏற்றுமதிக்கு முன் வேலையை குறைக்கவும் + மிகப் பெரிய படங்கள், நீண்ட தொகுப்புகள் மற்றும் உயர்தர வடிவங்கள் அதிக நினைவகத்தையும் நேரத்தையும் எடுக்கும். வேகமான பிழைத்திருத்தம் பொதுவாக மிக அதிகமான செயல்பாட்டிற்கு முன் வேலையின் அளவைக் குறைப்பதாகும், அதே அளவுள்ள உள்ளீடு முடிவடையும் வரை காத்திருக்காது. + கனமான வடிப்பான்கள், OCR அல்லது PDF மாற்றத்தைப் பயன்படுத்துவதற்கு முன்பு பெரிய படங்களை மறுஅளவிடவும். + அமைப்புகளை உறுதிப்படுத்த முதலில் ஒரு சிறிய தொகுப்பைப் பயன்படுத்தவும், பின்னர் மீதமுள்ளவற்றை அதே முன்னமைவுடன் செயலாக்கவும். + வெளியீட்டு கோப்பு எதிர்பார்த்ததை விட பெரியதாக இருக்கும்போது குறைந்த தரம் அல்லது வடிவமைப்பை மாற்றவும். + தற்காலிக சேமிப்பு மற்றும் முன்னோட்டங்கள் + அதிக அமர்வுகளுக்குப் பிறகு உருவாக்கப்பட்ட மாதிரிக்காட்சிகள், தற்காலிக சேமிப்பை சுத்தம் செய்தல் மற்றும் சேமிப்பகப் பயன்பாடு ஆகியவற்றைப் புரிந்து கொள்ளுங்கள் + சேமிப்பகத்தையும் வேகத்தையும் சமநிலையில் வைத்திருங்கள் + முன்னோட்ட உருவாக்கம் மற்றும் தற்காலிக சேமிப்பானது மீண்டும் மீண்டும் உலாவுவதை வேகமாகச் செய்யலாம், ஆனால் அதிக எடிட்டிங் அமர்வுகள் தற்காலிகத் தரவை விட்டுச் செல்லக்கூடும். பயன்பாடு மெதுவாக இருக்கும்போது, ​​சேமிப்பகம் இறுக்கமாக இருக்கும்போது அல்லது பல சோதனைகளுக்குப் பிறகு முன்னோட்டங்கள் பழையதாகத் தோன்றும்போது தற்காலிக சேமிப்பு அமைப்புகள் உதவுகின்றன. + தற்காலிக சேமிப்பகத்தைக் குறைப்பதை விட, பல படங்களை உலாவும்போது முன்னோட்டங்களை இயக்கி வைத்திருப்பது மிகவும் முக்கியமானது. + பெரிய தொகுப்புகள், தோல்வியுற்ற சோதனைகள் அல்லது பல உயர் தெளிவுத்திறன் கொண்ட கோப்புகளுடன் நீண்ட அமர்வுகளுக்குப் பிறகு தற்காலிக சேமிப்பை அழிக்கவும். + துவக்கங்களுக்கு இடையே முன்னோட்டத்தை சூடாக வைத்திருப்பதை விட சேமிப்பகத்தை சுத்தம் செய்ய விரும்பினால், தானியங்கி கேச் கிளியரிங் பயன்படுத்தவும். + திரையின் நடத்தையை சரிசெய்யவும் + முழுத்திரை, பாதுகாப்பான பயன்முறை, பிரகாசம் மற்றும் சிஸ்டம் பார் விருப்பங்களை மையப்படுத்திய பணிக்கு பயன்படுத்தவும் + இடைமுகத்தை சூழ்நிலைக்கு ஏற்றவாறு அமைக்கவும் + நீங்கள் நிலப்பரப்பில் திருத்தும்போது, ​​தனிப்பட்ட படங்களை வழங்கும்போது அல்லது நிலையான பிரகாசம் தேவைப்படும்போது திரை அமைப்புகள் உதவும். சாதாரண பயன்பாட்டிற்கு அவை தேவையில்லை, ஆனால் அவை QR ஆய்வு, தனிப்பட்ட மாதிரிக்காட்சிகள் அல்லது தடைபட்ட இயற்கை எடிட்டிங் போன்ற மோசமான சூழ்நிலைகளைத் தீர்க்கும். + நேவிகேஷன் குரோம் விட இடத்தைத் திருத்துவது முக்கியமானதாக இருக்கும் போது முழுத்திரை அல்லது சிஸ்டம் பார் அமைப்புகளைப் பயன்படுத்தவும். + ஸ்கிரீன்ஷாட்கள் அல்லது ஆப்ஸ் மாதிரிக்காட்சிகளில் தனிப்பட்ட படங்கள் தோன்றாதபோது பாதுகாப்பான பயன்முறையைப் பயன்படுத்தவும். + இருண்ட படங்கள் அல்லது QR குறியீடுகளை ஆய்வு செய்ய கடினமாக இருக்கும் கருவிகளுக்கு பிரகாச அமலாக்கத்தைப் பயன்படுத்தவும். + வெளிப்படைத்தன்மையை வைத்திருங்கள் + வெளிப்படையான பின்னணி வெள்ளை, கருப்பு அல்லது தட்டையாக மாறுவதைத் தடுக்கவும் + ஆல்பா விழிப்புணர்வு வெளியீட்டைப் பயன்படுத்தவும் + தேர்ந்தெடுக்கப்பட்ட கருவி மற்றும் வெளியீட்டு வடிவம் ஆல்பாவை ஆதரிக்கும் போது மட்டுமே வெளிப்படைத்தன்மை நிலைத்திருக்கும். ஏற்றுமதி செய்யப்பட்ட பின்புலம் வெள்ளையாகவோ கருப்பு நிறமாகவோ மாறினால், சிக்கல் பொதுவாக வெளியீட்டு வடிவம், நிரப்புதல்/பின்னணி அமைப்பு அல்லது படத்தைத் தட்டையாக்கும் இலக்கு பயன்பாட்டில் இருக்கும். + பின்னணி-அகற்றப்பட்ட படங்கள், ஸ்டிக்கர்கள், லோகோக்கள் அல்லது மேலடுக்குகளை ஏற்றுமதி செய்யும் போது PNG அல்லது WebP ஐத் தேர்வு செய்யவும். + வெளிப்படையான வெளியீட்டிற்கு JPEG ஐ தவிர்க்கவும் ஏனெனில் அது ஆல்பா சேனலை சேமிக்காது. + முன்னோட்டம் நிரப்பப்பட்ட பின்னணியைக் காட்டினால், ஆல்பா வடிவங்களுக்கான பின்னணி வண்ணம் தொடர்பான அமைப்புகளைச் சரிபார்க்கவும். + பகிர்தல் மற்றும் இறக்குமதி சிக்கல்களைச் சரிசெய்யவும் + கோப்புகள் தோன்றாதபோது அல்லது சரியாகத் திறக்காதபோது பிக்கர் அமைப்புகளையும் ஆதரிக்கப்படும் வடிவங்களையும் பயன்படுத்தவும் + பயன்பாட்டில் கோப்பைப் பெறவும் + வழங்குநரின் அனுமதிகள், ஆதரிக்கப்படாத வடிவங்கள் அல்லது பிக்கர் நடத்தை ஆகியவற்றால் இறக்குமதிச் சிக்கல்கள் பெரும்பாலும் ஏற்படுகின்றன. கிளவுட் ஆப்ஸ் மற்றும் மெசஞ்சர்களில் இருந்து பகிரப்படும் கோப்புகள் தற்காலிகமானதாக இருக்கலாம், எனவே நீண்ட திருத்தங்களுக்கு நிலையான மூலத்தைத் தேர்ந்தெடுப்பது மிகவும் நம்பகமானதாக இருக்கும். + சமீபத்திய கோப்புகள் குறுக்குவழிக்குப் பதிலாக கணினித் தேர்வி மூலம் கோப்பைத் திறக்க முயற்சிக்கவும். + ஒரு வடிவமைப்பை ஒரு கருவி ஆதரிக்கவில்லை என்றால், அதை முதலில் மாற்றவும் அல்லது ஒரு குறிப்பிட்ட கருவியைத் திறக்கவும். + பகிர்வு ஓட்டங்கள் அல்லது நேரடி கருவி திறப்பு எதிர்பார்த்ததை விட வித்தியாசமாக செயல்பட்டால் படத் தேர்வு மற்றும் துவக்கி அமைப்புகளை மதிப்பாய்வு செய்யவும். + வெளியேறுதல் உறுதிப்படுத்தல் + சைகைகள், பின் அழுத்தங்கள் அல்லது கருவி சுவிட்சுகள் தற்செயலாக நிகழும்போது சேமிக்கப்படாத திருத்தங்களை இழப்பதைத் தவிர்க்கவும் + முடிக்கப்படாத வேலையைப் பாதுகாக்கவும் + நீங்கள் சைகை வழிசெலுத்தலைப் பயன்படுத்தும்போது, ​​பெரிய கோப்புகளைத் திருத்தும்போது அல்லது அடிக்கடி பயன்பாடுகளுக்கு இடையில் மாறும்போது கருவி வெளியேறும் உறுதிப்படுத்தல் உதவியாக இருக்கும். இது ஒரு கருவியை விட்டுச் செல்வதற்கு முன் ஒரு சிறிய இடைநிறுத்தத்தைச் சேர்க்கிறது, எனவே முடிக்கப்படாத அமைப்புகளும் முன்னோட்டங்களும் தற்செயலாக இழக்கப்படாது. + நீங்கள் ஏற்றுமதி செய்வதற்கு முன், தற்செயலான பின் சைகைகள் எப்போதாவது ஒரு கருவியை மூடியிருந்தால், உறுதிப்படுத்தலை இயக்கவும். + நீங்கள் பெரும்பாலும் விரைவான ஒரு-படி மாற்றங்களைச் செய்து, வேகமான வழிசெலுத்தலை விரும்பினால், அதை முடக்கி வைக்கவும். + அமைப்புகளை மீண்டும் உருவாக்குவது எரிச்சலூட்டும் நீண்ட பணிப்பாய்வுகளுக்கு முன்னமைவுகளுடன் இதைப் பயன்படுத்தவும். + சிக்கல்களைப் புகாரளிக்கும் போது பதிவுகளைப் பயன்படுத்தவும் + சிக்கலைத் திறப்பதற்கு முன் அல்லது டெவலப்பரைத் தொடர்புகொள்வதற்கு முன் பயனுள்ள விவரங்களைச் சேகரிக்கவும் + சூழல் தொடர்பான சிக்கல்களைப் புகாரளிக்கவும் + படிகள், பயன்பாட்டின் பதிப்பு, உள்ளீட்டு வகை மற்றும் பதிவுகள் கொண்ட தெளிவான அறிக்கை கண்டறிய மிகவும் எளிதானது. ஒரு சிக்கலை மீண்டும் உருவாக்கிய உடனேயே பதிவுகள் மிகவும் பயனுள்ளதாக இருக்கும், ஏனெனில் அவை சுற்றியுள்ள சூழலை புதியதாக வைத்திருக்கின்றன. + கருவியின் பெயர், சரியான செயல் மற்றும் ஒரு கோப்பு அல்லது ஒவ்வொரு கோப்பிலும் இது நடக்கிறதா என்பதைக் கவனியுங்கள். + சிக்கலை மீண்டும் உருவாக்கிய பிறகு பயன்பாட்டுப் பதிவுகளைத் திறந்து, முடிந்தால் தொடர்புடைய பதிவு வெளியீட்டைப் பகிரவும். + ஸ்கிரீன் ஷாட்கள் அல்லது மாதிரி கோப்புகளில் தனிப்பட்ட தகவல்கள் இல்லாதபோது மட்டும் அவற்றை இணைக்கவும். + பின்னணி செயலாக்கம் நிறுத்தப்பட்டது + பின்னணி செயலாக்க அறிவிப்பை சரியான நேரத்தில் தொடங்க Android அனுமதிக்கவில்லை. இது ஒரு கணினி நேர வரம்பு, அதை நம்பத்தகுந்த முறையில் சரிசெய்ய முடியாது. பயன்பாட்டை மறுதொடக்கம் செய்து மீண்டும் முயற்சிக்கவும்; பயன்பாட்டைத் திறந்து வைத்திருப்பது மற்றும் அறிவிப்புகள் அல்லது பின்னணி வேலைகளை அனுமதிப்பது உதவக்கூடும். + கோப்பு எடுப்பதைத் தவிர்க்கவும் + பொதுவாக பகிர்வு, கிளிப்போர்டு அல்லது முந்தைய திரையில் இருந்து உள்ளீடு வரும்போது கருவிகளை வேகமாகத் திறக்கவும் + மீண்டும் மீண்டும் கருவியை வேகமாக தொடங்கவும் + கோப்பைத் தவிர்த்தல் ஆதரிக்கப்படும் கருவிகளின் முதல் படியை மாற்றுகிறது. நீங்கள் வழக்கமாக ஏற்கனவே தேர்ந்தெடுக்கப்பட்ட படத்துடன் அல்லது ஆண்ட்ராய்டு பகிர்வு செயல்களில் இருந்து ஒரு கருவியை உள்ளிடும்போது இது பயனுள்ளதாக இருக்கும், ஆனால் ஒவ்வொரு கருவியும் உடனடியாக கோப்பைக் கேட்கும் என நீங்கள் எதிர்பார்த்தால் அது குழப்பமாக இருக்கும். + கருவி திறக்கும் முன் உங்கள் வழக்கமான ஓட்டம் ஏற்கனவே மூலப் படத்தை வழங்கும் போது மட்டுமே அதை இயக்கவும். + பயன்பாட்டைக் கற்கும் போது அதை முடக்கி வைக்கவும், ஏனெனில் வெளிப்படையான தேர்வு ஒவ்வொரு கருவியின் ஓட்டத்தையும் எளிதாகப் புரிந்துகொள்ள உதவுகிறது. + வெளிப்படையான உள்ளீடு இல்லாமல் ஒரு கருவி திறக்கப்பட்டால், இந்த அமைப்பை முடக்கவும் அல்லது பகிர்/திறந்த உடன் தொடங்கவும், இதனால் ஆண்ட்ராய்ட் கோப்பை முதலில் அனுப்பும். + முதலில் எடிட்டரைத் திறக்கவும் + பகிரப்பட்ட படம் நேரடியாக எடிட்டிங் செய்யும்போது முன்னோட்டத்தைத் தவிர்க்கவும் + முதல் நிறுத்தமாக முன்னோட்டம் அல்லது திருத்தத்தைத் தேர்ந்தெடுக்கவும் + முன்னோட்டத்திற்குப் பதிலாகத் திருத்து என்பது படத்தை மாற்ற விரும்புவதை ஏற்கனவே அறிந்த பயனர்களுக்கானது. அரட்டை அல்லது கிளவுட் சேமிப்பகத்திலிருந்து கோப்புகளை ஆய்வு செய்யும் போது முன்னோட்டம் பாதுகாப்பானது; ஸ்கிரீன் ஷாட்கள், விரைவான பயிர்கள், சிறுகுறிப்புகள் மற்றும் ஒருமுறை திருத்தங்கள் ஆகியவற்றிற்கு நேரடித் திருத்தம் வேகமாக இருக்கும். + பெரும்பாலான பகிரப்பட்ட படங்களுக்கு உடனடியாக செதுக்குதல், மார்க்அப், வடிப்பான்கள் அல்லது ஏற்றுமதி மாற்றங்கள் தேவைப்பட்டால் நேரடித் திருத்தத்தை இயக்கவும். + நீங்கள் அடிக்கடி மெட்டாடேட்டா, பரிமாணங்கள், வெளிப்படைத்தன்மை அல்லது படத்தின் தரத்தை முடிவு செய்வதற்கு முன் ஆய்வு செய்யும் போது, ​​முதலில் முன்னோட்டத்தை விடுங்கள். + பிடித்தவைகளுடன் இதைப் பயன்படுத்தவும், எனவே பகிரப்பட்ட படங்கள் நீங்கள் உண்மையில் பயன்படுத்தும் கருவிகளுக்கு அருகில் இருக்கும். + முன்னமைக்கப்பட்ட உரை உள்ளீடு + கட்டுப்பாடுகளை மீண்டும் மீண்டும் சரிசெய்வதற்குப் பதிலாக சரியான முன்னமைக்கப்பட்ட மதிப்புகளை உள்ளிடவும் + ஸ்லைடர்கள் மெதுவாக இருக்கும்போது துல்லியமான மதிப்புகளைப் பயன்படுத்தவும் + மீண்டும் மீண்டும் வேலை செய்ய உங்களுக்கு சரியான எண்கள் தேவைப்படும்போது முன்னமைக்கப்பட்ட உரை உள்ளீடு உதவுகிறது: சமூகப் பட அளவுகள், ஆவண விளிம்புகள், சுருக்க இலக்குகள், வரி அகலங்கள் அல்லது சைகைகள் மூலம் அடைய எரிச்சலூட்டும் பிற மதிப்புகள். சிறந்த ஸ்லைடர் இயக்கம் கடினமாக இருக்கும் சிறிய திரைகளிலும் இது பயனுள்ளதாக இருக்கும். + நீங்கள் அடிக்கடி சரியான அளவுகள், சதவீதங்கள், தர மதிப்புகள் அல்லது கருவி அளவுருக்களை மீண்டும் பயன்படுத்தினால், உரை உள்ளீட்டை இயக்கவும். + மதிப்பை ஒரு முறை தட்டச்சு செய்த பிறகு முன்னமைவாகச் சேமித்து, அதே அமைப்பை மீண்டும் உருவாக்குவதற்குப் பதிலாக மீண்டும் பயன்படுத்தவும். + கடினமான காட்சி ட்யூனிங்கிற்கான சைகைக் கட்டுப்பாடுகளையும், கடுமையான வெளியீட்டுத் தேவைகளுக்காக தட்டச்சு செய்யப்பட்ட மதிப்புகளையும் வைத்திருங்கள். + அசல் அருகில் சேமிக்கவும் + கோப்புறை சூழல் முக்கியமானதாக இருக்கும் போது உருவாக்கப்பட்ட கோப்புகளை மூலப் படங்களுக்கு அருகில் வைக்கவும் + வெளியீடுகளை அவற்றின் ஆதாரங்களுடன் வைத்திருங்கள் + அசல் கோப்புறையில் சேமி என்பது கேமரா கோப்புறைகள், ப்ராஜெக்ட் கோப்புறைகள், ஆவணம் ஸ்கேன்கள் மற்றும் கிளையன்ட் பேட்ச்களுக்கு எளிதாக இருக்கும், திருத்தப்பட்ட நகல் மூலத்திற்கு அடுத்ததாக இருக்க வேண்டும். இது பிரத்யேக வெளியீட்டு கோப்புறையை விட குறைவான நேர்த்தியாக இருக்கும், எனவே தெளிவான கோப்பு பெயர் பின்னொட்டுகள் அல்லது வடிவங்களுடன் இணைக்கவும். + ஒவ்வொரு மூலக் கோப்புறையும் ஏற்கனவே அர்த்தமுள்ளதாக இருக்கும் போது அதை இயக்கவும் மற்றும் வெளியீடுகள் குழுவாக இருக்க வேண்டும். + கோப்புப் பெயர் பின்னொட்டுகள், முன்னொட்டுகள், நேர முத்திரைகள் அல்லது வடிவங்களைப் பயன்படுத்தவும், எனவே திருத்தப்பட்ட நகல்களை அசலில் இருந்து பிரிக்க எளிதாக இருக்கும். + ஒரே ஏற்றுமதி கோப்புறையில் உருவாக்கப்பட்ட அனைத்து கோப்புகளையும் சேகரிக்க விரும்பினால், கலவையான தொகுதிகளுக்கு அதை முடக்கவும். + பெரிய வெளியீட்டைத் தவிர்க்கவும் + எதிர்பாராத விதமாக மூலத்தை விட கனமான மாற்றங்களைச் சேமிப்பதைத் தவிர்க்கவும் + மோசமான அளவு ஆச்சரியங்களிலிருந்து தொகுதிகளைப் பாதுகாக்கவும் + சில மாற்றங்கள் கோப்புகளை பெரிதாக்குகின்றன, குறிப்பாக PNG ஸ்கிரீன்ஷாட்கள், ஏற்கனவே சுருக்கப்பட்ட புகைப்படங்கள், உயர்தர WebP அல்லது மெட்டாடேட்டா பாதுகாக்கப்பட்ட படங்கள். அளவைக் குறைப்பதே முக்கிய குறிக்கோளாக இருக்கும் பணிப்பாய்வுகளில் சிறிய மூலத்தை மாற்றுவதிலிருந்து பெரியதாக இருந்தால் தவிர் என்பதை அனுமதிக்கவும். + ஏற்றுதல் என்பது சுருக்க, அளவை மாற்ற அல்லது பதிவேற்ற வரம்புகளுக்கான கோப்புகளைத் தயாரிக்கும் போது அதை இயக்கவும். + ஒரு தொகுதிக்குப் பிறகு தவிர்க்கப்பட்ட கோப்புகளை மதிப்பாய்வு செய்யவும்; அவை ஏற்கனவே மேம்படுத்தப்பட்டிருக்கலாம் அல்லது வேறு வடிவம் தேவைப்படலாம். + இறுதி கோப்பு அளவை விட தரம், வெளிப்படைத்தன்மை அல்லது வடிவமைப்பு இணக்கத்தன்மை முக்கியமானதாக இருக்கும்போது அதை முடக்கவும். + கிளிப்போர்டு மற்றும் இணைப்புகள் + வேகமான உரை அடிப்படையிலான கருவிகளுக்கான தானியங்கி கிளிப்போர்டு உள்ளீடு மற்றும் இணைப்பு மாதிரிக்காட்சிகளைக் கட்டுப்படுத்தவும் + தானியங்கி உள்ளீட்டை யூகிக்கக்கூடியதாக ஆக்குங்கள் + கிளிப்போர்டு மற்றும் இணைப்பு அமைப்புகள் QR, Base64, URL மற்றும் உரை-கடுமையான பணிப்பாய்வுகளுக்கு வசதியானவை, ஆனால் தானியங்கி உள்ளீடு வேண்டுமென்றே இருக்க வேண்டும். பயன்பாடு தானாகவே புலங்களை நிரப்புவது போல் தோன்றினால், கிளிப்போர்டு பேஸ்ட் நடத்தையை சரிபார்க்கவும்; இணைப்பு அட்டைகள் சத்தமாகவோ அல்லது மெதுவாகவோ உணர்ந்தால், இணைப்பு முன்னோட்ட நடத்தையை சரிசெய்யவும். + நீங்கள் அடிக்கடி உரையை நகலெடுத்து, உடனடியாக பொருந்தும் கருவியைத் திறக்கும்போது தானியங்கி கிளிப்போர்டு பேஸ்ட்டை அனுமதிக்கவும். + நீங்கள் எதிர்பார்க்காத கருவிகளில் தனிப்பட்ட கிளிப்போர்டு உள்ளடக்கம் தோன்றினால், தானியங்கி ஒட்டுதலை முடக்கவும். + முன்னோட்டம் பெறுதல் மெதுவாக இருக்கும் போது, ​​கவனத்தை சிதறடிக்கும் அல்லது உங்கள் பணிப்பாய்வுக்கு தேவையற்றதாக இருக்கும் போது இணைப்பு முன்னோட்டங்களை முடக்கவும். + சிறிய கட்டுப்பாடுகள் + திரை இடம் இறுக்கமாக இருக்கும் போது அடர்த்தியான செலக்டர்கள் மற்றும் வேகமான செட்டிங்ஸ் பிளேஸ்மென்ட்டைப் பயன்படுத்தவும் + சிறிய திரைகளில் அதிக கட்டுப்பாடுகளை பொருத்தவும் + சிறிய ஃபோன்கள், இயற்கை எடிட்டிங் மற்றும் பல அளவுருக்கள் கொண்ட கருவிகளில் காம்பாக்ட் செலக்டர்கள் மற்றும் வேகமான செட்டிங்ஸ் பிளேஸ்மென்ட் உதவுகிறது. பரிமாற்றம் என்னவென்றால், கட்டுப்பாடுகள் அடர்த்தியாக உணர முடியும், எனவே நீங்கள் ஏற்கனவே பொதுவான விருப்பங்களை அறிந்த பிறகு இது சிறந்தது. + உரையாடல்கள் அல்லது விருப்பப் பட்டியல்கள் திரைக்கு மிக உயரமாக இருக்கும் போது சிறிய தேர்விகளை இயக்கவும். + டிஸ்பிளேயின் ஒரு பக்கத்திலிருந்து கருவிக் கட்டுப்பாடுகள் எளிதாக அடையக்கூடியதாக இருந்தால், வேகமான அமைப்புகளின் பக்கத்தைச் சரிசெய்யவும். + நீங்கள் இன்னும் பயன்பாட்டைக் கற்றுக் கொண்டிருந்தாலோ அல்லது அடர்த்தியான விருப்பங்களை அடிக்கடி தட்டாமல் இருந்தாலோ, கூடுதல் கட்டுப்பாடுகளுக்குத் திரும்பவும். + இணையத்திலிருந்து படங்களை ஏற்றவும் + ஒரு பக்கம் அல்லது பட URLலை ஒட்டவும், பாகுபடுத்தப்பட்ட படங்களை முன்னோட்டமிடவும், பின்னர் தேர்ந்தெடுத்த முடிவுகளைச் சேமிக்கவும் அல்லது பகிரவும் + வலைப் படங்களை வேண்டுமென்றே பயன்படுத்தவும் + இணையப் படத்தை ஏற்றுவது, வழங்கப்பட்ட URL இலிருந்து பட ஆதாரங்களை அலசுகிறது, முதலில் கிடைக்கும் படத்தை முன்னோட்டமிடுகிறது, மேலும் தேர்ந்தெடுக்கப்பட்ட பாகுபடுத்தப்பட்ட படங்களைச் சேமிக்க அல்லது பகிர உங்களை அனுமதிக்கிறது. சில தளங்கள் தற்காலிக, பாதுகாக்கப்பட்ட அல்லது ஸ்கிரிப்ட்-உருவாக்கப்பட்ட இணைப்புகளைப் பயன்படுத்துகின்றன, எனவே உலாவியில் செயல்படும் பக்கம் இன்னும் பயன்படுத்தக்கூடிய படங்களை வழங்காது. + நேரடி பட URL அல்லது பக்க URL ஐ ஒட்டவும் மற்றும் பாகுபடுத்தப்பட்ட பட பட்டியல் தோன்றும் வரை காத்திருக்கவும். + பக்கம் பல படங்களைக் கொண்டிருக்கும்போது, ​​அவற்றில் சில மட்டுமே உங்களுக்குத் தேவைப்படும்போது சட்டகம் அல்லது தேர்வுக் கட்டுப்பாடுகளைப் பயன்படுத்தவும். + எதுவும் ஏற்றப்படவில்லை என்றால், நேரடி பட இணைப்பை முயற்சிக்கவும் அல்லது முதலில் உலாவி மூலம் பதிவிறக்கவும்; தளம் பாகுபடுத்துவதைத் தடுக்கலாம் அல்லது தனிப்பட்ட இணைப்புகளைப் பயன்படுத்தலாம். + மறுஅளவிடுதல் வகையைத் தேர்ந்தெடுக்கவும் + புதிய பரிமாணங்களுக்கு ஒரு படத்தை கட்டாயப்படுத்துவதற்கு முன் வெளிப்படையான, நெகிழ்வான, பயிர் மற்றும் பொருத்தம் ஆகியவற்றைப் புரிந்து கொள்ளுங்கள் + வடிவம், கேன்வாஸ் மற்றும் விகிதத்தைக் கட்டுப்படுத்தவும் + கோரப்பட்ட அகலம் மற்றும் உயரம் அசல் விகிதத்துடன் பொருந்தாதபோது என்ன நடக்கும் என்பதை மறுஅளவிடுதல் வகை தீர்மானிக்கிறது. பிக்சல்களை நீட்டுவது, விகிதாச்சாரத்தைப் பாதுகாப்பது, கூடுதல் பகுதியைச் செதுக்குவது அல்லது பின்னணி கேன்வாஸைச் சேர்ப்பது ஆகியவற்றுக்கு இடையேயான வித்தியாசம் இது. + சிதைப்பது ஏற்கத்தக்கதாக இருக்கும் போது அல்லது மூலமானது ஏற்கனவே இலக்கின் அதே விகிதத்தைக் கொண்டிருக்கும் போது மட்டுமே வெளிப்படையானதைப் பயன்படுத்தவும். + முக்கியமான பக்கத்திலிருந்து அளவை மாற்றுவதன் மூலம் விகிதாச்சாரத்தை வைத்திருக்க நெகிழ்வானதைப் பயன்படுத்தவும், குறிப்பாக புகைப்படங்கள் மற்றும் கலவையான தொகுதிகளுக்கு. + பார்டர்கள் இல்லாத கண்டிப்பான பிரேம்களுக்கு Crop ஐப் பயன்படுத்தவும் அல்லது நிலையான கேன்வாஸில் முழுப் படமும் தெரியும் போது ஃபிட் செய்யவும். + பட அளவு பயன்முறையைத் தேர்ந்தெடுக்கவும் + கூர்மை, மென்மை, வேகம் மற்றும் விளிம்பு கலைப்பொருட்களைக் கட்டுப்படுத்தும் மறு மாதிரி வடிகட்டியைத் தேர்வு செய்யவும் + தற்செயலான மென்மை இல்லாமல் அளவை மாற்றவும் + மறுஅளவிடுதலின் போது புதிய பிக்சல்கள் எவ்வாறு கணக்கிடப்படுகின்றன என்பதை பட அளவிலான பயன்முறை கட்டுப்படுத்துகிறது. எளிய முறைகள் வேகமானவை மற்றும் யூகிக்கக்கூடியவை, அதே சமயம் மேம்பட்ட வடிப்பான்கள் விவரங்களைச் சிறப்பாகப் பாதுகாக்கும் ஆனால் விளிம்புகளைக் கூர்மைப்படுத்தலாம், ஒளிவட்டங்களை உருவாக்கலாம் அல்லது பெரிய படங்களை அதிக நேரம் எடுக்கலாம். + முடிவுகள் சிறியதாகவும் சுத்தமாகவும் இருக்கும் போது மட்டுமே தினசரி அளவை மாற்றுவதற்கு அடிப்படை அல்லது பிலினியரைப் பயன்படுத்தவும். + சிறந்த விவரங்கள், உரை அல்லது உயர்தரக் குறைப்பு விஷயத்தில் Lanczos, Robidoux, Spline அல்லது EWA-பாணி முறைகளைப் பயன்படுத்தவும். + மங்கலான பிக்சல்கள் தோற்றத்தைக் கெடுக்கும் பிக்சல் கலை, ஐகான்கள் மற்றும் கடினமான முனைகள் கொண்ட கிராபிக்ஸ் ஆகியவற்றிற்கு அருகிலுள்ளதைப் பயன்படுத்தவும். + வண்ண இடத்தை அளவிடவும் + மறுஅளவிடுதலின் போது பிக்சல்கள் மறுமாதிரி செய்யப்படும்போது வண்ணங்கள் எவ்வாறு கலக்கப்படுகின்றன என்பதைத் தீர்மானிக்கவும் + சாய்வு மற்றும் விளிம்புகளை இயற்கையாக வைத்திருங்கள் + அளவை மாற்றும் போது பயன்படுத்தப்படும் கணிதத்தை அளவிடும் வண்ண இடைவெளி மாற்றுகிறது. பெரும்பாலான படங்கள் இயல்புநிலையுடன் நன்றாக இருக்கும், ஆனால் நேரியல் அல்லது புலனுணர்வு இடைவெளிகள் சாய்வுகள், இருண்ட விளிம்புகள் மற்றும் நிறைவுற்ற வண்ணங்கள் வலுவான அளவிடுதலுக்குப் பிறகு சுத்தமாக இருக்கும். + சிறிய வண்ண வேறுபாடுகளைக் காட்டிலும் வேகம் மற்றும் இணக்கத்தன்மை முக்கியமானதாக இருக்கும்போது இயல்புநிலையை விட்டு விடுங்கள். + இருண்ட அவுட்லைன்கள், UI ஸ்கிரீன்ஷாட்கள், சாய்வுகள் அல்லது வண்ணப் பட்டைகள் மறுஅளவிற்குப் பிறகு தவறாகத் தோன்றினால், நேரியல் அல்லது புலனுணர்வு முறைகளை முயற்சிக்கவும். + உண்மையான இலக்குத் திரையில் முன்னும் பின்னும் ஒப்பிட்டுப் பாருங்கள், ஏனெனில் வண்ண-வெளி மாற்றங்கள் நுட்பமானதாகவும் உள்ளடக்கத்தைச் சார்ந்ததாகவும் இருக்கும். + மறுஅளவிடுதல் முறைகளை வரம்பிடவும் + அதிக வரம்பிற்குட்பட்ட படங்கள் எப்போது தவிர்க்கப்பட வேண்டும், மறுகுறியீடு செய்யப்பட வேண்டும் அல்லது பொருத்தமாக குறைக்கப்பட வேண்டும் என்பதை அறியவும் + வரம்பில் என்ன நடக்கிறது என்பதைத் தேர்ந்தெடுக்கவும் + வரம்பு மறுஅளவிடுதல் என்பது ஒரு சிறிய படக் கருவி மட்டுமல்ல. ஒரு ஆதாரம் ஏற்கனவே உங்கள் வரம்புகளுக்குள் இருக்கும்போது அல்லது ஒரு பக்கம் மட்டுமே விதியை மீறும் போது பயன்பாடு என்ன செய்கிறது என்பதை அதன் பயன்முறை தீர்மானிக்கிறது, இது கலப்பு கோப்புறைகள் மற்றும் பதிவேற்றம் தயாரிப்பிற்கு முக்கியமானது. + வரம்புகளுக்குள் இருக்கும் படங்கள் தீண்டப்படாமலும், மீண்டும் ஏற்றுமதி செய்யப்படாமலும் இருக்கும் போது Skip ஐப் பயன்படுத்தவும். + பரிமாணங்கள் ஏற்கத்தக்கதாக இருக்கும் போது Recode ஐப் பயன்படுத்தவும் ஆனால் உங்களுக்கு இன்னும் புதிய வடிவம், மெட்டாடேட்டா நடத்தை, தரம் அல்லது கோப்புப் பெயர் தேவை. + வரம்புகளை மீறும் படங்கள் அனுமதிக்கப்பட்ட பெட்டிக்கு பொருந்தும் வரை விகிதாச்சாரத்தில் குறைக்கப்படும் போது பெரிதாக்கு பயன்படுத்தவும். + தோற்ற விகித இலக்குகள் + நீட்டிக்கப்பட்ட முகங்கள், வெட்டு விளிம்புகள் மற்றும் கண்டிப்பான வெளியீட்டு அளவுகளில் எதிர்பாராத பார்டர்களைத் தவிர்க்கவும் + அளவை மாற்றுவதற்கு முன் வடிவத்தை பொருத்தவும் + அகலமும் உயரமும் மறுஅளவிடல் இலக்கில் பாதி மட்டுமே. படம் இயற்கையாக பொருந்துமா, செதுக்க வேண்டுமா அல்லது அதைச் சுற்றி ஒரு கேன்வாஸ் தேவையா என்பதை விகித விகிதம் தீர்மானிக்கிறது. முதலில் வடிவத்தைச் சரிபார்ப்பது மிகவும் ஆச்சரியமான மறுஅளவிடல் முடிவுகளைத் தடுக்கிறது. + வெளிப்படையான, பயிர் அல்லது பொருத்தத்தைத் தேர்ந்தெடுக்கும் முன், மூல வடிவத்தை இலக்கு வடிவத்துடன் ஒப்பிடவும். + பொருள் கூடுதல் பின்னணியை இழக்க நேரிடும் போது முதலில் செதுக்குங்கள் மற்றும் இலக்குக்கு கடுமையான சட்டகம் தேவை. + அசல் படத்தின் ஒவ்வொரு பகுதியும் தெரியும் போது பொருத்தம் அல்லது நெகிழ்வானது என்பதைப் பயன்படுத்தவும். + உயர்தர அல்லது சாதாரண அளவு + பிக்சல்களைச் சேர்க்கும் போது AI தேவை மற்றும் எளிய அளவிடுதல் போதுமானது என்பதை அறிந்து கொள்ளுங்கள் + விவரத்துடன் அளவைக் குழப்ப வேண்டாம் + பிக்சல்களை இடைக்கணிப்பதன் மூலம் இயல்பான மறுஅளவிடுதல் பரிமாணங்களை மாற்றுகிறது; அது உண்மையான விவரங்களை கண்டுபிடிக்க முடியாது. AI உயர்நிலையானது கூர்மையாகத் தோற்றமளிக்கும் விவரங்களை உருவாக்கலாம், ஆனால் இது மெதுவாகவும் மாயத்தோற்றத்தை உருவாக்கவும் கூடும், எனவே சரியான தேர்வு உங்களுக்கு இணக்கத்தன்மை, வேகம் அல்லது காட்சி மறுசீரமைப்பு தேவையா என்பதைப் பொறுத்தது. + சிறுபடங்கள், பதிவேற்ற வரம்புகள், ஸ்கிரீன்ஷாட்கள் மற்றும் எளிய பரிமாண மாற்றங்களுக்கு இயல்பான அளவைப் பயன்படுத்தவும். + ஒரு சிறிய அல்லது மென்மையான படம் பெரிய அளவில் நன்றாக இருக்க வேண்டும் என்றால், AI உயர்நிலையைப் பயன்படுத்தவும். + AI உயர்நிலைக்குப் பிறகு முகங்கள், உரை மற்றும் வடிவங்களை ஒப்பிட்டுப் பாருங்கள், ஏனெனில் உருவாக்கப்பட்ட விவரங்கள் நம்பத்தகுந்தவை ஆனால் தவறானவை. + வடிகட்டி வரிசை முக்கியமானது + தூய்மைப்படுத்தல், நிறம், கூர்மை மற்றும் இறுதி சுருக்கத்தை வேண்டுமென்றே வரிசைப்படுத்தவும் + சுத்தமான வடிகட்டி அடுக்கை உருவாக்கவும் + வடிப்பான்கள் எப்போதும் ஒன்றுக்கொன்று மாறக்கூடியவை அல்ல. கூர்மைப்படுத்துவதற்கு முன் ஒரு மங்கலானது, OCRக்கு முன் ஒரு கான்ட்ராஸ்ட் பூஸ்ட், அல்லது சுருக்கத்திற்கு முன் நிற மாற்றம் ஆகியவை ஒரே கட்டுப்பாடுகளிலிருந்து வேறுபட்ட வெளியீட்டை உருவாக்கலாம். + முதலில் செதுக்கி சுழற்றுங்கள், எனவே பின்னர் வடிகட்டிகள் மீதமுள்ள பிக்சல்களில் மட்டுமே செயல்படும். + கூர்மைப்படுத்துதல் அல்லது பகட்டான விளைவுகளுக்கு முன் வெளிப்பாடு, வெள்ளை சமநிலை மற்றும் மாறுபாடு ஆகியவற்றைப் பயன்படுத்தவும். + இறுதி அளவு மற்றும் சுருக்கத்தை இறுதிக்கு அருகில் வைத்திருங்கள், எனவே முன்னோட்ட விவரங்கள் ஏற்றுமதியை முன்னறிவிக்கும்படி வாழலாம். + பின்னணி விளிம்புகளை சரிசெய்யவும் + தானாக அகற்றப்பட்ட பிறகு ஒளிவட்டம், மீதமுள்ள நிழல்கள், முடி, துளைகள் மற்றும் மென்மையான வெளிப்புறங்களை சுத்தம் செய்யவும் + கட்அவுட்களை வேண்டுமென்றே தோற்றமளிக்கவும் + தானியங்கி முகமூடிகள் பெரும்பாலும் ஒரு பார்வையில் நன்றாக இருக்கும் ஆனால் பிரகாசமான, இருண்ட அல்லது வடிவமைக்கப்பட்ட பின்னணியில் சிக்கல்களை வெளிப்படுத்துகின்றன. எட்ஜ் க்ளீனப் என்பது விரைவான கட்அவுட் மற்றும் மீண்டும் பயன்படுத்தக்கூடிய ஸ்டிக்கர், லோகோ அல்லது தயாரிப்பு படத்திற்கு இடையே உள்ள வித்தியாசம். + ஒளிவட்டம் மற்றும் விடுபட்ட விளிம்பு விவரங்களை வெளிப்படுத்த, மாறுபட்ட பின்னணிக்கு எதிராக முடிவை முன்னோட்டமிடுங்கள். + சுற்றியுள்ள எச்சங்களை அழிக்கும் முன், முடி, மூலைகள் மற்றும் வெளிப்படையான பொருள்களுக்கு மீட்டமைப்பைப் பயன்படுத்தவும். + கட்அவுட் ஒரு வடிவமைப்பில் வைக்கப்படும் போது இறுதி பின்னணி வண்ணத்தில் ஒரு சிறிய சோதனையை ஏற்றுமதி செய்யவும். + படிக்கக்கூடிய வாட்டர்மார்க்ஸ் + புகைப்படத்தை அழிக்காமல், பிரகாசமான, இருண்ட, பிஸியான மற்றும் செதுக்கப்பட்ட படங்களில் குறிகள் தெரியும்படி செய்யுங்கள் + படத்தை மறைக்காமல் பாதுகாக்கவும் + ஒரு படத்தில் நன்றாக இருக்கும் வாட்டர்மார்க் மற்றொரு படத்தில் மறைந்து போகலாம். அளவு, ஒளிபுகாநிலை, மாறுபாடு, வேலை வாய்ப்பு மற்றும் திரும்பத் திரும்ப முதல் மாதிரிக்காட்சிக்கு மட்டும் தேர்வு செய்யப்பட வேண்டும். + எல்லா கோப்புகளையும் ஏற்றுமதி செய்வதற்கு முன், தொகுப்பில் உள்ள பிரகாசமான மற்றும் இருண்ட படங்களின் அடையாளத்தை சோதிக்கவும். + பெரிய மீண்டும் மீண்டும் மதிப்பெண்களுக்கு குறைந்த ஒளிபுகாநிலையையும் சிறிய மூலை மதிப்பெண்களுக்கு வலுவான மாறுபாட்டையும் பயன்படுத்தவும். + முக்கிய முகங்கள், உரை மற்றும் தயாரிப்பு விவரங்களைத் தெளிவாக வைத்திருங்கள், குறி மறுபயன்பாட்டைத் தடுக்கும் வரை. + ஒரு படத்தை ஓடுகளாகப் பிரிக்கவும் + வரிசைகள், நெடுவரிசைகள், தனிப்பயன் சதவீதங்கள், வடிவம் மற்றும் தரம் ஆகியவற்றின் அடிப்படையில் ஒரு படத்தை வெட்டுங்கள் + கட்டங்கள் மற்றும் ஆர்டர் செய்யப்பட்ட துண்டுகளை தயார் செய்யவும் + படத்தைப் பிரிப்பது ஒரு மூலப் படத்திலிருந்து பல வெளியீடுகளை உருவாக்குகிறது. இது வரிசை மற்றும் நெடுவரிசை எண்ணிக்கையால் பிரிக்கலாம், வரிசை அல்லது நெடுவரிசை சதவீதங்களை சரிசெய்யலாம் மற்றும் தேர்ந்தெடுக்கப்பட்ட வெளியீட்டு வடிவம் மற்றும் தரத்துடன் துண்டுகளைச் சேமிக்கலாம், இது கட்டங்கள், பக்க துண்டுகள், பனோரமாக்கள் மற்றும் ஆர்டர் செய்யப்பட்ட சமூக இடுகைகளுக்கு பயனுள்ளதாக இருக்கும். + மூலப் படத்தைத் தேர்வுசெய்து, உங்களுக்குத் தேவையான வரிசைகள் மற்றும் நெடுவரிசைகளின் எண்ணிக்கையை அமைக்கவும். + துணுக்குகள் அனைத்தும் சம அளவு இருக்கக்கூடாது எனும்போது வரிசை அல்லது நெடுவரிசை சதவீதங்களை சரிசெய்யவும். + சேமிக்கும் முன் வெளியீட்டு வடிவம் மற்றும் தரத்தைத் தேர்ந்தெடுக்கவும், அதனால் உருவாக்கப்பட்ட ஒவ்வொரு துண்டும் ஒரே ஏற்றுமதி விதிகளைப் பயன்படுத்துகிறது. + பட கீற்றுகளை வெட்டுங்கள் + செங்குத்து அல்லது கிடைமட்ட பகுதிகளை அகற்றி, மீதமுள்ள பகுதிகளை மீண்டும் ஒன்றாக இணைக்கவும் + இடைவெளி விடாமல் ஒரு பகுதியை அகற்றவும் + இமேஜ் கட்டிங் என்பது சாதாரண பயிரிலிருந்து வேறுபட்டது. இது தேர்ந்தெடுக்கப்பட்ட செங்குத்து அல்லது கிடைமட்ட துண்டுகளை அகற்றி, மீதமுள்ள பட பகுதிகளை ஒன்றாக இணைக்கலாம். தலைகீழ் பயன்முறையானது தேர்ந்தெடுக்கப்பட்ட பகுதியைப் பதிலாக வைத்திருக்கிறது, மேலும் இரண்டு தலைகீழ் திசைகளையும் பயன்படுத்துவது தேர்ந்தெடுக்கப்பட்ட செவ்வகத்தை வைத்திருக்கிறது. + பக்க பகுதிகள், நெடுவரிசைகள் அல்லது நடுத்தர கீற்றுகளுக்கு செங்குத்து வெட்டு பயன்படுத்தவும்; பேனர்கள், இடைவெளிகள் அல்லது வரிசைகளுக்கு கிடைமட்ட வெட்டு பயன்படுத்தவும். + தேர்ந்தெடுக்கப்பட்ட பகுதியை அகற்றுவதற்குப் பதிலாக, அச்சில் தலைகீழாக இயக்கவும். + வெட்டிய பின் விளிம்புகளை முன்னோட்டமிடவும், ஏனெனில் அகற்றப்பட்ட பகுதி முக்கியமான விவரங்களைக் கடக்கும்போது ஒன்றிணைக்கப்பட்ட உள்ளடக்கம் திடீரெனத் தோன்றும். + வெளியீட்டு வடிவமைப்பைத் தேர்ந்தெடுக்கவும் + உள்ளடக்கம் மற்றும் சேருமிடத்தின் அடிப்படையில் JPEG, PNG, WebP, AVIF, JXL அல்லது PDF ஐத் தேர்ந்தெடுக்கவும் + சேருமிடம் வடிவமைப்பை தீர்மானிக்கட்டும் + சிறந்த வடிவம், படத்தில் என்ன உள்ளது மற்றும் அது எங்கு பயன்படுத்தப்படும் என்பதைப் பொறுத்தது. புகைப்படங்கள், ஸ்கிரீன் ஷாட்கள், வெளிப்படையான சொத்துக்கள், ஆவணங்கள் மற்றும் அனிமேஷன் செய்யப்பட்ட கோப்புகள் அனைத்தும் தவறான வடிவமைப்பிற்குள் கட்டாயப்படுத்தப்படும்போது வெவ்வேறு வழிகளில் தோல்வியடைகின்றன. + வெளிப்படைத்தன்மை மற்றும் துல்லியமான பிக்சல்கள் தேவைப்படாதபோது பரந்த புகைப்பட இணக்கத்தன்மைக்கு JPEG ஐப் பயன்படுத்தவும். + கூர்மையான ஸ்கிரீன் ஷாட்கள், UI பிடிப்புகள், வெளிப்படையான கிராபிக்ஸ் மற்றும் இழப்பற்ற திருத்தங்களுக்கு PNG ஐப் பயன்படுத்தவும். + கச்சிதமான நவீன வெளியீடு முக்கியமான மற்றும் பெறும் பயன்பாடு அதை ஆதரிக்கும் போது WebP, AVIF அல்லது JXL ஐப் பயன்படுத்தவும். + ஆடியோ கவர்களை பிரித்தெடுக்கவும் + உட்பொதிக்கப்பட்ட ஆல்பம் கலை அல்லது ஆடியோ கோப்புகளிலிருந்து கிடைக்கும் மீடியா பிரேம்களை PNG படங்களாக சேமிக்கவும் + ஆடியோ கோப்புகளிலிருந்து கலைப்படைப்புகளை வெளியே இழுக்கவும் + ஆடியோ கோப்புகளிலிருந்து உட்பொதிக்கப்பட்ட கலைப்படைப்புகளைப் படிக்க ஆடியோ கவர்கள் Android மீடியா மெட்டாடேட்டாவைப் பயன்படுத்துகின்றன. உட்பொதிக்கப்பட்ட கலைப்படைப்பு கிடைக்காதபோது, ​​ஆண்ட்ராய்டு ஒன்றை வழங்க முடிந்தால், மீடியா சட்டகத்திற்கு திரும்பவும் திரும்பப் பெறலாம்; எதுவும் இல்லை என்றால், பிரித்தெடுக்க படமில்லை என்று கருவி தெரிவிக்கிறது. + ஆடியோ அட்டைகளைத் திறந்து, எதிர்பார்க்கப்படும் கவர் கலையுடன் ஒன்று அல்லது அதற்கு மேற்பட்ட ஆடியோ கோப்புகளைத் தேர்ந்தெடுக்கவும். + குறிப்பாக ஸ்ட்ரீமிங் அல்லது ரெக்கார்டிங் ஆப்ஸின் கோப்புகளை சேமிப்பதற்கு அல்லது பகிர்வதற்கு முன் பிரித்தெடுக்கப்பட்ட அட்டையை மதிப்பாய்வு செய்யவும். + படம் எதுவும் கிடைக்கவில்லை என்றால், ஆடியோ கோப்பில் உட்பொதிக்கப்பட்ட கவர் இருக்காது அல்லது ஆண்ட்ராய்டு பயன்படுத்தக்கூடிய சட்டகத்தை வெளிப்படுத்த முடியாது. + தேதிகள் மற்றும் இருப்பிட மெட்டாடேட்டா + பிடிப்பு நேரம் முக்கியமானதாக இருக்கும்போது எதைப் பாதுகாக்க வேண்டும் என்பதைத் தீர்மானிக்கவும், ஆனால் ஜிபிஎஸ் அல்லது சாதனத் தரவு கசியக்கூடாது + தனிப்பட்ட மெட்டாடேட்டாவிலிருந்து பயனுள்ள மெட்டாடேட்டாவைப் பிரிக்கவும் + மெட்டாடேட்டா அனைத்தும் சமமாக ஆபத்தானது அல்ல. பிடிப்பு தேதிகள் காப்பகங்கள் மற்றும் வரிசைப்படுத்துவதற்கு பயனுள்ளதாக இருக்கும், அதே நேரத்தில் GPS, சாதனத்தின் தொடர் போன்ற புலங்கள், மென்பொருள் குறிச்சொற்கள் அல்லது உரிமையாளர் புலங்கள் ஆகியவை நோக்கம் கொண்டதை விட அதிகமாக வெளிப்படுத்தலாம். + ஆல்பங்கள், ஸ்கேன்கள் அல்லது திட்ட வரலாறு ஆகியவற்றுக்கு காலவரிசை முக்கியமானதாக இருக்கும் போது தேதி மற்றும் நேரக் குறிச்சொற்களை வைத்திருங்கள். + பொதுப் பகிர்வு அல்லது அந்நியர்களுக்கு படங்களை அனுப்பும் முன் GPS மற்றும் சாதனத்தை அடையாளப்படுத்தும் குறிச்சொற்களை அகற்றவும். + தனியுரிமைத் தேவைகள் கடுமையாக இருக்கும்போது மாதிரியை ஏற்றுமதி செய்து, EXIFஐ மீண்டும் சரிபார்க்கவும். + கோப்பு பெயர் மோதல்களைத் தவிர்க்கவும் + பல கோப்புகள் image.jpg அல்லது screenshot.png போன்ற பெயர்களைப் பகிரும்போது தொகுதி வெளியீடுகளைப் பாதுகாக்கவும் + ஒவ்வொரு வெளியீட்டு பெயரையும் தனித்துவமாக்குங்கள் + மெசஞ்சர்கள், ஸ்கிரீன் ஷாட்கள், கிளவுட் ஃபோல்டர்கள் அல்லது பல கேமராக்களிலிருந்து படங்கள் வரும்போது நகல் கோப்புப் பெயர்கள் பொதுவானவை. ஒரு நல்ல வடிவமானது தற்செயலான மாற்றத்தைத் தடுக்கிறது மற்றும் வெளியீடுகளை அவற்றின் மூலங்களுக்குத் திரும்பக் கண்டறியக்கூடியதாக வைத்திருக்கிறது. + திருத்தப்பட்ட நகல் அடையாளம் காணக்கூடியதாக இருக்கும்போது அசல் பெயரையும் பின்னொட்டையும் சேர்க்கவும். + பெரிய கலப்புத் தொகுதிகளைச் செயலாக்கும்போது வரிசை, நேர முத்திரை, முன்னமைவு அல்லது பரிமாணங்களைச் சேர்க்கவும். + பெயரிடும் முறை மோத முடியாது என்பதை நீங்கள் சரிபார்க்கும் வரை மேலெழுதும் பணிப்பாய்வுகளைத் தவிர்க்கவும். + சேமிக்கவும் அல்லது பகிரவும் + நிலையான கோப்பு மற்றும் மற்றொரு பயன்பாட்டிற்கு தற்காலிக கையேடு ஆகியவற்றிற்கு இடையே தேர்வு செய்யவும் + சரியான நோக்கத்துடன் ஏற்றுமதி செய்யுங்கள் + சேமி மற்றும் பகிர் ஒரே மாதிரியாக உணரலாம், ஆனால் அவை வெவ்வேறு சிக்கல்களைத் தீர்க்கின்றன. சேமிப்பதன் மூலம் நீங்கள் பின்னர் கண்டுபிடிக்கக்கூடிய கோப்பை உருவாக்குகிறது; பகிர்தல் ஆண்ட்ராய்டு மூலம் உருவாக்கப்பட்ட முடிவை வேறொரு பயன்பாட்டிற்கு அனுப்புகிறது மற்றும் நீடித்த உள்ளூர் நகலை விடாமல் போகலாம். + வெளியீடு தெரிந்த கோப்புறையில் இருக்க வேண்டும், காப்புப் பிரதி எடுக்கப்பட வேண்டும் அல்லது பின்னர் மீண்டும் பயன்படுத்தப்பட வேண்டும் என்றால் சேமி என்பதைப் பயன்படுத்தவும். + விரைவான செய்திகள், மின்னஞ்சல் இணைப்புகள், சமூக இடுகைகள் அல்லது முடிவை மற்றொரு எடிட்டருக்கு அனுப்ப பகிர்வைப் பயன்படுத்தவும். + பெறும் பயன்பாடு நம்பகத்தன்மையற்றதாக இருக்கும்போது அல்லது தோல்வியுற்ற பகிர்வு மீண்டும் உருவாக்க எரிச்சலூட்டும் போது முதலில் சேமிக்கவும். + PDF பக்க அளவு மற்றும் ஓரங்கள் + ஆவணத்தை உருவாக்கும் முன் காகித அளவு, பொருத்தமான நடத்தை மற்றும் சுவாச அறை ஆகியவற்றைத் தேர்ந்தெடுக்கவும் + படப் பக்கங்களை அச்சிடக்கூடியதாகவும் படிக்கக்கூடியதாகவும் ஆக்குங்கள் + தேர்ந்தெடுக்கப்பட்ட காகித அளவுடன் அவற்றின் வடிவம் பொருந்தாதபோது படங்கள் மோசமான PDF பக்கங்களாக மாறும். விளிம்புகள், பொருத்தம் முறை மற்றும் பக்க நோக்குநிலை ஆகியவை உள்ளடக்கம் செதுக்கப்பட்டதா, சிறியதா, நீட்டிக்கப்பட்டதா அல்லது படிக்க வசதியாக உள்ளதா என்பதை தீர்மானிக்கிறது. + PDF அச்சிடப்படும் அல்லது படிவத்தில் சமர்ப்பிக்கப்படும் போது உண்மையான காகித அளவைப் பயன்படுத்தவும். + ஸ்கேன் மற்றும் ஸ்கிரீன் ஷாட்களுக்கு விளிம்புகளைப் பயன்படுத்தவும், இதனால் உரை பக்கத்தின் விளிம்பிற்கு எதிராக இருக்கக்கூடாது. + மூலப் படங்கள் கலவையான நோக்குநிலையைக் கொண்டிருக்கும்போது உருவப்படம் மற்றும் இயற்கைப் பக்கங்களை ஒன்றாக முன்னோட்டமிடுங்கள். + ஸ்கேன்களை சுத்தம் செய்யவும் + PDF அல்லது OCRக்கு முன் காகித விளிம்புகள், நிழல்கள், மாறுபாடு, சுழற்சி மற்றும் வாசிப்புத்திறனை மேம்படுத்தவும் + காப்பகப்படுத்துவதற்கு முன் பக்கங்களைத் தயாரிக்கவும் + ஸ்கேன் தொழில்நுட்ப ரீதியாகப் பிடிக்கப்படலாம், ஆனால் படிக்க கடினமாக உள்ளது. சுருக்க அமைப்புகளை விட, குறிப்பாக ரசீதுகள், கையால் எழுதப்பட்ட குறிப்புகள் மற்றும் குறைந்த வெளிச்சம் உள்ள பக்கங்களுக்கு PDF ஏற்றுமதிக்கு முன் சிறிய சுத்தம் செய்யும் படிகள் அதிகம். + சரியான கண்ணோட்டம் மற்றும் மேசை விளிம்புகள், விரல்கள், நிழல்கள் மற்றும் பின்னணி ஒழுங்கீனம் ஆகியவற்றை வெட்டவும். + முத்திரைகள், கையொப்பங்கள் அல்லது பென்சில் குறிகளை அழிக்காமல் உரை தெளிவாகும். + பக்கம் நிமிர்ந்து படிக்கக்கூடியதாக இருந்த பின்னரே OCR ஐ இயக்கவும், பின்னர் முக்கியமான எண்களை கைமுறையாக சரிபார்க்கவும். + PDFகளை சுருக்கவும், கிரேஸ்கேல் அல்லது பழுதுபார்க்கவும் + இலகுவான, அச்சிடக்கூடிய அல்லது மறுகட்டமைக்கப்பட்ட ஆவண நகல்களுக்கு ஒரு கோப்பு PDF செயல்பாடுகளைப் பயன்படுத்தவும் + PDF சுத்திகரிப்பு செயல்பாட்டைத் தேர்ந்தெடுக்கவும் + PDF கருவிகள் சுருக்க, கிரேஸ்கேல் மாற்றம் மற்றும் பழுதுபார்ப்பிற்கான தனி செயல்பாடுகளை உள்ளடக்கியது. சுருக்கம் மற்றும் கிரேஸ்கேல் முக்கியமாக உட்பொதிக்கப்பட்ட படங்கள் மூலம் வேலை செய்கின்றன, அதே சமயம் பழுது PDF கட்டமைப்பை மீண்டும் உருவாக்குகிறது; இவை எதுவும் ஒவ்வொரு ஆவணத்திற்கும் உத்தரவாதமான தீர்வாக கருதப்படக்கூடாது. + ஆவணத்தில் படங்கள் மற்றும் கோப்பு அளவு விஷயங்களைக் கொண்டிருக்கும் போது, ​​கம்ப்ரஸ் PDFஐப் பயன்படுத்தவும். + ஆவணம் அச்சிடுவதற்கு எளிதாக இருக்க வேண்டும் அல்லது வண்ணப் படங்கள் தேவையில்லை எனும்போது கிரேஸ்கேலைப் பயன்படுத்தவும். + ஒரு ஆவணம் திறக்கத் தவறினால் அல்லது உள் கட்டமைப்பை சேதப்படுத்தினால், நகலில் பழுதுபார்க்கும் PDF ஐப் பயன்படுத்தவும், பின்னர் மீண்டும் கட்டமைக்கப்பட்ட கோப்பை சரிபார்க்கவும். + PDF களில் இருந்து படங்களை பிரித்தெடுக்கவும் + உட்பொதிக்கப்பட்ட PDF படங்களை மீட்டெடுத்து, பிரித்தெடுக்கப்பட்ட முடிவுகளை ஒரு காப்பகத்தில் பேக் செய்யவும் + ஆவணங்களிலிருந்து மூலப் படங்களைச் சேமிக்கவும் + எக்ஸ்ட்ராக்ட் இமேஜஸ் உட்பொதிக்கப்பட்ட படப் பொருள்களுக்காக PDF ஐ ஸ்கேன் செய்கிறது, சாத்தியமான இடங்களில் நகல்களைத் தவிர்க்கிறது மற்றும் உருவாக்கப்பட்ட ZIP காப்பகத்தில் மீட்டெடுக்கப்பட்ட படங்களை சேமிக்கிறது. இது உண்மையில் PDF இல் உட்பொதிக்கப்பட்ட படங்களை மட்டுமே பிரித்தெடுக்கிறது, உரை, திசையன் வரைபடங்கள் அல்லது ஸ்கிரீன்ஷாட்டாக தெரியும் ஒவ்வொரு பக்கத்தையும் அல்ல. + பட்டியலிலிருந்து புகைப்படங்கள் அல்லது ஸ்கேன் செய்யப்பட்ட சொத்துக்கள் போன்ற PDFக்குள் சேமிக்கப்படும் படங்கள் தேவைப்படும்போது பிரித்தெடுத்தல் படங்களைப் பயன்படுத்தவும். + உரை மற்றும் தளவமைப்பு உட்பட முழுப் பக்கங்கள் தேவைப்படும்போது அதற்குப் பதிலாக படங்களுக்கு PDF அல்லது பக்கத்தைப் பிரித்தெடுக்கவும். + கருவி உட்பொதிக்கப்பட்ட படங்கள் எதுவும் இல்லை எனில், பக்கம் உரை/வெக்டார் உள்ளடக்கமாக இருக்கலாம் அல்லது படங்கள் பிரித்தெடுக்கக்கூடிய பொருட்களாக சேமிக்கப்படாமல் இருக்கலாம். + மெட்டாடேட்டா, தட்டையாக்குதல் மற்றும் அச்சு வெளியீடு + ஆவணப் பண்புகளைத் திருத்தவும், பக்கங்களைத் திருத்தவும் அல்லது தனிப்பயன் அச்சு தளவமைப்புகளை வேண்டுமென்றே தயார் செய்யவும் + இறுதி ஆவணச் செயலைத் தேர்ந்தெடுக்கவும் + PDF மெட்டாடேட்டா, தட்டையாக்குதல் மற்றும் அச்சுத் தயாரித்தல் ஆகியவை வெவ்வேறு இறுதிக் கட்டச் சிக்கல்களைத் தீர்க்கின்றன. மெட்டாடேட்டா ஆவணப் பண்புகளைக் கட்டுப்படுத்துகிறது, பிளாட்டன் PDF ஆனது குறைவான திருத்தக்கூடிய வெளியீடாக பக்கங்களைத் தரப்படுத்துகிறது, மேலும் அச்சு PDF ஆனது பக்க அளவு, நோக்குநிலை, விளிம்பு மற்றும் பக்கங்கள்-தாள் கட்டுப்பாடுகளுடன் புதிய தளவமைப்பைத் தயாரிக்கிறது. + ஆசிரியர், தலைப்பு, முக்கிய வார்த்தைகள், தயாரிப்பாளர் அல்லது தனியுரிமை சுத்திகரிப்பு முக்கியமானதாக இருக்கும்போது மெட்டாடேட்டாவைப் பயன்படுத்தவும். + காணக்கூடிய பக்க உள்ளடக்கம் தனித்தனி PDF பொருள்களாகத் திருத்த கடினமாக இருக்கும் போது Flatten PDF ஐப் பயன்படுத்தவும். + தனிப்பயன் பக்க அளவு, நோக்குநிலை, ஓரங்கள் அல்லது ஒரு தாளுக்கு பல பக்கங்கள் தேவைப்படும்போது PDF ஐ அச்சிடுக. + OCR க்கு படங்களைத் தயாரிக்கவும் + உரையைப் படிக்கும்படி OCR ஐக் கேட்கும் முன், செதுக்குதல், சுழற்சி, மாறுபாடு, மறைத்தல் மற்றும் அளவைப் பயன்படுத்தவும் + OCR கிளீனர் பிக்சல்களைக் கொடுங்கள் + OCR தவறுகள் பெரும்பாலும் OCR இயந்திரத்தை விட உள்ளீட்டு படத்திலிருந்து வரும். வளைந்த பக்கங்கள், குறைந்த மாறுபாடு, தெளிவின்மை, நிழல்கள், சிறிய உரை மற்றும் கலவையான பின்னணிகள் அனைத்தும் அங்கீகாரத் தரத்தைக் குறைக்கின்றன. + உரை பகுதிக்கு செதுக்கி படத்தை சுழற்றவும், அதனால் கோடுகள் அங்கீகாரத்திற்கு முன் கிடைமட்டமாக இருக்கும். + எழுத்துகளை எளிதாகப் படிக்கும் போது மட்டுமே மாறுபாட்டை அதிகரிக்கவும் அல்லது தூய்மையான கருப்பு-வெள்ளைக்கு மாற்றவும். + சிறிய உரையை மிதமாக மேல்நோக்கி அளவை மாற்றவும், ஆனால் போலியான விளிம்புகளை உருவாக்கும் தீவிரமான கூர்மைப்படுத்துதலைத் தவிர்க்கவும். + QR உள்ளடக்கத்தைச் சரிபார்க்கவும் + குறியீட்டை நம்புவதற்கு முன் இணைப்புகள், வைஃபை நற்சான்றிதழ்கள், தொடர்புகள் மற்றும் செயல்களை மதிப்பாய்வு செய்யவும் + டிகோட் செய்யப்பட்ட உரையை நம்பத்தகாத உள்ளீடாகக் கருதுங்கள் + QR குறியீடு URL, தொடர்பு அட்டை, Wi-Fi கடவுச்சொல், காலண்டர் நிகழ்வு, தொலைபேசி எண் அல்லது எளிய உரை ஆகியவற்றை மறைக்க முடியும். குறியீட்டிற்கு அருகில் உள்ள காணக்கூடிய லேபிள் உண்மையான பேலோடுடன் பொருந்தாமல் இருக்கலாம், எனவே டிகோட் செய்யப்பட்ட உள்ளடக்கத்தைச் செயல்படுத்தும் முன் அதை மதிப்பாய்வு செய்யவும். + முழு டிகோட் செய்யப்பட்ட URL ஐ திறக்கும் முன், குறிப்பாக சுருக்கப்பட்ட அல்லது அறிமுகமில்லாத டொமைன்களை ஆய்வு செய்யவும். + வைஃபை, தொடர்பு, காலண்டர், ஃபோன் மற்றும் எஸ்எம்எஸ் குறியீடுகளில் கவனமாக இருக்கவும், ஏனெனில் அவை முக்கியமான செயல்களைத் தூண்டும். + உள்ளடக்கத்தை உடனடியாகத் திறப்பதற்குப் பதிலாக வேறொரு இடத்தில் சரிபார்க்க வேண்டியிருக்கும் போது முதலில் அதை நகலெடுக்கவும். + QR குறியீடுகளை உருவாக்கவும் + எளிய உரை, URLகள், Wi-Fi, தொடர்புகள், மின்னஞ்சல், தொலைபேசி, SMS மற்றும் இருப்பிடத் தரவு ஆகியவற்றிலிருந்து குறியீடுகளை உருவாக்கவும் + சரியான QR பேலோடை உருவாக்கவும் + QR திரையில் உள்ளிடப்பட்ட உள்ளடக்கத்திலிருந்து குறியீடுகளை உருவாக்கலாம் அத்துடன் ஏற்கனவே உள்ளவற்றை ஸ்கேன் செய்யலாம். கட்டமைக்கப்பட்ட QR வகைகள், Wi-Fi உள்நுழைவு விவரங்கள், தொடர்பு அட்டைகள், மின்னஞ்சல் புலங்கள், தொலைபேசி எண்கள், SMS உள்ளடக்கம், URLகள் அல்லது புவியியல் ஒருங்கிணைப்புகள் போன்ற பிற பயன்பாடுகள் புரிந்துகொள்ளும் வடிவத்தில் தரவை குறியாக்க உதவுகின்றன. + எளிய குறிப்புகளுக்கு எளிய உரையைத் தேர்வுசெய்யவும் அல்லது குறியீடு Wi-Fi, தொடர்பு, URL, மின்னஞ்சல், தொலைபேசி, SMS அல்லது இருப்பிடத் தரவாகத் திறக்கப்படும்போது கட்டமைக்கப்பட்ட வகையைப் பயன்படுத்தவும். + உருவாக்கப்பட்ட குறியீட்டைப் பகிர்வதற்கு முன் ஒவ்வொரு புலத்தையும் சரிபார்க்கவும், ஏனெனில் தெரியும் முன்னோட்டமானது நீங்கள் உள்ளிட்ட பேலோடை மட்டுமே பிரதிபலிக்கிறது. + சேமிக்கப்பட்ட QR குறியீட்டை ஸ்கேனர் மூலம் சோதிக்கவும், அது அச்சிடப்படும் போது, ​​பொதுவில் இடுகையிடப்படும் அல்லது பிறரால் பயன்படுத்தப்படும். + செக்சம் பயன்முறையைத் தேர்ந்தெடுக்கவும் + கோப்புகள் அல்லது உரையிலிருந்து ஹாஷ்களைக் கணக்கிடுங்கள், ஒரு கோப்பை ஒப்பிட்டுப் பாருங்கள் அல்லது பல கோப்புகளைச் சரிபார்க்கவும் + தோற்றத்தை அல்ல, உள்ளடக்கத்தைச் சரிபார்க்கவும் + செக்சம் டூல்ஸ் கோப்பு ஹேஷிங், டெக்ஸ்ட் ஹாஷிங், தெரிந்த ஹாஷுடன் ஒரு கோப்பை ஒப்பிடுதல் மற்றும் தொகுதி ஒப்பீடு ஆகியவற்றிற்கு தனித்தனி தாவல்களைக் கொண்டுள்ளது. தேர்ந்தெடுக்கப்பட்ட அல்காரிதத்திற்கான பைட்-க்கு-பைட் அடையாளத்தை ஒரு செக்சம் நிரூபிக்கிறது; இரண்டு படங்கள் ஒரே மாதிரியானவை என்பதை நிரூபிக்கவில்லை. + கோப்புகளுக்கான கணக்கீடு மற்றும் தட்டச்சு செய்த அல்லது ஒட்டப்பட்ட உரைத் தரவுகளுக்கு உரை ஹாஷ் ஆகியவற்றைப் பயன்படுத்தவும். + ஒரு கோப்பிற்கான எதிர்பார்க்கப்படும் செக்சம் ஒன்றை யாராவது உங்களுக்கு வழங்கும்போது ஒப்பிடு பயன்படுத்தவும். + ஒரே பாஸில் பல கோப்புகளுக்கு ஹாஷ்கள் அல்லது சரிபார்ப்பு தேவைப்படும்போது Batch Compare ஐப் பயன்படுத்தவும், பிறகு தேர்ந்தெடுத்த அல்காரிதத்தை சீராக வைத்திருக்கவும். + கிளிப்போர்டு தனியுரிமை + நகலெடுக்கப்பட்ட தனிப்பட்ட தரவை தற்செயலாக வெளிப்படுத்தாமல் தானியங்கி கிளிப்போர்டு அம்சங்களைப் பயன்படுத்தவும் + நகலெடுக்கப்பட்ட உள்ளடக்கத்தை வேண்டுமென்றே வைத்திருங்கள் + கிளிப்போர்டு ஆட்டோமேஷன் வசதியானது, ஆனால் நகலெடுக்கப்பட்ட உரையில் கடவுச்சொற்கள், இணைப்புகள், முகவரிகள், டோக்கன்கள் அல்லது தனிப்பட்ட குறிப்புகள் இருக்கலாம். உங்கள் பழக்கவழக்கங்கள் மற்றும் தனியுரிமை எதிர்பார்ப்புகளுடன் பொருந்தக்கூடிய வேக அம்சமாக கிளிப்போர்டு அடிப்படையிலான உள்ளீட்டைக் கருதுங்கள். + எதிர்பாராதவிதமாக கருவிகளில் தனிப்பட்ட உரை தோன்றினால், தானியங்கி கிளிப்போர்டு பயன்பாட்டை முடக்கவும். + நீங்கள் வேண்டுமென்றே முடிவைச் சேமிப்பதைத் தவிர்க்க விரும்பினால் மட்டுமே கிளிப்போர்டுக்கு மட்டும் சேமிப்பைப் பயன்படுத்தவும். + முக்கியமான உரை, QR தரவு அல்லது Base64 பேலோடுகளைக் கையாண்ட பிறகு கிளிப்போர்டை அழிக்கவும் அல்லது மாற்றவும். + வண்ண வடிவங்கள் + மற்ற இடங்களில் ஒட்டுவதற்கு முன் HEX, RGB, HSV, ஆல்பா மற்றும் நகலெடுக்கப்பட்ட வண்ண மதிப்புகளைப் புரிந்து கொள்ளுங்கள் + இலக்கு எதிர்பார்க்கும் வடிவமைப்பை நகலெடுக்கவும் + ஒரே புலப்படும் நிறத்தை பல வழிகளில் எழுதலாம். ஒரு வடிவமைப்புக் கருவி, CSS புலம், ஆண்ட்ராய்டு வண்ண மதிப்பு அல்லது பட எடிட்டர் வேறுபட்ட வரிசை, ஆல்பா கையாளுதல் அல்லது வண்ண மாதிரியை எதிர்பார்க்கலாம். + கச்சிதமான வண்ணக் குறியீடுகளை எதிர்பார்க்கும் இணையம், வடிவமைப்பு அல்லது தீம் புலங்களில் ஒட்டும்போது HEXஐப் பயன்படுத்தவும். + சேனல்கள், பிரகாசம், செறிவு அல்லது சாயலை கைமுறையாக சரிசெய்ய வேண்டியிருக்கும் போது RGB அல்லது HSV ஐப் பயன்படுத்தவும். + வெளிப்படைத்தன்மை முக்கியமானதாக இருக்கும்போது ஆல்பாவைத் தனித்தனியாகச் சரிபார்க்கவும், ஏனெனில் சில இடங்கள் அதைப் புறக்கணிக்கின்றன அல்லது வேறு வரிசையைப் பயன்படுத்துகின்றன. + வண்ண நூலகத்தில் உலாவவும் + பெயரிடப்பட்ட வண்ணங்களை பெயர் அல்லது ஹெக்ஸ் மூலம் தேடி, பிடித்தவற்றை மேலே வைக்கவும் + மீண்டும் பயன்படுத்தக்கூடிய வண்ணங்களைக் கண்டறியவும் + வண்ண நூலகம் பெயரிடப்பட்ட வண்ணத் தொகுப்பை ஏற்றுகிறது, பெயரால் வரிசைப்படுத்துகிறது, வண்ணப் பெயர் அல்லது ஹெக்ஸ் மதிப்பின்படி வடிகட்டுகிறது, மேலும் பிடித்த வண்ணங்களைச் சேமிக்கிறது, எனவே முக்கியமான உள்ளீடுகள் எளிதில் சென்றடையும். படத்தில் இருந்து மாதிரி எடுப்பதற்குப் பதிலாக உண்மையான பெயரிடப்பட்ட வண்ணம் தேவைப்படும்போது இது பயனுள்ளதாக இருக்கும். + சிவப்பு, நீலம், ஸ்லேட் அல்லது புதினா போன்ற குடும்பம் உங்களுக்குத் தெரிந்தால் வண்ணப் பெயரைக் கொண்டு தேடவும். + உங்களிடம் ஏற்கனவே எண் வண்ணம் இருந்தால், பொருத்தமான அல்லது அருகிலுள்ள பெயரிடப்பட்ட உள்ளீடுகளைக் கண்டறிய விரும்பினால், HEX மூலம் தேடவும். + அடிக்கடி வண்ணங்களைப் பிடித்தவையாகக் குறிக்கவும், அதனால் உலாவும்போது அவை பொது நூலகத்திற்கு முன்னால் நகரும். + கண்ணி சாய்வு சேகரிப்புகளைப் பயன்படுத்தவும் + கிடைக்கும் மெஷ் கிரேடியன்ட் ஆதாரங்களைப் பதிவிறக்கி, தேர்ந்தெடுத்த படங்களைப் பகிரவும் + ரிமோட் மெஷ் கிரேடியண்ட் சொத்துகளைப் பயன்படுத்தவும் + Mesh Gradients தொலைநிலை ஆதார சேகரிப்பை ஏற்றலாம், விடுபட்ட கிரேடியன்ட் படங்களைப் பதிவிறக்கலாம், பதிவிறக்க முன்னேற்றத்தைக் காட்டலாம் மற்றும் தேர்ந்தெடுக்கப்பட்ட சாய்வுகளைப் பகிரலாம். நெட்வொர்க் அணுகல் மற்றும் ரிமோட் ரிசோர்ஸ் ஸ்டோரைப் பொறுத்து கிடைப்பது தங்கியுள்ளது, எனவே வெற்றுப் பட்டியல் பொதுவாக ஆதாரங்கள் இன்னும் கிடைக்கவில்லை என்று அர்த்தம். + நீங்கள் ஆயத்த கண்ணி சாய்வு படங்களை விரும்பும் போது சாய்வு கருவிகளில் இருந்து Mesh Gradients ஐ திறக்கவும். + சேகரிப்பு காலியாக இருப்பதாகக் கருதும் முன், ஆதார ஏற்றுதல் அல்லது பதிவிறக்க முன்னேற்றத்திற்காக காத்திருக்கவும். + உங்களுக்குத் தேவையான சாய்வுகளைத் தேர்ந்தெடுத்துப் பகிரவும் அல்லது தனிப்பயன் சாய்வை கைமுறையாக உருவாக்க விரும்பும் போது கிரேடியன்ட் மேக்கருக்குத் திரும்பவும். + உருவாக்கப்பட்ட சொத்து தீர்மானம் + சாய்வுகள், இரைச்சல், வால்பேப்பர்கள், SVG போன்ற கலை அல்லது அமைப்புகளை உருவாக்கும் முன் வெளியீட்டின் அளவைத் தேர்ந்தெடுக்கவும் + உங்களுக்கு தேவையான அளவில் உருவாக்கவும் + உருவாக்கப்பட்ட படங்களில் மீண்டும் விழுவதற்கு அசல் கேமரா இல்லை. வெளியீடு மிகவும் சிறியதாக இருந்தால், பின்னர் அளவிடுதல் வடிவங்களை மென்மையாக்கலாம், விவரங்களை மாற்றலாம் அல்லது சொத்து டைல்ஸ் மற்றும் சுருக்கங்களை மாற்றலாம். + வால்பேப்பர், ஐகான், பின்னணி, அச்சு அல்லது மேலடுக்கு போன்ற இறுதிப் பயன்பாட்டுக்கான பரிமாணங்களை முதலில் அமைக்கவும். + செதுக்கப்பட்ட, பெரிதாக்கப்பட்ட அல்லது பல தளவமைப்புகளில் மீண்டும் பயன்படுத்தக்கூடிய அமைப்புகளுக்கு பெரிய அளவுகளைப் பயன்படுத்தவும். + சோதனைப் பதிப்புகளை பெரிய வெளியீடுகளுக்கு முன் ஏற்றுமதி செய்யவும், ஏனெனில் செயல்முறை விவரங்கள் சுருக்க எவ்வாறு செயல்படுகிறது என்பதை மாற்றும். + தற்போதைய வால்பேப்பர்களை ஏற்றுமதி செய்யவும் + சாதனத்திலிருந்து கிடைக்கும் முகப்பு, பூட்டு மற்றும் உள்ளமைக்கப்பட்ட வால்பேப்பர்களை மீட்டெடுக்கவும் + நிறுவப்பட்ட வால்பேப்பர்களைச் சேமிக்கவும் அல்லது பகிரவும் + கணினி வால்பேப்பர் APIகள் மூலம் Android வெளிப்படுத்தும் வால்பேப்பர்களை வால்பேப்பர்கள் ஏற்றுமதி படிக்கிறது. சாதனம், ஆண்ட்ராய்டு பதிப்பு, அனுமதிகள் மற்றும் உருவாக்க மாறுபாட்டைப் பொறுத்து, வீடு, பூட்டு அல்லது உள்ளமைக்கப்பட்ட வால்பேப்பர்கள் தனித்தனியாகக் கிடைக்கலாம் அல்லது விடுபட்டிருக்கலாம். + வால்பேப்பர்களை ஏற்றுவதற்கு வால்பேப்பர்கள் ஏற்றுமதியைத் திறக்கவும், கணினி பயன்பாட்டைப் படிக்க அனுமதிக்கிறது. + நீங்கள் சேமிக்க, நகலெடுக்க அல்லது பகிர விரும்பும் வீடு, பூட்டு அல்லது உள்ளமைக்கப்பட்ட வால்பேப்பர் உள்ளீடுகளைத் தேர்ந்தெடுக்கவும். + வால்பேப்பர் காணவில்லை அல்லது அம்சம் கிடைக்கவில்லை என்றால், இது பொதுவாக எடிட்டிங் அமைப்பைக் காட்டிலும் அமைப்பு, அனுமதி அல்லது உருவாக்க-மாறுபாடு வரம்பு. + கார்னர்ஸ் அனிமேஷன் த்ரோட்டில் + வண்ணத்தை நகலெடுக்கவும் + ஹெக்ஸ் + பெயர் + மென்மையான முடி மற்றும் விளிம்பு கையாளுதலுடன் கூடிய வேகமான நபர் கட்அவுட்களுக்கான போர்ட்ரெய்ட் மேட்டிங் மாடல். + ஜீரோ-டிசிஇ++ வளைவு மதிப்பீட்டைப் பயன்படுத்தி வேகமான குறைந்த-ஒளி மேம்பாடு. + மழைக் கோடுகள் மற்றும் ஈரமான வானிலை கலைப்பொருட்களைக் குறைப்பதற்கான ரெஸ்டார்மர் பட மறுசீரமைப்பு மாதிரி. + ஒரு ஒருங்கிணைப்பு கட்டத்தை முன்னறிவிக்கும் மற்றும் வளைந்த பக்கங்களை பிளாட்டர் ஸ்கேனில் மறுவடிவமைக்கும் ஆவண அன்வார்ப்பிங் மாதிரி. + இயக்க கால அளவு + பயன்பாட்டு அனிமேஷன் வேகத்தைக் கட்டுப்படுத்துகிறது: 0 இயக்கத்தை முடக்குகிறது, 1 இயல்பானது, அதிக மதிப்புகள் அனிமேஷன்களை மெதுவாக்கும் + சமீபத்திய கருவிகளைக் காட்டு + பிரதான திரையில் சமீபத்தில் பயன்படுத்தப்பட்ட கருவிகளுக்கான விரைவான அணுகலைக் காண்பி + பயன்பாட்டு புள்ளிவிவரங்கள் + ஆப்ஸ் திறப்புகள், கருவி துவக்கங்கள் மற்றும் நீங்கள் அதிகம் பயன்படுத்திய கருவிகளை ஆராயுங்கள் + ஆப் திறக்கிறது + கருவி திறக்கிறது + கடைசி கருவி + வெற்றிகரமான சேமிப்பு + செயல்பாட்டுத் தொடர் + தரவு சேமிக்கப்பட்டது + மேல் வடிவம் + பயன்படுத்தப்படும் கருவிகள் + %1$d திறக்கிறது • %2$s + இதுவரை பயன்பாட்டு புள்ளிவிவரங்கள் இல்லை + சில கருவிகளைத் திறக்கவும், அவை தானாகவே இங்கே தோன்றும் + உங்கள் பயன்பாட்டு புள்ளிவிவரங்கள் உங்கள் சாதனத்தில் உள்ளூரில் மட்டுமே சேமிக்கப்படும். ImageToolbox உங்கள் தனிப்பட்ட செயல்பாடு, பிடித்த கருவிகள் மற்றும் சேமிக்கப்பட்ட தரவு நுண்ணறிவு ஆகியவற்றைக் காட்ட மட்டுமே அவற்றைப் பயன்படுத்துகிறது + அளவை மாற்ற அளவு தெளிவுத்திறன் பரிமாணங்கள் அகலம் உயரம் jpg jpeg png webp சுருக்கவும் + சுருக்க அளவு எடை பைட்டுகள் mb kb குறைக்கும் தரம் மேம்படுத்தும் + பின்னணி நீக்கி அழிக்கும் கட்அவுட் வெளிப்படையான ஆல்பாவை அகற்று + pdf ஆவணப் பக்கக் கருவிகள் + வடிவம் மாற்ற jpg jpeg png webp heic avif jxl bmp + கண்ணி சாய்வு பின்னணி + pdf merge combine join + pdf தனி பக்கங்களை பிரித்தது + pdf கையொப்பம் வரைதல் + pdf நீக்கும் பக்கங்களை நீக்கவும் + pdf க்ராப் டிரிம் ஓரங்கள் + pdf zip காப்பக சுருக்கம் + pdf அச்சு அச்சுப்பொறி + pdf முன்னோட்ட பார்வையாளர் படித்தார் + நடுநிலை மாறுபாடு + பிழை + துளி நிழல் + பல் உயரம் + கிடைமட்ட பல் வீச்சு + டாப் எட்ஜ் + வலது விளிம்பு + கீழ் விளிம்பு + இடது விளிம்பு + கிழிந்த விளிம்பு + எடிட்டர் எடிட் போட்டோ பிக்சர் ரீடச் அட்ஜஸ்ட் + பயிர் டிரிம் வெட்டு விகித விகிதம் + வடிகட்டி விளைவு வண்ண திருத்தம் லூட் மாஸ்க் சரிசெய்தல் + பெயிண்ட் பிரஷ் பென்சில் சிறுகுறிப்பு மார்க்அப் வரையவும் + மறைக்குறியீடு மறைகுறியாக்கம் கடவுச்சொல் ரகசியம் + முன்னோட்ட பார்வையாளர் கேலரி ஆய்வு திறந்திருக்கும் + stitch merge பனோரமா நீண்ட ஸ்கிரீன்ஷாட்டை இணைக்கவும் + url இணைய பதிவிறக்க இணைய இணைப்பு படம் + பிக்கர் ஐட்ராப்பர் மாதிரி ஹெக்ஸ் ஆர்ஜிபி நிறம் + தட்டு நிறங்கள் swatches சாறு ஆதிக்கம் + exif மெட்டாடேட்டா தனியுரிமை துண்டு சுத்தமான ஜிபிஎஸ் இருப்பிடத்தை நீக்குகிறது + பின் வேறுபாட்டை ஒப்பிடு + வரம்பு மறுஅளவிடுதல் அதிகபட்சம் நிமிடம் மெகாபிக்சல்கள் பரிமாணங்கள் கிளாம்ப் + ocr உரை அங்கீகரிக்கப்பட்ட சாறு ஸ்கேன் + சாய்வு பின்னணி வண்ண கண்ணி + வாட்டர்மார்க் லோகோ உரை முத்திரை மேலடுக்கு + gif அனிமேஷன் அனிமேஷன் மாற்றப்பட்ட சட்டங்கள் + apng அனிமேஷன் அனிமேஷன் png மாற்ற சட்டங்கள் + zip காப்பக சுருக்க பேக் கோப்புகளைத் திறக்கவும் + jxl jpeg xl jpg பட வடிவமைப்பை மாற்றுகிறது + svg வெக்டர் ட்ரேஸ் கன்வெர்ட் எக்ஸ்எம்எல் + ஆவண ஸ்கேனர் ஸ்கேன் காகித முன்னோக்கு பயிர் + qr பார்கோடு குறியீடு ஸ்கேனர் ஸ்கேன் + ஸ்டாக்கிங் மேலடுக்கு சராசரி சராசரி வானியற்பியல் + பிளவு ஓடுகள் கட்டம் துண்டு பாகங்கள் + ஹெக்ஸ் rgb hsl hsv cmyk ஆய்வகத்தை மாற்றும் வண்ணம் + webp அனிமேஷன் பட வடிவமைப்பை மாற்றுகிறது + இரைச்சல் ஜெனரேட்டர் சீரற்ற அமைப்பு தானிய + படத்தொகுப்பு கட்டம் அமைப்பை இணைக்கவும் + மார்க்அப் லேயர்கள் மேலடுக்கு திருத்தத்தைக் குறிக்கின்றன + அடிப்படை64 குறியாக்கம் தரவு உரி + செக்சம் ஹாஷ் md5 sha sha1 sha256 சரிபார்க்கவும் + exif மெட்டாடேட்டா ஜிபிஎஸ் தேதி கேமராவைத் திருத்தவும் + பிளவு கட்டம் துண்டுகள் ஓடுகள் வெட்டி + ஆடியோ கவர் ஆல்பம் கலை இசை மெட்டாடேட்டா + வால்பேப்பர் ஏற்றுமதி பின்னணி + ascii உரை கலை எழுத்துக்கள் + AI ml நரம்பியல் மேம்படுத்தும் மேல்தட்டு பிரிவு + ஷேடர் ஜிஎல்எஸ்எல் துண்டு விளைவு ஸ்டுடியோ + pdf பக்கங்களை சுழற்றும் நோக்குநிலை + pdf மறுசீரமைக்க பக்கங்களை வரிசைப்படுத்தவும் + pdf பக்க எண்கள் பேஜினேஷன் + pdf ocr உரை தேடக்கூடிய சாற்றை அங்கீகரிக்கிறது + pdf வாட்டர்மார்க் ஸ்டாம்ப் லோகோ உரை + pdf கடவுச்சொல் குறியாக்க பூட்டைப் பாதுகாக்கிறது + pdf அன்லாக் கடவுச்சொல்லை மறைகுறியாக்கி பாதுகாப்பு நீக்கவும் + pdf அமுக்கி அளவை மேம்படுத்துதல் + pdf கிரேஸ்கேல் கருப்பு வெள்ளை மோனோக்ரோம் + pdf பழுது சரிசெய்தல் உடைந்த மீட்க + pdf மெட்டாடேட்டா பண்புகள் exif ஆசிரியர் தலைப்பு + pdf தட்டையான சிறுகுறிப்புகள் அடுக்குகளை உருவாக்குகிறது + pdf பிரித்தெடுக்கப்பட்ட படங்கள் படங்கள் + pdf to images pages export jpg png + pdf சிறுகுறிப்பு கருத்துகள் சுத்தமாக நீக்கப்படும் + செங்குத்து பல் வீச்சு + வண்ண நூலகப் பொருள் தட்டு நிறங்கள் என்று பெயரிடப்பட்டது + படங்களை pdf ஆக மாற்ற jpg jpeg png ஆவணம் + பயன்பாட்டு புள்ளிவிவரங்களை மீட்டமைக்கவும் + கருவி திறக்கிறது, புள்ளிவிவரங்களைச் சேமிக்கிறது, ஸ்ட்ரீக், சேமித்த தரவு மற்றும் மேல் வடிவம் மீட்டமைக்கப்படும். ஆப்ஸ் ஓபன் ஆனது மாறாமல் இருக்கும். + ஆடியோ + கோப்பு + சுயவிவரங்களை ஏற்றுமதி செய்யுங்கள் + தற்போதைய சுயவிவரத்தை சேமிக்கவும் + ஏற்றுமதி சுயவிவரத்தை நீக்கவும் + \\"%1$s\\" என்ற ஏற்றுமதி சுயவிவரத்தை நீக்க விரும்புகிறீர்களா? + வரைதல் எல்லை + வரைதல் மற்றும் அழிக்கும் கேன்வாஸைச் சுற்றி ஒரு மெல்லிய பார்டரைக் காட்டு + அலை அலையானது + மென்மையான அலை அலையான விளிம்புடன் வட்டமான UI கூறுகள் + ஸ்கூப் + நாட்ச் + உள்நோக்கி செதுக்கப்பட்ட வட்டமான மூலைகள் + இரட்டை வெட்டு கொண்ட மூலைகள் + ப்ளேஸ்ஹோல்டர் + அசல் கோப்பு பெயரை நீட்டிப்பு இல்லாமல் செருக {filename} ஐப் பயன்படுத்தவும் + ஃப்ளேர் + அடிப்படை தொகை + கதிர் அளவு + வளைய அகலம் + பார்வையை சிதைக்கவும் + ஜாவா லுக் அண்ட் ஃபீல் + வெட்டு + மற்றும் கோணம் + வெளியீட்டின் அளவை மாற்றவும் + நீர் துளி + அலைநீளம் + கட்டம் + உயர் பாஸ் + மோதிர அளவு + எக்ஸ் கோணம் + வண்ண முகமூடி + குரோம் + கரைக்கவும் + மென்மை + பின்னூட்டம் + லென்ஸ் தெளிவின்மை + குறைந்த உள்ளீடு + உயர் உள்ளீடு + குறைந்த வெளியீடு + அதிக வெளியீடு + ஒளி விளைவுகள் + பம்ப் உயரம் + பம்ப் மென்மை + சிற்றலை + X வீச்சு + Y அலைவீச்சு + X அலைநீளம் + Y அலைநீளம் + அடாப்டிவ் மங்கல் + அமைப்பு உருவாக்கம் + பல்வேறு செயல்முறை அமைப்புகளை உருவாக்கி அவற்றை படங்களாக சேமிக்கவும் + அமைப்பு வகை + சார்பு + நேரம் + பிரகாசிக்கவும் + சிதறல் + மாதிரிகள் + கோண குணகம் + சாய்வு குணகம் + கட்டம் வகை + தூர சக்தி + அளவு Y + தெளிவின்மை + அடிப்படை வகை + கொந்தளிப்பு காரணி + அளவிடுதல் + மறு செய்கைகள் + மோதிரங்கள் + பிரஷ்டு உலோகம் + காஸ்டிக்ஸ் + செல்லுலார் + செக்கர்போர்டு + fBm + பளிங்கு + பிளாஸ்மா + குயில் + மரம் + சீரற்ற + சதுரம் + அறுகோணமானது + எண்கோணமானது + முக்கோணமானது + முகடு + VL சத்தம் + எஸ்சி சத்தம் + சமீபத்திய + இதுவரை எந்த வடிப்பான்களும் சமீபத்தில் பயன்படுத்தப்படவில்லை + டெக்ஸ்ச்சர் ஜெனரேட்டர் பிரஷ்டு மெட்டல் காஸ்டிக்ஸ் செல்லுலார் செக்கர்போர்டு மார்பிள் பிளாஸ்மா க்வில்ட் மர செங்கல் உருமறைப்பு செல் மேகம் விரிசல் துணி பசுமையாக தேன்கூடு பனி எரிமலை நெபுலா காகித துரு மணல் புகை கல் நிலப்பரப்பு நிலப்பரப்பு நீர் சிற்றலை + செங்கல் + உருமறைப்பு + செல் + மேகம் + விரிசல் + துணி + இலைகள் + தேன்கூடு + பனிக்கட்டி + எரிமலைக்குழம்பு + நெபுலா + காகிதம் + துரு + மணல் + புகை + கல் + நிலப்பரப்பு + நிலப்பரப்பு + நீர் சிற்றலை + மேம்பட்ட மரம் + மோட்டார் அகலம் + ஒழுங்கின்மை + பெவல் + முரட்டுத்தனம் + முதல் வாசல் + இரண்டாவது வாசல் + மூன்றாவது வாசல் + விளிம்பு மென்மை + பார்டர் அகலம் + கவரேஜ் + விவரம் + ஆழம் + கிளையிடுதல் + கிடைமட்ட நூல்கள் + செங்குத்து நூல்கள் + குழப்பம் + நரம்புகள் + விளக்கு + விரிசல் அகலம் + உறைபனி + ஓட்டம் + மேலோடு + மேக அடர்த்தி + நட்சத்திரங்கள் + ஃபைபர் அடர்த்தி + ஃபைபர் வலிமை + கறைகள் + அரிப்பு + பள்ளம் + செதில்கள் + குன்று அதிர்வெண் + காற்று கோணம் + சிற்றலைகள் + விஸ்ப்ஸ் + நரம்பு அளவு + நீர் நிலை + மலை நிலை + அரிப்பு + பனி நிலை + வரி எண்ணிக்கை + வரி தடிமன் + நிழல் + துளை நிறம் + மோட்டார் நிறம் + அடர் செங்கல் நிறம் + செங்கல் நிறம் + நிறத்தை முன்னிலைப்படுத்தவும் + அடர் நிறம் + காடு நிறம் + பூமி நிறம் + மணல் நிறம் + பின்னணி நிறம் + செல் நிறம் + விளிம்பு நிறம் + வானம் நிறம் + நிழல் நிறம் + வெளிர் நிறம் + மேற்பரப்பு நிறம் + மாறுபாடு நிறம் + விரிசல் நிறம் + வார்ப் நிறம் + வெஃப்ட் நிறம் + இருண்ட இலை நிறம் + இலை நிறம் + பார்டர் நிறம் + தேன் நிறம் + ஆழமான நிறம் + பனி நிறம் + பனி நிறம் + மேலோடு நிறம் + கழுவும் வண்ணம் + ஒளிரும் நிறம் + விண்வெளி நிறம் + வயலட் நிறம் + நீல நிறம் + அடிப்படை நிறம் + ஃபைபர் நிறம் + கறை நிறம் + உலோக நிறம் + அடர் துரு நிறம் + துரு நிறம் + ஆரஞ்சு நிறம் + புகை நிறம் + நரம்பு நிறம் + தாழ்நில நிறம் + நீர் நிறம் + பாறை நிறம் + பனி நிறம் + குறைந்த நிறம் + உயர் நிறம் + வரி நிறம் + மேலோட்டமான நிறம் + புல் + அழுக்கு + தோல் + கான்கிரீட் + நிலக்கீல் + பாசி + நெருப்பு + அரோரா + ஆயில் ஸ்லிக் + வாட்டர்கலர் + சுருக்க ஓட்டம் + ஓபல் + டமாஸ்கஸ் ஸ்டீல் + மின்னல் + வெல்வெட் + மை மார்பிங் + ஹாலோகிராபிக் படலம் + உயிர் ஒளிர்வு + காஸ்மிக் சுழல் + எரிமலை விளக்கு + நிகழ்வு அடிவானம் + ஃப்ராக்டல் ப்ளூம் + குரோமடிக் டன்னல் + கிரகணம் கொரோனா + விசித்திரமான கவர்ச்சி + ஃபெரோஃப்ளூயிட் கிரீடம் + சூப்பர்நோவா + கருவிழி + மயில் இறகு + நாட்டிலஸ் ஷெல் + வளையப்பட்ட கிரகம் + கத்தி அடர்த்தி + கத்தி நீளம் + காற்று + ஒட்டும் தன்மை + கொத்துகள் + ஈரம் + கூழாங்கற்கள் + சுருக்கங்கள் + துளைகள் + மொத்தமாக + விரிசல் + தார் + அணியுங்கள் + புள்ளிகள் + இழைகள் + சுடர் அதிர்வெண் + புகை + தீவிரம் + ரிப்பன்கள் + இசைக்குழுக்கள் + இரைடிசென்ஸ் + இருள் + பூக்கள் + நிறமி + விளிம்புகள் + காகிதம் + பரவல் + சமச்சீர் + கூர்மை + வண்ண விளையாட்டு + பால் தன்மை + அடுக்குகள் + மடிப்பு + போலிஷ் + கிளைகள் + திசை + ஷீன் + மடிப்புகள் + இறகுகள் + மை இருப்பு + ஸ்பெக்ட்ரம் + சுருக்கங்கள் + மாறுபாடு + ஆயுதங்கள் + திருப்பம் + கோர் பளபளப்பு + குமிழ்கள் + வட்டு சாய்வு + அடிவான அளவு + வட்டு அகலம் + லென்சிங் + இதழ்கள் + சுருட்டு + ஃபிலிகிரி + முகங்கள் + வளைவு + சந்திரனின் அளவு + கொரோனா அளவு + கதிர்கள் + வைர மோதிரம் + மடல்கள் + சுற்றுப்பாதை அடர்த்தி + தடிமன் + கூர்முனை + ஸ்பைக் நீளம் + உடல் அளவு + உலோகம் + அதிர்ச்சி ஆரம் + ஷெல் அகலம் + வெளியே தூக்கி எறியப்பட்டது + மாணவர் அளவு + கருவிழி அளவு + வண்ண மாறுபாடு + கேட்ச்லைட் + கண் அளவு + பார்ப் அடர்த்தி + திருப்புகிறது + அறைகள் + திறப்பு + முகடுகள் + முத்துமாலை + கிரக அளவு + மோதிர சாய்வு + வளைய அகலம் + வளிமண்டலம் + அழுக்கு நிறம் + அடர் புல் நிறம் + புல் நிறம் + முனை நிறம் + அடர் பூமி நிறம் + உலர் நிறம் + கூழாங்கல் நிறம் + தோல் நிறம் + கான்கிரீட் நிறம் + தார் நிறம் + நிலக்கீல் நிறம் + கல் நிறம் + தூசி நிறம் + மண் நிறம் + அடர் பாசி நிறம் + பாசி நிறம் + சிவப்பு நிறம் + மைய நிறம் + பச்சை நிறம் + சியான் நிறம் + மெஜந்தா நிறம் + தங்க நிறம் + காகித நிறம் + நிறமி நிறம் + இரண்டாம் நிலை நிறம் + முதல் நிறம் + இரண்டாவது நிறம் + இளஞ்சிவப்பு நிறம் + அடர் எஃகு நிறம் + எஃகு நிறம் + வெளிர் எஃகு நிறம் + ஆக்சைடு நிறம் + ஒளிவட்ட நிறம் + போல்ட் நிறம் + வெல்வெட் நிறம் + ஷீன் நிறம் + நீல மை நிறம் + சிவப்பு மை நிறம் + அடர் மை நிறம் + வெள்ளி நிறம் + மஞ்சள் நிறம் + திசு நிறம் + வட்டு நிறம் + சூடான நிறம் + லென்ஸ் நிறம் + வெளிப்புற நிறம் + உள் நிறம் + கொரோனா நிறம் + குளிர் நிறம் + சூடான நிறம் + மேக நிறம் + சுடர் நிறம் + இறகு நிறம் + ஷெல் நிறம் + முத்து நிறம் + கிரகத்தின் நிறம் + மோதிர நிறம் + சேமித்த பிறகு அசல் கோப்புகளை நீக்கவும் + வெற்றிகரமாக செயலாக்கப்பட்ட மூலக் கோப்புகள் மட்டுமே நீக்கப்படும் + அசல் கோப்புகள் நீக்கப்பட்டன: %1$d + அசல் கோப்புகளை நீக்க முடியவில்லை: %1$d + அசல் கோப்புகள் நீக்கப்பட்டன: %1$d, தோல்வியடைந்தது: %2$d + தாள் சைகைகள் + விரிவாக்கப்பட்ட விருப்பத் தாள்களை இழுக்க அனுமதிக்கவும் + குரோமா துணை மாதிரி + பிட் ஆழம் + இழப்பற்றது + %1$d-பிட் + தொகுதி மறுபெயர் + கோப்பு பெயர் வடிவங்களைப் பயன்படுத்தி பல கோப்புகளை மறுபெயரிடவும் + தொகுதி மறுபெயரிடு கோப்புகள் கோப்பு பெயர் முறை வரிசை தேதி நீட்டிப்பு + மறுபெயரிட கோப்புகளைத் தேர்ந்தெடுக்கவும் + கோப்பு பெயர் முறை + மறுபெயரிடவும் + தேதி ஆதாரம் + EXIF தேதி எடுக்கப்பட்டது + கோப்பு மாற்றப்பட்ட தேதி + கோப்பு உருவாக்கப்பட்ட தேதி + தற்போதைய தேதி + கையேடு தேதி + கையேடு தேதி + கைமுறை நேரம் + சில கோப்புகளுக்கு தேர்ந்தெடுக்கப்பட்ட தேதி கிடைக்கவில்லை. தற்போதைய தேதி அவர்களுக்குப் பயன்படுத்தப்படும். + கோப்பு பெயர் வடிவத்தை உள்ளிடவும் + முறை தவறான அல்லது மிக நீண்ட கோப்பு பெயரை உருவாக்குகிறது + பேட்டர்ன் நகல் கோப்புப் பெயர்களை உருவாக்குகிறது + எல்லா கோப்புப் பெயர்களும் ஏற்கனவே வடிவத்துடன் பொருந்துகின்றன + இந்த பேட்டர்ன் தொகுதி மறுபெயரிடக் கிடைக்காத டோக்கன்களைப் பயன்படுத்துகிறது + பெயர் அப்படியே இருக்கும் + கோப்புகள் வெற்றிகரமாக மறுபெயரிடப்பட்டன + சில கோப்புகளை மறுபெயரிட முடியவில்லை + அனுமதி வழங்கப்படவில்லை + அசல் கோப்பு பெயரை நீட்டிப்பு இல்லாமல் பயன்படுத்துகிறது, இது மூல அடையாளத்தை அப்படியே வைத்திருக்க உதவுகிறது. \\o{start:end} உடன் ஸ்லைசிங், \\o{t} உடன் ஒலிபெயர்ப்பு மற்றும் \\o{s/old/new/} உடன் மாற்றுவதையும் ஆதரிக்கிறது. + தானாக அதிகரிக்கும் கவுண்டர். தனிப்பயன் வடிவமைப்பை ஆதரிக்கிறது: \\c{padding} (எ.கா. \\c{3} -&gt; 001), \\c{start:step} அல்லது \\c{start:step:padding}. + அசல் கோப்பு அமைந்துள்ள மூலக் கோப்புறையின் பெயர். + அசல் கோப்பின் அளவு. யூனிட்களை ஆதரிக்கிறது: \\z{b}, \\z{kb}, \\z{mb} அல்லது மனிதர்கள் படிக்கக்கூடிய வடிவமைப்பிற்கு \\z. + கோப்புப் பெயரின் தனித்துவத்தை உறுதிப்படுத்த, ஒரு சீரற்ற உலகளாவிய தனித்துவ அடையாளங்காட்டியை (UUID) உருவாக்குகிறது. + கோப்புகளை மறுபெயரிடுவதை செயல்தவிர்க்க முடியாது. தொடர்வதற்கு முன் புதிய பெயர்கள் சரியாக உள்ளதா என்பதை உறுதி செய்து கொள்ளவும். + மறுபெயரை உறுதிப்படுத்தவும் + பக்க மெனு அமைப்புகள் + மெனு அளவு + மெனு ஆல்பா + ஆட்டோ ஒயிட் பேலன்ஸ் + கிளிப்பிங் + மறைக்கப்பட்ட வாட்டர்மார்க்ஸைச் சரிபார்க்கிறது + சேமிப்பு + தற்காலிக சேமிப்பு வரம்பு + இந்த அளவுக்கு அதிகமாக வளரும்போது, ​​தற்காலிக சேமிப்பை தானாகவே அழிக்கவும் + துப்புரவு இடைவெளி + கேச் அழிக்கப்பட வேண்டுமா என்பதை எத்தனை முறை சரிபார்க்க வேண்டும் + ஆப்ஸ் துவக்கத்தில் + 1 நாள் + 1 வாரம் + 1 மாதம் + தேர்ந்தெடுக்கப்பட்ட வரம்பு மற்றும் இடைவெளிக்கு ஏற்ப ஆப்ஸ் தற்காலிக சேமிப்பை தானாக அழிக்கவும் + அசையும் பயிர் பகுதி + அதன் உள்ளே இழுப்பதன் மூலம் முழு பயிர் பகுதியையும் நகர்த்த அனுமதிக்கிறது + GIF ஐ இணைக்கவும் + பல GIF கிளிப்களை ஒரு அனிமேஷனில் இணைக்கவும் + GIF கிளிப்புகள் ஆர்டர் + தலைகீழ் + தலைகீழாக விளையாடு + பூமராங் + முன்னும் பின்னும் விளையாடுங்கள் + சட்டத்தின் அளவை இயல்பாக்குங்கள் + மிகப்பெரிய கிளிப்புக்கு ஏற்றவாறு பிரேம்களை அளவிடவும்; அவற்றின் அசல் அளவைப் பாதுகாக்க அணைக்கவும் + கிளிப்புகள் இடையே தாமதம் (மிசி) + கோப்புறை + ஒரு கோப்புறை மற்றும் அதன் துணை கோப்புறைகளில் இருந்து அனைத்து படங்களையும் தேர்ந்தெடுக்கவும் + நகல் கண்டுபிடிப்பான் + துல்லியமான பிரதிகள் மற்றும் பார்வைக்கு ஒத்த படங்களைக் கண்டறியவும் + டூப்ளிகேட் ஃபைண்டர் ஒத்த படங்கள் சரியான பிரதிகள் புகைப்படங்களை சுத்தம் செய்யும் சேமிப்பு dHash SHA-256 + நகல்களைக் கண்டறிய படங்களைத் தேர்ந்தெடுக்கவும் + ஒற்றுமை உணர்திறன் + சரியான பிரதிகள் + ஒத்த படங்கள் + அனைத்து துல்லியமான நகல்களையும் தேர்ந்தெடுக்கவும் + பரிந்துரைக்கப்பட்டதைத் தவிர அனைத்தையும் தேர்ந்தெடுக்கவும் + நகல் எதுவும் இல்லை + மேலும் படங்களைச் சேர்க்க அல்லது ஒற்றுமை உணர்திறனை அதிகரிக்க முயற்சிக்கவும் + %1$d படத்தை(களை) படிக்க முடியவில்லை + %1$s • %2$s மீட்டெடுக்கக்கூடியது + %1$d குழுக்கள் • %2$s மீட்டெடுக்கக்கூடியது + %1$d தேர்ந்தெடுக்கப்பட்டது • %2$s + இதை செயல்தவிர்க்க முடியாது. கணினி உரையாடலில் நீக்குதலை உறுதிப்படுத்துமாறு Android உங்களிடம் கேட்கலாம். + கிடைக்கவில்லை + தொடங்குவதற்கு நகர்த்தவும் + முடிவுக்கு நகர்த்தவும் + \ No newline at end of file diff --git a/core/resources/src/main/res/values-te/strings.xml b/core/resources/src/main/res/values-te/strings.xml new file mode 100644 index 0000000..974cf0b --- /dev/null +++ b/core/resources/src/main/res/values-te/strings.xml @@ -0,0 +1,3740 @@ + + + + + పరిమాణం %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 నెట్‌వర్క్ + లెగసీ & 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 & బార్‌కోడ్ + 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 పేజీల నుండి లింక్‌లు, వ్యాఖ్యలు, ముఖ్యాంశాలు, ఆకారాలు లేదా ఫారమ్ ఫీల్డ్‌ల వంటి ఎంచుకున్న ఉల్లేఖన రకాలను తీసివేయండి + హైపర్‌లింక్‌లు + ఫైల్ జోడింపులు + లైన్లు + పాపప్‌లు + స్టాంపులు + ఆకారాలు + టెక్స్ట్ నోట్స్ + టెక్స్ట్ మార్కప్ + ఫారమ్ ఫీల్డ్స్ + మార్కప్ + తెలియదు + ఉల్లేఖనాలు + సమూహాన్ని తీసివేయండి + కాన్ఫిగర్ చేయగల రంగు మరియు ఆఫ్‌సెట్‌లతో లేయర్ వెనుక బ్లర్ షాడోని జోడించండి + కంటెంట్ అవేర్ వక్రీకరణ + ఈ చర్యను పూర్తి చేయడానికి తగినంత మెమరీ లేదు. దయచేసి చిన్న చిత్రాన్ని ఉపయోగించడం, ఇతర యాప్‌లను మూసివేయడం లేదా యాప్‌ని పునఃప్రారంభించడం ప్రయత్నించండి. + సమాంతర కార్మికులు + మార్చబడిన ఫైల్‌లు అసలైన వాటి కంటే పెద్దవిగా ఉన్నందున ఈ చిత్రాలు సేవ్ చేయబడలేదు. అసలు చిత్రాలను తొలగించే ముందు ఈ జాబితాను తనిఖీ చేయండి. + ఫైల్‌లు దాటవేయబడ్డాయి: %1$s + యాప్ లాగ్‌లు + సమస్యలను గుర్తించడానికి యాప్ లాగ్‌లను వీక్షించండి + సమూహ సాధనాల్లో ఇష్టమైనవి + సాధనాలు రకం ఆధారంగా సమూహం చేయబడినప్పుడు ఇష్టమైన వాటిని ట్యాబ్‌గా జోడిస్తుంది + చివరిగా ఇష్టమైనదాన్ని చూపించు + ఇష్టమైన సాధనాల ట్యాబ్‌ను చివరి వరకు తరలిస్తుంది + క్షితిజ సమాంతర అంతరం + నిలువు అంతరం + వెనుకబడిన శక్తి + ఫార్వర్డ్ ఎనర్జీ అల్గారిథమ్‌కు బదులుగా సాధారణ గ్రేడియంట్ మాగ్నిట్యూడ్ ఎనర్జీ మ్యాప్‌ని ఉపయోగించండి + ముసుగు వేసిన ప్రాంతాన్ని తొలగించండి + అతుకులు ముసుగు చేయబడిన ప్రాంతం గుండా వెళతాయి, దానిని రక్షించడానికి బదులుగా ఎంచుకున్న ప్రాంతాన్ని మాత్రమే తొలగిస్తుంది + VHS NTSC + అధునాతన NTSC సెట్టింగ్‌లు + బలమైన VHS మరియు ప్రసార కళాఖండాల కోసం అదనపు అనలాగ్ ట్యూనింగ్ + క్రోమా రక్తస్రావం + టేప్ దుస్తులు + ట్రాకింగ్ + లూమా స్మెర్ + రింగింగ్ + మంచు + ఫీల్డ్ ఉపయోగించండి + ఫిల్టర్ రకం + ఇన్‌పుట్ లూమా ఫిల్టర్ + ఇన్‌పుట్ క్రోమా లోపాస్ + క్రోమా డీమోడ్యులేషన్ + దశ షిఫ్ట్ + దశ ఆఫ్‌సెట్ + తల మారడం + తల మారే ఎత్తు + హెడ్ ​​స్విచ్చింగ్ ఆఫ్‌సెట్ + తల మారడం షిఫ్ట్ + మధ్య లైన్ స్థానం + మిడ్-లైన్ జిట్టర్ + శబ్దం ఎత్తును ట్రాక్ చేస్తోంది + ట్రాకింగ్ వేవ్ + మంచును ట్రాక్ చేస్తోంది + మంచు అనిసోట్రోపిని ట్రాక్ చేస్తోంది + ట్రాకింగ్ శబ్దం + శబ్దం తీవ్రతను ట్రాక్ చేస్తోంది + మిశ్రమ శబ్దం + మిశ్రమ శబ్దం ఫ్రీక్వెన్సీ + మిశ్రమ శబ్దం తీవ్రత + మిశ్రమ శబ్దం వివరాలు + రింగింగ్ ఫ్రీక్వెన్సీ + రింగింగ్ పవర్ + లూమా శబ్దం + లూమా శబ్దం ఫ్రీక్వెన్సీ + లూమా శబ్దం తీవ్రత + లూమా శబ్దం వివరాలు + క్రోమా శబ్దం + క్రోమా నాయిస్ ఫ్రీక్వెన్సీ + క్రోమా శబ్దం తీవ్రత + క్రోమా నాయిస్ వివరాలు + మంచు తీవ్రత + మంచు అనిసోట్రోపి + క్రోమా ఫేజ్ శబ్దం + క్రోమా దశ లోపం + క్రోమా ఆలస్యం క్షితిజ సమాంతర + క్రోమా ఆలస్యం నిలువు + VHS టేప్ వేగం + VHS క్రోమా నష్టం + VHS తీవ్రతను పదును పెడుతుంది + VHS పదునుపెట్టే ఫ్రీక్వెన్సీ + VHS అంచు తరంగ తీవ్రత + VHS అంచు తరంగ వేగం + VHS ఎడ్జ్ వేవ్ ఫ్రీక్వెన్సీ + VHS ఎడ్జ్ వేవ్ వివరాలు + అవుట్‌పుట్ క్రోమా లోపాస్ + స్కేల్ క్షితిజ సమాంతర + నిలువుగా స్కేల్ చేయండి + స్కేల్ ఫ్యాక్టర్ X + స్కేల్ ఫ్యాక్టర్ Y + సాధారణ చిత్ర వాటర్‌మార్క్‌లను గుర్తించి, వాటిని లామాతో పెయింట్ చేస్తుంది. స్వయంచాలకంగా గుర్తింపు మరియు పెయింటింగ్ నమూనాలను డౌన్‌లోడ్ చేస్తుంది + డ్యూటెరానోపియా + చిత్రాన్ని విస్తరించండి + YOLO v11 సెగ్మెంటేషన్‌ని ఉపయోగించి ఆబ్జెక్ట్ సెగ్మెంటేషన్ ఆధారిత బ్యాక్‌గ్రౌండ్ రిమూవర్ + లైన్ కోణాన్ని చూపించు + డ్రాయింగ్ చేస్తున్నప్పుడు ప్రస్తుత రేఖ భ్రమణాన్ని డిగ్రీలలో చూపుతుంది + యానిమేటెడ్ ఎమోజీలు + అందుబాటులో ఉన్న ఎమోజీలను యానిమేషన్‌లుగా చూపండి + ఒరిజినల్ ఫోల్డర్‌కి సేవ్ చేయండి + ఎంచుకున్న ఫోల్డర్‌కు బదులుగా అసలు ఫైల్ పక్కన కొత్త ఫైల్‌లను సేవ్ చేయండి + వాటర్‌మార్క్ PDF + తగినంత జ్ఞాపకశక్తి లేదు + చిత్రం ఈ పరికరంలో ప్రాసెస్ చేయడానికి చాలా పెద్దది కావచ్చు లేదా సిస్టమ్ అందుబాటులో ఉన్న మెమరీ అయిపోయింది. ఇమేజ్ రిజల్యూషన్‌ని తగ్గించడం, ఇతర యాప్‌లను మూసివేయడం లేదా చిన్న ఫైల్‌ని ఎంచుకోవడం ప్రయత్నించండి. + డీబగ్ మెనూ + యాప్ ఫంక్షన్‌లను పరీక్షించడానికి మెనూ, ఇది ప్రొడక్షన్ రిలీజ్‌లో చూపడానికి ఉద్దేశించబడలేదు + ప్రొవైడర్ + PaddleOCRకి మీ పరికరంలో అదనపు ONNX మోడల్‌లు అవసరం. మీరు %1$s డేటాను డౌన్‌లోడ్ చేయాలనుకుంటున్నారా? + యూనివర్సల్ + కొరియన్ + లాటిన్ + తూర్పు స్లావిక్ + థాయ్ + గ్రీకు + ఇంగ్లీష్ + సిరిలిక్ + అరబిక్ + దేవనాగరి + తమిళం + తెలుగు + షేడర్ + షేడర్ ప్రీసెట్ + షేడర్ ఏదీ ఎంచుకోబడలేదు + షేడర్ స్టూడియో + అనుకూల ఫ్రాగ్మెంట్ షేడర్‌లను సృష్టించండి, సవరించండి, ధృవీకరించండి, దిగుమతి చేయండి మరియు ఎగుమతి చేయండి + సేవ్ చేసిన షేడర్‌లు + సేవ్ చేసిన షేడర్‌లను తెరవండి, డూప్లికేట్ చేయండి, ఎగుమతి చేయండి, షేర్ చేయండి లేదా తొలగించండి + కొత్త షేడర్ + షేడర్ ఫైల్ + .itshader JSON + %1$d పారాములు + షేడర్ మూలం + శూన్యమైన ప్రధాన() బాడీని మాత్రమే వ్రాయండి. UV కోఆర్డినేట్‌ల కోసం టెక్స్‌చర్‌కోఆర్డినేట్ మరియు సోర్స్ శాంప్లర్‌గా ఇన్‌పుట్ ఇమేజ్‌టెక్చర్‌ని ఉపయోగించండి. + విధులు + ఐచ్ఛిక సహాయక విధులు, స్థిరాంకాలు మరియు నిర్మాణాలు శూన్యమైన ప్రధాన()కు ముందు చొప్పించబడ్డాయి. పారామ్‌ల నుండి యూనిఫాంలు ఉత్పత్తి చేయబడతాయి. + షేడర్‌కి సవరించగలిగే విలువలు అవసరమైనప్పుడు యూనిఫారాలను ఇక్కడ జోడించండి. + షేడర్‌ని రీసెట్ చేయండి + ప్రస్తుత షేడర్ డ్రాఫ్ట్ క్లియర్ చేయబడుతుంది + చెల్లని షేడర్ + మద్దతు లేని షేడర్ వెర్షన్ %1$d. మద్దతు ఉన్న సంస్కరణ: %2$d. + షేడర్ పేరు తప్పనిసరిగా ఖాళీగా ఉండకూడదు. + షేడర్ మూలం ఖాళీగా ఉండకూడదు. + షేడర్ మూలం మద్దతు లేని అక్షరాలను కలిగి ఉంది: %1$s. ASCII GLSL మూలాన్ని మాత్రమే ఉపయోగించండి. + పరామితి \\\"%1$s\\\" ఒకటి కంటే ఎక్కువసార్లు ప్రకటించబడింది. + పారామీటర్ పేర్లు ఖాళీగా ఉండకూడదు. + పరామితి \\\"%1$s\\\" రిజర్వు చేయబడిన GPUImage పేరును ఉపయోగిస్తుంది. + పరామితి \\\"%1$s\\\" తప్పనిసరిగా చెల్లుబాటు అయ్యే GLSL ఐడెంటిఫైయర్ అయి ఉండాలి. + పరామితి \\\"%1$s\\\" %2$s విలువ రకాన్ని కలిగి ఉంది \\\"%3$s\\\", ఊహించిన \\\"%4$s\\\". + పారామీటర్ \\\"%1$s\\\" బూల్ విలువల కోసం నిమి లేదా గరిష్టాన్ని నిర్వచించలేదు. + \\\"%1$s\\\" పరామితి గరిష్టం కంటే నిమి ఎక్కువ. + పరామితి \\\"%1$s\\\" డిఫాల్ట్ నిమి కంటే తక్కువగా ఉంది. + పరామితి \\\"%1$s\\\" డిఫాల్ట్ గరిష్టం కంటే ఎక్కువ. + షేడర్ తప్పనిసరిగా \\\"యూనిఫాం నమూనా2D %1$s;\\\"ని ప్రకటించాలి. + షేడర్ తప్పనిసరిగా \\\"వైవిధ్యమైన vec2 %1$s;\\\"ని ప్రకటించాలి. + షేడర్ తప్పనిసరిగా \\\"శూన్యం ప్రధాన()\\\"ను నిర్వచించాలి. + షేడర్ తప్పనిసరిగా gl_FragColorకి రంగును వ్రాయాలి. + ShaderToy మెయిన్‌ఇమేజ్ షేడర్‌లకు మద్దతు లేదు. GPUImage యొక్క శూన్యమైన ప్రధాన() ఒప్పందాన్ని ఉపయోగించండి. + యానిమేటెడ్ ShaderToy యూనిఫాం \\\"iTime\\\"కి మద్దతు లేదు. + యానిమేటెడ్ ShaderToy యూనిఫాం \\\"iFrame\\\"కి మద్దతు లేదు. + ShaderToy యూనిఫాం \\\"iResolution\\\"కు మద్దతు లేదు. + ShaderToy iChannel అల్లికలకు మద్దతు లేదు. + బాహ్య అల్లికలకు మద్దతు లేదు. + బాహ్య ఆకృతి పొడిగింపులకు మద్దతు లేదు. + లిబ్రెట్రో షేడర్ పారామీటర్‌లకు మద్దతు లేదు. + ఒక ఇన్‌పుట్ ఆకృతికి మాత్రమే మద్దతు ఉంది. \\\"యూనిఫాం %1$s %2$s;\\\"ని తీసివేయండి. + \\\"%1$s\\\" పరామితి తప్పనిసరిగా \\\"యూనిఫాం %2$s %1$s;\\\"గా ప్రకటించబడాలి. + \\\"%1$s\\\" పరామితి \\\"యూనిఫాం %2$s %1$s;\\\"గా ప్రకటించబడింది, అంచనా వేయబడిన \\\"యూనిఫాం %3$s %1$s;\\\". + షేడర్ ఫైల్ ఖాళీగా ఉంది. + షేడర్ ఫైల్ తప్పనిసరిగా వెర్షన్, పేరు మరియు షేడర్ ఫీల్డ్‌లతో కూడిన .itshader JSON ఆబ్జెక్ట్ అయి ఉండాలి. + షేడర్ ఫైల్ చెల్లుబాటు అయ్యే JSON కాదు లేదా .itshader ఫార్మాట్‌తో సరిపోలడం లేదు. + ఫీల్డ్ \\\"%1$s\\\" అవసరం. + %1$s అవసరం. + %1$s \\\"%2$s\\\" మద్దతు లేదు. మద్దతు ఉన్న రకాలు: %3$s. + \\\"%2$s\\\" పారామితుల కోసం %1$s అవసరం. + %1$s తప్పనిసరిగా పరిమిత సంఖ్య అయి ఉండాలి. + %1$s తప్పనిసరిగా పూర్ణాంకం అయి ఉండాలి. + %1$s తప్పక నిజం లేదా తప్పు. + %1$s తప్పనిసరిగా రంగు స్ట్రింగ్, RGB/RGBA శ్రేణి లేదా రంగు వస్తువు అయి ఉండాలి. + %1$s తప్పనిసరిగా #RRGGBB లేదా #RRGGBBAA ఆకృతిని ఉపయోగించాలి. + %1$s తప్పనిసరిగా చెల్లుబాటు అయ్యే హెక్సాడెసిమల్ రంగు ఛానెల్‌లను కలిగి ఉండాలి. + %1$s తప్పనిసరిగా 3 లేదా 4 రంగు ఛానెల్‌లను కలిగి ఉండాలి. + %1$s తప్పనిసరిగా 0 మరియు 255 మధ్య ఉండాలి. + %1$s తప్పనిసరిగా రెండు-సంఖ్యల శ్రేణి లేదా x మరియు y ఉన్న వస్తువు అయి ఉండాలి. + %1$s ఖచ్చితంగా 2 సంఖ్యలను కలిగి ఉండాలి. + నకిలీ + ఎల్లప్పుడూ EXIF ​​ని క్లియర్ చేయండి + మెటాడేటాను ఉంచాలని సాధనం అభ్యర్థించినప్పటికీ, సేవ్‌లో ఇమేజ్ EXIF ​​డేటాను తీసివేయండి + సహాయం & చిట్కాలు + %1$d ట్యుటోరియల్స్ + సాధనాన్ని తెరవండి + ప్రధాన సాధనాలు, ఎగుమతి ఎంపికలు, PDF ఫ్లోలు, రంగు వినియోగాలు మరియు సాధారణ సమస్యల పరిష్కారాలను తెలుసుకోండి + ప్రారంభించడం + సాధనాలను ఎంచుకోండి, ఫైల్‌లను దిగుమతి చేయండి, ఫలితాలను సేవ్ చేయండి మరియు సెట్టింగ్‌లను వేగంగా ఉపయోగించుకోండి + చిత్ర సవరణ + పరిమాణాన్ని మార్చండి, కత్తిరించండి, ఫిల్టర్ చేయండి, నేపథ్యాలను తొలగించండి, చిత్రాలను గీయండి మరియు వాటర్‌మార్క్ చేయండి + ఫైల్‌లు మరియు మెటాడేటా + ఫార్మాట్‌లను మార్చండి, అవుట్‌పుట్‌ను సరిపోల్చండి, గోప్యతను రక్షించండి మరియు ఫైల్ పేర్లను నియంత్రించండి + PDF మరియు పత్రాలు + PDFలను సృష్టించండి, పత్రాలు, OCR పేజీలను స్కాన్ చేయండి మరియు భాగస్వామ్యం కోసం ఫైల్‌లను సిద్ధం చేయండి + టెక్స్ట్, QR మరియు డేటా + వచనాన్ని గుర్తించండి, కోడ్‌లను స్కాన్ చేయండి, Base64ని ఎన్‌కోడ్ చేయండి, హ్యాష్‌లను మరియు ప్యాకేజీ ఫైల్‌లను తనిఖీ చేయండి + రంగు సాధనాలు + రంగులను ఎంచుకోండి, ప్యాలెట్‌లను రూపొందించండి, రంగు లైబ్రరీలను అన్వేషించండి మరియు ప్రవణతలను సృష్టించండి + సృజనాత్మక సాధనాలు + SVG, ASCII ఆర్ట్, నాయిస్ అల్లికలు, షేడర్‌లు మరియు ప్రయోగాత్మక దృశ్యాలను రూపొందించండి + ట్రబుల్షూటింగ్ + భారీ ఫైల్‌లు, పారదర్శకత, భాగస్వామ్యం, దిగుమతులు మరియు రిపోర్టింగ్ సమస్యలను పరిష్కరించండి + సరైన సాధనాన్ని ఎంచుకోండి + సరైన స్థలంలో ప్రారంభించడానికి వర్గాలు, శోధన, ఇష్టమైనవి మరియు భాగస్వామ్య చర్యలను ఉపయోగించండి + ఉత్తమ ఎంట్రీ పాయింట్ నుండి ప్రారంభించండి + ఇమేజ్ టూల్‌బాక్స్ అనేక కేంద్రీకృత సాధనాలను కలిగి ఉంది మరియు వాటిలో చాలా మొదటి చూపులో అతివ్యాప్తి చెందుతాయి. మీరు పూర్తి చేయాలనుకుంటున్న పని నుండి ప్రారంభించండి, ఆపై ఫలితం యొక్క అత్యంత ముఖ్యమైన భాగాన్ని నియంత్రించే సాధనాన్ని ఎంచుకోండి: పరిమాణం, ఆకృతి, మెటాడేటా, వచనం, PDF పేజీలు, రంగులు లేదా లేఅవుట్. + పునఃపరిమాణం, PDF, EXIF, OCR, QR లేదా రంగు వంటి చర్య పేరు మీకు తెలిసినప్పుడు శోధనను ఉపయోగించండి. + మీకు టాస్క్ రకం మాత్రమే తెలిసినప్పుడు వర్గాన్ని తెరవండి, ఆపై తరచుగా టూల్స్‌ను ఇష్టమైనవిగా పిన్ చేయండి. + మరొక యాప్ ఇమేజ్ టూల్‌బాక్స్‌లో చిత్రాన్ని షేర్ చేసినప్పుడు, షేర్ షీట్ ఫ్లో నుండి సాధనాన్ని ఎంచుకోండి. + ఫలితాలను దిగుమతి చేయండి, సేవ్ చేయండి మరియు భాగస్వామ్యం చేయండి + ఇన్‌పుట్‌ని ఎంచుకోవడం నుండి సవరించిన ఫైల్‌ని ఎగుమతి చేయడం వరకు సాధారణ విధానాన్ని అర్థం చేసుకోండి + సాధారణ సవరణ విధానాన్ని అనుసరించండి + చాలా సాధనాలు ఒకే రిథమ్‌ను అనుసరిస్తాయి: ఇన్‌పుట్‌ని ఎంచుకోండి, ఎంపికలను సర్దుబాటు చేయండి, ప్రివ్యూ చేయండి, ఆపై సేవ్ చేయండి లేదా భాగస్వామ్యం చేయండి. ముఖ్యమైన వివరాలు ఏమిటంటే, సేవ్ చేయడం అవుట్‌పుట్ ఫైల్‌ను సృష్టిస్తుంది, అయితే మరొక యాప్‌కి త్వరగా హ్యాండ్‌ఆఫ్ చేయడానికి భాగస్వామ్యం చేయడం ఉత్తమం. + పికర్ అనుమతించినప్పుడు సింగిల్-ఇమేజ్ సాధనాల కోసం ఒక ఫైల్ లేదా బ్యాచ్ సాధనాల కోసం అనేక ఫైల్‌లను ఎంచుకోండి. + సేవ్ చేయడానికి ముందు ప్రివ్యూ మరియు అవుట్‌పుట్ నియంత్రణలను తనిఖీ చేయండి, ముఖ్యంగా ఫార్మాట్, నాణ్యత మరియు ఫైల్ పేరు ఎంపికలు. + శీఘ్ర హ్యాండ్‌ఆఫ్ కోసం షేర్‌ని ఉపయోగించండి లేదా ఎంచుకున్న ఫోల్డర్‌లో మీకు నిరంతర కాపీ అవసరమైనప్పుడు సేవ్ చేయండి. + ఒకేసారి అనేక చిత్రాలతో పని చేయండి + పునరావృత పరిమాణాన్ని మార్చడానికి, మార్పిడి చేయడానికి, కుదింపు మరియు టాస్క్‌లకు పేరు పెట్టడానికి బ్యాచ్ సాధనాలు ఉత్తమమైనవి + ఊహించదగిన బ్యాచ్‌ను సిద్ధం చేయండి + ప్రతి అవుట్‌పుట్ ఒకే నియమాలను అనుసరించినప్పుడు బ్యాచ్ ప్రాసెసింగ్ వేగంగా జరుగుతుంది. పెద్ద పొరపాటు చేయడానికి ఇది సులభమైన ప్రదేశం, కాబట్టి సుదీర్ఘ ఎగుమతిని ప్రారంభించే ముందు పేరు, నాణ్యత, మెటాడేటా మరియు ఫోల్డర్ ప్రవర్తనను ఎంచుకోండి. + అన్ని సంబంధిత చిత్రాలను కలిపి ఎంచుకోండి మరియు అవుట్‌పుట్ క్రమం మీద ఆధారపడి ఉంటే క్రమాన్ని ఉంచండి. + ఎగుమతి ప్రారంభించడానికి ముందు పరిమాణం, ఆకృతి, నాణ్యత, మెటాడేటా మరియు ఫైల్ పేరు నియమాలను ఒకసారి సెట్ చేయండి. + సోర్స్ ఫైల్‌లు పెద్దగా ఉన్నప్పుడు లేదా మీకు టార్గెట్ ఫార్మాట్ కొత్తగా ఉన్నప్పుడు ముందుగా చిన్న టెస్ట్ బ్యాచ్‌ని అమలు చేయండి. + త్వరిత సింగిల్ సవరణ + మీకు ఒక చిత్రంపై అనేక సాధారణ మార్పులు అవసరమైనప్పుడు ఆల్ ఇన్ వన్ ఎడిటర్‌ని ఉపయోగించండి + టూల్ హోపింగ్ లేకుండా ఒక చిత్రాన్ని ముగించండి + చిత్రానికి ఒక ప్రత్యేక ఆపరేషన్ కాకుండా చిన్న మార్పుల గొలుసు అవసరమైనప్పుడు ఒకే సవరణ ఉపయోగపడుతుంది. బ్యాచ్ వర్క్‌ఫ్లోను నిర్మించకుండానే శీఘ్ర శుభ్రత, పరిదృశ్యం మరియు తుది కాపీని ఎగుమతి చేయడం కోసం ఇది సాధారణంగా వేగంగా ఉంటుంది. + సాధారణ సర్దుబాట్లు, మార్కప్, క్రాప్, రొటేషన్ లేదా ఎగుమతి మార్పులు అవసరమయ్యే ఒక చిత్రం కోసం ఒకే సవరణను తెరవండి. + ఫిల్టర్‌లకు ముందు కత్తిరించడం మరియు తుది కుదింపుకు ముందు ఫిల్టర్‌లు వంటి అతి తక్కువ పిక్సెల్‌లను మార్చే క్రమంలో సవరణలను వర్తింపజేయండి. + మీకు బ్యాచ్ ప్రాసెసింగ్, ఖచ్చితమైన ఫైల్-సైజ్ టార్గెట్‌లు, PDF అవుట్‌పుట్, OCR లేదా మెటాడేటా క్లీనప్ అవసరమైనప్పుడు బదులుగా ప్రత్యేక సాధనాన్ని ఉపయోగించండి. + ప్రీసెట్లు మరియు సెట్టింగ్‌లను ఉపయోగించండి + పునరావృత ఎంపికలను సేవ్ చేయండి మరియు మీ ప్రాధాన్య వర్క్‌ఫ్లో కోసం యాప్‌ను ట్యూన్ చేయండి + అదే సెటప్‌ను పునరావృతం చేయకుండా ఉండండి + మీరు ఒకే రకమైన ఫైల్‌ను తరచుగా ఎగుమతి చేసినప్పుడు ప్రీసెట్‌లు మరియు సెట్టింగ్‌లు ఉపయోగపడతాయి. మంచి ప్రీసెట్ రిపీట్ మాన్యువల్ చెక్‌లిస్ట్‌ను ఒక ట్యాప్‌గా మారుస్తుంది, అయితే గ్లోబల్ సెట్టింగ్‌లు కొత్త టూల్స్ ప్రారంభించే డిఫాల్ట్‌లను నిర్వచించాయి. + సాధారణ అవుట్‌పుట్ పరిమాణాలు, ఫార్మాట్‌లు, నాణ్యత విలువలు లేదా నామకరణ నమూనాల కోసం ప్రీసెట్‌లను సృష్టించండి. + డిఫాల్ట్ ఫార్మాట్, నాణ్యత, సేవ్ ఫోల్డర్, ఫైల్ పేరు, పికర్ మరియు ఇంటర్‌ఫేస్ ప్రవర్తన కోసం సెట్టింగ్‌లను సమీక్షించండి. + యాప్‌ని మళ్లీ ఇన్‌స్టాల్ చేయడానికి లేదా మరొక పరికరానికి తరలించడానికి ముందు సెట్టింగ్‌లను బ్యాకప్ చేయండి. + ఉపయోగకరమైన డిఫాల్ట్‌లను సెట్ చేయండి + డిఫాల్ట్ ఫార్మాట్, నాణ్యత, స్కేల్ మోడ్, రీసైజ్ రకం మరియు కలర్ స్పేస్‌ని ఒకసారి ఎంచుకోండి + కొత్త సాధనాలను మీ లక్ష్యానికి దగ్గరగా ప్రారంభించండి + డిఫాల్ట్ విలువలు కేవలం కాస్మెటిక్ ప్రాధాన్యతలు మాత్రమే కాదు. ప్రతి ఎగుమతిలో ఒకే ఎంపికలను మళ్లీ ఎంచుకోకుండా నివారించడంలో సహాయపడే అనేక సాధనాల్లో మొదటగా కనిపించే ఫార్మాట్, నాణ్యత, పరిమాణాన్ని మార్చడం, స్కేల్ మోడ్ మరియు కలర్ స్పేస్‌ను వారు నిర్ణయిస్తారు. + ఫోటోల కోసం JPEG లేదా ఆల్ఫా కోసం PNG/WebP వంటి మీ సాధారణ గమ్యస్థానానికి సరిపోయేలా డిఫాల్ట్ ఇమేజ్ ఫార్మాట్ మరియు నాణ్యతను సెట్ చేయండి. + మీరు తరచుగా కఠినమైన కొలతలు, సామాజిక ప్లాట్‌ఫారమ్‌లు లేదా డాక్యుమెంట్ పేజీల కోసం ఎగుమతి చేస్తుంటే డిఫాల్ట్ రీసైజ్ రకం మరియు స్కేల్ మోడ్‌ను ట్యూన్ చేయండి. + డిస్‌ప్లేలు, ప్రింట్ లేదా ఎక్స్‌టర్నల్ ఎడిటర్‌లలో మీ వర్క్‌ఫ్లో స్థిరమైన రంగు నిర్వహణ అవసరమైనప్పుడు మాత్రమే కలర్ స్పేస్ డిఫాల్ట్‌లను మార్చండి. + పికర్ మరియు లాంచర్ + యాప్ చాలా డైలాగ్‌లను తెరిచినప్పుడు లేదా తప్పు స్థానంలో ప్రారంభించినప్పుడు పికర్ సెట్టింగ్‌లను ఉపయోగించండి + ఫైల్‌లను తెరవడం మీ అలవాటుకు సరిపోయేలా చేయండి + పిక్కర్ మరియు లాంచర్ సెట్టింగ్‌లు ఇమేజ్ టూల్‌బాక్స్ మెయిన్ స్క్రీన్, టూల్ లేదా ఫైల్ సెలక్షన్ ఫ్లో నుండి ప్రారంభమవుతుందో లేదో నియంత్రిస్తాయి. మీరు సాధారణంగా ఆండ్రాయిడ్ షేర్ షీట్ నుండి ఎంటర్ చేసినప్పుడు లేదా యాప్ టూల్ లాంచర్ లాగా ఉండాలని మీరు కోరుకున్నప్పుడు ఈ ఎంపికలు ప్రత్యేకంగా ఉపయోగపడతాయి. + సిస్టమ్ పికర్ ఫైల్‌లను దాచినా, చాలా ఇటీవలి అంశాలను చూపినా లేదా క్లౌడ్ ఫైల్‌లను పేలవంగా హ్యాండిల్ చేసినా ఫోటో పికర్ సెట్టింగ్‌లను ఉపయోగించండి. + మీరు సాధారణంగా షేర్ నుండి నమోదు చేసే లేదా ఇప్పటికే సోర్స్ ఫైల్ తెలిసిన సాధనాల కోసం మాత్రమే స్కిప్ పికింగ్‌ని ప్రారంభించండి. + ఇమేజ్ టూల్‌బాక్స్ సాధారణ హోమ్ స్క్రీన్ కంటే టూల్ పికర్ లాగా ప్రవర్తించాలని మీరు కోరుకుంటే లాంచర్ మోడ్‌ని సమీక్షించండి. + చిత్ర మూలం + గ్యాలరీలు, ఫైల్ మేనేజర్‌లు మరియు క్లౌడ్ ఫైల్‌లతో ఉత్తమంగా పనిచేసే పికర్ మోడ్‌ను ఎంచుకోండి + సరైన మూలం నుండి ఫైల్‌లను ఎంచుకోండి + ఇమేజ్ సోర్స్ సెట్టింగ్‌లు యాప్ ఆండ్రాయిడ్‌ని చిత్రాల కోసం ఎలా అడుగుతుందో ప్రభావితం చేస్తుంది. మీ ఫైల్‌లు సిస్టమ్ గ్యాలరీలో, ఫైల్ మేనేజర్ ఫోల్డర్‌లో, క్లౌడ్ ప్రొవైడర్‌లో లేదా తాత్కాలిక కంటెంట్‌ను షేర్ చేసే మరొక యాప్‌లో నివసిస్తున్నాయా అనే దానిపై ఉత్తమ ఎంపిక ఆధారపడి ఉంటుంది. + మీరు సాధారణ గ్యాలరీ చిత్రాల కోసం అత్యంత సిస్టమ్-ఇంటిగ్రేటెడ్ అనుభవాన్ని పొందాలనుకున్నప్పుడు డిఫాల్ట్ పికర్‌ని ఉపయోగించండి. + మీరు ఆశించిన చోట ఆల్బమ్‌లు, ఫోల్డర్‌లు, క్లౌడ్ ఫైల్‌లు లేదా ప్రామాణికం కాని ఫార్మాట్‌లు కనిపించకపోతే పికర్ మోడ్‌ని మార్చండి. + సోర్స్ యాప్ నుండి నిష్క్రమించిన తర్వాత షేర్ చేసిన ఫైల్ కనిపించకుండా పోయినట్లయితే, దాన్ని నిరంతర పికర్ ద్వారా మళ్లీ తెరవండి లేదా ముందుగా స్థానిక కాపీని సేవ్ చేయండి. + ఇష్టమైనవి మరియు భాగస్వామ్య సాధనాలు + ప్రధాన స్క్రీన్‌ను ఫోకస్ చేసి, షేర్ ఫ్లోలలో అర్థం లేని సాధనాలను దాచండి + సాధన జాబితా శబ్దాన్ని తగ్గించండి + మీరు ప్రతిరోజూ కొన్ని సాధనాలను మాత్రమే ఉపయోగించినప్పుడు ఇష్టమైనవి మరియు భాగస్వామ్యం కోసం దాచిన సెట్టింగ్‌లు ఉపయోగకరంగా ఉంటాయి. మరొక యాప్ పంపే ఫైల్ రకాన్ని ఉపయోగించలేని సాధనాలను దాచడం ద్వారా వారు షేర్ ఫ్లోను ఆచరణాత్మకంగా ఉంచుతారు. + తరచుగా టూల్స్‌ని పిన్ చేయండి, తద్వారా టూల్స్‌ని వర్గం వారీగా సమూహం చేసినప్పటికీ వాటిని సులభంగా చేరుకోవచ్చు. + మరొక యాప్ నుండి వచ్చే ఫైల్‌లకు అవి అసంబద్ధంగా ఉన్నప్పుడు షేర్ ఫ్లోల నుండి సాధనాలను దాచండి. + మీరు చివరిలో ఇష్టమైనవి కావాలనుకుంటే లేదా సంబంధిత టూల్స్‌తో సమూహం చేసినట్లయితే ఇష్టమైన ఆర్డర్ సెట్టింగ్‌లను ఉపయోగించండి. + సెట్టింగ్‌లను బ్యాకప్ చేయండి + ప్రీసెట్లు, ప్రాధాన్యతలు మరియు యాప్ ప్రవర్తనను మరొక ఇన్‌స్టాల్‌కు సురక్షితంగా తరలించండి + మీ సెటప్‌ను పోర్టబుల్‌గా ఉంచండి + మీరు ప్రీసెట్‌లు, ఫోల్డర్‌లు, ఫైల్ పేరు నియమాలు, సాధనాల క్రమం మరియు దృశ్య ప్రాధాన్యతలను ట్యూన్ చేసిన తర్వాత బ్యాకప్ మరియు పునరుద్ధరణ చాలా ఉపయోగకరంగా ఉంటుంది. మెమరీ నుండి మీ వర్క్‌ఫ్లోను పునర్నిర్మించకుండా సెట్టింగ్‌లతో ప్రయోగాలు చేయడానికి లేదా యాప్‌ను మళ్లీ ఇన్‌స్టాల్ చేయడానికి బ్యాకప్ మిమ్మల్ని అనుమతిస్తుంది. + ప్రీసెట్లు, ఫైల్ పేరు ప్రవర్తన, డిఫాల్ట్ ఫార్మాట్‌లు లేదా టూల్ అమరికను మార్చిన తర్వాత బ్యాకప్‌ను సృష్టించండి. + మీరు డేటాను క్లియర్ చేయడానికి, మళ్లీ ఇన్‌స్టాల్ చేయడానికి లేదా పరికరాలను తరలించాలని ప్లాన్ చేస్తే, యాప్ ఫోల్డర్ వెలుపల ఎక్కడైనా బ్యాకప్‌ను నిల్వ చేయండి. + ముఖ్యమైన బ్యాచ్‌లను ప్రాసెస్ చేయడానికి ముందు సెట్టింగ్‌లను పునరుద్ధరించండి, తద్వారా మీ పాత సెటప్‌కు సరిపోయే ప్రవర్తనను డిఫాల్ట్ చేయండి మరియు సేవ్ చేయండి. + చిత్రాల పరిమాణాన్ని మార్చండి మరియు మార్చండి + ఒకటి లేదా అనేక చిత్రాల కోసం పరిమాణం, ఆకృతి, నాణ్యత మరియు మెటాడేటాను మార్చండి + పరిమాణం మార్చబడిన కాపీలను సృష్టించండి + రీసైజ్ మరియు కన్వర్ట్ అనేది ఊహాజనిత కొలతలు మరియు తేలికైన అవుట్‌పుట్ ఫైల్‌ల కోసం ప్రధాన సాధనం. చిత్రం పరిమాణం, అవుట్‌పుట్ ఫార్మాట్ మరియు మెటాడేటా ప్రవర్తన అన్నీ ఒకే సమయంలో ముఖ్యమైనప్పుడు దాన్ని ఉపయోగించండి. + ఒకటి లేదా అంతకంటే ఎక్కువ చిత్రాలను ఎంచుకోండి, ఆపై కొలతలు, స్కేల్ లేదా ఫైల్ పరిమాణం చాలా ముఖ్యమైనది కాదా అని ఎంచుకోండి. + అవుట్పుట్ ఫార్మాట్ మరియు నాణ్యతను ఎంచుకోండి; పారదర్శకత తప్పనిసరిగా సంరక్షించబడినప్పుడు PNG లేదా WebPని ఉపయోగించండి. + ఫలితాన్ని పరిదృశ్యం చేయండి, ఆపై అసలైన వాటిని మార్చకుండా రూపొందించిన కాపీలను సేవ్ చేయండి లేదా భాగస్వామ్యం చేయండి. + ఫైల్ పరిమాణం ద్వారా పరిమాణాన్ని మార్చండి + వెబ్‌సైట్, మెసెంజర్ లేదా ఫారమ్ భారీ చిత్రాలను తిరస్కరించినప్పుడు పరిమాణ పరిమితిని లక్ష్యంగా చేసుకోండి + ఖచ్చితమైన అప్‌లోడ్ పరిమితులను అమర్చండి + గమ్యం నిర్దిష్ట పరిమితి కంటే తక్కువ ఉన్న ఫైల్‌లను మాత్రమే ఆమోదించినప్పుడు నాణ్యత విలువలను ఊహించడం కంటే ఫైల్ పరిమాణం ద్వారా పరిమాణాన్ని మార్చడం ఉత్తమం. ఫలితాన్ని ఉపయోగించగలిగేలా ఉంచడానికి ప్రయత్నిస్తున్నప్పుడు సాధనం లక్ష్యాన్ని చేరుకోవడానికి కుదింపు మరియు కొలతలు సర్దుబాటు చేయవచ్చు. + మెటాడేటా మరియు ప్రొవైడర్ మార్పుల కోసం స్థలాన్ని వదిలివేయడానికి నిజమైన అప్‌లోడ్ పరిమితి కంటే కొంచెం దిగువన లక్ష్య పరిమాణాన్ని నమోదు చేయండి. + లక్ష్యం చిన్నగా ఉన్నప్పుడు ఫోటోల కోసం లాస్సీ ఫార్మాట్‌లను ఇష్టపడండి; పరిమాణం మార్చిన తర్వాత కూడా PNG పెద్దదిగా ఉంటుంది. + ఎగుమతి చేసిన తర్వాత ముఖాలు, వచనం మరియు అంచులను తనిఖీ చేయండి ఎందుకంటే దూకుడు పరిమాణ లక్ష్యాలు కనిపించే కళాఖండాలను పరిచయం చేయగలవు. + పరిమితి పరిమాణం + మీ గరిష్ట వెడల్పు, ఎత్తు లేదా ఫైల్ పరిమితులను మించిన చిత్రాలను మాత్రమే కుదించండి + ఇప్పటికే బాగానే ఉన్న చిత్రాల పరిమాణాన్ని మార్చడం మానుకోండి + పరిమితి పునఃపరిమాణం మిశ్రమ ఫోల్డర్‌లకు ఉపయోగపడుతుంది, ఇక్కడ కొన్ని చిత్రాలు భారీగా ఉంటాయి మరియు మరికొన్ని ఇప్పటికే సిద్ధం చేయబడ్డాయి. ప్రతి ఫైల్‌ను ఒకే పరిమాణంలో బలవంతంగా మార్చడానికి బదులుగా, ఇది ఆమోదయోగ్యమైన చిత్రాలను వాటి అసలు నాణ్యతకు దగ్గరగా ఉంచుతుంది. + ప్రతి ఫైల్‌ను గుడ్డిగా పరిమాణం మార్చడం కంటే గమ్యస్థానం ఆధారంగా గరిష్ట కొలతలు లేదా పరిమితులను సెట్ చేయండి. + మీరు మూలాధారాల కంటే భారీగా ఉండే అవుట్‌పుట్‌లను నివారించాలనుకున్నప్పుడు స్కిప్-ఇఫ్-లార్జర్ స్టైల్ ప్రవర్తనను ప్రారంభించండి. + వివిధ కెమెరాలు, స్క్రీన్‌షాట్‌లు లేదా డౌన్‌లోడ్‌ల నుండి మూలాధార పరిమాణాలు చాలా తేడా ఉన్న బ్యాచ్‌ల కోసం దీన్ని ఉపయోగించండి. + కత్తిరించండి, తిప్పండి మరియు నిఠారుగా చేయండి + ఇతర సాధనాల్లో చిత్రాన్ని ఎగుమతి చేయడానికి లేదా ఉపయోగించే ముందు ముఖ్యమైన ప్రాంతాన్ని ఫ్రేమ్ చేయండి + కూర్పును శుభ్రం చేయండి + ముందుగా కత్తిరించడం వలన తరువాతి ఫిల్టర్‌లు, OCR, PDF పేజీలు మరియు షేరింగ్ ఫలితాలు క్లీనర్‌గా మారతాయి. + క్రాప్ తెరిచి, మీరు సర్దుబాటు చేయాలనుకుంటున్న చిత్రాన్ని ఎంచుకోండి. + అవుట్‌పుట్ తప్పనిసరిగా పోస్ట్, డాక్యుమెంట్ లేదా అవతార్‌కు సరిపోయేటప్పుడు ఉచిత క్రాప్ లేదా కారక నిష్పత్తిని ఉపయోగించండి. + సేవ్ చేయడానికి ముందు తిప్పండి లేదా తిప్పండి, తద్వారా తదుపరి సాధనాలు సరిదిద్దబడిన చిత్రాన్ని పొందుతాయి. + ఫిల్టర్లు మరియు సర్దుబాట్లను వర్తింపజేయండి + ఎడిట్ చేయగల ఫిల్టర్ స్టాక్‌ను రూపొందించండి మరియు ఎగుమతి చేయడానికి ముందు ఫలితాన్ని ప్రివ్యూ చేయండి + చిత్రం రూపాన్ని ట్యూన్ చేయండి + శీఘ్ర దిద్దుబాట్లు, శైలీకృత అవుట్‌పుట్ మరియు OCR లేదా ప్రింటింగ్ కోసం చిత్రాలను సిద్ధం చేయడానికి ఫిల్టర్‌లు ఉపయోగపడతాయి. కాంట్రాస్ట్, షార్ప్‌నెస్ మరియు బ్రైట్‌నెస్‌లో చిన్న మార్పులు వచనం లేదా చక్కటి వివరాలను కలిగి ఉన్నప్పుడు భారీ ప్రభావాల కంటే ఎక్కువ ముఖ్యమైనవి. + ఫిల్టర్‌లను ఒక్కొక్కటిగా జోడించి, ప్రతి మార్పు తర్వాత ప్రివ్యూను గమనించండి. + పనిని బట్టి ప్రకాశం, కాంట్రాస్ట్, షార్ప్‌నెస్, బ్లర్, కలర్ లేదా మాస్క్‌లను సర్దుబాటు చేయండి. + ప్రివ్యూ మీ లక్ష్య పరికరం, పత్రం లేదా సామాజిక ప్లాట్‌ఫారమ్‌తో సరిపోలినప్పుడు మాత్రమే ఎగుమతి చేయండి. + ముందుగా ప్రివ్యూ చేయండి + భారీ సవరణ, మార్పిడి లేదా శుభ్రపరిచే సాధనాన్ని ఎంచుకునే ముందు చిత్రాలను తనిఖీ చేయండి + మీరు నిజంగా ఏమి అందుకున్నారో తనిఖీ చేయండి + ఒక ఫైల్ చాట్, క్లౌడ్ స్టోరేజ్ లేదా మరొక యాప్ నుండి వచ్చినప్పుడు మరియు అందులో ఏమి ఉందో మీకు ఖచ్చితంగా తెలియనప్పుడు ఇమేజ్ ప్రివ్యూ ఉపయోగపడుతుంది. కొలతలు, పారదర్శకత, ధోరణి మరియు దృశ్య నాణ్యతను తనిఖీ చేయడం మొదట సరైన ఫాలో-అప్ సాధనాన్ని ఎంచుకోవడంలో సహాయపడుతుంది. + మీరు వాటిని ఏమి చేయాలో నిర్ణయించే ముందు అనేక చిత్రాలను తనిఖీ చేయవలసి వచ్చినప్పుడు చిత్ర పరిదృశ్యాన్ని తెరవండి. + భ్రమణ సమస్యలు, పారదర్శక ప్రాంతాలు, కుదింపు కళాఖండాలు మరియు ఊహించని విధంగా పెద్ద కొలతలు కోసం చూడండి. + ఫిక్సింగ్ అవసరమని మీకు తెలిసిన తర్వాత మాత్రమే పరిమాణం మార్చడం, కత్తిరించడం, సరిపోల్చడం, EXIF ​​లేదా బ్యాక్‌గ్రౌండ్ రిమూవర్‌కి తరలించండి. + నేపథ్యాన్ని తీసివేయండి లేదా పునరుద్ధరించండి + పునరుద్ధరణ సాధనాలతో నేపథ్యాలను స్వయంచాలకంగా తొలగించండి లేదా అంచులను మాన్యువల్‌గా మెరుగుపరచండి + విషయాన్ని ఉంచండి, మిగిలిన వాటిని తీసివేయండి + మీరు ఆటోమేటిక్ ఎంపికను జాగ్రత్తగా మాన్యువల్ క్లీనప్‌తో కలిపినప్పుడు బ్యాక్‌గ్రౌండ్ రిమూవల్ ఉత్తమంగా పని చేస్తుంది. చివరి ఎగుమతి ఆకృతి మాస్క్‌కి సంబంధించినంత ముఖ్యమైనది, ఎందుకంటే ప్రివ్యూ సరిగ్గా కనిపించినప్పటికీ JPEG పారదర్శక ప్రాంతాలను చదును చేస్తుంది. + నేపథ్యం నుండి విషయం స్పష్టంగా వేరు చేయబడితే స్వయంచాలక తొలగింపుతో ప్రారంభించండి. + వెంట్రుకలు, మూలలు, నీడలు మరియు చిన్న వివరాలను పరిష్కరించడానికి జూమ్ ఇన్ చేసి, ఎరేజ్ మరియు రీస్టోర్ మధ్య మారండి. + మీకు చివరి ఫైల్‌లో పారదర్శకత అవసరమైనప్పుడు PNG లేదా WebPగా సేవ్ చేయండి. + గీయండి, గుర్తించండి మరియు వాటర్‌మార్క్ చేయండి + బాణాలు, గమనికలు, ముఖ్యాంశాలు, సంతకాలు లేదా పునరావృతమయ్యే వాటర్‌మార్క్ ఓవర్‌లేలను జోడించండి + కనిపించే సమాచారాన్ని జోడించండి + మార్కప్ సాధనాలు చిత్రాన్ని వివరించడంలో సహాయపడతాయి, అయితే వాటర్‌మార్కింగ్ బ్రాండ్ లేదా ఎగుమతి చేసిన ఫైల్‌లను రక్షించడంలో సహాయపడుతుంది. + మీకు ఫ్రీహ్యాండ్ నోట్స్, ఆకారాలు, హైలైట్ చేయడం లేదా శీఘ్ర ఉల్లేఖనాలు అవసరమైనప్పుడు డ్రా ఉపయోగించండి. + అవుట్‌పుట్‌లలో ఒకే టెక్స్ట్ లేదా ఇమేజ్ మార్క్ స్థిరంగా కనిపించినప్పుడు వాటర్‌మార్కింగ్‌ని ఉపయోగించండి. + ఎగుమతి చేయడానికి ముందు అస్పష్టత, స్థానం మరియు స్కేల్‌ను తనిఖీ చేయండి, తద్వారా గుర్తు కనిపిస్తుంది కానీ దృష్టి మరల్చదు. + డిఫాల్ట్‌లను గీయండి + వేగవంతమైన మార్కప్ కోసం లైన్ వెడల్పు, రంగు, పాత్ మోడ్, ఓరియంటేషన్ లాక్ మరియు మాగ్నిఫైయర్‌ని సెట్ చేయండి + డ్రాయింగ్ ఊహించదగిన అనుభూతిని కలిగించండి + మీరు స్క్రీన్‌షాట్‌లను ఉల్లేఖించినప్పుడు, డాక్యుమెంట్‌లను గుర్తించినప్పుడు లేదా చిత్రాలపై తరచుగా సంతకం చేసినప్పుడు డ్రా సెట్టింగ్‌లు సహాయపడతాయి. డిఫాల్ట్‌లు సెటప్ సమయాన్ని తగ్గిస్తాయి, అయితే మాగ్నిఫైయర్ మరియు ఓరియంటేషన్ లాక్ చిన్న స్క్రీన్‌లలో ఖచ్చితమైన సవరణలను సులభతరం చేస్తాయి. + డిఫాల్ట్ లైన్ వెడల్పును సెట్ చేయండి మరియు బాణాలు, ముఖ్యాంశాలు లేదా సంతకాల కోసం మీరు ఎక్కువగా ఉపయోగించే విలువలకు రంగును గీయండి. + మీరు సాధారణంగా ఒకే రకమైన స్ట్రోక్‌లు, ఆకారాలు లేదా మార్కప్‌లను గీసినప్పుడు డిఫాల్ట్ పాత్ మోడ్‌ని ఉపయోగించండి. + ఫింగర్ ఇన్‌పుట్ చిన్న వివరాలను ఖచ్చితంగా ఉంచడం కష్టతరం చేసినప్పుడు మాగ్నిఫైయర్ లేదా ఓరియంటేషన్ లాక్‌ని ప్రారంభించండి. + బహుళ-చిత్ర లేఅవుట్‌లు + స్క్రీన్‌షాట్‌లు, స్కాన్‌లు లేదా పోలిక స్ట్రిప్‌లను ఏర్పాటు చేయడానికి ముందు సరైన బహుళ-చిత్ర సాధనాన్ని ఎంచుకోండి + సరైన లేఅవుట్ సాధనాన్ని ఉపయోగించండి + ఈ సాధనాలు ఒకేలా అనిపిస్తాయి, అయితే ప్రతి ఒక్కటి విభిన్న రకాల బహుళ-చిత్రాల అవుట్‌పుట్ కోసం ట్యూన్ చేయబడింది. ముందుగా సరైనదాన్ని ఎంచుకోవడం వలన వేరొక ఫలితం కోసం రూపొందించబడిన లేఅవుట్ నియంత్రణల పోరాటాన్ని నివారించవచ్చు. + ఒక కూర్పులో అనేక చిత్రాలతో రూపొందించిన లేఅవుట్‌ల కోసం Collage Makerని ఉపయోగించండి. + చిత్రాలు ఒక పొడవైన నిలువు లేదా క్షితిజ సమాంతర స్ట్రిప్‌గా మారినప్పుడు ఇమేజ్ స్టిచింగ్ లేదా స్టాకింగ్‌ని ఉపయోగించండి. + ఒక పెద్ద చిత్రం అనేక చిన్న ముక్కలుగా మారవలసి వచ్చినప్పుడు ఇమేజ్ స్ప్లిటింగ్‌ని ఉపయోగించండి. + ఇమేజ్ ఫార్మాట్‌లను మార్చండి + పిక్సెల్‌లను మాన్యువల్‌గా సవరించకుండా JPEG, PNG, WebP, AVIF, JXL మరియు ఇతర కాపీలను సృష్టించండి + ఉద్యోగం కోసం ఆకృతిని ఎంచుకోండి + ఫోటోలు, స్క్రీన్‌షాట్‌లు, పారదర్శకత, కుదింపు లేదా అనుకూలత కోసం వివిధ ఫార్మాట్‌లు ఉత్తమం. డెస్టినేషన్ యాప్ దేనికి మద్దతిస్తుందో మరియు సోర్స్‌లో మీరు ఉంచాలనుకుంటున్న ఆల్ఫా, యానిమేషన్ లేదా మెటాడేటా ఉందో లేదో మీరు అర్థం చేసుకున్నప్పుడు మార్పిడి సురక్షితంగా ఉంటుంది. + అనుకూల ఫోటోల కోసం JPEG, లాస్‌లెస్ ఇమేజ్‌లు మరియు ఆల్ఫా కోసం PNG మరియు కాంపాక్ట్ షేరింగ్ కోసం WebPని ఉపయోగించండి. + ఫార్మాట్ మద్దతు ఇచ్చినప్పుడు నాణ్యతను సర్దుబాటు చేయండి; తక్కువ విలువలు పరిమాణాన్ని తగ్గిస్తాయి కానీ కళాఖండాలను జోడించవచ్చు. + మీరు మార్చబడిన ఫైల్‌లు లక్ష్య యాప్‌లో సరిగ్గా తెరిచి ఉన్నాయని ధృవీకరించే వరకు అసలైన వాటిని ఉంచండి. + EXIF మరియు గోప్యతను తనిఖీ చేయండి + సున్నితమైన ఫోటోలను భాగస్వామ్యం చేయడానికి ముందు మెటాడేటాను వీక్షించండి, సవరించండి లేదా తీసివేయండి + దాచిన చిత్ర డేటాను నియంత్రించండి + EXIF కెమెరా డేటా, తేదీలు, స్థానం, ధోరణి మరియు చిత్రంలో కనిపించని ఇతర వివరాలను కలిగి ఉంటుంది. పబ్లిక్ షేరింగ్, సపోర్ట్ రిపోర్ట్‌లు, మార్కెట్‌ప్లేస్ లిస్టింగ్‌లు లేదా ప్రైవేట్ స్థలాన్ని బహిర్గతం చేసే ఏదైనా ఫోటో ముందు ఇది తనిఖీ చేయడం విలువైనదే. + లొకేషన్ లేదా ప్రైవేట్ పరికర సమాచారాన్ని కలిగి ఉండే ఫోటోలను షేర్ చేయడానికి ముందు EXIF ​​టూల్స్ తెరవండి. + సున్నితమైన ఫీల్డ్‌లను తీసివేయండి లేదా తేదీ, ధోరణి, రచయిత లేదా కెమెరా డేటా వంటి తప్పు ట్యాగ్‌లను సవరించండి. + కొత్త కాపీని సేవ్ చేయండి మరియు గోప్యత ముఖ్యమైనప్పుడు అసలు కాపీని కాకుండా ఆ కాపీని షేర్ చేయండి. + ఆటో ఎక్సిఫ్ క్లీనప్ + తేదీలను భద్రపరచాలా వద్దా అని నిర్ణయించేటప్పుడు డిఫాల్ట్‌గా మెటాడేటాను తీసివేయండి + గోప్యతను డిఫాల్ట్‌గా చేయండి + ప్రతి ఎగుమతి కోసం గోప్యతా క్లీనప్‌ని గుర్తుంచుకోవడానికి బదులుగా స్వయంచాలకంగా జరగాలని మీరు కోరుకున్నప్పుడు EXIF ​​సెట్టింగ్‌ల సమూహం ఉపయోగకరంగా ఉంటుంది. తేదీ సమయాన్ని ఉంచడం అనేది ఒక ప్రత్యేక నిర్ణయం ఎందుకంటే తేదీలు క్రమబద్ధీకరించడానికి ఉపయోగపడతాయి కానీ భాగస్వామ్యం చేయడానికి సున్నితంగా ఉంటాయి. + మీ ఎగుమతులు చాలా వరకు పబ్లిక్ లేదా సెమీ పబ్లిక్ షేరింగ్ కోసం ఉద్దేశించబడినప్పుడు ఎల్లప్పుడూ క్లియర్ EXIFని ప్రారంభించండి. + ప్రతి టైమ్‌స్టాంప్‌ను తీసివేయడం కంటే క్యాప్చర్ సమయాన్ని భద్రపరచడం చాలా ముఖ్యమైనది అయినప్పుడు మాత్రమే Keep Date సమయాన్ని ప్రారంభించండి. + మీరు కొన్ని ఫీల్డ్‌లను ఉంచి, మరికొన్నింటిని తీసివేయాల్సిన ఫైల్‌ల కోసం మాన్యువల్ EXIF ​​ఎడిటింగ్‌ని ఉపయోగించండి. + ఫైల్ పేర్లను నియంత్రించండి + ఉపసర్గలు, ప్రత్యయాలు, టైమ్‌స్టాంప్‌లు, ప్రీసెట్‌లు, చెక్‌సమ్‌లు మరియు సీక్వెన్స్ నంబర్‌లను ఉపయోగించండి + ఎగుమతులను సులభంగా కనుగొనండి + మంచి ఫైల్ పేరు నమూనా బ్యాచ్‌లను క్రమబద్ధీకరించడంలో సహాయపడుతుంది మరియు ప్రమాదవశాత్తు ఓవర్‌రైట్‌లను నిరోధిస్తుంది. ఎగుమతులు సోర్స్ ఫోల్డర్‌కి తిరిగి వెళ్లినప్పుడు ఫైల్‌నేమ్ సెట్టింగ్‌లు ప్రత్యేకంగా ఉపయోగపడతాయి, ఇక్కడ సారూప్య పేర్లు త్వరగా గందరగోళంగా మారవచ్చు. + సెట్టింగ్‌లలో ఫైల్ పేరు ప్రిఫిక్స్, ప్రత్యయం, టైమ్‌స్టాంప్, అసలు పేరు, ప్రీసెట్ సమాచారం లేదా సీక్వెన్స్ ఎంపికలను సెట్ చేయండి. + పేజీలు, ఫ్రేమ్‌లు లేదా దృశ్య రూపకల్పనల వంటి ఆర్డర్ ముఖ్యమైన బ్యాచ్‌ల కోసం సీక్వెన్స్ నంబర్‌లను ఉపయోగించండి. + మీరు ఇప్పటికే ఉన్న ఫైల్‌లను భర్తీ చేయడం ప్రస్తుత ఫోల్డర్‌కు సురక్షితమైనదని మీరు నిర్ధారించుకున్నప్పుడు మాత్రమే ఓవర్‌రైట్‌ని ప్రారంభించండి. + ఫైల్‌లను ఓవర్‌రైట్ చేయండి + మీ వర్క్‌ఫ్లో ఉద్దేశపూర్వకంగా విధ్వంసకరంగా ఉన్నప్పుడు మాత్రమే సరిపోలే పేర్లతో అవుట్‌పుట్‌లను భర్తీ చేయండి + ఓవర్‌రైట్ మోడ్‌ను జాగ్రత్తగా ఉపయోగించండి + ఫైల్‌లను ఓవర్‌రైట్ చేయడం వల్ల పేరు పెట్టే వైరుధ్యాలు ఎలా నిర్వహించబడతాయో మారుస్తుంది. అదే ఫోల్డర్‌లోకి పునరావృత ఎగుమతులు చేయడానికి ఇది ఉపయోగపడుతుంది, అయితే మీ ఫైల్ పేరు నమూనా మళ్లీ అదే పేరును ఉత్పత్తి చేస్తే మునుపటి ఫలితాలను కూడా భర్తీ చేయవచ్చు. + పాత జెనరేట్ చేయబడిన ఫైల్‌లను రీప్లేస్ చేయడం మరియు తిరిగి పొందగలిగే ఫోల్డర్‌ల కోసం మాత్రమే ఓవర్‌రైట్‌ని ప్రారంభించండి. + అసలైనవి, క్లయింట్ ఫైల్‌లు, వన్-టైమ్ స్కాన్‌లు లేదా బ్యాకప్ లేకుండా ఏదైనా ఎగుమతి చేసేటప్పుడు ఓవర్‌రైట్ డిజేబుల్‌గా ఉంచండి. + ఉద్దేశపూర్వక ఫైల్ పేరు నమూనాతో ఓవర్‌రైట్‌ను కలపండి, తద్వారా ఉద్దేశించిన అవుట్‌పుట్‌లు మాత్రమే భర్తీ చేయబడతాయి. + ఫైల్ పేరు నమూనాలు + అసలు పేర్లు, ఉపసర్గలు, ప్రత్యయాలు, టైమ్‌స్టాంప్‌లు, ప్రీసెట్‌లు, స్కేల్ మోడ్ మరియు చెక్‌సమ్‌లను కలపండి + అవుట్‌పుట్‌ను వివరించే పేర్లను రూపొందించండి + ఫైల్ పేరు నమూనా సెట్టింగ్‌లు ఎగుమతి చేయబడిన ఫైల్‌లను వాటికి ఏమి జరిగిందో ఉపయోగకరమైన చరిత్రగా మార్చగలవు. ఇది బ్యాచ్‌లలో ముఖ్యమైనది ఎందుకంటే ఫోల్డర్ వీక్షణ మాత్రమే మీరు అసలు పరిమాణం, ప్రీసెట్, ఫార్మాట్ మరియు ప్రాసెసింగ్ క్రమాన్ని వేరు చేయగల ఏకైక ప్రదేశం కావచ్చు. + మాన్యువల్ సార్టింగ్ కోసం మీకు చదవగలిగే పేర్లు అవసరమైనప్పుడు అసలు ఫైల్ పేరు, ఉపసర్గ, ప్రత్యయం మరియు టైమ్‌స్టాంప్‌ని ఉపయోగించండి. + అవుట్‌పుట్‌లు ఎలా ఉత్పత్తి చేయబడతాయో తప్పనిసరిగా డాక్యుమెంట్ చేసినప్పుడు ప్రీసెట్, స్కేల్ మోడ్, ఫైల్ పరిమాణం లేదా చెక్‌సమ్ సమాచారాన్ని జోడించండి. + ఫైల్‌లు అసలైనవి లేదా పేజీ ఆర్డర్‌తో జతగా ఉండాల్సిన వర్క్‌ఫ్లోల కోసం యాదృచ్ఛిక పేర్లను నివారించండి. + ఫైల్‌లు ఎక్కడ సేవ్ చేయబడతాయో ఎంచుకోండి + ఫోల్డర్‌లు, ఒరిజినల్-ఫోల్డర్ సేవింగ్ మరియు వన్-టైమ్ సేవ్ లొకేషన్‌లను సెట్ చేయడం ద్వారా ఎగుమతులను కోల్పోకుండా ఉండండి + అవుట్‌పుట్‌లను ఊహించగలిగేలా ఉంచండి + మీరు చాలా ఫైల్‌లను ప్రాసెస్ చేసినప్పుడు లేదా క్లౌడ్ ప్రొవైడర్‌ల నుండి ఫైల్‌లను షేర్ చేసినప్పుడు, ప్రవర్తనను సేవ్ చేయడం చాలా ముఖ్యం. ఫోల్డర్ సెట్టింగ్‌లు అవుట్‌పుట్‌లు ఒక తెలిసిన స్థలంలో సేకరించాలా, అసలైన వాటి పక్కన కనిపించాలా లేదా నిర్దిష్ట పని కోసం తాత్కాలికంగా ఎక్కడికైనా వెళ్లాలా అని నిర్ణయిస్తాయి. + మీరు ఎల్లప్పుడూ ఒకే చోట ఎగుమతులు చేయాలనుకుంటే డిఫాల్ట్ సేవింగ్ ఫోల్డర్‌ను సెట్ చేయండి. + క్లీనప్ వర్క్‌ఫ్లోల కోసం సేవ్-టు-ఒరిజినల్-ఫోల్డర్‌ని ఉపయోగించండి, ఇక్కడ అవుట్‌పుట్ సోర్స్ ఫైల్‌కు సమీపంలో ఉండాలి. + మీ డిఫాల్ట్‌ను మార్చకుండా ఒక పనికి వేరే గమ్యస్థానం అవసరమైనప్పుడు వన్-టైమ్ సేవ్ లొకేషన్‌ను ఉపయోగించండి. + వన్-టైమ్ సేవ్ ఫోల్డర్‌లు + మీ సాధారణ గమ్యాన్ని మార్చకుండానే తదుపరి ఎగుమతులను తాత్కాలిక ఫోల్డర్‌కు పంపండి + ప్రత్యేక ఉద్యోగాలను వేరుగా ఉంచండి + తాత్కాలిక ప్రాజెక్ట్‌లు, క్లయింట్ ఫోల్డర్‌లు, క్లీనప్ సెషన్‌లు లేదా మీ సాధారణ ఇమేజ్ టూల్‌బాక్స్ ఫోల్డర్‌తో మిక్స్ చేయకూడని ఎగుమతుల కోసం వన్-టైమ్ సేవ్ లొకేషన్ ఉపయోగపడుతుంది. ఇది మీ డిఫాల్ట్ సెటప్‌ను తిరిగి వ్రాయకుండానే స్వల్పకాలిక గమ్యాన్ని అందిస్తుంది. + మీ సాధారణ ఎగుమతి స్థానానికి వెలుపల ఉన్న పనిని ప్రారంభించే ముందు ఒక పర్యాయ ఫోల్డర్‌ను ఎంచుకోండి. + చిన్న ప్రాజెక్ట్‌లు, భాగస్వామ్య ఫోల్డర్‌లు లేదా అవుట్‌పుట్‌లు తప్పనిసరిగా సమూహంగా ఉండే బ్యాచ్‌ల కోసం దీన్ని ఉపయోగించండి. + ఉద్యోగం ముగిసిన తర్వాత డిఫాల్ట్ ఫోల్డర్‌కి తిరిగి వెళ్లండి, తద్వారా తర్వాత ఎగుమతులు ఊహించని విధంగా తాత్కాలిక స్థానానికి చేరవు. + బ్యాలెన్స్ నాణ్యత మరియు ఫైల్ పరిమాణం + అంచనా వేయడానికి బదులుగా కొలతలు, ఆకృతి లేదా నాణ్యతను ఎప్పుడు మార్చాలో అర్థం చేసుకోండి + ఫైళ్లను ఉద్దేశపూర్వకంగా కుదించండి + ఫైల్ పరిమాణం చిత్రం కొలతలు, ఫార్మాట్, నాణ్యత, పారదర్శకత మరియు కంటెంట్ సంక్లిష్టతపై ఆధారపడి ఉంటుంది. ఫైల్ ఇప్పటికీ చాలా భారీగా ఉంటే, కొలతలు మార్చడం సాధారణంగా నాణ్యతను పదేపదే తగ్గించడం కంటే సహాయపడుతుంది. + గమ్యం ప్రదర్శించగలిగే దానికంటే చిత్రం పెద్దగా ఉన్నప్పుడు ముందుగా కొలతల పరిమాణాన్ని మార్చండి. + నష్టపోయిన ఫార్మాట్‌ల కోసం మాత్రమే తక్కువ నాణ్యత మరియు ఎగుమతి తర్వాత టెక్స్ట్, అంచులు, గ్రేడియంట్లు మరియు ముఖాలను తనిఖీ చేయండి. + నాణ్యత మార్పులు సరిపోనప్పుడు ఆకృతిని మార్చండి, ఉదాహరణకు భాగస్వామ్యం కోసం WebP లేదా స్ఫుటమైన పారదర్శక ఆస్తుల కోసం PNG. + యానిమేటెడ్ ఫార్మాట్‌లతో పని చేయండి + ఫైల్‌లో ఒక బిట్‌మ్యాప్‌కు బదులుగా ఫ్రేమ్‌లు ఉన్నప్పుడు GIF, APNG, WebP మరియు JXL సాధనాలను ఉపయోగించండి + ప్రతి చిత్రాన్ని నిశ్చలంగా పరిగణించవద్దు + యానిమేటెడ్ ఫార్మాట్‌లకు ఫ్రేమ్‌లు, వ్యవధి మరియు అనుకూలతను అర్థం చేసుకునే సాధనాలు అవసరం. + ఆ ఫార్మాట్‌ల కోసం మీకు ఫ్రేమ్-స్థాయి కార్యకలాపాలు అవసరమైనప్పుడు GIF లేదా APNG సాధనాలను ఉపయోగించండి. + మీకు ఆధునిక కంప్రెషన్ లేదా యానిమేటెడ్ WebP అవుట్‌పుట్ అవసరమైనప్పుడు WebP సాధనాలను ఉపయోగించండి. + కొన్ని ప్లాట్‌ఫారమ్‌లు యానిమేటెడ్ చిత్రాలను మార్చడం లేదా చదును చేయడం వలన భాగస్వామ్యం చేయడానికి ముందు యానిమేషన్ సమయాన్ని ప్రివ్యూ చేయండి. + అవుట్‌పుట్‌ను సరిపోల్చండి మరియు ప్రివ్యూ చేయండి + ఎగుమతి చేయడానికి ముందు మార్పులు, ఫైల్ పరిమాణం మరియు దృశ్యమాన తేడాలను తనిఖీ చేయండి + భాగస్వామ్యం చేయడానికి ముందు ధృవీకరించండి + కంప్రెషన్ కళాఖండాలు, తప్పు రంగులు లేదా అవాంఛిత సవరణలను ముందుగానే పట్టుకోవడంలో పోలిక సాధనాలు సహాయపడతాయి. + మీరు రెండు వెర్షన్లను పక్కపక్కనే తనిఖీ చేయాలనుకున్నప్పుడు సరిపోల్చండి తెరవండి. + సమస్యలు సులభంగా మిస్ అయ్యే అంచులు, వచనం, గ్రేడియంట్లు మరియు పారదర్శక ప్రాంతాలకు జూమ్ చేయండి. + అవుట్‌పుట్ చాలా భారీగా లేదా అస్పష్టంగా ఉంటే, మునుపటి సాధనానికి తిరిగి వెళ్లి నాణ్యత లేదా ఆకృతిని సర్దుబాటు చేయండి. + PDF టూల్స్ హబ్‌ని ఉపయోగించండి + PDF ఫైల్‌లను విలీనం చేయండి, విభజించండి, తిప్పండి, పేజీలను తీసివేయండి, కుదించండి, రక్షించండి, OCR మరియు వాటర్‌మార్క్ చేయండి + PDF ఆపరేషన్‌ని ఎంచుకోండి + PDF హబ్ ఒక ఎంట్రీ పాయింట్ వెనుక చర్యలను డాక్యుమెంట్ చేస్తుంది కాబట్టి మీరు ఖచ్చితమైన ఆపరేషన్‌ను ఎంచుకోవచ్చు. ఫైల్ ఇప్పటికే PDFగా ఉన్నప్పుడు అక్కడ ప్రారంభించండి మరియు మీ మూలం ఇప్పటికీ చిత్రాల సెట్‌గా ఉన్నప్పుడు చిత్రాలను PDF లేదా డాక్యుమెంట్ స్కానర్‌కు ఉపయోగించండి. + PDF సాధనాలను తెరిచి, మీ లక్ష్యానికి సరిపోయే ఆపరేషన్‌ను ఎంచుకోండి. + మూలాధార PDF లేదా చిత్రాలను ఎంచుకుని, పేజీ క్రమం, భ్రమణ, రక్షణ లేదా OCR ఎంపికలను సమీక్షించండి. + సృష్టించిన PDFని సేవ్ చేసి, పేజీ కౌంట్, ఆర్డర్ మరియు రీడబిలిటీని నిర్ధారించడానికి దాన్ని ఒకసారి తెరవండి. + చిత్రాలను PDFగా మార్చండి + స్కాన్‌లు, స్క్రీన్‌షాట్‌లు, రసీదులు లేదా ఫోటోలను ఒక భాగస్వామ్య పత్రంలో కలపండి + శుభ్రమైన పత్రాన్ని రూపొందించండి + చిత్రాలు కత్తిరించబడినప్పుడు, ఆర్డర్ చేయబడినప్పుడు మరియు స్థిరంగా పరిమాణంలో ఉన్నప్పుడు అవి మెరుగైన PDF పేజీలుగా మారుతాయి. చిత్ర జాబితాను పేజీ స్టాక్ లాగా పరిగణించండి: ప్రతి భ్రమణం, మార్జిన్ మరియు పేజీ-పరిమాణ ఎంపిక తుది పత్రం ఎంత రీడబుల్ అనిపిస్తుంది. + పేజీ అంచులు, నీడలు లేదా ఓరియంటేషన్ తప్పుగా ఉన్నప్పుడు మొదట చిత్రాలను కత్తిరించండి లేదా తిప్పండి. + ఉద్దేశించిన పేజీ క్రమంలో చిత్రాలను ఎంచుకోండి మరియు సాధనం వాటిని అందిస్తే పేజీ పరిమాణం లేదా మార్జిన్‌లను సర్దుబాటు చేయండి. + PDFని ఎగుమతి చేసి, పంపే ముందు అన్ని పేజీలు చదవగలిగేలా ఉన్నాయో లేదో తనిఖీ చేయండి. + పత్రాలను స్కాన్ చేయండి + పేపర్ పేజీలను క్యాప్చర్ చేయండి మరియు వాటిని PDF, OCR లేదా భాగస్వామ్యం కోసం సిద్ధం చేయండి + చదవగలిగే పేజీలను క్యాప్చర్ చేయండి + ఫ్లాట్ పేజీలు, మంచి లైటింగ్ మరియు సరిదిద్దబడిన దృక్పథంతో డాక్యుమెంట్ స్కానింగ్ ఉత్తమంగా పనిచేస్తుంది. OCR లేదా PDF ఎగుమతి ముందు క్లీన్ స్కాన్ సమయం ఆదా చేస్తుంది ఎందుకంటే టెక్స్ట్ గుర్తింపు మరియు కుదింపు రెండూ పేజీ నాణ్యతపై ఆధారపడి ఉంటాయి. + తగినంత కాంతి మరియు కనిష్ట నీడలతో పత్రాన్ని విరుద్ధమైన ఉపరితలంపై ఉంచండి. + గుర్తించబడిన మూలలను సర్దుబాటు చేయండి లేదా మాన్యువల్‌గా కత్తిరించండి, తద్వారా పేజీ ఫ్రేమ్‌ను శుభ్రంగా నింపుతుంది. + చిత్రాలు లేదా PDFగా ఎగుమతి చేయండి, ఆపై మీకు ఎంచుకోదగిన వచనం అవసరమైతే OCRని అమలు చేయండి. + PDF ఫైల్‌లను రక్షించండి లేదా అన్‌లాక్ చేయండి + పాస్‌వర్డ్‌లను జాగ్రత్తగా ఉపయోగించండి మరియు సాధ్యమైనప్పుడు సవరించగలిగే సోర్స్ కాపీని ఉంచండి + PDF యాక్సెస్‌ను సురక్షితంగా నిర్వహించండి + నియంత్రిత భాగస్వామ్యానికి రక్షణ సాధనాలు ఉపయోగపడతాయి, అయితే పాస్‌వర్డ్‌లు కోల్పోవడం సులభం మరియు తిరిగి పొందడం కష్టం. పంపిణీ కోసం కాపీలను రక్షించండి, అసురక్షిత సోర్స్ ఫైల్‌లను విడిగా ఉంచండి మరియు ఏదైనా తొలగించే ముందు ఫలితాన్ని ధృవీకరించండి. + మీ ఏకైక మూల పత్రాన్ని కాకుండా PDF కాపీని రక్షించండి. + రక్షిత ఫైల్‌ను భాగస్వామ్యం చేయడానికి ముందు పాస్‌వర్డ్‌ను ఎక్కడైనా సురక్షితంగా నిల్వ చేయండి. + మీరు సవరించడానికి అనుమతించబడిన ఫైల్‌ల కోసం మాత్రమే అన్‌లాక్‌ని ఉపయోగించండి, ఆపై అన్‌లాక్ చేయబడిన కాపీ సరిగ్గా తెరవబడిందని ధృవీకరించండి. + PDF పేజీలను నిర్వహించండి + విలీనం చేయండి, విభజించండి, పునర్వ్యవస్థీకరించండి, తిప్పండి, కత్తిరించండి, పేజీలను తీసివేయండి మరియు పేజీ సంఖ్యలను ఉద్దేశపూర్వకంగా జోడించండి + అలంకరణకు ముందు డాక్యుమెంట్ నిర్మాణాన్ని పరిష్కరించండి + మీరు పేపర్ డాక్యుమెంట్‌ను సిద్ధం చేసే క్రమంలోనే PDF పేజీ సాధనాలు ఉపయోగించడానికి సులభమైనవి: పేజీలను సేకరించడం, తప్పుగా ఉన్న వాటిని తీసివేయడం, వాటిని క్రమంలో ఉంచడం, సరైన ధోరణి, ఆపై పేజీ నంబర్‌లు లేదా వాటర్‌మార్క్‌ల వంటి తుది వివరాలను జోడించండి. + పత్రం ఇప్పటికీ అనేక ఫైల్‌లలో విభజించబడినప్పుడు ముందుగా విలీనం లేదా చిత్రాల నుండి PDFని ఉపయోగించండి. + పేజీ సంఖ్యలు, సంతకాలు లేదా వాటర్‌మార్క్‌లను జోడించే ముందు పేజీలను మళ్లీ అమర్చండి, తిప్పండి, కత్తిరించండి లేదా తీసివేయండి. + నిర్మాణాత్మక సవరణల తర్వాత చివరి PDFని పరిదృశ్యం చేయండి ఎందుకంటే ఒక పేజీ తప్పిపోయిన లేదా తిప్పబడిన పేజీని తర్వాత గమనించడం కష్టం. + PDF ఉల్లేఖనాలు + వీక్షకులు వ్యాఖ్యలు మరియు గుర్తులను భిన్నంగా చూపినప్పుడు ఉల్లేఖనాలను తీసివేయండి లేదా వాటిని చదును చేయండి + కనిపించే వాటిని నియంత్రించండి + ఉల్లేఖనాలు వ్యాఖ్యలు, ముఖ్యాంశాలు, డ్రాయింగ్‌లు, ఫారమ్ గుర్తులు లేదా ఇతర అదనపు PDF వస్తువులు కావచ్చు. కొంతమంది వీక్షకులు వాటిని విభిన్నంగా ప్రదర్శిస్తారు, కాబట్టి వాటిని తీసివేయడం లేదా చదును చేయడం ద్వారా భాగస్వామ్య పత్రాలను మరింత ఊహాజనితంగా చేయవచ్చు. + భాగస్వామ్య కాపీలో వ్యాఖ్యలు, ముఖ్యాంశాలు లేదా సమీక్ష గుర్తులను చేర్చనప్పుడు ఉల్లేఖనాలను తీసివేయండి. + కనిపించే గుర్తులు పేజీలో ఉన్నప్పుడు చదును చేయండి కానీ ఇకపై సవరించదగిన ఉల్లేఖనాల వలె ప్రవర్తించకూడదు. + ఒరిజినల్ కాపీని ఉంచండి ఎందుకంటే ఉల్లేఖనాలను తీసివేయడం లేదా చదును చేయడం రివర్స్ చేయడం కష్టం. + వచనాన్ని గుర్తించండి + ఎంచుకోదగిన OCR ఇంజిన్‌లు మరియు భాషలతో చిత్రాల నుండి వచనాన్ని సంగ్రహించండి + మెరుగైన OCR ఫలితాలను పొందండి + OCR ఖచ్చితత్వం చిత్రం పదును, భాష డేటా, కాంట్రాస్ట్ మరియు పేజీ ఓరియంటేషన్‌పై ఆధారపడి ఉంటుంది. సాధారణంగా చిత్రాన్ని ముందుగా సిద్ధం చేయడం ద్వారా ఉత్తమ ఫలితాలు వస్తాయి: శబ్దాన్ని కత్తిరించండి, నిటారుగా తిప్పండి, చదవగలిగే సామర్థ్యాన్ని పెంచండి, ఆపై వచనాన్ని గుర్తించండి. + చిత్రాన్ని టెక్స్ట్ ప్రాంతానికి కత్తిరించండి మరియు గుర్తించడానికి ముందు దాన్ని నిటారుగా తిప్పండి. + సరైన భాషను ఎంచుకుని, యాప్ అభ్యర్థించినట్లయితే భాష డేటాను డౌన్‌లోడ్ చేయండి. + గుర్తించబడిన వచనాన్ని కాపీ చేయండి లేదా భాగస్వామ్యం చేయండి, ఆపై పేర్లు, సంఖ్యలు మరియు విరామచిహ్నాలను మాన్యువల్‌గా తనిఖీ చేయండి. + OCR భాష డేటాను ఎంచుకోండి + స్క్రిప్ట్‌లు, భాషలు మరియు డౌన్‌లోడ్ చేసిన OCR మోడల్‌లను సరిపోల్చడం ద్వారా గుర్తింపును మెరుగుపరచండి + OCRని వచనానికి సరిపోల్చండి + తప్పు OCR భాష మంచి ఫోటోలు చెడ్డ గుర్తింపుగా కనిపించేలా చేస్తుంది. స్క్రిప్ట్‌లు, విరామచిహ్నాలు, సంఖ్యలు మరియు మిశ్రమ పత్రాల కోసం భాషా డేటా ముఖ్యమైనది, కాబట్టి ఫోన్ UI భాష కాకుండా చిత్రంలో కనిపించే భాషను ఎంచుకోండి. + ఫోన్ భాష మాత్రమే కాకుండా, చిత్రంలో కనిపించే భాష లేదా స్క్రిప్ట్‌ను ఎంచుకోండి. + ఆఫ్‌లైన్‌లో పని చేయడానికి లేదా బ్యాచ్‌ని ప్రాసెస్ చేయడానికి ముందు మిస్సింగ్ డేటాను డౌన్‌లోడ్ చేయండి. + మిశ్రమ భాషా పత్రాల కోసం, ముందుగా అత్యంత ముఖ్యమైన భాషను అమలు చేయండి మరియు పేర్లు మరియు సంఖ్యలను మాన్యువల్‌గా తనిఖీ చేయండి. + శోధించదగిన PDFలు + OCR వచనాన్ని తిరిగి ఫైల్‌లలోకి వ్రాయండి, తద్వారా స్కాన్ చేసిన పేజీలను శోధించవచ్చు మరియు కాపీ చేయవచ్చు + స్కాన్‌లను శోధించదగిన పత్రాలుగా మార్చండి + OCR క్లిప్‌బోర్డ్‌కి వచనాన్ని కాపీ చేయడం కంటే ఎక్కువ చేయగలదు. మీరు గుర్తించబడిన వచనాన్ని ఫైల్ లేదా శోధించదగిన PDFకి వ్రాసినప్పుడు, టెక్స్ట్ శోధించడం, ఎంచుకోవడం, సూచిక చేయడం లేదా ఆర్కైవ్ చేయడం సులభం అయినప్పుడు పేజీ యొక్క చిత్రం కనిపిస్తుంది. + స్కాన్ చేసిన పత్రాల కోసం శోధించదగిన PDF అవుట్‌పుట్‌ని ఉపయోగించండి, అవి ఇప్పటికీ అసలు పేజీల వలె కనిపిస్తాయి. + మీకు సంగ్రహించబడిన వచనం మాత్రమే అవసరమైనప్పుడు మరియు పేజీ చిత్రం గురించి పట్టించుకోనప్పుడు వ్రాయడానికి-ఫైల్‌ని ఉపయోగించండి. + ముఖ్యమైన సంఖ్యలు, పేర్లు మరియు పట్టికలను మాన్యువల్‌గా తనిఖీ చేయండి ఎందుకంటే OCR టెక్స్ట్ శోధించదగినది అయినప్పటికీ అసంపూర్ణంగా ఉంటుంది. + QR కోడ్‌లను స్కాన్ చేయండి + అందుబాటులో ఉన్నప్పుడు కెమెరా ఇన్‌పుట్ లేదా సేవ్ చేసిన చిత్రాల నుండి QR కంటెంట్‌ను చదవండి + కోడ్‌లను సురక్షితంగా చదవండి + QR కోడ్‌లు లింక్‌లు, Wi-Fi డేటా, పరిచయాలు, వచనం లేదా ఇతర పేలోడ్‌లను కలిగి ఉండవచ్చు. + స్కాన్ QR కోడ్‌ని తెరిచి, కోడ్‌ను పదునుగా, ఫ్లాట్‌గా మరియు పూర్తిగా ఫ్రేమ్ లోపల ఉంచండి. + లింక్‌లను తెరవడానికి లేదా సున్నితమైన డేటాను కాపీ చేయడానికి ముందు డీకోడ్ చేసిన కంటెంట్‌ను సమీక్షించండి. + స్కానింగ్ విఫలమైతే, లైటింగ్ మెరుగుపరచండి, కోడ్‌ను కత్తిరించండి లేదా అధిక రిజల్యూషన్ సోర్స్ చిత్రాన్ని ప్రయత్నించండి. + Base64 మరియు చెక్‌సమ్‌లను ఉపయోగించండి + చిత్రాలను టెక్స్ట్‌గా ఎన్‌కోడ్ చేయండి, వచనాన్ని తిరిగి చిత్రాలకు డీకోడ్ చేయండి మరియు ఫైల్ సమగ్రతను ధృవీకరించండి + ఫైల్‌లు మరియు టెక్స్ట్ మధ్య తరలించండి + చిన్న ఇమేజ్ పేలోడ్‌ల కోసం Base64 ఉపయోగపడుతుంది, అయితే చెక్‌సమ్‌లు ఫైల్‌లు మారలేదని నిర్ధారించడంలో సహాయపడతాయి. ఈ సాధనాలు సాంకేతిక వర్క్‌ఫ్లోలు, మద్దతు నివేదికలు, ఫైల్ బదిలీలు మరియు బైనరీ ఫైల్‌లు తాత్కాలికంగా టెక్స్ట్‌గా మారాల్సిన ప్రదేశాలలో చాలా సహాయకారిగా ఉంటాయి. + వర్క్‌ఫ్లోకు బైనరీ ఫైల్‌కు బదులుగా టెక్స్ట్ అవసరమైనప్పుడు చిన్న చిత్రాన్ని ఎన్‌కోడ్ చేయడానికి Base64 సాధనాలను ఉపయోగించండి. + విశ్వసనీయ మూలాల నుండి మాత్రమే Base64ని డీకోడ్ చేయండి మరియు సేవ్ చేయడానికి ముందు ప్రివ్యూని ధృవీకరించండి. + మీరు ఎగుమతి చేసిన ఫైల్‌లను సరిపోల్చడానికి లేదా అప్‌లోడ్/డౌన్‌లోడ్‌ను నిర్ధారించాల్సిన అవసరం వచ్చినప్పుడు చెక్‌సమ్ సాధనాలను ఉపయోగించండి. + డేటాను ప్యాకేజీ చేయండి లేదా రక్షించండి + ఫైల్‌లను బండిల్ చేయడానికి లేదా టెక్స్ట్ డేటాను మార్చడానికి జిప్ మరియు సాంకేతికలిపి సాధనాలను ఉపయోగించండి + సంబంధిత ఫైళ్లను కలిసి ఉంచండి + అనేక అవుట్‌పుట్‌లు ఒక ఫైల్‌గా ప్రయాణించేటప్పుడు ప్యాకేజింగ్ సాధనాలు సహాయపడతాయి. మీరు పూర్తి ఫలితాల సెట్‌ను పంపాల్సిన అవసరం వచ్చినప్పుడు రూపొందించిన చిత్రాలు, PDFలు, లాగ్‌లు మరియు మెటాడేటాను కలిపి ఉంచడానికి కూడా ఇవి మంచివి. + మీరు అనేక ఎగుమతి చేసిన చిత్రాలు లేదా పత్రాలను కలిసి పంపవలసి వచ్చినప్పుడు జిప్‌ని ఉపయోగించండి. + మీరు ఎంచుకున్న పద్ధతిని అర్థం చేసుకున్నప్పుడు మరియు అవసరమైన కీలను సురక్షితంగా నిల్వ చేయగలిగినప్పుడు మాత్రమే సాంకేతికలిపి సాధనాలను ఉపయోగించండి. + సోర్స్ ఫైల్‌లను తొలగించే ముందు సృష్టించిన ఆర్కైవ్ లేదా రూపాంతరం చెందిన వచనాన్ని పరీక్షించండి. + పేర్లలో చెక్‌సమ్‌లు + ప్రత్యేకత మరియు సమగ్రత రీడబిలిటీ కంటే ఎక్కువగా ఉన్నప్పుడు చెక్‌సమ్ ఆధారిత ఫైల్ పేర్లను ఉపయోగించండి + కంటెంట్ ద్వారా ఫైల్‌లకు పేరు పెట్టండి + ఎగుమతులు నకిలీ కనిపించే పేర్లను కలిగి ఉండవచ్చు లేదా మీరు రెండు ఫైల్‌లు ఒకేలా ఉన్నాయని నిరూపించాల్సిన అవసరం వచ్చినప్పుడు చెక్‌సమ్ ఫైల్ పేర్లు ఉపయోగపడతాయి. మాన్యువల్ బ్రౌజింగ్ కోసం అవి తక్కువ స్నేహపూర్వకంగా ఉంటాయి, కాబట్టి వాటిని సాంకేతిక ఆర్కైవ్‌లు మరియు ధృవీకరణ వర్క్‌ఫ్లోల కోసం ఉపయోగించండి. + మనుషులు చదవగలిగే శీర్షిక కంటే కంటెంట్ గుర్తింపు చాలా ముఖ్యమైనప్పుడు చెక్‌సమ్‌ని ఫైల్ పేరుగా ప్రారంభించండి. + ఆర్కైవ్‌లు, తగ్గింపు, పునరావృత ఎగుమతులు లేదా మళ్లీ అప్‌లోడ్ చేయబడి, డౌన్‌లోడ్ చేయబడే ఫైల్‌ల కోసం దీన్ని ఉపయోగించండి. + ఫైల్ దేనిని సూచిస్తుందో వ్యక్తులు ఇంకా అర్థం చేసుకోవలసి వచ్చినప్పుడు చెక్‌సమ్ పేర్లను ఫోల్డర్‌లు లేదా మెటాడేటాతో జత చేయండి. + చిత్రాల నుండి రంగులను ఎంచుకోండి + నమూనా పిక్సెల్‌లు, రంగు కోడ్‌లను కాపీ చేయండి మరియు ప్యాలెట్‌లు లేదా డిజైన్‌లలో రంగులను మళ్లీ ఉపయోగించుకోండి + ఖచ్చితమైన రంగును నమూనా చేయండి + డిజైన్‌ను సరిపోల్చడానికి, బ్రాండ్ రంగులను సంగ్రహించడానికి లేదా ఇమేజ్ విలువలను తనిఖీ చేయడానికి రంగు ఎంపిక ఉపయోగపడుతుంది. జూమ్ చేయడం ముఖ్యం ఎందుకంటే యాంటీఅలియాసింగ్, షాడోస్ మరియు కంప్రెషన్ పొరుగు పిక్సెల్‌లను మీరు ఆశించే రంగు నుండి కొద్దిగా భిన్నంగా చేయవచ్చు. + చిత్రం నుండి పిక్ కలర్‌ని తెరిచి, మీకు అవసరమైన రంగుతో మూల చిత్రాన్ని ఎంచుకోండి. + చిన్న వివరాలను నమూనా చేయడానికి ముందు జూమ్ ఇన్ చేయండి, తద్వారా సమీపంలోని పిక్సెల్‌లు ఫలితాన్ని ప్రభావితం చేయవు. + మీ గమ్యస్థానానికి అవసరమైన HEX, RGB లేదా HSV వంటి ఆకృతిలో రంగును కాపీ చేయండి. + పాలెట్‌లను సృష్టించండి మరియు రంగు లైబ్రరీని ఉపయోగించండి + ప్యాలెట్‌లను సంగ్రహించండి, సేవ్ చేసిన రంగులను బ్రౌజ్ చేయండి మరియు స్థిరమైన విజువల్ సిస్టమ్‌లను ఉంచండి + పునర్వినియోగపరచదగిన పాలెట్‌ను రూపొందించండి + ఎగుమతి చేయబడిన గ్రాఫిక్స్, వాటర్‌మార్క్‌లు మరియు డ్రాయింగ్‌లను దృశ్యమానంగా స్థిరంగా ఉంచడంలో ప్యాలెట్‌లు సహాయపడతాయి. మీరు స్క్రీన్‌షాట్‌లను పదేపదే ఉల్లేఖించినప్పుడు, బ్రాండ్ ఆస్తులను సిద్ధం చేసినప్పుడు లేదా అనేక సాధనాల్లో ఒకే రంగులు అవసరమైనప్పుడు అవి ప్రత్యేకంగా ఉపయోగపడతాయి. + చిత్రం నుండి పాలెట్‌ను రూపొందించండి లేదా మీకు ఇప్పటికే విలువలు తెలిసినప్పుడు మాన్యువల్‌గా రంగులను జోడించండి. + ముఖ్యమైన రంగులకు పేరు పెట్టండి లేదా నిర్వహించండి, తద్వారా మీరు వాటిని తర్వాత మళ్లీ కనుగొనవచ్చు. + డ్రా, వాటర్‌మార్క్, గ్రేడియంట్, SVG లేదా బాహ్య డిజైన్ సాధనాల్లో కాపీ చేసిన విలువలను ఉపయోగించండి. + ప్రవణతలను సృష్టించండి + నేపథ్యాలు, ప్లేస్‌హోల్డర్‌లు మరియు దృశ్య ప్రయోగాల కోసం గ్రేడియంట్ చిత్రాలను రూపొందించండి + రంగు పరివర్తనలను నియంత్రించండి + మీకు ఇప్పటికే ఉన్న చిత్రాన్ని సవరించడానికి బదులుగా రూపొందించబడిన చిత్రం అవసరమైనప్పుడు గ్రేడియంట్ సాధనాలు ఉపయోగపడతాయి. అవి క్లీన్ బ్యాక్‌గ్రౌండ్‌లు, ప్లేస్‌హోల్డర్‌లు, వాల్‌పేపర్‌లు, మాస్క్‌లు లేదా బాహ్య డిజైన్ సాధనాల కోసం సాధారణ ఆస్తులుగా మారవచ్చు. + గ్రేడియంట్ రకాన్ని ఎంచుకోండి మరియు ముందుగా ముఖ్యమైన రంగులను సెట్ చేయండి. + లక్ష్య వినియోగ కేసు కోసం దిశ, స్టాప్‌లు, సున్నితత్వం మరియు అవుట్‌పుట్ పరిమాణాన్ని సర్దుబాటు చేయండి. + గమ్యస్థానానికి సరిపోలే ఫార్మాట్‌లో ఎగుమతి చేయండి, సాధారణంగా క్లీన్ జెనరేట్ చేయబడిన గ్రాఫిక్స్ కోసం PNG. + రంగు ప్రాప్యత + UI చదవడం కష్టంగా ఉన్నప్పుడు డైనమిక్ రంగులు, కాంట్రాస్ట్ మరియు కలర్ బ్లైండ్ ఎంపికలను ఉపయోగించండి + రంగులను ఉపయోగించడానికి సులభతరం చేయండి + రంగు సెట్టింగ్‌లు సౌలభ్యం, రీడబిలిటీని ప్రభావితం చేస్తాయి మరియు మీరు నియంత్రణలను ఎంత త్వరగా స్కాన్ చేయవచ్చు. టూల్ చిప్‌లు, స్లయిడర్‌లు లేదా ఎంచుకున్న స్థితులను వేరు చేయడం కష్టంగా అనిపిస్తే, కేవలం డార్క్ మోడ్‌ని మార్చడం కంటే థీమ్ మరియు యాక్సెసిబిలిటీ ఎంపికలు మరింత ఉపయోగకరంగా ఉంటాయి. + యాప్ ప్రస్తుత వాల్‌పేపర్ ప్యాలెట్‌ను అనుసరించాలని మీరు కోరుకున్నప్పుడు డైనమిక్ రంగులను ప్రయత్నించండి. + బటన్లు మరియు ఉపరితలాలు తగినంతగా కనిపించనప్పుడు కాంట్రాస్ట్‌ను పెంచండి లేదా స్కీమ్‌ని మార్చండి. + కొన్ని రాష్ట్రాలు లేదా స్వరాలు వేరు చేయడం కష్టంగా ఉన్నట్లయితే రంగు-అంధత్వ పథకం ఎంపికలను ఉపయోగించండి. + ఆల్ఫా నేపథ్యం + పారదర్శక PNG, WebP మరియు సారూప్య అవుట్‌పుట్‌ల చుట్టూ ఉపయోగించిన ప్రివ్యూను లేదా పూరించే రంగును నియంత్రించండి + పారదర్శక ప్రివ్యూలను అర్థం చేసుకోండి + ఆల్ఫా ఫార్మాట్‌ల కోసం నేపథ్య రంగు పారదర్శక చిత్రాలను తనిఖీ చేయడం సులభం చేస్తుంది, అయితే మీరు ప్రివ్యూ/ఫిల్ బిహేవియర్‌ని మారుస్తున్నారా లేదా తుది ఫలితాన్ని చదును చేస్తున్నారా అనేది తెలుసుకోవడం ముఖ్యం. స్టిక్కర్‌లు, లోగోలు మరియు బ్యాక్‌గ్రౌండ్ తీసివేసిన చిత్రాలకు ఇది ముఖ్యమైనది. + పారదర్శక పిక్సెల్‌లు తప్పనిసరిగా ఎగుమతిలో జీవించి ఉండవలసి వచ్చినప్పుడు, ముందుగా PNG లేదా WebP వంటి ఆల్ఫా-సపోర్టింగ్ ఫార్మాట్‌ని ఉపయోగించండి. + యాప్ ఉపరితలంపై పారదర్శక అంచులు కనిపించడం కష్టంగా ఉన్నప్పుడు నేపథ్య రంగు సెట్టింగ్‌లను సర్దుబాటు చేయండి. + పారదర్శకత లేదా ఎంచుకున్న పూరకం సేవ్ చేయబడిందో లేదో నిర్ధారించడానికి చిన్న పరీక్షను ఎగుమతి చేసి, దాన్ని మరొక యాప్‌లో మళ్లీ తెరవండి. + AI మోడల్ ఎంపిక + ముందుగా పెద్దదాన్ని ఎంచుకునే బదులు ఇమేజ్ రకం మరియు లోపం ఆధారంగా మోడల్‌ను ఎంచుకోండి + మోడల్‌ను నష్టానికి సరిపోల్చండి + AI సాధనాలు వేర్వేరు ONNX/ORT మోడళ్లను డౌన్‌లోడ్ చేయగలవు లేదా దిగుమతి చేయగలవు మరియు ప్రతి మోడల్ నిర్దిష్ట రకమైన సమస్య కోసం శిక్షణ పొందుతుంది. JPEG బ్లాక్‌లు, అనిమే లైన్ క్లీనప్, డీనోయిజింగ్, బ్యాక్‌గ్రౌండ్ రిమూవల్, కలరైజేషన్ లేదా అప్‌స్కేలింగ్ కోసం మోడల్ తప్పు ఇమేజ్ టైప్‌లో ఉపయోగించినప్పుడు అధ్వాన్నమైన ఫలితాలను అందించవచ్చు. + డౌన్‌లోడ్ చేయడానికి ముందు మోడల్ వివరణను చదవండి; కంప్రెషన్, నాయిస్, హాఫ్‌టోన్, బ్లర్ లేదా బ్యాక్‌గ్రౌండ్ రిమూవల్ వంటి మీ అసలు సమస్యకు పేరు పెట్టే మోడల్‌ను ఎంచుకోండి. + కొత్త ఇమేజ్ రకాన్ని పరీక్షించేటప్పుడు చిన్న లేదా మరింత నిర్దిష్టమైన మోడల్‌తో ప్రారంభించండి, ఫలితం తగినంతగా లేకుంటే మాత్రమే భారీ మోడల్‌లతో సరిపోల్చండి. + మీరు నిజంగా ఉపయోగించే మోడల్‌లను మాత్రమే ఉంచండి, ఎందుకంటే డౌన్‌లోడ్ చేయబడిన మరియు దిగుమతి చేయబడిన మోడల్‌లు గుర్తించదగిన నిల్వ స్థలాన్ని తీసుకుంటాయి. + మోడల్ కుటుంబాలను అర్థం చేసుకోండి + మోడల్ పేర్లు తరచుగా వారి లక్ష్యాన్ని సూచిస్తాయి: RealESRGAN-శైలి మోడల్‌లు ఉన్నత స్థాయి, SCUNet మరియు FBCNN మోడల్‌లు క్లీన్ నాయిస్ లేదా కంప్రెషన్, బ్యాక్‌గ్రౌండ్ మోడల్‌లు మాస్క్‌లను సృష్టిస్తాయి మరియు కలర్‌రైజర్‌లు గ్రేస్కేల్ ఇన్‌పుట్‌కు రంగును జోడిస్తాయి. దిగుమతి చేసుకున్న కస్టమ్ మోడల్‌లు శక్తివంతంగా ఉంటాయి, కానీ అవి ఊహించిన ఇన్‌పుట్/అవుట్‌పుట్ ఆకృతికి సరిపోలాలి మరియు మద్దతు ఉన్న ONNX లేదా ORT ఆకృతిని ఉపయోగించాలి. + మీకు ఎక్కువ పిక్సెల్‌లు అవసరమైనప్పుడు అప్‌స్కేలర్‌లను, చిత్రం గ్రైనీగా ఉన్నప్పుడు డెనోయిజర్‌లను మరియు కంప్రెషన్ బ్లాక్‌లు లేదా బ్యాండింగ్ ప్రధాన సమస్య అయినప్పుడు ఆర్టిఫ్యాక్ట్ రిమూవర్‌లను ఉపయోగించండి. + విషయ విభజన కోసం మాత్రమే నేపథ్య నమూనాలను ఉపయోగించండి; అవి సాధారణ ఆబ్జెక్ట్ రిమూవల్ లేదా ఇమేజ్ మెరుగుదల లాంటివి కావు. + మీరు విశ్వసించే మూలాధారాల నుండి మాత్రమే అనుకూల నమూనాలను దిగుమతి చేయండి మరియు ముఖ్యమైన ఫైల్‌లను ఉపయోగించే ముందు వాటిని చిన్న చిత్రంపై పరీక్షించండి. + AI పారామితులు + నాణ్యత, వేగం మరియు జ్ఞాపకశక్తిని సమతుల్యం చేయడానికి భాగం పరిమాణం, అతివ్యాప్తి మరియు సమాంతర వర్కర్లను ట్యూన్ చేయండి + స్థిరత్వంతో వేగాన్ని సమతుల్యం చేయండి + మోడల్ ద్వారా ప్రాసెస్ చేయబడే ముందు ఇమేజ్ ముక్కలు ఎంత పెద్దవి కావాలో భాగం పరిమాణం నియంత్రిస్తుంది. సరిహద్దులను దాచడానికి పొరుగు భాగాలను అతివ్యాప్తి చేస్తుంది. సమాంతర కార్మికులు ప్రాసెసింగ్‌ని వేగవంతం చేయవచ్చు, కానీ ప్రతి కార్మికుడికి మెమరీ అవసరం, కాబట్టి అధిక విలువలు పరిమిత RAM ఉన్న ఫోన్‌లలో పెద్ద చిత్రాలను అస్థిరంగా చేస్తాయి. + మీ పరికరం మోడల్‌ను విశ్వసనీయంగా నిర్వహించినప్పుడు మరియు చిత్రం భారీగా లేనప్పుడు సున్నితమైన సందర్భం కోసం పెద్ద భాగాలను ఉపయోగించండి. + భాగం అంచులు కనిపించినప్పుడు అతివ్యాప్తిని పెంచండి, కానీ ఎక్కువ అతివ్యాప్తి అంటే మరింత పునరావృతమయ్యే పని అని గుర్తుంచుకోండి. + స్థిరమైన పరీక్ష తర్వాత మాత్రమే సమాంతర కార్మికులను పెంచండి; ప్రాసెసింగ్ నెమ్మదించినా, పరికరాన్ని వేడిచేసినా లేదా క్రాష్ అయినట్లయితే, ముందుగా కార్మికులను తగ్గించండి. + చంక్ కళాఖండాలను నివారించండి + చంకింగ్ మోడల్ కంటే పెద్దదిగా ఉన్న చిత్రాలను ఒకేసారి సౌకర్యవంతంగా నిర్వహించగలిగేలా యాప్‌ని ప్రాసెస్ చేస్తుంది. ప్రతి పలకకు తక్కువ పరిసర సందర్భం ఉంటుంది, కాబట్టి తక్కువ అతివ్యాప్తి లేదా చాలా చిన్న భాగాలు కనిపించే సరిహద్దులు, ఆకృతి మార్పులు లేదా పొరుగు ప్రాంతాల మధ్య అస్థిరమైన వివరాలను సృష్టించవచ్చు. + మీరు ప్రాసెస్ చేయబడిన ప్రాంతాల మధ్య దీర్ఘచతురస్రాకార అంచులు, పునరావృత ఆకృతి మార్పులు లేదా వివరాల జంప్‌లను చూసినట్లయితే అతివ్యాప్తిని పెంచండి. + అవుట్‌పుట్‌ను ఉత్పత్తి చేయడానికి ముందు ప్రక్రియ విఫలమైతే, ముఖ్యంగా పెద్ద చిత్రాలు లేదా భారీ మోడల్‌లపై భాగం పరిమాణాన్ని తగ్గించండి. + పూర్తి చిత్రాన్ని ప్రాసెస్ చేయడానికి ముందు చిన్న క్రాప్ పరీక్షను సేవ్ చేయండి, తద్వారా మీరు పారామితులను త్వరగా సరిపోల్చవచ్చు. + AI స్థిరత్వం + AI ప్రాసెసింగ్ క్రాష్ అయినప్పుడు లేదా స్తంభింపజేసినప్పుడు తక్కువ భాగం పరిమాణం, కార్మికులు మరియు సోర్స్ రిజల్యూషన్ + భారీ AI ఉద్యోగాల నుండి కోలుకోండి + యాప్‌లోని భారీ వర్క్‌ఫ్లోలలో AI ప్రాసెసింగ్ ఒకటి. క్రాష్‌లు, విఫలమైన సెషన్‌లు, ఫ్రీజ్‌లు లేదా ఆకస్మిక యాప్ రీస్టార్ట్‌లు అంటే సాధారణంగా మోడల్, సోర్స్ రిజల్యూషన్, భాగం పరిమాణం లేదా సమాంతర వర్కర్ కౌంట్ ప్రస్తుత పరికర స్థితికి చాలా డిమాండ్‌గా ఉంది. + యాప్ క్రాష్ అయితే లేదా మోడల్ సెషన్‌ను తెరవడంలో విఫలమైతే, సమాంతర వర్కర్లు మరియు భాగం పరిమాణాన్ని తగ్గించి, అదే చిత్రాన్ని మళ్లీ ప్రయత్నించండి. + లక్ష్యానికి అసలు రిజల్యూషన్ అవసరం లేనప్పుడు AI ప్రాసెసింగ్‌కు ముందు చాలా పెద్ద చిత్రాల పరిమాణాన్ని మార్చండి. + ఇతర భారీ యాప్‌లను మూసివేయండి, చిన్న మోడల్‌ని ఉపయోగించండి లేదా మెమరీ ఒత్తిడి తిరిగి వస్తున్నప్పుడు ఒకేసారి తక్కువ ఫైల్‌లను ప్రాసెస్ చేయండి. + మోడల్ వైఫల్యాలను గుర్తించండి + విఫలమైన AI రన్ ఎల్లప్పుడూ చిత్రం చెడ్డదని అర్థం కాదు. మోడల్ ఫైల్ అసంపూర్ణంగా ఉండవచ్చు, మద్దతు లేదు, మెమరీకి చాలా పెద్దది లేదా ఆపరేషన్‌తో సరిపోలలేదు. యాప్ విఫలమైన సెషన్‌ను నివేదించినప్పుడు, ముందుగా మోడల్ మరియు పారామితులను సరళీకృతం చేయడానికి దానిని సిగ్నల్‌గా పరిగణించండి. + డౌన్‌లోడ్ చేసిన వెంటనే మోడల్ విఫలమైతే లేదా ఫైల్ పాడైపోయినట్లు ప్రవర్తిస్తే దాన్ని తొలగించి, మళ్లీ డౌన్‌లోడ్ చేయండి. + దిగుమతి చేసుకున్న కస్టమ్ మోడల్‌ను నిందించే ముందు అంతర్నిర్మిత డౌన్‌లోడ్ చేయదగిన మోడల్‌ని ప్రయత్నించండి, ఎందుకంటే దిగుమతి చేయబడిన మోడల్‌లు అననుకూల ఇన్‌పుట్‌లను కలిగి ఉంటాయి. + మీరు క్రాష్ లేదా సెషన్ లోపాన్ని నివేదించాల్సిన అవసరం ఉన్నట్లయితే, వైఫల్యాన్ని పునరుత్పత్తి చేసిన తర్వాత యాప్ లాగ్‌లను ఉపయోగించండి. + షేడర్ స్టూడియోలో ప్రయోగం + అధునాతన ఇమేజ్ ప్రాసెసింగ్ మరియు అనుకూల రూపాల కోసం GPU షేడర్ ప్రభావాలను ఉపయోగించండి + షేడర్‌లతో జాగ్రత్తగా పని చేయండి + షేడర్ స్టూడియో శక్తివంతమైనది, కానీ షేడర్ కోడ్ మరియు పారామితులకు చిన్న, పరీక్షించదగిన మార్పులు అవసరం. దీన్ని ప్రయోగాత్మక సాధనంగా పరిగణించండి: వర్కింగ్ బేస్‌లైన్‌ను ఉంచండి, ఒక సమయంలో ఒక విషయాన్ని మార్చండి మరియు ప్రీసెట్‌ను విశ్వసించే ముందు విభిన్న చిత్ర పరిమాణాలతో పరీక్షించండి. + పారామితులను జోడించే ముందు తెలిసిన పని చేసే షేడర్ లేదా సాధారణ ప్రభావం నుండి ప్రారంభించండి. + ఒకేసారి ఒక పారామీటర్ లేదా కోడ్ బ్లాక్‌ని మార్చండి మరియు లోపాల కోసం ప్రివ్యూని తనిఖీ చేయండి. + కొన్ని విభిన్న చిత్ర పరిమాణాలలో వాటిని పరీక్షించిన తర్వాత మాత్రమే ప్రీసెట్‌లను ఎగుమతి చేయండి. + SVG లేదా ASCII కళను రూపొందించండి + సృజనాత్మక అవుట్‌పుట్ కోసం వెక్టార్ లాంటి ఆస్తులు లేదా వచన-ఆధారిత చిత్రాలను రూపొందించండి + సృజనాత్మక అవుట్‌పుట్ రకాన్ని ఎంచుకోండి + SVG మరియు ASCII సాధనాలు వేర్వేరు లక్ష్యాలను అందిస్తాయి: స్కేలబుల్ గ్రాఫిక్స్ లేదా టెక్స్ట్-స్టైల్ రెండరింగ్. రెండూ సృజనాత్మక మార్పిడులు, కాబట్టి చిన్న థంబ్‌నెయిల్ నుండి ఫలితాన్ని అంచనా వేయడం కంటే తుది పరిమాణంలో ప్రివ్యూ చేయడం చాలా ముఖ్యం. + ఫలితం క్లీన్‌గా స్కేల్ అయినప్పుడు లేదా డిజైన్ వర్క్‌ఫ్లోలలో మళ్లీ ఉపయోగించినప్పుడు SVG Makerని ఉపయోగించండి. + మీరు చిత్రం యొక్క శైలీకృత వచన ప్రాతినిధ్యం కావాలనుకున్నప్పుడు ASCII ఆర్ట్‌ని ఉపయోగించండి. + తుది పరిమాణంలో ప్రివ్యూ చేయండి ఎందుకంటే ఉత్పత్తి చేయబడిన అవుట్‌పుట్‌లో చిన్న వివరాలు అదృశ్యమవుతాయి. + శబ్ద అల్లికలను రూపొందించండి + నేపథ్యాలు, ముసుగులు, అతివ్యాప్తులు లేదా ప్రయోగాల కోసం విధానపరమైన అల్లికలను సృష్టించండి + ట్యూన్ విధానపరమైన అవుట్‌పుట్ + శబ్దం ఉత్పత్తి పారామీటర్ల నుండి కొత్త చిత్రాలను సృష్టిస్తుంది, కాబట్టి చిన్న మార్పులు చాలా భిన్నమైన ఫలితాలను అందిస్తాయి. ఇది టెక్స్‌చర్‌లు, మాస్క్‌లు, ఓవర్‌లేలు, ప్లేస్‌హోల్డర్‌లు లేదా వివరణాత్మక నమూనాలతో కంప్రెషన్‌ని పరీక్షించడానికి ఉపయోగపడుతుంది. + చక్కటి వివరాలను ట్యూన్ చేయడానికి ముందు నాయిస్ రకం మరియు అవుట్‌పుట్ పరిమాణాన్ని ఎంచుకోండి. + నమూనా లక్ష్య వినియోగానికి సరిపోయే వరకు స్కేల్, తీవ్రత, రంగులు లేదా విత్తనాన్ని సర్దుబాటు చేయండి. + ఉపయోగకరమైన ఫలితాలను సేవ్ చేయండి మరియు మీరు తర్వాత ఆకృతిని పునఃసృష్టించవలసి వస్తే పారామితులను వ్రాయండి. + మార్కప్ ప్రాజెక్ట్‌లు + యాప్‌ను మూసివేసిన తర్వాత ఉల్లేఖనాలను సవరించగలిగేలా ఉంచడానికి ప్రాజెక్ట్ ఫైల్‌లను ఉపయోగించండి + లేయర్డ్ సవరణలను మళ్లీ ఉపయోగించగలిగేలా ఉంచండి + స్క్రీన్‌షాట్, రేఖాచిత్రం లేదా సూచన చిత్రానికి తర్వాత మార్పులు అవసరమైనప్పుడు మార్కప్ ప్రాజెక్ట్ ఫైల్‌లు ఉపయోగపడతాయి. ఎగుమతి చేయబడిన PNG/JPEG ఫైల్‌లు తుది చిత్రాలు, అయితే ప్రాజెక్ట్ ఫైల్‌లు సవరించగలిగే లేయర్‌లను మరియు భవిష్యత్తు సర్దుబాట్ల కోసం ఎడిటింగ్ స్థితిని భద్రపరుస్తాయి. + ఉల్లేఖనాలు, కాల్‌అవుట్‌లు లేదా లేఅవుట్ మూలకాలు సవరించగలిగేలా ఉన్నప్పుడు మార్కప్ లేయర్‌లను ఉపయోగించండి. + మీరు మరిన్ని పునర్విమర్శలను ఆశించినట్లయితే తుది చిత్రాన్ని ఎగుమతి చేయడానికి ముందు ప్రాజెక్ట్ ఫైల్‌ను సేవ్ చేయండి లేదా మళ్లీ తెరవండి. + మీరు చదునైన తుది సంస్కరణను భాగస్వామ్యం చేయడానికి సిద్ధంగా ఉన్నప్పుడు మాత్రమే సాధారణ చిత్రాన్ని ఎగుమతి చేయండి. + ఫైళ్లను ఎన్క్రిప్ట్ చేయండి + ఏదైనా సోర్స్ కాపీని తొలగించే ముందు పాస్‌వర్డ్ మరియు టెస్ట్ డిక్రిప్షన్‌తో ఫైల్‌లను రక్షించండి + గుప్తీకరించిన అవుట్‌పుట్‌లను జాగ్రత్తగా నిర్వహించండి + సాంకేతికలిపి సాధనాలు పాస్‌వర్డ్ ఆధారిత ఎన్‌క్రిప్షన్‌తో ఫైల్‌లను రక్షించగలవు, అయితే అవి కీని గుర్తుంచుకోవడానికి లేదా బ్యాకప్‌లను ఉంచడానికి ప్రత్యామ్నాయం కాదు. రవాణా మరియు నిల్వ కోసం ఎన్‌క్రిప్షన్ ఉపయోగపడుతుంది, అయితే ప్రధాన లక్ష్యం అనేక అవుట్‌పుట్‌లను బండిల్ చేస్తున్నప్పుడు జిప్ మెరుగ్గా ఉంటుంది. + ముందుగా కాపీని ఎన్‌క్రిప్ట్ చేయండి మరియు మీరు నిల్వ చేసిన పాస్‌వర్డ్‌తో డిక్రిప్షన్ పనిచేస్తుందని మీరు పరీక్షించే వరకు అసలైనదాన్ని ఉంచండి. + కీ కోసం పాస్‌వర్డ్ మేనేజర్ లేదా మరొక సురక్షిత స్థలాన్ని ఉపయోగించండి; యాప్ మీ కోసం కోల్పోయిన పాస్‌వర్డ్‌ను ఊహించలేదు. + పాస్‌వర్డ్‌ను కలిగి ఉన్న వ్యక్తులతో మాత్రమే ఎన్‌క్రిప్ట్ చేసిన ఫైల్‌లను భాగస్వామ్యం చేయండి మరియు వాటిని ఏ సాధనం లేదా పద్ధతిలో డీక్రిప్ట్ చేయాలో తెలుసు. + సాధనాలను అమర్చండి + సమూహం, ఆర్డర్, శోధన మరియు పిన్ సాధనాలు కాబట్టి ప్రధాన స్క్రీన్ మీ నిజమైన వర్క్‌ఫ్లోతో సరిపోలుతుంది + ప్రధాన స్క్రీన్‌ను వేగంగా చేయండి + మీరు ఏ సాధనాలను ఎక్కువగా ఉపయోగిస్తున్నారో తెలుసుకున్న తర్వాత సాధన అమరిక సెట్టింగ్‌లు ట్యూన్ చేయడం విలువైనది. గ్రూపింగ్ అనేది అన్వేషణలో సహాయపడుతుంది, కస్టమ్ ఆర్డర్ కండరాల జ్ఞాపకశక్తికి సహాయపడుతుంది మరియు ఇష్టమైనవి పూర్తి జాబితా పెద్దదైనప్పటికీ రోజువారీ సాధనాలను అందుబాటులో ఉంచుతాయి. + యాప్‌ను నేర్చుకునేటప్పుడు లేదా టాస్క్ రకం ద్వారా ఆర్గనైజ్ చేయబడిన సాధనాలను మీరు ఇష్టపడుతున్నప్పుడు సమూహ మోడ్‌ని ఉపయోగించండి. + మీ తరచుగా ఉపయోగించే సాధనాలు మీకు ఇప్పటికే తెలిసినప్పుడు మరియు తక్కువ స్క్రోల్‌లు కావాలనుకున్నప్పుడు అనుకూల క్రమానికి మారండి. + మీరు పేరుతో గుర్తుపెట్టుకునే అరుదైన సాధనాల కోసం శోధన సెట్టింగ్‌లు మరియు ఇష్టమైనవి కలిసి ఉపయోగించండి, కానీ అన్ని సమయాలలో కనిపించకూడదనుకోండి. + పెద్ద ఫైళ్లను నిర్వహించండి + నెమ్మదిగా ఎగుమతులు, మెమరీ ఒత్తిడి మరియు ఊహించని విధంగా పెద్ద అవుట్‌పుట్‌ను నివారించండి + ఎగుమతి చేసే ముందు పనిని తగ్గించండి + చాలా పెద్ద చిత్రాలు, పొడవైన బ్యాచ్‌లు మరియు అధిక-నాణ్యత ఫార్మాట్‌లకు ఎక్కువ మెమరీ మరియు సమయం పడుతుంది. వేగవంతమైన పరిష్కారం సాధారణంగా భారీ ఆపరేషన్‌కు ముందు పని మొత్తాన్ని తగ్గించడం, అదే భారీ ఇన్‌పుట్ పూర్తయ్యే వరకు వేచి ఉండదు. + భారీ ఫిల్టర్‌లు, OCR లేదా PDF మార్పిడిని వర్తింపజేయడానికి ముందు భారీ చిత్రాల పరిమాణాన్ని మార్చండి. + సెట్టింగ్‌లను నిర్ధారించడానికి ముందుగా చిన్న బ్యాచ్‌ని ఉపయోగించండి, ఆపై మిగిలిన వాటిని అదే ప్రీసెట్‌తో ప్రాసెస్ చేయండి. + అవుట్‌పుట్ ఫైల్ ఊహించిన దాని కంటే పెద్దదిగా ఉన్నప్పుడు తక్కువ నాణ్యత లేదా ఆకృతిని మార్చండి. + కాష్ మరియు ప్రివ్యూలు + భారీ సెషన్‌ల తర్వాత రూపొందించిన ప్రివ్యూలు, కాష్ క్లీనప్ మరియు స్టోరేజ్ వినియోగాన్ని అర్థం చేసుకోండి + నిల్వ మరియు వేగాన్ని సమతుల్యంగా ఉంచండి + ప్రివ్యూ జనరేషన్ మరియు కాష్ రిపీట్ బ్రౌజింగ్‌ను వేగవంతం చేయగలవు, కానీ భారీ ఎడిటింగ్ సెషన్‌లు తాత్కాలిక డేటాను వదిలివేయవచ్చు. యాప్ స్లోగా అనిపించినప్పుడు, స్టోరేజ్ టైట్‌గా ఉన్నప్పుడు లేదా అనేక ప్రయోగాల తర్వాత ప్రివ్యూలు పాతబడిపోయినప్పుడు కాష్ సెట్టింగ్‌లు సహాయపడతాయి. + తాత్కాలిక నిల్వను కనిష్టీకరించడం కంటే అనేక చిత్రాలను బ్రౌజ్ చేస్తున్నప్పుడు ప్రివ్యూలను ప్రారంభించడం చాలా ముఖ్యం. + పెద్ద బ్యాచ్‌లు, విఫలమైన ప్రయోగాలు లేదా అనేక అధిక-రిజల్యూషన్ ఫైల్‌లతో సుదీర్ఘ సెషన్‌ల తర్వాత కాష్‌ను క్లియర్ చేయండి. + మీరు లాంచ్‌ల మధ్య ప్రివ్యూలను వెచ్చగా ఉంచడం కంటే స్టోరేజ్ క్లీనప్‌ను ఇష్టపడితే ఆటోమేటిక్ కాష్ క్లియరింగ్‌ని ఉపయోగించండి. + స్క్రీన్ ప్రవర్తనను సర్దుబాటు చేయండి + ఫోకస్ చేసిన పని కోసం పూర్తి స్క్రీన్, సురక్షిత మోడ్, ప్రకాశం మరియు సిస్టమ్ బార్ ఎంపికలను ఉపయోగించండి + ఇంటర్ఫేస్ పరిస్థితికి సరిపోయేలా చేయండి + మీరు ల్యాండ్‌స్కేప్‌లో సవరించినప్పుడు, ప్రైవేట్ చిత్రాలను ప్రదర్శించినప్పుడు లేదా స్థిరమైన ప్రకాశం అవసరమైనప్పుడు స్క్రీన్ సెట్టింగ్‌లు సహాయపడతాయి. అవి సాధారణ ఉపయోగం కోసం అవసరం లేదు, కానీ అవి QR తనిఖీ, ప్రైవేట్ ప్రివ్యూలు లేదా ఇరుకైన ల్యాండ్‌స్కేప్ ఎడిటింగ్ వంటి ఇబ్బందికరమైన పరిస్థితులను పరిష్కరిస్తాయి. + నావిగేషన్ క్రోమ్ కంటే ఖాళీని సవరించడం చాలా ముఖ్యం అయినప్పుడు పూర్తి స్క్రీన్ లేదా సిస్టమ్ బార్ సెట్టింగ్‌లను ఉపయోగించండి. + స్క్రీన్‌షాట్‌లు లేదా యాప్ ప్రివ్యూలలో ప్రైవేట్ చిత్రాలు కనిపించనప్పుడు సురక్షిత మోడ్‌ని ఉపయోగించండి. + డార్క్ ఇమేజ్‌లు లేదా QR కోడ్‌లను తనిఖీ చేయడం కష్టంగా ఉన్న టూల్స్ కోసం బ్రైట్‌నెస్ ఎన్‌ఫోర్స్‌మెంట్‌ని ఉపయోగించండి. + పారదర్శకత పాటించండి + పారదర్శక నేపథ్యాలు తెలుపు, నలుపు లేదా చదునుగా మారకుండా నిరోధించండి + ఆల్ఫా-అవేర్ అవుట్‌పుట్‌ని ఉపయోగించండి + ఎంచుకున్న సాధనం మరియు అవుట్‌పుట్ ఫార్మాట్ ఆల్ఫాకు మద్దతు ఇచ్చినప్పుడు మాత్రమే పారదర్శకత మనుగడలో ఉంటుంది. ఎగుమతి చేయబడిన నేపథ్యం తెలుపు లేదా నలుపు రంగులోకి మారినట్లయితే, సమస్య సాధారణంగా అవుట్‌పుట్ ఫార్మాట్, పూరక/నేపథ్య సెట్టింగ్ లేదా చిత్రాన్ని చదును చేసే గమ్యం యాప్. + నేపథ్యం తీసివేసిన చిత్రాలు, స్టిక్కర్లు, లోగోలు లేదా అతివ్యాప్తులను ఎగుమతి చేస్తున్నప్పుడు PNG లేదా WebPని ఎంచుకోండి. + పారదర్శక అవుట్‌పుట్ కోసం JPEGని నివారించండి ఎందుకంటే ఇది ఆల్ఫా ఛానెల్‌ని నిల్వ చేయదు. + ప్రివ్యూ నిండిన నేపథ్యాన్ని చూపిస్తే, ఆల్ఫా ఫార్మాట్‌ల కోసం నేపథ్య రంగుకు సంబంధించిన సెట్టింగ్‌లను తనిఖీ చేయండి. + భాగస్వామ్యం మరియు దిగుమతి సమస్యలను పరిష్కరించండి + ఫైల్‌లు కనిపించనప్పుడు లేదా సరిగ్గా తెరవనప్పుడు పికర్ సెట్టింగ్‌లు మరియు మద్దతు ఉన్న ఫార్మాట్‌లను ఉపయోగించండి + ఫైల్‌ను యాప్‌లోకి పొందండి + దిగుమతి సమస్యలు తరచుగా ప్రొవైడర్ అనుమతులు, మద్దతు లేని ఫార్మాట్‌లు లేదా పికర్ ప్రవర్తన వల్ల సంభవిస్తాయి. క్లౌడ్ యాప్‌లు మరియు మెసెంజర్‌ల నుండి షేర్ చేయబడిన ఫైల్‌లు తాత్కాలికంగా ఉండవచ్చు, కాబట్టి సుదీర్ఘ సవరణల కోసం నిరంతర మూలాన్ని ఎంచుకోవడం మరింత నమ్మదగినదిగా ఉంటుంది. + ఇటీవలి ఫైల్‌ల సత్వరమార్గానికి బదులుగా సిస్టమ్ పికర్ ద్వారా ఫైల్‌ను తెరవడానికి ప్రయత్నించండి. + ఫార్మాట్‌కు ఒక సాధనం మద్దతు ఇవ్వకపోతే, ముందుగా దాన్ని మార్చండి లేదా మరింత నిర్దిష్ట సాధనాన్ని తెరవండి. + షేర్ ఫ్లోలు లేదా డైరెక్ట్ టూల్ ఓపెనింగ్ ఊహించిన దానికంటే భిన్నంగా ప్రవర్తిస్తే ఇమేజ్ పికర్ మరియు లాంచర్ సెట్టింగ్‌లను రివ్యూ చేయండి. + నిష్క్రమణ నిర్ధారణ + సంజ్ఞలు, బ్యాక్ ప్రెస్‌లు లేదా టూల్ స్విచ్‌లు అనుకోకుండా జరిగినప్పుడు సేవ్ చేయని సవరణలను కోల్పోకుండా ఉండండి + అసంపూర్తిగా ఉన్న పనిని రక్షించండి + మీరు సంజ్ఞ నావిగేషన్‌ని ఉపయోగించినప్పుడు, పెద్ద ఫైల్‌లను సవరించినప్పుడు లేదా తరచుగా యాప్‌ల మధ్య మారినప్పుడు సాధనం నిష్క్రమణ నిర్ధారణ సహాయకరంగా ఉంటుంది. ఇది సాధనాన్ని వదిలివేసే ముందు చిన్న పాజ్‌ని జోడిస్తుంది కాబట్టి అసంపూర్తిగా ఉన్న సెట్టింగ్‌లు మరియు ప్రివ్యూలు ప్రమాదవశాత్తు కోల్పోవు. + మీరు ఎగుమతి చేయడానికి ముందు ప్రమాదవశాత్తు వెనుక సంజ్ఞలు ఎప్పుడైనా సాధనాన్ని మూసివేసి ఉంటే నిర్ధారణను ప్రారంభించండి. + మీరు ఎక్కువగా త్వరిత వన్-స్టెప్ కన్వర్షన్‌లు చేసి, వేగవంతమైన నావిగేషన్‌ను ఇష్టపడితే దాన్ని డిజేబుల్‌గా ఉంచండి. + రీబిల్డింగ్ సెట్టింగ్‌లు చికాకు కలిగించే సుదీర్ఘ వర్క్‌ఫ్లోల కోసం ప్రీసెట్‌లతో కలిసి దీన్ని ఉపయోగించండి. + సమస్యలను నివేదించేటప్పుడు లాగ్‌లను ఉపయోగించండి + సమస్యను తెరవడానికి లేదా డెవలపర్‌ని సంప్రదించడానికి ముందు ఉపయోగకరమైన వివరాలను సేకరించండి + సందర్భంతో సమస్యలను నివేదించండి + దశలు, యాప్ వెర్షన్, ఇన్‌పుట్ రకం మరియు లాగ్‌లతో కూడిన స్పష్టమైన నివేదిక నిర్ధారణ చేయడం చాలా సులభం. సమస్యను పునరుత్పత్తి చేసిన వెంటనే లాగ్‌లు చాలా ఉపయోగకరంగా ఉంటాయి ఎందుకంటే అవి చుట్టుపక్కల సందర్భాన్ని తాజాగా ఉంచుతాయి. + సాధనం పేరు, ఖచ్చితమైన చర్య మరియు ఇది ఒక ఫైల్ లేదా ప్రతి ఫైల్‌తో జరుగుతుందో లేదో గమనించండి. + సమస్యను పునరుత్పత్తి చేసిన తర్వాత యాప్ లాగ్‌లను తెరవండి మరియు సాధ్యమైనప్పుడు సంబంధిత లాగ్ అవుట్‌పుట్‌ను భాగస్వామ్యం చేయండి. + స్క్రీన్‌షాట్‌లు లేదా నమూనా ఫైల్‌లు ప్రైవేట్ సమాచారాన్ని కలిగి లేనప్పుడు మాత్రమే వాటిని అటాచ్ చేయండి. + బ్యాక్‌గ్రౌండ్ ప్రాసెసింగ్ నిలిపివేయబడింది + ఆండ్రాయిడ్ బ్యాక్‌గ్రౌండ్ ప్రాసెసింగ్ నోటిఫికేషన్‌ని సకాలంలో ప్రారంభించనివ్వలేదు. ఇది సిస్టమ్ సమయ పరిమితి, ఇది విశ్వసనీయంగా పరిష్కరించబడదు. అనువర్తనాన్ని పునఃప్రారంభించి, మళ్లీ ప్రయత్నించండి; యాప్‌ని తెరిచి ఉంచడం మరియు నోటిఫికేషన్‌లు లేదా బ్యాక్‌గ్రౌండ్ వర్క్‌ని అనుమతించడం సహాయపడవచ్చు. + ఫైల్ ఎంపికను దాటవేయి + ఇన్‌పుట్ సాధారణంగా షేర్, క్లిప్‌బోర్డ్ లేదా మునుపటి స్క్రీన్ నుండి వచ్చినప్పుడు సాధనాలను వేగంగా తెరవండి + రిపీట్ టూల్ లాంచ్‌లను వేగంగా చేయండి + ఫైల్ ఎంపికను దాటవేయి మద్దతు ఉన్న సాధనాల మొదటి దశను మారుస్తుంది. మీరు సాధారణంగా ఇప్పటికే ఎంచుకున్న చిత్రంతో లేదా Android భాగస్వామ్య చర్యల నుండి సాధనాన్ని నమోదు చేసినప్పుడు ఇది ఉపయోగకరంగా ఉంటుంది, అయితే ప్రతి సాధనం వెంటనే ఫైల్ కోసం అడగాలని మీరు ఆశించినట్లయితే అది గందరగోళంగా అనిపించవచ్చు. + సాధనం తెరవడానికి ముందు మీ సాధారణ ప్రవాహం ఇప్పటికే మూల చిత్రాన్ని అందించినప్పుడు మాత్రమే దాన్ని ప్రారంభించండి. + అనువర్తనాన్ని నేర్చుకునేటప్పుడు దాన్ని నిలిపివేయి ఉంచండి, ఎందుకంటే స్పష్టమైన ఎంపిక ప్రతి సాధనాన్ని సులభంగా అర్థం చేసుకోవడానికి చేస్తుంది. + స్పష్టమైన ఇన్‌పుట్ లేకుండా టూల్ తెరిస్తే, ఈ సెట్టింగ్‌ని నిలిపివేయండి లేదా షేర్/ఓపెన్ విత్ నుండి ప్రారంభించండి, తద్వారా Android ఫైల్‌ను ముందుగా పాస్ చేస్తుంది. + ముందుగా ఎడిటర్‌ని తెరవండి + భాగస్వామ్య చిత్రం నేరుగా ఎడిటింగ్‌లోకి వెళ్లినప్పుడు ప్రివ్యూను దాటవేయండి + మొదటి స్టాప్‌గా ప్రివ్యూ లేదా ఎడిట్‌ని ఎంచుకోండి + ప్రివ్యూకి బదులుగా సవరించు తెరవండి అనేది ఇప్పటికే వారు చిత్రాన్ని సవరించాలనుకుంటున్నారని తెలిసిన వినియోగదారుల కోసం. మీరు చాట్ లేదా క్లౌడ్ నిల్వ నుండి ఫైల్‌లను తనిఖీ చేసినప్పుడు ప్రివ్యూ సురక్షితంగా ఉంటుంది; స్క్రీన్‌షాట్‌లు, శీఘ్ర క్రాప్‌లు, ఉల్లేఖనాలు మరియు ఒక-ఆఫ్ దిద్దుబాట్ల కోసం ప్రత్యక్ష సవరణ వేగంగా ఉంటుంది. + చాలా షేర్ చేయబడిన చిత్రాలకు వెంటనే క్రాప్, మార్కప్, ఫిల్టర్‌లు లేదా ఎగుమతి మార్పులు అవసరమైతే నేరుగా సవరణను ప్రారంభించండి. + మీరు నిర్ణయించే ముందు మెటాడేటా, కొలతలు, పారదర్శకత లేదా చిత్ర నాణ్యతను తరచుగా తనిఖీ చేసినప్పుడు ముందుగా ప్రివ్యూను వదిలివేయండి. + దీన్ని ఇష్టమైన వాటితో కలిపి ఉపయోగించండి, తద్వారా మీరు నిజంగా ఉపయోగించే సాధనాలకు దగ్గరగా షేర్ చేయబడిన చిత్రాలు ఉంటాయి. + ప్రీసెట్ టెక్స్ట్ ఎంట్రీ + నియంత్రణలను పదేపదే సర్దుబాటు చేయడానికి బదులుగా ఖచ్చితమైన ప్రీసెట్ విలువలను నమోదు చేయండి + స్లయిడర్‌లు నెమ్మదిగా ఉన్నప్పుడు ఖచ్చితమైన విలువలను ఉపయోగించండి + మీకు పునరావృతమయ్యే పని కోసం ఖచ్చితమైన సంఖ్యలు అవసరమైనప్పుడు ప్రీసెట్ టెక్స్ట్ నమోదు సహాయపడుతుంది: సామాజిక చిత్ర పరిమాణాలు, డాక్యుమెంట్ మార్జిన్‌లు, కుదింపు లక్ష్యాలు, లైన్ వెడల్పులు లేదా సంజ్ఞలతో చేరుకోవడానికి బాధించే ఇతర విలువలు. చక్కటి స్లయిడర్ కదలిక కష్టంగా ఉన్న చిన్న స్క్రీన్‌లలో కూడా ఇది ఉపయోగపడుతుంది. + మీరు తరచుగా ఖచ్చితమైన పరిమాణాలు, శాతాలు, నాణ్యత విలువలు లేదా సాధన పారామితులను మళ్లీ ఉపయోగిస్తుంటే టెక్స్ట్ ఎంట్రీని ప్రారంభించండి. + ఒకసారి టైప్ చేసిన తర్వాత విలువను ప్రీసెట్‌గా సేవ్ చేసి, అదే సెటప్‌ని మళ్లీ నిర్మించడానికి బదులుగా దాన్ని మళ్లీ ఉపయోగించండి. + కఠినమైన విజువల్ ట్యూనింగ్ కోసం సంజ్ఞ నియంత్రణలను మరియు ఖచ్చితమైన అవుట్‌పుట్ అవసరాల కోసం టైప్ చేసిన విలువలను ఉంచండి. + అసలు దగ్గర సేవ్ చేయండి + ఫోల్డర్ సందర్భం ముఖ్యమైనప్పుడు ఉత్పత్తి చేయబడిన ఫైల్‌లను మూల చిత్రాల పక్కన ఉంచండి + వాటి మూలాధారాలతో అవుట్‌పుట్‌లను ఉంచండి + కెమెరా ఫోల్డర్‌లు, ప్రాజెక్ట్ ఫోల్డర్‌లు, డాక్యుమెంట్ స్కాన్‌లు మరియు క్లయింట్ బ్యాచ్‌ల కోసం ఒరిజినల్ ఫోల్డర్‌కు సేవ్ చేయడం చాలా ఉపయోగకరంగా ఉంటుంది, ఇక్కడ ఎడిట్ చేసిన కాపీ సోర్స్ పక్కన ఉండాలి. ఇది డెడికేటెడ్ అవుట్‌పుట్ ఫోల్డర్ కంటే తక్కువ చక్కగా ఉంటుంది, కాబట్టి దీన్ని స్పష్టమైన ఫైల్ పేరు ప్రత్యయాలు లేదా నమూనాలతో కలపండి. + ప్రతి సోర్స్ ఫోల్డర్ ఇప్పటికే అర్థవంతంగా ఉన్నప్పుడు మరియు అవుట్‌పుట్‌లు అక్కడ సమూహంగా ఉన్నప్పుడు దాన్ని ప్రారంభించండి. + ఫైల్ పేరు ప్రత్యయాలు, ఉపసర్గలు, టైమ్‌స్టాంప్‌లు లేదా నమూనాలను ఉపయోగించండి, తద్వారా సవరించిన కాపీలను అసలైన వాటి నుండి వేరు చేయడం సులభం. + మీరు ఒకే ఎగుమతి ఫోల్డర్‌లో సేకరించిన అన్ని జెనరేట్ చేయబడిన ఫైల్‌లను కోరుకున్నప్పుడు మిశ్రమ బ్యాచ్‌ల కోసం దీన్ని నిలిపివేయండి. + పెద్ద అవుట్‌పుట్‌ని దాటవేయండి + ఊహించని విధంగా మూలం కంటే భారీగా మారే మార్పిడులను సేవ్ చేయడం మానుకోండి + బ్యాడ్ సైజ్ సర్ప్రైజ్‌ల నుండి బ్యాచ్‌లను రక్షించండి + కొన్ని మార్పిడులు ఫైల్‌లను పెద్దవిగా చేస్తాయి, ముఖ్యంగా PNG స్క్రీన్‌షాట్‌లు, ఇప్పటికే కంప్రెస్ చేయబడిన ఫోటోలు, అధిక-నాణ్యత వెబ్‌పి లేదా మెటాడేటా భద్రపరచబడిన చిత్రాలు. పరిమాణాన్ని తగ్గించడం ప్రధాన లక్ష్యం అయిన వర్క్‌ఫ్లోలలో చిన్న మూలాన్ని భర్తీ చేయకుండా ఆ అవుట్‌పుట్‌లను పెద్దది నిరోధిస్తే దాటవేయడాన్ని అనుమతించండి. + ఎగుమతి అనేది అప్‌లోడ్ పరిమితుల కోసం ఫైల్‌లను కుదించడానికి, పరిమాణాన్ని మార్చడానికి లేదా సిద్ధం చేయడానికి ఉద్దేశించినప్పుడు దాన్ని ప్రారంభించండి. + ఒక బ్యాచ్ తర్వాత దాటవేయబడిన ఫైల్‌లను సమీక్షించండి; అవి ఇప్పటికే ఆప్టిమైజ్ చేయబడి ఉండవచ్చు లేదా వేరే ఫార్మాట్ అవసరం కావచ్చు. + తుది ఫైల్ పరిమాణం కంటే నాణ్యత, పారదర్శకత లేదా ఫార్మాట్ అనుకూలత ముఖ్యమైనప్పుడు దాన్ని నిలిపివేయండి. + క్లిప్‌బోర్డ్ మరియు లింక్‌లు + వేగవంతమైన టెక్స్ట్-ఆధారిత సాధనాల కోసం ఆటోమేటిక్ క్లిప్‌బోర్డ్ ఇన్‌పుట్ మరియు లింక్ ప్రివ్యూలను నియంత్రించండి + స్వయంచాలక ఇన్‌పుట్‌ను ఊహించగలిగేలా చేయండి + QR, Base64, URL మరియు టెక్స్ట్-హెవీ వర్క్‌ఫ్లోల కోసం క్లిప్‌బోర్డ్ మరియు లింక్ సెట్టింగ్‌లు సౌకర్యవంతంగా ఉంటాయి, అయితే ఆటోమేటిక్ ఇన్‌పుట్ ఉద్దేశపూర్వకంగా ఉండాలి. యాప్ స్వయంగా ఫీల్డ్‌లను పూరించినట్లు అనిపిస్తే, క్లిప్‌బోర్డ్ పేస్ట్ ప్రవర్తనను తనిఖీ చేయండి; లింక్ కార్డ్‌లు శబ్దం లేదా నెమ్మదిగా అనిపిస్తే, లింక్ ప్రివ్యూ ప్రవర్తనను సర్దుబాటు చేయండి. + మీరు తరచుగా వచనాన్ని కాపీ చేసి, వెంటనే సరిపోలే సాధనాన్ని తెరిచినప్పుడు ఆటోమేటిక్ క్లిప్‌బోర్డ్ పేస్ట్‌ను అనుమతించండి. + మీరు ఊహించని చోట ప్రైవేట్ క్లిప్‌బోర్డ్ కంటెంట్ టూల్స్‌లో కనిపిస్తే ఆటోమేటిక్ పేస్ట్‌ని నిలిపివేయండి. + ప్రివ్యూ పొందడం నెమ్మదిగా ఉన్నప్పుడు, అపసవ్యంగా లేదా మీ వర్క్‌ఫ్లో కోసం అనవసరంగా ఉన్నప్పుడు లింక్ ప్రివ్యూలను ఆఫ్ చేయండి. + కాంపాక్ట్ నియంత్రణలు + స్క్రీన్ స్పేస్ గట్టిగా ఉన్నప్పుడు దట్టమైన సెలెక్టర్లు మరియు వేగవంతమైన సెట్టింగ్‌ల ప్లేస్‌మెంట్‌ని ఉపయోగించండి + చిన్న స్క్రీన్‌లపై మరిన్ని నియంత్రణలను అమర్చండి + చిన్న ఫోన్‌లు, ల్యాండ్‌స్కేప్ ఎడిటింగ్ మరియు అనేక పారామితులతో కూడిన సాధనాలపై కాంపాక్ట్ సెలెక్టర్‌లు మరియు వేగవంతమైన సెట్టింగ్‌ల ప్లేస్‌మెంట్ సహాయం చేస్తుంది. మార్పిడి అనేది నియంత్రణలు దట్టంగా అనిపించవచ్చు, కాబట్టి మీరు ఇప్పటికే సాధారణ ఎంపికలను గుర్తించిన తర్వాత ఇది ఉత్తమం. + డైలాగ్‌లు లేదా ఎంపిక జాబితాలు స్క్రీన్‌కు చాలా పొడవుగా ఉన్నట్లు అనిపించినప్పుడు కాంపాక్ట్ సెలెక్టర్‌లను ప్రారంభించండి. + సాధన నియంత్రణలు డిస్‌ప్లే యొక్క ఒక వైపు నుండి సులభంగా చేరుకోగలిగితే, వేగవంతమైన సెట్టింగ్‌ల వైపు సర్దుబాటు చేయండి. + మీరు ఇప్పటికీ అనువర్తనాన్ని నేర్చుకుంటూ ఉంటే లేదా తరచుగా మిస్-ట్యాప్ దట్టమైన ఎంపికలను కలిగి ఉన్నట్లయితే, రూమియర్ నియంత్రణలకు తిరిగి వెళ్లండి. + వెబ్ నుండి చిత్రాలను లోడ్ చేయండి + పేజీ లేదా చిత్ర URLని అతికించండి, అన్వయించబడిన చిత్రాలను ప్రివ్యూ చేయండి, ఆపై ఎంచుకున్న ఫలితాలను సేవ్ చేయండి లేదా భాగస్వామ్యం చేయండి + వెబ్ చిత్రాలను ఉద్దేశపూర్వకంగా ఉపయోగించండి + వెబ్ ఇమేజ్ లోడింగ్ అందించిన URL నుండి చిత్ర మూలాలను అన్వయిస్తుంది, అందుబాటులో ఉన్న మొదటి చిత్రాన్ని ప్రివ్యూ చేస్తుంది మరియు ఎంచుకున్న పార్స్ చేసిన చిత్రాలను సేవ్ చేయడానికి లేదా భాగస్వామ్యం చేయడానికి మిమ్మల్ని అనుమతిస్తుంది. కొన్ని సైట్‌లు తాత్కాలిక, రక్షిత లేదా స్క్రిప్ట్-ఉత్పత్తి లింక్‌లను ఉపయోగిస్తాయి, కాబట్టి బ్రౌజర్‌లో పనిచేసే పేజీ ఇప్పటికీ ఉపయోగించదగిన చిత్రాలను అందించకపోవచ్చు. + ప్రత్యక్ష చిత్ర URL లేదా పేజీ URLని అతికించండి మరియు అన్వయించబడిన చిత్ర జాబితా కనిపించే వరకు వేచి ఉండండి. + పేజీ అనేక చిత్రాలను కలిగి ఉన్నప్పుడు ఫ్రేమ్ లేదా ఎంపిక నియంత్రణలను ఉపయోగించండి మరియు మీకు వాటిలో కొన్ని మాత్రమే అవసరం. + ఏమీ లోడ్ కాకపోతే, డైరెక్ట్ ఇమేజ్ లింక్‌ని ప్రయత్నించండి లేదా ముందుగా బ్రౌజర్ ద్వారా డౌన్‌లోడ్ చేసుకోండి; సైట్ పార్సింగ్‌ను నిరోధించవచ్చు లేదా ప్రైవేట్ లింక్‌లను ఉపయోగించవచ్చు. + పునఃపరిమాణం రకాన్ని ఎంచుకోండి + చిత్రాన్ని బలవంతంగా కొత్త కోణాల్లోకి తీసుకురావడానికి ముందు స్పష్టమైన, సౌకర్యవంతమైన, క్రాప్ మరియు ఫిట్‌ని అర్థం చేసుకోండి + ఆకారం, కాన్వాస్ మరియు కారక నిష్పత్తిని నియంత్రించండి + అభ్యర్థించిన వెడల్పు మరియు ఎత్తు అసలు కారక నిష్పత్తితో సరిపోలనప్పుడు ఏమి జరుగుతుందో పునఃపరిమాణం రకం నిర్ణయిస్తుంది. ఇది పిక్సెల్‌లను సాగదీయడం, నిష్పత్తులను సంరక్షించడం, అదనపు ప్రాంతాన్ని కత్తిరించడం లేదా నేపథ్య కాన్వాస్‌ను జోడించడం మధ్య వ్యత్యాసం. + వక్రీకరణ ఆమోదయోగ్యమైనప్పుడు లేదా మూలం ఇప్పటికే లక్ష్యం వలె అదే కారక నిష్పత్తిని కలిగి ఉన్నప్పుడు మాత్రమే స్పష్టమైన ఉపయోగించండి. + ముఖ్యంగా ఫోటోలు మరియు మిశ్రమ బ్యాచ్‌ల కోసం ముఖ్యమైన వైపు నుండి పరిమాణం మార్చడం ద్వారా నిష్పత్తులను ఉంచడానికి ఫ్లెక్సిబుల్‌ని ఉపయోగించండి. + అంచులు లేని కఠినమైన ఫ్రేమ్‌ల కోసం క్రాప్‌ని ఉపయోగించండి లేదా మొత్తం చిత్రం స్థిరమైన కాన్వాస్‌లో తప్పనిసరిగా కనిపించేలా ఫిట్ చేయండి. + ఇమేజ్ స్కేల్ మోడ్‌ను ఎంచుకోండి + పదును, సున్నితత్వం, వేగం మరియు అంచు కళాఖండాలను నియంత్రించే రీసాంప్లింగ్ ఫిల్టర్‌ని ఎంచుకోండి + ప్రమాదవశాత్తూ మృదుత్వం లేకుండా పరిమాణాన్ని మార్చండి + ఇమేజ్ స్కేల్ మోడ్ పునఃపరిమాణం సమయంలో కొత్త పిక్సెల్‌లను ఎలా లెక్కించాలో నియంత్రిస్తుంది. సాధారణ మోడ్‌లు వేగంగా మరియు ఊహాజనితంగా ఉంటాయి, అయితే అధునాతన ఫిల్టర్‌లు వివరాలను మెరుగ్గా భద్రపరచగలవు కానీ అంచులను పదును పెట్టవచ్చు, హాలోస్‌ను సృష్టించవచ్చు లేదా పెద్ద చిత్రాలపై ఎక్కువ సమయం పట్టవచ్చు. + ఫలితం చిన్నదిగా మరియు శుభ్రంగా ఉండవలసి వచ్చినప్పుడు వేగంగా రోజువారీ పరిమాణం మార్చడం కోసం బేసిక్ లేదా బిలినియర్‌ని ఉపయోగించండి. + చక్కటి వివరాలు, వచనం లేదా అధిక-నాణ్యత తగ్గింపు విషయంలో Lanczos, Robidoux, Spline లేదా EWA-శైలి మోడ్‌లను ఉపయోగించండి. + అస్పష్టమైన పిక్సెల్‌లు రూపాన్ని నాశనం చేసే పిక్సెల్ ఆర్ట్, చిహ్నాలు మరియు గట్టి అంచుగల గ్రాఫిక్‌ల కోసం సమీపంలోని ఉపయోగించండి. + స్కేల్ కలర్ స్పేస్ + పరిమాణాన్ని మార్చే సమయంలో పిక్సెల్‌లను పునఃప్రారంభించేటప్పుడు రంగులు ఎలా మిళితం చేయబడతాయో నిర్ణయించండి + గ్రేడియంట్లు మరియు అంచులను సహజంగా ఉంచండి + స్కేల్ కలర్ స్పేస్ పరిమాణం మార్చేటప్పుడు ఉపయోగించే గణితాన్ని మారుస్తుంది. చాలా చిత్రాలు డిఫాల్ట్‌తో బాగానే ఉంటాయి, కానీ లీనియర్ లేదా పర్సెప్చువల్ స్పేస్‌లు బలమైన స్కేలింగ్ తర్వాత గ్రేడియంట్లు, ముదురు అంచులు మరియు సంతృప్త రంగులు శుభ్రంగా కనిపించేలా చేస్తాయి. + చిన్న రంగు వ్యత్యాసాల కంటే వేగం మరియు అనుకూలత ఎక్కువగా ఉన్నప్పుడు డిఫాల్ట్‌ను వదిలివేయండి. + డార్క్ అవుట్‌లైన్‌లు, UI స్క్రీన్‌షాట్‌లు, గ్రేడియంట్లు లేదా రంగు బ్యాండ్‌లు పరిమాణం మార్చిన తర్వాత తప్పుగా కనిపించినప్పుడు లీనియర్ లేదా పర్సెప్చువల్ మోడ్‌లను ప్రయత్నించండి. + నిజమైన లక్ష్య స్క్రీన్‌లో ముందు మరియు తర్వాత సరిపోల్చండి, ఎందుకంటే రంగు-స్థల మార్పులు సూక్ష్మంగా మరియు కంటెంట్‌పై ఆధారపడి ఉంటాయి. + రీసైజ్ మోడ్‌లను పరిమితం చేయండి + ఓవర్-లిమిట్ ఇమేజ్‌లను ఎప్పుడు దాటవేయాలి, రీకోడ్ చేయాలి లేదా సరిపోయేలా తగ్గించాలి + పరిమితిలో ఏమి జరుగుతుందో ఎంచుకోండి + పరిమితి పునఃపరిమాణం అనేది చిన్న-చిత్ర సాధనం మాత్రమే కాదు. సోర్స్ ఇప్పటికే మీ పరిమితుల్లో ఉన్నప్పుడు లేదా ఒక వైపు మాత్రమే నియమాన్ని ఉల్లంఘించినప్పుడు యాప్ ఏమి చేస్తుందో దీని మోడ్ నిర్ణయిస్తుంది, ఇది మిశ్రమ ఫోల్డర్‌లు మరియు అప్‌లోడ్ తయారీకి ముఖ్యమైనది. + పరిమితుల లోపల ఉన్న చిత్రాలను తాకకుండా వదిలేయాలి మరియు మళ్లీ ఎగుమతి చేయనప్పుడు దాటవేయి ఉపయోగించండి. + కొలతలు ఆమోదయోగ్యంగా ఉన్నప్పుడు రీకోడ్‌ని ఉపయోగించండి, కానీ మీకు ఇంకా కొత్త ఫార్మాట్, మెటాడేటా ప్రవర్తన, నాణ్యత లేదా ఫైల్ పేరు అవసరం. + పరిమితులను ఉల్లంఘించే చిత్రాలు అనుమతించబడిన పెట్టెకు సరిపోయే వరకు దామాషా ప్రకారం స్కేల్ చేయబడినప్పుడు జూమ్‌ని ఉపయోగించండి. + కారక నిష్పత్తి లక్ష్యాలు + కచ్చితమైన అవుట్‌పుట్ పరిమాణాలలో విస్తరించిన ముఖాలు, కట్-ఆఫ్ అంచులు మరియు ఊహించని అంచులను నివారించండి + పరిమాణం మార్చడానికి ముందు ఆకారాన్ని సరిపోల్చండి + వెడల్పు మరియు ఎత్తు పరిమాణం పరిమాణం లక్ష్యంలో సగం మాత్రమే. చిత్రం సహజంగా సరిపోతుందా, క్రాపింగ్ కావాలా లేదా దాని చుట్టూ కాన్వాస్ కావాలా అనేది కారక నిష్పత్తి నిర్ణయిస్తుంది. ముందుగా ఆకారాన్ని తనిఖీ చేయడం వలన చాలా ఆశ్చర్యకరమైన రీసైజ్ ఫలితాలు నిరోధిస్తాయి. + స్పష్టమైన, క్రాప్ లేదా ఫిట్‌ని ఎంచుకోవడానికి ముందు మూల ఆకారాన్ని లక్ష్య ఆకారంతో సరిపోల్చండి. + సబ్జెక్ట్ అదనపు నేపథ్యాన్ని కోల్పోయి, గమ్యస్థానానికి కఠినమైన ఫ్రేమ్ అవసరం అయినప్పుడు ముందుగా కత్తిరించండి. + ఒరిజినల్ ఇమేజ్‌లోని ప్రతి భాగం తప్పనిసరిగా కనిపించేటప్పుడు ఫిట్ లేదా ఫ్లెక్సిబుల్‌ని ఉపయోగించండి. + ఉన్నత స్థాయి లేదా సాధారణ పరిమాణం + పిక్సెల్‌లను జోడించేటప్పుడు AI అవసరం మరియు సాధారణ స్కేలింగ్ ఎప్పుడు సరిపోతుందో తెలుసుకోండి + వివరాలతో పరిమాణాన్ని కంగారు పెట్టవద్దు + పిక్సెల్‌లను ఇంటర్‌పోలేట్ చేయడం ద్వారా సాధారణ పరిమాణాన్ని మారుస్తుంది; ఇది నిజమైన వివరాలను కనుగొనలేదు. AI అప్‌స్కేల్ పదునుగా కనిపించే వివరాలను సృష్టించగలదు, కానీ ఇది నెమ్మదిగా ఉంటుంది మరియు ఆకృతిని భ్రమింపజేస్తుంది, కాబట్టి సరైన ఎంపిక మీకు అనుకూలత, వేగం లేదా దృశ్య పునరుద్ధరణ అవసరమా అనే దానిపై ఆధారపడి ఉంటుంది. + థంబ్‌నెయిల్‌లు, అప్‌లోడ్ పరిమితులు, స్క్రీన్‌షాట్‌లు మరియు సాధారణ పరిమాణం మార్పుల కోసం సాధారణ పరిమాణాన్ని ఉపయోగించండి. + చిన్న లేదా మృదువైన చిత్రం పెద్ద పరిమాణంలో మెరుగ్గా కనిపించాల్సిన అవసరం వచ్చినప్పుడు AI ఉన్నత స్థాయిని ఉపయోగించండి. + AI అప్‌స్కేల్ తర్వాత ముఖాలు, వచనం మరియు నమూనాలను సరిపోల్చండి ఎందుకంటే రూపొందించబడిన వివరాలు నమ్మదగినవిగా కానీ తప్పుగా కనిపిస్తాయి. + ఫిల్టర్ ఆర్డర్ ముఖ్యమైనది + క్లీనప్, కలర్, షార్ప్‌నెస్ మరియు ఫైనల్ కంప్రెషన్‌ను ఉద్దేశపూర్వక క్రమంలో వర్తించండి + క్లీనర్ ఫిల్టర్ స్టాక్‌ను రూపొందించండి + ఫిల్టర్‌లు ఎల్లప్పుడూ పరస్పరం మార్చుకోలేవు. పదును పెట్టడానికి ముందు బ్లర్, OCR ముందు కాంట్రాస్ట్ బూస్ట్ లేదా కంప్రెషన్‌కు ముందు రంగు మార్పు ఒకే నియంత్రణల నుండి చాలా భిన్నమైన అవుట్‌పుట్‌ను ఉత్పత్తి చేస్తుంది. + ముందుగా కత్తిరించండి మరియు తిప్పండి కాబట్టి తర్వాత ఫిల్టర్‌లు మిగిలి ఉన్న పిక్సెల్‌లపై మాత్రమే పని చేస్తాయి. + పదునుపెట్టే లేదా శైలీకృత ప్రభావాలకు ముందు ఎక్స్‌పోజర్, వైట్ బ్యాలెన్స్ మరియు కాంట్రాస్ట్‌ని వర్తింపజేయండి. + చివరి పరిమాణాన్ని మరియు కుదింపును ముగింపులో ఉంచండి, తద్వారా ప్రివ్యూ చేయబడిన వివరాలు ఎగుమతిలో ఊహించదగిన విధంగా ఉంటాయి. + నేపథ్య అంచులను పరిష్కరించండి + ఆటోమేటిక్ రిమూవల్ తర్వాత హాలోస్, మిగిలిపోయిన నీడలు, జుట్టు, రంధ్రాలు మరియు మృదువైన రూపురేఖలను శుభ్రం చేయండి + కటౌట్‌లను ఉద్దేశపూర్వకంగా కనిపించేలా చేయండి + ఆటోమేటిక్ మాస్క్‌లు తరచుగా ఒక చూపులో బాగానే కనిపిస్తాయి కానీ ప్రకాశవంతమైన, చీకటి లేదా నమూనా ఉన్న నేపథ్యాలలో సమస్యలను బహిర్గతం చేస్తాయి. ఎడ్జ్ క్లీనప్ అనేది త్వరిత కటౌట్ మరియు పునర్వినియోగ స్టిక్కర్, లోగో లేదా ఉత్పత్తి ఇమేజ్ మధ్య వ్యత్యాసం. + హాలోస్ మరియు తప్పిపోయిన అంచు వివరాలను బహిర్గతం చేయడానికి కాంట్రాస్టింగ్ బ్యాక్‌గ్రౌండ్‌లకు వ్యతిరేకంగా ఫలితాన్ని ప్రివ్యూ చేయండి. + చుట్టుపక్కల మిగిలిపోయిన వాటిని చెరిపేసే ముందు జుట్టు, మూలలు మరియు పారదర్శక వస్తువుల కోసం పునరుద్ధరణను ఉపయోగించండి. + కటౌట్ డిజైన్‌లో ఉంచబడినప్పుడు చివరి నేపథ్య రంగుపై చిన్న పరీక్షను ఎగుమతి చేయండి. + చదవగలిగే వాటర్‌మార్క్‌లు + ఫోటోను నాశనం చేయకుండా ప్రకాశవంతమైన, చీకటి, బిజీగా ఉన్న మరియు కత్తిరించిన చిత్రాలపై గుర్తులు కనిపించేలా చేయండి + చిత్రాన్ని దాచకుండా రక్షించండి + ఒక చిత్రంపై మంచిగా కనిపించే వాటర్‌మార్క్ మరొకదానిపై కనిపించకుండా పోతుంది. మొదటి ప్రివ్యూ మాత్రమే కాకుండా మొత్తం బ్యాచ్‌కు పరిమాణం, అస్పష్టత, కాంట్రాస్ట్, ప్లేస్‌మెంట్ మరియు పునరావృతం ఎంచుకోవాలి. + అన్ని ఫైల్‌లను ఎగుమతి చేయడానికి ముందు బ్యాచ్‌లోని ప్రకాశవంతమైన మరియు చీకటి చిత్రాలపై గుర్తును పరీక్షించండి. + పెద్ద రిపీటెడ్ మార్కుల కోసం తక్కువ అస్పష్టతను మరియు చిన్న మూల గుర్తులకు బలమైన కాంట్రాస్ట్ ఉపయోగించండి. + ముఖ్యమైన ముఖాలు, వచనం మరియు ఉత్పత్తి వివరాలను స్పష్టంగా ఉంచండి, గుర్తు పునర్వినియోగాన్ని నిరోధించడానికి ఉద్దేశించినది కాదు. + ఒక చిత్రాన్ని టైల్స్‌గా విభజించండి + అడ్డు వరుసలు, నిలువు వరుసలు, అనుకూల శాతాలు, ఫార్మాట్ మరియు నాణ్యత ద్వారా ఒకే చిత్రాన్ని కత్తిరించండి + గ్రిడ్లు మరియు ఆర్డర్ చేసిన ముక్కలను సిద్ధం చేయండి + ఇమేజ్ స్ప్లిటింగ్ ఒక మూల చిత్రం నుండి బహుళ అవుట్‌పుట్‌లను సృష్టిస్తుంది. ఇది అడ్డు వరుస మరియు నిలువు వరుసల గణనల వారీగా విభజించవచ్చు, అడ్డు వరుస లేదా నిలువు వరుస శాతాలను సర్దుబాటు చేయవచ్చు మరియు ఎంచుకున్న అవుట్‌పుట్ ఫార్మాట్ మరియు నాణ్యతతో ముక్కలను సేవ్ చేయవచ్చు, ఇది గ్రిడ్‌లు, పేజీ ముక్కలు, పనోరమాలు మరియు ఆర్డర్ చేసిన సామాజిక పోస్ట్‌లకు ఉపయోగపడుతుంది. + మూల చిత్రాన్ని ఎంచుకోండి, ఆపై మీకు అవసరమైన అడ్డు వరుసలు మరియు నిలువు వరుసల సంఖ్యను సెట్ చేయండి. + ముక్కలు అన్నీ సమాన పరిమాణంలో ఉండనప్పుడు అడ్డు వరుస లేదా నిలువు వరుస శాతాలను సర్దుబాటు చేయండి. + సేవ్ చేయడానికి ముందు అవుట్‌పుట్ ఫార్మాట్ మరియు నాణ్యతను ఎంచుకోండి, తద్వారా ఉత్పత్తి చేయబడిన ప్రతి భాగం ఒకే ఎగుమతి నియమాలను ఉపయోగిస్తుంది. + ఇమేజ్ స్ట్రిప్స్‌ను కత్తిరించండి + నిలువు లేదా క్షితిజ సమాంతర ప్రాంతాలను తీసివేసి, మిగిలిన భాగాలను తిరిగి కలపండి + ఖాళీని వదలకుండా ప్రాంతాన్ని తీసివేయండి + ఇమేజ్ కటింగ్ సాధారణ పంటకు భిన్నంగా ఉంటుంది. ఇది ఎంచుకున్న నిలువు లేదా క్షితిజ సమాంతర స్ట్రిప్‌ను తీసివేసి, మిగిలిన చిత్ర భాగాలను ఒకదానితో ఒకటి కలపవచ్చు. విలోమ మోడ్ ఎంచుకున్న ప్రాంతాన్ని బదులుగా ఉంచుతుంది మరియు రెండు విలోమ దిశలను ఉపయోగించడం ఎంచుకున్న దీర్ఘచతురస్రాన్ని ఉంచుతుంది. + పక్క ప్రాంతాలు, నిలువు వరుసలు లేదా మధ్య స్ట్రిప్స్ కోసం నిలువు కట్టింగ్ ఉపయోగించండి; బ్యానర్‌లు, ఖాళీలు లేదా అడ్డు వరుసల కోసం క్షితిజ సమాంతర కట్టింగ్‌ని ఉపయోగించండి. + మీరు ఎంచుకున్న భాగాన్ని తీసివేయడానికి బదులుగా ఉంచాలనుకున్నప్పుడు అక్షంపై విలోమాన్ని ప్రారంభించండి. + కత్తిరించిన తర్వాత అంచులను పరిదృశ్యం చేయండి ఎందుకంటే తీసివేయబడిన ప్రాంతం ముఖ్యమైన వివరాలను దాటినప్పుడు విలీనం చేయబడిన కంటెంట్ ఆకస్మికంగా కనిపిస్తుంది. + అవుట్‌పుట్ ఆకృతిని ఎంచుకోండి + కంటెంట్ మరియు గమ్యస్థానం ఆధారంగా JPEG, PNG, WebP, AVIF, JXL లేదా PDFని ఎంచుకోండి + గమ్యం ఆకృతిని నిర్ణయించనివ్వండి + ఉత్తమ ఆకృతి చిత్రం ఏమి కలిగి ఉంది మరియు అది ఎక్కడ ఉపయోగించబడుతుంది అనే దానిపై ఆధారపడి ఉంటుంది. ఫోటోలు, స్క్రీన్‌షాట్‌లు, పారదర్శక ఆస్తులు, డాక్యుమెంట్‌లు మరియు యానిమేటెడ్ ఫైల్‌లు తప్పు ఫార్మాట్‌లోకి నెట్టబడినప్పుడు వివిధ మార్గాల్లో విఫలమవుతాయి. + పారదర్శకత మరియు ఖచ్చితమైన పిక్సెల్‌లు అవసరం లేనప్పుడు విస్తృత ఫోటో అనుకూలత కోసం JPEGని ఉపయోగించండి. + పదునైన స్క్రీన్‌షాట్‌లు, UI క్యాప్చర్‌లు, పారదర్శక గ్రాఫిక్‌లు మరియు లాస్‌లెస్ ఎడిట్‌ల కోసం PNGని ఉపయోగించండి. + కాంపాక్ట్ ఆధునిక అవుట్‌పుట్ ముఖ్యమైనప్పుడు మరియు స్వీకరించే యాప్ దానికి మద్దతు ఇచ్చినప్పుడు WebP, AVIF లేదా JXLని ఉపయోగించండి. + ఆడియో కవర్‌లను సంగ్రహించండి + ఆడియో ఫైల్‌ల నుండి పొందుపరిచిన ఆల్బమ్ ఆర్ట్ లేదా అందుబాటులో ఉన్న మీడియా ఫ్రేమ్‌లను PNG చిత్రాలుగా సేవ్ చేయండి + ఆడియో ఫైల్‌ల నుండి కళాకృతిని లాగండి + ఆడియో ఫైల్‌ల నుండి ఎంబెడెడ్ ఆర్ట్‌వర్క్‌ని చదవడానికి ఆడియో కవర్‌లు Android మీడియా మెటాడేటాను ఉపయోగిస్తాయి. ఎంబెడెడ్ ఆర్ట్‌వర్క్ అందుబాటులో లేనప్పుడు, ఆండ్రాయిడ్‌ను అందించగలిగితే రిట్రీవర్ మీడియా ఫ్రేమ్‌కి తిరిగి రావచ్చు; ఏదీ లేనట్లయితే, సంగ్రహించడానికి చిత్రం లేదని సాధనం నివేదిస్తుంది. + ఆడియో కవర్‌లను తెరిచి, ఊహించిన కవర్ ఆర్ట్‌తో ఒకటి లేదా అంతకంటే ఎక్కువ ఆడియో ఫైల్‌లను ఎంచుకోండి. + ముఖ్యంగా స్ట్రీమింగ్ లేదా రికార్డింగ్ యాప్‌ల నుండి ఫైల్‌లను సేవ్ చేయడానికి లేదా షేర్ చేయడానికి ముందు ఎక్స్‌ట్రాక్ట్ చేసిన కవర్‌ను రివ్యూ చేయండి. + చిత్రం కనుగొనబడకపోతే, ఆడియో ఫైల్‌లో పొందుపరిచిన కవర్ ఉండదు లేదా Android ఉపయోగించగల ఫ్రేమ్‌ను బహిర్గతం చేయదు. + తేదీలు మరియు స్థాన మెటాడేటా + క్యాప్చర్ సమయం ముఖ్యమైనప్పుడు ఏమి భద్రపరచాలో నిర్ణయించుకోండి కానీ GPS లేదా పరికర డేటా లీక్ కాకూడదు + ప్రైవేట్ మెటాడేటా నుండి ఉపయోగకరమైన మెటాడేటాను వేరు చేయండి + మెటాడేటా అన్నీ సమానంగా ప్రమాదకరం కాదు. క్యాప్చర్ తేదీలు ఆర్కైవ్‌లు మరియు క్రమబద్ధీకరణకు ఉపయోగపడతాయి, అయితే GPS, డివైజ్ సీరియల్ లాంటి ఫీల్డ్‌లు, సాఫ్ట్‌వేర్ ట్యాగ్‌లు లేదా ఓనర్ ఫీల్డ్‌లు ఉద్దేశించిన దానికంటే ఎక్కువ బహిర్గతం చేయగలవు. + ఆల్బమ్‌లు, స్కాన్‌లు లేదా ప్రాజెక్ట్ చరిత్ర కోసం కాలక్రమానుసారం ముఖ్యమైనప్పుడు తేదీ మరియు సమయ ట్యాగ్‌లను ఉంచండి. + పబ్లిక్ షేర్ చేయడానికి లేదా అపరిచితులకు చిత్రాలను పంపడానికి ముందు GPS మరియు పరికరాన్ని గుర్తించే ట్యాగ్‌లను తీసివేయండి. + గోప్యతా అవసరాలు కఠినంగా ఉన్నప్పుడు నమూనాను ఎగుమతి చేయండి మరియు EXIFని మళ్లీ తనిఖీ చేయండి. + ఫైల్ పేరు ఘర్షణలను నివారించండి + అనేక ఫైల్‌లు image.jpg లేదా screenshot.png వంటి పేర్లను షేర్ చేసినప్పుడు బ్యాచ్ అవుట్‌పుట్‌లను రక్షించండి + ప్రతి అవుట్‌పుట్ పేరును ప్రత్యేకంగా చేయండి + మెసెంజర్‌లు, స్క్రీన్‌షాట్‌లు, క్లౌడ్ ఫోల్డర్‌లు లేదా బహుళ కెమెరాల నుండి చిత్రాలు వచ్చినప్పుడు నకిలీ ఫైల్ పేర్లు సాధారణం. ఒక మంచి నమూనా ప్రమాదవశాత్తూ పునఃస్థాపనను నిరోధిస్తుంది మరియు అవుట్‌పుట్‌లను వాటి మూలాలకు తిరిగి గుర్తించగలిగేలా ఉంచుతుంది. + సవరించిన కాపీ గుర్తించదగినదిగా ఉన్నప్పుడు అసలు పేరు మరియు ప్రత్యయం చేర్చండి. + పెద్ద మిశ్రమ బ్యాచ్‌లను ప్రాసెస్ చేస్తున్నప్పుడు సీక్వెన్స్, టైమ్‌స్టాంప్, ప్రీసెట్ లేదా కొలతలు జోడించండి. + పేరు పెట్టే నమూనా కొట్టుకోలేదని మీరు ధృవీకరించే వరకు ఓవర్‌రైట్ వర్క్‌ఫ్లోలను నివారించండి. + సేవ్ చేయండి లేదా భాగస్వామ్యం చేయండి + స్థిరమైన ఫైల్ మరియు మరొక యాప్‌కి తాత్కాలిక హ్యాండ్‌ఆఫ్ మధ్య ఎంచుకోండి + సరైన ఉద్దేశ్యంతో ఎగుమతి చేయండి + సేవ్ చేయడం మరియు భాగస్వామ్యం చేయడం సారూప్యంగా అనిపించవచ్చు, కానీ అవి వేర్వేరు సమస్యలను పరిష్కరిస్తాయి. సేవ్ చేయడం వలన మీరు తర్వాత కనుగొనగలిగే ఫైల్‌ని సృష్టిస్తుంది; భాగస్వామ్యం చేయడం వలన Android ద్వారా రూపొందించబడిన ఫలితాన్ని మరొక యాప్‌కి పంపుతుంది మరియు మన్నికైన స్థానిక కాపీని వదిలివేయకపోవచ్చు. + అవుట్‌పుట్ తప్పనిసరిగా తెలిసిన ఫోల్డర్‌లో ఉండాలి, బ్యాకప్ చేయబడాలి లేదా తర్వాత మళ్లీ ఉపయోగించినప్పుడు సేవ్ చేయి ఉపయోగించండి. + శీఘ్ర సందేశాలు, ఇమెయిల్ జోడింపులు, సామాజిక పోస్టింగ్ లేదా మరొక ఎడిటర్‌కు ఫలితాన్ని పంపడం కోసం షేర్‌ని ఉపయోగించండి. + స్వీకరించే యాప్ నమ్మదగినది కానప్పుడు లేదా విఫలమైన భాగస్వామ్యాన్ని పునఃసృష్టించడానికి ఇబ్బందికరంగా ఉన్నప్పుడు ముందుగా సేవ్ చేయండి. + PDF పేజీ పరిమాణం మరియు అంచులు + పత్రాన్ని సృష్టించే ముందు కాగితం పరిమాణం, సరిపోయే ప్రవర్తన మరియు శ్వాస గదిని ఎంచుకోండి + చిత్ర పేజీలను ముద్రించదగిన మరియు చదవగలిగేలా చేయండి + ఎంచుకున్న కాగితం పరిమాణంతో వాటి ఆకారం సరిపోలనప్పుడు చిత్రాలు ఇబ్బందికరమైన PDF పేజీలుగా మారవచ్చు. మార్జిన్‌లు, ఫిట్ మోడ్ మరియు పేజీ ఓరియంటేషన్ ద్వారా కంటెంట్ కత్తిరించబడిందా, చిన్నదిగా, విస్తరించబడిందా లేదా చదవడానికి సౌకర్యంగా ఉందో లేదో నిర్ణయిస్తాయి. + PDF ముద్రించబడినప్పుడు లేదా ఫారమ్‌కు సమర్పించబడినప్పుడు నిజమైన కాగితం పరిమాణాన్ని ఉపయోగించండి. + స్కాన్‌లు మరియు స్క్రీన్‌షాట్‌ల కోసం మార్జిన్‌లను ఉపయోగించండి, తద్వారా టెక్స్ట్ పేజీ అంచుకు ఎదురుగా ఉండదు. + సోర్స్ ఇమేజ్‌లు మిశ్రమ ధోరణిని కలిగి ఉన్నప్పుడు పోర్ట్రెయిట్ మరియు ల్యాండ్‌స్కేప్ పేజీలను కలిపి ప్రివ్యూ చేయండి. + స్కాన్‌లను శుభ్రం చేయండి + PDF లేదా OCRకి ముందు పేపర్ అంచులు, నీడలు, కాంట్రాస్ట్, రొటేషన్ మరియు రీడబిలిటీని మెరుగుపరచండి + ఆర్కైవ్ చేయడానికి ముందు పేజీలను సిద్ధం చేయండి + స్కాన్‌ని సాంకేతికంగా క్యాప్చర్ చేయవచ్చు కానీ చదవడం కష్టం. PDF ఎగుమతి ముందు చిన్న క్లీనప్ దశలు తరచుగా కంప్రెషన్ సెట్టింగ్‌ల కంటే ముఖ్యమైనవి, ముఖ్యంగా రసీదులు, చేతితో వ్రాసిన గమనికలు మరియు తక్కువ-కాంతి పేజీల కోసం. + సరైన దృక్కోణం మరియు టేబుల్ అంచులు, వేళ్లు, నీడలు మరియు నేపథ్య అయోమయానికి దూరంగా కత్తిరించండి. + కాంట్రాస్ట్‌ను జాగ్రత్తగా పెంచండి, తద్వారా స్టాంపులు, సంతకాలు లేదా పెన్సిల్ గుర్తులను నాశనం చేయకుండా టెక్స్ట్ స్పష్టంగా మారుతుంది. + పేజీ నిటారుగా మరియు చదవగలిగిన తర్వాత మాత్రమే OCRని అమలు చేయండి, ఆపై ముఖ్యమైన సంఖ్యలను మాన్యువల్‌గా ధృవీకరించండి. + PDFలను కుదించండి, గ్రేస్కేల్ లేదా రిపేర్ చేయండి + తేలికైన, ముద్రించదగిన లేదా పునర్నిర్మించిన డాక్యుమెంట్ కాపీల కోసం ఒక-ఫైల్ PDF ఆపరేషన్‌లను ఉపయోగించండి + PDF క్లీనప్ ఆపరేషన్‌ని ఎంచుకోండి + PDF సాధనాలు కుదింపు, గ్రేస్కేల్ మార్పిడి మరియు మరమ్మత్తు కోసం ప్రత్యేక కార్యకలాపాలను కలిగి ఉంటాయి. కంప్రెషన్ మరియు గ్రేస్కేల్ ప్రధానంగా ఎంబెడెడ్ ఇమేజ్‌ల ద్వారా పని చేస్తాయి, అయితే మరమ్మత్తు PDF నిర్మాణాన్ని పునర్నిర్మిస్తుంది; వీటిలో ఏదీ ప్రతి డాక్యుమెంట్‌కు హామీనిచ్చే పరిష్కారంగా పరిగణించరాదు. + డాక్యుమెంట్‌లో ఇమేజ్‌లు మరియు ఫైల్ సైజు విషయాలను షేర్ చేయడం కోసం కంప్రెస్ PDFని ఉపయోగించండి. + పత్రం సులభంగా ముద్రించబడాలి లేదా రంగు చిత్రాలు అవసరం లేనప్పుడు గ్రేస్కేల్ ఉపయోగించండి. + పత్రం తెరవడంలో విఫలమైనప్పుడు లేదా అంతర్గత నిర్మాణం దెబ్బతిన్నప్పుడు కాపీపై రిపేర్ PDFని ఉపయోగించండి, ఆపై పునర్నిర్మించిన ఫైల్‌ను ధృవీకరించండి. + PDFల నుండి చిత్రాలను సంగ్రహించండి + పొందుపరిచిన PDF చిత్రాలను పునరుద్ధరించండి మరియు సంగ్రహించిన ఫలితాలను ఆర్కైవ్‌లో ప్యాక్ చేయండి + పత్రాల నుండి మూల చిత్రాలను సేవ్ చేయండి + ఎక్స్‌ట్రాక్ట్ ఇమేజ్‌లు పొందుపరిచిన ఇమేజ్ ఆబ్జెక్ట్‌ల కోసం PDFని స్కాన్ చేస్తుంది, సాధ్యమైన చోట నకిలీలను దాటవేస్తుంది మరియు పునరుద్ధరించబడిన చిత్రాలను రూపొందించిన జిప్ ఆర్కైవ్‌లో నిల్వ చేస్తుంది. ఇది వాస్తవానికి PDFలో పొందుపరిచిన చిత్రాలను మాత్రమే సంగ్రహిస్తుంది, టెక్స్ట్, వెక్టర్ డ్రాయింగ్‌లు లేదా స్క్రీన్‌షాట్‌గా కనిపించే ప్రతి పేజీని కాదు. + కేటలాగ్ నుండి ఫోటోలు లేదా స్కాన్ చేసిన ఆస్తులు వంటి PDFలో నిల్వ చేయబడిన చిత్రాలు మీకు అవసరమైనప్పుడు సంగ్రహ చిత్రాలను ఉపయోగించండి. + మీకు టెక్స్ట్ మరియు లేఅవుట్‌తో సహా పూర్తి పేజీలు అవసరమైనప్పుడు బదులుగా చిత్రాలకు PDF లేదా పేజీ వెలికితీతను ఉపయోగించండి. + సాధనం పొందుపరిచిన చిత్రాలను నివేదించకపోతే, పేజీ టెక్స్ట్/వెక్టార్ కంటెంట్ కావచ్చు లేదా ఇమేజ్‌లు సేకరించదగిన వస్తువులుగా నిల్వ చేయబడకపోవచ్చు. + మెటాడేటా, చదును చేయడం మరియు ప్రింట్ అవుట్‌పుట్ + పత్ర లక్షణాలను సవరించండి, పేజీలను రాస్టరైజ్ చేయండి లేదా కస్టమ్ ప్రింట్ లేఅవుట్‌లను ఉద్దేశపూర్వకంగా సిద్ధం చేయండి + చివరి పత్రం చర్యను ఎంచుకోండి + PDF మెటాడేటా, చదును చేయడం మరియు ముద్రణ తయారీ వివిధ చివరి దశ సమస్యలను పరిష్కరిస్తుంది. మెటాడేటా డాక్యుమెంట్ ప్రాపర్టీలను నియంత్రిస్తుంది, PDF పేజీలను తక్కువ ఎడిట్ చేయగల అవుట్‌పుట్‌గా రాస్టరైజ్ చేస్తుంది మరియు ప్రింట్ PDF పేజీ పరిమాణం, ఓరియంటేషన్, మార్జిన్ మరియు పేజీల-షీట్ నియంత్రణలతో కొత్త లేఅవుట్‌ను సిద్ధం చేస్తుంది. + రచయిత, శీర్షిక, కీలకపదాలు, నిర్మాత లేదా గోప్యతా క్లీనప్ ముఖ్యమైనప్పుడు మెటాడేటాను ఉపయోగించండి. + కనిపించే పేజీ కంటెంట్‌ని ప్రత్యేక PDF ఆబ్జెక్ట్‌లుగా సవరించడం కష్టంగా మారినప్పుడు ఫ్లాటెన్ PDFని ఉపయోగించండి. + మీకు అనుకూల పేజీ పరిమాణం, దిశ, మార్జిన్‌లు లేదా ఒక్కో షీట్‌కు బహుళ పేజీలు అవసరమైనప్పుడు ప్రింట్ PDFని ఉపయోగించండి. + OCR కోసం చిత్రాలను సిద్ధం చేయండి + వచనాన్ని చదవమని OCRని అడగడానికి ముందు క్రాప్, రొటేషన్, కాంట్రాస్ట్, డీనోయిస్ మరియు స్కేల్ ఉపయోగించండి + OCR క్లీనర్ పిక్సెల్‌లను ఇవ్వండి + OCR తప్పులు తరచుగా OCR ఇంజిన్ నుండి కాకుండా ఇన్‌పుట్ ఇమేజ్ నుండి వస్తాయి. వంకర పేజీలు, తక్కువ కాంట్రాస్ట్, బ్లర్, షాడోలు, చిన్న వచనం మరియు మిశ్రమ నేపథ్యాలు అన్నీ గుర్తింపు నాణ్యతను తగ్గిస్తాయి. + టెక్స్ట్ ప్రాంతానికి కత్తిరించండి మరియు ఇమేజ్‌ని తిప్పండి, తద్వారా గుర్తింపుకు ముందు పంక్తులు క్షితిజ సమాంతరంగా ఉంటాయి. + అక్షరాలను సులభంగా చదవగలిగేటప్పుడు మాత్రమే కాంట్రాస్ట్‌ని పెంచండి లేదా క్లీనర్ నలుపు-తెలుపుకి మార్చండి. + చిన్న వచనాన్ని మధ్యస్థంగా పైకి మార్చండి, కానీ నకిలీ అంచులను సృష్టించే దూకుడు పదును పెట్టడాన్ని నివారించండి. + QR కంటెంట్‌ని తనిఖీ చేయండి + కోడ్‌ను విశ్వసించే ముందు లింక్‌లు, Wi-Fi ఆధారాలు, పరిచయాలు మరియు చర్యలను సమీక్షించండి + డీకోడ్ చేసిన వచనాన్ని అవిశ్వసనీయ ఇన్‌పుట్‌గా పరిగణించండి + QR కోడ్ URL, కాంటాక్ట్ కార్డ్, Wi-Fi పాస్‌వర్డ్, క్యాలెండర్ ఈవెంట్, ఫోన్ నంబర్ లేదా సాదా వచనాన్ని దాచగలదు. కోడ్ దగ్గర కనిపించే లేబుల్ అసలు పేలోడ్‌తో సరిపోలకపోవచ్చు, కాబట్టి డీకోడ్ చేసిన కంటెంట్‌పై చర్య తీసుకునే ముందు దాన్ని రివ్యూ చేయండి. + పూర్తిగా డీకోడ్ చేయబడిన URLని తెరవడానికి ముందు దాన్ని తనిఖీ చేయండి, ముఖ్యంగా కుదించబడిన లేదా తెలియని డొమైన్‌లు. + Wi-Fi, పరిచయం, క్యాలెండర్, ఫోన్ మరియు SMS కోడ్‌లతో జాగ్రత్తగా ఉండండి ఎందుకంటే అవి సున్నితమైన చర్యలను ప్రారంభించగలవు. + మీరు కంటెంట్‌ని వెంటనే తెరవడానికి బదులు మరెక్కడైనా ధృవీకరించాల్సి వచ్చినప్పుడు ముందుగా దాన్ని కాపీ చేయండి. + QR కోడ్‌లను రూపొందించండి + సాదా వచనం, URLలు, Wi-Fi, పరిచయాలు, ఇమెయిల్, ఫోన్, SMS మరియు స్థాన డేటా నుండి కోడ్‌లను సృష్టించండి + సరైన QR పేలోడ్‌ను రూపొందించండి + QR స్క్రీన్ ఎంటర్ చేసిన కంటెంట్ నుండి కోడ్‌లను రూపొందించగలదు అలాగే ఇప్పటికే ఉన్న వాటిని స్కాన్ చేస్తుంది. నిర్మాణాత్మక QR రకాలు Wi-Fi లాగిన్ వివరాలు, సంప్రదింపు కార్డ్‌లు, ఇమెయిల్ ఫీల్డ్‌లు, ఫోన్ నంబర్‌లు, SMS కంటెంట్, URLలు లేదా భౌగోళిక కోఆర్డినేట్‌లు వంటి ఇతర యాప్‌లు అర్థం చేసుకునే ఫార్మాట్‌లో డేటాను ఎన్‌కోడ్ చేయడంలో సహాయపడతాయి. + సాధారణ గమనికల కోసం సాదా వచనాన్ని ఎంచుకోండి లేదా కోడ్ Wi-Fi, పరిచయం, URL, ఇమెయిల్, ఫోన్, SMS లేదా స్థాన డేటాగా తెరవబడినప్పుడు నిర్మాణాత్మక రకాన్ని ఉపయోగించండి. + కనిపించే ప్రివ్యూ మీరు నమోదు చేసిన పేలోడ్‌ను మాత్రమే ప్రతిబింబిస్తుంది కాబట్టి రూపొందించబడిన కోడ్‌ను భాగస్వామ్యం చేయడానికి ముందు ప్రతి ఫీల్డ్‌ను తనిఖీ చేయండి. + సేవ్ చేయబడిన QR కోడ్‌ను ప్రింట్ చేసినప్పుడు, పబ్లిక్‌గా పోస్ట్ చేసినప్పుడు లేదా ఇతర వ్యక్తులు ఉపయోగించినప్పుడు స్కానర్‌తో పరీక్షించండి. + చెక్‌సమ్ మోడ్‌ను ఎంచుకోండి + ఫైల్‌లు లేదా టెక్స్ట్ నుండి హ్యాష్‌లను లెక్కించండి, ఒక ఫైల్‌ను సరిపోల్చండి లేదా అనేక ఫైల్‌లను బ్యాచ్-చెక్ చేయండి + కంటెంట్‌ని ధృవీకరించండి, ప్రదర్శన కాదు + చెక్‌సమ్ సాధనాలు ఫైల్ హ్యాషింగ్, టెక్స్ట్ హ్యాషింగ్, తెలిసిన హాష్‌తో ఫైల్‌ను పోల్చడం మరియు బ్యాచ్ పోలిక కోసం ప్రత్యేక ట్యాబ్‌లను కలిగి ఉన్నాయి. ఎంచుకున్న అల్గోరిథం కోసం బైట్-ఫర్-బైట్ గుర్తింపును చెక్‌సమ్ రుజువు చేస్తుంది; రెండు చిత్రాలు కేవలం ఒకేలా కనిపిస్తున్నాయని నిరూపించలేదు. + ఫైల్‌ల కోసం లెక్కించు మరియు టైప్ చేసిన లేదా అతికించిన టెక్స్ట్ డేటా కోసం టెక్స్ట్ హాష్ ఉపయోగించండి. + ఎవరైనా మీకు ఒక ఫైల్ కోసం ఆశించిన చెక్‌సమ్‌ను అందించినప్పుడు సరిపోల్చండి ఉపయోగించండి. + ఒకే పాస్‌లో అనేక ఫైల్‌లకు హ్యాష్‌లు లేదా ధృవీకరణ అవసరమైనప్పుడు బ్యాచ్ కంపేర్‌ని ఉపయోగించండి, ఆపై ఎంచుకున్న అల్గారిథమ్‌ను స్థిరంగా ఉంచండి. + క్లిప్‌బోర్డ్ గోప్యత + అనుకోకుండా ప్రైవేట్ కాపీ చేసిన డేటాను బహిర్గతం చేయకుండా ఆటోమేటిక్ క్లిప్‌బోర్డ్ ఫీచర్‌లను ఉపయోగించండి + కాపీ చేసిన కంటెంట్‌ను ఉద్దేశపూర్వకంగా ఉంచండి + క్లిప్‌బోర్డ్ ఆటోమేషన్ సౌకర్యవంతంగా ఉంటుంది, కానీ కాపీ చేసిన వచనం పాస్‌వర్డ్‌లు, లింక్‌లు, చిరునామాలు, టోకెన్‌లు లేదా ప్రైవేట్ నోట్‌లను కలిగి ఉండవచ్చు. క్లిప్‌బోర్డ్ ఆధారిత ఇన్‌పుట్‌ను మీ అలవాట్లు మరియు గోప్యతా అంచనాలకు సరిపోయే స్పీడ్ ఫీచర్‌గా పరిగణించండి. + అనుకోకుండా టూల్స్‌లో ప్రైవేట్ టెక్స్ట్ కనిపిస్తే ఆటోమేటిక్ క్లిప్‌బోర్డ్ వినియోగాన్ని నిలిపివేయండి. + మీరు ఉద్దేశపూర్వకంగా స్టోరేజ్‌ను నివారించాలని కోరుకున్నప్పుడు మాత్రమే క్లిప్‌బోర్డ్-మాత్రమే సేవింగ్‌ను ఉపయోగించండి. + సున్నితమైన వచనం, QR డేటా లేదా Base64 పేలోడ్‌లను నిర్వహించిన తర్వాత క్లిప్‌బోర్డ్‌ను క్లియర్ చేయండి లేదా భర్తీ చేయండి. + రంగు ఆకృతులు + మరెక్కడా అతికించే ముందు HEX, RGB, HSV, ఆల్ఫా మరియు కాపీ చేసిన రంగు విలువలను అర్థం చేసుకోండి + లక్ష్యం ఆశించే ఆకృతిని కాపీ చేయండి + ఒకే కనిపించే రంగును అనేక విధాలుగా వ్రాయవచ్చు. డిజైన్ టూల్, CSS ఫీల్డ్, Android రంగు విలువ లేదా ఇమేజ్ ఎడిటర్ వేరే ఆర్డర్, ఆల్ఫా హ్యాండ్లింగ్ లేదా కలర్ మోడల్‌ని ఆశించవచ్చు. + కాంపాక్ట్ కలర్ కోడ్‌లను ఆశించే వెబ్, డిజైన్ లేదా థీమ్ ఫీల్డ్‌లలో అతికించేటప్పుడు HEXని ఉపయోగించండి. + మీరు ఛానెల్‌లు, ప్రకాశం, సంతృప్తత లేదా రంగును మాన్యువల్‌గా సర్దుబాటు చేయవలసి వచ్చినప్పుడు RGB లేదా HSVని ఉపయోగించండి. + పారదర్శకత ముఖ్యమైనప్పుడు ఆల్ఫాను విడిగా తనిఖీ చేయండి ఎందుకంటే కొన్ని గమ్యస్థానాలు దానిని విస్మరిస్తాయి లేదా వేరే క్రమాన్ని ఉపయోగిస్తాయి. + రంగు లైబ్రరీని బ్రౌజ్ చేయండి + పేరు లేదా HEX ద్వారా పేరున్న రంగులను శోధించండి మరియు ఇష్టమైన వాటిని ఎగువన ఉంచండి + పునర్వినియోగపరచదగిన రంగులను కనుగొనండి + రంగు లైబ్రరీ పేరు పెట్టబడిన రంగు సేకరణను లోడ్ చేస్తుంది, పేరు ద్వారా క్రమబద్ధీకరిస్తుంది, రంగు పేరు లేదా HEX విలువ ద్వారా ఫిల్టర్ చేస్తుంది మరియు ఇష్టమైన రంగులను నిల్వ చేస్తుంది కాబట్టి ముఖ్యమైన ఎంట్రీలు చేరుకోవడం సులభం. చిత్రం నుండి నమూనా చేయడానికి బదులుగా మీకు నిజమైన పేరున్న రంగు అవసరమైనప్పుడు ఇది ఉపయోగకరంగా ఉంటుంది. + ఎరుపు, నీలం, స్లేట్ లేదా పుదీనా వంటి కుటుంబం మీకు తెలిసినప్పుడు రంగు పేరుతో శోధించండి. + మీరు ఇప్పటికే సంఖ్యా రంగును కలిగి ఉన్నప్పుడు మరియు సరిపోలే లేదా సమీపంలోని పేరు నమోదులను కనుగొనాలనుకున్నప్పుడు HEX ద్వారా శోధించండి. + తరచుగా రంగులను ఇష్టమైనవిగా గుర్తించండి, తద్వారా అవి బ్రౌజ్ చేస్తున్నప్పుడు సాధారణ లైబ్రరీ కంటే ముందుకు కదులుతాయి. + మెష్ గ్రేడియంట్ సేకరణలను ఉపయోగించండి + అందుబాటులో ఉన్న మెష్ గ్రేడియంట్ వనరులను డౌన్‌లోడ్ చేయండి మరియు ఎంచుకున్న చిత్రాలను భాగస్వామ్యం చేయండి + రిమోట్ మెష్ గ్రేడియంట్ ఆస్తులను ఉపయోగించండి + మెష్ గ్రేడియంట్స్ రిమోట్ రిసోర్స్ సేకరణను లోడ్ చేయగలవు, తప్పిపోయిన గ్రేడియంట్ చిత్రాలను డౌన్‌లోడ్ చేయగలవు, డౌన్‌లోడ్ పురోగతిని చూపగలవు మరియు ఎంచుకున్న గ్రేడియంట్‌లను పంచుకోగలవు. లభ్యత నెట్‌వర్క్ యాక్సెస్ మరియు రిమోట్ రిసోర్స్ స్టోర్‌పై ఆధారపడి ఉంటుంది, కాబట్టి ఖాళీ జాబితా అంటే వనరులు ఇంకా అందుబాటులో లేవని అర్థం. + మీకు రెడీమేడ్ మెష్ గ్రేడియంట్ ఇమేజ్‌లు కావాలనుకున్నప్పుడు గ్రేడియంట్ టూల్స్ నుండి మెష్ గ్రేడియంట్స్ తెరవండి. + సేకరణ ఖాళీగా ఉందని భావించే ముందు వనరు లోడింగ్ లేదా డౌన్‌లోడ్ పురోగతి కోసం వేచి ఉండండి. + మీకు అవసరమైన గ్రేడియంట్‌లను ఎంచుకోండి మరియు భాగస్వామ్యం చేయండి లేదా మీరు కస్టమ్ గ్రేడియంట్‌ను మాన్యువల్‌గా నిర్మించాలనుకున్నప్పుడు గ్రేడియంట్ మేకర్‌కి తిరిగి వెళ్లండి. + ఆస్తి స్పష్టత రూపొందించబడింది + గ్రేడియంట్లు, నాయిస్, వాల్‌పేపర్‌లు, SVG లాంటి ఆర్ట్ లేదా అల్లికలను రూపొందించే ముందు అవుట్‌పుట్ పరిమాణాన్ని ఎంచుకోండి + మీకు అవసరమైన పరిమాణంలో రూపొందించండి + రూపొందించిన చిత్రాలకు తిరిగి రావడానికి అసలు కెమెరా ఉండదు. అవుట్‌పుట్ చాలా తక్కువగా ఉంటే, తర్వాత స్కేలింగ్ నమూనాలను మృదువుగా చేయవచ్చు, వివరాలను మార్చవచ్చు లేదా అసెట్ టైల్స్ మరియు కంప్రెస్ చేసే విధానాన్ని మార్చవచ్చు. + వాల్‌పేపర్, ఐకాన్, బ్యాక్‌గ్రౌండ్, ప్రింట్ లేదా ఓవర్‌లే వంటి ఫైనల్ యూజ్ కేస్ కోసం ముందుగా కొలతలు సెట్ చేయండి. + అనేక లేఅవుట్‌లలో కత్తిరించబడిన, జూమ్ చేయబడిన లేదా తిరిగి ఉపయోగించబడే అల్లికల కోసం పెద్ద పరిమాణాలను ఉపయోగించండి. + భారీ అవుట్‌పుట్‌లకు ముందు పరీక్ష సంస్కరణలను ఎగుమతి చేయండి ఎందుకంటే విధానపరమైన వివరాలు కుదింపు ఎలా ప్రవర్తిస్తుందో మార్చవచ్చు. + ప్రస్తుత వాల్‌పేపర్‌లను ఎగుమతి చేయండి + పరికరం నుండి అందుబాటులో ఉన్న ఇల్లు, లాక్ మరియు అంతర్నిర్మిత వాల్‌పేపర్‌లను తిరిగి పొందండి + ఇన్‌స్టాల్ చేసిన వాల్‌పేపర్‌లను సేవ్ చేయండి లేదా షేర్ చేయండి + సిస్టమ్ వాల్‌పేపర్ APIల ద్వారా Android బహిర్గతం చేసే వాల్‌పేపర్‌లను వాల్‌పేపర్‌ల ఎగుమతి చదువుతుంది. పరికరం, ఆండ్రాయిడ్ వెర్షన్, అనుమతులు మరియు బిల్డ్ వేరియంట్ ఆధారంగా, హోమ్, లాక్ లేదా బిల్ట్-ఇన్ వాల్‌పేపర్‌లు విడిగా అందుబాటులో ఉండవచ్చు లేదా కనిపించకుండా ఉండవచ్చు. + వాల్‌పేపర్‌లను లోడ్ చేయడానికి వాల్‌పేపర్‌ల ఎగుమతిని తెరవండి సిస్టమ్ యాప్‌ని చదవడానికి అనుమతిస్తుంది. + మీరు సేవ్ చేయాలనుకుంటున్న, కాపీ చేయాలనుకుంటున్న లేదా షేర్ చేయాలనుకుంటున్న అందుబాటులో ఉన్న హోమ్, లాక్ లేదా బిల్ట్-ఇన్ వాల్‌పేపర్ ఎంట్రీలను ఎంచుకోండి. + వాల్‌పేపర్ లేకుంటే లేదా ఫీచర్ అందుబాటులో లేకుంటే, ఇది సాధారణంగా ఎడిటింగ్ సెట్టింగ్ కాకుండా సిస్టమ్, అనుమతి లేదా బిల్డ్-వేరియంట్ పరిమితి. + కార్నర్స్ యానిమేషన్ థొరెటల్ + రంగును ఇలా కాపీ చేయండి + HEX + పేరు + మృదువైన జుట్టు మరియు అంచు నిర్వహణతో వేగవంతమైన వ్యక్తి కటౌట్‌ల కోసం పోర్ట్రెయిట్ మ్యాటింగ్ మోడల్. + జీరో-DCE++ కర్వ్ అంచనాను ఉపయోగించి వేగవంతమైన తక్కువ-కాంతి మెరుగుదల. + వర్షపు చారికలు మరియు తడి-వాతావరణ కళాఖండాలను తగ్గించడం కోసం రీస్టోర్మర్ ఇమేజ్ రీస్టోరేషన్ మోడల్. + కోఆర్డినేట్ గ్రిడ్‌ను అంచనా వేసే డాక్యుమెంట్ అన్‌వార్పింగ్ మోడల్ మరియు వంపు తిరిగిన పేజీలను ఫ్లాటర్ స్కాన్‌లో రీమ్యాప్ చేస్తుంది. + మోషన్ వ్యవధి స్కేల్ + యాప్ యానిమేషన్ వేగాన్ని నియంత్రిస్తుంది: 0 చలనాన్ని నిలిపివేస్తుంది, 1 సాధారణమైనది, అధిక విలువలు యానిమేషన్‌లను నెమ్మదిస్తాయి + ఇటీవలి సాధనాలను చూపు + ప్రధాన స్క్రీన్‌లో ఇటీవల ఉపయోగించిన సాధనాలకు త్వరిత ప్రాప్యతను ప్రదర్శించండి + వినియోగ గణాంకాలు + యాప్ ఓపెన్‌లు, టూల్ లాంచ్‌లు మరియు మీరు ఎక్కువగా ఉపయోగించిన సాధనాలను అన్వేషించండి + యాప్ ఓపెన్ అవుతుంది + సాధనం తెరవబడుతుంది + చివరి సాధనం + విజయవంతమైన ఆదా + కార్యాచరణ పరంపర + డేటా సేవ్ చేయబడింది + టాప్ ఫార్మాట్ + ఉపయోగించిన సాధనాలు + %1$d తెరవబడుతుంది • %2$s + ఇంకా వినియోగ గణాంకాలు లేవు + కొన్ని సాధనాలను తెరవండి మరియు అవి స్వయంచాలకంగా ఇక్కడ కనిపిస్తాయి + మీ వినియోగ గణాంకాలు మీ పరికరంలో స్థానికంగా మాత్రమే నిల్వ చేయబడతాయి. ImageToolbox మీ వ్యక్తిగత కార్యాచరణ, ఇష్టమైన సాధనాలు మరియు సేవ్ చేసిన డేటా అంతర్దృష్టులను చూపడానికి మాత్రమే వాటిని ఉపయోగిస్తుంది + ఎడిటర్ ఎడిట్ ఫోటో పిక్చర్ రీటచ్ సర్దుబాటు + పరిమాణాన్ని మార్చండి స్కేల్ రిజల్యూషన్ కొలతలు వెడల్పు ఎత్తు jpg jpeg png webp కంప్రెస్ + కంప్రెస్ సైజు బరువు బైట్‌లు mb kb నాణ్యతను ఆప్టిమైజ్‌ని తగ్గిస్తుంది + ఫిల్టర్ ప్రభావం రంగు దిద్దుబాటు సర్దుబాటు lut ముసుగు + సాంకేతికలిపి ఎన్‌క్రిప్ట్ డీక్రిప్ట్ పాస్‌వర్డ్ రహస్యం + బ్యాక్‌గ్రౌండ్ రిమూవర్ ఎరేస్ కటౌట్ పారదర్శక ఆల్ఫాను తీసివేయండి + url వెబ్ డౌన్‌లోడ్ ఇంటర్నెట్ లింక్ చిత్రం + exif మెటాడేటా గోప్యత స్ట్రిప్ క్లీన్ gps స్థానాన్ని తీసివేస్తుంది + తర్వాత ముందు తేడా తేడాను సరిపోల్చండి + పరిమితి పరిమాణాన్ని గరిష్టంగా నిమి మెగాపిక్సెల్స్ కొలతలు బిగింపు + గ్రేడియంట్ నేపథ్య రంగు మెష్ + వాటర్‌మార్క్ లోగో టెక్స్ట్ స్టాంప్ ఓవర్‌లే + gif యానిమేషన్ యానిమేటెడ్ కన్వర్ట్ ఫ్రేమ్‌లు + జిప్ ఆర్కైవ్ కంప్రెస్ ప్యాక్ ఫైళ్లను అన్‌ప్యాక్ చేయండి + jxl jpeg xl jpg ఇమేజ్ ఫార్మాట్‌ని మారుస్తుంది + ఫార్మాట్ మార్చడానికి jpg jpeg png webp heic avif jxl bmp + qr బార్‌కోడ్ కోడ్ స్కానర్ స్కాన్ + స్ప్లిట్ టైల్స్ గ్రిడ్ స్లైస్ భాగాలు + webp యానిమేటెడ్ చిత్ర ఆకృతిని మారుస్తుంది + శబ్దం జనరేటర్ యాదృచ్ఛిక ఆకృతి ధాన్యం + బేస్64 ఎన్కోడ్ డేటా యూరి + చెక్సమ్ హాష్ md5 sha sha1 sha256 ధృవీకరించండి + మెష్ గ్రేడియంట్ నేపథ్యం + exif మెటాడేటా gps తేదీ కెమెరాను సవరించండి + కట్ స్ప్లిట్ గ్రిడ్ ముక్కలు పలకలు + ఆడియో కవర్ ఆల్బమ్ ఆర్ట్ మ్యూజిక్ మెటాడేటా + వాల్‌పేపర్ ఎగుమతి నేపథ్యం + ascii టెక్స్ట్ ఆర్ట్ అక్షరాలు + షేడర్ glsl ఫ్రాగ్మెంట్ ఎఫెక్ట్ స్టూడియో + పిడిఎఫ్ విలీనం కలపండి + pdf ప్రత్యేక పేజీలను విభజించింది + pdf వాటర్‌మార్క్ స్టాంప్ లోగో టెక్స్ట్ + pdf సంతకం సైన్ డ్రా + pdf అన్‌లాక్ పాస్‌వర్డ్ డీక్రిప్ట్ రిమూవ్ ప్రొటెక్షన్ + pdf గ్రేస్కేల్ బ్లాక్ వైట్ మోనోక్రోమ్ + pdf చిత్రాలను సేకరించండి + pdf జిప్ ఆర్కైవ్ కంప్రెస్ + pdf ప్రింట్ ప్రింటర్ + pdf ప్రివ్యూ వ్యూయర్ చదివారు + చిత్రాలు pdfకు jpg jpeg png పత్రాన్ని మారుస్తాయి + pdf ఉల్లేఖనాల వ్యాఖ్యలు శుభ్రంగా తీసివేయబడతాయి + తటస్థ వేరియంట్ + లోపం + డ్రాప్ షాడో + పంటి ఎత్తు + క్షితిజసమాంతర టూత్ రేంజ్ + నిలువు టూత్ రేంజ్ + టాప్ ఎడ్జ్ + కుడి అంచు + దిగువ అంచు + ఎడమ అంచు + చిరిగిన అంచు + పంట ట్రిమ్ కట్ కారక నిష్పత్తి + పెయింట్ బ్రష్ పెన్సిల్ ఉల్లేఖన మార్కప్‌ను గీయండి + ప్రివ్యూ వ్యూయర్ గ్యాలరీ తనిఖీ తెరవబడింది + స్టిచ్ మెర్జ్ పనోరమా లాంగ్ స్క్రీన్‌షాట్‌ను కలపండి + పికర్ ఐడ్రాపర్ నమూనా హెక్స్ rgb రంగు + పాలెట్ రంగులు swatches సారం ఆధిపత్యం + pdf డాక్యుమెంట్ పేజీల సాధనాలు + ocr టెక్స్ట్ గుర్తింపు సారం స్కాన్ + apng యానిమేషన్ యానిమేటెడ్ png ఫ్రేమ్‌లను మారుస్తుంది + svg వెక్టర్ ట్రేస్ కన్వర్ట్ xml + డాక్యుమెంట్ స్కానర్ స్కాన్ పేపర్ పెర్స్పెక్టివ్ క్రాప్ + స్టాకింగ్ ఓవర్లే సగటు మధ్యస్థ ఆస్ట్రోఫోటోగ్రఫీ + రంగు మార్చడానికి hex rgb hsl hsv cmyk ల్యాబ్ + కోల్లెజ్ గ్రిడ్ లేఅవుట్ కలపండి + మార్కప్ లేయర్‌లు ఓవర్‌లే సవరణను ఉల్లేఖిస్తాయి + ai ml నాడీ ఉన్నత స్థాయి విభాగాన్ని మెరుగుపరుస్తుంది + రంగు లైబ్రరీ మెటీరియల్ పాలెట్ రంగులు అనే పేరు + pdf రొటేట్ పేజీల విన్యాసాన్ని + pdf పేజీల క్రమాన్ని క్రమాన్ని మార్చండి + pdf పేజీ సంఖ్యల పేజీ + pdf ocr టెక్స్ట్ శోధించదగిన సారాన్ని గుర్తించింది + pdf పాస్‌వర్డ్ ఎన్‌క్రిప్ట్ లాక్‌ని రక్షించండి + pdf కంప్రెస్ తగ్గించు పరిమాణాన్ని ఆప్టిమైజ్ చేయండి + pdf మరమ్మత్తు పరిష్కారము విరిగిన రికవరీ + pdf మెటాడేటా లక్షణాలు exif రచయిత శీర్షిక + pdf తొలగించు పేజీలను తొలగించండి + pdf క్రాప్ ట్రిమ్ మార్జిన్లు + pdf చదును ఉల్లేఖనాలు పొరలను ఏర్పరుస్తాయి + pdf to images pages export jpg png + వినియోగ గణాంకాలను రీసెట్ చేయండి + సాధనం తెరుచుకుంటుంది, గణాంకాలను సేవ్ చేయండి, స్ట్రీక్, సేవ్ చేసిన డేటా మరియు టాప్ ఫార్మాట్ రీసెట్ చేయబడుతుంది. యాప్ ఓపెన్‌లు మారవు. + ఆడియో + ఫైల్ + ప్రొఫైల్‌లను ఎగుమతి చేయండి + ప్రస్తుత ప్రొఫైల్‌ను సేవ్ చేయండి + ఎగుమతి ప్రొఫైల్‌ను తొలగించండి + మీరు \\"%1$s\\" ఎగుమతి ప్రొఫైల్‌ని తొలగించాలనుకుంటున్నారా? + డ్రాయింగ్ సరిహద్దు + డ్రాయింగ్ మరియు ఎరేసింగ్ కాన్వాస్ చుట్టూ సన్నని అంచుని చూపండి + ఉంగరాల + మృదువైన వేవీ అంచుతో గుండ్రని UI మూలకాలు + స్కూప్ + గీత + గుండ్రని మూలలు లోపలికి చెక్కబడ్డాయి + డబుల్ కట్‌తో స్టెప్డ్ మూలలు + ప్లేస్‌హోల్డర్ + పొడిగింపు లేకుండా అసలు ఫైల్ పేరుని చొప్పించడానికి {filename}ని ఉపయోగించండి + మంట + బేస్ మొత్తం + రింగ్ మొత్తం + రే మొత్తం + రింగ్ వెడల్పు + దృక్కోణాన్ని వక్రీకరించండి + జావా లుక్ అండ్ ఫీల్ + కోత + X కోణం + మరియు కోణం + అవుట్‌పుట్ పరిమాణాన్ని మార్చండి + నీటి చుక్క + తరంగదైర్ఘ్యం + దశ + హై పాస్ + రంగు ముసుగు + Chrome + కరిగించండి + మృదుత్వం + అభిప్రాయం + లెన్స్ బ్లర్ + తక్కువ ఇన్‌పుట్ + అధిక ఇన్పుట్ + తక్కువ అవుట్‌పుట్ + అధిక అవుట్‌పుట్ + కాంతి ప్రభావాలు + బంప్ ఎత్తు + బంప్ మృదుత్వం + అలలు + X వ్యాప్తి + Y వ్యాప్తి + X తరంగదైర్ఘ్యం + Y తరంగదైర్ఘ్యం + అడాప్టివ్ బ్లర్ + ఆకృతి జనరేషన్ + వివిధ విధానపరమైన అల్లికలను రూపొందించండి మరియు వాటిని చిత్రాలుగా సేవ్ చేయండి + ఆకృతి రకం + పక్షపాతం + సమయం + ప్రకాశించు + చెదరగొట్టడం + నమూనాలు + కోణ గుణకం + గ్రేడియంట్ కోఎఫీషియంట్ + గ్రిడ్ రకం + దూర శక్తి + స్కేల్ Y + అస్పష్టత + ఆధార రకం + టర్బులెన్స్ ఫ్యాక్టర్ + స్కేలింగ్ + పునరావృత్తులు + రింగ్స్ + బ్రష్డ్ మెటల్ + కాస్టిక్స్ + సెల్యులార్ + చెక్కర్‌బోర్డ్ + fBm + మార్బుల్ + ప్లాస్మా + మెత్తని బొంత + చెక్క + యాదృచ్ఛికంగా + చతురస్రం + షట్కోణాకారం + అష్టభుజి + త్రిభుజాకారం + రిడ్జ్డ్ + VL శబ్దం + SC శబ్దం + ఇటీవలి + ఇంకా ఇటీవల ఉపయోగించిన ఫిల్టర్‌లు లేవు + ఆకృతి జనరేటర్ బ్రష్డ్ మెటల్ కాస్టిక్స్ సెల్యులార్ చెకర్‌బోర్డ్ మార్బుల్ ప్లాస్మా మెత్తని బొంత చెక్క ఇటుక మభ్యపెట్టే సెల్ క్లౌడ్ క్రాక్ ఫాబ్రిక్ ఆకులు తేనెగూడు మంచు లావా నెబ్యులా కాగితం తుప్పు ఇసుక పొగ రాతి భూభాగం స్థలాకృతి నీటి అలల + ఇటుక + మభ్యపెట్టడం + సెల్ + మేఘం + క్రాక్ + ఫాబ్రిక్ + ఆకులు + తేనెగూడు + మంచు + లావా + నిహారిక + పేపర్ + రస్ట్ + ఇసుక + పొగ + రాయి + భూభాగం + స్థలాకృతి + నీటి అల + అధునాతన చెక్క + మోర్టార్ వెడల్పు + అక్రమం + బెవెల్ + కరుకుదనం + మొదటి థ్రెషోల్డ్ + రెండవ త్రెషోల్డ్ + మూడవ త్రెషోల్డ్ + అంచు మృదుత్వం + అంచు వెడల్పు + కవరేజ్ + వివరాలు + లోతు + బ్రాంచింగ్ + క్షితిజ సమాంతర దారాలు + నిలువు థ్రెడ్లు + ఫజ్ + సిరలు + లైటింగ్ + క్రాక్ వెడల్పు + ఫ్రాస్ట్ + ప్రవాహం + క్రస్ట్ + మేఘ సాంద్రత + నక్షత్రాలు + ఫైబర్ సాంద్రత + ఫైబర్ బలం + మరకలు + తుప్పు పట్టడం + పిట్టింగ్ + రేకులు + డూన్ ఫ్రీక్వెన్సీ + గాలి కోణం + అలలు + విస్ప్స్ + సిర స్థాయి + నీటి స్థాయి + పర్వత స్థాయి + ఎరోషన్ + మంచు స్థాయి + లైన్ కౌంట్ + లైన్ మందం + షేడింగ్ + రంధ్రాల రంగు + మోర్టార్ రంగు + ముదురు ఇటుక రంగు + ఇటుక రంగు + హైలైట్ రంగు + ముదురు రంగు + అటవీ రంగు + భూమి రంగు + ఇసుక రంగు + నేపథ్య రంగు + సెల్ రంగు + అంచు రంగు + ఆకాశ రంగు + నీడ రంగు + లేత రంగు + ఉపరితల రంగు + వైవిధ్యం రంగు + పగుళ్లు రంగు + వార్ప్ రంగు + వెఫ్ట్ రంగు + ముదురు ఆకు రంగు + ఆకు రంగు + అంచు రంగు + తేనె రంగు + లోతైన రంగు + మంచు రంగు + ఫ్రాస్ట్ రంగు + క్రస్ట్ రంగు + వాష్ రంగు + గ్లో కలర్ + స్పేస్ రంగు + వైలెట్ రంగు + నీలం రంగు + మూల రంగు + ఫైబర్ రంగు + మరక రంగు + మెటల్ రంగు + ముదురు తుప్పు రంగు + రస్ట్ రంగు + నారింజ రంగు + పొగ రంగు + సిర రంగు + లోతట్టు రంగు + నీటి రంగు + రాక్ రంగు + మంచు రంగు + తక్కువ రంగు + అధిక రంగు + పంక్తి రంగు + నిస్సార రంగు + గడ్డి + మురికి + తోలు + కాంక్రీటు + తారు + నాచు + అగ్ని + అరోరా + ఆయిల్ స్లిక్ + వాటర్ కలర్ + ఒపాల్ + డమాస్కస్ స్టీల్ + మెరుపు + వెల్వెట్ + హోలోగ్రాఫిక్ రేకు + బయోలుమినిసెన్స్ + కాస్మిక్ వోర్టెక్స్ + లావా దీపం + ఈవెంట్ హారిజన్ + వింత ఆకర్షణ + సూపర్నోవా + ఐరిస్ + నెమలి ఈక + నాటిలస్ షెల్ + బ్లేడ్ పొడవు + గాలి + గుబ్బలు + తేమ + గులకరాళ్లు + ముడతలు + రంద్రాలు + మొత్తం + పగుళ్లు + తారు + ధరించండి + మచ్చలు + ఫైబర్స్ + పొగ + తీవ్రత + రిబ్బన్లు + బ్యాండ్లు + ఇరిడెసెన్స్ + చీకటి + బ్లూమ్స్ + వర్ణద్రవ్యం + అంచులు + పేపర్ + వ్యాప్తి + సమరూపత + పదును + కలర్ ప్లే + పొరలు + మడత + పోలిష్ + శాఖలు + దిశ + శీను + మడతలు + ఈకలు వేయడం + స్పెక్ట్రమ్ + ముడతలు + వివర్తనము + ఆయుధాలు + ట్విస్ట్ + బొట్టు + లెన్సింగ్ + రేకులు + కర్ల్ + ఫిలిగ్రీ + ముఖభాగాలు + వక్రత + కిరణాలు + డైమండ్ రింగ్ + లోబ్స్ + మందం + వచ్చే చిక్కులు + శరీర పరిమాణం + మెటాలిక్ + విద్యార్థి పరిమాణం + రంగు వైవిధ్యం + కంటి పరిమాణం + మలుపులు + చాంబర్లు + తెరవడం + గట్లు + గ్రహ పరిమాణం + రింగ్ వెడల్పు + వాతావరణం + గడ్డి రంగు + చిట్కా రంగు + ముదురు భూమి రంగు + నేల రంగు + ఎరుపు రంగు + ఆకుపచ్చ రంగు + ద్వితీయ రంగు + పింక్ కలర్ + వెండి రంగు + పసుపు రంగు + జ్వాల రంగు + ఈక రంగు + షెల్ రంగు + వియుక్త ప్రవాహం + ఇంక్ మార్బ్లింగ్ + ఫ్రాక్టల్ బ్లూమ్ + క్రోమాటిక్ టన్నెల్ + గ్రహణం కరోనా + ఫెర్రోఫ్లూయిడ్ క్రౌన్ + రింగ్డ్ ప్లానెట్ + బ్లేడ్ సాంద్రత + అతుకులు + జ్వాల ఫ్రీక్వెన్సీ + మిల్కీనెస్ + ఇంక్ బ్యాలెన్స్ + కోర్ గ్లో + డిస్క్ టిల్ట్ + హోరిజోన్ పరిమాణం + డిస్క్ వెడల్పు + చంద్రుని పరిమాణం + కరోనా పరిమాణం + కక్ష్య సాంద్రత + స్పైక్ పొడవు + షాక్ వ్యాసార్థం + షెల్ వెడల్పు + బయట పడేశారు + కనుపాప పరిమాణం + క్యాచ్‌లైట్ + బార్బ్ సాంద్రత + ముత్యాలముత్యము + రింగ్ టిల్ట్ + మురికి రంగు + ముదురు గడ్డి రంగు + పొడి రంగు + గులకరాయి రంగు + లెదర్ రంగు + కాంక్రీటు రంగు + తారు రంగు + తారు రంగు + రాతి రంగు + దుమ్ము రంగు + ముదురు నాచు రంగు + నాచు రంగు + కోర్ రంగు + సియాన్ రంగు + మెజెంటా రంగు + బంగారు రంగు + కాగితం రంగు + వర్ణద్రవ్యం రంగు + మొదటి రంగు + రెండవ రంగు + ముదురు ఉక్కు రంగు + ఉక్కు రంగు + లేత ఉక్కు రంగు + ఆక్సైడ్ రంగు + హాలో రంగు + బోల్ట్ రంగు + వెల్వెట్ రంగు + షీన్ రంగు + నీలం సిరా రంగు + ఎరుపు సిరా రంగు + ముదురు సిరా రంగు + కణజాల రంగు + డిస్క్ రంగు + వేడి రంగు + లెన్స్ రంగు + బాహ్య రంగు + లోపలి రంగు + కరోనా రంగు + చల్లని రంగు + వెచ్చని రంగు + మేఘం రంగు + పెర్ల్ రంగు + గ్రహం రంగు + రింగ్ రంగు + సేవ్ చేసిన తర్వాత అసలు ఫైల్‌లను తొలగించండి + విజయవంతంగా ప్రాసెస్ చేయబడిన సోర్స్ ఫైల్‌లు మాత్రమే తొలగించబడతాయి + అసలు ఫైల్‌లు తొలగించబడ్డాయి: %1$d + అసలు ఫైల్‌లను తొలగించడంలో విఫలమైంది: %1$d + అసలు ఫైల్‌లు తొలగించబడ్డాయి: %1$d, విఫలమయ్యాయి: %2$d + షీట్ సంజ్ఞలు + విస్తరించిన ఎంపిక షీట్‌లను లాగడాన్ని అనుమతించండి + క్రోమా ఉప నమూనా + బిట్ లోతు + నష్టం లేని + %1$d-బిట్ + బ్యాచ్ పేరు మార్చండి + ఫైల్ పేరు నమూనాలను ఉపయోగించి బహుళ ఫైల్‌ల పేరు మార్చండి + బ్యాచ్ ఫైల్ పేరు మార్చు ఫైల్ పేరు నమూనా క్రమం తేదీ పొడిగింపు + పేరు మార్చడానికి ఫైల్‌లను ఎంచుకోండి + ఫైల్ పేరు నమూనా + పేరు మార్చండి + తేదీ మూలం + EXIF తేదీ తీసుకోబడింది + ఫైల్ సవరించిన తేదీ + ఫైల్ సృష్టించిన తేదీ + ప్రస్తుత తేదీ + మాన్యువల్ తేదీ + మాన్యువల్ తేదీ + మాన్యువల్ సమయం + ఎంచుకున్న తేదీ కొన్ని ఫైల్‌లకు అందుబాటులో లేదు. ప్రస్తుత తేదీ వారి కోసం ఉపయోగించబడుతుంది. + ఫైల్ పేరు నమూనాను నమోదు చేయండి + నమూనా చెల్లని లేదా చాలా పొడవైన ఫైల్ పేరును ఉత్పత్తి చేస్తుంది + నమూనా నకిలీ ఫైల్ పేర్లను ఉత్పత్తి చేస్తుంది + అన్ని ఫైల్ పేర్లు ఇప్పటికే నమూనాతో సరిపోలాయి + ఈ నమూనా బ్యాచ్ పేరు మార్చడానికి అందుబాటులో లేని టోకెన్‌లను ఉపయోగిస్తుంది + పేరు అలాగే ఉంటుంది + ఫైల్‌లు విజయవంతంగా పేరు మార్చబడ్డాయి + కొన్ని ఫైల్‌ల పేరు మార్చడం సాధ్యపడలేదు + అనుమతి లభించలేదు + పొడిగింపు లేకుండా అసలు ఫైల్ పేరును ఉపయోగిస్తుంది, సోర్స్ గుర్తింపును అలాగే ఉంచడంలో మీకు సహాయపడుతుంది. \\o{start:end}తో స్లైసింగ్, \\o{t}తో లిప్యంతరీకరణ మరియు \\o{s/old/new/}తో భర్తీ చేయడానికి కూడా మద్దతు ఇస్తుంది. + ఆటో-పెరుగుదల కౌంటర్. అనుకూల ఫార్మాటింగ్‌కు మద్దతు ఇస్తుంది: \\c{padding} (ఉదా. \\c{3} -&gt; 001), \\c{start:step} లేదా \\c{start:step:padding}. + అసలు ఫైల్ ఉన్న పేరెంట్ ఫోల్డర్ పేరు. + అసలు ఫైల్ పరిమాణం. యూనిట్‌లకు మద్దతు ఇస్తుంది: \\z{b}, \\z{kb}, \\z{mb} లేదా కేవలం \\z మానవ రీడబుల్ ఫార్మాట్ కోసం. + ఫైల్ పేరు ప్రత్యేకతను నిర్ధారించడానికి యాదృచ్ఛికంగా యూనివర్సల్లీ యూనిక్ ఐడెంటిఫైయర్ (UUID)ని రూపొందిస్తుంది. + ఫైల్‌ల పేరు మార్చడం రద్దు చేయబడదు. కొనసాగించడానికి ముందు కొత్త పేర్లు సరైనవని నిర్ధారించుకోండి. + పేరు మార్చడాన్ని నిర్ధారించండి + సైడ్ మెను సెట్టింగ్‌లు + మెనూ స్కేల్ + మెనూ ఆల్ఫా + ఆటో వైట్ బ్యాలెన్స్ + క్లిప్పింగ్ + దాచిన వాటర్‌మార్క్‌ల కోసం తనిఖీ చేస్తోంది + నిల్వ + కాష్ పరిమితి + కాష్ ఈ పరిమాణానికి మించి పెరిగినప్పుడు స్వయంచాలకంగా క్లియర్ చేయండి + శుభ్రపరిచే విరామం + కాష్ క్లియర్ చేయబడాలో లేదో ఎంత తరచుగా తనిఖీ చేయాలి + యాప్ లాంచ్‌లో + 1 రోజు + 1 వారం + 1 నెల + ఎంచుకున్న పరిమితి మరియు విరామం ప్రకారం యాప్ కాష్‌ని స్వయంచాలకంగా క్లియర్ చేయండి + కదిలే పంట ప్రాంతం + దాని లోపలికి లాగడం ద్వారా మొత్తం పంట ప్రాంతాన్ని తరలించడానికి అనుమతిస్తుంది + GIFని విలీనం చేయండి + బహుళ GIF క్లిప్‌లను ఒక యానిమేషన్‌లో కలపండి + GIF క్లిప్‌ల ఆర్డర్ + రివర్స్ + రివర్స్‌గా ఆడండి + బూమరాంగ్ + ముందుకు ఆపై వెనుకకు ఆడండి + ఫ్రేమ్ పరిమాణాన్ని సాధారణీకరించండి + అతిపెద్ద క్లిప్‌కు సరిపోయేలా ఫ్రేమ్‌లను స్కేల్ చేయండి; వాటి అసలు స్థాయిని కాపాడుకోవడానికి ఆఫ్ చేయండి + క్లిప్‌ల మధ్య ఆలస్యం (మిసె) + ఫోల్డర్ + ఫోల్డర్ మరియు దాని సబ్ ఫోల్డర్‌ల నుండి అన్ని చిత్రాలను ఎంచుకోండి + డూప్లికేట్ ఫైండర్ + ఖచ్చితమైన కాపీలు మరియు దృశ్యమానంగా సారూప్య చిత్రాలను కనుగొనండి + నకిలీ ఫైండర్ సారూప్య చిత్రాలు ఖచ్చితమైన కాపీలు ఫోటోలు శుభ్రపరిచే నిల్వ dHash SHA-256 + నకిలీలను కనుగొనడానికి చిత్రాలను ఎంచుకోండి + సారూప్యత సున్నితత్వం + ఖచ్చితమైన కాపీలు + ఇలాంటి చిత్రాలు + అన్ని ఖచ్చితమైన నకిలీలను ఎంచుకోండి + సిఫార్సు మినహా అన్నింటినీ ఎంచుకోండి + నకిలీలు ఏవీ కనుగొనబడలేదు + మరిన్ని చిత్రాలను జోడించడానికి లేదా సారూప్యత సున్నితత్వాన్ని పెంచడానికి ప్రయత్నించండి + %1$d చిత్రం(లు) చదవడం సాధ్యపడలేదు + %1$s • %2$s తిరిగి పొందదగినది + %1$d సమూహాలు • %2$s తిరిగి పొందదగినవి + %1$d ఎంచుకోబడింది • %2$s + ఇది రద్దు చేయబడదు. సిస్టమ్ డైలాగ్‌లో తొలగింపును నిర్ధారించమని Android మిమ్మల్ని అడగవచ్చు. + దొరకలేదు + ప్రారంభించడానికి తరలించండి + ముగింపుకు తరలించండి + \ No newline at end of file diff --git a/core/resources/src/main/res/values-th/strings.xml b/core/resources/src/main/res/values-th/strings.xml new file mode 100644 index 0000000..71d7203 --- /dev/null +++ b/core/resources/src/main/res/values-th/strings.xml @@ -0,0 +1,3738 @@ + + + + การปิดแอป + อยู่ + ปิด + รีเซ็ตภาพ + เพิ่มแท็ก + โฟลเดอร์เอาต์พุต + ค่าเริ่มต้น + กำหนดเอง + การตั้งค่า + ไม่มีอะไรจะวาง + หลัก + ตติยภูมิ + สีโมเน่ต์ + แอปพลิเคชันนี้ไม่มีค่าใช้จ่ายใด ๆ แต่ถ้าคุณต้องการสนับสนุนการพัฒนาโครงการ คุณสามารถคลิกที่นี่ + การจัดตำแหน่ง 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 + ไฮเปอร์ลิงก์ + ไฟล์แนบ + เส้น + ป๊อปอัป + แสตมป์ + รูปร่าง + หมายเหตุข้อความ + มาร์กอัปข้อความ + ฟิลด์แบบฟอร์ม + มาร์กอัป + ไม่ทราบ + คำอธิบายประกอบ + ยกเลิกการจัดกลุ่ม + เพิ่มเงาเบลอด้านหลังเลเยอร์ด้วยสีและออฟเซ็ตที่กำหนดค่าได้ + ความผิดเพี้ยนของการรับรู้เนื้อหา + หน่วยความจำไม่เพียงพอที่จะดำเนินการนี้ให้เสร็จสิ้น โปรดลองใช้รูปภาพที่เล็กลง ปิดแอปอื่นๆ หรือรีสตาร์ทแอป + คนงานคู่ขนาน + รูปภาพเหล่านี้ไม่ได้รับการบันทึกเนื่องจากไฟล์ที่แปลงแล้วจะมีขนาดใหญ่กว่าต้นฉบับ ตรวจสอบรายการนี้ก่อนที่จะลบภาพต้นฉบับ + ไฟล์ถูกข้าม: %1$s + บันทึกแอป + ดูบันทึกของแอปเพื่อระบุปัญหา + รายการโปรดในเครื่องมือที่จัดกลุ่ม + เพิ่มรายการโปรดเป็นแท็บเมื่อมีการจัดกลุ่มเครื่องมือตามประเภท + แสดงรายการโปรดเป็นครั้งสุดท้าย + ย้ายแท็บเครื่องมือโปรดไปจนสุด + ระยะห่างแนวนอน + ระยะห่างแนวตั้ง + พลังงานถอยหลัง + ใช้แผนที่พลังงานขนาดไล่ระดับสีอย่างง่ายแทนอัลกอริธึมพลังงานไปข้างหน้า + ลบพื้นที่ที่ปิดบัง + ตะเข็บจะทะลุผ่านบริเวณที่มาสก์ โดยเอาเฉพาะบริเวณที่เลือกออกแทนที่จะปกป้อง + วีเอชเอส เอ็นทีเอสซี + การตั้งค่า NTSC ขั้นสูง + การปรับแต่งแบบอะนาล็อกเพิ่มเติมเพื่อ VHS ที่แข็งแกร่งขึ้นและสิ่งประดิษฐ์ในการออกอากาศ + โครมาเลือดออก + การสึกหรอของเทป + การติดตาม + ลูม่าสเมียร์ + เสียงเรียกเข้า + หิมะ + ใช้สนาม + ประเภทตัวกรอง + อินพุตตัวกรองลูม่า + อินพุตโครมาโลว์พาส + โครมาดีโมดูเลชั่น + การเปลี่ยนเฟส + การชดเชยเฟส + การสลับหัว + ความสูงของการสลับหัว + การชดเชยการสลับหัว + การเปลี่ยนการเปลี่ยนหัว + ตำแหน่งกึ่งกลาง + กระวนกระวายใจกลางสาย + ติดตามความสูงของเสียงรบกวน + ติดตามคลื่น + ติดตามหิมะ + การติดตามแอนไอโซโทรปีของหิมะ + ติดตามเสียงรบกวน + ติดตามความเข้มของเสียงรบกวน + สัญญาณรบกวนคอมโพสิต + ความถี่เสียงคอมโพสิต + ความเข้มของเสียงคอมโพสิต + รายละเอียดสัญญาณรบกวนคอมโพสิต + ความถี่เสียงเรียกเข้า + พลังเสียงเรียกเข้า + เสียงลูม่า + ความถี่เสียงลูม่า + ความเข้มของเสียงลูมา + รายละเอียดเสียงรบกวนของ Luma + เสียงโครมา + ความถี่สัญญาณรบกวนโครมา + ความเข้มของสัญญาณรบกวนของโครมา + รายละเอียดสัญญาณรบกวนของโครมา + ความรุนแรงของหิมะ + แอนไอโซโทรปีของหิมะ + สัญญาณรบกวนเฟสโครมา + ข้อผิดพลาดเฟสโครมา + Chroma ล่าช้าในแนวนอน + โครมาดีเลย์แนวตั้ง + ความเร็วเทป VHS + การสูญเสียโครมา VHS + VHS เพิ่มความคมชัด + ความถี่ความคมชัดของ VHS + ความเข้มของคลื่นขอบ VHS + ความเร็วคลื่นขอบ VHS + ความถี่คลื่นขอบ VHS + รายละเอียดคลื่นขอบ VHS + เอาท์พุตโครมาโลว์พาส + สเกลแนวนอน + มาตราส่วนแนวตั้ง + สเกลแฟคเตอร์ X + สเกลแฟคเตอร์ Y + ตรวจจับลายน้ำทั่วไปของภาพและระบายสีด้วย LaMa ดาวน์โหลดโมเดลการตรวจจับและลงสีโดยอัตโนมัติ + ดิวเทอเรโนเปีย + ขยายรูปภาพ + เครื่องมือลบพื้นหลังตามการแบ่งส่วนวัตถุโดยใช้ YOLO v11 Segmentation + แสดงมุมของเส้น + แสดงการหมุนเส้นปัจจุบันเป็นองศาขณะวาด + อิโมจิแบบเคลื่อนไหว + แสดงอิโมจิที่มีอยู่เป็นภาพเคลื่อนไหว + บันทึกลงในโฟลเดอร์ต้นฉบับ + บันทึกไฟล์ใหม่ถัดจากไฟล์ต้นฉบับแทนโฟลเดอร์ที่เลือก + ลายน้ำ PDF + หน่วยความจำไม่เพียงพอ + รูปภาพอาจมีขนาดใหญ่เกินกว่าจะประมวลผลบนอุปกรณ์นี้ได้ หรือระบบมีหน่วยความจำเหลือไม่เพียงพอ ลองลดความละเอียดของภาพ ปิดแอปอื่นๆ หรือเลือกไฟล์ที่เล็กลง + เมนูแก้ไขข้อบกพร่อง + เมนูสำหรับทดสอบฟังก์ชันของแอป ไม่ได้ตั้งใจให้แสดงในเวอร์ชันที่ใช้งานจริง + ผู้ให้บริการ + PaddleOCR ต้องใช้รุ่น ONNX เพิ่มเติมบนอุปกรณ์ของคุณ คุณต้องการดาวน์โหลดข้อมูล %1$s หรือไม่ + สากล + เกาหลี + ละติน + สลาวิกตะวันออก + แบบไทย + กรีก + ภาษาอังกฤษ + ซีริลลิก + ภาษาอาหรับ + เทวนาครี + ทมิฬ + เตลูกู + เฉดเดอร์ + ตั้งค่าล่วงหน้าของ Shader + ไม่ได้เลือกเชเดอร์ + เชเดอร์ สตูดิโอ + สร้าง แก้ไข ตรวจสอบ นำเข้า และส่งออกเชเดอร์ส่วนที่กำหนดเอง + เชเดอร์ที่บันทึกไว้ + เปิดเชเดอร์ที่บันทึกไว้ ทำซ้ำ ส่งออก แชร์ หรือลบ + เชเดอร์ใหม่ + ไฟล์เชเดอร์ + .itshader JSON + %1$d พารามิเตอร์ + แหล่งที่มาของเชเดอร์ + เขียนเฉพาะส่วนเนื้อหาของ void main() ใช้ textureCoordinate สำหรับพิกัด UV และ inputImageTexture เป็นตัวเก็บตัวอย่างแหล่งที่มา + ฟังก์ชั่น + ฟังก์ชันตัวช่วยเสริม ค่าคงที่ และโครงสร้างที่แทรกไว้หน้า void main() เครื่องแบบถูกสร้างขึ้นจากพารามิเตอร์ + เพิ่มเครื่องแบบที่นี่เมื่อเชเดอร์ต้องการค่าที่แก้ไขได้ + รีเซ็ตเชเดอร์ + ร่างเชเดอร์ปัจจุบันจะถูกล้าง + เชเดอร์ไม่ถูกต้อง + ไม่รองรับเชเดอร์เวอร์ชัน %1$d เวอร์ชันที่รองรับ: %2$d + ชื่อเชเดอร์ต้องไม่เว้นว่าง + แหล่งที่มาของเชเดอร์ต้องไม่เว้นว่าง + แหล่งที่มาของ Shader มีอักขระที่ไม่รองรับ: %1$s ใช้แหล่งที่มา ASCII GLSL เท่านั้น + พารามิเตอร์ \\\"%1$s\\\" ถูกประกาศมากกว่าหนึ่งครั้ง + ชื่อพารามิเตอร์ต้องไม่เว้นว่าง + พารามิเตอร์ \\\"%1$s\\\" ใช้ชื่อ GPUImage ที่สงวนไว้ + พารามิเตอร์ \\\"%1$s\\\" ต้องเป็นตัวระบุ GLSL ที่ถูกต้อง + พารามิเตอร์ \\\"%1$s\\\" มีประเภทค่า %2$s \\\"%3$s\\\" ซึ่งคาดว่าจะเป็น \\\"%4$s\\\" + พารามิเตอร์ \\\"%1$s\\\" ไม่สามารถกำหนดค่าต่ำสุดหรือสูงสุดสำหรับค่าบูลได้ + พารามิเตอร์ \\\"%1$s\\\" มี min มากกว่าค่าสูงสุด + พารามิเตอร์ \\\"%1$s\\\" ค่าเริ่มต้นต่ำกว่าค่าต่ำสุด + พารามิเตอร์ \\\"%1$s\\\" ค่าเริ่มต้นมีค่ามากกว่าค่าสูงสุด + Shader ต้องประกาศ \\\"uniform Sampler2D %1$s;\\\" + Shader ต้องประกาศ \\\"varying vec2 %1$s;\\\" + Shader ต้องกำหนด \\\"void main()\\\" + Shader ต้องเขียนสีลงใน gl_FragColor + ไม่รองรับ ShaderToy mainImage shader ใช้สัญญาหลัก () ที่เป็นโมฆะของ GPUImage + ไม่รองรับชุดเคลื่อนไหว ShaderToy \\\"iTime\\\" + ไม่รองรับชุดเคลื่อนไหวของ ShaderToy \\\"iFrame\\\" + ไม่รองรับเครื่องแบบ \\\"iResolution\\\" ของ ShaderToy + ไม่รองรับพื้นผิว ShaderToy iChannel + ไม่รองรับพื้นผิวภายนอก + ไม่รองรับส่วนขยายพื้นผิวภายนอก + ไม่รองรับพารามิเตอร์เชเดอร์ Libretro + รองรับพื้นผิวอินพุตเดียวเท่านั้น ลบ \\\"เครื่องแบบ %1$s %2$s;\\\" ออก + พารามิเตอร์ \\\"%1$s\\\" จะต้องประกาศเป็น \\\"uniform %2$s %1$s;\\\" + พารามิเตอร์ \\\"%1$s\\\" ได้รับการประกาศเป็น \\\"uniform %2$s %1$s;\\\" ซึ่งคาดว่า \\\"uniform %3$s %1$s;\\\" + ไฟล์ Shader ว่างเปล่า + ไฟล์เชเดอร์ต้องเป็นออบเจ็กต์ .itshader JSON พร้อมช่องเวอร์ชัน ชื่อ และเชเดอร์ + ไฟล์ Shader ไม่ใช่ JSON ที่ถูกต้อง หรือไม่ตรงกับรูปแบบ .itshader + ต้องระบุฟิลด์ \\\"%1$s\\\" + %1$s จำเป็น + %1$s \\\"%2$s\\\" ไม่ได้รับการสนับสนุน ประเภทที่รองรับ: %3$s + %1$s จำเป็นสำหรับพารามิเตอร์ \\\"%2$s\\\" + %1$s ต้องเป็นจำนวนจำกัด + %1$s ต้องเป็นจำนวนเต็ม + %1$s จะต้องเป็นจริงหรือเท็จ + %1$s ต้องเป็นสตริงสี อาร์เรย์ RGB/RGBA หรือวัตถุสี + %1$s ต้องใช้รูปแบบ #RRGGBB หรือ #RRGGBBAA + %1$s ต้องมีช่องสีเลขฐานสิบหกที่ถูกต้อง + %1$s ต้องมี 3 หรือ 4 ช่องสี + %1$s ต้องอยู่ระหว่าง 0 ถึง 255 + %1$s ต้องเป็นอาร์เรย์สองตัวเลขหรือวัตถุที่มี x และ y + %1$s ต้องมีตัวเลข 2 ตัวพอดี + ทำซ้ำ + ล้าง EXIF ​​เสมอ + ลบข้อมูล EXIF ​​​​ของรูปภาพเมื่อบันทึก แม้ว่าเครื่องมือจะร้องขอให้เก็บข้อมูลเมตาก็ตาม + ช่วยเหลือและเคล็ดลับ + %1$d บทช่วยสอน + เปิดเครื่องมือ + เรียนรู้เครื่องมือหลัก ตัวเลือกการส่งออก โฟลว์ PDF ยูทิลิตี้สี และการแก้ไขปัญหาทั่วไป + เริ่มต้นใช้งาน + เลือกเครื่องมือ นำเข้าไฟล์ บันทึกผลลัพธ์ และใช้การตั้งค่าซ้ำได้เร็วขึ้น + การแก้ไขภาพ + ปรับขนาด ครอบตัด กรอง ลบพื้นหลัง วาดภาพ และใส่ลายน้ำ + ไฟล์และข้อมูลเมตา + แปลงรูปแบบ เปรียบเทียบเอาต์พุต ปกป้องความเป็นส่วนตัว และควบคุมชื่อไฟล์ + PDF และเอกสาร + สร้าง PDF, สแกนเอกสาร, หน้า OCR และเตรียมไฟล์สำหรับการแชร์ + ข้อความ QR และข้อมูล + จดจำข้อความ สแกนโค้ด เข้ารหัส Base64 ตรวจสอบแฮช และไฟล์แพ็คเกจ + เครื่องมือสี + เลือกสี สร้างจานสี สำรวจไลบรารีสี และสร้างการไล่ระดับสี + เครื่องมือสร้างสรรค์ + สร้าง SVG, งานศิลปะ ASCII, พื้นผิวนอยส์, เชเดอร์ และภาพทดลอง + การแก้ไขปัญหา + แก้ไขปัญหาไฟล์จำนวนมาก ความโปร่งใส การแชร์ การนำเข้า และการรายงาน + เลือกเครื่องมือที่เหมาะสม + ใช้หมวดหมู่ ค้นหา รายการโปรด และแบ่งปันการดำเนินการเพื่อเริ่มต้นในตำแหน่งที่ถูกต้อง + เริ่มจากจุดเริ่มต้นที่ดีที่สุด + Image Toolbox มีเครื่องมือที่เน้นไว้มากมาย และหลายเครื่องมือซ้อนทับกันตั้งแต่แรกเห็น เริ่มจากงานที่คุณต้องการทำให้เสร็จ จากนั้นเลือกเครื่องมือที่ควบคุมส่วนที่สำคัญที่สุดของผลลัพธ์ เช่น ขนาด รูปแบบ ข้อมูลเมตา ข้อความ หน้า PDF สี หรือเค้าโครง + ใช้การค้นหาเมื่อคุณทราบชื่อการดำเนินการ เช่น การปรับขนาด, PDF, EXIF, OCR, QR หรือสี + เปิดหมวดหมู่เมื่อคุณทราบเฉพาะประเภทงาน จากนั้นปักหมุดเครื่องมือที่ใช้บ่อยเป็นรายการโปรด + เมื่อแอปอื่นแชร์รูปภาพลงในกล่องเครื่องมือรูปภาพ ให้เลือกเครื่องมือจากโฟลว์แผ่นการแชร์ + นำเข้า บันทึก และแบ่งปันผลลัพธ์ + ทำความเข้าใจขั้นตอนปกติตั้งแต่การเลือกอินพุตไปจนถึงการส่งออกไฟล์ที่แก้ไข + ปฏิบัติตามขั้นตอนการแก้ไขทั่วไป + เครื่องมือส่วนใหญ่เป็นไปตามจังหวะเดียวกัน: ป้อนข้อมูล ปรับตัวเลือก ดูตัวอย่าง จากนั้นบันทึกหรือแชร์ รายละเอียดที่สำคัญคือการบันทึกจะสร้างไฟล์เอาท์พุต ในขณะที่การแชร์มักจะดีที่สุดสำหรับการส่งต่อไปยังแอปอื่นอย่างรวดเร็ว + เลือกหนึ่งไฟล์สำหรับเครื่องมือแสดงภาพเดี่ยวหรือหลายไฟล์สำหรับเครื่องมือเป็นชุดเมื่อเครื่องมือเลือกอนุญาต + ตรวจสอบตัวควบคุมการแสดงตัวอย่างและเอาต์พุตก่อนบันทึก โดยเฉพาะตัวเลือกรูปแบบ คุณภาพ และชื่อไฟล์ + ใช้การแชร์เพื่อส่งต่ออย่างรวดเร็ว หรือบันทึกเมื่อคุณต้องการสำเนาถาวรในโฟลเดอร์ที่เลือก + ทำงานกับภาพหลายภาพพร้อมกัน + เครื่องมือแบบแบตช์เหมาะที่สุดสำหรับงานปรับขนาด การแปลง การบีบอัด และการตั้งชื่อซ้ำๆ + เตรียมชุดที่คาดเดาได้ + การประมวลผลแบบแบตช์จะเร็วที่สุดเมื่อทุกเอาต์พุตควรเป็นไปตามกฎเดียวกัน นอกจากนี้ยังเป็นสถานที่ที่ง่ายที่สุดในการทำผิดพลาดครั้งใหญ่ ดังนั้นให้เลือกการตั้งชื่อ คุณภาพ ข้อมูลเมตา และลักษณะการทำงานของโฟลเดอร์ก่อนเริ่มการส่งออกที่ยาวนาน + เลือกรูปภาพที่เกี่ยวข้องทั้งหมดเข้าด้วยกัน และเก็บลำดับไว้หากผลลัพธ์ขึ้นอยู่กับลำดับ + ตั้งค่ากฎการปรับขนาด รูปแบบ คุณภาพ ข้อมูลเมตา และชื่อไฟล์หนึ่งครั้งก่อนที่จะเริ่มส่งออก + รันชุดทดสอบขนาดเล็กก่อนเมื่อไฟล์ต้นฉบับมีขนาดใหญ่หรือเมื่อรูปแบบเป้าหมายใหม่สำหรับคุณ + แก้ไขเดี่ยวอย่างรวดเร็ว + ใช้โปรแกรมแก้ไขแบบออลอินวันเมื่อคุณต้องการการเปลี่ยนแปลงง่ายๆ หลายอย่างในภาพเดียว + จบหนึ่งภาพโดยไม่ต้องใช้เครื่องมือกระโดด + การแก้ไขครั้งเดียวมีประโยชน์เมื่อรูปภาพต้องการการเปลี่ยนแปลงเล็กๆ น้อยๆ แทนที่จะเป็นการดำเนินการพิเศษเพียงครั้งเดียว โดยปกติแล้วจะเร็วกว่าสำหรับการล้างข้อมูล ดูตัวอย่าง และส่งออกสำเนาสุดท้ายอย่างรวดเร็วโดยไม่ต้องสร้างเวิร์กโฟลว์เป็นชุด + เปิดการแก้ไขครั้งเดียวสำหรับรูปภาพหนึ่งภาพที่ต้องมีการปรับเปลี่ยนทั่วไป มาร์กอัป ครอบตัด การหมุน หรือการเปลี่ยนแปลงการส่งออก + ใช้การแก้ไขตามลำดับที่เปลี่ยนพิกเซลน้อยที่สุดก่อน เช่น ครอบตัดก่อนตัวกรอง และตัวกรองก่อนการบีบอัดขั้นสุดท้าย + ใช้เครื่องมือพิเศษแทนเมื่อคุณต้องการการประมวลผลเป็นชุด เป้าหมายขนาดไฟล์ที่แน่นอน เอาต์พุต PDF, OCR หรือการล้างข้อมูลเมตา + ใช้การตั้งค่าล่วงหน้าและการตั้งค่า + บันทึกตัวเลือกซ้ำๆ และปรับแต่งแอปสำหรับขั้นตอนการทำงานที่คุณต้องการ + หลีกเลี่ยงการตั้งค่าเดิมซ้ำ + ค่าที่ตั้งล่วงหน้าและการตั้งค่าจะมีประโยชน์เมื่อคุณส่งออกไฟล์ประเภทเดียวกันบ่อยๆ การตั้งค่าล่วงหน้าที่ดีจะเปลี่ยนรายการตรวจสอบด้วยตนเองที่ทำซ้ำๆ ให้เป็นแตะครั้งเดียว ในขณะที่การตั้งค่าส่วนกลางจะกำหนดค่าเริ่มต้นที่เครื่องมือใหม่จะเริ่มต้นด้วย + สร้างค่าที่ตั้งไว้ล่วงหน้าสำหรับขนาดเอาต์พุต รูปแบบ ค่าคุณภาพ หรือรูปแบบการตั้งชื่อทั่วไป + ตรวจสอบการตั้งค่าสำหรับรูปแบบเริ่มต้น คุณภาพ โฟลเดอร์บันทึก ชื่อไฟล์ ตัวเลือก และลักษณะการทำงานของอินเทอร์เฟซ + สำรองข้อมูลการตั้งค่าก่อนที่จะติดตั้งแอปใหม่หรือย้ายไปยังอุปกรณ์อื่น + ตั้งค่าเริ่มต้นที่เป็นประโยชน์ + เลือกรูปแบบเริ่มต้น คุณภาพ โหมดมาตราส่วน ประเภทการปรับขนาด และพื้นที่สีหนึ่งครั้ง + ทำให้เครื่องมือใหม่เริ่มเข้าใกล้เป้าหมายของคุณมากขึ้น + ค่าเริ่มต้นไม่ได้เป็นเพียงการตั้งค่ารูปลักษณ์ภายนอกเท่านั้น พวกเขาตัดสินใจว่ารูปแบบ คุณภาพ ลักษณะการปรับขนาด โหมดมาตราส่วน และพื้นที่สีใดจะปรากฏเป็นอันดับแรกในเครื่องมือต่างๆ มากมาย ซึ่งช่วยหลีกเลี่ยงการเลือกตัวเลือกเดิมซ้ำในการส่งออกทุกครั้ง + ตั้งค่ารูปแบบและคุณภาพรูปภาพเริ่มต้นเพื่อให้ตรงกับปลายทางปกติของคุณ เช่น JPEG สำหรับรูปภาพหรือ PNG/WebP สำหรับอัลฟ่า + ปรับแต่งประเภทการปรับขนาดและโหมดมาตราส่วนเริ่มต้น หากคุณมักจะส่งออกสำหรับมิติข้อมูล แพลตฟอร์มโซเชียล หรือหน้าเอกสารที่เข้มงวด + เปลี่ยนค่าเริ่มต้นของพื้นที่สีเฉพาะเมื่อเวิร์กโฟลว์ของคุณต้องการการจัดการสีที่สอดคล้องกันในจอแสดงผล การพิมพ์ หรือโปรแกรมแก้ไขภายนอก + ตัวเลือกและตัวเรียกใช้งาน + ใช้การตั้งค่าเครื่องมือเลือกเมื่อแอปเปิดกล่องโต้ตอบมากเกินไปหรือเริ่มต้นผิดตำแหน่ง + ทำให้การเปิดไฟล์ตรงกับนิสัยของคุณ + การตั้งค่าตัวเลือกและตัวเรียกใช้งานจะควบคุมว่ากล่องเครื่องมือรูปภาพจะเริ่มต้นจากหน้าจอหลัก เครื่องมือ หรือขั้นตอนการเลือกไฟล์หรือไม่ ตัวเลือกเหล่านี้มีประโยชน์อย่างยิ่งเมื่อคุณมักจะเข้ามาจากเอกสารแบ่งปันของ Android หรือเมื่อคุณต้องการให้แอปรู้สึกเหมือนเป็นตัวเรียกใช้งานเครื่องมือ + ใช้การตั้งค่าตัวเลือกรูปภาพหากตัวเลือกระบบซ่อนไฟล์ แสดงรายการล่าสุดมากเกินไป หรือจัดการไฟล์บนคลาวด์ได้ไม่ดี + เปิดใช้งานการเลือกข้ามเฉพาะสำหรับเครื่องมือที่คุณมักจะป้อนจากการแชร์หรือรู้ไฟล์ต้นฉบับอยู่แล้ว + ตรวจสอบโหมดตัวเรียกใช้งานหากคุณต้องการให้ Image Toolbox ทำงานเหมือนกับตัวเลือกเครื่องมือมากกว่าหน้าจอหลักปกติ + แหล่งที่มาของภาพ + เลือกโหมดตัวเลือกที่ทำงานได้ดีที่สุดกับแกลเลอรี ตัวจัดการไฟล์ และไฟล์บนคลาวด์ + เลือกไฟล์จากแหล่งที่ถูกต้อง + การตั้งค่าแหล่งที่มาของรูปภาพส่งผลต่อวิธีที่แอปขอรูปภาพจาก Android ตัวเลือกที่ดีที่สุดขึ้นอยู่กับว่าไฟล์ของคุณอยู่ในแกลเลอรีระบบ โฟลเดอร์ตัวจัดการไฟล์ ผู้ให้บริการคลาวด์ หรือแอปอื่นที่แชร์เนื้อหาชั่วคราว + ใช้เครื่องมือเลือกเริ่มต้นเมื่อคุณต้องการประสบการณ์ที่ผสานรวมระบบมากที่สุดสำหรับรูปภาพแกลเลอรีทั่วไป + สลับโหมดตัวเลือกหากอัลบั้ม โฟลเดอร์ ไฟล์คลาวด์ หรือรูปแบบที่ไม่เป็นมาตรฐานไม่ปรากฏในตำแหน่งที่คุณคาดหวัง + หากไฟล์ที่แชร์หายไปหลังจากออกจากแอปต้นทาง ให้เปิดใหม่ผ่านตัวเลือกถาวรหรือบันทึกสำเนาในเครื่องก่อน + รายการโปรดและแบ่งปันเครื่องมือ + ทำให้หน้าจอหลักโฟกัสและซ่อนเครื่องมือที่ไม่สมเหตุสมผลในโฟลว์การแชร์ + ลดเสียงรบกวนรายการเครื่องมือ + รายการโปรดและการตั้งค่าที่ซ่อนไว้เพื่อแชร์จะมีประโยชน์เมื่อคุณใช้เครื่องมือเพียงไม่กี่อย่างทุกวัน พวกเขายังรักษาขั้นตอนการแชร์ให้ใช้งานได้จริงด้วยการซ่อนเครื่องมือที่ไม่สามารถใช้ประเภทไฟล์ที่แอพอื่นส่งได้ + ปักหมุดเครื่องมือที่ใช้บ่อยเพื่อให้เข้าถึงได้ง่ายแม้ว่าจะจัดกลุ่มเครื่องมือตามหมวดหมู่ก็ตาม + ซ่อนเครื่องมือจากขั้นตอนการแชร์เมื่อไม่เกี่ยวข้องกับไฟล์ที่มาจากแอปอื่น + ใช้การตั้งค่าการเรียงลำดับรายการโปรดหากคุณต้องการรายการโปรดในตอนท้ายหรือจัดกลุ่มตามเครื่องมือที่เกี่ยวข้อง + สำรองข้อมูลการตั้งค่า + ย้ายค่าที่ตั้งล่วงหน้า การตั้งค่า และลักษณะการทำงานของแอปไปยังการติดตั้งอื่นอย่างปลอดภัย + ทำให้การตั้งค่าของคุณพกพาสะดวก + การสำรองและการคืนค่าจะมีประโยชน์มากที่สุดหลังจากที่คุณได้ปรับค่าที่ตั้งล่วงหน้า โฟลเดอร์ กฎชื่อไฟล์ ลำดับเครื่องมือ และการตั้งค่าภาพแล้ว การสำรองข้อมูลช่วยให้คุณทดลองการตั้งค่าหรือติดตั้งแอปใหม่ได้โดยไม่ต้องสร้างเวิร์กโฟลว์ใหม่จากหน่วยความจำ + สร้างการสำรองข้อมูลหลังจากเปลี่ยนค่าที่ตั้งไว้ ลักษณะการทำงานของชื่อไฟล์ รูปแบบเริ่มต้น หรือการจัดเรียงเครื่องมือ + จัดเก็บข้อมูลสำรองไว้นอกโฟลเดอร์แอพ หากคุณวางแผนที่จะล้างข้อมูล ติดตั้งใหม่ หรือย้ายอุปกรณ์ + คืนค่าการตั้งค่าก่อนประมวลผลชุดงานที่สำคัญ ดังนั้นลักษณะการทำงานเริ่มต้นและการบันทึกจึงตรงกับการตั้งค่าเก่าของคุณ + ปรับขนาดและแปลงรูปภาพ + เปลี่ยนขนาด รูปแบบ คุณภาพ และข้อมูลเมตาของรูปภาพหนึ่งหรือหลายรูป + สร้างสำเนาที่ปรับขนาดแล้ว + ปรับขนาดและแปลงเป็นเครื่องมือหลักสำหรับขนาดที่คาดเดาได้และไฟล์เอาต์พุตที่เบากว่า ใช้เมื่อขนาดของรูปภาพ รูปแบบผลลัพธ์ และพฤติกรรมของข้อมูลเมตามีความสำคัญในเวลาเดียวกัน + เลือกรูปภาพหนึ่งภาพขึ้นไป จากนั้นเลือกว่าขนาด ขนาด หรือขนาดไฟล์มีความสำคัญมากที่สุด + เลือกรูปแบบผลลัพธ์และคุณภาพ ใช้ PNG หรือ WebP เมื่อต้องรักษาความโปร่งใสไว้ + ดูตัวอย่างผลลัพธ์ จากนั้นบันทึกหรือแชร์สำเนาที่สร้างขึ้นโดยไม่ต้องเปลี่ยนต้นฉบับ + ปรับขนาดตามขนาดไฟล์ + กำหนดเป้าหมายขีดจำกัดขนาดเมื่อเว็บไซต์ โปรแกรมส่งข้อความ หรือแบบฟอร์มปฏิเสธรูปภาพที่มีน้ำหนักมาก + พอดีกับขีดจำกัดการอัปโหลดที่เข้มงวด + การปรับขนาดตามขนาดไฟล์ดีกว่าการคาดเดาค่าคุณภาพเมื่อปลายทางยอมรับเฉพาะไฟล์ที่ต่ำกว่าขีดจำกัดที่กำหนดเท่านั้น เครื่องมืออาจปรับการบีบอัดและขนาดเพื่อให้เข้าถึงเป้าหมายในขณะที่พยายามรักษาผลลัพธ์ให้ใช้งานได้ + ป้อนขนาดเป้าหมายให้ต่ำกว่าขีดจำกัดการอัปโหลดจริงเล็กน้อยเพื่อให้เหลือพื้นที่สำหรับการเปลี่ยนแปลงข้อมูลเมตาและผู้ให้บริการ + ต้องการรูปแบบที่สูญหายสำหรับภาพถ่ายเมื่อเป้าหมายมีขนาดเล็ก PNG สามารถคงขนาดใหญ่ได้แม้จะปรับขนาดแล้วก็ตาม + ตรวจสอบใบหน้า ข้อความ และขอบหลังจากส่งออก เนื่องจากเป้าหมายที่มีขนาดสูงอาจแนะนำส่วนที่ผิดปกติที่มองเห็นได้ + จำกัดการปรับขนาด + ย่อเฉพาะรูปภาพที่เกินความกว้าง ความสูง หรือข้อจำกัดไฟล์สูงสุดของคุณ + หลีกเลี่ยงการปรับขนาดรูปภาพที่ปกติแล้ว + การจำกัดขนาดมีประโยชน์สำหรับโฟลเดอร์แบบผสมซึ่งบางภาพมีขนาดใหญ่และบางภาพได้เตรียมไว้แล้ว แทนที่จะบังคับให้ทุกไฟล์มีขนาดเท่ากัน ระบบจะเก็บภาพที่ยอมรับได้ให้ใกล้เคียงกับคุณภาพต้นฉบับมากขึ้น + กำหนดขนาดหรือขีดจำกัดสูงสุดตามปลายทาง แทนที่จะปรับขนาดทุกไฟล์แบบสุ่มสี่สุ่มห้า + เปิดใช้งานลักษณะการทำงานแบบข้ามหากใหญ่ขึ้น เมื่อคุณต้องการหลีกเลี่ยงเอาต์พุตที่หนักกว่าแหล่งที่มา + ใช้สิ่งนี้สำหรับชุดจากกล้อง ภาพหน้าจอ หรือการดาวน์โหลดที่แตกต่างกันซึ่งมีขนาดแหล่งที่มาแตกต่างกันมาก + ครอบตัด หมุน และยืดให้ตรง + วางกรอบพื้นที่สำคัญก่อนส่งออกหรือใช้รูปภาพในเครื่องมืออื่นๆ + ทำความสะอาดองค์ประกอบ + การครอบตัดก่อนจะทำให้ตัวกรอง, OCR, หน้า PDF และการแชร์ผลลัพธ์ในภายหลังสะอาดขึ้น + เปิดครอบตัดแล้วเลือกรูปภาพที่คุณต้องการปรับ + ใช้การครอบตัดอิสระหรืออัตราส่วนภาพเมื่อผลลัพธ์ต้องพอดีกับโพสต์ เอกสาร หรืออวาตาร์ + หมุนหรือพลิกก่อนที่จะบันทึก เพื่อให้เครื่องมือในภายหลังได้รับภาพที่แก้ไขแล้ว + ใช้ตัวกรองและการปรับเปลี่ยน + สร้างสแต็กตัวกรองที่แก้ไขได้และดูตัวอย่างผลลัพธ์ก่อนส่งออก + ปรับแต่งรูปลักษณ์ของภาพ + ฟิลเตอร์มีประโยชน์สำหรับการแก้ไขอย่างรวดเร็ว เอาท์พุตที่มีสไตล์ และการเตรียมภาพสำหรับ OCR หรือการพิมพ์ การเปลี่ยนแปลงคอนทราสต์ ความคมชัด และความสว่างเล็กน้อยอาจมีความสำคัญมากกว่าเอฟเฟกต์หนักๆ เมื่อรูปภาพมีข้อความหรือรายละเอียดเล็กๆ น้อยๆ + เพิ่มตัวกรองทีละตัวและคอยดูตัวอย่างหลังการเปลี่ยนแปลงแต่ละครั้ง + ปรับความสว่าง คอนทราสต์ ความคมชัด ความเบลอ สี หรือมาสก์ ขึ้นอยู่กับงาน + ส่งออกเมื่อการแสดงตัวอย่างตรงกับอุปกรณ์ เอกสาร หรือแพลตฟอร์มโซเชียลเป้าหมายของคุณเท่านั้น + ดูตัวอย่างก่อน + ตรวจสอบรูปภาพก่อนที่จะเลือกเครื่องมือแก้ไข แปลง หรือล้างข้อมูลที่หนักกว่า + ตรวจสอบสิ่งที่คุณได้รับจริง + การแสดงตัวอย่างรูปภาพมีประโยชน์เมื่อไฟล์มาจากแชท ที่เก็บข้อมูลบนคลาวด์ หรือแอพอื่น และคุณไม่แน่ใจว่าไฟล์นั้นมีอะไรบ้าง การตรวจสอบขนาด ความโปร่งใส การวางแนว และคุณภาพของภาพก่อนอื่นจะช่วยเลือกเครื่องมือติดตามผลที่ถูกต้อง + เปิดการแสดงตัวอย่างรูปภาพเมื่อคุณต้องการตรวจสอบรูปภาพหลายรูปก่อนตัดสินใจว่าจะทำอย่างไรกับรูปภาพเหล่านั้น + มองหาปัญหาในการหมุน พื้นที่โปร่งใส การบีบอัดข้อมูล และขนาดที่ใหญ่เกินคาด + ย้ายไปที่การปรับขนาด ครอบตัด เปรียบเทียบ EXIF ​​หรือตัวลบพื้นหลังหลังจากที่คุณทราบว่าจำเป็นต้องแก้ไขอะไรบ้าง + ลบหรือคืนค่าพื้นหลัง + ลบพื้นหลังโดยอัตโนมัติหรือปรับแต่งขอบด้วยตนเองด้วยเครื่องมือการคืนค่า + เก็บเรื่องไว้ ลบส่วนที่เหลือออก + การลบพื้นหลังจะทำงานได้ดีที่สุดเมื่อคุณรวมการเลือกอัตโนมัติเข้ากับการล้างข้อมูลด้วยตนเองอย่างระมัดระวัง รูปแบบการส่งออกขั้นสุดท้ายมีความสำคัญพอๆ กับมาสก์ เนื่องจาก JPEG จะทำให้พื้นที่โปร่งใสเรียบขึ้น แม้ว่าตัวอย่างจะดูถูกต้องก็ตาม + เริ่มต้นด้วยการลบอัตโนมัติหากวัตถุถูกแยกออกจากพื้นหลังอย่างชัดเจน + ซูมเข้าและสลับระหว่างการลบและคืนค่าเพื่อแก้ไขเส้นผม มุม เงา และรายละเอียดเล็กๆ น้อยๆ + บันทึกเป็น PNG หรือ WebP เมื่อคุณต้องการความโปร่งใสในไฟล์สุดท้าย + วาด มาร์กอัป และลายน้ำ + เพิ่มลูกศร บันทึกย่อ ไฮไลท์ ลายเซ็น หรือการวางซ้อนลายน้ำซ้ำ + เพิ่มข้อมูลที่มองเห็นได้ + เครื่องมือมาร์กอัปช่วยอธิบายรูปภาพ ในขณะที่ลายน้ำช่วยสร้างแบรนด์หรือปกป้องไฟล์ที่ส่งออก + ใช้ Draw เมื่อคุณต้องการบันทึกย่อ รูปร่าง การเน้น หรือคำอธิบายประกอบแบบด่วนด้วยมือเปล่า + ใช้ลายน้ำเมื่อข้อความหรือเครื่องหมายรูปภาพเดียวกันควรปรากฏอย่างสม่ำเสมอในเอาต์พุต + ตรวจสอบความทึบ ตำแหน่ง และมาตราส่วนก่อนส่งออก เพื่อให้มองเห็นเครื่องหมายได้แต่ไม่รบกวนสมาธิ + วาดค่าเริ่มต้น + ตั้งค่าความกว้างของเส้น สี โหมดเส้นทาง การล็อคการวางแนว และแว่นขยายเพื่อการมาร์กอัปที่เร็วขึ้น + ทำให้การวาดภาพเป็นเรื่องที่คาดเดาได้ + การตั้งค่าการวาดมีประโยชน์เมื่อคุณใส่คำอธิบายประกอบภาพหน้าจอ ทำเครื่องหมายเอกสาร หรือเซ็นชื่อรูปภาพบ่อยๆ ค่าเริ่มต้นจะช่วยลดเวลาในการตั้งค่า ในขณะที่แว่นขยายและการล็อกการวางแนวทำให้การแก้ไขที่แม่นยำบนหน้าจอขนาดเล็กทำได้ง่ายขึ้น + ตั้งค่าความกว้างของเส้นเริ่มต้นและสีวาดเป็นค่าที่คุณใช้บ่อยที่สุดสำหรับลูกศร ไฮไลท์ หรือลายเซ็น + ใช้โหมดเส้นทางเริ่มต้นเมื่อคุณมักจะวาดลายเส้น รูปร่าง หรือมาร์กอัปประเภทเดียวกัน + เปิดใช้งานแว่นขยายหรือการล็อคการวางแนวเมื่อการป้อนข้อมูลด้วยนิ้วทำให้รายละเอียดเล็กๆ น้อยๆ วางตำแหน่งได้อย่างแม่นยำได้ยาก + เค้าโครงหลายภาพ + เลือกเครื่องมือหลายภาพที่เหมาะสมก่อนจัดเรียงภาพหน้าจอ สแกน หรือแถบเปรียบเทียบ + ใช้เครื่องมือเค้าโครงที่เหมาะสม + เครื่องมือเหล่านี้ฟังดูคล้ายกัน แต่แต่ละเครื่องมือได้รับการปรับแต่งสำหรับเอาต์พุตหลายภาพที่แตกต่างกัน การเลือกอันที่ถูกต้องก่อนจะหลีกเลี่ยงการต่อสู้กับการควบคุมเลย์เอาต์ที่ออกแบบมาเพื่อผลลัพธ์ที่แตกต่าง + ใช้ Collage Maker สำหรับเลย์เอาต์ที่ออกแบบมาพร้อมรูปภาพหลายรูปในองค์ประกอบเดียว + ใช้การต่อภาพหรือการซ้อนภาพเมื่อรูปภาพควรกลายเป็นแถบยาวแนวตั้งหรือแนวนอนเส้นเดียว + ใช้การแยกภาพเมื่อภาพขนาดใหญ่หนึ่งภาพจำเป็นต้องกลายเป็นภาพเล็กๆ หลายภาพ + แปลงรูปแบบภาพ + สร้าง JPEG, PNG, WebP, AVIF, JXL และสำเนาอื่นๆ โดยไม่ต้องแก้ไขพิกเซลด้วยตนเอง + เลือกรูปแบบสำหรับงาน + รูปแบบที่แตกต่างกันจะดีกว่าสำหรับรูปภาพ ภาพหน้าจอ ความโปร่งใส การบีบอัด หรือความเข้ากันได้ การแปลงจะปลอดภัยที่สุดเมื่อคุณเข้าใจว่าแอปปลายทางรองรับอะไรบ้าง และแหล่งที่มานั้นมีอัลฟ่า ภาพเคลื่อนไหว หรือข้อมูลเมตาที่คุณต้องการเก็บไว้หรือไม่ + ใช้ JPEG สำหรับรูปภาพที่เข้ากันได้ PNG สำหรับรูปภาพและอัลฟ่าแบบไม่สูญเสียข้อมูล และใช้ WebP สำหรับการแชร์แบบกะทัดรัด + ปรับคุณภาพเมื่อรูปแบบรองรับ ค่าที่ต่ำกว่าจะลดขนาด แต่สามารถเพิ่มสิ่งประดิษฐ์ได้ + เก็บต้นฉบับไว้จนกว่าคุณจะตรวจสอบว่าไฟล์ที่แปลงแล้วเปิดอย่างถูกต้องในแอปเป้าหมาย + ตรวจสอบ EXIF ​​และความเป็นส่วนตัว + ดู แก้ไข หรือลบข้อมูลเมตาก่อนแชร์รูปภาพที่ละเอียดอ่อน + ควบคุมข้อมูลรูปภาพที่ซ่อนอยู่ + EXIF อาจมีข้อมูลกล้อง วันที่ สถานที่ การวางแนว และรายละเอียดอื่นๆ ที่ไม่ปรากฏในภาพ ควรตรวจสอบก่อนที่จะแชร์ต่อสาธารณะ รายงานสนับสนุน รายการตลาด หรือรูปภาพใดๆ ที่อาจเปิดเผยสถานที่ส่วนตัว + เปิดเครื่องมือ EXIF ​​​​ก่อนแชร์รูปภาพที่อาจมีข้อมูลตำแหน่งหรืออุปกรณ์ส่วนตัว + ลบฟิลด์ที่ละเอียดอ่อนหรือแก้ไขแท็กที่ไม่ถูกต้อง เช่น วันที่ การวางแนว ผู้เขียน หรือข้อมูลกล้อง + บันทึกสำเนาใหม่และแบ่งปันสำเนานั้นแทนต้นฉบับเมื่อความเป็นส่วนตัวมีความสำคัญ + การล้างข้อมูล EXIF ​​อัตโนมัติ + ลบข้อมูลเมตาตามค่าเริ่มต้นในขณะที่ตัดสินใจว่าควรเก็บวันที่ไว้หรือไม่ + ทำให้ความเป็นส่วนตัวเป็นค่าเริ่มต้น + กลุ่มการตั้งค่า EXIF ​​มีประโยชน์เมื่อคุณต้องการให้การล้างข้อมูลความเป็นส่วนตัวเกิดขึ้นโดยอัตโนมัติ แทนที่จะจดจำไว้สำหรับการส่งออกทุกครั้ง Keep Date Time เป็นการตัดสินใจแยกต่างหาก เนื่องจากวันที่อาจมีประโยชน์สำหรับการจัดเรียงแต่มีความละเอียดอ่อนในการแบ่งปัน + เปิดใช้งาน EXIF ​​​​ที่ชัดเจนเสมอเมื่อการส่งออกส่วนใหญ่ของคุณมีไว้สำหรับการแชร์แบบสาธารณะหรือกึ่งสาธารณะ + เปิดใช้งาน Keep Date Time เฉพาะเมื่อการรักษาเวลาในการจับภาพมีความสำคัญมากกว่าการลบการประทับเวลาทุกครั้ง + ใช้การแก้ไข EXIF ​​​​ด้วยตนเองสำหรับไฟล์แบบครั้งเดียวซึ่งคุณต้องเก็บบางฟิลด์และลบบางฟิลด์ออก + ควบคุมชื่อไฟล์ + ใช้คำนำหน้า คำต่อท้าย การประทับเวลา ค่าที่ตั้งล่วงหน้า ผลรวมตรวจสอบ และหมายเลขลำดับ + ทำให้การส่งออกหาง่าย + รูปแบบชื่อไฟล์ที่ดีจะช่วยจัดเรียงแบทช์และป้องกันการเขียนทับโดยไม่ตั้งใจ การตั้งค่าชื่อไฟล์มีประโยชน์อย่างยิ่งเมื่อการส่งออกกลับไปยังโฟลเดอร์ต้นทาง ซึ่งชื่อที่คล้ายกันอาจทำให้เกิดความสับสนได้อย่างรวดเร็ว + ตั้งค่าคำนำหน้าชื่อไฟล์ คำต่อท้าย เวลาประทับ ชื่อเดิม ข้อมูลที่กำหนดไว้ล่วงหน้า หรือตัวเลือกลำดับในการตั้งค่า + ใช้หมายเลขลำดับสำหรับชุดงานโดยคำนึงถึงลำดับ เช่น หน้า เฟรม หรือภาพต่อกัน + เปิดใช้งานการเขียนทับเมื่อคุณแน่ใจว่าการแทนที่ไฟล์ที่มีอยู่นั้นปลอดภัยสำหรับโฟลเดอร์ปัจจุบันเท่านั้น + เขียนทับไฟล์ + แทนที่เอาต์พุตด้วยชื่อที่ตรงกันเฉพาะเมื่อเวิร์กโฟลว์ของคุณตั้งใจทำลายล้างเท่านั้น + ใช้โหมดเขียนทับอย่างระมัดระวัง + เขียนทับไฟล์จะเปลี่ยนวิธีจัดการข้อขัดแย้งในการตั้งชื่อ มีประโยชน์สำหรับการส่งออกซ้ำไปยังโฟลเดอร์เดียวกัน แต่ยังสามารถแทนที่ผลลัพธ์ก่อนหน้าได้หากรูปแบบชื่อไฟล์ของคุณสร้างชื่อเดียวกันอีกครั้ง + เปิดใช้งานการเขียนทับเฉพาะสำหรับโฟลเดอร์ที่คาดว่าจะแทนที่ไฟล์เก่าที่สร้างขึ้นและสามารถกู้คืนได้ + ปิดการใช้งานการเขียนทับเมื่อส่งออกต้นฉบับ ไฟล์ไคลเอนต์ การสแกนครั้งเดียว หรือสิ่งอื่นใดที่ไม่มีการสำรองข้อมูล + รวมการเขียนทับด้วยรูปแบบชื่อไฟล์โดยเจตนาเพื่อแทนที่เฉพาะเอาต์พุตที่ต้องการเท่านั้น + รูปแบบชื่อไฟล์ + รวมชื่อดั้งเดิม คำนำหน้า คำต่อท้าย การประทับเวลา ค่าที่ตั้งไว้ โหมดสเกล และเช็คซัม + สร้างชื่อที่อธิบายผลลัพธ์ + การตั้งค่ารูปแบบชื่อไฟล์สามารถเปลี่ยนไฟล์ที่ส่งออกให้เป็นประวัติที่เป็นประโยชน์เกี่ยวกับสิ่งที่เกิดขึ้นกับไฟล์เหล่านั้น สิ่งนี้มีความสำคัญเป็นชุดเนื่องจากมุมมองโฟลเดอร์อาจเป็นที่เดียวที่คุณสามารถแยกแยะขนาดต้นฉบับ ค่าที่ตั้งล่วงหน้า รูปแบบ และลำดับการประมวลผลได้ + ใช้ชื่อไฟล์ คำนำหน้า ส่วนต่อท้าย และเวลาประทับดั้งเดิม เมื่อคุณต้องการชื่อที่อ่านได้สำหรับการจัดเรียงด้วยตนเอง + เพิ่มค่าที่ตั้งล่วงหน้า โหมดมาตราส่วน ขนาดไฟล์ หรือข้อมูลเช็คซัม เมื่อเอาต์พุตต้องบันทึกวิธีการผลิต + หลีกเลี่ยงชื่อแบบสุ่มสำหรับเวิร์กโฟลว์ที่ไฟล์จำเป็นต้องจับคู่กับต้นฉบับหรือลำดับหน้า + เลือกตำแหน่งที่จะบันทึกไฟล์ + หลีกเลี่ยงการสูญเสียการส่งออกโดยการตั้งค่าโฟลเดอร์ การบันทึกโฟลเดอร์ต้นฉบับ และตำแหน่งการบันทึกเพียงครั้งเดียว + ทำให้สามารถคาดเดาผลลัพธ์ได้ + พฤติกรรมการบันทึกมีความสำคัญมากที่สุดเมื่อคุณประมวลผลไฟล์จำนวนมากหรือแชร์ไฟล์จากผู้ให้บริการระบบคลาวด์ การตั้งค่าโฟลเดอร์จะกำหนดว่าเอาต์พุตจะรวบรวมไว้ในที่ที่รู้จัก ปรากฏข้างต้นฉบับ หรือไปที่อื่นชั่วคราวสำหรับงานเฉพาะ + ตั้งค่าโฟลเดอร์บันทึกเริ่มต้นหากคุณต้องการส่งออกในที่เดียวเสมอ + ใช้โฟลเดอร์ save-to-Original-สำหรับเวิร์กโฟลว์การล้างข้อมูลโดยที่เอาต์พุตควรอยู่ใกล้กับไฟล์ต้นฉบับ + ใช้ตำแหน่งบันทึกครั้งเดียวเมื่องานเดี่ยวต้องการปลายทางอื่นโดยไม่ต้องเปลี่ยนค่าเริ่มต้น + โฟลเดอร์บันทึกครั้งเดียว + ส่งการส่งออกครั้งต่อไปไปยังโฟลเดอร์ชั่วคราวโดยไม่ต้องเปลี่ยนปลายทางปกติของคุณ + แยกงานพิเศษออกจากกัน + ตำแหน่งบันทึกครั้งเดียวมีประโยชน์สำหรับโปรเจ็กต์ชั่วคราว โฟลเดอร์ไคลเอนต์ เซสชันการล้างข้อมูล หรือการส่งออกที่ไม่ควรรวมกับโฟลเดอร์ Image Toolbox ปกติของคุณ มันทำให้ปลายทางมีอายุสั้นโดยไม่ต้องเขียนการตั้งค่าเริ่มต้นใหม่ + เลือกโฟลเดอร์แบบครั้งเดียวก่อนเริ่มงานที่อยู่นอกตำแหน่งส่งออกปกติของคุณ + ใช้สำหรับโปรเจ็กต์ขนาดสั้น โฟลเดอร์ที่ใช้ร่วมกัน หรือแบตช์ที่ต้องรวมเอาต์พุตไว้ด้วยกัน + กลับไปยังโฟลเดอร์เริ่มต้นหลังงาน เพื่อที่การส่งออกในภายหลังจะได้ไม่ไปยังตำแหน่งชั่วคราวโดยไม่คาดคิด + ปรับสมดุลคุณภาพและขนาดไฟล์ + ทำความเข้าใจว่าเมื่อใดควรเปลี่ยนขนาด รูปแบบ หรือคุณภาพ แทนที่จะคาดเดา + ย่อขนาดไฟล์อย่างตั้งใจ + ขนาดไฟล์ขึ้นอยู่กับขนาดรูปภาพ รูปแบบ คุณภาพ ความโปร่งใส และความซับซ้อนของเนื้อหา หากไฟล์ยังหนักเกินไป การเปลี่ยนขนาดมักจะช่วยได้มากกว่าการลดคุณภาพซ้ำๆ + ปรับขนาดขนาดก่อนเมื่อรูปภาพมีขนาดใหญ่กว่าที่ปลายทางสามารถแสดงได้ + คุณภาพต่ำลงสำหรับรูปแบบที่สูญหายเท่านั้น และตรวจสอบข้อความ ขอบ การไล่ระดับสี และใบหน้าหลังการส่งออก + สลับรูปแบบเมื่อการเปลี่ยนแปลงคุณภาพไม่เพียงพอ เช่น WebP สำหรับการแชร์ หรือ PNG สำหรับเนื้อหาที่โปร่งใสคมชัด + ทำงานกับรูปแบบภาพเคลื่อนไหว + ใช้เครื่องมือ GIF, APNG, WebP และ JXL เมื่อไฟล์มีเฟรมแทนที่จะเป็นบิตแมปเดียว + อย่าปฏิบัติต่อทุกภาพเหมือนนิ่ง + รูปแบบภาพเคลื่อนไหวจำเป็นต้องมีเครื่องมือที่เข้าใจเฟรม ระยะเวลา และความเข้ากันได้ + ใช้เครื่องมือ GIF หรือ APNG เมื่อคุณต้องการการดำเนินการระดับเฟรมสำหรับรูปแบบเหล่านั้น + ใช้เครื่องมือ WebP เมื่อคุณต้องการการบีบอัดสมัยใหม่หรือเอาต์พุต WebP แบบเคลื่อนไหว + ดูตัวอย่างจังหวะภาพเคลื่อนไหวก่อนแชร์เนื่องจากบางแพลตฟอร์มแปลงหรือทำให้ภาพเคลื่อนไหวแบนลง + เปรียบเทียบและดูตัวอย่างผลลัพธ์ + ตรวจสอบการเปลี่ยนแปลง ขนาดไฟล์ และความแตกต่างทางภาพก่อนทำการส่งออก + ตรวจสอบก่อนแชร์ + เครื่องมือเปรียบเทียบช่วยตรวจจับสิ่งแปลกปลอมในการบีบอัด สีผิด หรือการแก้ไขที่ไม่ต้องการตั้งแต่เนิ่นๆ + เปิดการเปรียบเทียบเมื่อคุณต้องการตรวจสอบสองเวอร์ชันเคียงข้างกัน + ซูมเข้าไปในขอบ ข้อความ การไล่ระดับสี และขอบเขตโปร่งใสที่ซึ่งปัญหาจะพลาดได้ง่ายที่สุด + หากเอาต์พุตหนักเกินไปหรือเบลอ ให้กลับไปที่เครื่องมือก่อนหน้าแล้วปรับคุณภาพหรือรูปแบบ + ใช้ศูนย์กลางเครื่องมือ PDF + ผสาน แยก หมุน ลบหน้า บีบอัด ป้องกัน OCR และไฟล์ PDF ลายน้ำ + เลือกการดำเนินการ PDF + กลุ่มฮับ PDF จะบันทึกการดำเนินการเบื้องหลังจุดเข้าหนึ่งจุด เพื่อให้คุณสามารถเลือกการดำเนินการที่แน่นอนได้ เริ่มต้นเมื่อไฟล์เป็น PDF อยู่แล้ว และใช้รูปภาพเป็น PDF หรือเครื่องสแกนเอกสารเมื่อแหล่งที่มาของคุณยังคงเป็นชุดรูปภาพ + เปิดเครื่องมือ PDF และเลือกการดำเนินการที่ตรงกับเป้าหมายของคุณ + เลือกไฟล์ PDF หรือรูปภาพต้นฉบับ จากนั้นตรวจสอบลำดับหน้า การหมุน การป้องกัน หรือตัวเลือก OCR + บันทึก PDF ที่สร้างขึ้นแล้วเปิดหนึ่งครั้งเพื่อยืนยันจำนวนหน้า ลำดับ และความสามารถในการอ่าน + เปลี่ยนรูปภาพให้เป็น PDF + รวมการสแกน ภาพหน้าจอ ใบเสร็จรับเงิน หรือรูปถ่ายไว้ในเอกสารเดียวที่สามารถแชร์ได้ + สร้างเอกสารที่สะอาด + รูปภาพจะกลายเป็นหน้า PDF ที่ดีขึ้นเมื่อมีการครอบตัด เรียงลำดับ และปรับขนาดอย่างสม่ำเสมอ ปฏิบัติต่อรายการรูปภาพเหมือนการเรียงหน้า: การหมุน ระยะขอบ และการเลือกขนาดหน้าทุกครั้งจะส่งผลต่อความรู้สึกในการอ่านเอกสารขั้นสุดท้าย + ครอบตัดหรือหมุนรูปภาพก่อนเมื่อขอบหน้า เงา หรือการวางแนวไม่ถูกต้อง + เลือกรูปภาพตามลำดับหน้าที่ต้องการและปรับขนาดหน้าหรือระยะขอบหากเครื่องมือนำเสนอ + ส่งออก PDF จากนั้นตรวจสอบว่าทุกหน้าสามารถอ่านได้ก่อนที่จะส่ง + สแกนเอกสาร + จับภาพหน้ากระดาษและเตรียมเป็น PDF, OCR หรือการแชร์ + จับภาพหน้าที่อ่านได้ + การสแกนเอกสารทำงานได้ดีที่สุดกับหน้าเรียบ แสงดี และมุมมองที่ถูกต้อง การสแกนใหม่ทั้งหมดก่อนการส่งออก OCR หรือ PDF ช่วยประหยัดเวลาเนื่องจากการจดจำข้อความและการบีบอัดขึ้นอยู่กับคุณภาพของหน้า + วางเอกสารบนพื้นผิวที่ตัดกันโดยมีแสงเพียงพอและมีเงาน้อยที่สุด + ปรับมุมที่ตรวจพบหรือครอบตัดด้วยตนเองเพื่อให้หน้าเต็มกรอบอย่างหมดจด + ส่งออกเป็นรูปภาพหรือ PDF จากนั้นเรียกใช้ OCR หากคุณต้องการข้อความที่เลือกได้ + ป้องกันหรือปลดล็อคไฟล์ PDF + ใช้รหัสผ่านอย่างระมัดระวังและเก็บสำเนาต้นฉบับที่แก้ไขได้เมื่อเป็นไปได้ + จัดการการเข้าถึง PDF อย่างปลอดภัย + เครื่องมือป้องกันมีประโยชน์สำหรับการแชร์แบบควบคุม แต่รหัสผ่านนั้นสูญหายได้ง่ายและกู้คืนได้ยาก ป้องกันสำเนาเพื่อการเผยแพร่ เก็บไฟล์ต้นฉบับที่ไม่ได้รับการป้องกันแยกกัน และตรวจสอบผลลัพธ์ก่อนที่จะลบสิ่งใดๆ + ป้องกันสำเนาของ PDF ไม่ใช่เอกสารต้นฉบับเดียวของคุณ + เก็บรหัสผ่านไว้ในที่ที่ปลอดภัยก่อนแชร์ไฟล์ที่ได้รับการป้องกัน + ใช้การปลดล็อคเฉพาะกับไฟล์ที่คุณได้รับอนุญาตให้แก้ไข จากนั้นตรวจสอบว่าสำเนาที่ปลดล็อคเปิดอย่างถูกต้อง + จัดระเบียบหน้า PDF + ผสาน แยก จัดเรียงใหม่ หมุน ครอบตัด ลบหน้า และเพิ่มหมายเลขหน้าโดยเจตนา + แก้ไขโครงสร้างเอกสารก่อนตกแต่ง + เครื่องมือหน้า PDF ใช้งานง่ายที่สุดในลำดับเดียวกับที่คุณเตรียมเอกสารกระดาษ: รวบรวมหน้า ลบหน้าที่ไม่ถูกต้อง จัดเรียงตามลำดับ การวางแนวที่ถูกต้อง จากนั้นเพิ่มรายละเอียดสุดท้าย เช่น หมายเลขหน้าหรือลายน้ำ + ใช้การผสานหรือรูปภาพเป็น PDF ก่อนเมื่อเอกสารยังคงแบ่งออกเป็นหลายไฟล์ + จัดเรียง หมุน ครอบตัด หรือลบหน้าก่อนที่จะเพิ่มหมายเลขหน้า ลายเซ็น หรือลายน้ำ + ดูตัวอย่าง PDF สุดท้ายหลังจากแก้ไขโครงสร้าง เนื่องจากหน้าที่ขาดหายไปหรือถูกหมุนอาจสังเกตได้ยากในภายหลัง + คำอธิบายประกอบ PDF + ลบคำอธิบายประกอบหรือทำให้เรียบลงเมื่อผู้ดูแสดงความคิดเห็นและทำเครื่องหมายแตกต่างออกไป + ควบคุมสิ่งที่ยังคงมองเห็นได้ + คำอธิบายประกอบอาจเป็นความคิดเห็น ไฮไลท์ ภาพวาด เครื่องหมายแบบฟอร์ม หรือวัตถุ PDF พิเศษอื่นๆ ผู้ดูบางคนแสดงเอกสารเหล่านี้แตกต่างออกไป ดังนั้นการลบออกหรือทำให้เอกสารเรียบลงสามารถทำให้เอกสารที่แชร์คาดเดาได้ง่ายขึ้น + ลบคำอธิบายประกอบเมื่อไม่ควรรวมความคิดเห็น ไฮไลต์ หรือเครื่องหมายวิจารณ์ไว้ในสำเนาที่แชร์ + เรียบเมื่อเครื่องหมายที่มองเห็นควรอยู่บนหน้าแต่ไม่ทำงานเหมือนคำอธิบายประกอบที่แก้ไขได้อีกต่อไป + เก็บสำเนาต้นฉบับไว้ เนื่องจากการนำคำอธิบายประกอบออกหรือทำให้ราบเรียบอาจย้อนกลับได้ยาก + จดจำข้อความ + แยกข้อความออกจากรูปภาพด้วยเครื่องมือ OCR และภาษาที่เลือกได้ + รับผลลัพธ์ OCR ที่ดีขึ้น + ความแม่นยำของ OCR ขึ้นอยู่กับความคมชัดของภาพ ข้อมูลภาษา คอนทราสต์ และการวางแนวหน้า ผลลัพธ์ที่ดีที่สุดมักจะมาจากการเตรียมภาพก่อน: ตัดจุดรบกวนออก หมุนตั้งตรง เพิ่มความสามารถในการอ่าน จากนั้นจึงจดจำข้อความ + ครอบตัดรูปภาพลงในพื้นที่ข้อความแล้วหมุนตั้งตรงก่อนที่จะจดจำ + เลือกภาษาที่ถูกต้องและดาวน์โหลดข้อมูลภาษาหากแอปร้องขอ + คัดลอกหรือแชร์ข้อความที่รู้จัก จากนั้นตรวจสอบชื่อ ตัวเลข และเครื่องหมายวรรคตอนด้วยตนเอง + เลือกข้อมูลภาษา OCR + ปรับปรุงการจดจำโดยการจับคู่สคริปต์ ภาษา และโมเดล OCR ที่ดาวน์โหลด + จับคู่ OCR กับข้อความ + ภาษา OCR ที่ไม่ถูกต้องอาจทำให้รูปภาพที่ดีดูเหมือนมีการจดจำได้ไม่ดี ข้อมูลภาษามีความสำคัญต่อสคริปต์ เครื่องหมายวรรคตอน ตัวเลข และเอกสารผสม ดังนั้นให้เลือกภาษาที่ปรากฏในรูปภาพ แทนที่จะเลือกภาษาของ UI ของโทรศัพท์ + เลือกภาษาหรือสคริปต์ที่ปรากฏในรูปภาพ ไม่ใช่แค่ภาษาของโทรศัพท์ + ดาวน์โหลดข้อมูลที่ขาดหายไปก่อนที่จะทำงานแบบออฟไลน์หรือประมวลผลเป็นชุด + สำหรับเอกสารภาษาผสม ให้รันภาษาที่สำคัญที่สุดก่อน และตรวจสอบชื่อและหมายเลขด้วยตนเอง + PDF ที่ค้นหาได้ + เขียนข้อความ OCR กลับเข้าไปในไฟล์เพื่อให้สามารถค้นหาและคัดลอกหน้าที่สแกนได้ + เปลี่ยนการสแกนให้เป็นเอกสารที่สามารถค้นหาได้ + OCR สามารถทำได้มากกว่าการคัดลอกข้อความไปยังคลิปบอร์ด เมื่อคุณเขียนข้อความที่รู้จักลงในไฟล์หรือ PDF ที่ค้นหาได้ รูปภาพของหน้าจะยังคงมองเห็นได้ในขณะที่ข้อความจะค้นหา เลือก จัดทำดัชนี หรือเก็บถาวรได้ง่ายขึ้น + ใช้เอาต์พุต PDF ที่ค้นหาได้สำหรับเอกสารที่สแกนซึ่งควรมีลักษณะเหมือนหน้าต้นฉบับ + ใช้การเขียนเป็นไฟล์เมื่อคุณต้องการเฉพาะข้อความที่แยกออกมา และไม่สนใจรูปภาพของหน้า + ตรวจสอบตัวเลข ชื่อ และตารางที่สำคัญด้วยตนเอง เนื่องจากสามารถค้นหาข้อความ OCR ได้แต่ยังคงไม่สมบูรณ์ + สแกนรหัส QR + อ่านเนื้อหา QR จากอินพุตกล้องหรือภาพที่บันทึกไว้เมื่อมี + อ่านรหัสอย่างปลอดภัย + รหัส QR อาจมีลิงก์ ข้อมูล Wi-Fi รายชื่อติดต่อ ข้อความ หรือเพย์โหลดอื่นๆ + เปิดสแกน QR Code แล้วเก็บโค้ดให้คม แบน และอยู่ในกรอบจนสุด + ตรวจสอบเนื้อหาที่ถอดรหัสก่อนที่จะเปิดลิงก์หรือคัดลอกข้อมูลที่ละเอียดอ่อน + หากการสแกนล้มเหลว ให้ปรับปรุงแสง ครอบตัดโค้ด หรือลองใช้รูปภาพต้นฉบับที่มีความละเอียดสูงกว่า + ใช้ Base64 และเช็คซัม + เข้ารหัสรูปภาพเป็นข้อความ ถอดรหัสข้อความกลับเป็นรูปภาพ และตรวจสอบความสมบูรณ์ของไฟล์ + ย้ายไปมาระหว่างไฟล์และข้อความ + Base64 มีประโยชน์สำหรับเพย์โหลดรูปภาพขนาดเล็ก ในขณะที่เช็คซัมช่วยยืนยันว่าไฟล์ไม่มีการเปลี่ยนแปลง เครื่องมือเหล่านี้มีประโยชน์มากที่สุดในขั้นตอนการทำงานด้านเทคนิค รายงานการสนับสนุน การถ่ายโอนไฟล์ และตำแหน่งที่ไฟล์ไบนารีจำเป็นต้องกลายเป็นข้อความชั่วคราว + ใช้เครื่องมือ Base64 เพื่อเข้ารหัสรูปภาพขนาดเล็กเมื่อเวิร์กโฟลว์ต้องการข้อความแทนไฟล์ไบนารี + ถอดรหัส Base64 จากแหล่งที่เชื่อถือได้เท่านั้น และตรวจสอบตัวอย่างก่อนบันทึก + ใช้เครื่องมือตรวจสอบผลรวมเมื่อคุณต้องการเปรียบเทียบไฟล์ที่ส่งออกหรือยืนยันการอัปโหลด/ดาวน์โหลด + แพ็คเกจหรือปกป้องข้อมูล + ใช้เครื่องมือ ZIP และการเข้ารหัสเพื่อรวมไฟล์หรือแปลงข้อมูลข้อความ + เก็บไฟล์ที่เกี่ยวข้องไว้ด้วยกัน + เครื่องมือการทำบรรจุภัณฑ์มีประโยชน์เมื่อเอาต์พุตหลายรายการควรเดินทางเป็นไฟล์เดียว นอกจากนี้ยังเหมาะสำหรับการเก็บรูปภาพ, PDF, บันทึก และข้อมูลเมตาที่สร้างขึ้นไว้ด้วยกัน เมื่อคุณต้องการส่งชุดผลลัพธ์ที่สมบูรณ์ + ใช้ ZIP เมื่อคุณต้องการส่งรูปภาพหรือเอกสารที่ส่งออกจำนวนมากพร้อมกัน + ใช้เครื่องมือการเข้ารหัสเฉพาะเมื่อคุณเข้าใจวิธีการที่เลือกและสามารถจัดเก็บคีย์ที่จำเป็นได้อย่างปลอดภัย + ทดสอบไฟล์เก็บถาวรที่สร้างขึ้นหรือข้อความที่แปลงก่อนที่จะลบไฟล์ต้นฉบับ + เช็คซัมในชื่อ + ใช้ชื่อไฟล์ที่อิงตามเช็คซัมเมื่อความเป็นเอกลักษณ์และความสมบูรณ์มีความสำคัญมากกว่าความสามารถในการอ่าน + ตั้งชื่อไฟล์ตามเนื้อหา + ชื่อไฟล์ Checksum มีประโยชน์เมื่อการส่งออกอาจมีชื่อที่มองเห็นได้ซ้ำกัน หรือเมื่อคุณต้องการพิสูจน์ว่าสองไฟล์เหมือนกัน พวกมันไม่เป็นมิตรสำหรับการเรียกดูด้วยตนเอง ดังนั้นจึงใช้สำหรับการเก็บถาวรทางเทคนิคและเวิร์กโฟลว์การตรวจสอบ + เปิดใช้งานการตรวจสอบเป็นชื่อไฟล์เมื่อเอกลักษณ์ของเนื้อหามีความสำคัญมากกว่าชื่อที่มนุษย์สามารถอ่านได้ + ใช้สำหรับการเก็บข้อมูลถาวร การขจัดข้อมูลซ้ำซ้อน การส่งออกซ้ำ หรือไฟล์ที่อาจอัปโหลดและดาวน์โหลดอีกครั้ง + จับคู่ชื่อเช็คซัมกับโฟลเดอร์หรือข้อมูลเมตา เมื่อผู้คนยังต้องการเข้าใจว่าไฟล์นั้นหมายถึงอะไร + เลือกสีจากภาพ + ตัวอย่างพิกเซล คัดลอกรหัสสี และนำสีมาใช้ซ้ำในจานสีหรือการออกแบบ + ตัวอย่างสีที่แน่นอน + การเลือกสีมีประโยชน์สำหรับการจับคู่การออกแบบ การแยกสีของแบรนด์ หรือการตรวจสอบค่ารูปภาพ การซูมมีความสำคัญเนื่องจากการลดรอยหยัก เงา และการบีบอัดอาจทำให้พิกเซลข้างเคียงแตกต่างจากสีที่คุณคาดหวังเล็กน้อย + เปิด เลือกสีจากรูปภาพ และเลือกรูปภาพต้นฉบับที่มีสีที่คุณต้องการ + ซูมเข้าก่อนสุ่มตัวอย่างรายละเอียดเล็กๆ เพื่อให้พิกเซลใกล้เคียงไม่ส่งผลต่อผลลัพธ์ + คัดลอกสีในรูปแบบที่ปลายทางของคุณกำหนด เช่น HEX, RGB หรือ HSV + สร้างจานสีและใช้ไลบรารีสี + แยกจานสี เรียกดูสีที่บันทึกไว้ และรักษาระบบการมองเห็นที่สอดคล้องกัน + สร้างจานสีที่ใช้ซ้ำได้ + จานสีช่วยให้กราฟิก ลายน้ำ และภาพวาดที่ส่งออกมีความสอดคล้องกันทางสายตา มีประโยชน์อย่างยิ่งเมื่อคุณใส่คำอธิบายประกอบภาพหน้าจอซ้ำๆ เตรียมทรัพย์สินของแบรนด์ หรือต้องการสีเดียวกันในเครื่องมือต่างๆ + สร้างจานสีจากรูปภาพหรือเพิ่มสีด้วยตนเองเมื่อคุณทราบค่าแล้ว + ตั้งชื่อหรือจัดระเบียบสีที่สำคัญเพื่อให้คุณสามารถค้นหาได้อีกครั้งในภายหลัง + ใช้ค่าที่คัดลอกในการวาด ลายน้ำ การไล่ระดับสี SVG หรือเครื่องมือการออกแบบภายนอก + สร้างการไล่ระดับสี + สร้างภาพไล่ระดับสีสำหรับพื้นหลัง พื้นที่ที่สำรองไว้ และการทดลองด้วยภาพ + ควบคุมการเปลี่ยนสี + เครื่องมือไล่ระดับสีมีประโยชน์เมื่อคุณต้องการรูปภาพที่สร้างขึ้นแทนที่จะแก้ไขรูปภาพที่มีอยู่ สิ่งเหล่านี้สามารถกลายเป็นพื้นหลังที่ดูสะอาดตา พื้นที่ว่าง วอลล์เปเปอร์ มาสก์ หรือเนื้อหาที่เรียบง่ายสำหรับเครื่องมือออกแบบภายนอก + เลือกประเภทการไล่ระดับสีและตั้งค่าสีที่สำคัญก่อน + ปรับทิศทาง การหยุด ความเรียบ และขนาดเอาต์พุตสำหรับกรณีการใช้งานเป้าหมาย + ส่งออกในรูปแบบที่ตรงกับปลายทาง ซึ่งมักจะเป็น PNG สำหรับกราฟิกที่สร้างขึ้นใหม่ทั้งหมด + การเข้าถึงสี + ใช้ตัวเลือกสี คอนทราสต์ และการตาบอดสีแบบไดนามิกเมื่อ UI อ่านยาก + ทำให้สีใช้งานได้ง่ายขึ้น + การตั้งค่าสีส่งผลต่อความสะดวกสบาย ความสามารถในการอ่าน และความรวดเร็วในการสแกนตัวควบคุม หากชิปเครื่องมือ แถบเลื่อน หรือสถานะที่เลือกทำให้แยกแยะได้ยาก ตัวเลือกธีมและการเข้าถึงอาจมีประโยชน์มากกว่าการเปลี่ยนโหมดมืดเพียงอย่างเดียว + ลองใช้สีไดนามิกเมื่อคุณต้องการให้แอปเป็นไปตามชุดวอลเปเปอร์ปัจจุบัน + เพิ่มคอนทราสต์หรือเปลี่ยนรูปแบบเมื่อปุ่มและพื้นผิวไม่โดดเด่นเพียงพอ + ใช้ตัวเลือกรูปแบบการตาบอดสีหากสถานะหรือสำเนียงบางอย่างแยกแยะได้ยาก + พื้นหลังอัลฟ่า + ควบคุมการแสดงตัวอย่างหรือสีเติมที่ใช้กับ PNG, WebP และเอาต์พุตที่คล้ายกัน + ทำความเข้าใจการแสดงตัวอย่างที่โปร่งใส + สีพื้นหลังสำหรับรูปแบบอัลฟ่าสามารถทำให้ตรวจสอบภาพที่โปร่งใสได้ง่ายขึ้น แต่สิ่งสำคัญคือต้องทราบว่าคุณกำลังเปลี่ยนพฤติกรรมการแสดงตัวอย่าง/เติม หรือทำให้ผลลัพธ์สุดท้ายแบนลง สิ่งนี้สำคัญสำหรับสติกเกอร์ โลโก้ และรูปภาพที่ถูกลบพื้นหลัง + ใช้รูปแบบที่รองรับอัลฟ่าก่อน เช่น PNG หรือ WebP เมื่อพิกเซลโปร่งใสจะต้องส่งออกได้ + ปรับการตั้งค่าสีพื้นหลังเมื่อมองเห็นขอบโปร่งใสกับพื้นผิวแอปได้ยาก + ส่งออกการทดสอบขนาดเล็กแล้วเปิดใหม่ในแอปอื่นเพื่อยืนยันว่าบันทึกความโปร่งใสหรือการเติมที่เลือกไว้แล้วหรือไม่ + การเลือกโมเดล AI + เลือกโมเดลตามประเภทรูปภาพและข้อบกพร่อง แทนที่จะเลือกโมเดลที่ใหญ่ที่สุดก่อน + จับคู่โมเดลกับความเสียหาย + เครื่องมือ AI สามารถดาวน์โหลดหรือนำเข้าโมเดล ONNX/ORT ที่แตกต่างกัน และแต่ละโมเดลจะได้รับการฝึกอบรมสำหรับปัญหาเฉพาะประเภท แบบจำลองสำหรับการบล็อก JPEG การล้างบรรทัดอนิเมะ การลดสัญญาณรบกวน การลบพื้นหลัง การปรับสี หรือการลดขนาด อาจให้ผลลัพธ์ที่แย่ลงเมื่อใช้กับรูปภาพที่ไม่ถูกต้อง + อ่านคำอธิบายโมเดลก่อนดาวน์โหลด เลือกรุ่นที่ตั้งชื่อปัญหาที่แท้จริงของคุณ เช่น การบีบอัด สัญญาณรบกวน ฮาล์ฟโทน ความเบลอ หรือการลบพื้นหลัง + เริ่มต้นด้วยโมเดลที่เล็กกว่าหรือเฉพาะเจาะจงมากขึ้นเมื่อทดสอบประเภทรูปภาพใหม่ จากนั้นเปรียบเทียบกับโมเดลที่หนักกว่าเฉพาะในกรณีที่ผลลัพธ์ไม่ดีพอ + เก็บเฉพาะรุ่นที่คุณใช้จริงเท่านั้น เนื่องจากรุ่นที่ดาวน์โหลดและนำเข้าอาจใช้พื้นที่จัดเก็บข้อมูลที่เห็นได้ชัดเจน + ทำความเข้าใจตระกูลตัวอย่าง + ชื่อโมเดลมักจะบอกเป็นนัยถึงเป้าหมาย: โมเดลสไตล์ RealESRGAN ระดับสูง, โมเดล SCUNet และ FBCNN ขจัดสัญญาณรบกวนหรือการบีบอัด โมเดลพื้นหลังสร้างมาสก์ และตัวกำหนดสีจะเพิ่มสีให้กับอินพุตระดับสีเทา โมเดลแบบกำหนดเองที่นำเข้าอาจมีประสิทธิภาพ แต่ควรตรงกับรูปร่างอินพุต/เอาท์พุตที่คาดหวัง และใช้รูปแบบ ONNX หรือ ORT ที่รองรับ + ใช้ตัวขยายสเกลเมื่อคุณต้องการพิกเซลมากขึ้น ใช้ตัวลดนอยส์เมื่อภาพมีจุดหยาบ และใช้ตัวลบสิ่งแปลกปลอมเมื่อบล็อกการบีบอัดหรือแถบคาดเป็นปัญหาหลัก + ใช้โมเดลพื้นหลังเพื่อการแยกวัตถุเท่านั้น ไม่เหมือนกับการลบวัตถุทั่วไปหรือการปรับปรุงรูปภาพ + นำเข้าโมเดลที่กำหนดเองจากแหล่งที่คุณเชื่อถือเท่านั้น และทดสอบกับรูปภาพขนาดเล็กก่อนใช้ไฟล์สำคัญ + พารามิเตอร์เอไอ + ปรับแต่งขนาดก้อน การทับซ้อนกัน และผู้ปฏิบัติงานแบบขนานเพื่อสร้างความสมดุลระหว่างคุณภาพ ความเร็ว และหน่วยความจำ + ความเร็วสมดุลกับความเสถียร + ขนาดชิ้นจะควบคุมว่าชิ้นส่วนรูปภาพมีขนาดใหญ่เพียงใดก่อนที่แบบจำลองจะประมวลผล การทับซ้อนกันผสมผสานชิ้นส่วนที่อยู่ใกล้เคียงเพื่อซ่อนเส้นขอบ ผู้ปฏิบัติงานแบบขนานสามารถเร่งการประมวลผลได้ แต่ผู้ปฏิบัติงานแต่ละคนจำเป็นต้องมีหน่วยความจำ ดังนั้นค่าที่สูงอาจทำให้รูปภาพขนาดใหญ่ไม่เสถียรบนโทรศัพท์ที่มี RAM ที่จำกัด + ใช้ส่วนที่ใหญ่ขึ้นเพื่อบริบทที่ราบรื่นยิ่งขึ้น เมื่ออุปกรณ์ของคุณจัดการโมเดลได้อย่างน่าเชื่อถือและรูปภาพไม่ใหญ่มาก + เพิ่มการทับซ้อนเมื่อมองเห็นเส้นขอบของก้อน แต่โปรดจำไว้ว่าการทับซ้อนกันมากขึ้นหมายถึงการทำงานซ้ำมากขึ้น + ยกคนงานแบบขนานหลังจากการทดสอบที่เสถียรเท่านั้น หากการประมวลผลช้าลง ทำให้อุปกรณ์ร้อนขึ้น หรือขัดข้อง ให้ลดคนงานลงก่อน + หลีกเลี่ยงสิ่งประดิษฐ์ชิ้นใหญ่ + การแบ่งส่วนช่วยให้แอปประมวลผลรูปภาพที่มีขนาดใหญ่กว่าที่โมเดลสามารถจัดการได้อย่างสะดวกสบายในคราวเดียว ข้อดีข้อเสียคือแต่ละไทล์มีบริบทโดยรอบน้อยลง ดังนั้นการเหลื่อมซ้อนที่ต่ำหรือส่วนที่เล็กเกินไปอาจสร้างเส้นขอบที่มองเห็นได้ การเปลี่ยนแปลงพื้นผิว หรือรายละเอียดที่ไม่สอดคล้องกันระหว่างพื้นที่ใกล้เคียง + เพิ่มการทับซ้อนหากคุณเห็นเส้นขอบสี่เหลี่ยม การเปลี่ยนแปลงพื้นผิวซ้ำๆ หรือการข้ามรายละเอียดระหว่างขอบเขตที่ประมวลผล + ลดขนาดก้อนหากกระบวนการล้มเหลวก่อนที่จะสร้างเอาต์พุต โดยเฉพาะกับรูปภาพขนาดใหญ่หรือแบบจำลองที่มีน้ำหนักมาก + บันทึกการทดสอบครอบตัดเล็กน้อยก่อนประมวลผลภาพเต็ม เพื่อให้คุณสามารถเปรียบเทียบพารามิเตอร์ได้อย่างรวดเร็ว + ความเสถียรของเอไอ + ลดขนาดชิ้นข้อมูล ผู้ปฏิบัติงาน และความละเอียดของแหล่งที่มาเมื่อการประมวลผล AI ขัดข้องหรือค้าง + ฟื้นตัวจากงาน AI ที่หนักหน่วง + การประมวลผล AI เป็นหนึ่งในเวิร์กโฟลว์ที่หนักที่สุดในแอป ข้อขัดข้อง เซสชันที่ล้มเหลว การค้าง หรือการรีสตาร์ทแอปกะทันหันมักจะหมายความว่าโมเดล ความละเอียดของแหล่งที่มา ขนาดก้อน หรือจำนวนผู้ปฏิบัติงานแบบขนานมีความต้องการสถานะอุปกรณ์ปัจจุบันมากเกินไป + หากแอปขัดข้องหรือล้มเหลวในการเปิดเซสชันแบบจำลอง ให้ลดผู้ปฏิบัติงานแบบขนานและขนาดก้อนลง จากนั้นลองใช้รูปภาพเดิมอีกครั้ง + ปรับขนาดรูปภาพที่มีขนาดใหญ่มากก่อนการประมวลผลแบบ AI เมื่อเป้าหมายไม่ต้องการความละเอียดดั้งเดิม + ปิดแอพที่มีน้ำหนักมากอื่นๆ ใช้โมเดลที่เล็กลง หรือประมวลผลไฟล์น้อยลงในคราวเดียวเมื่อความกดดันของหน่วยความจำยังคงกลับมา + วินิจฉัยความล้มเหลวของโมเดล + การรัน AI ที่ล้มเหลวไม่ได้หมายความว่าภาพจะแย่เสมอไป ไฟล์โมเดลอาจไม่สมบูรณ์ ไม่รองรับ มีขนาดใหญ่เกินไปสำหรับหน่วยความจำ หรือไม่ตรงกับการดำเนินการ เมื่อแอปรายงานเซสชันที่ล้มเหลว ให้ถือเป็นสัญญาณเพื่อลดความซับซ้อนของโมเดลและพารามิเตอร์ก่อน + ลบและดาวน์โหลดโมเดลใหม่หากล้มเหลวทันทีหลังจากดาวน์โหลด หรือมีพฤติกรรมเหมือนกับไฟล์เสียหาย + ลองใช้โมเดลที่ดาวน์โหลดได้ในตัวก่อนที่จะตำหนิโมเดลแบบกำหนดเองที่นำเข้า เนื่องจากโมเดลที่นำเข้าอาจมีอินพุตที่เข้ากันไม่ได้ + ใช้ App Logs หลังจากจำลองความล้มเหลว หากคุณต้องการรายงานข้อขัดข้องหรือข้อผิดพลาดของเซสชัน + การทดลองใน Shader Studio + ใช้เอฟเฟกต์เชเดอร์ GPU สำหรับการประมวลผลภาพขั้นสูงและรูปลักษณ์ที่กำหนดเอง + ทำงานอย่างระมัดระวังกับเชเดอร์ + Shader Studio นั้นทรงพลัง แต่โค้ดและพารามิเตอร์ของเชเดอร์จำเป็นต้องเปลี่ยนแปลงเล็กน้อยและทดสอบได้ ปฏิบัติต่อมันเหมือนเป็นเครื่องมือทดลอง: รักษาพื้นฐานการทำงาน เปลี่ยนแปลงทีละอย่าง และทดสอบด้วยขนาดรูปภาพที่แตกต่างกันก่อนที่จะเชื่อถือค่าที่ตั้งล่วงหน้า + เริ่มจากเชเดอร์ที่ใช้งานได้หรือเอฟเฟกต์ง่ายๆ ก่อนที่จะเพิ่มพารามิเตอร์ + เปลี่ยนพารามิเตอร์หรือบล็อกโค้ดทีละรายการ และตรวจสอบตัวอย่างเพื่อหาข้อผิดพลาด + ส่งออกค่าที่ตั้งไว้ล่วงหน้าหลังจากทดสอบกับขนาดรูปภาพที่แตกต่างกันเล็กน้อยเท่านั้น + สร้างงานศิลปะ SVG หรือ ASCII + สร้างเนื้อหาที่มีลักษณะคล้ายเวกเตอร์หรือรูปภาพที่เป็นข้อความสำหรับเอาต์พุตเชิงสร้างสรรค์ + เลือกประเภทเอาต์พุตโฆษณา + เครื่องมือ SVG และ ASCII ตอบสนองเป้าหมายที่แตกต่างกัน: กราฟิกที่ปรับขนาดได้หรือการแสดงผลในรูปแบบข้อความ ทั้งสองอย่างเป็น Conversion ที่สร้างสรรค์ ดังนั้นการดูตัวอย่างในขนาดสุดท้ายจึงมีความสำคัญมากกว่าการตัดสินผลลัพธ์จากภาพขนาดย่อเล็กๆ + ใช้ SVG Maker เมื่อผลลัพธ์ควรปรับขนาดได้หมดจดหรือนำกลับมาใช้ใหม่ในเวิร์กโฟลว์การออกแบบ + ใช้ ASCII Art เมื่อคุณต้องการแสดงข้อความที่มีสไตล์ให้กับรูปภาพ + ดูตัวอย่างในขนาดสุดท้ายเนื่องจากรายละเอียดเล็กๆ น้อยๆ อาจหายไปในเอาต์พุตที่สร้างขึ้น + สร้างพื้นผิวที่มีเสียงรบกวน + สร้างพื้นผิวตามขั้นตอนสำหรับพื้นหลัง มาสก์ ภาพซ้อนทับ หรือการทดลอง + ปรับแต่งเอาต์พุตขั้นตอน + การสร้างสัญญาณรบกวนจะสร้างภาพใหม่จากพารามิเตอร์ ดังนั้นการเปลี่ยนแปลงเล็กๆ น้อยๆ จึงให้ผลลัพธ์ที่แตกต่างกันมาก ซึ่งมีประโยชน์สำหรับพื้นผิว มาสก์ ภาพซ้อนทับ พื้นที่ที่สำรองไว้ หรือการทดสอบการบีบอัดด้วยรูปแบบที่มีรายละเอียด + เลือกประเภทสัญญาณรบกวนและขนาดเอาต์พุตก่อนปรับรายละเอียดอย่างละเอียด + ปรับขนาด ความเข้ม สี หรือเมล็ดจนกว่ารูปแบบจะเหมาะกับเป้าหมายการใช้งาน + บันทึกผลลัพธ์ที่เป็นประโยชน์และจดบันทึกพารามิเตอร์หากคุณต้องการสร้างพื้นผิวใหม่ในภายหลัง + โครงการมาร์กอัป + ใช้ไฟล์โปรเจ็กต์เมื่อคำอธิบายประกอบจำเป็นต้องแก้ไขได้หลังจากปิดแอป + ทำให้การแก้ไขแบบเลเยอร์สามารถนำมาใช้ซ้ำได้ + ไฟล์โปรเจ็กต์มาร์กอัปมีประโยชน์เมื่อภาพหน้าจอ ไดอะแกรม หรือรูปภาพคำแนะนำอาจต้องมีการเปลี่ยนแปลงในภายหลัง ไฟล์ PNG/JPEG ที่ส่งออกเป็นภาพขั้นสุดท้าย ในขณะที่ไฟล์โปรเจ็กต์จะรักษาเลเยอร์ที่แก้ไขได้และสถานะการแก้ไขสำหรับการปรับเปลี่ยนในอนาคต + ใช้เลเยอร์มาร์กอัปเมื่อคำอธิบายประกอบ คำบรรยายภาพ หรือองค์ประกอบเค้าโครงควรยังคงแก้ไขได้ + บันทึกหรือเปิดไฟล์โปรเจ็กต์อีกครั้งก่อนที่จะส่งออกรูปภาพสุดท้าย หากคุณคาดว่าจะมีการแก้ไขเพิ่มเติม + ส่งออกรูปภาพปกติเมื่อคุณพร้อมที่จะแชร์เวอร์ชันสุดท้ายที่แบนราบแล้วเท่านั้น + เข้ารหัสไฟล์ + ป้องกันไฟล์ด้วยรหัสผ่านและทดสอบการถอดรหัสก่อนที่จะลบสำเนาต้นฉบับใดๆ + จัดการเอาต์พุตที่เข้ารหัสอย่างระมัดระวัง + เครื่องมือเข้ารหัสสามารถป้องกันไฟล์ด้วยการเข้ารหัสด้วยรหัสผ่าน แต่เครื่องมือเหล่านี้ไม่สามารถแทนที่การจดจำคีย์หรือการสำรองข้อมูลได้ การเข้ารหัสมีประโยชน์สำหรับการขนส่งและการจัดเก็บ ในขณะที่ ZIP จะดีกว่าเมื่อเป้าหมายหลักคือการรวมเอาเอาต์พุตหลายตัวเข้าด้วยกัน + เข้ารหัสสำเนาก่อนและเก็บต้นฉบับไว้จนกว่าคุณจะทดสอบว่าการถอดรหัสใช้งานได้กับรหัสผ่านที่คุณเก็บไว้ + ใช้ตัวจัดการรหัสผ่านหรือสถานที่ที่ปลอดภัยอื่นสำหรับกุญแจ แอปไม่สามารถเดารหัสผ่านที่หายไปให้คุณได้ + แชร์ไฟล์ที่เข้ารหัสเฉพาะกับผู้ที่มีรหัสผ่านและรู้ว่าเครื่องมือหรือวิธีการใดควรถอดรหัสไฟล์เหล่านั้น + จัดเตรียมเครื่องมือ + จัดกลุ่ม สั่งซื้อ ค้นหา และปักหมุดเครื่องมือเพื่อให้หน้าจอหลักตรงกับขั้นตอนการทำงานจริงของคุณ + ทำให้หน้าจอหลักเร็วขึ้น + การตั้งค่าการจัดเรียงเครื่องมือนั้นคุ้มค่าที่จะปรับเมื่อคุณทราบว่าคุณใช้เครื่องมือใดมากที่สุด การจัดกลุ่มช่วยในการสำรวจ ลำดับที่กำหนดเองช่วยให้ความจำของกล้ามเนื้อ และรายการโปรดช่วยให้เข้าถึงเครื่องมือประจำวันได้แม้ว่ารายการทั้งหมดจะมีจำนวนมากก็ตาม + ใช้โหมดจัดกลุ่มในขณะที่เรียนรู้แอปหรือเมื่อคุณต้องการเครื่องมือที่จัดตามประเภทงาน + สลับไปใช้ลำดับที่กำหนดเองเมื่อคุณรู้จักเครื่องมือที่ใช้บ่อยอยู่แล้วและต้องการการเลื่อนน้อยลง + ใช้การตั้งค่าการค้นหาและรายการโปรดร่วมกันสำหรับเครื่องมือหายากที่คุณจำชื่อได้ แต่ไม่ต้องการให้มองเห็นตลอดเวลา + จัดการไฟล์ขนาดใหญ่ + หลีกเลี่ยงการส่งออกที่ช้า ความกดดันของหน่วยความจำ และเอาต์พุตขนาดใหญ่โดยไม่คาดคิด + ลดงานก่อนส่งออก + รูปภาพที่มีขนาดใหญ่มาก แบทช์ที่ยาว และรูปแบบคุณภาพสูงอาจใช้หน่วยความจำและเวลามากขึ้น การแก้ไขที่เร็วที่สุดคือการลดปริมาณงานก่อนการดำเนินการที่หนักที่สุด โดยไม่รอให้อินพุตขนาดใหญ่เท่าเดิมเสร็จสิ้น + ปรับขนาดภาพขนาดใหญ่ก่อนที่จะใช้ฟิลเตอร์ขนาดใหญ่ OCR หรือการแปลง PDF + ใช้ชุดที่น้อยกว่าก่อนเพื่อยืนยันการตั้งค่า จากนั้นจึงประมวลผลส่วนที่เหลือด้วยการตั้งค่าล่วงหน้าเดียวกัน + คุณภาพลดลงหรือเปลี่ยนรูปแบบเมื่อไฟล์เอาต์พุตมีขนาดใหญ่กว่าที่คาดไว้ + แคชและการแสดงตัวอย่าง + ทำความเข้าใจการแสดงตัวอย่างที่สร้างขึ้น การล้างแคช และการใช้พื้นที่เก็บข้อมูลหลังจากเซสชันที่มีปริมาณมาก + รักษาพื้นที่จัดเก็บและความเร็วให้สมดุล + การสร้างตัวอย่างและแคชสามารถทำให้การเรียกดูซ้ำเร็วขึ้น แต่เซสชันการแก้ไขจำนวนมากอาจทำให้ข้อมูลชั่วคราวทิ้งไว้ การตั้งค่าแคชจะช่วยเมื่อแอปทำงานช้า พื้นที่เก็บข้อมูลเหลือน้อย หรือการแสดงตัวอย่างดูเก่าหลังจากการทดลองหลายครั้ง + เปิดใช้งานการแสดงตัวอย่างไว้เมื่อเรียกดูรูปภาพจำนวนมากมีความสำคัญมากกว่าการลดพื้นที่เก็บข้อมูลชั่วคราว + ล้างแคชหลังจากแบตช์จำนวนมาก การทดลองที่ล้มเหลว หรือเซสชันที่ยาวนานด้วยไฟล์ความละเอียดสูงจำนวนมาก + ใช้การล้างแคชอัตโนมัติ หากคุณต้องการล้างข้อมูลพื้นที่เก็บข้อมูลมากกว่าการเก็บตัวอย่างไว้อุ่นใจระหว่างการเปิดตัว + ปรับพฤติกรรมหน้าจอ + ใช้ตัวเลือกเต็มหน้าจอ โหมดปลอดภัย ความสว่าง และแถบระบบสำหรับงานที่เน้น + ทำให้อินเทอร์เฟซเหมาะสมกับสถานการณ์ + การตั้งค่าหน้าจอจะช่วยคุณเมื่อคุณแก้ไขในแนวนอน นำเสนอภาพส่วนตัว หรือต้องการความสว่างที่สม่ำเสมอ ไม่จำเป็นสำหรับการใช้งานปกติ แต่สามารถแก้ไขสถานการณ์ที่น่าอึดอัดใจ เช่น การตรวจสอบ QR การแสดงตัวอย่างส่วนตัว หรือการแก้ไขภูมิทัศน์ที่คับแคบ + ใช้การตั้งค่าเต็มหน้าจอหรือแถบระบบเมื่อการแก้ไขพื้นที่มีความสำคัญมากกว่าการนำทาง Chrome + ใช้โหมดปลอดภัยเมื่อรูปภาพส่วนตัวไม่ควรปรากฏในภาพหน้าจอหรือตัวอย่างแอป + ใช้การบังคับใช้ความสว่างสำหรับเครื่องมือที่ตรวจสอบรูปภาพสีเข้มหรือโค้ด QR ได้ยาก + รักษาความโปร่งใส + ป้องกันไม่ให้พื้นหลังโปร่งใสเปลี่ยนเป็นสีขาว สีดำ หรือแบน + ใช้เอาต์พุตแบบรับรู้อัลฟ่า + ความโปร่งใสยังคงอยู่เฉพาะเมื่อเครื่องมือที่เลือกและรูปแบบเอาต์พุตรองรับอัลฟ่าเท่านั้น หากพื้นหลังที่ส่งออกเปลี่ยนเป็นสีขาวหรือสีดำ ปัญหามักจะอยู่ที่รูปแบบเอาต์พุต การตั้งค่าการเติม/พื้นหลัง หรือแอปปลายทางที่ทำให้รูปภาพแบน + เลือก PNG หรือ WebP เมื่อส่งออกรูปภาพ สติกเกอร์ โลโก้ หรือภาพซ้อนทับที่ถูกลบออกจากพื้นหลัง + หลีกเลี่ยง JPEG สำหรับเอาต์พุตแบบโปร่งใสเนื่องจากไม่ได้จัดเก็บช่องอัลฟา + ตรวจสอบการตั้งค่าที่เกี่ยวข้องกับสีพื้นหลังสำหรับรูปแบบอัลฟ่า หากตัวอย่างแสดงพื้นหลังที่เติมไว้ + แก้ไขปัญหาการแชร์และการนำเข้า + ใช้การตั้งค่าตัวเลือกและรูปแบบที่รองรับเมื่อไฟล์ไม่ปรากฏหรือเปิดอย่างถูกต้อง + นำไฟล์ไปไว้ในแอป + ปัญหาการนำเข้ามักเกิดจากการอนุญาตของผู้ให้บริการ รูปแบบที่ไม่รองรับ หรือพฤติกรรมของตัวเลือก ไฟล์ที่แชร์จากแอปคลาวด์และโปรแกรมส่งข้อความอาจเป็นไฟล์ชั่วคราว ดังนั้นการเลือกแหล่งที่มาถาวรจึงเชื่อถือได้มากขึ้นสำหรับการแก้ไขที่ยาวนานขึ้น + ลองเปิดไฟล์ผ่านตัวเลือกระบบแทนทางลัดไฟล์ล่าสุด + หากเครื่องมือหนึ่งไม่รองรับรูปแบบ ให้แปลงก่อนหรือเปิดเครื่องมือเฉพาะเจาะจงมากขึ้น + ตรวจสอบการตั้งค่าตัวเลือกรูปภาพและตัวเรียกใช้งานหากขั้นตอนการแชร์หรือการเปิดเครื่องมือโดยตรงทำงานแตกต่างไปจากที่คาดไว้ + ออกจากการยืนยัน + หลีกเลี่ยงการสูญเสียการแก้ไขที่ไม่ได้บันทึกไว้เมื่อท่าทาง การกดกลับ หรือสวิตช์เครื่องมือเกิดขึ้นโดยไม่ได้ตั้งใจ + ปกป้องงานที่ยังไม่เสร็จ + การยืนยันการออกจากเครื่องมือมีประโยชน์เมื่อคุณใช้การนำทางด้วยท่าทาง แก้ไขไฟล์ขนาดใหญ่ หรือสลับระหว่างแอปบ่อยครั้ง โดยจะเพิ่มการหยุดชั่วคราวเล็กน้อยก่อนที่จะออกจากเครื่องมือ ดังนั้นการตั้งค่าและการดูตัวอย่างที่ยังทำไม่เสร็จจะไม่สูญหายไปโดยไม่ได้ตั้งใจ + เปิดใช้งานการยืนยันหากท่าทางสัมผัสด้านหลังโดยไม่ได้ตั้งใจเคยปิดเครื่องมือก่อนที่คุณจะส่งออก + ปิดการใช้งานไว้หากคุณทำการแปลงขั้นตอนเดียวอย่างรวดเร็วเป็นส่วนใหญ่และต้องการการนำทางที่เร็วขึ้น + ใช้ร่วมกับการตั้งค่าล่วงหน้าสำหรับเวิร์กโฟลว์ที่ยาวนานขึ้น ซึ่งการสร้างการตั้งค่าใหม่อาจสร้างความรำคาญได้ + ใช้บันทึกเมื่อรายงานปัญหา + รวบรวมรายละเอียดที่เป็นประโยชน์ก่อนเปิดประเด็นหรือติดต่อผู้พัฒนา + รายงานปัญหาเกี่ยวกับบริบท + รายงานที่ชัดเจนพร้อมขั้นตอน เวอร์ชันแอป ประเภทอินพุต และบันทึกทำให้วินิจฉัยได้ง่ายกว่ามาก บันทึกจะมีประโยชน์มากที่สุดทันทีหลังจากเกิดปัญหาขึ้นอีกครั้ง เนื่องจากจะทำให้บริบทโดยรอบมีความสดใหม่อยู่เสมอ + จดชื่อเครื่องมือ การดำเนินการที่แน่นอน และไม่ว่าจะเกิดขึ้นกับไฟล์เดียวหรือทุกไฟล์ + เปิดบันทึกแอปหลังจากเกิดปัญหาซ้ำแล้วแชร์ผลลัพธ์บันทึกที่เกี่ยวข้องเมื่อเป็นไปได้ + แนบภาพหน้าจอหรือไฟล์ตัวอย่างเมื่อไม่มีข้อมูลส่วนตัวเท่านั้น + การประมวลผลเบื้องหลังหยุดลง + Android ไม่ยอมให้การแจ้งเตือนการประมวลผลเบื้องหลังเริ่มทันเวลา นี่เป็นข้อจำกัดด้านเวลาของระบบที่ไม่สามารถแก้ไขได้อย่างน่าเชื่อถือ รีสตาร์ทแอปแล้วลองอีกครั้ง การเปิดแอปไว้และการอนุญาตการแจ้งเตือนหรือการทำงานเบื้องหลังอาจช่วยได้ + ข้ามการเลือกไฟล์ + เปิดเครื่องมือได้เร็วขึ้นเมื่ออินพุตมักจะมาจากการแชร์ คลิปบอร์ด หรือหน้าจอก่อนหน้า + ทำให้เครื่องมือเปิดซ้ำเร็วขึ้น + ข้ามการเลือกไฟล์จะเปลี่ยนขั้นตอนแรกของเครื่องมือที่รองรับ จะมีประโยชน์เมื่อคุณมักจะเข้าสู่เครื่องมือด้วยรูปภาพที่เลือกไว้แล้วหรือจากการแชร์ของ Android แต่อาจรู้สึกสับสนได้หากคุณคาดหวังว่าทุกเครื่องมือจะขอไฟล์ทันที + เปิดใช้งานเฉพาะเมื่อโฟลว์ปกติของคุณให้อิมเมจต้นฉบับก่อนที่เครื่องมือจะเปิดขึ้นเท่านั้น + ปิดการใช้งานไว้ในขณะที่เรียนรู้แอป เนื่องจากการเลือกที่ชัดเจนทำให้ทุกเครื่องมือเข้าใจได้ง่ายขึ้น + หากเครื่องมือเปิดขึ้นโดยไม่มีการป้อนข้อมูลที่ชัดเจน ให้ปิดใช้งานการตั้งค่านี้หรือเริ่มจากแชร์/เปิดด้วย เพื่อให้ Android ส่งไฟล์ก่อน + เปิดตัวแก้ไขก่อน + ข้ามการแสดงตัวอย่างเมื่อรูปภาพที่แชร์ควรเข้าสู่การแก้ไขโดยตรง + เลือกดูตัวอย่างหรือแก้ไขเป็นจุดแรก + Open Editแทนที่จะแสดงตัวอย่างมีไว้สำหรับผู้ใช้ที่รู้อยู่แล้วว่าต้องการแก้ไขรูปภาพ การดูตัวอย่างจะปลอดภัยยิ่งขึ้นเมื่อคุณตรวจสอบไฟล์จากการแชทหรือที่เก็บข้อมูลบนคลาวด์ การแก้ไขโดยตรงทำได้เร็วกว่าสำหรับภาพหน้าจอ การครอบตัดอย่างรวดเร็ว คำอธิบายประกอบ และการแก้ไขแบบครั้งเดียว + เปิดใช้งานการแก้ไขโดยตรงหากรูปภาพที่แชร์ส่วนใหญ่จำเป็นต้องครอบตัด มาร์กอัป ตัวกรอง หรือส่งออกการเปลี่ยนแปลงในทันที + ออกจากการแสดงตัวอย่างก่อนเมื่อคุณตรวจสอบข้อมูลเมตา ขนาด ความโปร่งใส หรือคุณภาพของภาพบ่อยครั้งก่อนตัดสินใจ + ใช้สิ่งนี้ร่วมกับรายการโปรดเพื่อให้รูปภาพที่แชร์เข้าใกล้เครื่องมือที่คุณใช้จริง + การป้อนข้อความที่กำหนดไว้ล่วงหน้า + ป้อนค่าที่ตั้งไว้ล่วงหน้าให้ถูกต้องแทนที่จะปรับการควบคุมซ้ำๆ + ใช้ค่าที่แม่นยำเมื่อแถบเลื่อนทำงานช้า + การป้อนข้อความที่กำหนดไว้ล่วงหน้าจะช่วยเมื่อคุณต้องการตัวเลขที่แน่นอนสำหรับงานซ้ำๆ เช่น ขนาดรูปภาพโซเชียล ระยะขอบเอกสาร เป้าหมายการบีบอัด ความกว้างของเส้น หรือค่าอื่นๆ ที่น่ารำคาญเมื่อเข้าถึงด้วยท่าทาง นอกจากนี้ยังมีประโยชน์บนหน้าจอขนาดเล็กที่การเลื่อนตัวเลื่อนอย่างละเอียดทำได้ยากขึ้น + เปิดใช้งานการป้อนข้อความหากคุณใช้ขนาด เปอร์เซ็นต์ ค่าคุณภาพ หรือพารามิเตอร์ของเครื่องมือซ้ำบ่อยๆ + บันทึกค่าเป็นค่าที่ตั้งไว้ล่วงหน้าหลังจากพิมพ์หนึ่งครั้ง จากนั้นใช้ซ้ำแทนที่จะสร้างการตั้งค่าเดิมขึ้นมาใหม่ + คงการควบคุมด้วยท่าทางไว้สำหรับการปรับแต่งภาพคร่าวๆ และค่าที่พิมพ์สำหรับข้อกำหนดเอาต์พุตที่เข้มงวด + บันทึกใกล้เคียงต้นฉบับ + วางไฟล์ที่สร้างขึ้นข้างรูปภาพต้นฉบับเมื่อบริบทของโฟลเดอร์มีความสำคัญ + เก็บผลลัพธ์ไว้กับแหล่งที่มา + บันทึกไปยังโฟลเดอร์ต้นฉบับมีประโยชน์สำหรับโฟลเดอร์กล้อง โฟลเดอร์โปรเจ็กต์ การสแกนเอกสาร และแบทช์ไคลเอนต์ที่สำเนาที่แก้ไขควรอยู่ถัดจากแหล่งที่มา มันอาจจะเป็นระเบียบน้อยกว่าโฟลเดอร์เอาท์พุตเฉพาะ ดังนั้นให้รวมเข้ากับส่วนต่อท้ายหรือรูปแบบชื่อไฟล์ที่ชัดเจน + เปิดใช้งานเมื่อโฟลเดอร์ต้นทางแต่ละโฟลเดอร์มีความหมายอยู่แล้ว และเอาต์พุตควรยังคงจัดกลุ่มอยู่ที่นั่น + ใช้ส่วนต่อท้ายชื่อไฟล์ คำนำหน้า การประทับเวลา หรือรูปแบบ เพื่อให้สำเนาที่แก้ไขแล้วแยกออกจากต้นฉบับได้ง่าย + ปิดการใช้งานสำหรับชุดผสม เมื่อคุณต้องการให้ไฟล์ที่สร้างขึ้นทั้งหมดรวบรวมไว้ในโฟลเดอร์ส่งออกเดียว + ข้ามเอาต์พุตที่ใหญ่กว่า + หลีกเลี่ยงการบันทึก Conversion ที่หนักกว่าแหล่งที่มาโดยไม่คาดคิด + ปกป้องแบตช์จากขนาดที่ไม่คาดคิดที่น่าประหลาดใจ + การแปลงบางอย่างทำให้ไฟล์มีขนาดใหญ่ขึ้น โดยเฉพาะภาพหน้าจอ PNG, รูปภาพที่บีบอัดแล้ว, WebP คุณภาพสูง หรือรูปภาพที่เก็บรักษาข้อมูลเมตาไว้ อนุญาตให้ข้ามหากใหญ่กว่าจะป้องกันไม่ให้เอาต์พุตเหล่านั้นแทนที่แหล่งที่เล็กกว่าในเวิร์กโฟลว์ที่เป้าหมายหลักคือการลดขนาด + เปิดใช้งานเมื่อการส่งออกมีจุดมุ่งหมายเพื่อบีบอัด ปรับขนาด หรือเตรียมไฟล์สำหรับขีดจำกัดการอัปโหลด + ตรวจสอบไฟล์ที่ข้ามไปหลังจากแบทช์ อาจได้รับการปรับให้เหมาะสมแล้วหรืออาจต้องใช้รูปแบบอื่น + ปิดใช้งานเมื่อคุณภาพ ความโปร่งใส หรือความเข้ากันได้ของรูปแบบมีความสำคัญมากกว่าขนาดไฟล์สุดท้าย + คลิปบอร์ดและลิงก์ + ควบคุมอินพุตคลิปบอร์ดอัตโนมัติและการแสดงตัวอย่างลิงก์เพื่อให้เครื่องมือที่ใช้ข้อความเร็วขึ้น + ทำให้สามารถคาดเดาการป้อนข้อมูลอัตโนมัติได้ + การตั้งค่าคลิปบอร์ดและลิงก์สะดวกสำหรับ QR, Base64, URL และเวิร์กโฟลว์ที่มีข้อความจำนวนมาก แต่การป้อนข้อมูลอัตโนมัติควรเป็นไปตามเจตนา หากดูเหมือนว่าแอปจะกรอกข้อมูลในฟิลด์ด้วยตัวเอง ให้ตรวจสอบลักษณะการวางคลิปบอร์ด หากการ์ดลิงก์มีสัญญาณรบกวนหรือช้า ให้ปรับลักษณะการแสดงตัวอย่างลิงก์ + อนุญาตให้วางคลิปบอร์ดอัตโนมัติเมื่อคุณคัดลอกข้อความบ่อยครั้งและเปิดเครื่องมือที่ตรงกันทันที + ปิดใช้งานการวางอัตโนมัติหากเนื้อหาคลิปบอร์ดส่วนตัวปรากฏในเครื่องมือที่คุณไม่คาดคิด + ปิดการแสดงตัวอย่างลิงก์เมื่อการดึงข้อมูลตัวอย่างช้า กวนใจ หรือไม่จำเป็นสำหรับเวิร์กโฟลว์ของคุณ + การควบคุมที่กะทัดรัด + ใช้ตัวเลือกที่หนาแน่นยิ่งขึ้นและการจัดวางการตั้งค่าที่รวดเร็วเมื่อพื้นที่หน้าจอคับแคบ + ปรับการควบคุมได้มากขึ้นบนหน้าจอขนาดเล็ก + ตัวเลือกขนาดกะทัดรัดและการวางตำแหน่งการตั้งค่าที่รวดเร็วช่วยได้ในโทรศัพท์ขนาดเล็ก การแก้ไขแนวนอน และเครื่องมือที่มีพารามิเตอร์มากมาย ข้อดีคือการควบคุมจะรู้สึกหนาแน่นขึ้น ดังนั้นวิธีที่ดีที่สุดคือหลังจากที่คุณรู้จักตัวเลือกทั่วไปแล้ว + เปิดใช้งานตัวเลือกแบบกะทัดรัดเมื่อรู้สึกว่ากล่องโต้ตอบหรือรายการตัวเลือกสูงเกินไปสำหรับหน้าจอ + ปรับด้านการตั้งค่าอย่างรวดเร็วหากเข้าถึงส่วนควบคุมเครื่องมือได้ง่ายกว่าจากด้านหนึ่งของจอแสดงผล + กลับสู่การควบคุมที่กว้างขึ้นหากคุณยังคงเรียนรู้แอปหรือแตะตัวเลือกที่หนาแน่นพลาดบ่อยครั้ง + โหลดภาพจากเว็บ + วางหน้าหรือ URL รูปภาพ ดูตัวอย่างภาพที่แยกวิเคราะห์ จากนั้นบันทึกหรือแชร์ผลลัพธ์ที่เลือก + จงใจใช้รูปภาพบนเว็บ + การโหลดรูปภาพบนเว็บจะแยกวิเคราะห์แหล่งที่มาของรูปภาพจาก URL ที่ให้ไว้ ดูตัวอย่างรูปภาพแรกที่มีอยู่ และช่วยให้คุณสามารถบันทึกหรือแชร์รูปภาพที่แยกวิเคราะห์ที่เลือกได้ เว็บไซต์บางแห่งใช้ลิงก์ชั่วคราว มีการป้องกัน หรือสร้างด้วยสคริปต์ ดังนั้นหน้าที่ใช้งานได้ในเบราว์เซอร์อาจยังคงไม่แสดงภาพที่ใช้งานได้ + วาง URL ของรูปภาพโดยตรงหรือ URL ของหน้า และรอให้รายการรูปภาพที่แยกวิเคราะห์ปรากฏขึ้น + ใช้ตัวควบคุมกรอบหรือส่วนที่เลือกเมื่อเพจประกอบด้วยรูปภาพหลายรูป และคุณต้องการเพียงบางรูปเท่านั้น + หากไม่มีสิ่งใดโหลด ให้ลองใช้ลิงก์รูปภาพโดยตรงหรือดาวน์โหลดผ่านเบราว์เซอร์ก่อน เว็บไซต์อาจบล็อกการแยกวิเคราะห์หรือใช้ลิงก์ส่วนตัว + เลือกประเภทการปรับขนาด + ทำความเข้าใจอย่างชัดเจน ยืดหยุ่น ครอบตัด และพอดี ก่อนที่จะบังคับรูปภาพให้เป็นมิติใหม่ + ควบคุมรูปร่าง แคนวาส และอัตราส่วนภาพ + ประเภทการปรับขนาดจะตัดสินใจว่าจะเกิดอะไรขึ้นเมื่อความกว้างและความสูงที่ร้องขอไม่ตรงกับอัตราส่วนเดิม เป็นความแตกต่างระหว่างการยืดพิกเซล การรักษาสัดส่วน การครอบตัดพื้นที่เพิ่มเติม หรือการเพิ่มผืนผ้าใบพื้นหลัง + ใช้ชัดเจนเฉพาะเมื่อยอมรับการบิดเบือนได้หรือแหล่งที่มามีอัตราส่วนภาพเดียวกันกับเป้าหมายอยู่แล้ว + ใช้แบบยืดหยุ่นเพื่อรักษาสัดส่วนโดยการปรับขนาดจากด้านที่สำคัญ โดยเฉพาะอย่างยิ่งสำหรับภาพถ่ายและชุดผสม + ใช้ครอบตัดสำหรับเฟรมที่เข้มงวดโดยไม่มีเส้นขอบ หรือใช้พอดีเมื่อต้องมองเห็นทั้งรูปภาพภายในผืนผ้าใบคงที่ + เลือกโหมดขนาดภาพ + เลือกตัวกรองการสุ่มตัวอย่างใหม่ที่ควบคุมความคมชัด ความราบรื่น ความเร็ว และความผิดปกติของขอบ + ปรับขนาดโดยไม่เกิดความนุ่มนวลโดยไม่ตั้งใจ + โหมดขนาดภาพควบคุมวิธีคำนวณพิกเซลใหม่ระหว่างการปรับขนาด โหมดธรรมดานั้นรวดเร็วและคาดเดาได้ ในขณะที่ฟิลเตอร์ขั้นสูงสามารถรักษารายละเอียดได้ดีกว่า แต่อาจทำให้ขอบคมชัดขึ้น สร้างรัศมี หรือใช้เวลานานกว่าในภาพขนาดใหญ่ + ใช้ Basic หรือ Bilinear เพื่อการปรับขนาดอย่างรวดเร็วทุกวัน เมื่อผลลัพธ์ต้องเล็กลงและสะอาดเท่านั้น + ใช้โหมด Lanczos, Robidoux, Spline หรือ EWA เมื่อรายละเอียด ข้อความ หรือการลดขนาดคุณภาพสูงมีความสำคัญ + ใช้ Nearest สำหรับภาพพิกเซล ไอคอน และกราฟิกที่มีขอบแข็ง ซึ่งพิกเซลที่เบลออาจทำให้รูปลักษณ์เสียไป + ปรับขนาดพื้นที่สี + ตัดสินใจว่าจะผสมสีอย่างไรในขณะที่พิกเซลถูกสุ่มตัวอย่างใหม่ในระหว่างการปรับขนาด + รักษาการไล่ระดับสีและขอบให้เป็นธรรมชาติ + พื้นที่สีมาตราส่วนจะเปลี่ยนคณิตศาสตร์ที่ใช้ขณะปรับขนาด รูปภาพส่วนใหญ่ใช้ค่าเริ่มต้นได้ดี แต่ช่องว่างเชิงเส้นหรือการรับรู้สามารถทำให้การไล่ระดับสี ขอบสีเข้ม และสีอิ่มตัวดูสะอาดตาขึ้นหลังจากการปรับขนาดที่แข็งแกร่ง + ปล่อยให้เป็นค่าเริ่มต้นเมื่อความเร็วและความเข้ากันได้มีความสำคัญมากกว่าความแตกต่างของสีเล็กๆ น้อยๆ + ลองใช้โหมดเชิงเส้นหรือการรับรู้เมื่อโครงร่างสีเข้ม ภาพหน้าจอ UI การไล่ระดับสี หรือแถบสีดูผิดหลังจากปรับขนาด + เปรียบเทียบก่อนและหลังบนหน้าจอเป้าหมายจริง เนื่องจากการเปลี่ยนแปลงปริภูมิสีอาจละเอียดอ่อนและขึ้นอยู่กับเนื้อหา + จำกัดโหมดการปรับขนาด + รู้ว่าเมื่อใดควรข้าม เข้ารหัสใหม่ หรือย่อขนาดรูปภาพที่เกินขีดจำกัดให้พอดี + เลือกสิ่งที่เกิดขึ้นที่ขีดจำกัด + Limit Resize ไม่ใช่แค่เครื่องมือสำหรับรูปภาพขนาดเล็กเท่านั้น โหมดนี้จะกำหนดว่าแอปจะทำอะไรเมื่อแหล่งที่มาอยู่ภายในขีดจำกัดของคุณอยู่แล้ว หรือเมื่อมีเพียงด้านเดียวที่ฝ่าฝืนกฎ ซึ่งเป็นสิ่งสำคัญสำหรับโฟลเดอร์แบบผสมและการเตรียมการอัปโหลด + ใช้ข้ามเมื่อรูปภาพที่อยู่ภายในขีดจำกัดไม่ควรถูกแตะต้องและไม่สามารถส่งออกได้อีก + ใช้ Recode เมื่อมิติข้อมูลเป็นที่ยอมรับ แต่คุณยังต้องการรูปแบบ ลักษณะการทำงานของข้อมูลเมตา คุณภาพ หรือชื่อไฟล์ใหม่ + ใช้การซูมเมื่อรูปภาพที่เกินขีดจำกัดควรถูกลดขนาดลงตามสัดส่วนจนกว่าจะพอดีกับช่องที่อนุญาต + เป้าหมายอัตราส่วนภาพ + หลีกเลี่ยงการยืดหน้า ขอบตัด และขอบที่ไม่คาดคิดในขนาดงานพิมพ์ที่เข้มงวด + จับคู่รูปร่างก่อนปรับขนาด + ความกว้างและความสูงเป็นเพียงครึ่งหนึ่งของเป้าหมายการปรับขนาด อัตราส่วนภาพจะกำหนดว่ารูปภาพจะพอดีตามธรรมชาติ ต้องการการครอบตัด หรือต้องใช้ผืนผ้าใบรอบๆ หรือไม่ การตรวจสอบรูปร่างก่อนจะช่วยป้องกันผลลัพธ์การปรับขนาดที่น่าประหลาดใจที่สุด + เปรียบเทียบรูปร่างแหล่งที่มากับรูปร่างเป้าหมายก่อนที่จะเลือกชัดเจน ครอบตัด หรือพอดี + ครอบตัดก่อนเมื่อตัวแบบอาจสูญเสียพื้นหลังเพิ่มเติมและปลายทางจำเป็นต้องมีกรอบที่เข้มงวด + ใช้พอดีหรือยืดหยุ่นเมื่อทุกส่วนของรูปภาพต้นฉบับต้องยังคงมองเห็นได้ + ปรับขนาดหรูหราหรือปกติ + รู้ว่าเมื่อใดการเพิ่มพิกเซลต้องใช้ AI และเมื่อใดที่การปรับขนาดแบบธรรมดาก็เพียงพอแล้ว + อย่าสับสนขนาดกับรายละเอียด + การปรับขนาดปกติจะเปลี่ยนขนาดโดยการประมาณพิกเซล มันไม่สามารถประดิษฐ์รายละเอียดที่แท้จริงได้ AI upscale สามารถสร้างรายละเอียดที่ดูคมชัดยิ่งขึ้น แต่จะช้ากว่าและอาจทำให้พื้นผิวเห็นภาพหลอน ดังนั้นตัวเลือกที่เหมาะสมจึงขึ้นอยู่กับว่าคุณต้องการความเข้ากันได้ ความเร็ว หรือการฟื้นฟูภาพ + ใช้การปรับขนาดปกติสำหรับภาพขนาดย่อ ขีดจำกัดการอัปโหลด ภาพหน้าจอ และการเปลี่ยนแปลงขนาดง่ายๆ + ใช้ AI upscale เมื่อรูปภาพขนาดเล็กหรือนุ่มนวลจำเป็นต้องดูดีขึ้นในขนาดที่ใหญ่ขึ้น + เปรียบเทียบใบหน้า ข้อความ และรูปแบบหลังจากเพิ่มสเกล AI เนื่องจากรายละเอียดที่สร้างขึ้นอาจดูน่าเชื่อถือแต่ไม่ถูกต้อง + เรื่องลำดับการกรอง + ใช้การล้างข้อมูล สี ความคมชัด และการบีบอัดขั้นสุดท้ายตามลำดับที่ต้องการ + สร้างกองกรองที่สะอาดยิ่งขึ้น + ตัวกรองไม่สามารถใช้แทนกันได้เสมอไป การเบลอก่อนเพิ่มความคมชัด การเพิ่มคอนทราสต์ก่อน OCR หรือการเปลี่ยนสีก่อนการบีบอัด สามารถสร้างเอาต์พุตที่แตกต่างกันมากจากตัวควบคุมเดียวกัน + ครอบตัดและหมุนก่อน ดังนั้นตัวกรองในภายหลังจะทำงานเฉพาะกับพิกเซลที่เหลืออยู่เท่านั้น + ใช้ค่าแสง สมดุลสีขาว และคอนทราสต์ก่อนเพิ่มความคมชัดหรือเอฟเฟ็กต์เก๋ๆ + เก็บการปรับขนาดและการบีบอัดขั้นสุดท้ายไว้ใกล้จุดสิ้นสุด เพื่อให้รายละเอียดที่แสดงตัวอย่างรอดพ้นจากการส่งออกอย่างคาดเดาได้ + แก้ไขขอบพื้นหลัง + ทำความสะอาดรัศมี เงาที่เหลือ ผม รู และโครงร่างที่นุ่มนวลหลังจากการลบออกโดยอัตโนมัติ + ทำให้พิลึกดูตั้งใจ + การมาสก์อัตโนมัติมักจะดูดีเมื่อมองแวบเดียว แต่เผยให้เห็นปัญหาบนพื้นหลังที่สว่าง มืด หรือมีลวดลาย การล้างขอบคือความแตกต่างระหว่างการตัดออกอย่างรวดเร็วกับสติกเกอร์ โลโก้ หรือรูปภาพผลิตภัณฑ์ที่ใช้ซ้ำได้ + ดูตัวอย่างผลลัพธ์กับพื้นหลังที่ตัดกันเพื่อเผยให้เห็นรัศมีและรายละเอียดขอบที่ขาดหายไป + ใช้การคืนค่าสำหรับเส้นผม มุม และวัตถุโปร่งใส ก่อนที่จะลบสิ่งที่เหลืออยู่โดยรอบ + ส่งออกการทดสอบเล็กๆ กับสีพื้นหลังสุดท้ายเมื่อคัตเอาต์จะถูกวางลงในการออกแบบ + ลายน้ำที่สามารถอ่านได้ + ทำให้มองเห็นรอยบนภาพที่สว่าง มืด ไม่ว่าง และถูกครอบตัดโดยไม่ทำให้ภาพเสียหาย + ปกป้องภาพโดยไม่ต้องซ่อนมัน + ลายน้ำที่ดูดีในภาพหนึ่งอาจหายไปในอีกภาพหนึ่ง ควรเลือกขนาด ความทึบ คอนทราสต์ ตำแหน่ง และการซ้ำสำหรับทั้งชุด ไม่ใช่แค่ตัวอย่างแรกเท่านั้น + ทดสอบเครื่องหมายบนภาพที่สว่างที่สุดและมืดที่สุดในชุดก่อนส่งออกไฟล์ทั้งหมด + ใช้ความทึบที่ต่ำกว่าสำหรับเครื่องหมายซ้ำขนาดใหญ่ และความเปรียบต่างที่เข้มกว่าสำหรับเครื่องหมายมุมเล็กๆ + รักษาใบหน้า ข้อความ และรายละเอียดผลิตภัณฑ์ที่สำคัญให้ชัดเจน เว้นแต่เครื่องหมายมีไว้เพื่อป้องกันการใช้ซ้ำ + แยกหนึ่งภาพออกเป็นไทล์ + ตัดรูปภาพเดียวตามแถว คอลัมน์ เปอร์เซ็นต์ รูปแบบ และคุณภาพที่กำหนดเอง + เตรียมตะแกรงและสั่งชิ้นส่วน + การแยกภาพจะสร้างเอาต์พุตหลายรายการจากอิมเมจต้นฉบับเดียว สามารถแบ่งตามจำนวนแถวและคอลัมน์ ปรับเปอร์เซ็นต์ของแถวหรือคอลัมน์ และบันทึกส่วนต่างๆ ด้วยรูปแบบและคุณภาพเอาต์พุตที่เลือก ซึ่งมีประโยชน์สำหรับตาราง ส่วนแบ่งหน้า ภาพพาโนรามา และโพสต์ทางสังคมที่เรียงลำดับ + เลือกรูปภาพต้นฉบับ จากนั้นกำหนดจำนวนแถวและคอลัมน์ที่คุณต้องการ + ปรับเปอร์เซ็นต์ของแถวหรือคอลัมน์เมื่อชิ้นส่วนไม่ควรมีขนาดเท่ากันทั้งหมด + เลือกรูปแบบเอาต์พุตและคุณภาพก่อนบันทึก เพื่อให้ชิ้นงานที่สร้างขึ้นทุกชิ้นใช้กฎการส่งออกเดียวกัน + ตัดแถบรูปภาพออก + ลบขอบเขตแนวตั้งหรือแนวนอนและรวมส่วนที่เหลือกลับเข้าด้วยกัน + ลบขอบเขตโดยไม่ทิ้งช่องว่าง + การตัดรูปภาพแตกต่างจากการครอบตัดทั่วไป สามารถลบแถบแนวตั้งหรือแนวนอนที่เลือก จากนั้นรวมส่วนรูปภาพที่เหลือเข้าด้วยกัน โหมดผกผันจะเก็บขอบเขตที่เลือกไว้แทน และการใช้ทิศทางผกผันทั้งสองจะคงสี่เหลี่ยมที่เลือกไว้ + ใช้การตัดแนวตั้งสำหรับบริเวณด้านข้าง คอลัมน์ หรือแถบตรงกลาง ใช้การตัดแนวนอนสำหรับแบนเนอร์ ช่องว่าง หรือแถว + เปิดใช้งานการผกผันบนแกนเมื่อคุณต้องการเก็บส่วนที่เลือกไว้แทนที่จะเอาออก + ดูตัวอย่างขอบหลังการตัด เนื่องจากเนื้อหาที่ผสานอาจดูฉับพลันเมื่อพื้นที่ที่ถูกลบข้ามรายละเอียดที่สำคัญ + เลือกรูปแบบผลลัพธ์ + เลือก JPEG, PNG, WebP, AVIF, JXL หรือ PDF ตามเนื้อหาและปลายทาง + ให้ปลายทางเป็นผู้กำหนดรูปแบบ + รูปแบบที่ดีที่สุดขึ้นอยู่กับว่ารูปภาพประกอบด้วยอะไรและจะใช้ที่ไหน รูปภาพ ภาพหน้าจอ เนื้อหาโปร่งใส เอกสาร และไฟล์ภาพเคลื่อนไหวจะล้มเหลวในลักษณะที่แตกต่างกันเมื่อถูกบังคับให้อยู่ในรูปแบบที่ไม่ถูกต้อง + ใช้ JPEG เพื่อความเข้ากันได้ของภาพถ่ายในวงกว้าง เมื่อไม่จำเป็นต้องมีความโปร่งใสและพิกเซลที่แน่นอน + ใช้ PNG สำหรับภาพหน้าจอที่คมชัด การจับภาพ UI กราฟิกโปร่งใส และการแก้ไขแบบไม่สูญเสียข้อมูล + ใช้ WebP, AVIF หรือ JXL เมื่อเอาต์พุตสมัยใหม่ขนาดกะทัดรัดมีความสำคัญและแอปที่รับสัญญาณรองรับ + แยกไฟล์เสียงออก + บันทึกปกอัลบั้มแบบฝังหรือเฟรมสื่อที่มีอยู่จากไฟล์เสียงเป็นภาพ PNG + ดึงอาร์ตเวิร์กออกจากไฟล์เสียง + Audio Covers ใช้ข้อมูลเมตาของสื่อ Android เพื่ออ่านอาร์ตเวิร์กที่ฝังจากไฟล์เสียง เมื่องานศิลปะที่ฝังไว้ไม่พร้อมใช้งาน รีทรีฟเวอร์อาจถอยกลับไปยังเฟรมสื่อหาก Android สามารถให้ได้ หากไม่มี เครื่องมือจะรายงานว่าไม่มีรูปภาพที่จะแยกออก + เปิดหน้าปกเสียงและเลือกไฟล์เสียงตั้งแต่หนึ่งไฟล์ขึ้นไปพร้อมภาพหน้าปกที่ต้องการ + ตรวจสอบหน้าปกที่แยกออกมาก่อนบันทึกหรือแชร์ โดยเฉพาะไฟล์จากแอพสตรีมมิ่งหรือบันทึก + หากไม่พบภาพ ไฟล์เสียงอาจไม่มีหน้าปกฝังอยู่ หรือ Android ไม่สามารถเปิดเฟรมที่ใช้งานได้ + วันที่และข้อมูลเมตาของสถานที่ + ตัดสินใจว่าจะเก็บอะไรไว้เมื่อเวลาในการจับภาพมีความสำคัญ แต่ข้อมูล GPS หรืออุปกรณ์ไม่ควรรั่วไหล + แยกข้อมูลเมตาที่เป็นประโยชน์ออกจากข้อมูลเมตาส่วนตัว + ข้อมูลเมตาไม่ได้มีความเสี่ยงเท่ากันทั้งหมด วันที่จับภาพอาจมีประโยชน์สำหรับการเก็บถาวรและการเรียงลำดับ ในขณะที่ GPS, ช่องที่มีลักษณะคล้ายซีเรียลของอุปกรณ์, แท็กซอฟต์แวร์ หรือช่องของเจ้าของสามารถเปิดเผยได้มากกว่าที่ตั้งใจไว้ + เก็บแท็กวันที่และเวลาไว้เมื่อการเรียงลำดับเวลามีความสำคัญสำหรับอัลบั้ม การสแกน หรือประวัติโปรเจ็กต์ + ลบ GPS และแท็กระบุอุปกรณ์ก่อนที่จะแชร์ต่อสาธารณะหรือส่งภาพไปยังคนแปลกหน้า + ส่งออกตัวอย่างและตรวจสอบ EXIF ​​อีกครั้งเมื่อข้อกำหนดความเป็นส่วนตัวเข้มงวด + หลีกเลี่ยงการชนกันของชื่อไฟล์ + ป้องกันเอาต์พุตแบบแบตช์เมื่อไฟล์จำนวนมากใช้ชื่อร่วมกัน เช่น image.jpg หรือ ภาพหน้าจอ.png + ทำให้ทุกชื่อเอาต์พุตไม่ซ้ำกัน + ชื่อไฟล์ที่ซ้ำกันเป็นเรื่องปกติเมื่อรูปภาพมาจากโปรแกรมส่งข้อความ ภาพหน้าจอ โฟลเดอร์บนคลาวด์ หรือจากกล้องหลายตัว รูปแบบที่ดีช่วยป้องกันการเปลี่ยนโดยไม่ตั้งใจและช่วยให้สามารถตรวจสอบย้อนกลับไปยังแหล่งที่มาได้ + รวมชื่อเดิมและส่วนต่อท้ายเมื่อควรจดจำสำเนาที่แก้ไขแล้ว + เพิ่มลำดับ การประทับเวลา ค่าที่ตั้งไว้ล่วงหน้า หรือมิติข้อมูลเมื่อประมวลผลชุดงานผสมจำนวนมาก + หลีกเลี่ยงการเขียนทับเวิร์กโฟลว์จนกว่าคุณจะตรวจสอบแล้วว่ารูปแบบการตั้งชื่อไม่ขัดแย้งกัน + บันทึกหรือแบ่งปัน + เลือกระหว่างไฟล์ถาวรและแฮนด์ออฟชั่วคราวไปยังแอปอื่น + ส่งออกด้วยเจตนาที่ถูกต้อง + บันทึกและแบ่งปันอาจรู้สึกคล้ายกัน แต่แก้ปัญหาได้ต่างกัน การบันทึกจะสร้างไฟล์ที่คุณสามารถค้นหาได้ในภายหลัง การแชร์ส่งผลที่สร้างผ่าน Android ไปยังแอปอื่นและอาจไม่ทิ้งสำเนาในเครื่องที่คงทน + ใช้บันทึกเมื่อเอาต์พุตต้องอยู่ในโฟลเดอร์ที่รู้จัก มีการสำรองข้อมูล หรือใช้ซ้ำในภายหลัง + ใช้แชร์สำหรับข้อความด่วน ไฟล์แนบในอีเมล การโพสต์บนโซเชียล หรือส่งผลลัพธ์ไปยังบรรณาธิการรายอื่น + บันทึกก่อนเมื่อแอปที่รับไม่น่าเชื่อถือหรือเมื่อการแชร์ล้มเหลวจะสร้างความรำคาญให้สร้างขึ้นใหม่ + ขนาดหน้า PDF และระยะขอบ + เลือกขนาดกระดาษ ลักษณะความพอดี และพื้นที่ว่างก่อนสร้างเอกสาร + ทำให้หน้ารูปภาพสามารถพิมพ์และอ่านได้ + รูปภาพอาจกลายเป็นหน้า PDF ที่ดูอึดอัดได้เมื่อรูปร่างไม่ตรงกับขนาดกระดาษที่เลือก ขอบ โหมดพอดี และการวางแนวหน้าจะตัดสินว่าเนื้อหาจะถูกครอบตัด เล็ก ยืดออก หรืออ่านสบาย + ใช้ขนาดกระดาษจริงเมื่อ PDF สามารถพิมพ์หรือส่งไปยังแบบฟอร์มได้ + ใช้ระยะขอบในการสแกนและภาพหน้าจอเพื่อให้ข้อความไม่ชิดขอบหน้า + ดูตัวอย่างหน้าแนวตั้งและแนวนอนพร้อมกันเมื่อรูปภาพต้นฉบับมีการวางแนวผสมกัน + ทำความสะอาดการสแกน + ปรับปรุงขอบกระดาษ เงา คอนทราสต์ การหมุน และความสามารถในการอ่านก่อน PDF หรือ OCR + เตรียมหน้าก่อนเก็บถาวร + ในทางเทคนิคแล้วการสแกนสามารถจับได้แต่ยังคงอ่านได้ยาก ขั้นตอนการล้างข้อมูลเล็กๆ น้อยๆ ก่อนการส่งออก PDF มักมีความสำคัญมากกว่าการตั้งค่าการบีบอัด โดยเฉพาะใบเสร็จรับเงิน บันทึกย่อที่เขียนด้วยลายมือ และหน้าที่แสงน้อย + มุมมองที่ถูกต้องและครอบตัดขอบตาราง นิ้ว เงา และความยุ่งเหยิงของพื้นหลังออกไป + เพิ่มคอนทราสต์อย่างระมัดระวังเพื่อให้ข้อความชัดเจนขึ้นโดยไม่ทำลายตราประทับ ลายเซ็น หรือเครื่องหมายดินสอ + เรียกใช้ OCR หลังจากที่หน้าตั้งตรงและสามารถอ่านได้เท่านั้น จากนั้นตรวจสอบตัวเลขที่สำคัญด้วยตนเอง + บีบอัด ระดับสีเทา หรือซ่อมแซม PDF + ใช้การดำเนินการ PDF ไฟล์เดียวเพื่อให้สำเนาเอกสารมีน้ำหนักเบา พิมพ์ได้ หรือสร้างใหม่ + เลือกการดำเนินการล้างข้อมูล PDF + เครื่องมือ PDF ประกอบด้วยการดำเนินการแยกต่างหากสำหรับการบีบอัด การแปลงระดับสีเทา และการซ่อมแซม การบีบอัดและระดับสีเทาทำงานผ่านรูปภาพที่ฝังไว้เป็นหลัก ในขณะที่การซ่อมแซมจะสร้างโครงสร้าง PDF ใหม่ สิ่งเหล่านี้ไม่ควรถือเป็นการแก้ไขที่รับประกันสำหรับเอกสารทุกฉบับ + ใช้บีบอัด PDF เมื่อเอกสารมีรูปภาพและขนาดไฟล์สำคัญสำหรับการแชร์ + ใช้ระดับสีเทาเมื่อเอกสารควรพิมพ์ได้ง่ายกว่าหรือไม่ต้องการภาพสี + ใช้การซ่อมแซม PDF กับสำเนาเมื่อเอกสารไม่สามารถเปิดได้หรือมีโครงสร้างภายในเสียหาย จากนั้นตรวจสอบไฟล์ที่สร้างใหม่ + แยกรูปภาพจาก PDF + กู้คืนรูปภาพ PDF ที่ฝังไว้และบรรจุผลลัพธ์ที่แยกออกมาไว้ในไฟล์เก็บถาวร + บันทึกภาพต้นฉบับจากเอกสาร + แยกรูปภาพจะสแกนไฟล์ PDF เพื่อหาออบเจ็กต์รูปภาพที่ฝัง ข้ามรายการที่ซ้ำกันหากเป็นไปได้ และจัดเก็บรูปภาพที่กู้คืนไว้ในไฟล์ ZIP ที่สร้างขึ้น โดยแยกเฉพาะรูปภาพที่ฝังอยู่ใน PDF จริงๆ ไม่ใช่ข้อความ ภาพวาดเวกเตอร์ หรือทุกหน้าที่มองเห็นได้เป็นภาพหน้าจอ + ใช้แยกรูปภาพเมื่อคุณต้องการรูปภาพที่จัดเก็บไว้ใน PDF เช่น รูปภาพจากแค็ตตาล็อกหรือเนื้อหาที่สแกน + ใช้ PDF เป็นรูปภาพหรือการแยกหน้าแทนเมื่อคุณต้องการหน้าเต็ม รวมถึงข้อความและเค้าโครง + หากเครื่องมือรายงานว่าไม่มีรูปภาพที่ฝัง หน้านั้นอาจเป็นเนื้อหาข้อความ/เวกเตอร์ หรือรูปภาพอาจไม่สามารถจัดเก็บเป็นวัตถุที่แยกออกมาได้ + ข้อมูลเมตา การทำให้เรียบ และเอาต์พุตการพิมพ์ + แก้ไขคุณสมบัติของเอกสาร แรสเตอร์หน้า หรือเตรียมเค้าโครงการพิมพ์แบบกำหนดเองตามความตั้งใจ + เลือกการดำเนินการกับเอกสารขั้นสุดท้าย + ข้อมูลเมตา PDF การทำให้เรียบ และการเตรียมการพิมพ์ช่วยแก้ปัญหาในขั้นตอนสุดท้ายที่แตกต่างกัน ข้อมูลเมตาควบคุมคุณสมบัติของเอกสาร Flatten PDF จะทำให้หน้าเป็นเอาต์พุตที่แก้ไขได้น้อยลง และ Print PDF เตรียมเค้าโครงใหม่ด้วยขนาดหน้า การวางแนว ระยะขอบ และการควบคุมจำนวนหน้าต่อแผ่น + ใช้ข้อมูลเมตาเมื่อผู้เขียน ชื่อเรื่อง คำสำคัญ ผู้ผลิต หรือการล้างข้อมูลความเป็นส่วนตัว + ใช้ Flatten PDF เมื่อเนื้อหาหน้าที่มองเห็นได้กลายเป็นเรื่องยากที่จะแก้ไขเป็นออบเจ็กต์ PDF แยกกัน + ใช้พิมพ์ PDF เมื่อคุณต้องการขนาดหน้ากระดาษ การวางแนว ระยะขอบ หรือหลายหน้าต่อแผ่นแบบกำหนดเอง + เตรียมภาพสำหรับ OCR + ใช้การครอบตัด การหมุน คอนทราสต์ ลดขนาด และมาตราส่วนก่อนที่จะขอให้ OCR อ่านข้อความ + ให้พิกเซลทำความสะอาด OCR + ข้อผิดพลาด OCR มักมาจากภาพอินพุตมากกว่ากลไก OCR หน้าที่คดเคี้ยว คอนทราสต์ต่ำ ความเบลอ เงา ข้อความขนาดเล็ก และพื้นหลังแบบผสม ล้วนลดคุณภาพการจดจำ + ครอบตัดไปที่พื้นที่ข้อความแล้วหมุนรูปภาพเพื่อให้เส้นอยู่ในแนวนอนก่อนที่จะจดจำ + เพิ่มคอนทราสต์หรือแปลงเป็นขาวดำที่สะอาดตาเฉพาะเมื่อทำให้ตัวอักษรอ่านง่ายขึ้นเท่านั้น + ปรับขนาดข้อความเล็กๆ ขึ้นในระดับปานกลาง แต่หลีกเลี่ยงการเพิ่มความคมชัดจนเกินไปจนทำให้เกิดขอบปลอม + ตรวจสอบเนื้อหา QR + ตรวจสอบลิงก์ ข้อมูลรับรอง Wi-Fi รายชื่อติดต่อ และการดำเนินการก่อนที่จะเชื่อถือรหัส + ถือว่าข้อความที่ถอดรหัสเป็นอินพุตที่ไม่น่าเชื่อถือ + รหัส QR สามารถซ่อน URL, บัตรรายชื่อ, รหัสผ่าน Wi-Fi, กิจกรรมในปฏิทิน, หมายเลขโทรศัพท์ หรือข้อความธรรมดา ป้ายที่มองเห็นได้ใกล้กับโค้ดอาจไม่ตรงกับเพย์โหลดจริง ดังนั้นโปรดตรวจสอบเนื้อหาที่ถอดรหัสก่อนดำเนินการ + ตรวจสอบ URL ที่ถอดรหัสแบบเต็มก่อนที่จะเปิด โดยเฉพาะโดเมนที่สั้นลงหรือไม่คุ้นเคย + โปรดใช้ความระมัดระวังกับรหัส Wi-Fi รายชื่อติดต่อ ปฏิทิน โทรศัพท์ และ SMS เนื่องจากอาจทำให้เกิดการดำเนินการที่ละเอียดอ่อนได้ + คัดลอกเนื้อหาก่อนเมื่อคุณต้องการตรวจสอบที่อื่นแทนที่จะเปิดทันที + สร้างรหัส QR + สร้างรหัสจากข้อความธรรมดา, URL, Wi-Fi, รายชื่อติดต่อ, อีเมล, โทรศัพท์, SMS และข้อมูลตำแหน่ง + สร้างเพย์โหลด QR ที่เหมาะสม + หน้าจอ QR สามารถสร้างโค้ดจากเนื้อหาที่ป้อนรวมทั้งสแกนโค้ดที่มีอยู่ได้ ประเภท QR ที่มีโครงสร้างช่วยเข้ารหัสข้อมูลในรูปแบบที่แอพอื่นเข้าใจ เช่น รายละเอียดการเข้าสู่ระบบ Wi-Fi บัตรรายชื่อ ช่องอีเมล หมายเลขโทรศัพท์ เนื้อหา SMS URL หรือพิกัดทางภูมิศาสตร์ + เลือกข้อความธรรมดาสำหรับบันทึกย่อง่ายๆ หรือใช้ประเภทที่มีโครงสร้างเมื่อโค้ดควรเปิดเป็น Wi-Fi, รายชื่อติดต่อ, URL, อีเมล, โทรศัพท์, SMS หรือข้อมูลตำแหน่ง + ตรวจสอบทุกช่องก่อนที่จะแชร์โค้ดที่สร้างขึ้น เนื่องจากการแสดงตัวอย่างที่มองเห็นได้สะท้อนถึงเพย์โหลดที่คุณป้อนเท่านั้น + ทดสอบโค้ด QR ที่บันทึกไว้ด้วยเครื่องสแกนเมื่อจะพิมพ์ โพสต์แบบสาธารณะ หรือใช้โดยบุคคลอื่น + เลือกโหมดการตรวจสอบผลรวม + คำนวณแฮชจากไฟล์หรือข้อความ เปรียบเทียบไฟล์เดียว หรือตรวจสอบหลายไฟล์เป็นกลุ่ม + ตรวจสอบเนื้อหา ไม่ใช่รูปลักษณ์ภายนอก + เครื่องมือ Checksum มีแท็บแยกต่างหากสำหรับการแฮชไฟล์ การแฮชข้อความ การเปรียบเทียบไฟล์กับแฮชที่รู้จัก และการเปรียบเทียบแบบแบตช์ เช็คซัมพิสูจน์เอกลักษณ์แบบไบต์ต่อไบต์สำหรับอัลกอริทึมที่เลือก มันไม่ได้พิสูจน์ว่าภาพสองภาพดูคล้ายกันเท่านั้น + ใช้คำนวณหาไฟล์และแฮชข้อความสำหรับข้อมูลข้อความที่พิมพ์หรือวาง + ใช้การเปรียบเทียบเมื่อมีคนให้ผลรวมตรวจสอบที่คาดหวังแก่คุณสำหรับไฟล์เดียว + ใช้ Batch Compare เมื่อไฟล์จำนวนมากต้องการแฮชหรือการตรวจสอบในครั้งเดียว จากนั้นให้อัลกอริธึมที่เลือกมีความสอดคล้องกัน + ความเป็นส่วนตัวของคลิปบอร์ด + ใช้คุณสมบัติคลิปบอร์ดอัตโนมัติโดยไม่เปิดเผยข้อมูลที่คัดลอกมาเป็นส่วนตัวโดยไม่ได้ตั้งใจ + ตั้งใจคัดลอกเนื้อหา + การทำงานอัตโนมัติของคลิปบอร์ดนั้นสะดวก แต่ข้อความที่คัดลอกอาจมีรหัสผ่าน ลิงก์ ที่อยู่ โทเค็น หรือบันทึกส่วนตัว ถือว่าการป้อนข้อมูลตามคลิปบอร์ดเป็นคุณลักษณะความเร็วที่ควรตรงกับนิสัยและความคาดหวังความเป็นส่วนตัวของคุณ + ปิดใช้งานการใช้คลิปบอร์ดอัตโนมัติหากข้อความส่วนตัวปรากฏในเครื่องมือโดยไม่คาดคิด + ใช้การบันทึกเฉพาะคลิปบอร์ดเฉพาะเมื่อคุณจงใจต้องการให้ผลลัพธ์หลีกเลี่ยงการจัดเก็บข้อมูล + ล้างหรือเปลี่ยนคลิปบอร์ดหลังจากจัดการข้อความที่ละเอียดอ่อน ข้อมูล QR หรือเพย์โหลด Base64 + รูปแบบสี + ทำความเข้าใจค่าสี HEX, RGB, HSV, alpha และคัดลอกก่อนที่จะวางที่อื่น + คัดลอกรูปแบบที่เป้าหมายต้องการ + สีที่มองเห็นเหมือนกันสามารถเขียนได้หลายวิธี เครื่องมือออกแบบ ช่อง CSS ค่าสีของ Android หรือโปรแกรมแก้ไขรูปภาพอาจต้องการลำดับ การจัดการอัลฟ่า หรือโมเดลสีที่แตกต่างกัน + ใช้ HEX เมื่อวางลงในช่องเว็บ การออกแบบ หรือธีมที่ต้องใช้รหัสสีแบบกะทัดรัด + ใช้ RGB หรือ HSV เมื่อคุณต้องการปรับช่อง ความสว่าง ความอิ่มตัวของสี หรือเฉดสีด้วยตนเอง + ตรวจสอบอัลฟ่าแยกกันเมื่อความโปร่งใสมีความสำคัญเนื่องจากบางปลายทางเพิกเฉยหรือใช้ลำดับอื่น + เรียกดูไลบรารีสี + ค้นหาสีที่มีชื่อตามชื่อหรือ HEX และเก็บรายการโปรดไว้ใกล้ด้านบน + ค้นหาสีที่มีชื่อที่นำมาใช้ซ้ำได้ + ไลบรารีสีจะโหลดคอลเลกชันสีที่มีชื่อ จัดเรียงตามชื่อ กรองตามชื่อสีหรือค่า HEX และจัดเก็บสีโปรดเพื่อให้เข้าถึงรายการสำคัญได้ง่ายขึ้น มีประโยชน์เมื่อคุณต้องการสีที่มีชื่อจริง แทนที่จะสุ่มตัวอย่างจากรูปภาพ + ค้นหาด้วยชื่อสีเมื่อคุณรู้จักตระกูล เช่น แดง น้ำเงิน ชนวน หรือมิ้นต์ + ค้นหาด้วยเลขฐานสิบหกเมื่อคุณมีสีที่เป็นตัวเลขอยู่แล้ว และต้องการค้นหารายการที่ตรงกันหรือใกล้เคียง + ทำเครื่องหมายสีที่ใช้บ่อยเป็นรายการโปรดเพื่อให้นำหน้าไลบรารีทั่วไปขณะเรียกดู + ใช้คอลเลกชันการไล่ระดับสีแบบตาข่าย + ดาวน์โหลดทรัพยากรการไล่ระดับสีแบบตาข่ายที่มีอยู่และแบ่งปันรูปภาพที่เลือก + ใช้เนื้อหาการไล่ระดับ Mesh ระยะไกล + การไล่ระดับสีแบบตาข่ายสามารถโหลดคอลเลกชันทรัพยากรระยะไกล ดาวน์โหลดรูปภาพการไล่ระดับสีที่ขาดหายไป แสดงความคืบหน้าในการดาวน์โหลด และแบ่งปันการไล่ระดับสีที่เลือก ความพร้อมใช้งานขึ้นอยู่กับการเข้าถึงเครือข่ายและการจัดเก็บทรัพยากรระยะไกล ดังนั้นรายการว่างมักจะหมายความว่าทรัพยากรยังไม่พร้อมใช้งาน + เปิดการไล่ระดับสีแบบตาข่ายจากเครื่องมือไล่ระดับสีเมื่อคุณต้องการภาพการไล่ระดับสีแบบตาข่ายสำเร็จรูป + รอความคืบหน้าในการโหลดทรัพยากรหรือการดาวน์โหลดก่อนที่จะถือว่าคอลเลกชันว่างเปล่า + เลือกและแชร์การไล่ระดับสีที่คุณต้องการ หรือกลับไปที่เครื่องสร้างไล่ระดับสีเมื่อคุณต้องการสร้างการไล่ระดับสีแบบกำหนดเองด้วยตนเอง + ความละเอียดของสินทรัพย์ที่สร้างขึ้น + เลือกขนาดเอาต์พุตก่อนสร้างการไล่ระดับสี นอยส์ วอลเปเปอร์ งานศิลปะที่เหมือน SVG หรือพื้นผิว + สร้างตามขนาดที่คุณต้องการ + รูปภาพที่สร้างขึ้นไม่มีต้นฉบับจากกล้องให้ถอยกลับ หากเอาต์พุตมีขนาดเล็กเกินไป การปรับขนาดในภายหลังอาจทำให้รูปแบบอ่อนลง เลื่อนรายละเอียด หรือเปลี่ยนวิธีเรียงชิ้นส่วนและบีบอัดเนื้อหา + กำหนดขนาดสำหรับกรณีการใช้งานขั้นสุดท้ายก่อน เช่น วอลเปเปอร์ ไอคอน พื้นหลัง ภาพพิมพ์ หรือการซ้อนทับ + ใช้ขนาดที่ใหญ่กว่าสำหรับพื้นผิวที่อาจครอบตัด ซูม หรือนำกลับมาใช้ใหม่ในหลายเลย์เอาต์ + ส่งออกเวอร์ชันทดสอบก่อนเอาต์พุตจำนวนมาก เนื่องจากรายละเอียดขั้นตอนสามารถเปลี่ยนวิธีการทำงานของการบีบอัดได้ + ส่งออกวอลเปเปอร์ปัจจุบัน + เรียกข้อมูลหน้าแรก ล็อค และวอลเปเปอร์ในตัวจากอุปกรณ์ + บันทึกหรือแชร์วอลเปเปอร์ที่ติดตั้ง + การส่งออกวอลเปเปอร์จะอ่านวอลเปเปอร์ที่ Android เปิดเผยผ่าน API ของวอลเปเปอร์ระบบ ขึ้นอยู่กับอุปกรณ์ เวอร์ชัน Android การอนุญาต และเวอร์ชันบิวด์ หน้าแรก ล็อค หรือวอลเปเปอร์ในตัวอาจมีจำหน่ายแยกต่างหากหรืออาจหายไป + เปิดการส่งออกวอลเปเปอร์เพื่อโหลดวอลเปเปอร์ที่ระบบอนุญาตให้แอปอ่านได้ + เลือกรายการหน้าแรก ล็อค หรือวอลเปเปอร์ในตัวที่คุณต้องการบันทึก คัดลอก หรือแชร์ + หากวอลเปเปอร์หายไปหรือฟีเจอร์ไม่พร้อมใช้งาน โดยปกติจะเป็นข้อจำกัดของระบบ สิทธิ์ หรือเวอร์ชันบิวด์ ไม่ใช่การตั้งค่าการแก้ไข + มุมแอนิเมชั่นเค้น + คัดลอกสีเป็น + เลขฐานสิบหก + ชื่อ + โมเดลการจัดวางแนวตั้งสำหรับการตัดบุคคลอย่างรวดเร็วด้วยเส้นผมที่นุ่มนวลและการจัดการขอบ + การปรับปรุงสภาพแสงน้อยอย่างรวดเร็วโดยใช้การประมาณค่าเส้นโค้ง Zero-DCE++ + แบบจำลองการฟื้นฟูอิมเมจ Restormer เพื่อลดเส้นสายฝนและสิ่งผิดปกติจากสภาพอากาศเปียก + เอกสารโมเดลที่ไม่บิดเบี้ยวซึ่งคาดการณ์ตารางพิกัดและแมปหน้าที่โค้งใหม่ให้เป็นการสแกนที่เรียบยิ่งขึ้น + สเกลระยะเวลาการเคลื่อนไหว + ควบคุมความเร็วของแอนิเมชันของแอป: 0 ปิดใช้งานการเคลื่อนไหว, 1 คือค่าปกติ ค่าที่สูงกว่าจะทำให้แอนิเมชั่นช้าลง + แสดงเครื่องมือล่าสุด + แสดงการเข้าถึงเครื่องมือที่ใช้ล่าสุดอย่างรวดเร็วบนหน้าจอหลัก + สถิติการใช้งาน + สำรวจการเปิดแอป การเปิดตัวเครื่องมือ และเครื่องมือที่คุณใช้บ่อยที่สุด + แอปเปิดขึ้น + เครื่องมือเปิดขึ้น + เครื่องมือชิ้นสุดท้าย + บันทึกได้สำเร็จ + กิจกรรมต่อเนื่อง + บันทึกข้อมูลแล้ว + รูปแบบยอดนิยม + เครื่องมือที่ใช้ + %1$d เปิด • %2$s + ยังไม่มีสถิติการใช้งาน + เปิดเครื่องมือบางอย่างแล้วเครื่องมือเหล่านั้นจะปรากฏที่นี่โดยอัตโนมัติ + สถิติการใช้งานของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณเท่านั้น ImageToolbox ใช้เพื่อแสดงกิจกรรมส่วนตัว เครื่องมือโปรด และข้อมูลเชิงลึกของข้อมูลที่บันทึกไว้เท่านั้น + บรรณาธิการ แก้ไขภาพ รีทัชภาพ ปรับภาพ + ปรับขนาด แปลง ขนาด ความละเอียด ขนาด ความกว้าง ความสูง jpg jpeg png webp บีบอัด + ขนาดบีบอัด น้ำหนัก ไบต์ mb kb ลดการเพิ่มประสิทธิภาพคุณภาพ + อัตราส่วนภาพตัดครอบตัด + ฟิลเตอร์เอฟเฟกต์การแก้ไขสีปรับมาส์กลูท + วาด พู่กัน ดินสอ มาร์กอัปคำอธิบายประกอบ + การเข้ารหัส ถอดรหัส ถอดรหัส รหัสผ่าน ความลับ + เครื่องมือลบพื้นหลัง ลบ ลบคัตเอาท์ อัลฟ่าโปร่งใส + ดูตัวอย่างแกลเลอรีของผู้ดูตรวจสอบเปิด + Stitch Merge รวมภาพพาโนรามาแบบยาว + url เว็บ ดาวน์โหลด ลิงค์อินเตอร์เน็ต รูปภาพ + ตัวเลือก eyedropper ตัวอย่างสี hex rgb + จานสี swatches แยกส่วนที่โดดเด่น + ความเป็นส่วนตัวของข้อมูลเมตา exif ลบแถบตำแหน่ง GPS ที่สะอาด + เปรียบเทียบความแตกต่างก่อนหลัง + จำกัดการปรับขนาดสูงสุดขั้นต่ำเมกะพิกเซลขนาดแคลมป์ + เครื่องมือหน้าเอกสาร PDF + ข้อความ ocr จดจำการสแกนสารสกัด + ตาข่ายสีพื้นหลังไล่ระดับสี + ลายน้ำโลโก้ข้อความซ้อนทับประทับตรา + GIF แอนิเมชั่น เฟรมแปลงภาพเคลื่อนไหว + apng ภาพเคลื่อนไหว png เคลื่อนไหวแปลงเฟรม + zip เก็บถาวร บีบอัด แพ็ค แกะไฟล์ + jxl jpeg xl แปลงรูปแบบภาพ jpg + svg vector Trace แปลง xml + รูปแบบ แปลง jpg jpeg png webp heic avif jxl bmp + เครื่องสแกนเอกสาร สแกน ครอบตัดกระดาษ มุมมอง + เครื่องสแกนบาร์โค้ด qr สแกน + การซ้อนทับภาพถ่ายทางดาราศาสตร์เฉลี่ยแบบซ้อนทับ + แยกส่วนชิ้นส่วนตารางกระเบื้อง + แปลงสี hex rgb hsl hsv cmyk lab + webp แปลงรูปแบบภาพเคลื่อนไหว + เครื่องกำเนิดเสียงเม็ดเนื้อแบบสุ่ม + รวมเค้าโครงตารางจับแพะชนแกะ + เลเยอร์มาร์กอัปใส่คำอธิบายประกอบการแก้ไขการซ้อนทับ + เข้ารหัส base64 ถอดรหัสข้อมูล uri + ตรวจสอบแฮช md5 sha sha1 sha256 + พื้นหลังไล่ระดับตาข่าย + ข้อมูลเมตาของ exif แก้ไขกล้องวันที่ของ gps + ตัดกระเบื้องชิ้นตารางแยก + ข้อมูลเมตาเพลงปกอัลบั้มปกเสียง + พื้นหลังการส่งออกวอลเปเปอร์ + อักขระศิลปะข้อความ ASCII + ai ml neural ยกระดับเซ็กเมนต์ระดับสูง + จานสีวัสดุไลบรารีสีที่มีชื่อสี + สตูดิโอเอฟเฟกต์ชิ้นส่วน shader glsl + pdf ผสาน รวม เข้าร่วม + pdf แยกหน้าต่างๆ + pdf หมุนการวางแนวหน้า + pdf จัดเรียงหน้าเรียงลำดับใหม่ + การแบ่งหน้าเลขหน้า.pdf + ข้อความ ocr pdf รู้จักสารสกัดที่ค้นหาได้ + ข้อความโลโก้ประทับตราลายน้ำ pdf + การจับฉลากลายเซ็น.pdf + pdf ป้องกันรหัสผ่านเข้ารหัสล็อค + pdf ปลดล็อค รหัสผ่าน ถอดรหัส ลบการป้องกัน + การบีบอัด pdf ลดขนาดให้เหมาะสม + pdf ระดับสีเทา ขาวดำ ขาวดำ + ซ่อมแซม pdf ซ่อมแซม กู้คืนเสียหาย + คุณสมบัติเมตาดาต้า pdf ชื่อผู้เขียน exif + pdf ลบหน้าลบ + ระยะขอบครอบตัด pdf + คำอธิบายประกอบแบบ pdf แผ่เป็นเลเยอร์ + pdf แยกรูปภาพรูปภาพ + บีบอัดไฟล์ zip pdf + เครื่องพิมพ์พิมพ์ pdf + โปรแกรมอ่านตัวอย่าง PDF อ่าน + รูปภาพเป็น pdf แปลงเอกสาร jpg jpeg png + PDF เป็นหน้ารูปภาพ ส่งออก jpg png + คำอธิบายประกอบ pdf ลบความคิดเห็นที่สะอาด + ตัวแปรที่เป็นกลาง + ข้อผิดพลาด + วางเงา + ความสูงของฟัน + ช่วงฟันแนวนอน + ช่วงฟันแนวตั้ง + ขอบบน + ขอบขวา + ขอบล่าง + ขอบซ้าย + ขอบฉีกขาด + รีเซ็ตสถิติการใช้งาน + เครื่องมือเปิดขึ้น บันทึกสถิติ สถิติต่อเนื่อง ข้อมูลที่บันทึกไว้ และรูปแบบสูงสุดจะถูกรีเซ็ต การเปิดแอปจะไม่เปลี่ยนแปลง + เสียง + ไฟล์ + ส่งออกโปรไฟล์ + บันทึกโปรไฟล์ปัจจุบัน + ลบโปรไฟล์การส่งออก + คุณต้องการลบโปรไฟล์การส่งออก \\"%1$s\\" หรือไม่ + การวาดเส้นขอบ + แสดงเส้นขอบบางๆ รอบภาพวาดและการลบผืนผ้าใบ + หยัก + องค์ประกอบ UI แบบโค้งมนพร้อมขอบหยักอันนุ่มนวล + ตัก + รอยบาก + มุมโค้งมนแกะสลักเข้าด้านใน + ก้าวมุมด้วยการตัดสองครั้ง + ตัวยึดตำแหน่ง + ใช้ {filename} เพื่อแทรกชื่อไฟล์ต้นฉบับโดยไม่มีนามสกุล + แฟลร์ + จำนวนฐาน + จำนวนแหวน + จำนวนเรย์ + ความกว้างของแหวน + บิดเบือนมุมมอง + Java รูปลักษณ์และความรู้สึก + เฉือน + มุมเอ็กซ์ + และมุม + ปรับขนาดเอาต์พุต + หยดน้ำ + ความยาวคลื่น + เฟส + ผ่านสูง + หน้ากากสี + โครเมียม + ละลาย + ความนุ่มนวล + ข้อเสนอแนะ + เลนส์เบลอ + อินพุตต่ำ + อินพุตสูง + ผลผลิตต่ำ + ผลผลิตสูง + เอฟเฟกต์แสง + ความสูงของการชน + กันกระแทกนุ่ม + ระลอกคลื่น + เอ็กซ์ แอมพลิจูด + Y แอมพลิจูด + ความยาวคลื่นเอ็กซ์ + ความยาวคลื่น Y + ปรับเบลอ + การสร้างพื้นผิว + สร้างพื้นผิวขั้นตอนต่างๆ และบันทึกเป็นรูปภาพ + ประเภทพื้นผิว + อคติ + เวลา + ส่องแสง + การกระจายตัว + ตัวอย่าง + ค่าสัมประสิทธิ์มุม + ค่าสัมประสิทธิ์การไล่ระดับสี + ประเภทกริด + พลังระยะทาง + สเกล Y + ความคลุมเครือ + ประเภทพื้นฐาน + ปัจจัยความปั่นป่วน + การปรับขนาด + การวนซ้ำ + แหวน + แปรงโลหะ + โซดาไฟ + เซลล์ + กระดานหมากรุก + เอฟบีเอ็ม + หินอ่อน + พลาสมา + ผ้าห่ม + ไม้ + สุ่ม + สี่เหลี่ยม + หกเหลี่ยม + แปดเหลี่ยม + สามเหลี่ยม + สัน + เสียง VL + เอสซี นอยส์ + ล่าสุด + ยังไม่มีตัวกรองที่ใช้ล่าสุด + พื้นผิว เครื่องกำเนิดไฟฟ้า แปรง โลหะ โซดาไฟ เซลล์ ตารางหมากรุก หินอ่อน พลาสม่า ผ้าห่ม ไม้ อิฐ อำพราง เซลล์ เมฆ รอยแตก, รอยแยก ผ้า ผ้า สิ่งทอ ใบไม้ รวงผึ้ง น้ำแข็ง ลาวา เนบิวลา กระดาษ สนิม ทราย ควัน หิน ภูมิประเทศ ภูมิประเทศ กระเพื่อมน้ำ + อิฐ + ลายพราง + เซลล์ + คลาวด์ + แตก + ผ้า + ใบไม้ + รังผึ้ง + น้ำแข็ง + ลาวา + เนบิวลา + กระดาษ + สนิม + ทราย + ควัน + หิน + ภูมิประเทศ + ภูมิประเทศ + ระลอกน้ำ + ไม้ขั้นสูง + ความกว้างของปูน + ความผิดปกติ + เอียง + ความหยาบ + เกณฑ์แรก + เกณฑ์ที่สอง + เกณฑ์ที่สาม + ความนุ่มนวลของขอบ + ความกว้างของเส้นขอบ + ความคุ้มครอง + รายละเอียด + ความลึก + การแตกแขนง + เธรดแนวนอน + เธรดแนวตั้ง + ฝอย + หลอดเลือดดำ + แสงสว่าง + ความกว้างของรอยร้าว + น้ำค้างแข็ง + ไหล + เปลือกโลก + ความหนาแน่นของเมฆ + ดาว + ความหนาแน่นของเส้นใย + ความแข็งแรงของเส้นใย + คราบ + การกัดกร่อน + บ่อ + สะเก็ด + ความถี่เนินทราย + มุมลม + ระลอกคลื่น + วิสป์ + ขนาดหลอดเลือดดำ + ระดับน้ำ + ระดับภูเขา + การพังทลาย + ระดับหิมะ + จำนวนบรรทัด + ความหนาของเส้น + การแรเงา + สีรูขุมขน + สีครก + สีอิฐเข้ม + สีอิฐ + ไฮไลท์สี + สีเข้ม + สีป่า + สีเอิร์ธโทน + สีทราย + สีพื้นหลัง + สีของเซลล์ + สีขอบ + สีท้องฟ้า + สีเงา + สีอ่อน + สีพื้นผิว + สีที่หลากหลาย + สีแตก + สีวาร์ป + สีพุ่ง + สีใบเข้ม + สีใบ + สีขอบ + สีน้ำผึ้ง + สีเข้ม + สีน้ำแข็ง + สีฟรอสต์ + สีเปลือกโลก + ล้างสี + สีเรืองแสง + สีอวกาศ + สีม่วง + สีฟ้า + สีฐาน + สีไฟเบอร์ + สีคราบ + สีเมทัล + สีสนิมเข้ม + สีสนิม + สีส้ม + สีควัน + สีหลอดเลือดดำ + สีพื้นราบ + สีน้ำ + สีร็อค + สีสโนว์ + สีต่ำ + สีสูง + สีเส้น + สีตื้น + หญ้า + สิ่งสกปรก + หนัง + คอนกรีต + ยางมะตอย + มอส + ไฟ + ออโรร่า + สลิคน้ำมัน + สีน้ำ + กระแสนามธรรม + โอปอล + เหล็กดามัสกัส + ฟ้าผ่า + กำมะหยี่ + หมึกหินอ่อน + ฟอยล์โฮโลแกรม + การเรืองแสงจากสิ่งมีชีวิต + กระแสน้ำวนจักรวาล + โคมไฟลาวา + ขอบฟ้าเหตุการณ์ + แฟร็กทัลบลูม + อุโมงค์สี + คราสโคโรนา + ตัวดึงดูดที่แปลกประหลาด + เฟอร์โรฟลูอิดคราวน์ + ซูเปอร์โนวา + ไอริส + ขนนกยูง + นอติลุส เชลล์ + วงแหวนดาวเคราะห์ + ความหนาแน่นของใบมีด + ความยาวใบมีด + ลม + ความหยาบ + ก้อน + ความชื้น + ก้อนกรวด + ริ้วรอย + รูขุมขน + รวม + รอยแตก + ทาร์ + สวมใส่ + จุด + เส้นใย + ความถี่เปลวไฟ + ควัน + ความเข้ม + ริบบิ้น + วงดนตรี + ความแวววาว + ความมืด + บุปผา + เม็ดสี + ขอบ + กระดาษ + การแพร่กระจาย + สมมาตร + ความคม + เล่นสี + ความน้ำนม + เลเยอร์ + พับ + ขัด + สาขา + ทิศทาง + ชีน + พับ + ขนนก + ความสมดุลของหมึก + สเปกตรัม + รอยย่น + การเลี้ยวเบน + แขน + บิด + แกนเรืองแสง + หยด + เอียงแผ่นดิสก์ + ขนาดขอบฟ้า + ความกว้างของดิสก์ + เลนซ์ + กลีบดอก + ขด + ลวดลายเป็นเส้น + แง่มุม + ความโค้ง + ขนาดพระจันทร์ + ขนาดโคโรนา + รังสี + แหวนเพชร + กลีบ + ความหนาแน่นของวงโคจร + ความหนา + เดือย + ความยาวสไปค์ + ขนาดลำตัว + เมทัลลิค + รัศมีแรงกระแทก + ความกว้างของเปลือก + โยนออกไป + ขนาดรูม่านตา + ขนาดไอริส + การเปลี่ยนแปลงสี + ประกายตา + ขนาดตา + ความหนาแน่นของหนาม + เลี้ยว + ห้อง + กำลังเปิด + สันเขา + ไข่มุก + ขนาดดาวเคราะห์ + แหวนเอียง + ความกว้างของแหวน + บรรยากาศ + สีฝุ่น + สีหญ้าเข้ม + สีหญ้า + สีทิป + สีเอิร์ธโทนเข้ม + สีแห้ง + สีกรวด + สีหนัง + สีคอนกรีต + สีทาร์ + สีแอสฟัลต์ + สีหิน + สีฝุ่น + สีดิน + สีมอสเข้ม + สีมอส + สีแดง + สีหลัก + สีเขียว + สีฟ้า + สีม่วงแดง + สีทอง + สีกระดาษ + สีรงควัตถุ + สีรอง + สีแรก + สีที่สอง + สีชมพู + สีเหล็กเข้ม + สีเหล็ก + สีเหล็กอ่อน + สีออกไซด์ + สีฮาโล + สีสายฟ้า + สีกำมะหยี่ + สีเงา + สีหมึกสีน้ำเงิน + สีหมึกแดง + หมึกสีเข้ม + สีเงิน + สีเหลือง + สีทิชชู่ + สีของดิสก์ + สีร้อนแรง + สีเลนส์ + สีภายนอก + สีภายใน + สีโคโรนา + สีเย็น + โทนสีอบอุ่น + สีเมฆ + สีเปลวไฟ + สีขนนก + สีเปลือก + สีมุก + สีดาวเคราะห์ + สีแหวน + ลบไฟล์ต้นฉบับหลังจากบันทึก + เฉพาะไฟล์ต้นฉบับที่ประมวลผลสำเร็จเท่านั้นที่จะถูกลบ + ไฟล์ต้นฉบับที่ถูกลบ: %1$d + ไม่สามารถลบไฟล์ต้นฉบับ: %1$d + ไฟล์ต้นฉบับที่ถูกลบ: %1$d, ล้มเหลว: %2$d + ท่าทางของแผ่นงาน + อนุญาตให้ลากแผ่นตัวเลือกที่ขยายออก + การสุ่มตัวอย่างโครมา + ความลึกบิต + ไม่มีการสูญเสีย + %1$d-บิต + การเปลี่ยนชื่อแบทช์ + เปลี่ยนชื่อหลายไฟล์โดยใช้รูปแบบชื่อไฟล์ + เปลี่ยนชื่อไฟล์เป็นชุด ชื่อไฟล์ ลำดับรูปแบบ นามสกุลวันที่ + เลือกไฟล์ที่จะเปลี่ยนชื่อ + รูปแบบชื่อไฟล์ + เปลี่ยนชื่อ + แหล่งที่มาของวันที่ + วันที่ EXIF ​​ถ่าย + วันที่แก้ไขไฟล์ + วันที่สร้างไฟล์ + วันที่ปัจจุบัน + วันที่ด้วยตนเอง + วันที่ด้วยตนเอง + เวลาด้วยตนเอง + วันที่ที่เลือกไม่พร้อมใช้งานสำหรับบางไฟล์ วันที่ปัจจุบันจะถูกใช้สำหรับพวกเขา + ป้อนรูปแบบชื่อไฟล์ + รูปแบบนี้สร้างชื่อไฟล์ที่ไม่ถูกต้องหรือยาวเกินไป + รูปแบบนี้สร้างชื่อไฟล์ที่ซ้ำกัน + ชื่อไฟล์ทั้งหมดตรงกับรูปแบบแล้ว + รูปแบบนี้ใช้โทเค็นที่ไม่สามารถเปลี่ยนชื่อเป็นชุดได้ + ชื่อก็จะเหมือนเดิม + เปลี่ยนชื่อไฟล์สำเร็จแล้ว + ไม่สามารถเปลี่ยนชื่อไฟล์บางไฟล์ได้ + ไม่ได้รับอนุญาต + ใช้ชื่อไฟล์ต้นฉบับโดยไม่มีนามสกุล ช่วยให้การระบุแหล่งที่มาไม่เสียหาย นอกจากนี้ยังรองรับการแบ่งส่วนด้วย \\o{start:end} การทับศัพท์ด้วย \\o{t} และแทนที่ด้วย \\o{s/old/new/} + เคาน์เตอร์เพิ่มขึ้นอัตโนมัติ รองรับการจัดรูปแบบที่กำหนดเอง: \\c{padding} (เช่น \\c{3} -&gt; 001), \\c{start:step} หรือ \\c{start:step:padding} + ชื่อของโฟลเดอร์หลักที่มีไฟล์ต้นฉบับอยู่ + ขนาดของไฟล์ต้นฉบับ รองรับหน่วย: \\z{b}, \\z{kb}, \\z{mb} หรือเพียง \\z สำหรับรูปแบบที่มนุษย์สามารถอ่านได้ + สร้าง Universally Unique Identifier (UUID) แบบสุ่มเพื่อให้แน่ใจว่าชื่อไฟล์ไม่ซ้ำกัน + การเปลี่ยนชื่อไฟล์ไม่สามารถยกเลิกได้ ตรวจสอบให้แน่ใจว่าชื่อใหม่ถูกต้องก่อนดำเนินการต่อ + ยืนยันการเปลี่ยนชื่อ + การตั้งค่าเมนูด้านข้าง + ขนาดเมนู + เมนูอัลฟ่า + สมดุลสีขาวอัตโนมัติ + การตัด + กำลังตรวจสอบลายน้ำที่ซ่อนอยู่ + พื้นที่จัดเก็บ + ขีดจำกัดแคช + ล้างแคชโดยอัตโนมัติเมื่อแคชมีขนาดใหญ่เกินขนาดนี้ + ช่วงเวลาการล้างข้อมูล + บ่อยแค่ไหนในการตรวจสอบว่าควรล้างแคชหรือไม่ + ในการเปิดตัวแอป + 1 วัน + 1 สัปดาห์ + 1 เดือน + ล้างแคชของแอปโดยอัตโนมัติตามขีดจำกัดและช่วงเวลาที่เลือก + พื้นที่เพาะปลูกที่สามารถเคลื่อนย้ายได้ + อนุญาตให้ย้ายพื้นที่ครอบตัดทั้งหมดโดยการลากเข้าไปข้างใน + รวม GIF + รวมคลิป GIF หลาย ๆ คลิปให้เป็นภาพเคลื่อนไหวเดียว + ลำดับคลิป GIF + ย้อนกลับ + เล่นแบบย้อนกลับ + บูมเมอแรง + เล่นไปข้างหน้าแล้วถอยหลัง + ปรับขนาดเฟรมให้เป็นปกติ + ปรับขนาดเฟรมให้พอดีกับคลิปที่ใหญ่ที่สุด ปิดเพื่อรักษาขนาดเดิม + ความล่าช้าระหว่างคลิป (มิลลิวินาที) + โฟลเดอร์ + เลือกภาพทั้งหมดจากโฟลเดอร์และโฟลเดอร์ย่อย + ตัวค้นหาซ้ำ + ค้นหาสำเนาที่ตรงกันและรูปภาพที่คล้ายกัน + โปรแกรมค้นหาที่ซ้ำกัน รูปภาพที่คล้ายกัน สำเนาถูกต้อง การล้างข้อมูล การจัดเก็บ dHash SHA-256 + เลือกภาพเพื่อค้นหารายการที่ซ้ำกัน + ความไวต่อความคล้ายคลึงกัน + สำเนาถูกต้อง + ภาพที่คล้ายกัน + เลือกรายการที่ซ้ำกันทุกประการ + เลือกทั้งหมด ยกเว้นที่แนะนำ + ไม่พบรายการที่ซ้ำกัน + ลองเพิ่มรูปภาพเพิ่มเติมหรือเพิ่มความละเอียดอ่อนของความคล้ายคลึงกัน + ไม่สามารถอ่าน %1$d รูปภาพ + %1$s • %2$s เรียกคืนได้ + %1$d กลุ่ม • %2$s เรียกคืนได้ + %1$d เลือกแล้ว • %2$s + สิ่งนี้ไม่สามารถยกเลิกได้ Android อาจขอให้คุณยืนยันการลบในกล่องโต้ตอบของระบบ + ไม่พบ + ย้ายไปเริ่มต้น + ย้ายไปสิ้นสุด + \ No newline at end of file diff --git a/core/resources/src/main/res/values-tr/strings.xml b/core/resources/src/main/res/values-tr/strings.xml new file mode 100644 index 0000000..5f402a1 --- /dev/null +++ b/core/resources/src/main/res/values-tr/strings.xml @@ -0,0 +1,3740 @@ + + + + + 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 + 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 + 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. + Çö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. + 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 + İçeriğe Duyarlı Bozulma + Bu eylemi tamamlamak için yeterli bellek yok. Lütfen daha küçük bir resim kullanmayı, diğer uygulamaları kapatmayı veya uygulamayı yeniden başlatmayı deneyin. + Paralel İşçiler + Dönüştürülen dosyalar orijinallerden daha büyük olacağından bu görüntüler kaydedilmedi. Orijinal görüntüleri silmeden önce bu listeyi kontrol edin. + Atlanan dosyalar: %1$s + Uygulama Günlükleri + Sorunları tespit etmek için uygulamanın günlüklerini görüntüleyin + Gruplandırılmış araçlardaki favoriler + Araçlar türe göre gruplandırıldığında favorileri sekme olarak ekler + Favoriyi son olarak göster + Favori araçlar sekmesini sonuna taşır + Yatay aralık + Dikey aralık + Geriye doğru enerji + İleri enerji algoritması yerine basit gradyan büyüklüğü enerji haritasını kullanın + Maskelenmiş alanı kaldır + Dikişler maskelenen bölgeden geçecek ve seçilen alanı korumak yerine yalnızca kaldırılacaktır + VHS NTSC + Gelişmiş NTSC ayarları + Daha güçlü VHS ve yayın eserleri için ekstra analog ayarlama + Renk kanaması + Bant aşınması + Takip + Luma yayması + Zil sesi + Kar + Alanı kullan + Filtre türü + Giriş luma filtresi + Giriş kroma düşük geçişi + Renk demodülasyonu + Faz kayması + Faz ofseti + Kafa değiştirme + Kafa değiştirme yüksekliği + Kafa değiştirme ofseti + Kafa değiştirme değişimi + Orta hat konumu + Orta hat titreşimi + Gürültü yüksekliğini izleme + İzleme dalgası + Kar takibi + Kar anizotropisinin izlenmesi + İzleme gürültüsü + Gürültü yoğunluğunun izlenmesi + Bileşik gürültü + Bileşik gürültü frekansı + Bileşik gürültü yoğunluğu + Bileşik gürültü detayı + Zil frekansı + Zil gücü + Luma gürültüsü + Luma gürültü frekansı + Luma gürültü yoğunluğu + Luma gürültü detayı + Renk gürültüsü + Kroma gürültü frekansı + Kroma gürültü yoğunluğu + Renk gürültüsü detayı + Kar yoğunluğu + Kar anizotropisi + Renk fazı gürültüsü + Renk fazı hatası + Renk gecikmesi yatay + Kroma gecikmesi dikey + VHS bant hızı + VHS kroma kaybı + VHS yoğunluğu keskinleştirir + VHS frekansı keskinleştirir + VHS kenar dalga yoğunluğu + VHS kenar dalga hızı + VHS kenar dalga frekansı + VHS kenar dalgası detayı + Çıkış kroma düşük geçişi + Yatay ölçeklendir + Dikey ölçeklendir + Ölçek faktörü X + Ölçek faktörü Y + Yaygın görüntü filigranlarını algılar ve bunları LaMa ile yeniden boyar. Algılama ve iç boyama modellerini otomatik olarak indirir + Döteranopi + Resmi Genişlet + YOLO v11 Segmentasyonunu kullanan nesne segmentasyonu tabanlı arka plan sökücü + Çizgi açısını göster + Çizim sırasında geçerli çizgi dönüşünü derece cinsinden gösterir + Hareketli Emojiler + Mevcut emojileri animasyon olarak göster + Orijinal Klasöre Kaydet + Yeni dosyaları seçilen klasör yerine orijinal dosyanın yanına kaydedin + Filigran PDF + Yeterli hafıza yok + Resim muhtemelen bu cihazda işlenemeyecek kadar büyük veya sistemin kullanılabilir belleği tükendi. Görüntü çözünürlüğünü azaltmayı, diğer uygulamaları kapatmayı veya daha küçük bir dosya seçmeyi deneyin. + Hata Ayıklama Menüsü + Uygulama işlevlerini test etmeye yönelik menü; bunun üretim sürümünde gösterilmesi amaçlanmamıştır. + sağlayıcı + PaddleOCR, cihazınızda ek ONNX modelleri gerektirir. %1$s verilerini indirmek istiyor musunuz? + Evrensel + Korece + Latince + Doğu Slav + Tay dili + Yunan + İngilizce + Kiril + Arapça + Devanagari + Tamilce + Telugu dili + Gölgelendirici + Gölgelendirici ön ayarı + Gölgelendirici seçilmedi + Gölgelendirici Stüdyosu + Özel parça gölgelendiricileri oluşturun, düzenleyin, doğrulayın, içe aktarın ve dışa aktarın + Kaydedilen gölgelendiriciler + Kaydedilen gölgelendiricileri açın, çoğaltın, dışa aktarın, paylaşın veya silin + Yeni gölgelendirici + Gölgelendirici dosyası + .itshader JSON + %1$d parametreleri + Gölgelendirici kaynağı + void main()\'in yalnızca gövdesini yazın. UV koordinatları için TextureCoorder\'ı ve kaynak örnekleyici olarak inputImageTexture\'ı kullanın. + Fonksiyonlar + İsteğe bağlı yardımcı işlevler, sabitler ve yapılar void main()\'den önce eklenir. Üniformalar parametrelerden oluşturulur. + Gölgelendiricinin düzenlenebilir değerlere ihtiyacı olduğunda buraya üniformalar ekleyin. + Gölgelendiriciyi sıfırla + Mevcut gölgelendirici taslağı temizlenecek + Geçersiz gölgelendirici + Desteklenmeyen gölgelendirici sürümü %1$d. Desteklenen sürüm: %2$d. + Gölgelendirici adı boş bırakılmamalıdır. + Gölgelendirici kaynağı boş olmamalıdır. + Gölgelendirici kaynağı desteklenmeyen karakterler içeriyor: %1$s. Yalnızca ASCII GLSL kaynağını kullanın. + \\\"%1$s\\\" parametresi birden fazla bildirildi. + Parametre adları boş bırakılmamalıdır. + \\\"%1$s\\\" parametresi ayrılmış bir GPUImage adı kullanır. + \\\"%1$s\\\" parametresi geçerli bir GLSL tanımlayıcısı olmalıdır. + \\\"%1$s\\\" parametresi %2$s değer türüne sahip \\\"%3$s\\\", beklenen \\\"%4$s\\\". + \\\"%1$s\\\" parametresi bool değerleri için minimum veya maksimumu tanımlayamıyor. + \\\"%1$s\\\" parametresinin minimum değeri maksimum değerinden büyük. + \\\"%1$s\\\" parametresi varsayılanı minimumdan düşük. + \\\"%1$s\\\" parametresi varsayılanı maks. değerden büyük. + Gölgelendiricinin \\\"tek tip örnekleyici2D %1$s;\\\" bildiriminde bulunması gerekir. + Gölgelendiricinin \\\"değişen vec2 %1$s;\\\" bildiriminde bulunması gerekir. + Gölgelendiricinin \\\"void main()\\\" tanımlaması gerekir. + Shader\'ın gl_FragColor\'a bir renk yazması gerekir. + ShaderToy mainImage gölgelendiricileri desteklenmez. GPUImage\'ın void main() sözleşmesini kullanın. + Animasyonlu ShaderToy üniforması \\\"iTime\\\" desteklenmiyor. + Animasyonlu ShaderToy üniforması \\\"iFrame\\\" desteklenmiyor. + ShaderToy üniforması \\\"iResolution\\\" desteklenmiyor. + ShaderToy iChannel dokuları desteklenmez. + Dış dokular desteklenmez. + Harici doku uzantıları desteklenmez. + Libretro gölgelendirici parametreleri desteklenmez. + Yalnızca bir giriş dokusu desteklenir. \\\"üniforma %1$s %2$s;\\\" öğesini kaldırın. + \\\"%1$s\\\" parametresi \\\"üniform %2$s %1$s;\\\" olarak bildirilmelidir. + \\\"%1$s\\\" parametresi \\\"tekdüze %2$s %1$s;\\\" olarak bildirildi, beklenen \\\"tekdüze %3$s %1$s;\\\". + Gölgelendirici dosyası boş. + Gölgelendirici dosyası, sürüm, ad ve gölgelendirici alanlarını içeren bir .itshader JSON nesnesi olmalıdır. + Gölgelendirici dosyası geçerli bir JSON değil veya .itshader biçimiyle eşleşmiyor. + \\\"%1$s\\\" alanı gereklidir. + %1$s gerekli. + %1$s \\\"%2$s\\\" desteklenmiyor. Desteklenen türler: %3$s. + \\\"%2$s\\\" parametreleri için %1$s gereklidir. + %1$s sonlu bir sayı olmalıdır. + %1$s bir tamsayı olmalıdır. + %1$s doğru veya yanlış olmalıdır. + %1$s bir renk dizisi, RGB/RGBA dizisi veya renk nesnesi olmalıdır. + %1$s #RRGGBB veya #RRGGBBAA biçimini kullanmalıdır. + %1$s geçerli onaltılık renk kanalları içermelidir. + %1$s 3 veya 4 renk kanalı içermelidir. + %1$s 0 ile 255 arasında olmalıdır. + %1$s iki sayılı bir dizi veya x ve y\'den oluşan bir nesne olmalıdır. + %1$s tam olarak 2 sayı içermelidir. + Kopyalamak + EXIF\'i her zaman temizle + Bir araç meta verileri saklamayı talep ettiğinde bile kaydetme sırasında görüntü EXIF ​​verilerini kaldırın + Yardım ve İpuçları + %1$d eğiticiler + Aracı aç + Ana araçları, dışa aktarma seçeneklerini, PDF akışlarını, renk yardımcı programlarını ve sık karşılaşılan sorunlara yönelik düzeltmeleri öğrenin + Başlarken + Araçları seçin, dosyaları içe aktarın, sonuçları kaydedin ve ayarları daha hızlı yeniden kullanın + Resim düzenleme + Resimleri yeniden boyutlandırın, kırpın, filtreleyin, silin, çizin ve filigran ekleyin + Dosyalar ve meta veriler + Formatları dönüştürün, çıktıları karşılaştırın, gizliliği koruyun ve dosya adlarını kontrol edin + PDF ve belgeler + PDF\'ler oluşturun, belgeleri, OCR sayfalarını tarayın ve dosyaları paylaşıma hazırlayın + Metin, QR ve veriler + Metni tanıyın, kodları tarayın, Base64\'ü kodlayın, karmaları kontrol edin ve dosyaları paketleyin + Renk araçları + Renkleri seçin, paletler oluşturun, renk kitaplıklarını keşfedin ve degradeler oluşturun + Yaratıcı araçlar + SVG, ASCII sanatı, gürültü dokuları, gölgelendiriciler ve deneysel görseller oluşturun + Sorun giderme + Ağır dosyalar, şeffaflık, paylaşım, içe aktarma ve raporlama sorunlarını düzeltin + Doğru aracı seçin + Doğru yerden başlamak için kategorileri, aramayı, favorileri ve paylaşım işlemlerini kullanın + En iyi giriş noktasından başlayın + Image Toolbox\'ta birçok odaklanmış araç vardır ve bunların birçoğu ilk bakışta örtüşür. Bitirmek istediğiniz görevden başlayın ve ardından sonucun en önemli bölümünü kontrol eden aracı seçin: boyut, format, meta veriler, metin, PDF sayfaları, renkler veya düzen. + Yeniden boyutlandırma, PDF, EXIF, OCR, QR veya renk gibi eylem adını bildiğinizde aramayı kullanın. + Yalnızca görev türünü bildiğiniz bir kategori açın, ardından sık kullandığınız araçları favorileriniz olarak sabitleyin. + Başka bir uygulama Image Toolbox\'ta bir görsel paylaştığında, paylaşım sayfası akışından aracı seçin. + Sonuçları içe aktarın, kaydedin ve paylaşın + Girdiyi seçmekten düzenlenen dosyayı dışa aktarmaya kadar olağan akışı anlayın + Ortak düzenleme akışını takip edin + Çoğu araç aynı ritmi takip eder: girişi seçin, seçenekleri ayarlayın, önizleyin, ardından kaydedin veya paylaşın. Önemli olan ayrıntı, kaydetmenin bir çıktı dosyası oluşturmasıdır; paylaşma ise genellikle başka bir uygulamaya hızlı geçiş için en iyisidir. + Seçici izin verdiğinde, tek görüntülü araçlar için bir dosya veya toplu araçlar için birkaç dosya seçin. + Kaydetmeden önce önizleme ve çıktı kontrollerini, özellikle de format, kalite ve dosya adı seçeneklerini kontrol edin. + Hızlı bir geçiş için paylaşımı kullanın veya seçili klasörde kalıcı bir kopyaya ihtiyaç duyduğunuzda kaydedin. + Aynı anda birçok görüntüyle çalışın + Toplu araçlar, tekrarlanan yeniden boyutlandırma, dönüştürme, sıkıştırma ve adlandırma görevleri için en iyisidir + Tahmin edilebilir bir parti hazırlayın + Toplu işleme, her çıktının aynı kurallara uyması gerektiğinde en hızlıdır. Aynı zamanda büyük bir hata yapılabilecek en kolay yerdir; bu nedenle, uzun bir dışa aktarma işlemine başlamadan önce adlandırma, kalite, meta veriler ve klasör davranışını seçin. + İlgili tüm görselleri birlikte seçin ve çıktının sıralamaya bağlı olması durumunda sıralamayı koruyun. + Dışa aktarmaya başlamadan önce yeniden boyutlandırma, biçimlendirme, kalite, meta veriler ve dosya adı kurallarını bir kez ayarlayın. + Kaynak dosyalar büyük olduğunda veya hedef format sizin için yeni olduğunda önce küçük bir test grubu çalıştırın. + Hızlı tek düzenleme + Tek bir görüntü üzerinde birkaç basit değişikliğe ihtiyaç duyduğunuzda hepsi bir arada düzenleyiciyi kullanın + Araç atlamadan bir görüntüyü bitirin + Tek Düzenleme, görüntünün tek bir özel işlem yerine küçük bir değişiklik zincirine ihtiyaç duyduğu durumlarda kullanışlıdır. Toplu iş akışı oluşturmadan hızlı temizleme, ön izleme ve son bir kopyayı dışa aktarma genellikle daha hızlıdır. + Ortak ayarlamalar, işaretleme, kırpma, döndürme veya dışa aktarma değişiklikleri gerektiren bir görüntü için Tek Düzenleme\'yi açın. + Düzenlemeleri, filtrelerden önce kırpma ve son sıkıştırmadan önce filtreler gibi, ilk önce en az pikseli değiştirecek şekilde uygulayın. + Toplu işleme, tam dosya boyutu hedeflerine, PDF çıktısına, OCR\'ye veya meta veri temizlemeye ihtiyaç duyduğunuzda bunun yerine özel bir araç kullanın. + Ön ayarları ve ayarları kullanma + Tekrarlanan seçimleri kaydedin ve uygulamayı tercih ettiğiniz iş akışına göre ayarlayın + Aynı kurulumu tekrarlamaktan kaçının + Aynı tür dosyayı sıklıkla dışa aktardığınızda ön ayarlar ve ayarlar kullanışlıdır. İyi bir ön ayar, tekrarlanan bir manuel kontrol listesini tek dokunuşa dönüştürürken genel ayarlar, yeni araçların başlayacağı varsayılanları tanımlar. + Ortak çıktı boyutları, formatları, kalite değerleri veya adlandırma kalıpları için hazır ayarlar oluşturun. + Varsayılan format, kalite, kaydetme klasörü, dosya adı, seçici ve arayüz davranışı için Ayarları inceleyin. + Uygulamayı yeniden yüklemeden veya başka bir cihaza geçmeden önce ayarları yedekleyin. + Yararlı varsayılanları ayarlayın + Varsayılan formatı, kaliteyi, ölçek modunu, yeniden boyutlandırma türünü ve renk alanını bir kez seçin + Yeni araçların hedefinize daha yakın olmasını sağlayın + Varsayılan değerler yalnızca kozmetik tercihler değildir. Birçok araçta hangi formatın, kalitenin, yeniden boyutlandırma davranışının, ölçek modunun ve renk alanının ilk önce görüneceğine karar verirler; bu, her dışa aktarmada aynı seçeneklerin yeniden seçilmesinin önlenmesine yardımcı olur. + Varsayılan görüntü formatını ve kalitesini, fotoğraflar için JPEG veya alfa için PNG/WebP gibi her zamanki hedefinize uyacak şekilde ayarlayın. + Sık sık katı boyutlar, sosyal platformlar veya belge sayfaları için dışa aktarma yapıyorsanız, varsayılan yeniden boyutlandırma türünü ve ölçek modunu ayarlayın. + Renk alanı varsayılanlarını yalnızca iş akışınızın ekranlar, yazdırma veya harici düzenleyiciler arasında tutarlı renk işlemeye ihtiyacı olduğunda değiştirin. + Seçici ve başlatıcı + Uygulama çok fazla iletişim kutusu açtığında veya yanlış yerden başladığında seçici ayarlarını kullanın + Dosya açma alışkanlığınıza uygun hale getirin + Seçici ve başlatıcı ayarları, Image Toolbox\'ın ana ekrandan mı, bir araçtan mı yoksa bir dosya seçim akışından mı başlatılacağını kontrol eder. Bu seçenekler, genellikle Android paylaşım sayfasından giriş yaptığınızda veya uygulamanın daha çok bir araç başlatıcı gibi görünmesini istediğinizde özellikle kullanışlıdır. + Sistem seçici dosyaları gizlerse, çok fazla yeni öğe gösterirse veya bulut dosyalarını kötü bir şekilde işliyorsa fotoğraf seçici ayarlarını kullanın. + Atlamayı yalnızca genellikle paylaşımdan girdiğiniz veya kaynak dosyayı zaten bildiğiniz araçlar için etkinleştirin. + Image Toolbox\'ın normal bir ana ekrandan çok bir araç seçici gibi davranmasını istiyorsanız başlatıcı modunu gözden geçirin. + Resim kaynağı + Galeriler, dosya yöneticileri ve bulut dosyalarıyla en iyi şekilde çalışan seçici modunu seçin + Dosyaları doğru kaynaktan seçin + Görüntü Kaynağı ayarları, uygulamanın Android\'den görselleri nasıl isteyeceğini etkiler. En iyi seçim, dosyalarınızın sistem galerisinde mi, bir dosya yöneticisi klasöründe mi, bir bulut sağlayıcısında mı yoksa geçici içerik paylaşan başka bir uygulamada mı bulunduğuna bağlıdır. + Yaygın galeri görselleri için sistemle en entegre deneyimi istediğinizde varsayılan seçiciyi kullanın. + Albümler, klasörler, bulut dosyaları veya standart olmayan formatlar beklediğiniz yerde görünmüyorsa seçici modunu değiştirin. + Paylaşılan bir dosya kaynak uygulamadan çıktıktan sonra kaybolursa dosyayı kalıcı bir seçici aracılığıyla yeniden açın veya önce yerel bir kopyayı kaydedin. + Favoriler ve paylaşım araçları + Ana ekrana odaklanın ve paylaşım akışlarında anlamlı olmayan araçları gizleyin + Takım listesi gürültüsünü azaltın + Sık kullanılanlar ve paylaşım için gizlenen ayarlar, her gün yalnızca birkaç araç kullandığınızda kullanışlıdır. Ayrıca başka bir uygulamanın gönderdiği dosya türünü kullanamayan araçları gizleyerek paylaşım akışını pratik tutarlar. + Sık kullanılan araçları sabitleyin, böylece araçlar kategoriye göre gruplandırıldığında bile bunlara kolayca erişebilirsiniz. + Başka bir uygulamadan gelen dosyalarla ilgisiz olduklarında araçları paylaşım akışlarından gizleyin. + Favorileri sonda veya ilgili araçlarla gruplandırılmış olarak tercih ediyorsanız, favori sıralama ayarlarını kullanın. + Ayarları yedekle + Ön ayarları, tercihleri ​​ve uygulama davranışını güvenli bir şekilde başka bir yüklemeye taşıyın + Kurulumunuzu taşınabilir tutun + Yedekleme ve Geri Yükleme, ön ayarları, klasörleri, dosya adı kurallarını, araç sırasını ve görsel tercihleri ​​ayarladıktan sonra en yararlı olur. Yedekleme, iş akışınızı bellekten yeniden oluşturmanıza gerek kalmadan ayarları denemenize veya uygulamayı yeniden yüklemenize olanak tanır. + Ön ayarları, dosya adı davranışını, varsayılan formatları veya araç düzenlemesini değiştirdikten sonra bir yedek oluşturun. + Verileri temizlemeyi, yeniden yüklemeyi veya cihazları taşımayı planlıyorsanız yedeği uygulama klasörünün dışında bir yerde saklayın. + Önemli toplu işlemleri işlemeden önce ayarları geri yükleyin; böylece varsayılanlar ve kaydetme davranışı eski kurulumunuzla eşleşir. + Resimleri yeniden boyutlandırın ve dönüştürün + Bir veya daha fazla görselin boyutunu, formatını, kalitesini ve meta verilerini değiştirme + Yeniden boyutlandırılmış kopyalar oluşturun + Yeniden Boyutlandır ve Dönüştür, öngörülebilir boyutlar ve daha hafif çıktı dosyaları için ana araçtır. Görüntünün boyutu, çıktı formatı ve meta veri davranışının aynı anda önemli olduğu durumlarda bunu kullanın. + Bir veya daha fazla görsel seçin, ardından boyutların, ölçeğin veya dosya boyutunun en önemli olanı seçin. + Çıktı formatını ve kalitesini seçin; şeffaflığın korunması gerektiğinde PNG veya WebP kullanın. + Sonucu önizleyin, ardından orijinalleri değiştirmeden oluşturulan kopyaları kaydedin veya paylaşın. + Dosya boyutuna göre yeniden boyutlandır + Bir web sitesi, mesajlaşma programı veya form ağır görselleri reddettiğinde boyut sınırını hedefleyin + Katı yükleme sınırlarına uyun + Hedefin yalnızca belirli bir sınırın altındaki dosyaları kabul etmesi durumunda, dosya boyutuna göre yeniden boyutlandırma, kalite değerlerini tahmin etmekten daha iyidir. Araç, sonucu kullanılabilir tutmaya çalışırken hedefe ulaşmak için sıkıştırmayı ve boyutları ayarlayabilir. + Meta veriler ve sağlayıcı değişikliklerine yer bırakmak için hedef boyutunu gerçek yükleme sınırının biraz altında girin. + Hedef küçük olduğunda fotoğraflar için kayıplı formatları tercih edin; PNG yeniden boyutlandırıldıktan sonra bile büyük kalabilir. + Dışa aktarma sonrasında yüzleri, metinleri ve kenarları inceleyin çünkü agresif boyutlu hedefler görünür yapay yapılara neden olabilir. + Yeniden boyutlandırmayı sınırla + Yalnızca maksimum genişlik, yükseklik veya dosya sınırlamalarınızı aşan görselleri küçültün + Zaten iyi durumda olan görselleri yeniden boyutlandırmaktan kaçının + Yeniden Boyutlandırmayı Sınırla, bazı görüntülerin çok büyük olduğu ve diğerlerinin zaten hazırlandığı karma klasörler için kullanışlıdır. Her dosyayı aynı yeniden boyutlandırmaya zorlamak yerine, kabul edilebilir görüntülerin orijinal kalitelerine daha yakın olmasını sağlar. + Her dosyayı körü körüne yeniden boyutlandırmak yerine hedefe göre maksimum boyutları veya sınırları belirleyin. + Çıkışların kaynaklardan daha ağır olmasını önlemek istediğinizde, daha büyükse atla stili davranışını etkinleştirin. + Kaynak boyutlarının çok farklı olduğu farklı kameralardan, ekran görüntülerinden veya indirmelerden gelen gruplar için bunu kullanın. + Kırpın, döndürün ve düzeltin + Görüntüyü dışa aktarmadan veya diğer araçlarda kullanmadan önce önemli alanı çerçeveleyin + Kompozisyonu temizleyin + Önce kırpma, daha sonraki filtreleri, OCR\'yi, PDF sayfalarını ve sonuçları paylaşmayı daha temiz hale getirir. + Kırp\'ı açın ve ayarlamak istediğiniz görüntüyü seçin. + Çıktının bir gönderiye, belgeye veya avatara sığması gerektiğinde ücretsiz kırpma veya en boy oranı kullanın. + Kaydetmeden önce döndürün veya çevirin, böylece daha sonra araçlar düzeltilmiş görüntüyü alır. + Filtreleri ve ayarlamaları uygulama + Düzenlenebilir bir filtre yığını oluşturun ve dışa aktarmadan önce sonucu önizleyin + Görüntü görünümünü ayarlayın + Filtreler hızlı düzeltmeler, stilize çıktılar ve görüntüleri OCR veya yazdırma için hazırlamak için kullanışlıdır. Görüntü metin veya ince ayrıntılar içerdiğinde kontrast, keskinlik ve parlaklıkta yapılan küçük değişiklikler, ağır etkilerden daha önemli olabilir. + Filtreleri tek ekleyin ve her değişiklikten sonra önizlemeyi takip edin. + Göreve bağlı olarak parlaklığı, kontrastı, keskinliği, bulanıklığı, rengi veya maskeleri ayarlayın. + Yalnızca önizleme hedef cihazınız, belgeniz veya sosyal platformunuzla eşleştiğinde dışa aktarın. + Önce önizleme + Daha ağır bir düzenleme, dönüştürme veya temizleme aracı seçmeden önce görselleri inceleyin + Gerçekte ne aldığınızı kontrol edin + Görüntü Önizleme, bir dosya sohbetten, bulut depolama alanından veya başka bir uygulamadan geldiğinde ve ne içerdiğinden emin olmadığınız durumlarda kullanışlıdır. Öncelikle boyutların, şeffaflığın, yönlendirmenin ve görsel kalitenin kontrol edilmesi, doğru takip aracının seçilmesine yardımcı olur. + Onlarla ne yapacağınıza karar vermeden önce birkaç görüntüyü incelemeniz gerektiğinde Görüntü Önizleme\'yi açın. + Döndürme sorunlarına, şeffaf alanlara, sıkıştırma kusurlarına ve beklenmedik derecede büyük boyutlara bakın. + Yalnızca neyin düzeltilmesi gerektiğini öğrendikten sonra Yeniden Boyutlandırma, Kırpma, Karşılaştırma, EXIF ​​veya Arka Plan Sökücü\'ye geçin. + Arka planı kaldırma veya geri yükleme + Geri yükleme araçlarıyla arka planları otomatik olarak silin veya kenarları manuel olarak iyileştirin + Konuyu koruyun, gerisini kaldırın + Arka planı kaldırma, otomatik seçimi dikkatli manuel temizlemeyle birleştirdiğinizde en iyi sonucu verir. Son dışa aktarma formatı maske kadar önemlidir çünkü JPEG, önizleme doğru görünse bile şeffaf alanları düzleştirir. + Nesne arka plandan net bir şekilde ayrılmışsa otomatik kaldırmayla başlayın. + Saçları, köşeleri, gölgeleri ve küçük ayrıntıları düzeltmek için yakınlaştırın ve silme ve geri yükleme arasında geçiş yapın. + Son dosyada şeffaflığa ihtiyacınız olduğunda PNG veya WebP olarak kaydedin. + Çizim yapın, işaretleyin ve filigran ekleyin + Oklar, notlar, vurgular, imzalar veya tekrarlanan filigran kaplamaları ekleyin + Görünür bilgiler ekleyin + İşaretleme araçları bir görselin açıklanmasına yardımcı olurken, filigranlama dışa aktarılan dosyaların markalanmasına veya korunmasına yardımcı olur. + Serbest notlara, şekillere, vurgulamaya veya hızlı açıklamalara ihtiyaç duyduğunuzda Çizim\'i kullanın. + Çıktılarda aynı metin veya resim işaretinin tutarlı bir şekilde görünmesi gerektiğinde Filigranlamayı kullanın. + İşaretin görünür olmasını ancak dikkat dağıtıcı olmamasını sağlamak için dışa aktarmadan önce opaklığı, konumu ve ölçeği kontrol edin. + Varsayılanları çiz + Daha hızlı işaretleme için çizgi genişliğini, rengini, yol modunu, yönlendirme kilidini ve büyüteci ayarlayın + Çizimin öngörülebilir olmasını sağlayın + Çizim ayarları, ekran görüntülerine açıklama eklediğinizde, belgeleri işaretlediğinizde veya görüntüleri sık imzaladığınızda faydalıdır. Varsayılanlar kurulum süresini kısaltırken, büyüteç ve yön kilidi küçük ekranlarda hassas düzenlemeleri kolaylaştırır. + Varsayılan çizgi genişliğini ayarlayın ve rengi oklar, vurgular veya imzalar için en çok kullandığınız değerlere göre ayarlayın. + Genellikle aynı türde konturlar, şekiller veya işaretlemeler çizdiğinizde varsayılan yol modunu kullanın. + Parmak girişi küçük ayrıntıların doğru şekilde yerleştirilmesini zorlaştırdığında büyüteci veya yön kilidini etkinleştirin. + Çoklu görüntü düzenleri + Ekran görüntülerini, taramaları veya karşılaştırma şeritlerini düzenlemeden önce doğru çoklu görüntü aracını seçin + Doğru düzen aracını kullanın + Bu araçlar kulağa benzer geliyor ancak her biri farklı türde çoklu görüntü çıkışı için ayarlandı. Önce doğru olanı seçmek, farklı bir sonuç için tasarlanmış düzen kontrolleriyle mücadele etmekten kaçınır. + Tek bir kompozisyonda birden fazla görsel içeren tasarlanmış düzenler için Collage Maker\'ı kullanın. + Görüntülerin uzun bir dikey veya yatay şerit haline gelmesi gerektiğinde Görüntü Birleştirme veya Yığınlama\'yı kullanın. + Büyük bir görüntünün birkaç küçük parçaya dönüşmesi gerektiğinde Görüntü Bölme\'yi kullanın. + Resim formatlarını dönüştürün + Pikselleri manuel olarak düzenlemeden JPEG, PNG, WebP, AVIF, JXL ve diğer kopyaları oluşturun + İşin formatını seçin + Fotoğraflar, ekran görüntüleri, şeffaflık, sıkıştırma veya uyumluluk açısından farklı formatlar daha iyidir. Hedef uygulamanın neyi desteklediğini ve kaynağın tutmak istediğiniz alfa, animasyon veya meta verileri içerip içermediğini anladığınızda dönüştürme en güvenli yöntemdir. + Uyumlu fotoğraflar için JPEG, kayıpsız görüntüler ve alfa için PNG ve kompakt paylaşım için WebP kullanın. + Format desteklediğinde kaliteyi ayarlayın; daha düşük değerler boyutu azaltır ancak yapaylıklar ekleyebilir. + Dönüştürülen dosyaların hedef uygulamada doğru şekilde açıldığını doğrulayana kadar orijinalleri saklayın. + EXIF\'i ve gizliliği kontrol edin + Hassas fotoğrafları paylaşmadan önce meta verileri görüntüleyin, düzenleyin veya kaldırın + Gizli görüntü verilerini kontrol edin + EXIF, kamera verilerini, tarihleri, konumu, yönü ve görüntüde görünmeyen diğer ayrıntıları içerebilir. Herkese açık paylaşımlardan, destek raporlarından, pazar yeri listelerinden veya özel bir yeri ortaya çıkarabilecek herhangi bir fotoğraftan önce kontrol etmekte fayda var. + Konum veya özel cihaz bilgisi içerebilecek fotoğrafları paylaşmadan önce EXIF ​​araçlarını açın. + Hassas alanları kaldırın veya tarih, yön, yazar veya kamera verileri gibi hatalı etiketleri düzenleyin. + Yeni bir kopya kaydedin ve gizliliğin önemli olduğu durumlarda orijinal yerine bu kopyayı paylaşın. + Otomatik EXIF ​​temizleme + Tarihlerin korunup korunmayacağına karar verirken meta verileri varsayılan olarak kaldırın + Gizliliği varsayılan yapın + EXIF ayarları grubu, gizlilik temizliğinin her dışa aktarımda hatırlanması yerine otomatik olarak gerçekleşmesini istediğinizde kullanışlıdır. Tarihleri ​​Sakla Saat ayrı bir karardır çünkü tarihler sıralama için faydalı ancak paylaşım için hassas olabilir. + Dışa aktarmalarınızın çoğu herkese açık veya yarı herkese açık paylaşıma yönelik olduğunda her zaman temiz EXIF\'i etkinleştirin. + Yalnızca yakalama süresini korumak, her zaman damgasını kaldırmaktan daha önemli olduğunda Tarih Saati Koru seçeneğini etkinleştirin. + Bazı alanları tutmanız ve bazılarını kaldırmanız gereken tek seferlik dosyalar için manuel EXIF ​​düzenlemeyi kullanın. + Dosya adlarını kontrol edin + Ön ekleri, son ekleri, zaman damgalarını, ön ayarları, sağlama toplamlarını ve sıra numaralarını kullanma + İhracatın bulunmasını kolaylaştırın + İyi bir dosya adı modeli, toplu işlerin sıralanmasına yardımcı olur ve yanlışlıkla üzerine yazmayı önler. Dosya adı ayarları, benzer adların hızla kafa karıştırıcı hale gelebileceği dışa aktarımlar kaynak klasöre geri döndüğünde özellikle kullanışlıdır. + Dosya adı önekini, son ekini, zaman damgasını, orijinal adını, ön ayar bilgisini veya sıra seçeneklerini Ayarlar\'da ayarlayın. + Sayfalar, çerçeveler veya kolajlar gibi sıranın önemli olduğu gruplar için sıra numaralarını kullanın. + Üzerine yazmayı yalnızca mevcut dosyaları değiştirmenin geçerli klasör için güvenli olduğundan emin olduğunuzda etkinleştirin. + Dosyaların üzerine yaz + Çıktıları yalnızca iş akışınız kasıtlı olarak yıkıcı olduğunda eşleşen adlarla değiştirin + Üzerine yazma modunu dikkatli kullanın + Dosyaların Üzerine Yaz, adlandırma çakışmalarının ele alınma biçimini değiştirir. Aynı klasöre tekrar dışa aktarmalar için kullanışlıdır, ancak dosya adı kalıbınız yeniden aynı adı verirse daha önceki sonuçların yerini de alabilir. + Üzerine yazmayı yalnızca eski oluşturulan dosyaların değiştirilmesinin beklendiği ve kurtarılabileceği klasörler için etkinleştirin. + Orijinalleri, istemci dosyalarını, tek seferlik taramaları veya yedeği olmayan herhangi bir şeyi dışa aktarırken üzerine yazmayı devre dışı bırakın. + Üzerine yazmayı bilinçli bir dosya adı modeliyle birleştirerek yalnızca amaçlanan çıktıların değiştirilmesini sağlayın. + Dosya adı kalıpları + Orijinal adları, önekleri, sonekleri, zaman damgalarını, hazır ayarları, ölçek modunu ve sağlama toplamlarını birleştirin + Çıktıyı açıklayan adlar oluşturun + Dosya adı modeli ayarları, dışa aktarılan dosyaları, başlarına ne geldiğine dair yararlı bir geçmişe dönüştürebilir. Bu, gruplar halinde önemlidir çünkü klasör görünümü orijinal boyutu, ön ayarı, formatı ve işleme sırasını ayırt edebileceğiniz tek yer olabilir. + Manuel sıralama için okunabilir adlara ihtiyaç duyduğunuzda orijinal dosya adını, öneki, son eki ve zaman damgasını kullanın. + Çıktıların nasıl üretildiklerini belgelemesi gerektiğinde ön ayar, ölçek modu, dosya boyutu veya sağlama toplamı bilgilerini ekleyin. + Dosyaların orijinallerle veya sayfa sırası ile eşleştirilmesi gereken iş akışları için rastgele adlardan kaçının. + Dosyaların nereye kaydedileceğini seçin + Klasörleri, orijinal klasöre kaydetmeyi ve tek seferlik kaydetme konumlarını ayarlayarak dışa aktarmaları kaybetmekten kaçının + Çıktıları öngörülebilir tutun + Kaydetme davranışı, çok sayıda dosyayı işlerken veya bulut sağlayıcılardan dosya paylaşırken en çok önem taşır. Klasör ayarları, çıktıların bilinen bir yerde mi toplanacağını, orijinallerin yanında mı görüneceğini, yoksa belirli bir görev için geçici olarak başka bir yere mi gideceğini belirler. + Dışa aktarmaların her zaman tek bir yerde olmasını istiyorsanız varsayılan bir kaydetme klasörü ayarlayın. + Çıktının kaynak dosyanın yakınında kalması gereken temizleme iş akışları için orijinal klasöre kaydet\'i kullanın. + Tek bir görev, varsayılan ayarınızı değiştirmeden farklı bir hedefe ihtiyaç duyduğunda tek seferlik kaydetme konumunu kullanın. + Tek seferlik kaydetme klasörleri + Sonraki dışa aktarımları normal hedefinizi değiştirmeden geçici bir klasöre gönderin + Özel işleri ayrı tutun + Tek seferlik kaydetme konumu, normal Image Toolbox klasörünüzle karıştırılmaması gereken geçici projeler, istemci klasörleri, temizleme oturumları veya dışa aktarmalar için kullanışlıdır. Varsayılan kurulumunuzu yeniden yazmanıza gerek kalmadan kısa ömürlü bir hedef sağlar. + Normal dışa aktarma konumunuzun dışına ait bir göreve başlamadan önce tek seferlik bir klasör seçin. + Kısa projeler, paylaşılan klasörler veya çıktıların bir arada gruplandırılması gereken gruplar için kullanın. + İşten sonra varsayılan klasöre dönün, böylece daha sonra dışa aktarmalar beklenmedik bir şekilde geçici konuma düşmez. + Kaliteyi ve dosya boyutunu dengeleyin + Tahmin etmek yerine boyutları, formatı veya kaliteyi ne zaman değiştirmeniz gerektiğini anlayın + Dosyaları kasıtlı olarak küçültün + Dosya boyutu görselin boyutlarına, formatına, kalitesine, şeffaflığına ve içeriğin karmaşıklığına bağlıdır. Bir dosya hala çok ağırsa, boyutları değiştirmek genellikle kaliteyi tekrar düşürmekten daha fazla yardımcı olur. + Resim hedefin görüntüleyebileceğinden daha büyük olduğunda ilk önce boyutları yeniden boyutlandırın. + Yalnızca kayıplı formatlar için kaliteyi düşürün ve dışa aktarma sonrasında metni, kenarları, degradeleri ve yüzleri kontrol edin. + Kalite değişiklikleri yeterli olmadığında formatı değiştirin; örneğin paylaşım için WebP veya net şeffaf varlıklar için PNG. + Animasyonlu formatlarla çalışın + Dosyada tek bir bitmap yerine çerçeveler bulunduğunda GIF, APNG, WebP ve JXL araçlarını kullanın + Her görüntüye hareketsizmiş gibi davranmayın + Animasyon formatları, kareleri, süreyi ve uyumluluğu anlayan araçlara ihtiyaç duyar. + Bu formatlar için kare düzeyinde işlemlere ihtiyaç duyduğunuzda GIF veya APNG araçlarını kullanın. + Modern sıkıştırmaya veya animasyonlu WebP çıktısına ihtiyaç duyduğunuzda WebP araçlarını kullanın. + Bazı platformlar animasyonlu görüntüleri dönüştürdüğü veya düzleştirdiği için paylaşmadan önce animasyon zamanlamasını önizleyin. + Çıktıyı karşılaştırın ve önizleyin + Dışa aktarmayı saklamadan önce değişiklikleri, dosya boyutunu ve görsel farklılıkları inceleyin + Paylaşmadan önce doğrulayın + Karşılaştırma araçları, sıkıştırma bozukluklarının, yanlış renklerin veya istenmeyen düzenlemelerin erkenden yakalanmasına yardımcı olur. + İki sürümü yan yana incelemek istediğinizde Karşılaştırma\'yı açın. + Sorunların gözden kaçırılmasının en kolay olduğu kenarlara, metinlere, degradelere ve şeffaf bölgelere yakınlaştırın. + Çıktı çok ağır veya bulanıksa önceki araca dönün ve kaliteyi veya formatı ayarlayın. + PDF araçları merkezini kullanın + PDF dosyalarını birleştirin, bölün, döndürün, kaldırın, sıkıştırın, koruyun, OCR ve filigran ekleyin + Bir PDF işlemi seçin + PDF hub\'ı belge eylemlerini tek bir giriş noktasının arkasında gruplandırır, böylece tam işlemi seçebilirsiniz. Dosya zaten bir PDF olduğunda buradan başlayın ve kaynağınız hala bir dizi görüntüden oluştuğunda Görüntüleri PDF\'ye veya Belge Tarayıcıyı kullanın. + PDF Araçlarını açın ve hedefinize uygun işlemi seçin. + Kaynak PDF\'yi veya görüntüleri seçin, ardından sayfa sırası, döndürme, koruma veya OCR seçeneklerini inceleyin. + Oluşturulan PDF\'yi kaydedin ve sayfa sayısını, sırasını ve okunabilirliğini onaylamak için bir kez açın. + Görüntüleri PDF\'ye dönüştürün + Taramaları, ekran görüntülerini, makbuzları veya fotoğrafları paylaşılabilir tek bir belgede birleştirin + Temiz bir belge oluşturun + Görüntüler tutarlı bir şekilde kırpıldığında, sıralandığında ve boyutlandırıldığında daha iyi PDF sayfaları haline gelir. Görüntü listesine bir sayfa yığını gibi davranın: her döndürme, kenar boşluğu ve sayfa boyutu seçimi, son belgenin ne kadar okunabilir olduğunu etkiler. + Sayfa kenarları, gölgeler veya yönlendirme yanlış olduğunda önce görüntüleri kırpın veya döndürün. + Görselleri istenilen sayfa sırasına göre seçin ve araç sunuyorsa sayfa boyutunu veya kenar boşluklarını ayarlayın. + PDF\'yi dışa aktarın ve göndermeden önce tüm sayfaların okunabilir olup olmadığını kontrol edin. + Belgeleri tarayın + Kâğıt sayfaları yakalayın ve bunları PDF, OCR veya paylaşım için hazırlayın + Okunabilir sayfaları yakalayın + Belge tarama, düz sayfalar, iyi aydınlatma ve düzeltilmiş perspektifle en iyi sonucu verir. OCR veya PDF\'yi dışa aktarmadan önce yapılan temiz bir tarama, metin tanıma ve sıkıştırmanın her ikisinin de sayfa kalitesine bağlı olması nedeniyle zaman tasarrufu sağlar. + Belgeyi yeterli ışık ve minimum gölge ile kontrast oluşturan bir yüzeye yerleştirin. + Sayfanın çerçeveyi temiz bir şekilde doldurması için algılanan köşeleri ayarlayın veya manuel olarak kırpın. + Görüntü veya PDF olarak dışa aktarın ve seçilebilir metne ihtiyacınız varsa OCR\'yi çalıştırın. + PDF dosyalarını koruyun veya kilidini açın + Şifreleri dikkatli kullanın ve mümkünse düzenlenebilir bir kaynak kopyasını saklayın + PDF erişimini güvenli bir şekilde yönetin + Koruma araçları kontrollü paylaşım için kullanışlıdır ancak şifrelerin kaybedilmesi kolaydır ve kurtarılması zordur. Kopyaları dağıtım için koruyun, korumasız kaynak dosyaları ayrı tutun ve herhangi bir şeyi silmeden önce sonucu doğrulayın. + Tek kaynak belgenizi değil, PDF\'nin bir kopyasını koruyun. + Korunan dosyayı paylaşmadan önce şifreyi güvenli bir yerde saklayın. + Kilit açmayı yalnızca düzenlemenize izin verilen dosyalar için kullanın, ardından kilidi açılmış kopyanın doğru şekilde açıldığını doğrulayın. + PDF sayfalarını düzenleyin + Sayfaları bilinçli olarak birleştirin, bölün, yeniden düzenleyin, döndürün, kırpın, kaldırın ve sayfa numaralarını ekleyin + Dekorasyondan önce belge yapısını düzeltin + PDF sayfa araçlarının kullanımı, kâğıt belge hazırladığınız sırayla kullanılması en kolay olanıdır: sayfaları toplayın, yanlış olanları kaldırın, sıraya koyun, yönlendirmeyi düzeltin ve ardından sayfa numaraları veya filigranlar gibi son ayrıntıları ekleyin. + Belge hala birden fazla dosyaya bölünmüş durumdayken, ilk olarak birleştirme veya PDF\'ye görselleri kullanın. + Sayfa numaraları, imzalar veya filigran eklemeden önce sayfaları yeniden düzenleyin, döndürün, kırpın veya kaldırın. + Eksik veya döndürülmüş bir sayfanın daha sonra fark edilmesi zor olabileceğinden, yapısal düzenlemelerden sonra son PDF\'yi önizleyin. + PDF ek açıklamaları + Görüntüleyenler yorumları ve işaretleri farklı gösterdiğinde ek açıklamaları kaldırın veya düzleştirin + Nelerin görünür kalacağını kontrol edin + Ek açıklamalar; yorumlar, vurgular, çizimler, form işaretleri veya diğer ekstra PDF nesneleri olabilir. Bazı görüntüleyenler bunları farklı şekilde görüntüler; bu nedenle bunların kaldırılması veya düzleştirilmesi, paylaşılan belgeleri daha öngörülebilir hale getirebilir. + Paylaşılan kopyaya yorumların, vurgulamaların veya inceleme işaretlerinin dahil edilmemesi gerektiğinde ek açıklamaları kaldırın. + Görünür işaretlerin sayfada kalması ancak artık düzenlenebilir ek açıklamalar gibi davranmaması gerektiğinde düzleştirin. + Ek açıklamaların kaldırılması veya düzleştirilmesinin geri alınması zor olabileceğinden orijinal bir kopyayı saklayın. + Metni tanı + Seçilebilir OCR motorları ve dilleriyle resimlerden metin çıkarın + Daha iyi OCR sonuçları elde edin + OCR doğruluğu görüntünün keskinliğine, dil verilerine, kontrasta ve sayfa yönüne bağlıdır. En iyi sonuçlar genellikle önce görüntünün hazırlanmasıyla elde edilir: gürültüyü kesin, dik döndürün, okunabilirliği artırın ve ardından metni tanıyın. + Resmi metin alanına kırpın ve tanınmadan önce dik olarak döndürün. + Doğru dili seçin ve uygulama isterse dil verilerini indirin. + Tanınan metni kopyalayın veya paylaşın, ardından adları, numaraları ve noktalama işaretlerini manuel olarak kontrol edin. + OCR dili verilerini seçin + Komut dosyalarını, dilleri ve indirilen OCR modellerini eşleştirerek tanımayı iyileştirin + OCR\'yi metinle eşleştirin + Yanlış OCR dili, iyi fotoğrafların kötü tanınma gibi görünmesine neden olabilir. Dil verileri; yazılar, noktalama işaretleri, sayılar ve karışık belgeler için önemlidir; bu nedenle, telefon kullanıcı arayüzünün dili yerine resimde görünen dili seçin. + Yalnızca telefon dilini değil, resimde görünen dili veya alfabeyi seçin. + Çevrimdışı çalışmadan veya bir toplu işlemi işlemeden önce eksik verileri indirin. + Karma dilli belgeler için önce en önemli dili çalıştırın ve adları ve numaraları manuel olarak kontrol edin. + Aranabilir PDF\'ler + Taranan sayfaların aranabilmesi ve kopyalanabilmesi için OCR metnini dosyalara geri yazın + Taramaları aranabilir belgelere dönüştürün + OCR, metni panoya kopyalamaktan daha fazlasını yapabilir. Tanınan metni bir dosyaya veya aranabilir PDF\'ye yazdığınızda, metnin aranması, seçilmesi, dizine eklenmesi veya arşivlenmesi kolaylaşırken sayfanın görüntüsü görünür kalır. + Orijinal sayfalara benzemesi gereken taranmış belgeler için aranabilir PDF çıktısını kullanın. + Yalnızca çıkarılan metne ihtiyaç duyduğunuzda ve sayfa görüntüsünü önemsemediğinizde dosyaya yazma özelliğini kullanın. + Önemli sayıları, adları ve tabloları manuel olarak kontrol edin; çünkü OCR metni aranabilir ancak yine de kusurlu olabilir. + QR kodlarını tarayın + Mümkün olduğunda kamera girişinden veya kayıtlı görüntülerden QR içeriğini okuyun + Kodları güvenle okuyun + QR kodları bağlantılar, Wi-Fi verileri, kişiler, metin veya diğer verileri içerebilir. + QR Kodunu Tara\'yı açın ve kodu keskin, düz ve tamamen çerçevenin içinde tutun. + Bağlantıları açmadan veya hassas verileri kopyalamadan önce kodu çözülmüş içeriği inceleyin. + Tarama başarısız olursa aydınlatmayı iyileştirin, kodu kırpın veya daha yüksek çözünürlüklü bir kaynak görüntüyü deneyin. + Base64\'ü ve sağlama toplamlarını kullanın + Görüntüleri metin olarak kodlayın, metnin kodunu görüntülere dönüştürün ve dosya bütünlüğünü doğrulayın + Dosyalar ve metin arasında geçiş yapma + Base64, küçük görüntü yükleri için kullanışlıdır; sağlama toplamları ise dosyaların değişmediğini doğrulamaya yardımcı olur. Bu araçlar en çok teknik iş akışlarında, destek raporlarında, dosya aktarımlarında ve ikili dosyaların geçici olarak metne dönüşmesi gereken yerlerde faydalıdır. + Bir iş akışının ikili dosya yerine metne ihtiyacı olduğunda küçük bir görüntüyü kodlamak için Base64 araçlarını kullanın. + Base64\'ün kodunu yalnızca güvenilir kaynaklardan çözün ve kaydetmeden önce önizlemeyi doğrulayın. + Dışa aktarılan dosyaları karşılaştırmanız veya bir yükleme/indirme işlemini onaylamanız gerektiğinde sağlama toplamı araçlarını kullanın. + Verileri paketleyin veya koruyun + Dosyaları paketlemek veya metin verilerini dönüştürmek için ZIP ve şifre araçlarını kullanın + İlgili dosyaları bir arada tutun + Paketleme araçları, birden fazla çıktının tek bir dosya olarak taşınması gerektiğinde faydalıdır. Ayrıca, eksiksiz bir sonuç kümesi göndermeniz gerektiğinde oluşturulan görüntüleri, PDF\'leri, günlükleri ve meta verileri bir arada tutmak için de kullanışlıdırlar. + Dışa aktarılan birçok görüntüyü veya belgeyi bir arada göndermeniz gerektiğinde ZIP\'i kullanın. + Şifreleme araçlarını yalnızca seçilen yöntemi anladığınızda ve gerekli anahtarları güvenli bir şekilde saklayabildiğinizde kullanın. + Kaynak dosyaları silmeden önce oluşturulan arşivi veya dönüştürülmüş metni test edin. + Adlardaki sağlama toplamları + Benzersizlik ve bütünlük okunabilirlikten daha önemli olduğunda sağlama toplamı tabanlı dosya adlarını kullanın + Dosyaları içeriğe göre adlandırın + Sağlama toplamı dosya adları, dışa aktarımlarda yinelenen görünür adlar bulunabildiğinde veya iki dosyanın aynı olduğunu kanıtlamanız gerektiğinde kullanışlıdır. Manuel tarama için daha az kullanışlıdırlar, bu nedenle bunları teknik arşivler ve doğrulama iş akışları için kullanın. + İçerik kimliği insanların okuyabileceği bir başlıktan daha önemli olduğunda dosya adı olarak sağlama toplamını etkinleştirin. + Arşivler, veri tekilleştirme, tekrarlanabilir dışa aktarmalar veya yeniden yüklenebilecek ve indirilebilecek dosyalar için kullanın. + İnsanların hâlâ dosyanın neyi temsil ettiğini anlaması gerektiğinde sağlama toplamı adlarını klasörler veya meta verilerle eşleştirin. + Resimlerden renk seçin + Paletlerdeki veya tasarımlardaki pikselleri örnekleyin, renk kodlarını kopyalayın ve renkleri yeniden kullanın + Tam rengi örnekleyin + Renk seçme, bir tasarımı eşleştirmek, marka renklerini çıkarmak veya görüntü değerlerini kontrol etmek için kullanışlıdır. Yakınlaştırma önemlidir çünkü kenar yumuşatma, gölgeler ve sıkıştırma komşu pikselleri beklediğiniz renkten biraz farklı hale getirebilir. + Görüntüden Renk Seç\'i açın ve ihtiyacınız olan renge sahip bir kaynak görüntü seçin. + Küçük ayrıntıları örneklemeden önce yakınlaştırın, böylece yakındaki pikseller sonucu etkilemez. + Rengi, HEX, RGB veya HSV gibi hedefinizin gerektirdiği biçimde kopyalayın. + Paletler oluşturun ve renk kitaplığını kullanın + Paletleri çıkarın, kayıtlı renklere göz atın ve görsel sistemleri tutarlı tutun + Yeniden kullanılabilir bir palet oluşturun + Paletler, dışa aktarılan grafiklerin, filigranların ve çizimlerin görsel olarak tutarlı kalmasına yardımcı olur. Bunlar özellikle ekran görüntülerine tekrar açıklama eklediğinizde, marka varlıkları hazırladığınızda veya çeşitli araçlarda aynı renklere ihtiyaç duyduğunuzda kullanışlıdır. + Bir görüntüden bir palet oluşturun veya değerleri zaten biliyorsanız renkleri manuel olarak ekleyin. + Daha sonra tekrar bulabilmek için önemli renkleri adlandırın veya düzenleyin. + Çizim, filigran, degrade, SVG veya harici tasarım araçlarında kopyalanan değerleri kullanın. + Degradeler oluştur + Arka planlar, yer tutucular ve görsel deneyler için degrade görüntüler oluşturun + Renk geçişlerini kontrol edin + Degrade araçları, mevcut bir görüntüyü düzenlemek yerine oluşturulmuş bir görüntüye ihtiyaç duyduğunuzda kullanışlıdır. Harici tasarım araçları için temiz arka planlar, yer tutucular, duvar kağıtları, maskeler veya basit varlıklar haline gelebilirler. + Degrade türünü seçin ve önce önemli renkleri ayarlayın. + Hedef kullanım durumuna göre yönü, durakları, düzgünlüğü ve çıktı boyutunu ayarlayın. + Temiz oluşturulmuş grafikler için genellikle PNG olmak üzere, hedefle eşleşen bir formatta dışa aktarın. + Renk erişilebilirliği + Kullanıcı arayüzünün okunması zor olduğunda dinamik renkleri, kontrastı ve renk körü seçeneklerini kullanın + Renklerin kullanımını kolaylaştırın + Renk ayarları rahatlığı, okunabilirliği ve kontrolleri ne kadar hızlı tarayabileceğinizi etkiler. Araç çipleri, kaydırıcılar veya seçili durumların ayırt edilmesi zor görünüyorsa tema ve erişilebilirlik seçenekleri, yalnızca karanlık modu değiştirmekten daha yararlı olabilir. + Uygulamanın mevcut duvar kağıdı paletini takip etmesini istediğinizde dinamik renkleri deneyin. + Düğmeler ve yüzeyler yeterince öne çıkmadığında kontrastı artırın veya düzeni değiştirin. + Bazı durumları veya vurguları ayırt etmek zorsa renk körü şema seçeneklerini kullanın. + Alfa arka planı + Şeffaf PNG, WebP ve benzeri çıktıların etrafında kullanılan önizleme veya dolgu rengini kontrol edin + Şeffaf önizlemeleri anlayın + Alfa formatları için arka plan rengi, şeffaf görüntülerin incelenmesini kolaylaştırabilir, ancak bir önizleme/doldurma davranışını mı değiştirdiğinizi yoksa nihai sonucu düzleştirip düzleştirmediğinizi bilmek önemlidir. Bu, çıkartmalar, logolar ve arka plandan kaldırılmış görseller için önemlidir. + Şeffaf piksellerin dışa aktarılmaya devam etmesi gerektiğinde öncelikle PNG veya WebP gibi alfa destekli bir format kullanın. + Saydam kenarların uygulama yüzeyinde görülmesi zor olduğunda arka plan rengi ayarlarını yapın. + Küçük bir testi dışa aktarın ve şeffaflığın mı yoksa seçilen dolgunun mu kaydedildiğini doğrulamak için başka bir uygulamada yeniden açın. + Yapay zeka modeli seçimi + Önce en büyüğünü seçmek yerine görüntü türüne ve kusura göre bir model seçin + Modeli hasarla eşleştirin + AI Tools farklı ONNX/ORT modellerini indirebilir veya içe aktarabilir ve her model belirli bir sorun türü için eğitilir. JPEG blokları, anime satırı temizleme, gürültü giderme, arka planı kaldırma, renklendirme veya yükseltmeye yönelik bir model, yanlış görüntü türünde kullanıldığında daha kötü sonuçlar doğurabilir. + İndirmeden önce model açıklamasını okuyun; Sıkıştırma, gürültü, yarı ton, bulanıklık veya arka plan kaldırma gibi asıl sorununuzu belirten modeli seçin. + Yeni bir görüntü türünü test ederken daha küçük veya daha spesifik bir modelle başlayın, ardından yalnızca sonuç yeterince iyi değilse daha ağır modellerle karşılaştırın. + İndirilen ve içe aktarılan modeller önemli miktarda depolama alanı kaplayabileceğinden, yalnızca gerçekten kullandığınız modelleri saklayın. + Model ailelerini anlayın + Model adları genellikle hedeflerine işaret eder: RealESRGAN tarzı modeller üst ölçektir, SCUNet ve FBCNN modelleri gürültüyü veya sıkıştırmayı temizler, arka plan modelleri maskeler oluşturur ve renklendiriciler gri tonlamalı girişe renk ekler. İçe aktarılan özel modeller güçlü olabilir ancak beklenen giriş/çıkış şekliyle eşleşmeli ve desteklenen ONNX veya ORT formatını kullanmalıdır. + Daha fazla piksele ihtiyaç duyduğunuzda ölçekleyicileri, görüntü grenli olduğunda gürültü gidericileri ve ana sorun sıkıştırma blokları veya şeritlenme olduğunda artefakt gidericileri kullanın. + Arka plan modellerini yalnızca konu ayrımı için kullanın; bunlar genel nesne kaldırma veya görüntü iyileştirme ile aynı değildir. + Özel modelleri yalnızca güvendiğiniz kaynaklardan içe aktarın ve önemli dosyaları kullanmadan önce bunları küçük bir görüntü üzerinde test edin. + AI parametreleri + Kaliteyi, hızı ve belleği dengelemek için yığın boyutunu, örtüşmeyi ve paralel çalışanları ayarlayın + Hız ile stabiliteyi dengeleyin + Parça boyutu, görüntü parçalarının model tarafından işlenmeden önce ne kadar büyük olacağını kontrol eder. Örtüşme, sınırları gizlemek için komşu parçaları harmanlar. Paralel çalışanlar işlemeyi hızlandırabilir ancak her çalışanın belleğe ihtiyacı vardır, bu nedenle yüksek değerler, sınırlı RAM\'e sahip telefonlarda büyük görüntülerin dengesiz olmasına neden olabilir. + Cihazınız modeli güvenilir bir şekilde işlediğinde ve görüntü çok büyük olmadığında, bağlamın daha düzgün olması için daha büyük parçalar kullanın. + Parça sınırları görünür hale geldiğinde örtüşmeyi artırın, ancak daha fazla örtüşmenin daha fazla tekrarlanan çalışma anlamına geldiğini unutmayın. + Paralel çalışanları yalnızca istikrarlı bir testten sonra yükseltin; İşleme yavaşlarsa, cihaz ısınırsa veya çökerse öncelikle çalışanları azaltın. + Parça artefaktlarından kaçının + Parçalama, uygulamanın modelin aynı anda rahatça işleyebileceğinden daha büyük görüntüleri işlemesine olanak tanır. Bunun dezavantajı, her döşemenin daha az çevre bağlamına sahip olmasıdır; bu nedenle düşük örtüşme veya çok küçük parçalar, komşu alanlar arasında görünür sınırlar, doku değişiklikleri veya tutarsız ayrıntılar oluşturabilir. + İşlenen bölgeler arasında dikdörtgen kenarlıklar, yinelenen doku değişiklikleri veya ayrıntı atlamaları görüyorsanız örtüşmeyi artırın. + Özellikle büyük resimlerde veya ağır modellerde, çıktı üretmeden önce işlem başarısız olursa yığın boyutunu azaltın. + Tam görüntüyü işlemeden önce küçük bir kırpma testi kaydedin, böylece parametreleri hızlı bir şekilde karşılaştırabilirsiniz. + Yapay zeka kararlılığı + Yapay zeka işleme çöktüğünde veya donduğunda parça boyutu, çalışanlar ve kaynak çözünürlüğü daha düşük + Ağır AI işlerinden kurtulun + Yapay zeka işleme, uygulamadaki en ağır iş akışlarından biridir. Kilitlenmeler, başarısız oturumlar, donmalar veya uygulamanın ani yeniden başlatılması genellikle modelin, kaynak çözünürlüğünün, parça boyutunun veya paralel çalışan sayısının mevcut cihaz durumu için çok zorlayıcı olduğu anlamına gelir. + Uygulama çökerse veya bir model oturumu açmazsa paralel çalışanları ve yığın boyutunu azaltın ve ardından aynı görüntüyü tekrar deneyin. + Hedef orijinal çözünürlüğü gerektirmediğinde, AI işlemeden önce çok büyük görüntüleri yeniden boyutlandırın. + Bellek baskısı artmaya devam ettiğinde diğer ağır uygulamaları kapatın, daha küçük bir model kullanın veya aynı anda daha az sayıda dosyayı işleyin. + Model hatalarını teşhis edin + Başarısız bir AI çalışması her zaman görüntünün kötü olduğu anlamına gelmez. Model dosyası eksik, desteklenmiyor, bellek için fazla büyük veya işlemle uyumsuz olabilir. Uygulama başarısız bir oturumu bildirdiğinde, öncelikle modeli ve parametreleri basitleştirmek için bunu bir sinyal olarak değerlendirin. + Bir model indirildikten hemen sonra başarısız olursa veya dosya bozukmuş gibi davranıyorsa, modeli silin ve yeniden indirin. + İçe aktarılan özel modeli suçlamadan önce yerleşik, indirilebilir bir modeli deneyin; çünkü içe aktarılan modellerde uyumsuz girişler bulunabilir. + Bir kilitlenme veya oturum hatasını bildirmeniz gerekiyorsa, hatayı yeniden oluşturduktan sonra Uygulama Günlüklerini kullanın. + Shader Studio\'da denemeler yapın + Gelişmiş görüntü işleme ve özel görünümler için GPU gölgelendirici efektlerini kullanın + Gölgelendiricilerle dikkatli çalışın + Shader Studio güçlüdür ancak gölgelendirici kodu ve parametrelerinin küçük, test edilebilir değişikliklere ihtiyacı vardır. Bunu deneysel bir araç gibi kullanın: Çalışan bir temel çizgiyi koruyun, her seferinde bir şeyi değiştirin ve bir ön ayara güvenmeden önce farklı görüntü boyutlarıyla test edin. + Parametreleri eklemeden önce, çalıştığı bilinen bir gölgelendiriciden veya basit bir efektten başlayın. + Bir seferde bir parametreyi veya kod bloğunu değiştirin ve önizlemede hatalar olup olmadığını kontrol edin. + Ön ayarları yalnızca birkaç farklı görüntü boyutunda test ettikten sonra dışa aktarın. + SVG veya ASCII sanatı yapın + Yaratıcı çıktılar için vektör benzeri varlıklar veya metin tabanlı görüntüler oluşturun + Yaratıcı çıktı türünü seçin + SVG ve ASCII araçları farklı hedeflere hizmet eder: ölçeklenebilir grafikler veya metin stili oluşturma. Her ikisi de yaratıcı dönüşümlerdir; bu nedenle, son boyutta önizleme yapmak, sonucu küçük bir küçük resimden değerlendirmekten daha önemlidir. + Sonucun temiz bir şekilde ölçeklendirilmesi veya tasarım iş akışlarında yeniden kullanılması gerektiğinde SVG Maker\'ı kullanın. + Bir görüntünün stilize edilmiş metin temsilini istediğinizde ASCII Art\'ı kullanın. + Oluşturulan çıktıda küçük ayrıntılar kaybolabileceğinden son boyutta önizleyin. + Gürültü dokuları oluşturun + Arka planlar, maskeler, kaplamalar veya deneyler için prosedürel dokular oluşturun + Prosedür çıktısını ayarla + Gürültü oluşumu parametrelerden yeni görüntüler oluşturur, dolayısıyla küçük değişiklikler çok farklı sonuçlar üretebilir. Dokular, maskeler, kaplamalar, yer tutucular veya ayrıntılı desenlerle sıkıştırmayı test etmek için kullanışlıdır. + İnce ayrıntıları ayarlamadan önce bir gürültü türü ve çıkış boyutu seçin. + Desen hedef kullanıma uygun olana kadar ölçeği, yoğunluğu, renkleri veya tohumu ayarlayın. + Yararlı sonuçları kaydedin ve dokuyu daha sonra yeniden oluşturmanız gerekirse parametreleri yazın. + İşaretleme projeleri + Uygulamayı kapattıktan sonra ek açıklamaların düzenlenebilir kalması gerektiğinde proje dosyalarını kullanın + Katmanlı düzenlemeleri yeniden kullanılabilir durumda tutun + İşaretleme proje dosyaları, bir ekran görüntüsü, diyagram veya talimat görselinin daha sonra değişiklik gerektirmesi durumunda kullanışlıdır. Dışa aktarılan PNG/JPEG dosyaları son görüntülerdir; proje dosyaları ise düzenlenebilir katmanları ve düzenleme durumunu gelecekteki ayarlamalar için korur. + Ek açıklamalar, belirtme çizgileri veya düzen öğelerinin düzenlenebilir kalması gerektiğinde İşaretleme Katmanlarını kullanın. + Daha fazla revizyon istiyorsanız son görüntüyü dışa aktarmadan önce proje dosyasını kaydedin veya yeniden açın. + Normal bir görüntüyü yalnızca düzleştirilmiş son sürümü paylaşmaya hazır olduğunuzda dışa aktarın. + Dosyaları şifrele + Herhangi bir kaynak kopyayı silmeden önce dosyaları bir parolayla koruyun ve şifre çözmeyi test edin + Şifrelenmiş çıktıları dikkatli bir şekilde kullanın + Şifreleme araçları, dosyaları parola tabanlı şifrelemeyle koruyabilir, ancak anahtarı hatırlamanın veya yedek tutmanın yerini tutmaz. Şifreleme, taşıma ve depolama için kullanışlıdır; asıl amaç birkaç çıktıyı bir araya getirmek olduğunda ZIP daha iyidir. + Önce bir kopyayı şifreleyin ve şifre çözmenin sakladığınız şifreyle çalıştığını test edene kadar orijinali saklayın. + Anahtar için bir şifre yöneticisi veya başka bir güvenli yer kullanın; uygulama sizin için kayıp bir şifreyi tahmin edemez. + Şifrelenmiş dosyaları yalnızca şifreye sahip olan ve şifrenin hangi araç veya yöntemle çözülmesi gerektiğini bilen kişilerle paylaşın. + Araçları düzenle + Ana ekranın gerçek iş akışınıza uyması için araçları gruplayın, sıralayın, arayın ve sabitleyin + Ana ekranı daha hızlı hale getirin + En çok hangi araçları kullandığınızı öğrendikten sonra araç düzenleme ayarları üzerinde ayarlama yapmaya değer. Gruplandırma keşfetmeye yardımcı olur, özel sıralama kas hafızasına yardımcı olur ve favoriler, tam liste genişlediğinde bile günlük araçların erişilebilir olmasını sağlar. + Uygulamayı öğrenirken veya görev türüne göre düzenlenen araçları tercih ettiğinizde gruplandırılmış modu kullanın. + Sık kullandığınız araçları zaten bildiğinizde ve daha az kaydırma istediğinizde özel sıralamaya geçin. + Adıyla hatırladığınız ancak her zaman görünür olmasını istemediğiniz nadir araçlar için arama ayarlarını ve favorileri bir arada kullanın. + Büyük dosyaları yönetin + Yavaş dışa aktarmalardan, bellek baskısından ve beklenmedik derecede büyük çıktılardan kaçının + İhracat öncesi işi azaltın + Çok büyük resimler, uzun gruplar ve yüksek kaliteli formatlar daha fazla bellek ve zaman alabilir. En hızlı düzeltme genellikle aynı büyük boyutlu girdinin bitmesini beklemek yerine, en ağır işlemden önce iş miktarını azaltmaktır. + Ağır filtreler, OCR veya PDF dönüştürme uygulamadan önce büyük görselleri yeniden boyutlandırın. + Ayarları onaylamak için önce daha küçük bir parti kullanın, ardından geri kalanını aynı ön ayarla işleyin. + Çıktı dosyası beklenenden büyük olduğunda kaliteyi düşürün veya formatı değiştirin. + Önbellek ve önizlemeler + Yoğun oturumlardan sonra oluşturulan önizlemeleri, önbellek temizlemeyi ve depolama kullanımını anlayın + Depolamayı ve hızı dengede tutun + Önizleme oluşturma ve önbellek, tekrarlanan taramayı daha hızlı hale getirebilir ancak yoğun düzenleme oturumları, geçici verileri geride bırakabilir. Önbellek ayarları, uygulama yavaşladığında, depolama alanı kısıtlı olduğunda veya birçok denemeden sonra önizlemeler eski göründüğünde yardımcı olur. + Çok sayıda görsele göz atarken önizlemeleri etkin tutmak, geçici depolamayı en aza indirmekten daha önemlidir. + Büyük gruplar, başarısız denemeler veya çok sayıda yüksek çözünürlüklü dosya içeren uzun oturumlardan sonra önbelleği temizleyin. + Başlatmalar arasında önizlemeleri sıcak tutmak yerine depolama alanını temizlemeyi tercih ediyorsanız otomatik önbellek temizlemeyi kullanın. + Ekran davranışını ayarlayın + Odaklanmış çalışma için tam ekran, güvenli mod, parlaklık ve sistem çubuğu seçeneklerini kullanın + Arayüzün duruma uygun olmasını sağlayın + Ekran ayarları, yatay olarak düzenleme yaptığınızda, özel görseller sunduğunuzda veya tutarlı bir parlaklığa ihtiyaç duyduğunuzda yardımcı olur. Normal kullanım için gerekli değildirler ancak QR incelemesi, özel önizlemeler veya sıkışık manzara düzenleme gibi garip durumları çözerler. + Alanı düzenlemek gezinme kromundan daha önemli olduğunda tam ekran veya sistem çubuğu ayarlarını kullanın. + Özel görsellerin ekran görüntülerinde veya uygulama önizlemelerinde görünmemesi gerektiğinde güvenli modu kullanın. + Karanlık görüntülerin veya QR kodlarının incelenmesinin zor olduğu araçlar için parlaklık uygulamasını kullanın. + Şeffaflığı koruyun + Saydam arka planların beyaza, siyaha dönmesini veya düzleşmesini önleyin + Alfa uyumlu çıktı kullan + Şeffaflık yalnızca seçilen araç ve çıktı formatı alfayı desteklediğinde hayatta kalır. Dışa aktarılan bir arka plan beyaz veya siyaha dönerse sorun genellikle çıktı biçimi, dolgu/arka plan ayarı veya görüntüyü düzleştiren hedef uygulamadır. + Arka planda kaldırılan görselleri, çıkartmaları, logoları veya kaplamaları dışa aktarırken PNG veya WebP\'yi seçin. + Bir alfa kanalı saklamadığından şeffaf çıktı için JPEG\'den kaçının. + Önizleme dolu bir arka plan gösteriyorsa, alfa formatları için arka plan rengiyle ilgili ayarları kontrol edin. + Paylaşım ve içe aktarma sorunlarını düzeltme + Dosyalar düzgün görünmediğinde veya açılmadığında seçici ayarlarını ve desteklenen biçimleri kullanın + Dosyayı uygulamaya alın + İçe aktarma sorunlarına genellikle sağlayıcı izinleri, desteklenmeyen biçimler veya seçici davranışı neden olur. Bulut uygulamalarından ve mesajlaşma programlarından paylaşılan dosyalar geçici olabilir, dolayısıyla daha uzun düzenlemeler için kalıcı bir kaynak seçmek daha güvenilir olabilir. + Dosyayı, son dosyalar kısayolu yerine sistem seçici aracılığıyla açmayı deneyin. + Bir format bir araç tarafından desteklenmiyorsa önce onu dönüştürün veya daha spesifik bir araç açın. + Paylaşım akışları veya doğrudan araç açma beklenenden farklı davranıyorsa görüntü seçici ve başlatıcı ayarlarını gözden geçirin. + Çıkış onayı + Yanlışlıkla hareketler, geri basmalar veya araç değişiklikleri meydana geldiğinde kaydedilmemiş düzenlemeleri kaybetmekten kaçının + Bitmemiş işleri koruyun + Araçtan çıkış onayı, hareketle gezinmeyi kullandığınızda, büyük dosyaları düzenlediğinizde veya sıklıkla uygulamalar arasında geçiş yaptığınızda faydalıdır. Araçtan ayrılmadan önce küçük bir duraklama ekler, böylece tamamlanmamış ayarlar ve önizlemeler kazara kaybolmaz. + Dışa aktarmadan önce yanlışlıkla geri hareketler bir aracı kapatmışsa onayı etkinleştirin. + Çoğunlukla hızlı tek adımlı dönüşümler yapıyorsanız ve daha hızlı gezinmeyi tercih ediyorsanız, bunu devre dışı bırakın. + Ayarların yeniden oluşturulmasının can sıkıcı olacağı daha uzun iş akışları için bunu ön ayarlarla birlikte kullanın. + Sorunları bildirirken günlükleri kullanın + Bir konuyu açmadan veya geliştiriciyle iletişime geçmeden önce faydalı ayrıntıları toplayın + Bağlamla ilgili sorunları bildirin + Adımları, uygulama sürümünü, giriş türünü ve günlükleri içeren net bir raporun teşhis edilmesi çok daha kolaydır. Günlükler, bir sorunu yeniden oluşturduktan hemen sonra çok kullanışlıdır çünkü çevredeki bağlamı taze tutarlar. + Araç adını, tam eylemi ve bunun bir dosyada mı yoksa her dosyada mı gerçekleştiğini not edin. + Sorunu yeniden oluşturduktan sonra Uygulama Günlüklerini açın ve mümkün olduğunda ilgili günlük çıktısını paylaşın. + Ekran görüntülerini veya örnek dosyaları yalnızca özel bilgiler içermediğinde ekleyin. + Arka planda işleme durduruldu + Android, arka planda işleme bildiriminin zamanında başlamasına izin vermedi. Bu, güvenilir bir şekilde düzeltilemeyen bir sistem zamanlama sınırlamasıdır. Uygulamayı yeniden başlatın ve tekrar deneyin; uygulamayı açık tutmak ve bildirimlere veya arka planda çalışmaya izin vermek yardımcı olabilir. + Dosya toplamayı atla + Giriş genellikle paylaşımdan, panodan veya önceki bir ekrandan geldiğinde araçları daha hızlı açın + Aracın tekrar başlatılmasını hızlandırın + Dosya Toplama\'yı Atla, desteklenen araçların ilk adımını değiştirir. Genellikle önceden seçilmiş bir görselle veya Android paylaşım eylemlerinden bir araca girdiğinizde kullanışlıdır, ancak her aracın hemen bir dosya istemesini beklerseniz kafa karıştırıcı olabilir. + Bunu yalnızca normal akışınız araç açılmadan önce kaynak görüntüyü zaten sağladığında etkinleştirin. + Uygulamayı öğrenirken onu devre dışı bırakın çünkü açık toplama her araç akışının anlaşılmasını kolaylaştırır. + Bir araç belirgin bir giriş olmadan açılırsa, bu ayarı devre dışı bırakın veya Paylaş/Birlikte Aç\'tan başlayın, böylece Android dosyayı önce aktarır. + Önce editörü aç + Paylaşılan bir görselin doğrudan düzenlemeye geçmesi gerektiğinde önizlemeyi atla + İlk durak olarak önizlemeyi veya düzenlemeyi seçin + Önizleme Yerine Düzenlemeyi Aç, görüntüyü değiştirmek istediklerini zaten bilen kullanıcılar içindir. Dosyaları sohbet veya bulut depolama alanından incelediğinizde önizleme daha güvenlidir; doğrudan düzenleme; ekran görüntüleri, hızlı kırpmalar, ek açıklamalar ve tek seferlik düzeltmeler için daha hızlıdır. + Paylaşılan görsellerin çoğunda anında kırpma, işaretleme, filtre veya dışa aktarma değişiklikleri gerekiyorsa doğrudan düzenlemeyi etkinleştirin. + Karar vermeden önce meta verileri, boyutları, şeffaflığı veya görüntü kalitesini sıklıkla incelediğinizde önce önizlemeyi bırakın. + Bunu favorilerle birlikte kullanın, böylece paylaşılan görseller gerçekte kullandığınız araçlara yakın olur. + Önceden ayarlanmış metin girişi + Kontrolleri tekrar ayarlamak yerine tam ön ayar değerlerini girin + Kaydırıcılar yavaş olduğunda kesin değerler kullanın + Önceden ayarlanmış metin girişi, tekrarlanan çalışmalar için kesin sayılara ihtiyaç duyduğunuzda yardımcı olur: sosyal görüntü boyutları, belge kenar boşlukları, sıkıştırma hedefleri, satır genişlikleri veya hareketlerle ulaşılması can sıkıcı olan diğer değerler. İnce kaydırıcı hareketinin daha zor olduğu küçük ekranlarda da kullanışlıdır. + Tam boyutları, yüzdeleri, kalite değerlerini veya araç parametrelerini sıklıkla yeniden kullanıyorsanız metin girişini etkinleştirin. + Değeri bir kez yazdıktan sonra ön ayar olarak kaydedin ve aynı kurulumu yeniden oluşturmak yerine yeniden kullanın. + Kaba görsel ayarlamalar için hareket kontrollerini ve sıkı çıktı gereksinimleri için yazılan değerleri koruyun. + Orijinale yakın kaydet + Klasör bağlamı önemli olduğunda oluşturulan dosyaları kaynak görüntülerin yanına koyun + Çıktıları kaynaklarıyla birlikte saklayın + Orijinal Klasöre Kaydet, düzenlenen kopyanın kaynağın yanında kalması gereken kamera klasörleri, proje klasörleri, belge taramaları ve istemci grupları için kullanışlıdır. Özel bir çıktı klasörüne göre daha az düzenli olabilir, bu nedenle onu anlaşılır dosya adı son ekleri veya kalıplarıyla birleştirin. + Her kaynak klasör zaten anlamlı olduğunda ve çıktıların orada gruplandırılmış kalması gerektiğinde bunu etkinleştirin. + Düzenlenen kopyaların orijinallerden kolayca ayrılmasını sağlamak için dosya adı son eklerini, öneklerini, zaman damgalarını veya kalıplarını kullanın. + Oluşturulan tüm dosyaların tek bir dışa aktarma klasöründe toplanmasını istediğinizde, karışık gruplar için bunu devre dışı bırakın. + Daha büyük çıktıyı atla + Beklenmedik bir şekilde kaynaktan daha ağır hale gelen dönüşümleri kaydetmekten kaçının + Partileri kötü boyut sürprizlerinden koruyun + Bazı dönüştürmeler dosyaları, özellikle de PNG ekran görüntülerini, önceden sıkıştırılmış fotoğrafları, yüksek kaliteli WebP\'yi veya meta verileri korunmuş görselleri büyütür. Büyükse Atlamaya İzin Ver, ana hedefin boyutu küçültmek olduğu iş akışlarında bu çıktıların daha küçük bir kaynağın yerini almasını önler. + Dışa aktarmanın dosyaları sıkıştırması, yeniden boyutlandırması veya yükleme sınırlarına hazırlaması gerektiğinde bunu etkinleştirin. + Atlanan dosyaları bir gruptan sonra gözden geçirin; zaten optimize edilmiş olabilirler veya farklı bir formata ihtiyaç duyabilirler. + Kalite, şeffaflık veya format uyumluluğu son dosya boyutundan daha önemli olduğunda bu özelliği devre dışı bırakın. + Pano ve bağlantılar + Daha hızlı metin tabanlı araçlar için otomatik pano girişini ve bağlantı önizlemelerini kontrol edin + Otomatik girişi öngörülebilir hale getirin + Pano ve bağlantı ayarları QR, Base64, URL ve metin ağırlıklı iş akışları için uygundur ancak otomatik giriş kasıtlı kalmalıdır. Uygulama alanları kendi kendine dolduruyor gibi görünüyorsa panoya yapıştırma davranışını kontrol edin; Bağlantı kartlarının gürültülü veya yavaş olduğunu düşünüyorsanız bağlantı önizleme davranışını ayarlayın. + Sık sık metin kopyaladığınızda otomatik panoya yapıştırmaya izin verin ve hemen bir eşleştirme aracı açın. + Özel pano içeriğinin beklemediğiniz bir yerde araçlarda görünmesi durumunda otomatik yapıştırmayı devre dışı bırakın. + Önizleme alma işlemi yavaş, dikkat dağıtıcı veya iş akışınız için gereksiz olduğunda bağlantı önizlemelerini kapatın. + Kompakt kontroller + Ekran alanı dar olduğunda daha yoğun seçicileri ve hızlı ayar yerleşimini kullanın + Küçük ekranlara daha fazla kontrol sığdırın + Kompakt seçiciler ve hızlı ayar yerleştirme, küçük telefonlarda, yatay düzenlemede ve birçok parametreli araçlarda yardımcı olur. Buradaki değiş tokuş, kontrollerin daha yoğun hissedilebilmesidir; bu nedenle, ortak seçenekleri zaten tanıdıktan sonra bu en iyisidir. + Diyaloglar veya seçenek listeleri ekran için çok uzun göründüğünde kompakt seçicileri etkinleştirin. + Araç kontrollerine ekranın bir tarafından erişilmesi daha kolaysa, hızlı ayarlar tarafını ayarlayın. + Uygulamayı hâlâ öğreniyorsanız veya yoğun seçeneklere sık dokunamıyorsanız daha geniş kontrollere dönün. + Web\'den resim yükleyin + Bir sayfa veya görsel URL\'sini yapıştırın, ayrıştırılmış görsellerin önizlemesini görüntüleyin, ardından seçilen sonuçları kaydedin veya paylaşın + Web görsellerini bilinçli olarak kullanın + Web Görüntüsü Yükleme, sağlanan URL\'deki görüntü kaynaklarını ayrıştırır, mevcut ilk görüntünün ön izlemesini yapar ve seçilen ayrıştırılmış görüntüleri kaydetmenize veya paylaşmanıza olanak tanır. Bazı siteler geçici, korumalı veya komut dosyası tarafından oluşturulan bağlantılar kullandığından, tarayıcıda çalışan bir sayfa yine de kullanılabilir görseller döndürmeyebilir. + Doğrudan resim URL\'sini veya sayfa URL\'sini yapıştırın ve ayrıştırılan resim listesinin görünmesini bekleyin. + Sayfada birden fazla resim varsa ve bunlardan yalnızca bazılarına ihtiyacınız varsa çerçeve veya seçim kontrollerini kullanın. + Hiçbir şey yüklenmezse, doğrudan görsel bağlantısını deneyin veya önce tarayıcıdan indirin; site ayrıştırmayı engelleyebilir veya özel bağlantılar kullanabilir. + Yeniden boyutlandırma türünü seçin + Bir görüntüyü yeni boyutlara zorlamadan önce Açık, Esnek, Kırp ve Sığdır özelliklerini anlayın + Şekli, tuvali ve en boy oranını kontrol edin + Yeniden boyutlandırma türü, istenen genişlik ve yükseklik orijinal en boy oranıyla eşleşmediğinde ne olacağına karar verir. Pikselleri uzatmak, oranları korumak, fazladan alanı kırpmak veya arka plan tuvali eklemek arasındaki farktır. + Açık seçeneğini yalnızca distorsiyon kabul edilebilir olduğunda veya kaynak zaten hedefle aynı en boy oranına sahip olduğunda kullanın. + Özellikle fotoğraflar ve karışık gruplar için önemli taraftan yeniden boyutlandırarak oranları korumak için Esnek\'i kullanın. + Kenarlıksız katı çerçeveler için Kırp\'ı veya görüntünün tamamının sabit bir tuval içinde görünür kalması gerektiğinde Sığdır\'ı kullanın. + Görüntü ölçeği modunu seç + Keskinliği, düzgünlüğü, hızı ve kenar bozulmalarını kontrol eden yeniden örnekleme filtresini seçin + Yanlışlıkla yumuşaklık olmadan yeniden boyutlandırma + Görüntü ölçeği modu, yeniden boyutlandırma sırasında yeni piksellerin nasıl hesaplanacağını kontrol eder. Basit modlar hızlı ve öngörülebilirdir; gelişmiş filtreler ise ayrıntıları daha iyi koruyabilir ancak kenarları keskinleştirebilir, haleler oluşturabilir veya büyük görüntülerde daha uzun zaman alabilir. + Sonucun yalnızca daha küçük ve temiz olması gerektiğinde, her gün hızlı yeniden boyutlandırma için Temel veya Bilinear\'ı kullanın. + İnce ayrıntılar, metin veya yüksek kaliteli ölçek küçültme söz konusu olduğunda Lanczos, Robidoux, Spline veya EWA tarzı modları kullanın. + Bulanık piksellerin görünümü bozacağı piksel sanatı, simgeler ve keskin kenarlı grafikler için En Yakın\'ı kullanın. + Renk alanını ölçeklendir + Yeniden boyutlandırma sırasında pikseller yeniden örneklenirken renklerin nasıl karıştırılacağına karar verin + Degradeleri ve kenarları doğal tutun + Ölçek renk alanı, yeniden boyutlandırma sırasında kullanılan matematiği değiştirir. Çoğu görüntü varsayılan ayarda uygundur ancak doğrusal veya algısal alanlar, güçlü ölçeklendirmeden sonra degradelerin, koyu kenarların ve doygun renklerin daha temiz görünmesini sağlayabilir. + Hız ve uyumluluk küçük renk farklılıklarından daha önemli olduğunda varsayılanı bırakın. + Yeniden boyutlandırma sonrasında koyu çizgiler, kullanıcı arayüzü ekran görüntüleri, degradeler veya renk bantları yanlış göründüğünde Doğrusal veya algısal modları deneyin. + Renk alanı değişiklikleri incelikli ve içeriğe bağlı olabileceğinden, gerçek hedef ekranda öncesi ve sonrasını karşılaştırın. + Yeniden Boyutlandırma modlarını sınırlayın + Limiti aşan görsellerin ne zaman atlanması, yeniden kodlanması veya sığacak şekilde küçültülmesi gerektiğini bilin + Sınırda ne olacağını seçin + Yeniden Boyutlandırmayı Sınırla yalnızca daha küçük bir görüntü aracı değildir. Modu, bir kaynak zaten sınırlarınızın içinde olduğunda veya yalnızca bir taraf kuralı ihlal ettiğinde uygulamanın ne yapacağına karar verir; bu, karışık klasörler ve yükleme hazırlığı için önemlidir. + Sınırların içindeki görüntülere dokunulmaması ve tekrar dışa aktarılmaması gerektiğinde Atla seçeneğini kullanın. + Boyutlar kabul edilebilir olduğunda ancak yine de yeni bir biçime, meta veri davranışına, kaliteye veya dosya adına ihtiyacınız olduğunda Recode\'u kullanın. + Sınırları aşan görsellerin, izin verilen kutuya sığıncaya kadar orantılı olarak küçültülmesi gerektiğinde Yakınlaştırma\'yı kullanın. + En boy oranı hedefleri + Katı çıktı boyutlarında uzatılmış yüzlerden, kesik kenarlardan ve beklenmeyen kenarlıklardan kaçının + Yeniden boyutlandırmadan önce şekli eşleştirin + Genişlik ve yükseklik, yeniden boyutlandırma hedefinin yalnızca yarısıdır. En boy oranı, görüntünün doğal olarak sığıp sığmayacağına, kırpılmaya mı yoksa etrafında bir tuvale mi ihtiyaç duyulacağına karar verir. İlk önce şeklin kontrol edilmesi, şaşırtıcı yeniden boyutlandırma sonuçlarının çoğunu önler. + Açık, Kırp veya Sığdır\'ı seçmeden önce kaynak şekli hedef şekille karşılaştırın. + Konu fazladan arka planı kaybedebileceğinde ve hedefin sıkı bir çerçeveye ihtiyacı olduğunda ilk önce kırpın. + Orijinal görüntünün her parçasının görünür kalması gerektiğinde Sığdır veya Esnek seçeneğini kullanın. + Lüks veya normal yeniden boyutlandırma + Piksel eklemenin ne zaman yapay zeka gerektirdiğini ve basit ölçeklendirmenin ne zaman yeterli olduğunu bilin + Boyutu detayla karıştırmayın + Normal yeniden boyutlandırma, piksellerin enterpolasyonunu yaparak boyutları değiştirir; gerçek ayrıntıları icat edemez. Yapay zeka yükseltmesi daha keskin görünen ayrıntılar oluşturabilir, ancak daha yavaştır ve dokuyu halüsinasyona uğratabilir; bu nedenle doğru seçim, uyumluluğa, hıza veya görsel restorasyona ihtiyacınız olup olmadığına bağlıdır. + Küçük resimler, yükleme sınırları, ekran görüntüleri ve basit boyut değişiklikleri için normal yeniden boyutlandırmayı kullanın. + Küçük veya yumuşak bir görüntünün daha büyük boyutta daha iyi görünmesi gerektiğinde AI yükseltmeyi kullanın. + Yapay zeka yükseltmesinden sonra yüzleri, metinleri ve desenleri karşılaştırın; çünkü oluşturulan ayrıntılar ikna edici ancak yanlış görünebilir. + Filtre sırası önemlidir + Temizleme, renk, keskinlik ve son sıkıştırmayı kasıtlı bir sırayla uygulayın + Daha temiz bir filtre yığını oluşturun + Filtreler her zaman birbirinin yerine kullanılamaz. Keskinleştirmeden önceki bulanıklık, OCR\'den önceki kontrast artışı veya sıkıştırmadan önceki renk değişikliği, aynı kontrollerden çok farklı çıktılar üretebilir. + Önce kırpın ve döndürün, böylece sonraki filtreler yalnızca kalan pikseller üzerinde çalışır. + Keskinleştirme veya stilize efektlerden önce pozlamayı, beyaz dengesini ve kontrastı uygulayın. + Son yeniden boyutlandırma ve sıkıştırmayı sona yakın tutun, böylece önizlemesi yapılan ayrıntılar dışa aktarmada öngörülebilir şekilde hayatta kalır. + Arka plan kenarlarını düzeltin + Otomatik kaldırmanın ardından haleleri, kalan gölgeleri, saçları, delikleri ve yumuşak hatları temizleyin + Kesiklerin kasıtlı görünmesini sağlayın + Otomatik maskeler genellikle ilk bakışta iyi görünür ancak parlak, karanlık veya desenli arka planlarda sorunları ortaya çıkarır. Kenar temizleme, hızlı kesme ile yeniden kullanılabilir çıkartma, logo veya ürün görseli arasındaki farktır. + Haleleri ve eksik kenar ayrıntılarını ortaya çıkarmak için sonucu zıt arka planlara karşı önizleyin. + Çevredeki artıkları silmeden önce saçlar, köşeler ve şeffaf nesneler için geri yüklemeyi kullanın. + Kesimin bir tasarıma yerleştirileceği son arka plan rengine ilişkin küçük bir testi dışa aktarın. + Okunabilir filigranlar + Fotoğrafı bozmadan parlak, karanlık, meşgul ve kırpılmış görüntülerde işaretleri görünür hale getirin + Görüntüyü gizlemeden koruyun + Bir görüntüde güzel görünen filigran diğerinde kaybolabilir. Boyut, opaklık, kontrast, yerleştirme ve tekrarlama yalnızca ilk önizleme için değil, tüm grup için seçilmelidir. + Tüm dosyaları dışa aktarmadan önce toplu işteki en parlak ve en karanlık görüntüler üzerinde işareti test edin. + Tekrarlanan büyük işaretler için daha düşük opaklık, küçük köşe işaretleri için daha güçlü kontrast kullanın. + İşaretin yeniden kullanımı önlemesi amaçlanmadıkça önemli yüzleri, metinleri ve ürün ayrıntılarını açık tutun. + Bir görüntüyü döşemelere bölme + Tek bir görüntüyü satırlara, sütunlara, özel yüzdelere, formata ve kaliteye göre kesin + Izgaraları ve sıralı parçaları hazırlayın + Görüntü Bölme, tek bir kaynak görüntüden birden fazla çıktı oluşturur. Satır ve sütun sayılarına göre bölebilir, satır veya sütun yüzdelerini ayarlayabilir ve ızgaralar, sayfa dilimleri, panoramalar ve sıralı sosyal gönderiler için yararlı olan seçilen çıktı formatı ve kalitesiyle parçaları kaydedebilir. + Kaynak görüntüyü seçin, ardından ihtiyacınız olan satır ve sütun sayısını ayarlayın. + Parçaların hepsinin eşit boyutta olmaması gerektiğinde satır veya sütun yüzdelerini ayarlayın. + Oluşturulan her parçanın aynı dışa aktarma kurallarını kullanması için kaydetmeden önce çıktı formatını ve kalitesini seçin. + Görüntü şeritlerini kesin + Dikey veya yatay bölgeleri kaldırın ve kalan parçaları tekrar birleştirin + Boşluk bırakmadan bir bölgeyi kaldırın + Görüntü Kesme normal kırpmadan farklıdır. Seçilen bir dikey veya yatay şeridi kaldırabilir, ardından kalan görüntü parçalarını bir araya getirebilir. Ters mod bunun yerine seçilen bölgeyi korur ve her iki ters yönün kullanılması da seçilen dikdörtgeni korur. + Yan bölgeler, sütunlar veya orta şeritler için dikey kesmeyi kullanın; afişler, boşluklar veya sıralar için yatay kesmeyi kullanın. + Seçilen parçayı kaldırmak yerine tutmak istediğinizde eksende tersini etkinleştirin. + Kestikten sonra kenarları önizleyin; çünkü birleştirilmiş içerik, kaldırılan alan önemli ayrıntıların üzerinden geçtiğinde ani görünebilir. + Çıktı formatını seçin + İçeriğe ve hedefe göre JPEG, PNG, WebP, AVIF, JXL veya PDF\'yi seçin + Hedefin formata karar vermesine izin verin + En iyi format, görselin ne içerdiğine ve nerede kullanılacağına bağlıdır. Fotoğraflar, ekran görüntüleri, şeffaf varlıklar, belgeler ve animasyonlu dosyaların tümü, yanlış formata zorlandığında farklı şekillerde başarısız olur. + Şeffaflık ve tam piksellerin gerekli olmadığı durumlarda geniş fotoğraf uyumluluğu için JPEG\'i kullanın. + Keskin ekran görüntüleri, kullanıcı arayüzü yakalamaları, şeffaf grafikler ve kayıpsız düzenlemeler için PNG\'yi kullanın. + Kompakt modern çıktı önemli olduğunda ve alıcı uygulama bunu desteklediğinde WebP, AVIF veya JXL\'i kullanın. + Ses kapaklarını çıkarın + Gömülü albüm resmini veya mevcut medya çerçevelerini ses dosyalarından PNG görüntüleri olarak kaydedin + Sanat eserlerini ses dosyalarından çıkarın + Ses Kapakları, ses dosyalarından gömülü resimleri okumak için Android medya meta verilerini kullanır. Gömülü görsel mevcut olmadığında, Android\'in sunabilmesi durumunda alıcı bir medya çerçevesine geri dönebilir; ikisi de mevcut değilse araç, çıkarılacak görüntü olmadığını bildirir. + Ses Kapaklarını açın ve beklenen kapak resmini içeren bir veya daha fazla ses dosyası seçin. + Özellikle akış veya kayıt uygulamalarındaki dosyalar için, kaydetmeden veya paylaşmadan önce çıkarılan kapağı inceleyin. + Hiçbir görüntü bulunamazsa, ses dosyasının büyük olasılıkla gömülü kapağı yoktur veya Android kullanılabilir bir çerçeve ortaya çıkaramamıştır. + Tarihler ve konum meta verileri + Yakalama süresinin önemli olduğu ancak GPS veya cihaz verilerinin sızmaması gerektiğinde neyin korunacağına karar verin + Yararlı meta verileri özel meta verilerden ayırın + Meta verilerin hepsi eşit derecede riskli değildir. Yakalama tarihleri ​​arşivler ve sıralama için yararlı olabilir; GPS, cihaz seri benzeri alanlar, yazılım etiketleri veya sahip alanları ise amaçlanandan daha fazlasını ortaya çıkarabilir. + Albümler, taramalar veya proje geçmişi için kronolojik sıralama önemli olduğunda tarih ve saat etiketlerini saklayın. + Görüntüleri herkese açık olarak paylaşmadan veya yabancılara göndermeden önce GPS\'i ve cihazı tanımlayan etiketleri kaldırın. + Bir örneği dışa aktarın ve gizlilik gereksinimleri katı olduğunda EXIF\'i tekrar inceleyin. + Dosya adı çakışmalarından kaçının + Birçok dosya image.jpg veya ekran görüntüsü.png gibi adları paylaştığında toplu çıktıları koruyun + Her çıktı adını benzersiz yapın + Görüntüler mesajlaşma programlarından, ekran görüntülerinden, bulut klasörlerinden veya birden fazla kameradan geldiğinde yinelenen dosya adları yaygındır. İyi bir model, yanlışlıkla değiştirilmesini önler ve çıktıların kaynaklarına kadar izlenebilir olmasını sağlar. + Düzenlenen kopyanın tanınabilmesi gerektiğinde orijinal adını ve bir son eki ekleyin. + Büyük karışık partileri işlerken sıra, zaman damgası, ön ayar veya boyutlar ekleyin. + Adlandırma modelinin çakışamayacağını doğrulayana kadar iş akışlarının üzerine yazmaktan kaçının. + Kaydet veya paylaş + Kalıcı bir dosya ile başka bir uygulamaya geçici aktarım arasında seçim yapın + Doğru niyetle ihracat yapın + Kaydet ve Paylaş benzer hislere sahip olabilir ancak farklı sorunları çözerler. Kaydetmek daha sonra bulabileceğiniz bir dosya oluşturur; paylaşım, oluşturulan sonucu Android aracılığıyla başka bir uygulamaya gönderir ve kalıcı bir yerel kopya bırakmayabilir. + Çıktının bilinen bir klasörde kalması, yedeklenmesi veya daha sonra yeniden kullanılması gerektiğinde Kaydet\'i kullanın. + Hızlı mesajlar, e-posta ekleri, sosyal paylaşımlar veya sonucu başka bir düzenleyiciye göndermek için Paylaş\'ı kullanın. + Alıcı uygulama güvenilmez olduğunda veya başarısız bir paylaşımın yeniden oluşturulması can sıkıcı olduğunda ilk önce kaydedin. + PDF sayfa boyutu ve kenar boşlukları + Belge oluşturmadan önce kâğıt boyutunu, sığma davranışını ve nefes alma alanını seçin + Resim sayfalarını yazdırılabilir ve okunabilir hale getirin + Resimlerin şekilleri seçilen kâğıt boyutuyla eşleşmediğinde garip PDF sayfaları haline gelebilir. Kenar boşlukları, sığdırma modu ve sayfa yönü, içeriğin kırpılacağını, küçültüleceğini, uzatılacağını veya rahat okunacağını belirler. + PDF\'nin yazdırılabileceği veya bir forma gönderilebileceği durumlarda gerçek kâğıt boyutunu kullanın. + Metnin sayfa kenarına yaslanmaması için taramalar ve ekran görüntüleri için kenar boşluklarını kullanın. + Kaynak görüntülerin yönü karışık olduğunda dikey ve yatay sayfaları birlikte önizleyin. + Taramaları temizleme + PDF veya OCR\'dan önce kâğıt kenarlarını, gölgeleri, kontrastı, döndürmeyi ve okunabilirliği iyileştirin + Arşivlemeden önce sayfaları hazırlayın + Bir tarama teknik olarak yakalanabilir ancak okunması hala zordur. PDF\'yi dışa aktarmadan önceki küçük temizleme adımları, özellikle faturalar, el yazısı notlar ve az ışıklı sayfalar için genellikle sıkıştırma ayarlarından daha önemlidir. + Perspektifi düzeltin ve masa kenarlarını, parmakları, gölgeleri ve arka plandaki dağınıklığı kırpın. + Damgalara, imzalara veya kurşun kalem izlerine zarar vermeden metnin daha net hale gelmesi için kontrastı dikkatli bir şekilde artırın. + OCR\'yi yalnızca sayfa düzgün ve okunabilir hale geldikten sonra çalıştırın, ardından önemli sayıları manuel olarak doğrulayın. + PDF\'leri sıkıştırın, gri tonlamalı veya onarın + Daha hafif, yazdırılabilir veya yeniden oluşturulmuş belge kopyaları için tek dosyalı PDF işlemlerini kullanın + PDF temizleme işlemini seçin + PDF Araçları sıkıştırma, gri tonlamalı dönüştürme ve onarım için ayrı işlemler içerir. Sıkıştırma ve gri tonlama esas olarak gömülü görüntüler aracılığıyla çalışır; onarım ise PDF yapısını yeniden oluşturur; bunların hiçbiri her belge için garantili bir düzeltme olarak değerlendirilmemelidir. + Belge görüntüler içerdiğinde ve dosya boyutu paylaşım için önemli olduğunda PDF\'yi Sıkıştır\'ı kullanın. + Belgenin yazdırılmasının daha kolay olması gerektiğinde veya renkli görüntülere ihtiyaç duyulmadığında Gri Tonlamalı seçeneğini kullanın. + Bir belge açılmadığında veya iç yapısı hasar gördüğünde kopyada PDF\'yi Onar\'ı kullanın ve ardından yeniden oluşturulan dosyayı doğrulayın. + PDF\'lerden görüntüleri çıkarın + Gömülü PDF görüntülerini kurtarın ve çıkarılan sonuçları bir arşive paketleyin + Kaynak görüntüleri belgelerden kaydedin + Görüntüleri Ayıkla, PDF\'yi gömülü görüntü nesneleri için tarar, mümkün olduğunda kopyaları atlar ve kurtarılan görüntüleri oluşturulan bir ZIP arşivinde saklar. Metinleri, vektör çizimlerini veya görünen her sayfayı ekran görüntüsü olarak değil, yalnızca PDF\'ye gerçekten gömülü olan görüntüleri çıkarır. + Katalogdaki fotoğraflar veya taranmış varlıklar gibi PDF içinde saklanan resimlere ihtiyaç duyduğunuzda Görüntüleri Çıkart\'ı kullanın. + Metin ve düzen de dahil olmak üzere tam sayfalara ihtiyaç duyduğunuzda bunun yerine PDF\'den Görüntülere veya sayfa çıkartmayı kullanın. + Araç hiçbir gömülü görüntü bildirmiyorsa sayfada metin/vektör içeriği olabilir veya görüntüler çıkarılabilir nesneler olarak saklanmıyor olabilir. + Meta veriler, düzleştirme ve yazdırma çıktısı + Belge özelliklerini düzenleyin, sayfaları rasterleştirin veya özel yazdırma düzenlerini kasıtlı olarak hazırlayın + Son belge eylemini seçin + PDF meta verileri, düzleştirme ve baskı hazırlığı, farklı son aşama sorunlarını çözer. Meta veriler belge özelliklerini kontrol eder, Düzleştir PDF, sayfaları daha az düzenlenebilir çıktılar halinde rasterleştirir ve PDF Yazdır, sayfa boyutu, yönlendirme, kenar boşluğu ve sayfa başına sayfa kontrolleriyle yeni bir düzen hazırlar. + Yazar, başlık, anahtar kelimeler, yapımcı veya gizlilik temizliği önemli olduğunda Meta Verileri kullanın. + Görünür sayfa içeriğinin ayrı PDF nesneleri olarak düzenlenmesi zorlaştığında PDF\'yi Düzleştir seçeneğini kullanın. + Özel sayfa boyutuna, yönlendirmeye, kenar boşluklarına veya yaprak başına birden fazla sayfaya ihtiyacınız olduğunda PDF Yazdır\'ı kullanın. + Görüntüleri OCR için hazırlama + OCR\'dan metni okumasını istemeden önce kırpma, döndürme, kontrast, gürültü giderme ve ölçekleme özelliklerini kullanın + OCR\'a daha temiz pikseller verin + OCR hataları genellikle OCR motorundan ziyade giriş görüntüsünden kaynaklanır. Çarpık sayfalar, düşük kontrast, bulanıklık, gölgeler, küçük metinler ve karışık arka planlar, tanıma kalitesini düşürür. + Metin alanını kırpın ve görüntüyü, çizgiler tanınmadan önce yatay olacak şekilde döndürün. + Yalnızca harflerin okunmasını kolaylaştırdığında kontrastı artırın veya daha temiz siyah beyaza dönüştürün. + Küçük metni orta derecede yukarı doğru yeniden boyutlandırın, ancak sahte kenarlar oluşturan agresif keskinleştirmelerden kaçının. + QR içeriğini kontrol edin + Bir koda güvenmeden önce bağlantıları, Wi-Fi kimlik bilgilerini, kişileri ve eylemleri inceleyin + Kodu çözülmüş metni güvenilmeyen giriş olarak değerlendir + QR kodu bir URL\'yi, kişi kartını, Wi-Fi şifresini, takvim etkinliğini, telefon numarasını veya düz metni gizleyebilir. Kodun yanındaki görünen etiket gerçek veriyle eşleşmeyebilir; bu nedenle, üzerinde işlem yapmadan önce kodu çözülmüş içeriği inceleyin. + Açmadan önce kodu çözülmüş URL\'nin tamamını, özellikle kısaltılmış veya tanıdık olmayan alan adlarını inceleyin. + Hassas eylemleri tetikleyebileceklerinden Wi-Fi, kişi, takvim, telefon ve SMS kodlarına dikkat edin. + İçeriği başka bir yerde doğrulamanız gerektiğinde hemen açmak yerine önce kopyalayın. + QR kodları oluşturun + Düz metin, URL\'ler, Wi-Fi, kişiler, e-posta, telefon, SMS ve konum verilerinden kodlar oluşturun + Doğru QR verisini oluşturun + QR ekranı girilen içerikten kod üretebildiği gibi mevcut içerikleri de tarayabilmektedir. Yapılandırılmış QR türleri, Wi-Fi oturum açma ayrıntıları, kişi kartları, e-posta alanları, telefon numaraları, SMS içeriği, URL\'ler veya coğrafi koordinatlar gibi verilerin diğer uygulamaların anlayabileceği bir biçimde kodlanmasına yardımcı olur. + Basit notlar için düz metin seçin veya kodun Wi-Fi, kişi, URL, e-posta, telefon, SMS veya konum verileri olarak açılması gerektiğinde yapılandırılmış bir tür kullanın. + Görünür önizleme yalnızca girdiğiniz yükü yansıttığından, oluşturulan kodu paylaşmadan önce her alanı kontrol edin. + Kaydedilen QR kodunu, yazdırılacağı, herkese açık olarak yayınlanacağı veya başkaları tarafından kullanılacağı zaman bir tarayıcıyla test edin. + Bir sağlama toplamı modu seçin + Dosyalardan veya metinlerden karmaları hesaplayın, bir dosyayı karşılaştırın veya birçok dosyayı toplu olarak kontrol edin + Görünümü değil içeriği doğrulayın + Sağlama Toplamı Araçları, dosya karma işlemi, metin karma işlemi, bir dosyayı bilinen bir karma değerle karşılaştırma ve toplu karşılaştırma için ayrı sekmelere sahiptir. Bir sağlama toplamı, seçilen algoritmanın bayt kimliğini kanıtlar; iki görüntünün yalnızca benzer göründüğünü kanıtlamaz. + Dosyalar için Hesapla\'yı ve yazılan veya yapıştırılan metin verileri için Metin Karmasını kullanın. + Birisi size bir dosya için beklenen sağlama toplamını verdiğinde Karşılaştırma\'yı kullanın. + Çok sayıda dosyanın tek geçişte karma veya doğrulamaya ihtiyacı olduğunda Toplu Karşılaştırma\'yı kullanın, ardından seçilen algoritmayı tutarlı tutun. + Pano gizliliği + Kopyalanan özel verileri yanlışlıkla açığa çıkarmadan otomatik pano özelliklerini kullanın + Kopyalanan içeriği kasıtlı tutun + Pano otomasyonu kullanışlıdır ancak kopyalanan metin şifreler, bağlantılar, adresler, belirteçler veya özel notlar içerebilir. Pano tabanlı girişi, alışkanlıklarınıza ve gizlilik beklentilerinize uyması gereken bir hız özelliği olarak değerlendirin. + Araçlarda beklenmedik bir şekilde özel metin görünüyorsa otomatik pano kullanımını devre dışı bırakın. + Yalnızca panoya kaydetmeyi yalnızca sonucun kasıtlı olarak depolamadan kaçınmasını istediğinizde kullanın. + Hassas metin, QR verileri veya Base64 verilerini işledikten sonra panoyu temizleyin veya değiştirin. + Renk formatları + Başka bir yere yapıştırmadan önce HEX, RGB, HSV, alpha ve kopyalanan renk değerlerini anlayın + Hedefin beklediği formatı kopyalayın + Aynı görünür renk birkaç şekilde yazılabilir. Bir tasarım aracı, CSS alanı, Android renk değeri veya resim düzenleyici farklı bir düzen, alfa işleme veya renk modeli bekleyebilir. + Kompakt renk kodları gerektiren web, tasarım veya tema alanlarına yapıştırma yaparken HEX kullanın. + Kanalları, parlaklığı, doygunluğu veya tonu manuel olarak ayarlamanız gerektiğinde RGB veya HSV\'yi kullanın. + Bazı hedeflerin bunu görmezden gelmesi veya farklı bir sıra kullanması nedeniyle şeffaflığın önemli olduğu durumlarda alfayı ayrı olarak kontrol edin. + Renk kitaplığına göz atın + Adlandırılmış renkleri ada veya HEX\'e göre arayın ve favorilerinizi en üstte tutun + Yeniden kullanılabilir adlandırılmış renkleri bulun + Renk Kitaplığı, adlandırılmış bir renk koleksiyonunu yükler, ada göre sıralar, renk adına veya HEX değerine göre filtreler ve favori renkleri saklar, böylece önemli girdilere daha kolay ulaşılabilir. Bir görüntüden örnekleme yapmak yerine gerçek adlandırılmış bir renge ihtiyacınız olduğunda kullanışlıdır. + Aileyi bildiğinizde kırmızı, mavi, barut rengi veya nane gibi bir renk adına göre arama yapın. + Zaten sayısal bir renginiz varsa ve eşleşen veya yakındaki adlandırılmış girişleri bulmak istiyorsanız HEX\'e göre arama yapın. + Sık kullanılan renkleri favori olarak işaretleyin, böylece gezinirken genel kitaplığın önüne geçmelerini sağlayın. + Kafes degrade koleksiyonlarını kullanma + Mevcut örgü degrade kaynaklarını indirin ve seçilen görselleri paylaşın + Uzak ağ degrade varlıklarını kullanma + Mesh Degradeler, uzak bir kaynak koleksiyonunu yükleyebilir, eksik degrade görüntülerini indirebilir, indirme ilerlemesini gösterebilir ve seçilen degradeleri paylaşabilir. Kullanılabilirlik, ağ erişimine ve uzak kaynak deposuna bağlıdır; dolayısıyla boş bir liste genellikle kaynakların henüz kullanılabilir olmadığı anlamına gelir. + Hazır kafes degrade görüntüleri istediğinizde degrade araçlarından Kafes Degradelerini açın. + Koleksiyonun boş olduğunu varsaymadan önce kaynak yükleme veya indirme işleminin ilerlemesini bekleyin. + İhtiyacınız olan degradeleri seçin ve paylaşın veya manuel olarak özel bir degrade oluşturmak istediğinizde Gradient Maker\'a dönün. + Oluşturulan varlık çözümlemesi + Degradeler, gürültü, duvar kağıtları, SVG benzeri resimler veya dokular oluşturmadan önce çıktı boyutunu seçin + İhtiyacınız olan boyutta oluşturun + Oluşturulan görüntülerin başvurabileceği bir kamera orijinali yoktur. Çıktı çok küçükse daha sonraki ölçeklendirme kalıpları yumuşatabilir, ayrıntıları kaydırabilir veya varlığın döşeme ve sıkıştırma şeklini değiştirebilir. + Öncelikle duvar kağıdı, simge, arka plan, baskı veya kaplama gibi son kullanım senaryosunun boyutlarını ayarlayın. + Kırpılabilen, yakınlaştırılabilen veya çeşitli düzenlerde yeniden kullanılabilen dokular için daha büyük boyutlar kullanın. + Prosedür ayrıntıları sıkıştırmanın nasıl davranacağını değiştirebileceğinden, büyük çıktılardan önce test sürümlerini dışa aktarın. + Mevcut duvar kağıtlarını dışa aktar + Cihazdan mevcut Ana Sayfa, Kilit ve yerleşik duvar kağıtlarını alın + Yüklü duvar kağıtlarını kaydedin veya paylaşın + Duvar Kağıtları Dışa Aktarma, Android\'in sistem duvar kağıdı API\'leri aracılığıyla sunduğu duvar kağıtlarını okur. Cihaza, Android sürümüne, izinlere ve yapı çeşidine bağlı olarak Ana Sayfa, Kilit veya yerleşik duvar kağıtları ayrı olarak mevcut olabilir veya eksik olabilir. + Sistemin uygulamanın okumasına izin verdiği duvar kağıtlarını yüklemek için Duvar Kağıtları Dışa Aktarma\'yı açın. + Kaydetmek, kopyalamak veya paylaşmak istediğiniz mevcut Ana Sayfa, Kilit veya yerleşik duvar kağıdı girişlerini seçin. + Bir duvar kağıdı eksikse veya özellik kullanılamıyorsa, bu genellikle bir düzenleme ayarından ziyade bir sistem, izin veya yapı çeşidi sınırlamasıdır. + Köşe Animasyonu Kısma Kolu + Rengi farklı kopyala + HEX + adı + Daha yumuşak saçlar ve kenar tutuşu ile hızlı insan kesimleri için portre paspas modeli. + Zero-DCE++ eğri tahminini kullanarak hızlı düşük ışık iyileştirmesi. + Yağmur izlerini ve yağışlı hava eserlerini azaltmak için Restormer görüntü restorasyon modeli. + Koordinat ızgarasını tahmin eden ve kavisli sayfaları daha düz bir taramayla yeniden eşleştiren çarpıklık giderme modelini belgeleyin. + Hareket Süresi Ölçeği + Uygulama animasyon hızını kontrol eder: 0 hareketi devre dışı bırakır, 1 normaldir, daha yüksek değerler animasyonları yavaşlatır + En Son Araçları Göster + Son kullanılan araçlara hızlı erişimi ana ekranda görüntüleyin + Kullanım İstatistikleri + Uygulama açılışlarını, araç açılışlarını ve en çok kullandığınız araçları keşfedin + Uygulama açılıyor + Araç açılır + Son araç + Başarılı kaydetmeler + Etkinlik serisi + Veriler kaydedildi + En iyi format + Kullanılan araçlar + %1$d açılıyor • %2$s + Henüz kullanım istatistiği yok + Birkaç araç açın, burada otomatik olarak görünecekler + Kullanım istatistikleriniz yalnızca yerel olarak cihazınızda saklanır. ImageToolbox bunları yalnızca kişisel etkinliğinizi, favori araçlarınızı ve kayıtlı veri öngörülerinizi göstermek için kullanır + düzenleyici fotoğraf düzenleme resim rötuş ayarlama + yeniden boyutlandır dönüştür ölçek çözünürlük boyutlar genişlik yükseklik jpg jpeg png webp sıkıştır + sıkıştır boyut ağırlık bayt mb kb kaliteyi azalt optimize et + kırpma kesme en boy oranı + filtre efekti renk düzeltme lut maskesini ayarlama + çizmek boya fırça kalem açıklama ekleme işaretleme + şifre şifrelemek şifresini çözmek şifre gizli + arka plan sökücü silme kaldır kesme şeffaf alfa + önizleme görüntüleyici galeri incelemeyi aç + dikiş birleştirme panorama uzun ekran görüntüsü + url web indir internet bağlantısı resmi + seçici damlalık örneği hex rgb rengi + palet renkleri renk örnekleri özü baskın + exif meta veri gizliliği şeridi kaldır gps konumunu temizle + fark farkını karşılaştırın önce sonra + sınır yeniden boyutlandırma maksimum min megapiksel boyutlar kelepçe + pdf belge sayfaları araçları + ocr metni tanıma tarama ayıklama + degrade arka plan renk ağı + filigran logo metin damgası yerleşimi + gif animasyon animasyonlu çerçeveleri dönüştürme + apng animasyon animasyonlu png çerçeveleri dönüştürme + zip arşivi sıkıştır paketi dosyaları aç + jxl jpeg xl jpg resim formatına dönüştürme + svg vektör izleme xml dönüştürme + format dönüştürme jpg jpeg png webp heic avif jxl bmp + belge tarayıcı tarama kağıdı perspektif kırpma + qr barkod kod tarayıcı taraması + istifleme kaplaması ortalama medyan astrofotografi + bölünmüş fayans ızgara dilim parçaları + renk dönüştürme hex rgb hsl hsv cmyk laboratuvarı + webp animasyonlu resim formatına dönüştürme + gürültü üreteci rastgele doku tanesi + kolaj ızgara düzeni birleştirme + işaretleme katmanları açıklama ekleme kaplama düzenleme + base64 kod çözme veri uri\'si + sağlama toplamı karma md5 sha1 sha256 doğrulamak + örgü degrade arka planı + exif meta verileri düzenleme gps tarih kamerası + bölünmüş ızgara parçalarını kesin + ses kapağı albüm sanat müzik meta verileri + duvar kağıdı ihracat arka planı + ascii metin sanatı karakterleri + ai ml nöral geliştirme lüks segment + renkler adlı renk kitaplığı malzeme paleti + gölgelendirici glsl parça efekt stüdyosu + pdf birleştirme + pdf ayrı sayfalara bölünmüş + pdf sayfa yönünü döndürme + pdf yeniden düzenleme yeniden sıralama sayfaları sıralama + pdf sayfa numaraları sayfalandırma + pdf ocr metni aranabilir özü tanır + pdf filigran damga logo metni + pdf imza işareti çizimi + pdf şifreyi koru şifreleme kilidi + pdf kilit açma şifre çözme korumayı kaldır + pdf sıkıştır boyutu küçült optimize et + pdf gri tonlamalı siyah beyaz tek renkli + pdf onarım düzeltme kırık kurtarma + pdf meta veri özellikleri exif yazar başlığı + pdf sayfaları sil kaldır + pdf kırpma kenar boşlukları + pdf düzleştirme ek açıklamalar katmanları oluşturur + pdf görüntüleri resim ayıklamak + pdf zip arşivi sıkıştırmak + pdf yazıcı + pdf önizleme görüntüleyici okuma + görüntüleri pdf\'ye dönüştürme jpg jpeg png belgesi + pdf\'yi resim sayfalarına jpg png\'ye aktar + pdf ek açıklamalar yorumlar kaldır temiz + Nötr Varyant + Hata + Alt Gölge + Diş Yüksekliği + Yatay Diş Aralığı + Dikey Diş Aralığı + Üst Kenar + Sağ Kenar + Alt Kenar + Sol Kenar + Yırtık Kenar + Kullanım istatistiklerini sıfırla + Araç açılır, istatistikleri kaydetme, seri, kayıtlı veriler ve üst format sıfırlanır. Uygulama açılışları değişmeden kalacaktır. + Ses + Dosya + Profilleri dışa aktar + Mevcut profili kaydet + Dışa aktarma profilini sil + \\"%1$s\\" dışa aktarma profilini silmek istiyor musunuz? + Kenarlık çizme + Çizim ve silme tuvalinin çevresinde ince bir kenarlık gösterme + Dalgalı + Yumuşak dalgalı kenarlı yuvarlak UI öğeleri + Kepçe + Çentik + İçe doğru oyulmuş yuvarlatılmış köşeler + Çift kesimli basamaklı köşeler + Yer tutucu + Orijinal dosya adını uzantısız eklemek için {filename} kullanın + parlama + Baz tutar + Zil miktarı + Işın miktarı + Halka genişliği + Perspektifi Deforme Et + Java Görünümü ve Hissi + Kırpmak + X açısı + ve açı + Çıktıyı yeniden boyutlandır + Su Damlası + Dalgaboyu + Faz + Yüksek Geçiş + Renk Maskesi + Krom + Çözün + Yumuşaklık + Geri bildirim + Mercek Bulanıklığı + Düşük giriş + Yüksek giriş + Düşük çıkış + Yüksek çıkış + Işık Efektleri + Çarpma yüksekliği + Yumru yumuşaklığı + Dalgalanma + X genliği + Y genliği + X dalga boyu + Y dalga boyu + Uyarlanabilir Bulanıklaştırma + Doku Üretimi + Çeşitli prosedür dokuları oluşturun ve bunları görüntü olarak kaydedin + Doku Türü + Ön yargı + Zaman + Parlamak + Dağılım + Örnekler + Açı katsayısı + Gradyan katsayısı + Izgara türü + Mesafe gücü + Y Ölçeği + bulanıklık + Temel türü + Türbülans faktörü + Ölçeklendirme + Yinelemeler + Yüzükler + Fırçalanmış Metal + Kostikler + Hücresel + Dama Tahtası + fBm + Mermer + Plazma + Yorgan + Odun + rastgele + Kare + Altıgen + Sekizgen + üçgen + çıkıntılı + VL Gürültüsü + SC Gürültüsü + Son + Henüz yakın zamanda kullanılan filtre yok + doku jeneratör fırçalanmış metal kostik hücresel dama tahtası mermer plazma yorgan ahşap tuğla kamuflaj hücre bulut çatlak kumaş yeşillik petek buz lav nebula kâğıt pas kum duman taş arazi topografya su dalgalanma + Tuğla + Kamuflaj + Hücre + Bulut + Çatırtı + Kumaş + Yeşillik + Petek + Buz + Lav + Bulutsusu + Kâğıt + Pas + Kum + Duman + Taş + Arazi + Topografya + Su Dalgalanması + Gelişmiş Ahşap + Harç genişliği + Düzensizlik + Eğim + Pürüzlülük + İlk eşik + İkinci eşik + Üçüncü eşik + Kenar yumuşaklığı + Kenarlık genişliği + Kapsam + Detay + Derinlik + Dallanma + Yatay dişler + Dikey dişler + tüyler + Damarlar + Aydınlatma + Çatlak genişliği + Don + Akış + Kabuk + Bulut yoğunluğu + Yıldızlar + Lif yoğunluğu + Elyaf gücü + Lekeler + Korozyon + Çukurlaşma + pul + Kumul frekansı + Rüzgar açısı + Dalgalar + Demetler + Damar ölçeği + Su seviyesi + Dağ seviyesi + Erozyon + Kar seviyesi + Satır sayısı + Çizgi kalınlığı + Gölgeleme + Gözenek rengi + Harç rengi + Koyu tuğla rengi + Tuğla rengi + Rengi vurgula + Koyu renk + Orman rengi + Toprak rengi + Kum rengi + Arka plan rengi + Hücre rengi + Kenar rengi + Gökyüzü rengi + Gölge rengi + Açık renk + Yüzey rengi + Varyasyon rengi + Çatlak rengi + Çözgü rengi + Atkı rengi + Koyu yaprak rengi + Yaprak rengi + Kenarlık rengi + Bal rengi + Derin renk + Buz rengi + Don rengi + Kabuk rengi + Yıkama rengi + Parlak renk + Uzay rengi + Menekşe rengi + Mavi renk + Temel renk + Elyaf rengi + Leke rengi + metalik renk + Koyu pas rengi + Pas rengi + Turuncu renk + Duman rengi + Damar rengi + Ova rengi + Su rengi + Kaya rengi + Kar rengi + Düşük renk + Yüksek renk + Çizgi rengi + Sığ renk + Çimen + Kir + Deri + Beton + Asfalt + Yosun + Ateş + Aurora + Petrol Kayganlığı + Suluboya + Soyut Akış + Opal + Şam Çelik + Yıldırım + Kadife + Mürekkep Ebru + Holografik Folyo + Biyolüminesans + Kozmik Girdap + Lav Lambası + Olay Ufku + Fraktal Bloom + Kromatik Tünel + Tutulma Korona + Garip Çekici + Ferrofluid Taç + Süpernova + İris + Tavus Kuşu Tüyü + Nautilus Kabuğu + Halkalı Gezegen + Bıçak yoğunluğu + Bıçak uzunluğu + Rüzgâr + Düzensizlik + Yığınlar + Nem + Çakıl taşları + Kırışıklıklar + Gözenekler + Agrega + Çatlaklar + katran + Giymek + Benekler + Lifler + Alev frekansı + Duman + Yoğunluk + Şeritler + Bantlar + yanardönerlik + Karanlık + çiçek açar + Pigment + Kenarlar + Kâğıt + Difüzyon + Simetri + Keskinlik + Renk oyunu + sütlülük + Katmanlar + Katlanır + Lehçe + Şubeler + Yön + parlaklık + Kıvrımlar + Tüylenme + Mürekkep dengesi + Spektrum + Kırışıklıklar + Kırınım + Silahlar + Büküm + Çekirdek parıltısı + Bloblar + Disk eğimi + Ufuk boyutu + Disk genişliği + Lensleme + Yapraklar + Kıvırmak + Telkari + Yönler + Eğrilik + Ay boyutu + Korona boyutu + Işınlar + Elmas yüzük + Loblar + Yörünge yoğunluğu + Kalınlık + Sivri uçlar + Başak uzunluğu + Vücut büyüklüğü + metalik + Şok yarıçapı + Kabuk genişliği + Dışarı atıldı + Öğrenci boyutu + İris boyutu + Renk değişimi + Yakalama ışığı + Göz boyutu + Diken yoğunluğu + Dönüşler + Odalar + Açılış + Sırtlar + Sedef + Gezegen boyutu + Halka eğimi + Halka genişliği + Atmosfer + Kir rengi + Koyu çim rengi + Çim rengi + İpucu rengi + Koyu toprak rengi + Kuru renk + Çakıl taşı rengi + Deri rengi + Beton rengi + Katran rengi + Asfalt rengi + Taş rengi + Toz rengi + Toprak rengi + Koyu yosun rengi + Yosun rengi + Kırmızı renk + Çekirdek rengi + Yeşil renk + Camgöbeği rengi + Macenta rengi + Altın rengi + Kâğıt rengi + Pigment rengi + İkincil renk + İlk renk + İkinci renk + Pembe renk + Koyu çelik rengi + Çelik rengi + Hafif çelik rengi + Oksit rengi + Hale rengi + Cıvata rengi + Kadife rengi + Parlak renk + Mavi mürekkep rengi + Kırmızı mürekkep rengi + Koyu mürekkep rengi + Gümüş rengi + Sarı renk + Doku rengi + Disk rengi + Sıcak renk + Mercek rengi + Dış renk + İç renk + Korona rengi + Soğuk renk + Sıcak renk + Bulut rengi + Alev rengi + Tüy rengi + Kabuk rengi + İnci rengi + Gezegen rengi + Halka rengi + Kaydettikten sonra orijinal dosyaları silin + Yalnızca başarıyla işlenen kaynak dosyalar silinecek + Orijinal dosyalar silindi: %1$d + Orijinal dosyalar silinemedi: %1$d + Orijinal dosyalar silindi: %1$d, başarısız oldu: %2$d + Sayfa hareketleri + Genişletilmiş seçenek sayfalarının sürüklenmesine izin ver + Renk alt örneklemesi + Bit derinliği + Kayıpsız + %1$d-bit + Toplu Yeniden Adlandırma + dosyaları toplu olarak yeniden adlandırın dosya adı desen sıra tarih uzantısı + Yeniden adlandırılacak dosyaları seçin + Dosya adı modeli + Yeniden isimlendirmek + Tarih kaynağı + EXIF\'in alındığı tarih + Dosyanın değiştirilme tarihi + Dosyanın oluşturulma tarihi + Güncel tarih + Manuel tarih + Manuel tarih + Manuel süre + Bir dosya adı modeli girin + Desen geçersiz veya aşırı uzun bir dosya adı üretiyor + Bu model, toplu yeniden adlandırma için kullanılamayan belirteçleri kullanır + İsim aynı kalacak + Dosya adı kalıplarını kullanarak birden fazla dosyayı yeniden adlandırın + Seçilen tarih bazı dosyalar için kullanılamıyor. Onlar için güncel tarih kullanılacaktır. + Desen yinelenen dosya adları üretiyor + Tüm dosya adları zaten kalıpla eşleşiyor + Dosyalar başarıyla yeniden adlandırıldı + Bazı dosyalar yeniden adlandırılamadı + İzin verilmedi + Orijinal dosya adını uzantısız olarak kullanarak kaynak kimliğini olduğu gibi korumanıza yardımcı olur. Ayrıca \\o{start:end} ile dilimlemeyi, \\o{t} ile harf çevirisini ve \\o{s/eski/yeni/ile değiştirmeyi de destekler. + Otomatik artan sayaç. Özel biçimlendirmeyi destekler: \\c{padding} (ör. \\c{3} -&gt; 001), \\c{start:step} veya \\c{start:step:padding}. + Orijinal dosyanın bulunduğu ana klasörün adı. + Orijinal dosyanın boyutu. Birimleri destekler: \\z{b}, \\z{kb}, \\z{mb} veya insan tarafından okunabilir format için yalnızca \\z. + Dosya adının benzersizliğini sağlamak için rastgele bir Evrensel Benzersiz Tanımlayıcı (UUID) oluşturur. + Dosyaları yeniden adlandırma işlemi geri alınamaz. Devam etmeden önce yeni adların doğru olduğundan emin olun. + Yeniden adlandırmayı onayla + Yan menü ayarları + Menü ölçeği + Menü alfa + Otomatik Beyaz Dengesi + Kırpma + Gizli filigranlar kontrol ediliyor + Depolamak + Önbellek sınırı + Bu boyutun ötesine geçtiğinde önbelleği otomatik olarak temizleyin + Temizleme aralığı + Önbelleğin temizlenmesi gerekip gerekmediğini kontrol etme sıklığı + Uygulama başlatıldığında + 1 gün + 1 hafta + 1 ay + Seçilen sınıra ve aralığa göre uygulama önbelleğini otomatik olarak temizleyin + Hareketli mahsul alanı + Kırpma alanının tamamının içine sürüklenerek taşınmasına olanak sağlar + GIF\'i birleştir + Birden fazla GIF klibini tek bir animasyonda birleştirin + GIF klipleri sırası + Tersi + Ters olarak oynat + Bumerang + İleri ve geri oynat + Çerçeve boyutunu normalleştir + Çerçeveleri en büyük klibe sığacak şekilde ölçeklendirin; orijinal ölçeklerini korumak için kapatın + Klipler arasındaki gecikme (ms) + Dosya + Bir klasördeki ve alt klasörlerindeki tüm görüntüleri seç + Yinelenen Bulucu + Tam kopyaları ve görsel olarak benzer görselleri bulun + yinelenen bulucu benzer görseller tam kopyalar fotoğraf temizleme depolama dHash SHA-256 + Yinelenenleri bulmak için görselleri seçin + Benzerlik duyarlılığı + Tam kopyalar + Benzer görseller + Tüm tam kopyaları seç + Önerilenler dışında tümünü seç + Kopya bulunamadı + Daha fazla resim eklemeyi veya benzerlik hassasiyetini artırmayı deneyin + %1$d resim okunamadı + %1$s • %2$s geri kazanılabilir + %1$d grup • %2$s geri kazanılabilir + %1$d seçildi • %2$s + Bu geri alınamaz. Android, sistem iletişim kutusunda silme işlemini onaylamanızı isteyebilir. + Bulunamadı + Başlamak için taşıyın + Sona taşı + \ No newline at end of file diff --git a/core/resources/src/main/res/values-ug/strings.xml b/core/resources/src/main/res/values-ug/strings.xml new file mode 100644 index 0000000..522e2b6 --- /dev/null +++ b/core/resources/src/main/res/values-ug/strings.xml @@ -0,0 +1,3739 @@ + + + + سۈرەت بەك چوڭ، ئالدىن كۆرگىلى بولمايدۇ، لېكىن يەنىلا ساقلاشنى سىناپ باقىمىز + رەسىمنى تاللاپ باشلاڭ + ئېگىزلىك %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 + دان + 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 & 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 & تاياقچە كودى + 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 + سەپلىگىلى بولىدىغان رەڭ ۋە سىزىقلار بىلەن قەۋەتنىڭ ئارقىسىغا تۇتۇق سايە قوشۇڭ + مەزمۇننى بۇرمىلاش + بۇ ھەرىكەتنى تاماملايدىغان ئىچكى ساقلىغۇچ يېتەرلىك ئەمەس. كىچىكرەك رەسىم ئىشلىتىپ ، باشقا ئەپلەرنى تاقاپ ياكى ئەپنى قايتا قوزغىتىپ بېقىڭ. + پاراللېل ئىشچىلار + ئۆزگەرتىلگەن ھۆججەتلەر ئەسلىدىن چوڭراق بولغاچقا بۇ رەسىملەر ساقلانمىدى. ئەسلى رەسىملەرنى ئۆچۈرۈشتىن بۇرۇن بۇ تىزىملىكنى تەكشۈرۈڭ. + ھۆججەتلەر ئاتلاپ كەتتى: %1$s + ئەپ خاتىرىسى + مەسىلىلەرنى بايقاش ئۈچۈن ئەپنىڭ خاتىرىسىنى كۆرۈڭ + گۇرۇپپا قوراللىرىدىكى ئامراقلار + قوراللار تۈرگە ئايرىغاندا ئامراقلارنى بەتكۈچ قىلىپ قوشىدۇ + ئەڭ ئاخىرقىسىنى ياخشى كۆرۈڭ + ياقتۇرىدىغان قوراللار بەتكۈچىنى ئاخىرىغا يۆتكەيدۇ + گورىزونتال ئارىلىقى + تىك بوشلۇق + قالاق ئېنېرگىيە + ئالغا ئىلگىرىلەش ئالگورىزىمنىڭ ئورنىغا ئاددىي دەرىجىدىكى چوڭلۇقتىكى ئېنېرگىيە خەرىتىسىنى ئىشلىتىڭ + نىقابلانغان رايوننى ئېلىۋېتىڭ + تاملار نىقابلانغان رايوندىن ئۆتىدۇ ، ئۇنى قوغداشنىڭ ئورنىغا تاللانغان رايوننىلا ئېلىۋېتىدۇ + VHS NTSC + ئىلغار NTSC تەڭشىكى + تېخىمۇ كۈچلۈك VHS ۋە تارقىتىش بۇيۇملىرى ئۈچۈن قوشۇمچە ئوخشىتىش تەڭشەش + خىرومنىڭ قېنى + لېنتا كىيىش + ئىز قوغلاش + Luma smear + قوڭغۇراق + قار + مەيدان ئىشلىتىڭ + سۈزگۈچ تىپى + Luma سۈزگۈچ كىرگۈزۈڭ + خروم تۆۋەن ئېغىزى + Chroma demodulation + باسقۇچ ئۆزگەرتىش + باسقۇچ باسقۇچى + باش ئالماشتۇرۇش + باش ئالماشتۇرۇش ئېگىزلىكى + باش ئالماشتۇرۇش + باش ئالماشتۇرۇش + ئوتتۇرا سىزىق ئورنى + Mid-line jitter + شاۋقۇن ئېگىزلىكىنى ئىز قوغلاش + ئىز قوغلاش دولقۇنى + قارنى ئىز قوغلاش + قار ئانانىزىمنى ئىز قوغلاش + ئىز قوغلاش شاۋقۇنى + شاۋقۇننىڭ كۈچلۈكلۈكىنى ئىز قوغلاش + بىرىكمە شاۋقۇن + بىرىكمە شاۋقۇن چاستوتىسى + بىرىكمە شاۋقۇن + بىرىكمە شاۋقۇن تەپسىلاتلىرى + ئۈزۈك چاستوتىسى + قوڭغۇراق كۈچى + Luma شاۋقۇنى + Luma شاۋقۇن چاستوتىسى + Luma شاۋقۇنىنىڭ كۈچلۈكلۈكى + Luma شاۋقۇن تەپسىلاتلىرى + خىروم شاۋقۇنى + خىرومنىڭ ئاۋاز چاستوتىسى + خىرومنىڭ شاۋقۇن ئاۋازى + خىرومنىڭ شاۋقۇن تەپسىلاتلىرى + قارنىڭ كۈچلۈكلۈكى + قار ئانسوتروپى + خروم فازا شاۋقۇنى + خىروم باسقۇچىدىكى خاتالىق + خىروم توغرىسىغا كېچىكىدۇ + خىروم تىك ھالەتتە + VHS لېنتا سۈرئىتى + VHS خروم يوقىتىش + VHS كۈچلۈكلۈكنى ئۆتكۈرلەشتۈرىدۇ + VHS چاستوتىنى ئۆتكۈرلەشتۈرىدۇ + VHS قىر دولقۇن كۈچلۈكلىكى + VHS قىر دولقۇن تېزلىكى + VHS قىر دولقۇن چاستوتىسى + VHS قىر دولقۇن تەپسىلاتلىرى + چىقىرىش خروم تۆۋەن ئېغىزى + كىچىك گورىزونتال + تارازىسى تىك + تارازا ئامىلى X. + تارازا ئامىلى Y. + كۆپ ئۇچرايدىغان رەسىم سۇ بەلگىسىنى بايقايدۇ ۋە ئۇلارنى LaMa بىلەن سىرلايدۇ. بايقاش ۋە سىزىش مودېللىرىنى ئاپتوماتىك چۈشۈرىدۇ + Deuteranopia + رەسىمنى كېڭەيتىش + YOLO v11 بۆلەك ئارقىلىق ئوبيېكت بۆلەكلىرىنى ئاساس قىلغان تەگلىك ئېلىۋېتىش + قۇر بۇلۇڭىنى كۆرسەت + رەسىم سىزىۋاتقاندا نۆۋەتتىكى سىزىقنىڭ ئايلىنىشىنى كۆرسىتىدۇ + Animated Emojis + ئىشلەتكىلى بولىدىغان emoji نى كارتون قىلىپ كۆرسىتىڭ + ئەسلى ھۆججەت قىسقۇچقا ساقلاڭ + تاللانغان ھۆججەت قىسقۇچنىڭ ئورنىغا يېڭى ھۆججەتلەرنى ئەسلى ھۆججەتنىڭ يېنىغا ساقلاڭ + Watermark PDF + ئىچكى ساقلىغۇچ يېتەرلىك ئەمەس + بۇ ئۈسكۈنىدە رەسىم بەك چوڭ بولۇپ كېتىشى مۇمكىن ، ياكى سىستېمىنىڭ ئىچكى ساقلىغۇچلىرى تۈگەپ كەتتى. رەسىمنىڭ ئېنىقلىق دەرىجىسىنى تۆۋەنلىتىش ، باشقا ئەپلەرنى تاقاش ياكى كىچىكرەك ھۆججەتنى تاللاڭ. + تىزىملىك ​​تىزىملىكى + ئەپ ئىقتىدارلىرىنى سىنايدىغان تىزىملىك ​​، بۇ مەھسۇلات ئېلان قىلىشتا كۆرسىتىشنى مەقسەت قىلمايدۇ + تەمىنلىگۈچى + PaddleOCR ئۈسكۈنىڭىزدە قوشۇمچە ONNX مودېللىرىنى تەلەپ قىلىدۇ. %1$s سانلىق مەلۇماتنى چۈشۈرمەكچىمۇ؟ + Universal + كورېيە + لاتىنچە + East Slavic + تايلاند + گرېتسىيە + ئىنگىلىزچە + Cyrillic + ئەرەبچە + Devanagari + تامىل + Telugu + Shader + Shader preset + سايە تاللانمىدى + Shader Studio + خاس بۆلەك سايە قۇرۇش ، تەھرىرلەش ، دەلىللەش ، ئىمپورتلاش ۋە ئېكسپورت قىلىش + ساقلانغان سايە + ساقلانغان سايەلەرنى ئېچىڭ ، كۆپەيتىڭ ، ئېكسپورت قىلىڭ ، ھەمبەھىرلەڭ ياكى ئۆچۈرۈڭ + يېڭى سايە + Shader ھۆججىتى + .itshader JSON + %1$d پارام + Shader source + پەقەت قۇرۇق ئاساسىي () نىلا يېزىڭ. ئۇلترا بىنەپشە نۇر كوئوردېناتى ۋە inputImageTexture نى مەنبە ئەۋرىشكە سۈپىتىدە ئىشلىتىڭ. + ئىقتىدارلىرى + ئىختىيارى ياردەمچى فۇنكسىيەسى ، تۇراقلىقلىقى ۋە قۇرلىرى قۇرۇق ئاساسىي () دىن بۇرۇن قىستۇرۇلغان. فورما پارامېتىردىن ھاسىل بولىدۇ. + سايە تەھرىرلىگىلى بولىدىغان قىممەتكە ئېھتىياجلىق بولغاندا بۇ يەرگە فورما قوشۇڭ. + سايە ئەسلىگە قايتۇرۇش + نۆۋەتتىكى سايە لايىھىسى تازىلىنىدۇ + ئىناۋەتسىز سايە + قوللىمايدىغان سايە نۇسخىسى %1$d. قوللايدىغان نۇسخىسى: %2$d. + سايە ئىسمى بوش بولماسلىقى كېرەك. + سايە مەنبەسى بوش بولماسلىقى كېرەك. + Shader مەنبە قوللىمايدىغان ھەرپلەرنى ئۆز ئىچىگە ئالىدۇ: %1$s. پەقەت ASCII GLSL مەنبەسىنى ئىشلىتىڭ. + "پارامېتىر \"__ PH_0 __ \\\" بىر نەچچە قېتىم ئېلان قىلىندى." + پارامېتىر ئىسىملىرى بوش بولماسلىقى كېرەك. + "پارامېتىر \"__ PH_0 __ \\\" زاپاس GPUImage نامىنى ئىشلىتىدۇ." + "پارامېتىر \"__ PH_0 __ \\\" چوقۇم ئۈنۈملۈك GLSL پەرقلىگۈچى بولۇشى كېرەك." + "پارامېتىر \"__ PH_0 __ \\\" %2$s قىممەت تىپى \"__ PH_2 __ \\\" بار ، مۆلچەردىكى \"__ PH_3 __ \\\"." + "پارامېتىر \"__ PH_0 __ \\\" بولاق قىممىتى ئۈچۈن min ياكى max نى بەلگىلىيەلمەيدۇ." + "پارامېتىر \"__ PH_0 __ \\\" نىڭ ئەڭ چوڭ چېكى ئەڭ چوڭ." + "پارامېتىر \"__ PH_0 __ \\\" سۈكۈتتىكى قىممىتى min دىن تۆۋەن." + "پارامېتىر \"__ PH_0 __ \\\" سۈكۈتتىكىسى ئەڭ چوڭ." + "Shader چوقۇم \"بىرلىككە كەلگەن sampler2D __PH_0 __; \\\" نى ئېلان قىلىشى كېرەك." + "Shader چوقۇم \"ئوخشىمىغان vec2 __PH_0 __; \\\" نى ئېلان قىلىشى كېرەك." + "Shader چوقۇم \"void main () \\\" نى ئېنىقلىشى كېرەك." + Shader چوقۇم gl_FragColor غا رەڭ يېزىشى كېرەك. + ShaderToy mainImage سايە قوللىمايدۇ. GPUImage نىڭ بىكار ئاساسلىق () توختامىنى ئىشلىتىڭ. + "Animated ShaderToy فورماتى \"iTime \\\" نى قوللىمايدۇ." + "Animated ShaderToy فورماتى \"iFrame \\\" نى قوللىمايدۇ." + "ShaderToy فورماتى \"iResolution \\\" نى قوللىمايدۇ." + ShaderToy iChannel تېكىستلىرىنى قوللىمايدۇ. + سىرتقى تېكىستلەرنى قوللىمايدۇ. + سىرتقى توقۇلمىلارنى كېڭەيتىشنى قوللىمايدۇ. + Libretro سايە پارامېتىرلىرىنى قوللىمايدۇ. + "پەقەت بىرلا كىرگۈزۈش قۇرۇلمىسىنى قوللايدۇ. \"بىرلىككە كەلگەن %1$s __PH_1 __; \\\" نى ئېلىڭ." + "پارامېتىر \"__ PH_0 __ \\\" چوقۇم \"بىردەك %2$s __PH_2 __; \\\" دەپ ئېلان قىلىنىشى كېرەك." + "پارامېتىر \"__ PH_0 __ \\\" \"بىردەك %2$s __PH_2 __; \\\" ، مۆلچەرلەنگەن \"بىردەك %3$s __PH_4 __; \\\" دەپ ئېلان قىلىندى." + Shader ھۆججىتى قۇرۇق. + Shader ھۆججىتى چوقۇم نەشرى ، ئىسمى ۋە سايە مەيدانى بار .itshader JSON ئوبيېكتى بولۇشى كېرەك. + Shader ھۆججىتى ئىناۋەتلىك JSON ئەمەس ياكى .itshader فورماتىغا ماس كەلمەيدۇ. + "مەيدان \"__ PH_0 __ \\\" تەلەپ قىلىنىدۇ." + %1$s تەلەپ قىلىنىدۇ. + "%1$s \"__ PH_1 __ \\\" قوللىمايدۇ. قوللايدىغان تىپلار: %3$s." + " \"__ PH_1 __ \\\" پارامېتىرلىرى ئۈچۈن %1$s تەلەپ قىلىنىدۇ." + %1$s چوقۇم چەكلىك سان بولۇشى كېرەك. + %1$s چوقۇم پۈتۈن سان بولۇشى كېرەك. + %1$s چوقۇم راست ياكى يالغان بولۇشى كېرەك. + %1$s چوقۇم رەڭلىك تىزما ، RGB / RGBA ساندۇقى ياكى رەڭ ئوبيېكتى بولۇشى كېرەك. + %1$s چوقۇم #RRGGBB ياكى #RRGGBBAA فورماتىنى ئىشلىتىشى كېرەك. + %1$s چوقۇم ئۈنۈملۈك ئالتە تەرەپلىك رەڭلىك قانال بولۇشى كېرەك. + %1$s چوقۇم 3 ياكى 4 رەڭلىك قانال بولۇشى كېرەك. + %1$s چوقۇم 0 دىن 255 گىچە بولۇشى كېرەك. + %1$s چوقۇم ئىككى خانىلىق سانلار گۇرپىسى ياكى x ۋە y بولغان جىسىم بولۇشى كېرەك. + %1$s چوقۇم 2 سان بولۇشى كېرەك. + كۆپەيتىلگەن + ھەمىشە EXIF ​​نى تازىلاڭ + قورال مېتا سانلىق مەلۇمات ساقلاشنى تەلەپ قىلسىمۇ ، ساقلاشتىكى رەسىم EXIF ​​سانلىق مەلۇماتلىرىنى ئۆچۈرۈڭ + ياردەم ۋە كۆرسەتمىلەر + %1$d دەرسلىكى + ئوچۇق قورال + ئاساسلىق قوراللار ، ئېكسپورت تاللانمىلىرى ، PDF ئېقىمى ، رەڭ ئەسلىھەلىرى ۋە كۆپ كۆرۈلىدىغان مەسىلىلەرنى ھەل قىلىشنى ئۆگىنىۋېلىڭ + باشلاش + قوراللارنى تاللاڭ ، ھۆججەتلەرنى ئەكىرىڭ ، نەتىجىنى ساقلاڭ ۋە تەڭشەكلەرنى تېز ئىشلىتىڭ + رەسىم تەھرىرلەش + رەسىملەرنى چوڭايتىش ، تېرىش ، سۈزۈش ، تەگلىكنى ئۆچۈرۈش ، سىزىش ۋە سۇ بەلگىسى رەسىملىرىنى + ھۆججەت ۋە مېتا سانلىق مەلۇمات + فورماتنى ئۆزگەرتىش ، چىقىرىشنى سېلىشتۇرۇش ، مەخپىيەتلىكنى قوغداش ۋە ھۆججەت نامىنى كونترول قىلىش + PDF ۋە ھۆججەتلەر + PDF قۇرۇش ، ھۆججەتلەرنى سايىلەش ، OCR بېتىنى قۇرۇش ۋە ئورتاقلىشىش ئۈچۈن ھۆججەت تەييارلاش + تېكىست ، QR ۋە سانلىق مەلۇمات + تېكىست ، سىكانېرلاش كودى ، Base64 نى كودلاش ، ئالدىراش ۋە ئورالما ھۆججىتىنى تونۇش + رەڭ قوراللىرى + رەڭ تاللاڭ ، پالتا ياساڭ ، رەڭلىك كۈتۈپخانىلارنى تەتقىق قىلىڭ ۋە رېشاتكا ھاسىل قىلىڭ + ئىجادىي قوراللار + SVG ، ASCII سەنئىتى ، شاۋقۇن تېكىستلىرى ، سايە ۋە تەجرىبە كۆرۈنۈشلىرىنى ھاسىل قىلىڭ + كاشىلا + ئېغىر ھۆججەت ، ئېنىقلىق ، ئورتاقلىشىش ، ئىمپورت قىلىش ۋە دوكلات قىلىش مەسىلىلىرىنى تۈزىتىڭ + مۇۋاپىق قورالنى تاللاڭ + كاتېگورىيە ، ئىزدەش ، ياقتۇرىدىغان ۋە ئورتاق بەھرىلىنىدىغان ھەرىكەتلەرنى ئىشلىتىپ جايىدا باشلاڭ + ئەڭ ياخشى كىرىش نۇقتىسىدىن باشلاڭ + رەسىم قورال ساندۇقىدا نۇرغۇنلىغان فوكۇسلانغان قوراللار بار ، نۇرغۇنلىرى بىر قاراشتىلا بىر-بىرىنى قاپلايدۇ. تاماملىماقچى بولغان ۋەزىپىڭىزدىن باشلاڭ ، ئاندىن نەتىجىنىڭ ئەڭ مۇھىم قىسمىنى كونترول قىلىدىغان قورالنى تاللاڭ: چوڭلۇقى ، فورماتى ، مېتا سانلىق مەلۇمات ، تېكىست ، PDF بەتلىرى ، رەڭلىرى ياكى ئورۇنلاشتۇرۇلۇشى. + چوڭ-كىچىكلىكى ، PDF ، EXIF ​​، OCR ، QR ياكى رەڭ دېگەندەك ھەرىكەت نامىنى بىلگەندە ئىزدەڭ. + ۋەزىپە تۈرىنىلا بىلگەندە بىر سەھىپە ئېچىڭ ، ئاندىن دائىم ئىشلىتىدىغان قوراللارنى ياخشى كۆرۈڭ. + باشقا بىر ئەپ رەسىم قورال ساندۇقىغا رەسىم ھەمبەھىرلىگەندە ، ئورتاقلىشىش ئېقىمىدىن قورالنى تاللاڭ. + نەتىجىنى ئەكىرىش ، ساقلاش ۋە ئورتاقلىشىش + كىرگۈزۈشنى تاللاشتىن تەھرىرلەنگەن ھۆججەتنى چىقىرىشقىچە بولغان ئادەتتىكى ئېقىمنى چۈشىنىڭ + ئورتاق تەھرىرلەش ئېقىمىغا ئەگىشىڭ + كۆپىنچە قوراللار ئوخشاش رېتىمغا ئەگىشىدۇ: كىرگۈزۈشنى تاللاش ، تاللاشلارنى تەڭشەش ، ئالدىن كۆرۈش ، ئاندىن ساقلاش ياكى ئورتاقلىشىش. مۇھىم تەپسىلات شۇكى ، تېجەش چىقىرىش ھۆججىتى ھاسىل قىلىدۇ ، ھالبۇكى ھەمبەھىرلىنىش ئادەتتە باشقا بىر ئەپكە تېزرەك تاپشۇرۇپ بېرىش ئۈچۈن ئەڭ ياخشى. + تاللىغۇچ رۇخسەت قىلغاندا بىر رەسىملىك ​​قورال ياكى بىر تۈركۈم ھۆججەتلەر ئۈچۈن بىر ھۆججەت تاللاڭ. + ساقلاشتىن بۇرۇن ئالدىن كۆرۈش ۋە چىقىرىش كونتروللىرىنى تەكشۈرۈڭ ، بولۇپمۇ فورمات ، سۈپەت ۋە ھۆججەت ئىسمى تاللانمىلىرى. + تېز ھەمبەھىرلىنىش ئۈچۈن ھەمبەھىرنى ئىشلىتىڭ ياكى تاللانغان ھۆججەت قىسقۇچتا ئۈزلۈكسىز كۆپەيتىلگەن نۇسخىغا ئېھتىياجلىق بولغاندا تېجەڭ. + بىرلا ۋاقىتتا نۇرغۇن رەسىملەر بىلەن ئىشلەڭ + يۈرۈشلۈك قوراللار قايتا-قايتا چوڭايتىش ، ئۆزگەرتىش ، پىرىسلاش ۋە ئىسىم قويۇش ۋەزىپىلىرىگە ماس كېلىدۇ + ئالدىن پەرەز قىلغىلى بولىدىغان بىر تۈركۈم تەييارلىق قىلىڭ + ھەر بىر مەھسۇلات ئوخشاش قائىدىگە ئەمەل قىلغاندا ، تۈركۈملەپ پىششىقلاش ئەڭ تېز. ئۇ يەنە چوڭ خاتالىق سادىر قىلىدىغان ئەڭ ئاسان جاي ، شۇڭا ئۇزۇن ئېكسپورتنى باشلاشتىن بۇرۇن ئىسىم قويۇش ، سۈپەت ، مېتا سانلىق مەلۇمات ۋە ھۆججەت قىسقۇچ ھەرىكىتىنى تاللاڭ. + بارلىق مۇناسىۋەتلىك رەسىملەرنى بىرلىكتە تاللاڭ ، ئەگەر چىقىرىش تەرتىپىگە باغلىق بولسا تەرتىپنى ساقلاڭ. + ئېكسپورتنى باشلاشتىن بۇرۇن چوڭ-كىچىكلىكى ، فورماتى ، سۈپىتى ، مېتا سانلىق مەلۇمات ۋە ھۆججەت ئىسمى قائىدىسىنى بىر قېتىم بەلگىلەڭ. + مەنبە ھۆججىتى چوڭ بولغاندا ياكى نىشان فورماتى سىز ئۈچۈن يېڭى بولغاندا ئالدى بىلەن كىچىك سىناق گۇرۇپپىسىنى ئىجرا قىلىڭ. + تېز تەھرىرلەش + بىر رەسىمدە بىر قانچە ئاددىي ئۆزگەرتىشكە ئېھتىياجلىق بولغاندا ھەممىنى بىر تەھرىرلىگۈچنى ئىشلىتىڭ + بىر رەسىمنى قورال ئۈزمەيلا تاماملاڭ + يەككە تەھرىرلەش رەسىم بىر ئالاھىدە مەشغۇلاتقا ئەمەس ، بەلكى كىچىك زەنجىرسىمان ئۆزگەرتىشكە ئېھتىياجلىق بولغاندا پايدىلىق. بىر تۈركۈم خىزمەت ئېقىمى قۇرماي تۇرۇپ ئەڭ ئاخىرقى كۆپەيتىلگەن نۇسخىسىنى تېز تازىلاش ، ئالدىن كۆرۈش ۋە ئېكسپورت قىلىش ئادەتتە تېز بولىدۇ. + ئورتاق تەڭشەش ، بەلگە ، زىرائەت ، ئايلىنىش ياكى ئېكسپورت ئۆزگەرتىشكە ئېھتىياجلىق بولغان بىر رەسىم ئۈچۈن يەككە تەھرىرلەشنى ئېچىڭ. + ئالدى بىلەن ئەڭ ئاز پىكسېلنى ئۆزگەرتىدىغان تەرتىپ بويىچە تەھرىرلەڭ ، مەسىلەن سۈزۈشتىن بۇرۇنقى زىرائەت ۋە ئاخىرقى پىرىسلاشتىن بۇرۇن سۈزگۈچ دېگەندەك. + تۈركۈملەپ پىششىقلاپ ئىشلەش ، ھۆججەت چوڭلۇقىدىكى نىشان ، PDF چىقىرىش ، OCR ياكى مېتا سانلىق مەلۇمات تازىلاشقا ئېھتىياجلىق بولغاندا ، ئۇنىڭ ئورنىغا مەخسۇس قورال ئىشلىتىڭ. + تەڭشەك ۋە تەڭشەكلەرنى ئىشلىتىڭ + قايتا-قايتا تاللاشنى تېجەڭ ۋە ياقتۇرىدىغان خىزمەت ئېقىمى ئۈچۈن ئەپنى تەڭشەڭ + ئوخشاش تەڭشەكنى تەكرارلاشتىن ساقلىنىڭ + ئوخشاش تۈردىكى ھۆججەتلەرنى دائىم ئېكسپورت قىلغاندا ئالدىن بەلگىلەش ۋە تەڭشەكلەر پايدىلىق. ياخشى ئالدىنئالا قايتا-قايتا قولدا تەكشۈرۈش تىزىملىكىنى بىر چېكىشكە ئايلاندۇرىدۇ ، يەرشارى تەڭشەكلىرى يېڭى قوراللارنىڭ باشلىنىدىغان سۈكۈتتىكى ھالىتىنى بەلگىلەيدۇ. + ئورتاق چىقىرىش چوڭلۇقى ، فورماتى ، سۈپەت قىممىتى ياكى ئىسىم قويۇش ئەندىزىسىگە ئالدىن بەلگىلەڭ. + سۈكۈتتىكى فورمات ، سۈپەت ، ھۆججەت قىسقۇچ ، ھۆججەت ئىسمى ، تاللىغۇچ ۋە كۆرۈنمە يۈزى ھەرىكىتىنى تەڭشەش. + ئەپنى قايتا قاچىلاش ياكى باشقا ئۈسكۈنىگە يۆتكىلىشتىن بۇرۇن تەڭشەكلەرنى زاپاسلاڭ. + پايدىلىق سۈكۈتنى تەڭشەڭ + سۈكۈتتىكى فورمات ، سۈپەت ، كۆلەم ھالىتى ، تىپنىڭ چوڭ-كىچىكلىكى ۋە رەڭ بوشلۇقىنى بىر قېتىم تاللاڭ + يېڭى قوراللارنى نىشانىڭىزغا يېقىنلاشتۇرۇڭ + سۈكۈتتىكى قىممەت گىرىم بويۇملىرىلا ئەمەس. ئۇلار نۇرغۇن قوراللاردا قايسى خىل فورمات ، سۈپەت ، چوڭلۇقتىكى ھەرىكەت ، كۆلەم ھالىتى ۋە رەڭ بوشلۇقىنىڭ بىرىنچى ئورۇندا تۇرىدىغانلىقىنى بەلگىلەيدۇ ، بۇ ھەر بىر ئېكسپورتتىكى ئوخشاش تاللاشنى قايتا تاللاشتىن ساقلايدۇ. + سۈكۈتتىكى رەسىم فورماتى ۋە سۈپىتىنى ئادەتتىكى مەنزىلىڭىزگە ماسلاشتۇرۇڭ ، مەسىلەن رەسىمدىكى JPEG ياكى ئالفا ئۈچۈن PNG / WebP. + ئەگەر سىز دائىم قاتتىق ئۆلچەم ، ئىجتىمائىي سۇپا ياكى ھۆججەت بېتىگە ئېكسپورت قىلسىڭىز ، سۈكۈتتىكى چوڭ-كىچىكلىكىنى تەڭشەڭ. + خىزمەت ئېقىمى كۆرسىتىش ، بېسىش ياكى سىرتقى تەھرىرلىگۈچتە ئىزچىل رەڭ بىر تەرەپ قىلىشقا ئېھتىياجلىق بولغاندا ، رەڭ بوشلۇقى سۈكۈتتىكى ھالىتىنى ئۆزگەرتىڭ. + تاللىغۇچ ۋە قويۇپ بەرگۈچى + ئەپ بەك كۆپ دىئالوگ ئاچقاندا ياكى خاتا جايدا باشلىغاندا تاللىغۇچ تەڭشىكىنى ئىشلىتىڭ + ئېچىش ھۆججىتىنى ئادىتىڭىزگە ماسلاشتۇرۇڭ + تاللىغۇچ ۋە قوزغىتىش تەڭشىكى رەسىم قورال ساندۇقىنىڭ ئاساسلىق ئېكران ، قورال ياكى ھۆججەت تاللاش ئېقىمىدىن باشلىنىدىغانلىقىنى كونترول قىلىدۇ. بۇ تاللاشلار ئادەتتە ئاندىرويىد ھەمبەھىر جەدۋىلىدىن كىرگەندە ياكى ئەپنىڭ قورال قوزغاتقۇچتەك ھېس قىلىشىنى ئۈمىد قىلسىڭىز تېخىمۇ پايدىلىق. + ئەگەر سىستېما تاللىغۇچ ھۆججەتلەرنى يوشۇرسا ، يېقىنقى نۇرغۇن نەرسىلەرنى كۆرسەتسە ياكى بۇلۇت ھۆججىتىنى ياخشى بىر تەرەپ قىلمىسا ، رەسىم تاللىغۇچ تەڭشىكىنى ئىشلىتىڭ. + ئادەتتە ئورتاقلىشىشتىن كىرگەن ياكى ئەسلى ھۆججەتنى بىلىدىغان قوراللار ئۈچۈنلا ئاتلاشنى تاللاڭ. + ئەگەر رەسىم قورال ساندۇقىنىڭ نورمال ئائىلە ئېكرانىغا قارىغاندا قورال تاللىغۇچقا ئوخشاش ھەرىكەت قىلىشىنى ئۈمىد قىلسىڭىز ، قوزغىتىش ھالىتىنى تەكشۈرۈڭ. + رەسىم مەنبەسى + رەسىمخانا ، ھۆججەت باشقۇرغۇچى ۋە بۇلۇت ھۆججىتى بىلەن ئەڭ ياخشى ماس كېلىدىغان تاللاش ھالىتىنى تاللاڭ + ھۆججەتلەرنى توغرا مەنبەدىن تاللاڭ + رەسىم مەنبە تەڭشىكى بۇ دېتالنىڭ ئاندىرويىدتىن رەسىم تەلەپ قىلىشىغا تەسىر كۆرسىتىدۇ. ئەڭ ياخشى تاللاش ھۆججەتلىرىڭىزنىڭ سىستېما كارىدورىدا ، ھۆججەت باشقۇرغۇچ ھۆججەت قىسقۇچىدا ، بۇلۇت تەمىنلىگۈچىدە ياكى ۋاقىتلىق مەزمۇننى ئورتاقلىشىدىغان باشقا ئەپتە ياشايدىغانلىقىغا باغلىق. + ئادەتتىكى رەسىم رەسىملىرى ئۈچۈن ئەڭ كۆپ سىستېما بىرلەشتۈرۈلگەن تەجرىبىگە ئېھتىياجلىق بولغاندا ، كۆڭۈلدىكى تاللىغۇچنى ئىشلىتىڭ. + ئەگەر سىز ئويلىغان يەردە پىلاستىنكا ، ھۆججەت قىسقۇچ ، بۇلۇت ھۆججىتى ياكى ئۆلچەمسىز فورمات كۆرۈنمىسە ، تاللاش ھالىتىنى ئالماشتۇرۇڭ. + ئەگەر ئورتاق بەھرىلىنىدىغان ھۆججەت مەنبە دېتالىدىن ئايرىلغاندىن كېيىن يوقاپ كەتسە ، ئۇنى ئۈزلۈكسىز تاللىغۇچ ئارقىلىق قايتا ئېچىڭ ياكى ئالدى بىلەن يەرلىك نۇسخىسىنى ساقلاڭ. + ياقتۇرىدىغان قوراللار + ئاساسلىق ئېكراننى مەركەزلەشتۈرۈپ ، ھەمبەھىر ئېقىمىدا ھېچقانداق ئەھمىيىتى يوق قوراللارنى يوشۇرۇڭ + قورال تىزىملىكى شاۋقۇنىنى ئازايتىش + ياقتۇرىدىغان ۋە ئورتاق بەھرىلىنىدىغان تەڭشەكلەر ھەر كۈنى پەقەت بىر قانچە قورالنى ئىشلەتسىڭىز پايدىلىق. ئۇلار يەنە باشقا ئەپ ئەۋەتكەن ھۆججەتنىڭ تۈرىنى ئىشلىتەلمەيدىغان قوراللارنى يوشۇرۇش ئارقىلىق ھەمبەھىر ئېقىمىنى ئەمەلىي ساقلايدۇ. + دائىم ئىشلىتىدىغان قوراللارنى سانجىڭ ، شۇڭا قوراللار تۈرلەرگە ئايرىلسىمۇ ئاسان ئېرىشەلەيدۇ. + قوراللارنى باشقا ئەپتىن كەلگەن ھۆججەتلەر بىلەن مۇناسىۋەتسىز بولغاندا ئورتاقلىشىش ئېقىمىدىن يوشۇرۇڭ. + ئاخىرىدا ياقتۇرىدىغان ياكى مۇناسىۋەتلىك قوراللار بىلەن گۇرۇپپىلاشسىڭىز ، ياقتۇرىدىغان زاكاز تەڭشەكلىرىنى ئىشلىتىڭ. + تەڭشەكلەرنى زاپاسلاڭ + ئالدىن قاچىلاش ، مايىللىق ۋە ئەپ ھەرىكىتىنى باشقا قاچىلاشقا بىخەتەر يۆتكەڭ + تەڭشەكلىرىڭىزنى ئېلىپ يۈرۈشكە ئەپلىك + زاپاسلاش ۋە ئەسلىگە كەلتۈرۈش ئالدىن بەلگىلەش ، ھۆججەت قىسقۇچ ، ھۆججەت ئىسمى قائىدىسى ، قورال تەرتىپى ۋە كۆرۈنۈش مايىللىقىنى تەڭشىگەندىن كېيىن ئەڭ پايدىلىق. زاپاسلاش ئارقىلىق خىزمەت ئېقىمىڭىزنى ئەستە ساقلىماي تۇرۇپ تەڭشەكلەرنى سىناق قىلالايسىز ياكى ئەپنى قايتا قاچىلىيالايسىز. + ئالدىن بەلگىلەش ، ھۆججەت ئىسمى ھەرىكىتى ، سۈكۈتتىكى فورماتى ياكى قورال ئورۇنلاشتۇرۇشىنى ئۆزگەرتكەندىن كېيىن زاپاسلاڭ. + ئەگەر سانلىق مەلۇماتلارنى تازىلاش ، قايتا قاچىلاش ياكى ئۈسكۈنىلەرنى يۆتكىمەكچى بولسىڭىز ، زاپاسلاشنى ئەپ قىسقۇچىنىڭ سىرتىدا ساقلاڭ. + مۇھىم تۈركۈملەرنى بىر تەرەپ قىلىشتىن بۇرۇن تەڭشەكلەرنى ئەسلىگە كەلتۈرۈڭ ، سۈكۈتتىكى ھالەتتە ساقلاڭ ھەمدە ھەرىكەتنى كونا تەڭشىكىڭىزگە ماسلاشتۇرۇڭ. + رەسىمنىڭ چوڭ-كىچىكلىكىنى ئۆزگەرتىڭ + بىر ياكى نۇرغۇن رەسىملەرنىڭ چوڭ-كىچىكلىكى ، فورماتى ، سۈپىتى ۋە مېتا سانلىق مەلۇماتلىرىنى ئۆزگەرتىڭ + چوڭ-كىچىكلىكى كۆپەيتىلگەن + رازمېرى ۋە ئايلاندۇرۇش ئالدىن مۆلچەرلىگىلى بولىدىغان ئۆلچەم ۋە يېنىك چىقىرىش ھۆججىتىنىڭ ئاساسلىق قورالى. رەسىمنىڭ چوڭ-كىچىكلىكى ، چىقىرىش شەكلى ۋە مېتا سانلىق مەلۇمات ھەرىكىتىنىڭ ھەممىسى بىرلا ۋاقىتتا ئىشلىتىڭ. + بىر ياكى بىر نەچچە رەسىمنى تاللاڭ ، ئاندىن چوڭ-كىچىكلىكى ، چوڭلۇقى ياكى ھۆججەت چوڭلۇقىنىڭ مۇھىملىقىنى تاللاڭ. + چىقىرىش شەكلى ۋە سۈپىتىنى تاللاڭ; سۈزۈكلۈكنى ساقلاشتا PNG ياكى WebP نى ئىشلىتىڭ. + نەتىجىنى ئالدىن كۆرۈڭ ، ئاندىن ئەسلى نۇسخىسىنى ئۆزگەرتمەي ھاسىل قىلىنغان كۆپەيتىلگەن نۇسخىسىنى ساقلاڭ ياكى ئورتاقلىشىڭ. + ھۆججەت چوڭلۇقى بويىچە چوڭايتىڭ + تور بېكەت ، خەۋەرچى ياكى شەكىل ئېغىر رەسىملەرنى رەت قىلغاندا چوڭلۇق چەكلىمىسىنى نىشان قىلىڭ + يۈكلەش چەكلىمىسىگە ماس كېلىدۇ + مەنزىل پەقەت مەلۇم چەكتىن تۆۋەن ھۆججەتلەرنى قوبۇل قىلغاندا ، ھۆججەتنىڭ چوڭ-كىچىكلىكىنى چوڭايتىش سۈپەت قىممىتىنى پەرەز قىلىشتىن ياخشى. بۇ قورال پىرىسلاش ۋە ئۆلچەشنى تەڭشەپ ، نەتىجىنى ئىشلىتىشكە ئۇرۇنغاندا نىشانغا يېتىشى مۇمكىن. + نىشاننىڭ چوڭ-كىچىكلىكىنى ھەقىقىي يۈكلەش چېكىدىن ئازراق كىرگۈزۈڭ ، مېتا سانلىق مەلۇمات ۋە تەمىنلىگۈچىنىڭ ئۆزگىرىشى ئۈچۈن بوشلۇق قالدۇرۇڭ. + نىشان كىچىك بولغاندا رەسىملەرنىڭ يوقاپ كەتكەن فورماتلىرىنى ياخشى كۆرۈڭ. PNG چوڭلۇقىنى ئۆزگەرتكەندىن كېيىنمۇ چوڭ ھالەتتە تۇرالايدۇ. + ئېكسپورت قىلىنغاندىن كېيىن يۈز ، تېكىست ۋە قىرلارنى تەكشۈرۈڭ ، چۈنكى تاجاۋۇزچىلىق چوڭلۇقى نىشانلىرى كۆرۈنەرلىك بۇيۇملارنى تونۇشتۇرالايدۇ. + چوڭ-كىچىكلىكىنى چەكلەڭ + ئەڭ چوڭ كەڭلىكى ، ئېگىزلىكى ياكى ھۆججەت چەكلىمىسىدىن ئېشىپ كەتكەن رەسىملەرنى كىچىكلىتىڭ + ئاللىقاچان ياخشى بولغان رەسىملەرنى چوڭايتىشتىن ساقلىنىڭ + چەكلىمىنىڭ چوڭ-كىچىكلىكى بەزى رەسىملەر يوغان ، بەزىلىرى ئاللىبۇرۇن تەييارلانغان ئارىلاش ھۆججەت قىسقۇچلارغا پايدىلىق. ھەر بىر ھۆججەتنى ئوخشاش چوڭلۇقتا زورلاشنىڭ ئورنىغا ، قوبۇل قىلغىلى بولىدىغان رەسىملەرنى ئەسلى سۈپىتىگە يېقىنلاشتۇرىدۇ. + ھەر بىر ھۆججەتنى قارىغۇلارچە چوڭايتماي ، مەنزىلگە ئاساسەن ئەڭ چوڭ ئۆلچەم ياكى چەك بەلگىلەڭ. + مەنبەدىن ئېغىرراق چىقىشنىڭ ئالدىنى ئالماقچى بولسىڭىز ، ئاتلاش-چوڭراق ئۇسلۇب ھەرىكىتىنى قوزغىتىڭ. + بۇنى ئوخشىمىغان كامېرا ، ئېكران رەسىمى ياكى چۈشۈرۈشنىڭ مەنبەسىنىڭ چوڭ-كىچىكلىكى ئوخشاش بولمىغان گۇرۇپپىلارغا ئىشلىتىڭ. + تېرىش ، ئايلاندۇرۇش ۋە تۈزلەش + رەسىمنى باشقا قوراللاردا چىقىرىش ياكى ئىشلىتىشتىن بۇرۇن مۇھىم رايوننى رامكا قىلىڭ + تەركىبىنى تازىلاڭ + تېرىش ئالدى بىلەن سۈزگۈچ ، OCR ، PDF بېتى ۋە ئورتاقلىشىش نەتىجىسىنى تېخىمۇ پاكىز قىلىدۇ. + Crop نى ئېچىپ تەڭشىمەكچى بولغان رەسىمنى تاللاڭ. + مەھسۇلات چوقۇم يازما ، ھۆججەت ياكى باش سۈرىتىگە ماس كەلگەندە ھەقسىز زىرائەت ياكى تەرەپ نىسبىتىنى ئىشلىتىڭ. + ساقلاشتىن بۇرۇن ئايلاندۇرۇش ياكى يۆتكەش ، شۇڭا كېيىنكى قوراللار تۈزىتىلگەن رەسىمنى تاپشۇرۇۋالىدۇ. + سۈزگۈچ ۋە تەڭشەشنى ئىشلىتىڭ + تەھرىرلىگىلى بولىدىغان سۈزگۈچ تاختىسىنى قۇرۇپ ، چىقىرىشتىن بۇرۇن نەتىجىنى ئالدىن كۆرۈڭ + رەسىمنىڭ كۆرۈنۈشىنى تەڭشەش + سۈزگۈچ تېز تۈزىتىش ، ئۇسلۇبتىكى چىقىرىش ۋە OCR ياكى بېسىپ چىقىرىش ئۈچۈن رەسىم تەييارلاشقا پايدىلىق. سېلىشتۇرما ، ئۆتكۈرلۈك ۋە يورۇقلۇققا بولغان كىچىك ئۆزگىرىشلەر رەسىم ياكى ئىنچىكە ھالقىلارنى ئۆز ئىچىگە ئالغاندا ئېغىر تەسىرلەردىن مۇھىم بولىدۇ. + سۈزگۈچلەرنى بىر-بىرلەپ قوشۇڭ ، ھەر قېتىملىق ئۆزگىرىشتىن كېيىن ئالدىن كۆزىتىشنى كۆزىتىڭ. + ۋەزىپىگە ئاساسەن يورۇقلۇق ، سېلىشتۇرما ، ئۆتكۈرلۈك ، تۇتۇق ، رەڭ ياكى ماسكىلارنى تەڭشەڭ. + ئالدىن كۆرۈش نىشانلىق ئۈسكۈنىڭىز ، ھۆججەت ياكى ئىجتىمائىي سۇپىڭىزغا ماس كەلگەندىلا چىقىرىڭ. + ئالدى بىلەن ئالدىن كۆرۈش + ئېغىرراق تەھرىرلەش ، ئۆزگەرتىش ياكى تازىلاش قورالىنى تاللاشتىن بۇرۇن رەسىملەرنى تەكشۈرۈڭ + تاپشۇرۇۋالغانلىرىڭىزنى تەكشۈرۈڭ + رەسىم ئالدىن كۆرۈش ھۆججەت پاراڭلىشىش ، بۇلۇت ساقلاش ياكى باشقا ئەپلەردىن كەلگەن ۋاقىتتا پايدىلىق بولۇپ ، ئۇنىڭ نېمە ئىكەنلىكىنى جەزملەشتۈرەلمەيسىز. ئۆلچەم ، سۈزۈكلۈك ، يۆنىلىش ۋە كۆرۈنۈش سۈپىتىنى تەكشۈرۈش ئالدى بىلەن توغرا ئىز قوغلاش قورالىنى تاللاشقا ياردەم بېرىدۇ. + ئۇلار بىلەن نېمە قىلىشنى قارار قىلىشتىن بۇرۇن بىر قانچە رەسىمنى تەكشۈرۈشكە توغرا كەلگەندە ، رەسىم ئالدىن كۆرۈشنى ئېچىڭ. + ئايلىنىش مەسىلىسى ، سۈزۈك رايونلار ، پىرىسلاش بۇيۇملىرى ۋە ئويلىمىغان يەردىن چوڭ رازمېرلارنى ئىزدەڭ. + چوڭايتىش ، تېرىش ، سېلىشتۇرۇش ، EXIF ​​ياكى تەگلىك ئۆچۈرۈشكە يۆتكەڭ ، ئوڭشاشقا ئېھتىياجلىق ئىكەنلىكىنى بىلگەندىن كېيىن. + تەگلىكنى ئۆچۈرۈڭ ياكى ئەسلىگە كەلتۈرۈڭ + تەگلىكنى ئاپتوماتىك ئۆچۈرۈڭ ياكى ئەسلىگە كەلتۈرۈش قوراللىرى بىلەن قىرلارنى قولدا ساپلاشتۇرۇڭ + تېمىنى ساقلاپ قويۇڭ ، قالغانلىرىنى ئېلىۋېتىڭ + تەگلىكنى ئۆچۈرۈش ئاپتوماتىك تاللاش بىلەن ئەستايىدىل قولدا تازىلاشنى بىرلەشتۈرگەندە ئەڭ ياخشى ئۈنۈم بېرىدۇ. ئاخىرقى ئېكسپورت شەكلى ماسكا بىلەن ئوخشاش مۇھىم ، چۈنكى JPEG ئالدىن كۆرۈش توغرا كۆرۈنسىمۇ سۈزۈك رايونلارنى تەكشى قىلىدۇ. + تېما ئارقا كۆرۈنۈشتىن ئېنىق ئايرىلسا ئاپتوماتىك ئۆچۈرۈشتىن باشلاڭ. + چوڭايتىپ ئۆچۈرۈڭ ۋە ئالماشتۇرۇڭ ، چاچ ، بۇلۇڭ ، سايە ۋە كىچىك تەپسىلاتلارنى ئوڭشاڭ. + ئاخىرقى ھۆججەتتە سۈزۈكلۈككە ئېھتىياجلىق بولغاندا PNG ياكى WebP قىلىپ ساقلاڭ. + سىزىش ، بەلگە قويۇش ۋە سۇ بەلگىسى + ئوق ، خاتىرە ، يارقىن نۇقتا ، ئىمزا ياكى قايتا-قايتا سۇ بەلگىسى قوشۇڭ + كۆرۈنگەن ئۇچۇرلارنى قوشۇڭ + بەلگە قوراللىرى رەسىمنى چۈشەندۈرۈشكە ياردەم بېرىدۇ ، سۇ بەلگىسى بولسا ماركا ياكى ئېكسپورت قىلىنغان ھۆججەتلەرنى قوغداشقا ياردەم بېرىدۇ. + ھەقسىز خاتىرە ، شەكىل ، يورۇتۇش ياكى تېز ئىزاھلاشقا ئېھتىياجلىق بولغاندا سىزىشنى ئىشلىتىڭ. + ئوخشاش بىر تېكىست ياكى رەسىم بەلگىسى چىقىش ئېغىزىدا بىردەك كۆرۈنگەندە سۇ بەلگىسىنى ئىشلىتىڭ. + ئېكسپورت قىلىشتىن بۇرۇن ئېنىقلىق ، ئورۇن ۋە كۆلەمنى تەكشۈرۈڭ ، بۇنداق بولغاندا بەلگە كۆرۈنىدۇ ، ئەمما دىققىتى چېچىلىپ كەتمەيدۇ. + سۈكۈتنى سىزىڭ + تېخىمۇ تېز بەلگە ئۈچۈن قۇر كەڭلىكى ، رەڭگى ، يول ھالىتى ، يۆنىلىش قۇلۇپى ۋە چوڭايتقۇچ بەلگىلەڭ + رەسىم سىزىشنى ئالدىن پەرەز قىلغىلى بولىدۇ + ئېكران كۆرۈنۈشلىرىنى ئىزاھلىغاندا ، ھۆججەتلەرگە بەلگە قويسىڭىز ياكى رەسىمگە دائىم ئىمزا قويسىڭىز ، تەڭشەك سىزىش پايدىلىق. سۈكۈتتىكى ھالەتتە تەڭشەش ۋاقتى قىسقارتىلىدۇ ، چوڭايتىش ۋە يۆنىلىش قۇلۇپى كىچىك ئېكرانلاردا ئېنىق تەھرىرلەشنى ئاسانلاشتۇرىدۇ. + سۈكۈتتىكى قۇر كەڭلىكىنى بەلگىلەڭ ھەمدە ئوق ، يارقىن نۇقتا ياكى ئىمزا ئۈچۈن ئەڭ كۆپ ئىشلىتىدىغان قىممەتلەرگە رەڭ سىزىڭ. + ئادەتتە ئوخشاش تۈردىكى سەكتە ، شەكىل ياكى بەلگە سىزغاندا سۈكۈتتىكى يول ھالىتىنى ئىشلىتىڭ. + بارماق كىرگۈزۈش كىچىك تەپسىلاتلارنى توغرا ئورۇنلاشتۇرۇشنى قىيىنلاشتۇرغاندا چوڭايتىش ياكى يۆنىلىش قۇلۇپىنى قوزغىتىڭ. + كۆپ رەسىملىك ​​ئورۇنلاشتۇرۇش + ئېكران رەسىمى ، سايىلەش ياكى سېلىشتۇرۇش بەلبېغىنى رەتلەشتىن بۇرۇن توغرا كۆپ رەسىملىك ​​قورالنى تاللاڭ + مۇۋاپىق ئورۇنلاشتۇرۇش قورالىنى ئىشلىتىڭ + بۇ قوراللار ئاڭلىماققا ئوخشايدۇ ، ئەمما ھەر بىرسى ئوخشىمىغان تىپتىكى كۆپ خىل رەسىم چىقىرىش ئۈچۈن تەڭشەلدى. توغرا تاللاش ئالدى بىلەن ئوخشىمىغان نەتىجە ئۈچۈن لايىھەلەنگەن ئورۇنلاشتۇرۇش كونترول قىلىش كونتروللۇقىدىن ساقلىنىدۇ. + بىر بۆلەكتە بىر قانچە رەسىم بىلەن لايىھەلەنگەن ئورۇنلاشتۇرۇش ئۈچۈن Collage Maker نى ئىشلىتىڭ. + رەسىملەر ئۇزۇن تىك ياكى توغرىسىغا توغرىلىنىدىغان ۋاقىتتا رەسىم تىكىش ياكى تىزىشنى ئىشلىتىڭ. + بىر چوڭ رەسىم بىر قانچە كىچىك پارچىغا ئايلىنىشقا توغرا كەلگەندە ، رەسىم بۆلۈشنى ئىشلىتىڭ. + رەسىم فورماتىنى ئۆزگەرتىڭ + پىكسېلنى قولدا تەھرىرلىمەي تۇرۇپ JPEG ، PNG ، WebP ، AVIF ، JXL ۋە باشقا نۇسخىلارنى قۇرۇڭ + خىزمەتنىڭ فورماتىنى تاللاڭ + ئوخشىمىغان فورمات رەسىم ، ئېكران رەسىمى ، سۈزۈكلۈك ، پىرىسلاش ياكى ماسلىشىشچانلىقى ئۈچۈن تېخىمۇ ياخشى. مەنزىل دېتالىنىڭ نېمىنى قوللايدىغانلىقىنى ۋە مەنبەدە سىز ساقلىماقچى بولغان ئالفا ، كارتون ياكى مېتا سانلىق مەلۇمات بار-يوقلۇقىنى چۈشەنگەندە ئۆزگەرتىش ئەڭ بىخەتەر. + ماس كېلىدىغان رەسىملەرگە JPEG ، زىيانسىز رەسىم ۋە ئالفا ئۈچۈن PNG ، ئىخچام ھەمبەھىرلىنىش ئۈچۈن WebP نى ئىشلىتىڭ. + فورمات ئۇنى قوللىغاندا سۈپەتنى تەڭشەڭ. تۆۋەن قىممەتلەر كىچىكلەيدۇ ، ئەمما ئاسارە-ئەتىقىلەرنى قوشالايدۇ. + نىشان ئەپتە ئۆزگەرتىلگەن ھۆججەتلەرنىڭ توغرا ئېچىلغانلىقىنى دەلىللىگۈچە ئەسلى نۇسخىسىنى ساقلاڭ. + EXIF ۋە مەخپىيەتلىكنى تەكشۈرۈڭ + سەزگۈر رەسىملەرنى ھەمبەھىرلەشتىن بۇرۇن مېتا سانلىق مەلۇماتنى كۆرۈش ، تەھرىرلەش ياكى ئۆچۈرۈش + يوشۇرۇن رەسىم سانلىق مەلۇماتلىرىنى كونترول قىلىڭ + EXIF كامېرا سانلىق مەلۇماتلىرى ، چېسلا ، ئورۇن ، يۆنىلىش ۋە رەسىمدە كۆرۈنمەيدىغان باشقا تەپسىلاتلارنى ئۆز ئىچىگە ئالىدۇ. ئاممىۋى ئورتاقلىشىش ، قوللاش دوكلاتى ، بازار تىزىملىكى ياكى شەخسىي ئورۇننى ئاشكارىلايدىغان سۈرەتلەرنى تەكشۈرۈشتىن بۇرۇن تەكشۈرۈشكە ئەرزىيدۇ. + ئورۇن ياكى شەخسىي ئۈسكۈنە ئۇچۇرلىرىنى ئۆز ئىچىگە ئالغان رەسىملەرنى ھەمبەھىرلەشتىن بۇرۇن EXIF ​​قوراللىرىنى ئېچىڭ. + سەزگۈر مەيدانلارنى ئۆچۈرۈڭ ياكى چېسلا ، يۆنىلىش ، ئاپتور ياكى كامېرا سانلىق مەلۇماتلىرى قاتارلىق خاتا بەلگىلەرنى تەھرىرلەڭ. + يېڭى كۆپەيتىلگەن نۇسخىنى ساقلاڭ ۋە مەخپىيەتلىك مۇھىم بولغاندا بۇ كۆپەيتىلگەن نۇسخىسىنى ئەسلى ئورنىغا ئالماشتۇرۇڭ. + ئاپتوماتىك EXIF ​​تازىلاش + چېسلانى ساقلاپ قېلىش-قالماسلىقنى قارار قىلىش جەريانىدا سۈكۈتتىكى مېتا سانلىق مەلۇماتنى ئۆچۈرۈڭ + مەخپىيەتلىكنى سۈكۈت قىلىڭ + EXIF تەڭشەك گۇرۇپپىسى مەخپىيەتلىكنى تازىلاشنىڭ ھەر بىر ئېكىسپورتتا ئەستە ساقلاشنىڭ ئورنىغا ئاپتوماتىك يۈز بېرىشىنى ئۈمىد قىلسىڭىز پايدىلىق. چېسلانى ساقلاش ئايرىم قارار ، چۈنكى چېسلا رەتلەشكە پايدىلىق ، ئەمما ھەمبەھىرلىنىشكە سەزگۈر. + ئېكسپورتىڭىزنىڭ كۆپىنچىسى ئاممىۋى ياكى يېرىم ئاممىۋى ئورتاقلىشىشنى مەقسەت قىلغاندا ، ھەمىشە ئېنىق بولغان EXIF ​​نى قوزغىتىڭ. + تۇتۇش ۋاقتىنى ساقلاش پەقەت ۋاقىت ۋاقتىنى ساقلاش ھەر بىر ۋاقىت تامغىسىنى چىقىرىۋەتكەندىنمۇ مۇھىم. + قولدا EXIF ​​تەھرىرلەشنى ئىشلىتىڭ ، بۇ يەردە بىر قىسىم ھۆججەتلەرنى ساقلاپ ، باشقىلارنى ئۆچۈرۈڭ. + ھۆججەت نامىنى كونترول قىلىڭ + ئالدى قوشۇلغۇچى ، قوشۇمچە ، ۋاقىت تامغىسى ، ئالدىن بەلگىلەش ، تەكشۈرۈش ۋە تەرتىپ نومۇرىنى ئىشلىتىڭ + ئېكسپورتنى ئاسان تاپقىلى بولىدۇ + ياخشى ھۆججەت ئىسمى ئەندىزىسى گۇرۇپپىلارنى رەتلەشكە ياردەم بېرىدۇ ھەمدە تاسادىپىي قاپلاپ كېتىشنىڭ ئالدىنى ئالىدۇ. ھۆججەت نامىنى تەڭشەش ئەسلى ھۆججەت قىسقۇچقا قايتقاندا ئالاھىدە پايدىلىق ، بۇ يەردىكى ئىسىملار تېزلا قالايمىقانلىشىپ كېتىدۇ. + ھۆججەت نامىنىڭ ئالدى قوشۇلغۇچىسى ، قوشۇمچىسى ، ۋاقىت تامغىسى ، ئەسلى ئىسمى ، ئالدىن بېكىتىلگەن ئۇچۇر ياكى تەرتىپ تاللانمىلىرىنى تەڭشەڭ. + تەرتىپ ، بەت ، رامكا ياكى قىستۇرما دېگەندەك مۇھىم بولغان گۇرۇپپىلارغا تەرتىپ نومۇرىنى ئىشلىتىڭ. + نۆۋەتتىكى ھۆججەتلەرنى ئالماشتۇرۇشنىڭ نۆۋەتتىكى ھۆججەت قىسقۇچ ئۈچۈن بىخەتەر ئىكەنلىكىنى جەزملەشتۈرگەندىلا ئاندىن قاپلاشنى قوزغىتىڭ. + ھۆججەتلەرنى قاپلىۋېتىڭ + خىزمەت ئېقىمىڭىز قەستەن بۇزۇلغاندا ئاندىن نەتىجىنى ماس ئىسىم بىلەن ئالماشتۇرۇڭ + قاپلاش ھالىتىنى ئەستايىدىل ئىشلىتىڭ + ھۆججەتلەرنى قاپلاش ئىسىم قويۇش توقۇنۇشىنىڭ قانداق بىر تەرەپ قىلىنىدىغانلىقىنى ئۆزگەرتىدۇ. ئوخشاش ھۆججەت قىسقۇچقا قايتا-قايتا ئېكسپورت قىلىشقا پايدىلىق ، ئەمما ھۆججەت ئىسمىڭىز ئوخشاش ئىسىمنى قايتا چىقارسا ، ئۇ ئىلگىرىكى نەتىجىنىڭ ئورنىنى ئالالايدۇ. + كونا ھۆججەتلەرنى ئالماشتۇرۇش ۋە ئەسلىگە كەلتۈرگىلى بولىدىغان ھۆججەت قىسقۇچلار ئۈچۈنلا قاپلاشنى قوزغىتىڭ. + ئەسلى ، خېرىدار ھۆججىتى ، بىر قېتىم سايىلەش ياكى زاپاسلىماي تۇرۇپ باشقا نەرسىلەرنى ئېكسپورت قىلغاندا چەكتىن ئاشۇرۇۋېتىڭ. + قاپلاشنى قەستەن ھۆججەت ئىسمى بىلەن بىرلەشتۈرۈڭ ، پەقەت كۆزلىگەن چىقىرىشنىڭلا ئورنىتىلىدۇ. + ھۆججەت ئىسمى + ئەسلى ئىسىم ، ئالدى قوشۇلغۇچى ، قوشۇمچە ، ۋاقىت تامغىسى ، ئالدىن بەلگىلەش ، كۆلەم ھالىتى ۋە تەكشۈرۈش جەدۋىلىنى بىرلەشتۈرۈڭ. + چىقىرىشنى چۈشەندۈرىدىغان ئىسىملارنى ياساڭ + ھۆججەت نامىنىڭ تەڭشىكى ئېكسپورت قىلىنغان ھۆججەتلەرنى ئۇلارغا يۈز بەرگەن ئىشلارنىڭ پايدىلىق تارىخىغا ئايلاندۇرالايدۇ. بۇ تۈركۈمدە مۇھىم ، چۈنكى ھۆججەت قىسقۇچ كۆرۈنۈشى سىز ئەسلىدىكى چوڭلۇق ، ئالدىن بەلگىلەش ، فورمات ۋە بىر تەرەپ قىلىش تەرتىپىنى پەرقلەندۈرەلەيدىغان بىردىنبىر جاي بولۇشى مۇمكىن. + قولدا رەتلەش ئۈچۈن ئوقۇشقا بولىدىغان ئىسىملارغا ئېھتىياجلىق بولغاندا ئەسلى ھۆججەت ئىسمى ، ئالدى قوشۇلغۇچىسى ، قوشۇمچىسى ۋە ۋاقىت تامغىسىنى ئىشلىتىڭ. + چىقىرىلغاندىن كېيىن ئالدىن بېكىتىلگەن ، كۆلەم ھالىتى ، ھۆججەت چوڭلۇقى ياكى تەكشۈرۈش ئۇچۇرى قوشۇڭ. + ھۆججەتلەرنىڭ ئەسلى ياكى بەت تەرتىپى بىلەن ماسلاشتۇرۇلۇشى كېرەك بولغان خىزمەت ئېقىمىنىڭ ئىختىيارى ئىسىملىرىدىن ساقلىنىڭ. + ھۆججەتلەرنىڭ قەيەردە ساقلانغانلىقىنى تاللاڭ + ھۆججەت قىسقۇچ ، ئەسلى ھۆججەت قىسقۇچنى ساقلاش ۋە بىر قېتىم ساقلاش ئورنى ئارقىلىق ئېكسپورتنى يوقىتىشتىن ساقلىنىڭ + نەتىجىنى ئالدىن پەرەز قىلىڭ + نۇرغۇن ھۆججەتلەرنى بىر تەرەپ قىلغاندا ياكى بۇلۇت تەمىنلىگۈچىلەردىن ھۆججەتلەرنى ھەمبەھىرلىگەندە ھەرىكەت مۇھىملىقىنى تېجەڭ. ھۆججەت قىسقۇچ تەڭشىكى چىقىرىشنىڭ مەلۇم بىر يەرگە توپلىنىشىنى ، ئەسلى يېنىدا كۆرۈنۈشنى ياكى مەلۇم بىر خىزمەت ئۈچۈن ۋاقىتلىق باشقا جايغا بېرىشنى قارار قىلىدۇ. + ئەگەر سىز دائىم بىر جايدا ئېكسپورت قىلماقچى بولسىڭىز ، كۆڭۈلدىكى ساقلاش قىسقۇچ ئورنىتىڭ. + ئەسلى خىزمەت ھۆججىتىنىڭ يېنىدا ساقلىنىدىغان خىزمەت ئېقىمىنى ساقلاش ئۈچۈن ئەسلىدىكى ھۆججەت قىسقۇچنى ئىشلىتىڭ. + سۈكۈتتىكى ھالىتىڭىزنى ئۆزگەرتمەي تۇرۇپ ، بىرلا ۋەزىپە باشقا مەنزىلگە ئېھتىياجلىق بولغاندا ، بىرلا ۋاقىتتا ساقلاش ئورنىنى ئىشلىتىڭ. + ھۆججەتلەرنى بىر قېتىم ساقلاش + كېيىنكى ئېكسپورتىڭىزنى نورمال مەنزىلىڭىزنى ئۆزگەرتمەي ۋاقىتلىق قىسقۇچقا ئەۋەتىڭ + ئالاھىدە خىزمەتلەرنى ئايرىم ساقلاڭ + بىر قېتىم تېجەش ئورنى ۋاقىتلىق رەسىم قورال ساندۇقى قىسقۇچ بىلەن ئارىلاشماسلىقى كېرەك بولغان ۋاقىتلىق تۈرلەر ، خېرىدارلار ھۆججەت قىسقۇچلىرى ، تازىلاش يىغىنلىرى ياكى ئېكسپورتلىرى ئۈچۈن پايدىلىق. سۈكۈتتىكى تەڭشىكىڭىزنى قايتا يازماي قىسقا مۇددەتلىك مەنزىل بېرىدۇ. + نورمال ئېكسپورت ئورنىڭىزنىڭ سىرتىدىكى ۋەزىپىنى باشلاشتىن بۇرۇن بىر قېتىم ھۆججەت قىسقۇچنى تاللاڭ. + ئۇنى قىسقا تۈرلەر ، ئورتاق ھۆججەت قىسقۇچلار ياكى تۈركۈملەر ئۈچۈن ئىشلىتىڭ ، بۇ يەردە چوقۇم بىر يەرگە جەم بولۇشى كېرەك. + خىزمەتتىن كېيىن سۈكۈتتىكى ھۆججەت قىسقۇچىغا قايتىڭ ، كېيىن ئېكسپورت ئويلىمىغان يەردىن ۋاقىتلىق ئورۇنغا چۈشمەيدۇ. + سۈپەت ۋە ھۆججەت چوڭلۇقى + پەرەز قىلىشنىڭ ئورنىغا ئۆلچەم ، فورمات ياكى سۈپەتنى قاچان ئۆزگەرتىدىغانلىقىنى چۈشىنىڭ + ھۆججەتلەرنى قەستەن كىچىكلىتىڭ + ھۆججەتنىڭ چوڭلۇقى رەسىمنىڭ چوڭ-كىچىكلىكى ، فورماتى ، سۈپىتى ، سۈزۈكلىكى ۋە مەزمۇننىڭ مۇرەككەپلىكىگە باغلىق. ئەگەر ھۆججەت يەنىلا بەك ئېغىر بولسا ، ئۆلچەم ئۆزگەرتىش ئادەتتە سۈپەتنى قايتا-قايتا تۆۋەنلىتىشكە ياردەم بېرىدۇ. + رەسىم مەنزىلدىن چوڭ بولغاندا ئالدى بىلەن چوڭ-كىچىكلىكىنى چوڭايتىڭ. + تۆۋەن سۈپەت پەقەت زىيانلىق فورماتلار ۋە ئېكسپورت قىلىنغاندىن كېيىن تېكىست ، گىرۋەك ، رېشاتكا ۋە يۈزلەرنى تەكشۈرۈڭ. + سۈپەت ئۆزگەرتىش يېتەرلىك بولمىغاندا فورماتنى ئالماشتۇرۇڭ ، مەسىلەن ھەمبەھىرلەش ئۈچۈن WebP ياكى سۈزۈك مۈلۈك ئۈچۈن PNG. + كارتون فورماتى بىلەن ئىشلەڭ + ھۆججەتنىڭ بىر bitmap ئورنىدا رامكا بولغاندا GIF ، APNG ، WebP ۋە JXL قوراللىرىنى ئىشلىتىڭ + ھەر بىر رەسىمگە يەنىلا مۇئامىلە قىلماڭ + كارتون فورماتى رامكا ، داۋاملىشىش ۋە ماسلىشىشچانلىقىنى چۈشىنىدىغان قوراللارغا موھتاج. + بۇ فورماتلار ئۈچۈن رامكىلىق مەشغۇلاتقا ئېھتىياجلىق بولغاندا GIF ياكى APNG قوراللىرىنى ئىشلىتىڭ. + زامانىۋى پىرىسلاش ياكى ھەرىكەتچان WebP چىقىرىشقا ئېھتىياجلىق بولغاندا WebP قوراللىرىنى ئىشلىتىڭ. + ھەمبەھىرلىنىشتىن بۇرۇن كارتون ۋاقتىنى ئالدىن كۆرۈڭ ، چۈنكى بەزى سۇپىلار جانلىق رەسىملەرنى ئايلاندۇرىدۇ ياكى تەكشى قىلىدۇ. + سېلىشتۇرۇش ۋە ئالدىن كۆرۈش + ئېكسپورتنى ساقلاشتىن بۇرۇن ئۆزگەرتىش ، ھۆججەتنىڭ چوڭ-كىچىكلىكى ۋە كۆرۈنۈش پەرقىنى تەكشۈرۈڭ + ئورتاقلىشىشتىن بۇرۇن تەكشۈرۈپ بېقىڭ + سېلىشتۇرۇش قوراللىرى پىرىسلاش بۇيۇملىرى ، خاتا رەڭلەر ياكى كېرەكسىز تەھرىرلەرنى بالدۇر تۇتۇشقا ياردەم بېرىدۇ. + ئىككى نەشرىنى بىرمۇبىر تەكشۈرمەكچى بولغاندا سېلىشتۇرۇڭ. + قىرغاق ، تېكىست ، رېشاتكا ۋە سۈزۈك رايونلارغا چوڭايتىپ ، مەسىلىلەر ئاسان قولدىن بېرىپ قويىدۇ. + ئەگەر چىقىرىش بەك ئېغىر ياكى تۇتۇق بولسا ، ئالدىنقى قورالغا قايتىپ ، سۈپەت ياكى فورماتنى تەڭشەڭ. + PDF قورال مەركىزىنى ئىشلىتىڭ + PDF ھۆججەتلىرىنى بىرلەشتۈرۈش ، بۆلۈش ، ئايلاندۇرۇش ، بەتلەرنى ئۆچۈرۈش ، پىرىسلاش ، قوغداش ، OCR ۋە سۇ بەلگىسى PDF ھۆججىتىنى بىرلەشتۈرۈش + PDF مەشغۇلاتىنى تاللاڭ + PDF تۈگۈنى گۇرۇپپىلىرى بىر كىرىش نۇقتىسىنىڭ ئارقىسىدىكى ھەرىكەتلەرنى خاتىرىلەيدۇ ، شۇنداق بولغاندا مەشغۇلاتنى تاللىيالايسىز. ھۆججەت ئاللىقاچان PDF بولغاندا شۇ يەردىن باشلاڭ ، مەنبەڭىز يەنىلا بىر يۈرۈش رەسىملەر بولغاندا رەسىملەرنى PDF ياكى ھۆججەت سايىلىغۇچقا ئىشلىتىڭ. + PDF قوراللىرىنى ئېچىڭ ھەمدە نىشانىڭىزغا ماس كېلىدىغان مەشغۇلاتنى تاللاڭ. + مەنبە PDF ياكى رەسىملەرنى تاللاڭ ، ئاندىن بەت تەرتىپى ، ئايلىنىش ، قوغداش ياكى OCR تاللانمىلىرىنى تەكشۈرۈڭ. + ھاسىل قىلىنغان PDF نى تېجەپ ، ئۇنى بىر قېتىم ئېچىپ ، بەت سانى ، تەرتىپى ۋە ئوقۇشچانلىقىنى جەزملەشتۈرۈڭ. + رەسىملەرنى PDF غا ئايلاندۇرۇڭ + سىكانىرلاش ، ئېكران رەسىمى ، تالون ياكى رەسىملەرنى بىر ئورتاق ھۆججەتكە بىرلەشتۈرۈڭ + پاكىز ھۆججەت ياساڭ + رەسىملەر ئۈزۈل-كېسىل كېسىلگەندە ، زاكاز قىلىنغاندا ۋە چوڭ-كىچىك بولغاندا تېخىمۇ ياخشى PDF بېتىگە ئايلىنىدۇ. رەسىم تىزىملىكىگە بەت قىسمىغا ئوخشاش مۇئامىلە قىلىڭ: ھەر بىر ئايلىنىش ، گىرۋەك ۋە بەت چوڭلۇقىدىكى تاللاش ئاخىرقى ھۆججەتنىڭ قانداق ئوقۇشچانلىقىغا تەسىر كۆرسىتىدۇ. + بەت گىرۋىكى ، سايە ياكى يۆنىلىش توغرا بولمىغاندا ئالدى بىلەن رەسىملەرنى كېسىش ياكى ئايلاندۇرۇش. + كۆزلىگەن بەت تەرتىپىدىكى رەسىملەرنى تاللاڭ ۋە قورال تەمىنلىگەندە بەت چوڭلۇقى ياكى گىرۋىكىنى تەڭشەڭ. + PDF نى چىقىرىڭ ، ئاندىن ئەۋەتىشتىن بۇرۇن بارلىق بەتلەرنىڭ ئوقۇغىلى بولىدىغانلىقىنى تەكشۈرۈڭ. + ھۆججەتلەرنى سايىلەڭ + قەغەز بەتلەرنى تۇتۇپ PDF ، OCR ياكى ئورتاقلىشىشقا تەييارلاڭ + ئوقۇغىلى بولىدىغان بەتلەرنى تۇتۇش + ھۆججەتلەرنى سىكانېرلاش تەكشى بەتلەر ، يورۇتۇش ياخشى ۋە تۈزىتىلگەن نۇقتىلار بىلەن ئەڭ ياخشى ئىشلەيدۇ. OCR ياكى PDF ئېكسپورتىدىن بۇرۇن پاكىز سىكانىرلاش ۋاقىت تېجەيدۇ ، چۈنكى تېكىست تونۇش ۋە پىرىسلاش ھەر ئىككىلىسى بەت سۈپىتىگە باغلىق. + ھۆججەتنى يېتەرلىك يورۇقلۇق ۋە ئەڭ ئاز سايە بىلەن سېلىشتۇرما يۈزىگە قويۇڭ. + بايقالغان بۇلۇڭ ياكى زىرائەتلەرنى قولدا تەڭشەڭ ، شۇڭا بەت رامكىنى پاكىز تولدۇرىدۇ. + رەسىم ياكى PDF قىلىپ چىقىرىڭ ، ئەگەر تاللاشقا بولىدىغان تېكىستكە ئېھتىياجلىق بولسىڭىز OCR نى ئىجرا قىلىڭ. + PDF ھۆججەتلىرىنى قوغداش ياكى ئېچىش + پارولنى ئېھتىياتچانلىق بىلەن ئىشلىتىڭ ، مۇمكىن بولسا تەھرىرلىگىلى بولىدىغان مەنبە كۆپەيتمىسىنى ساقلاڭ + PDF زىيارەتنى بىخەتەر بىر تەرەپ قىلىڭ + قوغداش قوراللىرى كونترول قىلىنغان ئورتاقلىشىشقا پايدىلىق ، ئەمما پارولنى يوقىتىش ئاسان ، ئەسلىگە كەلتۈرۈش تەس. كۆپەيتىش ئۈچۈن كۆپەيتىلگەن نۇسخىسىنى قوغداڭ ، قوغدالمىغان مەنبە ھۆججەتلىرىنى ئايرىم ساقلاڭ ، ھەمدە ھېچقانداق نەرسىنى ئۆچۈرۈشتىن بۇرۇن نەتىجىسىنى تەكشۈرۈپ بېقىڭ. + PDF نىڭ كۆپەيتىلگەن نۇسخىسىنى قوغداڭ ، بىردىنبىر مەنبە ھۆججىتىڭىز ئەمەس. + قوغدىلىدىغان ھۆججەتنى ئورتاقلىشىشتىن بۇرۇن پارولنى بىخەتەر بىر جايدا ساقلاڭ. + قۇلۇپنى پەقەت تەھرىرلەشكە رۇخسەت قىلىنغان ھۆججەتلەر ئۈچۈن ئىشلىتىڭ ، ئاندىن قۇلۇپلانغان نۇسخىسىنىڭ توغرا ئېچىلغانلىقىنى تەكشۈرۈپ بېقىڭ. + PDF بەتلىرىنى رەتلەڭ + بىرلەشتۈرۈش ، بۆلۈش ، قايتا رەتلەش ، ئايلاندۇرۇش ، تېرىش ، بەتلەرنى ئۆچۈرۈش ۋە بەت نومۇرىنى قەستەن قوشۇش + بېزەشتىن بۇرۇن ھۆججەت قۇرۇلمىسىنى ئوڭشاڭ + PDF بەت قوراللىرىنى قەغەز ھۆججەت تەييارلىغان تەرتىپ بويىچە ئىشلىتىش ئەڭ ئاسان: بەت توپلاڭ ، خاتالىرىنى ئۆچۈرۈۋېتىڭ ، تەرتىپكە سېلىڭ ، يۆنىلىشنى توغرىلاڭ ، ئاندىن بەت نومۇرى ياكى سۇ بەلگىسى قاتارلىق ئاخىرقى تەپسىلاتلارنى قوشۇڭ. + ھۆججەت يەنىلا بىر نەچچە ھۆججەتكە بۆلۈنگەندە ئالدى بىلەن بىرلەشتۈرۈش ياكى رەسىمدىن PDF نى ئىشلىتىڭ. + بەت نومۇرى ، ئىمزا ياكى سۇ بەلگىسىنى قوشۇشتىن بۇرۇن قايتا رەتلەش ، ئايلاندۇرۇش ، كېسىش ياكى ئۆچۈرۈش. + قۇرۇلما تەھرىرلىگەندىن كېيىن ئاخىرقى PDF نى ئالدىن كۆرۈڭ ، چۈنكى بىر يوقاپ كەتكەن ياكى ئايلانغان بەتنى كېيىن بايقاش تەسكە توختايدۇ. + PDF ئىزاھلىرى + كۆرۈرمەنلەر باھا ۋە بەلگىلەرنى باشقىچە كۆرسەتكەندە ئىزاھلارنى ئۆچۈرۈڭ ياكى تەكشى قىلىڭ + كۆرۈنگەننى كونترول قىلىڭ + ئىزاھلار باھا ، يارقىن نۇقتىلار ، سىزمىلار ، جەدۋەل بەلگىسى ياكى باشقا قوشۇمچە PDF ئوبيېكتى بولالايدۇ. بەزى كۆرۈرمەنلەر ئۇلارنى باشقىچە كۆرسىتىدۇ ، شۇڭا ئۇلارنى ئېلىۋېتىش ياكى تەكشى قىلىش ئورتاق ھۆججەتلەرنى تېخىمۇ ئالدىن پەرەز قىلالايدۇ. + باھا ، يارقىن نۇقتا ياكى تەكشۈرۈش بەلگىسى ئورتاق كۆپەيتىلگەن نۇسخىغا كىرگۈزۈلمىسە ئىزاھلارنى ئۆچۈرۈڭ. + كۆرۈنگەن بەلگىلەر بەت يۈزىدە تۇرغاندا تەكشى تۈزلەڭ ، ئەمما ئەمدى تەھرىرلىگىلى بولىدىغان ئىزاھلارغا ئوخشاش ھەرىكەت قىلماڭ. + ئەسلى نۇسخىسىنى ساقلاڭ ، چۈنكى ئىزاھلارنى ئۆچۈرۈۋېتىش ياكى تەكشى قىلىش كەينىگە قايتۇرۇش تەسكە توختايدۇ. + تېكىستنى تونۇش + تاللىغىلى بولىدىغان OCR ماتورى ۋە تىلى بار رەسىملەردىن تېكىست چىقىرىڭ + تېخىمۇ ياخشى OCR نەتىجىسىگە ئېرىشىڭ + OCR نىڭ توغرىلىقى رەسىمنىڭ ئۆتكۈرلۈكى ، تىل سانلىق مەلۇماتلىرى ، سېلىشتۇرما ۋە بەت يۆنىلىشىگە باغلىق. ئەڭ ياخشى نەتىجە ئادەتتە رەسىمنى تەييارلاشتىن كېلىدۇ: شاۋقۇننى يوقىتىش ، تىك ئايلىنىش ، ئوقۇشچانلىقىنى ئاشۇرۇش ، ئاندىن تېكىستنى تونۇش. + رەسىمنى تېكىست رايونىغا توغرىلاپ ، تونۇشتىن بۇرۇن تىك ئايلاندۇرۇڭ. + ئەگەر ئەپ تەلەپ قىلسا توغرا تىلنى تاللاڭ ۋە تىل سانلىق مەلۇماتلىرىنى چۈشۈرۈڭ. + ئېتىراپ قىلىنغان تېكىستنى كۆچۈرۈڭ ياكى ھەمبەھىرلەڭ ، ئاندىن ئىسىم ، سان ۋە تىنىش بەلگىلىرىنى قولدا تەكشۈرۈڭ. + OCR تىل سانلىق مەلۇماتلىرىنى تاللاڭ + ئورگىنال ، تىل ۋە چۈشۈرۈلگەن OCR مودېللىرىنى ماسلاشتۇرۇش ئارقىلىق تونۇشنى ياخشىلاڭ + OCR نى تېكىستكە ماسلاشتۇرۇڭ + خاتا OCR تىلى ياخشى رەسىملەرنى ناچار تونۇشقا ئوخشايدۇ. تىل سانلىق مەلۇماتلىرى قوليازما ، تىنىش بەلگىلىرى ، سان ۋە ئارىلاشما ھۆججەتلەر ئۈچۈن مۇھىم ، شۇڭا تېلېفون UI نىڭ ئورنىغا ئەمەس ، رەسىمدە كۆرۈلىدىغان تىلنى تاللاڭ. + تېلېفوندا ئەمەس ، رەسىمدە كۆرۈلىدىغان تىل ياكى قوليازمىنى تاللاڭ. + تورسىز ئىشلەش ياكى بىر تۈركۈم پىششىقلاپ ئىشلەشتىن بۇرۇن يوقاپ كەتكەن سانلىق مەلۇماتلارنى چۈشۈرۈڭ. + ئارىلاش تىللىق ھۆججەتلەر ئۈچۈن ئالدى بىلەن ئەڭ مۇھىم تىلنى ئىجرا قىلىڭ ھەمدە ئىسىم ۋە سانلارنى قولدا تەكشۈرۈڭ. + ئىزدىگىلى بولىدىغان PDF + OCR تېكىستىنى ھۆججەتلەرگە قايتا يېزىڭ ، شۇڭا سايىلەنگەن بەتلەرنى ئىزدەپ كۆچۈرگىلى بولىدۇ + سايىلەشنى ئىزدەشكە بولىدىغان ھۆججەتلەرگە ئايلاندۇرۇڭ + OCR چاپلاش تاختىسىغا تېكىست كۆچۈرۈشتىن باشقا ئىشلارنى قىلالايدۇ. ئېتىراپ قىلىنغان تېكىستنى ھۆججەتكە ياكى ئىزدەشكە بولىدىغان PDF قا يازسىڭىز ، تېكىست ئىزدەش ، تاللاش ، كۆرسەتكۈچ ياكى ئارخىپلاشتۇرۇشقا قولايلىق بولغاندا ، بەتنىڭ رەسىمى كۆرۈنۈپ تۇرىدۇ. + سىكانېرلانغان ھۆججەتلەر ئۈچۈن ئىزدەشكە بولىدىغان PDF چىقىرىشنى ئىشلىتىڭ ، بۇلار يەنىلا ئەسلى بەتلەرگە ئوخشايدۇ. + سىز پەقەت چىقىرىۋېتىلگەن تېكىستكە ئېھتىياجلىق بولۇپ ، بەت رەسىمىگە پەرۋا قىلمىسىڭىز ، يېزىقتىن ھۆججەتكە ئىشلىتىڭ. + مۇھىم سان ، ئىسىم ۋە جەدۋەلنى قولدا تەكشۈرۈپ بېقىڭ ، چۈنكى OCR تېكىستىنى ئىزدىگىلى بولىدۇ ، ئەمما يەنىلا مۇكەممەل ئەمەس. + QR كودىنى سايىلەڭ + ئىشلەتكەندە كامېرا كىرگۈزۈش ياكى ساقلانغان رەسىملەردىن QR مەزمۇنىنى ئوقۇڭ + كودلارنى بىخەتەر ئوقۇڭ + QR كودىدا ئۇلىنىش ، Wi-Fi سانلىق مەلۇماتلىرى ، ئالاقىلىشىش ، تېكىست ياكى باشقا يۈك يۈكى بار. + سىكانىرلاش QR كودىنى ئېچىڭ ھەمدە كودنى ئۆتكۈر ، تەكشى ۋە رامكىنىڭ ئىچىدە تولۇق ساقلاڭ. + ئۇلىنىش ئېچىش ياكى سەزگۈر سانلىق مەلۇماتلارنى كۆچۈرۈشتىن بۇرۇن كودلانغان مەزمۇننى تەكشۈرۈڭ. + ئەگەر سىكانىرلاش مەغلۇپ بولسا ، يورۇتۇشنى ياخشىلايدۇ ، كودنى كېسىڭ ياكى تېخىمۇ يۇقىرى ئېنىقلىقتىكى مەنبە رەسىمىنى سىناپ بېقىڭ. + Base64 ۋە تەكشۈرۈش جەدۋىلىنى ئىشلىتىڭ + رەسىملەرنى تېكىست سۈپىتىدە كودلاش ، تېكىستنى رەسىمگە قايتۇرۇش ۋە ھۆججەتنىڭ مۇكەممەللىكىنى دەلىللەش + ھۆججەت بىلەن تېكىست ئارىسىدا يۆتكەڭ + Base64 كىچىك رەسىم يۈكلەشكە پايدىلىق ، تەكشۈرۈش بولسا ھۆججەتلەرنىڭ ئۆزگەرمىگەنلىكىنى جەزملەشتۈرۈشكە ياردەم بېرىدۇ. بۇ قوراللار تېخنىكىلىق خىزمەت ئېقىمى ، قوللاش دوكلاتى ، ھۆججەت يوللاش ۋە ئىككىلىك ھۆججەتلەر ۋاقىتلىق تېكىستكە ئېھتىياجلىق جايلاردا ئەڭ پايدىلىق. + خىزمەت ئېقىمى ئىككىلىك ھۆججەتنىڭ ئورنىغا تېكىستكە ئېھتىياجلىق بولغاندا ، Base64 قوراللىرىنى ئىشلىتىپ كىچىك رەسىمنى كودلاڭ. + Base64 نى پەقەت ئىشەنچلىك مەنبەلەردىن يېشىپ ، ساقلاشتىن بۇرۇن ئالدىن كۆرۈشنى دەلىللەڭ. + ئېكسپورت قىلىنغان ھۆججەتلەرنى سېلىشتۇرۇش ياكى يۈكلەش / چۈشۈرۈشنى جەزملەشتۈرۈشكە توغرا كەلگەندە ، تەكشۈرۈش قوراللىرىنى ئىشلىتىڭ. + سانلىق مەلۇمات بوغچىسى ياكى قوغداش + ھۆججەتلەرنى باغلاش ياكى تېكىست سانلىق مەلۇماتلىرىنى ئۆزگەرتىش ئۈچۈن ZIP ۋە سىفىرلىق قوراللارنى ئىشلىتىڭ + مۇناسىۋەتلىك ھۆججەتلەرنى بىللە ساقلاڭ + بىر قانچە چىقىش ئېغىزى بىر ھۆججەت سۈپىتىدە ساياھەت قىلغاندا ، ئورالما قوراللىرى پايدىلىق. ئۇلار تولۇق نەتىجە توپلىمىنى ئەۋەتىشكە ئېھتىياجلىق بولغاندا ھاسىل قىلىنغان رەسىم ، PDF ، خاتىرە ۋە مېتا سانلىق مەلۇماتنى بىللە ساقلاشقا پايدىلىق. + نۇرغۇن ئېكسپورت قىلىنغان رەسىم ياكى ھۆججەتلەرنى بىللە ئەۋەتىشكە توغرا كەلگەندە ZIP نى ئىشلىتىڭ. + تاللانغان ئۇسۇلنى چۈشىنىپ ، لازىملىق كۇنۇپكىلارنى بىخەتەر ساقلىسىڭىز ئاندىن سىفىرلىق قوراللارنى ئىشلىتىڭ. + ئەسلى ھۆججەتلەرنى ئۆچۈرۈشتىن بۇرۇن قۇرۇلغان ئارخىپ ياكى ئۆزگەرتىلگەن تېكىستنى سىناڭ. + ئىسىمدىكى تەكشۈرۈش + خاسلىقى ۋە مۇكەممەللىكى ئوقۇشچانلىقىدىن مۇھىم بولغاندا ، تەكشۈرۈشنى ئاساس قىلغان ھۆججەت نامىنى ئىشلىتىڭ + ھۆججەتلەرگە مەزمۇن بويىچە ئىسىم قويۇڭ + تەكشۈرۈش ھۆججىتىنىڭ ئىسمى ئېكسپورتتا تەكرارلانغان ئىسىملار بولۇشى ياكى ئىككى ھۆججەتنىڭ ئوخشاش ئىكەنلىكىنى ئىسپاتلاشقا توغرا كەلگەندە پايدىلىق. ئۇلار قولدا كۆرۈشكە ئانچە دوستانە ئەمەس ، شۇڭا ئۇلارنى تېخنىكىلىق ئارخىپ ۋە دەلىللەش خىزمەت ئېقىمىغا ئىشلىتىڭ. + مەزمۇن كىملىكى ئىنسان ئوقۇغىلى بولىدىغان ماۋزۇدىن مۇھىم بولغاندا تەكشۈرۈش ھۆججىتىنى ھۆججەت ئىسمى قىلىپ قوزغىتىڭ. + ئۇنى ئارخىپ ، كۆپەيتىش ، قايتا-قايتا ئېكسپورت قىلىش ياكى قايتا يۈكلەشكە بولىدىغان ھۆججەتلەرگە ئىشلىتىڭ. + كىشىلەر ھۆججەتنىڭ نېمىگە ۋەكىللىك قىلىدىغانلىقىنى يەنىلا چۈشىنىشكە ئېھتىياجلىق بولغاندا ، ھۆججەت قىسقۇچ ياكى مېتا سانلىق مەلۇمات بىلەن جۈپ تەكشۈرۈش. + رەسىملەردىن رەڭ تاللاڭ + ئۈلگە پىكسېل ، رەڭلىك كودلارنى كۆپەيتىش ۋە رەڭلەرنى پالتا ياكى لايىھىلەشتە قايتا ئىشلىتىش + ئېنىق رەڭنى ئۆرنەك قىلىڭ + رەڭ تاللاش لايىھەنى ماسلاشتۇرۇش ، ماركا رەڭلىرىنى چىقىرىش ياكى رەسىم قىممىتىنى تەكشۈرۈشكە پايدىلىق. چوڭايتىش مۇھىم ، چۈنكى ئوكسىدلىنىشقا قارشى تۇرۇش ، سايە ۋە پىرىسلاش قوشنا پېكسىللارنى سىز ئويلىغان رەڭدىن سەل پەرقلەندۈرىدۇ. + رەسىمدىن رەڭ تاللاشنى ئېچىڭ ھەمدە لازىملىق رەڭ بىلەن ئەسلى رەسىمنى تاللاڭ. + كىچىك تەپسىلاتلارنى ئەۋرىشكە ئېلىشتىن بۇرۇن چوڭايتىڭ ، يېقىن ئەتراپتىكى پېكسىللار نەتىجىگە تەسىر كۆرسەتمەيدۇ. + رەڭنى مەنزىل تەلەپ قىلغان فورماتتا كۆچۈرۈڭ ، مەسىلەن HEX ، RGB ياكى HSV. + پالتا قۇرۇڭ ۋە رەڭلىك كۈتۈپخانىنى ئىشلىتىڭ + پالتا چىقىرىپ ، ساقلانغان رەڭلەرنى كۆرۈڭ ۋە ئىزچىل كۆرۈش سىستېمىسىنى ساقلاڭ + قايتا ئىشلەتكىلى بولىدىغان پالتا ياساڭ + Palettes ئېكسپورت قىلىنغان گرافىك ، سۇ بەلگىسى ۋە سىزىلغان رەسىملەرنى كۆرۈنۈشتە بىردەك ساقلاشقا ياردەم بېرىدۇ. ئېكران كۆرۈنۈشلىرىنى قايتا-قايتا ئىزاھلىغاندا ، ماركا مۈلكىنى تەييارلىغاندا ياكى بىر نەچچە قورالدا ئوخشاش رەڭگە ئېھتىياجلىق بولغاندا ، ئۇلار ئالاھىدە پايدىلىق. + قىممەتنى بىلگەندە رەسىمدىن پالتا ھاسىل قىلىڭ ياكى قولدا رەڭ قوشۇڭ. + مۇھىم رەڭلەرنى ئىسىم قويۇڭ ياكى رەتلەڭ ، كېيىن ئۇلارنى قايتا تاپالايسىز. + رەسىم ، سۇ بەلگىسى ، گرادېنت ، SVG ياكى سىرتقى لايىھىلەش قوراللىرىدا كۆچۈرۈلگەن قىممەتلەرنى ئىشلىتىڭ. + گرافىك قۇر + تەگلىك ، ئورۇن ئىگىلىرى ۋە كۆرۈنۈش تەجرىبىسى ئۈچۈن تەدرىجىي رەسىم ھاسىل قىلىڭ + رەڭ ئالماشتۇرۇشنى كونترول قىلىڭ + تەدرىجىي قوراللار مەۋجۇت رەسىمنى تەھرىرلەشنىڭ ئورنىغا ھاسىل قىلىنغان رەسىمگە ئېھتىياجلىق بولغاندا پايدىلىق. ئۇلار سىرتقى لايىھىلەش قوراللىرىنىڭ پاكىز ئارقا كۆرۈنۈشى ، ئورۇن ئىگىلىرى ، تام قەغىزى ، ماسكا ياكى ئاددىي مۈلۈككە ئايلىنالايدۇ. + تەدرىجىي تىپنى تاللاڭ ۋە مۇھىم رەڭلەرنى ئاۋۋال بەلگىلەڭ. + نىشان ئىشلىتىش قېپىنىڭ يۆنىلىشى ، توختىشى ، راۋانلىقى ۋە چىقىرىش مىقدارىنى تەڭشەڭ. + مەنزىلگە ماس كېلىدىغان فورماتتا چىقىرىڭ ، ئادەتتە پاكىز ھاسىل قىلىنغان گرافىك ئۈچۈن PNG. + رەڭ قولايلىق + UI ئوقۇش تەس بولغاندا ھەرىكەتچان رەڭ ، سېلىشتۇرما ۋە رەڭ قارىغۇ تاللاشلىرىنى ئىشلىتىڭ + رەڭلەرنى ئىشلىتىشكە قولايلىق يارىتىڭ + رەڭ تەڭشىكى راھەت ، ئوقۇشچانلىقى ۋە كونترولنى تېز سۈرئەتتە سىكانېرلىيالايسىز. ئەگەر قورال ئۆزىكى ، سىيرىلغۇچ ياكى تاللانغان ھالەتلەرنى پەرقلەندۈرۈش تەس تۇيۇلسا ، باشتېما ۋە قولايلىق تاللاشلار قاراڭغۇ ھالەتنى ئۆزگەرتىشتىنمۇ پايدىلىق بولىدۇ. + ئەپنىڭ نۆۋەتتىكى تام قەغىزىگە ئەگىشىشىنى ئۈمىد قىلسىڭىز ھەرىكەتچان رەڭلەرنى سىناپ بېقىڭ. + كونۇپكا ۋە يۈزلەر يېتەرلىك بولمىسا ، سېلىشتۇرۇشنى كۆپەيتىش ياكى ئۆزگەرتىش لايىھىسىنى ئۆزگەرتىش. + ئەگەر بەزى شىتاتلار ياكى تەلەپپۇزلارنى پەرقلەندۈرۈش قىيىن بولسا ، رەڭ قارىغۇ لايىھە تاللانمىلىرىنى ئىشلىتىڭ. + ئالفا تەگلىكى + سۈزۈك PNG ، WebP ۋە شۇنىڭغا ئوخشاش چىقىش ئېغىزىدا ئىشلىتىلىدىغان ئالدىن كۆرۈشنى كونترول قىلىڭ ياكى رەڭ تولدۇرۇڭ + سۈزۈك ئالدىن كۆرۈشنى چۈشىنىڭ + ئالفا فورماتىنىڭ تەگلىك رەڭلىرى سۈزۈك رەسىملەرنى تەكشۈرۈشنى ئاسانلاشتۇرىدۇ ، ئەمما ئالدىن كۆرۈش / تولدۇرۇش ھەرىكىتىنى ئۆزگەرتىۋاتقانلىقىڭىزنى ياكى ئاخىرقى نەتىجىنى تۈزلەۋاتقانلىقىڭىزنى بىلىش كېرەك. بۇ چاپلاق ، بەلگە ۋە تەگلىك چىقىرىۋېتىلگەن رەسىملەر ئۈچۈن مۇھىم. + سۈزۈك پېكسىل ئېكسپورتتا ساقلىنىشى كېرەك بولغاندا ، ئالدى بىلەن PNG ياكى WebP غا ئوخشاش ئالفا قوللايدىغان فورماتنى ئىشلىتىڭ. + ئەپ يۈزىگە سۈزۈك قىرلارنى كۆرۈش تەس بولغاندا تەگلىك رەڭ تەڭشىكىنى تەڭشەڭ. + كىچىك سىناقنى ئېكسپورت قىلىپ ، ئۇنى باشقا ئەپتە قايتا ئېچىپ ، سۈزۈكلۈك ياكى تاللانغان تولدۇرۇلغانلىقىنى جەزملەشتۈرۈڭ. + AI مودېل تاللاش + ئالدى بىلەن ئەڭ چوڭسىنى تاللاشنىڭ ئورنىغا رەسىم تىپى ۋە نۇقسان بويىچە مودېل تاللاڭ + مودېلنى زىيانغا ماسلاشتۇرۇڭ + سۈنئىي ئەقىل قوراللىرى ئوخشىمىغان ONNX / ORT تىپلىرىنى چۈشۈرەلەيدۇ ياكى ئىمپورتلىيالايدۇ ، ھەر بىر مودېل مەلۇم بىر مەسىلە ئۈچۈن تەربىيلىنىدۇ. JPEG بۆلەكلىرى ، كارتون لىنىيىسىنى تازىلاش ، رەتلەش ، تەگلىكنى ئېلىۋېتىش ، رەڭلەش ياكى يۇقىرى كۆتۈرۈشنىڭ مودېلى خاتا رەسىم تۈرىگە ئىشلىتىلگەندە تېخىمۇ ناچار نەتىجىنى كەلتۈرۈپ چىقىرىشى مۇمكىن. + چۈشۈرۈشتىن بۇرۇن مودېل چۈشەندۈرۈشىنى ئوقۇڭ. پىرىسلاش ، شاۋقۇن ، يېرىم ئاۋاز ، تۇتۇق ياكى تەگلىك ئۆچۈرۈش قاتارلىق ئەمەلىي مەسىلىلىرىڭىزگە ئىسىم قويغان مودېلنى تاللاڭ. + يېڭى رەسىم تىپىنى سىناق قىلغاندا كىچىك ياكى تېخىمۇ كونكرېت مودېلدىن باشلاڭ ، ئاندىن نەتىجىسى يېتەرلىك بولمىسا ئاندىن ئېغىر تىپلار بىلەن سېلىشتۇرۇڭ. + سىز ھەقىقىي ئىشلىتىدىغان مودېللارنىلا ساقلاڭ ، چۈنكى چۈشۈرۈلگەن ۋە ئىمپورت قىلىنغان مودېللار كۆرۈنەرلىك ساقلاش بوشلۇقىغا ئېرىشەلەيدۇ. + ئۈلگىلىك ئائىلىلەرنى چۈشىنىش + مودېل ئىسىملىرى ھەمىشە ئۇلارنىڭ نىشانىدىن بېشارەت بېرىدۇ: RealESRGAN ئۇسلۇبىدىكى مودېللارنىڭ يۇقىرى كۆتۈرۈلۈشى ، SCUNet ۋە FBCNN مودېللىرى پاكىز شاۋقۇن ياكى پىرىسلاش ، تەگلىك مودېللار ماسكا ھاسىل قىلىدۇ ، رەڭدارلار كۈلرەڭ كىرگۈزۈشكە رەڭ قوشىدۇ. ئىمپورت قىلىنغان ئىختىيارى مودېللار كۈچلۈك بولىدۇ ، ئەمما ئۇلار مۆلچەردىكى كىرگۈزۈش / چىقىرىش شەكلىگە ماس كېلىشى ھەمدە قوللايدىغان ONNX ياكى ORT فورماتىنى ئىشلىتىشى كېرەك. + سىز تېخىمۇ كۆپ پېكسىلغا ئېھتىياجلىق بولغاندا يۇقىرى كۆتۈرگۈچ ئىشلىتىڭ ، رەسىم دانچە بولغاندا كۆرسەتكۈچ ، پىرىسلاش توسۇش ياكى باغلاشتا ياسالغان بۇيۇملارنى يوقىتىش ئاساسلىق مەسىلە. + تەگلىك مودېللىرىنى پەقەت تېما ئايرىش ئۈچۈن ئىشلىتىڭ. ئۇلار ئادەتتىكى ئوبيېكتنى ئېلىۋېتىش ياكى رەسىمنى ئاشۇرۇش بىلەن ئوخشاش بولمايدۇ. + ئىختىيارى مودېللارنى پەقەت ئۆزىڭىز ئىشىنىدىغان مەنبەلەردىن ئەكىرىڭ ھەمدە مۇھىم ھۆججەتلەرنى ئىشلىتىشتىن بۇرۇن كىچىك رەسىمدە سىناپ بېقىڭ. + AI پارامېتىرلىرى + ئۇششاق-چۈششەك چوڭلۇق ، قاپلاش ۋە پاراللېل ئىشچىلارنى تەڭشەش ، سۈپەت ، سۈرئەت ۋە ئەستە تۇتۇش قابىلىيىتىنى تەڭپۇڭلاشتۇرۇش + مۇقىملىق بىلەن سۈرئەتنى تەڭپۇڭلاشتۇرۇش + پارچە چوڭلۇقى مودېل بىر تەرەپ قىلىشتىن ئىلگىرى قانچىلىك چوڭ رەسىم پارچىلىرىنى كونترول قىلىدۇ. قاپلاش قوشنا بۆلەكلەرنى ئارىلاشتۇرۇپ چېگرانى يوشۇرىدۇ. پاراللېل ئىشچىلار بىر تەرەپ قىلىشنى تېزلىتىدۇ ، ئەمما ھەر بىر ئىشچى ئىچكى ساقلىغۇچقا موھتاج ، شۇڭا يۇقىرى قىممەت ئىچكى ساقلىغۇچ چەكلىك تېلېفونلاردا چوڭ رەسىملەرنى تۇراقسىزلاشتۇرىدۇ. + ئۈسكۈنىڭىز مودېلنى ئىشەنچلىك بىر تەرەپ قىلغاندا ، رەسىم چوڭ بولمىسا ، تېخىمۇ چوڭ مەزمۇنلارنى ئىشلىتىڭ. + ئۇششاق-چۈششەك چېگرا كۆرۈنگەندە بىر-بىرىنى قاپلاشنى كۆپەيتىڭ ، ئەمما ئېسىڭىزدە تۇتۇڭ ، تېخىمۇ كۆپ تەكرارلاش تېخىمۇ كۆپ خىزمەتنى كۆرسىتىدۇ. + مۇقىم سىناقتىن كېيىن ئاندىن پاراللېل ئىشچىلارنى يېتىشتۈرۈش ئەگەر بىر تەرەپ قىلىش سۈرئىتى ئاستىلىسا ، ئۈسكۈنىنى قىزىتىدۇ ياكى سوقۇلۇپ كەتسە ، ئالدى بىلەن ئىشچىلارنى ئازايتىڭ. + ئۇششاق-چۈششەك بۇيۇملاردىن ساقلىنىڭ + چۇنكىڭ مودېلدىن چوڭ بولغان رەسىملەرنى بىرلا ۋاقىتتا راھەت بىر تەرەپ قىلالايدۇ. سودىدا ھەر بىر كاھىشنىڭ ئەتراپىنىڭ مۇھىتى بىر قەدەر تۆۋەن ، شۇڭا قاپلاش نىسبىتى تۆۋەن ياكى بەك كىچىك ئۇششاق كۆرۈنۈشلەر قوشنا رايونلار ئارىسىدا كۆرۈنەرلىك چېگرا ، تۈزۈلۈش ئۆزگىرىشى ياكى ماس كەلمەيدىغان تەپسىلاتلارنى پەيدا قىلىشى مۇمكىن. + ئەگەر تىك تۆت بۇلۇڭلۇق چېگرا ، قايتا-قايتا تۈزۈلۈش ئۆزگىرىشى ياكى پىششىقلاپ ئىشلەنگەن رايونلار ئارا سەكرەشنى كۆرسىڭىز ، قاپلاشنى كۆپەيتىڭ. + بولۇپمۇ چوڭ رەسىم ياكى ئېغىر تىپلاردا مەھسۇلات چىقىرىشتىن ئىلگىرى بۇ جەريان مەغلۇپ بولسا ، ئۇششاق چوڭلۇقنى ئازايتىڭ. + تولۇق رەسىمنى بىر تەرەپ قىلىشتىن بۇرۇن كىچىك زىرائەت سىنىقىنى تېجەڭ ، بۇنداق بولغاندا پارامېتىرلارنى تېز سېلىشتۇرالايسىز. + AI مۇقىملىقى + سۈنئىي ئەقىل پىششىقلاپ ئىشلەنگەندە ياكى توڭلاپ قالغاندا ئۇششاق-چۈششەك چوڭلۇق ، ئىشچىلار ۋە مەنبە ئېنىقلىق دەرىجىسى + ئېغىر سۈنئىي ئەقىل خىزمىتىدىن ئەسلىگە كېلىش + سۈنئىي ئەقىل بىر تەرەپ قىلىش ئەپتىكى ئەڭ ئېغىر خىزمەت ئېقىمىنىڭ بىرى. كاشىلا ، مەغلۇپ بولغان يىغىن ، توڭلىتىش ياكى تۇيۇقسىز ئەپنى قايتا قوزغىتىش ئادەتتە مودېل ، مەنبە ئېنىقلىق دەرىجىسى ، ئۇششاق-چۈششەك چوڭلۇقى ياكى پاراللېل ئىشچىلار سانىنىڭ نۆۋەتتىكى ئۈسكۈنىنىڭ ھالىتىگە بەك ئېھتىياجلىق ئىكەنلىكىنى كۆرسىتىدۇ. + ئەگەر بۇ دېتال چۈشۈپ كەتسە ياكى ئۈلگە يىغىنى ئاچالمىسا ، پاراللېل ئىشچىلار ۋە ئۇششاق-چۈششەك چوڭلۇقتا بولسا ، ئوخشاش رەسىمنى قايتا سىناڭ. + نىشان ئەسلى ئېنىقلىقنى تەلەپ قىلمىغان ۋاقىتتا سۈنئىي ئەقىل بىر تەرەپ قىلىشتىن ئىلگىرى ناھايىتى چوڭ رەسىملەرنى چوڭايتىڭ. + باشقا ئېغىر دېتاللارنى تاقاڭ ، كىچىكرەك مودېل ئىشلىتىڭ ياكى ئىچكى ساقلىغۇچ بېسىمى قايتىپ كەلگەندە بىر ئاز ھۆججەتلەرنى بىراقلا بىر تەرەپ قىلىڭ. + مودېلنىڭ مەغلۇبىيىتىگە دىئاگنوز قويۇڭ + مەغلۇپ بولغان سۈنئىي ئەقىل ئىجرا قىلىش ھەمىشە رەسىمنىڭ ناچارلىقىدىن دېرەك بەرمەيدۇ. مودېل ھۆججىتى تولۇق ئەمەس ، قوللىمايدىغان ، ئىچكى ساقلىغۇچ بەك چوڭ ياكى مەشغۇلات بىلەن ماسلاشمىغان بولۇشى مۇمكىن. ئەپ مەغلۇب بولغان يىغىننى دوكلات قىلغاندا ، ئالدى بىلەن مودېل ۋە پارامېتىرلارنى ئاددىيلاشتۇرىدىغان سىگنال دەپ قاراڭ. + ئەگەر مودېل چۈشۈرۈلگەندىن كېيىن دەرھال مەغلۇپ بولسا ياكى ھۆججەت بۇزۇلغاندەك ھەرىكەت قىلسا مودېلنى ئۆچۈرۈڭ ۋە قايتا چۈشۈرۈڭ. + ئىمپورت قىلىنغان ئىختىيارى مودېلنى ئەيىبلەشتىن بۇرۇن ئىچىگە چۈشۈرگىلى بولىدىغان مودېلنى سىناپ بېقىڭ ، چۈنكى ئىمپورت قىلىنغان مودېللارنىڭ ماس كەلمەيدىغان كىرگۈزۈشلىرى بولىدۇ. + ئەگەر كاشىلا ياكى يىغىن خاتالىقىنى دوكلات قىلىشقا توغرا كەلسە ، مەغلۇبىيەتنى كۆپەيتكەندىن كېيىن App خاتىرىسىنى ئىشلىتىڭ. + Shader Studio دىكى تەجرىبە + ئىلغار رەسىم بىر تەرەپ قىلىش ۋە ئىختىيارى كۆرۈنۈش ئۈچۈن GPU سايە ئۈنۈمىنى ئىشلىتىڭ + سايە بىلەن ئەستايىدىل ئىشلەڭ + Shader Studio كۈچلۈك ، ئەمما سايە كودى ۋە پارامېتىرلىرى كىچىك ، سىناق قىلىشقا بولىدىغان ئۆزگىرىشلەرگە موھتاج. ئۇنى تەجرىبە قورالىغا ئوخشاش بىر تەرەپ قىلىڭ: خىزمەت ئاساسىنى ساقلاپ ، بىر قېتىمدا بىر نەرسىنى ئۆزگەرتىڭ ھەمدە ئالدىن پەرەزگە ئىشىنىشتىن بۇرۇن ئوخشىمىغان رەسىم چوڭلۇقى بىلەن سىناق قىلىڭ. + پارامېتىر قوشۇشتىن بۇرۇن مەلۇم بىر سايە ياكى ئاددىي ئۈنۈمدىن باشلاڭ. + بىرلا ۋاقىتتا بىر پارامېتىر ياكى كود توپىنى ئۆزگەرتىپ ، ئالدىن كۆرۈشنى تەكشۈرۈڭ. + ئېكسپورت پەقەت بىر نەچچە ئوخشىمىغان رەسىم چوڭلۇقىدا سىناق قىلغاندىن كېيىن ئاندىن ئالدىن بەلگىلەيدۇ. + SVG ياكى ASCII سەنئىتىنى ياساڭ + ئىجادىي چىقىرىش ئۈچۈن ۋېكتورغا ئوخشاش مۈلۈك ياكى تېكىستنى ئاساس قىلغان رەسىم ھاسىل قىلىڭ + ئىجادىي چىقىرىش تۈرىنى تاللاڭ + SVG ۋە ASCII قوراللىرى ئوخشىمىغان نىشانلارغا مۇلازىمەت قىلىدۇ: كېڭەيتىشچان گرافىك ياكى تېكىست ئۇسلۇبىدىكى رەسىم. ھەر ئىككىسى ئىجادىي ئۆزگەرتىش ، شۇڭا ئاخىرقى چوڭلۇقتا ئالدىن كۆرۈش كىچىككىنە كىچىك كۆرۈنۈشتىن نەتىجىگە ھۆكۈم قىلىشتىن مۇھىم. + نەتىجە پاكىز كۆلەمدە ياكى لايىھىلەش خىزمەت ئېقىمىدا قايتا ئىشلىتىلگەندە SVG Maker نى ئىشلىتىڭ. + رەسىمنىڭ ئۇسلۇبتىكى تېكىست ئىپادىلىنىشىنى ئويلىسىڭىز ASCII سەنئىتىنى ئىشلىتىڭ. + ئاخىرقى چوڭلۇقتا ئالدىن كۆرۈڭ ، چۈنكى ھاسىل بولغان مەھسۇلاتتا كىچىك تەپسىلاتلار يوقىلىدۇ. + شاۋقۇن تېكىستلىرىنى ھاسىل قىلىڭ + تەگلىك ، ماسكا ، قاپلاش ياكى تەجرىبە ئۈچۈن تەرتىپلىك تۈزۈلۈش ھاسىل قىلىڭ + تەرتىپلىك چىقىرىش + شاۋقۇن ئەۋلادلىرى پارامېتىرلاردىن يېڭى رەسىم ھاسىل قىلىدۇ ، شۇڭا كىچىك ئۆزگىرىشلەر ئوخشىمىغان نەتىجىنى ھاسىل قىلالايدۇ. ئۇ توقۇلما ، ماسكا ، قاپلاش ، ئورۇن ئىگىلىرى ياكى ئىنچىكە نەقىشلەر بىلەن پىرىسلاشنى سىناشقا پايدىلىق. + ئىنچىكە تەپسىلاتلارنى تەڭشەشتىن بۇرۇن شاۋقۇن تىپى ۋە چىقىرىش چوڭلۇقىنى تاللاڭ. + ئەندىزە نىشانغا ماس كەلگۈچە كۆلەم ، كۈچلۈكلۈك ، رەڭ ياكى ئۇرۇقنى تەڭشەڭ. + كېيىنچە قايتا ھاسىل قىلىشقا توغرا كەلسە ، پايدىلىق نەتىجىنى تېجەڭ ۋە پارامېتىرلارنى يېزىڭ. + Markup projects + ئىزاھلار ئەپنى تاقىغاندىن كېيىن تەھرىرلەشكە ئېھتىياجلىق بولغاندا تۈر ھۆججىتىنى ئىشلىتىڭ + قاتلاملىق تەھرىرلەشنى قايتا ئىشلىتىڭ + Markup تۈر ھۆججىتى ئېكران رەسىمى ، دىئاگرامما ياكى كۆرسەتمە رەسىمى كېيىن ئۆزگەرتىشكە ئېھتىياجلىق بولغاندا پايدىلىق. ئېكسپورت قىلىنغان PNG / JPEG ھۆججەتلىرى ئاخىرقى رەسىملەر ، تۈر ھۆججەتلىرى تەھرىرلەشكە بولىدىغان قەۋەت ۋە تەھرىرلەش ھالىتىنى ساقلاپ قالىدۇ. + ئىزاھ ، چاقىرىش ياكى ئورۇنلاشتۇرۇش ئېلېمېنتلىرى تەھرىرلەشكە ئېھتىياجلىق بولغاندا Markup قەۋىتىنى ئىشلىتىڭ. + ئەگەر تېخىمۇ كۆپ تۈزىتىشنى ئۈمىد قىلسىڭىز ، ئاخىرقى رەسىمنى چىقىرىشتىن بۇرۇن تۈر ھۆججىتىنى ساقلاڭ ياكى قايتا ئېچىڭ. + تەكشى بولغان ئاخىرقى نەشرىنى ھەمبەھىرلەشكە تەييارلانغاندىلا نورمال رەسىمنى چىقىرىڭ. + ھۆججەتلەرنى مەخپىيلەشتۈرۈش + ھەر قانداق مەنبە نۇسخىسىنى ئۆچۈرۈشتىن بۇرۇن پارول ۋە مەخپىي شىفىرلىق ھۆججەتلەرنى قوغداڭ + شىفىرلانغان نەتىجىنى ئەستايىدىل بىر تەرەپ قىلىڭ + سىفىرلىق قوراللار مەخپىي نومۇرنى مەخپىيلەشتۈرۈش ئارقىلىق ھۆججەتلەرنى قوغدىيالايدۇ ، ئەمما ئۇلار ئاچقۇچنى ئەستە ساقلاش ياكى زاپاسلاشنى ساقلاشنىڭ ئورنى ئەمەس. شىفىرلاش توشۇش ۋە ساقلاشقا پايدىلىق ، ئاساسلىق نىشان بىر قانچە نەتىجىنى باغلىغاندا ZIP تېخىمۇ ياخشى. + شىفىر يېشىش سىز ساقلىغان پارول بىلەن ئىشلەيدىغانلىقىنى سىنىغۇچە ئاۋۋال بىر نۇسخىسىنى مەخپىيلەشتۈرۈڭ ھەمدە ئەسلى نۇسخىسىنى ساقلاڭ. + ئاچقۇچ ئۈچۈن مەخپىي نومۇر باشقۇرغۇچى ياكى باشقا بىخەتەر جاي ئىشلىتىڭ. بۇ دېتال سىز ئۈچۈن يوقاپ كەتكەن پارولنى پەرەز قىلالمايدۇ. + شىفىرلانغان ھۆججەتلەرنى پەقەت مەخپىي نومۇرى بار ۋە قايسى قورال ياكى ئۇسۇلنىڭ شىفىر يېشىشنى بىلىدىغان كىشىلەر بىلەن ئورتاقلىشىڭ. + قوراللارنى رەتلەڭ + گۇرۇپپا ، تەرتىپ ، ئىزدەش ۋە pin قوراللىرى ئارقىلىق ئاساسلىق ئېكران سىزنىڭ ھەقىقىي خىزمەت ئېقىمىڭىزغا ماس كېلىدۇ + ئاساسىي ئېكراننى تېزلىتىڭ + قوراللارنى تەڭشەش تەڭشەكلىرى قايسى قوراللارنى ئەڭ كۆپ ئىشلىتىدىغانلىقىڭىزنى بىلگەندىن كېيىن تەڭشەشكە ئەرزىيدۇ. گۇرۇپپىلاش ئىزدىنىشكە ياردەم بېرىدۇ ، زاكاز تەرتىپى مۇسكۇللارنىڭ ئەستە تۇتۇش قابىلىيىتىگە ياردەم بېرىدۇ ، ياقتۇرىدىغانلار تولۇق تىزىملىك ​​چوڭ بولغان تەقدىردىمۇ كۈندىلىك قوراللارنى يەتكۈزىدۇ. + ئەپنى ئۆگەنگەندە ياكى ۋەزىپە تىپى بويىچە تەشكىللەنگەن قوراللارنى ياقتۇرغاندا گۇرۇپپا ھالىتىنى ئىشلىتىڭ. + دائىم ئىشلىتىدىغان قوراللىرىڭىزنى بىلگەندىن كېيىن ، ئاز مىقداردا دومىلىتىشنى ئويلىسىڭىز ئىختىيارى تەرتىپكە ئالماشتۇرۇڭ. + ئىسىم بىلەن ئەستە ساقلايدىغان ، ئەمما دائىم كۆرۈنۈشىنى خالىمايدىغان ئاز ئۇچرايدىغان قوراللار ئۈچۈن ئىزدەش تەڭشەكلىرى ۋە ياقتۇرىدىغانلارنى ئىشلىتىڭ. + چوڭ ھۆججەتلەرنى بىر تەرەپ قىلىڭ + ئېكىسپورتنىڭ ئاستا بولۇشى ، ئىچكى ساقلىغۇچ بېسىمى ۋە ئويلىمىغان يەردىن چوڭ چىقىرىشتىن ساقلىنىڭ + ئېكسپورت قىلىشتىن بۇرۇن خىزمەتنى ئازايتىش + ناھايىتى چوڭ رەسىملەر ، ئۇزۇن تۈركۈملەر ۋە يۇقىرى سۈپەتلىك فورماتلار تېخىمۇ كۆپ ئىچكى ساقلىغۇچ ۋە ۋاقىت سەرپ قىلالايدۇ. ئەڭ تېز ئوڭشاش ئادەتتە ئەڭ ئېغىر مەشغۇلاتتىن ئىلگىرى خىزمەت مىقدارىنى ئازايتىدۇ ، ئوخشاش ئۆلچەمدىكى كىرگۈزۈشنىڭ تاماملىنىشىنى ساقلىمايدۇ. + ئېغىر سۈزگۈچ ، OCR ياكى PDF ئايلاندۇرۇشتىن بۇرۇن چوڭ رەسىملەرنى چوڭايتىڭ. + تەڭشەكلەرنى جەزملەشتۈرۈش ئۈچۈن ئالدى بىلەن كىچىكرەك بىر گۇرۇپپا ئىشلىتىڭ ، ئاندىن قالغانلىرىنى ئوخشاش ئالدىن بىر تەرەپ قىلىڭ. + چىقىرىش ھۆججىتى مۆلچەردىكىدىن چوڭ بولغاندا سۈپەت ياكى ئۆزگەرتىش فورماتى. + غەملەك ۋە ئالدىن كۆرۈش + ئېغىر يىغىنلاردىن كېيىن ھاسىل قىلىنغان ئالدىن كۆرۈش ، ساقلىغۇچ تازىلاش ۋە ساقلاشنى چۈشىنىش + ساقلاش ۋە سۈرئەتنى تەڭپۇڭلاشتۇرۇڭ + ئالدىن كۆرۈش ئەۋلاد ۋە ساقلىغۇچ قايتا-قايتا كۆرۈشنى تېزلىتىدۇ ، ئەمما ئېغىر تەھرىرلەش يىغىنلىرى ۋاقىتلىق سانلىق مەلۇماتلارنى قالدۇرۇپ قويۇشى مۇمكىن. كەش تەڭشەكلىرى ئەپ ئاستا ، ساقلاش چىڭ ياكى ئالدىن كۆرۈش نۇرغۇن سىناقلاردىن كېيىن توختاپ قالغاندەك ھېس قىلغاندا ياردەم بېرىدۇ. + نۇرغۇن رەسىملەرنى كۆرگەندە ئالدىن كۆرۈشنى قوزغىتىڭ ، ۋاقىتلىق ساقلاشنى ئازايتىشتىن مۇھىم. + چوڭ تۈركۈم ، مەغلۇب بولغان تەجرىبە ياكى نۇرغۇن يۇقىرى ئېنىقلىقتىكى ھۆججەتلەر بىلەن ئۇزۇن ئولتۇرۇشتىن كېيىن غەملەكنى تازىلاڭ. + ئەگەر ساقلاشنى تازىلاشنى ياقتۇرسىڭىز ، ئاپتوماتىك ساقلىغۇچ تازىلاشنى ئىشلىتىڭ. + ئېكران ھەرىكىتىنى تەڭشەش + پۈتۈن ئېكران ، بىخەتەر ھالەت ، يورۇقلۇق ۋە سىستېما بالداق تاللانمىلىرىنى ئىشلىتىڭ + كۆرۈنمە يۈزنى ئەھۋالغا ماسلاشتۇرۇڭ + مەنزىرە رايونىدا تەھرىرلىگەندە ، شەخسىي رەسىملەرنى كۆرسەتكەندە ياكى ئىزچىل يورۇقلۇققا ئېھتىياجلىق بولغاندا ئېكران تەڭشىكى ياردەم بېرىدۇ. ئۇلار نورمال ئىشلىتىش تەلەپ قىلىنمايدۇ ، ئەمما ئۇلار QR تەكشۈرۈش ، شەخسىي ئالدىن كۆرۈش ياكى تار مەنزىرە تەھرىرلەش قاتارلىق ئوڭايسىز ئەھۋاللارنى ھەل قىلىدۇ. + بوشلۇق تەھرىرلەشتە يول باشلاش خرومدىنمۇ مۇھىم بولغاندا پۈتۈن ئېكران ياكى سىستېما تاياقچە تەڭشىكىنى ئىشلىتىڭ. + ئېكران رەسىمى ياكى ئەپ ئالدىن كۆرۈشتە شەخسىي رەسىملەر كۆرۈنمىسە بىخەتەر ھالەتنى ئىشلىتىڭ. + قاراڭغۇ رەسىم ياكى QR كودىنى تەكشۈرۈش تەس بولغان قوراللارغا يورۇقلۇق ئىجرا قىلىشنى ئىشلىتىڭ. + سۈزۈكلۈكنى ساقلاڭ + سۈزۈك تەگلىكنىڭ ئاق ، قارا ياكى تەكشى بولۇپ كېتىشىنىڭ ئالدىنى ئالىدۇ + ئالفا بىلىدىغان چىقىرىشنى ئىشلىتىڭ + تاللانغان قورال ۋە چىقىرىش فورماتى ئالفانى قوللىغاندىلا ئاندىن سۈزۈكلۈك ساقلىنىپ قالىدۇ. ئەگەر ئېكسپورت قىلىنغان تەگلىك ئاق ياكى قارا رەڭگە ئۆزگەرسە ، مەسىلە ئادەتتە چىقىرىش شەكلى ، تولدۇرۇش / تەگلىك تەڭشىكى ياكى رەسىمنى تەكشى قىلىدىغان مەنزىل دېتالى. + تەگلىك چىقىرىۋېتىلگەن رەسىم ، چاپلاق ، بەلگە ياكى قاپلىما ئېكسپورت قىلغاندا PNG ياكى WebP نى تاللاڭ. + سۈزۈك چىقىرىش ئۈچۈن JPEG دىن ساقلىنىڭ ، چۈنكى ئۇ ئالفا قانىلىنى ساقلىمايدۇ. + ئالفا فورماتىنىڭ تەگلىك رەڭگىگە مۇناسىۋەتلىك تەڭشەكلەرنى تەكشۈرۈڭ. + ئورتاقلىشىش ۋە ئىمپورت قىلىش مەسىلىلىرىنى تۈزىتىڭ + ھۆججەتلەر كۆرۈنمىگەن ياكى ئېچىلمىغان ۋاقىتتا تاللىغۇچ تەڭشىكى ۋە قوللايدىغان فورماتلارنى ئىشلىتىڭ + ھۆججەتنى ئەپكە ئېلىڭ + ئىمپورت مەسىلىسى كۆپىنچە تەمىنلىگۈچىلەرنىڭ رۇخسىتى ، قوللىمايدىغان فورماتى ياكى تاللىغۇچىلارنىڭ ھەرىكىتىدىن كېلىپ چىقىدۇ. بۇلۇت ئەپلىرى ۋە ئەلچىلەردىن ھەمبەھىرلەنگەن ھۆججەتلەر ۋاقىتلىق بولۇشى مۇمكىن ، شۇڭا ئىزچىل مەنبەنى تاللاش ئۇزۇنراق تەھرىرلەشكە تېخىمۇ ئىشەنچلىك بولىدۇ. + ھۆججەتنى يېقىنقى ھۆججەت تېزلەتمىسىنىڭ ئورنىغا سىستېما تاللىغۇچ ئارقىلىق ئېچىڭ. + ئەگەر بىر فورماتنى بىر قورال قوللىمىسا ، ئۇنى ئاۋۋال ئايلاندۇرۇڭ ياكى تېخىمۇ كونكرېت قورالنى ئېچىڭ. + ئەگەر ئورتاقلىشىش ئېقىمى ياكى بىۋاسىتە قورال ئېچىش مۆلچەردىكىگە ئوخشىمايدىغان بولسا ، رەسىم تاللىغۇچ ۋە قوزغىتىش تەڭشىكىنى تەكشۈرۈڭ. + جەزملەشتۈرۈشتىن چېكىنىش + قول ئىشارىسى ، ئارقا بېسىش ياكى قورال ئالماشتۇرۇش تاسادىپىي يۈز بەرگەندە ساقلانمىغان تەھرىرلەشنى يوقىتىشتىن ساقلىنىڭ + تاماملانمىغان خىزمەتلەرنى قوغداش + قول ئىشارىسى يول باشلاش ، چوڭ ھۆججەتلەرنى تەھرىرلەش ياكى دائىم ئەپلەر ئارا ئالماشتۇرغاندا قورالدىن چېكىنىشنى جەزملەشتۈرۈش پايدىلىق. ئۇ قورالدىن ئايرىلىشتىن بۇرۇن ئازراق توختاپ قالىدۇ ، شۇڭا تاماملانمىغان تەڭشەكلەر ۋە ئالدىن كۆرۈشلەر تاسادىپىي يوقاپ كەتمەيدۇ. + ئەگەر ئېكسپورت قىلىشتىن بۇرۇن تاسادىپىي ئارقا ئىشارەت قورالنى تاقاپ قويغان بولسا جەزملەشتۈرۈشنى قوزغىتىڭ. + ئەگەر كۆپىنچە تېز بىر قەدەملىك ئۆزگەرتىش ئېلىپ بارسىڭىز ھەمدە تېز يول باشلاشنى ياقتۇرسىڭىز ئۇنى چەكلەڭ. + تەڭشەكلەرنى قايتا قۇرۇش ئادەمنى بىزار قىلىدىغان ئۇزۇن خىزمەت ئېقىمى ئۈچۈن ئالدىن بەلگىلەش بىلەن بىللە ئىشلىتىڭ. + مەسىلىلەرنى دوكلات قىلغاندا خاتىرە ئىشلىتىڭ + مەسىلە ئېچىش ياكى ئاچقۇچى بىلەن ئالاقىلىشىشتىن بۇرۇن پايدىلىق تەپسىلاتلارنى توپلاڭ + مەسىلىلەرنى مەزمۇن بىلەن دوكلات قىلىڭ + قەدەم باسقۇچ ، ئەپ نۇسخىسى ، كىرگۈزۈش تىپى ۋە خاتىرىلەر بىلەن ئېنىق دوكلاتقا دىئاگنوز قويۇش تېخىمۇ ئاسان. خاتىرە مەسىلە كۆپەيتىلگەندىن كېيىنلا ئەڭ پايدىلىق ، چۈنكى ئۇلار ئەتراپتىكى مەزمۇنلارنى يېڭى ھالەتتە ساقلايدۇ. + قورالنىڭ ئىسمى ، ئېنىق ھەرىكىتى ۋە ئۇنىڭ بىر ھۆججەت ياكى ھەر بىر ھۆججەتتە يۈز بەرگەنلىكىگە دىققەت قىلىڭ. + مەسىلە كۆپەيتىلگەندىن كېيىن ئەپ خاتىرىسىنى ئېچىڭ ھەمدە مۇمكىن بولغاندا مۇناسىۋەتلىك خاتىرىلەرنى ئورتاقلىشىڭ. + ئېكران رەسىمى ياكى ئەۋرىشكە ھۆججىتىنى شەخسىي ئۇچۇرلار بولمىغاندىلا ئۇلاڭ. + تەگلىك بىر تەرەپ قىلىش توختىتىلدى + ئاندىرويىد تەگلىك بىر تەرەپ قىلىش ئۇقتۇرۇشىنى ۋاقتىدا باشلىمىدى. بۇ ئىشەنچلىك مۇقىملاشتۇرغىلى بولمايدىغان سىستېما ۋاقىت چەكلىمىسى. ئەپنى قايتا قوزغىتىپ قايتا سىناڭ. ئەپنى ئوچۇق قىلىپ ، ئۇقتۇرۇش ياكى تەگلىك خىزمىتىگە يول قويسىڭىز بولىدۇ. + ھۆججەت تاللاشتىن ئاتلاڭ + كىرگۈزۈش ئادەتتە ھەمبەھىرلەش ، چاپلاش تاختىسى ياكى ئالدىنقى ئېكراندىن كەلگەندە قوراللارنى تېز ئېچىڭ + قايتا-قايتا قورال قوزغىتىشنى تېزلىتىڭ + ھۆججەت تاللاشتىن ئاتلاش قوللايدىغان قوراللارنىڭ بىرىنچى قەدىمىنى ئۆزگەرتىدۇ. سىز ئاللىبۇرۇن تاللانغان رەسىم ياكى ئاندىرويىد ھەمبەھىرلەش ھەرىكىتىدىن قورالنى كىرگۈزگەندە پايدىلىق ، ئەمما ھەر بىر قورالنىڭ دەرھال ھۆججەت تەلەپ قىلىشىنى ئۈمىد قىلسىڭىز ، گاڭگىراپ قالىدۇ. + ئادەتتىكى ئېقىمىڭىز قورال ئېچىشتىن بۇرۇن ئەسلى رەسىم بىلەن تەمىنلىگەندىلا ئاندىن ئۇنى قوزغىتىڭ. + ئەپنى ئۆگەنگەندە ئۇنى چەكلەڭ ، چۈنكى ئېنىق تاللاش ھەر بىر قورال ئېقىمىنى چۈشىنىشنى ئاسانلاشتۇرىدۇ. + ئەگەر بىر قورال ئېنىق كىرگۈزۈلمىگەن ھالەتتە ئېچىلسا ، بۇ تەڭشەكنى چەكلەڭ ياكى ھەمبەھىرلەش / ئېچىشتىن باشلاڭ ، شۇڭا ئاندىرويىد ھۆججەتنى ئالدى بىلەن ئۆتكۈزىدۇ. + ئالدى بىلەن تەھرىرلىگۈچنى ئېچىڭ + ھەمبەھىرلەنگەن رەسىم بىۋاسىتە تەھرىرلەشكە توغرا كەلگەندە ئالدىن كۆرۈشتىن ئاتلاڭ + ئالدىن كۆرۈش ياكى تەھرىرلەشنى تاللاڭ + ئالدىن كۆرۈشنىڭ ئورنىغا تەھرىرلەشنى ئېچىڭ ، ئۇلار رەسىمنى ئۆزگەرتمەكچى بولغانلىقىنى بىلگەن ئىشلەتكۈچىلەر ئۈچۈندۇر. پاراڭلىشىش ياكى بۇلۇت ساقلاش بوشلۇقىدىكى ھۆججەتلەرنى تەكشۈرگەندە ئالدىن كۆرۈش تېخىمۇ بىخەتەر بولىدۇ. ئېكران كۆرۈنۈشلىرى ، تېز زىرائەتلەر ، ئىزاھلار ۋە بىر قېتىمدىلا تۈزىتىش ئۈچۈن بىۋاسىتە تەھرىرلەش تېخىمۇ تېز. + ئەگەر ھەمبەھىرلەنگەن رەسىملەر دەرھال زىرائەت ، بەلگە ، سۈزگۈچ ياكى ئېكسپورت ئۆزگەرتىشكە ئېھتىياجلىق بولسا ، بىۋاسىتە تەھرىرلەشنى قوزغىتىڭ. + سىز قارار چىقىرىشتىن بۇرۇن مېتا سانلىق مەلۇمات ، ئۆلچەم ، سۈزۈكلۈك ياكى رەسىم سۈپىتىنى تەكشۈرگەندە ئالدى بىلەن ئالدىن كۆرۈپ بېقىڭ. + ياقتۇرىدىغان رەسىملەر بىلەن بۇنى ئورتاق ئىشلىتىڭ ، شۇڭا ئورتاق ئىشلىتىدىغان رەسىملەر سىز ئىشلىتىدىغان قوراللارغا يېقىنلىشىدۇ. + تېكىست كىرگۈزۈشنى ئالدىن بەلگىلەڭ + كونترولنى قايتا-قايتا تەڭشەشنىڭ ئورنىغا ئالدىن بېكىتىلگەن قىممەتنى كىرگۈزۈڭ + سىيرىلما ئاستا بولغاندا ئېنىق قىممەتنى ئىشلىتىڭ + قايتا-قايتا ئىشلەش ئۈچۈن ئېنىق سانغا ئېھتىياجلىق بولغاندا ، ئالدىن يېزىلغان تېكىست كىرگۈزۈش ياردەم بېرىدۇ: ئىجتىمائىي رەسىمنىڭ چوڭ-كىچىكلىكى ، ھۆججەت گىرۋىكى ، پىرىسلاش نىشانى ، قۇر كەڭلىكى ياكى قول ئىشارىسى بىلەن يەتكۈزىدىغان باشقا قىممەتلەر. ئىنچىكە سىيرىلما ھەرىكەت تېخىمۇ قىيىن بولغان كىچىك ئېكرانلارغىمۇ پايدىلىق. + ئەگەر سىز ھەمىشە چوڭ رازمېر ، پىرسەنت ، سۈپەت قىممىتى ياكى قورال پارامېتىرلىرىنى قايتا ئىشلەتسىڭىز ، تېكىست كىرگۈزۈشنى قوزغىتىڭ. + قىممەتنى بىر قېتىم كىرگۈزگەندىن كېيىن ئالدىن ساقلاپ قويۇڭ ، ئاندىن ئوخشاش تەڭشەكنى قايتا قۇرۇشنىڭ ئورنىغا قايتا ئىشلىتىڭ. + قوپال كۆرۈنۈشنى تەڭشەش ۋە قاتتىق چىقىرىش تەلىپى ئۈچۈن كىرگۈزۈلگەن قىممەتلەرنى قول ئىشارىتىنى كونترول قىلىڭ. + ئەسلىگە يېقىن ساقلاڭ + ھۆججەت قىسقۇچ مەزمۇنى مۇھىم بولغاندا ھاسىل قىلىنغان ھۆججەتلەرنى ئەسلى رەسىمنىڭ يېنىغا قويۇڭ + مەنبەنى مەنبە بىلەن ساقلاڭ + ئەسلى ھۆججەت قىسقۇچنى ساقلاش ھۆججەت قىسقۇچ ، تۈر قىسقۇچ ، ھۆججەت سايىلەش ۋە خېرىدارلار توپىغا ماس كېلىدۇ ، بۇ يەردە تەھرىرلەنگەن نۇسخىسى مەنبەنىڭ يېنىدا تۇرۇشى كېرەك. ئۇ مەخسۇس چىقىرىش ھۆججەت قىسقۇچىغا قارىغاندا تېخىمۇ رەتلىك بولالايدۇ ، شۇڭا ئۇنى ئېنىق ھۆججەت ئىسمى قوشۇمچىسى ياكى ئەندىزە بىلەن بىرلەشتۈرۈڭ. + ھەر بىر مەنبە ھۆججەت قىسقۇچى ئاللىقاچان ئەھمىيەتلىك بولغاندا ، ئۇنى شۇ يەردە گۇرۇپپىلاش كېرەك. + ھۆججەت ئىسمى قوشۇمچىسى ، ئالدى قوشۇلغۇچى ، ۋاقىت تامغىسى ياكى ئەندىزە ئىشلىتىڭ ، شۇڭا تەھرىرلەنگەن نۇسخىلارنى ئەسلىدىن پەرقلەندۈرۈش ئاسان. + بارلىق ئېكسپورت ھۆججەت قىسقۇچقا يىغىلغان ھۆججەتلەرنى خالىغان ۋاقىتتا ئارىلاشتۇرۇڭ. + چوڭراق چىقىرىشتىن ئاتلاڭ + ئويلىمىغان يەردىن مەنبەدىن ئېغىرلىشىپ كەتكەن ئۆزگەرتىشتىن ساقلىنىڭ + تۈركۈملەرنى ناچار چوڭلۇقتىكى كۈتۈلمىگەن ئەھۋاللاردىن ساقلاڭ + بەزى ئۆزگەرتىشلەر ھۆججەتلەرنى چوڭايتىدۇ ، بولۇپمۇ PNG ئېكران رەسىمى ، ئاللىبۇرۇن پىرىسلانغان رەسىملەر ، ئەلا سۈپەتلىك WebP ياكى مېتا سانلىق مەلۇمات ساقلانغان رەسىملەر. ئاتلاپ ئۆتۈپ كېتىشكە يول قويۇڭ ئەگەر چوڭراق بولسا بۇ مەھسۇلاتلارنىڭ خىزمەت ئېقىمىدىكى كىچىك مەنبەنى ئالماشتۇرۇشنىڭ ئالدىنى ئالسا ، چوڭ-كىچىكلىكىنى ئازايتىش ئاساسلىق نىشان. + ئېكسپورت ھۆججەتلەرنى پىرىسلاش ، چوڭ-كىچىكلىكى ياكى يۈكلەش چەكلىمىسى ئۈچۈن تەييارلاشنى مەقسەت قىلغاندا ئۇنى قوزغىتىڭ. + بىر تۈركۈمدىن كېيىن ئاتلاپ كەتكەن ھۆججەتلەرنى قايتا كۆرۈڭ. ئۇلار ئاللىبۇرۇن ئەلالاشتۇرۇلغان ياكى باشقا فورماتقا ئېھتىياجلىق بولۇشى مۇمكىن. + ئاخىرقى ھۆججەت چوڭلۇقىدىن سۈپەت ، سۈزۈكلۈك ياكى فورمات ماسلىشىشچانلىقى مۇھىم بولغاندا ئۇنى چەكلەڭ. + چاپلاش تاختىسى ۋە ئۇلىنىشلار + تېخىمۇ تېز تېكىستنى ئاساس قىلغان قوراللارنىڭ ئاپتوماتىك قىستۇرما كىرگۈزۈش ۋە ئۇلىنىش ئالدىن كۆزىتىشنى كونترول قىلىڭ + ئاپتوماتىك كىرگۈزۈشنى ئالدىن پەرەز قىلغىلى بولىدۇ + چاپلاش تاختىسى ۋە ئۇلىنىش تەڭشىكى QR ، Base64 ، URL ۋە تېكىست ئېغىر خىزمەت ئېقىمىغا قۇلايلىق ، ئەمما ئاپتوماتىك كىرگۈزۈش قەستەن ساقلىنىشى كېرەك. ئەگەر بۇ دېتال ئۆزى تولدۇرغاندەك بولسا ، چاپلاش تاختىسى چاپلاش ھەرىكىتىنى تەكشۈرۈپ بېقىڭ. ئەگەر ئۇلىنىش كارتىلىرى شاۋقۇن ياكى ئاستا تۇيۇلسا ، ئۇلىنىش ئالدىن كۆرۈش ھەرىكىتىنى تەڭشەڭ. + سىز دائىم تېكىستنى كۆچۈرگەندە ئاپتوماتىك چاپلاش تاختىسى چاپلاشقا يول قويۇڭ ھەمدە ماس كېلىدىغان قورالنى دەرھال ئېچىڭ. + ئەگەر سىز ئويلىمىغان قوراللاردا شەخسىي چاپلاش مەزمۇنى كۆرۈنسە ئاپتوماتىك چاپلاشنى چەكلەڭ. + ئالدىن كۆرۈش ئېلىپ بېرىش ئاستا ، دىققىتى چېچىلىدىغان ياكى خىزمەت ئېقىمىڭىز ئۈچۈن زۆرۈر بولمىغان ۋاقىتتا ئۇلىنىش ئالدىن كۆرۈشنى ئېتىۋېتىڭ. + ئىخچام كونترول + ئېكران بوشلۇقى تار بولغاندا قويۇق تاللىغۇچ ۋە تېز تەڭشەك ئورۇنلاشتۇرۇڭ + كىچىك ئېكرانلارغا تېخىمۇ كۆپ كونترول قىلىڭ + ئىخچام تاللىغۇچ ۋە تېز تەڭشەكلەرنى ئورۇنلاشتۇرۇش كىچىك تېلېفون ، مەنزىرە تەھرىرلەش ۋە نۇرغۇن پارامېتىرلىق قوراللارغا ياردەم بېرىدۇ. سودىدا كونترول قىلىش تېخىمۇ قويۇق ھېس قىلالايدۇ ، شۇڭا بۇ ئورتاق تاللاشنى تونۇپ بولغاندىن كېيىن ئەڭ ياخشى. + دىئالوگ ياكى تاللاش تىزىملىكى ئېكرانغا بەك ئېگىز تۇيۇلغاندا ئىخچام تاللىغۇچىلارنى قوزغىتىڭ. + ئەگەر قورال كونتروللىرى ئېكراننىڭ بىر تەرىپىدىن ئاسان قولغا كەلسە ، تېز تەڭشەك تەرىپىنى تەڭشەڭ. + ئەگەر سىز يەنىلا ئەپنى ئۆگىنىۋاتقان بولسىڭىز ياكى دائىم قويۇق قويۇق قويۇق تاللاشلارنى تاللىسىڭىز ، ياتاق كونتروللۇقىغا قايتىڭ. + توردىن رەسىملەرنى يۈكلەڭ + بەت ياكى رەسىم URL نى چاپلاڭ ، تەھلىل قىلىنغان رەسىملەرنى ئالدىن كۆرۈڭ ، ئاندىن تاللانغان نەتىجىنى ساقلاڭ ياكى ئورتاقلىشىڭ + تور رەسىملىرىنى قەستەن ئىشلىتىڭ + تور رەسىمى يۈكلەش تەمىنلەنگەن URL دىن رەسىم مەنبەسىنى تەھلىل قىلىدۇ ، تۇنجى بار بولغان رەسىمنى ئالدىن كۆرىدۇ ۋە تاللانغان تەھلىل قىلىنغان رەسىملەرنى ساقلىالايسىز ياكى ئورتاقلىشالايسىز. بەزى تور بېكەتلەر ۋاقىتلىق ، قوغدىلىدىغان ياكى قوليازما ھاسىل قىلغان ئۇلىنىشلارنى ئىشلىتىدۇ ، شۇڭا توركۆرگۈچتە ئىشلەيدىغان بەت يەنىلا ئىشلەتكىلى بولىدىغان رەسىملەرنى قايتۇرماسلىقى مۇمكىن. + بىۋاسىتە رەسىم URL ياكى بەت URL نى چاپلاپ ، تەھلىل قىلىنغان رەسىم تىزىملىكىنىڭ كۆرۈنۈشىنى ساقلاڭ. + بۇ بەتتە بىر قانچە رەسىم بار بولسا رامكا ياكى تاللاش كونتروللىرىنى ئىشلىتىڭ ، سىز پەقەت ئۇلارنىڭ بەزىلىرىگە ئېھتىياجلىق بولىسىز. + ئەگەر ھېچقانداق نەرسە يۈكلەنمىسە ، بىۋاسىتە رەسىم ئۇلىنىشىنى سىناپ بېقىڭ ياكى توركۆرگۈچ ئارقىلىق چۈشۈرۈڭ. تور بېكەت تەھلىل قىلىشنى چەكلەيدۇ ياكى شەخسىي ئۇلىنىشلارنى ئىشلىتەلەيدۇ. + چوڭ-كىچىكلىكىنى تاللاڭ + رەسىمنى يېڭى ئۆلچەمگە زورلاشتىن بۇرۇن ئېنىق ، ئەۋرىشىم ، زىرائەت ۋە ماسلىشىشنى چۈشىنىڭ + كونترول شەكلى ، كاناي ۋە تەرەپ نىسبىتى + تەلەپ قىلىنغان كەڭلىك ۋە ئېگىزلىك ئەسلىدىكى نىسبەت بىلەن ماس كەلمىسە نېمە ئىش يۈز بېرىدىغانلىقىنى بەلگىلەيدۇ. ئۇ پىكسېلنى ئۇزارتىش ، نىسبەتنى ساقلاش ، قوشۇمچە رايوننى تېرىش ياكى تەگلىك كاناي قوشۇشنىڭ پەرقى. + بۇرمىلاشنى قوبۇل قىلغاندا ياكى مەنبەنىڭ نىشان بىلەن ئوخشاش نىسبەتتە بولغاندىلا ئاندىن ئېنىق ئىشلىتىڭ. + Flexible نى ئىشلىتىپ مۇھىم تەرەپتىن چوڭ-كىچىكلىكىنى تەڭشەش ئارقىلىق نىسبەتنى ساقلاڭ ، بولۇپمۇ رەسىم ۋە ئارىلاشما گۇرۇپپىلار ئۈچۈن. + Crop نى چېگراسىز قاتتىق رامكىلارغا ئىشلىتىڭ ، ياكى پۈتۈن رەسىم چوقۇم مۇقىم كاناي ئىچىدە كۆرۈنگەندە Fit نى ئىشلىتىڭ. + رەسىم چوڭلۇقى ھالىتىنى تاللاڭ + ئۆتكۈرلۈك ، سىلىقلىق ، سۈرئەت ۋە گىرۋەك بۇيۇملىرىنى كونترول قىلىدىغان قايتا سۈزگۈچنى تاللاڭ + تاسادىپىي يۇمشاقلىقسىز چوڭلۇقتا + رەسىم چوڭلۇقى ھالىتى يېڭى پېكسىلنىڭ چوڭ-كىچىكلىكىنى قانداق ھېسابلايدىغانلىقىنى كونترول قىلىدۇ. ئاددىي ھالەتلەر تېز ھەم ئالدىن پەرەز قىلغىلى بولىدۇ ، ھالبۇكى ئىلغار سۈزگۈچلەر ئىنچىكە ھالقىلارنى ياخشى ساقلىيالايدۇ ، ئەمما قىرلارنى ئۆتكۈرلەشتۈرۈپ ، ھالو ھاسىل قىلىشى ياكى چوڭ رەسىملەرگە ئۇزۇنراق ۋاقىت كېتىشى مۇمكىن. + نەتىجىنى كىچىكرەك ۋە پاكىز قىلىشقا توغرا كەلگەندە ، كۈندىلىك چوڭ-كىچىكلىكىنى ئۆزگەرتىش ئۈچۈن Basic ياكى Bilinear نى ئىشلىتىڭ. + ئىنچىكە تەپسىلاتلار ، تېكىستلەر ياكى يۇقىرى سۈپەتلىك تۆۋەنلەش مەسىلىسىدە Lanczos ، Robidoux ، Spline ياكى EWA ئۇسلۇبىدىكى مودېللارنى ئىشلىتىڭ. + پېكسىل سەنئىتى ، سىنبەلگىسى ۋە قاتتىق قىرلىق گرافىكلارغا ئەڭ يېقىن ئىشلىتىڭ ، بۇ يەردە قالايمىقان پېكسىل كۆرۈنۈشنى بۇزىدۇ. + كۆلەم رەڭ بوشلۇقى + پىكسېلنىڭ چوڭ-كىچىكلىكى ئۆزگەرتىلگەن ۋاقىتتا رەڭلەرنىڭ قانداق ئارىلاشتۇرۇلىدىغانلىقىنى قارار قىلىڭ + رېشاتكىلار ۋە قىرلارنى تەبىئىي ساقلاڭ + چوڭلۇقتىكى بوشلۇق بوشلۇقى چوڭ-كىچىكلىكىنى ئۆزگەرتكەندە ئىشلىتىلگەن ماتېماتىكىنى ئۆزگەرتىدۇ. كۆپىنچە رەسىملەر سۈكۈتتىكى ھالەتتە ياخشى ، ئەمما سىزىقلىق ياكى ئىدراك بوشلۇقى كۈچلۈك تارتقاندىن كېيىن رېشاتكا ، قېنىق قىر ۋە تويۇنغان رەڭلەرنى تېخىمۇ پاكىز كۆرسىتىدۇ. + سۈرئەت ۋە ماسلىشىشچانلىقى كىچىك رەڭ پەرقىدىن ئېشىپ كەتكەندە سۈكۈتتىكى ھالەتنى قالدۇرۇڭ. + قېنىق سىزىقلار ، UI ئېكران كۆرۈنۈشلىرى ، رېشاتكىلار ياكى رەڭلىك بەلۋاغلار چوڭ-كىچىكلىكىنى ئۆزگەرتكەندىن كېيىن خاتا كۆرۈنگەندە سىزىقلىق ياكى ئىدراك ھالىتىنى سىناپ بېقىڭ. + ھەقىقىي نىشان ئېكرانىنىڭ ئالدى-كەينىنى سېلىشتۇرۇڭ ، چۈنكى رەڭ-بوشلۇق ئۆزگىرىشى ئىنچىكە ۋە مەزمۇنغا باغلىق بولىدۇ. + چوڭ-كىچىكلىكىنى ئۆزگەرتىش + چەكتىن ئاشقان رەسىملەرنى قاچان ئاتلاپ ، قايتا خاتىرىلەش ياكى كىچىكلىتىش كېرەكلىكىنى بىلىڭ + چەكتە يۈز بەرگەن ئىشلارنى تاللاڭ + چەكلىمىنى چوڭايتىش كىچىك رەسىملىك ​​قورال بولۇپلا قالماي. ئۇنىڭ ھالىتى مەنبە سىزنىڭ چەكلىمىڭىزدە بولغاندا ياكى ئارىلاشما ھۆججەت قىسقۇچ ۋە يوللاش تەييارلىقى ئۈچۈن ئىنتايىن مۇھىم بولغان بىر تەرەپ قائىدىگە خىلاپلىق قىلغاندا ، بۇ دېتالنىڭ نېمە ئىش قىلىدىغانلىقىنى بەلگىلەيدۇ. + چەك ئىچىدىكى رەسىملەرنى تەگمەي قويۇپ قايتا چىقارماسلىقتا Skip نى ئىشلىتىڭ. + ئۆلچەم قوبۇل قىلىنغاندا Recode نى ئىشلىتىڭ ، ئەمما سىز يەنىلا يېڭى فورمات ، مېتا سانلىق مەلۇمات ھەرىكىتى ، سۈپەت ياكى ھۆججەت نامىغا ئېھتىياجلىق بولىسىز. + چەكنى بۇزغان رەسىملەر رۇخسەت قىلىنغان قۇتىغا ماس كەلمىگۈچە ماس ھالدا كىچىكلىتىلگەندە چوڭايتىڭ. + نىسبەت نىسبىتى نىشانى + قاتتىق چىقىرىش ئۆلچىمىدە سوزۇلغان يۈز ، كېسىلگەن قىر ۋە كۈتۈلمىگەن چېگرالاردىن ساقلىنىڭ + چوڭ-كىچىكلىكىنى تەڭشەشتىن بۇرۇن شەكىلنى ماسلاشتۇرۇڭ + كەڭلىك ۋە ئېگىزلىك پەقەت چوڭلۇقتىكى نىشاننىڭ يېرىمى. تەرەپ نىسبىتى رەسىمنىڭ تەبىئىي ماسلىشالايدىغان ، تېرىشقا ئېھتىياجلىق ياكى ئەتراپىدىكى كانايغا ئېھتىياجلىق ياكى ئەمەسلىكىنى بەلگىلەيدۇ. شەكلىنى تەكشۈرۈش ئالدى بىلەن كىشىنى ھەيران قالدۇرىدىغان چوڭلۇقتىكى نەتىجىنىڭ ئالدىنى ئالىدۇ. + ئېنىق ، زىرائەت ياكى Fit نى تاللاشتىن بۇرۇن مەنبە شەكلىنى نىشان شەكلى بىلەن سېلىشتۇرۇڭ. + تېما قوشۇمچە ئارقا كۆرۈنۈشنى يوقىتىپ قويغاندا ، مەنزىل قاتتىق رامكىغا ئېھتىياجلىق بولغاندا ئالدى بىلەن تېرىڭ. + ئەسلى رەسىمنىڭ ھەر بىر قىسمى كۆرۈنۈپ تۇرغاندا Fit ياكى ئەۋرىشىم ئىشلىتىڭ. + چوڭ-كىچىكلىكى ياكى نورمال چوڭلۇقى + پېكسىل قوشقاندا سۈنئىي ئەقىلگە ئېھتىياجلىق ئىكەنلىكىنى ، ئاددىي كۆلەملەشتۈرۈشنىڭ يېتەرلىك ئىكەنلىكىنى بىلىڭ + چوڭ-كىچىكلىكىنى ئىنچىكە ئارىلاشتۇرماڭ + پىكسېلنى ئۆزئارا باغلاش ئارقىلىق ئۆزگەرتىشنىڭ چوڭ-كىچىكلىكىنى ئۆزگەرتىدۇ. ئۇ ھەقىقىي تەپسىلاتلارنى كەشىپ قىلالمايدۇ. سۈنئىي ئەقىلنىڭ يۇقىرى كۆتۈرۈلۈشى تېخىمۇ ئۆتكۈر كۆرۈنىدىغان تەپسىلاتلارنى بارلىققا كەلتۈرەلەيدۇ ، ئەمما ئۇ ئاستا ھەم توقۇلمىلارنى خىيالىغا كەلتۈرۈشى مۇمكىن ، شۇڭا توغرا تاللاش سىزنىڭ ماسلىشىشچانلىقىڭىز ، سۈرئىتىڭىز ياكى كۆرۈنۈشنى ئەسلىگە كەلتۈرۈشىڭىزگە باغلىق. + كىچىك كۆرۈنۈش ، يۈكلەش چەكلىمىسى ، ئېكران رەسىمى ۋە ئاددىي ئۆلچەم ئۆزگەرتىش ئۈچۈن نورمال چوڭلۇقنى ئىشلىتىڭ. + كىچىك ياكى يۇمشاق رەسىمنىڭ چوڭ-كىچىكلىكىگە تېخىمۇ ياخشى قاراشقا توغرا كەلگەندە ، سۈنئىي ئەقلىي ئىقتىدارنى ئىشلىتىڭ. + سۈنئىي ئەقىل يۇقىرى كۆتۈرۈلگەندىن كېيىنكى يۈز ، تېكىست ۋە ئەندىزىلەرنى سېلىشتۇرۇڭ ، چۈنكى ھاسىل قىلىنغان تەپسىلاتلار قايىل قىلارلىق ، ئەمما توغرا ئەمەس. + سۈزگۈچ زاكاز ئىشلىرى + تازىلاش ، رەڭ ، ئۆتكۈرلۈك ۋە ئاخىرقى پىرىسلاشنى قەستەن تەرتىپ بويىچە ئىشلىتىڭ + تېخىمۇ پاكىز سۈزگۈچ دۆۋىسى ياساڭ + سۈزگۈچلەر ھەمىشە ئالماشتۇرۇلمايدۇ. ئۆتكۈرلىشىشتىن بۇرۇن تۇتۇق بولۇش ، OCR دىن بۇرۇن سېلىشتۇرما كۈچەيتىش ياكى پىرىسلاشتىن بۇرۇن رەڭ ئۆزگىرىشى ئوخشاش كونترولدىن ئوخشىمايدىغان مەھسۇلات ھاسىل قىلالايدۇ. + ئالدى بىلەن كېسىش ۋە ئايلاندۇرۇش ، كېيىن سۈزگۈچلەر پەقەت قالغان پېكسىلدىلا ئىشلەيدۇ. + ئۆتكۈر ياكى ئۇسلۇبتىكى ئۈنۈمدىن بۇرۇن ئاشكارلىنىش ، ئاق تەڭپۇڭلۇق ۋە سېلىشتۇرما ئىشلىتىڭ. + ئاخىرقى چوڭلۇقتا ۋە پىرىسلاشنى ئاخىرلاشتۇرۇڭ ، شۇڭا ئالدىن كۆزىتىلگەن تەپسىلاتلار ئېكسپورتنى ئالدىن پەرەز قىلالايدۇ. + تەگلىك گىرۋىكىنى ئوڭشاڭ + ئاپتوماتىك ئېلىۋېتىلگەندىن كېيىن گالوس ، ئېشىپ قالغان سايە ، چاچ ، تۆشۈك ۋە يۇمشاق سىزىقلارنى پاكىز تازىلاڭ + كېسىشنى قەستەن كۆرسىتىدۇ + ئاپتوماتىك ماسكا ھەمىشە بىر قارىماققا ياخشى كۆرۈنىدۇ ، ئەمما يورۇق ، قاراڭغۇ ياكى ئەندىزە ئارقا كۆرۈنۈشتىكى مەسىلىلەرنى ئاشكارىلايدۇ. قىرلارنى تازىلاش تېز كېسىش بىلەن قايتا ئىشلەتكىلى بولىدىغان چاپلاق ، بەلگە ياكى مەھسۇلات رەسىمىنىڭ پەرقى. + سېلىشتۇرما ئارقا كۆرۈنۈشكە قارشى نەتىجىنى كۆرۈپ ، ھالوس ۋە يوقاپ كەتكەن تەپسىلاتلارنى ئاشكارلاڭ. + چاچ ، بۇلۇڭ ۋە سۈزۈك نەرسىلەرنى ئەسلىگە كەلتۈرۈش ئارقىلىق ئەتراپىدىكى قالدۇق ماددىلارنى ئۆچۈرۈڭ. + كېسىشمە لايىھەگە قويۇلغاندا ئاخىرقى تەگلىك رەڭگىدە كىچىك سىناق ئېكىسپورت قىلىڭ. + ئوقۇغىلى بولىدىغان سۇ بەلگىسى + رەسىمنى بۇزۇۋەتمەي ئوچۇق ، قاراڭغۇ ، ئالدىراش ۋە كېسىلگەن رەسىملەردە بەلگە كۆرۈنۈڭ + رەسىمنى يوشۇرماي قوغداڭ + بىر رەسىمگە ياخشى كۆرۈنگەن سۇ بەلگىسى يەنە بىر رەسىمدە يوقاپ كېتىشى مۇمكىن. چوڭ-كىچىكلىكى ، ئېنىقلىقى ، سېلىشتۇرمىسى ، ئورۇنلاشتۇرۇلۇشى ۋە تەكرارلىنىشى بىرىنچى تۈركۈمدىلا ئەمەس ، پۈتۈن تۈركۈمگە تاللىنىشى كېرەك. + بارلىق ھۆججەتلەرنى ئېكسپورت قىلىشتىن بۇرۇن تۈركۈمدىكى ئەڭ يورۇق ۋە ئەڭ قاراڭغۇ رەسىملەردە بەلگىنى سىناڭ. + چوڭ تەكرارلانغان بەلگىلەر ئۈچۈن تۆۋەن سۈزۈكلۈك ۋە كىچىك بۇلۇڭ بەلگىسى ئۈچۈن كۈچلۈك سېلىشتۇرما ئىشلىتىڭ. + بەلگە قايتا ئىشلىتىشنىڭ ئالدىنى ئېلىش ئۈچۈن بولمىسا ، مۇھىم چىراي ، تېكىست ۋە مەھسۇلات تەپسىلاتلىرىنى ئېنىق ساقلاڭ. + بىر رەسىمنى كاھىشلارغا بۆلۈڭ + بىر رەسىمنى قۇر ، ستون ، ئىختىيارى پىرسەنت ، فورمات ۋە سۈپەت بويىچە كېسىڭ + تور ۋە زاكاز قىلىنغان پارچىلارنى تەييارلاڭ + رەسىم بۆلۈش بىر مەنبەلىك رەسىمدىن كۆپ چىقىش ھاسىل قىلىدۇ. ئۇ قۇر ۋە ستون سانىغا ئايرىلىدۇ ، قۇر ياكى ستون نىسبىتىنى تەڭشىيەلەيدۇ ۋە تاللانغان چىقىرىش شەكلى ۋە سۈپىتى بىلەن پارچىلارنى تېجەپ قالالايدۇ ، بۇ تورلار ، بەت بۆلەكلىرى ، پۈتۈن مەنزىرىلەر ۋە زاكاز قىلىنغان ئىجتىمائىي يازمىلارغا پايدىلىق. + ئەسلى رەسىمنى تاللاڭ ، ئاندىن لازىملىق قۇر ۋە ستون سانىنى بەلگىلەڭ. + پارچىلار ئوخشاش چوڭلۇقتا بولمىسا ، قۇر ياكى ستون نىسبىتىنى تەڭشەڭ. + ساقلاشتىن بۇرۇن چىقىرىش شەكلى ۋە سۈپىتىنى تاللاڭ ، شۇڭا ھاسىل قىلىنغان ھەر بىر ئەسەر ئوخشاش ئېكسپورت قائىدىسىنى ئىشلىتىدۇ. + رەسىم سىزىقىنى كېسىڭ + تىك ياكى توغرىسىغا توغرىلانغان رايونلارنى چىقىرىپ تاشلاپ ، قالغان قىسىملىرىنى قايتا بىرلەشتۈرۈڭ + بوشلۇق قالدۇرماي بىر رايوننى ئېلىۋېتىڭ + رەسىم كېسىش نورمال زىرائەتلەرگە ئوخشىمايدۇ. ئۇ تاللانغان تىك ياكى توغرىسىغا سىزىقنى چىقىرىپ تاشلاپ ، قالغان رەسىم بۆلەكلىرىنى بىرلەشتۈرەلەيدۇ. تەتۈر ھالەت تاللانغان رايوننى ساقلاپ قالىدۇ ، ھەمدە تەتۈر يۆنىلىشنى ئىشلىتىش ئارقىلىق تاللانغان تىك تۆت بۇلۇڭنى ساقلايدۇ. + يان رايون ، تۈۋرۈك ياكى ئوتتۇرا سىزىقلارغا تىك كېسىش ئىشلىتىڭ. بايراق ، بوشلۇق ياكى قۇرلارغا توغرىسىغا كېسىش ئىشلىتىڭ. + تاللانغان قىسمىنى ئېلىۋەتمەي ، ساقلاپ قويماقچى بولغاندا ، ئوقنىڭ تەتۈر يۆنىلىشىنى قوزغىتىڭ. + كېسىلگەندىن كېيىن قىرلارنى ئالدىن كۆرۈڭ ، چۈنكى چىقىرىۋېتىلگەن رايون مۇھىم تەپسىلاتلارنى بېسىپ ئۆتكەندە بىرلەشتۈرۈلگەن مەزمۇن تۇيۇقسىز كۆرۈنىدۇ. + چىقىرىش فورماتىنى تاللاڭ + مەزمۇن ۋە مەنزىلگە ئاساسەن JPEG ، PNG ، WebP ، AVIF ، JXL ياكى PDF نى تاللاڭ + مەنزىل فورماتنى بەلگىلىسۇن + ئەڭ ياخشى فورمات رەسىمنىڭ نېمە ۋە قەيەردە ئىشلىتىلىدىغانلىقىغا باغلىق. رەسىم ، ئېكران رەسىمى ، سۈزۈك مۈلۈك ، ھۆججەت ۋە ھەرىكەتچان ھۆججەتلەرنىڭ ھەممىسى خاتا فورماتقا مەجبۇرلانغاندا ئوخشىمىغان ئۇسۇلدا مەغلۇپ بولىدۇ. + سۈزۈكلۈك ۋە ئېنىق پېكسىل تەلەپ قىلىنمىغان ۋاقىتتا JPEG نى كەڭ رەسىم ماسلىشىشچانلىقى ئۈچۈن ئىشلىتىڭ. + ئۆتكۈر ئېكران كۆرۈنۈشلىرى ، UI رەسىملىرى ، سۈزۈك گرافىك ۋە زىيانسىز تەھرىرلەش ئۈچۈن PNG نى ئىشلىتىڭ. + ئىخچام زامانىۋى چىقىرىش مەسىلىسىدە WebP ، AVIF ياكى JXL نى ئىشلىتىڭ ، قوبۇل قىلىش دېتالى ئۇنى قوللايدۇ. + ئاۋاز مۇقاۋىسىنى چىقىرىڭ + قىستۇرما پىلاستىنكا سەنئىتىنى ياكى ئىشلەتكىلى بولىدىغان مېدىيا رامكىسىنى ئاۋاز ھۆججىتىدىن PNG رەسىمى سۈپىتىدە ساقلاڭ + ئاۋاز ھۆججىتىدىن سەنئەت ئەسەرلىرىنى چىقىرىڭ + Audio Covers ئاندىرويىد مېدىيا ئۇچۇرلىرىنى ئىشلىتىپ ئاۋاز ھۆججىتىدىن قىستۇرۇلغان سەنئەت ئەسەرلىرىنى ئوقۇيدۇ. قىستۇرما سەنئەت ئەسەرلىرىنى ئىشلەتكىلى بولمىغاندا ، ئەگەر ئاندىرويىد تەمىنلىيەلەيدىغان بولسا ، يىغىۋالغۇچى مېدىيا رامكىسىغا قايتىپ كېلىشى مۇمكىن. ئەگەر مەۋجۇت بولمىسا ، بۇ قورال تارتقىلى بولىدىغان رەسىم يوقلىقىنى دوكلات قىلىدۇ. + ئاۋاز قاپقىقىنى ئېچىڭ ھەمدە مۆلچەردىكى مۇقاۋا سەنئىتى بىلەن بىر ياكى بىر قانچە ئاۋاز ھۆججىتىنى تاللاڭ. + ساقلانغان ياكى ھەمبەھىرلىنىشتىن بۇرۇن چىقىرىلغان مۇقاۋىنى كۆزدىن كەچۈرۈڭ ، بولۇپمۇ ئېقىن ياكى خاتىرىلەش پروگراممىلىرىدىكى ھۆججەتلەر ئۈچۈن. + ئەگەر رەسىم تېپىلمىسا ، ئاۋاز ھۆججىتىدە قىستۇرما مۇقاۋا يوق ياكى ئاندىرويىد ئىشلىتىشكە بولىدىغان رامكىنى ئاشكارىلىماسلىقى مۇمكىن. + چېسلا ۋە ئورۇن ئۇچۇرى + ۋاقىت مۇھىم بولغاندا نېمىلەرنى ساقلاشنى قارار قىلىڭ ، ئەمما GPS ياكى ئۈسكۈنىنىڭ سانلىق مەلۇماتلىرى ئاشكارىلانماسلىقى كېرەك + پايدىلىق مېتا سانلىق مەلۇماتلارنى شەخسىي مېتا سانلىق مەلۇماتتىن ئايرىڭ + Metadata نىڭ ھەممىسى ئوخشاشلا خەتەرلىك ئەمەس. رەسىمگە تارتىش ۋاقتى ئارخىپ ۋە تەرتىپكە سېلىشقا پايدىلىق ، GPS ، ئۈسكۈنە تەرتىپلىك ساھە ، يۇمشاق دېتال خەتكۈچلىرى ياكى ئىگىدارچىلىق مەيدانلىرى كۆزلىگەندىنمۇ كۆپ نەرسىلەرنى ئاشكارىلىيالايدۇ. + پىلاستىنكا ، سايىلەش ياكى تۈر تارىخىغا ۋاقىت تەرتىپى مۇھىم بولغاندا چېسلا ۋە ۋاقىت بەلگىسىنى ساقلاڭ. + ناتونۇش كىشىلەرگە رەسىم ئورتاقلىشىش ياكى رەسىم يوللاشتىن بۇرۇن GPS ۋە ئۈسكۈنىنى پەرقلەندۈرۈش بەلگىسىنى ئۆچۈرۈڭ. + ئەۋرىشكە ئېكىسپورت قىلىڭ ھەمدە مەخپىيەتلىك تەلىپى قاتتىق بولغاندا EXIF ​​نى قايتا تەكشۈرۈڭ. + ھۆججەت ئىسمى سوقۇلۇشتىن ساقلىنىڭ + نۇرغۇن ھۆججەتلەر image.jpg ياكى screenshot.png غا ئوخشاش ئىسىملارنى ئورتاقلاشقاندا تۈركۈملەپ چىقىشنى قوغداڭ + ھەر بىر چىقىرىش نامىنى ئۆزگىچە قىلىڭ + كۆپەيتىلگەن ھۆججەت ئىسمى رەسىملەر ، ئېكران رەسىمى ، بۇلۇت قىسقۇچ ياكى كۆپ كامېرادىن كەلگەن ۋاقىتتا كۆپ ئۇچرايدۇ. ياخشى ئەندىزە تاسادىپىي ئالماشتۇرۇشنىڭ ئالدىنى ئالىدۇ ھەمدە چىقىش مەنبەسىنى مەنبەسىگە قايتۇرىدۇ. + تەھرىرلەنگەن نۇسخىسىنى تونۇغاندا ئەسلىي ئىسىم ۋە قوشۇمچە قوشۇڭ. + چوڭ ئارىلاشما تۈركۈملەرنى بىر تەرەپ قىلغاندا تەرتىپ ، ۋاقىت تامغىسى ، ئالدىن بەلگىلەش ياكى ئۆلچەم قوشۇڭ. + ئىسىم قويۇش ئەندىزىسىنىڭ سوقۇلمايدىغانلىقىنى جەزملەشتۈرمىگۈچە ، خىزمەت ئېقىمىنى قاپلاشتىن ساقلىنىڭ. + ساقلاش ياكى ئورتاقلىشىش + داۋاملاشقان ھۆججەت بىلەن باشقا ئەپكە ۋاقىتلىق تاپشۇرۇشنى تاللاڭ + توغرا نىيەت بىلەن ئېكسپورت قىلىش + ساقلاش ۋە ئورتاقلىشىش ئوخشاش ھېس قىلالايدۇ ، ئەمما ئۇلار ئوخشىمىغان مەسىلىلەرنى ھەل قىلىدۇ. ساقلاش كېيىن تاپقىلى بولىدىغان ھۆججەت قۇرىدۇ. ھەمبەھىرلىنىش ھاسىل قىلىنغان نەتىجىنى ئاندىرويىد ئارقىلىق باشقا بىر ئەپكە ئەۋەتىدۇ ھەمدە چىداملىق يەرلىك نۇسخىسىنى قالدۇرماسلىقى مۇمكىن. + چىقىرىش چوقۇم مەلۇم بىر ھۆججەت قىسقۇچتا ساقلىنىشى ، زاپاسلىنىشى ياكى كېيىن قايتا ئىشلىتىلىشى كېرەك. + تېز ئۇچۇر ، ئېلېكترونلۇق خەت قوشۇمچە ھۆججەتلىرى ، ئىجتىمائىي يوللاش ياكى نەتىجىنى باشقا تەھرىرلىگۈچكە ئەۋەتىش ئۈچۈن ھەمبەھىرنى ئىشلىتىڭ. + قوبۇل قىلىش دېتالى ئىشەنچسىز بولغاندا ياكى مەغلۇپ بولغان ئۈلۈشنى قايتا قۇرۇش ئادەمنى بىزار قىلىدىغان ۋاقىتتا ئاۋۋال تېجەڭ. + PDF بەت چوڭلۇقى ۋە گىرۋەكلىرى + ھۆججەت قۇرۇشتىن بۇرۇن قەغەزنىڭ چوڭ-كىچىكلىكى ، ماسلىشىشچانلىقى ۋە نەپەس ئېلىش ئۆيىنى تاللاڭ + رەسىم بەتلىرىنى بېسىپ چىقىرىشقا ۋە ئوقۇشقا بولىدۇ + ئۇلارنىڭ شەكلى تاللانغان قەغەزنىڭ چوڭلۇقىغا ماس كەلمىسە ، رەسىملەر ئوڭايسىز PDF بېتىگە ئايلىنىپ قالىدۇ. گىرۋەك ، ماس ھالەت ۋە بەت يۆنىلىشى مەزمۇننىڭ كېسىلگەن ، كىچىك ، سوزۇلغان ياكى ئوقۇشقا راھەت ياكى ئەمەسلىكىنى بەلگىلەيدۇ. + PDF بېسىپ چىقارغاندا ياكى جەدۋەلگە يوللىغاندا ھەقىقىي قەغەز چوڭلۇقى ئىشلىتىڭ. + سىكانېرلاش ۋە ئېكران رەسىمى ئۈچۈن گىرۋەك ئىشلىتىڭ ، بۇنداق بولغاندا تېكىست بەت يۈزىگە قارىمايدۇ. + ئەسلى رەسىملەر ئارىلاش يۆنىلىشتە رەسىم ۋە مەنزىرە بەتلىرىنى ئالدىن كۆرۈڭ. + سايىلەشنى تازىلاڭ + PDF ياكى OCR دىن بۇرۇن قەغەز قىر ، سايە ، سېلىشتۇرما ، ئايلىنىش ۋە ئوقۇشچانلىقىنى ياخشىلاڭ + ئارخىپلاشتۇرۇشتىن بۇرۇن بەت تەييارلاڭ + سىكانىرلاشنى تېخنىكىلىق تۇتقىلى بولىدۇ ، ئەمما يەنىلا ئوقۇش تەس. PDF ئېكسپورتىدىن بۇرۇنقى كىچىك تازىلاش باسقۇچلىرى پىرىسلاش تەڭشەكلىرىدىنمۇ مۇھىم ، بولۇپمۇ تالون ، قولدا يېزىلغان خاتىرە ۋە تۆۋەن نۇرلۇق بەتلەر ئۈچۈن. + توغرا كۆز قاراش ۋە ئۈستەل گىرۋىكى ، بارماق ، سايە ۋە تەگلىك قالايمىقانچىلىقىنى كېسىڭ. + سېلىشتۇرۇشنى ئەستايىدىللىق بىلەن ئاشۇرۇڭ ، شۇڭا تامغا ، ئىمزا ياكى قەلەم بەلگىسىنى بۇزماي تېكىست تېخىمۇ ئېنىق بولىدۇ. + OCR نى ئىجرا قىلىپ ، بەت تىك ۋە ئوقۇغىلى بولغاندىن كېيىن ئاندىن مۇھىم سانلارنى قولدا تەكشۈرۈپ بېقىڭ. + PDF نى پىرىسلاش ، كۈلرەڭ ياكى رېمونت قىلىش + يېنىك ، باسقىلى بولىدىغان ياكى قايتا ياسالغان ھۆججەت نۇسخىلىرى ئۈچۈن بىر ھۆججەت PDF مەشغۇلاتىنى ئىشلىتىڭ + PDF تازىلاش مەشغۇلاتىنى تاللاڭ + PDF قوراللىرى پىرىسلاش ، كۈلرەڭ ئايلىنىش ۋە رېمونت قىلىش ئۈچۈن ئايرىم مەشغۇلاتلارنى ئۆز ئىچىگە ئالىدۇ. پىرىسلاش ۋە كۈلرەڭلىك خىزمىتى ئاساسلىقى قىستۇرما رەسىملەر ئارقىلىق ئىشلەيدۇ ، رېمونت قىلىش بولسا PDF قۇرۇلمىسىنى قايتا قۇرۇپ چىقىدۇ. بۇلارنىڭ ھېچقايسىسىنى ھەر بىر ھۆججەتكە كاپالەتلىك قىلىش دەپ قاراشقا بولمايدۇ. + ھۆججەتتە ھەمبەھىرلىنىش ئۈچۈن ھۆججەت ۋە ھۆججەت چوڭلۇقى بار بولغاندا Compress PDF نى ئىشلىتىڭ. + ھۆججەتنى بېسىپ چىقىرىش ئاسان ياكى رەڭلىك رەسىملەرگە ئېھتىياجلىق بولمىغاندا ، كۈلرەڭلىكنى ئىشلىتىڭ. + ھۆججەت ئېچىلمىسا ياكى ئىچكى قۇرۇلمىغا بۇزغۇنچىلىق قىلغاندا ، PDF نى كۆپەيتىشتە ئىشلىتىڭ ، ئاندىن قايتا ياسالغان ھۆججەتنى تەكشۈرۈپ بېقىڭ. + PDF دىن رەسىملەرنى چىقىرىڭ + قىستۇرۇلغان PDF رەسىملىرىنى ئەسلىگە كەلتۈرۈپ ، چىقىرىلغان نەتىجىنى ئارخىپقا قاچىلاڭ + ئەسلى رەسىملەرنى ھۆججەتلەردىن ساقلاڭ + قىستۇرما رەسىملەر قىستۇرما رەسىم ئوبيېكتى ئۈچۈن PDF سايىلەيدۇ ، ئىمكانقەدەر كۆپەيتىلگەن نۇسخىسىنى ئاتلاپ ، ئەسلىگە كەلتۈرۈلگەن رەسىملەرنى ھاسىل قىلىنغان ZIP ئارخىپىدا ساقلايدۇ. ئۇ پەقەت تېكىست ، ۋېكتور سىزمىسى ياكى كۆرۈنگەن ھەر بىر بەتنى ئېكران رەسىمى ئەمەس ، بەلكى PDF غا قىستۇرۇلغان رەسىملەرنىلا چىقىرىدۇ. + PDF نىڭ ئىچىدە ساقلانغان رەسىملەرگە ئېھتىياجلىق بولغاندا ، مۇندەرىجە ياكى سكاننېرلانغان مۈلۈككە ئوخشاش رەسىملەرنى ئىشلىتىش كېرەك. + تېكىست ۋە ئورۇنلاشتۇرۇش قاتارلىق تولۇق بەتلەرگە ئېھتىياجلىق بولغاندا ، ئۇنىڭ ئورنىغا PDF نى رەسىم ياكى بەت ئېلىشقا ئىشلىتىڭ. + ئەگەر قورال قىستۇرۇلمىغان رەسىملەرنى دوكلات قىلسا ، بۇ بەت تېكىست / ۋېكتور مەزمۇنى بولۇشى ياكى رەسىملەرنى تارتىپ چىقىرىشقا بولىدىغان نەرسە سۈپىتىدە ساقلاشقا بولمايدۇ. + مېتا سانلىق مەلۇمات ، تەكشىلىك ۋە بېسىش + ھۆججەت خاسلىقىنى تەھرىرلەش ، بەتلەرنى رەتلەش ياكى قەستەن بېسىش لايىھىسىنى تەييارلاش + ئاخىرقى ھۆججەت ھەرىكىتىنى تاللاڭ + PDF مېتا سانلىق مەلۇمات ، تۈزلەش ۋە بېسىپ چىقىرىش ئوخشىمىغان ئاخىرقى باسقۇچتىكى مەسىلىلەرنى ھەل قىلىدۇ. Metadata ھۆججەت خاسلىقىنى كونترول قىلىدۇ ، Flatten PDF بەتلەرنى تەھرىرلىگىلى بولمايدىغان مەھسۇلاتقا ئايلاندۇرىدۇ ، PDF بېسىش بەت چوڭلۇقى ، يۆنىلىشى ، گىرۋىكى ۋە ھەر بىر بەتنى كونترول قىلىدىغان يېڭى ئورۇنلاشتۇرۇش تەييارلايدۇ. + ئاپتور ، تېما ، ئاچقۇچلۇق سۆز ، ئىشلەپچىقارغۇچى ياكى مەخپىيەتلىك تازىلاش مەسىلىسىدە Metadata نى ئىشلىتىڭ. + كۆرۈنگەن بەت مەزمۇنىنى ئايرىم PDF ئوبيېكتى قىلىپ تەھرىرلەش تېخىمۇ قىيىنلاشقاندا Flatten PDF نى ئىشلىتىڭ. + ئىختىيارى بەت چوڭلۇقى ، يۆنىلىشى ، گىرۋەكلىرى ياكى ھەر بىر بەتتە بىر قانچە بەت لازىم بولغاندا ، بېسىش PDF نى ئىشلىتىڭ. + OCR ئۈچۈن رەسىم تەييارلاڭ + OCR دىن تېكىست ئوقۇشنى تەلەپ قىلىشتىن بۇرۇن زىرائەت ، ئايلىنىش ، سېلىشتۇرما ، ئېنىقلىما ۋە كۆلەم ئىشلىتىڭ + OCR تېخىمۇ پاكىز پېكسىل بېرىڭ + OCR خاتالىقى كۆپىنچە OCR ماتورىدىن ئەمەس كىرگۈزۈش سۈرىتىدىن كېلىدۇ. ئەگرى بەتلەر ، تۆۋەن سېلىشتۇرما ، تۇتۇق ، سايە ، كىچىك تېكىست ۋە ئارىلاشما ئارقا كۆرۈنۈشلەرنىڭ ھەممىسى تونۇش سۈپىتىنى تۆۋەنلىتىدۇ. + تېكست رايونىغا كېسىڭ ۋە رەسىمنى ئايلاندۇرۇڭ ، شۇڭا سىزىقلار تونۇشتىن بۇرۇن توغرىسىغا توغرىلىنىدۇ. + سېلىشتۇرۇشنى كۈچەيتىڭ ياكى خەتلەرنى ئوقۇشقا قولايلىق بولغاندىلا ئاندىن ئاق-قارىنى پاكىز تازىلاڭ. + كىچىك تېكىستنى ئوتتۇراھال چوڭلۇقتا چوڭايتىڭ ، ئەمما ساختا قىر پەيدا قىلىدىغان تاجاۋۇزچىلىق ئۆتكۈرلۈكىدىن ساقلىنىڭ. + QR مەزمۇنىنى تەكشۈرۈڭ + كودقا ئىشىنىشتىن بۇرۇن ئۇلىنىش ، Wi-Fi كىنىشكىسى ، ئالاقىلىشىش ۋە ھەرىكەتلەرنى تەكشۈرۈڭ + كودلانغان تېكىستنى ئىشەنچسىز كىرگۈزۈش دەپ قاراڭ + QR كودى URL ، ئالاقىلىشىش كارتىسى ، Wi-Fi پارولى ، كالېندار پائالىيىتى ، تېلېفون نومۇرى ياكى ئاددىي تېكىستنى يوشۇرالايدۇ. كود يېنىدىكى كۆرۈنگەن بەلگە ئەمەلىي يۈك يۈكىگە ماس كەلمەسلىكى مۇمكىن ، شۇڭا كودلاش مەزمۇنىنى ئىجرا قىلىشتىن بۇرۇن تەكشۈرۈپ بېقىڭ. + ئېچىشتىن بۇرۇن تولۇق كودلانغان URL نى تەكشۈرۈڭ ، بولۇپمۇ قىسقارتىلغان ياكى ناتونۇش دائىرە. + Wi-Fi ، ئالاقىلىشىش ، كالېندار ، تېلېفون ۋە قىسقا ئۇچۇر كودىغا دىققەت قىلىڭ ، چۈنكى ئۇلار سەزگۈر ھەرىكەتلەرنى قوزغىتالايدۇ. + مەزمۇننى دەرھال ئېچىشنىڭ ئورنىغا باشقا جايدا دەلىللەشكە توغرا كەلگەندە ئالدى بىلەن كۆچۈرۈڭ. + QR كودى ھاسىل قىلىڭ + ئاددىي تېكىست ، URL ، Wi-Fi ، ئالاقىلىشىش ، ئېلېكترونلۇق خەت ، تېلېفون ، قىسقا ئۇچۇر ۋە ئورۇن ئۇچۇرىدىن كود ھاسىل قىلىڭ + مۇۋاپىق QR يۈك يۈكىنى ياساڭ + QR ئېكرانى كىرگۈزۈلگەن مەزمۇندىن كود ھاسىل قىلالايدۇ شۇنداقلا بار بولغانلىرىنى سىكانىرلىيالايدۇ. قۇرۇلمىلىق QR تىپلىرى باشقا ئەپلەر چۈشىنىدىغان فورماتتىكى سانلىق مەلۇماتلارنى كودلاشقا ياردەم بېرىدۇ ، مەسىلەن Wi-Fi كىرىش تەپسىلاتلىرى ، ئالاقىلىشىش كارتىسى ، ئېلېكترونلۇق خەت ساندۇقى ، تېلېفون نومۇرى ، قىسقا ئۇچۇر ، URL ياكى جۇغراپىيىلىك كوئوردېنات. + ئاددىي خاتىرە ئۈچۈن ئاددىي تېكىستنى تاللاڭ ياكى كود Wi-Fi ، ئالاقىلىشىش ، URL ، ئېلېكترونلۇق خەت ، تېلېفون ، قىسقا ئۇچۇر ياكى ئورۇن ئۇچۇرى سۈپىتىدە ئېچىلغاندا قۇرۇلمىلىق تىپنى ئىشلىتىڭ. + ھاسىل قىلىنغان كودنى ھەمبەھىرلەشتىن بۇرۇن ھەر بىر ساھەنى تەكشۈرۈپ بېقىڭ ، چۈنكى كۆرۈنگەن ئالدىن كۆرۈش پەقەت سىز كىرگۈزگەن يۈك يۈكىنىلا كۆرسىتىدۇ. + ساقلانغان QR كودىنى سىكانېر ئارقىلىق بېسىپ چىقىرىڭ ، ئاشكارا ئېلان قىلىنىدۇ ياكى باشقىلار ئىشلىتىدۇ. + تەكشۈرۈش ھالىتىنى تاللاڭ + ھۆججەت ياكى تېكىستتىن ھەش-پەش دېگۈچە ھېسابلاڭ ، بىر ھۆججەتنى سېلىشتۇرۇڭ ياكى نۇرغۇن ھۆججەتلەرنى تۈركۈملەپ تەكشۈرۈڭ + كۆرۈنۈشنى ئەمەس ، مەزمۇننى تەكشۈرۈپ بېقىڭ + Checksum قورالىدا ھۆججەتلەرنى يۇيۇش ، تېكىستنى تېزلىتىش ، مەلۇم ھەش-پەش دېگۈچە سېلىشتۇرۇش ۋە تۈركۈملەپ سېلىشتۇرۇش ئۈچۈن ئايرىم بەتكۈچ بار. تەكشۈرۈش جەدۋىلى تاللانغان ئالگورىزىمنىڭ بايت-بايت كىملىكىنى ئىسپاتلايدۇ. ئۇ ئىككى رەسىمنىڭ پەقەت ئوخشايدىغانلىقىنى ئىسپاتلاپ بېرەلمەيدۇ. + ھۆججەتلەرنى ھېسابلاش ۋە كىرگۈزۈلگەن ياكى چاپلانغان تېكىست سانلىق مەلۇماتلىرى ئۈچۈن Text Hash نى ئىشلىتىڭ. + باشقىلار سىزگە بىر ھۆججەت ئۈچۈن كۈتكەن تەكشۈرۈشنى بەرگەندە سېلىشتۇرۇشنى ئىشلىتىڭ. + Batch نى ئىشلىتىڭ نۇرغۇن ھۆججەتلەر بىر ئۆتكەلدە ئالدىراش ياكى دەلىللەشكە ئېھتىياجلىق بولغاندا ، ئاندىن تاللانغان ھېسابلاش ئۇسۇلىنى ئىزچىل ساقلاڭ. + چاپلاش تاختىسى مەخپىيەتلىكى + شەخسىي كۆچۈرۈلگەن سانلىق مەلۇماتلارنى ئېھتىياتسىزلىقتىن ئاشكارىلىماي ئاپتوماتىك چاپلاش ئىقتىدارىنى ئىشلىتىڭ + كۆچۈرۈلگەن مەزمۇننى قەستەن ساقلاڭ + چاپلاش تاختىسىنى ئاپتوماتلاشتۇرۇش قۇلايلىق ، ئەمما كۆچۈرۈلگەن تېكىستتە پارول ، ئۇلىنىش ، ئادرېس ، بەلگە ياكى شەخسىي خاتىرىلەر بولۇشى مۇمكىن. چاپلاش تاختىسى ئارقىلىق كىرگۈزۈشنى ئادەت ۋە مەخپىيەتلىك مۆلچەرىڭىزگە ماس كېلىدىغان سۈرئەت ئىقتىدارى دەپ قاراڭ. + ئويلىمىغان يەردىن قوراللاردا شەخسىي تېكىست كۆرۈنسە ئاپتوماتىك چاپلاش تاختىسىنى ئىشلىتىشنى چەكلەڭ. + قەستەن ساقلاشنى ئىشلىتىڭ ، پەقەت قەستەن نەتىجىنى ساقلاشتىن ساقلىنىڭ. + سەزگۈر تېكىست ، QR سانلىق مەلۇماتلىرى ياكى Base64 يۈك يۈكىنى بىر تەرەپ قىلغاندىن كېيىن چاپلاش تاختىسىنى تازىلاڭ ياكى ئالماشتۇرۇڭ. + رەڭ فورماتى + باشقا يەرگە چاپلاشتىن بۇرۇن HEX ، RGB ، HSV ، ئالفا ۋە كۆپەيتىلگەن رەڭ قىممىتىنى چۈشىنىڭ + نىشان كۈتكەن فورماتنى كۆچۈرۈڭ + ئوخشاش كۆرۈنىدىغان رەڭنى بىر قانچە خىل يېزىشقا بولىدۇ. لايىھىلەش قورالى ، CSS مەيدانى ، ئاندىرويىد رەڭ قىممىتى ياكى رەسىم تەھرىرلىگۈچى باشقىچە تەرتىپ ، ئالفا بىر تەرەپ قىلىش ياكى رەڭ ئەندىزىسىنى ئۈمىد قىلىشى مۇمكىن. + ئىخچام رەڭلىك كودلارنى ئۈمىد قىلىدىغان تور ، لايىھىلەش ياكى باشتېما ساھەلىرىگە چاپلىغاندا HEX نى ئىشلىتىڭ. + قانال ، يورۇقلۇق ، تويۇنۇش ياكى رەڭنى قولدا تەڭشەشكە توغرا كەلگەندە RGB ياكى HSV نى ئىشلىتىڭ. + سۈزۈكلۈك مۇھىم بولغاندا ئالفانى ئايرىم تەكشۈرۈڭ ، چۈنكى بەزى مەنزىللەر بۇنىڭغا سەل قارايدۇ ياكى باشقىچە تەرتىپ ئىشلىتىدۇ. + رەڭ ئامبىرىنى كۆرۈڭ + ئىسمى ياكى HEX ئارقىلىق ئىسىم قويۇلغان رەڭلەرنى ئىزدەڭ ۋە ياقتۇرىدىغانلارنى ئۈستى تەرىپىگە ساقلاڭ + قايتا ئىشلەتكىلى بولىدىغان رەڭلەرنى تېپىڭ + رەڭ كۇتۇپخانىسى ئىسىملىك ​​رەڭ توپلىمىنى يۈكلەيدۇ ، ئۇنى ئىسىم بويىچە رەتلەيدۇ ، سۈزگۈچ رەڭ ئىسمى ياكى HEX قىممىتى بويىچە سۈزۈپ ، ياقتۇرىدىغان رەڭلەرنى ساقلايدۇ ، شۇڭا مۇھىم مەزمۇنلارغا ئېرىشىش ئاسان بولىدۇ. رەسىمدىن ئەۋرىشكە ئېلىشنىڭ ئورنىغا ھەقىقىي ئىسىم قويۇلغان رەڭگە ئېھتىياجلىق بولغاندا پايدىلىق. + ئائىلىنى بىلگەندە رەڭلىك ئىسىم بىلەن ئىزدەڭ ، مەسىلەن قىزىل ، كۆك ، تاختاي ياكى يالپۇز. + ئاللىقاچان رەقەملىك رەڭگە ئىگە بولۇپ ، ماس كېلىدىغان ياكى يېقىن ئەتراپتىكى ئىسىملارنى تاپماقچى بولسىڭىز HEX تەرىپىدىن ئىزدەڭ. + دائىم رەڭلەرنى ياقتۇرىدىغان قىلىپ بەلگە قىلىڭ ، شۇڭا ئۇلار تور كۆرگەندە ئادەتتىكى كۈتۈپخانىنىڭ ئالدىدا ماڭىدۇ. + تور تەدرىجىي توپلىمىنى ئىشلىتىڭ + ئىشلەتكىلى بولىدىغان تور تەدرىجىي مەنبەلىرىنى چۈشۈرۈپ ، تاللانغان رەسىملەرنى ھەمبەھىرلەڭ + يىراقتىكى تور تەدرىجىي مۈلۈكلىرىنى ئىشلىتىڭ + Mesh Gradients يىراقتىن بايلىق توپلاشنى يۈكلىيەلەيدۇ ، يوقاپ كەتكەن ئاستا-ئاستا رەسىملەرنى چۈشۈرەلەيدۇ ، چۈشۈرۈشنىڭ ئىلگىرىلىشىنى كۆرسىتىپ بېرەلەيدۇ ۋە تاللانغان رېشاتكىلارنى ئورتاقلىشالايدۇ. ئىشلىتىشچانلىقى تورغا چىقىش ۋە يىراقتىكى بايلىق دۇكىنىغا باغلىق ، شۇڭا قۇرۇق تىزىملىك ​​ئادەتتە بايلىقنىڭ تېخى تېپىلمىغانلىقىدىن دېرەك بېرىدۇ. + تەييار تور سۈرەتلىك رەسىملەرنى ئويلىسىڭىز ئاستا-ئاستا قوراللاردىن Mesh Gradients نى ئېچىڭ. + توپلامنى قۇرۇق دەپ پەرەز قىلىشتىن بۇرۇن بايلىق قاچىلاش ياكى چۈشۈرۈشنى ساقلاشنى ساقلاڭ. + ئۆزىڭىز ئېھتىياجلىق بولغان ئېلېكتر تورىنى تاللاڭ ۋە ئورتاقلىشىڭ ياكى قولدا ئىختىيارى گرافىك قۇرماقچى بولغاندا Gradient Maker غا قايتىڭ. + ھاسىل قىلىنغان مۈلۈك ئېنىقلىقى + رېشاتكا ، شاۋقۇن ، تام قەغىزى ، SVG غا ئوخشاش سەنئەت ياكى توقۇلمىلارنى ھاسىل قىلىشتىن بۇرۇن چىقىرىش مىقدارىنى تاللاڭ + ئۆزىڭىز ئېھتىياجلىق چوڭلۇقتا ھاسىل قىلىڭ + ھاسىل قىلىنغان رەسىملەرنىڭ كەينىگە چۈشۈپ قالىدىغان كامېرا يوق. ئەگەر چىقىرىش بەك كىچىك بولسا ، كېيىن كۆلەملەشتۈرۈش ئەندىزىسىنى يۇمشىتالايدۇ ، تەپسىلاتلارنى يۆتكىيەلەيدۇ ياكى مۈلۈك كاھىش ۋە پىرىسلاشنى ئۆزگەرتەلەيدۇ. + تام قەغىزى ، سىنبەلگە ، تەگلىك ، بېسىش ياكى قاپلاش قاتارلىق ئەڭ ئاخىرقى ئىشلىتىش قېپىنىڭ چوڭ-كىچىكلىكىنى بەلگىلەڭ. + كېسىش ، چوڭايتىش ياكى بىر نەچچە ئورۇنلاشتۇرۇشتا قايتا ئىشلىتىشكە بولىدىغان توقۇلمىلار ئۈچۈن چوڭ رازمېرلارنى ئىشلىتىڭ. + غايەت زور چىقىرىشتىن ئىلگىرى سىناق نۇسخىلىرىنى ئېكسپورت قىلىڭ ، چۈنكى جەريان تەپسىلاتلىرى پىرىسلاشنىڭ ھەرىكىتىنى ئۆزگەرتەلەيدۇ. + نۆۋەتتىكى تام قەغەزلىرىنى چىقىرىش + ئۈسكۈنىدىن ئۆي ، قۇلۇپ ۋە ئىچىگە قاچىلانغان تاملارنى ئېلىڭ + قاچىلانغان تام قەغەزنى ساقلاش ياكى ئورتاقلىشىش + تام قەغىزى ئېكسپورتى ئاندىرويىدنىڭ تام قەغىزى API لىرى ئارقىلىق ئاشكارىلىغان تام قەغەزلىرىنى ئوقۇيدۇ. ئۈسكۈنە ، ئاندىرويىد نەشرى ، ئىجازەتنامە ۋە ۋارىيانتلارغا ئاساسەن ، ئائىلە ، قۇلۇپ ياكى ئىچىگە ئورنىتىلغان تام قەغىزى ئايرىم ئىشلەتكىلى ياكى يوقاپ كېتىشى مۇمكىن. + تام قەغىزى ئېكسپورتىنى ئېچىڭ ، سىستېما بۇ دېتالنى ئوقۇيالايدۇ. + ساقلىماقچى ، كۆچۈرمەكچى ياكى ھەمبەھىرلىمەكچى بولغان ئۆي ، قۇلۇپ ياكى ئىچىگە قاچىلانغان تام قەغىزىنى تاللاڭ. + ئەگەر تام قەغىزى يوقاپ كەتسە ياكى بۇ ئىقتىدارنى ئىشلەتكىلى بولمىسا ، ئۇ ئادەتتە تەھرىرلەش تەڭشىكى بولماستىن ، سىستېما ، ئىجازەت ياكى قۇر ئۆزگەرتىش چەكلىمىسى بولىدۇ. + بۇلۇڭلار كارتون فىلىمى + رەڭنى كۆچۈرۈڭ + HEX + name + يۇمران چاچ ۋە قىرلارنى بىر تەرەپ قىلىدىغان تېز سۈرئەتلىك كىشىلەرنىڭ كېسىشمە سۈرىتىنى ماسلاشتۇرۇش مودېلى. + Zero-DCE ++ ئەگرى سىزىق ئارقىلىق تېز تۆۋەن نۇرنى ئاشۇرۇش. + يامغۇر سۈيىنى ۋە ھۆل-يېغىنلىق بۇيۇملارنى ئازايتىش ئۈچۈن رەسىمنى ئەسلىگە كەلتۈرۈش ئەندىزىسىنى ئەسلىگە كەلتۈرۈش. + كوئوردېنات تورىنى ئالدىن پەرەز قىلىدىغان ۋە ئەگمە بەتلەرنى تەكشى سىكانېرغا ئايلاندۇرىدىغان ھۆججەت يېشىش ئەندىزىسى. + ھەرىكەت ۋاقتى + ئەپنىڭ كارتون سۈرئىتىنى كونترول قىلىدۇ: 0 ھەرىكەتنى چەكلەيدۇ ، 1 نورمال ، تېخىمۇ يۇقىرى قىممەت كارتوننى ئاستىلىتىدۇ + يېقىنقى قوراللارنى كۆرسەت + ئاساسىي ئېكراندا يېقىندا ئىشلىتىلگەن قوراللارنى تېز زىيارەت قىلىڭ + ئىشلىتىش ستاتىستىكىسى + ئەپ ئېچىش ، قورال قوزغىتىش ۋە ئەڭ كۆپ ئىشلىتىدىغان قوراللىرىڭىزنى ئىزدەڭ + ئەپ ئېچىلدى + قورال ئېچىلدى + ئاخىرقى قورال + مۇۋەپپەقىيەتلىك تېجەيدۇ + پائالىيەت تەرتىپى + سانلىق مەلۇمات ساقلاندى + ئەڭ يۇقىرى فورماتى + ئىشلىتىلگەن قوراللار + %1$d ئېچىلدى • %2$s + ھازىرچە ئىشلىتىش سىتاتىستىكىسى يوق + بىر قانچە قورالنى ئېچىڭ ، ئۇلار بۇ يەردە ئاپتوماتىك كۆرۈنىدۇ + ئىشلىتىش ستاتىستىكىڭىز پەقەت ئۈسكۈنىڭىزدە ساقلىنىدۇ. ImageToolbox ئۇلارنى پەقەت شەخسىي پائالىيىتىڭىز ، ياقتۇرىدىغان قوراللىرىڭىز ۋە ساقلانغان سانلىق مەلۇماتلىرىڭىزنى كۆرسىتىش ئۈچۈنلا ئىشلىتىدۇ + خاتالىق + نېيترال ۋارىيانت + سايە تاشلاش + چىش ئېگىزلىكى + توغرىسىغا چىش دائىرىسى + تىك چىش دائىرىسى + Top Edge + Right Edge + Bottom Edge + سول قىر + Torn Edge + تەھرىرلىگۈچ تەھرىرلەش رەسىم رەسىمىنى قايتا تەڭشەش + چوڭ-كىچىكلىكىنى ئۆزگەرتىشنىڭ چوڭ-كىچىكلىكى ئېنىقلىق ئۆلچىمى كەڭلىكى jpg jpeg png webp پىرىسلاش + پىرىسلاش چوڭلۇقى ئېغىرلىقى بايت mb kb سۈپىتىنى ئەلالاشتۇرىدۇ + زىرائەت بېزەكلىرىنىڭ كېسىش نىسبىتى + سۈزگۈچ ئۈنۈم رەڭ تۈزىتىش لۆڭگە نىقابىنى تەڭشەش + بوياق چوتكىسى قەلەم ئىزاھلاش بەلگىسىنى سىزىڭ + شىفىر مەخپىيلەشتۈرۈش مەخپىي شىفىرى + تەگلىكنى ئۆچۈرۈۋېتىش كېسىلگەن سۈزۈك ئالفانى ئۆچۈرۈڭ + ئالدىن كۆرۈش كۆرۈرمەنلەر كۆرگەزمىسى ئېچىلدى + كەشتە بىرلەشتۈرۈش پۈتۈن مەنزىرىلىك ئۇزۇن ئېكراننى بىرلەشتۈردى + url تور چۈشۈرۈش تور ئۇلىنىش رەسىمى + تاللىغۇچ كۆزئەينەك ئەۋرىشكىسى hex rgb رەڭگى + پالتا رەڭلىرى ئالماشتۇرۇلغان + exif metadata مەخپىيەتلىكى بەلۋاغ پاكىز gps ئورنىنى ئۆچۈرۈڭ + ئۇنىڭدىن كېيىنكى پەرقنى سېلىشتۇرۇڭ + ئەڭ چوڭ چوڭلۇقتىكى ئەڭ چوڭ چوڭلۇقتىكى چوڭلۇقتىكى چوڭلۇقتىكى قىسقۇچ + pdf ھۆججەت بەت قوراللىرى + ocr تېكىستى جەۋھەر تەكشۈرۈشنى تونۇيدۇ + تەدرىجىي تەگلىك رەڭ تورى + سۇ بەلگىسى بەلگىسى تېكىست تامغىسى قاپلانغان + gif كارتون كارتون كارتون + apng كارتون كارتون png ئايلاندۇرۇش رامكىسى + zip ئارخىپ پىرىسلاش بوغچىسى ھۆججەتلەرنى يېشىش + jxl jpeg xl jpg رەسىم فورماتىنى ئايلاندۇرىدۇ + svg ۋېكتور ئىزلىرى xml + فورماتى jpg jpeg png webp heic avif jxl bmp + ھۆججەت سايىلىغۇچ سايىلەش قەغەز كۆز قارىشى + qr تاياقچە كود سايىلىگۈچ + ئوتتۇرا دەرىجىدىكى ئاسترونوگرافىيە + بۆلەك كاھىشلىق تور بۆلەكلىرى + رەڭ ئايلاندۇرۇش hex rgb hsl hsv cmyk تەجرىبىخانىسى + webp كارتون رەسىم فورماتىنى ئايلاندۇرىدۇ + شاۋقۇن گېنېراتورى تاسادىپىي توقۇلما دان + كوللاگېنلىق تور ئورۇنلاشتۇرۇشى بىرلەشتۈرۈلگەن + بەلگە قەۋىتى قاپلىما تەھرىرلەشنى ئىزاھلايدۇ + base64 كودلاش سانلىق مەلۇماتلىرى uri + checkum hash md5 sha sha1 sha256 دەلىللەيدۇ + mesh gradient background + exif metadata تەھرىرلەش gps چېسلا كامېراسى + بۆلۈنگەن كاتەكچە كاھىشلارنى كېسىڭ + audio cover album art music metadata + تام قەغىزى ئېكسپورتى + ascii تېكىست سەنئەت ھەرپلىرى + ai ml نېرۋا يۇقىرى دەرىجىدىكى بۆلەكنى كۈچەيتىدۇ + رەڭ كۇتۇپخانىسى ماتېرىياللىرى + shader glsl بۆلەك ئۈنۈم ستۇدىيىسى + pdf بىرلەشتۈرۈش بىرلەشتۈرۈش + pdf ئايرىم بەتلەرنى بۆلدى + pdf بەت يۆنىلىشىنى ئايلاندۇرىدۇ + pdf قايتا رەتلەش بەتلىرىنى رەتلەيدۇ + pdf بەت سانلىرى + pdf ocr تېكىستى ئىزدەشكە بولىدىغان جەۋھەرنى تونۇيدۇ + pdf سۇ بەلگىسى تامغىسى بەلگىسى تېكىستى + pdf ئىمزا بەلگىسى سىزىش + pdf مەخپىي شىفىر قۇلۇپىنى قوغدايدۇ + pdf مەخپىي شىفىرنى يېشىش قوغداشنى ئۆچۈرۈۋېتىدۇ + pdf پىرىسلاش چوڭلۇقىنى ئەلالاشتۇرىدۇ + pdf كۈلرەڭ قارا ئاق رەڭلىك يەككە رەڭلىك + pdf رېمونت قىلىش ئوڭشالدى + pdf metadata خاسلىقى exif ئاپتورنىڭ ئىسمى + pdf ئۆچۈرۈش بەتلىرىنى ئۆچۈرۈڭ + pdf زىرائەت بېزەكلىرى + pdf تەكشى ئىزاھلار قەۋەت ھاسىل قىلىدۇ + pdf رەسىم رەسىملىرى + pdf zip ئارخىپ پىرىسلاش + pdf بېسىش پرىنتېر + pdf ئالدىن كۆرۈش كۆرگۈچى ئوقۇدى + رەسىملەرنى pdf غا jpg jpeg png ھۆججىتىگە ئايلاندۇرىدۇ + رەسىم بەتلىرىگە pdf ئېكسپورت jpg png + pdf ئىزاھلىرى باھالارنى پاكىز ئۆچۈرۈۋېتىدۇ + ئىشلىتىش ستاتىستىكىسىنى ئەسلىگە كەلتۈرۈڭ + قورال ئېچىلىدۇ ، سانلىق مەلۇماتلارنى ساقلايدۇ ، رەتلەش ، ساقلانغان سانلىق مەلۇمات ۋە ئەڭ يۇقىرى فورماتى ئەسلىگە كېلىدۇ. ئەپ ئېچىلمايدۇ. + Audio + ھۆججەت + ئارخىپلارنى چىقىرىش + نۆۋەتتىكى ئارخىپنى ساقلاڭ + ئېكسپورت ئارخىپىنى ئۆچۈرۈڭ + ئېكسپورت ئارخىپىنى ئۆچۈرمەكچىمۇ؟ \ \"__ PH_0 __ \\"? + چېگرا سىزىش + رەسىم سىزىش ۋە كاناينى ئۆچۈرۈش ئەتراپىدا نېپىز چېگرانى كۆرسىتىڭ + دولقۇن + يۇمىلاق دولقۇنلۇق گىرۋەكلىك UI ئېلېمېنتلىرى + Scoop + Notch + ئىچىگە ئويۇلغان يۇمىلاق بۇلۇڭلار + قوش كېسىلگەن قەدەملەر + ئورۇن ئىگىسى + كېڭەيتىلمەي ئەسلى ھۆججەت نامىنى قىستۇرۇش ئۈچۈن {ھۆججەت ئىسمى} نى ئىشلىتىڭ + Flare + ئاساسى سومما + ئۈزۈك سوممىسى + رەي مىقدارى + ئۈزۈك كەڭلىكى + كۆز قاراشنى بۇرمىلاش + Java Look and Feel + Shear + X بۇلۇڭ + and angle + مەھسۇلاتنىڭ چوڭ-كىچىكلىكىنى تەڭشەش + Water Drop + دولقۇن ئۇزۇنلۇقى + باسقۇچ + High Pass + رەڭلىك ماسكا + Chrome + Dissolve + يۇمشاقلىق + تەكلىپ + Lens Blur + تۆۋەن كىرگۈزۈش + يۇقىرى كىرگۈزۈش + تۆۋەن مەھسۇلات + يۇقىرى مەھسۇلات + يېنىك ئۈنۈم + بوي ئېگىزلىكى + يۇمشاق دېتال + Ripple + X amplitude + Y amplitude + X دولقۇن ئۇزۇنلۇقى + Y دولقۇن ئۇزۇنلۇقى + Adaptive Blur + توقۇلما ئەۋلاد + ھەر خىل تەرتىپلىك تېكىستلەرنى ھاسىل قىلىپ ، ئۇلارنى رەسىم سۈپىتىدە ساقلاڭ + تېكىست تىپى + Bias + ۋاقىت + Shine + تارقاقلاشتۇرۇش + ئەۋرىشكە + بۇلۇڭ كوئېففىتسېنتى + تەدرىجىي كوئېففىتسېنت + كاتەكچە تىپى + ئارىلىق كۈچى + Scale Y. + Fuzziness + ئاساسى تىپى + تۇراقسىزلىق ئامىلى + كۆلەملەشتۈرۈش + Iterations + ئۈزۈك + Brushed Metal + Caustics + Cellular + تەكشۈرۈش تاختىسى + fBm + مەرمەر + پلازما + يوتقان + ياغاچ + تاسادىپىي + مەيدان + Hexagonal + سەككىز بۇرجەكلىك بىنا + ئۈچبۇلۇڭ + Ridged + VL شاۋقۇن + SC شاۋقۇن + يېقىنقى + يېقىندا ئىشلىتىلمىگەن سۈزگۈچ يوق + توقۇلما گېنېراتور چوتكىلانغان مېتال كاۋاك ھۈجەيرىلىك تەكشۈرۈش تاختىسى مەرمەر پلازما يوتقان ياغاچ خىش كامېرا ھۈجەيرىسى بۇلۇت يېرىلغان رەخت يوپۇرماق ھەسەل ھەرىسى مۇز لاۋا نېبۇلا قەغەز دات قۇم ئىس-تۈتەك تاش يەر شەكلى يەر شەكلى سۇ دولقۇنى. + خىش + Camouflage + Cell + بۇلۇت + Crack + رەخت + Foliage + ھەسەل ھەرىسى + مۇز + لاۋا + Nebula + قەغەز + رۇستەم + قۇم + تاماكا + تاش + Terrain + يەر شەكلى + Water Ripple + Advanced Wood + Mortar width + تەرتىپسىزلىك + Bevel + قوپاللىق + بىرىنچى چەك + ئىككىنچى چەك + ئۈچىنچى چەك + قىرلىق يۇمشاق + چېگرا كەڭلىكى + Coverage + Detail + چوڭقۇرلۇق + شاخلىتىپ سېتىش + توغرىسىغا تېما + تىك تېما + Fuzz + تومۇر + يورۇتۇش + يېرىق كەڭلىكى + ئۈششۈك + Flow + يەر پوستى + بۇلۇت زىچلىقى + Stars + تالا زىچلىقى + تالا كۈچى + داغ + چىرىش + Pitting + Flakes + Dune چاستوتىسى + شامال بۇلۇڭى + Ripples + Wisps + تومۇر ئۆلچىمى + سۇ ئورنى + تاغ دەرىجىسى + Erosion + قار دەرىجىسى + قۇر سانى + سىزىق قېلىنلىقى + سايە + رەڭگى قېنىق + Mortar color + قېنىق خىش رەڭگى + خىش رەڭگى + رەڭنى گەۋدىلەندۈرۈش + قېنىق رەڭ + ئورمان رەڭگى + يەر رەڭگى + قۇم رەڭ + تەگلىك رەڭگى + كاتەكچە رەڭ + قىر رەڭ + ئاسمان رەڭگى + سايە رەڭگى + سۇس رەڭ + يۈزىنىڭ رەڭگى + ئۆزگىرىش رەڭگى + يېرىق رەڭ + قېنىق رەڭ + توقۇلما رەڭ + قېنىق يوپۇرماق رەڭگى + يوپۇرماق رەڭگى + چېگرا رەڭگى + ھەسەل رەڭگى + قېنىق رەڭ + مۇز رەڭ + ئۈششۈك رەڭگى + يەر پوستى رەڭگى + رەڭنى يۇيۇش + پارقىراق رەڭ + بوشلۇق رەڭگى + بىنەپشە رەڭ + كۆك رەڭ + ئاساسى رەڭ + تالا رەڭگى + داغ رەڭ + مېتال رەڭ + قېنىق دات رەڭ + قوڭۇر رەڭ + ئاپېلسىن رەڭ + تۈتۈن رەڭگى + تومۇر رەڭگى + تۆۋەن رەڭ + سۇ رەڭگى + تاش رەڭ + قار رەڭگى + تۆۋەن رەڭ + يۇقىرى رەڭ + قۇر رەڭ + رەڭگى قېنىق + Grass + توپا + خۇرۇم + بېتون + ئاسفالت + Moss + ئوت + قۇتۇپ نۇرى + Oil Slick + سۇ بوياق + Abstract Flow + Opal + دەمەشىق پولات + چاقماق + Velvet + Ink Marbling + Holographic Foil + Bioluminescence + Cosmic Vortex + Lava Lamp + پائالىيەت ئۇپۇق سىزىقى + Fractal Bloom + خىروم تونېلى + Eclipse Corona + غەلىتە جەلپكار + Ferrofluid Crown + Supernova + Iris + پاقلان پەي + Nautilus Shell + Ringed Planet + تىغنىڭ زىچلىقى + تىغنىڭ ئۇزۇنلۇقى + شامال + ياماق + Clumps + نەم + Pebbles + قورۇق + تۆشۈكچىلەر + Aggregate + يېرىق + Tar + كىيىڭ + سپەكتاكل + تالا + يالقۇن چاستوتىسى + تاماكا + قويۇقلۇقى + لېنتا + بەلۋاغ + Iridescence + قاراڭغۇلۇق + چېچەكلەيدۇ + Pigment + Edges + قەغەز + Diffusion + Symmetry + ئۆتكۈرلۈك + رەڭ ئويناش + Milkiness + قەۋەت + Folding + پولشا + شاخلىتىپ سېتىش + يۆنىلىش + Sheen + Folds + Feathering + سىياھ تەڭپۇڭلۇقى + Spectrum + Crinkles + پەرقلەندۈرۈش + قورال + Twist + يادرولۇق پارقىراق + Blobs + دىسكا يانتۇ + Horizon size + دىسكىنىڭ كەڭلىكى + Lensing + Petals + Curl + Filigree + Facets + ئەگرى سىزىق + ئاي چوڭلۇقى + كورونا چوڭلۇقى + نۇر + ئالماس ئۈزۈك + Lobes + ئوربىتىنىڭ زىچلىقى + قېلىنلىق + Spikes + تاياق ئۇزۇنلۇقى + بەدەن چوڭلۇقى + Metallic + Shock radius + Shell width + تاشلىۋېتىلدى + ئوقۇغۇچىلارنىڭ چوڭلۇقى + Iris size + رەڭنىڭ ئۆزگىرىشى + Catchlight + كۆز چوڭلۇقى + قاۋاق زىچلىقى + بۇرۇلدى + Chambers + ئېچىش + Ridges + مەرۋايىت + سەييارە چوڭلۇقى + ئۈزۈك يانتۇ + ئۈزۈك كەڭلىكى + ئاتموسفېرا + توپا رەڭ + قېنىق ئوت-چۆپ رەڭگى + ئوت رەڭگى + كۆرسەتمە رەڭ + قېنىق يەر رەڭگى + قۇرغاق رەڭ + قېنىق رەڭ + خۇرۇم رەڭ + بېتون رەڭ + تار رەڭ + ئاسفالت رەڭ + تاش رەڭ + توپا رەڭ + تۇپراقنىڭ رەڭگى + قېنىق موس رەڭ + موس رەڭ + قىزىل رەڭ + يادرولۇق رەڭ + يېشىل رەڭ + رەڭگى + Magenta color + ئالتۇن رەڭ + قەغەز رەڭ + پىگمېنت رەڭگى + ئىككىلەمچى رەڭ + بىرىنچى رەڭ + ئىككىنچى رەڭ + ھالرەڭ + قېنىق پولات رەڭ + پولات رەڭ + سۇس پولات رەڭ + ئوكسىد رەڭگى + Halo color + بولت رەڭ + Velvet color + قېنىق رەڭ + كۆك سىياھ رەڭ + قىزىل سىياھ رەڭ + قېنىق سىياھ رەڭ + كۈمۈش رەڭ + سېرىق رەڭ + توقۇلما رەڭ + دىسكا رەڭگى + قىزىق رەڭ + لىنزا رەڭگى + سىرتقى رەڭ + ئىچكى رەڭ + كورونا رەڭگى + سوغۇق رەڭ + ئىللىق رەڭ + بۇلۇت رەڭ + يالقۇن رەڭ + تۈك رەڭگى + قېپى رەڭگى + مەرۋايىت رەڭ + سەييارە رەڭ + ئۈزۈك رەڭ + ساقلىغاندىن كېيىن ئەسلى ھۆججەتلەرنى ئۆچۈرۈڭ + پەقەت مۇۋەپپەقىيەتلىك بىر تەرەپ قىلىنغان مەنبە ھۆججەتلىرى ئۆچۈرۈلىدۇ + ئۆچۈرۈلگەن ئەسلى ھۆججەتلەر: %1$d + ئەسلى ھۆججەتلەرنى ئۆچۈرەلمىدى: %1$d + ئۆچۈرۈلگەن ئەسلى ھۆججەتلەر: %1$d ، مەغلۇپ بولدى: %2$d + ۋاراق ئىشارەتلىرى + كېڭەيتىلگەن تاللاش جەدۋىلىنى سۆرەشكە يول قويۇڭ + Chroma subampling + Bit چوڭقۇرلۇقى + زىيانسىز + __PH_0 __- bit + Batch Rename + ھۆججەت نامى ئارقىلىق كۆپ ھۆججەتنىڭ نامىنى ئۆزگەرتىڭ + تۈركۈمدىكى ھۆججەتلەرنىڭ نامىنى ئۆزگەرتىش ھۆججەت نامىنىڭ تەرتىپ تەرتىپىنى كېڭەيتىش + ھۆججەتنىڭ نامىنى تاللاڭ + ھۆججەت ئىسمى + ئىسىم ئۆزگەرتىش + چېسلا مەنبەسى + EXIF ۋاقتى ئېلىندى + ھۆججەت ئۆزگەرتىلگەن ۋاقىت + ھۆججەت قۇرۇلغان ۋاقىت + نۆۋەتتىكى چېسلا + قولدا چېسلا + قولدا چېسلا + قولدا ۋاقىت + تاللانغان ۋاقىت بەزى ھۆججەتلەرنى ئىشلەتكىلى بولمايدۇ. ھازىرقى چېسلا ئۇلار ئۈچۈن ئىشلىتىلىدۇ. + ھۆججەت نامىنى كىرگۈزۈڭ + ئەندىزە ئىناۋەتسىز ياكى ھەددىدىن زىيادە ئۇزۇن ھۆججەت نامىنى ھاسىل قىلىدۇ + ئەندىزە كۆپەيتىلگەن ھۆججەت نامىنى ھاسىل قىلىدۇ + بارلىق ھۆججەت ناملىرى ئاللىبۇرۇن قېلىپقا ماس كېلىدۇ + بۇ ئەندىزە تۈركۈمگە ئۆزگەرتىشكە بولمايدىغان بەلگىلەرنى ئىشلىتىدۇ + ئىسىم ئوخشاش ھالەتتە تۇرىدۇ + ھۆججەتلەر مۇۋەپپەقىيەتلىك ئۆزگەرتىلدى + بەزى ھۆججەتلەرنىڭ ئىسمىنى ئۆزگەرتەلمىدى + ئىجازەت بېرىلمىگەن + ئەسلى ھۆججەت نامىنى كېڭەيتمەيلا ئىشلىتىڭ ، مەنبە پەرقلەندۈرۈشنى ساقلىشىڭىزغا ياردەم بېرىدۇ. \\ o {باشلاش: ئاخىرلىشىش} ، \\ o {t with بىلەن تەرجىمە قىلىشنى ۋە \\ o {s / old / new /} بىلەن ئالماشتۇرۇشنى قوللايدۇ. + ئاپتوماتىك كۆپەيتىش ساندۇقى. ئىختىيارى فورماتلاشنى قوللايدۇ: \\ c {padding} (مەسىلەن \\ c {3} -&gt; 001) ، \\ c {باشلاش: قەدەم} ياكى \\ c {باشلاش: قەدەم: padding}. + ئەسلى ھۆججەت جايلاشقان ئانا ھۆججەت قىسقۇچنىڭ ئىسمى. + ئەسلى ھۆججەتنىڭ چوڭلۇقى. بىرلىكلەرنى قوللايدۇ: \\ z {b}, \\ z {kb}, \\ z {mb} ياكى ئىنسانلارنىڭ ئوقۇغىلى بولىدىغان فورماتى ئۈچۈن پەقەت \\ z. + ئىختىيارىي ئۇنىۋېرسال پەرقلىگۈچ (UUID) ھاسىل قىلىپ ، ھۆججەت نامىنىڭ خاسلىقىغا كاپالەتلىك قىلىدۇ. + ھۆججەتلەرنىڭ نامىنى بىكار قىلىشقا بولمايدۇ. داۋاملاشتۇرۇشتىن بۇرۇن يېڭى ئىسىملارنىڭ توغرا ئىكەنلىكىنى جەزملەشتۈرۈڭ. + نامىنى جەزملەشتۈرۈڭ + يان تىزىملىك ​​تەڭشىكى + تىزىملىك ​​ئۆلچىمى + تىزىملىك ​​ئالفا + ئاپتوماتىك ئاق تەڭپۇڭلۇق + Clipping + يوشۇرۇن سۇ بەلگىسىنى تەكشۈرۈش + ساقلاش + غەملەك چېكى + غەملەك بۇ چوڭلۇقتىن ئېشىپ كەتكەندە ئاپتوماتىك تازىلاڭ + تازىلاش ئارىلىقى + غەملەكنى تازىلاش كېرەكلىكىنى قانچە قېتىم تەكشۈرۈش كېرەك + ئەپ ئاچقاندا + 1 كۈن + 1 ھەپتە + 1 ئاي + تاللانغان چەك ۋە ئارىلىققا ئاساسەن ئەپ ساقلىغۇچنى ئاپتوماتىك تازىلاڭ + يۆتكىلىشچان زىرائەت رايونى + ئۇنىڭ ئىچىگە سۆرەپ پۈتكۈل زىرائەتلەرنى يۆتكەشكە يول قويىدۇ + GIF نى بىرلەشتۈرۈش + كۆپ خىل GIF قىسقۇچنى بىر كارتونغا بىرلەشتۈرۈڭ + GIF قىسقۇچ تەرتىپى + تەتۈر + تەتۈر يۆنىلىشتە ئويناڭ + Boomerang + ئالدىغا ئويناڭ + رامكىنىڭ چوڭ-كىچىكلىكىنى نورماللاشتۇرۇش + ئەڭ چوڭ قىسمىغا ماس كېلىدىغان كۆلەم رامكىسى ئەسلىدىكى كۆلىمىنى ساقلاپ قېلىش ئۈچۈن ئېتىۋېتىڭ + قىسقۇچ (ms) ئارىسىدا كېچىكىش + ھۆججەت قىسقۇچ + ھۆججەت قىسقۇچ ۋە ئۇنىڭ قىسقۇچتىن بارلىق رەسىملەرنى تاللاڭ + كۆپەيتكۈچى + ئېنىق نۇسخا ۋە كۆرۈنۈشكە ئوخشايدىغان رەسىملەرنى تېپىڭ + كۆپەيتكۈچى ئوخشاش رەسىملەر كۆپەيتىلگەن رەسىملەرنى تازىلاش ساقلاش dHash SHA-256 + كۆپەيتىلگەن نۇسخىسىنى تېپىش ئۈچۈن رەسىم تاللاڭ + ئوخشاشلىق سەزگۈرلىكى + ھەقىقىي كۆپەيتىلگەن نۇسخىسى + مۇشۇنىڭغا ئوخشاش رەسىملەر + بارلىق كۆپەيتىلگەن نۇسخىسىنى تاللاڭ + تەۋسىيە قىلىنغاندىن باشقا ھەممىسىنى تاللاڭ + كۆپەيتىلگەن نۇسخىسى تېپىلمىدى + تېخىمۇ كۆپ رەسىم قوشۇش ياكى ئوخشاشلىق سەزگۈرلۈكىنى ئاشۇرۇش + %1$d رەسىم (لەرنى) ئوقۇيالمىدى + %1$s • %2$s قايتۇرۇۋالغىلى بولىدۇ + %1$d گۇرۇپپىلىرى • %2$s قايتۇرۇۋالغىلى بولىدۇ + %1$d تاللانغان • %2$s + بۇنى ئەمەلدىن قالدۇرغىلى بولمايدۇ. ئاندىرويىد سىستېما دىئالوگىدا ئۆچۈرۈلگەنلىكىنى جەزملەشتۈرۈشىڭىزنى تەلەپ قىلىشى مۇمكىن. + تېپىلمىدى + باشلاڭ + ئاخىرىغا يۆتكەڭ + \ No newline at end of file diff --git a/core/resources/src/main/res/values-uk/strings.xml b/core/resources/src/main/res/values-uk/strings.xml new file mode 100644 index 0000000..6548f4c --- /dev/null +++ b/core/resources/src/main/res/values-uk/strings.xml @@ -0,0 +1,3741 @@ + + + + Зображення завелике для попереднього перегляду, але спроба збереження все одно буде здійснена + Виберіть зображення для початку + Тип масштабування + Ви впевнені, що хочете закрити програму? + Застосунок закривається + Скинуто + Перезавантаження + Скопійовано до буфера + Редагувати 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 вибрані типи анотацій, як-от посилання, коментарі, виділення, фігури або поля форми + Гіперпосилання + Вкладення файлів + Лінії + Спливаючі вікна + Марки + Фігури + Текстові примітки + Розмітка тексту + Поля форми + Розмітка + Невідомий + Анотації + Розгрупувати + Додайте розмиту тінь за шаром із настроюваним кольором і зсувами + Спотворення з урахуванням вмісту + Недостатньо пам\'яті для виконання цієї дії. Спробуйте зменшити зображення, закрити інші програми або перезапустити програму. + Паралельні працівники + Ці зображення не було збережено, оскільки конвертовані файли будуть більшими за оригінали. Перевірте цей список перед видаленням оригінальних зображень. + Пропущено файлів: %1$s + Журнали програми + Перегляньте журнали програми, щоб виявити проблеми + Вибране в згрупованих інструментах + Додає вибране як вкладку, коли інструменти згруповано за типом + Показати улюблене як останнє + Переміщує вкладку улюблених інструментів у кінець + Горизонтальний інтервал + Вертикальний інтервал + Зворотна енергія + Використовуйте просту енергетичну карту градієнтної величини замість прямого енергетичного алгоритму + Видалити замасковану область + Шви проходитимуть через замасковану область, видаляючи лише вибрану ділянку замість того, щоб захищати її + VHS NTSC + Розширені налаштування NTSC + Додаткове аналогове налаштування для посилення VHS і артефактів трансляції + Кровотеча кольоровості + Знос стрічки + Відстеження + Мазок Luma + Дзвінок + сніг + Використання поля + Тип фільтра + Вхідний фільтр яскравості + Низькочастотність вхідної кольоровості + Демодуляція кольоровості + Фазовий зсув + Зсув фази + Перемикання голови + Висота перемикання голови + Зсув перемикання головки + Перемикання голови + Положення по середній лінії + Тремтіння середньої лінії + Відстеження висоти шуму + Відстеження хвилі + Відстеження снігу + Відстеження анізотропії снігу + Відстеження шуму + Відстеження інтенсивності шуму + Композитний шум + Композитна шумова частота + Композитна інтенсивність шуму + Композитна шумова деталь + Частота дзвінків + Сила дзвінка + Шум яскравості + Частота шуму Luma + Інтенсивність шуму Luma + Деталі шуму яскравості + Кольоровий шум + Частота кольорового шуму + Інтенсивність кольорового шуму + Деталі кольорового шуму + Інтенсивність снігу + Анізотропія снігу + Фазовий шум кольоровості + Помилка фази кольоровості + Затримка кольоровості по горизонталі + Затримка кольоровості по вертикалі + Швидкість стрічки VHS + Втрата кольоровості VHS + Інтенсивність різкості VHS + Частота різкості VHS + Інтенсивність крайової хвилі VHS + Швидкість крайової хвилі VHS + Частота крайової хвилі VHS + Деталі краю хвилі VHS + Вихідна кольоровість низьких частот + Горизонтальний масштаб + Масштаб вертикальний + Масштабний коефіцієнт X + Коефіцієнт масштабу Y + Виявляє поширені водяні знаки зображень і зафарбовує їх за допомогою LaMa. Автоматично завантажує моделі виявлення та малювання + Дейтеранопія + Розгорнути зображення + Засіб видалення фону на основі сегментації об’єктів за допомогою сегментації YOLO v11 + Показати кут лінії + Показує поточний поворот лінії в градусах під час малювання + Анімовані емодзі + Показувати доступні емодзі як анімацію + Зберегти в оригінальну папку + Зберігайте нові файли поруч із вихідним файлом замість вибраної папки + Водяний знак PDF + Недостатньо пам\'яті + Імовірно, зображення завелике для обробки на цьому пристрої або в системі вичерпано доступну пам’ять. Спробуйте зменшити роздільну здатність зображення, закрити інші програми або вибрати файл меншого розміру. + Меню налагодження + Меню для перевірки функцій додатка, це не призначено для відображення у робочій версії + Провайдер + Для PaddleOCR потрібні додаткові моделі ONNX на вашому пристрої. Бажаєте завантажити дані %1$s? + Універсальний + корейська + латинь + східнослов\'янська + тайська + грецька + англійська + Кирилиця + арабська + Деванагарі + тамільська + Телугу + Шейдер + Попереднє налаштування шейдера + Шейдер не вибрано + Студія шейдерів + Створюйте, редагуйте, перевіряйте, імпортуйте та експортуйте власні шейдери фрагментів + Збережені шейдери + Відкрийте збережені шейдери, створіть копію, експортуйте, поділіться або видаліть + Новий шейдер + Файл шейдера + .itshader JSON + %1$d параметрів + Джерело шейдера + Напишіть лише тіло void main(). Використовуйте textureCoordinate для UV-координат і inputImageTexture як джерело вибірки. + Функції + Додаткові допоміжні функції, константи та структури, вставлені перед void main(). Уніформа генерується з параметрів. + Додайте уніформи сюди, коли шейдеру потрібні значення, які можна редагувати. + Скинути шейдер + Поточну чернетку шейдера буде очищено + Недійсний шейдер + Непідтримувана версія шейдера %1$d. Підтримувана версія: %2$d. + Необхідно вказати назву шейдера. + Джерело шейдера не має бути пустим. + Джерело шейдера містить непідтримувані символи: %1$s. Використовуйте лише джерело ASCII GLSL. + Параметр \\\"%1$s\\\" оголошено більше одного разу. + Імена параметрів не повинні бути пустими. + Параметр \\\"%1$s\\\" використовує зарезервоване ім\'я GPUImage. + Параметр \\\"%1$s\\\" має бути дійсним ідентифікатором GLSL. + Параметр \\\"%1$s\\\" має %2$s тип значення \\\"%3$s\\\", очікуваний \\\"%4$s\\\". + Параметр \\\"%1$s\\\" не може визначити min або max для логічних значень. + Параметр \\\"%1$s\\\" має min більше ніж max. + Параметр \\\"%1$s\\\" за умовчанням нижчий за min. + Параметр \\\"%1$s\\\" за умовчанням перевищує макс. + Шейдер повинен оголосити \\\"uniform sampler2D %1$s;\\\". + Шейдер має оголосити \\\"variying vec2 %1$s;\\\". + Шейдер повинен визначати \\\"void main()\\\". + Шейдер повинен записати колір у gl_FragColor. + ShaderToy mainImage шейдери не підтримуються. Використовуйте контракт void main() GPUImage. + Анімована форма ShaderToy \\\"iTime\\\" не підтримується. + Анімований однорідний ShaderToy \\\"iFrame\\\" не підтримується. + ShaderToy uniform \\\"iResolution\\\" не підтримується. + Текстури ShaderToy iChannel не підтримуються. + Зовнішні текстури не підтримуються. + Зовнішні розширення текстур не підтримуються. + Параметри шейдера Libretro не підтримуються. + Підтримується лише одна вхідна текстура. Видалити \\\"uniform %1$s %2$s;\\\". + Параметр \\\"%1$s\\\" повинен бути оголошений як \\\"uniform %2$s %1$s;\\\". + Параметр \\\"%1$s\\\" оголошено як \\\"uniform %2$s %1$s;\\\", очікується \\\"uniform %3$s %1$s;\\\". + Файл шейдера порожній. + Файл шейдера має бути об’єктом .itshader JSON із версією, назвою та полями шейдера. + Файл шейдера недійсний JSON або не відповідає формату .itshader. + Поле \\\"%1$s\\\" є обов\'язковим для заповнення. + Потрібен %1$s. + %1$s \\\"%2$s\\\" не підтримується. Підтримувані типи: %3$s. + %1$s необхідний для параметрів \\\"%2$s\\\". + %1$s має бути кінцевим числом. + %1$s має бути цілим числом. + %1$s має бути true або false. + %1$s має бути кольоровим рядком, масивом RGB/RGBA або кольоровим об’єктом. + %1$s має використовувати формат #RRGGBB або #RRGGBBAA. + %1$s має містити дійсні шістнадцяткові канали кольорів. + %1$s має містити 3 або 4 кольорові канали. + %1$s має бути від 0 до 255. + %1$s має бути масивом із двох чисел або об’єктом із x і y. + %1$s має містити рівно 2 числа. + дублікат + Завжди очищайте EXIF + Видаліть EXIF-дані зображення під час збереження, навіть якщо інструмент вимагає збереження метаданих + Довідка та поради + %1$d навчальні посібники + Відкритий інструмент + Ознайомтеся з основними інструментами, параметрами експорту, потоками PDF, утилітами кольору та виправленням типових проблем + Початок роботи + Вибирайте інструменти, імпортуйте файли, зберігайте результати та швидше повторно використовуйте налаштування + Редагування зображення + Змінюйте розмір, обрізайте, фільтруйте, видаляйте фони, малюйте та створюйте водяні знаки + Файли та метадані + Перетворюйте формати, порівнюйте вихідні дані, захищайте конфіденційність і керуйте іменами файлів + PDF і документи + Створюйте PDF-файли, скануйте документи, сторінки OCR і готуйте файли для спільного використання + Текст, QR та дані + Розпізнавайте текст, скануйте коди, кодуйте Base64, перевіряйте хеші та пакуйте файли + Кольорові інструменти + Вибирайте кольори, створюйте палітри, досліджуйте бібліотеки кольорів і створюйте градієнти + Творчі інструменти + Створюйте зображення SVG, ASCII, шумові текстури, шейдери та експериментальні візуальні ефекти + Усунення несправностей + Виправте проблеми з важкими файлами, прозорістю, спільним доступом, імпортом і звітами + Виберіть правильний інструмент + Використовуйте дії за категоріями, пошуком, уподобаннями та діліться, щоб почати з правильного місця + Почніть з найкращої точки входу + Image Toolbox має багато цілеспрямованих інструментів, і багато з них збігаються на перший погляд. Почніть із завдання, яке хочете завершити, а потім виберіть інструмент, який керує найважливішою частиною результату: розміром, форматом, метаданими, текстом, сторінками PDF, кольорами чи макетом. + Використовуйте пошук, коли знаєте назву дії, як-от зміна розміру, PDF, EXIF, OCR, QR або колір. + Відкрийте категорію, якщо знаєте лише тип завдання, а потім закріпіть часто використовувані інструменти як вибране. + Коли інша програма надає спільний доступ до зображення в Image Toolbox, виберіть інструмент із аркуша спільного доступу. + Імпортуйте, зберігайте та діліться результатами + Зрозумійте звичайний процес від вибору вхідних даних до експорту відредагованого файлу + Дотримуйтеся звичайного процесу редагування + Більшість інструментів дотримуються однакового ритму: виберіть введення, налаштуйте параметри, попередній перегляд, потім збережіть або надайте спільний доступ. Важлива деталь полягає в тому, що збереження створює вихідний файл, тоді як спільний доступ зазвичай найкращий для швидкого передачі в іншу програму. + Виберіть один файл для інструментів із одним зображенням або кілька файлів для пакетних інструментів, якщо це дозволяє засіб вибору. + Перед збереженням перевірте елементи керування попереднім переглядом і виведенням, особливо параметри формату, якості та імені файлу. + Використовуйте спільний доступ для швидкої передачі або збережіть, коли вам потрібна постійна копія у вибраній папці. + Працюйте з багатьма зображеннями одночасно + Пакетні інструменти найкраще підходять для повторних завдань зміни розміру, перетворення, стиснення та іменування + Приготуйте передбачувану партію + Пакетна обробка найшвидша, коли кожен вихід має відповідати тим самим правилам. Це також найпростіше місце, де можна зробити серйозну помилку, тому виберіть назву, якість, метадані та поведінку папки перед початком тривалого експорту. + Виберіть усі пов’язані зображення разом і збережіть порядок, якщо результат залежить від послідовності. + Перед початком експорту встановіть правила зміни розміру, формату, якості, метаданих і імені файлу. + Спочатку запустіть невеликий тестовий пакет, якщо вихідні файли великі або якщо цільовий формат для вас новий. + Швидке єдине редагування + Використовуйте універсальний редактор, коли вам потрібно кілька простих змін на одному зображенні + Завершіть одне зображення без перемикання інструментів + Одиночне редагування корисне, коли зображення потребує невеликого ланцюжка змін, а не однієї спеціалізованої операції. Зазвичай це швидше для швидкого очищення, попереднього перегляду та експорту однієї остаточної копії без створення пакетного робочого процесу. + Відкрийте Single Edit для одного зображення, яке потребує загальних коригувань, розмітки, обрізання, обертання або експорту змін. + Застосовуйте зміни в такому порядку, щоб спочатку змінювати найменшу кількість пікселів, наприклад обрізати перед фільтрами та фільтрувати перед остаточним стисненням. + Замість цього використовуйте спеціалізований інструмент, якщо вам потрібна пакетна обробка, точні цільові розміри файлів, виведення PDF, OCR або очищення метаданих. + Використовуйте пресети та налаштування + Збережіть повторювані вибори та налаштуйте програму для бажаного робочого процесу + Уникайте повторення того самого налаштування + Попередні налаштування та параметри корисні, якщо ви часто експортуєте файл одного типу. Хороші налаштування перетворюють повторюваний контрольний список вручну одним дотиком, тоді як глобальні налаштування визначають стандартні параметри, з яких починаються нові інструменти. + Створюйте стилі для стандартних вихідних розмірів, форматів, значень якості або шаблонів імен. + Перегляньте параметри формату за замовчуванням, якості, папки збереження, імені файлу, засобу вибору та поведінки інтерфейсу. + Зробіть резервну копію налаштувань перед перевстановленням програми або переходом на інший пристрій. + Встановіть корисні параметри за замовчуванням + Виберіть стандартний формат, якість, режим масштабування, тип зміни розміру та колірний простір один раз + Зробіть нові інструменти ближчими до вашої мети + Значення за замовчуванням – це не просто косметичні налаштування. Вони вирішують, який формат, якість, поведінка зміни розміру, режим масштабування та колірний простір відображатимуться першими в багатьох інструментах, що допомагає уникнути повторного вибору однакових варіантів під час кожного експорту. + Установіть стандартний формат і якість зображення відповідно до свого звичайного призначення, наприклад JPEG для фотографій або PNG/WebP для альфа-версії. + Налаштуйте тип зміни розміру за замовчуванням і режим масштабування, якщо ви часто експортуєте для строгих розмірів, соціальних платформ або сторінок документів. + Змінюйте колірний простір за замовчуванням, лише якщо ваш робочий процес потребує узгодженої обробки кольорів на дисплеях, друкованих виданнях або зовнішніх редакторах. + Вибір і запуск + Використовуйте налаштування вибору, коли програма відкриває забагато діалогових вікон або запускається не в тому місці + Зробіть так, щоб відкриття файлів відповідало вашій звичці + Параметри засобу вибору та запуску визначають, чи запускається Image Toolbox з головного екрана, інструмента чи процесу вибору файлів. Ці параметри особливо корисні, коли ви зазвичай входите з аркуша спільного доступу Android або коли ви хочете, щоб програма більше нагадувала засіб запуску інструментів. + Використовуйте параметри вибору фотографій, якщо системний засіб вибору приховує файли, показує забагато останніх елементів або погано обробляє хмарні файли. + Увімкніть пропуск вибору лише для інструментів, у які ви зазвичай входите зі спільного доступу або вже знаєте вихідний файл. + Перегляньте режим запуску, якщо ви хочете, щоб Image Toolbox діяв більше як засіб вибору інструментів, ніж звичайний головний екран. + Джерело зображення + Виберіть режим вибору, який найкраще працює з галереями, файловими менеджерами та хмарними файлами + Вибирайте файли з правильного джерела + Налаштування джерела зображення впливають на те, як програма запитує Android зображення. Найкращий вибір залежить від того, чи зберігаються ваші файли в системній галереї, папці файлового менеджера, хмарному провайдері чи іншій програмі, яка ділиться тимчасовим вмістом. + Використовуйте засіб вибору за замовчуванням, якщо вам потрібна найбільш інтегрована система для типових зображень галереї. + Перемкніть режим вибору, якщо альбоми, папки, хмарні файли або нестандартні формати відображаються не там, де ви очікуєте. + Якщо спільний файл зникає після виходу з вихідної програми, повторно відкрийте його за допомогою постійного вибору або спершу збережіть локальну копію. + Інструменти для вибраного та обміну + Зберігайте фокус на головному екрані та приховайте інструменти, які не мають сенсу в потоках обміну + Зменшити шум списку інструментів + Улюблені та налаштування прихованих для спільного використання корисні, коли ви використовуєте лише кілька інструментів щодня. Вони також забезпечують практичний потік спільного доступу, приховуючи інструменти, які не можуть використовувати тип файлу, який надсилає інша програма. + Закріпіть часто використовувані інструменти, щоб вони залишалися легко доступними, навіть якщо інструменти згруповані за категоріями. + Приховайте інструменти від потоків спільного доступу, якщо вони не мають відношення до файлів, які надходять з іншої програми. + Використовуйте параметри впорядкування вибраних, якщо ви віддаєте перевагу вибраним у кінці або згрупованим із пов’язаними інструментами. + Резервне копіювання налаштувань + Безпечно перемістіть стилі, параметри та поведінку додатка до іншої інсталяції + Зберігайте вашу установку портативною + Резервне копіювання та відновлення є найбільш корисним після того, як ви налаштували стилі, папки, правила імен файлів, порядок інструментів і візуальні параметри. Резервна копія дає змогу експериментувати з налаштуваннями або перевстановлювати програму без перебудови робочого процесу з пам’яті. + Створіть резервну копію після зміни попередніх налаштувань, поведінки імен файлів, форматів за замовчуванням або розташування інструментів. + Зберігайте резервну копію десь поза папкою програми, якщо ви плануєте очистити дані, перевстановити або перемістити пристрої. + Відновіть налаштування перед обробкою важливих пакетів, щоб параметри за замовчуванням і поведінка збереження відповідали вашим старим налаштуванням. + Змінюйте розмір і конвертуйте зображення + Змінюйте розмір, формат, якість і метадані для одного чи кількох зображень + Створюйте копії зі зміненим розміром + Змінити розмір і конвертувати — це основний інструмент для прогнозованих розмірів і легких вихідних файлів. Використовуйте його, коли розмір зображення, вихідний формат і поведінка метаданих мають значення водночас. + Виберіть одне чи кілька зображень, а потім виберіть, чи важливі розміри, масштаб чи розмір файлу. + Виберіть вихідний формат і якість; використовуйте PNG або WebP, коли потрібно зберегти прозорість. + Перегляньте результат, а потім збережіть або поділіться створеними копіями, не змінюючи оригінали. + Змінити розмір відповідно до розміру файлу + Встановіть обмеження розміру, коли веб-сайт, месенджер або форма відхиляють важкі зображення + Відповідайте суворим обмеженням завантаження + Змінювати розмір за розміром файлу краще, ніж вгадувати значення якості, коли адресат приймає лише файли, менші за певний ліміт. Інструмент може регулювати стиснення та розміри для досягнення мети, намагаючись зберегти результат придатним для використання. + Введіть цільовий розмір трохи нижче реального ліміту завантаження, щоб залишити місце для метаданих і змін постачальника. + Віддавайте перевагу форматам із втратами фотографій, коли ціль невелика; PNG може залишатися великим навіть після зміни розміру. + Перевірте грані, текст і краї після експорту, оскільки агресивні цільові розміри можуть викликати видимі артефакти. + Обмеження зміни розміру + Зменшуйте лише зображення, які перевищують максимальну ширину, висоту або обмеження файлу + Уникайте зміни розміру зображень, які вже є хорошими + Обмежити зміну розміру корисно для змішаних папок, де одні зображення величезні, а інші вже підготовлені. Замість того, щоб примусово змінювати розмір кожного файлу, він зберігає прийнятні зображення ближче до їх оригінальної якості. + Встановіть максимальні розміри або обмеження на основі місця призначення, а не змінюйте розмір кожного файлу наосліп. + Увімкніть поведінку стилю «пропустити, якщо більше», якщо ви хочете уникнути виходів, які стають важчими за джерела. + Використовуйте це для пакетів із різних камер, скріншотів або завантажень, де розмір джерела сильно відрізняється. + Обрізайте, повертайте та вирівнюйте + Перед експортом або використанням зображення в інших інструментах обрамте важливу область + Очистіть композицію + Обрізання спочатку робить пізніші фільтри, оптичне розпізнавання символів, PDF-сторінки та результати обміну чистішими. + Відкрийте «Обтинання» та виберіть зображення, яке потрібно налаштувати. + Використовуйте вільне кадрування або співвідношення сторін, коли результат має відповідати публікації, документу чи аватару. + Поверніть або переверніть перед збереженням, щоб пізніші інструменти отримали виправлене зображення. + Застосуйте фільтри та налаштування + Створіть редагований стек фільтрів і попередньо перегляньте результат перед експортом + Налаштування зовнішнього вигляду зображення + Фільтри корисні для швидкого виправлення, стилізованого виведення та підготовки зображень для OCR або друку. Невеликі зміни контрасту, різкості та яскравості можуть мати більше значення, ніж важкі ефекти, коли зображення містить текст або дрібні деталі. + Додавайте фільтри один за іншим і слідкуйте за попереднім переглядом після кожної зміни. + Налаштуйте яскравість, контраст, різкість, розмиття, колір або маски залежно від завдання. + Експортуйте лише тоді, коли попередній перегляд відповідає цільовому пристрою, документу чи соціальній платформі. + Перший попередній перегляд + Перевірте зображення перед тим, як вибрати більш важкий інструмент редагування, перетворення чи очищення + Перевірте, що ви насправді отримали + Попередній перегляд зображень корисний, коли файл надходить із чату, хмарного сховища чи іншої програми, і ви не впевнені, що він містить. Перевірка розмірів, прозорості, орієнтації та візуальної якості спершу допоможе вибрати правильний інструмент подальшого перегляду. + Відкрийте попередній перегляд зображень, коли вам потрібно перевірити кілька зображень, перш ніж вирішити, що з ними робити. + Шукайте проблеми з обертанням, прозорі області, артефакти стиснення та неочікувано великі розміри. + Переходьте до «Змінити розмір», «Обрізати», «Порівняти», «EXIF» або «Видалити фон» лише після того, як дізнаєтеся, що потрібно виправити. + Видаліть або відновіть фон + Видаліть фон автоматично або уточніть краї вручну за допомогою інструментів відновлення + Збережіть тему, решту видаліть + Видалення фону працює найкраще, якщо ви поєднуєте автоматичний вибір із ретельним ручним очищенням. Остаточний формат експорту має таке ж значення, як і маска, оскільки JPEG зрівняє прозорі області, навіть якщо попередній перегляд виглядає правильно. + Почніть з автоматичного видалення, якщо об’єкт чітко відділений від фону. + Збільште масштаб і перемикайтеся між видаленням і відновленням, щоб виправити волосся, кути, тіні та дрібні деталі. + Збережіть у форматі PNG або WebP, якщо вам потрібна прозорість кінцевого файлу. + Малювати, розмічати та водяні знаки + Додайте стрілки, примітки, виділення, підписи або повторювані водяні знаки + Додайте видиму інформацію + Інструменти розмітки допомагають пояснити зображення, а водяні знаки допомагають брендувати або захищати експортовані файли. + Використовуйте Draw, коли вам потрібні довільні нотатки, фігури, виділення або швидкі анотації. + Використовуйте водяні знаки, коли той самий текст або позначка зображення має з’являтися на виходах. + Перед експортом перевірте непрозорість, положення та масштаб, щоб позначку було видно, але не відволікало. + Малювати за замовчуванням + Встановіть ширину лінії, колір, режим шляху, блокування орієнтації та лупу для швидшої розмітки + Зробіть малюнок передбачуваним + Налаштування малювання корисні, коли ви часто коментуєте знімки екрана, позначаєте документи чи підписуєте зображення. Стандартні налаштування скорочують час налаштування, а лупа та блокування орієнтації спрощують точне редагування на маленьких екранах. + Установіть ширину лінії за замовчуванням і намалюйте колір відповідно до значень, які ви найчастіше використовуєте для стрілок, виділень або підписів. + Використовуйте режим контуру за замовчуванням, коли ви зазвичай малюєте штрихи, фігури або розмітку одного типу. + Увімкніть лупу або блокування орієнтації, коли введення пальцем ускладнює точне розміщення дрібних деталей. + Макети з кількома зображеннями + Виберіть правильний інструмент для кількох зображень, перш ніж створювати скріншоти, сканування чи порівняльні смужки + Використовуйте правильний інструмент макета + Ці інструменти звучать подібно, але кожен налаштований на різний тип виведення кількох зображень. Вибравши спочатку правильний, ви уникаєте боротьби з елементами керування макетом, які були розроблені для іншого результату. + Використовуйте Collage Maker для розроблених макетів із кількома зображеннями в одній композиції. + Використовуйте зшивання або накладання зображень, коли зображення мають стати однією довгою вертикальною або горизонтальною смугою. + Використовуйте розділення зображення, коли одне велике зображення має стати кількома меншими частинами. + Перетворення форматів зображень + Створюйте копії JPEG, PNG, WebP, AVIF, JXL та інші без редагування пікселів вручну + Виберіть формат роботи + Різні формати кращі для фотографій, скріншотів, прозорості, стиснення чи сумісності. Перетворення є найбезпечнішим, коли ви розумієте, що підтримує цільова програма та чи містить джерело альфа-версію, анімацію чи метадані, які ви хочете зберегти. + Використовуйте JPEG для сумісних фотографій, PNG для зображень без втрат і альфа-версії та WebP для компактного обміну. + Налаштуйте якість, якщо формат це підтримує; менші значення зменшують розмір, але можуть додавати артефакти. + Зберігайте оригінали, доки не перевірите, що конвертовані файли правильно відкриваються в цільовій програмі. + Перевірте EXIF ​​і конфіденційність + Перегляньте, відредагуйте або видаліть метадані, перш ніж ділитися конфіденційними фотографіями + Контролюйте приховані дані зображення + EXIF може містити дані камери, дати, розташування, орієнтацію та інші деталі, які не видно на зображенні. Варто перевірити, перш ніж оприлюднювати публічний доступ, звіти служби підтримки, списки на ринках або будь-яку фотографію, яка може виявити приватне місце. + Відкрийте інструменти EXIF, перш ніж ділитися фотографіями, які можуть містити інформацію про місцезнаходження або особисту інформацію про пристрій. + Видаліть конфіденційні поля або відредагуйте неправильні теги, такі як дата, орієнтація, автор або дані камери. + Збережіть нову копію та поділіться цією копією замість оригіналу, коли конфіденційність має значення. + Автоматичне очищення EXIF + Видаляйте метадані за замовчуванням, вирішуючи, чи слід зберігати дати + Зробіть конфіденційність стандартною + Група налаштувань EXIF ​​корисна, якщо потрібно, щоб очищення конфіденційності відбувалося автоматично, а не запам’ятовувалося для кожного експорту. Зберігати дату та час – це окреме рішення, оскільки дати можуть бути корисними для сортування, але чутливими для спільного використання. + Увімкніть завжди чистий EXIF, якщо більшість експортованих файлів призначено для публічного або напіввідкритого доступу. + Увімкніть функцію Keep Date Time лише тоді, коли збереження часу зйомки важливіше, ніж видалення кожної позначки часу. + Використовуйте ручне редагування EXIF ​​для одноразових файлів, де вам потрібно зберегти одні поля та видалити інші. + Контроль імен файлів + Використовуйте префікси, суфікси, мітки часу, попередні налаштування, контрольні суми та порядкові номери + Зробіть експорт легким для пошуку + Хороший шаблон імені файлу допомагає сортувати пакети та запобігає випадковому перезапису. Налаштування імен файлів особливо корисні, коли експорт повертається до вихідної папки, де подібні імена можуть швидко заплутатися. + Установіть префікс імені файлу, суфікс, мітку часу, оригінальну назву, попередньо встановлену інформацію або параметри послідовності в налаштуваннях. + Використовуйте порядкові номери для пакетів, де порядок має значення, наприклад для сторінок, рамок або колажів. + Увімкніть перезапис лише тоді, коли ви впевнені, що заміна наявних файлів безпечна для поточної папки. + Перезаписати файли + Замінюйте результати відповідними іменами, лише якщо ваш робочий процес є навмисно деструктивним + Обережно використовуйте режим перезапису + Перезаписати файли змінює спосіб обробки конфліктів імен. Це корисно для повторного експорту в ту саму папку, але воно також може замінити попередні результати, якщо ваш шаблон назви файлу знову створює те саме ім’я. + Увімкніть перезапис лише для папок, де очікується заміна старих згенерованих файлів, які можна відновити. + Тримайте перезапис вимкненим під час експорту оригіналів, клієнтських файлів, одноразових сканувань або будь-чого без резервної копії. + Поєднайте перезапис із навмисним шаблоном імен файлів, щоб замінити лише призначені виходи. + Шаблони імен файлів + Комбінуйте оригінальні назви, префікси, суфікси, мітки часу, попередні налаштування, режим масштабування та контрольні суми + Створення імен, які пояснюють результат + Параметри шаблону імені файлу можуть перетворити експортовані файли на корисну історію того, що з ними сталося. Це має значення для пакетів, оскільки перегляд папки може бути єдиним місцем, де можна розрізнити оригінальний розмір, попередні налаштування, формат і порядок обробки. + Використовуйте оригінальне ім’я файлу, префікс, суфікс і мітку часу, якщо вам потрібні читабельні імена для ручного сортування. + Додайте попередні налаштування, режим масштабування, розмір файлу або інформацію про контрольну суму, коли результати мають документувати, як вони були створені. + Уникайте випадкових імен для робочих процесів, у яких файли мають бути пов’язані з оригіналами чи порядком сторінок. + Виберіть місце збереження файлів + Запобігайте втраті експортованих файлів, налаштувавши папки, зберігаючи вихідні папки та місця для одноразового збереження + Тримайте результати передбачуваними + Поведінка збереження має найбільше значення, коли ви обробляєте багато файлів або ділитеся файлами з хмарних постачальників. Параметри папки визначають, чи зберігаються результати в одному відомому місці, відображаються поряд з оригіналами чи тимчасово відправляються в інше місце для виконання певного завдання. + Встановіть папку збереження за умовчанням, якщо ви завжди хочете експортувати в одному місці. + Використовуйте save-to-original-folder для робочих циклів очищення, де вихідні дані мають залишатися поруч із вихідним файлом. + Використовуйте одноразове місце збереження, коли одне завдання потребує іншого місця призначення, не змінюючи типове місце. + Папки для одноразового збереження + Надішліть наступні експорти до тимчасової папки, не змінюючи свого звичайного призначення + Тримайте спеціальні роботи окремо + Одноразове місце збереження корисне для тимчасових проектів, клієнтських папок, сеансів очищення або експорту, які не слід змішувати зі звичайною папкою Image Toolbox. Це дає короткочасне призначення без переписування налаштувань за замовчуванням. + Виберіть одноразову папку перед початком завдання, яке не належить до вашого звичайного місця експорту. + Використовуйте його для коротких проектів, спільних папок або пакетів, де результати повинні залишатися згрупованими разом. + Поверніться до папки за замовчуванням після завдання, щоб подальші експортовані файли несподівано не потрапили у тимчасове розташування. + Збалансуйте якість і розмір файлу + Зрозумійте, коли потрібно змінити розміри, формат або якість, а не вгадувати + Навмисно зменшуйте файли + Розмір файлу залежить від розмірів зображення, формату, якості, прозорості та складності вмісту. Якщо файл все ще занадто важкий, зміна розмірів зазвичай допомагає більше, ніж багаторазове зниження якості. + Спершу змініть розміри, коли зображення більше, ніж може відобразити місце призначення. + Нижча якість лише для форматів із втратами даних і перевірка тексту, країв, градієнтів і граней після експорту. + Змініть формат, коли зміни якості недостатньо, наприклад, WebP для спільного використання або PNG для чітких прозорих ресурсів. + Робота з анімаційними форматами + Використовуйте інструменти GIF, APNG, WebP і JXL, якщо файл містить кадри замість одного растрового зображення + Не розглядайте кожне зображення як нерухоме + Для анімованих форматів потрібні інструменти, які розуміють кадри, тривалість і сумісність. + Використовуйте інструменти GIF або APNG, коли вам потрібні операції на рівні кадру для цих форматів. + Використовуйте інструменти WebP, коли вам потрібне сучасне стиснення або анімований вихід WebP. + Перегляньте час анімації, перш ніж ділитися, оскільки деякі платформи перетворюють або зводять анімовані зображення. + Порівняйте та переглядайте результати + Перевірте зміни, розмір файлу та візуальні відмінності, перш ніж експортувати + Перевірте, перш ніж ділитися + Інструменти порівняння допомагають завчасно виявити артефакти стиснення, неправильні кольори або небажані редагування. + Відкрийте «Порівняти», якщо ви хочете перевірити дві версії поруч. + Збільште краї, текст, градієнти та прозорі області, де проблеми найлегше не помітити. + Якщо результат надто важкий або розмитий, поверніться до попереднього інструменту та відрегулюйте якість або формат. + Використовуйте центр інструментів PDF + Об’єднуйте, розділяйте, обертайте, видаляйте сторінки, стискайте, захищайте PDF-файли, OCR і водяні знаки + Виберіть операцію PDF + Концентратор PDF групує документовані дії за однією точкою входу, щоб ви могли вибрати точну операцію. Почніть там, коли файл уже є PDF, і використовуйте Images to PDF або Document Scanner, коли джерелом все ще є набір зображень. + Відкрийте PDF Tools і виберіть операцію, яка відповідає вашій меті. + Виберіть вихідний PDF-файл або зображення, а потім перегляньте параметри порядку сторінок, обертання, захисту чи OCR. + Збережіть створений PDF-файл і відкрийте його один раз, щоб підтвердити кількість сторінок, порядок і читабельність. + Перетворюйте зображення в PDF + Об’єднайте скани, знімки екрана, квитанції або фотографії в один документ, яким можна поділитися + Створіть чистий документ + Зображення стають кращими сторінками PDF, коли їх обрізають, упорядковують і змінюють розмір. Ставтеся до списку зображень як до стосу сторінок: кожен поворот, поле та вибір розміру сторінки впливають на те, наскільки читабельним буде остаточний документ. + Спочатку обріжте або поверніть зображення, якщо краї сторінки, тіні або орієнтація неправильні. + Виберіть зображення в потрібному порядку сторінок і відрегулюйте розмір сторінки або поля, якщо це пропонує інструмент. + Експортуйте PDF-файл, а потім перевірте, чи всі сторінки доступні для читання, перш ніж надсилати його. + Сканувати документи + Знімайте паперові сторінки та готуйте їх для PDF, OCR або спільного використання + Знімайте читабельні сторінки + Сканування документів найкраще працює з плоскими сторінками, хорошим освітленням і виправленою перспективою. Чисте сканування перед OCR або експортом PDF економить час, оскільки розпізнавання тексту та стиснення залежать від якості сторінки. + Покладіть документ на контрастну поверхню з достатньою кількістю світла та мінімальною кількістю тіней. + Відкоригуйте виявлені кути або обріжте вручну, щоб сторінка чітко заповнювала кадр. + Експортуйте як зображення або PDF, а потім запустіть OCR, якщо вам потрібен текст, який можна виділити. + Захистіть або розблокуйте файли PDF + Обережно використовуйте паролі та за можливості зберігайте вихідну копію, яку можна редагувати + Керуйтеся доступом до PDF безпечно + Інструменти захисту корисні для контрольованого обміну, але паролі легко втратити та важко відновити. Захистіть копії для розповсюдження, зберігайте незахищені вихідні файли окремо та перевіряйте результат перед видаленням. + Захистіть копію PDF-файлу, а не свій єдиний вихідний документ. + Зберігайте пароль у безпечному місці, перш ніж ділитися захищеним файлом. + Використовуйте розблокування лише для файлів, які вам дозволено редагувати, а потім перевірте, чи розблокована копія відкривається правильно. + Упорядкуйте PDF-сторінки + Об’єднуйте, розділяйте, змінюйте порядок, обертайте, обрізайте, видаляйте сторінки та навмисно додавайте номери сторінок + Виправте структуру документа перед оформленням + Інструменти PDF-сторінок найлегше використовувати в тому ж порядку, у якому ви б підготували паперовий документ: зібрати сторінки, видалити неправильні, упорядкувати їх, виправити орієнтацію, а потім додати останні деталі, як-от номери сторінок або водяні знаки. + Спершу скористайтеся об’єднанням або перетворенням зображень у PDF, якщо документ усе ще розділено на кілька файлів. + Переставляйте, обертайте, обрізайте або видаляйте сторінки перед додаванням номерів сторінок, підписів або водяних знаків. + Попередньо перегляньте остаточний PDF-файл після структурних змін, оскільки одну відсутню або повернуту сторінку може бути важко помітити пізніше. + PDF анотації + Видаліть анотації або зведіть їх, коли глядачі показують коментарі та позначки інакше + Контролюйте те, що залишається видимим + Анотаціями можуть бути коментарі, виділення, малюнки, позначки форми або інші додаткові об’єкти PDF. Деякі програми перегляду відображають їх по-іншому, тому їх видалення або зведення може зробити спільні документи більш передбачуваними. + Видаліть анотації, якщо коментарі, виділення або позначки рецензії не повинні бути включені до спільної копії. + Зрівняти, коли видимі позначки повинні залишатися на сторінці, але більше не поводитись як редаговані анотації. + Зберігайте оригінальну копію, оскільки видалення або зведення анотацій може бути важко повернути назад. + Розпізнати текст + Витягніть текст із зображень за допомогою механізмів оптичного розпізнавання тексту та мов, які можна вибрати + Отримайте кращі результати OCR + Точність OCR залежить від чіткості зображення, мовних даних, контрастності та орієнтації сторінки. Найкращі результати зазвичай досягаються, якщо спочатку підготувати зображення: обрізати шуми, повернути вертикально, покращити читабельність, а потім розпізнати текст. + Перед розпізнаванням обріжте зображення до текстової області та поверніть його вертикально. + Виберіть правильну мову та завантажте мовні дані, якщо це вимагає програма. + Скопіюйте або поділіться розпізнаним текстом, а потім вручну перевірте імена, цифри та знаки пунктуації. + Виберіть мову OCR + Покращуйте розпізнавання, зіставляючи сценарії, мови та завантажені моделі OCR + Установіть відповідність OCR до тексту + Неправильна мова OCR може призвести до того, що хороші фотографії виглядатимуть як погано розпізнані. Мовні дані важливі для скриптів, знаків пунктуації, чисел і змішаних документів, тому виберіть мову, яка відображається на зображенні, а не мову інтерфейсу телефону. + Виберіть мову або шрифт, який відображається на зображенні, а не лише мову телефону. + Завантажте відсутні дані перед роботою в автономному режимі або обробкою пакета. + Для документів з різними мовами спочатку запустіть найважливішу мову та вручну перевірте імена та номери. + PDF-файли з можливістю пошуку + Записуйте текст OCR назад у файли, щоб можна було шукати та копіювати відскановані сторінки + Перетворіть скановані документи на документи з можливістю пошуку + OCR може зробити більше, ніж копіювати текст у буфер обміну. Коли ви записуєте розпізнаний текст у файл або PDF-файл із можливістю пошуку, зображення сторінки залишається видимим, а текст стає легше шукати, вибирати, індексувати чи архівувати. + Використовуйте PDF-файл із можливістю пошуку для відсканованих документів, які мають виглядати як оригінальні сторінки. + Використовуйте запис у файл, коли вам потрібен лише витягнутий текст і не дбаєте про зображення сторінки. + Перевіряйте важливі номери, імена та таблиці вручну, оскільки OCR-текст може бути придатним для пошуку, але все ще недосконалим. + Скануйте QR-коди + Читайте QR-вміст із вхідних даних камери або збережених зображень, якщо вони доступні + Безпечно читайте коди + QR-коди можуть містити посилання, дані Wi-Fi, контакти, текст або інші корисні дані. + Відкрийте Сканувати QR-код і тримайте код чітким, рівним і повністю всередині рамки. + Перегляньте розкодований вміст, перш ніж відкривати посилання або копіювати конфіденційні дані. + Якщо сканування не вдається, покращте освітлення, обріжте код або спробуйте вихідне зображення з вищою роздільною здатністю. + Використовуйте Base64 і контрольні суми + Кодуйте зображення як текст, декодуйте текст назад у зображення та перевіряйте цілісність файлу + Перехід між файлами та текстом + Base64 корисний для невеликого корисного навантаження зображень, тоді як контрольні суми допомагають підтвердити, що файли не змінювалися. Ці інструменти найбільш корисні в технічних робочих процесах, звітах про підтримку, передачі файлів і місцях, де двійкові файли мають тимчасово стати текстовими. + Використовуйте інструменти Base64 для кодування невеликого зображення, коли для робочого процесу потрібен текст замість двійкового файлу. + Декодуйте Base64 лише з надійних джерел і перевірте попередній перегляд перед збереженням. + Використовуйте інструменти контрольної суми, коли вам потрібно порівняти експортовані файли або підтвердити завантаження/завантаження. + Пакуйте або захищайте дані + Використовуйте інструменти ZIP і шифрування для групування файлів або перетворення текстових даних + Зберігайте пов’язані файли разом + Інструменти пакування корисні, коли кілька вихідних даних мають переміщатися як один файл. Вони також підходять для зберігання згенерованих зображень, PDF-файлів, журналів і метаданих разом, коли вам потрібно надіслати повний набір результатів. + Використовуйте ZIP, коли потрібно надіслати разом багато експортованих зображень або документів. + Використовуйте засоби шифрування, лише якщо ви розумієте вибраний метод і можете безпечно зберігати необхідні ключі. + Перевірте створений архів або трансформований текст перед видаленням вихідних файлів. + Контрольні суми в іменах + Використовуйте імена файлів на основі контрольної суми, коли унікальність і цілісність важливіші за читабельність + Назвіть файли за вмістом + Імена файлів контрольної суми корисні, коли експортовані файли можуть мати повторювані видимі імена або коли вам потрібно довести, що два файли ідентичні. Вони менш зручні для ручного перегляду, тому використовуйте їх для технічних архівів і робочих процесів перевірки. + Увімкніть контрольну суму як назву файлу, якщо ідентичність вмісту важливіша за назву, яку можна прочитати людиною. + Використовуйте його для архівів, дедуплікації, повторного експорту або файлів, які можна завантажувати та завантажувати знову. + Поєднуйте імена контрольних сум із папками чи метаданими, коли людям все ще потрібно зрозуміти, що представляє файл. + Виберіть кольори із зображень + Зразки пікселів, копіювання кодів кольорів і повторне використання кольорів у палітрах або дизайнах + Зразок точного кольору + Вибір кольорів корисний для підбору дизайну, виділення кольорів бренду або перевірки значень зображення. Масштабування має значення, оскільки згладжування, тіні та стиснення можуть дещо відрізняти колір сусідніх пікселів від очікуваного. + Відкрийте «Вибрати колір із зображення» та виберіть вихідне зображення з потрібним кольором. + Збільште масштаб перед вибіркою дрібних деталей, щоб пікселі поблизу не вплинули на результат. + Скопіюйте колір у форматі, який вимагається для призначення, наприклад HEX, RGB або HSV. + Створюйте палітри та використовуйте бібліотеку кольорів + Витягуйте палітри, переглядайте збережені кольори та зберігайте послідовність візуальних систем + Створіть багаторазову палітру + Палітри допомагають підтримувати візуальну узгодженість експортованої графіки, водяних знаків і малюнків. Вони особливо корисні, коли ви неодноразово коментуєте знімки екрана, готуєте активи бренду або потребуєте однакових кольорів у кількох інструментах. + Створіть палітру із зображення або додайте кольори вручну, коли ви вже знаєте значення. + Назвіть або впорядкуйте важливі кольори, щоб потім знайти їх знову. + Використовуйте скопійовані значення в Draw, водяних знаках, градієнтах, SVG або зовнішніх інструментах дизайну. + Створення градієнтів + Створюйте градієнтні зображення для фону, заповнювачів і візуальних експериментів + Керуйте переходами кольорів + Інструменти градієнта корисні, коли вам потрібне згенероване зображення замість редагування існуючого. Вони можуть стати чистим фоном, заповнювачами, шпалерами, масками або простими ресурсами для зовнішніх інструментів дизайну. + Виберіть тип градієнта та спочатку встановіть важливі кольори. + Налаштуйте напрямок, зупинки, плавність і вихідний розмір для цільового варіанту використання. + Експортуйте у форматі, який відповідає цільовому формату, зазвичай у форматі PNG для чистої згенерованої графіки. + Кольорова доступність + Використовуйте динамічні кольори, контраст і параметри дальтоніку, коли інтерфейс користувача важко читати + Спростіть використання кольорів + Параметри кольору впливають на зручність, читабельність і швидкість сканування елементів керування. Якщо фішки інструментів, повзунки або вибрані стани важко розрізнити, параметри теми та спеціальних можливостей можуть бути кориснішими, ніж просто змінити темний режим. + Спробуйте динамічні кольори, якщо ви хочете, щоб програма дотримувалася поточної палітри шпалер. + Збільште контраст або змініть схему, якщо кнопки та поверхні недостатньо виділяються. + Використовуйте варіанти дальтоніки, якщо деякі стани або акценти важко розрізнити. + Альфа фону + Керуйте попереднім переглядом або кольором заливки, який використовується навколо прозорих PNG, WebP і подібних результатів + Розуміння прозорих попередніх переглядів + Колір фону для альфа-форматів може полегшити перевірку прозорих зображень, але важливо знати, чи змінюєте ви поведінку попереднього перегляду/заливки чи вирівнюєте кінцевий результат. Це важливо для наклейок, логотипів і зображень із видаленим фоном. + Спершу використовуйте формат із підтримкою альфа-версії, наприклад PNG або WebP, якщо прозорі пікселі мають витримати експорт. + Налаштуйте параметри кольору фону, якщо прозорі краї важко побачити на поверхні програми. + Експортуйте невеликий тест і повторно відкрийте його в іншій програмі, щоб перевірити, чи збережено прозорість або вибрану заливку. + Вибір моделі AI + Виберіть модель за типом зображення та дефектом замість того, щоб спочатку вибрати найбільшу + Установіть відповідність між моделлю та пошкодженням + Інструменти штучного інтелекту можуть завантажувати або імпортувати різні моделі ONNX/ORT, і кожна модель навчена для певного типу проблеми. Модель для блоків JPEG, очищення лінії аніме, усунення шумів, видалення фону, розфарбування або масштабування може дати гірші результати, якщо використовується на неправильному типі зображення. + Перед завантаженням прочитайте опис моделі; виберіть модель, яка визначає вашу справжню проблему, наприклад стиснення, шум, напівтони, розмиття або видалення фону. + Випробовуючи новий тип зображення, починайте з меншої або більш конкретної моделі, а потім порівнюйте з більш важкими моделями, лише якщо результат недостатньо хороший. + Зберігайте лише ті моделі, якими ви справді користуєтеся, оскільки завантажені та імпортовані моделі можуть зайняти значний обсяг пам’яті. + Зрозумійте моделі сімей + Назви моделей часто вказують на ціль: високоякісні моделі у стилі RealESRGAN, моделі SCUNet і FBCNN очищають шум або стиснення, фонові моделі створюють маски, а розфарбовувачі додають колір до введення в градаціях сірого. Імпортовані спеціальні моделі можуть бути потужними, але вони мають відповідати очікуваній формі вводу/виводу та використовувати підтримуваний формат ONNX або ORT. + Використовуйте засоби збільшення розміру, коли вам потрібно більше пікселів, засоби зменшення шуму, коли зображення зернисте, і засоби видалення артефактів, коли головною проблемою є блоки стиснення або смуги. + Використовуйте фонові моделі лише для поділу об’єктів; це не те саме, що загальне видалення об’єктів або покращення зображення. + Імпортуйте власні моделі лише з джерел, яким ви довіряєте, і перевіряйте їх на маленькому зображенні перед використанням важливих файлів. + Параметри ШІ + Налаштуйте розмір блоку, перекриття та паралельні робочі елементи, щоб збалансувати якість, швидкість і пам’ять + Збалансуйте швидкість зі стабільністю + Розмір фрагмента контролює, наскільки великими є фрагменти зображення до їх обробки моделлю. Перекриття змішує сусідні шматки, щоб приховати межі. Паралельні виконавці можуть пришвидшити обробку, але кожен робочий процес потребує пам’яті, тому високі значення можуть зробити великі зображення нестабільними на телефонах з обмеженою пам’яттю. + Використовуйте більші фрагменти для більш гладкого контексту, коли ваш пристрій надійно обробляє модель і зображення невелике. + Збільште перекриття, коли стануть видимими межі фрагментів, але пам’ятайте, що більше перекриття означає більше повторюваної роботи. + Виховувати паралельних працівників тільки після стабільного тесту; якщо обробка сповільнюється, нагрівається пристрій або виходить з ладу, спершу скоротіть працівників. + Уникайте артефактів фрагментів + Розбиття на фрагменти дозволяє додатку одночасно обробляти зображення, розмір яких перевищує модель. Компроміс полягає в тому, що кожна плитка має менше навколишнього контексту, тому низьке перекриття або занадто малі шматки можуть створювати видимі рамки, зміни текстури або неузгоджені деталі між сусідніми областями. + Збільште перекриття, якщо ви бачите прямокутні рамки, повторювані зміни текстури або стрибки деталей між обробленими областями. + Зменшіть розмір блоку, якщо процес не вдається перед створенням виводу, особливо на великих зображеннях або важких моделях. + Збережіть невеликий тест кадрування перед обробкою повного зображення, щоб ви могли швидко порівняти параметри. + Стабільність ШІ + Зменште розмір блоку, робочу здатність і роздільну здатність джерела, коли обробка AI виходить з ладу або зависає + Відновлення після важкої роботи ШІ + Обробка ШІ є одним із найважчих робочих процесів у програмі. Збої, невдалі сеанси, зависання або раптові перезапуски програми зазвичай означають, що модель, роздільна здатність джерела, розмір блоку або кількість паралельних робочих надто вимогливі для поточного стану пристрою. + Якщо програма аварійно завершує роботу або не вдається відкрити модельний сеанс, зменшіть розмір паралельних робочих частин і блоку, а потім спробуйте той самий образ ще раз. + Змінюйте розмір дуже великих зображень перед обробкою штучним інтелектом, якщо ціль не вимагає оригінальної роздільної здатності. + Закрийте інші важкі додатки, використовуйте меншу модель або обробляйте менше файлів одночасно, коли напруга пам’яті продовжує повертатися. + Діагностувати несправності моделі + Невдалий запуск ШІ не завжди означає, що зображення погане. Файл моделі може бути неповним, непідтримуваним, завеликим для пам’яті або не відповідати операції. Коли програма повідомляє про невдалий сеанс, сприймайте це як сигнал, щоб спочатку спростити модель і параметри. + Видаліть і повторно завантажте модель, якщо вона не працює відразу після завантаження або поводиться так, ніби файл пошкоджено. + Спробуйте вбудовану модель для завантаження, перш ніж звинувачувати імпортовану спеціальну модель, оскільки імпортовані моделі можуть мати несумісні вхідні дані. + Використовуйте журнали програми після відтворення збою, якщо вам потрібно повідомити про збій або помилку сеансу. + Експериментуйте в Shader Studio + Використовуйте шейдерні ефекти графічного процесора для розширеної обробки зображень і користувацького вигляду + Обережно працюйте з шейдерами + Shader Studio є потужною, але код і параметри шейдера потребують невеликих змін, які можна перевірити. Ставтеся до нього як до експериментального інструменту: зберігайте робочу базову лінію, змінюйте одну річ за раз і тестуйте зображення з різними розмірами, перш ніж довіряти попередньому налаштуванню. + Перш ніж додавати параметри, почніть із відомого робочого шейдера або простого ефекту. + Змінюйте один параметр або блок коду за раз і перевіряйте попередній перегляд на наявність помилок. + Експортуйте стилі лише після їх тестування на кількох зображеннях різних розмірів. + Зробіть зображення SVG або ASCII + Створюйте векторні ресурси або текстові зображення для творчого результату + Виберіть тип творчого результату + Інструменти SVG і ASCII служать різним цілям: масштабована графіка або рендеринг текстового стилю. Обидва є творчими перетвореннями, тому попередній перегляд у кінцевому розмірі важливіший, ніж оцінка результату за крихітною мініатюрою. + Використовуйте SVG Maker, коли результат потрібно чітко масштабувати або повторно використовувати в робочих процесах проектування. + Використовуйте ASCII Art, якщо вам потрібно стилізоване текстове представлення зображення. + Попередній перегляд у остаточному розмірі, оскільки дрібні деталі можуть зникнути у згенерованих результатах. + Створення шумових текстур + Створюйте процедурні текстури для фонів, масок, накладень або експериментів + Налаштуйте процедурний вихід + Генерація шуму створює нові зображення з параметрів, тому невеликі зміни можуть дати дуже різні результати. Це корисно для текстур, масок, накладень, заповнювачів або тестування стиснення з детальними шаблонами. + Виберіть тип шуму та вихідний розмір перед налаштуванням дрібних деталей. + Відрегулюйте масштаб, інтенсивність, кольори або початкові значення, доки візерунок не відповідатиме цільовому використанню. + Збережіть корисні результати та запишіть параметри, якщо вам знадобиться відтворити текстуру пізніше. + Проекти розмітки + Використовуйте файли проекту, коли анотації потрібно залишати доступними для редагування після закриття програми + Зберігайте багатошарові редагування для повторного використання + Файли проекту розмітки корисні, коли знімок екрана, діаграма чи зображення інструкцій можуть потребувати змін пізніше. Експортовані файли PNG/JPEG є остаточними зображеннями, тоді як файли проекту зберігають редаговані шари та стан редагування для майбутніх коригувань. + Використовуйте шари розмітки, коли анотації, виноски або елементи макета залишаються доступними для редагування. + Збережіть або повторно відкрийте файл проекту перед експортом остаточного зображення, якщо ви очікуєте більше змін. + Експортуйте звичайне зображення лише тоді, коли ви готові поділитися зведеною остаточною версією. + Шифрування файлів + Захистіть файли паролем і перевірте розшифровку перед видаленням будь-якої вихідної копії + Обережно поводьтеся з зашифрованими виходами + Інструменти шифрування можуть захистити файли за допомогою шифрування на основі пароля, але вони не замінять запам’ятовування ключа чи збереження резервних копій. Шифрування корисне для транспортування та зберігання, тоді як ZIP є кращим, коли основною метою є об’єднання кількох виходів. + Спочатку зашифруйте копію та зберігайте оригінал, доки не перевірите, чи працює розшифровка з паролем, який ви зберегли. + Використовуйте менеджер паролів або інше безпечне місце для ключа; програма не може вгадати втрачений пароль замість вас. + Діліться зашифрованими файлами лише з людьми, які також мають пароль і знають, яким інструментом чи методом їх розшифрувати. + Розставте інструменти + Інструменти групування, упорядкування, пошуку та закріплення, щоб головний екран відповідав вашому реальному робочому процесу + Зробіть головний екран швидшим + Параметри розташування інструментів варто налаштувати, коли ви знаєте, які інструменти ви використовуєте найчастіше. Групування допомагає досліджувати, спеціальний порядок допомагає м’язовій пам’яті, а вибране забезпечує доступність щоденних інструментів, навіть якщо повний список стає великим. + Використовуйте згрупований режим під час вивчення програми або коли ви віддаєте перевагу інструментам, упорядкованим за типом завдань. + Перейдіть до спеціального порядку, якщо ви вже знаєте, які інструменти часто використовуєте, і хочете менше прокручувань. + Використовуйте параметри пошуку та вибране разом для рідкісних інструментів, які ви пам’ятаєте за іменем, але не хочете, щоб їх постійно бачили. + Обробляти великі файли + Уникайте повільного експорту, тиску пам’яті та несподівано великого виведення + Скоротіть роботу перед експортом + Дуже великі зображення, великі пакети та високоякісні формати можуть займати більше пам’яті та часу. Як правило, найшвидше рішення полягає в тому, щоб зменшити обсяг роботи перед найважчою операцією, а не чекати, поки закінчиться той самий надто великий вхід. + Змінюйте розмір величезних зображень перед застосуванням жорстких фільтрів, OCR або конвертування PDF. + Спочатку використовуйте меншу партію для підтвердження налаштувань, а потім обробіть решту з тим же попереднім налаштуванням. + Нижча якість або змінення формату, якщо вихідний файл більший за очікуваний. + Кеш і попередній перегляд + Зрозумійте створені попередні перегляди, очищення кешу та використання пам’яті після важких сеансів + Збалансуйте пам’ять і швидкість + Створення попереднього перегляду та кеш-пам’ять можуть пришвидшити повторний перегляд, але важкі сеанси редагування можуть залишити тимчасові дані. Налаштування кешу допомагають, коли програма працює повільно, пам’яті мало або попередній перегляд виглядає несвіжим після багатьох експериментів. + Увімкніть попередній перегляд, коли перегляд багатьох зображень є важливішим, ніж мінімізація тимчасового зберігання. + Очищайте кеш після великих партій, невдалих експериментів або тривалих сеансів із багатьма файлами високої роздільної здатності. + Використовуйте автоматичне очищення кешу, якщо ви віддаєте перевагу очищенню пам’яті, а не підтримці попереднього перегляду в теплі між запусками. + Налаштуйте поведінку екрана + Використовуйте повноекранний режим, безпечний режим, параметри яскравості та системної панелі для цілеспрямованої роботи + Зробіть інтерфейс відповідним ситуації + Налаштування екрана допомагають, коли ви редагуєте в альбомній орієнтації, показуєте приватні зображення або потребуєте стабільної яскравості. Вони не потрібні для звичайного використання, але вони вирішують незручні ситуації, такі як перевірка QR, приватний попередній перегляд або обмежене редагування ландшафту. + Використовуйте налаштування повноекранного режиму або системної панелі, коли редагування простору важливіше, ніж навігаційний хром. + Використовуйте безпечний режим, коли приватні зображення не повинні з’являтися на знімках екрана чи попередньому перегляді програми. + Використовуйте контроль яскравості для інструментів, де темні зображення або QR-коди важко перевірити. + Зберігайте прозорість + Запобігайте тому, щоб прозорі фони ставали білими, чорними або згладженими + Використовуйте виведення з альфа-версією + Прозорість зберігається лише тоді, коли вибраний інструмент і вихідний формат підтримують альфа-версію. Якщо експортований фон стає білим або чорним, зазвичай проблема полягає у форматі виводу, налаштуваннях заливки/фону або цільовій програмі, яка зрівняла зображення. + Виберіть PNG або WebP під час експорту зображень, наклейок, логотипів або накладень без фону. + Уникайте JPEG для прозорого виведення, оскільки він не зберігає альфа-канал. + Перевірте налаштування, пов’язані з кольором фону для альфа-форматів, якщо попередній перегляд показує заповнений фон. + Виправте проблеми зі спільним доступом та імпортом + Використовуйте налаштування засобу вибору та підтримувані формати, якщо файли не відображаються або не відкриваються належним чином + Завантажте файл у додаток + Проблеми з імпортом часто виникають через дозволи постачальника, непідтримувані формати або поведінку засобу вибору. Файли, які надаються з хмарних додатків і месенджерів, можуть бути тимчасовими, тому вибір постійного джерела може бути надійнішим для тривалого редагування. + Спробуйте відкрити файл за допомогою засобу вибору системи замість ярлика останніх файлів. + Якщо формат не підтримується жодним інструментом, спочатку конвертуйте його або відкрийте більш спеціальний інструмент. + Перегляньте параметри засобу вибору зображень і запуску, якщо потоки спільного використання або пряме відкриття інструмента поводяться інакше, ніж очікувалося. + Підтвердження виходу + Уникайте втрати незбережених змін у разі випадкових жестів, натискань назад або перемикання інструментів + Захистити незавершену роботу + Підтвердження виходу з інструменту корисно, коли ви використовуєте навігацію жестами, редагуєте великі файли або часто перемикаєтеся між програмами. Він додає невелику паузу перед виходом з інструмента, щоб незавершені налаштування та попередній перегляд не були втрачені випадково. + Увімкніть підтвердження, якщо випадкові жести назад коли-небудь закривали інструмент перед експортом. + Вимкніть його, якщо ви здебільшого виконуєте швидкі одноетапні перетворення та віддаєте перевагу швидшій навігації. + Використовуйте його разом із попередніми настройками для тривалих робочих процесів, коли переналаштування налаштувань буде дратувати. + Використовуйте журнали, повідомляючи про проблеми + Зберіть корисну інформацію, перш ніж відкривати проблему або зв’язуватися з розробником + Повідомте про проблеми з контекстом + Набагато легше діагностувати чіткий звіт із кроками, версією програми, типом введення та журналами. Журнали є найбільш корисними відразу після відтворення проблеми, оскільки вони зберігають навколишній контекст свіжим. + Зверніть увагу на назву інструмента, точну дію та те, чи це відбувається з одним файлом чи з кожним файлом. + Відкрийте журнали програми після відтворення проблеми та поділіться відповідними результатами журналу, коли це можливо. + Долучайте знімки екрана або зразки файлів, лише якщо вони не містять конфіденційної інформації. + Фонову обробку зупинено + Android не дозволив вчасно запустити сповіщення про фонову обробку. Це обмеження системного часу, яке неможливо надійно виправити. Перезапустіть програму та повторіть спробу; може допомогти тримати програму відкритою та дозволяти сповіщення або фонову роботу. + Пропустити вибір файлів + Відкривайте інструменти швидше, коли введення зазвичай надходить із спільного доступу, буфера обміну чи попереднього екрана + Зробіть повторний запуск інструменту швидшим + Пропустити вибір файлу змінює перший крок підтримуваних інструментів. Це корисно, коли ви зазвичай входите в інструмент із уже вибраним зображенням або з дій спільного використання Android, але це може збентежити вас, якщо ви очікуєте, що кожен інструмент негайно запитуватиме файл. + Увімкніть його лише тоді, коли ваш звичайний потік уже надає вихідне зображення до відкриття інструмента. + Тримайте його вимкнутим під час вивчення програми, оскільки чіткий вибір робить кожен потік інструментів легшим для розуміння. + Якщо інструмент відкривається без явного введення, вимкніть це налаштування або почніть із «Поділитися/Відкрити за допомогою», щоб Android передасть файл першим. + Спочатку відкрийте редактор + Пропустіть попередній перегляд, коли спільне зображення має перейти безпосередньо до редагування + Виберіть попередній перегляд або редагування як першу зупинку + Open Edit Instead Of Preview призначений для користувачів, які вже знають, що хочуть змінити зображення. Попередній перегляд безпечніший, коли ви перевіряєте файли з чату чи хмарного сховища; пряме редагування швидше для скріншотів, швидкого кадрування, анотацій і одноразових виправлень. + Увімкніть пряме редагування, якщо більшість спільних зображень негайно потребують кадрування, розмітки, фільтрів або експорту змін. + Спершу залиште попередній перегляд, якщо ви часто перевіряєте метадані, розміри, прозорість або якість зображення, перш ніж приймати рішення. + Використовуйте це разом із вибраними, щоб спільні зображення були близькі до інструментів, якими ви насправді користуєтеся. + Попередній введення тексту + Введіть точні попередньо встановлені значення замість повторного налаштування елементів керування + Використовуйте точні значення, коли повзунки працюють повільно + Введення попередньо встановленого тексту допомагає, коли вам потрібні точні цифри для повторної роботи: розміри соціальних зображень, поля документів, цілі стиснення, ширина ліній або інші значення, які неприємно досягати за допомогою жестів. Це також корисно на невеликих екранах, де тонкий рух повзунка важчий. + Увімкніть введення тексту, якщо ви часто використовуєте точні розміри, відсотки, значення якості або параметри інструменту. + Збережіть значення як стиль, ввівши його один раз, а потім використовуйте його повторно замість повторного створення тих самих налаштувань. + Зберігайте елементи керування жестами для грубого візуального налаштування та введені значення для суворих вимог до виводу. + Зберегти близько до оригіналу + Розмістіть створені файли поруч із вихідними зображеннями, коли контекст папки має значення + Зберігайте результати з їхніми джерелами + Функція «Зберегти в оригінальну папку» зручна для папок камери, папок проектів, сканованих документів і клієнтських пакетів, де відредагована копія має залишатися поруч із джерелом. Він може бути менш охайним, ніж спеціальна вихідна папка, тому поєднайте його з чіткими суфіксами назв файлів або шаблонами. + Увімкніть його, коли кожна вихідна папка вже є значущою, а виходи мають залишатися там згрупованими. + Використовуйте суфікси імен файлів, префікси, мітки часу або шаблони, щоб відредаговані копії було легко відокремити від оригіналів. + Вимкніть його для змішаних пакетів, якщо ви хочете, щоб усі згенеровані файли були зібрані в одній папці експорту. + Пропустити більший вихід + Уникайте збереження перетворень, які неочікувано стають важчими за джерело + Захистіть партії від сюрпризів поганого розміру + Деякі перетворення збільшують файли, особливо скріншоти PNG, уже стиснуті фотографії, високоякісний WebP або зображення зі збереженням метаданих. Allow Skip If Larger запобігає заміні цих виходів меншим джерелом у робочих процесах, де головною метою є зменшення розміру. + Увімкніть його, якщо експорт призначений для стиснення, зміни розміру або підготовки файлів до обмежень на завантаження. + Перегляд пропущених файлів після пакета; можливо, вони вже оптимізовані або потребують іншого формату. + Вимкніть його, якщо якість, прозорість або сумісність формату важливіші за кінцевий розмір файлу. + Буфер обміну та посилання + Керуйте автоматичним введенням у буфер обміну та попереднім переглядом посилань для швидших текстових інструментів + Зробіть автоматичне введення передбачуваним + Налаштування буфера обміну та посилань зручні для робочих процесів QR, Base64, URL і тексту, але автоматичне введення має залишатися навмисним. Якщо програма сама заповнює поля, перевірте поведінку буфера обміну. якщо картки посилань шумлять або повільно, налаштуйте поведінку попереднього перегляду посилань. + Дозволити автоматичне вставлення з буфера обміну, коли ви часто копіюєте текст, і відразу відкривати відповідний інструмент. + Вимкніть автоматичне вставлення, якщо приватний вміст буфера обміну з’являється в інструментах, де ви цього не очікували. + Вимкніть попередній перегляд посилань, якщо попередній перегляд повільний, відволікає або непотрібний для вашого робочого процесу. + Компактні елементи керування + Використовуйте щільніші селектори та швидке розміщення налаштувань, коли на екрані мало місця + Розмістіть більше елементів керування на маленьких екранах + Компактні селектори та швидке розміщення налаштувань допомагають на маленьких телефонах, альбомне редагування та інструменти з багатьма параметрами. Компроміс полягає в тому, що елементи керування можуть здаватися щільнішими, тому це найкраще після того, як ви вже розпізнали загальні параметри. + Увімкніть компактні селектори, коли діалогові вікна чи списки параметрів здаються зависокими для екрана. + Відрегулюйте бік швидких налаштувань, якщо елементи керування інструментом легше дістатися з одного боку дисплея. + Поверніться до просторіших елементів керування, якщо ви все ще вивчаєте додаток або часто пропускаєте щільні опції. + Завантажте зображення з Інтернету + Вставте URL-адресу сторінки або зображення, попередньо перегляньте проаналізовані зображення, а потім збережіть або поділіться вибраними результатами + Умисно використовуйте веб-зображення + Web Image Loading аналізує джерела зображень із наданої URL-адреси, переглядає перше доступне зображення та дозволяє зберігати або ділитися вибраними проаналізованими зображеннями. Деякі сайти використовують тимчасові, захищені або згенеровані сценарієм посилання, тому сторінка, яка працює у браузері, може все одно не повертати придатних для використання зображень. + Вставте пряму URL-адресу зображення або URL-адресу сторінки та зачекайте, поки з’явиться проаналізований список зображень. + Використовуйте рамку або елементи керування виділенням, якщо сторінка містить кілька зображень і вам потрібні лише деякі з них. + Якщо нічого не завантажується, спробуйте спершу пряме посилання на зображення або завантажте через браузер; сайт може блокувати аналіз або використовувати приватні посилання. + Виберіть тип зміни розміру + Ознайомтеся з явними, гнучкими, обрізаними та відповідними перед тим, як надати зображенню нових розмірів + Керуйте формою, полотном і співвідношенням сторін + Тип зміни розміру вирішує, що станеться, якщо запитані ширина та висота не збігаються з оригінальним співвідношенням сторін. Це різниця між розтягуванням пікселів, збереженням пропорцій, обрізанням додаткової області або додаванням фонового полотна. + Використовуйте «Явний» лише тоді, коли спотворення є прийнятним або джерело вже має те саме співвідношення сторін, що й ціль. + Використовуйте Гнучкість, щоб зберегти пропорції, змінюючи розміри з важливого боку, особливо для фотографій і змішаних партій. + Використовуйте «Обтинання» для строгих рамок без рамок або «За розміром», коли все зображення має залишатися видимим усередині фіксованого полотна. + Виберіть режим масштабування зображення + Виберіть фільтр передискретизації, який контролює різкість, плавність, швидкість і артефакти країв + Змінюйте розмір без випадкового пом\'якшення + Режим масштабування зображення контролює спосіб обчислення нових пікселів під час зміни розміру. Прості режими є швидкими та передбачуваними, тоді як розширені фільтри можуть краще зберегти деталі, але можуть збільшити різкість країв, створити ореоли або зайняти більше часу на великих зображеннях. + Використовуйте Basic або Bilinear для швидкої щоденної зміни розміру, коли результат має бути лише меншим і чітким. + Використовуйте режими Lanczos, Robidoux, Spline або EWA, коли важливі деталі, текст або високоякісне зменшення масштабу. + Використовуйте «Найближчий» для піксельного мистецтва, значків і чіткої графіки, де розмиті пікселі зіпсують вигляд. + Шкала колірного простору + Вирішіть, як змішуються кольори, а пікселі змінюються під час зміни розміру + Зберігайте градієнти та краї природними + Шкала колірного простору змінює математичні обчислення, які використовуються під час зміни розміру. Більшість зображень підходять за замовчуванням, але лінійні або перцептивні простори можуть зробити градієнти, темні краї та насичені кольори чистішими після сильного масштабування. + Залиште значення за замовчуванням, якщо швидкість і сумісність важливіші за крихітні відмінності кольорів. + Спробуйте лінійний або перцепційний режими, якщо темні контури, знімки екрана інтерфейсу користувача, градієнти або кольорові смуги виглядають неправильно після зміни розміру. + Порівняйте до і після на реальному цільовому екрані, оскільки зміни колірного простору можуть бути незначними та залежати від вмісту. + Обмеження режимів зміни розміру + Дізнайтеся, коли зображення, що перевищують ліміт, слід пропускати, перекодувати або зменшувати, щоб відповідати розміру + Виберіть, що станеться на межі + Limit Resize — це не лише інструмент для зменшення розміру зображень. Його режим вирішує, що робитиме програма, коли джерело вже знаходиться в межах ваших обмежень або коли лише одна сторона порушує правило, що важливо для змішаних папок і підготовки до завантаження. + Використовуйте «Пропустити», коли зображення в межах обмежень потрібно залишити без змін і не експортувати знову. + Використовуйте «Перекодувати», якщо розміри прийнятні, але вам все одно потрібен новий формат, поведінка метаданих, якість або ім’я файлу. + Використовуйте масштабування, коли зображення, які порушують обмеження, мають бути пропорційно зменшені, доки вони не підійдуть до дозволеного поля. + Цільове співвідношення сторін + Уникайте розтягнутих поверхонь, обрізаних країв і неочікуваних рамок у строгих вихідних розмірах + Зіставте форму перед зміною розміру + Ширина та висота становлять лише половину цільового розміру. Співвідношення сторін вирішує, чи може зображення вписуватися природним чином, потребує обрізання чи полотна навколо нього. Попередня перевірка форми запобігає найдивовижнішим результатам зміни розміру. + Порівняйте вихідну форму з цільовою, перш ніж вибрати «Явний», «Обрізати» або «Припасувати». + Обріжте спочатку, коли об’єкт може втратити зайвий фон, а місце призначення потребує суворої рамки. + Використовуйте Fit або Flexible, коли кожна частина вихідного зображення має залишатися видимою. + Високий або нормальний розмір + Знайте, коли для додавання пікселів потрібен штучний інтелект, а коли достатньо простого масштабування + Не плутайте розмір з деталями + Нормальна зміна розміру змінює розміри шляхом інтерполяції пікселів; він не може вигадати реальні деталі. Високий рівень штучного інтелекту може створювати чіткіші деталі, але він повільніший і може виявляти текстуру, тому правильний вибір залежить від того, чи потрібна вам сумісність, швидкість чи візуальне відновлення. + Використовуйте нормальний розмір для ескізів, обмежень завантаження, знімків екрана та простих змін розмірів. + Використовуйте AI upscale, коли маленьке або м’яке зображення має виглядати краще у більшому розмірі. + Порівнюйте обличчя, текст і візерунки після підвищення масштабу ШІ, оскільки згенеровані деталі можуть виглядати переконливо, але неправильно. + Порядок фільтрів має значення + Застосуйте очищення, колір, різкість і остаточне стиснення в навмисній послідовності + Створіть чистішу систему фільтрів + Фільтри не завжди взаємозамінні. Розмиття перед збільшенням різкості, посилення контрасту перед оптичним розпізнаванням або зміна кольору перед стисненням може призвести до дуже різного результату від тих самих елементів керування. + Спочатку обріжте та оберніть, щоб пізніші фільтри діяли лише на пікселях, які залишаться. + Застосуйте експозицію, баланс білого та контраст перед різкістю або стилізованим ефектом. + Зберігайте остаточну зміну розміру та стиснення ближче до кінця, щоб попередньо переглянуті деталі передбачувано пережили експорт. + Виправте краї фону + Очистіть ореоли, залишки тіні, волосся, дірки та м’які контури після автоматичного видалення + Зробіть вирізи навмисними + Автоматичні маски часто добре виглядають на перший погляд, але виявляють проблеми на яскравому, темному або візерунковому тлі. Очищення країв – це різниця між швидким вирізом і багаторазовою наклейкою, логотипом або зображенням продукту. + Перегляньте результат на контрастному фоні, щоб виявити ореоли та відсутні деталі країв. + Використовуйте відновлення для волосся, кутів і прозорих об’єктів, перш ніж стерти навколишні залишки. + Експортуйте невеликий тест остаточного кольору фону, коли виріз буде розміщено в дизайні. + Читабельні водяні знаки + Робіть позначки видимими на яскравих, темних, зайнятих і обрізаних зображеннях, не псуючи фото + Захистіть зображення, не приховуючи його + Водяний знак, який добре виглядає на одному зображенні, може зникнути на іншому. Розмір, непрозорість, контраст, розташування та повторення слід вибирати для всієї партії, а не лише для першого попереднього перегляду. + Перевірте позначку на найяскравіших і найтемніших зображеннях у пакеті перед експортом усіх файлів. + Використовуйте меншу непрозорість для великих повторюваних позначок і сильніший контраст для маленьких кутових позначок. + Тримайте важливі обличчя, текст і деталі продукту чіткими, якщо позначка не призначена для запобігання повторному використанню. + Розбийте одне зображення на фрагменти + Виріжте одне зображення за рядками, стовпцями, настроюваними відсотками, форматом і якістю + Підготуйте решітки та замовлені шматки + Розділення зображення створює кілька вихідних даних з одного вихідного зображення. Він може розділяти за кількістю рядків і стовпців, регулювати відсоток рядків і стовпців і зберігати фрагменти з вибраним форматом і якістю виведення, що корисно для сіток, фрагментів сторінок, панорам і впорядкованих публікацій у соціальних мережах. + Виберіть вихідне зображення, а потім встановіть потрібну кількість рядків і стовпців. + Відрегулюйте відсоткове співвідношення рядка чи стовпця, коли всі частини не мають бути однакового розміру. + Виберіть вихідний формат і якість перед збереженням, щоб кожна згенерована частина використовувала однакові правила експорту. + Виріжте смужки зображення + Видаліть вертикальні або горизонтальні області та об’єднайте частини, що залишилися + Видаліть область, не залишаючи проміжків + Вирізання зображення відрізняється від звичайного кадрування. Він може видалити вибрану вертикальну або горизонтальну смугу, а потім об’єднати решту частин зображення. Інверсний режим натомість зберігає вибрану область, а використання обох інверсних напрямків зберігає вибраний прямокутник. + Використовуйте вертикальне різання для бічних областей, колон або середніх смуг; використовуйте горизонтальне різання для банерів, прогалин або рядків. + Увімкніть інверсію на осі, якщо ви хочете зберегти вибрану частину замість її видалення. + Перегляньте краї після вирізання, оскільки об’єднаний вміст може виглядати різким, коли видалена область перетинає важливі деталі. + Виберіть вихідний формат + Виберіть JPEG, PNG, WebP, AVIF, JXL або PDF залежно від вмісту та призначення + Нехай призначення визначить формат + Найкращий формат залежить від вмісту зображення та місця його використання. Фотографії, скріншоти, прозорі ресурси, документи й анімовані файли виходять з ладу різними способами, коли їх примусово передають у неправильний формат. + Використовуйте JPEG для широкої сумісності фотографій, коли прозорість і точні пікселі не потрібні. + Використовуйте PNG для чітких знімків екрана, захоплення інтерфейсу користувача, прозорої графіки та редагування без втрат. + Використовуйте WebP, AVIF або JXL, якщо важливий компактний сучасний вихід і програма-одержувач підтримує його. + Витягніть аудіо обкладинки + Зберігайте вбудовані обкладинки альбомів або доступні мультимедійні кадри з аудіофайлів як зображення PNG + Витягніть ілюстрації з аудіофайлів + Audio Covers використовує медіа-метадані Android для читання вбудованої ілюстрації з аудіофайлів. Коли вбудовані ілюстрації недоступні, ретрівер може повернутися до медіа-фрейму, якщо Android може його надати; якщо жодного з них не існує, інструмент повідомляє, що немає зображення для вилучення. + Відкрийте Audio Covers і виберіть один або кілька аудіофайлів із очікуваною обкладинкою. + Перегляньте витягнуту обкладинку, перш ніж зберігати або ділитися, особливо для файлів із потокових програм або програм для запису. + Якщо зображення не знайдено, аудіофайл, ймовірно, не має вбудованої обкладинки або Android не зміг відобразити придатний кадр. + Дати та метадані місця + Вирішіть, що зберегти, коли час зйомки має значення, але GPS або дані пристрою не повинні витікати + Відокремте корисні метадані від приватних метаданих + Не всі метадані однаково ризиковані. Дати запису можуть бути корисними для архівування та сортування, тоді як GPS, поля, схожі на серійний номер пристрою, теги програмного забезпечення або поля власника можуть виявити більше, ніж заплановано. + Зберігайте теги дати й часу, коли хронологічний порядок має значення для альбомів, сканів або історії проектів. + Видаліть GPS і теги ідентифікації пристрою, перш ніж оприлюднювати або надсилати зображення незнайомцям. + Експортуйте зразок і знову перевірте EXIF, коли вимоги до конфіденційності суворі. + Уникайте конфліктів імен файлів + Захистіть пакетні виходи, коли багато файлів мають спільні імена, наприклад image.jpg або screenshot.png + Зробіть кожну вихідну назву унікальною + Подвійні імена файлів часто трапляються, коли зображення надходять із месенджерів, скріншотів, хмарних папок або кількох камер. Хороший шаблон запобігає випадковій заміні та зберігає відстеження вихідних даних до їхніх джерел. + Включіть оригінальну назву та суфікс, коли відредагована копія має бути впізнаваною. + Додайте послідовність, мітку часу, попередні налаштування або розміри під час обробки великих змішаних партій. + Уникайте перезапису робочих процесів, доки не переконаєтеся, що шаблон іменування не може зіткнутися. + Збережіть або поділіться + Виберіть між постійним файлом і тимчасовим передаванням в іншу програму + Експортуйте з правильним наміром + Зберегти та поділитися може виглядати схожим, але вони вирішують різні проблеми. Під час збереження створюється файл, який можна знайти пізніше; спільний доступ надсилає згенерований результат через Android до іншої програми та може не залишати довговічну локальну копію. + Використовуйте «Зберегти», коли вихідні дані мають залишитися у відомій папці, створити резервну копію або повторно використати пізніше. + Використовуйте Share для швидких повідомлень, вкладень електронних листів, публікацій у соціальних мережах або надсилання результату іншому редактору. + Спершу збережіть, якщо програма-одержувач ненадійна або якщо повторне створення невдалого обміну буде неприємним. + Розмір і поля PDF-сторінки + Перш ніж створювати документ, виберіть розмір паперу, відповідність і простір + Зробіть сторінки зображень доступними для друку та читання + Зображення можуть стати незручними PDF-сторінками, якщо їх форма не відповідає вибраному розміру паперу. Поля, режим підгонки та орієнтація сторінки визначають, чи буде вміст обрізаним, маленьким, розтягнутим чи зручним для читання. + Використовуйте реальний розмір паперу, коли PDF можна роздрукувати або надіслати у форму. + Використовуйте поля для сканування та знімків екрана, щоб текст не прилягав до краю сторінки. + Попередньо переглядайте книжкову та альбомну сторінки разом, якщо вихідні зображення мають змішану орієнтацію. + Очистити скани + Покращте краї паперу, тіні, контраст, поворот і читабельність перед PDF або OCR + Підготуйте сторінки перед архівуванням + Сканування можна технічно зафіксувати, але все одно важко прочитати. Невеликі кроки очищення перед експортом PDF часто важливіші, ніж налаштування стиснення, особливо для квитанцій, рукописних нотаток і сторінок зі слабким освітленням. + Виправте перспективу та обріжте краї столу, пальці, тіні та фоновий безлад. + Обережно збільшуйте контраст, щоб текст став чіткішим, не знищуючи штампи, підписи чи позначки олівцем. + Запустіть оптичне розпізнавання символів лише після того, як сторінка стане вертикальною та доступною для читання, а потім перевірте важливі цифри вручну. + Стискайте, відтінки сірого або виправляйте PDF-файли + Використовуйте операції з PDF-файлами в одному файлі для полегшених, придатних для друку чи відновлених копій документів + Виберіть операцію очищення PDF + PDF Tools містить окремі операції для стиснення, перетворення градацій сірого та відновлення. Стиснення та відтінки сірого працюють переважно через вбудовані зображення, тоді як відновлення перебудовує структуру PDF; жоден із них не слід розглядати як гарантоване виправлення для кожного документа. + Використовуйте «Стиснути PDF», якщо документ містить зображення та розмір файлу має значення для спільного використання. + Використовуйте відтінки сірого, коли документ має бути легше надрукувати або не потребує кольорових зображень. + Використовуйте Repair PDF на копії, якщо документ не відкривається або має пошкоджену внутрішню структуру, а потім перевірте відновлений файл. + Витягніть зображення з PDF-файлів + Відновіть вбудовані зображення PDF і запакуйте витягнуті результати в архів + Зберігайте вихідні зображення з документів + Extract Images сканує PDF-файл на предмет вбудованих зображень, пропускає дублікати, де це можливо, і зберігає відновлені зображення в створеному архіві ZIP. Він витягує лише зображення, які фактично вбудовані в PDF, а не текст, векторні малюнки чи кожну видиму сторінку як знімок екрана. + Використовуйте Extract Images, коли вам потрібні зображення, збережені в PDF-файлі, наприклад фотографії з каталогу або відскановані ресурси. + Натомість використовуйте PDF-зображення або вилучення сторінки, коли вам потрібні цілі сторінки, включаючи текст і макет. + Якщо інструмент не повідомляє про відсутність вбудованих зображень, можливо, сторінка містить текстовий/векторний вміст або зображення не зберігаються як об’єкти, які можна витягнути. + Метадані, зведення та вихід на друк + Відредагуйте властивості документа, растеризуйте сторінки або навмисно підготуйте власні макети для друку + Виберіть дію з кінцевим документом + Метадані PDF, зведення та підготовка до друку вирішують різні проблеми на завершальному етапі. Метадані керують властивостями документа, Flatten PDF растеризує сторінки в менш редагований вихід, а Print PDF готує новий макет із елементами керування розміром сторінки, орієнтацією, полями та кількістю сторінок на аркуші. + Використовуйте метадані, коли автор, назва, ключові слова, продюсер або очищення конфіденційності мають значення. + Використовуйте Flatten PDF, коли видимий вміст сторінки стане важче редагувати як окремі об’єкти PDF. + Використовуйте Print PDF, коли вам потрібен нестандартний розмір сторінки, орієнтація, поля або кілька сторінок на аркуші. + Підготувати зображення для OCR + Використовуйте кадрування, обертання, контраст, усунення шумів і масштабування, перш ніж запитувати OCR для читання тексту + Надайте OCR чистіші пікселі + Помилки оптичного розпізнавання часто виникають через вхідне зображення, а не механізм OCR. Криві сторінки, низька контрастність, розмитість, тіні, дрібний текст і змішане тло – усе це погіршує якість розпізнавання. + Перед розпізнаванням обріжте до текстової області та поверніть зображення так, щоб лінії були горизонтальними. + Збільшуйте контраст або перетворюйте на чистіший чорно-білий лише тоді, коли це полегшує читання літер. + Помірно змінюйте розмір дрібного тексту вгору, але уникайте агресивного збільшення різкості, яке створює фальшиві краї. + Перевірте вміст QR + Перегляньте посилання, облікові дані Wi-Fi, контакти та дії, перш ніж довіряти коду + Розглядати декодований текст як ненадійний вхід + QR-код може приховати URL-адресу, картку контакту, пароль Wi-Fi, подію календаря, номер телефону або звичайний текст. Видима мітка біля коду може не відповідати фактичному корисному навантаженню, тому перегляньте розкодований вміст, перш ніж діяти з ним. + Перевірте повну розшифровану URL-адресу, перш ніж відкривати її, особливо скорочені або незнайомі домени. + Будьте обережні з кодами Wi-Fi, контактів, календаря, телефону та SMS, оскільки вони можуть викликати конфіденційні дії. + Спершу скопіюйте вміст, коли вам потрібно перевірити його в іншому місці, а не відразу відкривайте. + Генеруйте QR-коди + Створюйте коди зі звичайного тексту, URL-адрес, Wi-Fi, контактів, електронної пошти, телефону, SMS і даних про місцезнаходження + Створіть правильне корисне навантаження QR + QR-екран може генерувати коди з введеного вмісту, а також сканувати наявний. Структуровані типи QR допомагають кодувати дані у форматі, зрозумілому іншим програмам, наприклад дані для входу в Wi-Fi, контактні картки, поля електронної пошти, номери телефонів, вміст SMS, URL-адреси або географічні координати. + Виберіть простий текст для простих нотаток або використовуйте структурований тип, коли код має відкриватися як Wi-Fi, контакт, URL-адреса, електронна пошта, телефон, SMS або дані про місцезнаходження. + Перевірте кожне поле, перш ніж ділитися згенерованим кодом, оскільки видимий попередній перегляд відображає лише введене вами корисне навантаження. + Перевірте збережений QR-код за допомогою сканера, коли він буде надрукований, опублікований або використаний іншими людьми. + Виберіть режим контрольної суми + Обчислюйте хеші з файлів або тексту, порівнюйте один файл або пакетно перевіряйте багато файлів + Перевірте вміст, а не зовнішній вигляд + Інструменти контрольної суми мають окремі вкладки для хешування файлів, хешування тексту, порівняння файлу з відомим хешом і пакетного порівняння. Контрольна сума підтверджує побайтову ідентичність вибраного алгоритму; це не доводить, що два зображення просто виглядають схожими. + Використовуйте Calculate для файлів і Text Hash для введених або вставлених текстових даних. + Використовуйте Compare, коли хтось дає вам очікувану контрольну суму для одного файлу. + Використовуйте пакетне порівняння, коли багато файлів потребують хешів або перевірки за один прохід, а потім зберігайте вибраний алгоритм узгодженим. + Конфіденційність буфера обміну + Використовуйте автоматичні функції буфера обміну без випадкового відкриття приватних скопійованих даних + Зберігайте скопійований вміст навмисно + Автоматизація буфера обміну зручна, але скопійований текст може містити паролі, посилання, адреси, маркери або особисті нотатки. Розглядайте введення через буфер обміну як функцію швидкості, яка має відповідати вашим звичкам і очікуванням конфіденційності. + Вимкніть автоматичне використання буфера обміну, якщо приватний текст неочікувано з’являється в інструментах. + Використовуйте збереження лише в буфер обміну, лише якщо ви свідомо хочете, щоб результат не зберігався. + Очистіть або замініть буфер обміну після обробки конфіденційного тексту, QR-даних або корисних даних Base64. + Кольорові формати + Ознайомтеся зі значеннями кольорів HEX, RGB, HSV, альфа та скопійованих кольорів, перш ніж вставляти в інше місце + Скопіюйте формат, який очікує мета + Той самий видимий колір можна записати кількома способами. Інструмент дизайну, поле CSS, значення кольору Android або редактор зображень можуть очікувати іншого порядку, обробки альфа-версії або моделі кольорів. + Використовуйте HEX, коли вставляєте в поля веб-сайту, дизайну чи теми, для яких потрібні компактні кольорові коди. + Використовуйте RGB або HSV, коли вам потрібно налаштувати канали, яскравість, насиченість або відтінок вручну. + Перевірте альфа-версію окремо, якщо прозорість має значення, оскільки деякі пункти призначення її ігнорують або використовують інший порядок. + Перегляньте бібліотеку кольорів + Шукайте названі кольори за назвою або HEX і зберігайте улюблені вгорі + Знайдіть багаторазові іменовані кольори + Бібліотека кольорів завантажує іменовану колекцію кольорів, сортує її за назвою, фільтрує за назвою кольору або значенням HEX і зберігає улюблені кольори, щоб важливі записи залишалися легшими для доступу. Це корисно, коли вам потрібен справжній названий колір замість вибірки із зображення. + Шукайте за назвою кольору, якщо знаєте родину, наприклад червоний, синій, шиферний або м’ятний. + Якщо у вас уже є числовий колір і ви бажаєте знайти відповідні або найближчі записи з іменами, виконуйте пошук за ШІСТНИЧНИКОМ. + Позначайте кольори, які часто зустрічаються, як вибрані, щоб під час перегляду вони випереджали загальну бібліотеку. + Використовуйте колекції сітчастих градієнтів + Завантажте доступні ресурси градієнта сітки та поділіться вибраними зображеннями + Використовуйте віддалені ресурси градієнта сітки + Mesh Gradients може завантажувати віддалену колекцію ресурсів, завантажувати відсутні зображення градієнтів, показувати прогрес завантаження та ділитися вибраними градієнтами. Доступність залежить від доступу до мережі та віддаленого сховища ресурсів, тому порожній список зазвичай означає, що ресурси ще не були доступні. + Якщо вам потрібні готові зображення сітчастих градієнтів, відкрийте Mesh Gradients з інструментів градієнта. + Дочекайтеся завантаження ресурсу або прогресу завантаження, перш ніж вважати, що колекція порожня. + Виберіть і поділіться потрібними градієнтами або поверніться до Gradient Maker, якщо хочете створити власний градієнт вручну. + Розв’язання створених активів + Виберіть вихідний розмір, перш ніж генерувати градієнти, шум, шпалери, SVG-подібне зображення або текстури + Згенеруйте в потрібному розмірі + Згенеровані зображення не мають оригіналу камери, до якого можна було б повернутися. Якщо вихідні дані замалі, подальше масштабування може пом’якшити візерунки, змістити деталі або змінити спосіб розміщення та стиснення активів. + Спочатку встановіть розміри для кінцевого випадку використання, наприклад шпалери, значок, фон, друк або накладення. + Використовуйте більші розміри для текстур, які можна обрізати, масштабувати або повторно використовувати в кількох макетах. + Експортуйте тестові версії перед отриманням величезних результатів, оскільки процедурні деталі можуть змінити поведінку стиснення. + Експорт поточних шпалер + Отримати доступні шпалери Home, Lock і вбудовані шпалери з пристрою + Збережіть або поділіться встановленими шпалерами + Експорт шпалер зчитує шпалери, які Android відкриває через системні API шпалер. Залежно від пристрою, версії Android, дозволів і варіанта складання, шпалери «Дім», «Замок» або вбудовані шпалери можуть бути доступні окремо або відсутні. + Відкрийте експорт шпалер, щоб завантажити шпалери, які система дозволяє програмі читати. + Виберіть доступні записи «Дім», «Замок» або вбудовані шпалери, які потрібно зберегти, скопіювати або поділитися. + Якщо фонового малюнка немає або ця функція недоступна, це зазвичай пов’язано з системою, дозволом або обмеженням варіанту збірки, а не налаштуванням редагування. + Кути Анімація Дроссель + Копіювати колір як + HEX + назва + Портретна матова модель для швидкого підстригання обличчя з м’якшим волоссям і обробкою країв. + Швидке покращення в умовах слабкого освітлення за допомогою оцінки кривої Zero-DCE++. + Модель відновлення зображення Restormer для зменшення смуг дощу та артефактів у вологу погоду. + Модель деформації документа, яка прогнозує координатну сітку та переставляє вигнуті сторінки на більш пласке сканування. + Шкала тривалості руху + Керує швидкістю анімації програми: 0 вимикає рух, 1 – це нормально, вищі значення сповільнюють анімацію + Показати останні інструменти + Відображення швидкого доступу до нещодавно використаних інструментів на головному екрані + Статистика використання + Дізнайтеся про відкриття програм, запуски інструментів і інструменти, якими ви найчастіше користуєтеся + Відкриється програма + Інструмент відкривається + Останній інструмент + Успішні збереження + Смуга активності + Дані збережено + Топовий формат + Використані інструменти + %1$d відкривається • %2$s + Статистики використання ще немає + Відкрийте кілька інструментів, і вони з’являться тут автоматично + Ваша статистика використання зберігається лише локально на вашому пристрої. ImageToolbox використовує їх лише для відображення вашої особистої діяльності, улюблених інструментів і збережених даних + редактор редагувати фото ретушувати зображення налаштувати + змінити розмір конвертувати масштаб роздільна здатність розміри ширина висота jpg jpeg png webp стиснути + стиснути розмір вага байт mb kb зменшити якість оптимізувати + обрізати обрізати співвідношення сторін + фільтр ефект корекція кольору налаштувати lut mask + малювати пензель олівець анотувати розмітку + шифрувати шифрувати розшифрувати секретний пароль + видалення фону стерти видалити виріз прозорий альфа + попередній перегляд перегляду галерея огляд відкрити + стібок об\'єднати об\'єднати панораму довгий знімок екрана + URL-адреса веб-завантаження Інтернет-посилання зображення + вибір піпетки зразок шістнадцяткового кольору rgb + палітра кольорів зразки екстракт домінанта + конфіденційність метаданих exif видалити смугу, очистити розташування GPS + порівняти відмінність різниці до після + ліміт зміна розміру макс. мін. мегапікселів розміри затиск + Інструменти сторінок документа pdf + ocr текст розпізнає витяг сканування + сітка кольору градієнтного фону + водяний знак логотип текстовий штамп накладання + GIF-анімація, анімована конвертація кадрів + apng анімація анімований png конвертувати кадри + архів zip, стиснути, розпакувати файли + jxl jpeg xl перетворює формат зображення jpg + svg vector trace convert xml + формат конвертувати jpg jpeg png webp heic avif jxl bmp + сканер документів сканування паперу перспектива кадрування + Сканер сканера штрих-коду qr + накладення середньої середньої астрофотографії + розділити плитки сітки шматочок частин + перетворення кольору hex rgb hsl hsv cmyk лаб + webp конвертувати формат анімованих зображень + генератор шуму випадкова зернистість текстури + об\'єднати макет сітки колажу + шари розмітки анотувати редагувати накладення + base64 кодування декодування uri даних + контрольна сума хеш md5 sha sha1 sha256 перевірити + сітчастий градієнт фону + редагування метаданих exif gps дата камера + вирізати розділену сітку шматками плитки + музичні метадані обкладинки альбому + фон експорту шпалер + символи тексту ASCII + ai ml нейронний покращений висококласний сегмент + кольорова бібліотека матеріалів палітра з іменами кольорів + шейдер glsl фрагментна студія ефектів + pdf об\'єднати об\'єднати об\'єднати + pdf розділити на окремі сторінки + pdf з поворотом сторінок + pdf змінити порядок сторінок сортувати + Нумерація сторінок pdf + pdf, ocr, розпізнавання тексту з можливістю пошуку + PDF водяний знак штамп логотип текст + pdf малюнок підпису + pdf захистити паролем шифрувати блокування + pdf розблокувати пароль розшифрувати зняти захист + pdf стиснути зменшити розмір оптимізувати + pdf відтінки сірого чорно білий монохромний + pdf відновлення виправлення відновлення зламаного + властивості метаданих pdf exif назва автора + pdf видалити, видалити сторінки + Обрізання полів pdf + pdf flatten annotations утворює шари + pdf вилучення зображень зображень + Стиснути архів pdf zip + принтер для друку pdf + Прочитати засіб попереднього перегляду pdf + зображення в pdf конвертувати документ jpg jpeg png + pdf в зображення, експорт сторінок jpg png + pdf анотації коментарі видалити чисті + Нейтральний варіант + Помилка + Тінь + Висота зуба + Горизонтальний діапазон зубів + Вертикальний діапазон зубів + Верхній край + Правий край + Нижній край + Лівий край + Рваний край + Скинути статистику використання + Відкриття інструмента, збереження статистики, серії, збережені дані та верхній формат буде скинуто. Відкриття програми залишаться без змін. + Аудіо + Файл + Експорт профілів + Зберегти поточний профіль + Видалити профіль експорту + Ви бажаєте видалити профіль експорту \\"%1$s\\"? + Малювання кордону + Покажіть тонку рамку навколо малюнка та стирання полотна + хвилясті + Заокруглені елементи інтерфейсу користувача з м’яким хвилястим краєм + Совок + Виїмка + Заокруглені кути, різьблені всередину + Ступінчасті кути з подвійним зрізом + Заповнювач + Використовуйте {filename}, щоб вставити вихідну назву файлу без розширення + Відблиск + Базова сума + Сума кільця + Сума променя + Ширина кільця + Спотворити перспективу + Вигляд і відчуття Java + Зсув + Кут X + і кут + Змінити розмір виведення + Крапля води + Довжина хвилі + Фаза + Високий пас + Кольорова маска + Chrome + Розчинити + М\'якість + Зворотній зв\'язок + Розмиття + Низький вхід + Високий вхід + Низька продуктивність + Високий вихід + світлові ефекти + Висота шишки + М\'якість ударів + пульсація + Амплітуда X + Амплітуда Y + довжина хвилі X + Довжина хвилі Y + Адаптивне розмиття + Генерація текстур + Створюйте різноманітні процедурні текстури та зберігайте їх як зображення + Тип текстури + Упередженість + час + Блиск + дисперсія + Зразки + Кутовий коефіцієнт + Коефіцієнт градієнта + Тип сітки + Дистанційна потужність + Масштаб Y + нечіткість + Тип основи + Фактор турбулентності + Масштабування + Ітерації + Кільця + Матовий метал + їдкі речовини + Стільниковий + Шахова дошка + fBm + Мармур + плазма + Ковдра + Деревина + випадковий + Квадратний + Шестикутний + Восьмикутна + Трикутний + Гребеневий + В. Л. Шум + SC Шум + Останні + Нещодавно використаних фільтрів ще немає + текстура генератор матовий метал каустика клітинна шашка мармур плазма ковдра деревина цегла камуфляж клітина хмара тріщина тканина листя стільниковий лід лава туманність папір іржа пісок дим камінь рельєф місцевість брижа + Цегла + Камуфляж + Стільниковий + Хмара + тріщина + Тканина + листя + Стільниковий + лід + Лава + Туманність + Папір + Іржа + Пісок + дим + Камінь + Рельєф місцевості + Топографія + Водяна брижа + Advanced Wood + Ширина розчину + Нерегулярність + Фаска + Шорсткість + Перший поріг + Другий поріг + Третій поріг + М\'якість краю + Ширина кордону + Покриття + Деталь + Глибина + Розгалуження + Горизонтальні нитки + Вертикальні нитки + пух + вени + Освітлення + Ширина тріщини + Мороз + Потік + скоринка + Щільність хмар + Зірки + Щільність волокон + Міцність волокна + Плями + Корозія + Піттинг + пластівці + Частота дюни + Кут вітру + брижі + Випси + Жилкова шкала + Рівень води + Гірський рівень + Ерозія + Рівень снігу + Кількість рядків + Товщина лінії + Розтушовування + Колір пір + Розчинний колір + Колір темної цегли + Цегляного кольору + Колір виділення + Темний колір + Колір лісу + Колір землі + Пісочний колір + Колір фону + Колір клітини + Колір краю + Колір неба + Колір тіні + Світлий колір + Колір поверхні + Варіація кольору + Колір тріщин + Колір деформації + Колір піткання + Темний колір листя + Колір листя + Колір рамки + Колір меду + Глибокий колір + Колір льоду + Морозний колір + Колір скоринки + Змивний колір + Колір світіння + Космічний колір + Фіолетовий колір + Синій колір + Базовий колір + Колір волокна + Колір плями + Колір металу + Темний колір іржі + Колір іржі + Помаранчевий колір + Колір диму + Колір жилки + Низинний колір + Акварель + Скельний колір + Колір снігу + Низький колір + Високий колір + Колір лінії + Дрібний колір + Трава + Бруд + Шкіра + Бетонні + Асфальт + Мох + Вогонь + Аврора + Нафтова пляма + Акварель + Абстрактний потік + Опал + Дамаська сталь + Блискавка + Оксамит + Мармурування чорнилом + Голографічна фольга + Біолюмінесценція + Космічний вихор + лавова лампа + Горизонт подій + Фрактал Блум + Хроматичний тунель + Корона затемнення + Дивний атрактор + Ферофлюїдна корона + Наднова + Ірис + Павич перо + Мушля Наутілуса + Кільцева планета + Щільність леза + Довжина леза + Вітер + Нерівність + Згустки + вологість + Камінці + Зморшки + Пори + Агрегатний + Тріщини + дьоготь + Носити + цяточки + Волокна + Частота полум\'я + дим + Інтенсивність + Стрічки + Гурти + Райдужність + Темрява + Цвіте + Пігмент + Краї + Папір + дифузія + Симетрія + Різкість + Кольорова гра + Молочність + Шари + Складні + польський + Відділення + Напрямок + Шин + Складки + оперення + Баланс чорнила + Спектр + Зморшки + дифракція + Зброя + Твіст + Ядро світіння + Краплі + Нахил диска + Розмір горизонту + Ширина диска + Лінзування + Пелюстки + Локон + Скань + грані + Кривизна + Розмір місяця + Розмір корони + Промені + Кільце з діамантом + Частки + Щільність орбіти + Товщина + Шипи + Довжина шипа + Розмір тіла + Металік + Радіус удару + Ширина раковини + Викинули + Розмір зіниці + Розмір райдужки + Колірна варіація + Catchlight + Розмір очей + Щільність бородок + Повороти + Палати + відкриття + Гряди + перламутровий + Розмір планети + Нахил кільця + Ширина кільця + атмосфера + Колір бруду + Колір темної трави + Колір трави + Колір наконечника + Темно-земляний колір + Сухий колір + Колір гальковий + Колір шкіри + Колір бетону + Колір дьогтю + Колір асфальту + Колір каменю + Колір пилу + Колір грунту + Колір темного моху + Колір моху + Червоний колір + Колір серцевини + Зелений колір + Блакитний колір + Маджента кольору + Золотий колір + Колір паперу + Колір пігменту + Вторинний колір + Перший колір + Другий колір + Рожевий колір + Колір темна сталь + Сталевий колір + Світлий сталевий колір + Колір оксиду + Колір ореолу + Колір болта + Оксамитовий колір + Колір блиску + Синій колір чорнила + Червоний колір чорнила + Темний колір чорнила + Срібний колір + Жовтий колір + Колір тканини + Колір диска + Гарячий колір + Колір лінз + Зовнішній колір + Внутрішній колір + Корона кольору + Холодний колір + Теплий колір + Колір хмари + Колір полум\'я + Колір пір\'я + Колір раковини + Колір перловий + Колір планети + Колір кільця + Видаліть оригінальні файли після збереження + Будуть видалені лише успішно оброблені вихідні файли + Оригінальні файли видалено: %1$d + Не вдалося видалити оригінальні файли: %1$d + Оригінальні файли видалено: %1$d, не вдалося: %2$d + Жести аркуша + Дозволити перетягувати розгорнуті аркуші параметрів + Субдискретизація кольоровості + Бітова глибина + Без втрат + %1$d-біт + Пакетне перейменування + Перейменуйте кілька файлів, використовуючи шаблони імен файлів + пакетне перейменування файлів шаблон послідовності назви розширення дати + Виберіть файли для перейменування + Шаблон імені файлу + Перейменувати + Джерело дати + Дата взяття EXIF + Дата зміни файлу + Дата створення файлу + Поточна дата + Дата вручну + Дата вручну + Ручний час + Вибрана дата недоступна для деяких файлів. Для них використовуватиметься поточна дата. + Введіть шаблон імені файлу + Шаблон створює недійсну або надто довгу назву файлу + Шаблон створює повторювані імена файлів + Усі назви файлів уже відповідають шаблону + Цей шаблон використовує маркери, недоступні для пакетного перейменування + Назва залишиться незмінною + Файли успішно перейменовано + Деякі файли не вдалося перейменувати + Дозвіл не надано + Використовує оригінальну назву файлу без розширення, що допомагає зберегти ідентифікацію джерела. Також підтримує нарізку за допомогою \\o{start:end}, транслітерацію за допомогою \\o{t} і заміну на \\o{s/old/new/}. + Лічильник з автоматичним збільшенням. Підтримує спеціальне форматування: \\c{padding} (наприклад, \\c{3} -&gt; 001), \\c{start:step} або \\c{start:step:padding}. + Ім\'я батьківської папки, де знаходився вихідний файл. + Розмір вихідного файлу. Підтримує одиниці вимірювання: \\z{b}, \\z{kb}, \\z{mb} або просто \\z для читабельного формату. + Генерує випадковий універсальний унікальний ідентифікатор (UUID), щоб гарантувати унікальність імені файлу. + Перейменування файлів не можна скасувати. Перш ніж продовжити, переконайтеся, що нові імена правильні. + Підтвердити перейменування + Налаштування бічного меню + Шкала меню + Меню альфа + Автоматичний баланс білого + Вирізка + Перевірка прихованих водяних знаків + Зберігання + Обмеження кешу + Автоматично очищайте кеш, коли він перевищує цей розмір + Інтервал очищення + Як часто перевіряти, чи потрібно очищати кеш + При запуску програми + 1 день + 1 тиждень + 1 місяць + Автоматично очищайте кеш програми відповідно до вибраного обмеження та інтервалу + Пересувна площа посіву + Дозволяє переміщувати всю область кадрування шляхом перетягування всередину неї + Об’єднати GIF + Об’єднайте кілька GIF-кліпів в одну анімацію + Порядок кліпів GIF + Зворотний + Грайте як навпаки + Бумеранг + Грайте вперед, а потім назад + Нормалізуйте розмір кадру + Масштаб кадрів, щоб відповідати найбільшому кліпу; вимкніть, щоб зберегти їхній початковий масштаб + Затримка між кліпами (мс) + Папка + Виберіть усі зображення з папки та її вкладених папок + Пошук дублікатів + Знайдіть точні копії та візуально схожі зображення + пошук дублікатів схожі зображення точні копії фотографії очищення зберігання dHash SHA-256 + Виберіть зображення, щоб знайти дублікати + Чутливість до подібності + Точні копії + Подібні зображення + Виберіть усі точні копії + Виберіть усі, крім рекомендованих + Дублікатів не знайдено + Спробуйте додати більше зображень або збільшити чутливість до схожості + Не вдалося прочитати %1$d зображень + %1$s • %2$s можна повернути + %1$d груп • %2$s можна відновити + %1$d вибрано • %2$s + Це неможливо скасувати. Android може попросити вас підтвердити видалення в системному діалоговому вікні. + Не знайдено + Перемістіть, щоб почати + Перейти до кінця + \ No newline at end of file diff --git a/core/resources/src/main/res/values-v26/bools.xml b/core/resources/src/main/res/values-v26/bools.xml new file mode 100644 index 0000000..eccebf5 --- /dev/null +++ b/core/resources/src/main/res/values-v26/bools.xml @@ -0,0 +1,20 @@ + + + + true + \ No newline at end of file diff --git a/core/resources/src/main/res/values-v31/colors.xml b/core/resources/src/main/res/values-v31/colors.xml new file mode 100644 index 0000000..338b010 --- /dev/null +++ b/core/resources/src/main/res/values-v31/colors.xml @@ -0,0 +1,21 @@ + + + + @android:color/system_accent1_100 + @android:color/system_accent1_700 + \ No newline at end of file diff --git a/core/resources/src/main/res/values-vi/strings.xml b/core/resources/src/main/res/values-vi/strings.xml new file mode 100644 index 0000000..77eab21 --- /dev/null +++ b/core/resources/src/main/res/values-vi/strings.xml @@ -0,0 +1,3738 @@ + + + + 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 + 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 + 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. + 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. + 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 + Biến dạng nhận thức nội dung + Không đủ bộ nhớ để hoàn thành hành động này. Vui lòng thử sử dụng hình ảnh nhỏ hơn, đóng các ứng dụng khác hoặc khởi động lại ứng dụng. + Công nhân song song + Những hình ảnh này không được lưu vì tệp được chuyển đổi sẽ lớn hơn tệp gốc. Kiểm tra danh sách này trước khi xóa hình ảnh gốc. + Tệp bị bỏ qua: %1$s + Nhật ký ứng dụng + Xem nhật ký của ứng dụng để phát hiện sự cố + Mục yêu thích trong các công cụ được nhóm + Thêm mục yêu thích dưới dạng tab khi các công cụ được nhóm theo loại + Hiển thị mục yêu thích cuối cùng + Di chuyển tab công cụ yêu thích xuống cuối + Khoảng cách ngang + Khoảng cách dọc + Năng lượng ngược + Sử dụng bản đồ năng lượng cường độ gradient đơn giản thay vì thuật toán năng lượng chuyển tiếp + Loại bỏ vùng bị che + Các đường nối sẽ đi qua vùng bị che, chỉ loại bỏ vùng được chọn thay vì bảo vệ nó + VHS NTSC + Cài đặt NTSC nâng cao + Điều chỉnh tương tự bổ sung cho các tạo phẩm phát sóng và VHS mạnh hơn + Chảy máu màu + Độ mòn băng + Theo dõi + vết mờ Luma + Đổ chuông + Tuyết + Trường sử dụng + Loại bộ lọc + Bộ lọc luma đầu vào + Đường thông thấp sắc độ đầu vào + Giải điều chế sắc độ + Chuyển pha + Độ lệch pha + Chuyển đổi đầu + Chiều cao chuyển đổi đầu + Phần bù chuyển đổi đầu + Chuyển đổi đầu + Vị trí đường giữa + Giật giật giữa dòng + Theo dõi độ cao tiếng ồn + Theo dõi sóng + Theo dõi tuyết + Theo dõi tính bất đẳng hướng của tuyết + Theo dõi tiếng ồn + Theo dõi cường độ tiếng ồn + Tiếng ồn tổng hợp + Tần số tiếng ồn tổng hợp + Cường độ tiếng ồn tổng hợp + Chi tiết tiếng ồn tổng hợp + Tần số đổ chuông + Điện chuông + Tiếng ồn Luma + Tần số tiếng ồn Luma + Cường độ tiếng ồn Luma + Chi tiết tiếng ồn Luma + Nhiễu sắc độ + Tần số nhiễu sắc độ + Cường độ nhiễu sắc độ + Chi tiết nhiễu sắc độ + Cường độ tuyết + Bất đẳng hướng tuyết + Nhiễu pha sắc độ + Lỗi pha sắc độ + Độ trễ màu theo chiều ngang + Độ trễ màu dọc + Tốc độ băng VHS + Mất sắc độ VHS + Cường độ làm sắc nét VHS + VHS làm sắc nét tần số + Cường độ sóng biên VHS + Tốc độ sóng biên VHS + Tần số sóng biên VHS + Chi tiết sóng biên VHS + Đường thông thấp sắc độ đầu ra + Tỉ lệ ngang + Chia tỷ lệ theo chiều dọc + Hệ số tỷ lệ X + Hệ số tỷ lệ Y + Phát hiện các hình mờ hình ảnh phổ biến và khắc chúng bằng LaMa. Tự động tải xuống các mô hình phát hiện và inpainting + Deuteranopia + Mở rộng hình ảnh + Loại bỏ nền dựa trên phân đoạn đối tượng bằng cách sử dụng Phân đoạn YOLO v11 + Hiển thị góc đường + Hiển thị góc xoay dòng hiện tại theo độ trong khi vẽ + Biểu tượng cảm xúc hoạt hình + Hiển thị biểu tượng cảm xúc có sẵn dưới dạng hình động + Lưu vào thư mục gốc + Lưu tệp mới bên cạnh tệp gốc thay vì thư mục đã chọn + Hình mờ PDF + Không đủ bộ nhớ + Hình ảnh có thể quá lớn để xử lý trên thiết bị này hoặc hệ thống đã hết bộ nhớ khả dụng. Hãy thử giảm độ phân giải hình ảnh, đóng các ứng dụng khác hoặc chọn tệp nhỏ hơn. + Trình đơn gỡ lỗi + Menu để kiểm tra các chức năng của ứng dụng, mục này không nhằm mục đích hiển thị trong bản phát hành chính thức + nhà cung cấp + PaddleOCR yêu cầu các mẫu ONNX bổ sung trên thiết bị của bạn. Bạn có muốn tải xuống dữ liệu %1$s không? + Phổ quát + Tiếng Hàn + tiếng Latinh + Đông Slav + tiếng Thái + tiếng Hy Lạp + Tiếng Anh + chữ cái Cyrillic + tiếng Ả Rập + Devanagari + Tiếng Tamil + tiếng Telugu + đổ bóng + Shader cài sẵn + Không có trình đổ bóng nào được chọn + Studio đổ bóng + Tạo, chỉnh sửa, xác thực, nhập và xuất các trình đổ bóng phân đoạn tùy chỉnh + Trình đổ bóng đã lưu + Mở các shader đã lưu, sao chép, xuất, chia sẻ hoặc xóa + Trình đổ bóng mới + Tập tin đổ bóng + .itshader JSON + %1$d thông số + Nguồn đổ bóng + Chỉ viết phần thân của void main(). Sử dụng textCoordine cho tọa độ UV và inputImageTexture làm bộ lấy mẫu nguồn. + Chức năng + Các hàm trợ giúp, hằng số và cấu trúc tùy chọn được chèn trước void main(). Đồng phục được tạo từ params. + Thêm đồng phục vào đây khi trình đổ bóng cần các giá trị có thể chỉnh sửa. + Đặt lại trình đổ bóng + Bản nháp đổ bóng hiện tại sẽ bị xóa + Trình đổ bóng không hợp lệ + Phiên bản đổ bóng không được hỗ trợ %1$d. Phiên bản được hỗ trợ: %2$d. + Tên shader không được để trống. + Nguồn Shader không được để trống. + Nguồn trình đổ bóng chứa các ký tự không được hỗ trợ: %1$s. Chỉ sử dụng nguồn ASCII GLSL. + Tham số \\\"%1$s\\\" được khai báo nhiều lần. + Tên thông số không được để trống. + Tham số \\\"%1$s\\\" sử dụng tên GPUImage dành riêng. + Tham số \\\"%1$s\\\" phải là mã định danh GLSL hợp lệ. + Tham số \\\"%1$s\\\" có loại giá trị %2$s \\\"%3$s\\\", dự kiến ​​\\\"%4$s\\\". + Tham số \\\"%1$s\\\" không thể xác định giá trị tối thiểu hoặc tối đa cho các giá trị bool. + Tham số \\\"%1$s\\\" có giá trị tối thiểu lớn hơn tối đa. + Thông số \\\"%1$s\\\" mặc định thấp hơn giá trị tối thiểu. + Thông số \\\"%1$s\\\" mặc định lớn hơn giá trị tối đa. + Shader phải khai báo \\\"uniform sampler2D %1$s;\\\". + Shader phải khai báo \\\"thay đổi vec2 %1$s;\\\". + Trình đổ bóng phải xác định \\\"void main()\\\". + Shader phải ghi màu vào gl_FragColor. + ShaderToy mainImage shader không được hỗ trợ. Sử dụng hợp đồng void main() của GPUImage + Đồng phục ShaderToy hoạt hình \\\"iTime\\\" không được hỗ trợ. + Đồng phục ShaderToy hoạt hình \\\"iFrame\\\" không được hỗ trợ. + Đồng phục ShaderToy \\\"iResolution\\\" không được hỗ trợ. + Kết cấu ShaderToy iChannel không được hỗ trợ. + Kết cấu bên ngoài không được hỗ trợ. + Phần mở rộng kết cấu bên ngoài không được hỗ trợ. + Các tham số của trình đổ bóng Libretro không được hỗ trợ. + Chỉ có một kết cấu đầu vào được hỗ trợ. Xóa \\\"đồng phục %1$s %2$s;\\\". + Tham số \\\"%1$s\\\" phải được khai báo là \\\"đồng nhất %2$s %1$s;\\\". + Tham số \\\"%1$s\\\" được khai báo là \\\"đồng nhất %2$s %1$s;\\\", dự kiến ​​\\\"đồng nhất %3$s %1$s;\\\". + Tệp Shader trống. + Tệp Shader phải là đối tượng JSON .itshader có các trường phiên bản, tên và shader. + Tệp Shader không phải là JSON hợp lệ hoặc không khớp với định dạng .itshader. + Trường \\\"%1$s\\\" là bắt buộc. + %1$s là bắt buộc. + %1$s \\\"%2$s\\\" không được hỗ trợ. Các loại được hỗ trợ: %3$s. + %1$s là bắt buộc đối với tham số \\\"%2$s\\\". + %1$s phải là số hữu hạn. + %1$s phải là số nguyên. + %1$s phải đúng hoặc sai. + %1$s phải là chuỗi màu, mảng RGB/RGBA hoặc đối tượng màu. + %1$s phải sử dụng định dạng #RRGGBB hoặc #RRGGBBAA. + %1$s phải chứa các kênh màu thập lục phân hợp lệ. + %1$s phải chứa 3 hoặc 4 kênh màu. + %1$s phải nằm trong khoảng từ 0 đến 255. + %1$s phải là mảng hai số hoặc một đối tượng có x và y. + %1$s phải chứa đúng 2 số. + Nhân bản + Luôn xóa EXIF + Xóa dữ liệu EXIF ​​​​hình ảnh khi lưu, ngay cả khi công cụ yêu cầu giữ lại siêu dữ liệu + Trợ giúp & Mẹo + %1$d hướng dẫn + Mở công cụ + Tìm hiểu các công cụ chính, tùy chọn xuất, luồng PDF, tiện ích màu sắc và cách khắc phục các sự cố thường gặp + Bắt đầu + Chọn công cụ, nhập tệp, lưu kết quả và sử dụng lại cài đặt nhanh hơn + Chỉnh sửa hình ảnh + Thay đổi kích thước, cắt, lọc, xóa nền, vẽ và tạo hình mờ cho hình ảnh + Tệp và siêu dữ liệu + Chuyển đổi định dạng, so sánh đầu ra, bảo vệ quyền riêng tư và kiểm soát tên tệp + PDF và tài liệu + Tạo tệp PDF, quét tài liệu, trang OCR và chuẩn bị tệp để chia sẻ + Văn bản, QR và dữ liệu + Nhận dạng văn bản, quét mã, mã hóa Base64, kiểm tra hàm băm và gói tệp + Công cụ màu sắc + Chọn màu, xây dựng bảng màu, khám phá thư viện màu và tạo độ chuyển màu + Công cụ sáng tạo + Tạo nghệ thuật SVG, ASCII, kết cấu nhiễu, trình đổ bóng và hình ảnh thử nghiệm + Khắc phục sự cố + Khắc phục các sự cố nặng về tệp, tính minh bạch, chia sẻ, nhập và báo cáo + Chọn đúng công cụ + Sử dụng các danh mục, tìm kiếm, yêu thích và chia sẻ hành động để bắt đầu ở đúng nơi + Bắt đầu từ điểm vào tốt nhất + Hộp công cụ Hình ảnh có nhiều công cụ tập trung và nhiều công cụ trong số đó thoạt nhìn có vẻ trùng lặp. Bắt đầu từ tác vụ bạn muốn hoàn thành, sau đó chọn công cụ kiểm soát phần quan trọng nhất của kết quả: kích thước, định dạng, siêu dữ liệu, văn bản, trang PDF, màu sắc hoặc bố cục. + Sử dụng tìm kiếm khi bạn biết tên hành động, chẳng hạn như thay đổi kích thước, PDF, EXIF, OCR, QR hoặc màu sắc. + Mở một danh mục khi bạn chỉ biết loại nhiệm vụ, sau đó ghim các công cụ thường xuyên sử dụng làm mục yêu thích. + Khi một ứng dụng khác chia sẻ hình ảnh vào Hộp công cụ hình ảnh, hãy chọn công cụ đó từ quy trình chia sẻ. + Nhập, lưu và chia sẻ kết quả + Hiểu quy trình thông thường từ chọn đầu vào đến xuất tệp đã chỉnh sửa + Thực hiện theo quy trình chỉnh sửa chung + Hầu hết các công cụ đều tuân theo cùng một nhịp điệu: chọn đầu vào, điều chỉnh tùy chọn, xem trước, sau đó lưu hoặc chia sẻ. Chi tiết quan trọng là việc lưu sẽ tạo một tệp đầu ra, trong khi chia sẻ thường là cách tốt nhất để chuyển nhanh sang ứng dụng khác. + Chọn một tệp cho các công cụ hình ảnh đơn hoặc một số tệp cho các công cụ hàng loạt khi bộ chọn cho phép. + Kiểm tra các điều khiển xem trước và đầu ra trước khi lưu, đặc biệt là các tùy chọn định dạng, chất lượng và tên tệp. + Sử dụng chia sẻ để chuyển giao nhanh hoặc lưu khi bạn cần một bản sao liên tục trong thư mục đã chọn. + Làm việc với nhiều hình ảnh cùng một lúc + Công cụ hàng loạt là công cụ tốt nhất cho các tác vụ thay đổi kích thước, chuyển đổi, nén và đặt tên lặp đi lặp lại + Chuẩn bị một đợt có thể dự đoán được + Xử lý hàng loạt là nhanh nhất khi mọi đầu ra phải tuân theo các quy tắc giống nhau. Đây cũng là nơi dễ mắc lỗi lớn nhất, vì vậy hãy chọn cách đặt tên, chất lượng, siêu dữ liệu và hành vi thư mục trước khi bắt đầu quá trình xuất lâu dài. + Chọn tất cả các hình ảnh liên quan cùng nhau và giữ nguyên thứ tự nếu đầu ra phụ thuộc vào trình tự. + Đặt quy tắc thay đổi kích thước, định dạng, chất lượng, siêu dữ liệu và tên tệp một lần trước khi bắt đầu xuất. + Trước tiên hãy chạy một đợt thử nghiệm nhỏ khi tệp nguồn lớn hoặc khi định dạng đích mới đối với bạn. + Chỉnh sửa nhanh một lần + Sử dụng trình chỉnh sửa tất cả trong một khi bạn cần một vài thay đổi đơn giản trên một hình ảnh + Hoàn thành một hình ảnh mà không cần nhảy công cụ + Chỉnh sửa đơn rất hữu ích khi hình ảnh cần một chuỗi thay đổi nhỏ thay vì một thao tác chuyên biệt. Việc dọn dẹp, xem trước và xuất bản sao cuối cùng thường nhanh hơn mà không cần xây dựng quy trình làm việc hàng loạt. + Mở Chỉnh sửa đơn cho một hình ảnh cần các thay đổi điều chỉnh, đánh dấu, cắt, xoay hoặc xuất thông thường. + Áp dụng các chỉnh sửa theo thứ tự thay đổi ít pixel nhất trước tiên, chẳng hạn như cắt trước bộ lọc và bộ lọc trước khi nén lần cuối. + Thay vào đó, hãy sử dụng một công cụ chuyên dụng khi bạn cần xử lý hàng loạt, mục tiêu kích thước tệp chính xác, đầu ra PDF, OCR hoặc dọn dẹp siêu dữ liệu. + Sử dụng cài đặt trước và cài đặt + Lưu các lựa chọn lặp đi lặp lại và điều chỉnh ứng dụng cho quy trình làm việc ưa thích của bạn + Tránh lặp lại cùng một thiết lập + Các cài đặt trước và cài đặt rất hữu ích khi bạn thường xuyên xuất cùng một loại tệp. Một cài đặt trước tốt sẽ biến danh sách kiểm tra thủ công lặp đi lặp lại thành một lần nhấn, trong khi cài đặt chung xác định các giá trị mặc định mà các công cụ mới bắt đầu. + Tạo các cài đặt trước cho kích thước, định dạng, giá trị chất lượng hoặc mẫu đặt tên đầu ra phổ biến. + Xem lại Cài đặt để biết định dạng mặc định, chất lượng, thư mục lưu, tên tệp, bộ chọn và hành vi giao diện. + Sao lưu cài đặt trước khi cài đặt lại ứng dụng hoặc chuyển sang thiết bị khác. + Đặt mặc định hữu ích + Chọn định dạng mặc định, chất lượng, chế độ tỷ lệ, loại thay đổi kích thước và không gian màu một lần + Làm cho các công cụ mới bắt đầu gần hơn với mục tiêu của bạn + Giá trị mặc định không chỉ là sở thích về mặt thẩm mỹ. Họ quyết định định dạng, chất lượng, hành vi thay đổi kích thước, chế độ tỷ lệ và không gian màu nào xuất hiện đầu tiên trong nhiều công cụ, điều này giúp tránh phải chọn lại các lựa chọn giống nhau trên mỗi lần xuất. + Đặt định dạng và chất lượng hình ảnh mặc định để phù hợp với đích đến thông thường của bạn, chẳng hạn như JPEG cho ảnh hoặc PNG/WebP cho alpha. + Điều chỉnh loại thay đổi kích thước mặc định và chế độ tỷ lệ nếu bạn thường xuất cho các kích thước nghiêm ngặt, nền tảng xã hội hoặc trang tài liệu. + Chỉ thay đổi mặc định không gian màu khi quy trình làm việc của bạn cần xử lý màu nhất quán trên màn hình, bản in hoặc trình chỉnh sửa bên ngoài. + Bộ chọn và trình khởi chạy + Sử dụng cài đặt bộ chọn khi ứng dụng mở quá nhiều hộp thoại hoặc khởi động sai vị trí + Làm cho việc mở tập tin phù hợp với thói quen của bạn + Cài đặt bộ chọn và trình khởi chạy kiểm soát xem Hộp công cụ hình ảnh có bắt đầu từ màn hình chính, công cụ hay luồng chọn tệp hay không. Các tùy chọn này đặc biệt hữu ích khi bạn thường nhập từ bảng chia sẻ Android hoặc khi bạn muốn ứng dụng có cảm giác giống một trình khởi chạy công cụ hơn. + Sử dụng cài đặt bộ chọn ảnh nếu bộ chọn hệ thống ẩn tệp, hiển thị quá nhiều mục gần đây hoặc xử lý kém các tệp trên đám mây. + Chỉ bật bỏ qua chọn đối với các công cụ mà bạn thường nhập từ phần chia sẻ hoặc đã biết tệp nguồn. + Xem lại chế độ trình khởi chạy nếu bạn muốn Hộp công cụ Hình ảnh hoạt động giống một bộ chọn công cụ hơn là màn hình chính thông thường. + Nguồn hình ảnh + Chọn chế độ bộ chọn hoạt động tốt nhất với thư viện, trình quản lý tệp và tệp trên đám mây + Chọn tập tin từ đúng nguồn + Cài đặt Nguồn hình ảnh ảnh hưởng đến cách ứng dụng yêu cầu Android cung cấp hình ảnh. Lựa chọn tốt nhất tùy thuộc vào việc tệp của bạn có nằm trong thư viện hệ thống, thư mục quản lý tệp, nhà cung cấp đám mây hay ứng dụng khác chia sẻ nội dung tạm thời hay không. + Sử dụng bộ chọn mặc định khi bạn muốn có trải nghiệm tích hợp hệ thống nhất cho các hình ảnh thư viện phổ biến. + Chuyển chế độ bộ chọn nếu album, thư mục, tệp đám mây hoặc định dạng không chuẩn không xuất hiện ở nơi bạn mong đợi. + Nếu tệp được chia sẻ biến mất sau khi rời khỏi ứng dụng nguồn, trước tiên hãy mở lại tệp đó thông qua bộ chọn liên tục hoặc lưu bản sao cục bộ. + Công cụ yêu thích và chia sẻ + Giữ màn hình chính tập trung và ẩn các công cụ không có ý nghĩa trong luồng chia sẻ + Giảm tiếng ồn của danh sách công cụ + Cài đặt yêu thích và ẩn để chia sẻ rất hữu ích khi bạn chỉ sử dụng một vài công cụ mỗi ngày. Họ cũng giữ cho luồng chia sẻ trở nên thiết thực bằng cách ẩn các công cụ không thể sử dụng loại tệp mà ứng dụng khác gửi. + Ghim các công cụ thường xuyên để chúng luôn dễ dàng tiếp cận ngay cả khi các công cụ được nhóm theo danh mục. + Ẩn các công cụ khỏi luồng chia sẻ khi chúng không liên quan đến các tệp đến từ ứng dụng khác. + Sử dụng cài đặt thứ tự yêu thích nếu bạn thích mục yêu thích ở cuối hoặc được nhóm với các công cụ liên quan. + Sao lưu cài đặt + Di chuyển cài đặt trước, tùy chọn và hành vi ứng dụng sang cài đặt khác một cách an toàn + Giữ thiết lập của bạn di động + Sao lưu và Khôi phục hữu ích nhất sau khi bạn đã điều chỉnh các cài đặt trước, thư mục, quy tắc tên tệp, thứ tự công cụ và tùy chọn hình ảnh. Bản sao lưu cho phép bạn thử nghiệm cài đặt hoặc cài đặt lại ứng dụng mà không cần xây dựng lại quy trình làm việc từ bộ nhớ. + Tạo bản sao lưu sau khi thay đổi cài đặt trước, hành vi tên tệp, định dạng mặc định hoặc sắp xếp công cụ. + Lưu trữ bản sao lưu ở đâu đó bên ngoài thư mục ứng dụng nếu bạn định xóa dữ liệu, cài đặt lại hoặc di chuyển thiết bị. + Khôi phục cài đặt trước khi xử lý các lô quan trọng để mặc định và hành vi lưu khớp với thiết lập cũ của bạn. + Thay đổi kích thước và chuyển đổi hình ảnh + Thay đổi kích thước, định dạng, chất lượng và siêu dữ liệu cho một hoặc nhiều hình ảnh + Tạo bản sao đã thay đổi kích thước + Thay đổi kích thước và Chuyển đổi là công cụ chính để dự đoán kích thước và tệp đầu ra nhẹ hơn. Sử dụng nó khi kích thước của hình ảnh, định dạng đầu ra và hành vi siêu dữ liệu đều quan trọng cùng một lúc. + Chọn một hoặc nhiều hình ảnh, sau đó chọn kích thước, tỷ lệ hoặc kích thước tệp quan trọng nhất. + Chọn định dạng và chất lượng đầu ra; sử dụng PNG hoặc WebP khi cần bảo toàn độ trong suốt. + Xem trước kết quả, sau đó lưu hoặc chia sẻ bản sao được tạo mà không thay đổi bản gốc. + Thay đổi kích thước theo kích thước tập tin + Nhắm mục tiêu giới hạn kích thước khi trang web, trình nhắn tin hoặc biểu mẫu từ chối hình ảnh nặng + Phù hợp với giới hạn tải lên nghiêm ngặt + Thay đổi kích thước theo kích thước tệp sẽ tốt hơn việc đoán giá trị chất lượng khi đích chỉ chấp nhận các tệp dưới một giới hạn cụ thể. Công cụ này có thể điều chỉnh độ nén và kích thước để tiếp cận mục tiêu trong khi cố gắng giữ cho kết quả có thể sử dụng được. + Nhập kích thước mục tiêu thấp hơn một chút so với giới hạn tải lên thực tế để có chỗ cho những thay đổi về siêu dữ liệu và nhà cung cấp. + Thích các định dạng lossy cho ảnh khi mục tiêu nhỏ; PNG có thể vẫn ở kích thước lớn ngay cả sau khi thay đổi kích thước. + Kiểm tra các bề mặt, văn bản và các cạnh sau khi xuất vì các mục tiêu có kích thước quá lớn có thể tạo ra các thành phần lạ có thể nhìn thấy được. + Giới hạn thay đổi kích thước + Chỉ thu nhỏ những hình ảnh vượt quá giới hạn về chiều rộng, chiều cao hoặc tệp tối đa của bạn + Tránh thay đổi kích thước hình ảnh đã ổn + Giới hạn Thay đổi kích thước rất hữu ích cho các thư mục hỗn hợp trong đó một số hình ảnh rất lớn và những hình ảnh khác đã được chuẩn bị sẵn. Thay vì buộc mọi tệp phải thay đổi kích thước giống nhau, nó sẽ giữ cho hình ảnh có thể chấp nhận được gần với chất lượng ban đầu hơn. + Đặt kích thước hoặc giới hạn tối đa dựa trên đích đến thay vì thay đổi kích thước mọi tệp một cách mù quáng. + Bật hành vi kiểu bỏ qua nếu lớn hơn khi bạn muốn tránh đầu ra trở nên nặng hơn nguồn. + Sử dụng tính năng này cho các lô từ các máy ảnh, ảnh chụp màn hình hoặc nội dung tải xuống khác nhau trong đó kích thước nguồn khác nhau rất nhiều. + Cắt, xoay và làm thẳng + Đóng khung khu vực quan trọng trước khi xuất hoặc sử dụng hình ảnh trong các công cụ khác + Làm sạch thành phần + Việc cắt xén trước giúp các bộ lọc sau, OCR, trang PDF và chia sẻ kết quả rõ ràng hơn. + Mở Cắt và chọn hình ảnh bạn muốn điều chỉnh. + Sử dụng tỷ lệ khung hình hoặc cắt tự do khi đầu ra phải vừa với bài đăng, tài liệu hoặc hình đại diện. + Xoay hoặc lật trước khi lưu để các công cụ sau này nhận được hình ảnh đã chỉnh sửa. + Áp dụng bộ lọc và điều chỉnh + Xây dựng ngăn xếp bộ lọc có thể chỉnh sửa và xem trước kết quả trước khi xuất + Tinh chỉnh sự xuất hiện của hình ảnh + Bộ lọc rất hữu ích để chỉnh sửa nhanh, đầu ra cách điệu và chuẩn bị hình ảnh cho OCR hoặc in. Những thay đổi nhỏ về độ tương phản, độ sắc nét và độ sáng có thể quan trọng hơn những hiệu ứng nặng nề khi hình ảnh chứa văn bản hoặc chi tiết nhỏ. + Thêm từng bộ lọc một và theo dõi bản xem trước sau mỗi lần thay đổi. + Điều chỉnh độ sáng, độ tương phản, độ sắc nét, độ mờ, màu sắc hoặc mặt nạ tùy theo tác vụ. + Chỉ xuất khi bản xem trước khớp với thiết bị, tài liệu hoặc nền tảng xã hội mục tiêu của bạn. + Xem trước trước + Kiểm tra hình ảnh trước khi chọn công cụ chỉnh sửa, chuyển đổi hoặc dọn dẹp nặng hơn + Kiểm tra những gì bạn thực sự nhận được + Xem trước hình ảnh rất hữu ích khi có tệp đến từ cuộc trò chuyện, bộ nhớ đám mây hoặc ứng dụng khác và bạn không chắc chắn tệp đó chứa gì. Việc kiểm tra kích thước, độ trong suốt, định hướng và chất lượng hình ảnh trước tiên sẽ giúp chọn công cụ theo dõi chính xác. + Mở Xem trước hình ảnh khi bạn cần kiểm tra một số hình ảnh trước khi quyết định phải làm gì với chúng. + Tìm kiếm các vấn đề về xoay, vùng trong suốt, tạo tác nén và kích thước lớn bất ngờ. + Chỉ di chuyển đến Thay đổi kích thước, Cắt, So sánh, EXIF ​​​​hoặc Xóa nền sau khi bạn biết những gì cần sửa. + Xóa hoặc khôi phục nền + Tự động xóa nền hoặc tinh chỉnh các cạnh theo cách thủ công bằng các công cụ khôi phục + Giữ nguyên chủ đề, loại bỏ phần còn lại + Tính năng xóa nền hoạt động tốt nhất khi bạn kết hợp lựa chọn tự động với việc dọn dẹp thủ công cẩn thận. Định dạng xuất cuối cùng cũng quan trọng như mặt nạ, vì JPEG sẽ làm phẳng các vùng trong suốt ngay cả khi bản xem trước trông chính xác. + Bắt đầu bằng tính năng tự động xóa nếu chủ thể được tách biệt rõ ràng khỏi hậu cảnh. + Phóng to và chuyển đổi giữa xóa và khôi phục để sửa tóc, góc, bóng và các chi tiết nhỏ. + Lưu dưới dạng PNG hoặc WebP khi bạn cần độ trong suốt của tệp cuối cùng. + Vẽ, đánh dấu và hình mờ + Thêm mũi tên, ghi chú, đánh dấu, chữ ký hoặc lớp phủ hình mờ lặp lại + Thêm thông tin hiển thị + Các công cụ đánh dấu giúp giải thích hình ảnh, trong khi hình mờ giúp tạo thương hiệu hoặc bảo vệ các tệp được xuất. + Sử dụng Vẽ khi bạn cần ghi chú tự do, hình dạng, tô sáng hoặc chú thích nhanh. + Sử dụng Hình mờ khi cùng một dấu văn bản hoặc hình ảnh xuất hiện nhất quán trên đầu ra. + Kiểm tra độ mờ, vị trí và tỷ lệ trước khi xuất để đánh dấu có thể nhìn thấy nhưng không gây mất tập trung. + Vẽ mặc định + Đặt độ rộng dòng, màu sắc, chế độ đường dẫn, khóa định hướng và kính lúp để đánh dấu nhanh hơn + Làm cho bản vẽ có cảm giác dễ đoán + Cài đặt vẽ rất hữu ích khi bạn thường xuyên chú thích ảnh chụp màn hình, đánh dấu tài liệu hoặc ký tên vào hình ảnh. Các giá trị mặc định giúp giảm thời gian thiết lập, trong khi kính lúp và khóa hướng giúp chỉnh sửa chính xác dễ dàng hơn trên màn hình nhỏ. + Đặt độ rộng dòng mặc định và màu vẽ theo các giá trị bạn sử dụng nhiều nhất cho mũi tên, điểm đánh dấu hoặc chữ ký. + Sử dụng chế độ đường dẫn mặc định khi bạn thường vẽ cùng loại nét, hình dạng hoặc đánh dấu. + Bật kính lúp hoặc khóa định hướng khi thao tác nhập bằng ngón tay khiến các chi tiết nhỏ khó đặt chính xác. + Bố cục nhiều hình ảnh + Chọn công cụ tạo nhiều hình ảnh phù hợp trước khi sắp xếp ảnh chụp màn hình, bản quét hoặc dải so sánh + Sử dụng công cụ bố cục phù hợp + Những công cụ này nghe có vẻ giống nhau nhưng mỗi công cụ được điều chỉnh cho một loại đầu ra nhiều hình ảnh khác nhau. Việc chọn đúng trước tiên sẽ tránh gây tranh cãi với các điều khiển bố cục được thiết kế để mang lại kết quả khác. + Sử dụng Collage Maker để tạo bố cục được thiết kế với nhiều hình ảnh trong một bố cục. + Sử dụng Ghép ảnh hoặc Xếp chồng khi hình ảnh sẽ trở thành một dải dài dọc hoặc ngang. + Sử dụng tính năng Tách hình ảnh khi một hình ảnh lớn cần chia thành nhiều phần nhỏ hơn. + Chuyển đổi định dạng hình ảnh + Tạo JPEG, PNG, WebP, AVIF, JXL và các bản sao khác mà không cần chỉnh sửa pixel theo cách thủ công + Chọn định dạng cho công việc + Các định dạng khác nhau sẽ tốt hơn cho ảnh, ảnh chụp màn hình, độ trong suốt, khả năng nén hoặc khả năng tương thích. Chuyển đổi là an toàn nhất khi bạn hiểu ứng dụng đích hỗ trợ những gì và liệu nguồn có chứa alpha, hoạt ảnh hay siêu dữ liệu mà bạn muốn giữ lại hay không. + Sử dụng JPEG cho ảnh tương thích, PNG cho hình ảnh không mất dữ liệu và alpha và WebP để chia sẻ nhỏ gọn. + Điều chỉnh chất lượng khi định dạng hỗ trợ; giá trị thấp hơn làm giảm kích thước nhưng có thể thêm các tạo phẩm. + Giữ bản gốc cho đến khi bạn xác minh rằng các tệp đã chuyển đổi mở chính xác trong ứng dụng đích. + Kiểm tra EXIF ​​​​và quyền riêng tư + Xem, chỉnh sửa hoặc xóa siêu dữ liệu trước khi chia sẻ ảnh nhạy cảm + Kiểm soát dữ liệu hình ảnh ẩn + EXIF ​​có thể chứa dữ liệu máy ảnh, ngày tháng, vị trí, hướng và các chi tiết khác không hiển thị trong ảnh. Bạn nên kiểm tra trước khi chia sẻ công khai, báo cáo hỗ trợ, danh sách trên thị trường hoặc bất kỳ ảnh nào có thể tiết lộ địa điểm riêng tư. + Mở công cụ EXIF ​​​​trước khi chia sẻ ảnh có thể chứa thông tin vị trí hoặc thiết bị riêng tư. + Xóa các trường nhạy cảm hoặc chỉnh sửa các thẻ không chính xác như dữ liệu ngày, hướng, tác giả hoặc máy ảnh. + Lưu một bản sao mới và chia sẻ bản sao đó thay vì bản gốc khi vấn đề về quyền riêng tư. + Tự động dọn dẹp EXIF + Xóa siêu dữ liệu theo mặc định trong khi quyết định xem có nên giữ nguyên ngày hay không + Đặt quyền riêng tư làm mặc định + Nhóm cài đặt EXIF ​​​​hữu ích khi bạn muốn quá trình dọn dẹp quyền riêng tư diễn ra tự động thay vì ghi nhớ mỗi lần xuất. Giữ ngày Giờ là một quyết định riêng biệt vì ngày tháng có thể hữu ích cho việc sắp xếp nhưng lại nhạy cảm khi chia sẻ. + Bật EXIF ​​​​luôn rõ ràng khi hầu hết nội dung xuất của bạn được dùng để chia sẻ công khai hoặc bán công khai. + Chỉ bật Keep Date Time khi việc duy trì thời gian chụp quan trọng hơn việc xóa mọi dấu thời gian. + Sử dụng tính năng chỉnh sửa EXIF ​​​​thủ công cho các tệp dùng một lần mà bạn cần giữ lại một số trường và xóa các trường khác. + Kiểm soát tên tập tin + Sử dụng tiền tố, hậu tố, dấu thời gian, giá trị đặt trước, tổng kiểm tra và số thứ tự + Làm cho hàng xuất khẩu dễ tìm thấy + Mẫu tên tệp tốt giúp sắp xếp các lô và ngăn chặn việc vô tình ghi đè. Cài đặt tên tệp đặc biệt hữu ích khi xuất trở lại thư mục nguồn, nơi các tên tương tự có thể nhanh chóng gây nhầm lẫn. + Đặt các tùy chọn tiền tố, hậu tố, dấu thời gian, tên gốc, thông tin đặt trước hoặc trình tự tên tệp trong Cài đặt. + Sử dụng số thứ tự cho các lô có thứ tự quan trọng, chẳng hạn như trang, khung hoặc ảnh ghép. + Chỉ bật ghi đè khi bạn chắc chắn rằng việc thay thế các tệp hiện có là an toàn cho thư mục hiện tại. + Ghi đè tập tin + Chỉ thay thế kết quả đầu ra bằng tên phù hợp khi quy trình làm việc của bạn có chủ ý phá hoại + Sử dụng chế độ ghi đè cẩn thận + Ghi đè tập tin thay đổi cách xử lý xung đột đặt tên. Nó rất hữu ích khi xuất lặp lại vào cùng một thư mục, nhưng nó cũng có thể thay thế các kết quả trước đó nếu mẫu tên tệp của bạn tạo lại cùng tên. + Chỉ bật ghi đè cho các thư mục dự kiến ​​thay thế các tệp được tạo cũ hơn và có thể phục hồi được. + Tắt tính năng ghi đè khi xuất bản gốc, tệp khách, bản quét một lần hoặc bất kỳ thứ gì mà không có bản sao lưu. + Kết hợp ghi đè bằng mẫu tên tệp có chủ ý để chỉ thay thế các kết quả đầu ra dự định. + Mẫu tên tệp + Kết hợp tên gốc, tiền tố, hậu tố, dấu thời gian, cài đặt trước, chế độ tỷ lệ và tổng kiểm tra + Xây dựng tên giải thích đầu ra + Cài đặt mẫu tên tệp có thể biến các tệp đã xuất thành lịch sử hữu ích về những gì đã xảy ra với chúng. Điều này quan trọng theo từng đợt vì chế độ xem thư mục có thể là nơi duy nhất mà bạn có thể phân biệt kích thước ban đầu, cài đặt sẵn, định dạng và thứ tự xử lý. + Sử dụng tên tệp gốc, tiền tố, hậu tố và dấu thời gian khi bạn cần tên dễ đọc để sắp xếp thủ công. + Thêm giá trị đặt trước, chế độ tỷ lệ, kích thước tệp hoặc thông tin tổng kiểm tra khi đầu ra phải ghi lại cách chúng được tạo ra. + Tránh đặt tên ngẫu nhiên cho quy trình công việc trong đó các tệp cần được ghép nối với bản gốc hoặc thứ tự trang. + Chọn nơi lưu tập tin + Tránh mất dữ liệu xuất bằng cách đặt thư mục, lưu thư mục gốc và vị trí lưu một lần + Giữ kết quả đầu ra có thể dự đoán được + Hành vi lưu quan trọng nhất khi bạn xử lý nhiều tệp hoặc chia sẻ tệp từ nhà cung cấp đám mây. Cài đặt thư mục quyết định liệu đầu ra có được thu thập ở một nơi đã biết, xuất hiện bên cạnh bản gốc hay tạm thời đi đến một nơi khác để thực hiện một tác vụ cụ thể hay không. + Đặt thư mục lưu mặc định nếu bạn luôn muốn xuất ở một nơi. + Sử dụng thư mục lưu vào thư mục gốc để dọn dẹp quy trình công việc trong đó đầu ra phải ở gần tệp nguồn. + Sử dụng vị trí lưu một lần khi một tác vụ cần một đích đến khác mà không thay đổi mặc định của bạn. + Thư mục lưu một lần + Gửi các bản xuất tiếp theo tới một thư mục tạm thời mà không thay đổi đích đến thông thường của bạn + Giữ các công việc đặc biệt riêng biệt + Vị trí lưu một lần rất hữu ích cho các dự án tạm thời, thư mục khách, phiên dọn dẹp hoặc xuất không nên kết hợp với thư mục Hộp công cụ hình ảnh thông thường của bạn. Nó cung cấp đích đến trong thời gian ngắn mà không cần viết lại thiết lập mặc định của bạn. + Chọn thư mục dùng một lần trước khi bắt đầu tác vụ nằm ngoài vị trí xuất thông thường của bạn. + Sử dụng nó cho các dự án ngắn, thư mục dùng chung hoặc các lô mà đầu ra phải được nhóm lại với nhau. + Quay trở lại thư mục mặc định sau công việc để các lần xuất sau này không rơi vào vị trí tạm thời một cách bất ngờ. + Cân bằng chất lượng và kích thước tập tin + Hiểu thời điểm cần thay đổi kích thước, định dạng hoặc chất lượng thay vì đoán + Cố ý thu nhỏ tập tin + Kích thước tệp phụ thuộc vào kích thước, định dạng, chất lượng, độ trong suốt và độ phức tạp của nội dung. Nếu tệp vẫn còn quá nặng, việc thay đổi kích thước thường giúp ích nhiều hơn là liên tục giảm chất lượng. + Thay đổi kích thước kích thước trước tiên khi hình ảnh lớn hơn mức đích có thể hiển thị. + Chất lượng thấp hơn chỉ dành cho các định dạng bị mất và kiểm tra văn bản, cạnh, độ dốc và bề mặt sau khi xuất. + Chuyển đổi định dạng khi thay đổi về chất lượng là không đủ, ví dụ như WebP để chia sẻ hoặc PNG để có nội dung trong suốt, sắc nét. + Làm việc với các định dạng hoạt hình + Sử dụng các công cụ GIF, APNG, WebP và JXL khi tệp có khung thay vì một bitmap + Đừng coi mọi hình ảnh là tĩnh + Các định dạng hoạt hình cần các công cụ hiểu được khung hình, thời lượng và khả năng tương thích. + Sử dụng các công cụ GIF hoặc APNG khi bạn cần các thao tác ở cấp độ khung hình cho các định dạng đó. + Sử dụng các công cụ WebP khi bạn cần đầu ra WebP nén hoặc hoạt hình hiện đại. + Xem trước thời gian hoạt ảnh trước khi chia sẻ vì một số nền tảng chuyển đổi hoặc làm phẳng hình ảnh động. + So sánh và xem trước đầu ra + Kiểm tra các thay đổi, kích thước tệp và sự khác biệt về hình ảnh trước khi tiếp tục xuất + Xác minh trước khi chia sẻ + Các công cụ so sánh giúp phát hiện sớm các thành phần nén, sai màu hoặc các chỉnh sửa không mong muốn. + Mở So sánh khi bạn muốn kiểm tra hai phiên bản cạnh nhau. + Phóng to các cạnh, văn bản, độ chuyển màu và các vùng trong suốt nơi dễ bỏ sót vấn đề nhất. + Nếu đầu ra quá nặng hoặc mờ, hãy quay lại công cụ trước đó và điều chỉnh chất lượng hoặc định dạng. + Sử dụng trung tâm công cụ PDF + Hợp nhất, phân tách, xoay, xóa trang, nén, bảo vệ, OCR và hình mờ các tệp PDF + Chọn thao tác PDF + Trung tâm PDF nhóm các hành động ghi lại sau một điểm nhập để bạn có thể chọn thao tác chính xác. Bắt đầu từ đó khi tệp đã là PDF và sử dụng Hình ảnh sang PDF hoặc Trình quét tài liệu khi nguồn của bạn vẫn là một tập hợp hình ảnh. + Mở Công cụ PDF và chọn thao tác phù hợp với mục tiêu của bạn. + Chọn tệp PDF hoặc hình ảnh nguồn, sau đó xem lại các tùy chọn thứ tự trang, xoay, bảo vệ hoặc OCR. + Lưu tệp PDF đã tạo và mở nó một lần để xác nhận số lượng trang, thứ tự và khả năng đọc. + Biến hình ảnh thành PDF + Kết hợp các bản quét, ảnh chụp màn hình, biên lai hoặc ảnh vào một tài liệu có thể chia sẻ + Xây dựng một tài liệu sạch + Hình ảnh trở thành trang PDF tốt hơn khi chúng được cắt, sắp xếp và định kích thước một cách nhất quán. Hãy coi danh sách hình ảnh giống như một ngăn xếp trang: mọi lựa chọn xoay, lề và kích thước trang đều ảnh hưởng đến cảm giác dễ đọc của tài liệu cuối cùng. + Trước tiên, hãy cắt hoặc xoay hình ảnh khi cạnh trang, bóng hoặc hướng trang không chính xác. + Chọn hình ảnh theo thứ tự trang dự định và điều chỉnh kích thước trang hoặc lề nếu công cụ cung cấp chúng. + Xuất tệp PDF, sau đó kiểm tra xem tất cả các trang đều có thể đọc được trước khi gửi. + Quét tài liệu + Chụp các trang giấy và chuẩn bị chúng dưới dạng PDF, OCR hoặc chia sẻ + Chụp các trang có thể đọc được + Quét tài liệu hoạt động tốt nhất với các trang phẳng, ánh sáng tốt và phối cảnh được điều chỉnh. Quét sạch trước khi xuất OCR hoặc PDF giúp tiết kiệm thời gian vì nhận dạng và nén văn bản đều phụ thuộc vào chất lượng trang. + Đặt tài liệu trên một bề mặt tương phản có đủ ánh sáng và bóng tối tối thiểu. + Điều chỉnh các góc được phát hiện hoặc cắt thủ công để trang lấp đầy khung hình một cách rõ ràng. + Xuất dưới dạng hình ảnh hoặc PDF, sau đó chạy OCR nếu bạn cần văn bản có thể chọn. + Bảo vệ hoặc mở khóa tệp PDF + Sử dụng mật khẩu cẩn thận và giữ một bản sao nguồn có thể chỉnh sửa khi có thể + Xử lý truy cập PDF một cách an toàn + Các công cụ bảo vệ rất hữu ích cho việc chia sẻ có kiểm soát nhưng mật khẩu rất dễ bị mất và khó khôi phục. Bảo vệ các bản sao để phân phối, giữ riêng các tệp nguồn không được bảo vệ và xác minh kết quả trước khi xóa bất kỳ thứ gì. + Bảo vệ bản sao của tệp PDF chứ không phải tài liệu nguồn duy nhất của bạn. + Lưu trữ mật khẩu ở nơi an toàn trước khi chia sẻ tệp được bảo vệ. + Chỉ sử dụng tính năng mở khóa cho các tệp bạn được phép chỉnh sửa, sau đó xác minh rằng bản sao đã mở khóa sẽ mở chính xác. + Sắp xếp các trang PDF + Hợp nhất, chia tách, sắp xếp lại, xoay, cắt, xóa trang và thêm số trang một cách có chủ ý + Sửa cấu trúc tài liệu trước khi trang trí + Các công cụ trang PDF dễ sử dụng nhất theo thứ tự giống như bạn chuẩn bị một tài liệu giấy: thu thập các trang, xóa những trang sai, sắp xếp chúng theo thứ tự, sửa hướng, sau đó thêm các chi tiết cuối cùng như số trang hoặc hình mờ. + Trước tiên hãy sử dụng tính năng hợp nhất hoặc chuyển hình ảnh thành PDF khi tài liệu vẫn được chia thành nhiều tệp. + Sắp xếp lại, xoay, cắt hoặc xóa trang trước khi thêm số trang, chữ ký hoặc hình mờ. + Xem trước bản PDF cuối cùng sau khi chỉnh sửa cấu trúc vì sau này khó có thể nhận thấy một trang bị thiếu hoặc bị xoay. + Chú thích PDF + Xóa hoặc làm phẳng chú thích khi người xem hiển thị nhận xét và đánh dấu khác nhau + Kiểm soát những gì vẫn hiển thị + Chú thích có thể là nhận xét, đánh dấu, hình vẽ, dấu biểu mẫu hoặc các đối tượng PDF bổ sung khác. Một số trình xem hiển thị chúng theo cách khác nhau nên việc loại bỏ hoặc làm phẳng chúng có thể giúp tài liệu được chia sẻ dễ dự đoán hơn. + Xóa chú thích khi không nên đưa nhận xét, đánh dấu hoặc dấu đánh giá vào bản sao được chia sẻ. + Làm phẳng khi các dấu hiển thị sẽ vẫn ở trên trang nhưng không còn hoạt động giống như các chú thích có thể chỉnh sửa. + Giữ một bản gốc vì việc xóa hoặc làm phẳng các chú thích có thể khó đảo ngược. + Nhận dạng văn bản + Trích xuất văn bản từ hình ảnh bằng các công cụ và ngôn ngữ OCR có thể lựa chọn + Nhận kết quả OCR tốt hơn + Độ chính xác của OCR phụ thuộc vào độ sắc nét của hình ảnh, dữ liệu ngôn ngữ, độ tương phản và hướng trang. Kết quả tốt nhất thường đến từ việc chuẩn bị hình ảnh trước: loại bỏ nhiễu, xoay thẳng đứng, tăng khả năng đọc, sau đó nhận dạng văn bản. + Cắt hình ảnh theo vùng văn bản và xoay nó thẳng đứng trước khi nhận dạng. + Chọn ngôn ngữ chính xác và tải xuống dữ liệu ngôn ngữ nếu ứng dụng yêu cầu. + Sao chép hoặc chia sẻ văn bản được nhận dạng, sau đó kiểm tra tên, số và dấu câu theo cách thủ công. + Chọn dữ liệu ngôn ngữ OCR + Cải thiện khả năng nhận dạng bằng cách khớp tập lệnh, ngôn ngữ và mô hình OCR đã tải xuống + Khớp OCR với văn bản + Ngôn ngữ OCR sai có thể khiến những bức ảnh đẹp trông giống như khả năng nhận dạng kém. Dữ liệu ngôn ngữ quan trọng đối với chữ viết, dấu câu, số và tài liệu hỗn hợp, vì vậy hãy chọn ngôn ngữ xuất hiện trong hình ảnh thay vì ngôn ngữ của giao diện người dùng điện thoại. + Chọn ngôn ngữ hoặc chữ viết xuất hiện trong hình ảnh, không chỉ ngôn ngữ điện thoại. + Tải xuống dữ liệu bị thiếu trước khi làm việc ngoại tuyến hoặc xử lý hàng loạt. + Đối với các tài liệu có ngôn ngữ hỗn hợp, trước tiên hãy chạy ngôn ngữ quan trọng nhất rồi kiểm tra tên và số theo cách thủ công. + PDF có thể tìm kiếm + Viết lại văn bản OCR vào các tệp để có thể tìm kiếm và sao chép các trang được quét + Biến bản quét thành tài liệu có thể tìm kiếm được + OCR có thể làm được nhiều việc hơn là sao chép văn bản vào khay nhớ tạm. Khi bạn viết văn bản được nhận dạng vào một tệp hoặc tệp PDF có thể tìm kiếm, hình ảnh của trang vẫn hiển thị trong khi văn bản trở nên dễ dàng hơn để tìm kiếm, chọn, lập chỉ mục hoặc lưu trữ. + Sử dụng đầu ra PDF có thể tìm kiếm cho các tài liệu được quét vẫn trông giống như các trang gốc. + Sử dụng tính năng ghi vào tệp khi bạn chỉ cần trích xuất văn bản và không quan tâm đến hình ảnh trang. + Kiểm tra các số, tên và bảng quan trọng theo cách thủ công vì văn bản OCR có thể tìm kiếm được nhưng vẫn không hoàn hảo. + Quét mã QR + Đọc nội dung QR từ đầu vào của máy ảnh hoặc hình ảnh đã lưu khi có sẵn + Đọc mã an toàn + Mã QR có thể chứa các liên kết, dữ liệu Wi-Fi, danh bạ, văn bản hoặc các tải trọng khác. + Mở Quét mã QR và giữ mã sắc nét, phẳng và đầy đủ bên trong khung. + Xem lại nội dung được giải mã trước khi mở liên kết hoặc sao chép dữ liệu nhạy cảm. + Nếu quá trình quét không thành công, hãy cải thiện độ sáng, cắt mã hoặc thử hình ảnh nguồn có độ phân giải cao hơn. + Sử dụng Base64 và tổng kiểm tra + Mã hóa hình ảnh dưới dạng văn bản, giải mã văn bản trở lại hình ảnh và xác minh tính toàn vẹn của tệp + Di chuyển giữa các tập tin và văn bản + Base64 hữu ích cho tải trọng hình ảnh nhỏ, trong khi tổng kiểm tra giúp xác nhận rằng các tệp không thay đổi. Những công cụ này hữu ích nhất trong quy trình công việc kỹ thuật, báo cáo hỗ trợ, truyền tệp và những nơi mà tệp nhị phân cần tạm thời trở thành văn bản. + Sử dụng các công cụ Base64 để mã hóa một hình ảnh nhỏ khi quy trình làm việc cần văn bản thay vì tệp nhị phân. + Chỉ giải mã Base64 từ các nguồn đáng tin cậy và xác minh bản xem trước trước khi lưu. + Sử dụng công cụ tổng kiểm tra khi bạn cần so sánh các tệp đã xuất hoặc xác nhận tải lên/tải xuống. + Đóng gói hoặc bảo vệ dữ liệu + Sử dụng các công cụ ZIP và mật mã để đóng gói tệp hoặc chuyển đổi dữ liệu văn bản + Giữ các tập tin liên quan cùng nhau + Các công cụ đóng gói rất hữu ích khi một số đầu ra sẽ được truyền đi dưới dạng một tệp. Chúng cũng rất tốt để giữ các hình ảnh, tệp PDF, nhật ký và siêu dữ liệu được tạo cùng nhau khi bạn cần gửi một tập hợp kết quả hoàn chỉnh. + Sử dụng ZIP khi bạn cần gửi nhiều hình ảnh hoặc tài liệu đã xuất cùng nhau. + Chỉ sử dụng các công cụ mật mã khi bạn hiểu phương pháp đã chọn và có thể lưu trữ các khóa cần thiết một cách an toàn. + Kiểm tra kho lưu trữ đã tạo hoặc văn bản đã chuyển đổi trước khi xóa tệp nguồn. + Tổng kiểm tra trong tên + Sử dụng tên tệp dựa trên tổng kiểm tra khi tính duy nhất và tính toàn vẹn quan trọng hơn khả năng đọc + Đặt tên file theo nội dung + Tên tệp tổng kiểm tra rất hữu ích khi xuất có thể có tên hiển thị trùng lặp hoặc khi bạn cần chứng minh rằng hai tệp giống hệt nhau. Chúng ít thân thiện hơn với việc duyệt thủ công, vì vậy hãy sử dụng chúng để lưu trữ kỹ thuật và quy trình xác minh. + Bật tổng kiểm tra dưới dạng tên tệp khi nhận dạng nội dung quan trọng hơn tiêu đề mà con người có thể đọc được. + Sử dụng nó để lưu trữ, chống trùng lặp, xuất lặp lại hoặc các tệp có thể được tải lên và tải xuống lại. + Ghép nối tên tổng kiểm tra với các thư mục hoặc siêu dữ liệu khi mọi người vẫn cần hiểu tệp đó đại diện cho điều gì. + Chọn màu từ hình ảnh + Pixel mẫu, sao chép mã màu và sử dụng lại màu trong bảng màu hoặc thiết kế + Lấy mẫu màu chính xác + Việc chọn màu rất hữu ích trong việc kết hợp thiết kế, trích xuất màu sắc thương hiệu hoặc kiểm tra giá trị hình ảnh. Việc thu phóng rất quan trọng vì tính năng khử răng cưa, đổ bóng và nén có thể làm cho các pixel lân cận hơi khác so với màu bạn mong đợi. + Mở Chọn màu từ hình ảnh và chọn hình ảnh nguồn có màu bạn cần. + Phóng to trước khi lấy mẫu các chi tiết nhỏ để các pixel lân cận không ảnh hưởng đến kết quả. + Sao chép màu ở định dạng mà điểm đến của bạn yêu cầu, chẳng hạn như HEX, RGB hoặc HSV. + Tạo bảng màu và sử dụng thư viện màu + Trích xuất các bảng màu, duyệt các màu đã lưu và giữ cho hệ thống hình ảnh nhất quán + Xây dựng một bảng màu có thể tái sử dụng + Bảng màu giúp giữ cho đồ họa, hình mờ và bản vẽ được xuất nhất quán về mặt trực quan. Chúng đặc biệt hữu ích khi bạn liên tục chú thích ảnh chụp màn hình, chuẩn bị nội dung thương hiệu hoặc cần các màu giống nhau trên một số công cụ. + Tạo bảng màu từ hình ảnh hoặc thêm màu theo cách thủ công khi bạn đã biết các giá trị. + Đặt tên hoặc sắp xếp các màu quan trọng để bạn có thể tìm lại chúng sau này. + Sử dụng các giá trị được sao chép trong Draw, hình mờ, gradient, SVG hoặc các công cụ thiết kế bên ngoài. + Tạo độ dốc + Xây dựng hình ảnh chuyển màu cho nền, phần giữ chỗ và thử nghiệm trực quan + Kiểm soát quá trình chuyển đổi màu sắc + Công cụ chuyển màu rất hữu ích khi bạn cần một hình ảnh được tạo thay vì chỉnh sửa hình ảnh hiện có. Chúng có thể trở thành hình nền sạch, phần giữ chỗ, hình nền, mặt nạ hoặc nội dung đơn giản cho các công cụ thiết kế bên ngoài. + Chọn loại gradient và đặt các màu quan trọng trước tiên. + Điều chỉnh hướng, điểm dừng, độ mượt và kích thước đầu ra cho trường hợp sử dụng mục tiêu. + Xuất ở định dạng phù hợp với đích đến, thường là PNG để có đồ họa được tạo rõ ràng. + Khả năng tiếp cận màu sắc + Sử dụng các tùy chọn màu sắc, độ tương phản và mù màu động khi giao diện người dùng khó đọc + Làm cho màu sắc dễ sử dụng hơn + Cài đặt màu ảnh hưởng đến sự thoải mái, khả năng đọc và tốc độ bạn có thể quét các điều khiển. Nếu cảm thấy khó phân biệt các khối công cụ, thanh trượt hoặc trạng thái đã chọn thì các tùy chọn chủ đề và khả năng truy cập có thể hữu ích hơn là chỉ thay đổi chế độ tối. + Hãy thử màu sắc động khi bạn muốn ứng dụng tuân theo bảng màu hình nền hiện tại. + Tăng độ tương phản hoặc thay đổi sơ đồ khi các nút và bề mặt không đủ nổi bật. + Sử dụng các tùy chọn lược đồ mù màu nếu khó phân biệt một số trạng thái hoặc điểm nhấn. + Nền alpha + Kiểm soát bản xem trước hoặc màu tô được sử dụng xung quanh PNG, WebP trong suốt và các đầu ra tương tự + Hiểu bản xem trước minh bạch + Màu nền cho định dạng alpha có thể giúp kiểm tra hình ảnh trong suốt dễ dàng hơn nhưng điều quan trọng là phải biết liệu bạn đang thay đổi hành vi xem trước/điền hay làm phẳng kết quả cuối cùng. Điều này quan trọng đối với nhãn dán, biểu trưng và hình ảnh bị xóa nền. + Trước tiên, hãy sử dụng định dạng hỗ trợ alpha, chẳng hạn như PNG hoặc WebP, khi các pixel trong suốt phải tồn tại khi xuất. + Điều chỉnh cài đặt màu nền khi khó nhìn thấy các cạnh trong suốt trên bề mặt ứng dụng. + Xuất một thử nghiệm nhỏ và mở lại thử nghiệm đó trong một ứng dụng khác để xác nhận xem độ trong suốt hoặc phần tô đã chọn đã được lưu hay chưa. + Lựa chọn mô hình AI + Chọn mô hình theo loại hình ảnh và lỗi thay vì chọn mô hình lớn nhất trước + Ghép mô hình với thiệt hại + Công cụ AI có thể tải xuống hoặc nhập các mô hình ONNX/ORT khác nhau và mỗi mô hình được đào tạo cho một loại vấn đề cụ thể. Mô hình dành cho khối JPEG, dọn dẹp dòng anime, khử nhiễu, xóa nền, tô màu hoặc nâng cấp có thể tạo ra kết quả tồi tệ hơn khi sử dụng trên loại hình ảnh sai. + Đọc mô tả mô hình trước khi tải xuống; chọn mô hình đặt tên cho vấn đề thực tế của bạn, chẳng hạn như nén, nhiễu, bán sắc, mờ hoặc xóa nền. + Bắt đầu với mô hình nhỏ hơn hoặc cụ thể hơn khi thử nghiệm loại hình ảnh mới, sau đó chỉ so sánh với các mô hình nặng hơn nếu kết quả không đủ tốt. + Chỉ giữ lại những mẫu bạn thực sự sử dụng vì các mẫu đã tải xuống và nhập có thể chiếm dung lượng lưu trữ đáng kể. + Thấu hiểu gia đình kiểu mẫu + Tên mô hình thường gợi ý về mục tiêu của chúng: mô hình kiểu RealESRGAN nâng cấp, mô hình SCUNet và FBCNN loại bỏ tiếng ồn hoặc nén, mô hình nền tạo mặt nạ và bộ tạo màu thêm màu vào đầu vào thang độ xám. Các mô hình tùy chỉnh đã nhập có thể mạnh mẽ nhưng chúng phải phù hợp với hình dạng đầu vào/đầu ra dự kiến ​​và sử dụng định dạng ONNX hoặc ORT được hỗ trợ. + Sử dụng bộ nâng cấp khi bạn cần nhiều pixel hơn, bộ khử nhiễu khi hình ảnh bị nhiễu hạt và bộ loại bỏ giả tạo khi vấn đề chính là khối nén hoặc tạo dải. + Chỉ sử dụng mô hình nền để tách chủ thể; chúng không giống như việc loại bỏ đối tượng hoặc nâng cao hình ảnh nói chung. + Chỉ nhập các mô hình tùy chỉnh từ các nguồn bạn tin cậy và kiểm tra chúng trên một hình ảnh nhỏ trước khi sử dụng các tệp quan trọng. + thông số AI + Điều chỉnh kích thước khối, chồng chéo và xử lý song song để cân bằng chất lượng, tốc độ và bộ nhớ + Cân bằng tốc độ với sự ổn định + Kích thước khối kiểm soát kích thước của các phần hình ảnh trước khi chúng được mô hình xử lý. Chồng chéo pha trộn các khối lân cận để ẩn đường viền. Trình xử lý song song có thể tăng tốc độ xử lý nhưng mỗi trình xử lý đều cần bộ nhớ, vì vậy giá trị cao có thể khiến hình ảnh lớn không ổn định trên điện thoại có RAM hạn chế. + Sử dụng các khối lớn hơn để có bối cảnh mượt mà hơn khi thiết bị của bạn xử lý mô hình một cách đáng tin cậy và hình ảnh không lớn. + Tăng sự chồng chéo khi các đường viền của đoạn được hiển thị, nhưng hãy nhớ rằng chồng chéo nhiều hơn có nghĩa là công việc lặp lại nhiều hơn. + Chỉ tuyển công nhân song song sau khi kiểm tra ổn định; nếu quá trình xử lý chậm lại, làm nóng thiết bị hoặc gặp sự cố, trước tiên hãy giảm công nhân. + Tránh tạo tác chunk + Chunking cho phép ứng dụng xử lý các hình ảnh lớn hơn mức mà mô hình có thể xử lý một cách thoải mái cùng một lúc. Sự cân bằng là mỗi ô có ít bối cảnh xung quanh hơn, do đó, độ chồng lấp thấp hoặc các phần quá nhỏ có thể tạo ra các đường viền hiển thị, thay đổi kết cấu hoặc chi tiết không nhất quán giữa các khu vực lân cận. + Tăng sự chồng chéo nếu bạn thấy đường viền hình chữ nhật, thay đổi kết cấu lặp đi lặp lại hoặc nhảy chi tiết giữa các vùng được xử lý. + Giảm kích thước chunk nếu quá trình này không thành công trước khi tạo đầu ra, đặc biệt là trên các hình ảnh lớn hoặc mô hình nặng. + Lưu lại một thử nghiệm crop nhỏ trước khi xử lý toàn bộ ảnh để bạn có thể so sánh các thông số một cách nhanh chóng. + Tính ổn định của AI + Kích thước khối, trình chạy và độ phân giải nguồn thấp hơn khi quá trình xử lý AI gặp sự cố hoặc bị treo + Phục hồi từ các công việc AI nặng + Xử lý AI là một trong những quy trình công việc nặng nhất trong ứng dụng. Sự cố, phiên không thành công, treo hoặc khởi động lại ứng dụng đột ngột thường có nghĩa là kiểu máy, độ phân giải nguồn, kích thước khối hoặc số lượng nhân viên song song quá khắt khe đối với trạng thái thiết bị hiện tại. + Nếu ứng dụng gặp sự cố hoặc không mở được phiên mô hình, hãy giảm trình chạy song song và kích thước khối, sau đó thử lại cùng một hình ảnh. + Thay đổi kích thước hình ảnh rất lớn trước khi xử lý AI khi mục tiêu không yêu cầu độ phân giải gốc. + Đóng các ứng dụng nặng khác, sử dụng kiểu máy nhỏ hơn hoặc xử lý ít tệp hơn cùng lúc khi áp lực bộ nhớ tiếp tục quay trở lại. + Chẩn đoán lỗi mô hình + Việc chạy AI thất bại không phải lúc nào cũng có nghĩa là hình ảnh xấu. Tệp mô hình có thể không đầy đủ, không được hỗ trợ, quá lớn cho bộ nhớ hoặc không khớp với thao tác. Khi ứng dụng báo cáo phiên không thành công, trước tiên hãy coi đó là tín hiệu để đơn giản hóa mô hình và các tham số. + Xóa và tải lại một mô hình nếu nó bị lỗi ngay sau khi tải xuống hoặc hoạt động như thể tệp bị hỏng. + Hãy thử mô hình có thể tải xuống được tích hợp sẵn trước khi đổ lỗi cho mô hình tùy chỉnh đã nhập vì các mô hình đã nhập có thể có đầu vào không tương thích. + Sử dụng Nhật ký ứng dụng sau khi tái tạo lỗi nếu bạn cần báo cáo sự cố hoặc lỗi phiên. + Thử nghiệm trong Shader Studio + Sử dụng hiệu ứng đổ bóng GPU để xử lý hình ảnh nâng cao và giao diện tùy chỉnh + Làm việc cẩn thận với shader + Shader Studio rất mạnh mẽ nhưng mã và các tham số của shader cần những thay đổi nhỏ, có thể kiểm tra được. Hãy coi nó như một công cụ thử nghiệm: giữ đường cơ sở hoạt động, thay đổi từng thứ một và thử nghiệm với các kích thước hình ảnh khác nhau trước khi tin tưởng vào giá trị đặt trước. + Bắt đầu từ một trình đổ bóng đang hoạt động đã biết hoặc một hiệu ứng đơn giản trước khi thêm tham số. + Thay đổi một tham số hoặc khối mã mỗi lần và kiểm tra lỗi xem trước. + Chỉ xuất các cài đặt trước sau khi thử nghiệm chúng trên một số kích thước hình ảnh khác nhau. + Tạo nghệ thuật SVG hoặc ASCII + Tạo nội dung giống vector hoặc hình ảnh dựa trên văn bản cho sản phẩm sáng tạo + Chọn loại đầu ra quảng cáo + Các công cụ SVG và ASCII phục vụ các mục tiêu khác nhau: đồ họa có thể mở rộng hoặc hiển thị kiểu văn bản. Cả hai đều là những chuyển đổi sáng tạo, vì vậy việc xem trước ở kích thước cuối cùng quan trọng hơn việc đánh giá kết quả từ một hình thu nhỏ nhỏ. + Sử dụng SVG Maker khi kết quả cần được chia tỷ lệ rõ ràng hoặc được sử dụng lại trong quy trình thiết kế. + Sử dụng ASCII Art khi bạn muốn thể hiện văn bản cách điệu của một hình ảnh. + Xem trước ở kích thước cuối cùng vì các chi tiết nhỏ có thể biến mất trong đầu ra được tạo. + Tạo kết cấu tiếng ồn + Tạo họa tiết theo quy trình cho nền, mặt nạ, lớp phủ hoặc thử nghiệm + Điều chỉnh đầu ra thủ tục + Việc tạo nhiễu tạo ra hình ảnh mới từ các thông số, do đó những thay đổi nhỏ có thể tạo ra kết quả rất khác nhau. Nó rất hữu ích cho kết cấu, mặt nạ, lớp phủ, phần giữ chỗ hoặc thử nghiệm nén với các mẫu chi tiết. + Chọn loại nhiễu và kích thước đầu ra trước khi tinh chỉnh các chi tiết. + Điều chỉnh tỷ lệ, cường độ, màu sắc hoặc hạt giống cho đến khi mẫu phù hợp với mục đích sử dụng. + Lưu kết quả hữu ích và ghi lại các tham số nếu bạn cần tạo lại kết cấu sau này. + Dự án đánh dấu + Sử dụng tệp dự án khi chú thích cần được chỉnh sửa sau khi đóng ứng dụng + Giữ các chỉnh sửa theo lớp có thể tái sử dụng + Tệp dự án đánh dấu rất hữu ích khi ảnh chụp màn hình, sơ đồ hoặc hình ảnh hướng dẫn có thể cần thay đổi sau này. Các tệp PNG/JPEG đã xuất là hình ảnh cuối cùng, trong khi các tệp dự án giữ nguyên các lớp có thể chỉnh sửa và trạng thái chỉnh sửa để điều chỉnh trong tương lai. + Sử dụng Lớp đánh dấu khi chú thích, chú thích hoặc thành phần bố cục vẫn có thể chỉnh sửa được. + Lưu hoặc mở lại tệp dự án trước khi xuất hình ảnh cuối cùng nếu bạn muốn chỉnh sửa thêm. + Chỉ xuất hình ảnh bình thường khi bạn sẵn sàng chia sẻ phiên bản cuối cùng đã được làm phẳng. + Mã hóa tập tin + Bảo vệ tệp bằng mật khẩu và kiểm tra quá trình giải mã trước khi xóa bất kỳ bản sao nguồn nào + Xử lý đầu ra được mã hóa một cách cẩn thận + Các công cụ mật mã có thể bảo vệ các tệp bằng mã hóa dựa trên mật khẩu nhưng chúng không thể thay thế cho việc ghi nhớ khóa hoặc giữ các bản sao lưu. Mã hóa rất hữu ích cho việc vận chuyển và lưu trữ, trong khi ZIP tốt hơn khi mục tiêu chính là gộp một số đầu ra. + Trước tiên hãy mã hóa một bản sao và giữ lại bản gốc cho đến khi bạn kiểm tra rằng quá trình giải mã có hiệu quả với mật khẩu bạn đã lưu trữ hay không. + Sử dụng trình quản lý mật khẩu hoặc nơi an toàn khác để lấy chìa khóa; ứng dụng không thể đoán mật khẩu bị mất cho bạn. + Chỉ chia sẻ các tệp được mã hóa với những người cũng có mật khẩu và biết công cụ hoặc phương pháp nào sẽ giải mã chúng. + Sắp xếp dụng cụ + Các công cụ nhóm, sắp xếp, tìm kiếm và ghim để màn hình chính phù hợp với quy trình làm việc thực tế của bạn + Làm cho màn hình chính nhanh hơn + Cài đặt sắp xếp công cụ đáng được điều chỉnh khi bạn biết mình sử dụng công cụ nào nhiều nhất. Việc nhóm giúp khám phá, thứ tự tùy chỉnh giúp trí nhớ cơ bắp và các mục yêu thích giúp bạn có thể truy cập các công cụ hàng ngày ngay cả khi danh sách đầy đủ trở nên lớn. + Sử dụng chế độ nhóm trong khi tìm hiểu ứng dụng hoặc khi bạn thích các công cụ được sắp xếp theo loại nhiệm vụ. + Chuyển sang thứ tự tùy chỉnh khi bạn đã biết các công cụ thường dùng của mình và muốn có ít cuộn hơn. + Sử dụng cùng lúc cài đặt tìm kiếm và mục yêu thích cho các công cụ hiếm mà bạn nhớ theo tên nhưng không muốn lúc nào cũng hiển thị. + Xử lý các tập tin lớn + Tránh xuất chậm, áp lực bộ nhớ và sản lượng lớn bất ngờ + Giảm công việc trước khi xuất khẩu + Hình ảnh rất lớn, lô dài và định dạng chất lượng cao có thể chiếm nhiều bộ nhớ và thời gian hơn. Cách khắc phục nhanh nhất thường là giảm khối lượng công việc trước khi thực hiện thao tác nặng nhất, không phải đợi đầu vào quá khổ tương tự kết thúc. + Thay đổi kích thước hình ảnh lớn trước khi áp dụng các bộ lọc nặng, chuyển đổi OCR hoặc PDF. + Trước tiên hãy sử dụng lô nhỏ hơn để xác nhận cài đặt, sau đó xử lý phần còn lại với cùng cài đặt trước. + Chất lượng thấp hơn hoặc thay đổi định dạng khi tệp đầu ra lớn hơn mong đợi. + Bộ nhớ đệm và bản xem trước + Hiểu các bản xem trước được tạo, dọn dẹp bộ đệm và sử dụng bộ nhớ sau các phiên nặng + Giữ cân bằng lưu trữ và tốc độ + Việc tạo bản xem trước và bộ đệm có thể giúp việc duyệt lặp lại nhanh hơn nhưng các phiên chỉnh sửa nặng có thể để lại dữ liệu tạm thời. Cài đặt bộ nhớ đệm sẽ hữu ích khi ứng dụng có cảm giác chậm, bộ nhớ bị chật hẹp hoặc các bản xem trước trông cũ kỹ sau nhiều lần thử nghiệm. + Luôn bật chế độ xem trước khi duyệt nhiều hình ảnh quan trọng hơn việc giảm thiểu bộ nhớ tạm thời. + Xóa bộ nhớ đệm sau đợt lớn, thử nghiệm thất bại hoặc phiên dài với nhiều tệp có độ phân giải cao. + Sử dụng tính năng xóa bộ nhớ đệm tự động nếu bạn muốn dọn dẹp bộ nhớ hơn là giữ ấm các bản xem trước giữa các lần khởi chạy. + Điều chỉnh hành vi màn hình + Sử dụng các tùy chọn toàn màn hình, chế độ bảo mật, độ sáng và thanh hệ thống để tập trung làm việc + Làm cho giao diện phù hợp với tình huống + Cài đặt màn hình sẽ hữu ích khi bạn chỉnh sửa theo chiều ngang, hiển thị hình ảnh riêng tư hoặc cần độ sáng nhất quán. Chúng không bắt buộc phải sử dụng thông thường nhưng chúng giải quyết các tình huống khó xử như kiểm tra QR, xem trước riêng tư hoặc chỉnh sửa theo chiều ngang chật chội. + Sử dụng cài đặt toàn màn hình hoặc thanh hệ thống khi không gian chỉnh sửa quan trọng hơn chrome điều hướng. + Sử dụng chế độ bảo mật khi hình ảnh riêng tư không xuất hiện trong ảnh chụp màn hình hoặc bản xem trước ứng dụng. + Sử dụng biện pháp thực thi độ sáng cho những công cụ có hình ảnh tối hoặc mã QR khó kiểm tra. + Giữ tính minh bạch + Ngăn nền trong suốt chuyển sang màu trắng, đen hoặc phẳng + Sử dụng đầu ra nhận biết alpha + Tính minh bạch chỉ tồn tại khi công cụ đã chọn và định dạng đầu ra hỗ trợ alpha. Nếu nền được xuất chuyển sang màu trắng hoặc đen thì vấn đề thường là ở định dạng đầu ra, cài đặt nền/nền hoặc ứng dụng đích đã làm phẳng hình ảnh. + Chọn PNG hoặc WebP khi xuất hình ảnh, nhãn dán, biểu trưng hoặc lớp phủ đã bị xóa nền. + Tránh JPEG để có đầu ra trong suốt vì nó không lưu trữ kênh alpha. + Kiểm tra cài đặt liên quan đến màu nền cho định dạng alpha nếu bản xem trước hiển thị nền đầy. + Khắc phục sự cố chia sẻ và nhập + Sử dụng cài đặt bộ chọn và các định dạng được hỗ trợ khi tệp không xuất hiện hoặc mở chính xác + Đưa tập tin vào ứng dụng + Sự cố nhập thường do quyền của nhà cung cấp, định dạng không được hỗ trợ hoặc hành vi của bộ chọn gây ra. Các tệp được chia sẻ từ ứng dụng đám mây và trình nhắn tin có thể là tạm thời, do đó, việc chọn một nguồn ổn định có thể đáng tin cậy hơn để chỉnh sửa lâu hơn. + Hãy thử mở tệp thông qua bộ chọn hệ thống thay vì lối tắt tệp gần đây. + Nếu một công cụ không hỗ trợ định dạng, trước tiên hãy chuyển đổi định dạng đó hoặc mở một công cụ cụ thể hơn. + Xem lại cài đặt bộ chọn hình ảnh và trình khởi chạy nếu luồng chia sẻ hoặc mở công cụ trực tiếp hoạt động khác với mong đợi. + Xác nhận thoát + Tránh mất các chỉnh sửa chưa được lưu khi cử chỉ, thao tác nhấn quay lại hoặc chuyển đổi công cụ vô tình xảy ra + Bảo vệ công việc còn dang dở + Xác nhận thoát công cụ rất hữu ích khi bạn sử dụng điều hướng bằng cử chỉ, chỉnh sửa các tệp lớn hoặc thường xuyên chuyển đổi giữa các ứng dụng. Nó thêm một khoảng dừng nhỏ trước khi rời khỏi công cụ để các cài đặt và bản xem trước chưa hoàn thành không bị mất do vô tình. + Bật xác nhận nếu cử chỉ quay lại vô tình đã từng đóng một công cụ trước khi bạn xuất. + Hãy tắt tính năng này nếu bạn chủ yếu thực hiện chuyển đổi nhanh một bước và muốn điều hướng nhanh hơn. + Sử dụng nó cùng với các cài đặt trước để có quy trình làm việc dài hơn, nơi việc xây dựng lại cài đặt sẽ gây khó chịu. + Sử dụng nhật ký khi báo cáo sự cố + Thu thập thông tin chi tiết hữu ích trước khi mở một vấn đề hoặc liên hệ với nhà phát triển + Báo cáo vấn đề về ngữ cảnh + Một báo cáo rõ ràng với các bước, phiên bản ứng dụng, loại đầu vào và nhật ký sẽ dễ chẩn đoán hơn nhiều. Nhật ký hữu ích nhất ngay sau khi tái tạo một vấn đề vì chúng giữ cho bối cảnh xung quanh luôn mới mẻ. + Lưu ý tên công cụ, hành động chính xác và liệu nó xảy ra với một tệp hay mọi tệp. + Mở Nhật ký ứng dụng sau khi tái tạo sự cố và chia sẻ đầu ra nhật ký liên quan khi có thể. + Chỉ đính kèm ảnh chụp màn hình hoặc tệp mẫu khi chúng không chứa thông tin cá nhân. + Quá trình xử lý nền đã bị dừng + Android đã không cho phép thông báo xử lý nền bắt đầu kịp thời. Đây là giới hạn về thời gian của hệ thống và không thể khắc phục được một cách đáng tin cậy. Khởi động lại ứng dụng và thử lại; việc giữ ứng dụng luôn mở và cho phép thông báo hoặc hoạt động ở chế độ nền có thể hữu ích. + Bỏ qua việc chọn tập tin + Mở công cụ nhanh hơn khi dữ liệu nhập thường đến từ chia sẻ, bảng nhớ tạm hoặc màn hình trước đó + Làm cho công cụ lặp lại khởi chạy nhanh hơn + Bỏ qua Chọn tệp thay đổi bước đầu tiên của các công cụ được hỗ trợ. Điều này rất hữu ích khi bạn thường nhập một công cụ có hình ảnh đã được chọn hoặc từ các hành động chia sẻ trên Android, nhưng bạn có thể cảm thấy khó hiểu nếu mong đợi mọi công cụ đều yêu cầu tệp ngay lập tức. + Chỉ kích hoạt nó khi quy trình thông thường của bạn đã cung cấp hình ảnh nguồn trước khi công cụ mở ra. + Hãy tắt tính năng này trong khi tìm hiểu ứng dụng vì việc chọn rõ ràng giúp mọi luồng công cụ trở nên dễ hiểu hơn. + Nếu một công cụ mở ra mà không có thông tin đầu vào rõ ràng, hãy tắt cài đặt này hoặc bắt đầu từ Chia sẻ/Mở bằng để Android chuyển tệp trước. + Mở trình chỉnh sửa trước + Bỏ qua bản xem trước khi hình ảnh được chia sẻ sẽ được chuyển thẳng vào chỉnh sửa + Chọn xem trước hoặc chỉnh sửa làm điểm dừng đầu tiên + Mở Chỉnh sửa Thay vì Xem trước dành cho người dùng đã biết họ muốn sửa đổi hình ảnh. Xem trước sẽ an toàn hơn khi bạn kiểm tra tệp từ cuộc trò chuyện hoặc bộ lưu trữ đám mây; chỉnh sửa trực tiếp nhanh hơn đối với ảnh chụp màn hình, cắt nhanh, chú thích và chỉnh sửa một lần. + Bật chỉnh sửa trực tiếp nếu hầu hết các hình ảnh được chia sẻ ngay lập tức cần cắt, đánh dấu, bộ lọc hoặc xuất các thay đổi. + Để lại bản xem trước trước khi bạn thường xuyên kiểm tra siêu dữ liệu, kích thước, độ trong suốt hoặc chất lượng hình ảnh trước khi quyết định. + Sử dụng tính năng này cùng với các mục yêu thích để hình ảnh được chia sẻ gần với các công cụ bạn thực sự sử dụng. + Mục nhập văn bản đặt trước + Nhập các giá trị đặt trước chính xác thay vì điều chỉnh các điều khiển nhiều lần + Sử dụng các giá trị chính xác khi thanh trượt chậm + Mục nhập văn bản đặt sẵn sẽ hữu ích khi bạn cần các con số chính xác cho công việc lặp đi lặp lại: kích thước hình ảnh xã hội, lề tài liệu, mục tiêu nén, độ rộng dòng hoặc các giá trị khác gây khó chịu khi tiếp cận bằng cử chỉ. Nó cũng hữu ích trên màn hình nhỏ nơi việc di chuyển thanh trượt tinh tế khó hơn. + Bật nhập văn bản nếu bạn thường sử dụng lại kích thước, tỷ lệ phần trăm, giá trị chất lượng hoặc tham số công cụ chính xác. + Lưu giá trị làm giá trị đặt trước sau khi nhập một lần, sau đó sử dụng lại thay vì xây dựng lại thiết lập tương tự. + Giữ các điều khiển bằng cử chỉ để điều chỉnh hình ảnh thô và nhập các giá trị cho các yêu cầu đầu ra nghiêm ngặt. + Lưu gần bản gốc + Đặt các tệp được tạo bên cạnh hình ảnh nguồn khi bối cảnh thư mục quan trọng + Giữ kết quả đầu ra với nguồn của họ + Lưu vào thư mục gốc rất tiện lợi cho các thư mục máy ảnh, thư mục dự án, bản quét tài liệu và lô khách hàng trong đó bản sao đã chỉnh sửa phải ở bên cạnh nguồn. Nó có thể kém gọn gàng hơn một thư mục đầu ra chuyên dụng, vì vậy hãy kết hợp nó với các hậu tố hoặc mẫu tên tệp rõ ràng. + Kích hoạt nó khi mỗi thư mục nguồn đã có ý nghĩa và các đầu ra sẽ vẫn được nhóm ở đó. + Sử dụng hậu tố tên tệp, tiền tố, dấu thời gian hoặc mẫu để các bản sao đã chỉnh sửa dễ dàng tách biệt khỏi bản gốc. + Vô hiệu hóa nó cho các lô hỗn hợp khi bạn muốn tất cả các tệp được tạo được thu thập trong một thư mục xuất. + Bỏ qua đầu ra lớn hơn + Tránh lưu các chuyển đổi nặng hơn nguồn một cách bất ngờ + Bảo vệ lô khỏi những bất ngờ về kích thước xấu + Một số chuyển đổi làm cho tệp lớn hơn, đặc biệt là ảnh chụp màn hình PNG, ảnh đã được nén, WebP chất lượng cao hoặc hình ảnh được giữ nguyên siêu dữ liệu. Cho phép bỏ qua nếu lớn hơn ngăn những đầu ra đó thay thế nguồn nhỏ hơn trong quy trình làm việc mà mục tiêu chính là giảm kích thước. + Kích hoạt tính năng này khi xuất nhằm mục đích nén, thay đổi kích thước hoặc chuẩn bị tệp cho giới hạn tải lên. + Xem lại các tập tin bị bỏ qua sau một đợt; chúng có thể đã được tối ưu hóa hoặc có thể cần một định dạng khác. + Tắt nó khi chất lượng, độ trong suốt hoặc khả năng tương thích định dạng quan trọng hơn kích thước tệp cuối cùng. + Clipboard và liên kết + Kiểm soát việc xem trước liên kết và nhập bảng nhớ tạm tự động để có các công cụ dựa trên văn bản nhanh hơn + Làm cho đầu vào tự động có thể dự đoán được + Cài đặt bảng tạm và liên kết thuận tiện cho các quy trình làm việc có nhiều văn bản và QR, Base64, nhưng việc nhập tự động phải có chủ ý. Nếu ứng dụng dường như tự điền vào các trường, hãy kiểm tra hành vi dán vào bảng nhớ tạm; nếu thẻ liên kết cảm thấy ồn ào hoặc chậm, hãy điều chỉnh hành vi xem trước liên kết. + Cho phép dán clipboard tự động khi bạn thường xuyên sao chép văn bản và mở ngay công cụ so khớp. + Tắt tính năng dán tự động nếu nội dung clipboard riêng tư xuất hiện trong các công cụ mà bạn không mong đợi. + Tắt tính năng xem trước liên kết khi quá trình tìm nạp bản xem trước chậm, gây mất tập trung hoặc không cần thiết cho quy trình làm việc của bạn. + Điều khiển nhỏ gọn + Sử dụng bộ chọn dày đặc hơn và vị trí cài đặt nhanh khi không gian màn hình chật hẹp + Phù hợp với nhiều điều khiển hơn trên màn hình nhỏ + Bộ chọn nhỏ gọn và vị trí cài đặt nhanh trợ giúp trên điện thoại nhỏ, chỉnh sửa phong cảnh và các công cụ có nhiều tham số. Sự cân bằng là các điều khiển có thể có cảm giác dày đặc hơn, vì vậy điều này là tốt nhất sau khi bạn đã nhận ra các tùy chọn phổ biến. + Bật bộ chọn thu gọn khi hộp thoại hoặc danh sách tùy chọn có cảm giác quá cao so với màn hình. + Điều chỉnh phía cài đặt nhanh nếu các điều khiển công cụ dễ dàng tiếp cận hơn từ một phía của màn hình. + Quay lại các điều khiển rộng rãi hơn nếu bạn vẫn đang tìm hiểu ứng dụng hoặc thường xuyên nhấn nhầm vào các tùy chọn dày đặc. + Tải hình ảnh từ web + Dán URL trang hoặc hình ảnh, xem trước hình ảnh được phân tích cú pháp, sau đó lưu hoặc chia sẻ kết quả đã chọn + Sử dụng hình ảnh web có chủ ý + Tải hình ảnh trên web phân tích các nguồn hình ảnh từ URL được cung cấp, xem trước hình ảnh có sẵn đầu tiên và cho phép bạn lưu hoặc chia sẻ các hình ảnh được phân tích cú pháp đã chọn. Một số trang web sử dụng các liên kết tạm thời, được bảo vệ hoặc do tập lệnh tạo nên một trang hoạt động trong trình duyệt có thể vẫn không trả về hình ảnh nào có thể sử dụng được. + Dán URL hình ảnh trực tiếp hoặc URL trang và đợi danh sách hình ảnh được phân tích cú pháp xuất hiện. + Sử dụng các điều khiển khung hoặc lựa chọn khi trang chứa nhiều hình ảnh và bạn chỉ cần một số hình ảnh trong số đó. + Nếu không tải được gì, trước tiên hãy thử liên kết hình ảnh trực tiếp hoặc tải xuống qua trình duyệt; trang web có thể chặn phân tích cú pháp hoặc sử dụng các liên kết riêng tư. + Chọn loại thay đổi kích thước + Hiểu rõ ràng, linh hoạt, cắt xén và vừa vặn trước khi buộc hình ảnh sang các kích thước mới + Kiểm soát hình dạng, khung vẽ và tỷ lệ khung hình + Loại thay đổi kích thước quyết định điều gì sẽ xảy ra khi chiều rộng và chiều cao được yêu cầu không khớp với tỷ lệ khung hình gốc. Đó là sự khác biệt giữa việc kéo dài các pixel, giữ nguyên tỷ lệ, cắt bớt diện tích hoặc thêm khung nền. + Chỉ sử dụng Rõ ràng khi độ biến dạng có thể chấp nhận được hoặc nguồn đã có cùng tỷ lệ khung hình với mục tiêu. + Sử dụng Linh hoạt để giữ tỷ lệ bằng cách thay đổi kích thước từ phía quan trọng, đặc biệt đối với ảnh và lô hỗn hợp. + Sử dụng Cắt xén cho các khung nghiêm ngặt không có viền hoặc Vừa vặn khi toàn bộ hình ảnh phải hiển thị bên trong khung vẽ cố định. + Chọn chế độ tỷ lệ hình ảnh + Chọn bộ lọc lấy mẫu lại để kiểm soát độ sắc nét, độ mịn, tốc độ và các tạo phẩm cạnh + Thay đổi kích thước mà không vô tình làm mềm + Chế độ tỷ lệ hình ảnh kiểm soát cách tính các pixel mới trong khi thay đổi kích thước. Các chế độ đơn giản nhanh và có thể dự đoán được, trong khi các bộ lọc nâng cao có thể bảo toàn chi tiết tốt hơn nhưng có thể làm sắc nét các cạnh, tạo quầng sáng hoặc mất nhiều thời gian hơn trên hình ảnh lớn. + Sử dụng Cơ bản hoặc Song tuyến để thay đổi kích thước nhanh chóng hàng ngày khi kết quả chỉ cần nhỏ hơn và gọn gàng hơn. + Sử dụng các chế độ kiểu Lanczos, Robidoux, Spline hoặc EWA khi cần thu nhỏ chi tiết, văn bản hoặc thu nhỏ chất lượng cao. + Sử dụng Gần nhất cho ảnh nghệ thuật pixel, biểu tượng và đồ họa có viền cứng trong đó các pixel bị mờ sẽ làm hỏng giao diện. + Không gian màu tỷ lệ + Quyết định cách pha trộn màu sắc trong khi các pixel được lấy mẫu lại trong quá trình thay đổi kích thước + Giữ độ dốc và các cạnh tự nhiên + Không gian màu tỷ lệ thay đổi phép toán được sử dụng khi thay đổi kích thước. Hầu hết các hình ảnh đều ổn với chế độ mặc định, nhưng không gian tuyến tính hoặc cảm nhận có thể làm cho độ chuyển màu, cạnh tối và màu bão hòa trông rõ ràng hơn sau khi chia tỷ lệ mạnh. + Để mặc định khi tốc độ và khả năng tương thích quan trọng hơn sự khác biệt nhỏ về màu sắc. + Hãy thử các chế độ Tuyến tính hoặc cảm quan khi đường viền tối, ảnh chụp màn hình giao diện người dùng, dải màu chuyển màu hoặc dải màu trông sai sau khi thay đổi kích thước. + So sánh trước và sau trên màn hình đích thực, vì những thay đổi về không gian màu có thể rất tinh tế và phụ thuộc vào nội dung. + Giới hạn chế độ thay đổi kích thước + Biết khi nào hình ảnh vượt quá giới hạn nên được bỏ qua, mã hóa lại hoặc thu nhỏ lại để phù hợp + Chọn điều gì xảy ra ở giới hạn + Limit Resize không chỉ là một công cụ thu nhỏ hình ảnh. Chế độ của nó quyết định ứng dụng sẽ làm gì khi nguồn đã nằm trong giới hạn của bạn hoặc khi chỉ một bên vi phạm quy tắc, điều này rất quan trọng đối với việc chuẩn bị tải lên và thư mục hỗn hợp. + Sử dụng Bỏ qua khi hình ảnh nằm trong giới hạn sẽ được giữ nguyên và không được xuất lại. + Sử dụng Recode khi kích thước có thể chấp nhận được nhưng bạn vẫn cần định dạng, hành vi siêu dữ liệu, chất lượng hoặc tên tệp mới. + Sử dụng Zoom khi hình ảnh vượt quá giới hạn cần được thu nhỏ lại theo tỷ lệ cho đến khi vừa với ô cho phép. + Mục tiêu tỷ lệ khung hình + Tránh các mặt bị kéo dài, các cạnh bị cắt và các đường viền không mong muốn ở kích thước đầu ra nghiêm ngặt + Khớp hình dạng trước khi thay đổi kích thước + Chiều rộng và chiều cao chỉ bằng một nửa mục tiêu thay đổi kích thước. Tỷ lệ khung hình quyết định xem hình ảnh có thể vừa khít một cách tự nhiên, cần cắt xén hay cần có khung vẽ xung quanh nó. Việc kiểm tra hình dạng trước tiên sẽ ngăn cản hầu hết các kết quả thay đổi kích thước đáng ngạc nhiên. + So sánh hình dạng nguồn với hình dạng đích trước khi chọn Rõ ràng, Cắt hoặc Vừa. + Cắt trước khi chủ thể có thể mất thêm hậu cảnh và đích đến cần có khung hình nghiêm ngặt. + Sử dụng Fit hoặc Flex khi mọi phần của ảnh gốc vẫn phải hiển thị. + Thay đổi kích thước cao cấp hoặc bình thường + Biết khi nào việc thêm pixel cần AI và khi nào việc chia tỷ lệ đơn giản là đủ + Đừng nhầm lẫn kích thước với chi tiết + Thay đổi kích thước thông thường sẽ thay đổi kích thước bằng cách nội suy các pixel; nó không thể phát minh ra chi tiết thực sự. Tính năng nâng cấp AI có thể tạo ra các chi tiết trông sắc nét hơn nhưng chậm hơn và có thể tạo ra kết cấu ảo giác, vì vậy lựa chọn phù hợp tùy thuộc vào việc bạn cần khả năng tương thích, tốc độ hay khôi phục hình ảnh. + Sử dụng thay đổi kích thước thông thường cho hình thu nhỏ, giới hạn tải lên, ảnh chụp màn hình và các thay đổi kích thước đơn giản. + Sử dụng tính năng nâng cấp AI khi hình ảnh nhỏ hoặc mềm mại cần trông đẹp hơn ở kích thước lớn hơn. + So sánh khuôn mặt, văn bản và mẫu sau khi nâng cấp AI vì chi tiết được tạo ra có thể trông thuyết phục nhưng không chính xác. + Thứ tự lọc quan trọng + Áp dụng tính năng làm sạch, màu sắc, độ sắc nét và nén cuối cùng theo trình tự có chủ ý + Xây dựng ngăn xếp bộ lọc sạch hơn + Bộ lọc không phải lúc nào cũng có thể hoán đổi cho nhau. Làm mờ trước khi làm sắc nét, tăng độ tương phản trước OCR hoặc thay đổi màu sắc trước khi nén có thể tạo ra kết quả rất khác nhau từ cùng một điều khiển. + Cắt và xoay trước để các bộ lọc sau này chỉ hoạt động trên các pixel còn lại. + Áp dụng độ phơi sáng, cân bằng trắng và độ tương phản trước khi làm sắc nét hoặc tạo hiệu ứng cách điệu. + Giữ kích thước và độ nén cuối cùng ở gần cuối để các chi tiết được xem trước vẫn tồn tại khi xuất. + Sửa các cạnh nền + Làm sạch quầng sáng, bóng còn sót lại, tóc, lỗ và đường viền mềm mại sau khi loại bỏ tự động + Làm cho các vết cắt trông có chủ ý + Mặt nạ tự động thường trông đẹp khi nhìn thoáng qua nhưng lại bộc lộ các vấn đề trên nền sáng, tối hoặc có hoa văn. Làm sạch cạnh là sự khác biệt giữa việc cắt nhanh và hình dán, biểu tượng hoặc hình ảnh sản phẩm có thể tái sử dụng. + Xem trước kết quả trên nền tương phản để lộ quầng sáng và chi tiết cạnh bị thiếu. + Sử dụng tính năng khôi phục cho tóc, các góc và các vật thể trong suốt trước khi xóa phần còn sót lại xung quanh. + Xuất một bản thử nghiệm nhỏ về màu nền cuối cùng khi phần cắt ra sẽ được đưa vào thiết kế. + Hình mờ có thể đọc được + Tạo dấu hiệu rõ ràng trên ảnh sáng, tối, bận và bị cắt mà không làm hỏng ảnh + Bảo vệ hình ảnh mà không che giấu nó + Hình mờ trông đẹp trên một hình ảnh có thể biến mất trên hình ảnh khác. Nên chọn kích thước, độ mờ, độ tương phản, vị trí và sự lặp lại cho toàn bộ lô chứ không chỉ cho bản xem trước đầu tiên. + Kiểm tra điểm trên các ảnh sáng nhất và tối nhất trong lô trước khi xuất tất cả các tệp. + Sử dụng độ mờ thấp hơn cho các dấu lặp lại lớn và độ tương phản mạnh hơn cho các dấu góc nhỏ. + Giữ các khuôn mặt, văn bản và chi tiết sản phẩm quan trọng rõ ràng trừ khi nhãn hiệu nhằm mục đích ngăn chặn việc sử dụng lại. + Chia một hình ảnh thành các ô + Cắt một hình ảnh theo hàng, cột, tỷ lệ phần trăm tùy chỉnh, định dạng và chất lượng + Chuẩn bị lưới và các phần đặt hàng + Tách ảnh tạo ra nhiều đầu ra từ một ảnh nguồn. Nó có thể phân chia theo số lượng hàng và cột, điều chỉnh tỷ lệ phần trăm hàng hoặc cột và lưu các phần với định dạng và chất lượng đầu ra đã chọn, rất hữu ích cho lưới, lát trang, ảnh toàn cảnh và các bài đăng xã hội được sắp xếp. + Chọn hình ảnh nguồn, sau đó đặt số hàng và cột bạn cần. + Điều chỉnh phần trăm hàng hoặc cột khi các phần không được có kích thước bằng nhau. + Chọn định dạng và chất lượng đầu ra trước khi lưu để mọi phần được tạo đều sử dụng các quy tắc xuất giống nhau. + Cắt dải hình ảnh + Xóa các vùng dọc hoặc ngang và hợp nhất các phần còn lại lại với nhau + Xóa một vùng mà không để lại khoảng trống + Cắt ảnh khác với cắt ảnh thông thường. Nó có thể loại bỏ một dải dọc hoặc ngang đã chọn, sau đó ghép các phần hình ảnh còn lại lại với nhau. Thay vào đó, chế độ nghịch đảo sẽ giữ lại vùng đã chọn và sử dụng cả hai hướng nghịch đảo sẽ giữ lại hình chữ nhật đã chọn. + Sử dụng cắt dọc cho các vùng bên, cột hoặc dải giữa; sử dụng cắt ngang cho các biểu ngữ, khoảng trống hoặc hàng. + Bật nghịch đảo trên một trục khi bạn muốn giữ lại phần đã chọn thay vì xóa nó. + Xem trước các cạnh sau khi cắt vì nội dung được hợp nhất có thể trông đột ngột khi vùng bị xóa vượt qua các chi tiết quan trọng. + Chọn định dạng đầu ra + Chọn JPEG, PNG, WebP, AVIF, JXL hoặc PDF dựa trên nội dung và đích đến + Hãy để đích đến quyết định định dạng + Định dạng tốt nhất phụ thuộc vào nội dung hình ảnh và nơi nó sẽ được sử dụng. Ảnh, ảnh chụp màn hình, nội dung trong suốt, tài liệu và tệp hoạt ảnh đều không thành công theo những cách khác nhau khi bị ép ở định dạng sai. + Sử dụng JPEG để có khả năng tương thích ảnh rộng khi không yêu cầu độ trong suốt và pixel chính xác. + Sử dụng PNG để có ảnh chụp màn hình sắc nét, ảnh chụp giao diện người dùng, đồ họa trong suốt và chỉnh sửa không mất dữ liệu. + Sử dụng WebP, AVIF hoặc JXL khi đầu ra hiện đại nhỏ gọn quan trọng và ứng dụng nhận hỗ trợ nó. + Trích xuất bìa âm thanh + Lưu ảnh bìa album được nhúng hoặc các khung phương tiện có sẵn từ tệp âm thanh dưới dạng hình ảnh PNG + Kéo tác phẩm nghệ thuật ra khỏi tập tin âm thanh + Audio Covers sử dụng siêu dữ liệu phương tiện Android để đọc tác phẩm nghệ thuật được nhúng từ các tệp âm thanh. Khi không có tác phẩm nghệ thuật được nhúng, trình truy xuất có thể quay trở lại khung phương tiện nếu Android có thể cung cấp khung đó; nếu không tồn tại thì công cụ sẽ báo cáo rằng không có hình ảnh nào để trích xuất. + Mở Bìa âm thanh và chọn một hoặc nhiều tệp âm thanh có ảnh bìa dự kiến. + Xem lại bìa đã trích xuất trước khi lưu hoặc chia sẻ, đặc biệt đối với các tệp từ ứng dụng phát trực tuyến hoặc ghi. + Nếu không tìm thấy hình ảnh thì tệp âm thanh có thể không có bìa được nhúng hoặc Android không thể hiển thị khung có thể sử dụng được. + Siêu dữ liệu ngày và vị trí + Quyết định những gì cần lưu giữ khi thời gian chụp ảnh quan trọng nhưng dữ liệu thiết bị hoặc GPS không bị rò rỉ + Tách siêu dữ liệu hữu ích khỏi siêu dữ liệu riêng tư + Siêu dữ liệu không phải tất cả đều có rủi ro như nhau. Ngày chụp có thể hữu ích cho việc lưu trữ và sắp xếp, trong khi GPS, các trường giống như sê-ri của thiết bị, thẻ phần mềm hoặc trường chủ sở hữu có thể tiết lộ nhiều thông tin hơn dự định. + Giữ thẻ ngày và giờ khi thứ tự thời gian quan trọng đối với album, bản quét hoặc lịch sử dự án. + Xóa GPS và thẻ nhận dạng thiết bị trước khi chia sẻ công khai hoặc gửi hình ảnh cho người lạ. + Xuất mẫu và kiểm tra lại EXIF ​​​​khi yêu cầu về quyền riêng tư nghiêm ngặt. + Tránh xung đột tên tập tin + Bảo vệ kết quả đầu ra hàng loạt khi nhiều tệp có chung tên như image.jpg hoặc ảnh chụp màn hình.png + Làm cho mỗi tên đầu ra là duy nhất + Tên tệp trùng lặp thường xảy ra khi hình ảnh đến từ trình nhắn tin, ảnh chụp màn hình, thư mục đám mây hoặc nhiều camera. Một mẫu tốt sẽ ngăn chặn sự thay thế ngẫu nhiên và giữ cho kết quả đầu ra có thể truy nguyên được nguồn gốc của chúng. + Bao gồm tên gốc cộng với hậu tố khi bản sao đã chỉnh sửa có thể được nhận dạng. + Thêm trình tự, dấu thời gian, giá trị đặt trước hoặc kích thước khi xử lý các lô hỗn hợp lớn. + Tránh ghi đè quy trình công việc cho đến khi bạn xác minh rằng mẫu đặt tên không thể xung đột. + Lưu hoặc chia sẻ + Chọn giữa một tệp liên tục và chuyển giao tạm thời sang một ứng dụng khác + Xuất khẩu với mục đích đúng đắn + Lưu và Chia sẻ có vẻ giống nhau nhưng chúng giải quyết các vấn đề khác nhau. Việc lưu sẽ tạo một tệp mà bạn có thể tìm thấy sau này; việc chia sẻ sẽ gửi kết quả được tạo thông qua Android tới một ứng dụng khác và có thể không để lại bản sao cục bộ lâu dài. + Sử dụng Lưu khi đầu ra phải nằm trong một thư mục đã biết, được sao lưu hoặc được sử dụng lại sau này. + Sử dụng Chia sẻ để gửi tin nhắn nhanh, tệp đính kèm email, đăng bài trên mạng xã hội hoặc gửi kết quả cho người chỉnh sửa khác. + Lưu trước khi ứng dụng nhận không đáng tin cậy hoặc khi chia sẻ không thành công sẽ gây khó chịu khi tạo lại. + Kích thước và lề trang PDF + Chọn khổ giấy, kiểu dáng vừa vặn và không gian thở trước khi tạo tài liệu + Làm cho các trang hình ảnh có thể in và đọc được + Hình ảnh có thể trở thành trang PDF khó sử dụng khi hình dạng của chúng không khớp với khổ giấy đã chọn. Lề, chế độ vừa vặn và hướng trang quyết định xem nội dung có bị cắt, nhỏ, kéo dài hay dễ đọc hay không. + Sử dụng khổ giấy thật khi PDF có thể được in hoặc gửi dưới dạng biểu mẫu. + Sử dụng lề để quét và chụp ảnh màn hình để văn bản không nằm sát mép trang. + Xem trước các trang dọc và ngang cùng nhau khi hình ảnh nguồn có hướng khác nhau. + Dọn dẹp các bản quét + Cải thiện cạnh giấy, bóng, độ tương phản, xoay và khả năng đọc trước PDF hoặc OCR + Chuẩn bị trang trước khi lưu trữ + Bản quét có thể được ghi lại về mặt kỹ thuật nhưng vẫn khó đọc. Các bước dọn dẹp nhỏ trước khi xuất PDF thường quan trọng hơn cài đặt nén, đặc biệt đối với biên lai, ghi chú viết tay và các trang có ánh sáng yếu. + Phối cảnh chính xác và cắt bỏ các cạnh của bảng, ngón tay, bóng và sự lộn xộn của nền. + Tăng độ tương phản một cách cẩn thận để văn bản trở nên rõ ràng hơn mà không làm hỏng tem, chữ ký hoặc dấu bút chì. + Chỉ chạy OCR sau khi trang thẳng đứng và có thể đọc được, sau đó xác minh các số quan trọng theo cách thủ công. + Nén, thang độ xám hoặc sửa chữa tệp PDF + Sử dụng các thao tác PDF một tệp để có bản sao tài liệu nhẹ hơn, có thể in hoặc xây dựng lại + Chọn thao tác dọn dẹp PDF + Công cụ PDF bao gồm các hoạt động riêng biệt để nén, chuyển đổi thang độ xám và sửa chữa. Tính năng nén và thang độ xám hoạt động chủ yếu thông qua các hình ảnh được nhúng, trong khi việc sửa chữa sẽ xây dựng lại cấu trúc PDF; không điều nào trong số này được coi là bản sửa lỗi được đảm bảo cho mọi tài liệu. + Sử dụng Nén PDF khi tài liệu chứa hình ảnh và kích thước tệp quan trọng để chia sẻ. + Sử dụng Thang độ xám khi tài liệu cần in dễ dàng hơn hoặc không cần hình ảnh màu. + Sử dụng Sửa chữa PDF trên bản sao khi tài liệu không mở được hoặc cấu trúc bên trong bị hỏng, sau đó xác minh tệp được xây dựng lại. + Trích xuất hình ảnh từ tệp PDF + Khôi phục hình ảnh PDF được nhúng và đóng gói kết quả được trích xuất vào kho lưu trữ + Lưu hình ảnh nguồn từ tài liệu + Trích xuất hình ảnh quét tệp PDF để tìm các đối tượng hình ảnh được nhúng, bỏ qua các bản sao nếu có thể và lưu trữ các hình ảnh đã khôi phục trong kho lưu trữ ZIP được tạo. Nó chỉ trích xuất những hình ảnh thực sự được nhúng trong tệp PDF chứ không phải văn bản, bản vẽ vector hoặc mọi trang hiển thị dưới dạng ảnh chụp màn hình. + Sử dụng Trích xuất hình ảnh khi bạn cần hình ảnh được lưu trữ bên trong tệp PDF, chẳng hạn như ảnh từ danh mục hoặc nội dung được quét. + Thay vào đó, hãy sử dụng PDF sang Hình ảnh hoặc trích xuất trang khi bạn cần toàn bộ trang, bao gồm cả văn bản và bố cục. + Nếu công cụ báo cáo không có hình ảnh được nhúng thì trang đó có thể là nội dung văn bản/vectơ hoặc hình ảnh có thể không được lưu trữ dưới dạng đối tượng có thể trích xuất được. + Siêu dữ liệu, làm phẳng và in đầu ra + Chỉnh sửa thuộc tính tài liệu, sắp xếp các trang hoặc cố ý chuẩn bị bố cục in tùy chỉnh + Chọn hành động tài liệu cuối cùng + Siêu dữ liệu PDF, làm phẳng và chuẩn bị in giải quyết các vấn đề khác nhau ở giai đoạn cuối. Siêu dữ liệu kiểm soát các thuộc tính tài liệu, Flatten PDF sắp xếp các trang thành đầu ra ít chỉnh sửa hơn và Print PDF chuẩn bị bố cục mới với các điều khiển kích thước trang, hướng, lề và số trang trên mỗi trang. + Sử dụng Siêu dữ liệu khi tác giả, tiêu đề, từ khóa, nhà sản xuất hoặc việc dọn dẹp quyền riêng tư có vấn đề. + Sử dụng Flatten PDF khi nội dung trang hiển thị trở nên khó chỉnh sửa hơn dưới dạng các đối tượng PDF riêng biệt. + Sử dụng In PDF khi bạn cần tùy chỉnh kích thước trang, hướng, lề hoặc nhiều trang trên mỗi tờ. + Chuẩn bị hình ảnh cho OCR + Sử dụng cắt, xoay, độ tương phản, khử nhiễu và chia tỷ lệ trước khi yêu cầu OCR đọc văn bản + Cung cấp pixel sạch hơn OCR + Lỗi OCR thường đến từ hình ảnh đầu vào chứ không phải từ công cụ OCR. Các trang bị cong, độ tương phản thấp, mờ, bóng, văn bản nhỏ và nền hỗn hợp đều làm giảm chất lượng nhận dạng. + Cắt vùng văn bản và xoay hình ảnh sao cho các đường nằm ngang trước khi nhận dạng. + Tăng độ tương phản hoặc chỉ chuyển đổi sang màu đen trắng rõ ràng hơn khi điều đó làm cho các chữ cái dễ đọc hơn. + Thay đổi kích thước văn bản nhỏ lên vừa phải nhưng tránh làm sắc nét quá mức tạo ra các cạnh giả. + Kiểm tra nội dung QR + Xem lại các liên kết, thông tin xác thực Wi-Fi, danh bạ và hành động trước khi tin cậy vào mã + Coi văn bản được giải mã là đầu vào không đáng tin cậy + Mã QR có thể ẩn URL, thẻ liên hệ, mật khẩu Wi-Fi, sự kiện trên lịch, số điện thoại hoặc văn bản thuần túy. Nhãn hiển thị gần mã có thể không khớp với tải trọng thực tế, vì vậy hãy xem lại nội dung đã giải mã trước khi hành động. + Kiểm tra URL được giải mã đầy đủ trước khi mở nó, đặc biệt là các miền rút ngắn hoặc không quen thuộc. + Hãy cẩn thận với mã Wi-Fi, danh bạ, lịch, điện thoại và SMS vì chúng có thể kích hoạt các hành động nhạy cảm. + Hãy sao chép nội dung trước khi bạn cần xác minh ở nơi khác thay vì mở ngay. + Tạo mã QR + Tạo mã từ văn bản thuần túy, URL, Wi-Fi, danh bạ, email, điện thoại, SMS và dữ liệu vị trí + Xây dựng tải trọng QR phù hợp + Màn hình QR có thể tạo mã từ nội dung đã nhập cũng như quét nội dung hiện có. Các loại QR có cấu trúc giúp mã hóa dữ liệu theo định dạng mà các ứng dụng khác hiểu được, chẳng hạn như chi tiết đăng nhập Wi-Fi, thẻ liên hệ, trường email, số điện thoại, nội dung SMS, URL hoặc tọa độ địa lý. + Chọn văn bản thuần túy cho các ghi chú đơn giản hoặc sử dụng loại có cấu trúc khi mã sẽ mở dưới dạng Wi-Fi, liên hệ, URL, email, điện thoại, SMS hoặc dữ liệu vị trí. + Kiểm tra mọi trường trước khi chia sẻ mã được tạo vì bản xem trước hiển thị chỉ phản ánh tải trọng bạn đã nhập. + Kiểm tra mã QR đã lưu bằng máy quét khi mã đó sẽ được in, đăng công khai hoặc được người khác sử dụng. + Chọn chế độ tổng kiểm tra + Tính toán giá trị băm từ tệp hoặc văn bản, so sánh một tệp hoặc kiểm tra hàng loạt nhiều tệp + Xác minh nội dung, không phải hình thức + Công cụ tổng kiểm tra có các tab riêng biệt để băm tệp, băm văn bản, so sánh tệp với hàm băm đã biết và so sánh hàng loạt. Tổng kiểm tra chứng minh nhận dạng từng byte cho thuật toán đã chọn; nó không chứng minh được rằng hai hình ảnh trông giống nhau. + Sử dụng Tính toán cho tệp và Băm văn bản cho dữ liệu văn bản đã nhập hoặc dán. + Sử dụng So sánh khi ai đó cung cấp cho bạn tổng kiểm tra dự kiến ​​cho một tệp. + Sử dụng So sánh hàng loạt khi nhiều tệp cần băm hoặc xác minh trong một lần chuyển, sau đó giữ cho thuật toán đã chọn nhất quán. + Quyền riêng tư của bảng nhớ tạm + Sử dụng các tính năng clipboard tự động mà không vô tình làm lộ dữ liệu đã sao chép riêng tư + Giữ nội dung sao chép có chủ ý + Tự động hóa bảng tạm rất thuận tiện nhưng văn bản được sao chép có thể chứa mật khẩu, liên kết, địa chỉ, mã thông báo hoặc ghi chú riêng tư. Hãy coi hoạt động nhập dựa trên bảng nhớ tạm là một tính năng tốc độ phù hợp với thói quen và kỳ vọng về quyền riêng tư của bạn. + Vô hiệu hóa việc sử dụng clipboard tự động nếu văn bản riêng tư xuất hiện bất ngờ trong các công cụ. + Chỉ sử dụng tính năng lưu vào bảng nhớ tạm khi bạn cố tình muốn có kết quả để tránh phải lưu trữ. + Xóa hoặc thay thế bảng tạm sau khi xử lý văn bản nhạy cảm, dữ liệu QR hoặc tải trọng Base64. + Định dạng màu + Hiểu các giá trị màu HEX, RGB, HSV, alpha và sao chép trước khi dán vào nơi khác + Sao chép định dạng mà mục tiêu mong đợi + Màu sắc nhìn thấy được có thể được viết bằng nhiều cách. Công cụ thiết kế, trường CSS, giá trị màu của Android hoặc trình chỉnh sửa hình ảnh có thể có thứ tự, cách xử lý alpha hoặc mô hình màu khác. + Sử dụng HEX khi dán vào các trường web, thiết kế hoặc chủ đề yêu cầu mã màu nhỏ gọn. + Sử dụng RGB hoặc HSV khi bạn cần điều chỉnh kênh, độ sáng, độ bão hòa hoặc màu sắc theo cách thủ công. + Kiểm tra alpha riêng biệt khi tính minh bạch quan trọng vì một số đích sẽ bỏ qua nó hoặc sử dụng thứ tự khác. + Duyệt thư viện màu + Tìm kiếm các màu được đặt tên theo tên hoặc HEX và giữ các màu yêu thích ở gần đầu + Tìm các màu được đặt tên có thể tái sử dụng + Thư viện màu tải một bộ sưu tập màu được đặt tên, sắp xếp theo tên, lọc theo tên màu hoặc giá trị HEX và lưu trữ các màu yêu thích để các mục quan trọng luôn dễ tiếp cận hơn. Nó rất hữu ích khi bạn cần một màu có tên thật thay vì lấy mẫu từ hình ảnh. + Tìm kiếm theo tên màu khi bạn biết họ, chẳng hạn như đỏ, xanh lam, đá phiến hoặc bạc hà. + Tìm kiếm theo HEX khi bạn đã có màu số và muốn tìm các mục nhập có tên phù hợp hoặc gần đó. + Đánh dấu các màu thường xuyên làm mục yêu thích để chúng di chuyển lên trên thư viện chung trong khi duyệt. + Sử dụng bộ sưu tập gradient lưới + Tải xuống các tài nguyên gradient lưới có sẵn và chia sẻ hình ảnh đã chọn + Sử dụng nội dung gradient lưới từ xa + Mesh Gradents có thể tải bộ sưu tập tài nguyên từ xa, tải xuống các hình ảnh gradient bị thiếu, hiển thị tiến trình tải xuống và chia sẻ các gradient đã chọn. Tính khả dụng phụ thuộc vào quyền truy cập mạng và kho tài nguyên từ xa, do đó, danh sách trống thường có nghĩa là tài nguyên chưa có sẵn. + Mở Mesh Gradents từ các công cụ chuyển màu khi bạn muốn có các hình ảnh chuyển màu dạng lưới được tạo sẵn. + Đợi quá trình tải tài nguyên hoặc tải xuống trước khi cho rằng bộ sưu tập trống. + Chọn và chia sẻ các dải màu bạn cần hoặc quay lại Trình tạo dải màu khi bạn muốn tạo một dải màu tùy chỉnh theo cách thủ công. + Độ phân giải tài sản được tạo + Chọn kích thước đầu ra trước khi tạo độ chuyển màu, độ nhiễu, hình nền, tác phẩm nghệ thuật giống SVG hoặc họa tiết + Tạo ở kích thước bạn cần + Hình ảnh được tạo không có camera gốc để quay lại. Nếu đầu ra quá nhỏ, việc chia tỷ lệ sau này có thể làm mềm các mẫu, thay đổi chi tiết hoặc thay đổi cách sắp xếp và nén nội dung. + Trước tiên, hãy đặt kích thước cho trường hợp sử dụng cuối cùng, chẳng hạn như hình nền, biểu tượng, nền, bản in hoặc lớp phủ. + Sử dụng kích thước lớn hơn cho các họa tiết có thể bị cắt, thu phóng hoặc sử dụng lại trong một số bố cục. + Xuất các phiên bản thử nghiệm trước khi có kết quả đầu ra lớn vì chi tiết thủ tục có thể thay đổi cách hoạt động của quá trình nén. + Xuất hình nền hiện tại + Truy xuất hình nền Home, Lock và hình nền tích hợp có sẵn từ thiết bị + Lưu hoặc chia sẻ hình nền đã cài đặt + Xuất hình nền đọc các hình nền mà Android hiển thị thông qua API hình nền hệ thống. Tùy thuộc vào thiết bị, phiên bản Android, quyền và biến thể bản dựng, hình nền Màn hình chính, Khóa hoặc hình nền tích hợp có thể có sẵn riêng biệt hoặc có thể bị thiếu. + Mở Xuất hình nền để tải hình nền mà hệ thống cho phép ứng dụng đọc. + Chọn các mục nhập Màn hình chính, Khóa hoặc hình nền tích hợp sẵn mà bạn muốn lưu, sao chép hoặc chia sẻ. + Nếu hình nền bị thiếu hoặc tính năng này không khả dụng thì đó thường là giới hạn hệ thống, quyền hoặc biến thể bản dựng chứ không phải là cài đặt chỉnh sửa. + Van tiết lưu góc hoạt hình + Sao chép màu như + HEX + tên + Mẫu thảm chân dung giúp cắt nhanh người với mái tóc mềm hơn và khả năng xử lý cạnh. + Cải thiện nhanh trong điều kiện ánh sáng yếu bằng cách sử dụng ước tính đường cong Zero-DCE++. + Mô hình phục hồi hình ảnh Restormer để giảm các vệt mưa và hiện tượng thời tiết ẩm ướt. + Mô hình không cong vênh tài liệu dự đoán lưới tọa độ và ánh xạ lại các trang cong thành bản quét phẳng hơn. + Thang thời lượng chuyển động + Kiểm soát tốc độ hoạt ảnh của ứng dụng: 0 tắt chuyển động, 1 là bình thường, giá trị cao hơn sẽ làm chậm hoạt ảnh + Hiển thị các công cụ gần đây + Hiển thị quyền truy cập nhanh vào các công cụ được sử dụng gần đây trên màn hình chính + Thống kê sử dụng + Khám phá các lần mở ứng dụng, khởi chạy công cụ và các công cụ bạn sử dụng nhiều nhất + Ứng dụng mở ra + Công cụ mở ra + Công cụ cuối cùng + Lưu thành công + Chuỗi hoạt động + Đã lưu dữ liệu + Định dạng hàng đầu + Công cụ được sử dụng + %1$d mở ra • %2$s + Chưa có số liệu thống kê sử dụng + Mở một vài công cụ và chúng sẽ tự động xuất hiện ở đây + Số liệu thống kê sử dụng của bạn chỉ được lưu trữ cục bộ trên thiết bị của bạn. ImageToolbox chỉ sử dụng chúng để hiển thị hoạt động cá nhân, các công cụ yêu thích của bạn và thông tin chi tiết về dữ liệu đã lưu + Biến thể trung lập + Lỗi + biên tập chỉnh sửa ảnh chỉnh sửa ảnh điều chỉnh + thay đổi kích thước chuyển đổi tỷ lệ độ phân giải kích thước chiều rộng chiều cao jpg jpeg png webp nén + nén kích thước trọng lượng byte mb kb giảm chất lượng tối ưu hóa + tỷ lệ khung hình cắt xén + hiệu ứng lọc hiệu chỉnh màu sắc điều chỉnh mặt nạ lut + vẽ cọ sơn bút chì chú thích đánh dấu + mật mã mã hóa giải mã mật khẩu bí mật + xóa nền xóa loại bỏ phần cắt bỏ alpha trong suốt + xem trước thư viện xem kiểm tra mở + khâu hợp nhất kết hợp ảnh toàn cảnh dài + url web tải xuống liên kết internet hình ảnh + picker eyedropper mẫu màu hex rgb + bảng màu mẫu trích xuất chiếm ưu thế + Quyền riêng tư của siêu dữ liệu Exif xóa dải vị trí gps sạch + so sánh sự khác biệt trước sau + giới hạn thay đổi kích thước tối thiểu megapixel kích thước kẹp + công cụ tạo trang tài liệu pdf + quét văn bản ocr nhận dạng trích xuất + lưới màu nền gradient + lớp phủ tem văn bản logo hình mờ + hoạt hình gif chuyển đổi khung hình hoạt hình + hoạt hình apng hoạt hình png chuyển đổi khung + zip archive nén gói giải nén tập tin + jxl jpeg xl chuyển đổi định dạng ảnh jpg + chuyển đổi dấu vết vector svg xml + định dạng chuyển đổi jpg jpeg png webp heic avif jxl bmp + máy quét tài liệu quét giấy quan điểm cắt + quét mã vạch qr + xếp chồng lớp phủ chụp ảnh thiên văn trung bình + chia lát lưới các phần + chuyển đổi màu hex rgb hsl hsv cmyk lab + webp chuyển đổi định dạng ảnh động + máy tạo tiếng ồn hạt kết cấu ngẫu nhiên + kết hợp bố cục lưới cắt dán + lớp đánh dấu chú thích lớp phủ chỉnh sửa + mã hóa base64 giải mã dữ liệu uri + hàm băm tổng kiểm tra md5 sha sha1 sha256 xác minh + nền gradient lưới + Exif siêu dữ liệu chỉnh sửa máy ảnh ngày gps + cắt gạch mảnh lưới chia + bìa album âm thanh nghệ thuật siêu dữ liệu âm nhạc + hình nền xuất khẩu + ký tự nghệ thuật văn bản ascii + ai ml nâng cao thần kinh phân khúc cao cấp + thư viện màu bảng vật liệu có tên là màu sắc + studio hiệu ứng mảnh đổ bóng glsl + hợp nhất pdf kết hợp tham gia + pdf chia trang riêng biệt + hướng trang xoay pdf + sắp xếp lại pdf sắp xếp lại các trang sắp xếp + đánh số trang pdf + văn bản pdf ocr nhận dạng trích xuất có thể tìm kiếm + văn bản logo hình mờ pdf + vẽ chữ ký pdf + pdf bảo vệ khóa mã hóa mật khẩu + pdf mở khóa mật khẩu giải mã loại bỏ bảo vệ + nén pdf giảm kích thước tối ưu hóa + pdf thang độ xám đen trắng đơn sắc + sửa chữa pdf sửa chữa phục hồi bị hỏng + thuộc tính siêu dữ liệu pdf Exif tiêu đề tác giả + pdf xóa xóa trang + lề cắt xén pdf + chú thích làm phẳng pdf tạo thành các lớp + trích xuất hình ảnh pdf + nén kho lưu trữ zip pdf + máy in pdf + đọc bản xem trước pdf + hình ảnh sang pdf chuyển đổi tài liệu jpg jpeg png + trang pdf sang hình ảnh xuất jpg png + chú thích pdf loại bỏ ý kiến ​​sạch sẽ + Thả bóng + Chiều cao răng + Phạm vi răng ngang + Phạm vi răng dọc + Cạnh trên + Cạnh phải + Cạnh dưới + Cạnh trái + Rách cạnh + Đặt lại số liệu thống kê sử dụng + Công cụ mở ra, lưu số liệu thống kê, vệt, dữ liệu đã lưu và định dạng trên cùng sẽ được đặt lại. Ứng dụng mở sẽ không thay đổi. + Âm thanh + Tài liệu + Xuất hồ sơ + Lưu hồ sơ hiện tại + Xóa hồ sơ xuất + Bạn có muốn xóa hồ sơ xuất \\"%1$s\\" không? + Vẽ đường viền + Hiển thị đường viền mỏng xung quanh bản vẽ và xóa canvas + Lượn sóng + Các phần tử giao diện người dùng được làm tròn với cạnh lượn sóng mềm mại + muỗng + Notch + Các góc tròn được khoét vào trong + Các góc bậc thang có đường cắt đôi + Trình giữ chỗ + Sử dụng {filename} để chèn tên tệp gốc không có phần mở rộng + Bùng phát + Số tiền cơ bản + Số lượng chuông + Lượng tia + Chiều rộng vòng + Quan điểm bóp méo + Giao diện Java + cắt + góc X + và góc + Thay đổi kích thước đầu ra + Giọt Nước + Bước sóng + Giai đoạn + Đèo cao + Mặt nạ màu + Chrome + hòa tan + sự mềm mại + Nhận xét + Làm mờ ống kính + Đầu vào thấp + Đầu vào cao + Sản lượng thấp + Sản lượng cao + Hiệu ứng ánh sáng + Chiều cao va chạm + va chạm mềm mại + gợn sóng + Biên độ X + Biên độ Y + Bước sóng X + Bước sóng Y + Làm mờ thích ứng + Tạo kết cấu + Tạo các kết cấu thủ tục khác nhau và lưu chúng dưới dạng hình ảnh + Loại kết cấu + thiên vị + Thời gian + Chiếu sáng + phân tán + Mẫu + hệ số góc + hệ số độ dốc + Loại lưới + Khoảng cách điện + Thang đo Y + Hơi quăn + Loại cơ sở + Hệ số nhiễu loạn + Chia tỷ lệ + Lặp lại + Nhẫn + kim loại chải + chất ăn da + Di động + Bàn cờ + fBm + Đá cẩm thạch + Huyết tương + Chăn + Gỗ + ngẫu nhiên + Quảng trường + lục giác + hình bát giác + tam giác + có đường gờ + Tiếng ồn VL + SC tiếng ồn + Gần đây + Chưa có bộ lọc nào được sử dụng gần đây + kết cấu máy phát điện chải kim loại chất ăn da di động bàn cờ đá cẩm thạch plasma chăn gỗ gạch ngụy trang di động đám mây vết nứt vải tán lá tổ ong băng dung nham tinh vân giấy gỉ cát khói đá địa hình địa hình nước gợn sóng + Gạch + Ngụy trang + Tế bào + Đám mây + Nứt + Vải vóc + Tán lá + Tổ ong + Đá + dung nham + Tinh vân + Giấy + rỉ sét + Cát + Khói + Cục đá + địa hình + Địa hình + Nước gợn sóng + Gỗ cao cấp + Chiều rộng vữa + sự bất thường + Góc xiên + Độ nhám + Ngưỡng đầu tiên + Ngưỡng thứ hai + Ngưỡng thứ ba + Độ mềm của cạnh + Độ rộng đường viền + Bảo hiểm + Chi tiết + Độ sâu + Phân nhánh + Chủ đề ngang + Chủ đề dọc + Lông tơ + tĩnh mạch + Chiếu sáng + Chiều rộng vết nứt + sương giá + Chảy + vỏ bánh + Mật độ đám mây + Ngôi sao + Mật độ sợi + Độ bền của sợi + vết bẩn + Ăn mòn + rỗ + Mảnh + tần số cồn cát + Góc gió + gợn sóng + Wisps + Cân tĩnh mạch + mực nước + Cấp độ núi + Xói mòn + Mức độ tuyết + Số dòng + Độ dày đường + Bóng mát + Màu lỗ chân lông + Màu vữa + Màu gạch đậm + Màu gạch + Màu nổi bật + Màu tối + Màu rừng + màu đất + Màu cát + Màu nền + Màu tế bào + Màu cạnh + Màu trời + Màu bóng + Màu sáng + Màu bề mặt + Màu sắc đa dạng + Màu vết nứt + Màu dọc + Màu sợi ngang + Màu lá sẫm + Màu lá + Màu viền + Màu mật ong + Màu đậm + Màu băng + Màu sương giá + Màu vỏ + Rửa màu + Màu phát sáng + Màu không gian + Màu tím + Màu xanh + Màu cơ bản + Màu sợi + Màu vết + Màu kim loại + Màu rỉ sét đậm + Màu rỉ sét + Màu cam + Màu khói + Màu tĩnh mạch + Màu đất thấp + Màu nước + Màu đá + Màu tuyết + Màu sắc thấp + Màu sắc cao + Màu đường + Màu nông + Cỏ + Bụi bẩn + Da thú + Bê tông + Nhựa đường + Rêu + Ngọn lửa + cực quang + vết dầu loang + Màu nước + Dòng trừu tượng + Đá mắt mèo + Thép Damascus + Sét + nhung + Mực cẩm thạch + Lá ba chiều + phát quang sinh học + Vòng xoáy vũ trụ + Đèn dung nham + Chân trời sự kiện + Hoa Fractal + Đường hầm màu sắc + Nhật thực Corona + Sức hút kỳ lạ + Vương miện chất lỏng sắt + siêu tân tinh + mống mắt + Lông công + Vỏ ốc anh vũ + Hành tinh có vành đai + Mật độ lưỡi + Chiều dài lưỡi + Gió + loang lổ + vón cục + Độ ẩm + Sỏi + nếp nhăn + lỗ chân lông + Tổng hợp + vết nứt + hắc ín + Mặc + Đốm + Sợi + Tần số ngọn lửa + Khói + Cường độ + Ruy băng + Ban nhạc + ánh kim + bóng tối + hoa nở + sắc tố + Các cạnh + Giấy + Khuếch tán + tính đối xứng + Độ sắc nét + Chơi màu + độ sữa + Lớp + gấp + Đánh bóng + Chi nhánh + Phương hướng + ánh sáng + nếp gấp + Lông vũ + Cân bằng mực + quang phổ + nếp nhăn + Nhiễu xạ + vũ khí + xoắn + ánh sáng cốt lõi + đốm màu + Độ nghiêng đĩa + Kích thước đường chân trời + Chiều rộng đĩa + Ống kính + Cánh hoa + uốn cong + đồ nư + Khía cạnh + độ cong + Kích thước mặt trăng + Kích thước vành nhật hoa + Tia + Nhẫn kim cương + Thùy + Mật độ quỹ đạo + độ dày + gai + Chiều dài gai + Kích thước cơ thể + Kim loại + Bán kính sốc + Chiều rộng vỏ + Bị ném ra ngoài + Kích thước học sinh + Kích thước mống mắt + Biến đổi màu sắc + ánh sáng phản chiếu + Kích thước mắt + Mật độ Barb + lượt + Phòng + Khai mạc + Sườn núi + ngọc trai + kích thước hành tinh + Độ nghiêng vòng + Chiều rộng vòng + Bầu không khí + Màu bẩn + Màu cỏ đậm + Màu cỏ + Màu đầu + Màu đất đậm + Màu khô + Màu sỏi + Màu da + Màu bê tông + Màu hắc ín + Màu nhựa đường + Màu đá + Màu bụi + Màu đất + Màu rêu đậm + Màu rêu + Màu đỏ + Màu lõi + Màu xanh lá cây + Màu lục lam + Màu đỏ tươi + Màu vàng + Màu giấy + Màu sắc tố + Màu thứ cấp + Màu đầu tiên + Màu thứ hai + Màu hồng + Màu thép đậm + Màu thép + Màu thép nhẹ + Màu oxit + Màu hào quang + Màu bu lông + Màu nhung + Màu ánh sáng + Màu mực xanh + Màu mực đỏ + Màu mực đậm + Màu bạc + Màu vàng + Màu mô + Màu đĩa + Màu nóng + Màu ống kính + Màu sắc bên ngoài + Màu bên trong + Màu hào quang + Màu lạnh + Màu ấm + Màu mây + Màu ngọn lửa + Màu lông + Màu vỏ + Màu ngọc trai + Màu hành tinh + Màu nhẫn + Xóa file gốc sau khi lưu + Chỉ các tệp nguồn được xử lý thành công mới bị xóa + Tệp gốc đã bị xóa: %1$d + Không xóa được tệp gốc: %1$d + Tệp gốc đã bị xóa: %1$d, không thành công: %2$d + Cử chỉ trang tính + Cho phép kéo các trang tùy chọn mở rộng + Lấy mẫu phụ sắc độ + Độ sâu bit + không mất dữ liệu + %1$d-bit + Đổi tên hàng loạt + Đổi tên nhiều tệp bằng cách sử dụng mẫu tên tệp + đổi tên hàng loạt tập tin tên tệp mẫu trình tự phần mở rộng ngày + Chọn tập tin để đổi tên + Mẫu tên tệp + Đổi tên + Nguồn ngày + Ngày EXIF ​​​​được chụp + Ngày sửa đổi tập tin + Ngày tạo tập tin + Ngày hiện tại + Ngày thủ công + Ngày thủ công + thời gian thủ công + Ngày đã chọn không có sẵn cho một số tệp. Ngày hiện tại sẽ được sử dụng cho họ. + Nhập mẫu tên tệp + Mẫu tạo ra tên tệp không hợp lệ hoặc quá dài + Mẫu tạo ra tên tệp trùng lặp + Tất cả tên tệp đã khớp với mẫu + Mẫu này sử dụng mã thông báo không có sẵn để đổi tên hàng loạt + Tên sẽ giữ nguyên + Đã đổi tên tập tin thành công + Không thể đổi tên một số tệp + Quyền không được cấp + 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. Đồng thời hỗ trợ cắt bằng \\o{start:end}, chuyển ngữ bằng \\o{t} và thay thế bằng \\o{s/old/new/}. + Bộ đếm tăng tự động. Hỗ trợ định dạng tùy chỉnh: \\c{padding} (ví dụ \\c{3} -&gt; 001), \\c{start:step} hoặc \\c{start:step:padding}. + Tên của thư mục mẹ nơi chứa tệp gốc. + Kích thước của tập tin gốc. Hỗ trợ các đơn vị: \\z{b}, \\z{kb}, \\z{mb} hoặc chỉ \\z cho định dạng con người có thể đọc được. + Tạo Mã định danh duy nhất toàn cầu (UUID) ngẫu nhiên để đảm bảo tính duy nhất của tên tệp. + Việc đổi tên tập tin không thể hoàn tác được. Đảm bảo tên mới là chính xác trước khi tiếp tục. + Xác nhận đổi tên + Cài đặt menu bên + quy mô thực đơn + Thực đơn alpha + Cân bằng trắng tự động + cắt xén + Kiểm tra các hình mờ ẩn + Kho + Giới hạn bộ nhớ đệm + Tự động xóa bộ nhớ đệm khi nó vượt quá kích thước này + Khoảng thời gian dọn dẹp + Tần suất kiểm tra xem có nên xóa bộ nhớ đệm hay không + Khi ra mắt ứng dụng + 1 ngày + 1 tuần + 1 tháng + Tự động xóa bộ nhớ cache ứng dụng theo giới hạn và khoảng thời gian đã chọn + Diện tích cây trồng có thể di chuyển + Cho phép di chuyển toàn bộ vùng cắt bằng cách kéo vào bên trong nó + Hợp nhất GIF + Kết hợp nhiều clip GIF thành một ảnh động + Thứ tự các clip GIF + Đảo ngược + Chơi như đảo ngược + Boomerang + Chơi tiến rồi lùi + Chuẩn hóa kích thước khung hình + Chia tỷ lệ khung hình để vừa với clip lớn nhất; tắt để duy trì quy mô ban đầu của họ + Độ trễ giữa các clip (ms) + Thư mục + Chọn tất cả hình ảnh từ một thư mục và các thư mục con của nó + Trình tìm kiếm trùng lặp + Tìm bản sao chính xác và hình ảnh tương tự về mặt trực quan + công cụ tìm trùng lặp hình ảnh tương tự bản sao chính xác ảnh dọn dẹp lưu trữ dHash SHA-256 + Chọn hình ảnh để tìm bản sao + Độ nhạy tương tự + Bản sao chính xác + Hình ảnh tương tự + Chọn tất cả các bản sao chính xác + Chọn tất cả ngoại trừ được đề xuất + Không tìm thấy bản sao + Hãy thử thêm nhiều hình ảnh hơn hoặc tăng độ nhạy tương tự + Không thể đọc %1$d hình ảnh + %1$s • %2$s có thể thu hồi được + %1$d nhóm • %2$s có thể thu hồi được + %1$d đã chọn • %2$s + Điều này không thể hoàn tác được. Android có thể yêu cầu bạn xác nhận việc xóa trong hộp thoại hệ thống. + Không tìm thấy + Di chuyển để bắt đầu + Chuyển đến cuối + \ No newline at end of file diff --git a/core/resources/src/main/res/values-zh-rCN/strings.xml b/core/resources/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000..c83c19f --- /dev/null +++ b/core/resources/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,3739 @@ + + + + + 发生错误 + 选择图片以开始 + 关闭 + 发生错误:%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”,开启时将自动切换为所需来源。 + 震动强度 + 覆写文件 + + 后缀 + 自动复制 + 缩放模式 + Bilinear + Hermite + Lanczos + Nearest + Spline + Basic + 默认值 + 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 4×4 矩阵抖动 + Sierra 抖动 + 双行 Sierra 抖动 + False Floyd Steinberg 抖动 + 从左到右抖动 + 随机抖动 + 简单阈值抖动 + Bayer 8×8 矩阵抖动 + Bayer 2×2 矩阵抖动 + Jarvis Judice Ninke 抖动 + Floyd Steinberg 抖动 + Bayer 3×3 矩阵抖动 + Sierra Lite 抖动 + Atkinson 抖动 + Stucki 抖动 + Burkes 抖动 + 颜色标准差 + 空间标准差 + 中值模糊 + B Spline + 利用片断定义的双三次多项式函数平滑插值和近似曲线或曲面,灵活且连续地表示形状 + 原生堆栈模糊 + 倾斜移位 + 像素排序 + 故障 + 数量 + 种子 + 噪音 + 浮雕 + 随机播放 + 通道偏移 X + 通道偏移 Y + 故障规模 + 故障转移 X + 三角模糊 + 侧面淡化 + 侧边 + 顶部 + 增强故障 + 故障转移 Y + 底部 + 强度 + 扩散 + 传导 + 泊松模糊 + 对数色调映射 + 晶体化 + 幅度 + 大理石 + 湍流 + 油脂 + 尺寸 + 幅度 X + 幅度 Y + 柏林扭曲 + Hable 胶片色调映射 + ACES 山峰色调映射 + 侵蚀 + 各向异性扩散 + 快速双色模糊 + 水平风向交错 + ACES 胶片色调映射 + 笔画颜色 + 分形玻璃 + 水纹效果 + 频率 X + 频率 Y + Hejl Burgess 色调映射 + 去雾 + 速度 + 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) + 最小 + 在设置中授予相机扫描二维码的权限 + 在设置中授予相机文档扫描器的权限 + Cubic + Gaussian + Sphinx + Robidoux + Robidoux Sharp + Spline 16 + Spline 36 + Spline 64 + Kaiser + Box + 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-Spline + Hamming + Blackman + Hanning + Welch + Quadric + 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 + 色盲 + 选择模式以为所选色盲类型适配主题颜色 + 难以区分红色和绿色色调 + 难以区分绿色和红色色调 + 难以区分蓝色和黄色色调 + Lagrange 2 + Lanczos 6 Jinc + 阶数为 2 的拉格朗日插值滤波器,适用于平滑过渡的高质量图片缩放 + Lagrange 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 页面中删除选定的注释类型,例如链接、注释、突出显示、形状或表单字段 + 超链接 + 文件附件 + 线 + 弹出窗口 + 邮票 + 形状 + 文字注释 + 文本标记 + 表单字段 + 标记 + 未知 + 注释 + 取消分组 + 使用可配置的颜色和偏移在图层后面添加模糊阴影 + 内容感知失真 + 内存不足,无法完成此操作。请尝试使用较小的图像、关闭其他应用程序或重新启动应用程序。 + 并行工作进程 + 这些图像未保存,因为转换后的文件比原始文件大。在删除原始图像之前检查此列表。 + 跳过的文件:%1$s + 应用程序日志 + 查看应用程序日志以发现问题 + 分组工具中的收藏夹 + 当工具按类型分组时,将收藏夹添加为选项卡 + 将最爱显示为最后 + 将最喜爱的工具选项卡移至末尾 + 水平间距 + 垂直间距 + 反向能量 + 使用简单的梯度幅度能量图代替前向能量算法 + 删除遮罩区域 + 能量缝将穿过蒙版区域,仅移除所选区域,而不是保护它 + 录像带NTSC + 高级 NTSC 设置 + 额外的模拟调谐可提供更强的 VHS 和广播伪影 + 色度溢出 + 胶带磨损 + 追踪 + 亮度涂片 + 铃声 + + 使用领域 + 过滤器类型 + 输入亮度滤波器 + 输入色度低通 + 色度解调 + 相移 + 相位偏移 + 头部切换 + 机头切换高度 + 头部切换偏移 + 头部切换换档 + 中线位置 + 中线抖动 + 跟踪噪声高度 + 跟踪波 + 追踪雪 + 追踪雪的各向异性 + 追踪噪音 + 跟踪噪音强度 + 复合噪声 + 复合噪声频率 + 复合噪声强度 + 复合噪声细节 + 振铃频率 + 振铃功率 + 亮度噪声 + 亮度噪声频率 + 亮度噪声强度 + 亮度噪点细节 + 色度噪声 + 色度噪声频率 + 色度噪声强度 + 色度噪点细节 + 降雪强度 + 雪各向异性 + 色度相位噪声 + 色度相位误差 + 水平色度延迟 + 垂直色度延迟 + VHS 磁带速度 + VHS 色度损失 + VHS 锐化强度 + VHS 锐化频率 + VHS 边波强度 + VHS 边波速度 + VHS边波频率 + VHS 边缘波细节 + 输出色度低通 + 水平缩放 + 垂直缩放 + 比例因子X + 比例因子Y + 检测常见图像水印并使用 LaMa 修复它们。自动下载检测和修复模型 + 绿色盲 + 展开图片 + 使用 YOLO v11 分割的基于对象分割的背景去除器 + 动态表情 + 展示可用的动态表情 + 显示线角度 + 绘图时显示当前线旋转角度(以度为单位) + 保存到原始文件夹 + 将新文件保存在原始文件旁边,而不是所选文件夹 + 水印PDF + 内存不足 + 图像可能太大而无法在此设备上处理,或者系统已耗尽可用内存。尝试降低图像分辨率、关闭其他应用程序或选择较小的文件。 + 调试菜单 + 用于测试应用程序功能的菜单,这不打算出现在生产版本中 + 复制 + 提供者 + PaddleOCR 需要您的设备上有其他 ONNX 模型。您想下载 %1$s 数据吗? + 普遍的 + 韩国人 + 拉丁 + 东斯拉夫语 + 泰国 + 希腊语 + 英语 + 西里尔 + 阿拉伯 + 梵文 + 泰米尔语 + 泰卢固语 + 着色器 + 着色器预设 + 未选择着色器 + 着色器工作室 + 创建、编辑、验证、导入和导出自定义片段着色器 + 保存的着色器 + 打开保存的着色器,复制、导出、共享或删除 + 新着色器 + 着色器文件 + .itshader JSON + %1$d 参数 + 着色器源码 + 只写 void main() 的主体。使用纹理坐标作为 UV 坐标,使用 inputImageTexture 作为源采样器。 + 功能 + 在 void main() 之前插入可选的辅助函数、常量和结构。制服是从参数生成的。 + 当着色器需要可编辑值时,在此处添加制服。 + 重置着色器 + 当前的着色器草稿将被清除 + 着色器无效 + 不受支持的着色器版本 %1$d。支持的版本:%2$d。 + 着色器名称不能为空。 + 着色器源不能为空。 + 着色器源包含不受支持的字符:%1$s。仅使用 ASCII GLSL 源。 + 参数“%1$s”被声明多次。 + 参数名称不能为空。 + 参数“%1$s”使用保留的 GPUImage 名称。 + 参数“%1$s”必须是有效的 GLSL 标识符。 + 参数“%1$s”具有 %2$s 值类型“%3$s”,应为“%4$s”。 + 参数“%1$s”无法定义布尔值的最小值或最大值。 + 参数“%1$s”的最小值大于最大值。 + 参数“%1$s”默认低于最小值。 + 参数“%1$s”默认大于最大值。 + 着色器必须声明“uniform Sampler2D %1$s;”。 + 着色器必须声明“variing vec2 %1$s;”。 + 着色器必须定义“void main()”。 + 着色器必须将颜色写入gl_FragColor。 + ShaderToy mainImage 着色器不受支持。使用 GPUImage 的 void main() 合约。 + 不支持动画 ShaderToy 统一“iTime”。 + 不支持动画 ShaderToy 统一“iFrame”。 + 不支持 ShaderToy 统一“iResolution”。 + 不支持 ShaderToy iChannel 纹理。 + 不支持外部纹理。 + 不支持外部纹理扩展。 + 不支持 Libretro 着色器参数。 + 仅支持一种输入纹理。删除“uniform %1$s %2$s;”。 + 参数“%1$s”必须声明为“uniform %2$s %1$s;”。 + 参数“%1$s”声明为“uniform %2$s %1$s;”,预期为“uniform %3$s %1$s;”。 + 着色器文件为空。 + 着色器文件必须是包含版本、名称和着色器字段的 .itshader JSON 对象。 + 着色器文件不是有效的 JSON 或与 .itshader 格式不匹配。 + 字段“%1$s”为必填项。 + %1$s 为必填项。 + 不支持 %1$s \\\"%2$s\\\"。支持的类型:%3$s。 + “%2$s”参数需要 %1$s。 + %1$s 必须是有限数。 + %1$s 必须是整数。 + %1$s 必须是 true 或 false。 + %1$s 必须是颜色字符串、RGB/RGBA 数组或颜色对象。 + %1$s 必须使用#RRGGBB 或#RRGGBBAA 格式。 + %1$s 必须包含有效的十六进制颜色通道。 + %1$s 必须包含 3 或 4 个颜色通道。 + %1$s 必须介于 0 到 255 之间。 + %1$s 必须是两个数字的数组或具有 x 和 y 的对象。 + %1$s 必须恰好包含 2 个数字。 + 始终清除EXIF + 即使工具请求保留元数据,在保存时也会删除图像EXIF数据 + 帮助和提示 + %1$d 教程 + 打开工具 + 了解主要工具、导出选项、PDF 流程、颜色实用程序以及常见问题的修复 + 入门 + 更快地选择工具、导入文件、保存结果和重用设置 + 图像编辑 + 调整大小、裁剪、过滤、删除背景、绘制图像和水印图像 + 文件和元数据 + 转换格式、比较输出、保护隐私和控制文件名 + PDF 和文档 + 创建 PDF、扫描文档、OCR 页面并准备文件以供共享 + 文本、二维码和数据 + 识别文本、扫描代码、Base64 编码、检查哈希值和打包文件 + 色彩工具 + 选择颜色、构建调色板、探索颜色库并创建渐变 + 创意工具 + 生成 SVG、ASCII 艺术、噪声纹理、着色器和实验视觉效果 + 故障排除 + 修复大文件、透明度、共享、导入和报告问题 + 选择正确的工具 + 使用类别、搜索、收藏夹和共享操作从正确的位置开始 + 从最佳切入点开始 + Image Toolbox 有许多重点工具,乍一看其中许多工具是重叠的。从您想要完成的任务开始,然后选择控制结果最重要部分的工具:大小、格式、元数据、文本、PDF 页面、颜色或布局。 + 当您知道操作名称(例如调整大小、PDF、EXIF、OCR、QR 或颜色)时,请使用搜索。 + 当您只知道任务类型时打开一个类别,然后将常用工具固定为收藏夹。 + 当另一个应用程序将图像共享到图像工具箱时,从共享表流程中选择该工具。 + 导入、保存和共享结果 + 了解从选择输入到导出编辑文件的通常流程 + 遵循常见的编辑流程 + 大多数工具都遵循相同的节奏:选择输入、调整选项、预览,然后保存或共享。重要的细节是保存会创建一个输出文件,而共享通常最适合快速切换到另一个应用程序。 + 当选择器允许时,为单图像工具选择一个文件,或为批处理工具选择多个文件。 + 保存前检查预览和输出控件,尤其是格式、质量和文件名选项。 + 使用共享进行快速切换,或者在需要选定文件夹中的永久副本时进行保存。 + 一次处理许多图像 + 批处理工具最适合重复调整大小、转换、压缩和命名任务 + 准备可预测的批次 + 当每个输出都遵循相同的规则时,批处理速度最快。这也是最容易犯大错误的地方,因此在开始长时间导出之前选择命名、质量、元数据和文件夹行为。 + 如果输出取决于顺序,请一起选择所有相关图像并保持顺序。 + 在开始导出之前设置一次调整大小、格式、质量、元数据和文件名规则。 + 当源文件很大或目标格式对您来说是新的时,首先运行小批量测试。 + 快速单一编辑 + 当您需要对一张图像进行多项简单更改时,请使用一体化编辑器 + 无需跳刀即可完成一幅图像 + 当图像需要一小部分更改而不是一次专门操作时,单一编辑非常有用。它通常可以更快地进行快速清理、预览和导出最终副本,而无需构建批处理工作流程。 + 为需要常见调整、标记、裁剪、旋转或导出更改的一张图像打开“单一编辑”。 + 按照首先更改最少像素的顺序应用编辑,例如在过滤器之前裁剪和在最终压缩之前过滤。 + 当您需要批处理、精确的文件大小目标、PDF 输出、OCR 或元数据清理时,请使用专用工具。 + 使用预设和设置 + 保存重复的选择并根据您喜欢的工作流程调整应用程序 + 避免重复相同的设置 + 当您经常导出相同类型的文件时,预设和设置非常有用。良好的预设可以将重复的手动检查表变成一次点击,而全局设置则定义新工具开始时的默认值。 + 为常见输出大小、格式、质量值或命名模式创建预设。 + 查看默认格式、质量、保存文件夹、文件名、选择器和界面行为的设置。 + 在重新安装应用程序或移动到其他设备之前备份设置。 + 设置有用的默认值 + 选择默认格式、质量、缩放模式、调整大小类型和色彩空间一次 + 让新工具更接近您的目标 + 默认值不仅仅是外观偏好。他们决定在许多工具中首先出现的格式、质量、调整大小行为、缩放模式和色彩空间,这有助于避免在每次导出时重新选择相同的选择。 + 设置默认图像格式和质量以匹配您通常的目标,例如用于照片的 JPEG 或用于 Alpha 的 PNG/WebP。 + 如果您经常导出严格尺寸、社交平台或文档页面,请调整默认调整大小类型和缩放模式。 + 仅当您的工作流程需要跨显示器、打印或外部编辑器进行一致的颜色处理时,才更改颜色空间默认值。 + 选择器和启动器 + 当应用程序打开太多对话框或在错误位置启动时使用选择器设置 + 让打开文件符合您的习惯 + 选择器和启动器设置控制 Image Toolbox 是否从主屏幕、工具或文件选择流程启动。当您通常从 Android 共享表输入或希望应用程序更像工具启动器时,这些选项特别有用。 + 如果系统选择器隐藏文件、显示太多最近的项目或处理云文件效果不佳,请使用照片选择器设置。 + 仅对您通常从共享输入或已经知道源文件的工具启用跳过选取。 + 如果您希望 Image Toolbox 的行为更像工具选择器而不是普通的主屏幕,请查看启动器模式。 + 图片来源 + 选择最适合图库、文件管理器和云文件的选取器模式 + 从正确的来源选择文件 + 图像源设置会影响应用程序向 Android 请求图像的方式。最佳选择取决于您的文件是否位于系统库、文件管理器文件夹、云提供商或共享临时内容的其他应用程序中。 + 当您希望常见图库图像获得最系统集成的体验时,请使用默认选择器。 + 如果相册、文件夹、云文件或非标准格式未出现在您期望的位置,请切换选择器模式。 + 如果共享文件在离开源应用程序后消失,请通过持久选择器重新打开它或先保存本地副本。 + 收藏和分享工具 + 保持主屏幕聚焦并隐藏在共享流程中没有意义的工具 + 减少工具列表噪音 + 当您每天只使用少数工具时,收藏夹和隐藏共享设置非常有用。他们还通过隐藏无法使用其他应用程序发送的文件类型的工具来保持共享流程的实用性。 + 固定常用工具,以便即使工具按类别分组,也可以轻松访问它们。 + 当工具与来自其他应用程序的文件无关时,隐藏共享流中的工具。 + 如果您希望将收藏夹放在最后或与相关工具分组,请使用收藏夹排序设置。 + 备份设置 + 将预设、首选项和应用程序行为安全地移至另一个安装 + 让您的设置保持便携 + 在调整预设、文件夹、文件名规则、工具顺序和视觉首选项后,备份和恢复非常有用。通过备份,您可以尝试设置或重新安装应用程序,而无需从内存中重建工作流程。 + 更改预设、文件名行为、默认格式或工具排列后创建备份。 + 如果您计划清除数据、重新安装或移动设备,请将备份存储在应用程序文件夹之外的某个位置。 + 在处理重要批次之前恢复设置,以便默认设置和保存行为与您的旧设置相匹配。 + 调整图像大小和转换图像 + 更改一张或多张图像的大小、格式、质量和元数据 + 创建调整大小的副本 + 调整大小和转换是可预测尺寸和更轻的输出文件的主要工具。当图像大小、输出格式和元数据行为同时重要时使用它。 + 选择一张或多张图像,然后选择尺寸、比例或文件大小是否最重要。 + 选择输出格式和质量;当必须保留透明度时,请使用 PNG 或 WebP。 + 预览结果,然后保存或共享生成的副本,而不更改原始文件。 + 按文件大小调整大小 + 当网站、Messenger 或表单拒绝大量图像时,设定大小限制 + 符合严格的上传限制 + 当目标仅接受低于特定限制的文件时,按文件大小调整大小比猜测质量值更好。该工具可以调整压缩和尺寸以达到目标,同时尝试保持结果可用。 + 输入略低于实际上传限制的目标大小,以便为元数据和提供商更改留出空间。 + 当目标较小时,首选有损照片格式;即使调整大小后,PNG 也可以保持很大。 + 导出后检查面、文本和边缘,因为过大的尺寸目标可能会引入可见的伪影。 + 限制调整大小 + 仅缩小超出最大宽度、高度或文件限制的图像 + 避免调整已经很好的图像大小 + 限制调整大小对于混合文件夹非常有用,其中一些图像很大而其他图像已经准备好。它不会强制每个文件进行相同的调整大小,而是使可接受的图像更接近其原始质量。 + 根据目标设置最大尺寸或限制,而不是盲目地调整每个文件的大小。 + 当您想要避免输出变得比源重时,请启用“skip-if-larger”样式行为。 + 将此用于来自不同相机的批次、屏幕截图或源大小差异很大的下载。 + 裁剪、旋转和拉直 + 在导出或在其他工具中使用图像之前先框住重要区域 + 清理成分 + 首先进行裁剪可以使后面的过滤器、OCR、PDF 页面和共享结果更加清晰。 + 打开“裁剪”并选择要调整的图像。 + 当输出必须适合帖子、文档或头像时,请使用自由裁剪或宽高比。 + 保存前旋转或翻转,以便后续工具接收校正后的图像。 + 应用过滤器和调整 + 构建可编辑的过滤器堆栈并在导出前预览结果 + 调整图像外观 + 过滤器对于快速校正、风格化输出以及准备用于 OCR 或打印的图像非常有用。当图像包含文本或精细细节时,对比度、清晰度和亮度的微小变化可能比重大效果更重要。 + 逐一添加滤镜,并在每次更改后密切关注预览。 + 根据任务调整亮度、对比度、锐度、模糊、颜色或遮罩。 + 仅当预览与您的目标设备、文档或社交平台匹配时才导出。 + 先预览 + 在选择更繁重的编辑、转换或清理工具之前检查图像 + 检查您实际收到的内容 + 当文件来自聊天、云存储或其他应用程序并且您不确定它包含什么时,图像预览非常有用。首先检查尺寸、透明度、方向和视觉质量有助于选择正确的后续工具。 + 当您需要检查多个图像然后决定如何处理它们时,请打开图像预览。 + 查找旋转问题、透明区域、压缩伪影和意外的大尺寸。 + 仅当您知道需要修复哪些内容后,才可使用调整大小、裁剪、比较、EXIF 或背景去除器。 + 删除或恢复背景 + 自动擦除背景或使用恢复工具手动细化边缘 + 保留主题,删除其余内容 + 当您将自动选择与仔细的手动清理结合起来时,背景去除效果最佳。最终的导出格式与蒙版一样重要,因为即使预览看起来正确,JPEG 也会展平透明区域。 + 如果主体与背景清晰分离,则开始自动移除。 + 放大并在擦除和恢复之间切换以修复头发、角落、阴影和小细节。 + 当您需要最终文件的透明度时,另存为 PNG 或 WebP。 + 绘制、标记和水印 + 添加箭头、注释、突出显示、签名或重复的水印叠加 + 添加可见信息 + 标记工具有助于解释图像,而水印有助于品牌化或保护导出的文件。 + 当您需要手绘笔记、形状、突出显示或快速注释时,请使用“绘图”。 + 当相同的文本或图像标记应一致地出现在输出上时,请使用水印。 + 导出前检查不透明度、位置和比例,以便标记可见但不会分散注意力。 + 绘制默认值 + 设置线宽、颜色、路径模式、方向锁定和放大器以加快标记速度 + 让绘画感觉可预测 + 当您经常注释屏幕截图、标记文档或签署图像时,绘图设置非常有用。默认设置可减少设置时间,而放大器和方向锁定使小屏幕上的精确编辑变得更加容易。 + 将默认线宽和绘制颜色设置为您最常用于箭头、突出显示或签名的值。 + 当您通常绘制相同类型的笔画、形状或标记时,请使用默认路径模式。 + 当手指输入导致小细节难以准确放置时,启用放大器或方向锁定。 + 多图像布局 + 在安排屏幕截图、扫描或比较条之前选择正确的多图像工具 + 使用正确的布局工具 + 这些工具听起来很相似,但每一个都针对不同类型的多图像输出进行了调整。首先选择正确的一个可以避免与专为不同结果设计的布局控件发生冲突。 + 使用 Collage Maker 设计布局,在一个合成中包含多个图像。 + 当图像应成为一条长的垂直或水平条带时,请使用图像拼接或堆叠。 + 当一张大图像需要变成几张较小的图像时,请使用图像分割。 + 转换图像格式 + 创建 JPEG、PNG、WebP、AVIF、JXL 和其他副本,无需手动编辑像素 + 选择作业的格式 + 不同的格式更有利于照片、屏幕截图、透明度、压缩或兼容性。当您了解目标应用程序支持什么以及源是否包含您想要保留的 Alpha、动画或元数据时,转换是最安全的。 + 使用 JPEG 来兼容照片,使用 PNG 来表示无损图像和 Alpha,使用 WebP 来进行紧凑共享。 + 当格式支持时调整质量;较低的值会减小尺寸,但会增加伪影。 + 保留原始文件,直到您验证转换后的文件在目标应用程序中正确打开。 + 检查EXIF和隐私 + 在共享敏感照片之前查看、编辑或删除元数据 + 控制隐藏图像数据 + EXIF可以包含相机数据、日期、位置、方向以及图像中不可见的其他详细信息。在公开分享、支持报告、市场列表或任何可能泄露私人场所的照片之前,值得检查。 + 在共享可能包含位置或私人设备信息的照片之前,请打开EXIF工具。 + 删除敏感字段或编辑不正确的标签,例如日期、方向、作者或相机数据。 + 当隐私很重要时,保存新副本并共享该副本而不是原始副本。 + 自动EXIF清理 + 默认删除元数据,同时决定是否应保留日期 + 将隐私设置为默认设置 + 当您希望自动进行隐私清理而不是每次导出时都记住它时,EXIF设置组非常有用。保留日期时间是一个单独的决定,因为日期对于排序很有用,但对于共享很敏感。 + 当您的大部分导出内容用于公开或半公开共享时,请启用始终清晰的EXIF。 + 仅当保留捕获时间比删除每个时间戳更重要时才启用保留日期时间。 + 对需要保留某些字段并删除其他字段的一次性文件使用手动EXIF编辑。 + 控制文件名 + 使用前缀、后缀、时间戳、预设、校验和和序列号 + 让出口更容易找到 + 良好的文件名模式有助于对批次进行排序并防止意外覆盖。当导出返回到源文件夹时,文件名设置特别有用,因为相似的名称很快就会变得令人困惑。 + 在“设置”中设置文件名前缀、后缀、时间戳、原始名称、预设信息或序列选项。 + 对顺序重要的批次(例如页面、框架或拼贴画)使用序列号。 + 仅当您确定替换现有文件对于当前文件夹是安全的时才启用覆盖。 + 覆盖文件 + 仅当您的工作流程故意具有破坏性时,才将输出替换为匹配的名称 + 谨慎使用覆盖模式 + 覆盖文件改变了命名冲突的处理方式。它对于重复导出到同一文件夹很有用,但如果您的文件名模式再次生成相同的名称,它也可以替换先前的结果。 + 仅对需要替换旧生成文件且可恢复的文件夹启用覆盖。 + 导出原件、客户端文件、一次性扫描或任何没有备份的内容时,保持覆盖禁用状态。 + 将覆盖与有意的文件名模式相结合,以便仅替换预期的输出。 + 文件名模式 + 组合原始名称、前缀、后缀、时间戳、预设、缩放模式和校验和 + 构建解释输出的名称 + 文件名模式设置可以将导出的文件变成有关其发生情况的有用历史记录。这对于批量来说很重要,因为文件夹视图可能是唯一可以区分原始大小、预设、格式和处理顺序的地方。 + 当您需要可读名称进行手动排序时,请使用原始文件名、前缀、后缀和时间戳。 + 当输出必须记录其生成方式时,添加预设、缩放模式、文件大小或校验和信息。 + 避免为文件需要与原始文件或页面顺序保持配对的工作流程提供随机名称。 + 选择文件的保存位置 + 通过设置文件夹、原始文件夹保存和一次性保存位置来避免丢失导出 + 保持产出可预测 + 当您处理许多文件或共享来自云提供商的文件时,保存行为最为重要。文件夹设置决定输出是否收集在一个已知位置、显示在原件旁边或临时转到其他位置以执行特定任务。 + 如果您始终希望导出到一个位置,请设置默认保存文件夹。 + 使用保存到原始文件夹进行清理工作流程,其中输出应保留在源文件附近。 + 当一项任务需要不同的目标而不更改默认值时,请使用一次性保存位置。 + 一次性保存文件夹 + 将下一个导出发送到临时文件夹,而不更改您的正常目的地 + 将特殊工作分开 + 一次性保存位置对于临时项目、客户端文件夹、清理会话或不应与常规 Image Toolbox 文件夹混合的导出非常有用。它提供了一个短暂的目的地,而无需重写您的默认设置。 + 在开始属于正常导出位置之外的任务之前,请选择一个一次性文件夹。 + 将其用于短项目、共享文件夹或输出必须保持分组在一起的批处理。 + 作业完成后返回默认文件夹,以便以后的导出不会意外到达临时位置。 + 平衡质量和文件大小 + 了解何时更改尺寸、格式或质量,而不是猜测 + 故意缩小文件 + 文件大小取决于图像尺寸、格式、质量、透明度和内容复杂性。如果文件仍然太重,更改尺寸通常比反复降低质量更有效。 + 当图像大于目标可以显示的尺寸时,首先调整尺寸。 + 仅针对有损格式降低质量,并在导出后检查文本、边缘、渐变和面。 + 当质量变化不够时切换格式,例如用于共享的 WebP 或用于清晰透明资源的 PNG。 + 使用动画格式 + 当文件包含帧而不是一张位图时,使用 GIF、APNG、WebP 和 JXL 工具 + 不要将每张图像都视为静止的 + 动画格式需要能够理解帧、持续时间和兼容性的工具。 + 当您需要对这些格式进行帧级操作时,请使用 GIF 或 APNG 工具。 + 当您需要现代压缩或动画 WebP 输出时,请使用 WebP 工具。 + 在共享之前预览动画时序,因为某些平台会转换或拼合动画图像。 + 比较和预览输出 + 在保留导出之前检查更改、文件大小和视觉差异 + 分享前先验证 + 比较工具有助于尽早发现压缩伪影、错误的颜色或不需要的编辑。 + 当您想要并排检查两个版本时,请打开“比较”。 + 放大最容易错过问题的边缘、文本、渐变和透明区域。 + 如果输出太重或模糊,请返回到之前的工具并调整质量或格式。 + 使用 PDF 工具中心 + 合并、分割、旋转、删除页面、压缩、保护、OCR 和水印 PDF 文件 + 选择 PDF 操作 + PDF 中心将文档操作分组到一个入口点后面,以便您可以选择准确的操作。当文件已经是 PDF 时从这里开始,当您的源仍然是一组图像时,使用图像转 PDF 或文档扫描仪。 + 打开 PDF Tools 并选择符合您目标的操作。 + 选择源 PDF 或图像,然后查看页面顺序、旋转、保护或 OCR 选项。 + 保存生成的 PDF 并打开一次以确认页数、顺序和可读性。 + 将图像转换为 PDF + 将扫描件、屏幕截图、收据或照片合并到一份可共享文档中 + 建立一个干净的文档 + 当图像经过一致的裁剪、排序和尺寸调整后,它们会变成更好的 PDF 页面。将图像列表视为页面堆栈:每次旋转、边距和页面大小选择都会影响最终文档的可读性。 + 当页面边缘、阴影或方向不正确时,首先裁剪或旋转图像。 + 按照预期的页面顺序选择图像,并调整页面大小或边距(如果工具提供)。 + 导出 PDF,然后在发送之前检查所有页面是否可读。 + 扫描文档 + 捕获纸质页面并准备用于 PDF、OCR 或共享 + 捕获可读页面 + 文档扫描在平面页面、良好的照明和正确的透视下效果最佳。在 OCR 或 PDF 导出之前进行干净扫描可以节省时间,因为文本识别和压缩都取决于页面质量。 + 将文档放置在光线充足且阴影最少的对比表面上。 + 手动调整检测到的角或裁剪,使页面干净地填充框架。 + 导出为图像或PDF,然后运行OCR(如果您需要可选文本)。 + 保护或解锁 PDF 文件 + 谨慎使用密码并尽可能保留可编辑的源副本 + 安全处理 PDF 访问 + 保护工具对于受控共享很有用,但密码很容易丢失且难以恢复。保护分发的副本,单独保留未受保护的源文件,并在删除任何内容之前验证结果。 + 保护 PDF 的副本,而不是唯一的源文档。 + 在共享受保护的文件之前,将密码存储在安全的地方。 + 仅对允许编辑的文件使用解锁,然后验证解锁的副本是否正确打开。 + 组织 PDF 页面 + 合并、拆分、重新排列、旋转、裁剪、删除页面以及有意添加页码 + 装饰前修复文档结构 + PDF 页面工具最容易使用,其使用顺序与准备纸质文档的顺序相同:收集页面、删除错误的页面、按顺序排列、纠正方向,然后添加最终详细信息,例如页码或水印。 + 当文档仍分为多个文件时,请先使用合并或图像转 PDF。 + 在添加页码、签名或水印之前重新排列、旋转、裁剪或删除页面。 + 在结构编辑后预览最终的 PDF,因为稍后可能很难注意到缺失或旋转的页面。 + PDF注释 + 当查看者以不同方式显示评论和标记时,删除注释或将其拼合 + 控制仍然可见的内容 + 注释可以是注释、突出显示、绘图、表单标记或其他额外的 PDF 对象。有些查看者以不同的方式显示它们,因此删除或拼合它们可以使共享文档更容易预测。 + 当共享副本中不应包含评论、突出显示或审阅标记时,请删除注释。 + 当可见标记应保留在页面上但不再像可编辑注释时进行扁平化。 + 保留原始副本,因为删除或展平注释可能很难逆转。 + 识别文字 + 使用可选的 OCR 引擎和语言从图像中提取文本 + 获得更好的 OCR 结果 + OCR 准确性取决于图像清晰度、语言数据、对比度和页面方向。最好的结果通常来自于首先准备图像:裁剪掉噪点、垂直旋转、增加可读性,然后识别文本。 + 将图像裁剪到文本区域并在识别前将其垂直旋转。 + 选择正确的语言并在应用程序请求时下载语言数据。 + 复制或共享识别的文本,然后手动检查名称、数字和标点符号。 + 选择 OCR 语言数据 + 通过匹配脚本、语言和下载的 OCR 模型来提高识别率 + 将 OCR 与文本匹配 + 错误的 OCR 语言可能会让好的照片看起来像是糟糕的识别结果。语言数据对于脚本、标点符号、数字和混合文档很重要,因此请选择图像中显示的语言,而不是手机 UI 的语言。 + 选择图像中显示的语言或脚本,而不仅仅是手机语言。 + 在离线工作或处理批次之前下载缺失的数据。 + 对于混合语言文档,首先运行最重要的语言并手动检查名称和号码。 + 可搜索的 PDF + 将 OCR 文本写回到文件中,以便可以搜索和复制扫描的页面 + 将扫描件转换为可搜索文档 + OCR 的功能不仅仅是将文本复制到剪贴板。当您将识别的文本写入文件或可搜索的 PDF 时,页面图像仍然可见,而文本变得更易于搜索、选择、索引或存档。 + 使用可搜索的 PDF 输出来扫描文档,使其看起来仍像原始页面一样。 + 当您只需要提取文本而不关心页面图像时,请使用写入文件。 + 手动检查重要的数字、名称和表格,因为 OCR 文本可以搜索,但仍然不完善。 + 扫描二维码 + 从相机输入或保存的图像(如果可用)中读取 QR 内容 + 安全地读取代码 + QR 码可以包含链接、Wi-Fi 数据、联系人、文本或其他有效负载。 + 打开扫描二维码并保持代码清晰、平整并完全位于框架内。 + 在打开链接或复制敏感数据之前查看解码的内容。 + 如果扫描失败,请改善照明、裁剪代码或尝试更高分辨率的源图像。 + 使用 Base64 和校验和 + 将图像编码为文本,将文本解码回图像,并验证文件完整性 + 在文件和文本之间移动 + Base64 对于小型图像有效负载非常有用,而校验和有助于确认文件没有更改。这些工具在技术工作流程、支持报告、文件传输以及需要将二进制文件临时转换为文本的地方最有帮助。 + 当工作流程需要文本而不是二进制文件时,使用 Base64 工具对小图像进行编码。 + 仅从可信来源解码 Base64 并在保存前验证预览。 + 当您需要比较导出的文件或确认上传/下载时,请使用校验和工具。 + 打包或保护数据 + 使用 ZIP 和密码工具捆绑文件或转换文本数据 + 将相关文件放在一起 + 当多个输出应作为一个文件传输时,打包工具会很有帮助。当您需要发送完整的结果集时,它们还适合将生成的图像、PDF、日志和元数据保存在一起。 + 当您需要一起发送许多导出的图像或文档时,请使用 ZIP。 + 仅当您了解所选方法并可以安全地存储所需密钥时才使用密码工具。 + 在删除源文件之前测试创建的存档或转换后的文本。 + 名称中的校验和 + 当唯一性和完整性比可读性更重要时,使用基于校验和的文件名 + 按内容命名文件 + 当导出可能具有重复的可见名称或需要证明两个文件相同时,校验和文件名非常有用。它们对于手动浏览不太友好,因此将它们用于技术档案和验证工作流程。 + 当内容身份比人类可读的标题更重要时,启用校验和作为文件名。 + 将其用于存档、重复数据删除、可重复导出或可再次上传和下载的文件。 + 当人们仍然需要了解文件代表什么时,将校验和名称与文件夹或元数据配对。 + 从图像中选择颜色 + 采样像素、复制颜色代码以及在调色板或设计中重复使用颜色 + 采样准确的颜色 + 颜色选择对于匹配设计、提取品牌颜色或检查图像值非常有用。缩放很重要,因为抗锯齿、阴影和压缩可能会使相邻像素与您期望的颜色略有不同。 + 打开从图像中选取颜色并选择具有所需颜色的源图像。 + 在采样小细节之前放大,这样附近的像素就不会影响结果。 + 按照目标所需的格式复制颜色,例如 HEX、RGB 或 HSV。 + 创建调色板并使用颜色库 + 提取调色板、浏览保存的颜色并保持一致的视觉系统 + 构建可重复使用的调色板 + 调色板有助于保持导出的图形、水印和绘图在视觉上的一致性。当您重复注释屏幕截图、准备品牌资产或需要在多个工具中使用相同的颜色时,它们特别有用。 + 当您已经知道值时,从图像生成调色板或手动添加颜色。 + 命名或组织重要的颜色,以便您以后可以再次找到它们。 + 在绘图、水印、渐变、SVG 或外部设计工具中使用复制的值。 + 创建渐变 + 为背景、占位符和视觉实验构建渐变图像 + 控制颜色过渡 + 当您需要生成的图像而不是编辑现有图像时,渐变工具非常有用。它们可以成为干净的背景、占位符、壁纸、蒙版或外部设计工具的简单资源。 + 选择渐变类型并首先设置重要的颜色。 + 针对目标用例调整方向、停止点、平滑度和输出大小。 + 以与目标匹配的格式导出,通常为 PNG 以生成干净的图形。 + 颜色可访问性 + 当 UI 难以阅读时,使用动态颜色、对比度和色盲选项 + 让颜色更容易使用 + 颜色设置会影响舒适度、可读性以及扫描控件的速度。如果工具芯片、滑块或选定状态感觉难以区分,主题和辅助功能选项可能比简单地更改暗模式更有用。 + 当您希望应用程序遵循当前的壁纸调色板时,请尝试动态颜色。 + 当按钮和表面不够突出时,增加对比度或更改方案。 + 如果某些状态或重音难以区分,请使用色盲方案选项。 + 阿尔法背景 + 控制透明 PNG、WebP 和类似输出周围使用的预览或填充颜色 + 了解透明预览 + Alpha 格式的背景颜色可以使透明图像更易于检查,但重要的是要知道您是要更改预览/填充行为还是要展平最终结果。这对于贴纸、徽标和背景删除的图像很重要。 + 当透明像素必须在导出时保留下来时,请首先使用支持 Alpha 的格式,例如 PNG 或 WebP。 + 当应用程序表面难以看到透明边缘时,请调整背景颜色设置。 + 导出一个小测试并在另一个应用程序中重新打开它,以确认透明度或所选填充是否已保存。 + AI模型选择 + 根据图像类型和缺陷选择模型,而不是先选择最大的模型 + 将模型与损坏相匹配 + AI Tools 可以下载或导入不同的 ONNX/ORT 模型,并且每个模型都针对特定类型的问题进行训练。当用于错误的图像类型时,JPEG 块、动画线清理、去噪、背景去除、着色或升级的模型可能会产生更糟糕的结果。 + 下载前请阅读型号说明;选择命名您实际问题的模型,例如压缩、噪声、半色调、模糊或背景去除。 + 测试新图像类型时从较小或更具体的模型开始,然后仅在结果不够好时才与较重的模型进行比较。 + 仅保留您真正使用的模型,因为下载和导入的模型会占用大量存储空间。 + 了解模范家庭 + 模型名称通常暗示其目标:RealESRGAN 风格的高档模型、SCUNet 和 FBCNN 模型清除噪声或压缩、背景模型创建掩模、着色器为灰度输入添加颜色。导入的自定义模型可能很强大,但它们应该匹配预期的输入/输出形状并使用支持的 ONNX 或 ORT 格式。 + 当您需要更多像素时,请使用放大器;当图像有颗粒感时,请使用降噪器;当压缩块或条带是主要问题时,请使用伪影去除器。 + 仅使用背景模型来分离主体;它们与一般的对象去除或图像增强不同。 + 仅从您信任的来源导入自定义模型,并在使用重要文件之前在小图像上测试它们。 + AI参数 + 调整块大小、重叠和并行工作线程以平衡质量、速度和内存 + 平衡速度与稳定性 + 块大小控制图像片段在被模型处理之前的大小。重叠混合相邻块以隐藏边界。并行工作线程可以加快处理速度,但每个工作线程都需要内存,因此高值可能会使大图像在 RAM 有限的手机上不稳定。 + 当您的设备可靠地处理模型并且图像不大时,请使用较大的块以获得更平滑的上下文。 + 当块边界变得可见时增加重叠,但请记住,更多的重叠意味着更多的重复工作。 + 仅在测试稳定后才提高并行工作人员;如果处理速度减慢、设备发热或崩溃,请首先减少人员数量。 + 避免块伪影 + 分块让应用程序可以处理比模型可以轻松处理的图像更大的图像。代价是每个图块的周围环境较少,因此低重叠或太小的块可能会在相邻区域之间产生可见的边界、纹理变化或不一致的细节。 + 如果您看到矩形边框、重复的纹理变化或处理区域之间的细节跳跃,请增加重叠。 + 如果过程在生成输出之前失败,请减少块大小,尤其是在大型图像或重型模型上。 + 在处理完整图像之前保存小幅裁剪测试,以便您可以快速比较参数。 + 人工智能稳定性 + 当 AI 处理崩溃或冻结时,降低块大小、工作线程和源分辨率 + 从繁重的人工智能工作中恢复过来 + 人工智能处理是应用程序中最繁重的工作流程之一。崩溃、会话失败、冻结或应用程序突然重新启动通常意味着模型、源分辨率、块大小或并行工作线程数对于当前设备状态要求过高。 + 如果应用程序崩溃或无法打开模型会话,请降低并行工作线程和块大小,然后再次尝试相同的图像。 + 当目标不需要原始分辨率时,在 AI 处理之前调整非常大的图像大小。 + 当内存压力持续增加时,关闭其他繁重的应用程序,使用较小的模型,或一次处理更少的文件。 + 诊断模型故障 + 人工智能运行失败并不总是意味着图像不好。模型文件可能不完整、不受支持、内存太大或与操作不匹配。当应用程序报告失败的会话时,将其视为首先简化模型和参数的信号。 + 如果模型在下载后立即失败或表现为文件已损坏,请删除并重新下载模型。 + 在指责导入的自定义模型之前尝试内置的可下载模型,因为导入的模型可能具有不兼容的输入。 + 如果您需要报告崩溃或会话错误,请在重现故障后使用应用程序日志。 + 在 Shader Studio 中进行实验 + 使用 GPU 着色器效果进行高级图像处理和自定义外观 + 小心使用着色器 + Shader Studio 功能强大,但着色器代码和参数需要小的、可测试的更改。将其视为实验工具:保持工作基线,一次更改一件事,并在信任预设之前使用不同的图像尺寸进行测试。 + 在添加参数之前,从已知的工作着色器或简单效果开始。 + 一次更改一个参数或代码块并检查预览是否有错误。 + 仅在对几种不同的图像尺寸进行测试后才导出预设。 + 制作 SVG 或 ASCII 艺术作品 + 生成类似矢量的资源或基于文本的图像以进行创意输出 + 选择创意输出类型 + SVG 和 ASCII 工具服务于不同的目标:可缩放图形或文本样式渲染。两者都是创造性的转换,因此以最终尺寸预览比从微小的缩略图判断结果更重要。 + 当结果需要干净地缩放或在设计工作流程中重复使用时,请使用 SVG Maker。 + 当您想要图像的风格化文本表示时,请使用 ASCII Art。 + 以最终尺寸预览,因为小细节可能会在生成的输出中消失。 + 生成噪声纹理 + 为背景、蒙版、叠加或实验创建程序纹理 + 调整程序输出 + 噪声生成根据参数创建新图像,因此微小的变化可能会产生截然不同的结果。它对于纹理、蒙版、覆盖、占位符或使用详细图案测试压缩非常有用。 + 在调整精细细节之前选择噪声类型和输出大小。 + 调整比例、强度、颜色或种子,直到图案适合目标用途。 + 如果稍后需要重新创建纹理,请保存有用的结果并记下参数。 + 标记项目 + 当注释需要在关闭应用程序后保持可编辑状态时,请使用项目文件 + 保持分层编辑可重复使用 + 当屏幕截图、图表或说明图像稍后可能需要更改时,标记项目文件非常有用。导出的 PNG/JPEG 文件是最终图像,而项目文件保留可编辑图层和编辑状态以供将来调整。 + 当注释、标注或布局元素应保持可编辑时,请使用标记层。 + 如果您希望进行更多修订,请在导出最终图像之前保存或重新打开项目文件。 + 仅当您准备好共享扁平的最终版本时才导出普通图像。 + 加密文件 + 在删除任何源副本之前使用密码保护文件并测试解密 + 小心处理加密输出 + 密码工具可以使用基于密码的加密来保护文件,但它们不能替代记住密钥或保留备份。加密对于传输和存储很有用,而当主要目标是捆绑多个输出时,ZIP 会更好。 + 首先加密副本并保留原始文件,直到您测试过使用您存储的密码可以解密。 + 使用密码管理器或其他安全的地方存放密钥;该应用程序无法猜测您丢失的密码。 + 仅与也拥有密码并知道应使用哪种工具或方法解密的人员共享加密文件。 + 整理工具 + 分组、排序、搜索和固定工具,使主屏幕符合您的实际工作流程 + 使主屏幕更快 + 一旦您知道最常使用哪些工具,工具排列设置就值得调整。分组有助于探索,自定义顺序有助于增强记忆力,即使完整列表变大,收藏夹也能让日常工具保持可用。 + 在学习应用程序时或当您更喜欢按任务类型组织的工具时,请使用分组模式。 + 当您已经了解常用工具并且想要更少的卷轴时,请切换到自定义顺序。 + 将搜索设置和收藏夹结合使用,以获取您记住名称但不希望始终可见的稀有工具。 + 处理大文件 + 避免导出缓慢、内存压力和意外的大输出 + 减少出口前的工作 + 非常大的图像、长批次和高质量格式可能会占用更多内存和时间。最快的修复通常是在最繁重的操作之前减少工作量,而不是等待相同的超大输入完成。 + 在应用重型滤镜、OCR 或 PDF 转换之前调整巨大图像的大小。 + 首先使用较小的批次确认设置,然后使用相同的预设处理其余的。 + 当输出文件大于预期时,降低质量或更改格式。 + 缓存和预览 + 了解大量会话后生成的预览、缓存清理和存储使用情况 + 保持存储和速度平衡 + 预览生成和缓存可以使重复浏览速度更快,但繁重的编辑会话可能会留下临时数据。当应用程序感觉缓慢、存储紧张或预览在多次实验后看起来过时时,缓存设置会有所帮助。 + 浏览大量图像时保持预览启用比最小化临时存储更重要。 + 在大批量、失败的实验或使用许多高分辨率文件的长时间会话后清除缓存。 + 如果您更喜欢存储清理而不是在启动之间保持预览正常,请使用自动缓存清除。 + 调整屏幕行为 + 使用全屏、安全模式、亮度和系统栏选项来专注工作 + 使界面适合情况 + 当您横向编辑、呈现私人图像或需要一致的亮度时,屏幕设置会有所帮助。正常使用时不需要它们,但它们可以解决尴尬的情况,例如二维码检查、私人预览或狭窄的景观编辑。 + 当编辑空间比导航镶边更重要时,请使用全屏或系统栏设置。 + 当私人图像不应出现在屏幕截图或应用程序预览中时,请使用安全模式。 + 对难以检查暗图像或二维码的工具使用亮度强制。 + 保持透明度 + 防止透明背景变白、变黑或变平 + 使用 alpha 感知输出 + 仅当所选工具和输出格式支持 Alpha 时,透明度才会存在。如果导出的背景变成白色或黑色,问题通常是输出格式、填充/背景设置或拼合图像的目标应用程序。 + 导出已去除背景的图像、贴纸、徽标或叠加层时,请选择 PNG 或 WebP。 + 避免使用 JPEG 进行透明输出,因为它不存储 Alpha 通道。 + 如果预览显示填充背景,请检查与 Alpha 格式的背景颜色相关的设置。 + 修复共享和导入问题 + 当文件未显示或无法正确打开时,使用选择器设置和支持的格式 + 将文件获取到应用程序中 + 导入问题通常是由提供者权限、不受支持的格式或选择器行为引起的。从云应用程序和通讯工具共享的文件可能是临时的,因此选择持久源对于较长的编辑来说可能更可靠。 + 尝试通过系统选择器而不是最近文件快捷方式打开文件。 + 如果某个工具不支持某种格式,请先将其转换或打开更具体的工具。 + 如果共享流程或直接工具打开的行为与预期不同,请检查图像选择器和启动器设置。 + 退出确认 + 避免在意外发生手势、后按或工具切换时丢失未保存的编辑 + 保护未完成的工作 + 当您使用手势导航、编辑大文件或经常在应用程序之间切换时,工具退出确认非常有用。它在离开工具之前添加了一个小暂停,因此未完成的设置和预览不会意外丢失。 + 如果在导出之前意外的后退手势曾经关闭过工具,请启用确认。 + 如果您主要进行快速一步转换并喜欢更快的导航,请保持禁用状态。 + 将其与预设一起使用可实现较长的工作流程,而重建设置会很烦人。 + 报告问题时使用日志 + 在提出问题或联系开发人员之前收集有用的详细信息 + 报告带有上下文的问题 + 包含步骤、应用程序版本、输入类型和日志的清晰报告更容易诊断。日志在重现问题后最有用,因为它们使周围的上下文保持新鲜。 + 请注意工具名称、确切的操作以及它是针对一个文件还是针对每个文件发生。 + 重现问题后打开应用程序日志,并尽可能共享相关日志输出。 + 仅当屏幕截图或示例文件不包含私人信息时才附上它们。 + 后台处理已停止 + Android没有让后台处理通知及时启动。这是无法可靠修复的系统计时限制。重新启动应用程序并重试;保持应用程序打开并允许通知或后台工作可能会有所帮助。 + 跳过文件选取 + 当输入通常来自共享、剪贴板或上一个屏幕时,可以更快地打开工具 + 使重复工具启动更快 + 跳过文件选取更改了受支持工具的第一步。当您通常使用已选择的图像或通过 Android 共享操作输入工具时,它很有用,但如果您希望每个工具立即请求文件,则可能会感到困惑。 + 仅当您的常用流程在工具打开之前已提供源图像时才启用它。 + 在学习应用程序时将其禁用,因为明确的选择使每个工具流程更容易理解。 + 如果工具打开时没有明显的输入,请禁用此设置或从共享/打开方式开始,以便 Android 首先传递文件。 + 首先打开编辑器 + 当共享图像应直接进行编辑时跳过预览 + 选择预览或编辑作为第一站 + 打开编辑而不是预览适用于已经知道要修改图像的用户。当您检查聊天或云存储中的文件时,预览会更安全;直接编辑屏幕截图、快速裁剪、注释和一次性更正的速度更快。 + 如果大多数共享图像立即需要裁剪、标记、滤镜或导出更改,请启用直接编辑。 + 当您经常在决定之前检查元数据、尺寸、透明度或图像质量时,请先保留预览。 + 将其与收藏夹一起使用,以便共享图像接近您实际使用的工具。 + 预设文本输入 + 输入准确的预设值,而不是重复调整控件 + 当滑块缓慢时使用精确值 + 当您需要重复工作的精确数字时,预设文本输入会有所帮助:社交图像尺寸、文档边距、压缩目标、线宽或其他难以通过手势达到的值。它在小屏幕上也很有用,因为滑块的精细移动比较困难。 + 如果您经常重复使用精确尺寸、百分比、质量值或工具参数,请启用文本输入。 + 输入一次后将该值保存为预设,然后重复使用它而不是重建相同的设置。 + 保留手势控制以进行粗略的视觉调整,并保留键入值以满足严格的输出要求。 + 保存接近原始状态 + 当文件夹上下文很重要时,将生成的文件放在源图像旁边 + 将输出与其源保持一致 + “保存到原始文件夹”对于相机文件夹、项目文件夹、文档扫描和客户端批次来说非常方便,其中编辑的副本应保留在源旁边。它可能不如专用输出文件夹整洁,因此请将其与清晰的文件名后缀或模式结合起来。 + 当每个源文件夹已经有意义并且输出应保持分组时启用它。 + 使用文件名后缀、前缀、时间戳或模式,以便编辑后的副本很容易与原始文件分开。 + 当您希望将所有生成的文件收集在一个导出文件夹中时,请针对混合批次禁用它。 + 跳过较大的输出 + 避免保存意外变得比源重的转换 + 保护批次免受不良尺寸意外的影响 + 某些转换会使文件变大,尤其是 PNG 屏幕截图、已压缩的照片、高质量 WebP 或保留元数据的图像。如果较大则允许跳过可防止这些输出在以减小大小为主要目标的工作流程中替换较小的源。 + 当导出旨在压缩、调整大小或准备文件以满足上传限制时启用它。 + 批处理后查看跳过的文件;它们可能已经被优化或者可能需要不同的格式。 + 当质量、透明度或格式兼容性比最终文件大小更重要时,请禁用它。 + 剪贴板和链接 + 控制自动剪贴板输入和链接预览,以实现更快的基于文本的工具 + 使自动输入可预测 + 剪贴板和链接设置对于 QR、Base64、URL 和文本密集型工作流程来说很方便,但自动输入应该是有意的。如果应用程序似乎自行填充字段,请检查剪贴板粘贴行为;如果链接卡感觉嘈杂或缓慢,请调整链接预览行为。 + 当您经常复制文本并立即打开匹配工具时,允许自动剪贴板粘贴。 + 如果私有剪贴板内容出现在您不希望出现的工具中,请禁用自动粘贴。 + 当预览获取缓慢、分散注意力或对工作流程不必要时,请关闭链接预览。 + 紧凑型控制装置 + 当屏幕空间紧张时,使用更密集的选择器和快速设置放置 + 在小屏幕上安装更多控件 + 紧凑的选择器和快速的设置放置有助于小型手机、横向编辑和具有许多参数的工具。代价是控件会感觉更密集,因此最好在您已经认识了常见选项之后再进行操作。 + 当对话框或选项列表对于屏幕来说太高时,启用紧凑选择器。 + 如果从显示屏的一侧更容易触及工具控件,请调整快速设置侧。 + 如果您仍在学习该应用程序或经常错过密集选项,请返回更宽敞的控件。 + 从网络加载图像 + 粘贴页面或图像 URL,预览解析的图像,然后保存或共享所选结果 + 有意使用网络图像 + Web 图像加载从提供的 URL 解析图像源,预览第一个可用图像,并允许您保存或共享选定的解析图像。某些网站使用临时的、受保护的或脚本生成的链接,因此在浏览器中运行的页面可能仍不会返回可用的图像。 + 粘贴直接图像 URL 或页面 URL,然后等待解析的图像列表出现。 + 当页面包含多个图像并且您只需要其中一些图像时,请使用框架或选择控件。 + 如果没有加载,请尝试直接图像链接或先通过浏览器下载;该网站可能会阻止解析或使用私人链接。 + 选择调整大小类型 + 在将图像强制转换为新尺寸之前了解显式、灵活、裁剪和适合 + 控制形状、画布和纵横比 + 调整大小类型决定当请求的宽度和高度与原始宽高比不匹配时会发生什么。这是拉伸像素、保留比例、裁剪额外区域或添加背景画布之间的区别。 + 仅当失真可接受或源已经具有与目标相同的宽高比时才使用显式。 + 使用“灵活”可以通过从重要一侧调整大小来保持比例,特别是对于照片和混合批次。 + 对于没有边框的严格框架,请使用“裁剪”;当整个图像必须在固定画布内保持可见时,请使用“适合”。 + 选择图像缩放模式 + 选择控制锐度、平滑度、速度和边缘伪影的重采样滤波器 + 调整大小,不会意外变软 + 图像缩放模式控制在调整大小期间如何计算新像素。简单模式速度快且可预测,而高级滤镜可以更好地保留细节,但可能会锐化边缘、产生光晕或在大图像上花费更长的时间。 + 当结果只需要更小和更干净时,使用基本或双线性快速日常调整大小。 + 当精细细节、文本或高质量缩小很重要时,请使用 Lanczos、Robidoux、Spline 或 EWA 样式模式。 + 对像素艺术、图标和硬边图形使用“最近”,因为模糊的像素会破坏外观。 + 缩放色彩空间 + 决定在调整大小期间重新采样像素时如何混合颜色 + 保持渐变和边缘自然 + 缩放颜色空间会更改调整大小时使用的数学。大多数图像在默认情况下都很好,但线性或感知空间可以使渐变、暗边缘和饱和颜色在强力缩放后看起来更干净。 + 当速度和兼容性比微小的色差更重要时,请保留默认值。 + 当调整大小后深色轮廓、UI 屏幕截图、渐变或色带看起来不正确时,请尝试线性或感知模式。 + 在真实目标屏幕上比较之前和之后,因为色彩空间变化可能是微妙的并且依赖于内容。 + 限制调整大小模式 + 了解何时应跳过、重新编码或缩小超出限制的图像以适应 + 选择极限时发生的情况 + 限制调整大小不仅仅是一个较小图像的工具。它的模式决定了当源已经在您的限制范围内或只有一侧违反规则时应用程序执行的操作,这对于混合文件夹和上传准备非常重要。 + 当限制范围内的图像应保持不变且不再导出时,请使用“跳过”。 + 当尺寸可以接受但您仍然需要新的格式、元数据行为、质量或文件名时,请使用重新编码。 + 当超出限制的图像应按比例缩小直至适合允许的框时,请使用缩放。 + 纵横比目标 + 避免在严格的输出尺寸下出现拉伸面、切边和意外边框 + 调整大小之前匹配形状 + 宽度和高度只是调整大小目标的一半。宽高比决定图像是否可以自然地适合、需要裁剪或需要在其周围放置画布。首先检查形状可以防止最令人惊讶的调整大小结果。 + 在选择“显式”、“裁剪”或“适合”之前,将源形状与目标形状进行比较。 + 当拍摄对象可能失去额外的背景并且目的地需要严格的框架时,请先裁剪。 + 当原始图像的每个部分都必须保持可见时,请使用“适合”或“灵活”。 + 高档或普通调整大小 + 了解何时添加像素需要人工智能以及何时简单缩放就足够了 + 不要将尺寸与细节混淆 + 正常调整大小通过插值像素来改变尺寸;它无法发明真正的细节。 AI upscale 可以创建看起来更清晰的细节,但速度较慢,并且可能会产生纹理幻觉,因此正确的选择取决于您是否需要兼容性、速度或视觉还原。 + 对缩略图、上传限制、屏幕截图和简单的尺寸更改使用正常调整大小。 + 当小图像或柔和图像需要在较大尺寸下看起来更好时,请使用 AI 高档。 + 在 AI 升级后比较人脸、文本和图案,因为生成的细节可能看起来令人信服,但实际上是不正确的。 + 过滤顺序问题 + 按有意的顺序应用清理、颜色、清晰度和最终压缩 + 构建更清洁的过滤器堆栈 + 过滤器并不总是可以互换的。锐化前的模糊、OCR 前的对比度增强或压缩前的颜色变化可能会从相同的控件产生截然不同的输出。 + 首先裁剪并旋转,以便稍后的滤镜仅对保留的像素起作用。 + 在锐化或风格化效果之前应用曝光、白平衡和对比度。 + 保持最终的调整大小和压缩接近结束,以便预览的细节能够按预期导出。 + 修复背景边缘 + 自动去除后清除光晕、残留阴影、毛发、孔洞和柔和轮廓 + 让切口看起来是故意的 + 自动蒙版通常乍一看不错,但在明亮、黑暗或有图案的背景上却会暴露问题。边缘清理是快速剪切和可重复使用的贴纸、徽标或产品图像之间的区别。 + 在对比背景下预览结果,以显示光晕和缺失的边缘细节。 + 在擦除周围的残留物之前,对头发、角落和透明对象使用恢复。 + 当将剪切图放入设计中时,导出对最终背景颜色的小测试。 + 可读水印 + 在明亮、黑暗、繁忙和裁剪的图像上使标记可见,而不会破坏照片 + 保护图像而不隐藏它 + 在一张图像上看起来不错的水印可能在另一张图像上消失。应为整个批次选择尺寸、不透明度、对比度、位置和重复,而不仅仅是第一次预览。 + 在导出所有文件之前,测试批次中最亮和最暗图像的标记。 + 对大的重复标记使用较低的不透明度,对小角标记使用更强的对比度。 + 保持重要的面孔、文字和产品细节清晰,除非该标记是为了防止重复使用。 + 将一张图像分割成图块 + 按行、列、自定义百分比、格式和质量剪切单个图像 + 准备网格和订购的零件 + 图像分割从一张源图像创建多个输出。它可以按行和列计数进行拆分,调整行或列百分比,并以选定的输出格式和质量保存片段,这对于网格、页面切片、全景图和有序社交帖子非常有用。 + 选择源图像,然后设置所需的行数和列数。 + 当碎片的大小不应该相同时,调整行或列的百分比。 + 在保存之前选择输出格式和质量,以便每个生成的片段都使用相同的导出规则。 + 剪出图像条 + 删除垂直或水平区域并将剩余部分重新合并在一起 + 删除一个区域而不留间隙 + 图像裁剪与普通裁剪不同。它可以删除选定的垂直或水平条带,然后将剩余的图像部分合并在一起。反向模式会保留选定的区域,并且使用两个反向方向会保留选定的矩形。 + 对侧面区域、柱状或中间条带使用垂直切割;对横幅、间隙或行使用水平切割。 + 当您想要保留所选零件而不是删除它时,请在轴上启用反转。 + 剪切后预览边缘,因为当删除的区域跨越重要细节时,合并的内容可能看起来很突兀。 + 选择输出格式 + 根据内容和目标选择 JPEG、PNG、WebP、AVIF、JXL 或 PDF + 让目的地决定格式 + 最佳格式取决于图像包含的内容及其使用位置。当强制采用错误的格式时,照片、屏幕截图、透明资源、文档和动画文件都会以不同的方式失败。 + 当不需要透明度和精确像素时,请使用 JPEG 实现广泛的照片兼容性。 + 使用 PNG 进行清晰的屏幕截图、UI 捕获、透明图形和无损编辑。 + 当紧凑的现代输出很重要并且接收应用程序支持它时,请使用 WebP、AVIF 或 JXL。 + 提取音频封面 + 将音频文件中的嵌入专辑封面或可用媒体帧另存为 PNG 图像 + 从音频文件中提取艺术品 + Audio Covers 使用 Android 媒体元数据从音频文件中读取嵌入的艺术作品。当嵌入的艺术作品不可用时,如果 Android 可以提供媒体框架,检索器可能会退回到媒体框架;如果两者都不存在,则该工具报告没有要提取的图像。 + 打开音频封面并选择一个或多个具有预期封面艺术的音频文件。 + 在保存或共享之前查看提取的封面,尤其是来自流媒体或录制应用程序的文件。 + 如果未找到图像,则音频文件可能没有嵌入封面,或者 Android 无法公开可用的帧。 + 日期和位置元数据 + 当捕获时间很重要但 GPS 或设备数据不应泄露时,决定要保留哪些内容 + 将有用的元数据与私有元数据分开 + 元数据的风险并不相同。捕获日期对于存档和排序非常有用,而 GPS、类似设备序列号的字段、软件标签或所有者字段可以揭示超出预期的信息。 + 当时间顺序对相册、扫描或项目历史记录很重要时,保留日期和时间标签。 + 在公开分享或向陌生人发送图像之前,请删除 GPS 和设备识别标签。 + 当隐私要求严格时,导出样本并再次检查 EXIF。 + 避免文件名冲突 + 当许多文件共享 image.jpg 或 Screenshot.png 等名称时保护批处理输出 + 使每个输出名称唯一 + 当图像来自即时通讯工具、屏幕截图、云文件夹或多台相机时,重复的文件名很常见。良好的模式可以防止意外更换,并使输出可追溯到其来源。 + 当编辑后的副本应该可识别时,请包含原始名称和后缀。 + 处理大型混合批次时添加序列、时间戳、预设或维度。 + 在确认命名模式不会发生冲突之前,请避免覆盖工作流程。 + 保存或分享 + 在持久文件和临时移交到另一个应用程序之间进行选择 + 以正确的意图进行出口 + 保存和共享感觉很相似,但它们解决的问题不同。保存会创建一个您稍后可以找到的文件;共享通过 Android 将生成的结果发送到另一个应用程序,并且可能不会留下持久的本地副本。 + 当输出必须保留在已知文件夹中、进行备份或稍后重复使用时,请使用“保存”。 + 使用共享来快速发送消息、电子邮件附件、社交发布或将结果发送给其他编辑者。 + 当接收应用程序不可靠或失败的共享很难重新创建时,请先保存。 + PDF 页面大小和边距 + 创建文档之前选择纸张尺寸、适合行为和喘息空间 + 使图像页面可打印且可读 + 当图像的形状与所选的纸张尺寸不匹配时,图像可能会变成尴尬的 PDF 页面。页边距、适合模式和页面方向决定内容是否被裁剪、变小、拉伸或舒适阅读。 + 当 PDF 可以打印或提交到表单时,请使用实际纸张尺寸。 + 扫描和屏幕截图时使用边距,这样文本就不会紧贴页面边缘。 + 当源图像具有混合方向时,同时预览纵向和横向页面。 + 清理扫描 + 在 PDF 或 OCR 之前改善纸张边缘、阴影、对比度、旋转和可读性 + 归档前准备页面 + 扫描可以从技术上捕获,但仍然难以阅读。 PDF 导出之前的小清理步骤通常比压缩设置更重要,尤其是对于收据、手写笔记和低光页面。 + 纠正透视并剪掉桌子边缘、手指、阴影和杂乱的背景。 + 仔细增加对比度,使文本变得更清晰,而不会损坏印章、签名或铅笔标记。 + 仅在页面竖直且可读后才运行 OCR,然后手动验证重要数字。 + 压缩、灰度化或修复 PDF + 使用单文件 PDF 操作来获得更轻量、可打印或重建的文档副本 + 选择PDF清理操作 + PDF Tools 包括压缩、灰度转换和修复的单独操作。压缩和灰度主要通过嵌入图像进行工作,而修复则是重建PDF结构;这些都不应被视为对每个文档都有保证的修复。 + 当文档包含图像并且文件大小对共享很重要时,请使用压缩 PDF。 + 当文档应该更容易打印或不需要彩色图像时,请使用灰度。 + 当文档无法打开或内部结构损坏时,在副本上使用修复 PDF,然后验证重建的文件。 + 从 PDF 中提取图像 + 恢复嵌入的 PDF 图像并将提取的结果打包到存档中 + 保存文档中的源图像 + 提取图像扫描 PDF 中的嵌入图像对象,尽可能跳过重复项,并将恢复的图像存储在生成的 ZIP 存档中。它仅提取实际嵌入在 PDF 中的图像,而不是文本、矢量图形或每个可见页面的屏幕截图。 + 当您需要将图片存储在 PDF 中时(例如目录中的照片或扫描的资产),请使用提取图像。 + 当您需要完整页面(包括文本和布局)时,请使用 PDF 转图像或页面提取。 + 如果该工具报告没有嵌入图像,则页面可能是文本/矢量内容,或者图像可能未存储为可提取对象。 + 元数据、扁平化和打印输出 + 编辑文档属性、光栅化页面或有意准备自定义打印布局 + 选择最终文档操作 + PDF 元数据、拼合和打印准备解决了不同的最终阶段问题。元数据控制文档属性,拼合 PDF 将页面光栅化为不可编辑的输出,而打印 PDF 准备带有页面大小、方向、边距和每张页数控制的新布局。 + 当作者、标题、关键字、制作人或隐私清理很重要时使用元数据。 + 当可见页面内容难以作为单独的 PDF 对象进行编辑时,请使用拼合 PDF。 + 当您需要自定义页面大小、方向、边距或每张多页时,请使用打印 PDF。 + 准备 OCR 图像 + 在要求 OCR 读取文本之前使用裁剪、旋转、对比度、降噪和缩放 + 为 OCR 提供更清晰的像素 + OCR 错误通常来自输入图像而不是 OCR 引擎。弯曲的页面、低对比度、模糊、阴影、微小文本和混合背景都会降低识别质量。 + 裁剪到文本区域并旋转图像,使线条在识别前保持水平。 + 仅当字母更易于阅读时才增加对比度或转换为更清晰的黑白。 + 适度向上调整微小文本的大小,但避免过度锐化,以免产生假边缘。 + 检查二维码内容 + 在信任代码之前检查链接、Wi-Fi 凭据、联系人和操作 + 将解码后的文本视为不受信任的输入 + QR 码可以隐藏 URL、联系人名片、Wi-Fi 密码、日历事件、电话号码或纯文本。代码附近的可见标签可能与实际负载不匹配,因此请在对其进行操作之前检查解码的内容。 + 在打开之前检查完整的解码 URL,尤其是缩短或不熟悉的域。 + 请小心使用 Wi-Fi、联系人、日历、电话和短信代码,因为它们可能会触发敏感操作。 + 需要在其他地方验证时先复制内容,而不是立即打开。 + 生成二维码 + 从纯文本、URL、Wi-Fi、联系人、电子邮件、电话、短信和位置数据创建代码 + 构建正确的 QR 有效负载 + 二维码屏幕可以根据输入的内容生成代码并扫描现有的代码。结构化 QR 类型有助于以其他应用程序理解的格式对数据进行编码,例如 Wi-Fi 登录详细信息、联系人卡片、电子邮件字段、电话号码、短信内容、URL 或地理坐标。 + 选择纯文本作为简单注释,或者在代码应作为 Wi-Fi、联系人、URL、电子邮件、电话、短信或位置数据打开时使用结构化类型。 + 在共享生成的代码之前检查每个字段,因为可见预览仅反映您输入的有效负载。 + 当保存的二维码将被打印、公开发布或由其他人使用时,请使用扫描仪对其进行测试。 + 选择校验和模式 + 从文件或文本计算哈希值、比较一个文件或批量检查多个文件 + 验证内容,而不是外观 + 校验和工具具有单独的选项卡,用于文件哈希、文本哈希、将文件与已知哈希进行比较以及批量比较。校验和证明所选算法的逐字节同一性;它并不能证明两个图像仅仅是看起来相似。 + 对文件使用计算并对键入或粘贴的文本数据使用文本哈希。 + 当有人为您提供一个文件的预期校验和时,请使用比较。 + 当许多文件需要一次进行哈希或验证时,请使用批量比较,然后保持所选算法一致。 + 剪贴板隐私 + 使用自动剪贴板功能,不会意外暴露私有复制数据 + 保持复制内容的意图 + 剪贴板自动化很方便,但复制的文本可能包含密码、链接、地址、令牌或私人注释。将基于剪贴板的输入视为一种速度功能,应符合您的习惯和隐私期望。 + 如果工具中意外出现私有文本,请禁用自动剪贴板使用。 + 仅当您故意希望结果避免存储时才使用仅剪贴板保存。 + 处理敏感文本、QR 数据或 Base64 有效负载后清除或替换剪贴板。 + 颜色格式 + 在粘贴到其他地方之前了解 HEX、RGB、HSV、Alpha 和复制的颜色值 + 复制目标期望的格式 + 相同的可见颜色可以有多种书写方式。设计工具、CSS 字段、Android 颜色值或图像编辑器可能需要不同的顺序、Alpha 处理或颜色模型。 + 粘贴到需要紧凑颜色代码的网页、设计或主题字段时,请使用十六进制。 + 当您需要手动调整通道、亮度、饱和度或色调时,请使用 RGB 或 HSV。 + 当透明度很重要时,请单独检查 alpha,因为某些目的地会忽略它或使用不同的顺序。 + 浏览颜色库 + 按名称或十六进制搜索命名颜色并将收藏夹保留在顶部附近 + 查找可重复使用的命名颜色 + 颜色库加载命名的颜色集合,按名称对其进行排序,按颜色名称或十六进制值进行过滤,并存储最喜欢的颜色,以便更容易访问重要的条目。当您需要真实命名的颜色而不是从图像中采样时,它非常有用。 + 当您知道颜色系列时,可以按颜色名称进行搜索,例如红色、蓝色、石板色或薄荷色。 + 当您已经有了数字颜色并想要查找匹配或附近的命名条目时,可以通过十六进制进行搜索。 + 将常用颜色标记为收藏夹,以便它们在浏览时领先于一般库。 + 使用网格渐变集合 + 下载可用的网格渐变资源并共享选定的图像 + 使用远程网格渐变资源 + 网格渐变可以加载远程资源集合、下载缺失的渐变图像、显示下载进度以及共享选定的渐变。可用性取决于网络访问和远程资源存储,因此空列表通常意味着资源尚不可用。 + 当您需要现成的网格渐变图像时,从渐变工具中打开网格渐变。 + 在假设集合为空之前,等待资源加载或下载进度。 + 选择并共享您需要的渐变,或者当您想要手动构建自定义渐变时返回渐变制作器。 + 生成的资产解析 + 在生成渐变、噪点、壁纸、类似 SVG 的艺术或纹理之前选择输出大小 + 按您需要的大小生成 + 生成的图像没有可以依靠的相机原始图像。如果输出太小,以后的缩放可能会软化图案、移动细节或更改资源平铺和压缩的方式。 + 首先设置最终用例的尺寸,例如壁纸、图标、背景、打印或覆盖。 + 对可以在多个布局中裁剪、缩放或重复使用的纹理使用较大尺寸。 + 在大量输出之前导出测试版本,因为程序细节可能会改变压缩的行为方式。 + 导出当前壁纸 + 从设备中检索可用的主页、锁定和内置壁纸 + 保存或共享已安装的壁纸 + 壁纸导出通过系统壁纸 API 读取 Android 公开的壁纸。根据设备、Android 版本、权限和构建变体,主页、锁定或内置壁纸可能会单独提供,也可能会丢失。 + 打开壁纸导出以加载系统允许应用程序读取的壁纸。 + 选择要保存、复制或共享的可用主页、锁定或内置壁纸条目。 + 如果壁纸丢失或功能不可用,这通常是系统、权限或构建变体限制,而不是编辑设置。 + 边角动画节流 + 将颜色复制为 + 十六进制 + 名称 + 肖像抠图模型,用于快速人物剪裁,具有更柔软的头发和边缘处理。 + 使用零 DCE++ 曲线估计进行快速低光增强。 + 用于减少雨纹和潮湿天气伪影的 Restormer 图像恢复模型。 + 文档反扭曲模型可预测坐标网格并将弯曲页面重新映射为更平坦的扫描。 + 动效时长标尺 + 控制应用动画速度:0 关闭动效,1 为默认速度,数值越大动画越慢 + 展示最近使用的工具 + 主屏快捷展示最近使用的工具 + 使用统计 + 探索应用程序打开、工具启动以及您最常用的工具 + 应用程序打开 + 工具打开 + 最后一个工具 + 成功保存 + 连续活动 + 数据已保存 + 顶部格式 + 使用的工具 + %1$d 打开 • %2$s + 尚无使用统计 + 打开一些工具,它们会自动出现在这里 + 您的使用情况统计信息仅存储在您的设备本地。 ImageToolbox 仅使用它们来显示您的个人活动、最喜欢的工具和保存的数据见解 + 中性变体 + 错误 + 编辑器编辑照片图片修饰调整 + 调整大小 转换比例 分辨率 尺寸 宽度 高度 jpg jpeg png webp 压缩 + 压缩大小 重量 字节 mb kb 减少质量 优化 + 作物修剪切割纵横比 + 滤镜效果 色彩校正 调整 lut mask + 绘制油漆刷铅笔注释标记 + 密码 加密 解密 密码 秘密 + 背景去除器擦除去除切口透明阿尔法 + 预览查看器图库检查打开 + 缝合合并组合全景长截图 + url 网络 下载 互联网链接 图像 + 选择器吸管样本十六进制 RGB 颜色 + 调色板颜色样本提取主导 + exif 元数据隐私删除条清洁 GPS 位置 + 比较前后差异 + 限制调整大小最大最小百万像素尺寸夹 + pdf文档页面工具 + ocr文本识别提取扫描 + 渐变背景颜色网格 + 水印徽标文本图章覆盖 + gif 动画 动画转换帧 + apng 动画 动画 png 转换帧 + zip 存档 压缩包 解压文件 + jxl jpeg xl 转换 jpg 图像格式 + svg矢量跟踪转换xml + 格式转换 jpg jpeg png webp heic avif jxl bmp + 文档扫描仪扫描纸张透视裁剪 + qr 条形码扫描仪扫描 + 堆叠叠加平均中值天文摄影 + 分割瓷砖网格切片部分 + 颜色转换 hex rgb hsl hsv cmyk lab + webp转换动画图像格式 + 噪声发生器随机纹理颗粒 + 拼贴网格布局组合 + 标记图层注释叠加编辑 + Base64 编码解码数据 uri + 校验和哈希 md5 sha sha1 sha256 验证 + 网格渐变背景 + exif 元数据 编辑 GPS 日期 相机 + 切割分割网格块瓷砖 + 音频封面专辑艺术音乐元数据 + 壁纸 导出 背景 + ascii 文本艺术字符 + ai ml神经增强高档细分市场 + 颜色库材质调色板命名为颜色 + 着色器GLSL片段效果工作室 + pdf合并合并连接 + pdf分割单独的页面 + pdf旋转页面方向 + pdf 重新排列 重新排序 页面排序 + pdf页码分页 + pdf ocr 文本识别可搜索摘录 + pdf 水印 印章 徽标 文本 + pdf 签名 签名 抽签 + pdf保护密码加密锁 + pdf解锁密码解密删除保护 + pdf 压缩 缩小尺寸 优化 + pdf 灰度 黑白 单色 + pdf 修复 修复 恢复损坏 + pdf 元数据属性 exif 作者标题 + pdf删除删除页面 + pdf 裁剪边距 + pdf拼合注释形成图层 + pdf 提取图像 图片 + pdf zip 存档压缩 + pdf打印打印机 + pdf预览查看器阅读 + 图像转pdf 转换jpg jpeg png 文档 + pdf 到图像页面 导出 jpg png + pdf注释注释删除干净 + 投影 + 齿高 + 水平齿数范围 + 垂直齿数范围 + 顶边 + 右边缘 + 底边 + 左边缘 + 撕边 + 重置使用统计数据 + 工具打开,保存统计数据、条纹、保存的数据和顶部格式将被重置。应用程序打开将保持不变。 + 声音的 + 文件 + 导出配置文件 + 保存当前配置文件 + 删除导出配置文件 + 您想删除导出配置文件“%1$s”吗? + 绘制边框 + 在绘图和擦除画布周围显示细边框 + 波浪形 + 带有柔和波浪边缘的圆形 UI 元素 + + 缺口 + 圆角向内雕刻 + 双切阶梯角 + 占位符 + 使用 {filename} 插入不带扩展名的原始文件名 + 耀斑 + 基础金额 + 环量 + 射线量 + 环宽 + 扭曲视角 + Java 外观和感觉 + 剪切 + X角 + 和角度 + 调整输出大小 + 水滴 + 波长 + 阶段 + 高通 + 彩色面膜 + 铬合金 + 溶解 + 柔软度 + 反馈 + 镜头模糊 + 低输入 + 高投入 + 输出低 + 高输出 + 灯光效果 + 凸块高度 + 凹凸柔软度 + 波纹 + X振幅 + Y振幅 + X波长 + Y波长 + 自适应模糊 + 纹理生成 + 生成各种程序纹理并将其保存为图像 + 纹理类型 + 偏见 + 时间 + 闪耀 + 分散 + 样品 + 角度系数 + 梯度系数 + 网格型 + 距离功率 + 比例 Y + 模糊性 + 基础类型 + 湍流系数 + 缩放 + 迭代 + 戒指 + 拉丝金属 + 焦散 + 蜂窝网络 + 棋盘 + fBm + 大理石 + 等离子体 + 被子 + 木头 + 随机的 + 正方形 + 六角形 + 八角形 + 三角形 + 脊状 + VL 噪声 + 超导噪声 + 最近的 + 还没有最近使用过的过滤器 + 纹理生成器 拉丝金属 焦散 蜂窝棋盘 大理石 等离子被子 木砖 迷彩 细胞云 裂缝 织物 叶子 蜂窝 冰熔岩 星云 纸 铁锈 沙子 烟石 地形 地形 水波纹 + + 伪装 + 细胞 + + 裂缝 + 织物 + 叶子 + 蜂窝 + + 岩浆 + 星云 + + + + 抽烟 + 石头 + 地形 + 地形 + 水波纹 + 高级木材 + 砂浆宽度 + 不规则性 + 斜角 + 粗糙度 + 第一门槛 + 第二门槛 + 第三门槛 + 边缘柔软度 + 边框宽度 + 覆盖范围 + 细节 + 深度 + 分枝 + 水平螺纹 + 垂直螺纹 + 法兹 + 静脉 + 灯光 + 裂缝宽度 + + 流动 + 地壳 + 云密度 + 星星 + 纤维密度 + 纤维强度 + 污渍 + 腐蚀 + 点蚀 + 薄片 + 沙丘频率 + 风角 + 涟漪 + 缕缕 + 静脉鳞片 + 水位 + 山地水平 + 侵蚀 + 雪量 + 行数 + 线宽 + 阴影 + 毛孔颜色 + 砂浆颜色 + 深砖色 + 砖色 + 突出显示颜色 + 深色 + 森林色 + 大地色 + 沙色 + 背景颜色 + 细胞颜色 + 边缘颜色 + 天空的颜色 + 阴影颜色 + 浅色 + 表面颜色 + 变化颜色 + 裂纹颜色 + 经纱颜色 + 纬纱颜色 + 叶色深 + 叶色 + 边框颜色 + 蜜色 + 深色 + 冰色 + 霜色 + 地壳颜色 + + 发光颜色 + 空间色彩 + 紫罗兰色 + 蓝色 + 基色 + 纤维颜色 + 染色颜色 + 金属色 + 深铁锈色 + 铁锈色 + 橙色 + 烟色 + 静脉颜色 + 低地色彩 + 水彩 + 岩石色 + 雪色 + 低色度 + 高显色 + 线条颜色 + 浅色 + + 污垢 + 皮革 + 具体的 + 沥青 + 苔藓 + + 极光 + 浮油 + 水彩 + 抽象流 + 蛋白石 + 大马士革钢 + 闪电 + 天鹅绒 + 墨水大理石花纹 + 全息箔 + 生物发光 + 宇宙漩涡 + 熔岩灯 + 事件视界 + 分形绽放 + 彩色隧道 + 日食日冕 + 奇怪的吸引子 + 磁流体冠 + 超新星 + 鸢尾花 + 孔雀羽毛 + 鹦鹉螺壳 + 环行星 + 叶片密度 + 刀片长度 + + 斑驳 + 团块 + 水分 + 鹅卵石 + 皱纹 + 毛孔 + 总计的 + 裂纹 + 焦油 + 穿 + 斑点 + 纤维 + 火焰频率 + 抽烟 + 强度 + 丝带 + 乐队 + 彩虹色 + 黑暗 + 布鲁姆斯 + 颜料 + 边缘 + + 扩散 + 对称 + 清晰度 + 色彩游戏 + 乳白色 + 层数 + 折叠式的 + 抛光 + 分支机构 + 方向 + 光泽 + 褶皱 + 羽化 + 墨水平衡 + 光谱 + 皱纹 + 衍射 + 武器 + + 核心辉光 + 斑点 + 光盘倾斜 + 地平线尺寸 + 盘宽 + 透镜 + 花瓣 + 卷曲 + 花丝 + 刻面 + 曲率 + 月亮大小 + 电晕尺寸 + 射线 + 钻石戒指 + 叶瓣 + 轨道密度 + 厚度 + 鞋钉 + 鞋钉长度 + 机身尺寸 + 金属色 + 冲击半径 + 外壳宽度 + 被扔掉 + 瞳孔大小 + 虹膜大小 + 颜色变化 + 聚光灯 + 眼睛大小 + 倒刺密度 + 转弯 + 腔体 + 开幕 + 山脊 + 珠光 + 行星大小 + 环倾斜 + 环宽 + 气氛 + 污垢颜色 + 深草色 + 草色 + 笔尖颜色 + 深大地色 + 干色 + 卵石色 + 皮革颜色 + 具体颜色 + 焦油颜色 + 沥青色 + 石材颜色 + 灰尘颜色 + 土壤颜色 + 深苔藓色 + 苔藓色 + 红色 + 核心颜色 + 绿色 + 青色 + 洋红色 + 金色 + 纸张颜色 + 颜料颜色 + 次要颜色 + 第一种颜色 + 第二种颜色 + 粉色 + 深钢色 + 钢色 + 轻钢色 + 氧化物颜色 + 光晕颜色 + 螺栓颜色 + 丝绒色 + 光泽颜色 + 蓝色墨水颜色 + 红色墨水颜色 + 深色墨水颜色 + 银色 + 黄色 + 组织颜色 + 盘面颜色 + 热色 + 镜片颜色 + 外观颜色 + 内色 + 电晕颜色 + 冷色 + 暖色 + 云色 + 火焰颜色 + 羽毛颜色 + 外壳颜色 + 珍珠色 + 星球颜色 + 戒指颜色 + 保存后删除原文件 + 只有成功处理的源文件才会被删除 + 已删除的原始文件:%1$d + 无法删除原始文件:%1$d + 原始文件已删除:%1$d,失败:%2$d + 表单手势 + 允许拖动展开的选项表 + 色度子采样 + 位深度 + 无损 + %1$d 位 + 批量重命名 + 使用文件名模式重命名多个文件 + 批量重命名文件文件名模式序列日期扩展名 + 选择要重命名的文件 + 文件名模式 + 重命名 + 数据来源 + EXIF 拍摄日期 + 文件修改日期 + 文件创建日期 + 当前日期 + 手动日期 + 手动日期 + 手动时间 + 某些文件无法选择所选日期。他们将使用当前日期。 + 输入文件名模式 + 该模式产生无效或过长的文件名 + 该模式会产生重复的文件名 + 所有文件名都已与模式匹配 + 此模式使用不可用于批量重命名的标记 + 名称将保持不变 + 文件重命名成功 + 某些文件无法重命名 + 未获得许可 + 使用不带扩展名的原始文件名,帮助您保持源标识完整。还支持使用 \\o{start:end} 进行切片、使用 \\o{t} 进行音译以及使用 \\o{s/old/new/} 进行替换。 + 自动递增计数器。支持自定义格式:\\c{padding}(例如 \\c{3} -&gt; 001)、\\c{start:step} 或 \\c{start:step:padding}。 + 原始文件所在的父文件夹的名称。 + 原始文件的大小。支持单位:\\z{b}、\\z{kb}、\\z{mb} 或仅 \\z 以供人类可读格式。 + 生成随机的通用唯一标识符 (UUID) 以确保文件名的唯一性。 + 重命名文件无法撤消。在继续之前请确保新名称正确。 + 确认重命名 + 侧面菜单设置 + 菜单规模 + 阿尔法菜单 + 自动白平衡 + 剪裁 + 检查隐藏水印 + 贮存 + 缓存限制 + 当缓存超过此大小时自动清除缓存 + 清理间隔 + 多久检查一次缓存是否应该被清除 + 应用程序启动时 + 1天 + 1周 + 1个月 + 根据选择的限制和间隔自动清除应用程序缓存 + + 允许通过在裁剪区域内拖动来移动整个裁剪区域 + 合并 GIF + 将多个 GIF 剪辑合并为一个动画 + GIF 剪辑顺序 + 撤销 + 反向播放 + 回旋镖 + 向前播放然后向后播放 + 规范化帧大小 + 缩放框架以适合最大的剪辑;关闭以保留其原始比例 + 剪辑之间的延迟(毫秒) + 文件夹 + 选择文件夹及其子文件夹中的所有图像 + 重复查找器 + 查找精确的副本和视觉上相似的图像 + 重复查找器 相似图像 精确副本 照片清理 存储 dHash SHA-256 + 选择图像以查找重复项 + 相似度敏感度 + 精确的副本 + 类似图像 + 选择所有精确的重复项 + 除推荐外全部选择 + 没有发现重复项 + 尝试添加更多图像或提高相似度敏感度 + 无法读取 %1$d 图像 + %1$s • %2$s 可回收 + %1$d 组 • %2$s 可回收 + 已选择 %1$d • %2$s + 此操作无法撤消。 Android 可能会要求您在系统对话框中确认删除。 + 未找到 + 移至开始 + 移至末尾 + \ No newline at end of file diff --git a/core/resources/src/main/res/values-zh-rTW/strings.xml b/core/resources/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000..cea600a --- /dev/null +++ b/core/resources/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,3739 @@ + + + + + 大小 %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曲率 + 曲率 + 色度 + 像素融化 + 最大落差 + AI 工具 + 供多種內建 AI 模型的影像處理工具,支援消除雜訊與去除不自然感等功能 + 壓縮、鋸齒線 + 卡通、廣播壓縮 + 一般壓縮、一般噪音 + 無色卡通噪音 + 快速、一般壓縮、一般噪音、動畫/漫畫/動漫 + 書籍掃描 + 曝光校正 + 最擅長一般壓縮、彩色圖像 + 最擅長一般壓縮、灰度圖像 + 一般壓縮,灰度圖像,更強 + 一般噪聲、彩色圖像 + 一般噪點、彩色圖像、更好的細節 + 一般噪聲、灰度圖像 + 一般噪點,灰度圖像,較強 + 一般噪聲,灰度圖像,最強 + 一般壓縮 + 一般壓縮 + 紋理化、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 页面中删除选定的注释类型,例如链接、注释、突出显示、形状或表单字段 + 超链接 + 文件附件 + 线路 + 弹出窗口 + 邮票 + 形状 + 文字注释 + 文本标记 + 表单字段 + 标记 + 未知 + 注释 + 取消分组 + 使用可配置的颜色和偏移在图层后面添加模糊阴影 + 内容感知失真 + 内存不足,无法完成此操作。请尝试使用较小的图像、关闭其他应用程序或重新启动应用程序。 + 并行工作者 + 这些图像未保存,因为转换后的文件比原始文件大。在删除原始图像之前检查此列表。 + 跳过的文件:%1$s + 应用程序日志 + 查看应用程序日志以发现问题 + 分组工具中的收藏夹 + 当工具按类型分组时,将收藏夹添加为选项卡 + 将最爱显示为最后 + 将最喜爱的工具选项卡移至末尾 + 水平间距 + 垂直间距 + 落后能源 + 使用简单的梯度幅度能量图代替前向能量算法 + 删除遮罩区域 + 接缝将穿过遮罩区域,仅移除选定区域而不是保护它 + 录像带NTSC + 高级 NTSC 设置 + 额外的模拟调谐可提供更强的 VHS 和广播伪影 + 色度溢出 + 胶带磨损 + 追踪 + 亮度涂片 + 铃声 + + 使用领域 + 过滤器类型 + 输入亮度滤波器 + 输入色度低通 + 色度解调 + 相移 + 相位偏移 + 头部切换 + 机头切换高度 + 头部切换偏移 + 头部切换换档 + 中线位置 + 中线抖动 + 跟踪噪声高度 + 跟踪波 + 追踪雪 + 追踪雪的各向异性 + 追踪噪音 + 跟踪噪音强度 + 复合噪声 + 复合噪声频率 + 复合噪声强度 + 复合噪声细节 + 振铃频率 + 振铃功率 + 亮度噪声 + 亮度噪声频率 + 亮度噪声强度 + 亮度噪点细节 + 色度噪声 + 色度噪声频率 + 色度噪声强度 + 色度噪点细节 + 降雪强度 + 雪各向异性 + 色度相位噪声 + 色度相位误差 + 水平色度延迟 + 垂直色度延迟 + VHS 磁带速度 + VHS 色度损失 + VHS 锐化强度 + VHS 锐化频率 + VHS 边波强度 + VHS 边波速度 + VHS边波频率 + VHS 边缘波细节 + 输出色度低通 + 水平缩放 + 垂直缩放 + 比例因子 X + 比例因子Y + 检测常见图像水印并使用 LaMa 修复它们。自动下载检测和修复模型 + 绿盲 + 展开图片 + 使用 YOLO v11 分割的基于对象分割的背景去除器 + 显示线角度 + 绘图时显示当前线旋转角度(以度为单位) + 动画表情符号 + 将可用的表情符号显示为动画 + 保存到原始文件夹 + 将新文件保存在原始文件旁边,而不是所选文件夹 + 水印PDF + 内存不足 + 图像可能太大而无法在此设备上处理,或者系统已耗尽可用内存。尝试降低图像分辨率、关闭其他应用程序或选择较小的文件。 + 调试菜单 + 用于测试应用程序功能的菜单,这不打算出现在生产版本中 + 提供者 + PaddleOCR 需要您的设备上有其他 ONNX 模型。您想下载 %1$s 数据吗? + 普遍的 + 韩国人 + 拉丁 + 东斯拉夫语 + 泰国 + 希腊语 + 英语 + 西里尔 + 阿拉伯 + 梵文 + 泰米尔语 + 泰卢固语 + 着色器 + 着色器预设 + 未选择着色器 + 着色器工作室 + 创建、编辑、验证、导入和导出自定义片段着色器 + 保存的着色器 + 打开保存的着色器,复制、导出、共享或删除 + 新着色器 + 着色器文件 + .itshader JSON + %1$d 参数 + 着色器源码 + 只写 void main() 的主体。使用纹理坐标作为 UV 坐标,使用 inputImageTexture 作为源采样器。 + 功能 + 在 void main() 之前插入可选的辅助函数、常量和结构。制服是从参数生成的。 + 当着色器需要可编辑值时,在此处添加制服。 + 重置着色器 + 当前的着色器草稿将被清除 + 着色器无效 + 不受支持的着色器版本 %1$d。支持的版本:%2$d。 + 着色器名称不能为空。 + 着色器源不能为空。 + 着色器源包含不受支持的字符:%1$s。仅使用 ASCII GLSL 源。 + 参数“%1$s”被声明多次。 + 参数名称不能为空。 + 参数“%1$s”使用保留的 GPUImage 名称。 + 参数“%1$s”必须是有效的 GLSL 标识符。 + 参数“%1$s”具有 %2$s 值类型“%3$s”,应为“%4$s”。 + 参数“%1$s”无法定义布尔值的最小值或最大值。 + 参数“%1$s”的最小值大于最大值。 + 参数“%1$s”默认低于最小值。 + 参数“%1$s”默认大于最大值。 + 着色器必须声明“uniform Sampler2D %1$s;”。 + 着色器必须声明“variing vec2 %1$s;”。 + 着色器必须定义“void main()”。 + 着色器必须将颜色写入gl_FragColor。 + ShaderToy mainImage 着色器不受支持。使用 GPUImage 的 void main() 合约。 + 不支持动画 ShaderToy 统一“iTime”。 + 不支持动画 ShaderToy 统一“iFrame”。 + 不支持 ShaderToy 统一“iResolution”。 + 不支持 ShaderToy iChannel 纹理。 + 不支持外部纹理。 + 不支持外部纹理扩展。 + 不支持 Libretro 着色器参数。 + 仅支持一种输入纹理。删除“uniform %1$s %2$s;”。 + 参数“%1$s”必须声明为“uniform %2$s %1$s;”。 + 参数“%1$s”声明为“uniform %2$s %1$s;”,预期为“uniform %3$s %1$s;”。 + 着色器文件为空。 + 着色器文件必须是包含版本、名称和着色器字段的 .itshader JSON 对象。 + 着色器文件不是有效的 JSON 或与 .itshader 格式不匹配。 + 字段“%1$s”为必填项。 + %1$s 为必填项。 + 不支持 %1$s \\\"%2$s\\\"。支持的类型:%3$s。 + “%2$s”参数需要 %1$s。 + %1$s 必须是有限数。 + %1$s 必须是整数。 + %1$s 必须是 true 或 false。 + %1$s 必须是颜色字符串、RGB/RGBA 数组或颜色对象。 + %1$s 必须使用#RRGGBB 或#RRGGBBAA 格式。 + %1$s 必须包含有效的十六进制颜色通道。 + %1$s 必须包含 3 或 4 个颜色通道。 + %1$s 必须介于 0 到 255 之间。 + %1$s 必须是两个数字的数组或具有 x 和 y 的对象。 + %1$s 必须恰好包含 2 个数字。 + 复制 + 始终清除 EXIF + 即使工具请求保留元数据,在保存时也会删除图像 EXIF 数据 + 幫助和提示 + %1$d 教程 + 開啟工具 + 了解主要工具、匯出選項、PDF 流程、顏色實用程式以及常見問題的修復 + 入門 + 更快地選擇工具、匯入檔案、儲存結果和重複使用設定 + 圖像編輯 + 調整大小、裁剪、過濾、刪除背景、繪製影像和浮水印影像 + 文件和元數據 + 轉換格式、比較輸出、保護隱私和控製檔名 + PDF 和文件 + 建立 PDF、掃描文件、OCR 頁面並準備文件以供共享 + 識別文字、掃描代碼、Base64 編碼、檢查雜湊值和打包文件 + 色彩工具 + 選擇顏色、建立調色板、探索顏色庫並建立漸變 + 創意工具 + 生成 SVG、ASCII 藝術、雜訊紋理、著色器和實驗視覺效果 + 故障排除 + 修復大文件、透明度、共享、匯入和報告問題 + 選擇正確的工具 + 使用類別、搜尋、收藏夾和共享操作從正確的位置開始 + 從最佳切入點開始 + Image Toolbox 有許多重點工具,乍看之下其中許多工具是重疊的。從您想要完成的任務開始,然後選擇控制結果最重要部分的工具:大小、格式、元資料、文字、PDF 頁面、顏色或佈局。 + 當您知道操作名稱(例如調整大小、PDF、EXIF、OCR、QR 或顏色)時,請使用搜尋。 + 當您只知道任務類型時開啟一個類別,然後將常用工具固定為收藏夾。 + 當另一個應用程式將影像共用到影像工具箱時,請從共用表格流程中選擇該工具。 + 了解從選擇輸入到匯出編輯文件的通常流程 + 大多數工具都遵循相同的節奏:選擇輸入、調整選項、預覽,然後儲存或分享。重要的細節是保存會創建一個輸出文件,而共享通常最適合快速切換到另一個應用程式。 + 當選擇器允許時,為單一影像工具選擇一個文件,或為批次工具選擇多個文件。 + 儲存前檢查預覽和輸出控件,尤其是格式、品質和檔案名稱選項。 + 使用共用進行快速切換,或在需要選定資料夾中的永久副本時進行儲存。 + 一次處理許多影像 + 批次工具最適合重複調整大小、轉換、壓縮和命名任務 + 準備可預測的批次 + 當每個輸出都遵循相同的規則時,批次速度最快。這也是最容易犯大錯誤的地方,因此在開始長時間匯出之前選擇命名、品質、元資料和資料夾行為。 + 如果輸出取決於順序,請一起選擇所有相關影像並保持順序。 + 在開始匯出之前設定一次調整大小、格式、品質、元資料和檔案名稱規則。 + 當原始檔案很大或目標格式對您來說是新的時,請先執行小批量測試。 + 快速單一編輯 + 當您需要對一張圖像進行多項簡單更改時,請使用一體化編輯器 + 當圖像需要一小部分更改而不是一次專門操作時,單一編輯非常有用。它通常可以更快地進行快速清理、預覽和匯出最終副本,而無需建立批次工作流程。 + 為需要常見調整、標記、裁剪、旋轉或匯出變更的一張影像開啟「單一編輯」。 + 按照先更改最少像素的順序套用編輯,例如在過濾器之前裁剪和在最終壓縮之前過濾。 + 當您需要批次、精確的檔案大小目標、PDF 輸出、OCR 或元資料清理時,請使用專用工具。 + 使用預設和設定 + 儲存重複的選擇並根據您喜歡的工作流程調整應用程式 + 當您經常匯出相同類型的文件時,預設和設定非常有用。良好的預設可以將重複的手動檢查表變成一次點擊,而全域設定則定義新工具開始時的預設值。 + 為常見輸出大小、格式、品質值或命名模式建立預設。 + 查看預設格式、品質、儲存資料夾、檔案名稱、選擇器和介面行為的設定。 + 在重新安裝應用程式或移動到其他裝置之前備份設定。 + 設定有用的預設值 + 選擇預設格式、品質、縮放模式、調整大小類型和色彩空間一次 + 預設值不僅僅是外觀偏好。他們決定在許多工具中首先出現的格式、品質、調整大小行為、縮放模式和色彩空間,這有助於避免在每次匯出時重新選擇相同的選擇。 + 設定預設影像格式和品質以符合您通常的目標,例如用於照片的 JPEG 或用於 Alpha 的 PNG/WebP。 + 如果您經常匯出嚴格尺寸、社交平台或文件頁面,請調整預設調整大小類型和縮放模式。 + 只有當您的工作流程需要跨顯示器、列印或外部編輯器進行一致的顏色處理時,才會變更色彩空間預設值。 + 選擇器和啟動器 + 當應用程式開啟太多對話方塊或在錯誤位置啟動時使用選擇器設置 + 選擇器和啟動器設定控制 Image Toolbox 是否從主畫面、工具或檔案選擇流程啟動。當您通常從 Android 共享表輸入或希望應用程式更像工具啟動器時,這些選項特別有用。 + 如果系統選擇器隱藏檔案、顯示太多最近的項目或處理雲端檔案效果不佳,請使用照片選擇器設定。 + 僅對您通常從共用輸入或已經知道來源檔案的工具啟用跳過選取。 + 如果您希望 Image Toolbox 的行為更像工具選擇器而不是普通的主螢幕,請查看啟動器模式。 + 圖片來源 + 選擇最適合圖庫、檔案管理器和雲端檔案的選取器模式 + 圖像來源設定會影響應用程式向 Android 請求圖像的方式。最佳選擇取決於您的檔案是否位於系統庫、檔案管理器資料夾、雲端提供者或共享臨時內容的其他應用程式中。 + 當您希望常見圖庫影像獲得最系統整合的體驗時,請使用預設選擇器。 + 如果相簿、資料夾、雲端檔案或非標準格式未出現在您期望的位置,請切換選擇器模式。 + 如果共用檔案在離開來源應用程式後消失,請透過持久選擇器重新開啟它或先儲存本機副本。 + 收藏與分享工具 + 保持主螢幕聚焦並隱藏在共享流程中沒有意義的工具 + 當您每天只使用少數工具時,收藏夾和隱藏共享設定非常有用。他們還透過隱藏無法使用其他應用程式發送的文件類型的工具來保持共享流程的實用性。 + 固定常用工具,以便即使工具按類別分組,也可以輕鬆存取它們。 + 當工具與來自其他應用程式的檔案無關時,隱藏共享流中的工具。 + 如果您希望將收藏放在最後或與相關工具分組,請使用收藏夾排序設定。 + 備份設定 + 將預設、首選項和應用程式行為安全地移至另一個安裝 + 在調整預設、資料夾、檔案名稱規則、工具順序和視覺首選項後,備份和還原非常有用。透過備份,您可以嘗試設定或重新安裝應用程序,而無需從記憶體重建工作流程。 + 變更預設、檔案名稱行為、預設格式或工具排列後建立備份。 + 如果您打算清除資料、重新安裝或移動設備,請將備份儲存在應用程式資料夾之外的某個位置。 + 在處理重要批次之前恢復設置,以便預設和保存行為與您的舊設定相符。 + 調整影像大小和轉換影像 + 更改一張或多張影像的大小、格式、品質和元數據 + 建立調整大小的副本 + 調整大小和轉換是可預測尺寸和更輕的輸出檔案的主要工具。當影像大小、輸出格式和元資料行為同時重要時使用它。 + 選擇一張或多張影像,然後選擇尺寸、比例或檔案大小是否最重要。 + 選擇輸出格式和品質;當必須保留透明度時,請使用 PNG 或 WebP。 + 預覽結果,然後儲存或共用產生的副本,而不會變更原始檔案。 + 按檔案大小調整大小 + 當網站、Messenger 或表單拒絕大量圖片時,設定大小限制 + 符合嚴格的上傳限制 + 當目標僅接受低於特定限制的檔案時,按檔案大小調整大小比猜測品質值更好。該工具可以調整壓縮和尺寸以達到目標,同時嘗試保持結果可用。 + 輸入略低於實際上傳限制的目標大小,以便為元資料和提供者變更留出空間。 + 當目標較小時,首選有損照片格式;即使調整大小後,PNG 也可以保持很大。 + 匯出後檢查面、文字和邊緣,因為過大的尺寸目標可能會引入可見的偽影。 + 限制調整大小 + 僅縮小超出最大寬度、高度或檔案限制的影像 + 避免調整已經很好的圖片大小 + 限制調整大小對於混合資料夾非常有用,其中一些圖像很大而其他圖像已經準備好。它不會強制每個檔案進行相同的調整大小,而是使可接受的影像更接近其原始品質。 + 根據目標設定最大尺寸或限制,而不是盲目地調整每個檔案的大小。 + 當您想要避免輸出變得比來源重時,請啟用「skip-if-larger」樣式行為。 + 將此用於來自不同相機的批次、螢幕截圖或來源大小差異很大的下載。 + 裁剪、旋轉和拉直 + 在匯出或在其他工具中使用影像之前先框住重要區域 + 清理成分 + 首先進行裁切可以讓後面的濾鏡、OCR、PDF 頁面和分享結果更加清晰。 + 開啟“裁切”並選擇要調整的影像。 + 當輸出必須適合貼文、文件或頭像時,請使用自由裁剪或寬高比。 + 儲存前旋轉或翻轉,以便後續工具接收校正後的影像。 + 應用濾鏡和調整 + 建立可編輯的過濾器堆疊並在匯出前預覽結果 + 調整影像外觀 + 濾鏡對於快速校正、風格化輸出以及準備用於 OCR 或列印的影像非常有用。當圖像包含文字或精細細節時,對比度、清晰度和亮度的微小變化可能比重大效果更重要。 + 逐一添加濾鏡,並在每次更改後密切關注預覽。 + 根據任務調整亮度、對比、銳利度、模糊、色彩或遮罩。 + 僅當預覽與您的目標裝置、文件或社交平台相符時才匯出。 + 先預覽 + 在選擇更繁重的編輯、轉換或清理工具之前檢查圖像 + 檢查您實際收到的內容 + 當文件來自聊天、雲端儲存或其他應用程式並且您不確定它包含什麼時,圖像預覽非常有用。首先檢查尺寸、透明度、方向和視覺品質有助於選擇正確的後續工具。 + 當您需要檢查多個影像然後決定如何處理它們時,請開啟影像預覽。 + 尋找旋轉問題、透明區域、壓縮偽影和意外的大尺寸。 + 只有在您知道需要修復哪些內容後,才可使用調整大小、裁剪、比較、EXIF 或背景去除器。 + 刪除或恢復背景 + 自動擦除背景或使用復原工具手動細化邊緣 + 保留主題,刪除其餘內容 + 當您將自動選擇與仔細的手動清理結合時,背景去除效果最佳。最終的匯出格式與蒙版一樣重要,因為即使預覽看起來正確,JPEG 也會展平透明區域。 + 如果主體與背景清晰分離,則開始自動移除。 + 放大並在擦除和恢復之間切換以修復頭髮、角落、陰影和小細節。 + 當您需要最終檔案的透明度時,另存為 PNG 或 WebP。 + 繪製、標記和浮水印 + 添加箭頭、註釋、突出顯示、簽名或重複的水印疊加 + 添加可見訊息 + 標記工具有助於解釋影像,而浮水印有助於品牌化或保護匯出的檔案。 + 當您需要手繪筆記、形狀、反白或快速註釋時,請使用「繪圖」。 + 當相同的文字或圖像標記應一致地出現在輸出上時,請使用浮水印。 + 匯出前檢查不透明度、位置和比例,以便標記可見但不會分散注意力。 + 繪製預設值 + 設定線寬、顏色、路徑模式、方向鎖定和擴大機以加快標記速度 + 讓繪畫感覺可預測 + 當您經常註釋螢幕截圖、標記文件或簽署圖像時,繪圖設定非常有用。預設設定可減少設定時間,而擴大機和方向鎖定使小螢幕上的精確編輯變得更加容易。 + 將預設線寬和繪製顏色設定為您最常用於箭頭、反白或簽名的值。 + 當您通常繪製相同類型的筆畫、形狀或標記時,請使用預設路徑模式。 + 當手指輸入導緻小細節難以準確放置時,啟用擴大機或方向鎖定。 + 多圖像佈局 + 在安排螢幕截圖、掃描或比較條之前選擇正確的多影像工具 + 使用正確的佈局工具 + 這些工具聽起來很相似,但每一個都針對不同類型的多影像輸出進行了調整。首先選擇正確的一個可以避免與專為不同結果設計的佈局控制項發生衝突。 + 使用 Collage Maker 設計佈局,在一個合成中包含多個圖像。 + 當影像應成為一條長的垂直或水平條帶時,請使用影像拼接或堆疊。 + 當一張大圖像需要變成幾張較小的圖像時,請使用圖像分割。 + 轉換影像格式 + 建立 JPEG、PNG、WebP、AVIF、JXL 和其他副本,無需手動編輯像素 + 選擇作業的格式 + 不同的格式更有利於照片、螢幕截圖、透明度、壓縮或相容性。當您了解目標應用程式支援什麼以及來源是否包含您想要保留的 Alpha、動畫或元資料時,轉換是最安全的。 + 使用 JPEG 來相容於照片,使用 PNG 來表示無損影像和 Alpha,使用 WebP 來進行緊湊共享。 + 當格式支援時調整品質;較低的值會減少尺寸,但會增加偽影。 + 保留原始文件,直到您驗證轉換後的文件在目標應用程式中正確開啟。 + 檢查 EXIF 和隱私 + 在共享敏感照片之前查看、編輯或刪除元數據 + 控制隱藏影像數據 + EXIF 可以包含相機資料、日期、位置、方向以及影像中不可見的其他詳細資訊。在公開分享、支援報告、市場列表或任何可能洩露私人場所的照片之前,值得檢查。 + 在分享可能包含位置或私人裝置資訊的照片之前,請開啟 EXIF 工具。 + 刪除敏感欄位或編輯不正確的標籤,例如日期、方向、作者或相機資料。 + 當隱私很重要時,請保存新副本並共享該副本而不是原始副本。 + 自動 EXIF 清理 + 預設刪除元數據,同時決定是否應保留日期 + 將隱私設定為預設設定 + 當您希望自動進行隱私清理而不是每次匯出時都記住它時,EXIF 設定群組非常有用。保留日期時間是一個單獨的決定,因為日期對於排序很有用,但對於共享很敏感。 + 當您的大部分匯出內容用於公開或半公開分享時,請啟用始終清晰的 EXIF。 + 僅當保留捕獲時間比刪除每個時間戳記更重要時才啟用保留日期時間。 + 對需要保留某些欄位並刪除其他欄位的一次性檔案使用手動 EXIF 編輯。 + 控製檔名 + 使用前綴、後綴、時間戳、預設、校驗和和序號 + 讓出口更容易找到 + 良好的檔案名稱模式有助於對批次進行排序並防止意外覆蓋。當匯出返回到來源資料夾時,檔案名稱設定特別有用,因為相似的名稱很快就會變得令人困惑。 + 在「設定」中設定檔案名稱前綴、後綴、時間戳記、原始名稱、預設資訊或序列選項。 + 對順序重要的批次(例如頁面、框架或拼貼)使用序號。 + 僅當您確定替換現有文件對於當前資料夾是安全的時才啟用覆蓋。 + 覆蓋文件 + 只有當您的工作流程故意具有破壞性時,才將輸出替換為符合的名稱 + 謹慎使用覆蓋模式 + 覆蓋檔案改變了命名衝突的處理方式。它對於重複匯出到同一資料夾很有用,但如果您的檔案名稱模式再次產生相同的名稱,它也可以替換先前的結果。 + 僅對需要替換舊生成檔案且可恢復的資料夾啟用覆蓋。 + 匯出原件、用戶端檔案、一次性掃描或任何沒有備份的內容時,保持覆蓋停用狀態。 + 將覆蓋與有意的檔案名稱模式結合,以便僅替換預期的輸出。 + 檔案名稱模式 + 組合原始名稱、前綴、後綴、時間戳、預設、縮放模式和校驗和 + 建構解釋輸出的名稱 + 檔案名稱模式設定可以將匯出的檔案變成有關其發生情況的有用歷史記錄。這對於批次來說很重要,因為資料夾視圖可能是唯一可以區分原始大小、預設、格式和處理順序的地方。 + 當您需要可讀名稱進行手動排序時,請使用原始檔案名稱、前綴、後綴和時間戳記。 + 當輸出必須記錄其產生方式時,請新增預設、縮放模式、檔案大小或校驗和資訊。 + 避免為文件需要與原始文件或頁面順序保持配對的工作流程提供隨機名稱。 + 選擇文件的儲存位置 + 透過設定資料夾、原始資料夾儲存和一次性儲存位置來避免遺失匯出 + 保持產出可預測 + 當您處理許多文件或共享來自雲端提供者的文件時,保存行為最為重要。資料夾設定決定輸出是否收集在一個已知位置、顯示在原件旁邊或暫時轉到其他位置以執行特定任務。 + 如果您始終希望匯出到一個位置,請設定預設儲存資料夾。 + 使用儲存到原始資料夾進行清理工作流程,其中輸出應保留在來源檔案附近。 + 當一項任務需要不同的目標而不更改預設值時,請使用一次性儲存位置。 + 一次保存資料夾 + 將下一個匯出傳送到臨時資料夾,而不更改您的正常目的地 + 將特殊工作分開 + 一次性保存位置對於臨時專案、用戶端資料夾、清理會話或不應與常規 Image Toolbox 資料夾混合的匯出非常有用。它提供了一個短暫的目的地,而無需重寫您的預設。 + 在開始屬於正常匯出位置之外的任務之前,請選擇一個一次性資料夾。 + 將其用於短項目、共用資料夾或輸出必須保持分組在一起的批次。 + 作業完成後返回預設資料夾,以便以後的匯出不會意外到達臨時位置。 + 平衡品質和文件大小 + 了解何時更改尺寸、格式或質量,而不是猜測 + 故意縮小文件 + 檔案大小取決於影像尺寸、格式、品質、透明度和內容複雜性。如果文件仍然太重,更改尺寸通常比反覆降低品質更有效。 + 當影像大於目標可以顯示的尺寸時,請先調整尺寸。 + 僅針對有損格式降低質量,並在匯出後檢查文字、邊緣、漸層和麵。 + 當品質變化不夠時切換格式,例如用於共享的 WebP 或用於清晰透明資源的 PNG。 + 使用動畫格式 + 當檔案包含幀而不是一張點陣圖時,使用 GIF、APNG、WebP 和 JXL 工具 + 不要將每張影像都視為靜止的 + 動畫格式需要能夠理解幀、持續時間和相容性的工具。 + 當您需要對這些格式進行幀級操作時,請使用 GIF 或 APNG 工具。 + 當您需要現代壓縮或動畫 WebP 輸出時,請使用 WebP 工具。 + 在共享之前預覽動畫時序,因為某些平台會轉換或拼合動畫圖像。 + 比較和預覽輸出 + 在保留匯出之前檢查變更、檔案大小和視覺差異 + 分享前先驗證 + 比較工具有助於儘早發現壓縮偽影、錯誤的顏色或不需要的編輯。 + 當您想要並排檢查兩個版本時,請開啟「比較」。 + 放大最容易錯過問題的邊緣、文字、漸層和透明區域。 + 如果輸出太重或模糊,請回到先前的工具並調整品質或格式。 + 使用 PDF 工具中心 + 合併、分割、旋轉、刪除頁面、壓縮、保護、OCR 和浮水印 PDF 文件 + 選擇 PDF 操作 + PDF 中心將文件操作分組到一個入口點後面,以便您可以選擇準確的操作。當文件已經是 PDF 時從這裡開始,當您的來源仍然是一組影像時,請使用影像轉 PDF 或文件掃描器。 + 開啟 PDF Tools 並選擇符合您目標的操作。 + 選擇來源 PDF 或影像,然後查看頁面順序、旋轉、保護或 OCR 選項。 + 儲存產生的 PDF 並開啟一次以確認頁數、順序和可讀性。 + 將圖像轉換為 PDF + 將掃描件、螢幕截圖、收據或照片合併到一份可共享文件中 + 建立一個乾淨的文檔 + 當影像經過一致的裁剪、排序和尺寸調整後,它們會變成更好的 PDF 頁面。將圖像清單視為頁面堆疊:每次旋轉、邊距和頁面大小選擇都會影響最終文件的可讀性。 + 當頁面邊緣、陰影或方向不正確時,首先裁剪或旋轉影像。 + 依照預期的頁面順序選擇影像,並調整頁面大小或邊距(如果工具提供)。 + 匯出 PDF,然後在發送之前檢查所有頁面是否可讀。 + 掃描文件 + 捕獲紙本頁面並準備用於 PDF、OCR 或共享 + 捕捉可讀頁面 + 文件掃描在平面頁面、良好的照明和正確的透視下效果最佳。在 OCR 或 PDF 匯出之前進行乾淨掃描可以節省時間,因為文字辨識和壓縮都取決於頁面品質。 + 將文件放置在光線充足且陰影最少的對比表面上。 + 手動調整偵測到的角落或裁剪,使頁面乾淨地填滿框架。 + 匯出為圖像或 PDF,然後執行 OCR(如果您需要可選文字)。 + 保護或解鎖 PDF 文件 + 謹慎使用密碼並儘可能保留可編輯的來源副本 + 安全處理 PDF 訪問 + 保護工具對於受控共享很有用,但密碼很容易遺失且難以恢復。保護分發的副本,單獨保留未受保護的來源文件,並在刪除任何內容之前驗證結果。 + 保護 PDF 的副本,而不是唯一的來源文件。 + 在共享受保護的文件之前,將密碼儲存在安全的地方。 + 僅對允許編輯的文件使用解鎖,然後驗證解鎖的副本是否正確開啟。 + 組織 PDF 頁面 + 合併、拆分、重新排列、旋轉、裁剪、刪除頁面以及有意添加頁碼 + 裝飾前修復文件結構 + PDF 頁面工具最容易使用,其使用順序與準備紙質文件的順序相同:收集頁面、刪除錯誤的頁面、按順序排列、糾正方向,然後添加最終詳細信息,例如頁碼或水印。 + 當文件仍分為多個文件時,請先使用合併或影像轉 PDF。 + 在新增頁碼、簽名或浮水印之前重新排列、旋轉、裁剪或刪除頁面。 + 在結構編輯後預覽最終的 PDF,因為稍後可能很難注意到缺少或旋轉的頁面。 + PDF註釋 + 當查看者以不同方式顯示評論和標記時,刪除註釋或將其拼合 + 控制仍然可見的內容 + 註釋可以是註釋、突出顯示、繪圖、表單標記或其他額外的 PDF 物件。有些檢視者以不同的方式顯示它們,因此刪除或拼合它們可以使共用文件更容易預測。 + 當共用副本中不應包含註解、反白或審閱標記時,請刪除註解。 + 當可見標記應保留在頁面上但不再像可編輯註釋時進行扁平化。 + 保留原始副本,因為刪除或展平註釋可能很難逆轉。 + 辨識文字 + 使用可選的 OCR 引擎和語言從圖像中提取文本 + 獲得更好的 OCR 結果 + OCR 準確度取決於影像清晰度、語言資料、對比度和頁面方向。最好的結果通常來自於先準備圖像:裁切掉雜訊、垂直旋轉、增加可讀性,然後辨識文字。 + 將圖像裁剪到文字區域並在識別前將其垂直旋轉。 + 選擇正確的語言並在應用程式請求時下載語言資料。 + 複製或分享識別的文本,然後手動檢查名稱、數字和標點符號。 + 選擇 OCR 語言數據 + 透過匹配腳本、語言和下載的 OCR 模型來提高識別率 + 將 OCR 與文字匹配 + 錯誤的 OCR 語言可能會讓好的照片看起來像是糟糕的辨識結果。語言資料對於腳本、標點符號、數字和混合文件很重要,因此請選擇圖像中顯示的語言,而不是手機 UI 的語言。 + 選擇圖像中顯示的語言或腳本,而不僅僅是手機語言。 + 在離線工作或處理批次之前下載缺少的資料。 + 對於混合語言文檔,首先運行最重要的語言並手動檢查名稱和號碼。 + 可搜尋的 PDF + 將 OCR 文字寫回文件中,以便可以搜尋和複製掃描的頁面 + 將掃描件轉換為可搜尋文檔 + OCR 的功能不僅僅是將文字複製到剪貼簿。當您將識別的文字寫入文件或可搜尋的 PDF 時,頁面圖像仍然可見,而文字變得更易於搜尋、選擇、索引或存檔。 + 使用可搜尋的 PDF 輸出來掃描文檔,使其看起來仍像原始頁面一樣。 + 當您只需要提取文字而不關心頁面圖像時,請使用寫入檔案。 + 手動檢查重要的數字、名稱和表格,因為 OCR 文字可以搜索,但仍然不完善。 + 掃描二維碼 + 從相機輸入或儲存的影像(如果可用)中讀取 QR 內容 + 安全地讀取程式碼 + QR 碼可以包含連結、Wi-Fi 資料、聯絡人、文字或其他有效負載。 + 打開掃描二維碼並保持程式碼清晰、平整並完全位於框架內。 + 在開啟連結或複製敏感資料之前查看解碼的內容。 + 如果掃描失敗,請改善照明、裁切程式碼或嘗試更高解析度的來源影像。 + 使用 Base64 和校驗和 + 將圖像編碼為文本,將文本解碼回圖像,並驗證文件完整性 + 在文件和文字之間移動 + Base64 對於小型影像有效負載非常有用,而校驗和有助於確認檔案沒有變更。這些工具在技術工作流程、支援報告、文件傳輸以及需要將二進位檔案暫時轉換為文字的地方最有幫助。 + 當工作流程需要文字而不是二進位時,使用 Base64 工具對小圖像進行編碼。 + 僅從可信任來源解碼 Base64 並在儲存前驗證預覽。 + 當您需要比較匯出的檔案或確認上傳/下載時,請使用校驗和工具。 + 打包或保護數據 + 使用 ZIP 和密碼工具捆綁檔案或轉換文字數據 + 將相關文件放在一起 + 當多個輸出應作為一個檔案傳輸時,打包工具會很有幫助。當您需要傳送完整的結果集時,它們也適合將產生的影像、PDF、日誌和元資料保存在一起。 + 當您需要一起傳送許多匯出的影像或文件時,請使用 ZIP。 + 僅當您了解所選方法並可以安全地儲存所需密鑰時才使用密碼工具。 + 在刪除來源檔案之前測試建立的存檔或轉換後的文字。 + 名稱中的校驗和 + 當唯一性和完整性比可讀性更重要時,使用基於校驗和的檔案名 + 按內容命名文件 + 當匯出可能具有重複的可見名稱或需要證明兩個檔案相同時,校驗和檔案名稱非常有用。它們對於手動瀏覽不太友好,因此將它們用於技術檔案和驗證工作流程。 + 當內容身分比人類可讀的標題更重要時,啟用校驗和作為檔案名稱。 + 將其用於存檔、重複資料刪除、可重複匯出或可再次上傳和下載的檔案。 + 當人們仍然需要了解文件代表什麼時,將校驗和名稱與資料夾或元資料配對。 + 從影像中選擇顏色 + 採樣像素、複製顏色代碼以及在調色板或設計中重複使用顏色 + 取樣準確的顏色 + 顏色選擇對於匹配設計、提取品牌顏色或檢查圖像值非常有用。縮放很重要,因為抗鋸齒、陰影和壓縮可能會使相鄰像素與您期望的顏色略有不同。 + 開啟從影像中選取顏色並選擇具有所需顏色的來源影像。 + 在採樣小細節之前放大,這樣附近的像素就不會影響結果。 + 依照目標所需的格式複製顏色,例如 HEX、RGB 或 HSV。 + 建立調色板並使用顏色庫 + 提取調色板、瀏覽保存的顏色並保持一致的視覺系統 + 建立可重複使用的調色板 + 調色板有助於保持導出的圖形、浮水印和繪圖在視覺上的一致性。當您重複註釋螢幕截圖、準備品牌資產或需要在多個工具中使用相同的顏色時,它們特別有用。 + 當您已經知道值時,從圖像生成調色板或手動添加顏色。 + 命名或組織重要的顏色,以便您以後可以再次找到它們。 + 在繪圖、浮水印、漸層、SVG 或外部設計工具中使用複製的值。 + 創建漸變 + 為背景、佔位符和視覺實驗建立漸變影像 + 控制顏色過渡 + 當您需要產生的圖像而不是編輯現有圖像時,漸變工具非常有用。它們可以成為乾淨的背景、佔位符、壁紙、蒙版或外部設計工具的簡單資源。 + 選擇漸層類型並先設定重要的顏色。 + 針對目標用例調整方向、停止點、平滑度和輸出大小。 + 以與目標相符的格式匯出,通常為 PNG 以產生乾淨的圖形。 + 顏色可訪問性 + 當 UI 難以閱讀時,使用動態顏色、對比和色盲選項 + 讓顏色更容易使用 + 顏色設定會影響舒適度、可讀性以及掃描控制的速度。如果工具晶片、滑桿或選定狀態感覺難以區分,主題和輔助功能選項可能比簡單地更改暗模式更有用。 + 當您希望應用程式遵循當前的壁紙調色板時,請嘗試動態顏色。 + 當按鈕和表面不夠突出時,增加對比或更改方案。 + 如果某些狀態或重音難以區分,請使用色盲方案選項。 + 阿爾法背景 + 控制透明 PNG、WebP 和類似輸出周圍使用的預覽或填滿顏色 + 了解透明預覽 + Alpha 格式的背景顏色可以讓透明影像更易於檢查,但重要的是要知道您是要更改預覽/填充行為還是要展平最終結果。這對於貼紙、徽標和背景刪除的圖像很重要。 + 當透明像素必須在匯出時保留時,請先使用支援 Alpha 的格式,例如 PNG 或 WebP。 + 當應用程式表面難以看到透明邊緣時,請調整背景顏色設定。 + 匯出一個小測試並在另一個應用程式中重新開啟它,以確認透明度或所選填充是否已儲存。 + AI模型選擇 + 根據影像類型和缺陷選擇模型,而不是先選擇最大的模型 + 將模型與損壞相匹配 + AI Tools 可以下載或匯入不同的 ONNX/ORT 模型,並且每個模型都針對特定類型的問題進行訓練。當用於錯誤的影像類型時,JPEG 區塊、動畫線清理、去噪、背景去除、著色或升級的模型可能會產生更糟糕的結果。 + 下載前請閱讀型號說明;選擇命名您實際問題的模型,例如壓縮、雜訊、半色調、模糊或背景去除。 + 測試新影像類型時從較小或更具體的模型開始,然後僅在結果不夠好時才與較重的模型進行比較。 + 僅保留您真正使用的模型,因為下載和匯入的模型會佔用大量儲存空間。 + 了解模範家庭 + 模型名稱通常暗示其目標:RealESRGAN 風格的高檔模型、SCUNet 和 FBCNN 模型清除雜訊或壓縮、背景模型建立掩模、著色器為灰階輸入添加顏色。匯入的自訂模型可能很強大,但它們應該匹配預期的輸入/輸出形狀並使用支援的 ONNX 或 ORT 格式。 + 當您需要更多像素時,請使用放大器;當影像有顆粒感時,請使用降噪器;當壓縮塊或條帶是主要問題時,請使用偽影去除器。 + 僅使用背景模型來分離主體;它們與一般的物件移除或影像增強不同。 + 僅從您信任的來源匯入自訂模型,並在使用重要檔案之前在小圖像上測試它們。 + AI參數 + 調整區塊大小、重疊和並行工作線程以平衡品質、速度和內存 + 平衡速度與穩定性 + 區塊大小控制影像片段在被模型處理之前的大小。重疊混合相鄰區塊以隱藏邊界。並行工作線程可以加快處理速度,但每個工作線程都需要內存,因此高值可能會使大圖像在 RAM 有限的手機上不穩定。 + 當您的裝置可靠地處理模型且影像不大時,請使用較大的區塊以獲得更平滑的上下文。 + 當區塊邊界變得可見時增加重疊,但請記住,更多的重疊意味著更多的重複工作。 + 僅在測試穩定後才提高並行工作人員;如果處理速度減慢、設備發熱或崩潰,請先減少人員數量。 + 避免塊偽影 + 分塊讓應用程式可以處理比模型可以輕鬆處理的圖像更大的圖像。代價是每個圖塊的周圍環境較少,因此低重疊或太小的區塊可能會在相鄰區域之間產生可見的邊界、紋理變化或不一致的細節。 + 如果您看到矩形邊框、重複的紋理變化或處理區域之間的細節跳躍,請增加重疊。 + 如果製程在生成輸出之前失敗,請減少區塊大小,尤其是在大型影像或重型模型上。 + 在處理完整影像之前儲存小幅裁切測試,以便您可以快速比較參數。 + 人工智慧穩定性 + 當 AI 處理崩潰或凍結時,降低區塊大小、工作線程和來源分辨率 + 從繁重的人工智慧工作中恢復過來 + 人工智慧處理是應用程式中最繁重的工作流程之一。崩潰、會話失敗、凍結或應用程式突然重新啟動通常意味著模型、來源解析度、區塊大小或並行工作執行緒數對於當前裝置狀態要求過高。 + 如果應用程式崩潰或無法開啟模型會話,請降低並行工作線程和區塊大小,然後再次嘗試相同的圖像。 + 當目標不需要原始解析度時,在 AI 處理之前調整非常大的影像大小。 + 當記憶體壓力持續增加時,關閉其他繁重的應用程序,使用較小的模型,或一次處理較少的檔案。 + 診斷模型故障 + 人工智慧運行失敗並不總是意味著圖像不好。模型檔案可能不完整、不受支援、記憶體太大或與操作不符。當應用程式報告失敗的會話時,將其視為首先簡化模型和參數的訊號。 + 如果模型在下載後立即失敗或表現為檔案已損壞,請刪除並重新下載模型。 + 在指責導入的自訂模型之前嘗試內建的可下載模型,因為導入的模型可能具有不相容的輸入。 + 如果您需要報告崩潰或會話錯誤,請在重現故障後使用應用程式日誌。 + 在 Shader Studio 中進行實驗 + 使用 GPU 著色器效果進行進階影像處理和自訂外觀 + 小心使用著色器 + Shader Studio 功能強大,但著色器程式碼和參數需要小的、可測試的變更。將其視為實驗工具:保持工作基線,一次更改一件事,並在信任預設之前使用不同的影像尺寸進行測試。 + 在新增參數之前,從已知的工作著色器或簡單效果開始。 + 一次更改一個參數或程式碼區塊並檢查預覽是否有錯誤。 + 僅在對幾種不同的影像尺寸進行測試後才匯出預設。 + 製作 SVG 或 ASCII 藝術作品 + 產生類似向量的資源或基於文字的圖像以進行創意輸出 + 選擇創意輸出類型 + SVG 和 ASCII 工具服務於不同的目標:可縮放圖形或文字樣式渲染。兩者都是創造性的轉換,因此以最終尺寸預覽比從微小的縮圖判斷結果更重要。 + 當結果需要乾淨地縮放或在設計工作流程中重複使用時,請使用 SVG Maker。 + 當您想要圖像的風格化文字表示時,請使用 ASCII Art。 + 以最終尺寸預覽,因為小細節可能會在生成的輸出中消失。 + 生成噪音紋理 + 為背景、蒙版、疊加或實驗創建程式紋理 + 調整程式輸出 + 雜訊生成會根據參數創建新影像,因此微小的變化可能會產生截然不同的結果。它對於紋理、蒙版、覆蓋、佔位符或使用詳細圖案測試壓縮非常有用。 + 在調整精細細節之前選擇雜訊類型和輸出大小。 + 調整比例、強度、顏色或種子,直到圖案適合目標用途。 + 如果稍後需要重新建立紋理,請儲存有用的結果並記下參數。 + 標記項目 + 當註釋需要在關閉應用程式後保持可編輯狀態時,請使用專案文件 + 保持分層編輯可重複使用 + 當螢幕截圖、圖表或說明圖像稍後可能需要更改時,標記項目文件非常有用。匯出的 PNG/JPEG 檔案是最終影像,而專案檔案保留可編輯圖層和編輯狀態以供將來調整。 + 當註釋、標註或版面元素應保持可編輯時,請使用標記層。 + 如果您希望進行更多修訂,請在匯出最終影像之前儲存或重新開啟專案檔案。 + 僅當您準備好共享扁平的最終版本時才匯出普通圖像。 + 加密檔案 + 在刪除任何來源副本之前使用密碼保護檔案並測試解密 + 小心處理加密輸出 + 密碼工具可以使用基於密碼的加密來保護文件,但它們不能替代記住金鑰或保留備份。加密對於傳輸和儲存很有用,而當主要目標是捆綁多個輸出時,ZIP 會更好。 + 首先加密副本並保留原始文件,直到您測試使用您儲存的密碼可以解密。 + 使用密碼管理器或其他安全的地方存放金鑰;該應用程式無法猜測您遺失的密碼。 + 僅與也擁有密碼並知道應使用哪種工具或方法解密的人員共用加密檔案。 + 整理工具 + 分組、排序、搜尋和固定工具,使主畫面符合您的實際工作流程 + 使主螢幕更快 + 一旦您知道最常使用哪些工具,工具排列設定就值得調整。分組有助於探索,自訂順序有助於增強記憶力,即使完整清單變大,收藏夾也能讓日常工具保持可用。 + 在學習應用程式時或當您喜歡按任務類型組織的工具時,請使用分組模式。 + 當您已經了解常用工具並且想要更少的捲軸時,請切換到自訂順序。 + 將搜尋設定和收藏夾結合使用,以獲取您記住名稱但不希望始終可見的稀有工具。 + 處理大文件 + 避免導出緩慢、記憶體壓力和意外的大輸出 + 減少出口前的工作 + 非常大的圖像、長批次和高品質格式可能會佔用更多記憶體和時間。最快的修復通常是在最繁重的操作之前減少工作量,而不是等待相同的超大輸入完成。 + 在應用重型濾鏡、OCR 或 PDF 轉換之前調整巨大影像的大小。 + 首先使用較小的批次確認設置,然後使用相同的預設處理其餘的。 + 當輸出檔案大於預期時,降低品質或更改格式。 + 快取和預覽 + 了解大量會話後產生的預覽、快取清理和儲存使用情況 + 保持儲存和速度平衡 + 預覽產生和快取可以使重複瀏覽速度更快,但繁重的編輯工作階段可能會留下臨時資料。當應用程式感覺緩慢、儲存緊張或預覽在多次實驗後看起來過時時,快取設定會有所幫助。 + 瀏覽大量圖像時保持預覽啟用比最小化臨時儲存更重要。 + 在大批量、失敗的實驗或使用許多高解析度檔案的長時間會話後清除快取。 + 如果您喜歡儲存清理而不是在啟動之間保持預覽正常,請使用自動快取清除。 + 調整螢幕行為 + 使用全螢幕、安全模式、亮度和系統列選項來專注工作 + 使介面適合情況 + 當您橫向編輯、呈現私人影像或需要一致的亮度時,螢幕設定會有所幫助。正常使用時不需要它們,但它們可以解決尷尬的情況,例如二維碼檢查、私人預覽或狹窄的景觀編輯。 + 當編輯空間比導覽鑲邊更重要時,請使用全螢幕或系統列設定。 + 當私人圖像不應出現在螢幕截圖或應用程式預覽中時,請使用安全模式。 + 對難以檢查暗影像或二維碼的工具使用亮度強制。 + 保持透明度 + 防止透明背景變白、變黑或變平 + 使用 alpha 感知輸出 + 只有當所選工具和輸出格式支援 Alpha 時,透明度才會存在。如果匯出的背景變成白色或黑色,問題通常是輸出格式、填滿/背景設定或拼合影像的目標應用程式。 + 匯出已移除背景的圖像、貼紙、標誌或疊加層時,請選擇 PNG 或 WebP。 + 避免使用 JPEG 進行透明輸出,因為它不會儲存 Alpha 通道。 + 如果預覽顯示填滿背景,請檢查與 Alpha 格式的背景顏色相關的設定。 + 修復共享和導入問題 + 當文件未顯示或無法正確開啟時,使用選擇器設定和支援的格式 + 將文件獲取到應用程式中 + 導入問題通常是由提供者權限、不受支援的格式或選擇器行為引起的。從雲端應用程式和通訊工具共享的檔案可能是臨時的,因此選擇持久性來源對於較長的編輯來說可能更可靠。 + 嘗試透過系統選擇器而不是最近文件快捷方式開啟文件。 + 如果某個工具不支援某種格式,請先轉換或開啟更具體的工具。 + 如果共用流程或直接工具開啟的行為與預期不同,請檢查影像選擇器和啟動器設定。 + 退出確認 + 避免在意外發生手勢、後按或工具切換時遺失未儲存的編輯 + 保護未完成的工作 + 當您使用手勢導航、編輯大檔案或經常在應用程式之間切換時,工具退出確認非常有用。它在離開工具之前添加了一個小暫停,因此未完成的設定和預覽不會意外丟失。 + 如果在匯出之前意外的後退手勢曾經關閉過工具,請啟用確認。 + 如果您主要進行快速一步轉換並喜歡更快的導航,請保持停用狀態。 + 將其與預設一起使用可實現較長的工作流程,而重建設定會很煩人。 + 報告問題時使用日誌 + 在提出問題或聯繫開發人員之前收集有用的詳細信息 + 報告帶有上下文的問題 + 包含步驟、應用程式版本、輸入類型和日誌的清晰報告更容易診斷。日誌在重現問題後最有用,因為它們使周圍的上下文保持新鮮。 + 請注意工具名稱、確切的操作以及它是針對一個檔案還是每個檔案發生。 + 重現問題後開啟應用程式日誌,並儘可能共用相關日誌輸出。 + 僅當螢幕截圖或範例文件不包含私人資訊時才附上它們。 + 後台處理已停止 + Android沒有讓後台處理通知及時啟動。這是無法可靠修復的系統計時限制。重新啟動應用程式並重試;保持應用程式開啟並允許通知或後台工作可能會有所幫助。 + 文字、二維碼和數據 + 導入、儲存和分享結果 + 遵循常見的編輯流程 + 無需跳刀即可完成一幅圖像 + 避免重複相同的設置 + 讓新工具更接近您的目標 + 讓開啟檔案符合您的習慣 + 從正確的來源選擇文件 + 減少工具清單噪音 + 讓您的設置保持便攜 + 跳過文件選取 + 當輸入通常來自共享、剪貼簿或上一個畫面時,可以更快地打開工具 + 使重複工具啟動更快 + 跳過文件選取更改了受支援工具的第一步。當您通常使用已選擇的圖像或透過 Android 共享操作輸入工具時,它很有用,但如果您希望每個工具立即請求文件,則可能會感到困惑。 + 僅當您的常用流程在工具開啟之前已提供來源影像時才啟用它。 + 在學習應用程式時將其停用,因為明確的選擇使每個工具流程更容易理解。 + 如果工具開啟時沒有明顯的輸入,請停用此設定或從共享/開啟方式開始,以便 Android 首先傳遞檔案。 + 首先開啟編輯器 + 當共享影像應直接進行編輯時跳過預覽 + 選擇預覽或編輯作為第一站 + 開啟編輯而不是預覽適用於已經知道要修改圖像的使用者。當您檢查聊天或雲端儲存中的文件時,預覽會更安全;直接編輯螢幕截圖、快速裁剪、註釋和一次性更正的速度更快。 + 如果大多數共享影像立即需要裁剪、標記、濾鏡或匯出更改,請啟用直接編輯。 + 當您經常在決定之前檢查元資料、尺寸、透明度或影像品質時,請先保留預覽。 + 將其與收藏夾一起使用,以便共享圖像接近您實際使用的工具。 + 預設文字輸入 + 輸入準確的預設值,而不是重複調整控件 + 當滑塊緩慢時使用精確值 + 當您需要重複工作的精確數字時,預設文字輸入會有所幫助:社交影像尺寸、文件邊距、壓縮目標、線寬或其他難以透過手勢達到的值。它在小螢幕上也很有用,因為滑桿的精細移動比較困難。 + 如果您經常重複使用精確尺寸、百分比、品質值或工具參數,請啟用文字輸入。 + 輸入一次後將該值儲存為預設,然後重複使用它而不是重建相同的設定。 + 保留手勢控制以進行粗略的視覺調整,並保留鍵入值以滿足嚴格的輸出要求。 + 保存接近原始狀態 + 當資料夾上下文很重要時,將產生的檔案放在來源影像旁邊 + 將輸出與其來源保持一致 + 「儲存到原始資料夾」對於相機資料夾、專案資料夾、文件掃描和用戶端批次來說非常方便,其中編輯的副本應保留在來源旁邊。它可能不如專用輸出資料夾整潔,因此請將其與清晰的檔案名稱後綴或模式結合。 + 當每個來源資料夾已經有意義並且輸出應保持分組時啟用它。 + 使用檔案名稱後綴、前綴、時間戳或模式,以便編輯後的副本很容易與原始檔案分開。 + 當您希望將所有產生的檔案收集在一個匯出資料夾中時,請針對混合批次停用它。 + 跳過較大的輸出 + 避免保存意外變得比來源重的轉換 + 保護批次免受不良尺寸意外的影響 + 某些轉換會使檔案變大,尤其是 PNG 螢幕截圖、已壓縮的照片、高品質 WebP 或保留元資料的圖片。如果較大則允許跳過可防止這些輸出在以減小大小為主要目標的工作流程中替換較小的來源。 + 當導出旨在壓縮、調整大小或準備檔案以滿足上傳限制時啟用它。 + 批次後查看跳過的文件;它們可能已經被優化或可能需要不同的格式。 + 當品質、透明度或格式相容性比最終檔案大小更重要時,請停用它。 + 剪貼簿和鏈接 + 控制自動剪貼簿輸入和連結預覽,以實現更快的基於文字的工具 + 使自動輸入可預測 + 剪貼簿和連結設定對於 QR、Base64、URL 和文字密集型工作流程來說很方便,但自動輸入應該是有意的。如果應用程式似乎自行填充字段,請檢查剪貼簿貼上行為;如果連結卡感覺嘈雜或緩慢,請調整連結預覽行為。 + 當您經常複製文字並立即開啟配對工具時,允許自動剪貼簿貼上。 + 如果私有剪貼簿內容出現在您不希望出現的工具中,請停用自動貼上。 + 當預覽獲取緩慢、分散注意力或對工作流程不必要時,請關閉連結預覽。 + 緊湊型控制裝置 + 當螢幕空間緊張時,使用更密集的選擇器和快速設定放置 + 在小螢幕上安裝更多控件 + 緊湊的選擇器和快速的設定放置有助於小型手機、橫向編輯和具有許多參數的工具。代價是控制會感覺更密集,因此最好在您已經認識了常見選項之後再進行操作。 + 當對話方塊或選項清單對於螢幕來說太高時,啟用緊湊選擇器。 + 如果從顯示器的一側​​更容易觸及工具控件,請調整快速設定側。 + 如果您仍在學習該應用程式或經常錯過密集選項,請返回更寬敞的控制項。 + 從網頁載入圖像 + 貼上頁面或圖像 URL,預覽解析的圖像,然後儲存或分享所選結果 + 有意使用網路影像 + Web 圖片載入從提供的 URL 解析圖像來源,預覽第一個可用圖像,並允許您儲存或共享選定的解析圖像。某些網站使用臨時的、受保護的或腳本生成的鏈接,因此在瀏覽器中運行的頁面可能仍不會返回可用的圖像。 + 貼上直接圖像 URL 或頁面 URL,然後等待解析的圖像清單出現。 + 當頁麵包含多個圖像並且您只需要其中一些圖像時,請使用框架或選擇控制項。 + 如果沒有加載,請嘗試直接圖像連結或先透過瀏覽器下載;網站可能會阻止解析或使用私人連結。 + 選擇調整大小類型 + 在將影像強制轉換為新尺寸之前了解顯式、靈活、裁剪和適合 + 控制形狀、畫布和縱橫比 + 調整大小類型決定當請求的寬度和高度與原始寬高比不符時會發生什麼。這是拉伸像素、保留比例、裁剪額外區域或添加背景畫布之間的差異。 + 僅當失真可接受或來源已經具有與目標相同的寬高比時才使用顯式。 + 使用「靈活」可以透過從重要一側調整大小來保持比例,特別是對於照片和混合批次。 + 對於沒有邊框的嚴格框架,請使用「裁剪」;當整個影像必須在固定畫布內保持可見時,請使用「適合」。 + 選擇影像縮放模式 + 選擇控制銳利度、平滑度、速度和邊緣偽影的重採樣濾波器 + 調整大小,不會意外變軟 + 影像縮放模式控制在調整大小期間如何計算新像素。簡單模式速度快且可預測,而高級濾鏡可以更好地保留細節,但可能會銳化邊緣、產生光暈或在大圖像上花費更長的時間。 + 當結果只需要更小和更乾淨時,使用基本或雙線性快速日常調整大小。 + 當精細細節、文字或高品質縮小很重要時,請使用 Lanczos、Robidoux、Spline 或 EWA 樣式模式。 + 對像素藝術、圖標和硬邊圖形使用“最近”,因為模糊的像素會破壞外觀。 + 縮放色彩空間 + 決定在調整大小期間重新取樣像素時如何混合顏色 + 保持漸層和邊緣自然 + 縮放色彩空間會變更調整大小時所使用的數學。大多數影像在預設情況下都很好,但線性或感知空間可以使漸變、暗邊緣和飽和顏色在強力縮放後看起來更乾淨。 + 當速度和相容性比微小的色差更重要時,請保留預設值。 + 當調整大小後深色輪廓、UI 螢幕截圖、漸層或色帶看起來不正確時,請嘗試線性或感知模式。 + 在真實目標螢幕上比較之前和之後,因為色彩空間變化可能是微妙的並且依賴內容。 + 限制調整大小模式 + 了解何時應跳過、重新編碼或縮小超出限制的圖像以適應 + 選擇極限時發生的情況 + 限制調整大小不僅僅是一個較小圖像的工具。它的模式決定了當來源已經在您的限制範圍內或只有一側違反規則時應用程式執行的操作,這對於混合資料夾和上傳準備非常重要。 + 當限制範圍內的影像應保持不變且不再導出時,請使用「跳過」。 + 當尺寸可以接受但您仍然需要新的格式、元資料行為、品質或檔案名稱時,請使用重新編碼。 + 當超出限制的影像應按比例縮小直至適合允許的方塊時,請使用縮放。 + 縱橫比目標 + 避免在嚴格的輸出尺寸下出現拉伸面、切邊和意外邊框 + 調整大小之前匹配形狀 + 寬度和高度只是調整大小目標的一半。寬高比決定圖像是否可以自然地適合、需要裁剪或需要在其周圍放置畫布。首先檢查形狀可以防止最令人驚訝的調整大小結果。 + 在選擇「明確」、「裁切」或「適合」之前,將來源形狀與目標形狀進行比較。 + 當拍攝對象可能失去額外的背景並且目的地需要嚴格的框架時,請先裁剪。 + 當原始影像的每個部分都必須保持可見時,請使用「適合」或「靈活」。 + 高檔或普通調整大小 + 了解何時添加像素需要人工智慧以及何時簡單縮放就足夠了 + 不要將尺寸與細節混淆 + 正常調整大小透過插值像素來改變尺寸;它無法發明真正的細節。 AI upscale 可以創造出看起來更清晰的細節,但速度較慢,並且可能會產生紋理幻覺,因此正確的選擇取決於您是否需要相容性、速度或視覺還原。 + 對縮圖、上傳限制、螢幕截圖和簡單的尺寸變更使用正常調整大小。 + 當小圖像或柔和圖像需要在較大尺寸下看起來更好時,請使用 AI 高檔。 + 在 AI 升級後比較人臉、文字和圖案,因為生成的細節可能看起來令人信服,但實際上是不正確的。 + 過濾順序問題 + 依有意的順序套用清理、色彩、清晰度和最終壓縮 + 建立更清潔的過濾器堆疊 + 過濾器並不總是可以互換的。銳利化前的模糊、OCR 前的對比度增強或壓縮前的顏色變化可能會從相同的控制產生截然不同的輸出。 + 首先裁剪並旋轉,以便稍後的濾鏡僅對保留的像素起作用。 + 在銳利化或風格化效果之前應用曝光、白平衡和對比。 + 保持最終的調整大小和壓縮接近結束,以便預覽的細節能夠如預期匯出。 + 修復背景邊緣 + 自動去除後清除光暈、殘留陰影、毛髮、孔洞和柔和輪廓 + 讓切口看起來是故意的 + 自動蒙版通常乍看之下不錯,但在明亮、黑暗或有圖案的背景上卻會暴露問題。邊緣清理是快速剪切和可重複使用的貼紙、標誌或產品圖像之間的區別。 + 在對比背景下預覽結果,以顯示光暈和缺失的邊緣細節。 + 在擦除周圍的殘留物之前,對頭髮、角落和透明物件使用恢復。 + 當剪切圖放入設計中時,導出對最終背景顏色的小測試。 + 可讀浮水印 + 在明亮、黑暗、繁忙和裁剪的圖像上使標記可見,而不會破壞照片 + 保護圖像而不隱藏它 + 在一張圖像上看起來不錯的浮水印可能在另一張圖像上消失。應為整個批次選擇尺寸、不透明度、對比度、位置和重複,而不僅僅是第一次預覽。 + 在匯出所有檔案之前,測試批次中最亮和最暗影像的標記。 + 對大的重複標記使用較低的不透明度,對小角標記使用更強的對比。 + 保持重要的面孔、文字和產品細節清晰,除非該標記是為了防止重複使用。 + 將一張影像分割成圖塊 + 按行、列、自訂百分比、格式和品質剪下單一影像 + 準備網格和訂購的零件 + 影像分割從一張來源影像創建多個輸出。它可以按行和列計數進行拆分,調整行或列百分比,並以選定的輸出格式和品質保存片段,這對於網格、頁面切片、全景圖和有序社交帖子非常有用。 + 選擇來源影像,然後設定所需的行數和列數。 + 當碎片的大小不應該相同時,調整行或列的百分比。 + 在儲存之前選擇輸出格式和質量,以便每個產生的片段都使用相同的匯出規則。 + 剪出影像條 + 刪除垂直或水平區域並將剩餘部分重新合併在一起 + 刪除一個區域而不留間隙 + 影像裁剪與普通裁剪不同。它可以刪除選定的垂直或水平條帶,然後將剩餘的影像部分合併在一起。反向模式會保留選取的區域,並且使用兩個反向方向會保留選取的矩形。 + 對側面區域、柱狀或中間條帶使用垂直切割;橫幅、間隙或行使用水平切割。 + 當您想要保留所選零件而不是刪除它時,請在軸上啟用反轉。 + 剪下後預覽邊緣,因為當刪除的區域跨越重要細節時,合併的內容可能看起來很突兀。 + 選擇輸出格式 + 根據內容和目標選擇 JPEG、PNG、WebP、AVIF、JXL 或 PDF + 讓目的地決定格式 + 最佳格式取決於圖像包含的內容及其使用位置。當強制採用錯誤的格式時,照片、螢幕截圖、透明資源、文件和動畫文件都會以不同的方式失敗。 + 當不需要透明度和精確像素時,請使用 JPEG 實現廣泛的照片相容性。 + 使用 PNG 進行清晰的螢幕截圖、UI 擷取、透明圖形和無損編輯。 + 當緊湊的現代輸出很重要並且接收應用程式支援它時,請使用 WebP、AVIF 或 JXL。 + 提取音訊封面 + 將音訊檔案中的嵌入專輯封面或可用媒體影格另存為 PNG 影像 + 從音訊檔案中提取藝術品 + Audio Covers 使用 Android 媒體元資料從音訊檔案讀取嵌入的藝術作品。當嵌入的藝術作品不可用時,如果 Android 可以提供媒體框架,檢索器可能會退回到媒體框架;如果兩者都不存在,則該工具報告沒有要提取的圖像。 + 打開音訊封面並選擇一個或多個具有預期封面藝術的音訊檔案。 + 在儲存或分享之前查看提取的封面,尤其是來自串流媒體或錄製應用程式的檔案。 + 如果未找到圖像,則音訊檔案可能沒有嵌入封面,或者 Android 無法公開可用的幀。 + 日期和位置元數據 + 當擷取時間很重要但 GPS 或裝置資料不該外洩時,決定要保留哪些內容 + 將有用的元資料與私有元資料分開 + 元資料的風險並不相同。擷取日期對於存檔和排序非常有用,而 GPS、類似裝置序號的欄位、軟體標籤或擁有者欄位可以揭示超出預期的資訊。 + 當時間順序對相簿、掃描或項目歷史記錄很重要時,保留日期和時間標籤。 + 在公開分享或向陌生人發送圖像之前,請刪除 GPS 和裝置識別標籤。 + 當隱私要求嚴格時,導出樣本並再次檢查 EXIF。 + 避免檔案名稱衝突 + 當許多檔案共用 image.jpg 或 Screenshot.png 等名稱時保護批次輸出 + 使每個輸出名稱唯一 + 當影像來自即時通訊工具、螢幕截圖、雲端資料夾或多台相機時,重複的檔案名稱很常見。良好的模式可以防止意外更換,並使輸出可追溯到其來源。 + 當編輯後的副本應該可識別時,請包含原始名稱和後綴。 + 處理大型混合批次時新增序列、時間戳記、預設或維度。 + 在確認命名模式不會發生衝突之前,請避免覆蓋工作流程。 + 儲存或分享 + 在持久文件和臨時移交到另一個應用程式之間進行選擇 + 以正確的意圖進行出口 + 保存和分享感覺很相似,但它們解決的問題不同。保存會創建一個您稍後可以找到的文件;共享透過 Android 將生成的結果發送到另一個應用程序,並且可能不會留下持久的本地副本。 + 當輸出必須保留在已知資料夾中、進行備份或稍後重複使用時,請使用「儲存」。 + 使用分享來快速發送訊息、電子郵件附件、社交發布或將結果發送給其他編輯者。 + 當接收應用程式不可靠或失敗的共用很難重新建立時,請先儲存。 + PDF 頁面大小和邊距 + 在建立文件之前選擇紙張尺寸、適合行為和喘息空間 + 使圖像頁面可列印且可讀 + 當影像的形狀與所選的紙張尺寸不符時,影像可能會變成尷尬的 PDF 頁面。頁邊距、適合模式和頁面方向決定內容是否被裁剪、變小、拉伸或舒適閱讀。 + 當 PDF 可以列印或提交到表單時,請使用實際紙張尺寸。 + 掃描和螢幕截圖時使用邊距,這樣文字就不會緊貼頁面邊緣。 + 當來源影像具有混合方向時,同時預覽縱向和橫向頁面。 + 清理掃描 + 在 PDF 或 OCR 之前改善紙張邊緣、陰影、對比度、旋轉和可讀性 + 歸檔前準備頁面 + 掃描可以從技術上捕獲,但仍然難以閱讀。 PDF 匯出先前的小清理步驟通常比壓縮設定更重要,尤其是對於收據、手寫筆記和低光頁面。 + 修正透視並剪掉桌子邊緣、手指、陰影和雜亂的背景。 + 仔細增加對比度,使文字變得更清晰,而不會損壞印章、簽名或鉛筆標記。 + 僅在頁面垂直且可讀後才執行 OCR,然後手動驗證重要數字。 + 壓縮、灰階化或修復 PDF + 使用單文件 PDF 操作來獲得更輕量、可列印或重建的文件副本 + 選擇PDF清理操作 + PDF Tools 包括壓縮、灰階轉換和修復的單獨操作。壓縮和灰階主要透過嵌入影像進行工作,而修復則是重建PDF結構;這些都不應被視為對每個文件都有保證的修復。 + 當文件包含圖像且文件大小對共享很重要時,請使用壓縮 PDF。 + 當文件應該更容易列印或不需要彩色影像時,請使用灰階。 + 當文件無法開啟或內部結構損壞時,在副本上使用修復 PDF,然後驗證重建的文件。 + 從 PDF 中提取圖像 + 恢復嵌入的 PDF 影像並將提取的結果打包到檔案中 + 儲存文件中的來源影像 + 提取影像掃描 PDF 中的嵌入影像對象,盡可能跳過重複項,並將恢復的影像儲存在生成的 ZIP 檔案中。它僅提取實際嵌入在 PDF 中的圖像,而不是文字、向量圖形或每個可見頁面的螢幕截圖。 + 當您需要將圖片儲存在 PDF 中時(例如目錄中的照片或掃描的資產),請使用擷取影像。 + 當您需要完整頁面(包括文字和佈局)時,請使用 PDF 轉圖像或頁面提取。 + 如果該工具報告沒有嵌入圖像,則頁面可能是文字/向量內容,或者圖像可能未儲存為可提取物件。 + 元資料、扁平化和列印輸出 + 編輯文件屬性、光柵化頁面或有意準備自訂列印佈局 + 選擇最終文檔操作 + PDF 元資料、拼合和列印準備解決了不同的最終階段問題。元資料控製文件屬性,拼合 PDF 將頁面光柵化為不可編輯的輸出,而列印 PDF 準備帶有頁面大小、方向、邊距和每張頁數控制的新版面。 + 當作者、標題、關鍵字、製作人或隱私清理很重要時使用元資料。 + 當可見頁面內容難以以單獨的 PDF 物件編輯時,請使用拼合 PDF。 + 當您需要自訂頁面大小、方向、邊距或每張多頁時,請使用列印 PDF。 + 準備 OCR 影像 + 在要求 OCR 讀取文字之前使用裁剪、旋轉、對比、降噪和縮放 + 為 OCR 提供更清晰的像素 + OCR 錯誤通常來自輸入影像而不是 OCR 引擎。彎曲的頁面、低對比度、模糊、陰影、微小文字和混合背景都會降低辨識品質。 + 裁剪到文字區域並旋轉圖像,使線條在識別前保持水平。 + 僅當字母更易於閱讀時才增加對比度或轉換為更清晰的黑白。 + 適度向上調整微小文字的大小,但避免過度銳利化,以免產生假邊緣。 + 檢查二維碼內容 + 在信任代碼之前檢查連結、Wi-Fi 憑證、聯絡人和操作 + 將解碼後的文字視為不受信任的輸入 + QR 碼可以隱藏 URL、聯絡人名片、Wi-Fi 密碼、日曆事件、電話號碼或純文字。代碼附近的可見標籤可能與實際負載不匹配,因此在對其進行操作之前請檢查解碼的內容。 + 在打開之前檢查完整的解碼 URL,尤其是縮短或不熟悉的網域。 + 請小心使用 Wi-Fi、聯絡人、日曆、電話和簡訊代碼,因為它們可能會觸發敏感操作。 + 需要在其他地方驗證時先複製內容,而不是立即開啟。 + 產生二維碼 + 從純文字、URL、Wi-Fi、聯絡人、電子郵件、電話、簡訊和位置資料建立代碼 + 建立正確的 QR 有效負載 + 二維碼螢幕可以根據輸入的內容產生代碼並掃描現有的代碼。結構化 QR 類型有助於以其他應用程式理解的格式對資料進行編碼,例如 Wi-Fi 登入詳細資訊、聯絡人卡片、電子郵件欄位、電話號碼、簡訊內容、URL 或地理座標。 + 選擇純文字作為簡單註釋,或在程式碼應作為 Wi-Fi、聯絡人、URL、電子郵件、電話、簡訊或位置資料開啟時使用結構化類型。 + 在共享生成的程式碼之前檢查每個字段,因為可見預覽僅反映您輸入的有效負載。 + 當儲存的二維碼將被列印、公開發布或由其他人使用時,請使用掃描器對其進行測試。 + 選擇校驗和模式 + 從檔案或文字計算雜湊值、比較一個檔案或批次檢查多個文件 + 驗證內容,而不是外觀 + 校驗和工具具有單獨的選項卡,用於檔案雜湊、文字雜湊、將檔案與已知雜湊進行比較以及批次比較。校驗和證明所選演算法的逐字節同一性;它並不能證明兩個圖像只是看起來相似。 + 對檔案使用“計算”,對鍵入或貼上的文字資料使用“文字雜湊”。 + 當有人為您提供一個文件的預期校驗和時,請使用比較。 + 當許多檔案需要一次進行雜湊或驗證時,請使用批次比較,然後保持所選演算法一致。 + 剪貼簿隱私 + 使用自動剪貼簿功能,不會意外暴露私有複製數據 + 保持複製內容的意圖 + 剪貼簿自動化很方便,但複製的文字可能包含密碼、連結、地址、令牌或私人註解。將基於剪貼簿的輸入視為速度功能,應符合您的習慣和隱私期望。 + 如果工具中意外出現私有文本,請停用自動剪貼簿使用。 + 僅當您故意希望結果避免儲存時才使用僅剪貼簿保存。 + 處理敏感文字、QR 資料或 Base64 有效負載後清除或替換剪貼簿。 + 顏色格式 + 在貼上到其他地方之前了解 HEX、RGB、HSV、Alpha 和複製的顏色值 + 複製目標期望的格式 + 相同的可見顏色可以有多種書寫方式。設計工具、CSS 欄位、Android 顏色值或影像編輯器可能需要不同的順序、Alpha 處理或顏色模型。 + 貼到需要緊湊顏色代碼的網頁、設計或主題欄位時,請使用十六進位。 + 當您需要手動調整通道、亮度、飽和度或色調時,請使用 RGB 或 HSV。 + 當透明度很重要時,請單獨檢查 alpha,因為某些目的地會忽略它或使用不同的順序。 + 瀏覽顏色庫 + 按名稱或十六進制搜尋命名顏色並將收藏夾保留在頂部附近 + 尋找可重複使用的命名顏色 + 顏色庫載入命名的顏色集合,按名稱對其進行排序,按顏色名稱或十六進制值進行過濾,並儲存最喜歡的顏色,以便更容易存取重要的條目。當您需要真實命名的顏色而不是從圖像中採樣時,它非常有用。 + 當您知道顏色系列時,可以按顏色名稱進行搜索,例如紅色、藍色、石板色或薄荷色。 + 當您已經有了數位顏色並想要尋找符合或附近的命名條目時,可以透過十六進位進行搜尋。 + 將常用顏色標記為收藏夾,以便它們在瀏覽時領先於一般庫。 + 使用網格漸層集合 + 下載可用的網格漸層資源並分享選定的圖像 + 使用遠程網格漸層資源 + 網格漸進可以載入遠端資源集合、下載缺少的漸進式影像、顯示下載進度以及共享選定的漸進。可用性取決於網路存取和遠端資源存儲,因此空列表通常意味著資源尚不可用。 + 當您需要現成的網格漸層影像時,請從漸層工具中開啟網格漸層。 + 在假設集合為空之前,等待資源載入或下載進度。 + 選擇並分享您需要的漸變,或當您想要手動建立自訂漸層時返回漸層製作器。 + 產生的資產解析 + 在產生漸層、雜訊、桌布、類似 SVG 的藝術或紋理之前選擇輸出大小 + 按您需要的大小生成 + 產生的影像沒有可以依靠的相機原始影像。如果輸出太小,以後的縮放可能會軟化圖案、移動細節或更改資源平鋪和壓縮的方式。 + 首先設定最終用例的尺寸,例如桌布、圖示、背景、列印或覆蓋。 + 對可以在多個佈局中裁剪、縮放或重複使用的紋理使用較大尺寸。 + 在大量輸出之前匯出測試版本,因為程式細節可能會改變壓縮的行為。 + 匯出當前桌布 + 從裝置中擷取可用的主頁、鎖定和內建桌布 + 儲存或分享已安裝的壁紙 + 壁紙匯出透過系統桌布 API 讀取 Android 公開的桌布。根據裝置、Android 版本、權限和建置變體,主頁、鎖定或內建桌布可能會單獨提供,也可能會遺失。 + 打開壁紙導出以加載系統允許應用程式讀取的壁紙。 + 選擇要儲存、複製或分享的可用主頁、鎖定或內建桌布條目。 + 如果壁紙遺失或功能不可用,這通常是系統、權限或建置變體限制,而不是編輯設定。 + 角落動畫油門 + 將顏色複製為 + 十六進位 + 名稱 + 肖像摳圖模型,用於快速人物剪裁,具有更柔軟的頭髮和邊緣處理。 + 使用零 DCE++ 曲線估計進行快速低光增強。 + 用於減少雨紋和潮濕天氣偽影的 Restormer 影像恢復模型。 + 文件反扭曲模型可預測座標網格並將彎曲頁面重新映射為更平坦的掃描。 + 運動持續時間量表 + 控制應用程式動畫速度:0 停用運動,1 正常,較高的值會減慢動畫速度 + 在主畫面上顯示對最近使用的工具的快速訪問 + 使用統計 + 探索應用程式開啟、工具啟動以及您最常用的工具 + 應用程式打開 + 工具開啟 + 最後一個工具 + 成功保存 + 連續活動 + 資料已儲存 + 頂部格式 + 使用的工具 + 尚無使用統計 + 打開一些工具,它們會自動出現在這裡 + 顯示最近使用的工具 + %1$d 開啟 • %2$s + 您的使用情況統計資料僅儲存在您的裝置本機。 ImageToolbox 僅使用它們來顯示您的個人活動、最喜歡的工具和保存的資料見解 + 中性變體 + 錯誤 + 編輯器編輯照片圖片修飾調整 + 調整大小 轉換比例 解析度 尺寸 寬度 高度 jpg jpeg png webp 壓縮 + 壓縮大小 重量 位元組 mb kb 減少品質 最佳化 + 作物修剪切割縱橫比 + 濾鏡效果 色彩校正 調整 lut mask + 繪製油漆刷鉛筆註釋標記 + 密碼 加密 解密 密碼 秘密 + 背景去除器擦除去除切口透明阿爾法 + 預覽檢視器圖庫檢查打開 + 縫合合併組合全景長截圖 + url 網路 下載 網路連結 圖片 + 選擇器吸管樣本十六進位 RGB 顏色 + 調色板顏色樣本提取主導 + exif 元資料隱私刪除條清潔 GPS 位置 + 比較前後差異 + 限制調整大小最大最小百萬畫素尺寸夾 + pdf文檔頁面工具 + ocr文字辨識擷取掃描 + 漸層背景顏色網格 + 水印標誌文字圖章覆蓋 + gif 動畫 動畫轉換幀 + apng 動畫 動畫 png 轉換幀 + zip 檔案 壓縮包 解壓縮文件 + jxl jpeg xl 轉換 jpg 圖片格式 + svg向量追蹤轉換xml + 格式轉換 jpg jpeg png webp heic avif jxl bmp + 文件掃描器掃描紙張透視裁剪 + qr 條碼掃描器掃描 + 堆疊疊加平均中位數天文攝影 + 分割磁磚網格切片部分 + 顏色轉換 hex rgb hsl hsv cmyk lab + webp轉換動畫圖像格式 + 噪音產生器隨機紋理顆粒 + 拼貼網格佈局組合 + 標記圖層註釋疊加編輯 + Base64 編碼解碼資料 uri + 校驗和哈希 md5 sha sha1 sha256 驗證 + 網格漸層背景 + exif 元資料 編輯 GPS 日期 相機 + 切割分割網格塊磁磚 + 音頻封面專輯藝術音樂元數據 + 壁紙 匯出 背景 + ascii 文字藝術字符 + ai ml神經增強高檔細分市場 + 顏色庫材質調色盤命名為顏色 + 著色器GLSL片段效果工作室 + pdf合併合併連接 + pdf分割單獨的頁面 + pdf旋轉頁面方向 + pdf 重新排列 重新排序 頁面排序 + pdf頁碼分頁 + pdf ocr 文字辨識可搜尋摘錄 + pdf 浮水印 印章 標誌 文本 + pdf 簽名 簽名 抽籤 + pdf保護密碼加密鎖 + pdf解鎖密碼解密刪除保護 + pdf 壓縮 縮小尺寸 最佳化 + pdf 灰階 黑白 單色 + pdf 修復 修復 恢復損壞 + pdf 元資料屬性 exif 作者標題 + pdf刪除刪除頁面 + pdf 裁切邊距 + pdf拼合註釋形成圖層 + pdf 擷取影像 圖片 + pdf zip 檔案壓縮 + pdf列印印表機 + pdf預覽檢視器閱讀 + 影像轉pdf 轉換jpg jpeg png 文檔 + pdf 到圖片頁面 匯出 jpg png + pdf註釋註釋刪除乾淨 + 投影 + 齒高 + 水平齒數範圍 + 垂直齒數範圍 + 頂邊 + 右邊緣 + 左邊緣 + 撕邊 + 底邊 + 重置使用統計數據 + 工具打開,保存統計資料、條紋、保存的資料和頂部格式將被重置。應用程式開啟將保持不變。 + 聲音的 + 文件 + 匯出設定檔 + 儲存目前設定檔 + 刪除匯出設定檔 + 您想刪除匯出設定檔「%1$s」嗎? + 繪製邊框 + 在繪圖和擦除畫布周圍顯示細邊框 + 波浪形 + 帶有柔和波浪邊緣的圓形 UI 元素 + + 缺口 + 圓角向內雕刻 + 雙切階梯角 + 佔位符 + 使用 {filename} 插入不含副檔名的原始檔名 + 耀斑 + 基礎金額 + 環量 + 射線量 + 環寬 + 扭曲視角 + Java 外觀和感覺 + 剪切 + X角 + 和角度 + 調整輸出大小 + 水滴 + 波長 + 階段 + 高通 + 彩色面膜 + 鉻合金 + 溶解 + 柔軟度 + 回饋 + 鏡頭模糊 + 低輸入 + 高投入 + 輸出低 + 高輸出 + 燈光效果 + 凸塊高度 + 凹凸柔軟度 + 波紋 + X振幅 + Y振幅 + X波長 + Y波長 + 自適應模糊 + 紋理生成 + 生成各種程式紋理並將其儲存為圖像 + 紋理類型 + 偏見 + 時間 + 閃耀 + 分散 + 樣品 + 角度係數 + 梯度係數 + 網格型 + 距離功率 + 比例 Y + 模糊性 + 基礎類型 + 湍流係數 + 縮放 + 迭代 + 戒指 + 拉絲金屬 + 焦散 + 蜂窩網路 + 棋盤 + fBm + 大理石 + 電漿 + 被子 + 木頭 + 隨機的 + 方塊 + 六角形 + 八角形 + 三角形 + 脊狀 + VL 噪音 + 超導噪音 + 最近的 + 還沒有最近使用過的過濾器 + 紋理產生器 拉絲金屬 焦散 蜂巢棋盤 大理石 等離子被子 木磚 迷彩 細胞雲 裂縫 織物 葉子 蜂巢 冰熔岩 星雲 紙 鐵鏽 沙子 煙石 地形 地形 水波紋 + + 偽裝 + 細胞 + + 裂縫 + 織物 + 葉子 + 蜂巢 + + 岩漿 + 星雲 + + + + 抽煙 + 結石 + 地形 + 地形 + 水波紋 + 高級木材 + 砂漿寬度 + 不規則性 + 斜角 + 粗糙度 + 第一門檻 + 第二門檻 + 第三門檻 + 邊緣柔軟度 + 邊框寬度 + 覆蓋範圍 + 細節 + 深度 + 分枝 + 水平螺紋 + 垂直螺紋 + 法茲 + 靜脈 + 燈光 + 裂縫寬度 + + 流動 + 地殼 + 雲密度 + 星星 + 纖維密度 + 纖維強度 + 污漬 + 腐蝕 + 點蝕 + 薄片 + 沙丘頻率 + 風角 + 漣漪 + 縷縷 + 靜脈鱗片 + 水位 + 山區水平 + 侵蝕 + 雪量 + 行數 + 線寬 + 陰影 + 毛孔顏色 + 砂漿顏色 + 深磚色 + 磚色 + 突出顯示顏色 + 深色 + 森林色 + 大地色 + 沙色 + 背景顏色 + 細胞顏色 + 邊緣顏色 + 天空的顏色 + 陰影顏色 + 淺色 + 表面顏色 + 變化顏色 + 裂紋顏色 + 經紗顏色 + 緯紗顏色 + 葉色深 + 葉色 + 邊框顏色 + 蜜色 + 深色 + 冰色 + 霜色 + 地殼顏色 + + 發光顏色 + 空間色彩 + 紫羅蘭色 + 藍色 + 基色 + 纖維色 + 染色顏色 + 金屬色 + 深鐵鏽色 + 鐵鏽色 + 橘色 + 煙色 + 靜脈顏色 + 低地色彩 + 水彩 + 岩石色 + 雪色 + 低色度 + 高顯色 + 線條顏色 + 淺色 + + 污垢 + 皮革 + 具體的 + 瀝青 + 苔蘚 + + 極光 + 浮油 + 水彩 + 抽象流 + 蛋白石 + 大馬士革鋼 + 閃電 + 天鵝絨 + 墨水大理石花紋 + 全像箔 + 生物發光 + 宇宙漩渦 + 熔岩燈 + 事件視界 + 分形綻放 + 彩色隧道 + 日食日冕 + 奇怪的吸引子 + 磁流體冠 + 超新星 + 鳶尾花 + 孔雀羽毛 + 鸚鵡螺殼 + 圓環行星 + 葉片密度 + 刀刃長度 + + 斑駁 + 團塊 + 水分 + 鵝卵石 + 皺紋 + 毛孔 + 總計的 + 裂紋 + 焦油 + 穿 + 斑點 + 纖維 + 火焰頻率 + 抽煙 + 強度 + 絲帶 + 樂團 + 彩虹色 + 黑暗 + 布魯姆斯 + 顏料 + 邊緣 + + 擴散 + 對稱 + 清晰度 + 色彩遊戲 + 乳白色 + 層數 + 折疊式的 + 拋光 + 分公司 + 方向 + 光澤 + 褶皺 + 羽化 + 墨水平衡 + 光譜 + 皺紋 + 繞射 + 武器 + + 核心輝光 + 斑點 + 光碟傾斜 + 地平線尺寸 + 盤寬 + 透鏡 + 花瓣 + 捲曲 + 花絲 + 刻面 + 曲率 + 月亮大小 + 電暈尺寸 + 射線 + 鑽石戒指 + 葉瓣 + 軌道密度 + 厚度 + 鞋釘 + 鞋釘長度 + 機身尺寸 + 金屬色 + 衝擊半徑 + 外殼寬度 + 被丟掉 + 瞳孔大小 + 虹膜大小 + 顏色變化 + 聚光燈 + 眼睛大小 + 倒刺密度 + 轉彎 + 錢伯斯 + 開幕 + 山脊 + 珠光 + 行星大小 + 環傾斜 + 環寬 + 氣氛 + 污垢顏色 + 深草色 + 草色 + 筆尖顏色 + 深層大地色 + 乾色 + 卵石色 + 皮革顏色 + 具體顏色 + 焦油顏色 + 瀝青色 + 石材顏色 + 灰塵顏色 + 土壤顏色 + 深苔色 + 苔蘚色 + 紅色 + 核心色 + 綠色 + 青色 + 洋紅色 + 金色 + 紙張顏色 + 顏料顏色 + 次要顏色 + 第一種顏色 + 第二種顏色 + 粉紅色 + 深鋼色 + 鋼色 + 輕鋼色 + 氧化物顏色 + 光暈顏色 + 螺栓顏色 + 絲絨色 + 光澤顏色 + 藍色墨水顏色 + 紅色墨水顏色 + 深色墨水顏色 + 銀色 + 黃色 + 組織顏色 + 盤面顏色 + 熱色 + 鏡片顏色 + 外觀顏色 + 內色 + 電暈顏色 + 冷色系 + 暖色 + 雲色 + 火焰顏色 + 羽毛顏色 + 外殼顏色 + 珍珠色 + 星球顏色 + 戒指顏色 + 儲存後刪除原文件 + 只有成功處理的來源檔案才會被刪除 + 已刪除的原始檔案:%1$d + 無法刪除原始檔案:%1$d + 原始檔案已刪除:%1$d,失敗:%2$d + 表單手勢 + 允許拖曳展開的選項表 + 色度子取樣 + 位元深度 + 無損 + %1$d 位 + 批次重命名 + 使用檔案名稱模式重命名多個文件 + 批次重命名檔案檔案名稱模式序列日期副檔名 + 選擇要重新命名的文件 + 檔案名稱模式 + 重新命名 + 數據來源 + EXIF 拍攝日期 + 文件修改日期 + 文件創建日期 + 目前日期 + 手動日期 + 手動日期 + 手動時間 + 某些文件無法選擇所選日期。他們將使用當前日期。 + 輸入檔名模式 + 此模式產生無效或過長的檔案名 + 此模式會產生重複的檔案名 + 所有檔案名稱都已與模式匹配 + 此模式使用不可用於批次重命名的標記 + 名稱將保持不變 + 文件重命名成功 + 某些檔案無法重新命名 + 未獲得許可 + 使用不含副檔名的原始檔名,幫助您保持來源標識完整。也支援使用 \\o{start:end} 進行切片、使用 \\o{t} 進行音譯以及使用 \\o{s/old/new/} 進行替換。 + 自動遞增計數器。支援自訂格式:\\c{padding}(例如 \\c{3} -&gt; 001)、\\c{start:step} 或 \\c{start:step:padding}。 + 原始檔案所在的父資料夾的名稱。 + 原始文件的大小。支援單位:\\z{b}、\\z{kb}、\\z{mb} 或僅 \\z 以供人類可讀格式。 + 產生隨機的通用唯一識別碼 (UUID) 以確保檔案名稱的唯一性。 + 重新命名檔案無法撤銷。在繼續之前請確保新名稱正確。 + 確認重命名 + 側邊選單設置 + 菜單規模 + 阿爾法菜單 + 自動白平衡 + 剪裁 + 檢查隱藏浮水印 + 貯存 + 快取限制 + 當快取超過此大小時自動清除緩存 + 清理間隔 + 多久檢查一次快取是否應該清除 + 應用程式啟動時 + 1天 + 1週 + 1個月 + 根據選擇的限制和間隔自動清除應用程式緩存 + + 允許透過在裁剪區域內拖曳來移動整個裁剪區域 + 合併 GIF + 將多個 GIF 剪輯合併為一個動畫 + GIF 剪輯順序 + 反向 + 反向播放 + 迴力鏢 + 向前播放然後向後播放 + 規範化幀大小 + 縮放框架以適合最大的剪輯;關閉以保留其原始比例 + 剪輯之間的延遲(毫秒) + 資料夾 + 選擇資料夾及其子資料夾中的所有影像 + 重複查找器 + 尋找精確的副本和視覺上相似的圖像 + 重複查找器 相似影像 精確副本 照片清理 儲存 dHash SHA-256 + 選擇圖像以查找重複項 + 相似度敏感度 + 精確的副本 + 類似影像 + 選擇所有精確的重複項 + 除推薦外全部選擇 + 沒有發現重複項 + 嘗試新增更多影像或提高相似度敏感度 + 無法讀取 %1$d 影像 + %1$s • %2$s 可回收 + %1$d 組 • %2$s 可回收 + 已選擇 %1$d • %2$s + 此操作無法撤銷。 Android 可能會要求您在系統對話方塊中確認刪除。 + 未找到 + 移至開始 + 移至末端 + \ No newline at end of file diff --git a/core/resources/src/main/res/values/arrays.xml b/core/resources/src/main/res/values/arrays.xml new file mode 100644 index 0000000..0293233 --- /dev/null +++ b/core/resources/src/main/res/values/arrays.xml @@ -0,0 +1,138 @@ + + + + + + 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 + + \ No newline at end of file diff --git a/core/resources/src/main/res/values/bools.xml b/core/resources/src/main/res/values/bools.xml new file mode 100644 index 0000000..94f9e79 --- /dev/null +++ b/core/resources/src/main/res/values/bools.xml @@ -0,0 +1,20 @@ + + + + false + \ No newline at end of file diff --git a/core/resources/src/main/res/values/colors.xml b/core/resources/src/main/res/values/colors.xml new file mode 100644 index 0000000..f665878 --- /dev/null +++ b/core/resources/src/main/res/values/colors.xml @@ -0,0 +1,21 @@ + + + + #ABE87C + #394F29 + \ No newline at end of file diff --git a/core/resources/src/main/res/values/ic_launcher_background.xml b/core/resources/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..30f0b88 --- /dev/null +++ b/core/resources/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,20 @@ + + + + @color/primary + \ No newline at end of file diff --git a/core/resources/src/main/res/values/strings.xml b/core/resources/src/main/res/values/strings.xml new file mode 100644 index 0000000..17d6ed2 --- /dev/null +++ b/core/resources/src/main/res/values/strings.xml @@ -0,0 +1,3765 @@ + + + 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 + Copy color as + HEX + name + 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 + Help & Tips + %1$d tutorials + Open tool + Learn the main tools, export options, PDF flows, color utilities, and fixes for common problems + Getting started + Choose tools, import files, save results, and reuse settings faster + Image editing + Resize, crop, filter, erase backgrounds, draw, and watermark images + Files and metadata + Convert formats, compare output, protect privacy, and control filenames + PDF and documents + Create PDFs, scan documents, OCR pages, and prepare files for sharing + Text, QR, and data + Recognize text, scan codes, encode Base64, check hashes, and package files + Color tools + Pick colors, build palettes, explore color libraries, and create gradients + Creative tools + Generate SVG, ASCII art, noise textures, shaders, and experimental visuals + Troubleshooting + Fix heavy files, transparency, sharing, imports, and reporting issues + Choose the right tool + Use categories, search, favorites, and share actions to start in the right place + Start from the best entry point + Image Toolbox has many focused tools, and many of them overlap at first glance. Start from the task you want to finish, then choose the tool that controls the most important part of the result: size, format, metadata, text, PDF pages, colors, or layout. + Use search when you know the action name, such as resize, PDF, EXIF, OCR, QR, or color. + Open a category when you only know the task type, then pin frequent tools as favorites. + When another app shares an image into Image Toolbox, pick the tool from the share sheet flow. + Import, save, and share results + Understand the usual flow from picking input to exporting the edited file + Follow the common editing flow + Most tools follow the same rhythm: choose input, adjust options, preview, then save or share. The important detail is that saving creates an output file, while sharing is usually best for quick handoff to another app. + Pick one file for single-image tools or several files for batch tools when the picker allows it. + Check the preview and output controls before saving, especially format, quality, and filename options. + Use share for a quick handoff, or save when you need a persistent copy in the selected folder. + Work with many images at once + Batch tools are best for repeated resize, conversion, compression, and naming tasks + Prepare a predictable batch + Batch processing is fastest when every output should follow the same rules. It is also the easiest place to make a large mistake, so choose naming, quality, metadata, and folder behavior before starting a long export. + Select all related images together and keep the order if the output depends on sequence. + Set resize, format, quality, metadata, and filename rules once before starting export. + Run a small test batch first when source files are large or when the target format is new for you. + Quick single edit + Use the all-in-one editor when you need several simple changes on one image + Finish one image without tool hopping + Single Edit is useful when the image needs a small chain of changes rather than one specialized operation. It is usually faster for quick cleanup, previewing, and exporting one final copy without building a batch workflow. + Open Single Edit for one image that needs common adjustments, markup, crop, rotation, or export changes. + Apply edits in the order that changes the fewest pixels first, such as crop before filters and filters before final compression. + Use a specialized tool instead when you need batch processing, exact file-size targets, PDF output, OCR, or metadata cleanup. + Use presets and settings + Save repeated choices and tune the app for your preferred workflow + Avoid repeating the same setup + Presets and settings are useful when you export the same kind of file often. A good preset turns a repeated manual checklist into one tap, while global settings define the defaults that new tools start with. + Create presets for common output sizes, formats, quality values, or naming patterns. + Review Settings for default format, quality, save folder, filename, picker, and interface behavior. + Back up settings before reinstalling the app or moving to another device. + Set useful defaults + Choose default format, quality, scale mode, resize type, and color space once + Make new tools start closer to your goal + Default values are not just cosmetic preferences. They decide what format, quality, resize behavior, scale mode, and color space appear first in many tools, which helps avoid reselecting the same choices on every export. + Set default image format and quality to match your usual destination, such as JPEG for photos or PNG/WebP for alpha. + Tune default resize type and scale mode if you often export for strict dimensions, social platforms, or document pages. + Change color space defaults only when your workflow needs consistent color handling across displays, print, or external editors. + Picker and launcher + Use picker settings when the app opens too many dialogs or starts in the wrong place + Make opening files match your habit + Picker and launcher settings control whether Image Toolbox starts from the main screen, a tool, or a file selection flow. These options are especially useful when you usually enter from the Android share sheet or when you want the app to feel more like a tool launcher. + Use photo picker settings if the system picker hides files, shows too many recent items, or handles cloud files poorly. + Enable skip picking only for tools where you usually enter from share or already know the source file. + Review launcher mode if you want Image Toolbox to behave more like a tool picker than a normal home screen. + Image source + Choose the picker mode that works best with galleries, file managers, and cloud files + Pick files from the right source + Image Source settings affect how the app asks Android for images. The best choice depends on whether your files live in the system gallery, a file manager folder, a cloud provider, or another app that shares temporary content. + Use the default picker when you want the most system-integrated experience for common gallery images. + Switch picker mode if albums, folders, cloud files, or nonstandard formats do not appear where you expect. + If a shared file disappears after leaving the source app, reopen it through a persistent picker or save a local copy first. + Skip file picking + Open tools faster when input usually comes from share, clipboard, or a previous screen + Make repeated tool launches faster + Skip File Picking changes the first step of supported tools. It is useful when you usually enter a tool with an already selected image or from Android share actions, but it can feel confusing if you expect every tool to ask for a file immediately. + Enable it only when your usual flow already provides the source image before the tool opens. + Keep it disabled while learning the app, because explicit picking makes every tool flow easier to understand. + If a tool opens with no obvious input, disable this setting or start from Share/Open With so Android passes the file first. + Open editor first + Skip preview when a shared image should go straight into editing + Choose preview or edit as the first stop + Open Edit Instead Of Preview is for users who already know they want to modify the image. Preview is safer when you inspect files from chat or cloud storage; direct edit is faster for screenshots, quick crops, annotations, and one-off corrections. + Enable direct edit if most shared images immediately need crop, markup, filters, or export changes. + Leave preview first when you often inspect metadata, dimensions, transparency, or image quality before deciding. + Use this together with favorites so shared images land close to the tools you actually use. + Favorites and share tools + Keep the main screen focused and hide tools that do not make sense in share flows + Reduce tool list noise + Favorites and hidden-for-share settings are useful when you use only a few tools every day. They also keep the share flow practical by hiding tools that cannot use the type of file another app sends. + Pin frequent tools so they stay easy to reach even when tools are grouped by category. + Hide tools from share flows when they are irrelevant for files coming from another app. + Use the favorite ordering settings if you prefer favorites at the end or grouped with related tools. + Back up settings + Move presets, preferences, and app behavior to another install safely + Keep your setup portable + Backup and Restore is most helpful after you have tuned presets, folders, filename rules, tool order, and visual preferences. A backup lets you experiment with settings or reinstall the app without rebuilding your workflow from memory. + Create a backup after changing presets, filename behavior, default formats, or tool arrangement. + Store the backup somewhere outside the app folder if you plan to clear data, reinstall, or move devices. + Restore settings before processing important batches so defaults and save behavior match your old setup. + Preset text entry + Enter exact preset values instead of adjusting controls repeatedly + Use precise values when sliders are slow + Preset text entry helps when you need exact numbers for repeated work: social image sizes, document margins, compression targets, line widths, or other values that are annoying to reach with gestures. It is also useful on small screens where fine slider movement is harder. + Enable text entry if you often reuse exact sizes, percentages, quality values, or tool parameters. + Save the value as a preset after typing it once, then reuse it instead of rebuilding the same setup. + Keep gesture controls for rough visual tuning and typed values for strict output requirements. + Load images from the web + Paste a page or image URL, preview parsed images, then save or share selected results + Use web images deliberately + Web Image Loading parses image sources from the provided URL, previews the first available image, and lets you save or share selected parsed images. Some sites use temporary, protected, or script-generated links, so a page that works in a browser may still return no usable images. + Paste a direct image URL or a page URL and wait for the parsed image list to appear. + Use the frame or selection controls when the page contains several images and you only need some of them. + If nothing loads, try a direct image link or download through the browser first; the site may block parsing or use private links. + Resize and convert images + Change size, format, quality, and metadata for one or many images + Create resized copies + Resize and Convert is the main tool for predictable dimensions and lighter output files. Use it when the size of the image, the output format, and the metadata behavior all matter at the same time. + Pick one or more images, then choose whether dimensions, scale, or file size matters most. + Select the output format and quality; use PNG or WebP when transparency must be preserved. + Preview the result, then save or share the generated copies without changing the originals. + Resize by file size + Target a size limit when a website, messenger, or form rejects heavy images + Fit strict upload limits + Resize by file size is better than guessing quality values when the destination accepts only files below a specific limit. The tool may adjust compression and dimensions to reach the target while trying to keep the result usable. + Enter the target size slightly below the real upload limit to leave room for metadata and provider changes. + Prefer lossy formats for photos when the target is small; PNG can stay large even after resizing. + Inspect faces, text, and edges after export because aggressive size targets can introduce visible artifacts. + Limit resize + Shrink only images that exceed your maximum width, height, or file constraints + Avoid resizing images that are already fine + Limit Resize is useful for mixed folders where some images are huge and others are already prepared. Instead of forcing every file through the same resize, it keeps acceptable images closer to their original quality. + Set maximum dimensions or limits based on the destination rather than resizing every file blindly. + Enable skip-if-larger style behavior when you want to avoid outputs that become heavier than sources. + Use this for batches from different cameras, screenshots, or downloads where source sizes vary a lot. + Choose resize type + Understand Explicit, Flexible, Crop, and Fit before forcing an image into new dimensions + Control shape, canvas, and aspect ratio + Resize type decides what happens when the requested width and height do not match the original aspect ratio. It is the difference between stretching pixels, preserving proportions, cropping extra area, or adding a background canvas. + Use Explicit only when distortion is acceptable or the source already has the same aspect ratio as the target. + Use Flexible to keep proportions by resizing from the important side, especially for photos and mixed batches. + Use Crop for strict frames without borders, or Fit when the whole image must remain visible inside a fixed canvas. + Pick image scale mode + Choose the resampling filter that controls sharpness, smoothness, speed, and edge artifacts + Resize without accidental softness + Image scale mode controls how new pixels are calculated during resizing. Simple modes are fast and predictable, while advanced filters can preserve detail better but may sharpen edges, create halos, or take longer on large images. + Use Basic or Bilinear for fast everyday resizing when the result only needs to be smaller and clean. + Use Lanczos, Robidoux, Spline, or EWA-style modes when fine details, text, or high-quality downscaling matter. + Use Nearest for pixel art, icons, and hard-edged graphics where blurred pixels would ruin the look. + Scale color space + Decide how colors are blended while pixels are resampled during resize + Keep gradients and edges natural + Scale color space changes the math used while resizing. Most images are fine with the default, but linear or perceptual spaces can make gradients, dark edges, and saturated colors look cleaner after strong scaling. + Leave the default when speed and compatibility matter more than tiny color differences. + Try Linear or perceptual modes when dark outlines, UI screenshots, gradients, or color bands look wrong after resizing. + Compare before and after on the real target screen, because color-space changes can be subtle and content dependent. + Limit Resize modes + Know when over-limit images should be skipped, recoded, or scaled down to fit + Choose what happens at the limit + Limit Resize is not only a smaller-image tool. Its mode decides what the app does when a source is already inside your limits or when only one side breaks the rule, which is important for mixed folders and upload preparation. + Use Skip when images inside the limits should be left untouched and not exported again. + Use Recode when dimensions are acceptable but you still need a new format, metadata behavior, quality, or filename. + Use Zoom when images that break the limits should be scaled down proportionally until they fit the allowed box. + Aspect ratio targets + Avoid stretched faces, cut-off edges, and unexpected borders in strict output sizes + Match the shape before resizing + Width and height are only half of a resize target. The aspect ratio decides whether the image can fit naturally, needs cropping, or needs a canvas around it. Checking the shape first prevents most surprising resize results. + Compare the source shape with the target shape before choosing Explicit, Crop, or Fit. + Crop first when the subject can lose extra background and the destination needs a strict frame. + Use Fit or Flexible when every part of the original image must remain visible. + Upscale or normal resize + Know when adding pixels needs AI and when simple scaling is enough + Do not confuse size with detail + Normal resize changes dimensions by interpolating pixels; it cannot invent real detail. AI upscale can create sharper-looking detail, but it is slower and may hallucinate texture, so the right choice depends on whether you need compatibility, speed, or visual restoration. + Use normal resize for thumbnails, upload limits, screenshots, and simple dimension changes. + Use AI upscale when a small or soft image needs to look better at a larger size. + Compare faces, text, and patterns after AI upscale because generated detail can look convincing but incorrect. + Crop, rotate, and straighten + Frame the important area before exporting or using the image in other tools + Clean up the composition + Cropping first makes later filters, OCR, PDF pages, and sharing results cleaner. + Open Crop and choose the image you want to adjust. + Use free crop or an aspect ratio when the output must fit a post, document, or avatar. + Rotate or flip before saving so later tools receive the corrected image. + Apply filters and adjustments + Build an editable filter stack and preview the result before export + Tune image appearance + Filters are useful for quick corrections, stylized output, and preparing images for OCR or printing. Small changes to contrast, sharpness, and brightness can matter more than heavy effects when the image contains text or fine details. + Add filters one by one and keep an eye on the preview after each change. + Adjust brightness, contrast, sharpness, blur, color, or masks depending on the task. + Export only when the preview matches your target device, document, or social platform. + Filter order matters + Apply cleanup, color, sharpness, and final compression in an intentional sequence + Build a cleaner filter stack + Filters are not always interchangeable. A blur before sharpening, a contrast boost before OCR, or a color change before compression can produce very different output from the same controls. + Crop and rotate first so later filters work only on the pixels that will remain. + Apply exposure, white balance, and contrast before sharpening or stylized effects. + Keep final resize and compression near the end so previewed details survive export predictably. + Preview first + Inspect images before choosing a heavier edit, conversion, or cleanup tool + Check what you actually received + Image Preview is useful when a file comes from chat, cloud storage, or another app and you are not sure what it contains. Checking dimensions, transparency, orientation, and visual quality first helps choose the correct follow-up tool. + Open Image Preview when you need to inspect several images before deciding what to do with them. + Look for rotation problems, transparent areas, compression artifacts, and unexpectedly large dimensions. + Move to Resize, Crop, Compare, EXIF, or Background Remover only after you know what needs fixing. + Remove or restore a background + Erase backgrounds automatically or refine edges manually with restore tools + Keep the subject, remove the rest + Background removal works best when you combine automatic selection with careful manual cleanup. The final export format matters as much as the mask, because JPEG will flatten transparent areas even if the preview looks correct. + Start with automatic removal if the subject is separated clearly from the background. + Zoom in and switch between erase and restore to fix hair, corners, shadows, and small details. + Save as PNG or WebP when you need transparency in the final file. + Fix background edges + Clean halos, leftover shadows, hair, holes, and soft outlines after automatic removal + Make cutouts look intentional + Automatic masks often look good at a glance but reveal problems on bright, dark, or patterned backgrounds. Edge cleanup is the difference between a quick cutout and a reusable sticker, logo, or product image. + Preview the result against contrasting backgrounds to reveal halos and missing edge detail. + Use restore for hair, corners, and transparent objects before erasing surrounding leftovers. + Export a small test on the final background color when the cutout will be placed into a design. + Draw, mark up, and watermark + Add arrows, notes, highlights, signatures, or repeated watermark overlays + Add visible information + Markup tools help explain an image, while watermarking helps brand or protect exported files. + Use Draw when you need freehand notes, shapes, highlighting, or quick annotations. + Use Watermarking when the same text or image mark should appear consistently on outputs. + Check opacity, position, and scale before exporting so the mark is visible but not distracting. + Readable watermarks + Make marks visible on bright, dark, busy, and cropped images without ruining the photo + Protect the image without hiding it + A watermark that looks good on one image may disappear on another. Size, opacity, contrast, placement, and repetition should be chosen for the whole batch, not only the first preview. + Test the mark on the brightest and darkest images in the batch before exporting all files. + Use lower opacity for large repeated marks and stronger contrast for small corner marks. + Keep important faces, text, and product details clear unless the mark is meant to prevent reuse. + Draw defaults + Set line width, color, path mode, orientation lock, and magnifier for faster markup + Make drawing feel predictable + Draw settings are helpful when you annotate screenshots, mark documents, or sign images often. Defaults reduce setup time, while the magnifier and orientation lock make precise edits easier on small screens. + Set default line width and draw color to the values you use most for arrows, highlights, or signatures. + Use default path mode when you usually draw the same kind of strokes, shapes, or markup. + Enable magnifier or orientation lock when finger input makes small details hard to place accurately. + Multi-image layouts + Pick the right multi-image tool before arranging screenshots, scans, or comparison strips + Use the right layout tool + These tools sound similar, but each one is tuned for a different kind of multi-image output. Choosing the right one first avoids fighting layout controls that were designed for a different result. + Use Collage Maker for designed layouts with several images in one composition. + Use Image Stitching or Stacking when images should become one long vertical or horizontal strip. + Use Image Splitting when one large image needs to become several smaller pieces. + Split one image into tiles + Cut a single image by rows, columns, custom percentages, format, and quality + Prepare grids and ordered pieces + Image Splitting creates multiple outputs from one source image. It can split by row and column counts, adjust row or column percentages, and save pieces with the selected output format and quality, which is useful for grids, page slices, panoramas, and ordered social posts. + Choose the source image, then set the number of rows and columns you need. + Adjust row or column percentages when the pieces should not all be equal size. + Pick output format and quality before saving so every generated piece uses the same export rules. + Cut out image strips + Remove vertical or horizontal regions and merge the remaining parts back together + Remove a region without leaving a gap + Image Cutting is different from normal crop. It can remove a selected vertical or horizontal strip, then merge the remaining image parts together. Inverse mode keeps the selected region instead, and using both inverse directions keeps the selected rectangle. + Use vertical cutting for side regions, columns, or middle strips; use horizontal cutting for banners, gaps, or rows. + Enable inverse on an axis when you want to keep the selected part instead of removing it. + Preview edges after cutting because merged content can look abrupt when the removed area crossed important details. + Convert image formats + Create JPEG, PNG, WebP, AVIF, JXL, and other copies without editing pixels manually + Pick the format for the job + Different formats are better for photos, screenshots, transparency, compression, or compatibility. Conversion is safest when you understand what the destination app supports and whether the source contains alpha, animation, or metadata you want to keep. + Use JPEG for compatible photos, PNG for lossless images and alpha, and WebP for compact sharing. + Adjust quality when the format supports it; lower values reduce size but can add artifacts. + Keep originals until you verify the converted files open correctly in the target app. + Choose the output format + Pick JPEG, PNG, WebP, AVIF, JXL, or PDF based on content and destination + Let the destination decide the format + The best format depends on what the image contains and where it will be used. Photos, screenshots, transparent assets, documents, and animated files all fail in different ways when forced into the wrong format. + Use JPEG for broad photo compatibility when transparency and exact pixels are not required. + Use PNG for sharp screenshots, UI captures, transparent graphics, and lossless edits. + Use WebP, AVIF, or JXL when compact modern output matters and the receiving app supports it. + Extract audio covers + Save embedded album art or available media frames from audio files as PNG images + Pull artwork out of audio files + Audio Covers uses Android media metadata to read embedded artwork from audio files. When embedded artwork is unavailable, the retriever may fall back to a media frame if Android can provide one; if neither exists, the tool reports that there is no image to extract. + Open Audio Covers and select one or more audio files with expected cover art. + Review the extracted cover before saving or sharing, especially for files from streaming or recording apps. + If no image is found, the audio file likely has no embedded cover or Android could not expose a usable frame. + Check EXIF and privacy + View, edit, or remove metadata before sharing sensitive photos + Control hidden image data + EXIF can contain camera data, dates, location, orientation, and other details not visible in the image. It is worth checking before public sharing, support reports, marketplace listings, or any photo that may reveal a private place. + Open EXIF tools before sharing photos that may contain location or private device information. + Remove sensitive fields or edit incorrect tags such as date, orientation, author, or camera data. + Save a new copy and share that copy instead of the original when privacy matters. + Dates and location metadata + Decide what to preserve when capture time matters but GPS or device data should not leak + Separate useful metadata from private metadata + Metadata is not all equally risky. Capture dates can be useful for archives and sorting, while GPS, device serial-like fields, software tags, or owner fields can reveal more than intended. + Keep date and time tags when chronological order matters for albums, scans, or project history. + Remove GPS and device-identifying tags before public sharing or sending images to strangers. + Export a sample and inspect EXIF again when privacy requirements are strict. + Auto EXIF cleanup + Remove metadata by default while deciding whether dates should be preserved + Make privacy the default + The EXIF settings group is useful when you want privacy cleanup to happen automatically instead of remembering it for every export. Keep Date Time is a separate decision because dates can be useful for sorting but sensitive for sharing. + Enable always-clear EXIF when most of your exports are meant for public or semi-public sharing. + Enable Keep Date Time only when preserving capture time is more important than removing every timestamp. + Use manual EXIF editing for one-off files where you need to keep some fields and remove others. + Control filenames + Use prefixes, suffixes, timestamps, presets, checksums, and sequence numbers + Make exports easy to find + A good filename pattern helps sort batches and prevents accidental overwrites. Filename settings are especially useful when exports go back to the source folder, where similar names can become confusing quickly. + Set filename prefix, suffix, timestamp, original name, preset info, or sequence options in Settings. + Use sequence numbers for batches where the order matters, such as pages, frames, or collages. + Enable overwrite only when you are sure replacing existing files is safe for the current folder. + Avoid filename collisions + Protect batch outputs when many files share names like image.jpg or screenshot.png + Make every output name unique + Duplicate filenames are common when images come from messengers, screenshots, cloud folders, or multiple cameras. A good pattern prevents accidental replacement and keeps outputs traceable back to their sources. + Include original name plus a suffix when the edited copy should be recognizable. + Add sequence, timestamp, preset, or dimensions when processing large mixed batches. + Avoid overwrite workflows until you have verified that the naming pattern cannot collide. + Overwrite files + Replace outputs with matching names only when your workflow is intentionally destructive + Use overwrite mode carefully + Overwrite Files changes how naming conflicts are handled. It is useful for repeat exports into the same folder, but it can also replace earlier results if your filename pattern produces the same name again. + Enable overwrite only for folders where replacing older generated files is expected and recoverable. + Keep overwrite disabled when exporting originals, client files, one-time scans, or anything without a backup. + Combine overwrite with a deliberate filename pattern so only the intended outputs are replaced. + Filename patterns + Combine original names, prefixes, suffixes, timestamps, presets, scale mode, and checksums + Build names that explain the output + Filename pattern settings can turn exported files into a useful history of what happened to them. This matters in batches because the folder view may be the only place where you can distinguish original size, preset, format, and processing order. + Use original filename, prefix, suffix, and timestamp when you need readable names for manual sorting. + Add preset, scale mode, file size, or checksum info when outputs must document how they were produced. + Avoid random names for workflows where files need to stay paired with originals or page order. + Choose where files are saved + Avoid losing exports by setting folders, original-folder saving, and one-time save locations + Keep outputs predictable + Save behavior matters most when you process many files or share files from cloud providers. Folder settings decide whether outputs collect in one known place, appear beside originals, or temporarily go somewhere else for a specific task. + Set a default saving folder if you always want exports in one place. + Use save-to-original-folder for cleanup workflows where output should stay near the source file. + Use one-time save location when a single task needs a different destination without changing your default. + Save or share + Choose between a persistent file and a temporary handoff to another app + Export with the right intent + Save and Share can feel similar, but they solve different problems. Saving creates a file you can find later; sharing sends a generated result through Android to another app and may not leave a durable local copy. + Use Save when the output must stay in a known folder, be backed up, or be reused later. + Use Share for quick messages, email attachments, social posting, or sending the result to another editor. + Save first when the receiving app is unreliable or when a failed share would be annoying to recreate. + Save near original + Put generated files beside source images when folder context matters + Keep outputs with their sources + Save To Original Folder is handy for camera folders, project folders, document scans, and client batches where the edited copy should stay next to the source. It can be less tidy than a dedicated output folder, so combine it with clear filename suffixes or patterns. + Enable it when each source folder is already meaningful and outputs should remain grouped there. + Use filename suffixes, prefixes, timestamps, or patterns so edited copies are easy to separate from originals. + Disable it for mixed batches when you want all generated files collected in one export folder. + One-time save folders + Send the next exports to a temporary folder without changing your normal destination + Keep special jobs separate + One-time save location is useful for temporary projects, client folders, cleanup sessions, or exports that should not mix with your regular Image Toolbox folder. It gives a short-lived destination without rewriting your default setup. + Choose a one-time folder before starting a task that belongs outside your normal export location. + Use it for short projects, shared folders, or batches where outputs must stay grouped together. + Return to the default folder after the job so later exports do not unexpectedly land in the temporary location. + Skip larger output + Avoid saving conversions that unexpectedly become heavier than the source + Protect batches from bad size surprises + Some conversions make files larger, especially PNG screenshots, already-compressed photos, high-quality WebP, or images with metadata preserved. Allow Skip If Larger prevents those outputs from replacing a smaller source in workflows where reducing size is the main goal. + Enable it when the export is meant to compress, resize, or prepare files for upload limits. + Review skipped files after a batch; they may already be optimized or may need a different format. + Disable it when quality, transparency, or format compatibility matters more than final file size. + Balance quality and file size + Understand when to change dimensions, format, or quality instead of guessing + Shrink files intentionally + File size depends on image dimensions, format, quality, transparency, and content complexity. If a file is still too heavy, changing dimensions usually helps more than repeatedly lowering quality. + Resize dimensions first when the image is larger than the destination can display. + Lower quality only for lossy formats and check text, edges, gradients, and faces after export. + Switch format when quality changes are not enough, for example WebP for sharing or PNG for crisp transparent assets. + Work with animated formats + Use GIF, APNG, WebP, and JXL tools when a file has frames instead of one bitmap + Do not treat every image as still + Animated formats need tools that understand frames, duration, and compatibility. + Use GIF or APNG tools when you need frame-level operations for those formats. + Use WebP tools when you need modern compression or animated WebP output. + Preview animation timing before sharing because some platforms convert or flatten animated images. + Compare and preview output + Inspect changes, file size, and visual differences before keeping an export + Verify before sharing + Comparison tools help catch compression artifacts, wrong colors, or unwanted edits early. + Open Compare when you want to inspect two versions side by side. + Zoom into edges, text, gradients, and transparent regions where problems are easiest to miss. + If the output is too heavy or blurry, return to the previous tool and adjust quality or format. + Use the PDF tools hub + Merge, split, rotate, remove pages, compress, protect, OCR, and watermark PDF files + Choose a PDF operation + The PDF hub groups document actions behind one entry point so you can pick the exact operation. Start there when the file is already a PDF, and use Images to PDF or Document Scanner when your source is still a set of images. + Open PDF Tools and choose the operation that matches your goal. + Select the source PDF or images, then review page order, rotation, protection, or OCR options. + Save the generated PDF and open it once to confirm page count, order, and readability. + Turn images into a PDF + Combine scans, screenshots, receipts, or photos into one shareable document + Build a clean document + Images become better PDF pages when they are cropped, ordered, and sized consistently. Treat the image list like a page stack: every rotation, margin, and page-size choice affects how readable the final document feels. + Crop or rotate images first when page edges, shadows, or orientation are incorrect. + Select images in the intended page order and adjust page size or margins if the tool offers them. + Export the PDF, then check that all pages are readable before sending it. + PDF page size and margins + Choose paper size, fit behavior, and breathing room before creating a document + Make image pages printable and readable + Images can become awkward PDF pages when their shape does not match the chosen paper size. Margins, fit mode, and page orientation decide whether content is cropped, tiny, stretched, or comfortable to read. + Use a real paper size when the PDF may be printed or submitted to a form. + Use margins for scans and screenshots so text does not sit against the page edge. + Preview portrait and landscape pages together when the source images have mixed orientation. + Scan documents + Capture paper pages and prepare them for PDF, OCR, or sharing + Capture readable pages + Document scanning works best with flat pages, good lighting, and corrected perspective. A clean scan before OCR or PDF export saves time because text recognition and compression both depend on page quality. + Place the document on a contrasting surface with enough light and minimal shadows. + Adjust detected corners or crop manually so the page fills the frame cleanly. + Export as images or PDF, then run OCR if you need selectable text. + Clean up scans + Improve paper edges, shadows, contrast, rotation, and readability before PDF or OCR + Prepare pages before archiving + A scan can be technically captured but still hard to read. Small cleanup steps before PDF export often matter more than compression settings, especially for receipts, handwritten notes, and low-light pages. + Correct perspective and crop away table edges, fingers, shadows, and background clutter. + Increase contrast carefully so text becomes clearer without destroying stamps, signatures, or pencil marks. + Run OCR only after the page is upright and readable, then verify important numbers manually. + Protect or unlock PDF files + Use passwords carefully and keep an editable source copy when possible + Handle PDF access safely + Protection tools are useful for controlled sharing, but passwords are easy to lose and hard to recover. Protect copies for distribution, keep unprotected source files separately, and verify the result before deleting anything. + Protect a copy of the PDF, not your only source document. + Store the password somewhere safe before sharing the protected file. + Use unlock only for files you are allowed to edit, then verify the unlocked copy opens correctly. + Organize PDF pages + Merge, split, rearrange, rotate, crop, remove pages, and add page numbers deliberately + Fix document structure before decoration + PDF page tools are easiest to use in the same order you would prepare a paper document: collect pages, remove the wrong ones, put them in order, correct orientation, then add final details such as page numbers or watermarks. + Use merge or images-to-PDF first when the document is still split across several files. + Rearrange, rotate, crop, or remove pages before adding page numbers, signatures, or watermarks. + Preview the final PDF after structural edits because one missing or rotated page can be hard to notice later. + PDF annotations + Remove annotations or flatten them when viewers show comments and marks differently + Control what remains visible + Annotations can be comments, highlights, drawings, form marks, or other extra PDF objects. Some viewers display them differently, so removing or flattening them can make shared documents more predictable. + Remove annotations when comments, highlights, or review marks should not be included in the shared copy. + Flatten when visible marks should stay on the page but no longer behave like editable annotations. + Keep an original copy because removing or flattening annotations can be difficult to reverse. + Compress, grayscale, or repair PDFs + Use one-file PDF operations for lighter, printable, or rebuilt document copies + Pick the PDF cleanup operation + PDF Tools includes separate operations for compression, grayscale conversion, and repair. Compression and grayscale work mainly through embedded images, while repair rebuilds the PDF structure; none of these should be treated as a guaranteed fix for every document. + Use Compress PDF when the document contains images and file size matters for sharing. + Use Grayscale when the document should be easier to print or does not need color images. + Use Repair PDF on a copy when a document fails to open or has damaged internal structure, then verify the rebuilt file. + Extract images from PDFs + Recover embedded PDF images and pack the extracted results into an archive + Save source images from documents + Extract Images scans the PDF for embedded image objects, skips duplicates where possible, and stores recovered images in a generated ZIP archive. It only extracts images that are actually embedded in the PDF, not text, vector drawings, or every visible page as a screenshot. + Use Extract Images when you need pictures stored inside a PDF, such as photos from a catalog or scanned assets. + Use PDF to Images or page extraction instead when you need full pages, including text and layout. + If the tool reports no embedded images, the page may be text/vector content or the images may not be stored as extractable objects. + Metadata, flattening, and print output + Edit document properties, rasterize pages, or prepare custom print layouts intentionally + Choose the final-document action + PDF metadata, flattening, and print preparation solve different final-stage problems. Metadata controls document properties, Flatten PDF rasterizes pages into less editable output, and Print PDF prepares a new layout with page size, orientation, margin, and pages-per-sheet controls. + Use Metadata when author, title, keywords, producer, or privacy cleanup matters. + Use Flatten PDF when visible page content should become harder to edit as separate PDF objects. + Use Print PDF when you need custom page size, orientation, margins, or multiple pages per sheet. + Recognize text + Extract text from images with selectable OCR engines and languages + Get better OCR results + OCR accuracy depends on image sharpness, language data, contrast, and page orientation. The best results usually come from preparing the image first: crop away noise, rotate upright, increase readability, then recognize text. + Crop the image to the text area and rotate it upright before recognition. + Choose the correct language and download language data if the app requests it. + Copy or share the recognized text, then manually check names, numbers, and punctuation. + Prepare images for OCR + Use crop, rotation, contrast, denoise, and scale before asking OCR to read text + Give OCR cleaner pixels + OCR mistakes often come from the input image rather than the OCR engine. Crooked pages, low contrast, blur, shadows, tiny text, and mixed backgrounds all reduce recognition quality. + Crop to the text area and rotate the image so lines are horizontal before recognition. + Increase contrast or convert to cleaner black-and-white only when it makes letters easier to read. + Resize tiny text upward moderately, but avoid aggressive sharpening that creates fake edges. + Pick OCR language data + Improve recognition by matching scripts, languages, and downloaded OCR models + Match OCR to the text + The wrong OCR language can make good photos look like bad recognition. Language data matters for scripts, punctuation, numbers, and mixed documents, so choose the language that appears in the image rather than the language of the phone UI. + Choose the language or script that appears in the image, not just the phone language. + Download missing data before working offline or processing a batch. + For mixed-language documents, run the most important language first and manually check names and numbers. + Searchable PDFs + Write OCR text back into files so scanned pages can be searched and copied + Turn scans into searchable documents + OCR can do more than copy text to the clipboard. When you write recognized text to a file or searchable PDF, the image of the page remains visible while text becomes easier to search, select, index, or archive. + Use searchable PDF output for scanned documents that should still look like the original pages. + Use write-to-file when you only need extracted text and do not care about the page image. + Check important numbers, names, and tables manually because OCR text can be searchable but still imperfect. + Scan QR codes + Read QR content from camera input or saved images when available + Read codes safely + QR codes can contain links, Wi-Fi data, contacts, text, or other payloads. + Open Scan QR Code and keep the code sharp, flat, and fully inside the frame. + Review the decoded content before opening links or copying sensitive data. + If scanning fails, improve lighting, crop the code, or try a higher-resolution source image. + Check QR content + Review links, Wi-Fi credentials, contacts, and actions before trusting a code + Treat decoded text as untrusted input + A QR code can hide a URL, contact card, Wi-Fi password, calendar event, phone number, or plain text. The visible label near the code may not match the actual payload, so review the decoded content before acting on it. + Inspect the full decoded URL before opening it, especially shortened or unfamiliar domains. + Be careful with Wi-Fi, contact, calendar, phone, and SMS codes because they can trigger sensitive actions. + Copy the content first when you need to verify it elsewhere instead of opening it immediately. + Generate QR codes + Create codes from plain text, URLs, Wi-Fi, contacts, email, phone, SMS, and location data + Build the right QR payload + The QR screen can generate codes from entered content as well as scan existing ones. Structured QR types help encode data in a format other apps understand, such as Wi-Fi login details, contact cards, email fields, phone numbers, SMS content, URLs, or geographic coordinates. + Choose plain text for simple notes, or use a structured type when the code should open as Wi-Fi, contact, URL, email, phone, SMS, or location data. + Check every field before sharing the generated code because the visible preview only reflects the payload you entered. + Test the saved QR code with a scanner when it will be printed, posted publicly, or used by other people. + Use Base64 and checksums + Encode images as text, decode text back to images, and verify file integrity + Move between files and text + Base64 is useful for small image payloads, while checksums help confirm that files did not change. These tools are most helpful in technical workflows, support reports, file transfers, and places where binary files need to become text temporarily. + Use Base64 tools to encode a small image when a workflow needs text instead of a binary file. + Decode Base64 only from trusted sources and verify the preview before saving. + Use checksum tools when you need to compare exported files or confirm an upload/download. + Package or protect data + Use ZIP and cipher tools for bundling files or transforming text data + Keep related files together + Packaging tools are helpful when several outputs should travel as one file. They are also good for keeping generated images, PDFs, logs, and metadata together when you need to send a complete result set. + Use ZIP when you need to send many exported images or documents together. + Use cipher tools only when you understand the selected method and can store required keys safely. + Test the created archive or transformed text before deleting source files. + Checksums in names + Use checksum-based filenames when uniqueness and integrity matter more than readability + Name files by content + Checksum filenames are useful when exports may have duplicate visible names or when you need to prove that two files are identical. They are less friendly for manual browsing, so use them for technical archives and verification workflows. + Enable checksum as filename when content identity is more important than a human-readable title. + Use it for archives, deduplication, repeatable exports, or files that may be uploaded and downloaded again. + Pair checksum names with folders or metadata when people still need to understand what the file represents. + Choose a checksum mode + Calculate hashes from files or text, compare one file, or batch-check many files + Verify content, not appearance + Checksum Tools has separate tabs for file hashing, text hashing, comparing a file against a known hash, and batch comparison. A checksum proves byte-for-byte identity for the selected algorithm; it does not prove that two images merely look similar. + Use Calculate for files and Text Hash for typed or pasted text data. + Use Compare when someone gives you an expected checksum for one file. + Use Batch Compare when many files need hashes or verification in one pass, then keep the selected algorithm consistent. + Clipboard and links + Control automatic clipboard input and link previews for faster text-based tools + Make automatic input predictable + Clipboard and link settings are convenient for QR, Base64, URL, and text-heavy workflows, but automatic input should stay intentional. If the app seems to fill fields by itself, check clipboard paste behavior; if link cards feel noisy or slow, adjust link preview behavior. + Allow automatic clipboard paste when you often copy text and immediately open a matching tool. + Disable automatic paste if private clipboard content appears in tools where you did not expect it. + Turn link previews off when preview fetching is slow, distracting, or unnecessary for your workflow. + Clipboard privacy + Use automatic clipboard features without accidentally exposing private copied data + Keep copied content intentional + Clipboard automation is convenient, but copied text may contain passwords, links, addresses, tokens, or private notes. Treat clipboard-based input as a speed feature that should match your habits and privacy expectations. + Disable automatic clipboard use if private text appears in tools unexpectedly. + Use clipboard-only saving only when you deliberately want the result to avoid storage. + Clear or replace the clipboard after handling sensitive text, QR data, or Base64 payloads. + Pick colors from images + Sample pixels, copy color codes, and reuse colors in palettes or designs + Sample the exact color + Color picking is useful for matching a design, extracting brand colors, or checking image values. Zooming matters because antialiasing, shadows, and compression can make neighboring pixels slightly different from the color you expect. + Open Pick Color From Image and choose a source image with the color you need. + Zoom in before sampling small details so nearby pixels do not affect the result. + Copy the color in the format required by your destination, such as HEX, RGB, or HSV. + Color formats + Understand HEX, RGB, HSV, alpha, and copied color values before pasting elsewhere + Copy the format the target expects + The same visible color can be written several ways. A design tool, CSS field, Android color value, or image editor may expect a different order, alpha handling, or color model. + Use HEX when pasting into web, design, or theme fields that expect compact color codes. + Use RGB or HSV when you need to adjust channels, brightness, saturation, or hue manually. + Check alpha separately when transparency matters because some destinations ignore it or use a different order. + Create palettes and use the color library + Extract palettes, browse saved colors, and keep consistent visual systems + Build a reusable palette + Palettes help keep exported graphics, watermarks, and drawings visually consistent. They are especially useful when you repeatedly annotate screenshots, prepare brand assets, or need the same colors across several tools. + Generate a palette from an image or add colors manually when you already know the values. + Name or organize important colors so you can find them again later. + Use copied values in Draw, watermark, gradient, SVG, or external design tools. + Browse the color library + Search named colors by name or HEX and keep favorites near the top + Find reusable named colors + Color Library loads a named color collection, sorts it by name, filters by color name or HEX value, and stores favorite colors so important entries stay easier to reach. It is useful when you need a real named color instead of sampling from an image. + Search by a color name when you know the family, such as red, blue, slate, or mint. + Search by HEX when you already have a numeric color and want to find matching or nearby named entries. + Mark frequent colors as favorites so they move ahead of the general library while browsing. + Create gradients + Build gradient images for backgrounds, placeholders, and visual experiments + Control color transitions + Gradient tools are useful when you need a generated image instead of editing an existing one. They can become clean backgrounds, placeholders, wallpapers, masks, or simple assets for external design tools. + Choose the gradient type and set the important colors first. + Adjust direction, stops, smoothness, and output size for the target use case. + Export in a format that matches the destination, usually PNG for clean generated graphics. + Use mesh gradient collections + Download available mesh gradient resources and share selected images + Use remote mesh gradient assets + Mesh Gradients can load a remote resource collection, download missing gradient images, show download progress, and share selected gradients. Availability depends on network access and the remote resource store, so an empty list usually means resources were not available yet. + Open Mesh Gradients from the gradient tools when you want ready-made mesh gradient images. + Wait for resource loading or download progress before assuming the collection is empty. + Select and share the gradients you need, or return to Gradient Maker when you want to build a custom gradient manually. + Color accessibility + Use dynamic colors, contrast, and color-blind options when the UI is hard to read + Make colors easier to use + Color settings affect comfort, readability, and how quickly you can scan controls. If tool chips, sliders, or selected states feel hard to distinguish, theme and accessibility options can be more useful than simply changing dark mode. + Try dynamic colors when you want the app to follow the current wallpaper palette. + Increase contrast or change scheme when buttons and surfaces do not stand out enough. + Use color-blind scheme options if some states or accents are difficult to distinguish. + Alpha background + Control the preview or fill color used around transparent PNG, WebP, and similar outputs + Understand transparent previews + Background color for alpha formats can make transparent images easier to inspect, but it is important to know whether you are changing a preview/fill behavior or flattening the final result. This matters for stickers, logos, and background-removed images. + Use an alpha-supporting format first, such as PNG or WebP, when transparent pixels must survive export. + Adjust background color settings when transparent edges are hard to see against the app surface. + Export a small test and reopen it in another app to confirm whether transparency or the chosen fill was saved. + AI model choice + Pick a model by image type and defect instead of choosing the largest one first + Match the model to the damage + AI Tools can download or import different ONNX/ORT models, and each model is trained for a particular kind of problem. A model for JPEG blocks, anime line cleanup, denoising, background removal, colorization, or upscaling may produce worse results when used on the wrong image type. + Read the model description before downloading; choose the model that names your actual problem, such as compression, noise, halftone, blur, or background removal. + Start with a smaller or more specific model when testing a new image type, then compare with heavier models only if the result is not good enough. + Keep only models you really use, because downloaded and imported models can take noticeable storage space. + Understand model families + Model names often hint at their target: RealESRGAN-style models upscale, SCUNet and FBCNN models clean noise or compression, background models create masks, and colorizers add color to grayscale input. Imported custom models can be powerful, but they should match the expected input/output shape and use supported ONNX or ORT format. + Use upscalers when you need more pixels, denoisers when the image is grainy, and artifact removers when compression blocks or banding are the main issue. + Use background models only for subject separation; they are not the same as general object removal or image enhancement. + Import custom models only from sources you trust and test them on a small image before using important files. + AI parameters + Tune chunk size, overlap, and parallel workers to balance quality, speed, and memory + Balance speed with stability + Chunk size controls how large image pieces are before they are processed by the model. Overlap blends neighboring chunks to hide borders. Parallel workers can speed up processing, but each worker needs memory, so high values can make large images unstable on phones with limited RAM. + Use larger chunks for smoother context when your device handles the model reliably and the image is not huge. + Increase overlap when chunk borders become visible, but remember that more overlap means more repeated work. + Raise parallel workers only after a stable test; if processing slows down, heats the device, or crashes, reduce workers first. + Avoid chunk artifacts + Chunking lets the app process images that are bigger than the model can comfortably handle at once. The tradeoff is that each tile has less surrounding context, so low overlap or too-small chunks may create visible borders, texture changes, or inconsistent detail between neighboring areas. + Increase overlap if you see rectangular borders, repeated texture changes, or detail jumps between processed regions. + Reduce chunk size if the process fails before producing output, especially on large images or heavy models. + Save a small crop test before processing the full image so you can compare parameters quickly. + AI stability + Lower chunk size, workers, and source resolution when AI processing crashes or freezes + Recover from heavy AI jobs + AI processing is one of the heaviest workflows in the app. Crashes, failed sessions, freezes, or sudden app restarts usually mean the model, source resolution, chunk size, or parallel worker count is too demanding for the current device state. + If the app crashes or fails to open a model session, lower parallel workers and chunk size, then try the same image again. + Resize very large images before AI processing when the goal does not require the original resolution. + Close other heavy apps, use a smaller model, or process fewer files at once when memory pressure keeps returning. + Diagnose model failures + A failed AI run does not always mean the image is bad. The model file may be incomplete, unsupported, too large for memory, or mismatched with the operation. When the app reports a failed session, treat it as a signal to simplify the model and parameters first. + Delete and redownload a model if it fails immediately after download or behaves as if the file is corrupted. + Try a built-in downloadable model before blaming an imported custom model, because imported models can have incompatible inputs. + Use App Logs after reproducing the failure if you need to report a crash or session error. + Experiment in Shader Studio + Use GPU shader effects for advanced image processing and custom looks + Work carefully with shaders + Shader Studio is powerful, but shader code and parameters need small, testable changes. Treat it like an experimental tool: keep a working baseline, change one thing at a time, and test with different image sizes before trusting a preset. + Start from a known working shader or a simple effect before adding parameters. + Change one parameter or code block at a time and check the preview for errors. + Export presets only after testing them on a few different image sizes. + Make SVG or ASCII art + Generate vector-like assets or text-based images for creative output + Choose the creative output type + SVG and ASCII tools serve different targets: scalable graphics or text-style rendering. Both are creative conversions, so previewing at the final size is more important than judging the result from a tiny thumbnail. + Use SVG Maker when the result should scale cleanly or be reused in design workflows. + Use ASCII Art when you want a stylized text representation of an image. + Preview at the final size because small details can disappear in generated output. + Generate noise textures + Create procedural textures for backgrounds, masks, overlays, or experiments + Tune procedural output + Noise generation creates new images from parameters, so small changes can produce very different results. It is useful for textures, masks, overlays, placeholders, or testing compression with detailed patterns. + Choose a noise type and output size before tuning fine details. + Adjust scale, intensity, colors, or seed until the pattern fits the target use. + Save useful results and write down the parameters if you need to recreate the texture later. + Generated asset resolution + Pick output size before generating gradients, noise, wallpapers, SVG-like art, or textures + Generate at the size you need + Generated images do not have a camera original to fall back on. If the output is too small, later scaling can soften patterns, shift details, or change how the asset tiles and compresses. + Set dimensions for the final use case first, such as wallpaper, icon, background, print, or overlay. + Use larger sizes for textures that may be cropped, zoomed, or reused in several layouts. + Export test versions before huge outputs because procedural detail can change how compression behaves. + Export current wallpapers + Retrieve available Home, Lock, and built-in wallpapers from the device + Save or share installed wallpapers + Wallpapers Export reads the wallpapers Android exposes through the system wallpaper APIs. Depending on device, Android version, permissions, and build variant, Home, Lock, or built-in wallpapers may be available separately or may be missing. + Open Wallpapers Export to load the wallpapers the system allows the app to read. + Select the available Home, Lock, or built-in wallpaper entries you want to save, copy, or share. + If a wallpaper is missing or the feature is unavailable, it is usually a system, permission, or build-variant limitation rather than an editing setting. + Markup projects + Use project files when annotations need to stay editable after closing the app + Keep layered edits reusable + Markup project files are useful when a screenshot, diagram, or instruction image may need changes later. Exported PNG/JPEG files are final images, while project files preserve editable layers and editing state for future adjustments. + Use Markup Layers when annotations, callouts, or layout elements should remain editable. + Save or reopen the project file before exporting a final image if you expect more revisions. + Export a normal image only when you are ready to share a flattened final version. + Encrypt files + Protect files with a password and test decryption before deleting any source copy + Handle encrypted outputs carefully + Cipher tools can protect files with password-based encryption, but they are not a replacement for remembering the key or keeping backups. Encryption is useful for transport and storage, while ZIP is better when the main goal is bundling several outputs. + Encrypt a copy first and keep the original until you have tested that decryption works with the password you stored. + Use a password manager or another safe place for the key; the app cannot guess a lost password for you. + Share encrypted files only with people who also have the password and know which tool or method should decrypt them. + Arrange tools + Group, order, search, and pin tools so the main screen matches your real workflow + Make the main screen faster + Tool arrangement settings are worth tuning once you know which tools you use most. Grouping helps exploration, custom order helps muscle memory, and favorites keep daily tools reachable even when the full list becomes large. + Use grouped mode while learning the app or when you prefer tools organized by task type. + Switch to custom order when you already know your frequent tools and want fewer scrolls. + Use search settings and favorites together for rare tools you remember by name but do not want visible all the time. + Handle large files + Avoid slow exports, memory pressure, and unexpectedly large output + Reduce work before export + Very large images, long batches, and high-quality formats can take more memory and time. The fastest fix is usually reducing the amount of work before the heaviest operation, not waiting for the same oversized input to finish. + Resize huge images before applying heavy filters, OCR, or PDF conversion. + Use a smaller batch first to confirm settings, then process the rest with the same preset. + Lower quality or change format when the output file is larger than expected. + Cache and previews + Understand generated previews, cache cleanup, and storage use after heavy sessions + Keep storage and speed balanced + Preview generation and cache can make repeated browsing faster, but heavy editing sessions may leave temporary data behind. Cache settings help when the app feels slow, storage is tight, or previews look stale after many experiments. + Keep previews enabled when browsing many images is more important than minimizing temporary storage. + Clear cache after large batches, failed experiments, or long sessions with many high-resolution files. + Use automatic cache clearing if you prefer storage cleanup over keeping previews warm between launches. + Adjust screen behavior + Use fullscreen, secure mode, brightness, and system bar options for focused work + Make the interface fit the situation + Screen settings help when you edit in landscape, present private images, or need consistent brightness. They are not required for normal use, but they solve awkward situations such as QR inspection, private previews, or cramped landscape editing. + Use fullscreen or system bar settings when editing space is more important than navigation chrome. + Use secure mode when private images should not appear in screenshots or app previews. + Use brightness enforcement for tools where dark images or QR codes are hard to inspect. + Compact controls + Use denser selectors and fast settings placement when screen space is tight + Fit more controls on small screens + Compact selectors and fast settings placement help on small phones, landscape editing, and tools with many parameters. The tradeoff is that controls can feel denser, so this is best after you already recognize the common options. + Enable compact selectors when dialogs or option lists feel too tall for the screen. + Adjust fast settings side if tool controls are easier to reach from one side of the display. + Return to roomier controls if you are still learning the app or frequently miss-tap dense options. + Keep transparency + Prevent transparent backgrounds from turning white, black, or flattened + Use alpha-aware output + Transparency survives only when the selected tool and output format support alpha. If an exported background turns white or black, the problem is usually the output format, a fill/background setting, or a destination app that flattened the image. + Choose PNG or WebP when exporting background-removed images, stickers, logos, or overlays. + Avoid JPEG for transparent output because it does not store an alpha channel. + Check settings related to background color for alpha formats if the preview shows a filled background. + Fix sharing and import issues + Use picker settings and supported formats when files do not appear or open correctly + Get the file into the app + Import problems are often caused by provider permissions, unsupported formats, or picker behavior. Files shared from cloud apps and messengers may be temporary, so picking a persistent source can be more reliable for longer edits. + Try opening the file through the system picker instead of a recent-files shortcut. + If a format is not supported by one tool, convert it first or open a more specific tool. + Review image picker and launcher settings if share flows or direct tool opening behave differently than expected. + Exit confirmation + Avoid losing unsaved edits when gestures, back presses, or tool switches happen accidentally + Protect unfinished work + Tool exit confirmation is helpful when you use gesture navigation, edit large files, or often switch between apps. It adds a small pause before leaving a tool so unfinished settings and previews are not lost by accident. + Enable confirmation if accidental back gestures have ever closed a tool before you exported. + Keep it disabled if you mostly do quick one-step conversions and prefer faster navigation. + Use it together with presets for longer workflows where rebuilding settings would be annoying. + Use logs when reporting problems + Collect useful details before opening an issue or contacting the developer + Report issues with context + A clear report with steps, app version, input type, and logs is much easier to diagnose. Logs are most useful right after reproducing a problem because they keep the surrounding context fresh. + Note the tool name, the exact action, and whether it happens with one file or every file. + Open App Logs after reproducing the issue and share the relevant log output when possible. + Attach screenshots or sample files only when they do not contain private information. + 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 + Primary + Tertiary + Secondary + Border thickness + Surface + Neutral Variant + Error + 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 + 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 + Drop Shadow + 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 + Show line angle + Shows the current line rotation in degrees while drawing + 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 + Movable crop area + Allows moving the entire crop area by dragging inside it + Drawing border + Show a thin border around the drawing and erasing canvas + 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 + Tooth Height + Horizontal Tooth Range + Vertical Tooth Range + Top Edge + Right Edge + Bottom Edge + Left Edge + 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 + Deuteranopia + 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 + Animated Emojis + Show available emoji as animations + 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 + Provider + Tesseract + PaddleOCR + PaddleOCRv5 + PaddleOCRv6 + PaddleOCR requires additional ONNX models on your device. Do you want to download %1$s data? + Universal + Korean + Latin + East Slavic + Thai + Greek + English + Cyrillic + Arabic + Devanagari + Tamil + Telugu + 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 + Favorites in grouped tools + Adds favorites as a tab when tools are grouped by type + Show favorite as last + Moves the favorite tools tab to the end + 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 + Flare + Base amount + Ring amount + Ray amount + Ring width + Distort Perspective + Java Look and Feel + Shear + X angle + Y angle + Resize output + Water Drop + Wavelength + Phase + High Pass + Color Mask + Chrome + Dissolve + Softness + Feedback + Lens Blur + Low input + High input + Low output + High output + Light Effects + Bump height + Bump softness + Ripple + X amplitude + Y amplitude + X wavelength + Y wavelength + Adaptive Blur + 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 + Torn Edge + 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 + Parallel Workers + 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. + Portrait matting model for fast person cutouts with softer hair and edge handling. + 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 + Scoop + Notch + Rounded + Smooth + Sharp edges without rounding + Rounded corners carved inward + Stepped corners with a double cut + Classic rounded corners + Shapes Type + Corners Size + Corners Animation Throttle + Squircle + Elegant rounded UI elements + Wavy + Rounded UI elements with a soft wavy edge + 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. Also supports slicing with \\o{start:end}, transliteration with \\o{t} and replace with \\o{s/old/new/}. + Auto-incrementing counter. Supports custom formatting: \\c{padding} (e.g. \\c{3} -> 001), \\c{start:step} or \\c{start:step:padding}. + 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. + The name of the parent folder where the original file was located. + The size of the original file. Supports units: \\z{b}, \\z{kb}, \\z{mb} or just \\z for human readable format. + Generates a random Universally Unique Identifier (UUID) to ensure filename uniqueness. + 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 + Motion Duration Scale + Controls app animation speed: 0 disables motion, 1 is normal, higher values slow animations down + 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. + Fast low-light enhancement using Zero-DCE++ curve estimation. + Restormer image restoration model for reducing rain streaks and wet-weather artifacts. + Document unwarping model that predicts a coordinate grid and remaps curved pages into a flatter scan. + 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 + Always clear EXIF + Remove image EXIF data on save, even when a tool requests keeping metadata + 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 + Content Aware Distortion + Not enough memory to complete this action. Please try using a smaller image, closing other apps, or restarting the app. + These images were not saved because the converted files would be larger than the originals. Check this list before deleting original images. + Files skipped: %1$s + App Logs + View logs of the app to spot the issues + Horizontal spacing + Vertical spacing + Backward energy + Use simple gradient magnitude energy map instead of forward energy algorithm + Remove masked area + Seams will pass through the masked region, removing only the selected area instead of protecting it + VHS NTSC + Advanced NTSC settings + Extra analog tuning for stronger VHS and broadcast artifacts + Chroma bleed + Tape wear + Tracking + Luma smear + Ringing + Snow + Use field + Filter type + Input luma filter + Input chroma lowpass + Chroma demodulation + Phase shift + Phase offset + Head switching + Head switching height + Head switching offset + Head switching shift + Mid-line position + Mid-line jitter + Tracking noise height + Tracking wave + Tracking snow + Tracking snow anisotropy + Tracking noise + Tracking noise intensity + Composite noise + Composite noise frequency + Composite noise intensity + Composite noise detail + Ringing frequency + Ringing power + Luma noise + Luma noise frequency + Luma noise intensity + Luma noise detail + Chroma noise + Chroma noise frequency + Chroma noise intensity + Chroma noise detail + Snow intensity + Snow anisotropy + Chroma phase noise + Chroma phase error + Chroma delay horizontal + Chroma delay vertical + VHS tape speed + VHS chroma loss + VHS sharpen intensity + VHS sharpen frequency + VHS edge wave intensity + VHS edge wave speed + VHS edge wave frequency + VHS edge wave detail + Output chroma lowpass + Scale horizontal + Scale vertical + Scale factor X + Scale factor Y + Detects common image watermarks and inpaints them with LaMa. Downloads detection and inpainting models automatically + Expand Image + Object segmentation based background remover using YOLO v11 Segmentation + Save to Original Folder + Save new files next to the original file instead of the selected folder + Watermark PDF + Not enough memory + The image is likely too large to process on this device, or the system has run out of available memory. Try reducing the image resolution, closing other apps, or choosing a smaller file. + Background processing was stopped + Android did not let the background processing notification start in time. This is a system timing limitation that cannot be reliably fixed. Restart the app and try again; keeping the app open and allowing notifications or background work may help. + Debug Menu + Menu to test app functions, this is not intended to show up in production release + Shader + Shader preset + No shader selected + Shader Studio + Create, edit, validate, import, and export custom fragment shaders + Saved shaders + Open saved shaders, duplicate, export, share, or delete + New shader + Shader file + .itshader JSON + %1$d params + Shader source + Write only the body of void main(). Use textureCoordinate for UV coordinates and inputImageTexture as the source sampler. + Functions + Optional helper functions, constants, and structs inserted before void main(). Uniforms are generated from params. + Add uniforms here when the shader needs editable values. + Reset shader + Current shader draft will be cleared + Invalid shader + Unsupported shader version %1$d. Supported version: %2$d. + Shader name must not be blank. + Shader source must not be blank. + Shader source contains unsupported characters: %1$s. Use ASCII GLSL source only. + Parameter \"%1$s\" is declared more than once. + Parameter names must not be blank. + Parameter \"%1$s\" uses a reserved GPUImage name. + Parameter \"%1$s\" must be a valid GLSL identifier. + Parameter \"%1$s\" has %2$s value type \"%3$s\", expected \"%4$s\". + Parameter \"%1$s\" cannot define min or max for bool values. + Parameter \"%1$s\" has min greater than max. + Parameter \"%1$s\" default is lower than min. + Parameter \"%1$s\" default is greater than max. + Shader must declare \"uniform sampler2D %1$s;\". + Shader must declare \"varying vec2 %1$s;\". + Shader must define \"void main()\". + Shader must write a color to gl_FragColor. + ShaderToy mainImage shaders are not supported. Use GPUImage\'s void main() contract. + Animated ShaderToy uniform \"iTime\" is not supported. + Animated ShaderToy uniform \"iFrame\" is not supported. + ShaderToy uniform \"iResolution\" is not supported. + ShaderToy iChannel textures are not supported. + External textures are not supported. + External texture extensions are not supported. + Libretro shader parameters are not supported. + Only one input texture is supported. Remove \"uniform %1$s %2$s;\". + Parameter \"%1$s\" must be declared as \"uniform %2$s %1$s;\". + Parameter \"%1$s\" is declared as \"uniform %2$s %1$s;\", expected \"uniform %3$s %1$s;\". + Shader file is empty. + Shader file must be a .itshader JSON object with version, name, and shader fields. + Shader file is not valid JSON or does not match the .itshader format. + Field \"%1$s\" is required. + %1$s is required. + %1$s \"%2$s\" is not supported. Supported types: %3$s. + %1$s is required for \"%2$s\" parameters. + %1$s must be a finite number. + %1$s must be an integer. + %1$s must be true or false. + %1$s must be a color string, RGB/RGBA array, or color object. + %1$s must use #RRGGBB or #RRGGBBAA format. + %1$s must contain valid hexadecimal color channels. + %1$s must contain 3 or 4 color channels. + %1$s must be between 0 and 255. + %1$s must be a two-number array or an object with x and y. + %1$s must contain exactly 2 numbers. + Duplicate + Show Recent Tools + Display quick access to recently used tools on the main screen + Usage Statistics + Explore app opens, tool launches, and your most used tools + App opens + Tool opens + Last tool + Successful saves + Activity streak + Data saved + Top format + Tools used + %1$d opens • %2$s + No usage statistics yet + Open a few tools and they will appear here automatically + Your usage statistics are stored only locally on your device. ImageToolbox uses them only to show your personal activity, favorite tools, and saved data insights + Reset usage statistics + Tool opens, save stats, streak, saved data, and top format will be reset. App opens will stay unchanged. + editor edit photo picture retouch adjust + resize convert scale resolution dimensions width height jpg jpeg png webp compress + compress size weight bytes mb kb reduce quality optimize + crop trim cut aspect ratio + filter effect color correction adjust lut mask + draw paint brush pencil annotate markup + cipher encrypt decrypt password secret + background remover erase remove cutout transparent alpha + preview viewer gallery inspect open + stitch merge combine panorama long screenshot + url web download internet link image + picker eyedropper sample hex rgb color + palette colors swatches extract dominant + exif metadata privacy remove strip clean gps location + compare diff difference before after + limit resize max min megapixels dimensions clamp + pdf document pages tools + ocr text recognize extract scan + gradient background color mesh + watermark logo text stamp overlay + gif animation animated convert frames + apng animation animated png convert frames + zip archive compress pack unpack files + jxl jpeg xl convert jpg image format + svg vector trace convert xml + format convert jpg jpeg png webp heic avif jxl bmp + document scanner scan paper perspective crop + qr barcode code scanner scan + stacking overlay average median astrophotography + split tiles grid slice parts + color convert hex rgb hsl hsv cmyk lab + webp convert animated image format + noise generator random texture grain + collage grid layout combine + markup layers annotate overlay edit + base64 encode decode data uri + checksum hash md5 sha1 sha256 verify + mesh gradient background + exif metadata edit gps date camera + cut split grid pieces tiles + audio cover album art music metadata + wallpaper export background + ascii text art characters + ai ml neural enhance upscale segment + color library material palette named colors + shader glsl fragment effect studio + pdf merge combine join + pdf split separate pages + pdf rotate pages orientation + pdf rearrange reorder pages sort + pdf page numbers pagination + pdf ocr text recognize searchable extract + pdf watermark stamp logo text + pdf signature sign draw + pdf protect password encrypt lock + pdf unlock password decrypt remove protection + pdf compress reduce size optimize + pdf grayscale black white monochrome + pdf repair fix recover broken + pdf metadata properties exif author title + pdf remove delete pages + pdf crop trim margins + pdf flatten annotations forms layers + pdf extract images pictures + pdf zip archive compress + pdf print printer + pdf preview viewer read + images to pdf convert jpg jpeg png document + pdf to images pages export jpg png + pdf annotations comments remove clean + Audio + File + Export profiles + Save current profile + Delete export profile + Do you want to delete export profile \"%1$s\"? + Placeholder + Use {filename} to insert the original filename without extension + texture generator brushed metal caustics cellular checkerboard marble plasma quilt wood brick camouflage cell cloud crack fabric foliage honeycomb ice lava nebula paper rust sand smoke stone terrain topography water ripple + Texture Generation + Generate various procedural textures and save them as images + Texture Type + Bias + Time + Shine + Dispersion + Samples + Angle coefficient + Gradient coefficient + Grid type + Distance power + Scale Y + Fuzziness + Basis type + Turbulence factor + Scaling + Iterations + Rings + Brushed Metal + Caustics + Cellular + Checkerboard + fBm + Marble + Plasma + Quilt + Wood + Random + Square + Hexagonal + Octagonal + Triangular + Ridged + VL Noise + SC Noise + Recent + No recently used filters yet + Brick + Camouflage + Cell + Cloud + Crack + Fabric + Foliage + Honeycomb + Ice + Lava + Nebula + Paper + Rust + Sand + Smoke + Stone + Terrain + Topography + Water Ripple + Advanced Wood + Mortar width + Irregularity + Bevel + Roughness + First threshold + Second threshold + Third threshold + Edge softness + Border width + Coverage + Detail + Depth + Branching + Horizontal threads + Vertical threads + Fuzz + Veins + Lighting + Crack width + Frost + Flow + Crust + Cloud density + Stars + Fiber density + Fiber strength + Stains + Corrosion + Pitting + Flakes + Dune frequency + Wind angle + Ripples + Wisps + Vein scale + Water level + Mountain level + Erosion + Snow level + Line count + Line thickness + Shading + Pore color + Mortar color + Dark brick color + Brick color + Highlight color + Dark color + Forest color + Earth color + Sand color + Background color + Cell color + Edge color + Sky color + Shadow color + Light color + Surface color + Variation color + Crack color + Warp color + Weft color + Dark leaf color + Leaf color + Border color + Honey color + Deep color + Ice color + Frost color + Crust color + Lava color + Glow color + Space color + Violet color + Blue color + Base color + Fiber color + Stain color + Metal color + Dark rust color + Rust color + Orange color + Smoke color + Vein color + Lowland color + Water color + Rock color + Snow color + Low color + High color + Line color + Shallow color + Grass + Dirt + Leather + Concrete + Asphalt + Moss + Fire + Aurora + Oil Slick + Watercolor + Abstract Flow + Opal + Damascus Steel + Lightning + Velvet + Ink Marbling + Holographic Foil + Bioluminescence + Cosmic Vortex + Lava Lamp + Event Horizon + Fractal Bloom + Chromatic Tunnel + Eclipse Corona + Strange Attractor + Ferrofluid Crown + Supernova + Iris + Peacock Feather + Nautilus Shell + Ringed Planet + Blade density + Blade length + Wind + Patchiness + Clumps + Moisture + Pebbles + Wrinkles + Pores + Aggregate + Cracks + Tar + Wear + Speckles + Fibers + Flame frequency + Smoke + Intensity + Ribbons + Bands + Iridescence + Darkness + Blooms + Pigment + Edges + Paper + Diffusion + Symmetry + Sharpness + Color play + Milkiness + Layers + Folding + Polish + Branches + Direction + Sheen + Folds + Feathering + Ink balance + Spectrum + Crinkles + Diffraction + Arms + Twist + Core glow + Blobs + Disk tilt + Horizon size + Disk width + Lensing + Petals + Curl + Filigree + Facets + Curvature + Moon size + Corona size + Rays + Diamond ring + Lobes + Orbit density + Thickness + Spikes + Spike length + Body size + Metallic + Shock radius + Shell width + Ejecta + Pupil size + Iris size + Color variation + Catchlight + Eye size + Barb density + Turns + Chambers + Opening + Ridges + Pearlescence + Planet size + Ring tilt + Ring width + Atmosphere + Dirt color + Dark grass color + Grass color + Tip color + Dark earth color + Dry color + Pebble color + Leather color + Concrete color + Tar color + Asphalt color + Stone color + Dust color + Soil color + Dark moss color + Moss color + Red color + Core color + Green color + Cyan color + Magenta color + Gold color + Paper color + Pigment color + Secondary color + First color + Second color + Pink color + Dark steel color + Steel color + Light steel color + Oxide color + Halo color + Bolt color + Velvet color + Sheen color + Blue ink color + Red ink color + Dark ink color + Silver color + Yellow color + Tissue color + Disk color + Hot color + Lens color + Outer color + Inner color + Corona color + Cold color + Warm color + Cloud color + Flame color + Feather color + Shell color + Pearl color + Planet color + Ring color + Delete original files after saving + Only successfully processed source files will be deleted + Original files deleted: %1$d + Failed to delete original files: %1$d + Original files deleted: %1$d, failed: %2$d + Sheet gestures + Allow dragging expanded option sheets + Chroma subsampling + Bit depth + Lossless + %1$d-bit + Batch Rename + Rename multiple files using filename patterns + batch rename files filename pattern sequence date extension + Pick files to rename + Filename pattern + Rename + Date source + EXIF date taken + File modified date + File created date + Current date + Manual date + Manual date + Manual time + The selected date is unavailable for some files. The current date will be used for them. + Enter a filename pattern + The pattern produces an invalid or overly long filename + The pattern produces duplicate filenames + All filenames already match the pattern + This pattern uses tokens that are not available for batch rename + Name will stay the same + Files renamed successfully + Some files could not be renamed + Permission was not granted + Renaming files cannot be undone. Make sure the new names are correct before continuing. + Confirm rename + Side menu settings + Menu scale + Menu alpha + Auto White Balance + Clipping + Checking for hidden watermarks + Storage + Cache limit + Clear the cache automatically when it grows beyond this size + Cleanup interval + How often to check whether the cache should be cleared + On app launch + 1 day + 1 week + 1 month + Clear the app cache automatically according to the selected limit and interval + Merge GIF + Combine multiple GIF clips into one animation + GIF clips order + Reverse + Move to start + Move to end + Play as reversed + Boomerang + Play forward and then backward + Normalize frame size + Scale frames to fit the largest clip; turn off to preserve their original scale + Delay between clips (ms) + Folder + Select all images from a folder and its subfolders + Duplicate Finder + Find exact copies and visually similar images + duplicate finder similar images exact copies photos cleanup storage dHash SHA-256 + Pick images to find duplicates + Similarity sensitivity + Exact copies + Similar images + Select all exact duplicates + Select all except recommended + No duplicates found + Try adding more images or increasing similarity sensitivity + Could not read %1$d image(s) + %1$s • %2$s reclaimable + %1$d groups • %2$s reclaimable + %1$d selected • %2$s + This cannot be undone. Android may ask you to confirm deletion in a system dialog. + Not Found + diff --git a/core/resources/src/main/res/values/themes.xml b/core/resources/src/main/res/values/themes.xml new file mode 100644 index 0000000..6d845b2 --- /dev/null +++ b/core/resources/src/main/res/values/themes.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/core/resources/src/main/res/xml/backup_rules.xml b/core/resources/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000..bd9b0fc --- /dev/null +++ b/core/resources/src/main/res/xml/backup_rules.xml @@ -0,0 +1,29 @@ + + + + + + \ No newline at end of file diff --git a/core/resources/src/main/res/xml/data_extraction_rules.xml b/core/resources/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..f35eed7 --- /dev/null +++ b/core/resources/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/core/resources/src/main/res/xml/file_paths.xml b/core/resources/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..0fddde8 --- /dev/null +++ b/core/resources/src/main/res/xml/file_paths.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/core/settings/.gitignore b/core/settings/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/settings/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/settings/build.gradle.kts b/core/settings/build.gradle.kts new file mode 100644 index 0000000..63530d5 --- /dev/null +++ b/core/settings/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/core/settings/src/main/AndroidManifest.xml b/core/settings/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/core/settings/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/di/SettingsStateEntryPoint.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/di/SettingsStateEntryPoint.kt new file mode 100644 index 0000000..0fa61c2 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/di/SettingsStateEntryPoint.kt @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/AutoCacheCleanupUseCase.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/AutoCacheCleanupUseCase.kt new file mode 100644 index 0000000..9f218ae --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/AutoCacheCleanupUseCase.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +interface AutoCacheCleanupUseCase { + fun clearCacheIfNeeded(settings: SettingsState) +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SettingsInteractor.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SettingsInteractor.kt new file mode 100644 index 0000000..0c5a780 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SettingsInteractor.kt @@ -0,0 +1,291 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.CacheAutoClearInterval +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 setCacheAutoClearLimitBytes(bytes: Long) + + suspend fun setCacheAutoClearInterval(interval: CacheAutoClearInterval) + + suspend fun setLastCacheAutoClearTimestampMillis(timestampMillis: Long) + + suspend fun toggleGroupOptionsByTypes() + + suspend fun toggleShowFavoriteToolsInGroupedMode() + + suspend fun toggleShowFavoriteAsLast() + + 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 toggleDrawBitmapBorder() + + suspend fun toggleExifWidgetInitialState() + + suspend fun setInitialOCRLanguageCodes(list: List) + + suspend fun setScreensWithBrightnessEnforcement(data: List) + + suspend fun toggleConfettiEnabled() + + suspend fun toggleSecureMode() + + suspend fun toggleUseRandomEmojis() + + suspend fun toggleUseAnimatedEmojis() + + 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 toggleEnableSheetGestures() + + 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 setInitialOcrEngine(engine: Int) + + suspend fun setInitialPaddleOcrModel(model: 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 setShapeByInteractionThrottle(delay: Long) + + suspend fun setFilenamePattern(pattern: String?) + + suspend fun setFlingType(type: FlingType) + + suspend fun setHiddenForShareScreens(data: List) + + suspend fun toggleKeepDateTime() + + suspend fun toggleAlwaysClearExif() + + suspend fun toggleEnableBackgroundColorForAlphaFormats() + + suspend fun toggleShowToolsHistory() + + suspend fun setMotionDurationScale(scale: Float) + +} + +fun SettingsInteractor.toSimpleSettingsInteractor(): SimpleSettingsInteractor = + object : SimpleSettingsInteractor by this {} diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SettingsManager.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SettingsManager.kt new file mode 100644 index 0000000..4a57bc3 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SettingsManager.kt @@ -0,0 +1,20 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SettingsProvider.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SettingsProvider.kt new file mode 100644 index 0000000..5641425 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SettingsProvider.kt @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SimpleSettingsInteractor.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SimpleSettingsInteractor.kt new file mode 100644 index 0000000..28c1ce1 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SimpleSettingsInteractor.kt @@ -0,0 +1,70 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 toggleCropOverlayDraggable() + + 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 toggleSaveToOriginalFolder() + + suspend fun toggleDeleteOriginalsAfterSave() + + suspend fun setSpotHealMode(mode: Int) + + suspend fun setBorderWidth(width: Float) + +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/CacheAutoClearInterval.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/CacheAutoClearInterval.kt new file mode 100644 index 0000000..9566479 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/CacheAutoClearInterval.kt @@ -0,0 +1,49 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 kotlin.time.Duration +import kotlin.time.Duration.Companion.days + +enum class CacheAutoClearInterval( + val key: String, + val duration: Duration, +) { + OnAppLaunch( + key = "on_app_launch", + duration = Duration.ZERO + ), + Day( + key = "day", + duration = 1.days + ), + Week( + key = "week", + duration = 7.days + ), + Month( + key = "month", + duration = 30.days + ); + + companion object { + fun fromKey(key: String?): CacheAutoClearInterval? = entries.find { + it.key == key + } + } +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/ColorHarmonizer.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/ColorHarmonizer.kt new file mode 100644 index 0000000..d879534 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/ColorHarmonizer.kt @@ -0,0 +1,49 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + } + +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/CopyToClipboardMode.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/CopyToClipboardMode.kt new file mode 100644 index 0000000..e719730 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/CopyToClipboardMode.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + } + +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/DomainFontFamily.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/DomainFontFamily.kt new file mode 100644 index 0000000..02e14f0 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/DomainFontFamily.kt @@ -0,0 +1,122 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/FastSettingsSide.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/FastSettingsSide.kt new file mode 100644 index 0000000..802a512 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/FastSettingsSide.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/FilenameBehavior.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/FilenameBehavior.kt new file mode 100644 index 0000000..2a3ab62 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/FilenameBehavior.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/FlingType.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/FlingType.kt new file mode 100644 index 0000000..7b2d768 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/FlingType.kt @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/NightMode.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/NightMode.kt new file mode 100644 index 0000000..4fa9456 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/NightMode.kt @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + } +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/OneTimeSaveLocation.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/OneTimeSaveLocation.kt new file mode 100644 index 0000000..f67a2b8 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/OneTimeSaveLocation.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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" \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SettingsState.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SettingsState.kt new file mode 100644 index 0000000..fb436d2 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SettingsState.kt @@ -0,0 +1,287 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 cacheAutoClearLimitBytes: Long, + val cacheAutoClearInterval: CacheAutoClearInterval, + val lastCacheAutoClearTimestampMillis: Long, + val showUpdateDialogOnStartup: Boolean, + val groupOptionsByTypes: Boolean, + val showFavoriteToolsInGroupedMode: Boolean, + val showFavoriteAsLast: Boolean, + val screenList: List, + val colorTupleList: String?, + val addSequenceNumber: Boolean, + val saveFolderUri: String?, + val saveToOriginalFolder: Boolean, + val deleteOriginalsAfterSave: Boolean, + 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 cropOverlayDraggable: Boolean, + val drawBitmapBorder: Boolean, + val exifWidgetInitialState: Boolean, + val initialOcrCodes: List, + val screenListWithMaxBrightnessEnforcement: List, + val isConfettiEnabled: Boolean, + val isSecureMode: Boolean, + val useRandomEmojis: Boolean, + val useAnimatedEmojis: 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 enableSheetGestures: 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 initialOcrEngine: Int, + val initialPaddleOcrModel: Int, + val spotHealMode: Int, + val snowfallMode: SnowfallMode, + val defaultImageFormat: ImageFormat?, + val defaultQuality: Quality, + val shapesType: ShapeType, + val shapeByInteractionThrottle: Long, + val filenamePattern: String?, + val filenameBehavior: FilenameBehavior, + val flingType: FlingType, + val hiddenForShareScreens: List, + val keepDateTime: Boolean, + val isAlwaysClearExif: Boolean, + val enableBackgroundColorForAlphaFormats: Boolean, + val showToolsHistory: Boolean, + val motionDurationScale: Float, +) { + + 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, + cacheAutoClearLimitBytes = 20L * 1024 * 1024, + cacheAutoClearInterval = CacheAutoClearInterval.OnAppLaunch, + lastCacheAutoClearTimestampMillis = 0L, + showUpdateDialogOnStartup = !Flavor.isFoss(), + groupOptionsByTypes = true, + showFavoriteToolsInGroupedMode = false, + showFavoriteAsLast = false, + screenList = emptyList(), + colorTupleList = null, + addSequenceNumber = true, + saveFolderUri = null, + saveToOriginalFolder = false, + deleteOriginalsAfterSave = false, + 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, + cropOverlayDraggable = true, + drawBitmapBorder = true, + exifWidgetInitialState = false, + initialOcrCodes = listOf("eng"), + screenListWithMaxBrightnessEnforcement = emptyList(), + isConfettiEnabled = true, + isSecureMode = false, + useRandomEmojis = false, + useAnimatedEmojis = true, + iconShape = 0, + useEmojiAsPrimaryColor = false, + dragHandleWidth = 64, + confettiType = 0, + allowAutoClipboardPaste = false, + confettiColorHarmonizer = ColorHarmonizer.Primary, + confettiHarmonizationLevel = 0.5f, + skipImagePicking = false, + generatePreviews = true, + enableSheetGestures = false, + 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, + initialOcrEngine = 0, + initialPaddleOcrModel = 0, + spotHealMode = 0, + snowfallMode = SnowfallMode.Auto, + defaultImageFormat = null, + defaultQuality = Quality.Base(), + shapesType = ShapeType.Rounded(), + shapeByInteractionThrottle = 200, + filenamePattern = null, + filenameBehavior = FilenameBehavior.None(), + flingType = FlingType.DEFAULT, + hiddenForShareScreens = emptyList(), + keepDateTime = false, + isAlwaysClearExif = false, + enableBackgroundColorForAlphaFormats = false, + showToolsHistory = true, + motionDurationScale = 1f, + ) + } + } + +} diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/ShapeType.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/ShapeType.kt new file mode 100644 index 0000000..078a0fe --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/ShapeType.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + val scaleRatio: Float + get() = when (this) { + is Notch, + is Scoop -> 0.65f + + else -> 1f + } + + val effectiveStrength: Float + get() = strength * scaleRatio + + 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) + is Wavy -> Wavy(strength = strength) + is Scoop -> Scoop(strength = strength) + is Notch -> Notch(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 + + class Wavy( + override val strength: Float = 1f + ) : ShapeType + + class Scoop( + override val strength: Float = 1f + ) : ShapeType + + class Notch( + override val strength: Float = 1f + ) : ShapeType + + companion object { + val entries by lazy { + listOf( + Rounded(), + Cut(), + Squircle(), + Smooth(), + Wavy(), + Scoop(), + Notch(), + ) + } + } +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SliderType.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SliderType.kt new file mode 100644 index 0000000..0a1fb14 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SliderType.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } + +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SnowfallMode.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SnowfallMode.kt new file mode 100644 index 0000000..35d875e --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SnowfallMode.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SwitchType.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SwitchType.kt new file mode 100644 index 0000000..f2059d7 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SwitchType.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } + +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/EditPresetsController.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/EditPresetsController.kt new file mode 100644 index 0000000..2b0de4e --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/EditPresetsController.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + ) + } +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/IconShape.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/IconShape.kt new file mode 100644 index 0000000..2b5f554 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/IconShape.kt @@ -0,0 +1,137 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + } + } +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/PicturePickerMode.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/PicturePickerMode.kt new file mode 100644 index 0000000..a09f434 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/PicturePickerMode.kt @@ -0,0 +1,105 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.ui.graphics.vector.ImageVector +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FolderImage +import com.t8rin.imagetoolbox.core.resources.icons.FolderOpen +import com.t8rin.imagetoolbox.core.resources.icons.ImageEmbedded +import com.t8rin.imagetoolbox.core.resources.icons.ImagesMode +import com.t8rin.imagetoolbox.core.resources.icons.PhotoCameraBack +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 + ) + + data object Folder : PicturePickerMode( + ordinal = 5, + icon = Icons.Outlined.FolderOpen, + title = R.string.folder_picker, + subtitle = R.string.folder_picker_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 + 5 -> Folder + else -> SafeEmbedded + } + + val entries by lazy { + listOf( + SafeEmbedded, PhotoPicker, Gallery, GetContent, CameraCapture, Folder + ).distinct() + } + } +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/Setting.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/Setting.kt new file mode 100644 index 0000000..90b85fc --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/Setting.kt @@ -0,0 +1,784 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +import kotlinx.serialization.Serializable + +@Serializable +sealed class Setting( + val title: Int, + val subtitle: Int?, +) { + @Serializable + data object AddFileSize : Setting( + title = R.string.add_file_size, + subtitle = R.string.add_file_size_sub + ) + + @Serializable + data object AddOriginalFilename : Setting( + title = R.string.add_original_filename, + subtitle = R.string.add_original_filename_sub + ) + + @Serializable + data object AllowBetas : Setting( + title = R.string.allow_betas, + subtitle = R.string.allow_betas_sub + ) + + @Serializable + data object AllowImageMonet : Setting( + title = R.string.allow_image_monet, + subtitle = R.string.allow_image_monet_sub + ) + + @Serializable + data object AmoledMode : Setting( + title = R.string.amoled_mode, + subtitle = R.string.amoled_mode_sub + ) + + @Serializable + data object Analytics : Setting( + title = R.string.analytics, + subtitle = R.string.analytics_sub + ) + + @Serializable + data object Author : Setting( + title = R.string.app_developer, + subtitle = R.string.app_developer_nick + ) + + @Serializable + data object AutoCacheClear : Setting( + title = R.string.auto_cache_clearing, + subtitle = R.string.auto_cache_clearing_sub + ) + + @Serializable + data object AutoCheckUpdates : Setting( + title = R.string.check_updates, + subtitle = R.string.check_updates_sub + ) + + @Serializable + data object Backup : Setting( + title = R.string.backup, + subtitle = R.string.backup_sub + ) + + @Serializable + data object BorderThickness : Setting( + title = R.string.border_thickness, + subtitle = null + ) + + @Serializable + data object ChangeFont : Setting( + title = R.string.font, + subtitle = null + ) + + @Serializable + data object ChangeLanguage : Setting( + title = R.string.language, + subtitle = null + ) + + @Serializable + data object CheckUpdatesButton : Setting( + title = R.string.check_updates, + subtitle = R.string.check_updates_sub + ) + + @Serializable + data object ClearCache : Setting( + title = R.string.cache, + subtitle = R.string.cache_size + ) + + @Serializable + data object ColorScheme : Setting( + title = R.string.color_scheme, + subtitle = R.string.color_scheme + ) + + @Serializable + data object Crashlytics : Setting( + title = R.string.crashlytics, + subtitle = R.string.crashlytics_sub + ) + + @Serializable + data object CurrentVersionCode : Setting( + title = R.string.app_name, + subtitle = R.string.version + ) + + @Serializable + data object Donate : Setting( + title = R.string.donation, + subtitle = R.string.donation_sub + ) + + @Serializable + data object DynamicColors : Setting( + title = R.string.dynamic_colors, + subtitle = R.string.dynamic_colors_sub + ) + + @Serializable + data object EmojisCount : Setting( + title = R.string.emojis_count, + subtitle = null + ) + + @Serializable + data object Emoji : Setting( + title = R.string.emoji, + subtitle = R.string.emoji_sub + ) + + @Serializable + data object ContainerShadows : Setting( + title = R.string.containers_shadow, + subtitle = R.string.containers_shadow_sub + ) + + @Serializable + data object AppBarShadows : Setting( + title = R.string.app_bars_shadow, + subtitle = R.string.app_bars_shadow_sub + ) + + @Serializable + data object SliderShadows : Setting( + title = R.string.sliders_shadow, + subtitle = R.string.sliders_shadow_sub + ) + + @Serializable + data object SwitchShadows : Setting( + title = R.string.switches_shadow, + subtitle = R.string.switches_shadow_sub + ) + + @Serializable + data object FABShadows : Setting( + title = R.string.fabs_shadow, + subtitle = R.string.fabs_shadow_sub + ) + + @Serializable + data object ButtonShadows : Setting( + title = R.string.buttons_shadow, + subtitle = R.string.buttons_shadow_sub + ) + + @Serializable + data object FabAlignment : Setting( + title = R.string.fab_alignment, + subtitle = null + ) + + @Serializable + data object FilenamePrefix : Setting( + title = R.string.prefix, + subtitle = null + ) + + @Serializable + data object FontScale : Setting( + title = R.string.font_scale, + subtitle = null + ) + + @Serializable + data object GroupOptions : Setting( + title = R.string.group_tools_by_type, + subtitle = R.string.group_tools_by_type_sub + ) + + @Serializable + data object FavoriteToolsInGroupedMode : Setting( + title = R.string.favorite_tools_in_grouped_mode, + subtitle = R.string.favorite_tools_in_grouped_mode_sub + ) + + @Serializable + data object ShowFavoriteAsLast : Setting( + title = R.string.show_favorite_as_last, + subtitle = R.string.show_favorite_as_last_sub + ) + + @Serializable + data object HelpTranslate : Setting( + title = R.string.help_translate, + subtitle = R.string.help_translate_sub + ) + + @Serializable + data object HelpTips : Setting( + title = R.string.help_tips, + subtitle = R.string.help_tips_settings_sub + ) + + @Serializable + data object ImagePickerMode : Setting( + title = R.string.photo_picker, + subtitle = R.string.photo_picker_sub + ) + + @Serializable + data object IssueTracker : Setting( + title = R.string.issue_tracker, + subtitle = R.string.issue_tracker_sub + ) + + @Serializable + data object LockDrawOrientation : Setting( + title = R.string.lock_draw_orientation, + subtitle = R.string.lock_draw_orientation_sub + ) + + @Serializable + data object NightMode : Setting( + title = R.string.night_mode, + subtitle = null + ) + + @Serializable + data object Presets : Setting( + title = R.string.presets, + subtitle = R.string.presets_sub + ) + + @Serializable + data object RandomizeFilename : Setting( + title = R.string.randomize_filename, + subtitle = R.string.randomize_filename_sub + ) + + @Serializable + data object ReplaceSequenceNumber : Setting( + title = R.string.replace_sequence_number, + subtitle = R.string.replace_sequence_number_sub + ) + + @Serializable + data object Reset : Setting( + title = R.string.reset, + subtitle = R.string.reset_settings_sub + ) + + @Serializable + data object Restore : Setting( + title = R.string.restore, + subtitle = R.string.restore_sub + ) + + @Serializable + data object SavingFolder : Setting( + title = R.string.folder, + subtitle = null + ) + + @Serializable + data object SaveToOriginalFolder : Setting( + title = R.string.save_to_original_folder, + subtitle = R.string.save_to_original_folder_sub + ) + + @Serializable + data object DeleteOriginalsAfterSave : Setting( + title = R.string.delete_originals_after_save, + subtitle = R.string.delete_originals_after_save_sub + ) + + @Serializable + data object ScreenOrder : Setting( + title = R.string.order, + subtitle = R.string.order_sub + ) + + @Serializable + data object ScreenSearch : Setting( + title = R.string.search_option, + subtitle = R.string.search_option_sub + ) + + @Serializable + data object SourceCode : Setting( + title = R.string.check_source_code, + subtitle = R.string.check_source_code_sub + ) + + @Serializable + data object TelegramGroup : Setting( + title = R.string.tg_chat, + subtitle = R.string.tg_chat_sub + ) + + @Serializable + data object AutoPinClipboard : Setting( + title = R.string.auto_pin, + subtitle = R.string.auto_pin_sub + ) + + @Serializable + data object AutoPinClipboardOnlyClip : Setting( + title = R.string.only_clip, + subtitle = R.string.only_clip_sub + ) + + @Serializable + data object VibrationStrength : Setting( + title = R.string.vibration_strength, + subtitle = null + ) + + @Serializable + data object OverwriteFiles : Setting( + title = R.string.overwrite_files, + subtitle = R.string.overwrite_files_sub + ) + + @Serializable + data object FilenameSuffix : Setting( + title = R.string.suffix, + subtitle = null + ) + + @Serializable + data object DefaultScaleMode : Setting( + title = R.string.scale_mode, + subtitle = null + ) + + @Serializable + data object DefaultColorSpace : Setting( + title = R.string.tag_color_space, + subtitle = null + ) + + @Serializable + data object SwitchType : Setting( + title = R.string.switch_type, + subtitle = null + ) + + @Serializable + data object Magnifier : Setting( + title = R.string.magnifier, + subtitle = R.string.magnifier_sub + ) + + @Serializable + data object DrawBitmapBorder : Setting( + title = R.string.draw_bitmap_border, + subtitle = R.string.draw_bitmap_border_sub + ) + + @Serializable + data object ExifWidgetInitialState : Setting( + title = R.string.force_exif_widget_initial_value, + subtitle = R.string.force_exif_widget_initial_value_sub + ) + + @Serializable + data object BrightnessEnforcement : Setting( + title = R.string.brightness_enforcement, + subtitle = null + ) + + @Serializable + data object Confetti : Setting( + title = R.string.confetti, + subtitle = R.string.confetti_sub + ) + + @Serializable + data object SecureMode : Setting( + title = R.string.secure_mode, + subtitle = R.string.secure_mode_sub + ) + + @Serializable + data object UseRandomEmojis : Setting( + title = R.string.random_emojis, + subtitle = R.string.random_emojis_sub + ) + + @Serializable + data object UseAnimatedEmojis : Setting( + title = R.string.animated_emojis, + subtitle = R.string.animated_emojis_sub + ) + + @Serializable + data object IconShape : Setting( + title = R.string.icon_shape, + subtitle = R.string.icon_shape_sub + ) + + @Serializable + data object DragHandleWidth : Setting( + title = R.string.drag_handle_width, + subtitle = null + ) + + @Serializable + data object ShapeByInteractionThrottle : Setting( + title = R.string.shape_by_interaction_throttle, + subtitle = null + ) + + @Serializable + data object ConfettiType : Setting( + title = R.string.confetti_type, + subtitle = null + ) + + @Serializable + data object AllowAutoClipboardPaste : Setting( + title = R.string.auto_paste, + subtitle = R.string.auto_paste_sub + ) + + @Serializable + data object ConfettiHarmonizer : Setting( + title = R.string.harmonization_color, + subtitle = null + ) + + @Serializable + data object ConfettiHarmonizationLevel : Setting( + title = R.string.harmonization_level, + subtitle = null + ) + + @Serializable + data object SkipFilePicking : Setting( + title = R.string.skip_file_picking, + subtitle = R.string.skip_file_picking_sub + ) + + @Serializable + data object GeneratePreviews : Setting( + title = R.string.generate_previews, + subtitle = R.string.generate_previews_sub + ) + + @Serializable + data object EnableSheetGestures : Setting( + title = R.string.sheet_gestures, + subtitle = R.string.sheet_gestures_sub + ) + + @Serializable + data object ShowSettingsInLandscape : Setting( + title = R.string.show_settings_in_landscape, + subtitle = R.string.show_settings_in_landscape_sub + ) + + @Serializable + data object UseFullscreenSettings : Setting( + title = R.string.fullscreen_settings, + subtitle = R.string.fullscreen_settings_sub + ) + + @Serializable + data object DefaultDrawLineWidth : Setting( + title = R.string.default_line_width, + subtitle = null + ) + + @Serializable + data object OpenEditInsteadOfPreview : Setting( + title = R.string.open_edit_instead_of_preview, + subtitle = R.string.open_edit_instead_of_preview_sub + ) + + @Serializable + data object CanEnterPresetsByTextField : Setting( + title = R.string.allow_enter_by_text_field, + subtitle = R.string.allow_enter_by_text_field_sub + ) + + @Serializable + data object ColorBlindScheme : Setting( + title = R.string.color_blind_scheme, + subtitle = R.string.color_blind_scheme_sub + ) + + @Serializable + data object EnableLinksPreview : Setting( + title = R.string.links_preview, + subtitle = R.string.links_preview_sub + ) + + @Serializable + data object DefaultDrawColor : Setting( + title = R.string.default_draw_color, + subtitle = null + ) + + @Serializable + data object DefaultDrawPathMode : Setting( + title = R.string.default_draw_path_mode, + subtitle = null + ) + + @Serializable + data object AddTimestampToFilename : Setting( + title = R.string.add_timestamp, + subtitle = R.string.add_timestamp_sub + ) + + @Serializable + data object UseFormattedFilenameTimestamp : Setting( + title = R.string.formatted_timestamp, + subtitle = R.string.formatted_timestamp_sub + ) + + @Serializable + data object OneTimeSaveLocation : Setting( + title = R.string.one_time_save_location, + subtitle = R.string.one_time_save_location_sub + ) + + @Serializable + data object TelegramChannel : Setting( + title = R.string.ci_channel, + subtitle = R.string.ci_channel_sub + ) + + @Serializable + data object FreeSoftwarePartner : Setting( + title = R.string.free_software_partner, + subtitle = R.string.free_software_partner_sub + ) + + @Serializable + data object DefaultResizeType : Setting( + title = R.string.resize_type, + subtitle = null + ) + + @Serializable + data object SystemBarsVisibility : Setting( + title = R.string.system_bars_visibility, + subtitle = null + ) + + @Serializable + data object ShowSystemBarsBySwipe : Setting( + title = R.string.show_system_bars_by_swipe, + subtitle = R.string.show_system_bars_by_swipe_sub + ) + + @Serializable + data object UseCompactSelectors : Setting( + title = R.string.compact_selectors, + subtitle = R.string.compact_selectors_sub + ) + + @Serializable + data object MainScreenTitle : Setting( + title = R.string.main_screen_title, + subtitle = null + ) + + @Serializable + data object SliderType : Setting( + title = R.string.slider_type, + subtitle = null + ) + + @Serializable + data object CenterAlignDialogButtons : Setting( + title = R.string.center_align_dialog_buttons, + subtitle = R.string.center_align_dialog_buttons_sub + ) + + @Serializable + data object OpenSourceLicenses : Setting( + title = R.string.open_source_licenses, + subtitle = R.string.open_source_licenses_sub + ) + + @Serializable + data object FastSettingsSide : Setting( + title = R.string.fast_settings_side, + subtitle = R.string.fast_settings_side_sub + ) + + @Serializable + data object ChecksumAsFilename : Setting( + title = R.string.checksum_as_filename, + subtitle = R.string.checksum_as_filename_sub + ) + + @Serializable + data object EnableToolExitConfirmation : Setting( + title = R.string.tool_exit_confirmation, + subtitle = R.string.tool_exit_confirmation_sub + ) + + @Serializable + data object SendLogs : Setting( + title = R.string.send_logs, + subtitle = R.string.send_logs_sub + ) + + @Serializable + data object AppLogs : Setting( + title = R.string.app_logs, + subtitle = R.string.app_logs_sub + ) + + @Serializable + data object AppUsageStatistics : Setting( + title = R.string.usage_statistics, + subtitle = R.string.usage_statistics_sub + ) + + @Serializable + data object AddPresetToFilename : Setting( + title = R.string.add_preset_to_filename, + subtitle = R.string.add_preset_to_filename_sub + ) + + @Serializable + data object AddImageScaleModeToFilename : Setting( + title = R.string.add_image_scale_mode_to_filename, + subtitle = R.string.add_image_scale_mode_to_filename_sub + ) + + @Serializable + data object AllowSkipIfLarger : Setting( + title = R.string.allow_skip_if_larger, + subtitle = R.string.allow_skip_if_larger_sub + ) + + @Serializable + data object EnableLauncherMode : Setting( + title = R.string.launcher_mode, + subtitle = R.string.launcher_mode_sub + ) + + @Serializable + data object SnowfallMode : Setting( + title = R.string.snowfall_mode, + subtitle = null + ) + + @Serializable + data object DefaultImageFormat : Setting( + title = R.string.image_format, + subtitle = null + ) + + @Serializable + data object DefaultQuality : Setting( + title = R.string.quality, + subtitle = null + ) + + @Serializable + data object ShapeType : Setting( + title = R.string.shapes_type, + subtitle = null + ) + + @Serializable + data object CornersSize : Setting( + title = R.string.corners_size, + subtitle = null + ) + + @Serializable + data object FilenamePattern : Setting( + title = R.string.filename_format, + subtitle = null + ) + + @Serializable + data object FlingType : Setting( + title = R.string.fling_type, + subtitle = null + ) + + @Serializable + data object MotionDurationScale : Setting( + title = R.string.motion_duration_scale, + subtitle = R.string.motion_duration_scale_sub + ) + + @Serializable + data object ToolsHiddenForShare : Setting( + title = R.string.hidden_for_share, + subtitle = null + ) + + @Serializable + data object KeepDateTime : Setting( + title = R.string.keep_date_time, + subtitle = R.string.keep_date_time_sub + ) + + @Serializable + data object AlwaysClearExif : Setting( + title = R.string.always_clear_exif, + subtitle = R.string.always_clear_exif_sub + ) + + @Serializable + data object EnableBackgroundColorForAlphaFormats : Setting( + title = R.string.background_color_for_alpha_formats, + subtitle = R.string.background_color_for_alpha_formats_sub + ) + + @Serializable + data object DebugMenu : Setting( + title = R.string.debug_menu, + subtitle = R.string.debug_menu_sub + ) + + @Serializable + data object ShowToolsHistory : Setting( + title = R.string.show_recent_tools, + subtitle = R.string.show_recent_tools_sub + ) + + @Serializable + data object CacheAutoClearLimit : Setting( + title = R.string.cache_auto_clear_limit, + subtitle = R.string.cache_auto_clear_limit_sub + ) + + @Serializable + data object CacheAutoClearInterval : Setting( + title = R.string.cache_auto_clear_interval, + subtitle = R.string.cache_auto_clear_interval_sub + ) + +} diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/SettingsGroup.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/SettingsGroup.kt new file mode 100644 index 0000000..aed148b --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/SettingsGroup.kt @@ -0,0 +1,453 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.ui.graphics.vector.ImageVector +import com.t8rin.imagetoolbox.core.domain.utils.Flavor +import com.t8rin.imagetoolbox.core.resources.BuildConfig +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Celebration +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.Description +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.FolderOpen +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.Info +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 +import java.util.Locale + +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.ShapeByInteractionThrottle, + Setting.FlingType, + Setting.MotionDurationScale, + 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.ShowToolsHistory, + Setting.ScreenSearch, + Setting.EnableLauncherMode, + Setting.GroupOptions, + Setting.FavoriteToolsInGroupedMode, + Setting.ShowFavoriteAsLast + ), + 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, + Setting.DrawBitmapBorder + ), + initialState = false + ) + + data object Exif : SettingsGroup( + id = 11, + icon = Icons.Rounded.Exif, + titleId = R.string.exif, + settingsList = listOf( + Setting.AlwaysClearExif, + Setting.ExifWidgetInitialState, + Setting.KeepDateTime + ), + initialState = false + ) + + data object Folder : SettingsGroup( + id = 12, + icon = Icons.Rounded.FolderOpen, + titleId = R.string.folder, + settingsList = listOf( + Setting.SavingFolder, + Setting.SaveToOriginalFolder, + Setting.DeleteOriginalsAfterSave, + 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 Storage : SettingsGroup( + id = 14, + icon = Icons.Rounded.Database, + titleId = R.string.storage, + settingsList = listOf( + Setting.ClearCache, + Setting.AutoCacheClear, + Setting.CacheAutoClearLimit, + Setting.CacheAutoClearInterval + ), + 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 = listOfNotNull( + Setting.CurrentVersionCode, + Setting.AppUsageStatistics, + Setting.AppLogs, + Setting.OpenSourceLicenses, + Setting.HelpTranslate, + Setting.IssueTracker, + Setting.FreeSoftwarePartner.takeIf { !Flavor.isFoss() && Locale.getDefault().language == "ru" }, + Setting.TelegramGroup, + Setting.TelegramChannel, + Setting.SourceCode, + Setting.HelpTips, + Setting.DebugMenu.takeIf { BuildConfig.DEBUG } + ), + 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.UseAnimatedEmojis, + 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, + Setting.EnableSheetGestures + ), + initialState = false + ) + + companion object { + val entries: List by lazy { + listOfNotNull( + ContactMe, + PrimaryCustomization, + SecondaryCustomization, + NightMode, + Layout, + Emoji, + Confetti, + Shadows, + Haptics, + Screen, + Font, + Behavior, + ToolsArrangement, + Presets, + DefaultValues, + Draw, + Exif, + Folder, + Filename, + Clipboard, + Storage, + ImageSource, + BackupRestore, + Firebase.takeIf { !Flavor.isFoss() }, + Updates, + AboutApp + ) + } + } +} diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/UiFontFamily.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/UiFontFamily.kt new file mode 100644 index 0000000..e632845 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/UiFontFamily.kt @@ -0,0 +1,510 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + ) + ) +) \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/UiSettingsState.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/UiSettingsState.kt new file mode 100644 index 0000000..7c7afbd --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/UiSettingsState.kt @@ -0,0 +1,518 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.resources.emoji.Emoji.initEmoji +import com.t8rin.imagetoolbox.core.settings.domain.model.CacheAutoClearInterval +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 cacheAutoClearLimitBytes: Long, + val cacheAutoClearInterval: CacheAutoClearInterval, + val lastCacheAutoClearTimestampMillis: Long, + val groupOptionsByTypes: Boolean, + val showFavoriteToolsInGroupedMode: Boolean, + val showFavoriteAsLast: Boolean, + val screenList: List, + val colorTupleList: List, + val addSequenceNumber: Boolean, + val saveFolderUri: Uri?, + val saveToOriginalFolder: Boolean, + val deleteOriginalsAfterSave: Boolean, + 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 cropOverlayDraggable: Boolean, + val drawBitmapBorder: Boolean, + val exifWidgetInitialState: Boolean, + val screenListWithMaxBrightnessEnforcement: List, + val isConfettiEnabled: Boolean, + val isSecureMode: Boolean, + val useRandomEmojis: Boolean, + val useAnimatedEmojis: 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 enableSheetGestures: 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 shapeByInteractionThrottle: Long, + val filenamePattern: String?, + val filenameBehavior: FilenameBehavior, + val flingType: FlingType, + val hiddenForShareScreens: List, + val keepDateTime: Boolean, + val isAlwaysClearExif: Boolean, + val enableBackgroundColorForAlphaFormats: Boolean, + val showToolsHistory: Boolean, + val motionDurationScale: Float, +) + +fun UiSettingsState.isFirstLaunch( + approximate: Boolean = true, +) = if (approximate) { + appOpenCount <= 3 +} else appOpenCount <= 1 + +@Composable +fun SettingsState.toUiState( + randomEmojiKey: Any? = null, +): UiSettingsState { + val context = LocalContext.current + context.initEmoji() + + val allEmojis = Emoji.allIcons + val allIconShapes: ImmutableList = IconShape.entries + + val scope = rememberCoroutineScope() + + val isNightMode = nightMode.isNightMode() + + val selectedEmojiIndex by remember(selectedEmoji, useRandomEmojis, randomEmojiKey) { + derivedStateOf { + selectedEmoji?.takeIf { it != -1 && allEmojis.isNotEmpty() }?.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, + cacheAutoClearLimitBytes = cacheAutoClearLimitBytes, + cacheAutoClearInterval = cacheAutoClearInterval, + lastCacheAutoClearTimestampMillis = lastCacheAutoClearTimestampMillis, + groupOptionsByTypes = groupOptionsByTypes, + showFavoriteToolsInGroupedMode = showFavoriteToolsInGroupedMode, + showFavoriteAsLast = showFavoriteAsLast, + screenList = screenList, + colorTupleList = colorTupleList, + addSequenceNumber = addSequenceNumber, + saveFolderUri = saveFolderUri, + saveToOriginalFolder = saveToOriginalFolder, + deleteOriginalsAfterSave = deleteOriginalsAfterSave, + 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, + cropOverlayDraggable = cropOverlayDraggable, + drawBitmapBorder = drawBitmapBorder, + exifWidgetInitialState = exifWidgetInitialState, + screenListWithMaxBrightnessEnforcement = screenListWithMaxBrightnessEnforcement, + isConfettiEnabled = isConfettiEnabled, + isSecureMode = isSecureMode, + useRandomEmojis = useRandomEmojis, + useAnimatedEmojis = useAnimatedEmojis, + iconShape = iconShape, + useEmojiAsPrimaryColor = useEmojiAsPrimaryColor, + dragHandleWidth = dragHandleWidth, + confettiType = confettiType, + allowAutoClipboardPaste = allowAutoClipboardPaste, + confettiColorHarmonizer = confettiColorHarmonizer, + confettiHarmonizationLevel = confettiHarmonizationLevel, + skipImagePicking = skipImagePicking, + generatePreviews = generatePreviews, + enableSheetGestures = enableSheetGestures, + 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, + shapeByInteractionThrottle = shapeByInteractionThrottle, + filenamePattern = filenamePattern, + filenameBehavior = filenameBehavior, + flingType = flingType, + hiddenForShareScreens = hiddenForShareScreens, + keepDateTime = keepDateTime, + isAlwaysClearExif = isAlwaysClearExif, + enableBackgroundColorForAlphaFormats = enableBackgroundColorForAlphaFormats, + showToolsHistory = showToolsHistory, + motionDurationScale = motionDurationScale, + ) + } + }.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(), + neutralVariant = temp.getOrNull(4)?.toIntOrNull()?.toColor(), + error = temp.getOrNull(5)?.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) }, + neutralVariant = colorTuple.getOrNull(4)?.toIntOrNull()?.let { Color(it) }, + error = colorTuple.getOrNull(5)?.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) +) diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/provider/LocalSettingsState.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/provider/LocalSettingsState.kt new file mode 100644 index 0000000..4c83aa0 --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/provider/LocalSettingsState.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) +} \ No newline at end of file diff --git a/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/utils/RoundedPolygonUtils.kt b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/utils/RoundedPolygonUtils.kt new file mode 100644 index 0000000..7bfc4cb --- /dev/null +++ b/core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/utils/RoundedPolygonUtils.kt @@ -0,0 +1,122 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() +} \ No newline at end of file diff --git a/core/ui/.gitignore b/core/ui/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/ui/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts new file mode 100644 index 0000000..069974c --- /dev/null +++ b/core/ui/build.gradle.kts @@ -0,0 +1,121 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + + implementation(libs.dotlottie.android) + + // 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.coilCompose) + api(libs.ktor) + + //Modules + api(projects.lib.dynamicTheme) + api(projects.lib.colors) + api(projects.lib.gesture) + api(projects.lib.image) + api(projects.lib.modalsheet) + api(projects.lib.zoomable) + api(projects.lib.snowfall) + implementation(projects.lib.cropper) + implementation(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.backdrop) + api(libs.capsule) + api(libs.squircle.shape) + + api(libs.evaluator) + + api(libs.flinger) +} \ No newline at end of file diff --git a/core/ui/src/foss/java/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/DocumentScannerImpl.kt b/core/ui/src/foss/java/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/DocumentScannerImpl.kt new file mode 100644 index 0000000..f712389 --- /dev/null +++ b/core/ui/src/foss/java/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/DocumentScannerImpl.kt @@ -0,0 +1,147 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.toArgb +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.CameraAlt +import com.t8rin.imagetoolbox.core.ui.theme.onPrimaryContainerFixed +import com.t8rin.imagetoolbox.core.ui.theme.primaryContainerFixed +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 colorScheme = MaterialTheme.colorScheme + val cropperHandleColor = colorScheme.onPrimaryContainerFixed + val cropperFrameColor = colorScheme.onPrimaryContainerFixed + val buttonTintColor = colorScheme.onPrimaryContainerFixed + val buttonContainerColor = colorScheme.primaryContainerFixed + val cropperStrokeWidthDp = 1.2f + val doneButtonTintColor = colorScheme.onPrimaryFixed + val doneButtonContainerColor = colorScheme.primaryFixed + + val scanner = remember( + context, + cropperHandleColor, + cropperFrameColor, + buttonTintColor, + buttonContainerColor, + buttonContainerColor, + cropperStrokeWidthDp, + doneButtonTintColor, + doneButtonContainerColor + ) { + DocumentScannerDelegate( + activity = context, + cropperHandleColor = cropperHandleColor.toArgb(), + cropperHandleOutlineColor = buttonContainerColor.toArgb(), + cropperFrameColor = cropperFrameColor.toArgb(), + cropperStrokeWidthDp = cropperStrokeWidthDp, + buttonTintColor = buttonTintColor.toArgb(), + buttonContainerColor = buttonContainerColor.toArgb(), + doneButtonTintColor = doneButtonTintColor.toArgb(), + doneButtonContainerColor = doneButtonContainerColor.toArgb(), + 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, scanner, scannerLauncher, requestPermissionLauncher) { + DocumentScannerImpl( + context = context, + scanner = scanner, + scannerLauncher = scannerLauncher, + requestPermissionLauncher = requestPermissionLauncher + ) + } +} diff --git a/core/ui/src/foss/java/com/t8rin/imagetoolbox/core/ui/utils/helper/ReviewHandlerImpl.kt b/core/ui/src/foss/java/com/t8rin/imagetoolbox/core/ui/utils/helper/ReviewHandlerImpl.kt new file mode 100644 index 0000000..3f6d746 --- /dev/null +++ b/core/ui/src/foss/java/com/t8rin/imagetoolbox/core/ui/utils/helper/ReviewHandlerImpl.kt @@ -0,0 +1,77 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/foss/java/com/t8rin/imagetoolbox/core/ui/widget/sheets/UpdateSheetImpl.kt b/core/ui/src/foss/java/com/t8rin/imagetoolbox/core/ui/widget/sheets/UpdateSheetImpl.kt new file mode 100644 index 0000000..e148056 --- /dev/null +++ b/core/ui/src/foss/java/com/t8rin/imagetoolbox/core/ui/widget/sheets/UpdateSheetImpl.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/AndroidManifest.xml b/core/ui/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/core/ui/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/di/FailureNotifierModule.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/di/FailureNotifierModule.kt new file mode 100644 index 0000000..492cb46 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/di/FailureNotifierModule.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.core.ui.di + +import com.t8rin.imagetoolbox.core.domain.saving.FailureNotifier +import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object FailureNotifierModule { + + @Provides + @Singleton + fun notifier(): FailureNotifier = FailureNotifier(AppToastHost::showFailureToast) + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Color.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Color.kt new file mode 100644 index 0000000..a567cb3 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Color.kt @@ -0,0 +1,201 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.material3.ColorScheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.graphics.takeOrElse +import androidx.compose.ui.graphics.toArgb +import androidx.core.graphics.ColorUtils +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +import com.t8rin.imagetoolbox.core.resources.utils.toSafeSrgb +import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState + +@Stable +fun ColorScheme.outlineVariant( + luminance: Float = 0.3f, + onTopOf: Color = surfaceContainer +) = onSecondaryContainer + .copy(alpha = luminance) + .compositeOverSafe(onTopOf.takeOrElse { surface }) + +@Composable +inline fun takeColorFromScheme( + action: @Composable ColorScheme.(isNightMode: Boolean) -> Color +) = animateColorAsState( + MaterialTheme.colorScheme.run { + action( + LocalSettingsState.current.isNightMode + ) + } +).value + +@Stable +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 + ) + } + +@Stable +inline fun Color.takeIf(predicate: Boolean) = if (predicate) this else Color.Unspecified + +@Stable +inline fun Color.takeUnless(predicate: Boolean) = takeIf(!predicate) + +@Stable +fun Color.blend( + color: Color, + @FloatRange(from = 0.0, to = 1.0) fraction: Float = 0.2f +): Color = ColorUtils.blendARGB(this.toArgb(), color.toArgb(), fraction).toSafeSrgb(this) + +@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)) + +@Stable +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)) + +@Stable +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) + +@Stable +fun Int.toColor() = toSafeSrgb() + +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) diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Motion.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Motion.kt new file mode 100644 index 0000000..93dead7 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Motion.kt @@ -0,0 +1,91 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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() +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Theme.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Theme.kt new file mode 100644 index 0000000..cf6a1be --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Theme.kt @@ -0,0 +1,178 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.MaterialExpressiveTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Shapes +import androidx.compose.material3.Surface +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 = { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA && DeviceInfo.isPixel()) { + val colors = dynamicLightColorScheme(context) + + ColorTuple( + primary = colors.primary, + secondary = colors.secondary, + tertiary = colors.tertiary, + surface = colors.surface, + neutralVariant = colors.surfaceVariant, + error = colors.error + ) + } else null + }, + amoledMode = settingsState.isAmoledMode, + isDarkTheme = settingsState.isNightMode, + contrastLevel = settingsState.themeContrastLevel, + style = settingsState.themeStyle, + isInvertColors = settingsState.isInvertThemeColors, + colorAnimationSpec = tween( + durationMillis = 400, + easing = FancyTransitionEasing + ), + content = { + MaterialExpressiveTheme( + motionScheme = CustomMotionScheme, + 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 +} + +const val DisabledAlpha = 0.38f \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/ThemePreview.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/ThemePreview.kt new file mode 100644 index 0000000..c376e68 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/ThemePreview.kt @@ -0,0 +1,299 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.MaterialExpressiveTheme +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.AsyncImagePainter +import coil3.compose.AsyncImagePreviewHandler +import coil3.compose.LocalAsyncImagePreviewHandler +import coil3.request.ErrorResult +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, + isImageError: Boolean = false, + shapesType: ShapeType = ShapeType.Rounded(), + mapSettings: (SettingsState) -> SettingsState = { it }, + content: @Composable () -> Unit +) { + LocalContext.current.applicationContext.initAppContext() + + FakeLoader( + isImageError = isImageError, + tuple = 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, + isNightMode = isDarkTheme + ), + LocalSimpleSettingsInteractor provides FakeSettings, + LocalResourceManager provides FakeRes, + LocalScreenSize provides rememberScreenSize() + ) { + MaterialExpressiveTheme( + motionScheme = CustomMotionScheme, + shapes = modifiedShapes(), + content = { + Surface { + content() + } + } + ) + } + } + ) + } +} + +@Composable +private fun FakeLoader( + isImageError: Boolean, + tuple: ColorTuple, + content: @Composable () -> Unit +) { + DynamicTheme( + state = rememberDynamicThemeState(tuple), + isDarkTheme = false, + defaultColorTuple = tuple + ) { + val colorScheme = MaterialTheme.colorScheme + + val fakeLoader = remember(colorScheme) { + if (isImageError) { + AsyncImagePreviewHandler { _, request -> + AsyncImagePainter.State.Error(null, ErrorResult(null, request, Throwable())) + } + } else { + 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 toggleCropOverlayDraggable() = 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 toggleSaveToOriginalFolder() = Unit + + override suspend fun toggleDeleteOriginalsAfterSave() = 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)) + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Type.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Type.kt new file mode 100644 index 0000000..0439699 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Type.kt @@ -0,0 +1,174 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.MaterialExpressiveTheme +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( + fontFamily = fontRes.fontFamily, + 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 +) { + MaterialExpressiveTheme( + typography = rememberTypography(fontRes), + content = content + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/transformation/ImageInfoTransformation.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/transformation/ImageInfoTransformation.kt new file mode 100644 index 0000000..46f376b --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/transformation/ImageInfoTransformation.kt @@ -0,0 +1,128 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.ensureActive +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 = coroutineScope { + ensureActive() + val transformedInput = imageScaler.scaleImage( + image = input, + width = size.width.pxOrElse { imageInfo.width }, + height = size.height.pxOrElse { imageInfo.height }, + resizeType = ResizeType.Flexible + ) + ensureActive() + + val originalUri = shareProvider.cacheImage( + image = input, + imageInfo = ImageInfo( + width = input.width, + height = input.height + ) + ) + ensureActive() + + 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) + } + ) + ) + ensureActive() + + 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 + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/BaseComponent.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/BaseComponent.kt new file mode 100644 index 0000000..8eb7339 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/BaseComponent.kt @@ -0,0 +1,183 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.navigation.coroutineScope +import com.t8rin.imagetoolbox.core.ui.utils.state.update +import com.t8rin.imagetoolbox.core.utils.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 SaveResultHandler { + + 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 + + override fun send(error: Throwable) = Unit + } + + fun inject(keepAliveService: KeepAliveService) { + this.keepAliveService = keepAliveService + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/BaseHistoryComponent.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/BaseHistoryComponent.kt new file mode 100644 index 0000000..bd54e40 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/BaseHistoryComponent.kt @@ -0,0 +1,195 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.model.ColorModel +import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay + +abstract class BaseHistoryComponent( + dispatchersHolder: DispatchersHolder, + componentContext: ComponentContext +) : BaseComponent(dispatchersHolder, componentContext) { + + protected open val maxHistorySize: Int = 25 + protected open val historyTransactionDebounce: Long = 700L + protected open val formatHistoryTransactionDebounce: Long = 2500L + + private val _history: MutableState> = mutableStateOf(emptyList()) + protected val history: List by _history + + private val _redoHistory: MutableState> = mutableStateOf(emptyList()) + protected val redoHistory: List by _redoHistory + + private var pendingHistorySnapshot: Snapshot? = null + private var pendingHistoryJob: Job? = null + private var pendingHistoryDelayMillis: Long = historyTransactionDebounce + + private val _hasPendingHistoryTransaction = mutableStateOf(false) + + protected var pendingHistoryMode: PendingHistoryMode? = null + private set + + val canUndo: Boolean + get() = history.size > 1 || (_hasPendingHistoryTransaction.value && history.isNotEmpty()) + + val canRedo: Boolean + get() = redoHistory.isNotEmpty() + + fun undo() { + finalizePendingHistoryTransaction() + if (!canUndo) return + + val current = history.last() + val previous = history[history.lastIndex - 1] + + _history.value = history.dropLast(1) + _redoHistory.value = (redoHistory + current).takeLast(maxHistorySize) + applyHistorySnapshot(previous) + registerChanges() + } + + fun redo() { + finalizePendingHistoryTransaction() + if (!canRedo) return + + val snapshot = redoHistory.last() + + _redoHistory.value = redoHistory.dropLast(1) + _history.value = (history + snapshot).takeLast(maxHistorySize) + applyHistorySnapshot(snapshot) + registerChanges() + } + + protected abstract fun currentHistorySnapshot(): Snapshot + + protected abstract fun applyHistorySnapshot(snapshot: Snapshot) + + protected open fun hasSameUndoState( + first: Snapshot, + second: Snapshot + ): Boolean = first == second + + protected fun restoreBackgroundColorForNoAlphaFormats( + settingsManager: SettingsManager, + backgroundColorForNoAlphaFormats: ColorModel + ) { + if ( + settingsManager.settingsState.value.backgroundForNoAlphaImageFormats != + backgroundColorForNoAlphaFormats + ) { + componentScope.launch { + settingsManager.setBackgroundColorForNoAlphaFormats( + color = backgroundColorForNoAlphaFormats + ) + } + } + } + + protected fun commitHistoryFrom(beforeSnapshot: Snapshot) { + val afterSnapshot = currentHistorySnapshot() + val hasStateChange = !hasSameUndoState(afterSnapshot, beforeSnapshot) + val hasHistoryChange = history + .lastOrNull() + ?.let { !hasSameUndoState(it, afterSnapshot) } == true + + if (!hasStateChange && !hasHistoryChange) return + + _history.value = history + .appendHistorySnapshot(beforeSnapshot) + .appendHistorySnapshot(afterSnapshot) + .takeLast(maxHistorySize) + _redoHistory.value = emptyList() + registerChanges() + } + + protected fun resetHistory() { + cancelPendingHistoryTransaction() + _history.value = listOf(currentHistorySnapshot()) + _redoHistory.value = emptyList() + } + + protected fun clearHistory() { + cancelPendingHistoryTransaction() + _history.value = emptyList() + _redoHistory.value = emptyList() + } + + protected fun beginPendingHistoryTransaction( + mode: PendingHistoryMode? = null, + commitDelayMillis: Long = historyTransactionDebounce + ) { + if (pendingHistorySnapshot == null) { + pendingHistorySnapshot = currentHistorySnapshot() + pendingHistoryMode = mode + _hasPendingHistoryTransaction.value = true + } else if (mode != null) { + pendingHistoryMode = mode + } + pendingHistoryDelayMillis = commitDelayMillis + } + + protected fun schedulePendingHistoryCommit() { + pendingHistoryJob?.cancel() + pendingHistoryJob = componentScope.launch { + delay(pendingHistoryDelayMillis) + val beforeSnapshot = pendingHistorySnapshot + pendingHistorySnapshot = null + pendingHistoryJob = null + pendingHistoryMode = null + _hasPendingHistoryTransaction.value = false + beforeSnapshot?.let(::commitHistoryFrom) + } + } + + protected fun finalizePendingHistoryTransaction() { + pendingHistoryJob?.cancel() + pendingHistoryJob = null + val beforeSnapshot = pendingHistorySnapshot + pendingHistorySnapshot = null + pendingHistoryMode = null + _hasPendingHistoryTransaction.value = false + beforeSnapshot?.let(::commitHistoryFrom) + } + + protected fun cancelPendingHistoryTransaction() { + pendingHistoryJob?.cancel() + pendingHistoryJob = null + pendingHistorySnapshot = null + pendingHistoryMode = null + _hasPendingHistoryTransaction.value = false + } + + private fun List.appendHistorySnapshot( + snapshot: Snapshot + ): List = when { + isEmpty() -> listOf(snapshot) + hasSameUndoState(last(), snapshot) -> dropLast(1) + snapshot + else -> this + snapshot + } + + enum class PendingHistoryMode { + FormatChange + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/ComposeActivity.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/ComposeActivity.kt new file mode 100644 index 0000000..f05225f --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/ComposeActivity.kt @@ -0,0 +1,297 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.MotionDurationScale +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.createLifecycleAwareWindowRecomposer +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.content_pickers.LocalImagePickerEventEmitter +import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePickerEventEmitter +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.LocalWindowSizeClass +import com.t8rin.imagetoolbox.core.ui.utils.state.update +import com.t8rin.imagetoolbox.core.utils.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() + + setContent( + parent = window.decorView.createLifecycleAwareWindowRecomposer( + coroutineContext = object : MotionDurationScale { + override val scaleFactor: Float get() = settingsState.motionDurationScale + }, + lifecycle = lifecycle + ) + ) { + CompositionLocalProvider( + LocalSimpleSettingsInteractor provides settingsManager.toSimpleSettingsInteractor(), + LocalMetadataProvider provides fileController.toMetadataProvider(), + LocalKeepAliveService provides keepAliveService, + LocalImagePickerEventEmitter provides rememberImagePickerEventEmitter(), + LocalResourceManager provides resourceManager, + LocalWindowSizeClass provides calculateWindowSizeClass(this), + content = ::Content + ) + } + } + + fun applyDynamicColors() { + val colorTuple = settingsState.appColorTuple.asColorTuple() + runCatching { + DynamicColors.applyToActivityIfAvailable( + this@ComposeActivity, + DynamicColorsOptions.Builder() + .setContentBasedSource(colorTuple.primary.toArgb()) + .build() + ) + }.onFailure { + it.makeLog("applyDynamicColors") + } + } + + 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() + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/ComposeApplication.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/ComposeApplication.kt new file mode 100644 index 0000000..a338e1b --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/ComposeApplication.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/ImageExportProfilesHolder.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/ImageExportProfilesHolder.kt new file mode 100644 index 0000000..6eb143f --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/ImageExportProfilesHolder.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.net.Uri +import androidx.compose.runtime.Stable +import com.t8rin.imagetoolbox.core.domain.image.ImageExportProfilesUseCase +import com.t8rin.imagetoolbox.core.domain.image.model.ImageExportProfile +import com.t8rin.imagetoolbox.core.domain.image.model.Preset +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +@Stable +interface ImageExportProfilesHolder { + + val imageProfiles: StateFlow> + + val currentProfileKeepExif: Boolean? + get() = null + + fun updateProfile(profile: Preset) + + fun applyProfile(profile: ImageExportProfile) + + fun saveProfile(name: String) + + fun deleteProfile(profile: ImageExportProfile) + + fun exportProfile( + preset: ImageExportProfile, + uri: Uri + ) + + fun shareProfile(preset: ImageExportProfile) + + fun importProfile(uri: Uri) + + companion object { + operator fun invoke( + imageExportProfilesUseCase: ImageExportProfilesUseCase, + componentScope: CoroutineScope + ): ImageExportProfilesHolder = ImageExportProfilesHolderImpl( + imageExportProfilesUseCase = imageExportProfilesUseCase, + componentScope = componentScope + ) + } + +} + +private class ImageExportProfilesHolderImpl( + private val imageExportProfilesUseCase: ImageExportProfilesUseCase, + private val componentScope: CoroutineScope +) : ImageExportProfilesHolder { + + override val imageProfiles: StateFlow> = + imageExportProfilesUseCase.profiles + .stateIn(componentScope, SharingStarted.Eagerly, emptyList()) + + override fun updateProfile(profile: Preset) = Unit + + override fun applyProfile(profile: ImageExportProfile) = Unit + + override fun saveProfile(name: String) = Unit + + override fun deleteProfile(profile: ImageExportProfile) { + componentScope.launch { + imageExportProfilesUseCase.delete(profile) + } + } + + override fun exportProfile( + preset: ImageExportProfile, + uri: Uri + ) { + componentScope.launch { + imageExportProfilesUseCase.export( + profile = preset, + uri = uri.toString() + ) + } + } + + override fun shareProfile(preset: ImageExportProfile) { + componentScope.launch { + imageExportProfilesUseCase.share(preset) + } + } + + override fun importProfile(uri: Uri) { + componentScope.launch { + imageExportProfilesUseCase.importProfile(uri.toString()) + } + } + +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/Animate.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/Animate.kt new file mode 100644 index 0000000..0ae9567 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/Animate.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/Animations.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/Animations.kt new file mode 100644 index 0000000..3aa6388 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/Animations.kt @@ -0,0 +1,131 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/CombinedMutableInteractionSource.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/CombinedMutableInteractionSource.kt new file mode 100644 index 0000000..21eec35 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/CombinedMutableInteractionSource.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) } + } + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/Easing.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/Easing.kt new file mode 100644 index 0000000..f724e09 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/Easing.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + +val SoftEasing = CubicBezierEasing(0.46f, 0.41f, 0.29f, 0.92f) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/Capturable.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/Capturable.kt new file mode 100644 index 0000000..7429dc6 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/Capturable.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + } +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/CaptureController.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/CaptureController.kt new file mode 100644 index 0000000..2a031da --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/CaptureController.kt @@ -0,0 +1,81 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/impl/CapturableNew.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/impl/CapturableNew.kt new file mode 100644 index 0000000..95e15e4 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/impl/CapturableNew.kt @@ -0,0 +1,149 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/impl/CapturableOld.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/impl/CapturableOld.kt new file mode 100644 index 0000000..52cbbe6 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/impl/CapturableOld.kt @@ -0,0 +1,204 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/confetti/ConfettiHostState.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/confetti/ConfettiHostState.kt new file mode 100644 index 0000000..70fd68e --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/confetti/ConfettiHostState.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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() + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/confetti/Particles.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/confetti/Particles.kt new file mode 100644 index 0000000..1bf7c7a --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/confetti/Particles.kt @@ -0,0 +1,286 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/BarcodeScanner.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/BarcodeScanner.kt new file mode 100644 index 0000000..f729c9c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/BarcodeScanner.kt @@ -0,0 +1,137 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.CameraAlt +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.makeLog +import com.t8rin.imagetoolbox.core.utils.toQrType +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, + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/ContactPicker.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/ContactPicker.kt new file mode 100644 index 0000000..fe79f48 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/ContactPicker.kt @@ -0,0 +1,408 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Person +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.imagetoolbox.core.utils.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.Person + ) + } + } + + 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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/DocumentScanner.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/DocumentScanner.kt new file mode 100644 index 0000000..6c554e7 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/DocumentScanner.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/FileMaker.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/FileMaker.kt new file mode 100644 index 0000000..e4decaf --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/FileMaker.kt @@ -0,0 +1,96 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.imagetoolbox.core.utils.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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/FilePicker.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/FilePicker.kt new file mode 100644 index 0000000..85e45be --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/FilePicker.kt @@ -0,0 +1,158 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.imagetoolbox.core.utils.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) + } +) diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/FolderImagePicker.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/FolderImagePicker.kt new file mode 100644 index 0000000..caec772 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/FolderImagePicker.kt @@ -0,0 +1,118 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +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.provider.LocalComponentActivity +import com.t8rin.imagetoolbox.core.utils.listFilesInDirectoryProgressive +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +internal fun interface FolderImagePicker { + fun pickFolder(allowMultiple: Boolean) +} + +@Composable +internal fun rememberFolderImagePicker( + initialFolderUri: Uri? = LocalSettingsState.current.saveFolderUri, + onFailure: () -> Unit, + onSuccess: (List) -> Unit +): FolderImagePicker { + val context = LocalComponentActivity.current + val eventEmitter = LocalImagePickerEventEmitter.current + val scope = rememberCoroutineScope { Dispatchers.IO } + val allowMultiple = remember { mutableStateOf(false) } + + val folderPicker = rememberFolderPicker( + onFailure = onFailure, + onSuccess = { folderUri -> + scope.launch { + val requestId = eventEmitter.onFolderProcessingStarted() + try { + val targetAllowMultiple = allowMultiple.value + val uris = withContext(Dispatchers.IO) { + var count = 0 + val flow = folderUri.listFilesInDirectoryProgressive() + .mapNotNull { uri -> + uri.takeIf { it.isAcceptedImage(context) } + } + .onEach { + count++ + eventEmitter.onFolderProcessingProgress( + requestId = requestId, + count = count + ) + } + + if (targetAllowMultiple) flow.toList() else flow.take(1).toList() + } + + uris.takeIf { it.isNotEmpty() }?.let(onSuccess) ?: onFailure() + } catch (e: CancellationException) { + throw e + } catch (e: Throwable) { + onFailure() + AppToastHost.handleFileSystemFailure(e) + } finally { + eventEmitter.onFolderProcessingFinished(requestId) + } + } + } + ) + + return remember(folderPicker, initialFolderUri) { + FolderImagePicker { targetAllowMultiple -> + allowMultiple.value = targetAllowMultiple + folderPicker.pickFolder(initialFolderUri) + } + } +} + +private fun Uri.isAcceptedImage(context: Context): Boolean { + if (EXCLUDED.any { toString().endsWith(".$it", true) }) return false + + val mime = context.contentResolver.getType(this).orEmpty() + return "audio" !in mime && "video" !in mime +} + +private val EXCLUDED = listOf( + "xml", + "mov", + "zip", + "apk", + "mp4", + "mp3", + "pdf", + "ldb", + "ttf", + "gz", + "rar" +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/FolderPicker.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/FolderPicker.kt new file mode 100644 index 0000000..821828c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/FolderPicker.kt @@ -0,0 +1,95 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.imagetoolbox.core.utils.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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/ImagePicker.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/ImagePicker.kt new file mode 100644 index 0000000..9348b24 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/ImagePicker.kt @@ -0,0 +1,458 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.CameraAlt +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.ContextUtils.takePersistablePermission +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.imagetoolbox.core.utils.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 folderPicker: FolderImagePicker, + 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 + addFlags( + Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or + Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + ) + putExtra( + Intent.EXTRA_ALLOW_MULTIPLE, + mode == ImagePickerMode.GetContentMultiple + ) + } + getContent.launch( + Intent.createChooser( + intent, + context.getString(R.string.pick_image) + ) + ) + } + val folderAction = { + folderPicker.pickFolder( + allowMultiple = mode == ImagePickerMode.FolderMultiple + ) + } + + 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() + + ImagePickerMode.FolderSingle, + ImagePickerMode.FolderMultiple -> folderAction() + } + }.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 + PicturePickerMode.Folder -> if (multiple) ImagePickerMode.FolderMultiple else ImagePickerMode.FolderSingle + } + + val basePicker = ImagePickerImpl( + context = context, + mode = mode, + currentAccent = currentAccent, + photoPickerSingle = photoPickerSingle, + photoPickerMultiple = photoPickerMultiple, + getContent = getContent, + takePhoto = takePhoto, + folderPicker = folderPicker, + 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, + FolderSingle, + FolderMultiple +} + +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 + PicturePickerMode.Folder -> if (multiple) ImagePickerMode.FolderMultiple else ImagePickerMode.FolderSingle + } + } + }.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 folderPicker = rememberFolderImagePicker( + onFailure = onFailure, + onSuccess = onSuccess + ) + + 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.let { uris -> + if (mode == ImagePickerMode.GetContentSingle || mode == ImagePickerMode.GetContentMultiple) { + val modeFlags = intent?.flags?.and( + Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) ?: 0 + + uris.map { it.takePersistablePermission(modeFlags) } + } else uris + }.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, + folderPicker, + mode + ) { + derivedStateOf { + ImagePickerImpl( + context = context, + mode = mode, + currentAccent = currentAccent, + photoPickerSingle = photoPickerSingle, + photoPickerMultiple = photoPickerMultiple, + getContent = getContent, + takePhoto = takePhoto, + folderPicker = folderPicker, + 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 = "*" \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/ImagePickerEventEmitter.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/ImagePickerEventEmitter.kt new file mode 100644 index 0000000..9cd2b61 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/ImagePickerEventEmitter.kt @@ -0,0 +1,126 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.foundation.layout.width +import androidx.compose.runtime.Composable +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 androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog +import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.receiveAsFlow +import java.util.concurrent.atomic.AtomicLong + +internal interface ImagePickerEventEmitter { + val events: Flow + + fun onFolderProcessingStarted(): Long + + fun onFolderProcessingProgress(requestId: Long, count: Int) + + fun onFolderProcessingFinished(requestId: Long) +} + +@Composable +internal fun rememberImagePickerEventEmitter(): ImagePickerEventEmitter = + remember { ImagePickerEventEmitterImpl() } + +internal sealed interface ImagePickerEvent { + data class FolderProcessingStarted(val requestId: Long) : ImagePickerEvent + data class FolderProcessingProgress(val requestId: Long, val count: Int) : ImagePickerEvent + data class FolderProcessingFinished(val requestId: Long) : ImagePickerEvent +} + +private class ImagePickerEventEmitterImpl : ImagePickerEventEmitter { + + private val eventChannel = Channel(Channel.UNLIMITED) + override val events: Flow = eventChannel.receiveAsFlow() + + private val nextRequestId = AtomicLong() + + override fun onFolderProcessingStarted(): Long = nextRequestId.incrementAndGet().also { + eventChannel.trySend(ImagePickerEvent.FolderProcessingStarted(it)) + } + + override fun onFolderProcessingProgress(requestId: Long, count: Int) { + eventChannel.trySend(ImagePickerEvent.FolderProcessingProgress(requestId, count)) + } + + override fun onFolderProcessingFinished(requestId: Long) { + eventChannel.trySend(ImagePickerEvent.FolderProcessingFinished(requestId)) + } +} + +internal val LocalImagePickerEventEmitter = compositionLocalOf { + error("ImagePickerEventEmitter not registered") +} + +@Composable +internal fun ImagePickerEventsHandler() { + val eventEmitter = LocalImagePickerEventEmitter.current + var activeRequests by remember { mutableStateOf(emptyMap()) } + + LaunchedEffect(eventEmitter) { + eventEmitter.events.collect { event -> + activeRequests = when (event) { + is ImagePickerEvent.FolderProcessingStarted -> { + activeRequests + (event.requestId to 0) + } + + is ImagePickerEvent.FolderProcessingProgress -> { + activeRequests + (event.requestId to event.count) + } + + is ImagePickerEvent.FolderProcessingFinished -> { + activeRequests - event.requestId + } + } + } + } + + val count = remember(activeRequests) { + activeRequests.values.sum() + } + + LoadingDialog( + visible = activeRequests.isNotEmpty(), + progress = { if (count <= 0) 1f else 0f }, + canCancel = false, + isLayoutSwappable = count <= 0, + additionalContent = { size -> + if (count > 0) { + AutoSizeText( + text = count.toString(), + maxLines = 1, + fontWeight = FontWeight.Medium, + textAlign = TextAlign.Center, + modifier = Modifier.width(size * 0.7f) + ) + } + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/ResultLauncher.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/ResultLauncher.kt new file mode 100644 index 0000000..fae5252 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/ResultLauncher.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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() +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/Scanner.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/Scanner.kt new file mode 100644 index 0000000..319888d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/Scanner.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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() +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ActivityUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ActivityUtils.kt new file mode 100644 index 0000000..b3299d3 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ActivityUtils.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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" \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/AppSkippedImagesHost.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/AppSkippedImagesHost.kt new file mode 100644 index 0000000..8eac24a --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/AppSkippedImagesHost.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.core.net.toUri + +data object AppSkippedImagesHost { + + val state = SkippedImagesHostState() + + fun showSkippedImages( + uris: List + ) { + state.showSkippedImages(uris) + } + + fun dismiss() { + state.dismiss() + } + +} + +@Stable +@Immutable +class SkippedImagesHostState { + + private val skippedImageUrisState = mutableStateOf>(emptyList()) + val skippedImageUris by skippedImageUrisState + + fun showSkippedImages( + uris: List + ) { + skippedImageUrisState.value = uris + .filter(String::isNotBlank) + .distinct() + .map { it.toUri() } + } + + fun dismiss() { + skippedImageUrisState.value = emptyList() + } + +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/AppToastHost.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/AppToastHost.kt new file mode 100644 index 0000000..a744b23 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/AppToastHost.kt @@ -0,0 +1,135 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.ui.graphics.vector.ImageVector +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FolderOff +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( + 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 + ) + } + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/BlendingModeExt.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/BlendingModeExt.kt new file mode 100644 index 0000000..0327b71 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/BlendingModeExt.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Clipboard.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Clipboard.kt new file mode 100644 index 0000000..40011c8 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Clipboard.kt @@ -0,0 +1,132 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.CopyAll +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.Outlined.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.Outlined.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) + } + } + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ClipboardUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ClipboardUtils.kt new file mode 100644 index 0000000..9a5f617 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ClipboardUtils.kt @@ -0,0 +1,166 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/CoilUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/CoilUtils.kt new file mode 100644 index 0000000..f932577 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/CoilUtils.kt @@ -0,0 +1,56 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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()) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ColorUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ColorUtils.kt new file mode 100644 index 0000000..828f4fe --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ColorUtils.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/CompositionLocalUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/CompositionLocalUtils.kt new file mode 100644 index 0000000..d0ae11d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/CompositionLocalUtils.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ContextUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ContextUtils.kt new file mode 100644 index 0000000..ab74b44 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ContextUtils.kt @@ -0,0 +1,537 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.imagetoolbox.core.utils.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.getStringEnglish( + @StringRes + resId: Int + ): String = getStringLocalized(resId, Locale.ENGLISH) + + 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( + modeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ): Uri = apply { + if (modeFlags == 0) return@apply + + runCatching { + appContext.contentResolver.takePersistableUriPermission( + this, + modeFlags + ) + }.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)) + } + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/DensityUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/DensityUtils.kt new file mode 100644 index 0000000..11f2a27 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/DensityUtils.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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() \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/DeviceInfo.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/DeviceInfo.kt new file mode 100644 index 0000000..35bc15d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/DeviceInfo.kt @@ -0,0 +1,81 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.imagetoolbox.core.utils.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 + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/DrawUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/DrawUtils.kt new file mode 100644 index 0000000..db4a170 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/DrawUtils.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/HandleDeeplinks.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/HandleDeeplinks.kt new file mode 100644 index 0000000..677fc8f --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/HandleDeeplinks.kt @@ -0,0 +1,284 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.domain.BACKUP_FILE_EXT +import com.t8rin.imagetoolbox.core.domain.TEMPLATE_EXT +import com.t8rin.imagetoolbox.core.domain.model.ExtraDataType +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Error +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?.isEndsWith(ext: String): Boolean = this?.lowercase().orEmpty().endsWith(ext) + + fun Uri?.isMarkupProject(): Boolean = this?.toString().isEndsWith(".itp") || + this?.filename().isEndsWith(".itp") + + fun Uri?.isTemplate(): Boolean = this?.toString().isEndsWith(TEMPLATE_EXT) || + this?.filename().isEndsWith(TEMPLATE_EXT) + + val startsWithImage = type?.startsWith("image/") == true + val hasExtraFormats = clipData?.clipList() + ?.any { + it.toString().endsWith(".jxl") || + it.toString().endsWith(".qoi") || + it.toString().endsWith(".itp") + } == true + val dataHasExtraFormats = data.toString().let { + it.endsWith(".jxl") || it.endsWith(".qoi") || it.endsWith(".itp") + } + + when { + data.isTemplate() -> onHasExtraDataType(ExtraDataType.Template(data.toString())) + + data.isMarkupProject() -> onNavigate(Screen.MarkupLayers(data)) + + 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)) + } + } + } + } + + 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.isTemplate()) { + onHasExtraDataType(ExtraDataType.Template(it.toString())) + 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.isTemplate()) { + onHasExtraDataType(ExtraDataType.Template(uri.toString())) + 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.Outlined.Error + ) + } + } + + else -> Unit + } + }.getOrNull() ?: AppToastHost.showToast( + message = appContext.getString(R.string.something_went_wrong), + icon = Icons.Outlined.Error + ) +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/HandlerUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/HandlerUtils.kt new file mode 100644 index 0000000..456a294 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/HandlerUtils.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ImageUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ImageUtils.kt new file mode 100644 index 0000000..09a5cce --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ImageUtils.kt @@ -0,0 +1,397 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + } + } + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/IntentUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/IntentUtils.kt new file mode 100644 index 0000000..4b6ac3f --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/IntentUtils.kt @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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() +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LazyUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LazyUtils.kt new file mode 100644 index 0000000..580c2b6 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LazyUtils.kt @@ -0,0 +1,142 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LinkUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LinkUtils.kt new file mode 100644 index 0000000..5fab1f5 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LinkUtils.kt @@ -0,0 +1,97 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LocalFilterPreviewModel.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LocalFilterPreviewModel.kt new file mode 100644 index 0000000..53f4382 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LocalFilterPreviewModel.kt @@ -0,0 +1,108 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LocaleConfigCompat.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LocaleConfigCompat.kt new file mode 100644 index 0000000..8e5c347 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LocaleConfigCompat.kt @@ -0,0 +1,224 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.imagetoolbox.core.utils.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]!! } \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/PaddingUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/PaddingUtils.kt new file mode 100644 index 0000000..7fe3ae1 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/PaddingUtils.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/PredictiveBackObserver.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/PredictiveBackObserver.kt new file mode 100644 index 0000000..8298f0c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/PredictiveBackObserver.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Preview.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Preview.kt new file mode 100644 index 0000000..9ea56d3 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Preview.kt @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Rect.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Rect.kt new file mode 100644 index 0000000..509c403 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Rect.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ReviewHandler.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ReviewHandler.kt new file mode 100644 index 0000000..6471f60 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ReviewHandler.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.imagetoolbox.core.utils.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 + } + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Ripple.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Ripple.kt new file mode 100644 index 0000000..f1b187d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Ripple.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/SafeUriHandler.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/SafeUriHandler.kt new file mode 100644 index 0000000..9588b85 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/SafeUriHandler.kt @@ -0,0 +1,95 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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://" \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/SaveResultHandler.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/SaveResultHandler.kt new file mode 100644 index 0000000..6f788b4 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/SaveResultHandler.kt @@ -0,0 +1,217 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult +import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.firstOfType +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Info +import com.t8rin.imagetoolbox.core.resources.icons.Save +import com.t8rin.imagetoolbox.core.ui.widget.other.ToastDuration +import com.t8rin.imagetoolbox.core.utils.getString +import com.t8rin.imagetoolbox.core.utils.makeLog + +interface SaveResultHandler { + fun parseSaveResult(saveResult: SaveResult) + + fun parseFileSaveResult(saveResult: SaveResult) + + fun parseSaveResults(results: List) + + companion object : SaveResultHandler by SaveResultHandlerImpl +} + +private 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 + ) + AppSkippedImagesHost.showSkippedImages(listOfNotNull(saveResult.uri)) + } + + 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 + ) + AppSkippedImagesHost.showSkippedImages(listOfNotNull(saveResult.uri)) + } + + 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 skippedImageUris = results.mapNotNull { (it as? SaveResult.Skipped)?.uri } + 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_count, skipped), + icon = Icons.Outlined.Info, + duration = ToastDuration.Short + ) + AppSkippedImagesHost.showSkippedImages(skippedImageUris) + } + + 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_count, skipped), + icon = Icons.Outlined.Info, + duration = ToastDuration.Short + ) + AppSkippedImagesHost.showSkippedImages(skippedImageUris) + } + return + } + + if (skipped > 0 && done == 0 && failed == 0) { + AppToastHost.showToast( + message = getString(R.string.skipped_saving_count, skipped), + icon = Icons.Outlined.Info, + duration = ToastDuration.Short + ) + AppSkippedImagesHost.showSkippedImages(skippedImageUris) + } + } + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ScanResult.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ScanResult.kt new file mode 100644 index 0000000..cbd8be1 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ScanResult.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/DrawCache.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/DrawCache.kt new file mode 100644 index 0000000..64af754 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/DrawCache.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/GroupComponent.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/GroupComponent.kt new file mode 100644 index 0000000..2d9f51b --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/GroupComponent.kt @@ -0,0 +1,355 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/ImageVectorUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/ImageVectorUtils.kt new file mode 100644 index 0000000..2820292 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/ImageVectorUtils.kt @@ -0,0 +1,181 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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()) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/PathComponent.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/PathComponent.kt new file mode 100644 index 0000000..93dc47c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/PathComponent.kt @@ -0,0 +1,197 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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() +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/VNode.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/VNode.kt new file mode 100644 index 0000000..b031c9b --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/VNode.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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() +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/VectorComponent.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/VectorComponent.kt new file mode 100644 index 0000000..46e65a4 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/VectorComponent.kt @@ -0,0 +1,148 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/VectorPainter.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/VectorPainter.kt new file mode 100644 index 0000000..f63a849 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/VectorPainter.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/navigation/Decompose.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/navigation/Decompose.kt new file mode 100644 index 0000000..d7231f8 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/navigation/Decompose.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.imagetoolbox.core.utils.makeLog +import kotlinx.coroutines.CancellationException +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 -> + if (t !is CancellationException) { + 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> diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/navigation/Screen.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/navigation/Screen.kt new file mode 100644 index 0000000..63e2adf --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/navigation/Screen.kt @@ -0,0 +1,1117 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.vector.ImageVector +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Animation +import com.t8rin.imagetoolbox.core.resources.icons.Apng +import com.t8rin.imagetoolbox.core.resources.icons.ArtTrack +import com.t8rin.imagetoolbox.core.resources.icons.AutoFixHigh +import com.t8rin.imagetoolbox.core.resources.icons.CompareArrows +import com.t8rin.imagetoolbox.core.resources.icons.Exif +import com.t8rin.imagetoolbox.core.resources.icons.FilePresent +import com.t8rin.imagetoolbox.core.resources.icons.Gif +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.Texture +import com.t8rin.imagetoolbox.core.resources.icons.Webp +import com.t8rin.imagetoolbox.core.settings.presentation.model.Setting +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 object AppLogs : Screen( + id = -6, + title = 0, + subtitle = 0 + ) + + @Serializable + data class Help( + val categoryName: String? = null, + val tipId: String? = null + ) : Screen( + id = -7, + title = 0, + subtitle = 0 + ) + + @Serializable + data object UsageStatistics : Screen( + id = -8, + title = 0, + subtitle = 0 + ) + + @Serializable + data class Settings( + val searchQuery: String = "", + val targetSetting: Setting? = null + ) : 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.Outlined.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.watermark_pdf, + 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 MergeGif -> Icons.Rounded.CompareArrows + 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 MergeGif( + val gifUris: List? = null + ) : Type( + title = R.string.gif_type_merge, + subtitle = R.string.gif_type_merge_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(), + MergeGif(), + ) + } + } + } + } + + @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 + ) + + @Serializable + data object ShaderStudio : Screen( + id = 68, + title = R.string.shader_studio, + subtitle = R.string.shader_studio_sub + ) + + @Serializable + data object TextureGeneration : Screen( + id = 69, + title = R.string.texture_generation, + subtitle = R.string.texture_generation_sub + ) + + @Serializable + data class BatchRename( + val uris: List? = null + ) : Screen( + id = 70, + title = R.string.batch_rename, + subtitle = R.string.batch_rename_sub + ) + + @Serializable + data class DuplicateFinder( + val uris: List? = null + ) : Screen( + id = 71, + title = R.string.duplicate_finder, + subtitle = R.string.duplicate_finder_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 +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/navigation/ScreenSearch.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/navigation/ScreenSearch.kt new file mode 100644 index 0000000..dcbe8c4 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/navigation/ScreenSearch.kt @@ -0,0 +1,214 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.annotation.StringRes +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.getStringEnglish +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.AppLogs +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.BatchRename +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.DuplicateFinder +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.Help +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.ShaderStudio +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.TextureGeneration +import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.UsageStatistics +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 com.t8rin.imagetoolbox.core.utils.appContext + +fun Screen.matchesSearchQuery(query: String): Boolean { + val trimmedQuery = query.trim() + + if (trimmedQuery.isEmpty()) return true + + val keywordsRes = searchKeywordsRes() + + val searchableText = listOf( + searchableTextFor( + title = title, + subtitle = subtitle + ), + keywordsRes?.let(appContext::getString).orEmpty(), + keywordsRes?.let { appContext.getStringEnglish(it) }.orEmpty(), + simpleName.replace("_", " "), + typeSearchableText() + ).joinToString(separator = " ") + + return searchableText.contains( + other = trimmedQuery, + ignoreCase = true + ) || trimmedQuery.split(Regex("\\s+")).all { word -> + searchableText.contains( + other = word, + ignoreCase = true + ) + } +} + +private fun Screen.typeSearchableText(): String = when (this) { + is ApngTools -> type?.let { searchableTextFor(it.title, it.subtitle) } + is Filter -> type?.let { searchableTextFor(it.title, it.subtitle) } + is GifTools -> type?.let { searchableTextFor(it.title, it.subtitle) } + is JxlTools -> type?.let { searchableTextFor(it.title, it.subtitle) } + is RecognizeText -> type?.let { searchableTextFor(it.title, it.subtitle) } + is WebpTools -> type?.let { searchableTextFor(it.title, it.subtitle) } + else -> null +}.orEmpty() + +private fun searchableTextFor( + @StringRes title: Int, + @StringRes subtitle: Int +): String = listOf( + appContext.getString(title), + appContext.getString(subtitle), + appContext.getStringEnglish(title), + appContext.getStringEnglish(subtitle) +).joinToString(separator = " ") + +@StringRes +private fun Screen.searchKeywordsRes(): Int? = when (this) { + is SingleEdit -> R.string.search_keywords_single_edit + is ResizeAndConvert -> R.string.search_keywords_resize_and_convert + is WeightResize -> R.string.search_keywords_weight_resize + is Crop -> R.string.search_keywords_crop + is Filter -> R.string.search_keywords_filter + is Draw -> R.string.search_keywords_draw + is DuplicateFinder -> R.string.search_keywords_duplicate_finder + is Cipher -> R.string.search_keywords_cipher + is EraseBackground -> R.string.search_keywords_erase_background + is ImagePreview -> R.string.search_keywords_image_preview + is ImageStitching -> R.string.search_keywords_image_stitching + is LoadNetImage -> R.string.search_keywords_load_net_image + is PickColorFromImage -> R.string.search_keywords_pick_color_from_image + is PaletteTools -> R.string.search_keywords_palette_tools + is DeleteExif -> R.string.search_keywords_delete_exif + is Compare -> R.string.search_keywords_compare + is LimitResize -> R.string.search_keywords_limit_resize + is PdfTools -> R.string.search_keywords_pdf_tools + is RecognizeText -> R.string.search_keywords_recognize_text + is GradientMaker -> R.string.search_keywords_gradient_maker + is Watermarking -> R.string.search_keywords_watermarking + is GifTools -> R.string.search_keywords_gif_tools + is ApngTools -> R.string.search_keywords_apng_tools + is Zip -> R.string.search_keywords_zip + is JxlTools -> R.string.search_keywords_jxl_tools + is SvgMaker -> R.string.search_keywords_svg_maker + is FormatConversion -> R.string.search_keywords_format_conversion + is DocumentScanner -> R.string.search_keywords_document_scanner + is ScanQrCode -> R.string.search_keywords_scan_qr_code + is ImageStacking -> R.string.search_keywords_image_stacking + is ImageSplitting -> R.string.search_keywords_image_splitting + is ColorTools -> R.string.search_keywords_color_tools + is WebpTools -> R.string.search_keywords_webp_tools + is NoiseGeneration -> R.string.search_keywords_noise_generation + is TextureGeneration -> R.string.search_keywords_texture_generation + is CollageMaker -> R.string.search_keywords_collage_maker + is MarkupLayers -> R.string.search_keywords_markup_layers + is Base64Tools -> R.string.search_keywords_base64_tools + is BatchRename -> R.string.search_keywords_batch_rename + is ChecksumTools -> R.string.search_keywords_checksum_tools + is MeshGradients -> R.string.search_keywords_mesh_gradients + is EditExif -> R.string.search_keywords_edit_exif + is ImageCutter -> R.string.search_keywords_image_cutter + is AudioCoverExtractor -> R.string.search_keywords_audio_cover_extractor + is WallpapersExport -> R.string.search_keywords_wallpapers_export + is AsciiArt -> R.string.search_keywords_ascii_art + is AiTools -> R.string.search_keywords_ai_tools + is ColorLibrary -> R.string.search_keywords_color_library + is ShaderStudio -> R.string.search_keywords_shader_studio + is PdfTools.Merge -> R.string.search_keywords_pdf_merge + is PdfTools.Split -> R.string.search_keywords_pdf_split + is PdfTools.Rotate -> R.string.search_keywords_pdf_rotate + is PdfTools.Rearrange -> R.string.search_keywords_pdf_rearrange + is PdfTools.PageNumbers -> R.string.search_keywords_pdf_page_numbers + is PdfTools.OCR -> R.string.search_keywords_pdf_ocr + is PdfTools.Watermark -> R.string.search_keywords_pdf_watermark + is PdfTools.Signature -> R.string.search_keywords_pdf_signature + is PdfTools.Protect -> R.string.search_keywords_pdf_protect + is PdfTools.Unlock -> R.string.search_keywords_pdf_unlock + is PdfTools.Compress -> R.string.search_keywords_pdf_compress + is PdfTools.Grayscale -> R.string.search_keywords_pdf_grayscale + is PdfTools.Repair -> R.string.search_keywords_pdf_repair + is PdfTools.Metadata -> R.string.search_keywords_pdf_metadata + is PdfTools.RemovePages -> R.string.search_keywords_pdf_remove_pages + is PdfTools.Crop -> R.string.search_keywords_pdf_crop + is PdfTools.Flatten -> R.string.search_keywords_pdf_flatten + is PdfTools.ExtractImages -> R.string.search_keywords_pdf_extract_images + is PdfTools.ZipConvert -> R.string.search_keywords_pdf_zip_convert + is PdfTools.Print -> R.string.search_keywords_pdf_print + is PdfTools.Preview -> R.string.search_keywords_pdf_preview + is PdfTools.ImagesToPdf -> R.string.search_keywords_pdf_images_to_pdf + is PdfTools.ExtractPages -> R.string.search_keywords_pdf_extract_pages + is PdfTools.RemoveAnnotations -> R.string.search_keywords_pdf_remove_annotations + is EasterEgg, + is Main, + is Settings, + is AppLogs, + is LibrariesInfo, + is LibraryDetails, + is Help, + is UsageStatistics -> null +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/navigation/ScreenUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/navigation/ScreenUtils.kt new file mode 100644 index 0000000..67c99b7 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/navigation/ScreenUtils.kt @@ -0,0 +1,533 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.ui.graphics.vector.ImageVector +import androidx.core.net.toUri +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Album +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.AutoFixHigh +import com.t8rin.imagetoolbox.core.resources.icons.Base64 +import com.t8rin.imagetoolbox.core.resources.icons.Bolt +import com.t8rin.imagetoolbox.core.resources.icons.Brick +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.EvShadow +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.FileRename +import com.t8rin.imagetoolbox.core.resources.icons.FilterBAndW +import com.t8rin.imagetoolbox.core.resources.icons.FindInPage +import com.t8rin.imagetoolbox.core.resources.icons.FolderZip +import com.t8rin.imagetoolbox.core.resources.icons.FormatPaintVariant +import com.t8rin.imagetoolbox.core.resources.icons.GifBox +import com.t8rin.imagetoolbox.core.resources.icons.Gradient +import com.t8rin.imagetoolbox.core.resources.icons.HashTag +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.ImageSearch +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.Palette +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.WatermarkAlt +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.AppLogs +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.BatchRename +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.DuplicateFinder +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.Help +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.ShaderStudio +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.TextureGeneration +import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.UsageStatistics +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 DuplicateFinder -> "Duplicate_Finder" + 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 TextureGeneration -> "Texture_Generation" + is CollageMaker -> "Collage_Maker" + is AppLogs -> "App_Logs" + is LibrariesInfo -> "Libraries_Info" + is MarkupLayers -> "Markup_Layers" + is Base64Tools -> "Base64_Tools" + is BatchRename -> "Batch_Rename" + 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 ShaderStudio -> "Shader_Studio" + is Help -> "Help_Tips" + is UsageStatistics -> "Usage_Statistics" + 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 AppLogs, + is LibrariesInfo, + is MeshGradients, + is LibraryDetails, + is Help, + is UsageStatistics -> 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 DuplicateFinder -> Icons.Outlined.ImageSearch + 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.Palette + is WebpTools -> Icons.Outlined.WebpBox + is NoiseGeneration -> Icons.Outlined.NoiseAlt + is TextureGeneration -> Icons.Outlined.Brick + is CollageMaker -> Icons.Outlined.Collage + is MarkupLayers -> Icons.Outlined.Stacks + is Base64Tools -> Icons.Outlined.Base64 + is BatchRename -> Icons.Outlined.FileRename + is ChecksumTools -> Icons.Rounded.HashTag + 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 ShaderStudio -> Icons.Rounded.EvShadow + 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.WatermarkAlt + 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.Rounded.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 AppLogs, + is LibrariesInfo, + is MeshGradients, + is LibraryDetails, + is Help, + is UsageStatistics -> 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 DuplicateFinder -> Icons.TwoTone.ImageSearch + 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.TwoTone.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.Palette + is WebpTools -> Icons.TwoTone.WebpBox + is NoiseGeneration -> Icons.Outlined.NoiseAlt + is TextureGeneration -> Icons.TwoTone.Brick + is CollageMaker -> Icons.TwoTone.Collage + is MarkupLayers -> Icons.TwoTone.Stacks + is Base64Tools -> Icons.TwoTone.Base64 + is BatchRename -> Icons.TwoTone.FileRename + is ChecksumTools -> Icons.Rounded.HashTag + is EditExif -> Icons.TwoTone.ExifEdit + is ImageCutter -> Icons.TwoTone.ScissorsSmall + is AudioCoverExtractor -> Icons.TwoTone.Album + is WallpapersExport -> Icons.TwoTone.WallpaperAlt + is AsciiArt -> Icons.Outlined.Ascii + is AiTools -> Icons.TwoTone.Neurology + is ColorLibrary -> Icons.TwoTone.FormatPaintVariant + is ShaderStudio -> Icons.TwoTone.EvShadow + 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.WatermarkAlt + 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(), + BatchRename(), + ), + 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(), + ShaderStudio, + NoiseGeneration, + TextureGeneration, + ), + title = R.string.create, + selectedIcon = Icons.Rounded.WandShine, + baseIcon = Icons.Outlined.WandShine + ), + ScreenGroup( + entries = listOf( + PickColorFromImage(), + RecognizeText(), + Compare(), + DuplicateFinder(), + 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 = 88 + PdfTools.options.size +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/painter/CenterCropPainter.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/painter/CenterCropPainter.kt new file mode 100644 index 0000000..f145008 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/painter/CenterCropPainter.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/painter/RoundCornersPainter.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/painter/RoundCornersPainter.kt new file mode 100644 index 0000000..e3f960f --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/painter/RoundCornersPainter.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/permission/PermissionResult.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/permission/PermissionResult.kt new file mode 100644 index 0000000..5d28d2d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/permission/PermissionResult.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/permission/PermissionStatus.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/permission/PermissionStatus.kt new file mode 100644 index 0000000..824ad83 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/permission/PermissionStatus.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/permission/PermissionUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/permission/PermissionUtils.kt new file mode 100644 index 0000000..d6199ae --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/permission/PermissionUtils.kt @@ -0,0 +1,146 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + } + } + } + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/ImageToolboxCompositionLocals.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/ImageToolboxCompositionLocals.kt new file mode 100644 index 0000000..ccd7506 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/ImageToolboxCompositionLocals.kt @@ -0,0 +1,145 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.content_pickers.ImagePickerEventsHandler +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 com.t8rin.imagetoolbox.core.ui.widget.sheets.SkippedImagesSheetHost +import kotlinx.coroutines.delay + +@Composable +fun ImageToolboxCompositionLocals( + settingsState: UiSettingsState, + filterPreviewModel: ImageModel? = null, + canSetDynamicFilterPreview: Boolean = false, + currentScreen: Screen? = null, + onNavigate: (Screen) -> Unit = {}, + 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() + + SkippedImagesSheetHost( + onNavigate = onNavigate + ) + + ConfettiHost() + + ImagePickerEventsHandler() + + 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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalComponentActivity.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalComponentActivity.kt new file mode 100644 index 0000000..65d6b8c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalComponentActivity.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 } \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalContainerShape.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalContainerShape.kt new file mode 100644 index 0000000..eab220f --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalContainerShape.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.Brush +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 LocalContainerBrush = compositionLocalOf { null } + +val SafeLocalContainerColor + @Composable + get() = LocalContainerColor.current?.takeOrElse { + MaterialTheme.colorScheme.surfaceContainerLow + } ?: MaterialTheme.colorScheme.surfaceContainerLow + +@Composable +fun ProvideContainerDefaults( + shape: Shape? = null, + color: Color? = null, + brush: Brush? = null, + content: @Composable () -> Unit +) = CompositionLocalProvider( + values = arrayOf( + LocalContainerShape provides shape, + LocalContainerColor provides color, + LocalContainerBrush provides brush + ), + content = content +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalKeepAliveService.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalKeepAliveService.kt new file mode 100644 index 0000000..e8d1f7e --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalKeepAliveService.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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") } \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalMetadataProvider.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalMetadataProvider.kt new file mode 100644 index 0000000..76b07df --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalMetadataProvider.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalResourceManager.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalResourceManager.kt new file mode 100644 index 0000000..e6bb8d6 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalResourceManager.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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") } \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalScreenSize.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalScreenSize.kt new file mode 100644 index 0000000..24445c3 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalScreenSize.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalWindowSizeClass.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalWindowSizeClass.kt new file mode 100644 index 0000000..44e3084 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalWindowSizeClass.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.windowsizeclass.WindowSizeClass +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp + +val LocalWindowSizeClass = compositionLocalOf { + WindowSizeClass.calculateFromSize(DpSize(400.dp, 1000.dp)) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/state/ObjectSaverDelegate.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/state/ObjectSaverDelegate.kt new file mode 100644 index 0000000..6ce3e2c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/state/ObjectSaverDelegate.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/state/Update.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/state/Update.kt new file mode 100644 index 0000000..e90ed31 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/state/Update.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 } +//} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/AdaptiveBottomScaffoldLayoutScreen.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/AdaptiveBottomScaffoldLayoutScreen.kt new file mode 100644 index 0000000..680e805 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/AdaptiveBottomScaffoldLayoutScreen.kt @@ -0,0 +1,395 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.core.animateFloatAsState +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.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.rememberBottomSheetState +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.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.RectangleShape +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.graphicsLayer +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +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.PredictiveBackObserver +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.AutoCornersShape +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.delay +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 = rememberBottomSheetState( + initialValue = SheetValue.PartiallyExpanded, + enabledValues = setOf( + SheetValue.PartiallyExpanded, + SheetValue.Expanded + ), + 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.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) + } + } + } + } + } + + val isExpanded by remember { + derivedStateOf { + scaffoldState.bottomSheetState.currentValue == SheetValue.Expanded || + scaffoldState.bottomSheetState.targetValue == SheetValue.Expanded + } + } + val scope = rememberCoroutineScope() + + 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 = + settingsState.enableSheetGestures || + scaffoldState.bottomSheetState.currentValue == SheetValue.PartiallyExpanded + && !scaffoldState.bottomSheetState.isAnimationRunning + + var predictiveBackProgress by remember { + mutableFloatStateOf(0f) + } + val animatedPredictiveBackProgress by animateFloatAsState(predictiveBackProgress) + + val clean = { + predictiveBackProgress = 0f + } + + PredictiveBackObserver( + onProgress = { progress -> + predictiveBackProgress = progress / 6f + }, + onClean = { isCompleted -> + if (isCompleted) { + scope.launch { + delay(150) + clean() + } + scaffoldState.bottomSheetState.partialExpand() + } + clean() + }, + enabled = isExpanded + ) + + BottomSheetScaffold( + modifier = Modifier.fillMaxSize(), + scaffoldState = scaffoldState, + sheetPeekHeight = 80.dp + WindowInsets.navigationBars.asPaddingValues() + .calculateBottomPadding(), + sheetDragHandle = null, + sheetShape = RectangleShape, + containerColor = Color.Transparent, + sheetContainerColor = Color.Transparent, + sheetShadowElevation = 0.dp, + sheetSwipeEnabled = sheetSwipeEnabled, + sheetContent = { + val animatedShape = AutoCornersShape( + (32.dp * (animatedPredictiveBackProgress * 10f)).coerceIn(0.dp, 32.dp) + ) + Scaffold( + modifier = Modifier + .heightIn(max = screenHeight * 0.7f) + .graphicsLayer { + val progress = animatedPredictiveBackProgress + scaleX = (1f - progress).coerceAtLeast(0.85f) + scaleY = (1f - progress).coerceAtLeast(0.85f) + transformOrigin = TransformOrigin( + pivotFractionX = 0.5f, + pivotFractionY = 1f + ) + shape = animatedShape + clip = progress > 0f + } + .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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/AdaptiveLayoutScreen.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/AdaptiveLayoutScreen.kt new file mode 100644 index 0000000..6f978bb --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/AdaptiveLayoutScreen.kt @@ -0,0 +1,338 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.ArrowBack +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.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 + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/BottomButtonsBlock.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/BottomButtonsBlock.kt new file mode 100644 index 0000000..4ff4b8a --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/BottomButtonsBlock.kt @@ -0,0 +1,392 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt +import com.t8rin.imagetoolbox.core.resources.icons.Save +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, + drawBothStrokes: Boolean = false +) { + 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 + ) + .drawHorizontalStroke( + top = false, + enabled = drawBothStrokes && 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 + ) + .drawHorizontalStroke( + top = false, + enabled = drawBothStrokes && 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)) + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/CompareButton.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/CompareButton.kt new file mode 100644 index 0000000..2e8fea8 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/CompareButton.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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) + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/EraseModeButton.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/EraseModeButton.kt new file mode 100644 index 0000000..5f43742 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/EraseModeButton.kt @@ -0,0 +1,64 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Eraser +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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) + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/MediaCheckBox.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/MediaCheckBox.kt new file mode 100644 index 0000000..d4f78ed --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/MediaCheckBox.kt @@ -0,0 +1,141 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.padding +import androidx.compose.foundation.layout.size +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.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.CheckCircle +import com.t8rin.imagetoolbox.core.resources.icons.Circle +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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.Rounded.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() + scaleIn() togetherWith fadeOut() + scaleOut() + } + ) { icon -> + Icon( + imageVector = icon, + contentDescription = null, + tint = color + ) + } + } + } else { + AnimatedContent( + targetState = Triple(isChecked, image, selectionIndex), + transitionSpec = { + if (initialState.third >= 0 && targetState.third >= 0) { + fadeIn() + scaleIn(initialScale = 0.85f) togetherWith fadeOut() + scaleOut( + targetScale = 0.85f + ) + } else { + fadeIn() + scaleIn() togetherWith fadeOut() + scaleOut() + } + } + ) { (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, + fontWeight = FontWeight.Bold + ) + } + } else { + Icon( + imageVector = image, + modifier = modifier, + contentDescription = null, + tint = color + ) + } + } else { + Icon( + imageVector = image, + modifier = modifier, + contentDescription = null, + tint = color + ) + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/PagerScrollPanel.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/PagerScrollPanel.kt new file mode 100644 index 0000000..f007aac --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/PagerScrollPanel.kt @@ -0,0 +1,99 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.ArrowBackIos +import com.t8rin.imagetoolbox.core.resources.icons.ArrowForwardIos +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.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.Rounded.ArrowForwardIos, + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/PanModeButton.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/PanModeButton.kt new file mode 100644 index 0000000..e3002e1 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/PanModeButton.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FrontHand +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 = if (selected) { + Icons.Rounded.FrontHand + } else { + Icons.Outlined.FrontHand + }, + contentDescription = stringResource(R.string.draw) + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/ShareButton.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/ShareButton.kt new file mode 100644 index 0000000..68e5850 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/ShareButton.kt @@ -0,0 +1,154 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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.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.ContentCopy +import com.t8rin.imagetoolbox.core.resources.icons.Image +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.icons.Share +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 + ) + } + } + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/ShowOriginalButton.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/ShowOriginalButton.kt new file mode 100644 index 0000000..7259216 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/ShowOriginalButton.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.History +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) + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/SupportingButton.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/SupportingButton.kt new file mode 100644 index 0000000..45bb06b --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/SupportingButton.kt @@ -0,0 +1,84 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Info +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() + } + ) + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/ZoomButton.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/ZoomButton.kt new file mode 100644 index 0000000..9143978 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/ZoomButton.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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.ZoomIn +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.Outlined.ZoomIn, + contentDescription = stringResource(R.string.zoom) + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/AvailableColorTuplesSheet.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/AvailableColorTuplesSheet.kt new file mode 100644 index 0000000..0db298a --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/AvailableColorTuplesSheet.kt @@ -0,0 +1,513 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AddCircle +import com.t8rin.imagetoolbox.core.resources.icons.Contrast +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.EditAlt +import com.t8rin.imagetoolbox.core.resources.icons.EmojiEmotions +import com.t8rin.imagetoolbox.core.resources.icons.InvertColors +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.Outlined.AddCircle, + 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() + + LaunchedEffect(settingsState.isDynamicColors, visible) { + if (settingsState.isDynamicColors && visible) 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() + ) {} + } + } +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorInfo.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorInfo.kt new file mode 100644 index 0000000..7d2f719 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorInfo.kt @@ -0,0 +1,327 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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.hex +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ContentCopy +import com.t8rin.imagetoolbox.core.resources.icons.ContentPaste +import com.t8rin.imagetoolbox.core.resources.icons.Palette +import com.t8rin.imagetoolbox.core.resources.icons.Shuffle +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 = color.hex(), + 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 = color.hex(), + 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(color.hex()) } + 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)) + } + } + ) + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorPicker.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorPicker.kt new file mode 100644 index 0000000..6c3ec6c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorPicker.kt @@ -0,0 +1,627 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.MaterialTheme +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.drawOutline +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.DrawStyle +import androidx.compose.ui.graphics.drawscope.Fill +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.drawscope.withTransform +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.graphics.takeOrElse +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.tooling.preview.Preview +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.resources.shapes.MaterialStarShape +import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState +import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview +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.Unspecified, + 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) { + val colorScheme = MaterialTheme.colorScheme + val isDark = LocalSettingsState.current.isNightMode + + Canvas(modifier = Modifier.fillMaxSize()) { + val thumbY = (hue / 359f) * (sliderSize.height - thumbHeightPx) + + drawRoundRect( + color = thumbConfig.color.takeOrElse { + if (isDark) { + colorScheme.onPrimaryFixed + } else { + colorScheme.primaryFixed + } + }, + topLeft = Offset( + x = thumbConfig.borderSize.toPx() / 2 + 4f, + y = thumbY + 4f + ), + size = Size( + width = sliderSize.width - thumbConfig.borderSize.toPx() - 8f, + height = thumbHeightPx - 8f + ), + style = Stroke(width = thumbConfig.borderSize.toPx()), + cornerRadius = CornerRadius( + thumbConfig.borderRadius, + thumbConfig.borderRadius + ) + ) + + drawRoundRect( + color = thumbConfig.color.takeOrElse { + if (isDark) { + colorScheme.primaryFixed + } else { + colorScheme.onPrimaryFixed + } + }, + 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.height > 0f) { + val colorScheme = MaterialTheme.colorScheme + val isDark = LocalSettingsState.current.isNightMode + + Canvas(modifier = Modifier.fillMaxSize()) { + val thumbY = (1f - alpha) * (sliderSize.height - thumbHeightPx) + drawRoundRect( + color = thumbConfig.color.takeOrElse { + if (isDark) { + colorScheme.onPrimaryFixed + } else { + colorScheme.primaryFixed + } + }, + topLeft = Offset( + x = thumbConfig.borderSize.toPx() / 2 + 4f, + y = thumbY + 4f + ), + size = Size( + width = sliderSize.width - thumbConfig.borderSize.toPx() - 8f, + height = thumbHeightPx - 8f + ), + style = Stroke(width = thumbConfig.borderSize.toPx()), + cornerRadius = CornerRadius( + thumbConfig.borderRadius, + thumbConfig.borderRadius + ) + ) + + drawRoundRect( + color = thumbConfig.color.takeOrElse { + if (isDark) { + colorScheme.primaryFixed + } else { + colorScheme.onPrimaryFixed + } + }, + 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 + ) + ) + } + } + } +} + +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 +) { + val colorScheme = MaterialTheme.colorScheme + + Canvas(modifier = modifier) { + drawBlendingRectGradient( + dst = brushDst, + src = brushSrc, + blendMode = BlendMode.Multiply + ) + drawHueSelectionCircle( + center = selectorPosition, + radius = selectorRadius * 2.25f, + colorScheme = colorScheme + ) + } +} + +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, + colorScheme: ColorScheme +) { + fun drawStarOutline( + starSize: Float, + color: Color, + style: DrawStyle = Fill, + ) { + val outline = MaterialStarShape.createOutline( + size = Size(starSize, starSize), + layoutDirection = layoutDirection, + density = this + ) + + withTransform( + transformBlock = { + translate( + left = center.x - starSize / 2f, + top = center.y - starSize / 2f + ) + } + ) { + drawOutline( + outline = outline, + color = color, + style = style + ) + } + } + + drawStarOutline( + starSize = radius, + color = colorScheme.primaryFixedDim, + style = Stroke(width = radius / 4) + ) + + drawStarOutline( + starSize = radius + radius / 8, + color = colorScheme.onPrimaryFixed, + style = Stroke(width = radius / 8) + ) +} + +@Preview +@Composable +private fun Preview() = ImageToolboxThemeForPreview(true, keyColor = Color.Red) { + ColorPicker( + selectedColor = Color(0xFF438243), + onColorSelected = {}, + modifier = Modifier + .padding(16.dp) + .fillMaxWidth() + .height(200.dp), + hueSliderConfig = HueSliderThumbConfig( + withAlpha = true + ) + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorPickerSheet.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorPickerSheet.kt new file mode 100644 index 0000000..c822939 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorPickerSheet.kt @@ -0,0 +1,173 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Bookmark +import com.t8rin.imagetoolbox.core.resources.icons.BookmarkRemove +import com.t8rin.imagetoolbox.core.resources.icons.Palette +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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, + additionalContent: @Composable ColumnScope.(onColorChange: (Color) -> Unit) -> Unit = {} +) { + 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) + ) { + BasicColorsCard( + value = tempColor, + onValueChange = { tempColor = it }, + allowAlpha = allowAlpha, + modifier = Modifier.padding(bottom = 8.dp) + ) + + RecentAndFavoriteColorsCard( + onRecentColorClick = { tempColor = it }, + onFavoriteColorClick = { tempColor = it } + ) + + additionalContent { 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.Palette + ) + }, + 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)) + } + } + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorSelection.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorSelection.kt new file mode 100644 index 0000000..0b91b7a --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorSelection.kt @@ -0,0 +1,58 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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)) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorSelectionRow.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorSelectionRow.kt new file mode 100644 index 0000000..fe9f422 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorSelectionRow.kt @@ -0,0 +1,383 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.Block +import com.t8rin.imagetoolbox.core.resources.icons.Done +import com.t8rin.imagetoolbox.core.resources.icons.Error +import com.t8rin.imagetoolbox.core.resources.icons.Palette +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, + allowCustom: Boolean = true +) { + 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) + ) + } + } + } + if (allowCustom) { + 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.Done, + 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) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorTupleDefaults.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorTupleDefaults.kt new file mode 100644 index 0000000..77e7523 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorTupleDefaults.kt @@ -0,0 +1,59 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.calculateErrorColor +import com.t8rin.dynamic.theme.calculateNeutralVariantColor +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(), + neutralVariant = it.calculateNeutralVariantColor().toColor(), + error = it.calculateErrorColor().toColor() + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorTuplePicker.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorTuplePicker.kt new file mode 100644 index 0000000..173d5aa --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorTuplePicker.kt @@ -0,0 +1,356 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.calculateErrorColor +import com.t8rin.dynamic.theme.calculateNeutralVariantColor +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ContentPaste +import com.t8rin.imagetoolbox.core.resources.icons.FormatColorFill +import com.t8rin.imagetoolbox.core.resources.icons.Palette +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() + ) + } + var neutralVariant by rememberSaveable(colorTuple, stateSaver = ColorSaver) { + mutableStateOf( + colorTuple.neutralVariant ?: colorTuple.primary.calculateNeutralVariantColor().toColor() + ) + } + var error by rememberSaveable(colorTuple, settingsState, stateSaver = ColorSaver) { + mutableStateOf( + colorTuple.error ?: colorTuple.primary.calculateErrorColor(settingsState.themeStyle) + .toColor() + ) + } + + val appColorTuple = rememberAppColorTuple( + defaultColorTuple = settingsState.appColorTuple, + dynamicColor = true, + darkTheme = false + ) + + val scheme = rememberColorScheme( + amoledMode = false, + isDarkTheme = false, + 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() + neutralVariant = + colorTuple.neutralVariant + ?: colorTuple.primary.calculateNeutralVariantColor().toColor() + error = + colorTuple.error + ?: colorTuple.primary.calculateErrorColor(settingsState.themeStyle).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 + neutralVariant = this.surfaceVariant + error = this.error + } + } + }, + ) { + 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() + neutralVariant = it.calculateNeutralVariantColor().toColor() + error = it.calculateErrorColor(settingsState.themeStyle) + .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 + ) + } + } + item { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .container(ShapeDefaults.extraLarge) + .padding(horizontal = 20.dp) + ) { + TitleItem(text = stringResource(R.string.neutral_variant)) + ColorSelection( + value = neutralVariant, + onValueChange = { + neutralVariant = it + }, + infoContainerColor = MaterialTheme.colorScheme.surface + ) + } + } + item { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .container(ShapeDefaults.extraLarge) + .padding(horizontal = 20.dp) + ) { + TitleItem(text = stringResource(R.string.error)) + ColorSelection( + value = error, + onValueChange = { + error = it + }, + infoContainerColor = MaterialTheme.colorScheme.surface + ) + } + } + } + } + } + }, + confirmButton = { + EnhancedButton( + onClick = { + onColorChange( + ColorTuple( + primary = primary, + secondary = secondary, + tertiary = tertiary, + surface = surface, + neutralVariant = neutralVariant, + error = error + ) + ) + onDismiss() + } + ) { + AutoSizeText(stringResource(R.string.save)) + } + }, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorTuplePreview.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorTuplePreview.kt new file mode 100644 index 0000000..252fbe4 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorTuplePreview.kt @@ -0,0 +1,138 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Done +import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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)) + ) + } + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/RecentAndFavoriteColorsCard.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/RecentAndFavoriteColorsCard.kt new file mode 100644 index 0000000..1acfaf1 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/RecentAndFavoriteColorsCard.kt @@ -0,0 +1,343 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Bookmark +import com.t8rin.imagetoolbox.core.resources.icons.ContentPasteGo +import com.t8rin.imagetoolbox.core.resources.icons.DeleteSweep +import com.t8rin.imagetoolbox.core.resources.icons.History +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.controls.selection.ColorRowSelector +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 +internal fun BasicColorsCard( + value: Color, + onValueChange: (Color) -> Unit, + allowAlpha: Boolean = true, + modifier: Modifier = Modifier +) { + ColorRowSelector( + value = value, + onValueChange = onValueChange, + allowAlpha = allowAlpha, + title = stringResource(R.string.basic), + modifier = modifier.container( + shape = ShapeDefaults.extraLarge + ), + allowCustom = false + ) +} + +@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( + start = 16.dp, + end = 16.dp, + bottom = 16.dp, + top = 8.dp + ), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + BoxAnimatedVisibility(recentColors.isNotEmpty()) { + Column { + TitleItem( + text = stringResource(R.string.recently_used), + icon = Icons.Rounded.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.Outlined.Bookmark, + 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() + ) + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/FileReorderVerticalList.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/FileReorderVerticalList.kt new file mode 100644 index 0000000..b5cf33b --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/FileReorderVerticalList.kt @@ -0,0 +1,395 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Add +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 scope = rememberCoroutineScope() + + 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) + ) + } + + 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)) + ReorderItemMoveMenu( + canMoveToStart = index > 0, + canMoveToEnd = index < data.value.lastIndex, + onMoveToStart = { + data.value = data.value.toMutableList().apply { + add(0, removeAt(index)) + } + onReorder(data.value) + scope.launch { listState.animateScrollToItem(0) } + }, + onMoveToEnd = { + val targetIndex = data.value.lastIndex + data.value = data.value.toMutableList().apply { + add(removeAt(index)) + } + onReorder(data.value) + scope.launch { listState.animateScrollToItem(targetIndex) } + } + ) + Spacer(Modifier.width(8.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 + ) { + 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") + } + } +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/FormatExifWarning.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/FormatExifWarning.kt new file mode 100644 index 0000000..2ee3e7f --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/FormatExifWarning.kt @@ -0,0 +1,72 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/IcoSizeWarning.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/IcoSizeWarning.kt new file mode 100644 index 0000000..58603e8 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/IcoSizeWarning.kt @@ -0,0 +1,71 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ImageReorderCarousel.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ImageReorderCarousel.kt new file mode 100644 index 0000000..84ec561 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ImageReorderCarousel.kt @@ -0,0 +1,369 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.offset +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.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.platform.LocalInspectionMode +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +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.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Add +import com.t8rin.imagetoolbox.core.resources.icons.MoreVert +import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview +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 scope = rememberCoroutineScope() + 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) + ) + } + + 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) + ) { + Row( + modifier = Modifier + .padding(top = 4.dp) + .width(120.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + EnhancedButton( + contentPadding = PaddingValues(), + onClick = { onNeedToRemoveImageAt(index) }, + containerColor = MaterialTheme.colorScheme.errorContainer.copy( + 0.5f + ), + contentColor = MaterialTheme.colorScheme.onErrorContainer, + shape = ShapeDefaults.bottomStart, + modifier = Modifier + .height(30.dp) + .width(86.dp) + ) { + Text( + text = stringResource(id = R.string.remove), + fontSize = 11.sp + ) + } + ReorderItemMoveMenu( + button = { onClick -> + EnhancedButton( + contentPadding = PaddingValues(), + onClick = onClick, + containerColor = MaterialTheme + .colorScheme.secondaryContainer.copy(0.4f), + contentColor = MaterialTheme + .colorScheme.onSecondaryContainer.copy(0.8f), + shape = ShapeDefaults.bottomEnd, + modifier = Modifier.size(30.dp) + ) { + Icon( + imageVector = Icons.Rounded.MoreVert, + contentDescription = null, + modifier = Modifier + .size(20.dp) + .offset( + x = (-0.5).dp, + y = (-0.5).dp + ) + ) + } + }, + modifier = Modifier.size(30.dp), + canMoveToStart = index > 0, + canMoveToEnd = index < data.value.lastIndex, + onMoveToStart = { + data.value = data.value.toMutableList().apply { + add(0, removeAt(index)) + } + onReorder(data.value) + scope.launch { listState.animateScrollToItem(0) } + }, + onMoveToEnd = { + val targetIndex = data.value.lastIndex + data.value = data.value.toMutableList().apply { + add(removeAt(index)) + } + onReorder(data.value) + scope.launch { + listState.animateScrollToItem(targetIndex) + } + } + ) + } + } + } + } + } + } + } + } + + if (!LocalInspectionMode.current) { + ImagePager( + visible = previewUri != null, + selectedUri = previewUri, + uris = images, + onNavigate = onNavigate, + onUriSelected = { previewUri = it }, + onShare = { context.shareUris(listOf(it)) }, + onDismiss = { previewUri = null } + ) + } +} + +@Preview +@Composable +private fun Preview() = ImageToolboxThemeForPreview(true) { + ImageReorderCarousel( + images = List(10) { "$it".toUri() }, + onReorder = {}, + onNeedToAddImage = {}, + onNavigate = {}, + onNeedToRemoveImageAt = {} + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ImageTransformBar.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ImageTransformBar.kt new file mode 100644 index 0000000..ccc911a --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ImageTransformBar.kt @@ -0,0 +1,220 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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.domain.image.model.ImageFormat +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AutoFixHigh +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.resources.icons.RotateLeft +import com.t8rin.imagetoolbox.core.resources.icons.RotateRight +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.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.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) + ) + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/OOMWarning.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/OOMWarning.kt new file mode 100644 index 0000000..2e5eeb2 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/OOMWarning.kt @@ -0,0 +1,73 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ReorderItemMoveMenu.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ReorderItemMoveMenu.kt new file mode 100644 index 0000000..9debcd4 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ReorderItemMoveMenu.kt @@ -0,0 +1,133 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +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.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.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.LastPage +import com.t8rin.imagetoolbox.core.resources.icons.MoreVert +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.modifier.ShapeDefaults +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +@Composable +internal fun ReorderItemMoveMenu( + canMoveToStart: Boolean, + canMoveToEnd: Boolean, + onMoveToStart: () -> Unit, + onMoveToEnd: () -> Unit, + button: @Composable (onClick: () -> Unit) -> Unit = { + EnhancedIconButton( + onClick = it, + forceMinimumInteractiveComponentSize = false, + modifier = Modifier.size(32.dp) + ) { + Icon( + imageVector = Icons.Rounded.MoreVert, + contentDescription = null + ) + } + }, + modifier: Modifier = Modifier +) { + var expanded by rememberSaveable { mutableStateOf(false) } + val scope = rememberCoroutineScope() + + Box(modifier) { + button { expanded = true } + EnhancedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + shape = ShapeDefaults.large + ) { + Column( + modifier = Modifier + .width(IntrinsicSize.Max) + .padding(horizontal = 8.dp) + ) { + EnhancedButton( + modifier = Modifier.fillMaxWidth(), + onClick = { + expanded = false + scope.launch { + delay(300) + onMoveToStart() + } + }, + shape = ShapeDefaults.top, + containerColor = MaterialTheme.colorScheme.secondary, + enabled = canMoveToStart + ) { + Icon( + imageVector = Icons.Outlined.LastPage, + contentDescription = null, + modifier = Modifier.scale(scaleX = -1f, scaleY = 1f) + ) + Spacer(Modifier.width(8.dp)) + Text(stringResource(R.string.move_to_start)) + } + Spacer(Modifier.height(4.dp)) + EnhancedButton( + modifier = Modifier.fillMaxWidth(), + onClick = { + expanded = false + scope.launch { + delay(300) + onMoveToEnd() + } + }, + shape = ShapeDefaults.bottom, + containerColor = MaterialTheme.colorScheme.secondary, + enabled = canMoveToEnd + ) { + Icon( + imageVector = Icons.Outlined.LastPage, + contentDescription = null + ) + Spacer(Modifier.width(8.dp)) + Text(stringResource(R.string.move_to_end)) + } + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ResizeImageField.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ResizeImageField.kt new file mode 100644 index 0000000..342708a --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ResizeImageField.kt @@ -0,0 +1,206 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Calculate +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, + shape: Shape = ShapeDefaults.extraLarge +) { + Column( + modifier = modifier + .container(shape = shape) + .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()) } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/SaveExifWidget.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/SaveExifWidget.kt new file mode 100644 index 0000000..3cc6759 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/SaveExifWidget.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +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 + val canSaveExif = imageFormat.canWriteExif && !settingsState.isAlwaysClearExif + LaunchedEffect(settingsState.exifWidgetInitialState, settingsState.isAlwaysClearExif) { + onCheckedChange(settingsState.exifWidgetInitialState && !settingsState.isAlwaysClearExif) + } + PreferenceRowSwitch( + modifier = modifier, + title = stringResource(R.string.keep_exif), + subtitle = if (settingsState.isAlwaysClearExif) { + stringResource(R.string.always_clear_exif_sub) + } else if (imageFormat.canWriteExif) { + stringResource(R.string.keep_exif_sub) + } else { + stringResource( + R.string.image_exif_warning, + imageFormat.title + ) + }, + checked = checked, + enabled = canSaveExif, + shape = ShapeDefaults.extraLarge, + containerColor = backgroundColor, + onClick = onCheckedChange, + startIcon = Icons.Outlined.Exif + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ScaleSmallImagesToLargeToggle.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ScaleSmallImagesToLargeToggle.kt new file mode 100644 index 0000000..1c5a4d8 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ScaleSmallImagesToLargeToggle.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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.LinearScale +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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/SortButton.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/SortButton.kt new file mode 100644 index 0000000..284f8ae --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/SortButton.kt @@ -0,0 +1,148 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FilterAlt +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) + SortType.Reverse -> stringResource(R.string.reverse) + SortType.Shuffle -> stringResource(R.string.shuffle) + } \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/UndoRedoButtons.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/UndoRedoButtons.kt new file mode 100644 index 0000000..4f57255 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/UndoRedoButtons.kt @@ -0,0 +1,71 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.Row +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.unit.Dp +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.Redo +import com.t8rin.imagetoolbox.core.resources.icons.Undo +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 + +@Composable +fun UndoRedoButtons( + canUndo: Boolean, + canRedo: Boolean, + onUndo: () -> Unit, + onRedo: () -> Unit, + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.surface, + resultPadding: Dp = 0.dp +) { + Row( + modifier = modifier.container( + shape = ShapeDefaults.circle, + color = color, + resultPadding = resultPadding + ) + ) { + EnhancedIconButton( + enabled = canUndo, + onClick = onUndo + ) { + Icon( + imageVector = Icons.Rounded.Undo, + contentDescription = "Undo" + ) + } + EnhancedIconButton( + enabled = canRedo, + onClick = onRedo + ) { + Icon( + imageVector = Icons.Rounded.Redo, + contentDescription = "Redo" + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PageInputDialog.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PageInputDialog.kt new file mode 100644 index 0000000..d67dd56 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PageInputDialog.kt @@ -0,0 +1,83 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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.res.stringResource +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Pages +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)) + } + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PageInputField.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PageInputField.kt new file mode 100644 index 0000000..53c6d1f --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PageInputField.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) +} + diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PageSelectionItem.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PageSelectionItem.kt new file mode 100644 index 0000000..100f89a --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PageSelectionItem.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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 com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.icons.Pages +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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PagesSelectionParser.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PagesSelectionParser.kt new file mode 100644 index 0000000..21dc747 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PagesSelectionParser.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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(", ") + } + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/resize_group/ResizeTypeSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/resize_group/ResizeTypeSelector.kt new file mode 100644 index 0000000..674afd2 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/resize_group/ResizeTypeSelector.kt @@ -0,0 +1,386 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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) + } + + LaunchedEffect(value) { + when (value) { + is ResizeType.CenterCrop -> { + canvasColor = value.canvasColor?.let(::Color) ?: Color.Transparent + useBlurredBgInsteadOfColor = value.canvasColor == null + blurRadius = value.blurRadius + position = value.position + } + + is ResizeType.Fit -> { + canvasColor = value.canvasColor?.let(::Color) ?: Color.Transparent + useBlurredBgInsteadOfColor = value.canvasColor == null + blurRadius = value.blurRadius + position = value.position + } + + else -> Unit + } + } + + val centerCropResizeType by remember( + canvasColor, + useBlurredBgInsteadOfColor, + blurRadius, + position + ) { + derivedStateOf { + ResizeType.CenterCrop( + canvasColor = canvasColor.toArgb() + .takeIf { !useBlurredBgInsteadOfColor }, + blurRadius = blurRadius, + position = position + ) + } + } + val fitResizeType by remember( + canvasColor, + useBlurredBgInsteadOfColor, + blurRadius, + position + ) { + derivedStateOf { + ResizeType.Fit( + canvasColor = canvasColor.toArgb() + .takeIf { !useBlurredBgInsteadOfColor }, + blurRadius = blurRadius, + position = position + ) + } + } + + val updateCompoundResizeType = { + if (value is ResizeType.CenterCrop) { + onValueChange(centerCropResizeType) + } else if (value is ResizeType.Fit) { + 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 || value is ResizeType.Fit, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + Column( + modifier = Modifier.padding(8.dp) + ) { + PositionSelector( + value = position, + onValueChange = { + position = it + updateCompoundResizeType() + }, + shape = ShapeDefaults.top, + color = MaterialTheme.colorScheme.surface + ) + Spacer(modifier = Modifier.height(4.dp)) + UseBlurredBackgroundToggle( + checked = useBlurredBgInsteadOfColor, + onCheckedChange = { + useBlurredBgInsteadOfColor = it + updateCompoundResizeType() + }, + 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 + updateCompoundResizeType() + }, + shape = ShapeDefaults.bottom + ) + } else { + ColorRowSelector( + modifier = Modifier + .container( + shape = ShapeDefaults.bottom, + color = MaterialTheme.colorScheme.surface + ), + value = canvasColor, + onValueChange = { + canvasColor = it + updateCompoundResizeType() + } + ) + } + } + } + } + } + + 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 +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/resize_group/components/BlurRadiusSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/resize_group/components/BlurRadiusSelector.kt new file mode 100644 index 0000000..a178bc0 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/resize_group/components/BlurRadiusSelector.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.BlurCircular +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.Outlined.BlurCircular, + valueRange = valueRange, + internalStateTransformation = { + it.roundToInt() + }, + onValueChange = { + onValueChange(it.roundToInt()) + }, + shape = shape, + containerColor = color + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/resize_group/components/UseBlurredBackgroundToggle.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/resize_group/components/UseBlurredBackgroundToggle.kt new file mode 100644 index 0000000..3b259a7 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/resize_group/components/UseBlurredBackgroundToggle.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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 com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.BlurLinear +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.Outlined.BlurLinear + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/AlphaSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/AlphaSelector.kt new file mode 100644 index 0000000..43fbb61 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/AlphaSelector.kt @@ -0,0 +1,64 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Opacity +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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/BlendingModeSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/BlendingModeSelector.kt new file mode 100644 index 0000000..1421ae8 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/BlendingModeSelector.kt @@ -0,0 +1,68 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Layers +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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ColorRowSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ColorRowSelector.kt new file mode 100644 index 0000000..9a32de3 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ColorRowSelector.kt @@ -0,0 +1,285 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Block +import com.t8rin.imagetoolbox.core.resources.icons.Palette +import com.t8rin.imagetoolbox.core.resources.icons.PushPin +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, + allowCustom: Boolean = true +) { + 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, + allowCustom = allowCustom + ) + 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 + ) + } + } + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/CropOverlayDraggableSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/CropOverlayDraggableSelector.kt new file mode 100644 index 0000000..01a23b0 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/CropOverlayDraggableSelector.kt @@ -0,0 +1,56 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.TouchApp +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 CropOverlayDraggableSelector( + 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.crop_overlay_draggable), + subtitle = stringResource(R.string.crop_overlay_draggable_sub), + checked = settingsState.cropOverlayDraggable, + onClick = { + scope.launch { + settingsInteractor.toggleCropOverlayDraggable() + } + }, + startIcon = Icons.Outlined.TouchApp + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/DataSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/DataSelector.kt new file mode 100644 index 0000000..a711518 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/DataSelector.kt @@ -0,0 +1,308 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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 androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.KeyboardArrowDown +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 }, + minSpanCount: Int = 1, + 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 + ), + chipHeight: Dp = 36.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(chipHeight - 6.dp), + modifier = Modifier + .heightIn( + max = animateDpAsState( + if (expanded) { + (chipHeight + 16.dp) * realSpanCount - 8.dp * (realSpanCount - 1) + } else { + (chipHeight + 16.dp) * minSpanCount - 8.dp * (minSpanCount - 1) + } + ).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, + chipHeight = chipHeight + ) + } + } + } 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, + chipHeight = chipHeight + ) + } + } + } + } +} + +@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, + chipHeight: Dp = 36.dp +) { + 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(chipHeight), + defaultMinSize = chipHeight + ) { + 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) + ) + } + } + itemContentText(item)?.let { text -> + Text( + text = text + ) + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/FontSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/FontSelector.kt new file mode 100644 index 0000000..132425d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/FontSelector.kt @@ -0,0 +1,165 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.KeyboardArrowDown +import com.t8rin.imagetoolbox.core.resources.icons.TextFields +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.Rounded.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 + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/HelperGridParamsSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/HelperGridParamsSelector.kt new file mode 100644 index 0000000..753b743 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/HelperGridParamsSelector.kt @@ -0,0 +1,180 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FormatLineSpacing +import com.t8rin.imagetoolbox.core.resources.icons.GridOn +import com.t8rin.imagetoolbox.core.resources.icons.LineWeight +import com.t8rin.imagetoolbox.core.resources.icons.Palette +import com.t8rin.imagetoolbox.core.resources.icons.TableRows +import com.t8rin.imagetoolbox.core.resources.icons.ViewColumn +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.Outlined.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.Palette, + title = stringResource(R.string.grid_color) + ) + EnhancedSliderItem( + value = value.linesWidth, + title = stringResource(R.string.line_width), + icon = Icons.Rounded.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 } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ImageExportPresetItems.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ImageExportPresetItems.kt new file mode 100644 index 0000000..7ed71d4 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ImageExportPresetItems.kt @@ -0,0 +1,421 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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.width +import androidx.compose.foundation.lazy.LazyItemScope +import androidx.compose.material3.Button +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.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.graphics.vector.ImageVector +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.image.model.ImageExportProfile +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.image.model.Quality +import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType +import com.t8rin.imagetoolbox.core.domain.image.model.title +import com.t8rin.imagetoolbox.core.domain.model.DomainAspectRatio +import com.t8rin.imagetoolbox.core.domain.model.MimeType +import com.t8rin.imagetoolbox.core.domain.utils.trimTrailingZero +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.DownloadFile +import com.t8rin.imagetoolbox.core.resources.icons.Loyalty +import com.t8rin.imagetoolbox.core.resources.icons.MoreVert +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonChecked +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.icons.Save +import com.t8rin.imagetoolbox.core.resources.icons.Share +import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview +import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator +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.hapticsClickable +import com.t8rin.imagetoolbox.core.ui.widget.image.aspectRatios +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.PreferenceItemOverload +import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField +import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem +import kotlinx.coroutines.launch +import kotlin.math.abs + +@Composable +internal fun LazyItemScope.ImagePresetItem( + index: Int, + profilesCount: Int, + item: ImageExportProfile, + selected: Boolean, + onApplyProfile: (ImageExportProfile) -> Unit, + onExportProfile: (ImageExportProfile, Uri) -> Unit, + onShareProfile: (ImageExportProfile) -> Unit, + onWantDelete: (ImageExportProfile) -> Unit +) { + val scope = rememberCoroutineScope() + val state = rememberRevealState() + val shape = ShapeDefaults.byIndex(index, profilesCount) + + SwipeToReveal( + state = state, + directions = setOf(RevealDirection.EndToStart), + 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(item) + } + ) { + 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 + ) + } + }, + swipeableContent = { + PreferenceItemOverload( + title = item.name, + subtitle = item.subtitle(), + onClick = { + onApplyProfile(item) + }, + drawStartIconContainer = false, + modifier = Modifier.fillMaxWidth(), + startIcon = { + Icon( + imageVector = if (selected) { + Icons.Rounded.RadioButtonChecked + } else { + Icons.Rounded.RadioButtonUnchecked + }, + contentDescription = null + ) + }, + endIcon = { + ImagePresetItemMenu( + preset = item, + onExportProfile = onExportProfile, + onShareProfile = onShareProfile, + modifier = Modifier.offset(x = 8.dp) + ) + }, + shape = shape, + containerColor = if (selected) { + MaterialTheme.colorScheme.primaryContainer + } else { + Color.Unspecified + }, + contentColor = if (selected) { + MaterialTheme.colorScheme.onPrimaryContainer + } else { + Color.Unspecified + } + ) + } + ) +} + +@Composable +internal fun AddImagePresetBlock( + preset: Preset, + imageInfo: ImageInfo, + onSave: (String) -> Unit, + modifier: Modifier = Modifier +) { + var name by rememberSaveable { mutableStateOf("") } + val canSave = name.isNotBlank() + + Column( + modifier = modifier + .container( + shape = ShapeDefaults.default, + resultPadding = 16.dp + ) + ) { + TitleItem( + icon = Icons.Outlined.Loyalty, + text = stringResource(R.string.save_export_profile), + subtitle = imageInfo.summary(preset), + modifier = Modifier + ) + + Spacer(Modifier.height(12.dp)) + + RoundedTextField( + value = name, + onValueChange = { name = it }, + label = stringResource(R.string.name), + endIcon = { + EnhancedIconButton( + onClick = { + onSave(name) + name = "" + }, + enabled = canSave + ) { + Icon( + imageVector = Icons.Rounded.Save, + contentDescription = null + ) + } + }, + singleLine = true + ) + } +} + +@Composable +private fun ImagePresetItemMenu( + preset: ImageExportProfile, + onExportProfile: (ImageExportProfile, Uri) -> Unit, + onShareProfile: (ImageExportProfile) -> Unit, + modifier: Modifier = Modifier +) { + var showMenu by rememberSaveable(preset.name) { mutableStateOf(false) } + val exportPicker = rememberFileCreator( + mimeType = MimeType.All, + onSuccess = { uri -> + onExportProfile(preset, uri) + } + ) + + Box( + modifier = modifier + ) { + EnhancedIconButton(onClick = { showMenu = true }) { + Icon( + imageVector = Icons.Rounded.MoreVert, + contentDescription = null + ) + } + EnhancedDropdownMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false }, + shape = ShapeDefaults.large + ) { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier + .width(IntrinsicSize.Max) + .padding(horizontal = 8.dp) + ) { + ImagePresetMenuAction( + title = R.string.export, + icon = Icons.Outlined.DownloadFile, + shape = ShapeDefaults.top, + color = MaterialTheme.colorScheme.primary, + onClick = { + showMenu = false + exportPicker.make(preset.fileName) + } + ) + ImagePresetMenuAction( + title = R.string.share, + icon = Icons.Outlined.Share, + shape = ShapeDefaults.bottom, + color = MaterialTheme.colorScheme.secondary, + onClick = { + showMenu = false + onShareProfile(preset) + } + ) + } + } + } +} + +@Composable +private fun ImagePresetMenuAction( + title: Int, + icon: ImageVector, + shape: Shape, + color: Color, + onClick: () -> Unit +) { + EnhancedButton( + modifier = Modifier.fillMaxWidth(), + onClick = onClick, + shape = shape, + containerColor = color + ) { + Icon( + imageVector = icon, + contentDescription = null + ) + Spacer(Modifier.width(8.dp)) + Text(stringResource(title)) + } +} + +@Composable +private fun ImageExportProfile.subtitle(): String = imageInfo.summary(preset) + +private val ImageExportProfile.fileName: String + get() = "${name.safeFileName()}.itpreset" + +@Composable +private fun ImageInfo.summary(preset: Preset): String = listOfNotNull( + preset.targetLabel(this), + imageFormatLabel(), + resizeType.title(), + imageScaleMode.titleOrNull() +).joinToString(SummarySeparator) + +@Composable +private fun Preset.targetLabel(imageInfo: ImageInfo): String = when (this) { + is Preset.AspectRatio -> buildString { + append(stringResource(R.string.aspect_ratio)) + append(" ") + append(ratio.aspectRatioLabel()) + if (isFit) { + append(", ") + append(stringResource(R.string.fit)) + } + } + + Preset.None -> imageInfo.sizeLabel() + is Preset.Percentage -> "$value%" + Preset.Telegram -> stringResource(R.string.telegram) +} + +private fun ImageInfo.sizeLabel(): String = "${width}x$height" + +private fun ImageInfo.imageFormatLabel(): String { + val quality = quality.qualityLabel( + showQuality = imageFormat.canChangeCompressionValue + ) + return listOfNotNull(imageFormat.title, quality).joinToString(" ") +} + +private fun Quality.qualityLabel(showQuality: Boolean): String? { + if (!showQuality) return null + + return when (this) { + is Quality.Avif -> "${qualityValue}%, E$effort" + is Quality.Base -> "${qualityValue}%" + is Quality.Heic -> "${qualityValue}%" + is Quality.Vvc -> "${qualityValue}%" + is Quality.Jxl -> "${qualityValue}%, E$effort" + is Quality.PngLossy -> "L$compressionLevel, ${maxColors}c" + is Quality.PngQuant -> "${quality}%, ${maxColors}c" + is Quality.Tiff -> "C$compressionScheme" + } +} + +@Composable +private fun ResizeType.title(): String = stringResource( + when (this) { + ResizeType.Explicit -> R.string.explicit + is ResizeType.Flexible -> R.string.flexible + is ResizeType.CenterCrop -> R.string.crop + is ResizeType.Fit -> R.string.fit + } +) + +@Composable +private fun ImageScaleMode.titleOrNull(): String? = + if (this == ImageScaleMode.Base || this == ImageScaleMode.NotPresent) { + null + } else { + stringResource(title) + } + +@Composable +private fun Float.aspectRatioLabel(): String { + val ratios = aspectRatios() + + val ratio = remember(ratios, this) { + ratios.filterIsInstance().firstOrNull { + abs(it.value - this) < 0.001f + } + } ?: return this.toString().trimTrailingZero() + + val width = ratio.widthProportion.toString() + .trimTrailingZero() + val height = ratio.heightProportion.toString() + .trimTrailingZero() + + return "$width:$height" +} + +private const val SummarySeparator = " / " + +private fun String.safeFileName(): String = trim() + .replace(Regex("""[^\w.-]+"""), "_") + .trim('_') + .ifBlank { "preset" } + +@Preview +@Composable +private fun Preview() = ImageToolboxThemeForPreview(true) { + Button(onClick = {}) { } + + AddImagePresetBlock( + preset = Preset.Telegram, + imageInfo = ImageInfo(), + onSave = {} + ) +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ImageExportProfileSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ImageExportProfileSelector.kt new file mode 100644 index 0000000..5b0f302 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ImageExportProfileSelector.kt @@ -0,0 +1,200 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +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.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.domain.image.model.ImageExportProfile +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 +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.Loyalty +import com.t8rin.imagetoolbox.core.resources.icons.UploadFile +import com.t8rin.imagetoolbox.core.ui.utils.ImageExportProfilesHolder +import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker +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.EnhancedModalBottomSheet +import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip +import com.t8rin.imagetoolbox.core.ui.widget.modifier.clearFocusOnTap +import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem + +@Composable +fun ImageExportProfileSelector( + profiles: List, + selectedProfile: ImageExportProfile?, + imageInfo: ImageInfo, + preset: Preset, + imageExportProfilesHolder: ImageExportProfilesHolder, + modifier: Modifier = Modifier +) { + var showSheet by rememberSaveable { mutableStateOf(false) } + var profileToDelete by remember { mutableStateOf(null) } + val importPicker = rememberFilePicker( + mimeType = MimeType.All, + onSuccess = imageExportProfilesHolder::importProfile + ) + + EnhancedChip( + selected = selectedProfile != null, + onClick = { showSheet = true }, + selectedColor = MaterialTheme.colorScheme.primary, + shape = MaterialTheme.shapes.medium, + modifier = modifier + ) { + Icon( + imageVector = Icons.Outlined.Loyalty, + contentDescription = stringResource(R.string.export_profiles) + ) + } + + EnhancedModalBottomSheet( + visible = showSheet, + onDismiss = { showSheet = it }, + title = { + TitleItem( + icon = Icons.Rounded.Loyalty, + text = stringResource(R.string.export_profiles) + ) + }, + confirmButton = { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + EnhancedIconButton( + onClick = importPicker::pickFile, + containerColor = MaterialTheme.colorScheme.secondaryContainer + ) { + Icon( + imageVector = Icons.Outlined.UploadFile, + contentDescription = stringResource(R.string.import_word) + ) + } + EnhancedButton( + onClick = { + showSheet = false + }, + containerColor = MaterialTheme.colorScheme.primaryContainer + ) { + Text(stringResource(R.string.close)) + } + } + } + ) { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(4.dp), + contentPadding = PaddingValues(12.dp), + modifier = Modifier + .animateContentSizeNoClip() + .clearFocusOnTap() + ) { + if (selectedProfile == null || profiles.isEmpty()) { + item("AddImagePresetBlock") { + AddImagePresetBlock( + preset = preset, + imageInfo = imageInfo, + onSave = imageExportProfilesHolder::saveProfile, + modifier = Modifier + .padding( + bottom = 4.dp + ) + .animateItem() + ) + } + } + itemsIndexed( + items = profiles, + key = { _, item -> item.name } + ) { index, item -> + ImagePresetItem( + index = index, + profilesCount = profiles.size, + item = item, + selected = item == selectedProfile, + onApplyProfile = imageExportProfilesHolder::applyProfile, + onExportProfile = imageExportProfilesHolder::exportProfile, + onShareProfile = imageExportProfilesHolder::shareProfile, + onWantDelete = { profileToDelete = it } + ) + } + } + } + + EnhancedAlertDialog( + visible = profileToDelete != null, + icon = { + Icon( + imageVector = Icons.Outlined.Delete, + contentDescription = null + ) + }, + title = { + Text(stringResource(R.string.delete_export_profile)) + }, + text = { + Text( + stringResource( + R.string.delete_export_profile_sub, + profileToDelete?.name ?: "" + ) + ) + }, + onDismissRequest = { profileToDelete = null }, + confirmButton = { + EnhancedButton( + onClick = { + profileToDelete?.let(imageExportProfilesHolder::deleteProfile) + profileToDelete = null + } + ) { + Text(stringResource(R.string.confirm)) + } + }, + dismissButton = { + EnhancedButton( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + onClick = { profileToDelete = null } + ) { + Text(stringResource(R.string.cancel)) + } + }, + placeAboveAll = true + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ImageFormatSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ImageFormatSelector.kt new file mode 100644 index 0000000..8b25bc1 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ImageFormatSelector.kt @@ -0,0 +1,379 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FileReplace +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.Outlined.FileReplace + ) + } + + 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 + } else { + allFormats + } + + 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, + 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 (enableItemsCardBackground && 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() + ) + if (enableItemsCardBackground) { + val previous = value + + previous?.let { + onValueChange( + ImageFormat.entries.run { + rightFrom(indexOf(value)) + } + ) + onValueChange(previous) + } + } + } + }, + allowAlpha = !isNonAlpha && enableBackgroundColorForAlphaFormats + ) + } + + if (enableItemsCardBackground) Spacer(Modifier.height(4.dp)) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ImageSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ImageSelector.kt new file mode 100644 index 0000000..24c5989 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ImageSelector.kt @@ -0,0 +1,180 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt +import com.t8rin.imagetoolbox.core.resources.icons.File +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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) + .compositeOverSafe(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.TwoTone.File, + contentDescription = null, + modifier = Modifier + .fillMaxSize() + .clip(CloverShape) + .background( + MaterialTheme.colorScheme.secondaryContainer + .copy(0.5f) + .compositeOverSafe(color) + ) + .padding(8.dp) + ) + } + ) + }, + endIcon = { + Icon( + imageVector = Icons.Rounded.MiniEdit, + contentDescription = stringResource(R.string.edit) + ) + }, + modifier = modifier, + shape = shape, + containerColor = color, + drawStartIconContainer = false + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/MagnifierEnabledSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/MagnifierEnabledSelector.kt new file mode 100644 index 0000000..23baeae --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/MagnifierEnabledSelector.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.ZoomIn +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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/PositionSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/PositionSelector.kt new file mode 100644 index 0000000..af411cb --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/PositionSelector.kt @@ -0,0 +1,69 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Place +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 + ) +} + +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) + } \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/PresetSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/PresetSelector.kt new file mode 100644 index 0000000..91bc062 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/PresetSelector.kt @@ -0,0 +1,481 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.collectAsState +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.toArgb +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.ImageExportProfile +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.DomainAspectRatio +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AspectRatio +import com.t8rin.imagetoolbox.core.resources.icons.EditAlt +import com.t8rin.imagetoolbox.core.resources.icons.FitScreen +import com.t8rin.imagetoolbox.core.resources.icons.Info +import com.t8rin.imagetoolbox.core.resources.icons.Telegram +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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.utils.ImageExportProfilesHolder +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, + imageExportProfilesHolder: ImageExportProfilesHolder? = null, + imageInfo: ImageInfo? = null +) { + 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() + val imagePresets = + imageExportProfilesHolder?.imageProfiles?.collectAsState()?.value ?: emptyList() + val currentBackgroundColorForNoAlphaFormats = settingsState + .backgroundForNoAlphaImageFormats + .toArgb() + val selectedProfile by remember( + imagePresets, + imageInfo, + value, + imageExportProfilesHolder?.currentProfileKeepExif, + currentBackgroundColorForNoAlphaFormats + ) { + derivedStateOf { + imageInfo?.let { currentImageInfo -> + imagePresets.firstOrNull { + it.matchesCurrentPreset( + imageInfo = currentImageInfo, + preset = value, + keepExif = imageExportProfilesHolder?.currentProfileKeepExif, + backgroundColorForNoAlphaFormats = currentBackgroundColorForNoAlphaFormats + ) + } + } + } + } + val selectedChipColor = if (selectedProfile == null) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.primaryContainer + } + + 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 = selectedChipColor, + 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 = selectedChipColor, + shape = MaterialTheme.shapes.medium + ) { + Icon( + imageVector = Icons.Outlined.AspectRatio, + contentDescription = stringResource(R.string.aspect_ratio) + ) + } + } + } + if (imageInfo != null && imageExportProfilesHolder != null) { + item(key = "image_presets") { + ImageExportProfileSelector( + profiles = imagePresets, + selectedProfile = selectedProfile, + imageInfo = imageInfo, + preset = value, + imageExportProfilesHolder = imageExportProfilesHolder + ) + } + } + items( + items = data, + key = { it } + ) { + val selected = value.value() == it + EnhancedChip( + selected = selected, + onClick = { onValueChange(Preset.Percentage(it)) }, + selectedColor = selectedChipColor, + 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) + .compositeOverSafe( + 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)) + } + ) +} + +private fun ImageExportProfile.matchesCurrentPreset( + imageInfo: ImageInfo, + preset: Preset, + keepExif: Boolean?, + backgroundColorForNoAlphaFormats: Int? +): Boolean { + if (this.preset != preset) return false + if (keepExif != null && this.keepExif != null && this.keepExif != keepExif) return false + if ( + this.backgroundColorForNoAlphaFormats != null && + backgroundColorForNoAlphaFormats != null && + this.backgroundColorForNoAlphaFormats != backgroundColorForNoAlphaFormats + ) return false + + return this.imageInfo.comparableFor(preset) == imageInfo.comparableFor(preset) +} + +private fun ImageInfo.comparableFor( + preset: Preset +): ImageInfo = copy( + width = width.takeIf { preset.isEmpty() } ?: 0, + height = height.takeIf { preset.isEmpty() } ?: 0, + sizeInBytes = 0, + originalUri = null +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/QualitySelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/QualitySelector.kt new file mode 100644 index 0000000..8e7214d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/QualitySelector.kt @@ -0,0 +1,671 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Adaptive +import androidx.compose.foundation.lazy.staggeredgrid.items +import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState +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.AvifChromaSubsampling +import com.t8rin.imagetoolbox.core.domain.image.model.HeicChromaSubsampling +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.domain.image.model.VvcBitDepth +import com.t8rin.imagetoolbox.core.domain.image.model.VvcChroma +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Palette +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.resources.icons.Speed +import com.t8rin.imagetoolbox.core.resources.icons.Stream +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) + } + } + } + + AnimatedContent( + targetState = actualImageFormat, + contentKey = { it.qualitySelectorContentKey }, + modifier = modifier.container(shape) + ) { actualImageFormat -> + Column { + 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.Heic -> quality.qualityValue + is Quality.Vvc -> quality.qualityValue + is Quality.Tiff -> quality.qualityValue + is Quality.PngQuant -> 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.Heic -> quality.copy(qualityValue = it.toInt()) + is Quality.Vvc -> quality.copy(qualityValue = it.toInt()) + is Quality.Tiff -> quality.copy(compressionScheme = it.toInt()) + is Quality.PngQuant -> quality.copy(quality = 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.Heic -> quality.copy(qualityValue = it.toInt()) + is Quality.Vvc -> quality.copy(qualityValue = it.toInt()) + is Quality.Tiff -> quality.copy(compressionScheme = it.toInt()) + is Quality.PngQuant -> quality.copy(quality = 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) + ) + } + } + } + when (actualImageFormat) { + ImageFormat.Heic.VvcLossless, + ImageFormat.Heic.VvcLossy -> { + val vvcQuality = quality as? Quality.Vvc + Column { + QualityOptionSelector( + entries = VvcChroma.entries, + value = vvcQuality?.chroma ?: VvcChroma.YUV_420, + title = stringResource(R.string.chroma_subsampling), + itemTitle = { it.title }, + onValueChange = { + vvcQuality?.copy(chroma = it) + ?.coerceIn(actualImageFormat) + ?.let(onQualityChange) + }, + inactiveButtonColor = inactiveButtonColor, + activeButtonColor = activeButtonColor + ) + QualityOptionSelector( + entries = VvcBitDepth.entries, + value = vvcQuality?.bitDepth ?: VvcBitDepth.EIGHT, + title = stringResource(R.string.bit_depth), + itemTitle = { it.title }, + onValueChange = { + vvcQuality?.copy(bitDepth = it) + ?.coerceIn(actualImageFormat) + ?.let(onQualityChange) + }, + inactiveButtonColor = inactiveButtonColor, + activeButtonColor = activeButtonColor + ) + } + } + + is ImageFormat.Heic -> { + val heicQuality = quality as? Quality.Heic + Column { + QualityOptionSelector( + entries = HeicChromaSubsampling.entries, + value = heicQuality?.chromaSubsampling + ?: HeicChromaSubsampling.Yuv420, + title = stringResource(R.string.chroma_subsampling), + itemTitle = { it.title }, + onValueChange = { + heicQuality?.copy(chromaSubsampling = it) + ?.coerceIn(actualImageFormat) + ?.let(onQualityChange) + }, + inactiveButtonColor = inactiveButtonColor, + activeButtonColor = activeButtonColor + ) + } + } + + is ImageFormat.Avif -> { + val avifQuality = quality as? Quality.Avif + Column { + QualityOptionSelector( + entries = AvifChromaSubsampling.entries, + value = avifQuality?.chromaSubsampling + ?: AvifChromaSubsampling.Auto, + title = stringResource(R.string.chroma_subsampling), + itemTitle = { it.title }, + onValueChange = { + avifQuality?.copy(chromaSubsampling = it) + ?.coerceIn(actualImageFormat) + ?.let(onQualityChange) + }, + inactiveButtonColor = inactiveButtonColor, + activeButtonColor = activeButtonColor + ) + } + } + + 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 + ) + } + } + + 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.Palette, + valueRange = 2f..1024f, + internalStateTransformation = { + it.roundToInt().coerceIn(2..1024).toFloat() + }, + onValueChange = { + pngLossyQuality?.copy( + maxColors = it.roundToInt() + )?.coerceIn(actualImageFormat)?.let(onQualityChange) + }, + behaveAsContainer = false + ) + } + + is ImageFormat.Tiff, 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 = 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 + ) + } + } + } + } + } + } + + is ImageFormat.Png.ImageQuant -> { + val pngQuantQuality = quality as? Quality.PngQuant + Column { + EnhancedSliderItem( + value = pngQuantQuality?.speed ?: 0, + title = stringResource(R.string.speed), + icon = Icons.Outlined.Speed, + valueRange = 1f..10f, + steps = 8, + internalStateTransformation = { + it.roundToInt().coerceIn(0..10).toFloat() + }, + onValueChange = { + pngQuantQuality?.copy( + speed = it.roundToInt() + )?.coerceIn(actualImageFormat)?.let(onQualityChange) + }, + behaveAsContainer = false + ) { + Text( + text = stringResource( + R.string.speed_sub, + 1, 10 + ), + 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) + ) + } + EnhancedSliderItem( + value = pngQuantQuality?.maxColors ?: 0, + title = stringResource(R.string.max_colors_count), + icon = Icons.Outlined.Palette, + valueRange = 2f..1024f, + internalStateTransformation = { + it.roundToInt().coerceIn(2..1024).toFloat() + }, + onValueChange = { + pngQuantQuality?.copy( + maxColors = it.roundToInt() + )?.coerceIn(actualImageFormat)?.let(onQualityChange) + }, + behaveAsContainer = false + ) + } + } + + else -> Unit + } + } + } + } +} + +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 ImageFormat.qualitySelectorContentKey: QualitySelectorContentKey + get() = QualitySelectorContentKey( + compressionTypes = compressionTypes, + additionalControls = when (this) { + ImageFormat.Heic.VvcLossless, + ImageFormat.Heic.VvcLossy -> AdditionalQualityControls.Vvc + is ImageFormat.Heic -> AdditionalQualityControls.Heic + is ImageFormat.Avif -> AdditionalQualityControls.Avif + is ImageFormat.Jxl -> AdditionalQualityControls.Jxl + ImageFormat.Png.Lossy -> AdditionalQualityControls.PngLossy + ImageFormat.Tiff, + ImageFormat.Tif -> AdditionalQualityControls.Tiff + + ImageFormat.Png.ImageQuant -> AdditionalQualityControls.PngQuant + else -> AdditionalQualityControls.None + } + ) + +private data class QualitySelectorContentKey( + val compressionTypes: List, + val additionalControls: AdditionalQualityControls +) + +private enum class AdditionalQualityControls { + None, + Vvc, + Heic, + Avif, + Jxl, + PngLossy, + Tiff, + PngQuant +} + +@Composable +private fun QualityOptionSelector( + entries: List, + value: T, + title: String, + itemTitle: @Composable (T) -> String, + onValueChange: (T) -> Unit, + inactiveButtonColor: Color, + activeButtonColor: Color +) { + EnhancedButtonGroup( + entries = entries, + value = value, + itemContent = { Text(itemTitle(it)) }, + modifier = Modifier + .fillMaxWidth() + .padding(4.dp) + .container( + shape = ShapeDefaults.large, + color = MaterialTheme.colorScheme.surface + ) + .padding(4.dp), + title = title, + onValueChange = onValueChange, + inactiveButtonColor = inactiveButtonColor, + activeButtonColor = activeButtonColor + ) +} + +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) + } + +private val AvifChromaSubsampling.title: String + @Composable + get() = when (this) { + AvifChromaSubsampling.Auto -> stringResource(R.string.auto) + AvifChromaSubsampling.Yuv420 -> "4:2:0" + AvifChromaSubsampling.Yuv422 -> "4:2:2" + AvifChromaSubsampling.Yuv444 -> "4:4:4" + AvifChromaSubsampling.Yuv400 -> stringResource(R.string.monochrome) + AvifChromaSubsampling.Lossless -> stringResource(R.string.lossless) + } + +private val HeicChromaSubsampling.title: String + get() = when (this) { + HeicChromaSubsampling.Yuv420 -> "4:2:0" + HeicChromaSubsampling.Yuv422 -> "4:2:2" + HeicChromaSubsampling.Yuv444 -> "4:4:4" + } + +private val VvcChroma.title: String + @Composable + get() = when (this) { + VvcChroma.MONOCHROME -> stringResource(R.string.monochrome) + VvcChroma.YUV_420 -> "4:2:0" + VvcChroma.YUV_422 -> "4:2:2" + VvcChroma.YUV_444 -> "4:4:4" + } + +private val VvcBitDepth.title: String + @Composable + get() = stringResource( + R.string.bit_depth_value, + when (this) { + VvcBitDepth.EIGHT -> 8 + VvcBitDepth.TEN -> 10 + VvcBitDepth.TWELVE -> 12 + } + ) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ScaleModeSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ScaleModeSelector.kt new file mode 100644 index 0000000..b4f563c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ScaleModeSelector.kt @@ -0,0 +1,421 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Palette +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.Palette, + 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 + } \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/CalculatorDialog.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/CalculatorDialog.kt new file mode 100644 index 0000000..21c833c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/CalculatorDialog.kt @@ -0,0 +1,124 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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.github.keelar.exprk.Expressions +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Calculate +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)) + } + ) + } + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/ColorCopyFormatSelectionDialog.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/ColorCopyFormatSelectionDialog.kt new file mode 100644 index 0000000..68316cc --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/ColorCopyFormatSelectionDialog.kt @@ -0,0 +1,243 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.Box +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.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +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.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.t8rin.colors.parser.ColorWithName +import com.t8rin.colors.util.ColorUtil +import com.t8rin.colors.util.ColorUtil.hex +import com.t8rin.imagetoolbox.core.domain.utils.trimTrailingZero +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.HashTag +import com.t8rin.imagetoolbox.core.resources.icons.ShortText +import com.t8rin.imagetoolbox.core.resources.icons.TagText +import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard +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.container +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 +import kotlinx.coroutines.delay +import kotlin.math.pow +import kotlin.math.roundToInt + +@Composable +fun ColorCopyFormatSelectionDialog( + target: ColorWithName?, + onDismiss: () -> Unit +) { + val colorWithName by produceState( + target ?: ColorWithName( + color = Color.Transparent, + name = "" + ), + key1 = target + ) { + if (target == null) { + delay(600) + value = target ?: ColorWithName( + color = Color.Transparent, + name = "" + ) + } else { + value = target + } + } + + ColorCopyFormatSelectionDialog( + visible = target != null, + onDismiss = onDismiss, + color = colorWithName.color, + colorName = colorWithName.name + ) +} + +@Composable +fun ColorCopyFormatSelectionDialog( + visible: Boolean, + onDismiss: () -> Unit, + color: Color, + colorName: String +) { + EnhancedAlertDialog( + visible = visible, + onDismissRequest = onDismiss, + placeAboveAll = true, + icon = { + Box( + modifier = Modifier + .size(24.dp) + .container( + shape = ShapeDefaults.circle, + color = color, + resultPadding = 0.dp + ) + ) + }, + title = { + Text(stringResource(R.string.copy_color_as)) + }, + text = { + val formats = remember { ColorCopyFormat.entries } + val scrollState = rememberScrollState() + + Column( + modifier = Modifier + .fillMaxWidth() + .fadingEdges( + scrollableState = scrollState, + isVertical = true + ) + .enhancedVerticalScroll(scrollState), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + formats.forEachIndexed { index, format -> + val textToCopy = remember(format, color, colorName) { + format.textToCopy( + color = color, + colorName = colorName + ) + } + + PreferenceItem( + title = format.title(), + subtitle = textToCopy, + startIcon = format.icon(), + shape = ShapeDefaults.byIndex( + index = index, + size = formats.size + ), + titleFontStyle = PreferenceItemDefaults.TitleFontStyle.copy( + textAlign = TextAlign.Start + ), + onClick = { + Clipboard.copy( + text = textToCopy, + message = R.string.color_copied + ) + onDismiss() + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + ) + } + } + }, + confirmButton = { + EnhancedButton( + onClick = onDismiss, + containerColor = MaterialTheme.colorScheme.secondaryContainer + ) { + Text(stringResource(R.string.close)) + } + } + ) +} + +private enum class ColorCopyFormat { + Hex, + Name, + HexAndName, + RGB, + RGBA, + HSL, + HSV, + CMYK; + + @Composable + fun title(): String = when (this) { + Hex -> "HEX" + Name -> stringResource(R.string.name) + HexAndName -> "HEX + ${stringResource(R.string.name)}" + RGB -> "RGB" + RGBA -> "RGBA" + HSL -> "HSL" + HSV -> "HSV" + CMYK -> "CMYK" + } + + fun icon(): ImageVector = when (this) { + Hex -> Icons.Rounded.HashTag + Name -> Icons.Outlined.TagText + HexAndName, + RGB, + RGBA, + HSL, + HSV, + CMYK -> Icons.Rounded.ShortText + } + + fun textToCopy( + color: Color, + colorName: String + ): String { + val rgb = ColorUtil.colorToRGBArray(color) + val hsl = ColorUtil.colorToHSL(color) + val hsv = ColorUtil.colorToHSV(color) + val alpha = color.alpha.round(2) + + return when (this) { + Hex -> color.hex() + Name -> colorName + HexAndName -> "$colorName - ${color.hex()}" + RGB -> "rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})" + RGBA -> "rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, $alpha)" + HSL -> "hsl(${hsl[0].roundToInt()}, ${(hsl[1] * 100).roundToInt()}%, ${(hsl[2] * 100).roundToInt()}%)" + HSV -> "hsv(${hsv[0].roundToInt()}, ${(hsv[1] * 100).roundToInt()}%, ${(hsv[2] * 100).roundToInt()}%)" + CMYK -> color.toCmykString() + } + } +} + +private fun Color.toCmykString(): String { + val black = 1f - maxOf(red, green, blue) + val denominator = 1f - black + val cyan = if (denominator == 0f) 0f else (1f - red - black) / denominator + val magenta = if (denominator == 0f) 0f else (1f - green - black) / denominator + val yellow = if (denominator == 0f) 0f else (1f - blue - black) / denominator + + return "cmyk(${(cyan * 100).roundToInt()}%, ${(magenta * 100).roundToInt()}%, ${(yellow * 100).roundToInt()}%, ${(black * 100).roundToInt()}%)" +} + +private fun Float.round(digits: Int): String { + val factor = 10f.pow(digits) + return ((this * factor).roundToInt() / factor).toString().trimTrailingZero() +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/ExitWithoutSavingDialog.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/ExitWithoutSavingDialog.kt new file mode 100644 index 0000000..35306bc --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/ExitWithoutSavingDialog.kt @@ -0,0 +1,105 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.Save +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 + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/LoadingDialog.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/LoadingDialog.kt new file mode 100644 index 0000000..73afc6d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/LoadingDialog.kt @@ -0,0 +1,221 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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() + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/OneTimeImagePickingDialog.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/OneTimeImagePickingDialog.kt new file mode 100644 index 0000000..1ca5b78 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/OneTimeImagePickingDialog.kt @@ -0,0 +1,165 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ImageSearch +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonChecked +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 + ) + ) + } + } + } + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/OneTimeSaveLocationSelectionDialog.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/OneTimeSaveLocationSelectionDialog.kt new file mode 100644 index 0000000..dfe7e64 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/OneTimeSaveLocationSelectionDialog.kt @@ -0,0 +1,404 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.CreateNewFolder +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.FileRename +import com.t8rin.imagetoolbox.core.resources.icons.FileReplace +import com.t8rin.imagetoolbox.core.resources.icons.Folder +import com.t8rin.imagetoolbox.core.resources.icons.FolderOpen +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonChecked +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.icons.SaveAs +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 canChooseSaveLocation = + settingsState.filenameBehavior !is FilenameBehavior.Overwrite && + !settingsState.saveToOriginalFolder + + 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 = canChooseSaveLocation, + 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 && canChooseSaveLocation, + 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 = canChooseSaveLocation, + 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.FileRename, + shape = ShapeDefaults.default, + titleFontStyle = PreferenceItemDefaults.TitleFontStyleSmall, + enabled = canChooseSaveLocation, + 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.deleteOriginalsAfterSave && !settingsState.saveToOriginalFolder && + (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 + ) + + PreferenceRowSwitch( + title = stringResource(id = R.string.save_to_original_folder), + subtitle = stringResource(id = R.string.save_to_original_folder_sub), + startIcon = Icons.Outlined.FolderOpen, + enabled = settingsState.filenameBehavior !is FilenameBehavior.Overwrite, + shape = ShapeDefaults.default, + titleFontStyle = PreferenceItemDefaults.TitleFontStyleSmall, + checked = settingsState.saveToOriginalFolder, + onClick = { + scope.launch { settingsInteractor.toggleSaveToOriginalFolder() } + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 4.dp, vertical = 2.dp), + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + + PreferenceRowSwitch( + title = stringResource(id = R.string.delete_originals_after_save), + subtitle = stringResource(id = R.string.delete_originals_after_save_sub), + startIcon = Icons.Outlined.Delete, + enabled = settingsState.filenameBehavior !is FilenameBehavior.Overwrite, + shape = ShapeDefaults.default, + titleFontStyle = PreferenceItemDefaults.TitleFontStyleSmall, + checked = settingsState.deleteOriginalsAfterSave, + onClick = { + scope.launch { settingsInteractor.toggleDeleteOriginalsAfterSave() } + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 4.dp, vertical = 2.dp), + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + } + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/PasswordRequestDialog.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/PasswordRequestDialog.kt new file mode 100644 index 0000000..51f9cab --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/PasswordRequestDialog.kt @@ -0,0 +1,140 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Password +import com.t8rin.imagetoolbox.core.resources.icons.Shield +import com.t8rin.imagetoolbox.core.resources.icons.Visibility +import com.t8rin.imagetoolbox.core.resources.icons.VisibilityOff +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.Rounded.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)) + } + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/ResetDialog.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/ResetDialog.kt new file mode 100644 index 0000000..a6c736d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/ResetDialog.kt @@ -0,0 +1,88 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Done +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, + dismissText: String? = null, + 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() + if (dismissText == null) { + AppToastHost.showToast( + message = getString(R.string.values_reset), + icon = Icons.Rounded.Done + ) + } + } + ) { + Text(dismissText ?: stringResource(R.string.reset)) + } + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/WantCancelLoadingDialog.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/WantCancelLoadingDialog.kt new file mode 100644 index 0000000..45cf18e --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/WantCancelLoadingDialog.kt @@ -0,0 +1,78 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedAlertDialog.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedAlertDialog.kt new file mode 100644 index 0000000..e2d0fd4 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedAlertDialog.kt @@ -0,0 +1,390 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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( + colorScheme = MaterialTheme.colorScheme, + shape = shape, + autoElevation = animateDpAsState( + if (LocalSettingsState.current.drawContainerShadows) 16.dp + else 0.dp + ).value + ) + .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 + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedBadge.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedBadge.kt new file mode 100644 index 0000000..15b9e1d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedBadge.kt @@ -0,0 +1,73 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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() + } + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedButton.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedButton.kt new file mode 100644 index 0000000..5fe34c0 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedButton.kt @@ -0,0 +1,172 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.resources.utils.animation.animateColorAsState +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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedButtonGroup.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedButtonGroup.kt new file mode 100644 index 0000000..666cb65 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedButtonGroup.kt @@ -0,0 +1,533 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.Animatable +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.PressInteraction +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.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.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.runtime.withFrameMillis +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.layout +import androidx.compose.ui.layout.onSizeChanged +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 androidx.compose.ui.zIndex +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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.animation.lessSpringySpec +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 +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlin.math.roundToInt + +@Composable +fun EnhancedButtonGroup( + modifier: Modifier = defaultModifier, + enabled: Boolean = true, + items: List, + selectedIndex: Int, + title: String? = null, + onIndexChange: (Int) -> Unit, + isScrollable: Boolean = true, + contentPadding: PaddingValues = DefaultContentPadding, + inactiveButtonColor: Color = MaterialTheme.colorScheme.surface, + activeButtonColor: Color = MaterialTheme.colorScheme.secondary, +) { + 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) + ) + } + }, + isScrollable = isScrollable, + contentPadding = contentPadding, + inactiveButtonColor = inactiveButtonColor, + activeButtonColor = activeButtonColor, + ) +} + +@Composable +fun EnhancedButtonGroup( + modifier: Modifier = defaultModifier, + enabled: Boolean, + items: List, + selectedIndex: Int, + title: @Composable RowScope.() -> Unit = {}, + onIndexChange: (Int) -> Unit, + isScrollable: Boolean = true, + contentPadding: PaddingValues = DefaultContentPadding, + inactiveButtonColor: Color = MaterialTheme.colorScheme.surface, + activeButtonColor: Color = MaterialTheme.colorScheme.secondary, +) { + 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, + isScrollable = isScrollable, + contentPadding = contentPadding, + inactiveButtonColor = inactiveButtonColor, + activeButtonColor = activeButtonColor, + ) +} + +@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) + .compositeOverSafe(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() + + LocalMinimumInteractiveComponentSize.ProvidesValue(Dp.Unspecified) { + val animationSpec = remember { lessSpringySpec() } + + 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 interactionSource = remember { MutableInteractionSource() } + + val activeContainerColor = if (enabled) { + activeButtonColor + } else { + MaterialTheme.colorScheme.surfaceContainer + } + + val selected = index in selectedIndices + + val disableSmoothness = + !selected && index == 0 || index == itemCount - 1 + + 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() + ) + }, + modifier = Modifier + .enlargeOnPress( + isScrollable = isScrollable, + interactionSource = interactionSource, + animationSpec = animationSpec, + rowScope = this + ) + .zIndex(if (selected) 1f else 0f), + interactionSource = interactionSource + ) { + if (!isScrollable) { + Row( + modifier = Modifier.marquee() + ) { + itemContent(index) + } + } else { + itemContent(index) + } + } + } + } + } + } + } + } +} + +@Composable +private fun Modifier.enlargeOnPress( + isScrollable: Boolean, + interactionSource: MutableInteractionSource, + animationSpec: AnimationSpec, + rowScope: RowScope, + factor: Float = 0.2f +): Modifier { + val pressedAnimatable = remember { Animatable(0f) } + + LaunchedEffect(interactionSource) { + val pressInteractions = mutableListOf() + + interactionSource.interactions + .map { interaction -> + when (interaction) { + is PressInteraction.Press -> pressInteractions.add(interaction) + is PressInteraction.Release -> pressInteractions.remove(interaction.press) + is PressInteraction.Cancel -> pressInteractions.remove(interaction.press) + } + pressInteractions.isNotEmpty() + } + .distinctUntilChanged() + .collectLatest { pressed -> + if (pressed) { + launch { pressedAnimatable.animateTo(1f, animationSpec) } + } else { + delay(150) + + val initialTimeMillis = withFrameMillis { it } + while (!(pressedAnimatable.value > 0.75f)) { + val timeMillis = withFrameMillis { it } + if (timeMillis - initialTimeMillis > 1_000L) break + } + pressedAnimatable.animateTo(0f, animationSpec) + } + } + } + + return this then if (isScrollable) { + var buttonWidth by remember { mutableIntStateOf(0) } + + Modifier + .onSizeChanged { + if (buttonWidth == 0) buttonWidth = it.width + } + .layout { measurable, constraints -> + val mod = + (buttonWidth * pressedAnimatable.value * factor).roundToInt() + .coerceAtLeast(0) + + val placeable = measurable.measure( + if (buttonWidth == 0) { + constraints + } else { + constraints.copy( + minWidth = buttonWidth + mod, + maxWidth = buttonWidth + mod + ) + } + ) + + layout( + placeable.measuredWidth, + placeable.measuredHeight + ) { + placeable.place(0, 0) + } + } + } else { + with(rowScope) { + Modifier.weight(1f + pressedAnimatable.value * factor) + } + } +} + +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) diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedCheckbox.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedCheckbox.kt new file mode 100644 index 0000000..7baa5cb --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedCheckbox.kt @@ -0,0 +1,69 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedChip.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedChip.kt new file mode 100644 index 0000000..bb35b31 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedChip.kt @@ -0,0 +1,121 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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) + .compositeOverSafe(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() + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedCircularProgressIndicator.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedCircularProgressIndicator.kt new file mode 100644 index 0000000..c1b8ef8 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedCircularProgressIndicator.kt @@ -0,0 +1,236 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.Spacer +import androidx.compose.foundation.layout.size +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 +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 + .then( + if (onCancel != null) { + Modifier.pointerInput(onCancel) { + detectTapGestures { + onCancel() + } + } + } else { + Modifier + } + ), + contentAlignment = Alignment.Center + ) { + if (onCancel != null) { + Icon( + imageVector = Icons.Outlined.CancelSmall, + contentDescription = null, + tint = cancelIconColor, + modifier = Modifier.size(18.dp) + ) + } else { + Spacer(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() +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedDatePickerDialog.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedDatePickerDialog.kt new file mode 100644 index 0000000..8d579cb --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedDatePickerDialog.kt @@ -0,0 +1,541 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.animation.core.animateDpAsState +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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Keyboard +import com.t8rin.imagetoolbox.core.resources.icons.Schedule +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( + colorScheme = MaterialTheme.colorScheme, + shape = shape, + autoElevation = animateDpAsState( + if (LocalSettingsState.current.drawContainerShadows) 16.dp + else 0.dp + ).value + ) + .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 timePickerContentY = landContentTopPadding + remainingSpace / 2 + contentPlaceable.place(x = contentPadding, y = timePickerContentY) + val actionsY = + timePickerContentY + contentPlaceable.height + landContentActionsPadding - + adjustedActionsBottomPadding + remainingSpace / 2 + actionsPlaceable.place(x = contentPadding, y = actionsY) + } else { + titlePlaceable.place(x = landTitleTopPadding, 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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedDropdownMenu.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedDropdownMenu.kt new file mode 100644 index 0000000..fa3fc98 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedDropdownMenu.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedFlingBehavior.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedFlingBehavior.kt new file mode 100644 index 0000000..4fb9e48 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedFlingBehavior.kt @@ -0,0 +1,104 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 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() +} + +@Composable +fun Modifier.enhancedVerticalScroll( + state: ScrollState, + enabled: Boolean = true, + flingBehavior: FlingBehavior? = null, + reverseScrolling: Boolean = false, +) = verticalScroll( + state = state, + enabled = enabled, + flingBehavior = flingBehavior ?: enhancedFlingBehavior(), + reverseScrolling = reverseScrolling, +) + +@Composable +fun Modifier.enhancedVerticalScroll( + state: ScrollState, + overscrollEffect: OverscrollEffect?, + enabled: Boolean = true, + flingBehavior: FlingBehavior? = null, + reverseScrolling: Boolean = false, +) = verticalScroll( + state = state, + enabled = enabled, + flingBehavior = flingBehavior ?: enhancedFlingBehavior(), + reverseScrolling = reverseScrolling, + overscrollEffect = overscrollEffect +) + +@Composable +fun Modifier.enhancedHorizontalScroll( + state: ScrollState, + enabled: Boolean = true, + flingBehavior: FlingBehavior? = null, + reverseScrolling: Boolean = false, +) = horizontalScroll( + state = state, + enabled = enabled, + flingBehavior = flingBehavior ?: enhancedFlingBehavior(), + reverseScrolling = reverseScrolling, +) + +@Composable +fun Modifier.enhancedHorizontalScroll( + state: ScrollState, + overscrollEffect: OverscrollEffect?, + enabled: Boolean = true, + flingBehavior: FlingBehavior? = null, + reverseScrolling: Boolean = false, +) = horizontalScroll( + state = state, + enabled = enabled, + flingBehavior = flingBehavior ?: enhancedFlingBehavior(), + reverseScrolling = reverseScrolling, + overscrollEffect = overscrollEffect +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedFloatingActionButton.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedFloatingActionButton.kt new file mode 100644 index 0000000..9dfc3e7 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedFloatingActionButton.kt @@ -0,0 +1,224 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.resources.utils.animation.animateColorAsState +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.autoElevatedBorder +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) + .autoElevatedBorder( + 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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedHapticFeedback.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedHapticFeedback.kt new file mode 100644 index 0000000..c182083 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedHapticFeedback.kt @@ -0,0 +1,245 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.imagetoolbox.core.utils.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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedIconButton.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedIconButton.kt new file mode 100644 index 0000000..c485bcf --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedIconButton.kt @@ -0,0 +1,164 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.resources.utils.animation.animateColorAsState +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 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 = animateColorAsState( + if (enabled) contentColor + else contentColor.copy(alpha = DisabledAlpha) + ).value, + containerColor = animateColorAsState( + if (enabled) containerColor else Color.Transparent + ).value + ), + 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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedLoadingIndicator.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedLoadingIndicator.kt new file mode 100644 index 0000000..9ee69bd --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedLoadingIndicator.kt @@ -0,0 +1,199 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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() + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedModalBottomSheet.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedModalBottomSheet.kt new file mode 100644 index 0000000..df2a780 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedModalBottomSheet.kt @@ -0,0 +1,415 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 = @Suppress("DEPRECATION") 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 + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedModalSheetDragHandle.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedModalSheetDragHandle.kt new file mode 100644 index 0000000..3325bde --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedModalSheetDragHandle.kt @@ -0,0 +1,149 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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).compositeOverSafe( + 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 + ) + } + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedNavigationBarItem.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedNavigationBarItem.kt new file mode 100644 index 0000000..7110090 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedNavigationBarItem.kt @@ -0,0 +1,456 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.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.resources.utils.animation.animateColorAsState +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 + } \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedNavigationRailItem.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedNavigationRailItem.kt new file mode 100644 index 0000000..a907075 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedNavigationRailItem.kt @@ -0,0 +1,627 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.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.resources.utils.animation.animateColorAsState +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()) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedRadioButton.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedRadioButton.kt new file mode 100644 index 0000000..f88f114 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedRadioButton.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedRangeSliderItem.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedRangeSliderItem.kt new file mode 100644 index 0000000..04e070e --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedRangeSliderItem.kt @@ -0,0 +1,419 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedSlider.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedSlider.kt new file mode 100644 index 0000000..9e17cb7 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedSlider.kt @@ -0,0 +1,285 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.settings.domain.model.SliderType +import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState +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 +import com.t8rin.imagetoolbox.core.ui.widget.sliders.fancyThumbShape + +@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 = fancyThumbShape(), + 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 = fancyThumbShape(), + 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 + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedSliderItem.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedSliderItem.kt new file mode 100644 index 0000000..621158a --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedSliderItem.kt @@ -0,0 +1,370 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 resetValue = rememberSaveable { value.toFloat() } + + val reset: () -> Unit = { + internalState.value = internalStateTransformation(resetValue) + onValueChange(resetValue) + onValueChangeFinished?.invoke(resetValue) + } + + 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, + onLongClick = reset + ) + } + } 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, + onLongClick = reset + ) + } + 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) + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedSwitch.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedSwitch.kt new file mode 100644 index 0000000..45a94b2 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedSwitch.kt @@ -0,0 +1,222 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.takeOrElse +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.resources.utils.compositeOverSafe +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.ComposeSwitch +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) + .compositeOverSafe(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 -> { + ComposeSwitch( + 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.takeOrElse { + MaterialTheme.colorScheme.surface + } + ) + } + + 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 + ) + } + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedToggleButton.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedToggleButton.kt new file mode 100644 index 0000000..0d7123f --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedToggleButton.kt @@ -0,0 +1,201 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.animateDpAsState +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.draw.drawWithCache +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.nativePaint +import androidx.compose.ui.graphics.toArgb +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.settings.presentation.provider.LocalSettingsState +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(), + 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 settingsState = LocalSettingsState.current + + val scope = rememberCoroutineScope() + + val shadowElevation = animateDpAsState( + if (settingsState.borderWidth > 0.dp || !enabled || !settingsState.drawButtonShadows) { + 0.dp + } else { + 0.5.dp + } + ).value + + LocalMinimumInteractiveComponentSize.ProvidesValue(Dp.Unspecified) { + Surface( + checked = checked, + onCheckedChange = { + haptics.longPress() + onCheckedChange(it) + + scope.launch { + delay(200) + focus.clearFocus() + } + }, + modifier = modifier + .toggleButtonShadow( + shape = buttonShape, + elevation = shadowElevation + ) + .semantics { role = Role.Checkbox }, + enabled = enabled, + shape = buttonShape, + color = containerColor, + contentColor = contentColor, + shadowElevation = 0.dp, + 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 + ) + } + } + } +} + +private fun Modifier.toggleButtonShadow( + shape: Shape, + elevation: Dp +) = this then if (elevation <= 0.dp) { + Modifier +} else { + Modifier.drawWithCache { + val shadowColor = Color.Black.copy(alpha = 0.18f).toArgb() + val transparentColor = Color.Transparent.toArgb() + val outline = shape.createOutline(size, layoutDirection, this) + val path = Path().apply { + when (outline) { + is Outline.Rectangle -> addRect(outline.rect) + is Outline.Rounded -> addRoundRect(outline.roundRect) + is Outline.Generic -> addPath(outline.path) + } + } + val radius = elevation.toPx() + val paint = Paint().apply { + nativePaint.color = transparentColor + nativePaint.setShadowLayer( + radius * 1f, + 0f, + radius * 0.9f, + shadowColor + ) + } + + onDrawBehind { + drawIntoCanvas { + it.drawPath(path, paint) + } + } + } +} + +@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 + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedTopAppBar.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedTopAppBar.kt new file mode 100644 index 0000000..1334f5c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedTopAppBar.kt @@ -0,0 +1,144 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/derivative/EnhancedZoomableModalBottomSheet.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/derivative/EnhancedZoomableModalBottomSheet.kt new file mode 100644 index 0000000..275d75e --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/derivative/EnhancedZoomableModalBottomSheet.kt @@ -0,0 +1,122 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.ColumnScope +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.navigationBarsPadding +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.draw.clipToBounds +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.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.EnhancedModalSheetDragHandle +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.ZoomBadge +import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText +import net.engawapg.lib.zoomable.rememberZoomState +import net.engawapg.lib.zoomable.zoomable + +@Composable +fun EnhancedZoomableModalBottomSheet( + visible: Boolean, + onDismiss: () -> Unit, + containerColor: Color = EnhancedBottomSheetDefaults.barContainerColor, + confirmButton: @Composable () -> Unit = { + EnhancedButton( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + onClick = onDismiss, + modifier = Modifier.padding(horizontal = 12.dp) + ) { + AutoSizeText(stringResource(R.string.close)) + } + }, + dragHandle: @Composable ColumnScope.() -> Unit = { + EnhancedModalSheetDragHandle( + color = containerColor, + drawStroke = false, + heightWhenDisabled = 20.dp + ) + }, + title: @Composable RowScope.() -> Unit, + content: @Composable BoxScope.() -> Unit +) { + EnhancedModalBottomSheet( + sheetContent = { + val zoomState = rememberZoomState(maxScale = 20f) + + Column( + modifier = Modifier + .background(containerColor) + .navigationBarsPadding() + ) { + Box( + modifier = Modifier.weight(1f) + ) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp) + .clip(ShapeDefaults.extraSmall) + .transparencyChecker() + .clipToBounds() + .zoomable( + zoomState = zoomState + ), + contentAlignment = Alignment.Center, + content = content + ) + ZoomBadge( + zoomLevel = zoomState.scale, + modifier = Modifier.align(Alignment.TopStart) + ) + } + Row( + modifier = Modifier.padding(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + title() + Spacer(Modifier.weight(1f)) + confirmButton() + } + } + }, + visible = visible, + onDismiss = { + if (!it) onDismiss() + }, + dragHandle = dragHandle + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/derivative/OnlyAllowedSliderItem.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/derivative/OnlyAllowedSliderItem.kt new file mode 100644 index 0000000..3eae1fe --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/derivative/OnlyAllowedSliderItem.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/icon_shape/IconShapeContainer.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/icon_shape/IconShapeContainer.kt new file mode 100644 index 0000000..9d70b53 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/icon_shape/IconShapeContainer.kt @@ -0,0 +1,162 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.resources.utils.compositeOverSafe +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.compositeOverSafe(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 } \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/AspectRatioSelector.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/AspectRatioSelector.kt new file mode 100644 index 0000000..05860bf --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/AspectRatioSelector.kt @@ -0,0 +1,408 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.CropFree +import com.t8rin.imagetoolbox.core.resources.icons.DashboardCustomize +import com.t8rin.imagetoolbox.core.resources.icons.Image +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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.modifier.shapeByInteraction +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 + } + } + + val interactionSource = remember { MutableInteractionSource() } + + val cardShape = shapeByInteraction( + shape = ShapeDefaults.default, + pressedShape = ShapeDefaults.pressed, + interactionSource = interactionSource + ) + + if (isNumeric) { + AspectRatioSelectionCard( + modifier = Modifier + .width(90.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() + }, + shape = cardShape + ) + .hapticsClickable( + interactionSource = interactionSource, + indication = LocalIndication.current + ) { + 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() + }, + shape = cardShape + ) + .hapticsClickable( + interactionSource = interactionSource, + indication = LocalIndication.current + ) { + 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.Rounded.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) + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/AspectRatios.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/AspectRatios.kt new file mode 100644 index 0000000..4794f8c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/AspectRatios.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/AutoFilePicker.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/AutoFilePicker.kt new file mode 100644 index 0000000..d825a70 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/AutoFilePicker.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/BadImageWidget.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/BadImageWidget.kt new file mode 100644 index 0000000..2c3db84 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/BadImageWidget.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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 + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/HistogramChart.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/HistogramChart.kt new file mode 100644 index 0000000..4515a0d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/HistogramChart.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageContainer.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageContainer.kt new file mode 100644 index 0000000..7d93673 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageContainer.kt @@ -0,0 +1,153 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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() + } + } + } + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageCounter.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageCounter.kt new file mode 100644 index 0000000..2b85a0e --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageCounter.kt @@ -0,0 +1,118 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.ChangeCircle +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) + ) + } + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageHeaderState.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageHeaderState.kt new file mode 100644 index 0000000..0f6367b --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageHeaderState.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +) diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageNotPickedWidget.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageNotPickedWidget.kt new file mode 100644 index 0000000..66bb761 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageNotPickedWidget.kt @@ -0,0 +1,247 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FileOpen +import com.t8rin.imagetoolbox.core.resources.icons.Image +import com.t8rin.imagetoolbox.core.resources.shapes.MorphShape +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.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.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 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 shapesType = LocalSettingsState.current.shapesType + + val nonPressedProgress = when (shapesType) { + is ShapeType.Rounded, + is ShapeType.Smooth, + is ShapeType.Squircle -> 0.2f + + else -> 0f + } + val pressedScale = when (shapesType) { + is ShapeType.Rounded, + is ShapeType.Smooth, + is ShapeType.Squircle -> 1f + + else -> 1.1f + } + + val percentage = animateFloatAsState( + targetValue = if (pressed) 1f else nonPressedProgress, + animationSpec = springySpec() + ) + val scale by animateFloatAsState( + if (pressed) pressedScale + else 1.1f + ) + + val morph = remember(shapesType) { + val (start, end) = when (shapesType) { + is ShapeType.Cut -> MaterialShapes.Gem to MaterialShapes.Diamond + is ShapeType.Wavy -> MaterialShapes.Cookie12Sided to MaterialShapes.Clover4Leaf + is ShapeType.Scoop -> MaterialShapes.Clover8Leaf to MaterialShapes.Bun + is ShapeType.Notch -> MaterialShapes.PixelCircle to ShapeDefaults.polygonSquare + + is ShapeType.Rounded, + is ShapeType.Smooth, + is ShapeType.Squircle -> MaterialShapes.Cookie4Sided to MaterialShapes.Square + } + Morph( + start = start, + end = end + ) + } + val shape = remember(morph) { + 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 + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImagePager.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImagePager.kt new file mode 100644 index 0000000..4b6404e --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImagePager.kt @@ -0,0 +1,485 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.RectangleShape +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt +import com.t8rin.imagetoolbox.core.resources.icons.EditAlt +import com.t8rin.imagetoolbox.core.resources.icons.Share +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +import com.t8rin.imagetoolbox.core.ui.theme.White +import com.t8rin.imagetoolbox.core.ui.theme.blend +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 + .fillMaxSize() + .background( + takeColorFromScheme { isNightMode -> + errorContainer.copy( + if (isNightMode) 0.25f + else 1f + ).compositeOverSafe(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.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 = MaterialTheme.colorScheme.primaryFixed.blend(White, 0.5f) + ) + } + } + } + + 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 + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImagePreviewGrid.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImagePreviewGrid.kt new file mode 100644 index 0000000..a17df95 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImagePreviewGrid.kt @@ -0,0 +1,191 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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.Icons +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) + } + LaunchedEffect(imageFrames, isSelectable, data?.size) { + val hasSelectedFrames = imageFrames + ?.getFramePositions(data.orEmpty().size) + ?.isNotEmpty() == true + isSelectionMode = isSelectable && hasSelectedFrames + isSelectionModePrevious = isSelectionMode + } + + 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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageStickyHeader.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageStickyHeader.kt new file mode 100644 index 0000000..6a14f1c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageStickyHeader.kt @@ -0,0 +1,300 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Lock +import com.t8rin.imagetoolbox.core.resources.icons.LockOpen +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) + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImagesPreviewWithSelection.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImagesPreviewWithSelection.kt new file mode 100644 index 0000000..07facb7 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImagesPreviewWithSelection.kt @@ -0,0 +1,494 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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 +import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt +import com.t8rin.imagetoolbox.core.resources.icons.CheckCircle +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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 + .fillMaxSize() + .background( + takeColorFromScheme { isNightMode -> + errorContainer.copy( + if (isNightMode) 0.25f + else 1f + ).compositeOverSafe(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.Rounded.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.Rounded.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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/MetadataPreviewButton.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/MetadataPreviewButton.kt new file mode 100644 index 0000000..6596a34 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/MetadataPreviewButton.kt @@ -0,0 +1,299 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Metadata +import com.t8rin.imagetoolbox.core.domain.image.model.MetadataTag +import com.t8rin.imagetoolbox.core.domain.image.toMap +import com.t8rin.imagetoolbox.core.domain.model.IntegerSize +import com.t8rin.imagetoolbox.core.domain.utils.humanFileSize +import com.t8rin.imagetoolbox.core.domain.utils.timestamp +import com.t8rin.imagetoolbox.core.resources.Icons +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.imageSize +import com.t8rin.imagetoolbox.core.utils.lastModified +import com.t8rin.imagetoolbox.core.utils.path +import java.util.Locale + +@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) }, + imageSize: (Uri) -> IntegerSize? = { it.imageSize() } +) { + 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 metadataImageSize by remember(metadata) { + derivedStateOf { + metadata?.imageSize + } + } + val info by remember( + uri, + dateModified, + dateAdded, + path, + name, + fileSize, + imageSize, + metadataImageSize + ) { + derivedStateOf { + UriInfo( + dateModified = dateModified(uri), + dateAdded = dateAdded(uri), + path = path(uri), + name = name(uri), + fileSize = fileSize(uri), + imageSize = metadataImageSize ?: imageSize(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 imageSize: IntegerSize? +) { + 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) + } + + imageSize?.formatResolution()?.let { + add(R.string.resolution 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) + } + } +} + +private val Metadata.imageSize: IntegerSize? + get() { + val width = getAttribute(MetadataTag.PixelXDimension)?.toIntOrNull() + val height = getAttribute(MetadataTag.PixelYDimension)?.toIntOrNull() + return IntegerSize(width ?: 0, height ?: 0).takeIf { it.width > 0 && it.height > 0 } + } + +private fun IntegerSize.formatResolution(): String? = takeIf { + it.width > 0 && it.height > 0 +}?.run { + val megapixels = width.toLong() * height / 1_000_000f + "$width × $height • ${String.format(Locale.US, "%.1f", megapixels)} MP" +}?.let { + if ("0.0" !in it) it else it.substringBeforeLast("•").trim() +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/Picture.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/Picture.kt new file mode 100644 index 0000000..6937c8c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/Picture.kt @@ -0,0 +1,343 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.Box +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.compose.AsyncImage +import coil3.compose.AsyncImageModelEqualityDelegate +import coil3.compose.AsyncImagePainter +import coil3.compose.LocalAsyncImageModelEqualityDelegate +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 ((AsyncImagePainter.State.Loading) -> Unit)? = null, + success: @Composable ((AsyncImagePainter.State.Success) -> Unit)? = null, + error: @Composable ((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 ((AsyncImagePainter.State.Loading) -> Unit)?, + success: @Composable ((AsyncImagePainter.State.Success) -> Unit)?, + error: @Composable ((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) } + + var painterState by remember { mutableStateOf(null) } + + var isSuccess by remember { mutableStateOf(false) } + + 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 + } + } + } + + val imageModifier = modifier + .clip(shape) + .then( + if (!LocalInspectionMode.current) { + Modifier + .then( + if (showTransparencyChecker && !shimmerVisible && isSuccess) { + Modifier.transparencyChecker() + } else Modifier + ) + .then(if (shimmerEnabled) Modifier.shimmer(shimmerVisible || isLoadingFromDifferentPlace) else Modifier) + } else { + Modifier + } + ) + .padding(contentPadding) + + Box(modifier = imageModifier) { + AsyncImage( + model = request, + contentDescription = contentDescription, + onState = { + isSuccess = it is AsyncImagePainter.State.Success + + painterState = if (error != null || loading != null || success != null) it else null + + when (it) { + is AsyncImagePainter.State.Loading -> { + shimmerVisible = true + onLoading?.invoke(it) + onState?.invoke(it) + } + + is AsyncImagePainter.State.Success -> { + 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) + } + + is AsyncImagePainter.State.Error -> { + if (error != null) shimmerVisible = false + onError?.invoke(it) + onState?.invoke(it) + errorOccurred = true + } + + else -> onState?.invoke(it) + } + }, + alignment = alignment, + contentScale = contentScale, + alpha = alpha, + colorFilter = colorFilter, + filterQuality = filterQuality, + modifier = Modifier.matchParentSize() + ) + + Box(Modifier.matchParentSize()) { + when (val state = painterState) { + is AsyncImagePainter.State.Loading -> loading?.invoke(state) + is AsyncImagePainter.State.Success -> success?.invoke(state) + is AsyncImagePainter.State.Error -> error?.invoke(state) + else -> Unit + } + } + } + + //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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/SimplePicture.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/SimplePicture.kt new file mode 100644 index 0000000..d4c56ed --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/SimplePicture.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +// } +// } +//} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/SubcomposePicture.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/SubcomposePicture.kt new file mode 100644 index 0000000..0e2c6f9 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/SubcomposePicture.kt @@ -0,0 +1,323 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 SubcomposePicture( + 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 -> { + CoilSubcomposePicture( + 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 CoilSubcomposePicture( + 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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/UrisCarousel.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/UrisCarousel.kt new file mode 100644 index 0000000..da00181 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/UrisCarousel.kt @@ -0,0 +1,118 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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 + ), + shape = MaterialTheme.shapes.medium, + contentScale = ContentScale.Fit + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/UrisPreview.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/UrisPreview.kt new file mode 100644 index 0000000..815d61b --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/UrisPreview.kt @@ -0,0 +1,293 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.fillMaxSize +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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt +import com.t8rin.imagetoolbox.core.resources.icons.File +import com.t8rin.imagetoolbox.core.resources.icons.RemoveCircle +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.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.Outlined.AddPhotoAlt, + 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.Outlined.File, + 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), + ) { + repeat(uris.size + 1) { index -> + val uri = uris.getOrNull(index) + + if (uri != null && 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( + modifier = Modifier.fillMaxSize(), + 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.Outlined.RemoveCircle, + 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 } + ) + } +} + +@Composable +fun Modifier.urisPreview( + isPortrait: Boolean, + scrollState: ScrollState? = null +): Modifier = this then 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) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AdvancedShadow.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AdvancedShadow.kt new file mode 100644 index 0000000..f4f3317 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AdvancedShadow.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.drawWithCache +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 +) = drawWithCache { + val shadowColor = color.copy(alpha = alpha).toArgb() + val transparentColor = color.copy(alpha = 0f).toArgb() + val cornersRadiusPx = cornersRadius.toPx() + val shadowBlurRadiusPx = shadowBlurRadius.toPx() + val offsetXPx = offsetX.toPx() + val offsetYPx = offsetY.toPx() + val paint = Paint().apply { + nativePaint.color = transparentColor + nativePaint.setShadowLayer( + shadowBlurRadiusPx, + offsetXPx, + offsetYPx, + shadowColor + ) + } + + onDrawBehind { + drawIntoCanvas { + it.drawRoundRect( + 0f, + 0f, + size.width, + size.height, + cornersRadiusPx, + cornersRadiusPx, + paint + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AlertDialogBorder.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AlertDialogBorder.kt new file mode 100644 index 0000000..cc0376d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AlertDialogBorder.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.Dp +import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant + +@Composable +fun Modifier.alertDialogBorder( + colorScheme: ColorScheme, + shape: Shape, + autoElevation: Dp +) = autoElevatedBorder( + color = colorScheme.outlineVariant( + luminance = 0.15f, + onTopOf = colorScheme.surfaceContainerHigh + ), + shape = shape, + autoElevation = autoElevation +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AnimateContentSizeNoClip.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AnimateContentSizeNoClip.kt new file mode 100644 index 0000000..d5bbb3d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AnimateContentSizeNoClip.kt @@ -0,0 +1,262 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AutoCornersShape.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AutoCornersShape.kt new file mode 100644 index 0000000..be175ca --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AutoCornersShape.kt @@ -0,0 +1,385 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + ) + + is ShapeType.Wavy -> WavyShape( + topStart = topStart.toAuto(shapesType), + topEnd = topEnd.toAuto(shapesType), + bottomEnd = bottomEnd.toAuto(shapesType), + bottomStart = bottomStart.toAuto(shapesType) + ) + + is ShapeType.Scoop -> ScoopShape( + topStart = topStart.toAuto(shapesType), + topEnd = topEnd.toAuto(shapesType), + bottomEnd = bottomEnd.toAuto(shapesType), + bottomStart = bottomStart.toAuto(shapesType), + ) + + is ShapeType.Notch -> NotchShape( + topStart = topStart.toAuto(shapesType), + topEnd = topEnd.toAuto(shapesType), + bottomEnd = bottomEnd.toAuto(shapesType), + bottomStart = bottomStart.toAuto(shapesType), + ) + } +} + +@Stable +fun AutoCircleShape(shapesType: ShapeType) = when (shapesType) { + is ShapeType.Cut -> CutCircleShape + is ShapeType.Rounded -> CircleShape + is ShapeType.Smooth -> SmoothCircleShape + is ShapeType.Squircle -> SquircleCircleShape + is ShapeType.Wavy -> WavyCircleShape + is ShapeType.Scoop -> ScoopCircleShape + is ShapeType.Notch -> NotchCircleShape +}.let { shape -> + val strength = shapesType.effectiveStrength + + if (strength >= 1f) { + shape + } else { + shape.copy(shape.topStart.toAuto(strength)) + } +} + +@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 WavyCircleShape = WavyShape(percent = 50) + +@Stable +val ScoopCircleShape = ScoopShape(percent = 50) + +@Stable +val NotchCircleShape = NotchShape(percent = 50) + +@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.effectiveStrength) + +@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) + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AutoElevatedBorder.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AutoElevatedBorder.kt new file mode 100644 index 0000000..fe48355 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AutoElevatedBorder.kt @@ -0,0 +1,70 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.runtime.Composable +import androidx.compose.ui.Modifier +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 + +@Composable +fun Modifier.autoElevatedBorder( + height: Dp = Dp.Unspecified, + shape: Shape? = null, + color: Color = Color.Unspecified, + autoElevation: Dp = 6.dp +): Modifier { + val heightValue = if (height.isUnspecified) { + LocalSettingsState.current.borderWidth.takeIf { it > 0.dp } + } else null + + val shapeValue = shape ?: FloatingActionButtonDefaults.shape + + val elevation = animateDpAsState(if (heightValue == null) autoElevation else 0.dp) + + return this then if (heightValue == null) { + Modifier + } else { + Modifier.border( + width = heightValue, + color = if (color.isSpecified) color + else { + MaterialTheme.colorScheme.outlineVariant( + luminance = 0.3f, + onTopOf = MaterialTheme.colorScheme.suggestContainerColorBy(LocalContentColor.current) + ) + }, + shape = shapeValue + ) + }.materialShadow( + elevation = elevation, + shape = shapeValue + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Container.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Container.kt new file mode 100644 index 0000000..7a640b4 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Container.kt @@ -0,0 +1,175 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.runtime.Composable +import androidx.compose.runtime.remember +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.Shape +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.isUnspecified +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.takeOrElse +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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.LocalContainerBrush +import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalContainerShape +import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor + +@Composable +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 = if (color.isUnspecified) true else color.alpha < 1f, + isStandaloneContainer: Boolean = true, + shadowColor: Color = Color.Black +): Modifier { + val resultShape = LocalContainerShape.current ?: 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.compositeOverSafe(colorScheme.background) + } else color + + val enableShadow = if (isStandaloneContainer) settingsState.drawContainerShadows else true + + val outlineColor = remember(borderColor, colorScheme.onSecondaryContainer, containerColor) { + borderColor ?: colorScheme.outlineVariant( + luminance = 0.1f, + onTopOf = containerColor + ) + } + + val brush = LocalContainerBrush.current + + return this + .materialShadow( + shape = resultShape, + elevation = animateDpAsState( + if (targetBorderWidth > 0.dp || !enableShadow) 0.dp else autoShadowElevation.coerceAtLeast( + 0.dp + ) + ), + isClipped = isShadowClip, + color = shadowColor + ) + .then( + remember( + resultShape, + clip, + resultPadding, + brush, + containerColor, + targetBorderWidth, + outlineColor + ) { + Modifier + .then( + if (resultShape is CornerBasedShape) { + Modifier + .then( + brush?.let { + Modifier.background( + brush = brush, + shape = resultShape + ) + } ?: Modifier.background( + color = containerColor, + shape = resultShape + ) + ) + .then( + if (targetBorderWidth > 0.dp) { + Modifier.border( + width = targetBorderWidth, + color = outlineColor, + shape = resultShape + ) + } else { + Modifier + } + ) + } else { + Modifier.drawWithCache { + val outline = resultShape.createOutline( + size = size, + layoutDirection = layoutDirection, + density = this + ) + val stroke = if (targetBorderWidth > 0.dp) { + Stroke(targetBorderWidth.toPx()) + } else { + null + } + + onDrawWithContent { + brush?.let { + drawOutline( + outline = outline, + brush = brush + ) + } ?: drawOutline( + outline = outline, + color = containerColor + ) + + stroke?.let { + drawOutline( + outline = outline, + color = outlineColor, + style = stroke + ) + } + drawContent() + } + } + } + ) + .then(if (clip) Modifier.clip(resultShape) else Modifier) + .then(if (resultPadding > 0.dp) Modifier.padding(resultPadding) else Modifier) + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ContiniousRoundedRectangle.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ContiniousRoundedRectangle.kt new file mode 100644 index 0000000..00c40cc --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ContiniousRoundedRectangle.kt @@ -0,0 +1,290 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/DetectSwipe.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/DetectSwipe.kt new file mode 100644 index 0000000..f368eea --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/DetectSwipe.kt @@ -0,0 +1,107 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + } + ) + } +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/DragHandler.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/DragHandler.kt new file mode 100644 index 0000000..fc30eb4 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/DragHandler.kt @@ -0,0 +1,257 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.MutatePriority +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.awaitLongPressOrCancellation +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +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.PointerInputScope +import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed +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.CancellationException +import kotlinx.coroutines.Job +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch + + +/** +[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 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) { + coroutineScope { + var initialKey: Int? = null + var currentKey: Int? = null + var dragPosition: Offset? + var autoScrollSpeed = 0f + var autoScrollJob: Job? = null + + fun updateSelection(position: Offset) { + val initial = initialKey ?: return + + lazyGridState + .gridItemKeyAtPosition(position) + ?.let { key -> + if (currentKey != key) { + val newItems = selectedItems.value + .minus(initial..currentKey!!) + .minus(currentKey!!..initial) + .plus(initial..key) + .plus(key..initial) + + selectedItems.update { newItems } + onSelectionChange(newItems) + currentKey = key + } + } + } + + fun stopDrag() { + initialKey = null + currentKey = null + dragPosition = null + autoScrollSpeed = 0f + autoScrollJob?.cancel() + autoScrollJob = null + } + + detectDragGesturesAfterLongPressInInitialPass( + onDragStart = { offset -> + val position = + if (isRtl) offset.copy(x = size.width - offset.x) else offset + lazyGridState + .gridItemKeyAtPosition(position) + ?.let { key -> + if (!selectedItems.value.contains(key) && shouldHandleLongTap) { + initialKey = key + currentKey = key + dragPosition = position + val newItems = selectedItems.value + key + selectedItems.update { newItems } + onSelectionChange(newItems) + + autoScrollJob = launch { + lazyGridState.scroll(MutatePriority.PreventUserInput) { + while (isActive) { + val speed = autoScrollSpeed + if (speed != 0f) { + scrollBy(speed) + dragPosition?.let(::updateSelection) + } + delay(10) + } + } + } + } + haptics.longPress() + onLongTap(key - 1) + } + }, + onDragCancel = ::stopDrag, + onDragEnd = ::stopDrag, + onDrag = { change, _ -> + if (initialKey != null) { + val position = if (isRtl) { + change.position.copy(x = size.width - change.position.x) + } else { + change.position + } + + dragPosition = 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 = when { + distFromBottom < autoScrollThreshold -> autoScrollThreshold - distFromBottom + distFromTop < autoScrollThreshold -> -(autoScrollThreshold - distFromTop) + else -> 0f + } + + updateSelection(position) + } + } + ) + } + } + } +} + +private suspend fun PointerInputScope.detectDragGesturesAfterLongPressInInitialPass( + onDragStart: (Offset) -> Unit, + onDragEnd: () -> Unit, + onDragCancel: () -> Unit, + onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit +) { + awaitEachGesture { + try { + val down = awaitFirstDown(requireUnconsumed = false) + val longPress = awaitLongPressOrCancellation(down.id) ?: return@awaitEachGesture + + onDragStart(longPress.position) + var pointerId = longPress.id + var isFinished = false + + while (!isFinished) { + val event = awaitPointerEvent(PointerEventPass.Initial) + var change = event.changes.firstOrNull { it.id == pointerId } + + if (change?.pressed != true) { + change = event.changes.firstOrNull { it.pressed } + if (change == null) { + event.changes + .filter(PointerInputChange::changedToUpIgnoreConsumed) + .forEach(PointerInputChange::consume) + isFinished = true + } else { + pointerId = change.id + } + } + + if (!isFinished && change != null) { + val dragAmount = change.position - change.previousPosition + onDrag(change, dragAmount) + change.consume() + } + } + + onDragEnd() + } catch (exception: CancellationException) { + onDragCancel() + throw exception + } + } +} + +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() +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/DrawHorizontalStroke.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/DrawHorizontalStroke.kt new file mode 100644 index 0000000..f46e1fa --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/DrawHorizontalStroke.kt @@ -0,0 +1,325 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.VectorConverter +import androidx.compose.material3.MaterialTheme.LocalMaterialTheme +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.Paint +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.addOutline +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.clipPath +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.nativePaint +import androidx.compose.ui.graphics.scale +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.node.CompositionLocalConsumerModifierNode +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.ObserverModifierNode +import androidx.compose.ui.node.currentValueOf +import androidx.compose.ui.node.invalidateDraw +import androidx.compose.ui.node.observeReads +import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.isUnspecified +import com.gigamole.composeshadowsplus.common.ShadowsPlusDefaults +import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState +import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant +import kotlinx.coroutines.launch + +fun Modifier.drawHorizontalStroke( + top: Boolean = false, + height: Dp = Dp.Unspecified, + autoElevation: Dp = 6.dp, + enabled: Boolean = true +): Modifier { + if (!enabled) return this + + return this.then( + DrawHorizontalStrokeElement( + top = top, + height = height, + autoElevation = autoElevation + ) + ) +} + +private data class DrawHorizontalStrokeElement( + val top: Boolean, + val height: Dp, + val autoElevation: Dp +) : ModifierNodeElement() { + + override fun create(): DrawHorizontalStrokeNode { + return DrawHorizontalStrokeNode( + top = top, + height = height, + autoElevation = autoElevation + ) + } + + override fun update(node: DrawHorizontalStrokeNode) { + node.update( + top = top, + height = height, + autoElevation = autoElevation + ) + } + + override fun InspectorInfo.inspectableProperties() { + name = "drawHorizontalStroke" + properties["top"] = top + properties["height"] = height + properties["autoElevation"] = autoElevation + } +} + +private class DrawHorizontalStrokeNode( + private var top: Boolean, + private var height: Dp, + private var autoElevation: Dp +) : Modifier.Node(), + DrawModifierNode, + CompositionLocalConsumerModifierNode, + ObserverModifierNode { + + private val shadowRadius = Animatable(0.dp, Dp.VectorConverter) + + private var strokeHeight: Dp? = null + private var strokeColor: Color = Color.Unspecified + + fun update( + top: Boolean, + height: Dp, + autoElevation: Dp + ) { + this.top = top + this.height = height + this.autoElevation = autoElevation + + updateState() + invalidateDraw() + } + + override fun onAttach() { + updateState() + } + + override fun onObservedReadsChanged() { + updateState() + } + + private fun updateState() { + observeReads { + val settingsState = currentValueOf(LocalSettingsState) + val borderWidth = settingsState.borderWidth + + val newStrokeHeight = if (height.isUnspecified) { + borderWidth.takeIf { it > 0.dp } + } else { + height + } + + val colorScheme = currentValueOf(LocalMaterialTheme).colorScheme + + val newStrokeColor = colorScheme.outlineVariant( + luminance = 0.1f, + onTopOf = colorScheme.surfaceContainer + ) + + val targetRadius = if ( + newStrokeHeight == null && + settingsState.drawAppBarShadows + ) { + autoElevation + } else { + 0.dp + } + + val shouldInvalidate = + strokeHeight != newStrokeHeight || + strokeColor != newStrokeColor + + strokeHeight = newStrokeHeight + strokeColor = newStrokeColor + + if (shouldInvalidate) { + invalidateDraw() + } + + animateShadowTo(targetRadius) + } + } + + private fun animateShadowTo(target: Dp) { + if (shadowRadius.targetValue == target) return + + coroutineScope.launch { + shadowRadius.animateTo(target) + invalidateDraw() + } + } + + override fun ContentDrawScope.draw() { + drawSoftLayerShadow( + radius = shadowRadius.value, + color = ShadowsPlusDefaults.ShadowColor, + shape = RectangleShape, + spread = (-2).dp, + offset = DpOffset( + x = 0.dp, + y = if (top) (-1).dp else 2.dp + ), + isAlphaContentClip = false + ) + + drawContent() + + val h = strokeHeight ?: return + val heightPx = h.toPx() + + drawRect( + color = strokeColor, + topLeft = if (top) { + Offset.Zero + } else { + Offset( + x = 0f, + y = size.height - heightPx + ) + }, + size = Size( + width = size.width, + height = heightPx + ) + ) + } + + private fun ContentDrawScope.drawSoftLayerShadow( + radius: Dp, + color: Color, + shape: Shape, + spread: Dp, + offset: DpOffset, + isAlphaContentClip: Boolean + ) { + val radiusPx = radius.toPx() + val isRadiusValid = radiusPx > 0f + + val outline = shape.createOutline( + size = size, + layoutDirection = LayoutDirection.Rtl, + density = this + ) + + val path = Path().apply { + addOutline(outline) + } + + val paint = Paint().apply { + this.color = if (isRadiusValid) { + Color.Transparent + } else { + color + } + + nativePaint.apply { + isDither = true + isAntiAlias = true + + if (isRadiusValid) { + setShadowLayer( + radiusPx, + offset.x.toPx(), + offset.y.toPx(), + color.toArgb() + ) + } else { + clearShadowLayer() + } + } + } + + val block: DrawScope.() -> Unit = { + drawIntoCanvas { canvas -> + canvas.save() + + if (!isRadiusValid) { + canvas.translate( + dx = offset.x.toPx(), + dy = offset.y.toPx() + ) + } + + if (spread.value != 0f) { + val spreadPx = spread.toPx() + + canvas.scale( + sx = spreadScale( + spread = spreadPx, + size = size.width + ), + sy = spreadScale( + spread = spreadPx, + size = size.height + ), + pivotX = center.x, + pivotY = center.y + ) + } + + canvas.drawOutline( + outline = outline, + paint = paint + ) + + canvas.restore() + } + } + + if (isAlphaContentClip) { + clipPath( + path = path, + block = block + ) + } else { + block() + } + } +} + +private fun spreadScale( + spread: Float, + size: Float +): Float { + if (size == 0f) return 1f + return (size + spread * 2f) / size +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/FadingEdges.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/FadingEdges.kt new file mode 100644 index 0000000..7222742 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/FadingEdges.kt @@ -0,0 +1,183 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.ui.Modifier +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 + +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 then if (!enabled) { + Modifier +} else if (scrollableState != null && !scrollableState.canScrollBackward && !scrollableState.canScrollForward) { + 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 ?: scrollableState.layoutInfo.maxSpan + ), + fillType = fillType, + length = length + ) + } else { + Modifier.horizontalFadingEdges( + gravity = gravity, + contentType = FadingEdgesContentType.Dynamic.Lazy.Grid( + scrollConfig = scrollConfig, + state = scrollableState, + spanCount = spanCount ?: scrollableState.layoutInfo.maxSpan + ), + 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 + ) + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/HelperGrid.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/HelperGrid.kt new file mode 100644 index 0000000..4b8a467 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/HelperGrid.kt @@ -0,0 +1,118 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + ) + ) { } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/LayoutCorners.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/LayoutCorners.kt new file mode 100644 index 0000000..449f12f --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/LayoutCorners.kt @@ -0,0 +1,198 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Line.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Line.kt new file mode 100644 index 0000000..c0b0dc1 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Line.kt @@ -0,0 +1,92 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + } + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/MaterialShadow.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/MaterialShadow.kt new file mode 100644 index 0000000..9ed52c1 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/MaterialShadow.kt @@ -0,0 +1,166 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +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.Paint +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.nativePaint +import androidx.compose.ui.graphics.toArgb +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 + +@Composable +fun Modifier.materialShadow( + shape: Shape, + elevation: Dp, + enabled: Boolean = true, + isClipped: Boolean = true, + color: Color = Color.Black +): Modifier = materialShadow( + shape = shape, + elevation = animateDpAsState(if (enabled) elevation else 0.dp), + isClipped = isClipped, + color = color +) + +@Composable +fun Modifier.materialShadow( + shape: Shape, + elevation: State, + isClipped: Boolean = true, + color: Color = Color.Black +): Modifier { + return this then if (elevation.value > 0.dp) { + val shape = remember(shape) { + 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 isWavy = + shape is WavyShape || (shape is AnimatedShape && shape.shapesType is ShapeType.Wavy) + + fun api21Shadow() = Modifier.rsBlurShadow( + shape = shape, + radius = elevation.value, + isAlphaContentClip = isClipped, + color = color + ) + + fun api29Shadow() = if (isClipped) { + Modifier.clippedShadow( + shape = shape, + elevation = elevation.value, + ambientColor = color, + spotColor = color + ) + } else { + Modifier.shadow( + shape = shape, + elevation = elevation.value, + ambientColor = color, + spotColor = color + ) + } + + when { + isWavy -> { + if (elevation.value <= 0.dp) { + Modifier + } else { + Modifier.drawWithCache { + val shadowColor = Color.Black.copy(alpha = 0.18f).toArgb() + val transparentColor = Color.Transparent.toArgb() + val outline = shape.createOutline( + size = size, + layoutDirection = layoutDirection, + density = this + ) + val path = when (outline) { + is Outline.Rectangle -> Path().apply { addRect(outline.rect) } + is Outline.Rounded -> Path().apply { addRoundRect(outline.roundRect) } + is Outline.Generic -> outline.path + } + val radius = elevation.value.toPx() + val paint = Paint().apply { + nativePaint.color = transparentColor + nativePaint.setShadowLayer( + radius * 1f, + 0f, + radius * 0.9f, + shadowColor + ) + } + + onDrawBehind { + drawIntoCanvas { + it.drawPath(path, paint) + } + } + } + } + } + + Build.VERSION.SDK_INT < Build.VERSION_CODES.Q -> { + val isConcavePath = remember(shape) { + shape.createOutline( + size = Size(1f, 1f), + layoutDirection = LayoutDirection.Ltr, + density = Density(1f) + ).let { + it is Outline.Generic && !it.path.isConvex + } + } + + if (isConcavePath) api21Shadow() else api29Shadow() + } + + else -> api29Shadow() + } + } else Modifier +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/MeshGradient.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/MeshGradient.kt new file mode 100644 index 0000000..3bbbb04 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/MeshGradient.kt @@ -0,0 +1,357 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/NegativePadding.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/NegativePadding.kt new file mode 100644 index 0000000..8b37f4c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/NegativePadding.kt @@ -0,0 +1,73 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/NotchShape.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/NotchShape.kt new file mode 100644 index 0000000..af61eeb --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/NotchShape.kt @@ -0,0 +1,143 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.IntRange +import androidx.compose.foundation.shape.CornerBasedShape +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.runtime.Immutable +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.toRect +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +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 + +@Immutable +class NotchShape( + topStart: CornerSize, + topEnd: CornerSize, + bottomEnd: CornerSize, + bottomStart: CornerSize +) : CornerBasedShape( + topStart = topStart, + topEnd = topEnd, + bottomEnd = bottomEnd, + bottomStart = bottomStart +) { + + constructor(size: CornerSize) : this( + topStart = size, + topEnd = size, + bottomEnd = size, + bottomStart = size + ) + + constructor(size: Dp) : this(size = CornerSize(size)) + + constructor(@IntRange(from = 0, to = 100) percent: Int) : this(size = CornerSize(percent)) + + constructor( + topStart: Dp = 0.dp, + topEnd: Dp = 0.dp, + bottomEnd: Dp = 0.dp, + bottomStart: Dp = 0.dp + ) : this( + topStart = CornerSize(topStart), + topEnd = CornerSize(topEnd), + bottomEnd = CornerSize(bottomEnd), + bottomStart = CornerSize(bottomStart) + ) + + override fun createOutline( + size: Size, + topStart: Float, + topEnd: Float, + bottomEnd: Float, + bottomStart: Float, + layoutDirection: LayoutDirection + ): Outline { + if (topStart + topEnd + bottomStart + bottomEnd == 0f) { + return Outline.Rectangle(size.toRect()) + } + + val topLeft = if (layoutDirection == Ltr) topStart else topEnd + val topRight = if (layoutDirection == Ltr) topEnd else topStart + val bottomRight = if (layoutDirection == Ltr) bottomEnd else bottomStart + val bottomLeft = if (layoutDirection == Ltr) bottomStart else bottomEnd + + return Outline.Generic( + Path().apply { + moveTo(topLeft, 0f) + lineTo(size.width - topRight, 0f) + lineTo(size.width - topRight, topRight) + lineTo(size.width, topRight) + lineTo(size.width, size.height - bottomRight) + lineTo(size.width - bottomRight, size.height - bottomRight) + lineTo(size.width - bottomRight, size.height) + lineTo(bottomLeft, size.height) + lineTo(bottomLeft, size.height - bottomLeft) + lineTo(0f, size.height - bottomLeft) + lineTo(0f, topLeft) + lineTo(topLeft, topLeft) + close() + } + ) + } + + override fun copy( + topStart: CornerSize, + topEnd: CornerSize, + bottomEnd: CornerSize, + bottomStart: CornerSize + ): NotchShape { + return NotchShape( + topStart = topStart, + topEnd = topEnd, + bottomEnd = bottomEnd, + bottomStart = bottomStart + ) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is NotchShape) 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 + + 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() + return result + } + + override fun toString(): String { + return "NotchShape(topStart=$topStart, topEnd=$topEnd, bottomEnd=$bottomEnd, " + + "bottomStart=$bottomStart)" + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ObservePointersCount.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ObservePointersCount.kt new file mode 100644 index 0000000..92adbfa --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ObservePointersCount.kt @@ -0,0 +1,117 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/PointerInput.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/PointerInput.kt new file mode 100644 index 0000000..3e13b2d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/PointerInput.kt @@ -0,0 +1,56 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 }) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Pulsate.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Pulsate.kt new file mode 100644 index 0000000..dcf6156 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Pulsate.kt @@ -0,0 +1,162 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.AnimationState +import androidx.compose.animation.core.VectorConverter +import androidx.compose.animation.core.animateTo +import androidx.compose.animation.core.tween +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.withTransform +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.invalidateDraw +import androidx.compose.ui.platform.InspectorInfo +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +fun Modifier.pulsate( + range: ClosedFloatingPointRange = 0.95f..1.05f, + duration: Int = 1000, + enabled: Boolean = true +): Modifier = this then PulsateElement( + range = range, + duration = duration, + enabled = enabled +) + +private data class PulsateElement( + val range: ClosedFloatingPointRange, + val duration: Int, + val enabled: Boolean +) : ModifierNodeElement() { + + override fun create(): PulsateNode = PulsateNode( + range = range, + duration = duration, + enabled = enabled + ) + + override fun update(node: PulsateNode) { + node.update( + range = range, + duration = duration, + enabled = enabled + ) + } + + override fun InspectorInfo.inspectableProperties() { + name = "pulsate" + properties["range"] = range + properties["duration"] = duration + properties["enabled"] = enabled + } +} + +private class PulsateNode( + private var range: ClosedFloatingPointRange, + private var duration: Int, + private var enabled: Boolean +) : Modifier.Node(), DrawModifierNode { + + private var scale = range.start + private var job: Job? = null + + override fun onAttach() { + start() + } + + override fun onDetach() { + job?.cancel() + job = null + } + + fun update( + range: ClosedFloatingPointRange, + duration: Int, + enabled: Boolean + ) { + val changed = this.range != range || + this.duration != duration || + this.enabled != enabled + + this.range = range + this.duration = duration + this.enabled = enabled + + if (changed && isAttached) { + start() + } + } + + private fun start() { + job?.cancel() + + if (!enabled) { + scale = 1f + invalidateDraw() + return + } + + job = coroutineScope.launch { + val animationState = AnimationState( + typeConverter = Float.VectorConverter, + initialValue = scale.coerceIn(range.start, range.endInclusive) + ) + + while (true) { + animationState.animateTo( + targetValue = range.endInclusive, + animationSpec = tween(durationMillis = duration) + ) { + scale = value + invalidateDraw() + } + + animationState.animateTo( + targetValue = range.start, + animationSpec = tween(durationMillis = duration) + ) { + scale = value + invalidateDraw() + } + } + } + } + + override fun ContentDrawScope.draw() { + if (enabled) { + val contentDrawScope = this + + withTransform( + transformBlock = { + scale( + scaleX = scale, + scaleY = scale, + pivot = center + ) + } + ) { + contentDrawScope.drawContent() + } + } else { + drawContent() + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/RealisticSnowfall.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/RealisticSnowfall.kt new file mode 100644 index 0000000..9c14401 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/RealisticSnowfall.kt @@ -0,0 +1,58 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.ui.Modifier +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( + color: Color, + enabled: Boolean = true, +): Modifier = this then if (enabled) { + Modifier.snowfall( + type = FlakeType.Custom(flakes), + color = color + ) +} 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, + ) + ) + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/RotateAnimation.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/RotateAnimation.kt new file mode 100644 index 0000000..854af08 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/RotateAnimation.kt @@ -0,0 +1,156 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.AnimationState +import androidx.compose.animation.core.VectorConverter +import androidx.compose.animation.core.animateTo +import androidx.compose.animation.core.tween +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.rotate +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.invalidateDraw +import androidx.compose.ui.platform.InspectorInfo +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +fun Modifier.rotateAnimation( + range: IntRange = 0..180, + duration: Int = 1000, + enabled: Boolean = true +): Modifier = this then RotateAnimationElement( + range = range, + duration = duration, + enabled = enabled +) + +private data class RotateAnimationElement( + val range: IntRange, + val duration: Int, + val enabled: Boolean +) : ModifierNodeElement() { + + override fun create(): RotateAnimationNode = RotateAnimationNode( + range = range, + duration = duration, + enabled = enabled + ) + + override fun update(node: RotateAnimationNode) { + node.update( + range = range, + duration = duration, + enabled = enabled + ) + } + + override fun InspectorInfo.inspectableProperties() { + name = "rotateAnimation" + properties["range"] = range + properties["duration"] = duration + properties["enabled"] = enabled + } +} + +private class RotateAnimationNode( + private var range: IntRange, + private var duration: Int, + private var enabled: Boolean +) : Modifier.Node(), DrawModifierNode { + + private var rotation = range.first.toFloat() + private var job: Job? = null + + override fun onAttach() { + start() + } + + override fun onDetach() { + job?.cancel() + job = null + } + + fun update( + range: IntRange, + duration: Int, + enabled: Boolean + ) { + val changed = this.range != range || + this.duration != duration || + this.enabled != enabled + + this.range = range + this.duration = duration + this.enabled = enabled + + if (changed && isAttached) { + start() + } + } + + private fun start() { + job?.cancel() + + if (!enabled) { + rotation = 0f + invalidateDraw() + return + } + + job = coroutineScope.launch { + val animationState = AnimationState( + typeConverter = Float.VectorConverter, + initialValue = rotation.coerceIn( + minimumValue = range.first.toFloat(), + maximumValue = range.last.toFloat() + ) + ) + + while (true) { + animationState.animateTo( + targetValue = range.last.toFloat(), + animationSpec = tween(durationMillis = duration) + ) { + rotation = value + invalidateDraw() + } + + animationState.animateTo( + targetValue = range.first.toFloat(), + animationSpec = tween(durationMillis = duration) + ) { + rotation = value + invalidateDraw() + } + } + } + } + + override fun ContentDrawScope.draw() { + val scope = this + + rotate( + degrees = rotation, + pivot = center + ) { + scope.drawContent() + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ScaleOnTap.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ScaleOnTap.kt new file mode 100644 index 0000000..adf9baf --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ScaleOnTap.kt @@ -0,0 +1,222 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.foundation.gestures.PressGestureScope +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.PressInteraction +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.withTransform +import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode +import androidx.compose.ui.node.CompositionLocalConsumerModifierNode +import androidx.compose.ui.node.DelegatingNode +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.currentValueOf +import androidx.compose.ui.node.invalidateDraw +import androidx.compose.ui.platform.InspectorInfo +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.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +fun Modifier.scaleOnTap( + initial: Float = 1f, + min: Float = 0.8f, + max: Float = 1.3f, + interactionSource: MutableInteractionSource? = null, + onHold: () -> Unit = {}, + onRelease: (time: Long) -> Unit +): Modifier = this then ScaleOnTapElement( + initial = initial, + min = min, + max = max, + interactionSource = interactionSource, + onHold = onHold, + onRelease = onRelease +) + +private data class ScaleOnTapElement( + val initial: Float, + val min: Float, + val max: Float, + val interactionSource: MutableInteractionSource?, + val onHold: () -> Unit, + val onRelease: (time: Long) -> Unit +) : ModifierNodeElement() { + + override fun create(): ScaleOnTapNode = ScaleOnTapNode( + initial = initial, + min = min, + max = max, + interactionSource = interactionSource, + onHold = onHold, + onRelease = onRelease + ) + + override fun update(node: ScaleOnTapNode) { + node.update( + initial = initial, + min = min, + max = max, + interactionSource = interactionSource, + onHold = onHold, + onRelease = onRelease + ) + } + + override fun InspectorInfo.inspectableProperties() { + name = "scaleOnTap" + properties["initial"] = initial + properties["min"] = min + properties["max"] = max + properties["interactionSource"] = interactionSource + properties["onHold"] = onHold + properties["onRelease"] = onRelease + } +} + +private class ScaleOnTapNode( + private var initial: Float, + private var min: Float, + private var max: Float, + private var interactionSource: MutableInteractionSource?, + private var onHold: () -> Unit, + private var onRelease: (time: Long) -> Unit +) : DelegatingNode(), + DrawModifierNode, + CompositionLocalConsumerModifierNode { + + private var scale = initial + private val animatable = Animatable(initial) + + private var animationJob: Job? = null + + private val pointerInputNode = delegate( + SuspendingPointerInputModifierNode { + detectTapGestures( + onPress = { onPress(it) } + ) + } + ) + + fun update( + initial: Float, + min: Float, + max: Float, + interactionSource: MutableInteractionSource?, + onHold: () -> Unit, + onRelease: (time: Long) -> Unit + ) { + val initialChanged = this.initial != initial + + val shouldResetPointerInput = + this.initial != initial || + this.min != min || + this.max != max + + this.initial = initial + this.min = min + this.max = max + this.interactionSource = interactionSource + this.onHold = onHold + this.onRelease = onRelease + + if (initialChanged) { + setTarget(initial) + } + + if (shouldResetPointerInput) { + pointerInputNode.resetPointerInputHandler() + } + } + + override fun onDetach() { + animationJob?.cancel() + animationJob = null + } + + private suspend fun PressGestureScope.onPress(offset: Offset) { + val time = System.currentTimeMillis() + + setTarget(max) + + currentValueOf(LocalHapticFeedback).longPress() + + val press = PressInteraction.Press(offset) + + interactionSource?.emit(press) + + onHold() + + delay(200) + + tryAwaitRelease() + + onRelease(System.currentTimeMillis() - time) + + currentValueOf(LocalHapticFeedback).longPress() + + interactionSource?.emit(PressInteraction.Release(press)) + + setTarget(min) + + delay(200) + + setTarget(initial) + } + + private fun setTarget(target: Float) { + animationJob?.cancel() + + animationJob = coroutineScope.launch { + animatable.animateTo( + targetValue = target, + animationSpec = springySpec() + ) { + scale = value + invalidateDraw() + } + + scale = animatable.value + invalidateDraw() + } + } + + override fun ContentDrawScope.draw() { + val scope = this + + withTransform( + transformBlock = { + scale( + scaleX = scale, + scaleY = scale, + pivot = center + ) + } + ) { + scope.drawContent() + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ScoopShape.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ScoopShape.kt new file mode 100644 index 0000000..3f65989 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ScoopShape.kt @@ -0,0 +1,197 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.IntRange +import androidx.compose.foundation.shape.CornerBasedShape +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.runtime.Immutable +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.toRect +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +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 + +@Immutable +class ScoopShape( + topStart: CornerSize, + topEnd: CornerSize, + bottomEnd: CornerSize, + bottomStart: CornerSize +) : CornerBasedShape( + topStart = topStart, + topEnd = topEnd, + bottomEnd = bottomEnd, + bottomStart = bottomStart +) { + + constructor(size: CornerSize) : this( + topStart = size, + topEnd = size, + bottomEnd = size, + bottomStart = size + ) + + constructor(size: Dp) : this(size = CornerSize(size)) + + constructor(@IntRange(from = 0, to = 100) percent: Int) : this(size = CornerSize(percent)) + + constructor( + topStart: Dp = 0.dp, + topEnd: Dp = 0.dp, + bottomEnd: Dp = 0.dp, + bottomStart: Dp = 0.dp + ) : this( + topStart = CornerSize(topStart), + topEnd = CornerSize(topEnd), + bottomEnd = CornerSize(bottomEnd), + bottomStart = CornerSize(bottomStart) + ) + + override fun createOutline( + size: Size, + topStart: Float, + topEnd: Float, + bottomEnd: Float, + bottomStart: Float, + layoutDirection: LayoutDirection + ): Outline { + if (topStart + topEnd + bottomStart + bottomEnd == 0f) { + return Outline.Rectangle(size.toRect()) + } + + val topLeft = if (layoutDirection == Ltr) topStart else topEnd + val topRight = if (layoutDirection == Ltr) topEnd else topStart + val bottomRight = if (layoutDirection == Ltr) bottomEnd else bottomStart + val bottomLeft = if (layoutDirection == Ltr) bottomStart else bottomEnd + + return Outline.Generic( + Path().apply { + moveTo(topLeft, 0f) + lineTo(size.width - topRight, 0f) + concaveArcTo( + left = size.width - topRight, + top = -topRight, + right = size.width + topRight, + bottom = topRight, + startAngle = 180f, + sweepAngle = -90f, + radius = topRight + ) + lineTo(size.width, size.height - bottomRight) + concaveArcTo( + left = size.width - bottomRight, + top = size.height - bottomRight, + right = size.width + bottomRight, + bottom = size.height + bottomRight, + startAngle = -90f, + sweepAngle = -90f, + radius = bottomRight + ) + lineTo(bottomLeft, size.height) + concaveArcTo( + left = -bottomLeft, + top = size.height - bottomLeft, + right = bottomLeft, + bottom = size.height + bottomLeft, + startAngle = 0f, + sweepAngle = -90f, + radius = bottomLeft + ) + lineTo(0f, topLeft) + concaveArcTo( + left = -topLeft, + top = -topLeft, + right = topLeft, + bottom = topLeft, + startAngle = 90f, + sweepAngle = -90f, + radius = topLeft + ) + close() + } + ) + } + + private fun Path.concaveArcTo( + left: Float, + top: Float, + right: Float, + bottom: Float, + startAngle: Float, + sweepAngle: Float, + radius: Float + ) { + if (radius > 0f) { + arcTo( + rect = Rect( + left = left, + top = top, + right = right, + bottom = bottom + ), + startAngleDegrees = startAngle, + sweepAngleDegrees = sweepAngle, + forceMoveTo = false + ) + } + } + + override fun copy( + topStart: CornerSize, + topEnd: CornerSize, + bottomEnd: CornerSize, + bottomStart: CornerSize + ): ScoopShape { + return ScoopShape( + topStart = topStart, + topEnd = topEnd, + bottomEnd = bottomEnd, + bottomStart = bottomStart + ) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ScoopShape) 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 + + 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() + return result + } + + override fun toString(): String { + return "ScoopShape(topStart=$topStart, topEnd=$topEnd, bottomEnd=$bottomEnd, " + + "bottomStart=$bottomStart)" + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ShapeDefaults.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ShapeDefaults.kt new file mode 100644 index 0000000..9bbc212 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ShapeDefaults.kt @@ -0,0 +1,513 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.FocusInteraction +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.interaction.PressInteraction +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.produceState +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.graphics.shapes.RoundedPolygon +import androidx.graphics.shapes.rectangle +import androidx.lifecycle.compose.LifecycleResumeEffect +import com.t8rin.imagetoolbox.core.domain.utils.autoCast +import com.t8rin.imagetoolbox.core.domain.utils.throttleLatest +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.flow.collectLatest +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +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() + ) + } + + @Composable + fun byGridIndex( + index: Int, + size: Int, + crossAxisCount: Int, + forceDefault: Boolean = false, + vertical: Boolean = true + ): Shape { + if ( + index == -1 || + index !in 0 until size || + size <= 1 || + forceDefault || + crossAxisCount <= 1 + ) { + return byIndex( + index = index, + size = size, + forceDefault = forceDefault, + vertical = vertical + ) + } + + val mainAxisIndex = index / crossAxisCount + val crossAxisIndex = index % crossAxisCount + + fun hasItem( + mainAxisIndex: Int, + crossAxisIndex: Int + ): Boolean { + if (crossAxisIndex !in 0 until crossAxisCount) return false + + val itemIndex = mainAxisIndex * crossAxisCount + crossAxisIndex + return itemIndex in 0 until size + } + + val hasTop: Boolean + val hasBottom: Boolean + val hasStart: Boolean + val hasEnd: Boolean + + if (vertical) { + hasTop = hasItem(mainAxisIndex - 1, crossAxisIndex) + hasBottom = hasItem(mainAxisIndex + 1, crossAxisIndex) + hasStart = hasItem(mainAxisIndex, crossAxisIndex - 1) + hasEnd = hasItem(mainAxisIndex, crossAxisIndex + 1) + } else { + hasTop = hasItem(mainAxisIndex, crossAxisIndex - 1) + hasBottom = hasItem(mainAxisIndex, crossAxisIndex + 1) + hasStart = hasItem(mainAxisIndex - 1, crossAxisIndex) + hasEnd = hasItem(mainAxisIndex + 1, crossAxisIndex) + } + + return AutoCornersShape( + topStart = if (!hasTop && !hasStart) { + default.topStart.animate() + } else { + center.topStart.animate() + }, + topEnd = if (!hasTop && !hasEnd) { + default.topEnd.animate() + } else { + center.topEnd.animate() + }, + bottomStart = if (!hasBottom && !hasStart) { + default.bottomStart.animate() + } else { + center.bottomStart.animate() + }, + bottomEnd = if (!hasBottom && !hasEnd) { + default.bottomEnd.animate() + } else { + center.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 bottomStart + @Composable get() = AutoCornersShape( + topEnd = 4.dp, + topStart = 4.dp, + bottomEnd = 4.dp, + bottomStart = 16.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() + + val polygonSquare by lazy { + RoundedPolygon.rectangle( + width = 1f, + height = 1f + ).normalized() + } + + @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 = remember { 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 = remember { lessSpringySpec() }, +): Shape = rememberAnimatedShape( + currentShape = targetValue, + animationSpec = animationSpec +) + +@Composable +fun shapeByInteraction( + shape: Shape, + pressedShape: Shape, + interactionSource: InteractionSource?, + animationSpec: FiniteAnimationSpec = remember { lessSpringySpec() }, + enabled: Boolean = true +): Shape { + if (!enabled || interactionSource == null) return shape + + val settingsState = LocalSettingsState.current + val throttle = settingsState.shapeByInteractionThrottle + + val active by produceState(false, interactionSource, throttle) { + val pressInteractions = mutableListOf() + val focusInteractions = mutableListOf() + + interactionSource.interactions + .map { interaction -> + when (interaction) { + is PressInteraction.Press -> pressInteractions.add(interaction) + is PressInteraction.Release -> pressInteractions.remove(interaction.press) + is PressInteraction.Cancel -> pressInteractions.remove(interaction.press) + is FocusInteraction.Focus -> focusInteractions.add(interaction) + is FocusInteraction.Unfocus -> focusInteractions.remove(interaction.focus) + } + + pressInteractions.isNotEmpty() || focusInteractions.isNotEmpty() + } + .distinctUntilChanged() + .throttleLatest(throttle) + .collectLatest { value = it } + } + + val targetShape = if (active) pressedShape else shape + + return (targetShape as? CornerBasedShape)?.let { + animateShape( + targetValue = it, + animationSpec = animationSpec, + ) + } ?: targetShape +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Shimmer.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Shimmer.kt new file mode 100644 index 0000000..d42d874 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Shimmer.kt @@ -0,0 +1,332 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.compose.animation.core.Animatable +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.material3.MaterialTheme.LocalMaterialTheme +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Stable +import androidx.compose.ui.Modifier +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.Paint +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.takeOrElse +import androidx.compose.ui.node.CompositionLocalConsumerModifierNode +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.ObserverModifierNode +import androidx.compose.ui.node.currentValueOf +import androidx.compose.ui.node.invalidateDraw +import androidx.compose.ui.node.observeReads +import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.lerp +import com.t8rin.imagetoolbox.core.ui.theme.blend +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlin.math.max + +fun Modifier.shimmer( + visible: Boolean, + color: Color = Color.Unspecified, +): Modifier = this then ShimmerElement( + visible = visible, + color = color, +) + +private data class ShimmerElement( + private val visible: Boolean, + private val color: Color, +) : ModifierNodeElement() { + + override fun create(): ShimmerNode = ShimmerNode( + visible = visible, + color = color, + ) + + override fun update(node: ShimmerNode) { + node.update( + visible = visible, + color = color, + ) + } + + override fun InspectorInfo.inspectableProperties() { + name = "shimmer" + properties["visible"] = visible + properties["color"] = color + } +} + +private class ShimmerNode( + private var visible: Boolean, + private var color: Color, +) : Modifier.Node(), + DrawModifierNode, + CompositionLocalConsumerModifierNode, + ObserverModifierNode { + + private val placeholderAlpha = Animatable(if (visible) 1f else 0f) + private val contentAlpha = Animatable(if (visible) 0f else 1f) + private val highlightProgress = Animatable(0f) + + private val paint = Paint() + + private var crossfadeJob: Job? = null + private var shimmerJob: Job? = null + + private var lastSize: Size? = null + private var lastLayoutDirection: LayoutDirection? = null + + private val resolvedColor: Color + get() { + val colorScheme = currentValueOf(LocalMaterialTheme).colorScheme + + return color.takeOrElse { + colorScheme.surfaceColorAtElevation(16.dp) + } + } + + private val resolvedHighlightColor: Color + get() { + val colorScheme = currentValueOf(LocalMaterialTheme).colorScheme + + return Color.Unspecified.blend( + color = colorScheme.primary, + fraction = 0.5f, + ) + } + + override fun onAttach() { + startCrossfade() + startOrStopShimmer() + } + + override fun onDetach() { + crossfadeJob?.cancel() + shimmerJob?.cancel() + } + + fun update( + visible: Boolean, + color: Color, + ) { + val visibleChanged = this.visible != visible + val colorChanged = this.color != color + + this.visible = visible + this.color = color + + if (visibleChanged) { + startCrossfade() + startOrStopShimmer() + } + + if (colorChanged) { + invalidateDraw() + } + } + + override fun onObservedReadsChanged() { + invalidateDraw() + } + + private fun startCrossfade() { + crossfadeJob?.cancel() + crossfadeJob = coroutineScope.launch { + launch { + placeholderAlpha.animateTo( + targetValue = if (visible) 1f else 0f, + animationSpec = spring(), + ) { + invalidateDraw() + startOrStopShimmer() + } + } + + launch { + contentAlpha.animateTo( + targetValue = if (visible) 0f else 1f, + animationSpec = spring(), + ) { + invalidateDraw() + } + } + } + } + + private fun startOrStopShimmer() { + val shouldRun = visible || placeholderAlpha.value >= 0.01f + + if (!shouldRun) { + shimmerJob?.cancel() + shimmerJob = null + return + } + + if (shimmerJob?.isActive == true) return + + shimmerJob = coroutineScope.launch { + highlightProgress.animateTo( + targetValue = 1f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 1700, delayMillis = 200), + repeatMode = RepeatMode.Restart, + ), + ) { + invalidateDraw() + } + } + } + + override fun ContentDrawScope.draw() { + observeReads { + drawShimmer() + } + } + + private fun ContentDrawScope.drawShimmer() { + val contentAlpha = contentAlpha.value + val placeholderAlpha = placeholderAlpha.value + + if (contentAlpha in 0.01f..0.99f) { + paint.alpha = contentAlpha + withLayer(paint) { + drawContent() + } + } else if (contentAlpha >= 0.99f) { + drawContent() + } + + if (placeholderAlpha < 0.01f) return + + val placeholderColor = resolvedColor + val highlight = PlaceholderHighlight.shimmer( + highlightColor = resolvedHighlightColor, + ) + + if (placeholderAlpha in 0.01f..0.99f) { + paint.alpha = placeholderAlpha + withLayer(paint) { + drawPlaceholder( + color = placeholderColor, + highlight = highlight, + ) + } + } else { + drawPlaceholder( + color = placeholderColor, + highlight = highlight, + ) + } + + lastSize = size + lastLayoutDirection = layoutDirection + } + + private fun DrawScope.drawPlaceholder( + color: Color, + highlight: PlaceholderHighlight, + ) { + val progress = highlightProgress.value + + drawRect(color = color) + drawRect( + brush = highlight.brush(progress, size), + alpha = highlight.alpha(progress), + ) + } +} + +private inline fun ContentDrawScope.withLayer( + paint: Paint, + drawBlock: ContentDrawScope.() -> Unit, +) = drawIntoCanvas { canvas -> + canvas.saveLayer(size.toRect(), paint) + drawBlock() + canvas.restore() +} + +@Stable +private interface PlaceholderHighlight { + + fun brush( + @FloatRange(from = 0.0, to = 1.0) progress: Float, + size: Size, + ): Brush + + @FloatRange(from = 0.0, to = 1.0) + fun alpha( + @FloatRange(from = 0.0, to = 1.0) progress: Float, + ): Float + + companion object +} + +private fun PlaceholderHighlight.Companion.shimmer( + highlightColor: Color, + @FloatRange(from = 0.0, to = 1.0) progressForMaxAlpha: Float = 0.6f, +): PlaceholderHighlight = ShimmerHighlight( + highlightColor = highlightColor, + progressForMaxAlpha = progressForMaxAlpha, +) + +private data class ShimmerHighlight( + private val highlightColor: Color, + private val progressForMaxAlpha: Float, +) : PlaceholderHighlight { + + override fun brush( + progress: Float, + size: Size, + ): Brush = Brush.radialGradient( + colors = listOf( + highlightColor.copy(alpha = 0f), + highlightColor, + highlightColor.copy(alpha = 0f), + ), + center = Offset.Zero, + radius = (max(size.width, size.height) * progress * 2f).coerceAtLeast(0.01f), + ) + + override fun alpha(progress: Float): Float = when { + progress <= progressForMaxAlpha -> lerp( + start = 0f, + stop = 1f, + fraction = progress / progressForMaxAlpha, + ) + + else -> lerp( + start = 1f, + stop = 0f, + fraction = (progress - progressForMaxAlpha) / (1f - progressForMaxAlpha), + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Tappable.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Tappable.kt new file mode 100644 index 0000000..248f534 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Tappable.kt @@ -0,0 +1,110 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.SuspendingPointerInputModifierNode +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.node.CompositionLocalConsumerModifierNode +import androidx.compose.ui.node.DelegatingNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.currentValueOf +import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.platform.LocalFocusManager + +fun Modifier.clearFocusOnTap( + enabled: Boolean = true +): Modifier { + if (!enabled) return this + + return this.then(ClearFocusOnTapElement) +} + +private data object ClearFocusOnTapElement : ModifierNodeElement() { + + override fun create(): ClearFocusOnTapNode { + return ClearFocusOnTapNode() + } + + override fun update(node: ClearFocusOnTapNode) { + node.resetPointerInput() + } + + override fun InspectorInfo.inspectableProperties() { + name = "clearFocusOnTap" + properties["enabled"] = true + } +} + +private class ClearFocusOnTapNode : DelegatingNode(), + CompositionLocalConsumerModifierNode { + + private val pointerInputNode = delegate( + SuspendingPointerInputModifierNode { + detectTapGestures( + onTap = { + currentValueOf(LocalFocusManager).clearFocus() + } + ) + } + ) + + fun resetPointerInput() { + pointerInputNode.resetPointerInputHandler() + } +} + +fun Modifier.tappable( + key1: Any? = Unit, + onTap: PointerInputScope.(Offset) -> Unit +): Modifier = pointerInput(key1) { + detectTapGestures { onTap(it) } +} + +@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() + } + ) {} + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/TransparencyChecker.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/TransparencyChecker.kt new file mode 100644 index 0000000..79ca3f3 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/TransparencyChecker.kt @@ -0,0 +1,162 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.LocalMaterialTheme +import androidx.compose.material3.surfaceColorAtElevation +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.drawscope.ContentDrawScope +import androidx.compose.ui.node.CompositionLocalConsumerModifierNode +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.currentValueOf +import androidx.compose.ui.node.invalidateDraw +import androidx.compose.ui.platform.InspectorInfo +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, +): Modifier = this.then( + TransparencyCheckerElement( + colorScheme = colorScheme, + checkerWidth = checkerWidth, + checkerHeight = checkerHeight + ) +) + +private data class TransparencyCheckerElement( + val colorScheme: ColorScheme?, + val checkerWidth: Dp, + val checkerHeight: Dp, +) : ModifierNodeElement() { + + override fun create(): TransparencyCheckerNode { + return TransparencyCheckerNode( + colorScheme = colorScheme, + checkerWidth = checkerWidth, + checkerHeight = checkerHeight + ) + } + + override fun update(node: TransparencyCheckerNode) { + node.colorScheme = colorScheme + node.checkerWidth = checkerWidth + node.checkerHeight = checkerHeight + node.invalidateCache() + } + + override fun InspectorInfo.inspectableProperties() { + name = "transparencyChecker" + properties["colorScheme"] = colorScheme + properties["checkerWidth"] = checkerWidth + properties["checkerHeight"] = checkerHeight + } +} + +private class TransparencyCheckerNode( + var colorScheme: ColorScheme?, + var checkerWidth: Dp, + var checkerHeight: Dp, +) : Modifier.Node(), + DrawModifierNode, + CompositionLocalConsumerModifierNode { + + private var cache: CheckerCache? = null + + override fun ContentDrawScope.draw() { + val scheme = colorScheme ?: currentValueOf(LocalMaterialTheme).colorScheme + val checkerWidthPx = checkerWidth.toPx() + val checkerHeightPx = checkerHeight.toPx() + + if (checkerWidthPx <= 0f || checkerHeightPx <= 0f) { + drawContent() + return + } + + val surface = scheme.surface + val elevatedSurface = scheme.surfaceColorAtElevation(20.dp) + val background = elevatedSurface.blend(surface, 0.5f) + val currentCache = cache?.takeIf { + it.size == size && + it.checkerWidthPx == checkerWidthPx && + it.checkerHeightPx == checkerHeightPx && + it.surface == surface && + it.elevatedSurface == elevatedSurface && + it.background == background + } ?: CheckerCache( + size = size, + checkerWidthPx = checkerWidthPx, + checkerHeightPx = checkerHeightPx, + horizontalSteps = (size.width / checkerWidthPx).toInt(), + verticalSteps = (size.height / checkerHeightPx).toInt(), + surface = surface, + elevatedSurface = elevatedSurface, + background = background + ).also { + cache = it + } + + drawRect(currentCache.background) + + for (y in 0..currentCache.verticalSteps) { + for (x in 0..currentCache.horizontalSteps) { + drawRect( + color = if ((x + y) % 2 == 1) { + currentCache.elevatedSurface + } else { + currentCache.surface + }, + topLeft = Offset( + x = x * currentCache.checkerWidthPx, + y = y * currentCache.checkerHeightPx + ), + size = Size( + width = currentCache.checkerWidthPx, + height = currentCache.checkerHeightPx + ) + ) + } + } + + drawContent() + } + + fun invalidateCache() { + cache = null + invalidateDraw() + } +} + +private data class CheckerCache( + val size: Size, + val checkerWidthPx: Float, + val checkerHeightPx: Float, + val horizontalSteps: Int, + val verticalSteps: Int, + val surface: Color, + val elevatedSurface: Color, + val background: Color +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/WavyShape.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/WavyShape.kt new file mode 100644 index 0000000..dc4a29a --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/WavyShape.kt @@ -0,0 +1,603 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 android.content.res.Resources +import androidx.annotation.IntRange +import androidx.compose.foundation.shape.CornerBasedShape +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.runtime.Immutable +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.RoundRect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +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 kotlin.math.PI +import kotlin.math.abs +import kotlin.math.ceil +import kotlin.math.cos +import kotlin.math.min +import kotlin.math.sin + +@Immutable +class WavyShape( + topStart: CornerSize, + topEnd: CornerSize, + bottomEnd: CornerSize, + bottomStart: CornerSize, + private val waveLength: Dp = DefaultWaveLength, + private val waveHeight: Dp = DefaultWaveHeight, + private val maxPathPoints: Float = MAX_PATH_POINTS, + private val sampleStepsPerWave: Float = SAMPLE_STEPS_PER_WAVE +) : CornerBasedShape( + topStart = topStart, + topEnd = topEnd, + bottomEnd = bottomEnd, + bottomStart = bottomStart +) { + private var outlineCache: WavyShapeOutlineCache? = null + + constructor( + size: CornerSize, + waveLength: Dp = DefaultWaveLength, + waveHeight: Dp = DefaultWaveHeight + ) : this( + topStart = size, + topEnd = size, + bottomEnd = size, + bottomStart = size, + waveLength = waveLength, + waveHeight = waveHeight + ) + + constructor( + size: Dp, + waveLength: Dp = DefaultWaveLength, + waveHeight: Dp = DefaultWaveHeight + ) : this( + size = CornerSize(size), + waveLength = waveLength, + waveHeight = waveHeight + ) + + constructor( + @IntRange(from = 0, to = 100) percent: Int, + waveLength: Dp = DefaultWaveLength, + waveHeight: Dp = DefaultWaveHeight + ) : this( + size = CornerSize(percent), + waveLength = waveLength, + waveHeight = waveHeight + ) + + constructor( + topStart: Dp = 0.dp, + topEnd: Dp = 0.dp, + bottomEnd: Dp = 0.dp, + bottomStart: Dp = 0.dp, + waveLength: Dp = DefaultWaveLength, + waveHeight: Dp = DefaultWaveHeight + ) : this( + topStart = CornerSize(topStart), + topEnd = CornerSize(topEnd), + bottomEnd = CornerSize(bottomEnd), + bottomStart = CornerSize(bottomStart), + waveLength = waveLength, + waveHeight = waveHeight + ) + + @Suppress("UnnecessaryVariable") + override fun createOutline( + size: Size, + topStart: Float, + topEnd: Float, + bottomEnd: Float, + bottomStart: Float, + layoutDirection: LayoutDirection + ): Outline { + val maxRadius = min(size.width, size.height) / 2f + val topLeftRadius = (if (layoutDirection == Ltr) topStart else topEnd) + .coerceIn(0f, maxRadius) + val topRightRadius = (if (layoutDirection == Ltr) topEnd else topStart) + .coerceIn(0f, maxRadius) + val bottomRightRadius = (if (layoutDirection == Ltr) bottomEnd else bottomStart) + .coerceIn(0f, maxRadius) + val bottomLeftRadius = (if (layoutDirection == Ltr) bottomStart else bottomEnd) + .coerceIn(0f, maxRadius) + + val density = Resources.getSystem().displayMetrics.density + outlineCache?.takeIf { + it.size == size && + it.topLeftRadius == topLeftRadius && + it.topRightRadius == topRightRadius && + it.bottomRightRadius == bottomRightRadius && + it.bottomLeftRadius == bottomLeftRadius && + it.density == density + }?.let { return it.outline } + + val minDimension = min(size.width, size.height) + val waveScale = (minDimension / (MIN_ADAPTIVE_SIZE.value * density)) + .coerceIn(MIN_WAVE_SCALE, 1f) + val wavelength = (waveLength.value * density * waveScale).coerceAtLeast(1f) + val amplitude = (waveHeight.value * density * waveScale) + .coerceAtMost(minDimension / 4f) + .coerceAtLeast(0f) + val topWaveAmplitude = sideAmplitude( + firstRadius = topLeftRadius, + secondRadius = topRightRadius, + amplitude = amplitude + ) + val rightWaveAmplitude = sideAmplitude( + firstRadius = topRightRadius, + secondRadius = bottomRightRadius, + amplitude = amplitude + ) + val bottomWaveAmplitude = sideAmplitude( + firstRadius = bottomRightRadius, + secondRadius = bottomLeftRadius, + amplitude = amplitude + ) + val leftWaveAmplitude = sideAmplitude( + firstRadius = bottomLeftRadius, + secondRadius = topLeftRadius, + amplitude = amplitude + ) + + if (amplitude <= 0f || size.width <= 0f || size.height <= 0f) { + return roundedOutline( + size = size, + topLeftRadius = topLeftRadius, + topRightRadius = topRightRadius, + bottomRightRadius = bottomRightRadius, + bottomLeftRadius = bottomLeftRadius + ).cache( + size = size, + topLeftRadius = topLeftRadius, + topRightRadius = topRightRadius, + bottomRightRadius = bottomRightRadius, + bottomLeftRadius = bottomLeftRadius, + density = density + ) + } + + if ( + topWaveAmplitude <= 0f && + rightWaveAmplitude <= 0f && + bottomWaveAmplitude <= 0f && + leftWaveAmplitude <= 0f + ) { + return roundedOutline( + size = size, + topLeftRadius = topLeftRadius, + topRightRadius = topRightRadius, + bottomRightRadius = bottomRightRadius, + bottomLeftRadius = bottomLeftRadius + ).cache( + size = size, + topLeftRadius = topLeftRadius, + topRightRadius = topRightRadius, + bottomRightRadius = bottomRightRadius, + bottomLeftRadius = bottomLeftRadius, + density = density + ) + } + + val topLeftCenterRadius = (topLeftRadius - amplitude).coerceAtLeast(0f) + val topRightCenterRadius = (topRightRadius - amplitude).coerceAtLeast(0f) + val bottomRightCenterRadius = (bottomRightRadius - amplitude).coerceAtLeast(0f) + val bottomLeftCenterRadius = (bottomLeftRadius - amplitude).coerceAtLeast(0f) + val left = leftWaveAmplitude + val top = topWaveAmplitude + val right = size.width - rightWaveAmplitude + val bottom = size.height - bottomWaveAmplitude + val topLineStartX = if (topLeftRadius > 0f) { + left + topLeftCenterRadius + } else 0f + val topLineEndX = if (topRightRadius > 0f) { + right - topRightCenterRadius + } else size.width + val rightLineStartY = if (topRightRadius > 0f) { + top + topRightCenterRadius + } else 0f + val rightLineEndY = if (bottomRightRadius > 0f) { + bottom - bottomRightCenterRadius + } else size.height + val bottomLineStartX = if (bottomRightRadius > 0f) { + right - bottomRightCenterRadius + } else size.width + val bottomLineEndX = if (bottomLeftRadius > 0f) { + left + bottomLeftCenterRadius + } else 0f + val leftLineStartY = if (bottomLeftRadius > 0f) { + bottom - bottomLeftCenterRadius + } else size.height + val leftLineEndY = if (topLeftRadius > 0f) { + top + topLeftCenterRadius + } else 0f + val topLength = (topLineEndX - topLineStartX) + .coerceAtLeast(0f) + val rightLength = (rightLineEndY - rightLineStartY) + .coerceAtLeast(0f) + val bottomLength = (bottomLineStartX - bottomLineEndX) + .coerceAtLeast(0f) + val leftLength = (leftLineStartY - leftLineEndY) + .coerceAtLeast(0f) + val topLeftArcLength = (PI.toFloat() * topLeftCenterRadius) / 2f + val topRightArcLength = (PI.toFloat() * topRightCenterRadius) / 2f + val bottomRightArcLength = (PI.toFloat() * bottomRightCenterRadius) / 2f + val bottomLeftArcLength = (PI.toFloat() * bottomLeftCenterRadius) / 2f + val perimeter = topLength + rightLength + bottomLength + leftLength + + topLeftArcLength + topRightArcLength + bottomRightArcLength + bottomLeftArcLength + + if (perimeter <= 0f) { + return roundedOutline( + size = size, + topLeftRadius = topLeftRadius, + topRightRadius = topRightRadius, + bottomRightRadius = bottomRightRadius, + bottomLeftRadius = bottomLeftRadius + ).cache( + size = size, + topLeftRadius = topLeftRadius, + topRightRadius = topRightRadius, + bottomRightRadius = bottomRightRadius, + bottomLeftRadius = bottomLeftRadius, + density = density + ) + } + + val waves = ceil(perimeter / wavelength).toInt().coerceAtLeast(1) + val actualWavelength = perimeter / waves + val sampleStep = maxOf( + actualWavelength / sampleStepsPerWave, + perimeter / maxPathPoints, + 1f + ) + + val path = Path().apply { + var distance = 0f + var hasPreviousPoint = false + var firstPointX = 0f + var firstPointY = 0f + var previousPointX = 0f + var previousPointY = 0f + + fun addPoint( + x: Float, + y: Float, + normalX: Float, + normalY: Float, + pointDistance: Float, + waveAmplitude: Float + ) { + val wave = waveAmplitude * sin( + 2f * PI.toFloat() * pointDistance / actualWavelength + ) + val pointX = x + normalX * wave + val pointY = y + normalY * wave + + if (!hasPreviousPoint) { + moveTo(pointX, pointY) + hasPreviousPoint = true + firstPointX = pointX + firstPointY = pointY + } else { + quadraticTo( + x1 = previousPointX, + y1 = previousPointY, + x2 = (previousPointX + pointX) / 2f, + y2 = (previousPointY + pointY) / 2f + ) + } + + previousPointX = pointX + previousPointY = pointY + } + + fun addLine( + fromX: Float, + fromY: Float, + toX: Float, + toY: Float, + normalX: Float, + normalY: Float, + length: Float, + waveAmplitude: Float + ) { + val steps = ceil(length / sampleStep).toInt().coerceAtLeast(1) + + val firstIndex = if (!hasPreviousPoint) 0 else 1 + for (index in firstIndex..steps) { + val progress = index / steps.toFloat() + addPoint( + x = fromX + (toX - fromX) * progress, + y = fromY + (toY - fromY) * progress, + normalX = normalX, + normalY = normalY, + pointDistance = distance + length * progress, + waveAmplitude = waveAmplitude + ) + } + distance += length + } + + fun addArc( + centerX: Float, + centerY: Float, + radius: Float, + startAngle: Float, + sweepAngle: Float, + waveAmplitude: Float + ) { + val length = abs(sweepAngle) * radius + val steps = ceil(length / sampleStep).toInt().coerceAtLeast(1) + + val firstIndex = if (!hasPreviousPoint) 0 else 1 + for (index in firstIndex..steps) { + val progress = index / steps.toFloat() + val angle = startAngle + sweepAngle * progress + val normalX = cos(angle) + val normalY = sin(angle) + addPoint( + x = centerX + radius * normalX, + y = centerY + radius * normalY, + normalX = normalX, + normalY = normalY, + pointDistance = distance + length * progress, + waveAmplitude = waveAmplitude + ) + } + distance += length + } + + addLine( + fromX = topLineStartX, + fromY = top, + toX = topLineEndX, + toY = top, + normalX = 0f, + normalY = -1f, + length = topLength, + waveAmplitude = topWaveAmplitude + ) + addArc( + centerX = right - topRightCenterRadius, + centerY = top + topRightCenterRadius, + radius = topRightCenterRadius, + startAngle = -PI.toFloat() / 2f, + sweepAngle = PI.toFloat() / 2f, + waveAmplitude = amplitude + ) + addLine( + fromX = right, + fromY = rightLineStartY, + toX = right, + toY = rightLineEndY, + normalX = 1f, + normalY = 0f, + length = rightLength, + waveAmplitude = rightWaveAmplitude + ) + addArc( + centerX = right - bottomRightCenterRadius, + centerY = bottom - bottomRightCenterRadius, + radius = bottomRightCenterRadius, + startAngle = 0f, + sweepAngle = PI.toFloat() / 2f, + waveAmplitude = amplitude + ) + addLine( + fromX = bottomLineStartX, + fromY = bottom, + toX = bottomLineEndX, + toY = bottom, + normalX = 0f, + normalY = 1f, + length = bottomLength, + waveAmplitude = bottomWaveAmplitude + ) + addArc( + centerX = left + bottomLeftCenterRadius, + centerY = bottom - bottomLeftCenterRadius, + radius = bottomLeftCenterRadius, + startAngle = PI.toFloat() / 2f, + sweepAngle = PI.toFloat() / 2f, + waveAmplitude = amplitude + ) + addLine( + fromX = left, + fromY = leftLineStartY, + toX = left, + toY = leftLineEndY, + normalX = -1f, + normalY = 0f, + length = leftLength, + waveAmplitude = leftWaveAmplitude + ) + addArc( + centerX = left + topLeftCenterRadius, + centerY = top + topLeftCenterRadius, + radius = topLeftCenterRadius, + startAngle = PI.toFloat(), + sweepAngle = PI.toFloat() / 2f, + waveAmplitude = amplitude + ) + quadraticTo( + x1 = previousPointX, + y1 = previousPointY, + x2 = firstPointX, + y2 = firstPointY + ) + close() + } + return Outline.Generic(path).cache( + size = size, + topLeftRadius = topLeftRadius, + topRightRadius = topRightRadius, + bottomRightRadius = bottomRightRadius, + bottomLeftRadius = bottomLeftRadius, + density = density + ) + } + + private fun sideAmplitude( + firstRadius: Float, + secondRadius: Float, + amplitude: Float + ): Float { + return if (firstRadius > 0f || secondRadius > 0f) amplitude else 0f + } + + private fun roundedOutline( + size: Size, + topLeftRadius: Float, + topRightRadius: Float, + bottomRightRadius: Float, + bottomLeftRadius: Float + ): Outline { + return Outline.Rounded( + RoundRect( + left = 0f, + top = 0f, + right = size.width, + bottom = size.height, + topLeftCornerRadius = CornerRadius(topLeftRadius), + topRightCornerRadius = CornerRadius(topRightRadius), + bottomRightCornerRadius = CornerRadius(bottomRightRadius), + bottomLeftCornerRadius = CornerRadius(bottomLeftRadius) + ) + ) + } + + private fun Outline.cache( + size: Size, + topLeftRadius: Float, + topRightRadius: Float, + bottomRightRadius: Float, + bottomLeftRadius: Float, + density: Float + ): Outline { + outlineCache = WavyShapeOutlineCache( + size = size, + topLeftRadius = topLeftRadius, + topRightRadius = topRightRadius, + bottomRightRadius = bottomRightRadius, + bottomLeftRadius = bottomLeftRadius, + density = density, + outline = this + ) + return this + } + + override fun copy( + topStart: CornerSize, + topEnd: CornerSize, + bottomEnd: CornerSize, + bottomStart: CornerSize + ): WavyShape { + return WavyShape( + topStart = topStart, + topEnd = topEnd, + bottomEnd = bottomEnd, + bottomStart = bottomStart, + waveLength = waveLength, + waveHeight = waveHeight, + maxPathPoints = maxPathPoints, + sampleStepsPerWave = sampleStepsPerWave + ) + } + + fun copy( + topStart: CornerSize = this.topStart, + topEnd: CornerSize = this.topEnd, + bottomEnd: CornerSize = this.bottomEnd, + bottomStart: CornerSize = this.bottomStart, + waveLength: Dp = this.waveLength, + waveHeight: Dp = this.waveHeight + ): WavyShape { + return WavyShape( + topStart = topStart, + topEnd = topEnd, + bottomEnd = bottomEnd, + bottomStart = bottomStart, + waveLength = waveLength, + waveHeight = waveHeight, + maxPathPoints = maxPathPoints, + sampleStepsPerWave = sampleStepsPerWave + ) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is WavyShape) 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 (waveLength != other.waveLength) return false + if (waveHeight != other.waveHeight) return false + if (maxPathPoints != other.maxPathPoints) return false + if (sampleStepsPerWave != other.sampleStepsPerWave) 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 + waveLength.hashCode() + result = 31 * result + waveHeight.hashCode() + result = 31 * result + maxPathPoints.hashCode() + result = 31 * result + sampleStepsPerWave.hashCode() + return result + } + + override fun toString(): String { + return "WavyShape(topStart=$topStart, topEnd=$topEnd, bottomEnd=$bottomEnd, " + + "bottomStart=$bottomStart, waveLength=$waveLength, waveHeight=$waveHeight, " + + "maxPathPoints=$maxPathPoints, sampleStepsPerWave=$sampleStepsPerWave)" + } + + companion object { + val DefaultWaveLength = 12.dp + val DefaultWaveHeight = 0.9.dp + private const val MAX_PATH_POINTS = 360f + private const val SAMPLE_STEPS_PER_WAVE = 8f + private const val MIN_WAVE_SCALE = 0.42f + private val MIN_ADAPTIVE_SIZE = 48.dp + } +} + +private data class WavyShapeOutlineCache( + val size: Size, + val topLeftRadius: Float, + val topRightRadius: Float, + val bottomRightRadius: Float, + val bottomLeftRadius: Float, + val density: Float, + val outline: Outline +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/WithModifier.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/WithModifier.kt new file mode 100644 index 0000000..98356df --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/WithModifier.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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() } \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/AnimatedBorder.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/AnimatedBorder.kt new file mode 100644 index 0000000..9035174 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/AnimatedBorder.kt @@ -0,0 +1,107 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/BoxAnimatedVisibility.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/BoxAnimatedVisibility.kt new file mode 100644 index 0000000..f3a9db1 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/BoxAnimatedVisibility.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/ColorWithNameItem.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/ColorWithNameItem.kt new file mode 100644 index 0000000..914e365 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/ColorWithNameItem.kt @@ -0,0 +1,278 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Bookmark +import com.t8rin.imagetoolbox.core.resources.icons.BookmarkRemove +import com.t8rin.imagetoolbox.core.resources.icons.ContentCopy +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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: (name: String) -> 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(colorName) + } + ) + } 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(colorName) + } + ) + .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.Bookmark + }, + 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() + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/DrawLockScreenOrientation.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/DrawLockScreenOrientation.kt new file mode 100644 index 0000000..00c63f3 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/DrawLockScreenOrientation.kt @@ -0,0 +1,86 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/EmojiItem.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/EmojiItem.kt new file mode 100644 index 0000000..5e3e75e --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/EmojiItem.kt @@ -0,0 +1,171 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.LocalTextStyle +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +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.FilterQuality +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.layout.layout +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil3.compose.AsyncImage +import com.lottiefiles.dotlottie.core.compose.runtime.DotLottieController +import com.lottiefiles.dotlottie.core.compose.runtime.DotLottiePlayerState +import com.lottiefiles.dotlottie.core.compose.ui.DotLottieAnimation +import com.lottiefiles.dotlottie.core.util.DotLottieSource +import com.t8rin.imagetoolbox.core.domain.utils.throttleLatest +import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape +import com.t8rin.imagetoolbox.core.ui.widget.modifier.shimmer +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.withContext + +@Composable +fun EmojiItem( + emoji: String?, + animatedEmoji: String? = null, + modifier: Modifier = Modifier, + fontSize: TextUnit = LocalTextStyle.current.fontSize, + filterQuality: FilterQuality = FilterQuality.High, + animateEmojiChange: Boolean = true, + containerColor: Color? = null, + shape: Shape? = null, + contentPadding: Dp? = null, + onNoEmoji: (@Composable () -> Unit)? = null +) { + key(animatedEmoji) { + val content: @Composable (emoji: String?) -> Unit = { currentEmoji -> + currentEmoji?.let { + var shimmering by remember(currentEmoji) { + mutableStateOf(true) + } + + val emojiModifier = remember(shimmering) { + Modifier + .then( + if (fontSize > 0.sp) { + Modifier.layout { measurable, constraints -> + val size = fontSize.roundToPx() + 4.dp.roundToPx() + + val placeable = measurable.measure( + constraints.copy( + minWidth = size, + minHeight = size, + maxWidth = size, + maxHeight = size + ) + ) + + layout(size, size) { + placeable.place(0, 0) + } + } + } else { + Modifier.fillMaxSize() + } + ) + .clip(shape ?: CloverShape) + .then( + if (containerColor != null) { + Modifier.background(containerColor) + } else { + Modifier + } + ) + .shimmer(shimmering) + .padding(2.dp + (contentPadding ?: 0.dp)) + } + + if (animatedEmoji != null) { + val controller = remember { DotLottieController() } + + LaunchedEffect(controller) { + withContext(Dispatchers.Default) { + controller.currentState.throttleLatest(100).collectLatest { + shimmering = it != DotLottiePlayerState.PLAYING + } + } + } + + DotLottieAnimation( + source = remember(animatedEmoji) { + DotLottieSource.Asset(animatedEmoji.removePrefix("file:///android_asset/")) + }, + loop = true, + autoplay = true, + controller = controller, + modifier = emojiModifier + ) + } else { + AsyncImage( + model = currentEmoji, + onLoading = { + shimmering = true + }, + onSuccess = { + shimmering = false + }, + filterQuality = filterQuality, + contentDescription = null, + modifier = emojiModifier + ) + } + } ?: onNoEmoji?.invoke() + } + + if (animateEmojiChange) { + AnimatedContent( + targetState = emoji, + modifier = modifier, + transitionSpec = { + fadeIn() + scaleIn(initialScale = 0.85f) togetherWith fadeOut() + scaleOut( + targetScale = 0.85f + ) + } + ) { currentEmoji -> + content(currentEmoji) + } + } else { + Box(modifier) { + content(emoji) + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/ExpandableItem.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/ExpandableItem.kt new file mode 100644 index 0000000..cdc7051 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/ExpandableItem.kt @@ -0,0 +1,146 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.KeyboardArrowDown +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 +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/FeatureNotAvailableContent.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/FeatureNotAvailableContent.kt new file mode 100644 index 0000000..b20ed2b --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/FeatureNotAvailableContent.kt @@ -0,0 +1,130 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.ArrowBack +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.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 + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/FontSelectionItem.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/FontSelectionItem.kt new file mode 100644 index 0000000..6e42716 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/FontSelectionItem.kt @@ -0,0 +1,83 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.border +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonChecked +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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)})" +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/GradientEdge.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/GradientEdge.kt new file mode 100644 index 0000000..b70733b --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/GradientEdge.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + ) + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/InfoContainer.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/InfoContainer.kt new file mode 100644 index 0000000..219306e --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/InfoContainer.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.Info +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 + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/LinkPreviewCard.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/LinkPreviewCard.kt new file mode 100644 index 0000000..e6d9d4c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/LinkPreviewCard.kt @@ -0,0 +1,175 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Language +import com.t8rin.imagetoolbox.core.resources.icons.Link +import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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.Rounded.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.Rounded.Language, + contentDescription = null, + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.surface) + .padding(12.dp) + .clip(MaterialStarShape) + .background( + MaterialTheme.colorScheme.tertiaryContainer + .copy(0.5f) + .compositeOverSafe(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 + ) + ) + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/LinkPreviewList.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/LinkPreviewList.kt new file mode 100644 index 0000000..b75efb1 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/LinkPreviewList.kt @@ -0,0 +1,151 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.KeyboardArrowDown +import com.t8rin.imagetoolbox.core.resources.icons.Link +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 + ) + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/QrPainter.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/QrPainter.kt new file mode 100644 index 0000000..58db3c4 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/QrPainter.kt @@ -0,0 +1,829 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/SearchBar.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/SearchBar.kt new file mode 100644 index 0000000..79a42f7 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/SearchBar.kt @@ -0,0 +1,114 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + ) + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/SwipeToReveal.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/SwipeToReveal.kt new file mode 100644 index 0000000..5b18295 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/SwipeToReveal.kt @@ -0,0 +1,234 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.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, + maxAmountOfOverflowPx = with(LocalDensity.current) { maxAmountOfOverflow.toPx() }, + layoutDirection = LocalLayoutDirection.current, + directions = directions, + interactionSource = interactionSource + ) + } else Modifier + ) + ) { + swipeableContent() + } + } +} + + +fun Modifier.revealSwipeable( + maxRevealPx: Float, + maxAmountOfOverflowPx: Float, + layoutDirection: LayoutDirection, + directions: Set, + state: RevealState, + enabled: Boolean = true, + interactionSource: MutableInteractionSource +): Modifier { + val isRtl = layoutDirection == 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 + + return 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, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/ToastHost.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/ToastHost.kt new file mode 100644 index 0000000..5b656af --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/ToastHost.kt @@ -0,0 +1,489 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.icons.Error +import com.t8rin.imagetoolbox.core.resources.icons.Folder +import com.t8rin.imagetoolbox.core.resources.icons.Memory +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.extractMessage +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( + throwable: Throwable +) = showFailureToast( + message = throwable.extractMessage(), + icon = if (throwable is OutOfMemoryError) { + Icons.Outlined.Memory + } else { + null + } +) + +suspend fun ToastHostState.showFailureToast( + message: String, + icon: ImageVector? = null +) = showToast( + message = message, + icon = icon ?: Icons.Outlined.Error, + duration = ToastDuration.Long +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/TopAppBarEmoji.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/TopAppBarEmoji.kt new file mode 100644 index 0000000..d19a75c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/TopAppBarEmoji.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.runtime.remember +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.imagetoolbox.core.resources.emoji.Emoji +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( + allowMultiple: Boolean = true, + containerColor: Color? = null, + shape: Shape? = null, + contentPadding: Dp? = null +) { + val settingsState = LocalSettingsState.current + + Box( + modifier = Modifier + .padding(end = 12.dp) + .scaleOnTap { + AppToastHost.showConfetti() + } + ) { + Row { + val count = if (allowMultiple) 5 else 1 + + val emoji = remember(settingsState.selectedEmoji) { + settingsState.selectedEmoji?.toString() + } + val animatedEmoji = + remember(settingsState.selectedEmoji, settingsState.useAnimatedEmojis) { + settingsState.selectedEmoji + ?.takeIf { settingsState.useAnimatedEmojis } + ?.let(Emoji::animatedIconFor) + ?.toString() + } + + repeat(count) { index -> + AnimatedVisibility( + visible = settingsState.emojisCount > index, + enter = fadeIn() + slideInHorizontally(), + exit = fadeOut() + slideOutHorizontally() + ) { + EmojiItem( + emoji = emoji, + animatedEmoji = animatedEmoji, + fontSize = MaterialTheme.typography.headlineLarge.fontSize, + modifier = if (index != count - 1) { + Modifier.padding(end = 2.dp) + } else { + Modifier + }, + containerColor = containerColor, + shape = shape, + contentPadding = contentPadding + ) + } + } + } + } +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/ZoomBadge.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/ZoomBadge.kt new file mode 100644 index 0000000..6ed72c1 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/ZoomBadge.kt @@ -0,0 +1,77 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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.graphics.Color +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.domain.utils.trimTrailingZero +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.ui.theme.blend +import com.t8rin.imagetoolbox.core.ui.theme.onPrimaryContainerFixed +import com.t8rin.imagetoolbox.core.ui.theme.primaryContainerFixed +import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults + +@Composable +fun ZoomBadge( + zoomLevel: Float, + modifier: Modifier, + showAfter: Float = 1f, + containerColor: Color = MaterialTheme.colorScheme.primaryContainerFixed.blend(Color.Black), + contentColor: Color = MaterialTheme.colorScheme.onPrimaryContainerFixed, + shape: Shape = ShapeDefaults.extraSmall +) { + BoxAnimatedVisibility( + visible = zoomLevel > showAfter, + modifier = modifier + .padding( + horizontal = 24.dp, + vertical = 8.dp + ), + enter = scaleIn() + fadeIn(), + exit = scaleOut() + fadeOut() + ) { + val level = remember(zoomLevel) { + zoomLevel.roundToTwoDigits().toString().trimTrailingZero() + } + Text( + text = stringResource(R.string.zoom) + " ${level}x", + modifier = Modifier + .background( + color = containerColor, + shape = shape + ) + .padding(horizontal = 8.dp, vertical = 4.dp), + style = MaterialTheme.typography.labelLarge, + color = contentColor + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/palette_selection/PaletteMappings.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/palette_selection/PaletteMappings.kt new file mode 100644 index 0000000..1659ba9 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/palette_selection/PaletteMappings.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/palette_selection/PaletteStyleSelection.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/palette_selection/PaletteStyleSelection.kt new file mode 100644 index 0000000..9718048 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/palette_selection/PaletteStyleSelection.kt @@ -0,0 +1,115 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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) + } + ) + } + } + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/palette_selection/PaletteStyleSelectionItem.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/palette_selection/PaletteStyleSelectionItem.kt new file mode 100644 index 0000000..9362f08 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/palette_selection/PaletteStyleSelectionItem.kt @@ -0,0 +1,68 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.border +import androidx.compose.foundation.layout.fillMaxWidth +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.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonChecked +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceItem.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceItem.kt new file mode 100644 index 0000000..06cda8a --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceItem.kt @@ -0,0 +1,173 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceItemOverload.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceItemOverload.kt new file mode 100644 index 0000000..31a53cc --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceItemOverload.kt @@ -0,0 +1,220 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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, + badgeAlignment: Alignment.Vertical = Alignment.Top, + 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, + composeColorOnTopOfBackground = containerColor != Color.Transparent, + 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( + verticalAlignment = badgeAlignment + ) { + 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() + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceRow.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceRow.kt new file mode 100644 index 0000000..78136a9 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceRow.kt @@ -0,0 +1,290 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceRowSwitch.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceRowSwitch.kt new file mode 100644 index 0000000..13fbd6d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceRowSwitch.kt @@ -0,0 +1,176 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.Check +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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) + .compositeOverSafe(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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/ScreenPreference.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/ScreenPreference.kt new file mode 100644 index 0000000..79a937f --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/ScreenPreference.kt @@ -0,0 +1,115 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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() + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/saver/OneTimeEffect.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/saver/OneTimeEffect.kt new file mode 100644 index 0000000..3adb4e9 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/saver/OneTimeEffect.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/saver/Savers.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/saver/Savers.kt new file mode 100644 index 0000000..80cd09e --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/saver/Savers.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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]) + } +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/AddExifSheet.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/AddExifSheet.kt new file mode 100644 index 0000000..8087e31 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/AddExifSheet.kt @@ -0,0 +1,298 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.AddCircle +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.Exif +import com.t8rin.imagetoolbox.core.resources.icons.RemoveCircle +import com.t8rin.imagetoolbox.core.resources.icons.Search +import com.t8rin.imagetoolbox.core.resources.icons.SearchOff +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.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.Outlined.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.Outlined.RemoveCircle + } else { + Icons.Outlined.AddCircle + } + } + } + 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.Outlined.SearchOff, + contentDescription = null, + modifier = Modifier + .weight(2f) + .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) + .fillMaxSize() + ) + Spacer(Modifier.weight(1f)) + } + } + } + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/DefaultUpdateSheet.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/DefaultUpdateSheet.kt new file mode 100644 index 0000000..a5c7ee0 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/DefaultUpdateSheet.kt @@ -0,0 +1,91 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.ReleaseAlert +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.ReleaseAlert + ) + }, + 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)) + } + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/EditExifSheet.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/EditExifSheet.kt new file mode 100644 index 0000000..7dfef54 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/EditExifSheet.kt @@ -0,0 +1,303 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.AddCircle +import com.t8rin.imagetoolbox.core.resources.icons.DeleteSweep +import com.t8rin.imagetoolbox.core.resources.icons.Exif +import com.t8rin.imagetoolbox.core.resources.icons.RemoveCircle +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.Outlined.AddCircle, + 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.Outlined.RemoveCircle, + 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)) + } + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/EmojiSelectionSheet.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/EmojiSelectionSheet.kt new file mode 100644 index 0000000..6145468 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/EmojiSelectionSheet.kt @@ -0,0 +1,441 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +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.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.FilterQuality +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.emoji.Emoji +import com.t8rin.imagetoolbox.core.resources.icons.Face5 +import com.t8rin.imagetoolbox.core.resources.icons.Face6 +import com.t8rin.imagetoolbox.core.resources.icons.KeyboardArrowDown +import com.t8rin.imagetoolbox.core.resources.icons.MotionMode +import com.t8rin.imagetoolbox.core.resources.icons.Shuffle +import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape +import com.t8rin.imagetoolbox.core.resources.shapes.SquircleShape +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState +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.other.TopAppBarEmoji +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults +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 +import kotlinx.coroutines.launch +import kotlin.random.Random + +@Composable +fun EmojiSelectionSheet( + selectedEmojiIndex: Int?, + onEmojiPicked: (Int) -> Unit, + visible: Boolean, + icon: ImageVector = Icons.Rounded.Face5, + onDismiss: () -> Unit, + useAnimatedEmojis: Boolean = LocalSettingsState.current.useAnimatedEmojis +) { + val state = rememberLazyGridState() + val emojiWithCategories = Emoji.allIconsCategorized + val allEmojis = Emoji.allIcons + val emojiIndices = remember(allEmojis) { + allEmojis.withIndex().associate { (index, emoji) -> + emoji to index + } + } + val animatedEmojiPaths = remember(allEmojis) { + allEmojis.associateWith { emoji -> + Emoji.animatedIconFor(emoji)?.toString() + } + } + + var expandedCategories by rememberSaveable(visible) { + mutableStateOf(emptyList()) + } + + LaunchedEffect(visible) { + delay(300) + + expandedCategories = if ((selectedEmojiIndex ?: -1) >= 0) { + emojiWithCategories.find { (_, _, emojis) -> + emojis.any { emoji -> + emojiIndices[emoji] == selectedEmojiIndex + } + }?.title?.let(::listOf) ?: emptyList() + } else emptyList() + + delay(300) + if ((selectedEmojiIndex ?: -1) >= 0) { + var count = 0 + val item = emojiWithCategories.find { (_, _, emojis) -> + val index = emojis.indexOfFirst { emoji -> + emojiIndices[emoji] == selectedEmojiIndex + } + if (index >= 0) { + count = index + 1 + true + } else false + } ?: return@LaunchedEffect + val index = emojiWithCategories.indexOf(item) + + state.animateScrollToItem( + index = index, + scrollOffset = 60 * count / 6 + ) + } + } + + val emojiEnabled = 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 = { + if (emojiEnabled) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding( + horizontal = 16.dp, + vertical = 14.dp + ) + ) { + TopAppBarEmoji( + allowMultiple = false, + containerColor = MaterialTheme.colorScheme.surface, + shape = SquircleShape, + contentPadding = 4.dp + ) + Text( + text = stringResource(R.string.emoji), + style = PreferenceItemDefaults.TitleFontStyle, + modifier = Modifier.offset(x = (-8).dp) + ) + } + } else { + TitleItem( + text = stringResource(R.string.emoji), + icon = icon + ) + } + }, + visible = visible, + onDismiss = { + if (!it) onDismiss() + } + ) { + val alphaState = 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 + ) + } + } + + 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( + enabled = emojiEnabled, + interactionSource = interactionSource, + indication = LocalIndication.current + ) { + expandedCategories = if (expanded) { + expandedCategories - title + } else expandedCategories + title + } + .padding(16.dp), + text = stringResource(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) { + items( + items = emojis, + key = { it } + ) { emoji -> + val index = emojiIndices[emoji] ?: -1 + val selected = 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(enabled = emojiEnabled) { + onEmojiPicked(index) + }, + contentAlignment = Alignment.Center + ) { + val animatedEmoji = animatedEmojiPaths[emoji] + EmojiItem( + emoji = emoji.toString(), + animatedEmoji = animatedEmoji.takeIf { + useAnimatedEmojis && emojiEnabled + }, + fontSize = MaterialTheme.typography.headlineLarge.fontSize, + filterQuality = FilterQuality.Medium, + animateEmojiChange = false + ) + if (useAnimatedEmojis && animatedEmoji != null) { + Icon( + imageVector = Icons.Outlined.MotionMode, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary.copy(0.6f), + modifier = Modifier + .align(Alignment.TopEnd) + .offset((-6).dp, 6.dp) + .size(12.dp) + ) + } + } + } + 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 + } + } + } + } +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/PickImageFromUrisSheet.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/PickImageFromUrisSheet.kt new file mode 100644 index 0000000..cd61f6a --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/PickImageFromUrisSheet.kt @@ -0,0 +1,229 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.PhotoLibrary +import com.t8rin.imagetoolbox.core.resources.icons.RemoveCircle +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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.Outlined.RemoveCircle, + 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() + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/ProcessImagesPreferenceSheet.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/ProcessImagesPreferenceSheet.kt new file mode 100644 index 0000000..e9ade70 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/ProcessImagesPreferenceSheet.kt @@ -0,0 +1,448 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.Context +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.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.rotate +import androidx.compose.ui.graphics.vector.ImageVector +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Album +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.Extension +import com.t8rin.imagetoolbox.core.resources.icons.File +import com.t8rin.imagetoolbox.core.resources.icons.GifBox +import com.t8rin.imagetoolbox.core.resources.icons.Image +import com.t8rin.imagetoolbox.core.resources.icons.KeyboardArrowDown +import com.t8rin.imagetoolbox.core.resources.icons.LayersSearchOutline +import com.t8rin.imagetoolbox.core.resources.icons.Pdf +import com.t8rin.imagetoolbox.core.resources.icons.SearchOff +import com.t8rin.imagetoolbox.core.resources.icons.SettingsBackupRestore +import com.t8rin.imagetoolbox.core.resources.icons.TextFields +import com.t8rin.imagetoolbox.core.resources.icons.Visibility +import com.t8rin.imagetoolbox.core.resources.icons.VisibilityOff +import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview +import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen +import com.t8rin.imagetoolbox.core.ui.utils.navigation.matchesSearchQuery +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.sheets.ProcessImagesSheetTitle.Companion.processImagesSheetTitle +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 com.t8rin.imagetoolbox.core.utils.appContext + +@Composable +fun ProcessImagesPreferenceSheet( + uris: List, + extraDataType: ExtraDataType? = null, + visible: Boolean, + onDismiss: () -> Unit, + onNavigate: (Screen) -> Unit +) { + var isSearching by rememberSaveable { + mutableStateOf(false) + } + var searchKeyword by rememberSaveable(isSearching) { + mutableStateOf("") + } + + val (rawScreenList, hiddenScreenList) = uris.screenList(extraDataType).value + val sheetTitle = remember(extraDataType, uris.size) { + appContext.processImagesSheetTitle( + extraDataType = extraDataType, + uriCount = uris.size + ) + } + + val urisCorrespondingScreens by remember(hiddenScreenList, rawScreenList, searchKeyword) { + derivedStateOf { + if (searchKeyword.isNotBlank()) { + (rawScreenList + hiddenScreenList).filter { + it.matchesSearchQuery(searchKeyword) + } + } 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.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 = sheetTitle.title, + icon = sheetTitle.icon + ) + 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 = { + 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 || extraDataType == ExtraDataType.Pdf) { + item( + span = StaggeredGridItemSpan.FullLine + ) { + UrisCarousel(uris) + } + } + items( + items = urisCorrespondingScreens, + key = { it.toString() } + ) { screen -> + ScreenPreference( + screen = screen, + navigate = { + onNavigate(screen) + onDismiss() + } + ) + } + + 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 = { + onNavigate(screen) + onDismiss() + } + ) + } + } + } + } + } 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.Outlined.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() + } + ) +} + +private data class ProcessImagesSheetTitle( + val title: String, + val icon: ImageVector +) { + companion object { + fun Context.processImagesSheetTitle( + extraDataType: ExtraDataType?, + uriCount: Int + ): ProcessImagesSheetTitle = when (extraDataType) { + null -> if (uriCount > 1) { + ProcessImagesSheetTitle( + title = getString(R.string.images, uriCount), + icon = Icons.Rounded.Image + ) + } else { + ProcessImagesSheetTitle( + title = getString(R.string.image), + icon = Icons.Rounded.Image + ) + } + + ExtraDataType.Audio -> ProcessImagesSheetTitle( + title = getString(R.string.audio), + icon = Icons.Rounded.Album + ) + + is ExtraDataType.Backup -> ProcessImagesSheetTitle( + title = getString(R.string.backup), + icon = Icons.Rounded.SettingsBackupRestore + ) + + ExtraDataType.File -> ProcessImagesSheetTitle( + title = getString(R.string.file), + icon = Icons.Rounded.File + ) + + ExtraDataType.Gif -> ProcessImagesSheetTitle( + title = "GIF", + icon = Icons.Rounded.GifBox + ) + + ExtraDataType.Pdf -> ProcessImagesSheetTitle( + title = "PDF", + icon = Icons.Rounded.Pdf + ) + + is ExtraDataType.Template -> ProcessImagesSheetTitle( + title = getString(R.string.template), + icon = Icons.Rounded.Extension + ) + + is ExtraDataType.Text -> ProcessImagesSheetTitle( + title = getString(R.string.text), + icon = Icons.Rounded.TextFields + ) + } + } +} + +@Preview +@Composable +private fun Preview() = ImageToolboxThemeForPreview(true) { + ProcessImagesPreferenceSheet( + uris = listOf("fff".toUri()), + visible = true, + extraDataType = null, + onDismiss = {}, + onNavigate = {} + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/SkippedImagesSheet.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/SkippedImagesSheet.kt new file mode 100644 index 0000000..65f52c8 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/SkippedImagesSheet.kt @@ -0,0 +1,225 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +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.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.FilterQuality +import androidx.compose.ui.graphics.Shape +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.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.WarningAmber +import com.t8rin.imagetoolbox.core.ui.utils.helper.AppSkippedImagesHost +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.SkippedImagesHostState +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.hapticsClickable +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.container +import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer +import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem +import com.t8rin.imagetoolbox.core.utils.path + +@Composable +fun SkippedImagesSheetHost( + hostState: SkippedImagesHostState = AppSkippedImagesHost.state, + onNavigate: (Screen) -> Unit +) { + SkippedImagesSheet( + uris = hostState.skippedImageUris, + onNavigate = onNavigate, + onDismiss = hostState::dismiss + ) +} + +@Composable +private fun SkippedImagesSheet( + uris: List, + onNavigate: (Screen) -> Unit, + onDismiss: () -> Unit +) { + EnhancedModalBottomSheet( + visible = uris.isNotEmpty(), + onDismiss = { + if (!it) onDismiss() + }, + title = { + TitleItem( + text = stringResource( + R.string.skipped_saving_count, + uris.size + ), + icon = Icons.Outlined.WarningAmber + ) + }, + confirmButton = { + EnhancedButton(onClick = onDismiss) { + Text(stringResource(R.string.close)) + } + } + ) { + var selectedUri by remember { + mutableStateOf(null) + } + val context = LocalContext.current + + Column( + modifier = Modifier.fillMaxWidth() + ) { + LazyColumn( + modifier = Modifier.fillMaxWidth(), + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + flingBehavior = enhancedFlingBehavior() + ) { + item { + InfoContainer( + text = stringResource(R.string.skipped_images_warning), + containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(0.8f), + contentColor = MaterialTheme.colorScheme.onSecondaryContainer.copy(0.8f), + modifier = Modifier.padding(bottom = 4.dp) + ) + } + itemsIndexed( + items = uris, + key = { _, uri -> uri.toString() } + ) { index, uri -> + SkippedImageItem( + uri = uri, + shape = ShapeDefaults.byIndex( + index = index, + size = uris.size + ), + onClick = { + selectedUri = uri + } + ) + } + } + } + + ImagePager( + visible = selectedUri != null, + selectedUri = selectedUri, + uris = uris, + onNavigate = { + selectedUri = null + onDismiss() + onNavigate(it) + }, + onUriSelected = { + selectedUri = it + }, + onShare = { + context.shareUris(listOf(it)) + }, + onDismiss = { + selectedUri = null + } + ) + } +} + +@Composable +private fun SkippedImageItem( + uri: Uri, + shape: Shape, + onClick: () -> Unit +) { + val filename = rememberFilename(uri) + val path = remember(uri, filename) { + uri.path()?.let { path -> + filename?.let { name -> + path + .removeSuffix("/$name") + .removeSuffix("/${name.substringBeforeLast('.')}") + } ?: path + } + } + + Row( + modifier = Modifier + .fillMaxWidth() + .heightIn(min = 88.dp) + .container( + color = MaterialTheme.colorScheme.surfaceContainer, + shape = shape, + resultPadding = 0.dp + ) + .hapticsClickable(onClick = onClick) + .padding(10.dp), + verticalAlignment = Alignment.Top + ) { + Picture( + model = uri, + modifier = Modifier.size(68.dp), + shape = ShapeDefaults.mini, + contentDescription = filename, + filterQuality = FilterQuality.High + ) + Spacer(Modifier.width(12.dp)) + Column( + modifier = Modifier.weight(1f) + ) { + Text( + text = filename ?: uri.toString(), + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.SemiBold + ) + path?.takeIf { it.isNotBlank() }?.let { + Text( + text = it, + modifier = Modifier.padding(top = 4.dp), + style = MaterialTheme.typography.bodySmall, + color = LocalContentColor.current.copy(alpha = 0.5f) + ) + } + } + } +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/UpdateSheet.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/UpdateSheet.kt new file mode 100644 index 0000000..44dfbd6 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/UpdateSheet.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/ZoomModalSheet.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/ZoomModalSheet.kt new file mode 100644 index 0000000..d257745 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/ZoomModalSheet.kt @@ -0,0 +1,72 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.aspectRatio +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.layout.ContentScale +import androidx.compose.ui.res.stringResource +import coil3.transform.Transformation +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ZoomIn +import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.derivative.EnhancedZoomableModalBottomSheet +import com.t8rin.imagetoolbox.core.ui.widget.image.Picture +import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem + +@Composable +fun ZoomModalSheet( + data: Any?, + visible: Boolean, + onDismiss: () -> Unit, + transformations: List = emptyList() +) { + if (data != null) { + EnhancedZoomableModalBottomSheet( + visible = visible, + onDismiss = onDismiss, + title = { + TitleItem( + text = stringResource(R.string.zoom), + icon = Icons.Outlined.ZoomIn + ) + } + ) { + var aspectRatio by remember(data) { + mutableFloatStateOf(1f) + } + Picture( + model = data, + contentDescription = null, + onSuccess = { + aspectRatio = it.result.image.safeAspectRatio + }, + contentScale = ContentScale.FillBounds, + showTransparencyChecker = false, + transformations = transformations, + modifier = Modifier.aspectRatio(aspectRatio) + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/FancySlider.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/FancySlider.kt new file mode 100644 index 0000000..c1930ac --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/FancySlider.kt @@ -0,0 +1,471 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.Canvas +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.fillMaxWidth +import androidx.compose.foundation.layout.height +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.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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.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.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.materialShadow +import com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider.CustomRangeSlider +import com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider.CustomRangeSliderState +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.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) + .compositeOverSafe(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 -> + FancyTrack( + 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) + .compositeOverSafe(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 -> + FancyTrack( + 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 +) + +@Composable +internal fun fancyThumbShape(): Shape { + val shapesType = LocalSettingsState.current.shapesType + + return if (shapesType is ShapeType.Cut || shapesType is ShapeType.Scoop || shapesType is ShapeType.Notch) { + remember(shapesType) { + AutoCircleShape( + shapesType = shapesType.copy( + strength = shapesType.strength.coerceAtLeast(0.5f) + ) + ) + } + } else { + MaterialStarShape + } +} + +@Composable +private fun FancyTrack( + sliderState: CustomSliderState, + colors: CustomSliderColors, + enabled: Boolean, + trackHeight: Dp +) { + FancyTrack( + tickFractions = sliderState.tickFractions, + activeRangeStart = 0f, + activeRangeEnd = sliderState.coercedValueAsFraction, + colors = colors, + enabled = enabled, + trackHeight = trackHeight + ) +} + +@Composable +private fun FancyTrack( + rangeSliderState: CustomRangeSliderState, + colors: CustomSliderColors, + enabled: Boolean, + trackHeight: Dp +) { + FancyTrack( + tickFractions = rangeSliderState.tickFractions, + activeRangeStart = rangeSliderState.coercedActiveRangeStartAsFraction, + activeRangeEnd = rangeSliderState.coercedActiveRangeEndAsFraction, + colors = colors, + enabled = enabled, + trackHeight = trackHeight + ) +} + +@Composable +private fun FancyTrack( + tickFractions: FloatArray, + activeRangeStart: Float, + activeRangeEnd: Float, + colors: CustomSliderColors, + enabled: Boolean, + trackHeight: Dp, + shape: Shape = ShapeDefaults.circle +) { + 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) + ) { + drawFancyTrack( + tickFractions = tickFractions, + activeRangeStart = activeRangeStart, + activeRangeEnd = activeRangeEnd, + inactiveTrackColor = inactiveTrackColor, + activeTrackColor = activeTrackColor, + inactiveTickColor = inactiveTickColor, + activeTickColor = activeTickColor, + shape = shape + ) + } +} + +private fun DrawScope.drawFancyTrack( + tickFractions: FloatArray, + activeRangeStart: Float, + activeRangeEnd: Float, + inactiveTrackColor: Color, + activeTrackColor: Color, + inactiveTickColor: Color, + activeTickColor: Color, + shape: Shape +) { + val isRtl = layoutDirection == LayoutDirection.Rtl + val activeStart = if (isRtl) 1f - activeRangeEnd else activeRangeStart + val activeEnd = if (isRtl) 1f - activeRangeStart else activeRangeEnd + val tickSize = TickSize.toPx() + + drawFancyTrackSegment( + startFraction = 0f, + endFraction = 1f, + color = inactiveTrackColor, + shape = shape + ) + drawFancyTrackSegment( + startFraction = activeStart, + endFraction = activeEnd, + color = activeTrackColor, + shape = shape + ) + + for (tick in tickFractions) { + val outsideFraction = tick !in activeRangeStart..activeRangeEnd + val visualTick = if (isRtl) 1f - tick else tick + drawCircle( + color = if (outsideFraction) inactiveTickColor else activeTickColor, + center = Offset(size.width * visualTick, center.y), + radius = tickSize / 2f + ) + } +} + +private fun DrawScope.drawFancyTrackSegment( + startFraction: Float, + endFraction: Float, + color: Color, + shape: Shape +) { + val capRadius = size.height / 2f + val left = size.width * startFraction.coerceIn(0f, 1f) - capRadius + val right = size.width * endFraction.coerceIn(0f, 1f) + capRadius + val width = right - left + + if (width <= 0f) return + + val outline = shape.createOutline( + size = Size( + width = width, + height = size.height + ), + layoutDirection = layoutDirection, + density = this + ) + + translate(left = left) { + drawFancyTrackOutline(outline, color) + } +} + +private fun DrawScope.drawFancyTrackOutline( + outline: Outline, + color: Color +) { + when (outline) { + is Outline.Rectangle -> drawRect( + color = color, + topLeft = outline.rect.topLeft, + size = outline.rect.size + ) + + is Outline.Rounded -> drawPath( + path = Path().apply { addRoundRect(outline.roundRect) }, + color = color + ) + + is Outline.Generic -> drawPath( + path = outline.path, + color = color + ) + } +} + +private val TickSize = 2.dp \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/HyperOSSlider.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/HyperOSSlider.kt new file mode 100644 index 0000000..333a376 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/HyperOSSlider.kt @@ -0,0 +1,183 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.unit.dp +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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) + .compositeOverSafe( + 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) + .compositeOverSafe( + 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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/M2Slider.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/M2Slider.kt new file mode 100644 index 0000000..3d8bc00 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/M2Slider.kt @@ -0,0 +1,200 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.unit.dp +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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) + .compositeOverSafe( + 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) + .compositeOverSafe( + 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 + ) + } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/M3Slider.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/M3Slider.kt new file mode 100644 index 0000000..f46528e --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/M3Slider.kt @@ -0,0 +1,185 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.unit.dp +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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) + .compositeOverSafe( + 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) + .compositeOverSafe( + 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, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomRangeSlider.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomRangeSlider.kt new file mode 100644 index 0000000..598e3bb --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomRangeSlider.kt @@ -0,0 +1,560 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomRangeSliderState.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomRangeSliderState.kt new file mode 100644 index 0000000..b36af59 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomRangeSliderState.kt @@ -0,0 +1,195 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomRangeSliderUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomRangeSliderUtils.kt new file mode 100644 index 0000000..287e4da --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomRangeSliderUtils.kt @@ -0,0 +1,152 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSlider.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSlider.kt new file mode 100644 index 0000000..3a1d115 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSlider.kt @@ -0,0 +1,461 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderColors.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderColors.kt new file mode 100644 index 0000000..64feb6c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderColors.kt @@ -0,0 +1,122 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderDefaults.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderDefaults.kt new file mode 100644 index 0000000..7ff1d12 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderDefaults.kt @@ -0,0 +1,334 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderRange.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderRange.kt new file mode 100644 index 0000000..f178c64 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderRange.kt @@ -0,0 +1,125 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderState.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderState.kt new file mode 100644 index 0000000..8783c49 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderState.kt @@ -0,0 +1,166 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderUtils.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderUtils.kt new file mode 100644 index 0000000..018dae5 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderUtils.kt @@ -0,0 +1,184 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/ComposeSwitch.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/ComposeSwitch.kt new file mode 100644 index 0000000..dbe1f1f --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/ComposeSwitch.kt @@ -0,0 +1,202 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.indication +import androidx.compose.foundation.interaction.MutableInteractionSource +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.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SwitchColors +import androidx.compose.material3.SwitchDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.Stable +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.semantics.Role +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +import com.t8rin.imagetoolbox.core.ui.utils.helper.rememberRipple +import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults + +@SuppressLint("UseOfNonLambdaOffsetOverload") +@Composable +@Suppress("ComposableLambdaParameterNaming", "ComposableLambdaParameterPosition") +fun ComposeSwitch( + checked: Boolean, + onCheckedChange: ((Boolean) -> Unit)?, + modifier: Modifier = Modifier, + thumbContent: (@Composable () -> Unit)? = null, + enabled: Boolean = true, + colors: SwitchColors = SwitchDefaults.colors(), + interactionSource: MutableInteractionSource? = null, +) { + val realInteractionSource = interactionSource ?: remember { + MutableInteractionSource() + } + val pressed by realInteractionSource.collectIsPressedAsState() + val circleShape = ShapeDefaults.circle + + val toggleableModifier = if (onCheckedChange != null) { + Modifier + .toggleable( + value = checked, + onValueChange = onCheckedChange, + enabled = enabled, + role = Role.Switch, + interactionSource = realInteractionSource, + indication = null + ) + } else { + Modifier + } + + val trackColor by animateColorAsState( + targetValue = trackColor(enabled, checked, colors) + ) + val thumbColor by animateColorAsState( + targetValue = thumbColor(enabled, checked, colors) + ) + val borderColor by animateColorAsState( + targetValue = borderColor(enabled, checked, colors) + ) + val iconColor by animateColorAsState( + targetValue = iconColor(enabled, checked, colors) + ) + + val thumbSize by animateDpAsState( + targetValue = when { + pressed -> PressedThumbSize + thumbContent != null || checked -> ThumbSize + else -> UncheckedThumbSize + } + ) + val thumbOffset by animateDpAsState( + targetValue = when { + pressed && checked -> CheckedThumbOffset - TrackOutlineWidth + pressed -> TrackOutlineWidth + checked -> CheckedThumbOffset + else -> (SwitchHeight - thumbSize) / 2f + }, + animationSpec = MaterialTheme.motionScheme.fastSpatialSpec() + ) + + Box( + modifier = modifier + .then(toggleableModifier) + .wrapContentSize(Alignment.Center) + .requiredSize(SwitchWidth, SwitchHeight) + .border( + width = TrackOutlineWidth, + color = borderColor, + shape = circleShape + ) + .background(trackColor, circleShape) + ) { + Box( + modifier = Modifier + .align(Alignment.CenterStart) + .offset(x = thumbOffset) + .indication( + interactionSource = realInteractionSource, + indication = rememberRipple( + bounded = false, + radius = StateLayerSize / 2f + ) + ) + .background(thumbColor, circleShape) + .size(thumbSize), + contentAlignment = Alignment.Center + ) { + if (thumbContent != null) { + CompositionLocalProvider( + LocalContentColor provides iconColor, + content = thumbContent + ) + } + } + } +} + +@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 +} + +@Stable +private fun iconColor( + enabled: Boolean, + checked: Boolean, + colors: SwitchColors +): Color = if (enabled) { + if (checked) colors.checkedIconColor else colors.uncheckedIconColor +} else { + if (checked) colors.disabledCheckedIconColor else colors.disabledUncheckedIconColor +} + +private val SwitchWidth = 52.dp +private val SwitchHeight = 32.dp +private val TrackOutlineWidth = 2.dp +private val StateLayerSize = 40.dp +private val ThumbSize = 24.dp +private val UncheckedThumbSize = 16.dp +private val PressedThumbSize = 28.dp +private val ThumbPadding = (SwitchHeight - ThumbSize) / 2f +private val CheckedThumbOffset = SwitchWidth - ThumbSize - ThumbPadding \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/CupertinoSwitch.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/CupertinoSwitch.kt new file mode 100644 index 0000000..55ae9ab --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/CupertinoSwitch.kt @@ -0,0 +1,499 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.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.graphics.takeOrElse +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.resources.utils.animation.animateColorAsState +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 = remember { MutableInteractionSource() } + ) + + LiquidToggle( + checked = { checked }, + onCheckedChange = onCheckedChange, + backdrop = rememberCanvasBackdrop { + drawRect(backgroundColor.takeOrElse { Color.Transparent }) + }, + 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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/FluentSwitch.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/FluentSwitch.kt new file mode 100644 index 0000000..4d84ce4 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/FluentSwitch.kt @@ -0,0 +1,229 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.resources.utils.animation.animateColorAsState +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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/HyperOSSwitch.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/HyperOSSwitch.kt new file mode 100644 index 0000000..a9da89a --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/HyperOSSwitch.kt @@ -0,0 +1,255 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.resources.utils.animation.animateColorAsState +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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/LiquidToggle.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/LiquidToggle.kt new file mode 100644 index 0000000..0c0b7d8 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/LiquidToggle.kt @@ -0,0 +1,483 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/M3Switch.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/M3Switch.kt new file mode 100644 index 0000000..b214724 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/M3Switch.kt @@ -0,0 +1,221 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/OneUISwitch.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/OneUISwitch.kt new file mode 100644 index 0000000..fbe5f04 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/OneUISwitch.kt @@ -0,0 +1,429 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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.resources.utils.animation.animateColorAsState +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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) + .compositeOverSafe(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 + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/PixelSwitch.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/PixelSwitch.kt new file mode 100644 index 0000000..7ffba97 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/PixelSwitch.kt @@ -0,0 +1,203 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.resources.utils.animation.animateColorAsState +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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/AutoSizeText.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/AutoSizeText.kt new file mode 100644 index 0000000..3723c17 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/AutoSizeText.kt @@ -0,0 +1,86 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.length } +) { + 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() } + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/FilenamePatternEditSheet.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/FilenamePatternEditSheet.kt new file mode 100644 index 0000000..3c27224 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/FilenamePatternEditSheet.kt @@ -0,0 +1,228 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +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.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.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.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.FileSize +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.ParentFolder +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.Uuid +import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Width +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Description +import com.t8rin.imagetoolbox.core.resources.icons.Info +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.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.PreferenceItemOverload + +@Composable +fun FilenamePatternEditSheet( + visible: Boolean, + onDismiss: () -> Unit, + value: String, + onValueChange: (String) -> Unit, + onValueChangeFinished: (String) -> Unit, + allowedPatterns: List? = null, + exampleFilename: (@Composable ColumnScope.() -> Unit)? = null +) { + val settingsState = LocalSettingsState.current + + EnhancedModalBottomSheet( + visible = visible, + onDismiss = { onDismiss() }, + 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 = { + onValueChangeFinished(value.trim()) + onDismiss() + }, + 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 + ), + ) { + exampleFilename?.invoke(this) + + RoundedTextField( + modifier = Modifier + .container( + shape = MaterialTheme.shapes.large, + resultPadding = 8.dp + ), + value = value, + textStyle = MaterialTheme.typography.titleMedium, + onValueChange = onValueChange, + maxLines = Int.MAX_VALUE, + singleLine = false, + hint = { Text(stringResource(R.string.format_pattern)) }, + label = null, + visualTransformation = allowedPatterns?.let { + PatternHighlightTransformation.forPatterns(allowedPatterns) + } ?: PatternHighlightTransformation.default() + ) + + Spacer(Modifier.height(4.dp)) + + Column( + verticalArrangement = Arrangement.spacedBy( + space = 4.dp, + alignment = Alignment.CenterVertically + ), + ) { + val linkHandler = LocalUriHandler.current + + (allowedPatterns ?: FilenamePattern.entries).forEachIndexed { index, pattern -> + PreferenceItemOverload( + title = when (pattern) { + Date -> pattern.value + "{pattern}" + Rand -> pattern.value + "{count}" + OriginalName -> pattern.value + "{start:end}" + Sequence -> pattern.value + "{start:step:padding}" + FileSize -> pattern.value + "{unit}" + 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) + ParentFolder -> stringResource(R.string.parent_folder_pattern_description) + FileSize -> stringResource(R.string.file_size_pattern_description) + Uuid -> stringResource(R.string.uuid_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 = allowedPatterns?.size ?: FilenamePattern.entries.size + ), + modifier = Modifier + ) + } + } + } + } +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/HtmlText.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/HtmlText.kt new file mode 100644 index 0000000..7be8e1b --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/HtmlText.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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, + 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 + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/IsKeyboardVisibleAsState.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/IsKeyboardVisibleAsState.kt new file mode 100644 index 0000000..3c655cb --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/IsKeyboardVisibleAsState.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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() + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/Linkify.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/Linkify.kt new file mode 100644 index 0000000..c8c7644 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/Linkify.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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,})") diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/Marquee.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/Marquee.kt new file mode 100644 index 0000000..77dcf31 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/Marquee.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/OutlinedText.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/OutlinedText.kt new file mode 100644 index 0000000..b931736 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/OutlinedText.kt @@ -0,0 +1,114 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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, + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/PatternHighlightTransformation.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/PatternHighlightTransformation.kt new file mode 100644 index 0000000..7ca8313 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/PatternHighlightTransformation.kt @@ -0,0 +1,169 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + ) + } + } + + @Composable + fun forPatterns( + allowedPatterns: List + ): PatternHighlightTransformation { + val color = takeColorFromScheme { + primary.blend(primaryContainer, 0.1f) + } + val colorUpper = takeColorFromScheme { + tertiary.blend(tertiaryContainer, 0.1f) + } + + return remember(color, colorUpper, allowedPatterns) { + val lowerLetters = allowedPatterns + .map { it.value.last() } + .distinct() + .sorted() + .joinToString("") + val upperLetters = FilenamePattern.upperEntries + .asSequence() + .filter { upper -> + allowedPatterns.any { pattern -> + pattern.value.equals(upper.value, ignoreCase = true) + } + } + .map { it.value.last() } + .distinct() + .sorted() + .joinToString("") + + PatternHighlightTransformation( + buildMap { + if (lowerLetters.isNotEmpty()) { + put( + Regex("""\\[$lowerLetters](\{[^}]*\})?"""), + color + ) + } + if (upperLetters.isNotEmpty()) { + put( + Regex("""\\[$upperLetters](\{[^}]*\})?"""), + colorUpper + ) + } + } + ) + } + } + } + +} + +private val PATTERN_TOKENS = Regex( + """\\[pwdhrcoimsefu](\{[^}]*\})?""" +) + +private val UPPER_PATTERN_TOKENS = Regex( + """\\[PDOIMSEFU](\{[^}]*\})?""" +) + +@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() + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/RoundedTextField.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/RoundedTextField.kt new file mode 100644 index 0000000..79f9d0c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/RoundedTextField.kt @@ -0,0 +1,332 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + ) + ) + } \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/TitleItem.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/TitleItem.kt new file mode 100644 index 0000000..45759a7 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/TitleItem.kt @@ -0,0 +1,130 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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 = 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() + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/TopAppBarTitle.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/TopAppBarTitle.kt new file mode 100644 index 0000000..f3add1c --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/TopAppBarTitle.kt @@ -0,0 +1,183 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/AutoContentBasedColors.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/AutoContentBasedColors.kt new file mode 100644 index 0000000..101196d --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/AutoContentBasedColors.kt @@ -0,0 +1,104 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.ImageBitmap +import androidx.compose.ui.graphics.asAndroidBitmap +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(it).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 { + when (model) { + is Bitmap -> model + is ImageBitmap -> model.asAndroidBitmap() + else -> 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) + } +} diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/AvailableHeight.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/AvailableHeight.kt new file mode 100644 index 0000000..c67c7c0 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/AvailableHeight.kt @@ -0,0 +1,120 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/RememberRetainedLazyListState.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/RememberRetainedLazyListState.kt new file mode 100644 index 0000000..aeb3bd1 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/RememberRetainedLazyListState.kt @@ -0,0 +1,68 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/ScreenList.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/ScreenList.kt new file mode 100644 index 0000000..69288ff --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/ScreenList.kt @@ -0,0 +1,355 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.BatchRename(uris), + Screen.Zip(uris) + ) + } else { + listOf( + Screen.BatchRename(uris), + 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.DuplicateFinder(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.DuplicateFinder(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.Template -> 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 } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/value/ValueDialog.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/value/ValueDialog.kt new file mode 100644 index 0000000..4d01428 --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/value/ValueDialog.kt @@ -0,0 +1,187 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +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.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.focus.focusRequester +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import com.t8rin.imagetoolbox.core.domain.utils.trimTrailingZero +import com.t8rin.imagetoolbox.core.resources.Icons +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 kotlinx.coroutines.android.awaitFrame +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) { + val text = valueState.trimTrailingZero() + mutableStateOf( + TextFieldValue( + text = text, + selection = TextRange(0, text.length) + ) + ) + } + val parsedValue = value.text.toFloatOrNull()?.takeIf(Float::isFinite) + val submit: () -> Unit = { + if (parsedValue != null) { + onDismiss() + onValueUpdate(parsedValue.roundTo(roundTo).coerceIn(valueRange)) + } + } + + 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 = { + val requester = remember { FocusRequester() } + + LaunchedEffect(Unit) { + awaitFrame() + runCatching { requester.requestFocus() } + } + + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + OutlinedTextField( + shape = ShapeDefaults.default, + value = value, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Decimal, + imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions( + onDone = { submit() } + ), + textStyle = MaterialTheme.typography.titleMedium.copy(textAlign = TextAlign.Center), + maxLines = 1, + onValueChange = { number -> + val text = number.text.filterDecimal() + value = number.copy( + text = text, + selection = TextRange( + start = number.selection.start.coerceAtMost(text.length), + end = number.selection.end.coerceAtMost(text.length) + ) + ) + }, + modifier = Modifier.focusRequester(requester) + ) + } + }, + confirmButton = { + EnhancedButton( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + enabled = parsedValue != null, + onClick = submit, + ) { + Text(stringResource(R.string.ok)) + } + } + ) +} + +fun String.filterDecimal(): String { + var tempS = replace(',', '.').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 \ No newline at end of file diff --git a/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/value/ValueText.kt b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/value/ValueText.kt new file mode 100644 index 0000000..15458fd --- /dev/null +++ b/core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/value/ValueText.kt @@ -0,0 +1,112 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.hapticsCombinedClickable +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)?, + onLongClick: (() -> Unit)? = null, + 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 || onLongClick != null) { + Modifier.hapticsCombinedClickable( + enabled = enabled, + onClick = { onClick?.invoke() }, + onLongClick = onLongClick, + interactionSource = interactionSource, + indication = LocalIndication.current + ) + } else Modifier + ) + .padding(horizontal = 16.dp, vertical = 6.dp), + lineHeight = 18.sp + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/market/java/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/DocumentScannerImpl.kt b/core/ui/src/market/java/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/DocumentScannerImpl.kt new file mode 100644 index 0000000..0f15a11 --- /dev/null +++ b/core/ui/src/market/java/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/DocumentScannerImpl.kt @@ -0,0 +1,95 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } +} \ No newline at end of file diff --git a/core/ui/src/market/java/com/t8rin/imagetoolbox/core/ui/utils/helper/ReviewHandlerImpl.kt b/core/ui/src/market/java/com/t8rin/imagetoolbox/core/ui/utils/helper/ReviewHandlerImpl.kt new file mode 100644 index 0000000..efebc14 --- /dev/null +++ b/core/ui/src/market/java/com/t8rin/imagetoolbox/core/ui/utils/helper/ReviewHandlerImpl.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.imagetoolbox.core.utils.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") + } + } + +} \ No newline at end of file diff --git a/core/ui/src/market/java/com/t8rin/imagetoolbox/core/ui/widget/sheets/UpdateSheetImpl.kt b/core/ui/src/market/java/com/t8rin/imagetoolbox/core/ui/widget/sheets/UpdateSheetImpl.kt new file mode 100644 index 0000000..8c26fbb --- /dev/null +++ b/core/ui/src/market/java/com/t8rin/imagetoolbox/core/ui/widget/sheets/UpdateSheetImpl.kt @@ -0,0 +1,83 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.DownloadOff +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.DownloadOff, + message = getString(R.string.no_updates) + ) + } + } + }.onFailure { + AppToastHost.showToast( + icon = Icons.Rounded.DownloadOff, + message = getString(R.string.no_updates) + ) + } + } + } + } else { + DefaultUpdateSheet( + changelog = changelog, + tag = tag, + visible = visible, + onDismiss = onDismiss + ) + } +} \ No newline at end of file diff --git a/core/utils/.gitignore b/core/utils/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/utils/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/utils/build.gradle.kts b/core/utils/build.gradle.kts new file mode 100644 index 0000000..fc9c089 --- /dev/null +++ b/core/utils/build.gradle.kts @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.androidx.documentfile) + "marketImplementation"(libs.quickie.bundled) + "fossImplementation"(libs.quickie.foss) + implementation(libs.zxing.core) +} \ No newline at end of file diff --git a/core/utils/src/main/AndroidManifest.xml b/core/utils/src/main/AndroidManifest.xml new file mode 100644 index 0000000..44008a4 --- /dev/null +++ b/core/utils/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/AppContext.kt b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/AppContext.kt new file mode 100644 index 0000000..e791ba4 --- /dev/null +++ b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/AppContext.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +import com.t8rin.imagetoolbox.core.resources.R + +class AppContext private constructor( + application: Context +) : ContextWrapper(application) { + + companion object { + internal var appContext: AppContext? = null + + internal fun init(application: Context) { + if (appContext == null) { + appContext = AppContext(application) + } else { + "AppContext is already initialized".makeLog("AppContext") + } + } + } + +} + +fun Context.initAppContext() = AppContext.init(applicationContext) + +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) + +fun Throwable.extractMessage(): String = if (this is OutOfMemoryError) { + getString(R.string.oom_description) +} else { + getString( + R.string.smth_went_wrong, + (localizedMessage?.takeIf { it.isNotBlank() } ?: message)?.decodeEscaped().orEmpty() + .ifEmpty { + this::class.java.simpleName + } + ) +} \ No newline at end of file diff --git a/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/LogLineReference.kt b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/LogLineReference.kt new file mode 100644 index 0000000..12cffef --- /dev/null +++ b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/LogLineReference.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +data class LogLineReference( + val number: Int, + val start: Long, + val end: Long +) { + val key: String = "$number:$start:$end" +} + +data class LogLinesSnapshot( + val lines: List = emptyList(), + val endOffset: Long = 0L, + val lastLineNumber: Int = 0 +) diff --git a/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/Logger.kt b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/Logger.kt new file mode 100644 index 0000000..8165c59 --- /dev/null +++ b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/Logger.kt @@ -0,0 +1,203 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.app.Application +import android.net.Uri +import androidx.core.net.toUri +import com.t8rin.imagetoolbox.core.utils.Logger.Level +import com.t8rin.imagetoolbox.core.utils.Logger.reportError +import com.t8rin.imagetoolbox.core.utils.LogsWriter.Companion.MAX_SIZE +import com.t8rin.imagetoolbox.core.utils.LogsWriter.Companion.STARTUP_LOG +import android.util.Log as RealLog + +/** +[Logger] is a wrapper around [android.util.Log] + **/ +data object Logger { + + data object Flags { + var useCallerClassAsTag = false + } + + internal var logWriter: LogsWriter? = null + + inline fun makeLog( + tag: String = createTag(), + level: Level = Level.Debug, + dataBlock: () -> T + ) { + val data = dataBlock() + val message = if (data is Throwable) { + reportError(data) + RealLog.getStackTraceString(data) + } else { + data.toString() + } + + makeLog( + Log( + tag = tag, + message = message, + level = if (data is Throwable) Level.Error else level + ) + ) + } + + fun reportError(throwable: Throwable) { + logWriter?.errorHandler(throwable) + } + + fun makeLog(log: Log) { + val (tag, message, level) = log + + when (level) { + is Level.Assert -> RealLog.println(level.priority, tag, message) + Level.Debug -> RealLog.d(tag, message) + Level.Error -> RealLog.e(tag, message) + Level.Info -> RealLog.i(tag, message) + Level.Verbose -> RealLog.v(tag, message) + Level.Warn -> RealLog.w(tag, message) + } + + logWriter?.writeLog(log) + } + + fun shareLogs() = logWriter?.shareLogs() ?: throw LogsWriterNotInitialized() + + fun shareLogsViaEmail( + email: String + ) = logWriter?.shareLogsViaEmail(email) ?: throw LogsWriterNotInitialized() + + fun getLogsFile(): Uri = logWriter?.logsFile?.toUri() ?: throw LogsWriterNotInitialized() + + fun readLogs(maxBytes: Int = LogsWriter.PREVIEW_SIZE): String = + logWriter?.readLogs(maxBytes).orEmpty() + + fun readLogLineReferences( + startOffset: Long = 0L, + startLineNumber: Int = 0, + query: String = "" + ): LogLinesSnapshot = logWriter?.readLogLineReferences( + startOffset = startOffset, + startLineNumber = startLineNumber, + query = query + ) ?: LogLinesSnapshot() + + fun readLogLine(line: LogLineReference): String = logWriter?.readLogLine(line).orEmpty() + + fun logsFileLength(): Long = logWriter?.logsFile?.takeIf { it.exists() }?.length() ?: 0L + + sealed interface Level { + data class Assert( + val priority: Int + ) : Level { + override fun toString(): String = "Assert" + } + + data object Error : Level + data object Warn : Level + data object Info : Level + data object Debug : Level + data object Verbose : Level + } + + data class Log( + val tag: String, + val message: String, + val level: Level + ) +} + +inline fun makeLog( + data: T, + tag: String = createTag(), + level: Level = Level.Debug, +) = Logger.makeLog( + tag = tag, + level = level, + dataBlock = { data } +) + +fun makeLog( + level: Level = Level.Debug, + separator: String = " - ", + vararg data: Any +) = Logger.makeLog( + level = level, + dataBlock = { + data.toList().joinToString(separator) + } +) + +inline fun T.makeLog( + tag: String = createTag(), + level: Level = Level.Debug, + dataBlock: (T) -> Any? = { it } +): T = also { + if (it is Throwable) { + RealLog.e(tag, it.localizedMessage, it) + reportError(it) + Logger.makeLog( + tag = tag, + level = Level.Error, + dataBlock = { dataBlock(it) } + ) + } else { + Logger.makeLog( + tag = tag, + level = level, + dataBlock = { dataBlock(it) } + ) + } + +} + +inline infix fun T.makeLog( + tag: String +): T = makeLog(tag) { this } + + +inline fun createTag(): String = if (Logger.Flags.useCallerClassAsTag) { + Throwable().stackTrace[1].className +} else { + "Logger" + (T::class.simpleName?.let { "_$it" } ?: "") +} + + +fun Logger.attachLogWriter( + context: Application, + fileProvider: String, + logsFilename: String, + isSyncCreate: Boolean = false, + startupLog: Logger.Log = STARTUP_LOG, + logMapper: LogMapper = LogMapper.Default, + errorHandler: (Throwable) -> Unit = {}, + maxFileSize: Int? = MAX_SIZE +) { + logWriter = LogsWriter( + context = context, + fileProvider = fileProvider, + logsFilename = logsFilename, + maxFileSize = maxFileSize, + isSyncCreate = isSyncCreate, + startupLog = startupLog, + errorHandler = errorHandler, + logMapper = logMapper + ) +} \ No newline at end of file diff --git a/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/LogsWriter.kt b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/LogsWriter.kt new file mode 100644 index 0000000..7ef5bd7 --- /dev/null +++ b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/LogsWriter.kt @@ -0,0 +1,298 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.app.Application +import android.content.Intent +import android.net.Uri +import androidx.core.content.FileProvider +import com.t8rin.imagetoolbox.core.domain.utils.timestamp +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import java.io.BufferedWriter +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.FileOutputStream +import java.io.InputStream +import java.io.RandomAccessFile + +internal class LogsWriter( + private val context: Application, + private val fileProvider: String, + private val logsFilename: String, + isSyncCreate: Boolean, + startupLog: Logger.Log = STARTUP_LOG, + internal val errorHandler: (Throwable) -> Unit = {}, + private val logMapper: LogMapper = LogMapper.Default, + private val maxFileSize: Int? = MAX_SIZE, +) { + + internal var logsFile: File? = null + + init { + val create = suspend { + if (logsFile != null) throw IllegalStateException("LogWriter must be initialized only once") + + logsFile = File(context.filesDir, logsFilename).apply { + if (maxFileSize != null && length() > maxFileSize) { + var lineCount = 0 + val lines = mutableListOf() + withContext(Dispatchers.IO) { + coroutineScope { + bufferedReader().use { reader -> + while (reader.readLine() != null) { + lineCount++ + } + } + } + coroutineScope { + bufferedReader().use { reader -> + var tempLineCount = 0 + repeat(lineCount) { + tempLineCount++ + val line = reader.readLine() + + if (tempLineCount >= lineCount - 1000 && line != null) { + lines.add(line) + } + } + } + delete() + createNewFile() + writeData(this@apply) { writer -> + lines.forEach { + writer.write(it) + writer.newLine() + } + } + } + } + } + if (!exists()) createNewFile() + } + writeData { writer -> + writer.write( + logMapper.asMessage(startupLog) + ) + writer.newLine() + } + } + + if (isSyncCreate) { + runBlocking { create() } + } else { + CoroutineScope(Dispatchers.Main).launch { + create() + } + } + } + + private fun File.getUri(): Uri = FileProvider.getUriForFile(context, fileProvider, this) + + private fun writeData( + file: File? = logsFile, + use: (BufferedWriter) -> Unit + ) { + runCatching { + FileOutputStream(file, true) + .bufferedWriter() + .use(use) + } + } + + fun shareLogs() { + val sendIntent = Intent(Intent.ACTION_SEND_MULTIPLE).apply { + putParcelableArrayListExtra(Intent.EXTRA_STREAM, arrayListOf(logsFile!!.getUri())) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + type = "text/plain" + } + val shareIntent = + Intent.createChooser(sendIntent, "Share Logs") + shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(shareIntent) + } + + fun shareLogsViaEmail(email: String) { + val sendIntent = Intent(Intent.ACTION_SEND).apply { + type = "vnd.android.cursor.dir/email" + putExtra(Intent.EXTRA_EMAIL, arrayOf(email)) + putExtra(Intent.EXTRA_STREAM, logsFile!!.getUri()) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + context.startActivity(sendIntent) + } + + fun writeLog(log: Logger.Log) { + writeData { writer -> + writer.write( + logMapper.asMessage(log) + ) + writer.newLine() + } + } + + fun readLogs(maxBytes: Int = PREVIEW_SIZE): String { + val file = logsFile?.takeIf { it.exists() } ?: return "" + if (maxBytes <= 0 || file.length() <= maxBytes) return file.readText() + + val bytes = ByteArray(maxBytes) + RandomAccessFile(file, "r").use { randomAccessFile -> + randomAccessFile.seek(file.length() - maxBytes) + randomAccessFile.readFully(bytes) + } + + return bytes.toString(Charsets.UTF_8) + } + + fun readLogLineReferences( + startOffset: Long = 0L, + startLineNumber: Int = 0, + query: String = "" + ): LogLinesSnapshot { + val file = logsFile?.takeIf { it.exists() } ?: return LogLinesSnapshot() + val normalizedStartOffset = startOffset.coerceIn(0L, file.length()) + val searchQuery = query.takeIf(String::isNotEmpty) + val lines = mutableListOf() + val lineBytes = searchQuery?.let { ByteArrayOutputStream() } + + var position = normalizedStartOffset + var lineStart = normalizedStartOffset + var lineNumber = startLineNumber + var previousWasCarriageReturn = false + + fun addLine(lineEnd: Long) { + lineNumber++ + val end = lineEnd.coerceAtLeast(lineStart) + val matches = searchQuery?.let { query -> + lineBytes + ?.toByteArray() + ?.toString(Charsets.UTF_8) + ?.contains(query, ignoreCase = true) == true + } ?: true + + if (matches) { + lines.add( + LogLineReference( + number = lineNumber, + start = lineStart, + end = end + ) + ) + } + lineBytes?.reset() + } + + file.inputStream().buffered().use { inputStream -> + inputStream.skipFully(normalizedStartOffset) + + while (true) { + val value = inputStream.read() + if (value == -1) break + + if (value == NEW_LINE) { + addLine( + lineEnd = if (previousWasCarriageReturn) position - 1 else position + ) + lineStart = position + 1 + previousWasCarriageReturn = false + } else { + lineBytes?.write(value) + previousWasCarriageReturn = value == CARRIAGE_RETURN + } + + position++ + } + } + + if (position > lineStart) { + addLine( + lineEnd = if (previousWasCarriageReturn) position - 1 else position + ) + } + + return LogLinesSnapshot( + lines = lines, + endOffset = position, + lastLineNumber = lineNumber + ) + } + + fun readLogLine(line: LogLineReference): String { + val file = logsFile?.takeIf { it.exists() } ?: return "" + val availableBytes = (file.length() - line.start).coerceAtLeast(0L) + val bytesCount = (line.end - line.start) + .coerceAtLeast(0L) + .coerceAtMost(availableBytes) + .toInt() + if (bytesCount == 0) return "" + + val bytes = ByteArray(bytesCount) + RandomAccessFile(file, "r").use { randomAccessFile -> + randomAccessFile.seek(line.start) + randomAccessFile.readFully(bytes) + } + + return bytes.toString(Charsets.UTF_8) + } + + companion object { + internal val STARTUP_LOG = Logger.Log( + tag = "Logger_Launch", + message = "* Application Launched *", + level = Logger.Level.Info + ) + internal const val MAX_SIZE = 40 * 1024 * 1024 * 8 + internal const val PREVIEW_SIZE = 512 * 1024 + private const val NEW_LINE = '\n'.code + private const val CARRIAGE_RETURN = '\r'.code + } +} + +private fun InputStream.skipFully(bytesToSkip: Long) { + var remaining = bytesToSkip + while (remaining > 0) { + val skipped = skip(remaining) + if (skipped > 0) { + remaining -= skipped + } else if (read() == -1) { + return + } else { + remaining-- + } + } +} + +internal class LogsWriterNotInitialized : Throwable() + +fun interface LogMapper { + fun asMessage(log: Logger.Log): String + + companion object { + val Default by lazy { + LogMapper { (tag, message, level) -> + "${timestamp("dd-MM-yyyy HH:mm:ss")} ($tag) [$level]: $message" + } + } + } +} \ No newline at end of file diff --git a/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/QrType.kt b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/QrType.kt new file mode 100644 index 0000000..8cf7018 --- /dev/null +++ b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/QrType.kt @@ -0,0 +1,168 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/Typeface.kt b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/Typeface.kt new file mode 100644 index 0000000..3bed703 --- /dev/null +++ b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/Typeface.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) +} \ No newline at end of file diff --git a/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/Update.kt b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/Update.kt new file mode 100644 index 0000000..77dc915 --- /dev/null +++ b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/Update.kt @@ -0,0 +1,100 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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", "") +} \ No newline at end of file diff --git a/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/UriUtils.kt b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/UriUtils.kt new file mode 100644 index 0000000..c8f94a2 --- /dev/null +++ b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/UriUtils.kt @@ -0,0 +1,590 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.graphics.BitmapFactory +import android.net.Uri +import android.os.Build +import android.os.ParcelFileDescriptor +import android.provider.DocumentsContract +import android.provider.MediaStore +import android.provider.OpenableColumns +import android.system.Os +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.IntegerSize +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 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 kotlinx.coroutines.withContext +import java.io.File +import java.net.URLDecoder +import java.net.URLEncoder +import java.util.LinkedList +import java.util.Locale +import java.util.concurrent.ConcurrentHashMap + +fun Uri?.uiPath( + default: String, + context: Context = appContext +): String = this?.let { uri -> + runCatching { + if (DocumentFile.isDocumentUri(context, uri)) { + DocumentFile.fromSingleUri(context, uri) + } else { + DocumentFile.fromTreeUri(context, uri) + }?.uri?.path?.split(":") + ?.lastOrNull()?.let { p -> + val endPath = p.takeIf { + it.isNotEmpty() + }?.let { "/$it" } ?: "" + val startPath = if ( + uri.toString() + .split("%")[0] + .contains("primary") + ) context.getString(R.string.device_storage) + else context.getString(R.string.external_storage) + + startPath + endPath + }?.decodeEscaped() + }.getOrNull() +} ?: 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.imageSize(): IntegerSize? = tryExtractOriginal().run { + val mediaStoreSize = if (scheme == ContentResolver.SCHEME_CONTENT) { + runCatching { + appContext.contentResolver.query( + this, + arrayOf(MediaStore.MediaColumns.WIDTH, MediaStore.MediaColumns.HEIGHT), + null, + null, + null + )?.use { cursor -> + if (cursor.moveToFirst()) { + val widthIndex = cursor.getColumnIndex(MediaStore.MediaColumns.WIDTH) + val heightIndex = cursor.getColumnIndex(MediaStore.MediaColumns.HEIGHT) + val width = widthIndex + .takeIf { it != -1 && !cursor.isNull(it) } + ?.let(cursor::getInt) + val height = heightIndex + .takeIf { it != -1 && !cursor.isNull(it) } + ?.let(cursor::getInt) + + IntegerSize(width ?: 0, height ?: 0).takeIf { it.isValidImageSize() } + } else null + } + }.getOrNull() + } else null + + mediaStoreSize ?: runCatching { + val options = BitmapFactory.Options().apply { + inJustDecodeBounds = true + } + if (scheme == ContentResolver.SCHEME_CONTENT) { + appContext.contentResolver.openInputStream(this)?.use { + BitmapFactory.decodeStream(it, null, options) + } + } else { + BitmapFactory.decodeFile(toFile().absolutePath, options) + } + IntegerSize(options.outWidth, options.outHeight).takeIf { it.isValidImageSize() } + }.getOrNull() +} + +private fun IntegerSize.isValidImageSize(): Boolean = width > 0 && height > 0 + +fun Uri.tryExtractOriginal(): Uri = UriReplacements.resolve(this).run { + 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 + } + + UriReplacements.resolve( + 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.distinctUris(): List = withContext(Dispatchers.IO) { + val identities = HashSet(size) + + return@withContext filter { uri -> + identities.add(uri.uriIdentity()) + } +} + +private fun Uri.uriIdentity(): UriIdentity { + val normalizedUri = tryExtractOriginal() + + normalizedUri.fileDescriptorIdentity()?.let { + return it + } + + if (normalizedUri.scheme == ContentResolver.SCHEME_FILE) { + normalizedUri.path?.let(::File)?.let { file -> + return UriIdentity.FilePath( + runCatching { file.canonicalPath }.getOrDefault(file.absolutePath) + ) + } + } + + return UriIdentity.NormalizedUri(normalizedUri.normalizeScheme().toString()) +} + +private fun Uri.fileDescriptorIdentity(): UriIdentity.FileDescriptor? = runCatching { + openFileDescriptor()?.use { descriptor -> + Os.fstat(descriptor.fileDescriptor).let { stat -> + UriIdentity.FileDescriptor( + device = stat.st_dev, + inode = stat.st_ino + ).takeIf { stat.st_ino != 0L } + } + } +}.getOrNull() + +private fun Uri.openFileDescriptor(): ParcelFileDescriptor? = when (scheme) { + ContentResolver.SCHEME_FILE -> path?.let(::File)?.let { file -> + ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) + } + + ContentResolver.SCHEME_CONTENT -> appContext.contentResolver.openFileDescriptor(this, "r") + else -> null +} + +private sealed interface UriIdentity { + data class FileDescriptor( + val device: Long, + val inode: Long + ) : UriIdentity + + data class FilePath(val path: String) : UriIdentity + + data class NormalizedUri(val uri: String) : UriIdentity +} + +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) + SortType.Reverse -> reversed() + SortType.Shuffle -> shuffled() + } +} + +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 +): List { + val comparator = Comparator> { first, second -> + first.second.orEmpty().naturalCompareTo(second.second.orEmpty()) + }.let { if (descending) it.reversed() else it } + + return map { it to it.filename() } + .sortedWith(comparator) + .map { it.first } +} + +private fun String.naturalCompareTo(other: String): Int { + var firstIndex = 0 + var secondIndex = 0 + + while (firstIndex < length && secondIndex < other.length) { + val firstChar = this[firstIndex] + val secondChar = other[secondIndex] + + if (firstChar.isDigit() && secondChar.isDigit()) { + val firstEnd = indexOfFirstNonDigit(firstIndex) + val secondEnd = other.indexOfFirstNonDigit(secondIndex) + val firstNumber = substring(firstIndex, firstEnd) + val secondNumber = other.substring(secondIndex, secondEnd) + val firstSignificant = firstNumber.trimStart('0').ifEmpty { "0" } + val secondSignificant = secondNumber.trimStart('0').ifEmpty { "0" } + + val lengthComparison = firstSignificant.length.compareTo(secondSignificant.length) + if (lengthComparison != 0) return lengthComparison + + val numberComparison = firstSignificant.compareTo(secondSignificant) + if (numberComparison != 0) return numberComparison + + val leadingZeroComparison = firstNumber.length.compareTo(secondNumber.length) + if (leadingZeroComparison != 0) return leadingZeroComparison + + firstIndex = firstEnd + secondIndex = secondEnd + } else { + val characterComparison = + firstChar.lowercaseChar().compareTo(secondChar.lowercaseChar()) + if (characterComparison != 0) return characterComparison + + firstIndex++ + secondIndex++ + } + } + + return length.compareTo(other.length) +} + +private fun String.indexOfFirstNonDigit(startIndex: Int): Int { + var index = startIndex + while (index < length && this[index].isDigit()) index++ + return index +} + +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 + } + +object UriReplacements { + private val replacements = ConcurrentHashMap() + + fun register( + originalUri: Uri, + replacementUri: Uri + ) { + if (originalUri == Uri.EMPTY || originalUri == replacementUri) return + replacements[originalUri.toString()] = resolve(replacementUri.toString()) + } + + fun resolve(uri: String): String { + var current = uri + val visited = mutableSetOf() + + while (visited.add(current)) { + current = replacements[current] ?: break + } + + return current + } + + fun resolve(uri: Uri): Uri = resolve(uri.toString()).toUri() +} \ No newline at end of file diff --git a/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/Zip.kt b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/Zip.kt new file mode 100644 index 0000000..703b5d0 --- /dev/null +++ b/core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/Zip.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() +} \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/13.txt b/fastlane/metadata/android/en-US/changelogs/13.txt new file mode 100644 index 0000000..e6e7522 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/13.txt @@ -0,0 +1,3 @@ +* Update strings.xml by @Ilithy in #11 +* Redesign +* Bug fixes diff --git a/fastlane/metadata/android/en-US/changelogs/16.txt b/fastlane/metadata/android/en-US/changelogs/16.txt new file mode 100644 index 0000000..7ce026f --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/16.txt @@ -0,0 +1,5 @@ +### What's new +* Only cropping image +* Color picking +* Design improved +* Bug fixes diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 0000000..2eabc6b --- /dev/null +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1,60 @@ +

✨ 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
  • +
\ No newline at end of file diff --git a/fastlane/metadata/android/en-US/images/banner/banner1.png b/fastlane/metadata/android/en-US/images/banner/banner1.png new file mode 100644 index 0000000..2e9720d Binary files /dev/null and b/fastlane/metadata/android/en-US/images/banner/banner1.png differ diff --git a/fastlane/metadata/android/en-US/images/banner/banner2.png b/fastlane/metadata/android/en-US/images/banner/banner2.png new file mode 100644 index 0000000..a31dd75 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/banner/banner2.png differ diff --git a/fastlane/metadata/android/en-US/images/banner/banner3.png b/fastlane/metadata/android/en-US/images/banner/banner3.png new file mode 100644 index 0000000..ad1c258 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/banner/banner3.png differ diff --git a/fastlane/metadata/android/en-US/images/banner/banner_shapes.png b/fastlane/metadata/android/en-US/images/banner/banner_shapes.png new file mode 100644 index 0000000..ec1c819 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/banner/banner_shapes.png differ diff --git a/fastlane/metadata/android/en-US/images/buttons/fdroid.svg b/fastlane/metadata/android/en-US/images/buttons/fdroid.svg new file mode 100644 index 0000000..27b56b6 --- /dev/null +++ b/fastlane/metadata/android/en-US/images/buttons/fdroid.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + diff --git a/fastlane/metadata/android/en-US/images/buttons/github.svg b/fastlane/metadata/android/en-US/images/buttons/github.svg new file mode 100644 index 0000000..d0d70de --- /dev/null +++ b/fastlane/metadata/android/en-US/images/buttons/github.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/fastlane/metadata/android/en-US/images/buttons/gplay.svg b/fastlane/metadata/android/en-US/images/buttons/gplay.svg new file mode 100644 index 0000000..3f4fc1a --- /dev/null +++ b/fastlane/metadata/android/en-US/images/buttons/gplay.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + diff --git a/fastlane/metadata/android/en-US/images/buttons/obtainium-pre-release.svg b/fastlane/metadata/android/en-US/images/buttons/obtainium-pre-release.svg new file mode 100644 index 0000000..5feceba --- /dev/null +++ b/fastlane/metadata/android/en-US/images/buttons/obtainium-pre-release.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/fastlane/metadata/android/en-US/images/buttons/obtainium.svg b/fastlane/metadata/android/en-US/images/buttons/obtainium.svg new file mode 100644 index 0000000..430dde1 --- /dev/null +++ b/fastlane/metadata/android/en-US/images/buttons/obtainium.svg @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png new file mode 100644 index 0000000..1806921 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/icon.png differ diff --git a/fastlane/metadata/android/en-US/images/icon.svg b/fastlane/metadata/android/en-US/images/icon.svg new file mode 100644 index 0000000..e603ab2 --- /dev/null +++ b/fastlane/metadata/android/en-US/images/icon.svg @@ -0,0 +1 @@ +Image Toolbox diff --git a/fastlane/metadata/android/en-US/images/logo/logo.png b/fastlane/metadata/android/en-US/images/logo/logo.png new file mode 100644 index 0000000..b1bb685 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/logo/logo.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png new file mode 100644 index 0000000..ea90ec6 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png new file mode 100644 index 0000000..a3b6e19 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png new file mode 100644 index 0000000..8586812 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png new file mode 100644 index 0000000..1be68a6 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png new file mode 100644 index 0000000..5cc7a78 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png new file mode 100644 index 0000000..9a5b4d1 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png new file mode 100644 index 0000000..d0ae17e Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png differ diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 0000000..a07510e --- /dev/null +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +Edit, create & convert images with powerful AI — your all-in-one studio \ No newline at end of file diff --git a/fastlane/metadata/android/ru/full_description.txt b/fastlane/metadata/android/ru/full_description.txt new file mode 100644 index 0000000..e063e9e --- /dev/null +++ b/fastlane/metadata/android/ru/full_description.txt @@ -0,0 +1,81 @@ +Набор инструментов для работы с изображениями ✨ + +Возможности: + +
    +
  • Пакетная обработка
  • +
  • Применение цепочек фильтров (более 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 и в почти всех типов изображений
    • +
    • Сохранение в любую папку
    • +
    +
\ No newline at end of file diff --git a/fastlane/metadata/android/ru/short_description.txt b/fastlane/metadata/android/ru/short_description.txt new file mode 100644 index 0000000..1b7ca5e --- /dev/null +++ b/fastlane/metadata/android/ru/short_description.txt @@ -0,0 +1 @@ +Фильтр, Растягивание, Сравнение, Обрезание - делайте с изображениями что угодно \ No newline at end of file diff --git a/feature/ai-tools/.gitignore b/feature/ai-tools/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/ai-tools/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/ai-tools/build.gradle.kts b/feature/ai-tools/build.gradle.kts new file mode 100644 index 0000000..e7539f2 --- /dev/null +++ b/feature/ai-tools/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/AndroidManifest.xml b/feature/ai-tools/src/main/AndroidManifest.xml new file mode 100644 index 0000000..44008a4 --- /dev/null +++ b/feature/ai-tools/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/AiProcessor.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/AiProcessor.kt new file mode 100644 index 0000000..f0d75bb --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/AiProcessor.kt @@ -0,0 +1,585 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.OnnxTensor +import ai.onnxruntime.OrtSession +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.utils.extractMessage +import com.t8rin.imagetoolbox.core.utils.makeLog +import com.t8rin.imagetoolbox.feature.ai_tools.data.model.ModelInfo +import com.t8rin.imagetoolbox.feature.ai_tools.data.model.TensorSize +import com.t8rin.imagetoolbox.feature.ai_tools.data.model.Tile +import com.t8rin.imagetoolbox.feature.ai_tools.data.model.TileFiles +import com.t8rin.imagetoolbox.feature.ai_tools.data.model.TileGrid +import com.t8rin.imagetoolbox.feature.ai_tools.data.utils.AiExtensions.LOG_TAG +import com.t8rin.imagetoolbox.feature.ai_tools.data.utils.AiExtensions.OPAQUE +import com.t8rin.imagetoolbox.feature.ai_tools.data.utils.appendControlInputs +import com.t8rin.imagetoolbox.feature.ai_tools.data.utils.clamp255 +import com.t8rin.imagetoolbox.feature.ai_tools.data.utils.createInputTensor +import com.t8rin.imagetoolbox.feature.ai_tools.data.utils.extractOutputArray +import com.t8rin.imagetoolbox.feature.ai_tools.data.utils.fitToTensorSize +import com.t8rin.imagetoolbox.feature.ai_tools.data.utils.mixColors +import com.t8rin.imagetoolbox.feature.ai_tools.data.utils.readModelInput +import com.t8rin.imagetoolbox.feature.ai_tools.data.utils.smoothStep +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 dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.atomicfu.atomic +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit +import kotlinx.coroutines.withContext +import java.io.File +import java.io.FileOutputStream +import javax.inject.Inject + +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 = { + "Processing was cancelled; dropping temporary tiles".makeLog(LOG_TAG) + chunksDir.deleteRecursively() + }, + onFailure = { error -> + listener.onError(error.extractMessage()) + }, + action = { + renderBitmap( + session = session, + source = inputBitmap, + listener = object : AiProgressListener { + override fun onError(error: String) { + listener.onError(error) + } + + override fun onProgress(currentChunkIndex: Int, totalChunks: Int) { + listener.onProgress( + currentChunkIndex = currentChunkIndex, + totalChunks = totalChunks + ) + + if (totalChunks > 0) { + service.updateProgress( + done = currentChunkIndex, + total = totalChunks + ) + } + } + }, + info = ModelInfo( + strength = params.strength, + session = session, + chunkSize = params.chunkSize, + overlap = params.overlap, + model = model, + disableChunking = !params.enableChunking + ), + parallelWorkers = params.parallelWorkers + ) + } + ) + } + + private suspend fun renderBitmap( + session: OrtSession, + source: Bitmap, + listener: AiProgressListener, + info: ModelInfo, + parallelWorkers: Int, + ): Bitmap = withContext(defaultDispatcher) { + ensureActive() + + val tileLimit = info.tileLimit + + if (source.width > tileLimit || source.height > tileLimit) { + renderByTiles( + session = session, + source = source, + listener = listener, + info = info, + tileLimit = tileLimit, + requestedWorkers = parallelWorkers + ) + } else { + renderSingleBitmap( + session = session, + source = source, + info = info + ) + } + } + + private suspend fun renderSingleBitmap( + session: OrtSession, + source: Bitmap, + info: ModelInfo + ): Bitmap { + val workingCopy = source.copy(Bitmap.Config.ARGB_8888, true) + return try { + runModelOnBitmap( + session = session, + bitmap = workingCopy, + info = info + ) + } finally { + workingCopy.recycle() + } + } + + private suspend fun renderByTiles( + session: OrtSession, + source: Bitmap, + listener: AiProgressListener, + info: ModelInfo, + tileLimit: Int, + requestedWorkers: Int + ): Bitmap = withContext(defaultDispatcher) { + val grid = TileGrid.from( + imageWidth = source.width, + imageHeight = source.height, + tileLimit = tileLimit, + overlap = info.overlap + ) + + listOf( + "Tile mode ${source.width}x${source.height}", + "limit=$tileLimit", + "step=${grid.step}", + "grid=${grid.columns}x${grid.rows}", + "overlap=${info.overlap}" + ).joinToString().makeLog(LOG_TAG) + + val tiles = grid.tiles { index -> + TileFiles( + input = File(chunksDir, "ai_tile_$index.png"), + output = File(chunksDir, "ai_tile_${index}_out.png") + ) + } + + try { + writeSourceTiles( + source = source, + tiles = tiles + ) + + "Stored ${tiles.size} tile(s) for AI processing".makeLog(LOG_TAG) + if (tiles.size > 1) { + listener.onProgress(0, tiles.size) + } + + processTiles( + session = session, + tiles = tiles, + info = info, + listener = listener, + requestedWorkers = requestedWorkers + ) + + composeTiles( + tiles = tiles, + imageSize = TensorSize(source.width, source.height), + overlap = info.overlap, + scaleFactor = info.scaleFactor + ) + } finally { + chunksDir.deleteRecursively() + } + } + + private suspend fun processTiles( + session: OrtSession, + tiles: List, + info: ModelInfo, + listener: AiProgressListener, + requestedWorkers: Int + ) = coroutineScope { + val workers = resolveParallelWorkers( + requestedWorkers = requestedWorkers, + tileCount = tiles.size + ) + + listOf( + "Processing ${tiles.size} tile(s)", + "workers=$workers", + "requested=$requestedWorkers" + ).joinToString().makeLog(LOG_TAG) + + val completedTiles = atomic(0) + + suspend fun processTile(tile: Tile) { + ensureActive() + transformStoredTile( + session = session, + tile = tile, + info = info + ) + + if (tiles.size > 1) { + listener.onProgress( + currentChunkIndex = completedTiles.incrementAndGet(), + totalChunks = tiles.size + ) + } + } + + if (workers <= 1) { + tiles.forEach { tile -> + processTile(tile) + } + } else { + val gate = Semaphore(workers) + + tiles.forEach { tile -> + launch { + gate.withPermit { + processTile(tile) + } + } + } + } + } + + private suspend fun writeSourceTiles( + source: Bitmap, + tiles: List, + ) = coroutineScope { + tiles.forEach { tile -> + ensureActive() + val extracted = Bitmap.createBitmap( + source, + tile.area.x, + tile.area.y, + tile.area.width, + tile.area.height + ) + + try { + savePng( + bitmap = extracted, + file = tile.files.input + ) + } finally { + extracted.recycle() + } + } + } + + private suspend fun transformStoredTile( + session: OrtSession, + tile: Tile, + info: ModelInfo + ) { + val tileBitmap = loadBitmap( + file = tile.files.input, + label = "tile ${tile.index}" + ) + + val transformed = try { + runModelOnBitmap( + session = session, + bitmap = tileBitmap, + info = info + ) + } finally { + tileBitmap.recycle() + } + + try { + savePng( + bitmap = transformed, + file = tile.files.output + ) + withContext(ioDispatcher) { + tile.files.input.delete() + } + } finally { + transformed.recycle() + } + } + + private suspend fun composeTiles( + tiles: List, + imageSize: TensorSize, + overlap: Int, + scaleFactor: Int + ): Bitmap = withContext(defaultDispatcher) { + val outputSize = imageSize.scaledBy(scaleFactor) + val result = createBitmap(outputSize.width, outputSize.height) + + tiles.forEach { tile -> + ensureActive() + val tileBitmap = loadBitmap( + file = tile.files.output, + label = "processed tile ${tile.index}" + ) + + try { + drawTile( + result = result, + tileBitmap = tileBitmap, + tile = tile, + overlap = overlap, + scaleFactor = scaleFactor + ) + } finally { + tileBitmap.recycle() + } + } + + result + } + + private suspend fun runModelOnBitmap( + session: OrtSession, + bitmap: Bitmap, + info: ModelInfo + ): Bitmap = withContext(defaultDispatcher) { + ensureActive() + + val sourceSize = TensorSize(bitmap.width, bitmap.height) + val tensorSize = info.tensorSizeFor(sourceSize) + val tensorBitmap = bitmap.fitToTensorSize(target = tensorSize) + val hasAlpha = bitmap.hasAlpha() + val alpha = if (hasAlpha) FloatArray(tensorSize.pixelCount) else null + val inputFloats = tensorBitmap.bitmap.readModelInput( + channels = info.inputChannels, + alpha = alpha + ) + val inputShape = longArrayOf( + 1, + info.inputChannels.toLong(), + tensorSize.height.toLong(), + tensorSize.width.toLong() + ) + val tensors = linkedMapOf() + + try { + tensors[info.inputName] = createInputTensor( + data = inputFloats, + shape = inputShape, + fp16 = info.isFp16 + ) + appendControlInputs( + destination = tensors, + info = info + ) + + session.run(tensors).use { result -> + val modelOutputSize = tensorSize.scaledBy(info.scaleFactor) + val (outputFloats, actualChannels) = withContext(defaultDispatcher) { + extractOutputArray( + outputValue = result[0].value, + channels = info.outputChannels, + h = modelOutputSize.height, + w = modelOutputSize.width + ) + } + + val rendered = renderModelOutput( + values = outputFloats, + channels = actualChannels, + outputSize = modelOutputSize, + sourceSize = tensorSize, + alpha = alpha, + scaleFactor = info.scaleFactor + ) + + if (tensorSize == sourceSize) { + rendered + } else { + val cropSize = sourceSize.scaledBy(info.scaleFactor) + Bitmap.createBitmap( + rendered, + 0, + 0, + cropSize.width, + cropSize.height + ).also { + rendered.recycle() + } + } + } + } finally { + tensors.values.forEach(OnnxTensor::close) + if (tensorBitmap.recycleAfterUse) { + tensorBitmap.bitmap.recycle() + } + } + } + + private suspend fun renderModelOutput( + values: FloatArray, + channels: Int, + outputSize: TensorSize, + sourceSize: TensorSize, + alpha: FloatArray?, + scaleFactor: Int + ): Bitmap = coroutineScope { + val pixels = IntArray(outputSize.pixelCount) + val planeSize = outputSize.pixelCount + + for (index in pixels.indices) { + ensureActive() + val alphaValue = alpha?.let { + val sourceY = ((index / outputSize.width) / scaleFactor) + .coerceIn(0, sourceSize.height - 1) + val sourceX = ((index % outputSize.width) / scaleFactor) + .coerceIn(0, sourceSize.width - 1) + clamp255(it[sourceY * sourceSize.width + sourceX] * OPAQUE) + } ?: OPAQUE + + pixels[index] = if (channels == 1) { + val gray = clamp255(values[index] * OPAQUE) + Color.argb(alphaValue, gray, gray, gray) + } else { + Color.argb( + alphaValue, + clamp255(values[index] * OPAQUE), + clamp255(values[planeSize + index] * OPAQUE), + clamp255(values[planeSize * 2 + index] * OPAQUE) + ) + } + } + + createBitmap(outputSize.width, outputSize.height).apply { + setPixels(pixels, 0, outputSize.width, 0, 0, outputSize.width, outputSize.height) + } + } + + private suspend fun drawTile( + result: Bitmap, + tileBitmap: Bitmap, + tile: Tile, + overlap: Int, + scaleFactor: Int + ) = withContext(defaultDispatcher) { + val targetX = tile.area.x * scaleFactor + val targetY = tile.area.y * scaleFactor + val blendWidth = overlap * scaleFactor + val shouldBlendLeft = tile.position.column > 0 + val shouldBlendTop = tile.position.row > 0 + + if (!shouldBlendLeft && !shouldBlendTop) { + Canvas(result).drawBitmap(tileBitmap, targetX.toFloat(), targetY.toFloat(), null) + return@withContext + } + + val width = tileBitmap.width + val height = tileBitmap.height + val existing = IntArray(width * height) + val incoming = IntArray(width * height) + + try { + result.getPixels(existing, 0, width, targetX, targetY, width, height) + } catch (_: Throwable) { + Canvas(result).drawBitmap(tileBitmap, targetX.toFloat(), targetY.toFloat(), null) + return@withContext + } + + tileBitmap.getPixels(incoming, 0, width, 0, 0, width, height) + + for (localY in 0 until height) { + for (localX in 0 until width) { + ensureActive() + + val mixLeft = shouldBlendLeft && localX < blendWidth + val mixTop = shouldBlendTop && localY < blendWidth + if (!mixLeft && !mixTop) continue + + val blend = minOf( + if (mixLeft) smoothStep(localX, blendWidth) else 1f, + if (mixTop) smoothStep(localY, blendWidth) else 1f + ) + val index = localY * width + localX + incoming[index] = mixColors( + from = existing[index], + to = incoming[index], + amount = blend + ) + } + } + + result.setPixels(incoming, 0, width, targetX, targetY, width, height) + } + + private fun resolveParallelWorkers( + requestedWorkers: Int, + tileCount: Int + ): Int { + val detectedCores = Runtime.getRuntime().availableProcessors().coerceAtLeast(1) + val workers = if (requestedWorkers <= 0) { + when { + detectedCores >= 8 -> 4 + detectedCores >= 6 -> 2 + else -> 1 + } + } else { + requestedWorkers + } + + return workers.coerceIn(1, minOf(detectedCores, tileCount.coerceAtLeast(1))) + } + + private suspend fun savePng( + bitmap: Bitmap, + file: File + ) = withContext(ioDispatcher) { + FileOutputStream(file).use { stream -> + bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream) + } + } + + private suspend fun loadBitmap( + file: File, + label: String + ): Bitmap = withContext(ioDispatcher) { + BitmapFactory.decodeFile(file.absolutePath) + } ?: error("Could not read $label") + +} diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/AndroidAiToolsRepository.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/AndroidAiToolsRepository.kt new file mode 100644 index 0000000..8a0ec0b --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/AndroidAiToolsRepository.kt @@ -0,0 +1,517 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.extractMessage +import com.t8rin.imagetoolbox.core.utils.filename +import com.t8rin.imagetoolbox.core.utils.makeLog +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.neural_tools.bgremover.BgRemover +import com.t8rin.neural_tools.bgremover.GenericBackgroundRemover +import com.t8rin.neural_tools.inpaint.WatermarkRemoverProcessor +import com.t8rin.neural_tools.scans.UvDocUnwarper +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.onCompletion +import kotlinx.coroutines.flow.onStart +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, + WatermarkRemoverProcessor.isDownloaded + ).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.isWatermarkRemover) { + WatermarkRemoverProcessor.startDownload() + .onStart { + trySend( + DownloadProgress( + currentPercent = 0f, + currentTotalSize = model.downloadSize + ) + ) + } + .onCompletion { + selectModelForced(model) + close() + } + .collect { + trySend( + DownloadProgress( + currentPercent = it.currentPercent, + currentTotalSize = it.currentTotalSize + ) + ) + } + } else 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) + } + } + } + + model.isWatermarkRemover -> { + processImage { + withClosedSession(listener) { + listener.onProgress(0, 2) + WatermarkRemoverProcessor.removeWatermark( + image = image, + onMaskFound = { + listener.onProgress(1, 2) + } + ) ?: run { + listener.onError(getString(R.string.processing_failed)) + null + } + } + } + } + + model.isUvDocUnwarper -> { + 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)) + } + + keepAliveService.track( + onFailure = { + listener.onError(it.extractMessage()) + }, + action = { + listener.onProgress(0, 1) + UvDocUnwarper.unwarp( + image = image, + session = ortSession + )?.also { + listener.onProgress(1, 1) + } ?: run { + listener.onError(getString(R.string.processing_failed)) + null + } + } + ) + } + } + + 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.extractMessage()) + }, + 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) { + if (model.isWatermarkRemover) { + WatermarkRemoverProcessor.deleteDownloadedModels() + } else { + model.file.delete() + model.asBgRemover()?.checkModel() + } + if (selectedModel.value?.name == model.name) selectModel(null) + updateFlow.emit(Unit) + } + + override fun cleanup() { + BgRemover.closeAll() + WatermarkRemoverProcessor.close() + 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.isFile && !it.name.orEmpty() + .endsWith(".tmp") && !it.name.isNullOrEmpty() && it.length() > 0 + }.also { files -> + val watermarkFile = + WatermarkRemoverProcessor.modelFile.takeIf { it.exists() && it.length() > 0 } + + onGetFiles( + if (watermarkFile != null) files + watermarkFile else files + ) + }.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)) + ) + }.let { fromDir -> + if (WatermarkRemoverProcessor.isDownloaded.value) { + val watermarkModel = NeuralModel.entries.find { it.isWatermarkRemover } + ?: return@let fromDir + + if (fromDir.none { it.name == watermarkModel.name }) { + fromDir + watermarkModel + } else { + fromDir + } + } else { + fromDir.filter { !it.isWatermarkRemover } + } + } + } + + 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("modnet_portrait_matting") -> BgRemover.Type.MODNet + name.startsWith("isnet") -> BgRemover.Type.ISNet + name.startsWith("yolo") -> BgRemover.Type.YOLO + 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") \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/ImageTensor.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/ImageTensor.kt new file mode 100644 index 0000000..124d2d8 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/ImageTensor.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.TensorInfo +import kotlin.math.abs + +internal data class ImageTensor( + val name: String, + val channels: Int, + val isFloat16: Boolean, + val height: Int?, + val width: Int?, + private val rawChannels: Long +) { + fun outputChannels(keepSignedValue: Boolean): Int { + val channelsValue = if (keepSignedValue) rawChannels else abs(rawChannels) + return if (channelsValue == 1L) 1 else 3 + } + + companion object { + fun Map.firstImageTensor(): ImageTensor? { + fun TensorInfo.isImageFloatTensor(shape: LongArray): Boolean { + val isFloatTensor = type == OnnxJavaType.FLOAT || type == OnnxJavaType.FLOAT16 + return isFloatTensor && shape.size == 4 + } + + fun Long.positiveDimension(): Int? = takeIf { it > 0L }?.toInt() + + for ((name, nodeInfo) in this) { + val tensorInfo = nodeInfo.info as? TensorInfo ?: continue + val shape = tensorInfo.shape + if (!tensorInfo.isImageFloatTensor(shape)) continue + + return ImageTensor( + name = name, + channels = if (shape[1] == 1L) 1 else 3, + isFloat16 = tensorInfo.type == OnnxJavaType.FLOAT16, + height = shape[2].positiveDimension(), + width = shape[3].positiveDimension(), + rawChannels = shape[1] + ) + } + + return null + } + } +} diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/ModelInfo.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/ModelInfo.kt new file mode 100644 index 0000000..13348ae --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/ModelInfo.kt @@ -0,0 +1,116 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.OrtSession +import com.t8rin.imagetoolbox.core.utils.makeLog +import com.t8rin.imagetoolbox.feature.ai_tools.data.model.ImageTensor.Companion.firstImageTensor +import com.t8rin.imagetoolbox.feature.ai_tools.data.utils.AiExtensions.MODEL_ALIGNMENT +import com.t8rin.imagetoolbox.feature.ai_tools.data.utils.roundedUpTo +import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel + +internal class ModelInfo( + val strength: Float, + val overlap: Int, + chunkSize: Int, + disableChunking: Boolean, + session: OrtSession, + model: NeuralModel +) { + val inputInfoMap: Map = session.inputInfo + + private val inputTensor = inputInfoMap.firstImageTensor() + ?: error("ONNX session does not expose an image input tensor") + + val inputName: String = inputTensor.name + val inputChannels: Int = inputTensor.channels + val isScuNet = model.name.startsWith("scunet_") + val outputChannels: Int = session.outputInfo + .firstImageTensor() + ?.outputChannels(keepSignedValue = isScuNet) + ?: 3 + val isFp16: Boolean = inputTensor.isFloat16 + val expectedWidth: Int? = inputTensor.width + val expectedHeight: Int? = inputTensor.height + val isScuNetColor = model.name.startsWith("scunet_color") + val isNonChunkable = model.isNonChunkable || disableChunking + + val minSpatialSize = spatialSizeMap.entries.find { + model.name.contains(it.key) + }?.value ?: 256 + + val scaleFactor: Int = scaleMap.entries.find { + model.name.contains(it.key) + }?.value ?: 1 + + val tileLimit = run { + if (isNonChunkable) return@run Int.MAX_VALUE + + val chunkSize = if (isScuNet) { + minOf(chunkSize, 256) + } else { + chunkSize + } + + if (expectedWidth != null && expectedHeight != null) { + minOf(chunkSize, expectedWidth, expectedHeight) + } else { + chunkSize + } + } + + fun tensorSizeFor(source: TensorSize): TensorSize { + val useMinimumSide = isScuNetColor || minSpatialSize > 256 + val width = expectedWidth?.takeIf { it > 0 } ?: run { + val aligned = source.width.roundedUpTo(MODEL_ALIGNMENT) + if (useMinimumSide) maxOf(aligned, minSpatialSize) else aligned + } + val height = expectedHeight?.takeIf { it > 0 } ?: run { + val aligned = source.height.roundedUpTo(MODEL_ALIGNMENT) + if (useMinimumSide) maxOf(aligned, minSpatialSize) else aligned + } + return TensorSize(width, height) + } + + init { + "Model chunk=$chunkSize, overlap=$overlap, scale=$scaleFactor".makeLog("ModelInfo") + + val inputType = if (isFp16) "FP16" else "FP32" + val widthText = expectedWidth ?: "dynamic" + val heightText = expectedHeight ?: "dynamic" + val tensorText = "Tensor input=$inputType/$inputChannels channel(s), " + + "output=$outputChannels channel(s), " + + "size=${widthText}x$heightText" + + tensorText.makeLog("ModelInfo") + } +} + +private val scaleMap = buildMap { + repeat(16) { + val scale = it + 1 + + put("x$scale", scale) + put("${scale}x", scale) + } +} + +private val spatialSizeMap = mapOf( + "nafnet" to 512 +) diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/PreparedBitmap.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/PreparedBitmap.kt new file mode 100644 index 0000000..47d55b6 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/PreparedBitmap.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 android.graphics.Bitmap + +internal data class PreparedBitmap( + val bitmap: Bitmap, + val recycleAfterUse: Boolean +) \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/TensorSize.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/TensorSize.kt new file mode 100644 index 0000000..fc078cb --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/TensorSize.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +internal data class TensorSize( + val width: Int, + val height: Int +) { + val pixelCount: Int + get() = width * height + + fun scaledBy(scale: Int): TensorSize = TensorSize( + width = width * scale, + height = height * scale + ) +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/Tile.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/Tile.kt new file mode 100644 index 0000000..8e63c06 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/Tile.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +internal data class Tile( + val index: Int, + val area: TileArea, + val position: TilePosition, + val files: TileFiles +) \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/TileArea.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/TileArea.kt new file mode 100644 index 0000000..5ca8e19 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/TileArea.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +internal data class TileArea( + val x: Int, + val y: Int, + val width: Int, + val height: Int +) \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/TileFiles.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/TileFiles.kt new file mode 100644 index 0000000..38917cd --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/TileFiles.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 TileFiles( + val input: File, + val output: File +) \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/TileGrid.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/TileGrid.kt new file mode 100644 index 0000000..41381d1 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/TileGrid.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 kotlin.math.ceil + +internal data class TileGrid( + val imageWidth: Int, + val imageHeight: Int, + val tileLimit: Int, + val overlap: Int, + val step: Int, + val columns: Int, + val rows: Int +) { + fun tiles(filesFor: (Int) -> TileFiles): List = buildList { + var index = 0 + for (row in 0 until rows) { + for (column in 0 until columns) { + val x = column * step + val y = row * step + val width = minOf(x + tileLimit, imageWidth) - x + val height = minOf(y + tileLimit, imageHeight) - y + + if (width > 0 && height > 0) { + add( + Tile( + index = index, + area = TileArea( + x = x, + y = y, + width = width, + height = height + ), + position = TilePosition( + column = column, + row = row + ), + files = filesFor(index) + ) + ) + index++ + } + } + } + } + + companion object { + fun from( + imageWidth: Int, + imageHeight: Int, + tileLimit: Int, + overlap: Int + ): TileGrid { + val step = tileLimit - overlap + val columns = if (imageWidth <= tileLimit) { + 1 + } else { + ceil((imageWidth - overlap).toFloat() / step).toInt() + } + val rows = if (imageHeight <= tileLimit) { + 1 + } else { + ceil((imageHeight - overlap).toFloat() / step).toInt() + } + + return TileGrid( + imageWidth = imageWidth, + imageHeight = imageHeight, + tileLimit = tileLimit, + overlap = overlap, + step = step, + columns = columns, + rows = rows + ) + } + } +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/TilePosition.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/TilePosition.kt new file mode 100644 index 0000000..e280def --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/TilePosition.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +internal data class TilePosition( + val column: Int, + val row: Int +) \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/utils/AiExtensions.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/utils/AiExtensions.kt new file mode 100644 index 0000000..fd64d45 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/utils/AiExtensions.kt @@ -0,0 +1,281 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.utils + +import ai.onnxruntime.OnnxJavaType +import ai.onnxruntime.OnnxTensor +import ai.onnxruntime.OrtEnvironment +import ai.onnxruntime.TensorInfo +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import androidx.core.graphics.createBitmap +import com.t8rin.imagetoolbox.core.utils.makeLog +import com.t8rin.imagetoolbox.feature.ai_tools.data.model.ModelInfo +import com.t8rin.imagetoolbox.feature.ai_tools.data.model.PreparedBitmap +import com.t8rin.imagetoolbox.feature.ai_tools.data.model.TensorSize +import com.t8rin.imagetoolbox.feature.ai_tools.data.utils.AiExtensions.LOG_TAG +import com.t8rin.imagetoolbox.feature.ai_tools.data.utils.AiExtensions.OPAQUE +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.ensureActive +import java.lang.Float.floatToIntBits +import java.lang.Float.intBitsToFloat +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.FloatBuffer + +internal object AiExtensions { + const val LOG_TAG = "AiProcessor" + const val MODEL_ALIGNMENT = 8 + const val OPAQUE = 255 +} + +internal fun logNestedShape( + precision: String, + batchCount: Int, + channels: Int, + height: Int, + width: Int, + copiedChannels: Int +) { + val message = "Nested $precision output shape: " + + "[$batchCount, $channels, $height, $width], " + + "reading $copiedChannels channel(s)" + message.makeLog(LOG_TAG) +} + +internal fun LongArray.withResolvedUnknowns(): LongArray = clone().also { shape -> + for (index in shape.indices) { + if (shape[index] == -1L) shape[index] = 1L + } +} + +internal fun Int.roundedUpTo(block: Int): Int = ((this + block - 1) / block) * block + +internal fun clamp255(value: Float): Int = value.toInt().coerceIn(0, OPAQUE) + +internal fun smoothStep( + position: Int, + length: Int +): Float { + val x = (position.toFloat() / (length - 1).coerceAtLeast(1)).coerceIn(0f, 1f) + return x * x * (3f - 2f * x) +} + +internal fun mixColors( + from: Int, + to: Int, + amount: Float +): Int { + val keep = 1f - amount + return Color.argb( + (keep * Color.alpha(from) + amount * Color.alpha(to)).toInt(), + (keep * Color.red(from) + amount * Color.red(to)).toInt(), + (keep * Color.green(from) + amount * Color.green(to)).toInt(), + (keep * Color.blue(from) + amount * Color.blue(to)).toInt() + ) +} + +internal 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() +} + +internal 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 normalizedExponent = -14 + var normalizedMantissa = mantissa + while ((normalizedMantissa and 0x400) == 0) { + normalizedMantissa = normalizedMantissa shl 1 + normalizedExponent-- + } + normalizedMantissa = normalizedMantissa and 0x3FF + return intBitsToFloat( + sign or ((normalizedExponent + 127) shl 23) or (normalizedMantissa 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)) +} + +internal fun createStrengthTensor( + env: OrtEnvironment, + strength: Float, + shape: LongArray, + type: OnnxJavaType +): OnnxTensor { + if (type == OnnxJavaType.FLOAT16) { + val storage = ByteBuffer + .allocateDirect(Short.SIZE_BYTES) + .order(ByteOrder.nativeOrder()) + storage.asShortBuffer().put(floatToFloat16(strength)) + storage.rewind() + return OnnxTensor.createTensor(env, storage, shape, OnnxJavaType.FLOAT16) + } + + return OnnxTensor.createTensor( + env, + FloatBuffer.wrap(floatArrayOf(strength)), + shape + ) +} + +internal fun createInputTensor( + data: FloatArray, + shape: LongArray, + fp16: Boolean +): OnnxTensor { + val env = OrtEnvironment.getEnvironment() + + if (!fp16) { + return OnnxTensor.createTensor(env, FloatBuffer.wrap(data), shape) + } + + val halfPrecision = ShortArray(data.size) { index -> + floatToFloat16(data[index]) + } + val storage = ByteBuffer + .allocateDirect(halfPrecision.size * Short.SIZE_BYTES) + .order(ByteOrder.nativeOrder()) + storage.asShortBuffer().put(halfPrecision) + storage.rewind() + return OnnxTensor.createTensor(env, storage, shape, OnnxJavaType.FLOAT16) +} + +internal fun appendControlInputs( + destination: MutableMap, + info: ModelInfo +) { + val env = OrtEnvironment.getEnvironment() + + info.inputInfoMap.forEach { (name, nodeInfo) -> + if (name == info.inputName) return@forEach + + val tensorInfo = nodeInfo.info as? TensorInfo ?: return@forEach + if (tensorInfo.type != OnnxJavaType.FLOAT && tensorInfo.type != OnnxJavaType.FLOAT16) { + return@forEach + } + + val shape = tensorInfo.shape.withResolvedUnknowns() + if (shape.contentEquals(longArrayOf(1L, 1L))) { + destination[name] = createStrengthTensor( + env = env, + strength = info.strength / 100f, + shape = shape, + type = tensorInfo.type + ) + } + } +} + +internal suspend fun Bitmap.fitToTensorSize( + target: TensorSize +): PreparedBitmap = coroutineScope { + if (width == target.width && height == target.height) { + return@coroutineScope PreparedBitmap( + bitmap = this@fitToTensorSize, + recycleAfterUse = false + ) + } + + "Extending bitmap edges from ${width}x$height to ${target.width}x${target.height}".makeLog( + LOG_TAG + ) + val padded = createBitmap(target.width, target.height) + val canvas = Canvas(padded) + canvas.drawBitmap(this@fitToTensorSize, 0f, 0f, null) + + if (target.width > width) { + val edge = Bitmap.createBitmap(this@fitToTensorSize, width - 1, 0, 1, height) + try { + for (x in width until target.width) { + ensureActive() + canvas.drawBitmap(edge, x.toFloat(), 0f, null) + } + } finally { + edge.recycle() + } + } + + if (target.height > height) { + val edge = Bitmap.createBitmap(padded, 0, height - 1, target.width, 1) + try { + for (y in height until target.height) { + ensureActive() + canvas.drawBitmap(edge, 0f, y.toFloat(), null) + } + } finally { + edge.recycle() + } + } + + PreparedBitmap( + bitmap = padded, + recycleAfterUse = true + ) +} + +internal suspend fun Bitmap.readModelInput( + channels: Int, + alpha: FloatArray? +): FloatArray = coroutineScope { + val size = TensorSize(width, height) + val pixels = IntArray(size.pixelCount) + getPixels(pixels, 0, size.width, 0, 0, size.width, size.height) + + FloatArray(channels * size.pixelCount).also { output -> + pixels.forEachIndexed { index, color -> + ensureActive() + if (channels == 1) { + output[index] = ( + Color.red(color) + Color.green(color) + Color.blue(color) + ) / 3 / 255f + } else { + output[index] = Color.red(color) / 255f + output[size.pixelCount + index] = Color.green(color) / 255f + output[size.pixelCount * 2 + index] = Color.blue(color) / 255f + } + + alpha?.set(index, Color.alpha(color) / 255f) + } + } +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/utils/OnnxConversion.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/utils/OnnxConversion.kt new file mode 100644 index 0000000..bcea18c --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/utils/OnnxConversion.kt @@ -0,0 +1,155 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.utils + +import com.t8rin.imagetoolbox.core.utils.makeLog +import com.t8rin.imagetoolbox.feature.ai_tools.data.utils.AiExtensions.LOG_TAG +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.ensureActive + +internal suspend fun extractOutputArray( + outputValue: Any, + channels: Int, + h: Int, + w: Int +): Pair = coroutineScope { + ensureActive() + "ONNX output payload: ${outputValue.javaClass.name}".makeLog(LOG_TAG) + + when (outputValue) { + is FloatArray -> outputValue to channels + is ShortArray -> FloatArray(outputValue.size) { index -> + float16ToFloat(outputValue[index]) + } to channels + + is Array<*> -> readNestedOutput( + outputValue = outputValue, + channels = channels, + height = h, + width = w + ) + + else -> throw IllegalArgumentException( + "Unsupported ONNX output: ${outputValue.javaClass}" + ) + } +} + +@Suppress("UNCHECKED_CAST") +private suspend fun readNestedOutput( + outputValue: Array<*>, + channels: Int, + height: Int, + width: Int +): Pair { + val firstError = runCatching { + copyNestedFloatOutput( + tensor = outputValue as Array>>, + requestedChannels = channels, + height = height, + width = width + ) + } + + if (firstError.isSuccess) { + return firstError.getOrThrow() + } + + return runCatching { + copyNestedHalfOutput( + tensor = outputValue as Array>>, + requestedChannels = channels, + height = height, + width = width + ) + }.getOrElse { secondError -> + throw IllegalStateException( + "Unable to unpack ONNX result: " + + "${firstError.exceptionOrNull()?.message}, ${secondError.message}" + ) + } +} + +private suspend fun copyNestedFloatOutput( + tensor: Array>>, + requestedChannels: Int, + height: Int, + width: Int +): Pair = coroutineScope { + val batchCount = tensor.size + val actualChannels = tensor[0].size + val actualHeight = tensor[0][0].size + val actualWidth = tensor[0][0][0].size + val channelsToCopy = maxOf(requestedChannels, actualChannels) + + logNestedShape( + precision = "FP32", + batchCount = batchCount, + channels = actualChannels, + height = actualHeight, + width = actualWidth, + copiedChannels = channelsToCopy + ) + + val flattened = FloatArray(channelsToCopy * height * width) + for (channel in 0 until channelsToCopy) { + for (y in 0 until height) { + for (x in 0 until width) { + ensureActive() + flattened[channel * height * width + y * width + x] = tensor[0][channel][y][x] + } + } + } + + flattened to channelsToCopy +} + +private suspend fun copyNestedHalfOutput( + tensor: Array>>, + requestedChannels: Int, + height: Int, + width: Int +): Pair = coroutineScope { + val batchCount = tensor.size + val actualChannels = tensor[0].size + val actualHeight = tensor[0][0].size + val actualWidth = tensor[0][0][0].size + val channelsToCopy = maxOf(requestedChannels, actualChannels) + + logNestedShape( + precision = "FP16", + batchCount = batchCount, + channels = actualChannels, + height = actualHeight, + width = actualWidth, + copiedChannels = channelsToCopy + ) + + val flattened = FloatArray(channelsToCopy * height * width) + for (channel in 0 until channelsToCopy) { + for (y in 0 until height) { + for (x in 0 until width) { + ensureActive() + flattened[channel * height * width + y * width + x] = + float16ToFloat(tensor[0][channel][y][x]) + } + } + } + + flattened to channelsToCopy +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/di/AiToolsModule.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/di/AiToolsModule.kt new file mode 100644 index 0000000..35ba9b7 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/di/AiToolsModule.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/AiProgressListener.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/AiProgressListener.kt new file mode 100644 index 0000000..d59f8c7 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/AiProgressListener.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/AiToolsRepository.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/AiToolsRepository.kt new file mode 100644 index 0000000..ddcf8f0 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/AiToolsRepository.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/model/NeuralConstants.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/model/NeuralConstants.kt new file mode 100644 index 0000000..661bf9f --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/model/NeuralConstants.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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" +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/model/NeuralModel.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/model/NeuralModel.kt new file mode 100644 index 0000000..32876e0 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/model/NeuralModel.kt @@ -0,0 +1,1027 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 isWatermarkRemover = name == WATERMARK_REMOVER_MODEL_NAME + val isUvDocUnwarper = name == "uvdoc_grid.onnx" + + val isNonChunkable = + name.contains("ddcolor") || type == Type.REMOVE_BG || isWatermarkRemover || isUvDocUnwarper + + 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 { + private const val WATERMARK_REMOVER_MODEL_NAME = "watermark_mit_b5_sigmoid.onnx" + + 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("zero_dcepp.onnx"), + title = "Zero-DCE++", + description = R.string.model_zero_dcepp, + type = Type.ENHANCE, + downloadSize = 52261, + speed = Speed.VeryFast(0.002f), + checksum = "8a78b1d5dd66dae64a12b87e9908215d70fc9ae941dd55006b2beebdf9c510a3" + ), + 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("modnet_portrait_matting.onnx"), + title = "MODNet", + description = R.string.model_modnet, + type = Type.REMOVE_BG, + downloadSize = 25888640, + speed = Speed.VeryFast(0.061f), + checksum = "07c308cf0fc7e6e8b2065a12ed7fc07e1de8febb7dc7839d7b7f15dd66584df9" + ), + 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(WATERMARK_REMOVER_MODEL_NAME), + title = "Watermark Remover", + description = R.string.model_watermark_remover, + type = Type.ARTIFACTS, + downloadSize = 340_590_653, + speed = Speed.Normal(8.324f), + checksum = "" + ), + 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("restormer_deraining.onnx"), + title = "Restormer Deraining", + description = R.string.model_restormer_deraining, + type = Type.ENHANCE, + downloadSize = 107114307, + speed = Speed.Normal(12.615f), + checksum = "1ef76f2f59cfe98fd626590f11e2c2cbe5b0870d9a4ce4f2b01ca3f02caefc87" + ), + NeuralModel( + downloadLink = res("uvdoc_grid.onnx"), + title = "UVDOC Grid", + description = R.string.model_uvdoc_grid, + type = Type.SCANS, + downloadSize = 31802768, + speed = Speed.VeryFast(0.912f), + checksum = "16cb598b88b186d49c1ab0d81df2bdb2a66c26159b119871f129c1c3f9c85fd2" + ), + 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" + ), + NeuralModel( + downloadLink = res("yolo11x-seg.onnx"), + title = "YOLO v11", + description = R.string.model_yolo, + type = Type.REMOVE_BG, + downloadSize = 249033763, + speed = Speed.Normal(1.260f), + checksum = "ef451889f4744d3d5ab47b82c16b7ffe3f15d2233c7aa77dfd4773fd8221b508" + ) + ).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) + } + +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/model/NeuralParams.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/model/NeuralParams.kt new file mode 100644 index 0000000..39d4eec --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/model/NeuralParams.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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, + val parallelWorkers: Int = 0 +) { + companion object { + val Default by lazy { + NeuralParams( + strength = 65f, + chunkSize = 512, + overlap = 16, + enableChunking = true, + parallelWorkers = 0 + ) + } + } +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/AiToolsContent.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/AiToolsContent.kt new file mode 100644 index 0000000..61aef7c --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/AiToolsContent.kt @@ -0,0 +1,228 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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(isPortrait = isPortrait), + 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 + ) +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/AiToolsControls.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/AiToolsControls.kt new file mode 100644 index 0000000..32b6d2b --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/AiToolsControls.kt @@ -0,0 +1,212 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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.Memory +import com.t8rin.imagetoolbox.core.resources.icons.Stacks +import com.t8rin.imagetoolbox.core.resources.icons.WarningAmber +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 kotlinx.collections.immutable.toPersistentMap +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)) + + val workerLimit = remember { + Runtime.getRuntime().availableProcessors().coerceAtLeast(1) + } + val workerOptions = remember(workerLimit) { + listOf(0) + (1..8) + } + val workerIndex = workerOptions.indexOf( + component.params.parallelWorkers.coerceIn(0, workerLimit) + ).coerceAtLeast(0) + + val auto = stringResource(R.string.auto) + + EnhancedSliderItem( + value = workerIndex, + internalStateTransformation = { it.roundToInt() }, + onValueChange = { + val index = it.roundToInt().coerceIn(workerOptions.indices) + component.updateParams { + copy(parallelWorkers = workerOptions[index]) + } + }, + valueRange = 0f..workerOptions.lastIndex.toFloat(), + steps = (workerOptions.size - 2).coerceAtLeast(0), + title = stringResource(R.string.parallel_workers), + icon = Icons.Outlined.Memory, + valuesPreviewMapping = remember(workerOptions, auto) { + buildMap { + workerOptions.forEachIndexed { index, workers -> + put( + key = index.toFloat(), + value = if (workers == 0) auto else workers.toString() + ) + } + }.toPersistentMap() + }, + isAnimated = false, + canInputValue = false, + shape = ShapeDefaults.large + ) + } + } + + 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) } + ) +} diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/DeleteModelDialog.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/DeleteModelDialog.kt new file mode 100644 index 0000000..58fa819 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/DeleteModelDialog.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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)) + } + } + ) +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/FilteredModels.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/FilteredModels.kt new file mode 100644 index 0000000..6256800 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/FilteredModels.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelFilterSheet.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelFilterSheet.kt new file mode 100644 index 0000000..6e0bfd9 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelFilterSheet.kt @@ -0,0 +1,204 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.Cancel +import com.t8rin.imagetoolbox.core.resources.icons.FilterAlt +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.Rounded.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 + ) + } + } +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelSelectionSheet.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelSelectionSheet.kt new file mode 100644 index 0000000..15c99fc --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelSelectionSheet.kt @@ -0,0 +1,161 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FilterAlt +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 + + key(typeFilters, speedFilters, keywordFilter) { + NeuralModelsColumn( + selectedModel = selectedModel, + downloadedModels = filteredDownloadedModels, + notDownloadedModels = filteredNotDownloadedModels, + onSelectModel = onSelectModel, + onDownloadModel = onDownloadModel, + onDeleteModel = onDeleteModel, + onImportModel = onImportModel, + downloadProgresses = downloadProgresses, + occupiedStorageSize = occupiedStorageSize, + onCancelDownload = onCancelDownload + ) + } + } + ) +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelSelector.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelSelector.kt new file mode 100644 index 0000000..1ae471b --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelSelector.kt @@ -0,0 +1,135 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 = {} + ) +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelTypeResources.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelTypeResources.kt new file mode 100644 index 0000000..8d01f53 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelTypeResources.kt @@ -0,0 +1,476 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AutoFixHigh +import com.t8rin.imagetoolbox.core.resources.icons.Bolt +import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt +import com.t8rin.imagetoolbox.core.resources.icons.Cloud +import com.t8rin.imagetoolbox.core.resources.icons.DirectionsWalk +import com.t8rin.imagetoolbox.core.resources.icons.Eraser +import com.t8rin.imagetoolbox.core.resources.icons.Eyedropper +import com.t8rin.imagetoolbox.core.resources.icons.File +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.QualityHigh +import com.t8rin.imagetoolbox.core.resources.icons.Rabbit +import com.t8rin.imagetoolbox.core.resources.icons.Scanner +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.QualityHigh + 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.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.Rounded.File + } 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 + ) + } + } + } +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelsColumn.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelsColumn.kt new file mode 100644 index 0000000..9468365 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelsColumn.kt @@ -0,0 +1,645 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.Download +import com.t8rin.imagetoolbox.core.resources.icons.DownloadDone +import com.t8rin.imagetoolbox.core.resources.icons.DownloadForOffline +import com.t8rin.imagetoolbox.core.resources.icons.File +import com.t8rin.imagetoolbox.core.resources.icons.FileImport +import com.t8rin.imagetoolbox.core.resources.icons.Link +import com.t8rin.imagetoolbox.core.resources.icons.ModelTraining +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonChecked +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.icons.SearchOff +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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.Rounded.File, + 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.Rounded.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.Rounded.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.Outlined.SearchOff, + contentDescription = null, + modifier = Modifier + .weight(2f) + .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) + .fillMaxSize() + ) + } + } + } + } +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralSaveProgress.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralSaveProgress.kt new file mode 100644 index 0000000..91c207b --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralSaveProgress.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralSaveProgressDialog.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralSaveProgressDialog.kt new file mode 100644 index 0000000..0efd2f1 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralSaveProgressDialog.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } + } + ) + } +} \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/Savers.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/Savers.kt new file mode 100644 index 0000000..1682411 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/Savers.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) } + } +) \ No newline at end of file diff --git a/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/screenLogic/AiToolsComponent.kt b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/screenLogic/AiToolsComponent.kt new file mode 100644 index 0000000..c760958 --- /dev/null +++ b/feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/screenLogic/AiToolsComponent.kt @@ -0,0 +1,390 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.CheckCircle +import com.t8rin.imagetoolbox.core.resources.icons.Info +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.extractMessage +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.CancellationException +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) + if (it !is CancellationException) { + AppToastHost.showFailureToast(it.extractMessage()) + } + } + .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())) { + is 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 + } + + +} \ No newline at end of file diff --git a/feature/apng-tools/.gitignore b/feature/apng-tools/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/apng-tools/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/apng-tools/build.gradle.kts b/feature/apng-tools/build.gradle.kts new file mode 100644 index 0000000..4e23707 --- /dev/null +++ b/feature/apng-tools/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/apng-tools/src/main/AndroidManifest.xml b/feature/apng-tools/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/apng-tools/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/data/AndroidApngConverter.kt b/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/data/AndroidApngConverter.kt new file mode 100644 index 0000000..cc9d308 --- /dev/null +++ b/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/data/AndroidApngConverter.kt @@ -0,0 +1,171 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + } + +} \ No newline at end of file diff --git a/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/di/ApngToolsModule.kt b/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/di/ApngToolsModule.kt new file mode 100644 index 0000000..0643190 --- /dev/null +++ b/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/di/ApngToolsModule.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/domain/ApngConverter.kt b/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/domain/ApngConverter.kt new file mode 100644 index 0000000..337398a --- /dev/null +++ b/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/domain/ApngConverter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + +} \ No newline at end of file diff --git a/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/domain/ApngParams.kt b/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/domain/ApngParams.kt new file mode 100644 index 0000000..72eb1bb --- /dev/null +++ b/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/domain/ApngParams.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + ) + } + } +} \ No newline at end of file diff --git a/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/presentation/ApngToolsContent.kt b/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/presentation/ApngToolsContent.kt new file mode 100644 index 0000000..1cd5e53 --- /dev/null +++ b/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/presentation/ApngToolsContent.kt @@ -0,0 +1,504 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Apng +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.Gif +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.Rounded.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(isPortrait = isPortrait), + 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 { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.windowInsetsPadding( + WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal) + ) + ) { + 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)) + 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 + ) +} \ No newline at end of file diff --git a/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/presentation/components/ApngParamsSelector.kt b/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/presentation/components/ApngParamsSelector.kt new file mode 100644 index 0000000..bd9bf3a --- /dev/null +++ b/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/presentation/components/ApngParamsSelector.kt @@ -0,0 +1,161 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.PhotoSizeSelectLarge +import com.t8rin.imagetoolbox.core.resources.icons.RepeatOne +import com.t8rin.imagetoolbox.core.resources.icons.Stream +import com.t8rin.imagetoolbox.core.resources.icons.Timelapse +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.Rounded.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..10_000f, + internalStateTransformation = { it.roundToInt() }, + onValueChange = { + onValueChange( + value.copy( + delay = it.roundToInt() + ) + ) + }, + shape = ShapeDefaults.extraLarge + ) + } +} \ No newline at end of file diff --git a/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/presentation/screenLogic/ApngToolsComponent.kt b/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/presentation/screenLogic/ApngToolsComponent.kt new file mode 100644 index 0000000..140a6ae --- /dev/null +++ b/feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/presentation/screenLogic/ApngToolsComponent.kt @@ -0,0 +1,498 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + } +} \ No newline at end of file diff --git a/feature/app-logs/.gitignore b/feature/app-logs/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/app-logs/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/app-logs/build.gradle.kts b/feature/app-logs/build.gradle.kts new file mode 100644 index 0000000..94eaf87 --- /dev/null +++ b/feature/app-logs/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.app_logs" \ No newline at end of file diff --git a/feature/app-logs/src/main/AndroidManifest.xml b/feature/app-logs/src/main/AndroidManifest.xml new file mode 100644 index 0000000..205decf --- /dev/null +++ b/feature/app-logs/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/app-logs/src/main/java/com/t8rin/imagetoolbox/presentation/app_logs/AppLogsContent.kt b/feature/app-logs/src/main/java/com/t8rin/imagetoolbox/presentation/app_logs/AppLogsContent.kt new file mode 100644 index 0000000..21610fb --- /dev/null +++ b/feature/app-logs/src/main/java/com/t8rin/imagetoolbox/presentation/app_logs/AppLogsContent.kt @@ -0,0 +1,407 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.presentation.app_logs + +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.tween +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.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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +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.plus +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.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.text.KeyboardOptions +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.pulltorefresh.PullToRefreshBox +import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState +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.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.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.Search +import com.t8rin.imagetoolbox.core.resources.icons.SearchOff +import com.t8rin.imagetoolbox.core.resources.icons.Share +import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState +import com.t8rin.imagetoolbox.core.ui.utils.animation.springySpec +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedCancellableCircularProgressIndicator +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedCircularProgressIndicator +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.enhanced.EnhancedTopAppBar +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.clearFocusOnTap +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.RoundedTextField +import com.t8rin.imagetoolbox.core.ui.widget.text.marquee +import com.t8rin.imagetoolbox.presentation.app_logs.components.LogLineItem +import com.t8rin.imagetoolbox.presentation.app_logs.screenLogic.AppLogsComponent +import kotlinx.coroutines.delay +import my.nanihadesuka.compose.InternalLazyColumnScrollbar +import my.nanihadesuka.compose.ScrollbarSelectionMode +import my.nanihadesuka.compose.ScrollbarSettings + +@Composable +fun AppLogsContent( + component: AppLogsComponent +) { + val lineCount = component.linesCount + val searchQuery = component.searchQuery + val isSendingLogs = component.isSendingLogs + var showSearch by rememberSaveable { mutableStateOf(false) } + + BackHandler(enabled = showSearch) { + component.updateSearchQuery("") + showSearch = false + } + + Scaffold( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.surface) + .clearFocusOnTap(), + topBar = { + EnhancedTopAppBar( + title = { + Text( + text = stringResource(R.string.app_logs), + modifier = Modifier.marquee() + ) + }, + navigationIcon = { + EnhancedIconButton( + onClick = { + if (showSearch) { + component.updateSearchQuery("") + showSearch = false + } else { + component.onGoBack() + } + } + ) { + Icon( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = null + ) + } + }, + actions = { + TopAppBarEmoji() + } + ) + }, + bottomBar = { + val insets = WindowInsets.navigationBars.union( + WindowInsets.displayCutout.only( + WindowInsetsSides.Horizontal + ) + ) + + AnimatedContent( + targetState = showSearch, + modifier = Modifier.fillMaxWidth() + ) { isSearch -> + if (isSearch) { + Box( + modifier = Modifier + .fillMaxWidth() + .drawHorizontalStroke(true) + .background( + MaterialTheme.colorScheme.surfaceContainer + ) + .pointerInput(Unit) { detectTapGestures { } } + .windowInsetsPadding(insets) + .padding(16.dp) + ) { + val focus = LocalFocusManager.current + + 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 = searchQuery, + onValueChange = component::updateSearchQuery, + endIcon = { + AnimatedVisibility(component.isSearchLoading) { + EnhancedCancellableCircularProgressIndicator( + progress = { 0f }, + onCancel = null, + modifier = Modifier + .padding(end = 8.dp) + .size(24.dp), + trackColor = MaterialTheme.colorScheme.primary.copy(0.2f), + strokeWidth = 3.dp, + ) + } + AnimatedVisibility(!component.isSearchLoading) { + EnhancedIconButton( + onClick = { + if (component.searchQuery.isNotBlank()) { + component.updateSearchQuery("") + } else { + showSearch = 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 { + val settingsState = LocalSettingsState.current + + Box( + modifier = Modifier + .windowInsetsPadding(insets) + .padding(16.dp) + .fillMaxWidth() + ) { + Row( + modifier = Modifier.align( + settingsState.fabAlignment.takeIf { it != Alignment.BottomCenter } + ?: Alignment.BottomEnd + ) + ) { + val progressAnimatable = + remember { Animatable(if (isSendingLogs) 1f else 0f) } + val progress = progressAnimatable.value + + LaunchedEffect(isSendingLogs) { + delay(400) + if (isSendingLogs) { + progressAnimatable.animateTo( + targetValue = 1f, + animationSpec = springySpec() + ) + } else { + progressAnimatable.animateTo( + targetValue = 0f, + animationSpec = tween(200) + ) + } + } + + EnhancedFloatingActionButton( + onClick = component::shareLogs, + containerColor = MaterialTheme.colorScheme.secondaryContainer, + type = EnhancedFloatingActionButtonType.SecondaryHorizontal + ) { + if (progress > 0f) { + EnhancedCircularProgressIndicator( + modifier = Modifier.size(24.dp * progress), + trackColor = MaterialTheme.colorScheme.primary.copy(0.2f), + strokeWidth = 3.dp + ) + } else { + Icon( + imageVector = Icons.Outlined.Share, + contentDescription = null + ) + } + } + Spacer(Modifier.width(8.dp)) + EnhancedFloatingActionButton( + onClick = { showSearch = true } + ) { + Icon( + imageVector = Icons.Outlined.Search, + contentDescription = null + ) + } + } + } + } + } + } + ) { contentPadding -> + val state = rememberPullToRefreshState() + + PullToRefreshBox( + isRefreshing = component.isRefreshing && lineCount > 0, + onRefresh = component::refreshLogs, + state = state, + modifier = Modifier.fillMaxSize(), + indicator = { + Indicator( + modifier = Modifier + .align(Alignment.TopCenter) + .padding(contentPadding), + isRefreshing = component.isRefreshing && lineCount > 0, + state = state, + color = MaterialTheme.colorScheme.primary, + containerColor = MaterialTheme.colorScheme.surfaceContainerHighest + ) + }, + contentAlignment = Alignment.Center + ) { + val noMatches = lineCount == 0 && !component.isRefreshing && !component.isSearchLoading + val isLoading = lineCount <= 0 && component.isRefreshing + + AnimatedContent( + targetState = isLoading to noMatches, + modifier = Modifier.fillMaxSize() + ) { (isLoading, noMatches) -> + if (isLoading) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(contentPadding), + contentAlignment = Alignment.Center + ) { + EnhancedLoadingIndicator() + } + } else if (noMatches) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(contentPadding), + 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.Outlined.SearchOff, + contentDescription = null, + modifier = Modifier + .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) + .fillMaxSize() + ) + Spacer(Modifier.weight(1f)) + } + } else { + val listState = rememberLazyListState() + + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = listState, + contentPadding = contentPadding + PaddingValues(12.dp), + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + items( + count = lineCount, + key = component::lineKey + ) { index -> + component.lineAtOrNull(index)?.let { line -> + LogLineItem( + line = line, + query = searchQuery + ) + } + } + } + + InternalLazyColumnScrollbar( + state = listState, + settings = ScrollbarSettings( + thumbUnselectedColor = MaterialTheme.colorScheme.secondary, + thumbSelectedColor = MaterialTheme.colorScheme.primary, + scrollbarPadding = 0.dp, + thumbThickness = 10.dp, + selectionMode = ScrollbarSelectionMode.Full, + thumbShape = AutoCircleShape(), + hideDelayMillis = 1500 + ), + indicatorContent = { _, _ -> + Spacer( + Modifier + .width(64.dp) + .height(28.dp) + ) + }, + modifier = Modifier.padding(contentPadding) + ) + } + } + } + } +} \ No newline at end of file diff --git a/feature/app-logs/src/main/java/com/t8rin/imagetoolbox/presentation/app_logs/components/LogLineItem.kt b/feature/app-logs/src/main/java/com/t8rin/imagetoolbox/presentation/app_logs/components/LogLineItem.kt new file mode 100644 index 0000000..ee97b20 --- /dev/null +++ b/feature/app-logs/src/main/java/com/t8rin/imagetoolbox/presentation/app_logs/components/LogLineItem.kt @@ -0,0 +1,143 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.presentation.app_logs.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.foundation.layout.widthIn +import androidx.compose.foundation.text.selection.SelectionContainer +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.produceState +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle +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.utils.LogLineReference +import com.t8rin.imagetoolbox.core.utils.Logger +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +@Composable +internal fun LogLineItem( + line: LogLineReference, + query: String +) { + val text by produceState(initialValue = null, line) { + value = withContext(Dispatchers.IO) { + Logger.readLogLine(line) + } + } + + Surface( + modifier = Modifier.fillMaxWidth(), + shape = ShapeDefaults.mini, + color = MaterialTheme.colorScheme.surfaceContainerLow + ) { + Row( + modifier = Modifier.padding( + horizontal = 12.dp, + vertical = 10.dp + ), + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Text( + text = line.number.toString(), + modifier = Modifier.widthIn(min = 44.dp), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.primary, + fontFamily = FontFamily.Monospace + ) + SelectionContainer( + modifier = Modifier.weight(1f) + ) { + Text( + text = highlightedLogLine( + text = text.orEmpty(), + query = query + ), + style = MaterialTheme.typography.bodySmall, + fontFamily = FontFamily.Monospace, + fontSize = 12.sp, + lineHeight = 16.sp + ) + } + } + } +} + +@Composable +private fun highlightedLogLine( + text: String, + query: String +): AnnotatedString { + val background = MaterialTheme.colorScheme.tertiary + val content = MaterialTheme.colorScheme.onTertiary + + return remember( + text, + query, + background, + content + ) { + if (query.isBlank()) { + AnnotatedString(text) + } else { + buildAnnotatedString { + var startIndex = 0 + + while (startIndex < text.length) { + val matchIndex = text.indexOf( + string = query, + startIndex = startIndex, + ignoreCase = true + ) + + if (matchIndex == -1) { + append(text.substring(startIndex)) + break + } + + append(text.substring(startIndex, matchIndex)) + withStyle( + SpanStyle( + background = background, + color = content, + fontWeight = FontWeight.SemiBold + ) + ) { + append(text.substring(matchIndex, matchIndex + query.length)) + } + startIndex = matchIndex + query.length + } + } + } + } +} \ No newline at end of file diff --git a/feature/app-logs/src/main/java/com/t8rin/imagetoolbox/presentation/app_logs/screenLogic/AppLogsComponent.kt b/feature/app-logs/src/main/java/com/t8rin/imagetoolbox/presentation/app_logs/screenLogic/AppLogsComponent.kt new file mode 100644 index 0000000..932764e --- /dev/null +++ b/feature/app-logs/src/main/java/com/t8rin/imagetoolbox/presentation/app_logs/screenLogic/AppLogsComponent.kt @@ -0,0 +1,221 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.presentation.app_logs.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.image.ShareProvider +import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching +import com.t8rin.imagetoolbox.core.domain.utils.smartJob +import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager +import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent +import com.t8rin.imagetoolbox.core.ui.utils.state.update +import com.t8rin.imagetoolbox.core.utils.LogLineReference +import com.t8rin.imagetoolbox.core.utils.Logger +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext + +class AppLogsComponent @AssistedInject constructor( + @Assisted componentContext: ComponentContext, + @Assisted val onGoBack: () -> Unit, + dispatchersHolder: DispatchersHolder, + private val shareProvider: ShareProvider, + private val settingsManager: SettingsManager +) : BaseComponent(dispatchersHolder, componentContext) { + + private val allLines = mutableStateListOf() + private val matchingLines = mutableStateListOf() + + private val _searchQuery = mutableStateOf("") + val searchQuery by _searchQuery + + private val _isRefreshing = mutableStateOf(true) + val isRefreshing by _isRefreshing + + private val _isSearchLoading = mutableStateOf(false) + val isSearchLoading by _isSearchLoading + + private val _isSendingLogs = mutableStateOf(false) + val isSendingLogs by _isSendingLogs + + private var refreshJob by smartJob() + private var searchJob by smartJob() + private var indexedOffset = 0L + private var indexedLineNumber = 0 + private var searchIndexedOffset = 0L + private var searchIndexedLineNumber = 0 + + val linesCount: Int + get() = activeLines.size + + private val activeLines: List + get() = if (searchQuery.isBlank()) allLines else matchingLines + + init { + refreshLogs() + observeLogs() + } + + fun refreshLogs() { + refreshJob = componentScope.launch { + syncLogs( + forceRebuild = true, + showLoading = true + ) + } + } + + fun updateSearchQuery(query: String) { + _searchQuery.update { query } + searchJob = componentScope.launch { + _isSearchLoading.update { true } + delay(400) + if (query.isBlank()) { + matchingLines.clear() + searchIndexedOffset = indexedOffset + searchIndexedLineNumber = indexedLineNumber + _isSearchLoading.update { false } + return@launch + } + + try { + syncSearch( + query = query, + forceRebuild = true + ) + } finally { + if (query == searchQuery) { + _isSearchLoading.update { false } + } + } + } + } + + fun lineAtOrNull(index: Int): LogLineReference? { + val lines = activeLines + return lines.getOrNull(lines.lastIndex - index) + } + + fun lineKey(index: Int): String = lineAtOrNull(index)?.key ?: index.toString() + + fun shareLogs() { + componentScope.launch { + _isSendingLogs.update { true } + runSuspendCatching { + shareProvider.shareUri( + uri = settingsManager.createLogsExport(), + onComplete = {} + ) + } + _isSendingLogs.update { false } + } + } + + private fun observeLogs() { + componentScope.launch { + while (true) { + delay(LOG_REFRESH_INTERVAL) + runCatching { + syncLogs( + forceRebuild = false, + showLoading = false + ) + } + } + } + } + + private suspend fun syncLogs( + forceRebuild: Boolean, + showLoading: Boolean + ) { + if (showLoading) _isRefreshing.update { true } + + try { + val currentLength = withContext(ioDispatcher) { + Logger.logsFileLength() + } + val shouldRebuild = forceRebuild || currentLength < indexedOffset + + if (!shouldRebuild && currentLength == indexedOffset) return + + val snapshot = withContext(ioDispatcher) { + Logger.readLogLineReferences( + startOffset = if (shouldRebuild) 0L else indexedOffset, + startLineNumber = if (shouldRebuild) 0 else indexedLineNumber + ) + } + + if (shouldRebuild) allLines.clear() + allLines.addAll(snapshot.lines) + indexedOffset = snapshot.endOffset + indexedLineNumber = snapshot.lastLineNumber + + searchQuery.takeIf(String::isNotBlank)?.let { query -> + syncSearch( + query = query, + forceRebuild = shouldRebuild || searchIndexedOffset > indexedOffset + ) + } + } finally { + if (showLoading) { + _isRefreshing.update { false } + } + } + } + + private suspend fun syncSearch( + query: String, + forceRebuild: Boolean + ) { + val shouldRebuild = forceRebuild || searchIndexedOffset > indexedOffset + val snapshot = withContext(ioDispatcher) { + Logger.readLogLineReferences( + startOffset = if (shouldRebuild) 0L else searchIndexedOffset, + startLineNumber = if (shouldRebuild) 0 else searchIndexedLineNumber, + query = query + ) + } + + if (query != searchQuery) return + + if (shouldRebuild) matchingLines.clear() + matchingLines.addAll(snapshot.lines) + searchIndexedOffset = snapshot.endOffset + searchIndexedLineNumber = snapshot.lastLineNumber + } + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + onGoBack: () -> Unit + ): AppLogsComponent + } + + private companion object { + const val LOG_REFRESH_INTERVAL = 2_000L + } + +} \ No newline at end of file diff --git a/feature/ascii-art/.gitignore b/feature/ascii-art/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/ascii-art/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/ascii-art/build.gradle.kts b/feature/ascii-art/build.gradle.kts new file mode 100644 index 0000000..4a03d95 --- /dev/null +++ b/feature/ascii-art/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/ascii-art/src/main/AndroidManifest.xml b/feature/ascii-art/src/main/AndroidManifest.xml new file mode 100644 index 0000000..44008a4 --- /dev/null +++ b/feature/ascii-art/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/data/AndroidAsciiConverter.kt b/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/data/AndroidAsciiConverter.kt new file mode 100644 index 0000000..09b55d5 --- /dev/null +++ b/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/data/AndroidAsciiConverter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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, + isInverted: Boolean + ): String = withContext(defaultDispatcher) { + ASCIIConverter( + fontSize = fontSize, + mapper = Gradient(gradient).toMapper(), + reverseLuma = isInverted + ).convertToAscii(image) + } + +} \ No newline at end of file diff --git a/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/di/AsciiArtModule.kt b/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/di/AsciiArtModule.kt new file mode 100644 index 0000000..38c7eac --- /dev/null +++ b/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/di/AsciiArtModule.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/domain/AsciiConverter.kt b/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/domain/AsciiConverter.kt new file mode 100644 index 0000000..cb10b94 --- /dev/null +++ b/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/domain/AsciiConverter.kt @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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, + isInverted: Boolean + ): String + +} \ No newline at end of file diff --git a/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/presentation/AsciiArtContent.kt b/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/presentation/AsciiArtContent.kt new file mode 100644 index 0000000..0eae439 --- /dev/null +++ b/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/presentation/AsciiArtContent.kt @@ -0,0 +1,212 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ContentCopy +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.controls.UndoRedoButtons +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 = { + if (!isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + ShareButton( + enabled = component.uri != Uri.EMPTY, + onShare = { + component.convertToAsciiString { + appContext.shareText(it) + } + }, + ) + if (isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + }, + 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) + } + } + ) +} \ No newline at end of file diff --git a/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/presentation/components/AsciiArtControls.kt b/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/presentation/components/AsciiArtControls.kt new file mode 100644 index 0000000..1224dac --- /dev/null +++ b/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/presentation/components/AsciiArtControls.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.filters.presentation.widget.filterItem.AsciiParamsSelector +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Build +import com.t8rin.imagetoolbox.core.resources.icons.InvertColors +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)) + } +} \ No newline at end of file diff --git a/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/presentation/screenLogic/AsciiArtComponent.kt b/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/presentation/screenLogic/AsciiArtComponent.kt new file mode 100644 index 0000000..13119db --- /dev/null +++ b/feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/presentation/screenLogic/AsciiArtComponent.kt @@ -0,0 +1,171 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.BaseHistoryComponent +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 com.t8rin.imagetoolbox.feature.ascii_art.presentation.screenLogic.AsciiArtComponent.HistorySnapshot +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 +) : BaseHistoryComponent( + dispatchersHolder = dispatchersHolder, + componentContext = 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) { + clearHistory() + registerChangesCleared() + _uri.update { uri } + if (uri != Uri.EMPTY) { + resetHistory() + } + } + + fun convertToAsciiString( + onSuccess: (String) -> Unit + ) { + debouncedImageCalculation { + imageGetter.getImage(uri)?.let { image -> + onSuccess( + asciiConverter.imageToAscii( + image = image, + fontSize = fontSize, + gradient = gradient, + isInverted = isInvertImage + ) + ) + } + } + } + + fun setGradient(gradient: String) { + if (asciiParams.gradient != gradient) { + beginPendingHistoryTransaction() + _asciiParams.update { + it.copy(gradient = gradient) + } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setFontSize(fontSize: Float) { + if (asciiParams.fontSize != fontSize) { + beginPendingHistoryTransaction() + _asciiParams.update { + it.copy(fontSize = fontSize) + } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun toggleIsInvertImage() { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _isInvertImage.update { !it } + commitHistoryFrom(beforeSnapshot) + } + + fun getAsciiTransformations(): List = buildList { + if (isInvertImage) add(UiNegativeFilter()) + add(UiAsciiFilter(asciiParams)) + }.map { + filterProvider.filterToTransformation(it).toCoil() + } + + override fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( + uri = uri, + asciiParams = asciiParams, + isInvertImage = isInvertImage + ) + + override fun applyHistorySnapshot(snapshot: HistorySnapshot) { + _uri.update { snapshot.uri } + _asciiParams.update { snapshot.asciiParams } + _isInvertImage.update { snapshot.isInvertImage } + } + + data class HistorySnapshot( + val uri: Uri = Uri.EMPTY, + val asciiParams: AsciiParams = AsciiParams.Default.copy(isGrayscale = true), + val isInvertImage: Boolean = false + ) + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUri: Uri?, + onGoBack: () -> Unit, + ): AsciiArtComponent + } +} \ No newline at end of file diff --git a/feature/audio-cover-extractor/.gitignore b/feature/audio-cover-extractor/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/audio-cover-extractor/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/audio-cover-extractor/build.gradle.kts b/feature/audio-cover-extractor/build.gradle.kts new file mode 100644 index 0000000..908e29f --- /dev/null +++ b/feature/audio-cover-extractor/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/audio-cover-extractor/src/main/AndroidManifest.xml b/feature/audio-cover-extractor/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d5fcf6a --- /dev/null +++ b/feature/audio-cover-extractor/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/data/AndroidAudioCoverRetriever.kt b/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/data/AndroidAudioCoverRetriever.kt new file mode 100644 index 0000000..9f59bf9 --- /dev/null +++ b/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/data/AndroidAudioCoverRetriever.kt @@ -0,0 +1,99 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + } + +} \ No newline at end of file diff --git a/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/di/AudioCoverExtractorModule.kt b/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/di/AudioCoverExtractorModule.kt new file mode 100644 index 0000000..906f3bf --- /dev/null +++ b/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/di/AudioCoverExtractorModule.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/domain/AudioCoverRetriever.kt b/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/domain/AudioCoverRetriever.kt new file mode 100644 index 0000000..dcd9a96 --- /dev/null +++ b/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/domain/AudioCoverRetriever.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/domain/model/AudioCoverResult.kt b/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/domain/model/AudioCoverResult.kt new file mode 100644 index 0000000..8dba026 --- /dev/null +++ b/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/domain/model/AudioCoverResult.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/ui/AudioCoverExtractorContent.kt b/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/ui/AudioCoverExtractorContent.kt new file mode 100644 index 0000000..61d2385 --- /dev/null +++ b/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/ui/AudioCoverExtractorContent.kt @@ -0,0 +1,295 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Album +import com.t8rin.imagetoolbox.core.resources.icons.MusicAdd +import com.t8rin.imagetoolbox.core.resources.icons.NoteAdd +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.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 = remember(covers) { + covers.map { it.imageCoverUri ?: it.audioUri } + } + + UrisPreview( + modifier = Modifier.urisPreview(isPortrait = isPortrait), + uris = uris, + isPortrait = true, + onRemoveUri = component::removeCover, + onAddUris = addAudioPicker::pickFile, + addUrisContent = { width -> + Icon( + imageVector = Icons.Rounded.NoteAdd, + contentDescription = stringResource(R.string.add), + modifier = Modifier.size(width / 3f) + ) + }, + errorContent = { index, width -> + val isNightMode = LocalSettingsState.current.isNightMode + + Box( + modifier = Modifier + .background( + takeColorFromScheme { + if (isNightMode) errorContainer else error + }.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 = takeColorFromScheme { + if (isNightMode) onErrorContainer else onError + }.copy(0.5f) + ) + 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 + ) +} \ No newline at end of file diff --git a/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/ui/components/AudioWithCover.kt b/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/ui/components/AudioWithCover.kt new file mode 100644 index 0000000..5c563f8 --- /dev/null +++ b/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/ui/components/AudioWithCover.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/ui/screenLogic/AudioCoverExtractorComponent.kt b/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/ui/screenLogic/AudioCoverExtractorComponent.kt new file mode 100644 index 0000000..326efb1 --- /dev/null +++ b/feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/ui/screenLogic/AudioCoverExtractorComponent.kt @@ -0,0 +1,316 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} \ No newline at end of file diff --git a/feature/base64-tools/.gitignore b/feature/base64-tools/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/base64-tools/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/base64-tools/build.gradle.kts b/feature/base64-tools/build.gradle.kts new file mode 100644 index 0000000..7125004 --- /dev/null +++ b/feature/base64-tools/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/base64-tools/src/main/AndroidManifest.xml b/feature/base64-tools/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d5fcf6a --- /dev/null +++ b/feature/base64-tools/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/data/AndroidBase64Converter.kt b/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/data/AndroidBase64Converter.kt new file mode 100644 index 0000000..f4314a0 --- /dev/null +++ b/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/data/AndroidBase64Converter.kt @@ -0,0 +1,70 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + +} \ No newline at end of file diff --git a/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/di/Base64ToolsModule.kt b/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/di/Base64ToolsModule.kt new file mode 100644 index 0000000..c4dfa4c --- /dev/null +++ b/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/di/Base64ToolsModule.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/domain/Base64Converter.kt b/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/domain/Base64Converter.kt new file mode 100644 index 0000000..94ee5e2 --- /dev/null +++ b/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/domain/Base64Converter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/presentation/Base64ToolsContent.kt b/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/presentation/Base64ToolsContent.kt new file mode 100644 index 0000000..d0358f1 --- /dev/null +++ b/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/presentation/Base64ToolsContent.kt @@ -0,0 +1,277 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme +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 = { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxSize() + .background( + takeColorFromScheme { isNightMode -> + errorContainer.copy( + if (isNightMode) 0.25f + else 1f + ).compositeOverSafe(surface) + } + ) + ) { + Icon( + imageVector = Icons.Rounded.BrokenImageAlt, + contentDescription = null, + modifier = Modifier.fillMaxSize(0.5f), + tint = MaterialTheme.colorScheme.onErrorContainer.copy(0.8f) + ) + } + }, + 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 + ) +} \ No newline at end of file diff --git a/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/presentation/components/Base64ToolsTiles.kt b/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/presentation/components/Base64ToolsTiles.kt new file mode 100644 index 0000000..041ac23 --- /dev/null +++ b/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/presentation/components/Base64ToolsTiles.kt @@ -0,0 +1,252 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Base64 +import com.t8rin.imagetoolbox.core.resources.icons.ContentPaste +import com.t8rin.imagetoolbox.core.resources.icons.CopyAll +import com.t8rin.imagetoolbox.core.resources.icons.FileOpen +import com.t8rin.imagetoolbox.core.resources.icons.Save +import com.t8rin.imagetoolbox.core.resources.icons.Share +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) + } + + 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.Outlined.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 + ) + } +} \ No newline at end of file diff --git a/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/presentation/screenLogic/Base64ToolsComponent.kt b/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/presentation/screenLogic/Base64ToolsComponent.kt new file mode 100644 index 0000000..4ab6679 --- /dev/null +++ b/feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/presentation/screenLogic/Base64ToolsComponent.kt @@ -0,0 +1,272 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Base64 +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.getString +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) { + componentScope.launch { + val text = fileController.readBytes(uri.toString()).decodeToString().trimToBase64() + if (text.isBase64()) { + setBase64(text) + } else { + AppToastHost.showToast( + message = getString(R.string.not_a_valid_base_64), + icon = Icons.Rounded.Base64 + ) + } + } + } + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUri: Uri?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): Base64ToolsComponent + } + +} \ No newline at end of file diff --git a/feature/batch-rename/.gitignore b/feature/batch-rename/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/batch-rename/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/batch-rename/build.gradle.kts b/feature/batch-rename/build.gradle.kts new file mode 100644 index 0000000..1416065 --- /dev/null +++ b/feature/batch-rename/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.batch_rename" \ No newline at end of file diff --git a/feature/batch-rename/src/main/AndroidManifest.xml b/feature/batch-rename/src/main/AndroidManifest.xml new file mode 100644 index 0000000..063ac92 --- /dev/null +++ b/feature/batch-rename/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + \ No newline at end of file diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/data/AndroidRenameManager.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/data/AndroidRenameManager.kt new file mode 100644 index 0000000..67c74d3 --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/data/AndroidRenameManager.kt @@ -0,0 +1,451 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.data + +import android.app.RecoverableSecurityException +import android.content.ContentResolver +import android.content.ContentUris +import android.content.ContentValues +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.os.Process +import android.provider.DocumentsContract +import android.provider.MediaStore +import androidx.annotation.RequiresApi +import androidx.core.net.toUri +import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder +import com.t8rin.imagetoolbox.core.domain.image.Metadata +import com.t8rin.imagetoolbox.core.domain.image.get +import com.t8rin.imagetoolbox.core.domain.image.model.MetadataTag +import com.t8rin.imagetoolbox.core.domain.saving.FileController +import com.t8rin.imagetoolbox.core.utils.fileSize +import com.t8rin.imagetoolbox.core.utils.filename +import com.t8rin.imagetoolbox.core.utils.imageSize +import com.t8rin.imagetoolbox.core.utils.lastModified +import com.t8rin.imagetoolbox.core.utils.path +import com.t8rin.imagetoolbox.core.utils.tryExtractOriginal +import com.t8rin.imagetoolbox.feature.batchrename.domain.RenameManager +import com.t8rin.imagetoolbox.feature.batchrename.domain.model.RenameFile +import com.t8rin.imagetoolbox.feature.batchrename.domain.model.RenameResult +import com.t8rin.imagetoolbox.feature.batchrename.domain.model.RenameTarget +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext +import java.io.File +import java.nio.file.Files +import java.nio.file.attribute.BasicFileAttributes +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.UUID +import javax.inject.Inject + +internal class AndroidRenameManager @Inject constructor( + @ApplicationContext private val context: Context, + private val fileController: FileController, + dispatchersHolder: DispatchersHolder +) : RenameManager, DispatchersHolder by dispatchersHolder { + + private val contentResolver: ContentResolver + get() = context.contentResolver + + override suspend fun readFiles(uris: Iterable): List = + withContext(ioDispatcher) { + uris.distinct().map { it.toUri() }.mapNotNull { uri -> + val name = uri.filename(context) + ?.takeIf(String::isNotBlank) + ?: uri.lastPathSegment?.substringAfterLast('/')?.takeIf(String::isNotBlank) + ?: return@mapNotNull null + val size = uri.imageSize() + val providerDates = uri.tryExtractOriginal().providerDates() + val metadata = + runCatching { fileController.readMetadata(uri.toString()) }.getOrNull() + RenameFile( + uri = uri.toString(), + originalName = name, + width = size?.width, + height = size?.height, + exifDateTaken = metadata?.exifDateTaken() ?: providerDates.dateTaken, + modifiedDate = uri.lastModified() ?: providerDates.modified, + createdDate = uri.creationDate() ?: providerDates.created, + fileSize = uri.fileSize(), + parentFolder = uri.path()?.let { path -> + if ('/' in path) { + path.substringBeforeLast('/').substringAfterLast('/') + } else "" + }.orEmpty() + ) + } + } + + override suspend fun rename( + files: List, + writableUris: Set + ): RenameResult = withContext(ioDispatcher) { + val changed = files.filter { it.file.originalName != it.newName } + val unchanged = files.size - changed.size + if (changed.isEmpty()) return@withContext RenameResult.Success( + renamed = 0, + unchanged = unchanged + ) + + requestMediaStorePermissionIfNeeded(changed, writableUris)?.let { return@withContext it } + + val originalNames = changed.map { it.file.originalName.lowercase() }.toSet() + val hasCycleOrOverlap = changed.any { target -> + target.newName.lowercase() != target.file.originalName.lowercase() && + target.newName.lowercase() in originalNames + } + + if (!hasCycleOrOverlap) { + val renamed = mutableListOf>() + changed.forEach { target -> + val result = runCatching { + renameOne(target.file.uri.toUri(), target.newName) + } + result.onSuccess { finalUri -> + renamed += target to finalUri + }.onFailure { cause -> + renamed.asReversed().forEach { (completed, finalUri) -> + runCatching { renameOne(finalUri, completed.file.originalName) } + } + return@withContext result.permissionResult(target.file.uri.toUri()) + ?: RenameResult.Failure( + renamed = 0, + failedNames = changed.map { it.file.originalName }, + cause = cause + ) + } + } + return@withContext RenameResult.Success( + renamed = renamed.size, + unchanged = unchanged + ) + } + + val staged = mutableListOf() + for (target in changed) { + val temporaryName = createTemporaryName(target.file.extension) + val result = runCatching { + renameOne( + uri = target.file.uri.toUri(), + newName = temporaryName + ) + } + val temporaryUri = result.getOrNull() + if (temporaryUri == null) { + rollback(staged) + return@withContext result.permissionResult(target.file.uri.toUri()) + ?: RenameResult.Failure( + renamed = 0, + failedNames = changed.map { it.file.originalName }, + cause = result.exceptionOrNull() + ) + } + staged += StagedRename( + temporaryUri = temporaryUri, + originalName = target.file.originalName, + finalName = target.newName + ) + } + + val renamed = mutableListOf>() + staged.forEachIndexed { index, item -> + val result = runCatching { + renameOne(item.temporaryUri, item.finalName) + } + result.onSuccess { finalUri -> + renamed += item to finalUri + }.onFailure { cause -> + var rollbackFailed = false + val finalizedRollback = renamed.mapNotNull { (completed, finalUri) -> + runCatching { + val extension = completed.originalName.substringAfterLast( + delimiter = '.', + missingDelimiterValue = "" + ) + completed to renameOne(finalUri, createTemporaryName(extension)) + }.onFailure { + rollbackFailed = true + }.getOrNull() + } + rollbackFailed = runCatching { + renameOne(item.temporaryUri, item.originalName) + }.isFailure || rollbackFailed + staged.drop(index + 1).forEach { pending -> + rollbackFailed = runCatching { + renameOne(pending.temporaryUri, pending.originalName) + }.isFailure || rollbackFailed + } + finalizedRollback.asReversed().forEach { (completed, rollbackUri) -> + rollbackFailed = runCatching { + renameOne(rollbackUri, completed.originalName) + }.isFailure || rollbackFailed + } + return@withContext RenameResult.Failure( + renamed = renamed.size.takeIf { rollbackFailed } ?: 0, + failedNames = changed.map { it.file.originalName }, + cause = cause + ) + } + } + + return@withContext RenameResult.Success( + renamed = renamed.size, + unchanged = unchanged + ) + } + + private fun Metadata.exifDateTaken(): Long? { + val rawDate = this[MetadataTag.DatetimeOriginal] + ?: this[MetadataTag.DatetimeDigitized] + ?: return null + return EXIF_DATE_FORMATS.firstNotNullOfOrNull { format -> + runCatching { + SimpleDateFormat(format, Locale.getDefault()).parse(rawDate)?.time + }.getOrNull() + } + } + + private fun Uri.creationDate(): Long? { + val file = directFile(this) ?: return null + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + runCatching { + Files.readAttributes(file.toPath(), BasicFileAttributes::class.java) + .creationTime() + .toMillis() + }.getOrNull() + } else { + null + } + } + + private fun Uri.providerDates(): ProviderDates = runCatching { + contentResolver.query(this, null, null, null, null)?.use { cursor -> + if (!cursor.moveToFirst()) return@use ProviderDates() + + fun millis(column: String, storedInSeconds: Boolean): Long? { + val index = cursor.getColumnIndex(column) + if (index < 0 || cursor.isNull(index)) return null + return cursor.getLong(index).takeIf { it > 0 }?.let { + if (storedInSeconds) { + it * MILLIS_IN_SECOND + } else { + it + } + } + } + + ProviderDates( + dateTaken = millis(MediaStore.MediaColumns.DATE_TAKEN, false), + modified = millis(MediaStore.MediaColumns.DATE_MODIFIED, true), + created = millis(MediaStore.MediaColumns.DATE_ADDED, true) + ) + } + }.getOrNull() ?: ProviderDates() + + private suspend fun rollback(staged: List) { + staged.asReversed().forEach { item -> + runCatching { renameOne(item.temporaryUri, item.originalName) } + } + } + + private suspend fun renameOne(uri: Uri, newName: String): Uri { + if (uri.scheme == ContentResolver.SCHEME_FILE) { + val file = uri.path?.let(::File)?.takeIf(File::exists) + ?: error("File does not exist: $uri") + return renameFile(file, newName) + } + + check(uri.scheme == ContentResolver.SCHEME_CONTENT) { "Unsupported URI: $uri" } + + val providerResult = runCatching { + if (DocumentsContract.isDocumentUri(context, uri)) { + var lastException: Throwable? = null + repeat(3) { attempt -> + runCatching { + DocumentsContract.renameDocument(contentResolver, uri, newName) + }.onSuccess { + if (it != null) return@runCatching it + }.onFailure { + lastException = it + } + if (attempt < 2) delay(100) + } + lastException?.let { throw it } + } + val values = ContentValues(1).apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, newName) + } + contentResolver.update(uri, values, null, null) + .takeIf { it > 0 } + ?.let { uri } + ?: error("The content provider did not rename the file") + } + + providerResult.getOrNull()?.let { return it } + + directFile(uri)?.let { return renameFile(it, newName) } + throw providerResult.exceptionOrNull() ?: error("Failed to rename $uri") + } + + private fun renameFile(file: File, newName: String): Uri { + val destination = File(file.parentFile, newName) + check(!destination.exists()) { "A file named $newName already exists" } + check(file.renameTo(destination)) { "Failed to rename ${file.name}" } + return Uri.fromFile(destination) + } + + private fun directFile(uri: Uri): File? { + if (uri.scheme == ContentResolver.SCHEME_FILE) { + return uri.path?.let(::File)?.takeIf(File::exists) + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || !Environment.isExternalStorageManager()) { + return null + } + return uri.path()?.let(::File)?.takeIf(File::exists) + } + + private fun requestMediaStorePermissionIfNeeded( + targets: List, + writableUris: Set + ): RenameResult.PermissionRequired? { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || Environment.isExternalStorageManager()) { + return null + } + + val uris = targets.asSequence() + .map { it.file.uri.toUri() } + .filter { it.authority == MediaStore.AUTHORITY } + .mapNotNull { it.toMediaStoreItemUri() } + .filterNot { writableUris.contains(it.toString()) } + .filterNot(::canWriteWithoutRequest) + .distinct() + .toList() + if (uris.isEmpty()) return null + + return RenameResult.PermissionRequired( + intentSender = MediaStore.createWriteRequest(contentResolver, uris).intentSender, + uris = uris.mapTo(mutableSetOf()) { it.toString() } + ) + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun canWriteWithoutRequest(uri: Uri): Boolean { + val hasGrant = context.checkUriPermission( + uri, + Process.myPid(), + Process.myUid(), + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) == PackageManager.PERMISSION_GRANTED + if (hasGrant) return true + + return runCatching { + contentResolver.query( + uri, + arrayOf(MediaStore.MediaColumns.OWNER_PACKAGE_NAME), + null, + null, + null + )?.use { cursor -> + cursor.moveToFirst() && cursor.getString(0) == context.packageName + } + }.getOrNull() == true + } + + private fun Result.permissionResult(uri: Uri): RenameResult.PermissionRequired? { + val exception = exceptionOrNull() + return when { + Build.VERSION.SDK_INT == Build.VERSION_CODES.Q && exception is RecoverableSecurityException -> { + RenameResult.PermissionRequired( + intentSender = exception.userAction.actionIntent.intentSender, + uris = setOf(uri.toString()) + ) + } + + Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && exception is SecurityException -> { + val mediaStoreUri = uri.toMediaStoreItemUri() ?: return null + RenameResult.PermissionRequired( + intentSender = MediaStore.createWriteRequest( + contentResolver, + listOf(mediaStoreUri) + ).intentSender, + uris = setOf(mediaStoreUri.toString()) + ) + } + + else -> null + } + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun Uri.toMediaStoreItemUri(): Uri? { + if (authority == MediaStore.AUTHORITY && lastPathSegment?.toLongOrNull() != null) { + return this + } + if (authority != MEDIA_DOCUMENTS_AUTHORITY) return null + + val parts = runCatching { DocumentsContract.getDocumentId(this) } + .getOrNull() + ?.split(':', limit = 2) + ?.takeIf { it.size == 2 } + ?: return null + val id = parts[1].toLongOrNull() ?: return null + val collection = when (parts[0]) { + "image" -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI + "video" -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI + "audio" -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + else -> MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL) + } + return ContentUris.withAppendedId(collection, id) + } + + private fun createTemporaryName(extension: String): String = buildString { + append("ImageToolbox_temp_") + append(UUID.randomUUID().toString().replace("-", "")) + if (extension.isNotEmpty()) { + append('.') + append(extension) + } + } + + private data class StagedRename( + val temporaryUri: Uri, + val originalName: String, + val finalName: String + ) + + private data class ProviderDates( + val dateTaken: Long? = null, + val modified: Long? = null, + val created: Long? = null + ) + + private companion object { + const val MILLIS_IN_SECOND = 1000 + const val MEDIA_DOCUMENTS_AUTHORITY = "com.android.providers.media.documents" + val EXIF_DATE_FORMATS = listOf( + "yyyy:MM:dd HH:mm:ss", + "yyyy-MM-dd HH:mm:ss", + "yyyy:MM:dd HH:mm:ssXXX" + ) + } +} diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/di/BatchRenameModule.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/di/BatchRenameModule.kt new file mode 100644 index 0000000..e7fcea5 --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/di/BatchRenameModule.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.di + +import com.t8rin.imagetoolbox.feature.batchrename.data.AndroidRenameManager +import com.t8rin.imagetoolbox.feature.batchrename.domain.RenameManager +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 BatchRenameModule { + + @Binds + @Singleton + fun renameManager(impl: AndroidRenameManager): RenameManager + +} diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/RenameManager.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/RenameManager.kt new file mode 100644 index 0000000..e2cfc18 --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/RenameManager.kt @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.domain + +import com.t8rin.imagetoolbox.feature.batchrename.domain.model.RenameFile +import com.t8rin.imagetoolbox.feature.batchrename.domain.model.RenameResult +import com.t8rin.imagetoolbox.feature.batchrename.domain.model.RenameTarget + +interface RenameManager { + + suspend fun readFiles(uris: Iterable): List + + suspend fun rename( + files: List, + writableUris: Set = emptySet() + ): RenameResult + +} \ No newline at end of file diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/helper/FilenamePatternResolver.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/helper/FilenamePatternResolver.kt new file mode 100644 index 0000000..95dc4ad --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/helper/FilenamePatternResolver.kt @@ -0,0 +1,160 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.domain.helper + +import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern +import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.replace +import com.t8rin.imagetoolbox.core.domain.utils.humanFileSize +import com.t8rin.imagetoolbox.core.domain.utils.slice +import com.t8rin.imagetoolbox.core.domain.utils.timestamp +import com.t8rin.imagetoolbox.core.domain.utils.transliterate +import com.t8rin.imagetoolbox.feature.batchrename.domain.model.RenameFile +import kotlin.random.Random +import kotlin.uuid.Uuid + +class FilenamePatternResolver( + private val prefix: String, + private val suffix: String, + randomSeed: Long = System.currentTimeMillis() +) { + private val random = Random(randomSeed) + + fun resolve( + pattern: String, + file: RenameFile, + sequence: Int, + dateMillis: Long + ): String { + fun randomDigits(count: Int): String = buildString { + repeat(count.coerceIn(0, 500)) { + append(random.nextInt(10)) + } + } + + var result = pattern + + result = runCatching { + result.replace(DATE_PATTERN) { match -> + timestamp(match.groupValues[1], dateMillis) + }.replace(DATE_PATTERN_UPPER) { match -> + timestamp(match.groupValues[1], dateMillis).uppercase() + } + }.getOrDefault(result) + + result = runCatching { + result.replace(SEQUENCE_PATTERN) { match -> + val params = match.groupValues[1].split(":") + val start = params.getOrNull(0)?.toIntOrNull() ?: 1 + val step = params.getOrNull(1)?.toIntOrNull() ?: 1 + val padding = params.getOrNull(2)?.toIntOrNull() + ?: if (params.size == 1) params[0].toIntOrNull() ?: 0 else 0 + + val current = start + (sequence - 1) * step + current.toString().padStart(padding, '0') + } + }.getOrDefault(result) + + result = runCatching { + result.replace(RANDOM_PATTERN) { match -> + randomDigits(match.groupValues[1].toIntOrNull() ?: DEFAULT_RANDOM_LENGTH) + } + }.getOrDefault(result) + + val extension = file.extension + val originalName = file.nameWithoutExtension + val defaultDate = timestamp(date = dateMillis) + + result = runCatching { + result.replace(ORIGINAL_NAME_SLICE_PATTERN) { match -> + val start = match.groupValues[1].toIntOrNull() + val end = match.groupValues[2].toIntOrNull() + originalName.slice(start, end) + }.replace(ORIGINAL_NAME_SLICE_PATTERN_UPPER) { match -> + val start = match.groupValues[1].toIntOrNull() + val end = match.groupValues[2].toIntOrNull() + originalName.uppercase().slice(start, end) + }.replace(ORIGINAL_NAME_TRANS_PATTERN) { + originalName.transliterate() + }.replace(ORIGINAL_NAME_TRANS_PATTERN_UPPER) { + originalName.uppercase().transliterate() + }.replace(ORIGINAL_NAME_REPLACE_PATTERN) { match -> + val old = match.groupValues[1] + val new = match.groupValues[2] + originalName.replace(old, new) + }.replace(ORIGINAL_NAME_REPLACE_PATTERN_UPPER) { match -> + val old = match.groupValues[1] + val new = match.groupValues[2] + originalName.uppercase().replace(old, new) + } + }.getOrDefault(result) + val parentFolder = file.parentFolder.orEmpty() + + val fileSizeRaw = file.fileSize ?: 0L + result = runCatching { + result.replace(FILE_SIZE_PATTERN) { match -> + val unit = match.groupValues[1].lowercase() + when (unit) { + "b" -> fileSizeRaw.toString() + "kb" -> (fileSizeRaw / 1024.0).let { "%.1f".format(it) } + "mb" -> (fileSizeRaw / (1024.0 * 1024.0)).let { "%.1f".format(it) } + else -> humanFileSize(fileSizeRaw) + } + } + }.getOrDefault(result) + + val fileSize = humanFileSize(fileSizeRaw) + + return result + .replace(FilenamePattern.Width, file.width?.toString().orEmpty()) + .replace(FilenamePattern.Height, file.height?.toString().orEmpty()) + .replace(FilenamePattern.Prefix, prefix) + .replace(FilenamePattern.Suffix, suffix) + .replace(FilenamePattern.OriginalName, originalName) + .replace(FilenamePattern.Sequence, sequence.toString()) + .replace(FilenamePattern.Extension, extension) + .replace(FilenamePattern.Rand, randomDigits(DEFAULT_RANDOM_LENGTH)) + .replace(FilenamePattern.Date, defaultDate) + .replace(FilenamePattern.ParentFolder, parentFolder) + .replace(FilenamePattern.FileSize, fileSize) + .replace(FilenamePattern.Uuid, Uuid.random().toString()) + .replace(FilenamePattern.PrefixUpper, prefix.uppercase()) + .replace(FilenamePattern.SuffixUpper, suffix.uppercase()) + .replace(FilenamePattern.OriginalNameUpper, originalName.uppercase()) + .replace(FilenamePattern.ExtensionUpper, extension.uppercase()) + .replace(FilenamePattern.DateUpper, defaultDate.uppercase()) + .replace(FilenamePattern.ParentFolderUpper, parentFolder.uppercase()) + .replace(FilenamePattern.FileSizeUpper, fileSize.uppercase()) + .replace(FilenamePattern.UuidUpper, Uuid.random().toString().uppercase()) + } + + private companion object { + const val DEFAULT_RANDOM_LENGTH = 4 + + val DATE_PATTERN = """\\d[{]([^}]*)[}]""".toRegex() + val DATE_PATTERN_UPPER = """\\D[{]([^}]*)[}]""".toRegex() + val RANDOM_PATTERN = """\\r[{](\d+)[}]""".toRegex() + val ORIGINAL_NAME_SLICE_PATTERN = """\\o\{(-?\d+)?:(-?\d+)?\}""".toRegex() + val ORIGINAL_NAME_SLICE_PATTERN_UPPER = """\\O\{(-?\d+)?:(-?\d+)?\}""".toRegex() + val ORIGINAL_NAME_TRANS_PATTERN = """\\o\{t\}""".toRegex() + val ORIGINAL_NAME_TRANS_PATTERN_UPPER = """\\O\{t\}""".toRegex() + val ORIGINAL_NAME_REPLACE_PATTERN = """\\o\{s/(.*?)/(.*?)/\}""".toRegex() + val ORIGINAL_NAME_REPLACE_PATTERN_UPPER = """\\O\{s/(.*?)/(.*?)/\}""".toRegex() + val SEQUENCE_PATTERN = """\\c\{([^}]*)\}""".toRegex() + val FILE_SIZE_PATTERN = """\\z\{([^}]*)\}""".toRegex() + } +} \ No newline at end of file diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/helper/RenamePatterns.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/helper/RenamePatterns.kt new file mode 100644 index 0000000..06c379f --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/helper/RenamePatterns.kt @@ -0,0 +1,81 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.domain.helper + +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.FileSize +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.ParentFolder +import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Prefix +import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Rand +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.Uuid +import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Width + +internal object RenamePatterns { + + val entries: List = listOf( + Prefix, + OriginalName, + Width, + Height, + Date, + Rand, + Sequence, + ParentFolder, + FileSize, + Uuid, + Suffix, + Extension + ) + + val Default = "${OriginalName}.${Extension}" + + private val disallowedTokens = listOf( + FilenamePattern.PresetInfo, + FilenamePattern.ScaleMode, + FilenamePattern.PresetInfoUpper, + FilenamePattern.ScaleModeUpper + ) + + fun containsDisallowed(pattern: String): Boolean = disallowedTokens.any { token -> + pattern.contains(token.value, ignoreCase = false) + } + + fun sanitize(pattern: String): String { + var result = pattern + disallowedTokens.forEach { token -> + result = result.replace(token.value, "") + } + return result.cleanPatternArtifacts() + } + + private fun String.cleanPatternArtifacts(): String = replace("[]", "") + .replace("{}", "") + .replace("()x()", "") + .replace("()", "") + .replace("___", "_") + .replace("__", "_") + .replace("_.", ".") + .removePrefix("_") + .removeSuffix(".") +} \ No newline at end of file diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/helper/RenameValidator.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/helper/RenameValidator.kt new file mode 100644 index 0000000..ca9b5fe --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/helper/RenameValidator.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.domain.helper + +import com.t8rin.imagetoolbox.feature.batchrename.domain.model.RenameValidationError +import com.t8rin.imagetoolbox.feature.batchrename.presentation.components.RenamePreview + +internal object RenameValidator { + + private const val MAX_FILENAME_BYTES = 255 + + fun validate( + pattern: String, + previews: List + ): RenameValidationError? { + if (pattern.isBlank()) return RenameValidationError.EmptyPattern + if (RenamePatterns.containsDisallowed(pattern)) { + return RenameValidationError.UnsupportedPattern + } + + val names = previews.map(RenamePreview::newName) + if (names.any(::isInvalidFilename)) return RenameValidationError.InvalidName + if (names.groupingBy { it.lowercase() }.eachCount().any { it.value > 1 }) { + return RenameValidationError.DuplicateName + } + if (previews.none(RenamePreview::isChanged)) { + return RenameValidationError.NoChanges + } + return null + } + + private fun isInvalidFilename(name: String): Boolean = name.isBlank() || + name == "." || + name == ".." || + '/' in name || + '\u0000' in name || + name.toByteArray().size > MAX_FILENAME_BYTES +} \ No newline at end of file diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/model/DateSource.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/model/DateSource.kt new file mode 100644 index 0000000..9b37c6d --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/model/DateSource.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.domain.model + +enum class DateSource { + ExifDateTaken, + FileModified, + FileCreated, + Current, + Manual +} \ No newline at end of file diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/model/RenameFile.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/model/RenameFile.kt new file mode 100644 index 0000000..66f46b6 --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/model/RenameFile.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.domain.model + +data class RenameFile( + val uri: String, + val originalName: String, + val width: Int? = null, + val height: Int? = null, + val exifDateTaken: Long? = null, + val modifiedDate: Long? = null, + val createdDate: Long? = null, + val fileSize: Long? = null, + val parentFolder: String? = null +) { + val extension: String + get() = originalName.substringAfterLast('.', missingDelimiterValue = "") + + val nameWithoutExtension: String + get() = if (extension.isEmpty()) originalName else originalName.substringBeforeLast('.') + + fun dateFor( + source: DateSource, + currentDate: Long, + manualDate: Long + ): Long? = when (source) { + DateSource.ExifDateTaken -> exifDateTaken + DateSource.FileModified -> modifiedDate + DateSource.FileCreated -> createdDate + DateSource.Current -> currentDate + DateSource.Manual -> manualDate + } +} \ No newline at end of file diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/model/RenameResult.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/model/RenameResult.kt new file mode 100644 index 0000000..94de919 --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/model/RenameResult.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.domain.model + +sealed interface RenameResult { + data class Success( + val renamed: Int, + val unchanged: Int + ) : RenameResult + + data class PermissionRequired( + val intentSender: Any, + val uris: Set + ) : RenameResult + + data class Failure( + val renamed: Int, + val failedNames: List, + val cause: Throwable? = null + ) : RenameResult +} diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/model/RenameTarget.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/model/RenameTarget.kt new file mode 100644 index 0000000..4b69d72 --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/model/RenameTarget.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.domain.model + +data class RenameTarget( + val file: RenameFile, + val newName: String +) \ No newline at end of file diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/model/RenameValidationError.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/model/RenameValidationError.kt new file mode 100644 index 0000000..db25b54 --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/domain/model/RenameValidationError.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.domain.model + +enum class RenameValidationError { + EmptyPattern, + InvalidName, + DuplicateName, + NoChanges, + UnsupportedPattern +} \ No newline at end of file diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/presentation/BatchRenameContent.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/presentation/BatchRenameContent.kt new file mode 100644 index 0000000..0d3a9a5 --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/presentation/BatchRenameContent.kt @@ -0,0 +1,176 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.presentation + +import android.app.Activity +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.IntentSenderRequest +import androidx.activity.result.contract.ActivityResultContracts +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.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 androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FileOpen +import com.t8rin.imagetoolbox.core.resources.icons.FileRename +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.dialogs.ResetDialog +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.batchrename.presentation.components.BatchRenameControls +import com.t8rin.imagetoolbox.feature.batchrename.presentation.screenLogic.BatchRenameComponent + +@Composable +fun BatchRenameContent(component: BatchRenameComponent) { + var showExitDialog by rememberSaveable { mutableStateOf(false) } + + val onBack = { + if (component.files.isNotEmpty()) { + showExitDialog = true + } else { + component.onGoBack() + } + } + + val filePicker = rememberFilePicker(onSuccess = component::setUris) + val additionalFilePicker = rememberFilePicker(onSuccess = component::addUris) + + AutoFilePicker( + onAutoPick = filePicker::pickFile, + isPickedAlready = !component.initialUris.isNullOrEmpty() + ) + + val permissionLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartIntentSenderForResult() + ) { result -> + component.onPermissionResult(result.resultCode == Activity.RESULT_OK) + } + + LaunchedEffect(component.pendingPermission) { + component.pendingPermission?.let { sender -> + runCatching { + permissionLauncher.launch(IntentSenderRequest.Builder(sender).build()) + }.onFailure { + component.onPermissionResult(false) + } + } + } + + val validationError by component.validationError.collectAsStateWithLifecycle() + + AdaptiveLayoutScreen( + shouldDisableBackHandler = component.files.isEmpty(), + onGoBack = onBack, + title = { + Text( + text = stringResource(R.string.batch_rename), + modifier = Modifier.marquee() + ) + }, + actions = {}, + topAppBarPersistentActions = { TopAppBarEmoji() }, + imagePreview = {}, + placeImagePreview = false, + showImagePreviewAsStickyHeader = false, + addHorizontalCutoutPaddingIfNoPreview = component.files.isNotEmpty(), + noDataControls = { + FileNotPickedWidget( + onPickFile = filePicker::pickFile, + text = stringResource(R.string.pick_files_to_rename) + ) + }, + controls = { + BatchRenameControls( + component = component, + validationError = validationError + ) + }, + buttons = { + var showConfirmDialog by rememberSaveable { + mutableStateOf(false) + } + + BottomButtonsBlock( + isNoData = component.files.isEmpty(), + onSecondaryButtonClick = if (component.files.isEmpty()) { + filePicker::pickFile + } else { + additionalFilePicker::pickFile + }, + secondaryButtonIcon = Icons.Rounded.FileOpen, + secondaryButtonText = stringResource(R.string.pick_files), + onPrimaryButtonClick = { showConfirmDialog = true }, + primaryButtonIcon = Icons.Outlined.FileRename, + primaryButtonText = stringResource(R.string.rename), + isPrimaryButtonEnabled = validationError == null, + actions = { + if (component.files.isNotEmpty()) { + EnhancedChip( + selected = true, + onClick = null, + selectedColor = MaterialTheme.colorScheme.secondaryContainer, + modifier = Modifier.padding(8.dp) + ) { + Text(component.files.size.toString()) + } + } + } + ) + + ResetDialog( + visible = showConfirmDialog, + onDismiss = { showConfirmDialog = false }, + onReset = component::rename, + title = stringResource(R.string.confirm_rename_title), + text = stringResource(R.string.rename_cannot_undone), + icon = Icons.Outlined.FileRename, + dismissText = stringResource(R.string.rename) + ) + }, + canShowScreenData = component.files.isNotEmpty() + ) + + ExitWithoutSavingDialog( + onExit = component.onGoBack, + onDismiss = { showExitDialog = false }, + visible = showExitDialog + ) + + LoadingDialog( + visible = component.isLoading, + canCancel = component.pendingPermission == null, + onCancelLoading = component::cancel, + ) +} diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/presentation/components/BatchRenameControls.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/presentation/components/BatchRenameControls.kt new file mode 100644 index 0000000..b7dfec3 --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/presentation/components/BatchRenameControls.kt @@ -0,0 +1,269 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.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.material3.MaterialTheme +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.material3.rememberTimePickerState +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.domain.saving.model.FilenamePattern +import com.t8rin.imagetoolbox.core.domain.utils.timestamp +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AccessTime +import com.t8rin.imagetoolbox.core.resources.icons.CalendarMonth +import com.t8rin.imagetoolbox.core.resources.icons.Description +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +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.EnhancedDatePickerDialog +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.other.InfoContainer +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem +import com.t8rin.imagetoolbox.core.ui.widget.text.FilenamePatternEditSheet +import com.t8rin.imagetoolbox.feature.batchrename.domain.helper.RenamePatterns +import com.t8rin.imagetoolbox.feature.batchrename.domain.model.DateSource +import com.t8rin.imagetoolbox.feature.batchrename.domain.model.RenameValidationError +import com.t8rin.imagetoolbox.feature.batchrename.presentation.screenLogic.BatchRenameComponent +import java.util.Calendar + +@Composable +internal fun BatchRenameControls( + component: BatchRenameComponent, + validationError: RenameValidationError? +) { + val previews by component.previews.collectAsStateWithLifecycle() + val exampleFilename = previews.firstOrNull()?.newName.orEmpty() + val isPortrait by isPortraitOrientationAsState() + val usedFallbackDate = remember(previews) { + previews.any { it.usedFallbackDate } + } + val hasDate = remember(component.pattern) { + component.pattern.contains( + other = FilenamePattern.Date.value, + ignoreCase = true + ) || component.pattern.contains( + other = FilenamePattern.DateUpper.value, + ignoreCase = true + ) + } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = if (isPortrait) 20.dp else 0.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + var showEditDialog by rememberSaveable { mutableStateOf(false) } + + PreferenceItem( + onClick = { showEditDialog = true }, + title = stringResource(R.string.filename_pattern), + subtitle = exampleFilename.ifBlank { component.pattern }.takeIf { it.isNotBlank() }, + endIcon = Icons.Rounded.MiniEdit, + startIcon = Icons.Outlined.Description, + modifier = Modifier.fillMaxWidth() + ) + + FilenamePatternEditSheet( + visible = showEditDialog, + onDismiss = { showEditDialog = false }, + value = component.pattern, + onValueChange = component::updatePattern, + onValueChangeFinished = component::updatePattern, + allowedPatterns = RenamePatterns.entries, + exampleFilename = { + AnimatedVisibility(exampleFilename.isNotBlank()) { + PreferenceItem( + title = stringResource(R.string.filename), + subtitle = exampleFilename, + modifier = Modifier + ) + } + } + ) + + validationError?.let { error -> + InfoContainer( + text = stringResource(error.messageRes()), + containerColor = if (error == RenameValidationError.NoChanges) { + MaterialTheme.colorScheme.secondaryContainer + } else { + MaterialTheme.colorScheme.errorContainer + }, + contentColor = if (error == RenameValidationError.NoChanges) { + MaterialTheme.colorScheme.onSecondaryContainer + } else { + MaterialTheme.colorScheme.onErrorContainer + } + ) + } + + AnimatedVisibility(hasDate) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + DataSelector( + value = component.dateSource, + onValueChange = component::updateDateSource, + entries = DateSource.entries, + title = stringResource(R.string.date_source), + titleIcon = Icons.Rounded.CalendarMonth, + itemContentText = { stringResource(it.titleRes()) }, + spanCount = 2, + selectedItemColor = MaterialTheme.colorScheme.secondaryContainer, + modifier = Modifier.fillMaxWidth() + ) + + AnimatedVisibility(component.dateSource == DateSource.Manual) { + ManualDateControls( + value = component.manualDate, + onValueChange = component::updateManualDate + ) + } + + AnimatedVisibility(usedFallbackDate) { + InfoContainer( + text = stringResource(R.string.date_source_fallback) + ) + } + } + } + + Column( + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + previews.forEachIndexed { index, preview -> + RenamePreviewItem( + preview = preview, + index = index, + shape = ShapeDefaults.byIndex(index, previews.size), + onRemove = { component.removeFile(preview.uri) }, + modifier = Modifier.fillMaxWidth() + ) + } + } + } +} + +@Composable +private fun ManualDateControls( + value: Long, + onValueChange: (Long) -> Unit +) { + val calendar = remember(value) { + Calendar.getInstance().apply { timeInMillis = value } + } + var showDatePicker by rememberSaveable { mutableStateOf(false) } + var showTimePicker by rememberSaveable { mutableStateOf(false) } + val datePickerState = rememberDatePickerState(initialSelectedDateMillis = value) + val timePickerState = rememberTimePickerState( + initialHour = calendar.get(Calendar.HOUR_OF_DAY), + initialMinute = calendar.get(Calendar.MINUTE) + ) + + Column( + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + PreferenceItem( + title = stringResource(R.string.manual_date), + subtitle = remember(value) { + timestamp("yyyy-MM-dd", value) + }, + startIcon = Icons.Rounded.CalendarMonth, + onClick = { showDatePicker = true }, + shape = ShapeDefaults.top, + modifier = Modifier.fillMaxWidth() + ) + PreferenceItem( + title = stringResource(R.string.manual_time), + subtitle = remember(value) { + timestamp("HH:mm", value) + }, + startIcon = Icons.Outlined.AccessTime, + onClick = { showTimePicker = true }, + shape = ShapeDefaults.bottom, + modifier = Modifier.fillMaxWidth() + ) + } + + EnhancedDatePickerDialog( + visible = showDatePicker, + onDismissRequest = { showDatePicker = false }, + state = datePickerState, + onDatePicked = { selected -> + val selectedCalendar = Calendar.getInstance().apply { timeInMillis = selected } + val result = Calendar.getInstance().apply { + timeInMillis = value + set(Calendar.YEAR, selectedCalendar.get(Calendar.YEAR)) + set(Calendar.MONTH, selectedCalendar.get(Calendar.MONTH)) + set(Calendar.DAY_OF_MONTH, selectedCalendar.get(Calendar.DAY_OF_MONTH)) + } + onValueChange(result.timeInMillis) + } + ) + EnhancedTimePickerDialog( + visible = showTimePicker, + onDismissRequest = { showTimePicker = false }, + state = timePickerState, + onTimePicked = { hour, minute -> + onValueChange( + Calendar.getInstance().apply { + timeInMillis = value + set(Calendar.HOUR_OF_DAY, hour) + set(Calendar.MINUTE, minute) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + ) + } + ) +} + +private fun DateSource.titleRes(): Int = when (this) { + DateSource.ExifDateTaken -> R.string.date_source_exif + DateSource.FileModified -> R.string.date_source_modified + DateSource.FileCreated -> R.string.date_source_created + DateSource.Current -> R.string.date_source_current + DateSource.Manual -> R.string.date_source_manual +} + +private fun RenameValidationError.messageRes(): Int = when (this) { + RenameValidationError.EmptyPattern -> R.string.rename_error_empty_pattern + RenameValidationError.InvalidName -> R.string.rename_error_invalid_name + RenameValidationError.DuplicateName -> R.string.rename_error_duplicate_name + RenameValidationError.NoChanges -> R.string.rename_error_no_changes + RenameValidationError.UnsupportedPattern -> R.string.rename_error_unsupported_pattern +} diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/presentation/components/RenamePreview.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/presentation/components/RenamePreview.kt new file mode 100644 index 0000000..aff0516 --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/presentation/components/RenamePreview.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.presentation.components + +import android.net.Uri +import androidx.compose.runtime.Immutable + +@Immutable +data class RenamePreview( + val uri: Uri, + val originalName: String, + val newName: String, + val usedFallbackDate: Boolean, + val isChanged: Boolean +) \ No newline at end of file diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/presentation/components/RenamePreviewItem.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/presentation/components/RenamePreviewItem.kt new file mode 100644 index 0000000..274469b --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/presentation/components/RenamePreviewItem.kt @@ -0,0 +1,234 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.presentation.components + +import android.net.Uri +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.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.width +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.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape +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.text.style.TextDecoration +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.CheckBoxOutlineBlank +import com.t8rin.imagetoolbox.core.resources.icons.RemoveCircle +import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview +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 + +@Composable +internal fun RenamePreviewItem( + preview: RenamePreview, + index: Int, + shape: Shape, + onRemove: () -> Unit, + modifier: Modifier = Modifier +) { + val contentAlpha = if (preview.isChanged) 1f else 0.55f + val thumbnailShape = ShapeDefaults.small + + Column( + modifier = modifier + .fillMaxWidth() + .container( + shape = shape, + resultPadding = 12.dp + ) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(52.dp) + .container( + shape = thumbnailShape, + color = MaterialTheme.colorScheme.surface, + resultPadding = 0.dp + ) + ) { + Picture( + model = preview.uri, + modifier = Modifier.fillMaxSize(), + shape = RectangleShape, + contentScale = ContentScale.Crop, + error = { + BoxWithConstraints( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxSize() + ) { + val width = this.maxWidth + + Icon( + imageVector = Icons.Rounded.CheckBoxOutlineBlank, + contentDescription = null, + modifier = Modifier + .size(width / 1.4f) + .align(Alignment.Center), + tint = MaterialTheme.colorScheme.primary + ) + } + } + ) + Box( + modifier = Modifier + .matchParentSize() + .background( + MaterialTheme.colorScheme.surfaceContainer.copy(0.3f) + ), + contentAlignment = Alignment.Center + ) { + Text( + text = "${index + 1}", + color = MaterialTheme.colorScheme.onSurface, + fontSize = 20.sp, + fontWeight = FontWeight.Bold + ) + } + } + + Spacer(Modifier.width(12.dp)) + + Text( + text = preview.originalName, + fontWeight = FontWeight.Medium, + lineHeight = 16.sp, + color = LocalContentColor.current.copy(alpha = contentAlpha), + textDecoration = if (preview.isChanged) { + TextDecoration.LineThrough + } else { + TextDecoration.None + }, + modifier = Modifier.weight(1f) + ) + + Spacer(Modifier.width(16.dp)) + + Box( + modifier = Modifier + .padding(end = 4.dp) + .clip(ShapeDefaults.circle) + .hapticsClickable( + onClick = onRemove + ) + ) { + Icon( + imageVector = Icons.Outlined.RemoveCircle, + contentDescription = stringResource(R.string.delete) + ) + } + } + + if (!preview.isChanged || preview.newName.isNotBlank()) { + Spacer(Modifier.height(8.dp)) + Text( + text = if (preview.isChanged) { + preview.newName + } else { + stringResource(R.string.rename_unchanged) + }, + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Start, + fontWeight = FontWeight.SemiBold, + lineHeight = 16.sp, + color = if (preview.isChanged) { + MaterialTheme.colorScheme.primary + } else { + LocalContentColor.current.copy(alpha = 0.5f) + } + ) + } + } +} + +@Composable +@Preview(locale = "ru") +private fun Preview() = ImageToolboxThemeForPreview( + isDarkTheme = false, + isImageError = true +) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + RenamePreviewItem( + preview = RenamePreview( + uri = Uri.EMPTY, + originalName = "preview.png", + newName = "Preview-new.png", + usedFallbackDate = false, + isChanged = true + ), + index = 0, + shape = ShapeDefaults.default, + onRemove = {} + ) + RenamePreviewItem( + preview = RenamePreview( + uri = Uri.EMPTY, + originalName = "preview.jpg", + newName = "preview.jpg", + usedFallbackDate = false, + isChanged = false + ), + index = 0, + shape = ShapeDefaults.default, + onRemove = {} + ) + RenamePreviewItem( + preview = RenamePreview( + uri = Uri.EMPTY, + originalName = "preview.jpg", + newName = "", + usedFallbackDate = false, + isChanged = false + ), + index = 0, + shape = ShapeDefaults.default, + onRemove = {} + ) + } +} \ No newline at end of file diff --git a/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/presentation/screenLogic/BatchRenameComponent.kt b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/presentation/screenLogic/BatchRenameComponent.kt new file mode 100644 index 0000000..33f6587 --- /dev/null +++ b/feature/batch-rename/src/main/java/com/t8rin/imagetoolbox/feature/batchrename/presentation/screenLogic/BatchRenameComponent.kt @@ -0,0 +1,255 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.batchrename.presentation.screenLogic + +import android.content.IntentSender +import android.net.Uri +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf +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.utils.smartJob +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Save +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.state.update +import com.t8rin.imagetoolbox.feature.batchrename.domain.RenameManager +import com.t8rin.imagetoolbox.feature.batchrename.domain.helper.FilenamePatternResolver +import com.t8rin.imagetoolbox.feature.batchrename.domain.helper.RenamePatterns +import com.t8rin.imagetoolbox.feature.batchrename.domain.helper.RenameValidator +import com.t8rin.imagetoolbox.feature.batchrename.domain.model.DateSource +import com.t8rin.imagetoolbox.feature.batchrename.domain.model.RenameFile +import com.t8rin.imagetoolbox.feature.batchrename.domain.model.RenameResult +import com.t8rin.imagetoolbox.feature.batchrename.domain.model.RenameTarget +import com.t8rin.imagetoolbox.feature.batchrename.domain.model.RenameValidationError +import com.t8rin.imagetoolbox.feature.batchrename.presentation.components.RenamePreview +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn + +class BatchRenameComponent @AssistedInject internal constructor( + @Assisted componentContext: ComponentContext, + @Assisted val initialUris: List?, + @Assisted val onGoBack: () -> Unit, + private val manager: RenameManager, + settingsProvider: SettingsProvider, + dispatchersHolder: DispatchersHolder +) : BaseComponent(dispatchersHolder, componentContext) { + + private val settings = settingsProvider.settingsState.value + + private val _files: MutableState> = mutableStateOf(emptyList()) + val files by _files + + private val _pattern: MutableState = mutableStateOf(RenamePatterns.Default) + val pattern by _pattern + + private val _dateSource: MutableState = mutableStateOf(DateSource.Current) + val dateSource by _dateSource + + private val _manualDate: MutableState = mutableLongStateOf(System.currentTimeMillis()) + val manualDate by _manualDate + + private val _isLoading: MutableState = mutableStateOf(false) + val isLoading by _isLoading + + private val _pendingPermission: MutableState = mutableStateOf(null) + val pendingPermission by _pendingPermission + + private val timestamp: Long = System.currentTimeMillis() + + private var pendingPermissionUris: Set = emptySet() + private val writableUris = mutableSetOf() + + private var renameJob: Job? by smartJob { + _isLoading.value = false + } + + val previews: StateFlow> = combine( + settingsProvider.settingsState, + snapshotFlow { files }, + snapshotFlow { pattern }, + snapshotFlow { dateSource }, + snapshotFlow { manualDate } + ) { _, _, _, dateSource, manualDate -> + createTargets().map { target -> + RenamePreview( + uri = target.file.uri.toUri(), + originalName = target.file.originalName, + newName = target.newName, + usedFallbackDate = target.file.dateFor( + source = dateSource, + currentDate = timestamp, + manualDate = manualDate + ) == null, + isChanged = target.file.originalName != target.newName + ) + } + }.stateIn( + scope = componentScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = emptyList() + ) + + val validationError: StateFlow = combine( + snapshotFlow { pattern }, + previews + ) { pattern, previews -> + RenameValidator.validate(pattern, previews) + }.stateIn( + scope = componentScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = null + ) + + init { + initialUris?.let(::setUris) + } + + fun setUris(uris: List) { + componentScope.launch { + _isLoading.value = true + _files.value = manager.readFiles(uris.map { it.toString() }) + writableUris.clear() + _isLoading.value = false + } + } + + fun addUris(uris: List) { + setUris((files.map { it.uri.toUri() } + uris).distinct()) + } + + fun removeFile(uri: Uri) { + _files.update { current -> current.filterNot { it.uri.toUri() == uri } } + } + + fun updatePattern(value: String) { + _pattern.value = value + } + + fun updateDateSource(value: DateSource) { + _dateSource.value = value + } + + fun updateManualDate(value: Long) { + _manualDate.value = value + } + + fun rename() { + if (files.isEmpty() || validationError.value != null) return + + renameJob = componentScope.launch { + _isLoading.value = true + val targetUris = createTargets() + val result = manager.rename( + files = targetUris, + writableUris = writableUris.map { it.toString() }.toSet() + ) + + when (result) { + is RenameResult.Success -> { + _pattern.value = RenamePatterns.Default + _files.value = emptyList() + writableUris.clear() + _isLoading.value = false + AppToastHost.showToast( + message = R.string.batch_rename_success, + icon = Icons.Rounded.Save + ) + AppToastHost.showConfetti() + } + + is RenameResult.PermissionRequired -> { + pendingPermissionUris = result.uris.mapTo(mutableSetOf()) { it.toUri() } + _pendingPermission.value = result.intentSender as? IntentSender + } + + is RenameResult.Failure -> { + _isLoading.value = false + result.cause?.let(AppToastHost::showFailureToast) + ?: AppToastHost.showFailureToast(R.string.batch_rename_failed) + } + } + } + } + + fun onPermissionResult(granted: Boolean) { + _pendingPermission.value = null + if (granted) { + writableUris += pendingPermissionUris + pendingPermissionUris = emptySet() + _isLoading.value = false + rename() + } else { + pendingPermissionUris = emptySet() + _isLoading.value = false + AppToastHost.showFailureToast(R.string.permission_not_granted) + } + } + + fun cancel() { + renameJob = null + } + + private fun createTargets(): List { + val resolver = FilenamePatternResolver( + prefix = settings.filenamePrefix, + suffix = settings.filenameSuffix, + randomSeed = timestamp + ) + val pattern = RenamePatterns.sanitize(pattern) + + return files.mapIndexed { index, file -> + val date = file.dateFor( + source = dateSource, + currentDate = timestamp, + manualDate = manualDate + ) ?: timestamp + RenameTarget( + file = file, + newName = resolver.resolve( + pattern = pattern, + file = file, + sequence = index + 1, + dateMillis = date + ) + ) + } + } + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUris: List?, + onGoBack: () -> Unit + ): BatchRenameComponent + } +} diff --git a/feature/checksum-tools/.gitignore b/feature/checksum-tools/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/checksum-tools/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/checksum-tools/build.gradle.kts b/feature/checksum-tools/build.gradle.kts new file mode 100644 index 0000000..6fddcc5 --- /dev/null +++ b/feature/checksum-tools/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/checksum-tools/src/main/AndroidManifest.xml b/feature/checksum-tools/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d5fcf6a --- /dev/null +++ b/feature/checksum-tools/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/data/AndroidChecksumManager.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/data/AndroidChecksumManager.kt new file mode 100644 index 0000000..a90c0cf --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/data/AndroidChecksumManager.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + +} \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/di/ChecksumToolsModule.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/di/ChecksumToolsModule.kt new file mode 100644 index 0000000..0763895 --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/di/ChecksumToolsModule.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/domain/ChecksumManager.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/domain/ChecksumManager.kt new file mode 100644 index 0000000..bf0fb01 --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/domain/ChecksumManager.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/domain/ChecksumSource.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/domain/ChecksumSource.kt new file mode 100644 index 0000000..87d8be3 --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/domain/ChecksumSource.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + +} \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/ChecksumToolsContent.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/ChecksumToolsContent.kt new file mode 100644 index 0000000..91aefa4 --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/ChecksumToolsContent.kt @@ -0,0 +1,184 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.HashTag +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.HashTag, + 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 = {} + ) +} \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumEnterField.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumEnterField.kt new file mode 100644 index 0000000..4d5e231 --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumEnterField.kt @@ -0,0 +1,72 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.Cancel +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 + ) +} \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumPage.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumPage.kt new file mode 100644 index 0000000..dcaa9dd --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumPage.kt @@ -0,0 +1,97 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumPreviewField.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumPreviewField.kt new file mode 100644 index 0000000..5320cdc --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumPreviewField.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.ContentCopy +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.Rounded.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 + ) +} \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumResultCard.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumResultCard.kt new file mode 100644 index 0000000..81d615d --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumResultCard.kt @@ -0,0 +1,103 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.layout.fillMaxWidth +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.CheckCircle +import com.t8rin.imagetoolbox.core.resources.icons.WarningAmber +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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) + } +} \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumToolsTabs.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumToolsTabs.kt new file mode 100644 index 0000000..04330b0 --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumToolsTabs.kt @@ -0,0 +1,207 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Calculate +import com.t8rin.imagetoolbox.core.resources.icons.CompareArrows +import com.t8rin.imagetoolbox.core.resources.icons.FolderMatch +import com.t8rin.imagetoolbox.core.resources.icons.TextFields +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 + ) + } + } +} \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/UriWithHashItem.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/UriWithHashItem.kt new file mode 100644 index 0000000..de6493d --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/UriWithHashItem.kt @@ -0,0 +1,85 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.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 + ) + } +} \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CalculateFromTextPage.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CalculateFromTextPage.kt new file mode 100644 index 0000000..5f4b39b --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CalculateFromTextPage.kt @@ -0,0 +1,69 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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), + ) + } +} \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CalculateFromUriPage.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CalculateFromUriPage.kt new file mode 100644 index 0000000..bc952de --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CalculateFromUriPage.kt @@ -0,0 +1,59 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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), + ) + } + } +} \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CompareWithUriPage.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CompareWithUriPage.kt new file mode 100644 index 0000000..14f260d --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CompareWithUriPage.kt @@ -0,0 +1,77 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } +} \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CompareWithUrisPage.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CompareWithUrisPage.kt new file mode 100644 index 0000000..96d63af --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CompareWithUrisPage.kt @@ -0,0 +1,191 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.FileCopy +import com.t8rin.imagetoolbox.core.resources.icons.FolderOpen +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.Rounded.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) + } +} \ No newline at end of file diff --git a/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/screenLogic/ChecksumToolsComponent.kt b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/screenLogic/ChecksumToolsComponent.kt new file mode 100644 index 0000000..1c727b3 --- /dev/null +++ b/feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/screenLogic/ChecksumToolsComponent.kt @@ -0,0 +1,236 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + +} \ No newline at end of file diff --git a/feature/cipher/.gitignore b/feature/cipher/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/cipher/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/cipher/build.gradle.kts b/feature/cipher/build.gradle.kts new file mode 100644 index 0000000..79387f4 --- /dev/null +++ b/feature/cipher/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/cipher/src/main/AndroidManifest.xml b/feature/cipher/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/cipher/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/data/AndroidCryptographyManager.kt b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/data/AndroidCryptographyManager.kt new file mode 100644 index 0000000..439e17c --- /dev/null +++ b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/data/AndroidCryptographyManager.kt @@ -0,0 +1,237 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + } +} \ No newline at end of file diff --git a/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/data/AndroidRandomStringGenerator.kt b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/data/AndroidRandomStringGenerator.kt new file mode 100644 index 0000000..2294d9f --- /dev/null +++ b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/data/AndroidRandomStringGenerator.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + +} \ No newline at end of file diff --git a/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/di/CipherModule.kt b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/di/CipherModule.kt new file mode 100644 index 0000000..d792612 --- /dev/null +++ b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/di/CipherModule.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/domain/CryptographyManager.kt b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/domain/CryptographyManager.kt new file mode 100644 index 0000000..2e8bb25 --- /dev/null +++ b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/domain/CryptographyManager.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/domain/WrongKeyException.kt b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/domain/WrongKeyException.kt new file mode 100644 index 0000000..9e49904 --- /dev/null +++ b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/domain/WrongKeyException.kt @@ -0,0 +1,20 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() \ No newline at end of file diff --git a/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/CipherContent.kt b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/CipherContent.kt new file mode 100644 index 0000000..3467064 --- /dev/null +++ b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/CipherContent.kt @@ -0,0 +1,196 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FileOpen +import com.t8rin.imagetoolbox.core.resources.icons.KeyVertical +import com.t8rin.imagetoolbox.core.resources.icons.Lock +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 = component.uri != null, + ) + + ExitWithoutSavingDialog( + onExit = component.onGoBack, + onDismiss = { showExitDialog = false }, + visible = showExitDialog + ) + + CipherTipSheet( + visible = showTip, + onDismiss = component::hideTip + ) + + LoadingDialog( + visible = component.isSaving, + onCancelLoading = component::cancelSaving + ) + +} \ No newline at end of file diff --git a/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/components/CipherControls.kt b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/components/CipherControls.kt new file mode 100644 index 0000000..e5e8f01 --- /dev/null +++ b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/components/CipherControls.kt @@ -0,0 +1,344 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Cancel +import com.t8rin.imagetoolbox.core.resources.icons.CheckCircle +import com.t8rin.imagetoolbox.core.resources.icons.Download +import com.t8rin.imagetoolbox.core.resources.icons.File +import com.t8rin.imagetoolbox.core.resources.icons.HashTag +import com.t8rin.imagetoolbox.core.resources.icons.Help +import com.t8rin.imagetoolbox.core.resources.icons.Share +import com.t8rin.imagetoolbox.core.resources.icons.Shuffle +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.Outlined.Help, + 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.Rounded.File + ) + 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.Download, + 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.HashTag, + itemContentText = { + it.name + }, + initialExpanded = true + ) + } +} \ No newline at end of file diff --git a/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/components/CipherTipSheet.kt b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/components/CipherTipSheet.kt new file mode 100644 index 0000000..ddf288f --- /dev/null +++ b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/components/CipherTipSheet.kt @@ -0,0 +1,158 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Extension +import com.t8rin.imagetoolbox.core.resources.icons.File +import com.t8rin.imagetoolbox.core.resources.icons.Functions +import com.t8rin.imagetoolbox.core.resources.icons.Interface +import com.t8rin.imagetoolbox.core.resources.icons.Security +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.Outlined.File + ) + 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.Extension + ) + 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)) + } + } + ) +} \ No newline at end of file diff --git a/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/screenLogic/CipherComponent.kt b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/screenLogic/CipherComponent.kt new file mode 100644 index 0000000..3804dbb --- /dev/null +++ b/feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/screenLogic/CipherComponent.kt @@ -0,0 +1,198 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} \ No newline at end of file diff --git a/feature/collage-maker/.gitignore b/feature/collage-maker/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/collage-maker/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/collage-maker/build.gradle.kts b/feature/collage-maker/build.gradle.kts new file mode 100644 index 0000000..f53651e --- /dev/null +++ b/feature/collage-maker/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + implementation(projects.lib.cropper) +} \ No newline at end of file diff --git a/feature/collage-maker/src/main/AndroidManifest.xml b/feature/collage-maker/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d5fcf6a --- /dev/null +++ b/feature/collage-maker/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/collage-maker/src/main/java/com/t8rin/imagetoolbox/collage_maker/presentation/CollageMakerContent.kt b/feature/collage-maker/src/main/java/com/t8rin/imagetoolbox/collage_maker/presentation/CollageMakerContent.kt new file mode 100644 index 0000000..593b152 --- /dev/null +++ b/feature/collage-maker/src/main/java/com/t8rin/imagetoolbox/collage_maker/presentation/CollageMakerContent.kt @@ -0,0 +1,738 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Add +import com.t8rin.imagetoolbox.core.resources.icons.BackgroundColor +import com.t8rin.imagetoolbox.core.resources.icons.Collage +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.FormatLineSpacing +import com.t8rin.imagetoolbox.core.resources.icons.ImageReset +import com.t8rin.imagetoolbox.core.resources.icons.PhotoSizeSelectSmall +import com.t8rin.imagetoolbox.core.resources.icons.PinEnd +import com.t8rin.imagetoolbox.core.resources.icons.RoundedCorner +import com.t8rin.imagetoolbox.core.resources.icons.SwapHoriz +import com.t8rin.imagetoolbox.core.resources.icons.SwipeVertical +import com.t8rin.imagetoolbox.core.resources.icons.Tune +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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.Collage + ) + } + } + } + + 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.Collage + ) + } + } + + 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.Outlined.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.Rounded.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 + }, + drawBothStrokes = 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) + } + } +} diff --git a/feature/collage-maker/src/main/java/com/t8rin/imagetoolbox/collage_maker/presentation/components/CollageParams.kt b/feature/collage-maker/src/main/java/com/t8rin/imagetoolbox/collage_maker/presentation/components/CollageParams.kt new file mode 100644 index 0000000..a956217 --- /dev/null +++ b/feature/collage-maker/src/main/java/com/t8rin/imagetoolbox/collage_maker/presentation/components/CollageParams.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 +) \ No newline at end of file diff --git a/feature/collage-maker/src/main/java/com/t8rin/imagetoolbox/collage_maker/presentation/screenLogic/CollageMakerComponent.kt b/feature/collage-maker/src/main/java/com/t8rin/imagetoolbox/collage_maker/presentation/screenLogic/CollageMakerComponent.kt new file mode 100644 index 0000000..72c5ce1 --- /dev/null +++ b/feature/collage-maker/src/main/java/com/t8rin/imagetoolbox/collage_maker/presentation/screenLogic/CollageMakerComponent.kt @@ -0,0 +1,315 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 + } + +} \ No newline at end of file diff --git a/feature/color-library/.gitignore b/feature/color-library/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/color-library/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/color-library/build.gradle.kts b/feature/color-library/build.gradle.kts new file mode 100644 index 0000000..94796a0 --- /dev/null +++ b/feature/color-library/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/color-library/src/main/AndroidManifest.xml b/feature/color-library/src/main/AndroidManifest.xml new file mode 100644 index 0000000..205decf --- /dev/null +++ b/feature/color-library/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/color-library/src/main/java/com/t8rin/imagetoolbox/color_library/presentation/ColorLibraryContent.kt b/feature/color-library/src/main/java/com/t8rin/imagetoolbox/color_library/presentation/ColorLibraryContent.kt new file mode 100644 index 0000000..45f8728 --- /dev/null +++ b/feature/color-library/src/main/java/com/t8rin/imagetoolbox/color_library/presentation/ColorLibraryContent.kt @@ -0,0 +1,308 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.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.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.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.parser.ColorWithName +import com.t8rin.imagetoolbox.color_library.presentation.screenLogic.ColorLibraryComponent +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.Search +import com.t8rin.imagetoolbox.core.resources.icons.SearchOff +import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState +import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ColorCopyFormatSelectionDialog +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 focus = LocalFocusManager.current + var isSearching by rememberSaveable { + mutableStateOf(false) + } + var colorCopyTarget by remember { + mutableStateOf(null) + } + + 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.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.Outlined.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 = { + colorCopyTarget = colorWithName + }, + 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.Outlined.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() + } + } + } + } + + ColorCopyFormatSelectionDialog( + target = colorCopyTarget, + onDismiss = { colorCopyTarget = null } + ) +} \ No newline at end of file diff --git a/feature/color-library/src/main/java/com/t8rin/imagetoolbox/color_library/presentation/components/FavoriteColors.kt b/feature/color-library/src/main/java/com/t8rin/imagetoolbox/color_library/presentation/components/FavoriteColors.kt new file mode 100644 index 0000000..fa879b2 --- /dev/null +++ b/feature/color-library/src/main/java/com/t8rin/imagetoolbox/color_library/presentation/components/FavoriteColors.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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() +) \ No newline at end of file diff --git a/feature/color-library/src/main/java/com/t8rin/imagetoolbox/color_library/presentation/screenLogic/ColorLibraryComponent.kt b/feature/color-library/src/main/java/com/t8rin/imagetoolbox/color_library/presentation/screenLogic/ColorLibraryComponent.kt new file mode 100644 index 0000000..810e4ab --- /dev/null +++ b/feature/color-library/src/main/java/com/t8rin/imagetoolbox/color_library/presentation/screenLogic/ColorLibraryComponent.kt @@ -0,0 +1,131 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 + } + +} \ No newline at end of file diff --git a/feature/color-tools/.gitignore b/feature/color-tools/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/color-tools/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/color-tools/build.gradle.kts b/feature/color-tools/build.gradle.kts new file mode 100644 index 0000000..37edf0a --- /dev/null +++ b/feature/color-tools/build.gradle.kts @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" + +dependencies { + implementation(libs.toolbox.histogram) +} \ No newline at end of file diff --git a/feature/color-tools/src/main/AndroidManifest.xml b/feature/color-tools/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1d2906e --- /dev/null +++ b/feature/color-tools/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + \ No newline at end of file diff --git a/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/ColorToolsContent.kt b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/ColorToolsContent.kt new file mode 100644 index 0000000..12a78bd --- /dev/null +++ b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/ColorToolsContent.kt @@ -0,0 +1,191 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.Palette +import com.t8rin.imagetoolbox.core.resources.icons.PushPin +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 + ) + } +} \ No newline at end of file diff --git a/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorHarmonies.kt b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorHarmonies.kt new file mode 100644 index 0000000..c1a722e --- /dev/null +++ b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorHarmonies.kt @@ -0,0 +1,159 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.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.colors.parser.ColorWithName +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.BarChart +import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ColorCopyFormatSelectionDialog +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) + } + } + var colorCopyTarget by remember { + mutableStateOf(null) + } + + 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 = { name -> + colorCopyTarget = ColorWithName( + color = color, + name = name + ) + }, + modifier = Modifier + .heightIn(min = 120.dp) + .weight(1f) + ) + } + } + } + } + }, + shape = ShapeDefaults.extraLarge, + initialState = false + ) + + ColorCopyFormatSelectionDialog( + target = colorCopyTarget, + onDismiss = { colorCopyTarget = null } + ) +} \ No newline at end of file diff --git a/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorHarmoniesUtils.kt b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorHarmoniesUtils.kt new file mode 100644 index 0000000..6ea6710 --- /dev/null +++ b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorHarmoniesUtils.kt @@ -0,0 +1,131 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 com.t8rin.imagetoolbox.core.resources.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() +} \ No newline at end of file diff --git a/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorHistogram.kt b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorHistogram.kt new file mode 100644 index 0000000..701a000 --- /dev/null +++ b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorHistogram.kt @@ -0,0 +1,129 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorInfo.kt b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorInfo.kt new file mode 100644 index 0000000..aa2cdda --- /dev/null +++ b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorInfo.kt @@ -0,0 +1,128 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.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.colors.parser.ColorWithName +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Info +import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard +import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ColorCopyFormatSelectionDialog +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() + var colorCopyTarget by remember { + mutableStateOf(null) + } + + 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 = { name -> + colorCopyTarget = ColorWithName( + color = selectedColor, + name = name + ) + } + ) + 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 + ) + + ColorCopyFormatSelectionDialog( + target = colorCopyTarget, + onDismiss = { colorCopyTarget = null } + ) +} \ No newline at end of file diff --git a/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorInfoDisplay.kt b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorInfoDisplay.kt new file mode 100644 index 0000000..ac00c18 --- /dev/null +++ b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorInfoDisplay.kt @@ -0,0 +1,301 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.ContentCopy +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() \ No newline at end of file diff --git a/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorMixing.kt b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorMixing.kt new file mode 100644 index 0000000..7041b25 --- /dev/null +++ b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorMixing.kt @@ -0,0 +1,153 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.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.colors.parser.ColorWithName +import com.t8rin.dynamic.theme.ColorTuple +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Blender +import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector +import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ColorCopyFormatSelectionDialog +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 + ) + } + } + var colorCopyTarget by remember { + mutableStateOf(null) + } + + 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 = { name -> + colorCopyTarget = ColorWithName( + color = color, + name = name + ) + } + ) + } + } + } + }, + shape = ShapeDefaults.extraLarge, + initialState = false + ) + + ColorCopyFormatSelectionDialog( + target = colorCopyTarget, + onDismiss = { colorCopyTarget = null } + ) +} \ No newline at end of file diff --git a/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorMixingUtils.kt b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorMixingUtils.kt new file mode 100644 index 0000000..a882478 --- /dev/null +++ b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorMixingUtils.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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) +} \ No newline at end of file diff --git a/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorShading.kt b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorShading.kt new file mode 100644 index 0000000..0cab604 --- /dev/null +++ b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorShading.kt @@ -0,0 +1,162 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.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.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.colors.parser.ColorWithName +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Swatch +import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ColorCopyFormatSelectionDialog +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 + ) + } + } + var colorCopyTarget by remember { + mutableStateOf(null) + } + + 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 = { name -> + colorCopyTarget = ColorWithName( + color = color, + name = name + ) + }, + modifier = Modifier.heightIn(min = 100.dp) + ) + } + } + } + } + } + }, + shape = ShapeDefaults.extraLarge, + initialState = false + ) + + ColorCopyFormatSelectionDialog( + target = colorCopyTarget, + onDismiss = { colorCopyTarget = null } + ) +} \ No newline at end of file diff --git a/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/screenLogic/ColorToolsComponent.kt b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/screenLogic/ColorToolsComponent.kt new file mode 100644 index 0000000..07c6ce2 --- /dev/null +++ b/feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/screenLogic/ColorToolsComponent.kt @@ -0,0 +1,69 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 + } + +} \ No newline at end of file diff --git a/feature/compare/.gitignore b/feature/compare/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/compare/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/compare/build.gradle.kts b/feature/compare/build.gradle.kts new file mode 100644 index 0000000..6968991 --- /dev/null +++ b/feature/compare/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/compare/src/main/AndroidManifest.xml b/feature/compare/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/compare/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/CompareContent.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/CompareContent.kt new file mode 100644 index 0000000..ae69415 --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/CompareContent.kt @@ -0,0 +1,236 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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 + ) +} \ No newline at end of file diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareLabel.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareLabel.kt new file mode 100644 index 0000000..a762d94 --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareLabel.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.ui.theme.blend +import com.t8rin.imagetoolbox.core.ui.theme.onPrimaryContainerFixed +import com.t8rin.imagetoolbox.core.ui.theme.primaryContainerFixed +import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.rememberFilename + +@Composable +internal fun BoxScope.CompareLabel( + uri: Uri?, + alignment: Alignment, + enabled: Boolean, + shape: Shape, + containerColor: Color = MaterialTheme.colorScheme.primaryContainerFixed.blend(Color.Black), + contentColor: Color = MaterialTheme.colorScheme.onPrimaryContainerFixed, + 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 = containerColor, + shape = shape + ) + .padding(horizontal = 8.dp, vertical = 4.dp), + style = MaterialTheme.typography.labelMedium, + color = contentColor + ) + } +} \ No newline at end of file diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareScreenContent.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareScreenContent.kt new file mode 100644 index 0000000..825744d --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareScreenContent.kt @@ -0,0 +1,516 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.runtime.snapshotFlow +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt +import com.t8rin.imagetoolbox.core.resources.icons.Highlight +import com.t8rin.imagetoolbox.core.resources.icons.Pix +import com.t8rin.imagetoolbox.core.resources.icons.Tune +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 progressState = remember { mutableFloatStateOf(compareProgress()) } + LaunchedEffect(compareProgress) { + snapshotFlow { compareProgress() }.collect { value -> + if (progressState.floatValue != value) { + progressState.floatValue = value + } + } + } + + 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, + compareProgressState = progressState, + onCompareProgressChange = onCompareProgressChange, + isPortrait = true, + isLabelsEnabled = isLabelsEnabled, + pixelByPixelCompareState = pixelByPixelCompareState, + createPixelByPixelTransformation = createPixelByPixelTransformation + ) + } + + tuneButton() + } + + val showButtonsAtTheTop = + 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 = progressState.floatValue, + onValueChange = { progressState.floatValue = it }, + onValueChangeFinished = { + onCompareProgressChange(progressState.floatValue) + }, + valueRange = 0f..100f, + isAnimated = false + ) + } + } + } + ) + } + } 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, + compareProgressState = progressState, + onCompareProgressChange = onCompareProgressChange, + isPortrait = false, + isLabelsEnabled = isLabelsEnabled, + pixelByPixelCompareState = pixelByPixelCompareState, + createPixelByPixelTransformation = createPixelByPixelTransformation + ) + } + + tuneButton() + } + + val showButtonsAtTheStart = + 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 = progressState.floatValue, + onValueChange = { progressState.floatValue = it }, + onValueChangeFinished = { + onCompareProgressChange(progressState.floatValue) + }, + valueRange = 0f..100f, + isAnimated = false + ) + } + } + + 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) + ) + } + } +} \ No newline at end of file diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareScreenContentImpl.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareScreenContentImpl.kt new file mode 100644 index 0000000..42c4d3e --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareScreenContentImpl.kt @@ -0,0 +1,428 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.MutableFloatState +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.draw.clip +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.graphicsLayer +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 kotlinx.coroutines.flow.collectLatest +import net.engawapg.lib.zoomable.rememberZoomState +import net.engawapg.lib.zoomable.zoomable + +@Composable +internal fun CompareScreenContentImpl( + compareType: CompareType, + bitmapPair: CompareData, + pixelByPixelCompareState: PixelByPixelCompareState, + compareProgressState: MutableFloatState, + 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 = { compareProgressState.floatValue }, + sharedProgress = compareProgressState, + 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.small.only( + CornerSides.BottomEnd + ) + ) + } + }, + afterLabel = { + Box( + modifier = Modifier.matchParentSize() + ) { + CompareLabel( + uri = afterData.uri, + alignment = Alignment.BottomEnd, + enabled = isLabelsEnabled, + shape = ShapeDefaults.small.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, + contentScale = ContentScale.Fit, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .then(zoomModifier) + ) + HorizontalDivider() + } + if (second != null) { + Picture( + model = second, + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .then(zoomModifier) + ) + } + } + CompareLabel( + uri = bitmapPair.first?.uri, + alignment = Alignment.TopStart, + enabled = isLabelsEnabled, + shape = ShapeDefaults.small.only( + CornerSides.BottomEnd + ) + ) + CompareLabel( + uri = bitmapPair.second?.uri, + alignment = Alignment.BottomStart, + enabled = isLabelsEnabled, + shape = ShapeDefaults.small.only( + CornerSides.TopEnd + ) + ) + } else { + Row( + modifier = Modifier.fillMaxSize(), + verticalAlignment = Alignment.CenterVertically + ) { + if (first != null) { + Picture( + model = first, + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier + .fillMaxHeight() + .weight(1f) + .then(zoomModifier) + ) + VerticalDivider() + } + if (second != null) { + Picture( + model = second, + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier + .fillMaxHeight() + .weight(1f) + .then(zoomModifier) + ) + } + } + CompareLabel( + uri = bitmapPair.first?.uri, + alignment = Alignment.TopStart, + enabled = isLabelsEnabled, + shape = ShapeDefaults.small.only( + CornerSides.BottomEnd + ) + ) + CompareLabel( + uri = bitmapPair.second?.uri, + alignment = Alignment.TopEnd, + enabled = isLabelsEnabled, + shape = ShapeDefaults.small.only( + CornerSides.BottomStart + ) + ) + } + } + } + + CompareType.Tap -> { + var showSecondImage by rememberSaveable { + mutableStateOf(false) + } + Box( + modifier = modifier + .fillMaxSize() + .tappable { + showSecondImage = !showSecondImage + } + ) { + val first = bitmapPair.first?.image + val second = bitmapPair.second?.image + if (!showSecondImage && first != null) { + Picture( + model = first, + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier.fillMaxSize() + ) + } + if (showSecondImage && second != null) { + Picture( + model = second, + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier.fillMaxSize() + ) + } + 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.small.only( + CornerSides.TopStart + ) + } else { + ShapeDefaults.small.only( + CornerSides.BottomEnd + ) + } + ) + } + } + } + + CompareType.Transparency -> { + Box( + modifier = modifier.fillMaxSize() + ) { + val first = bitmapPair.first?.image + val second = bitmapPair.second?.image + if (first != null) { + Picture( + model = first, + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier.fillMaxSize() + ) + } + if (second != null) { + Picture( + model = second, + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier + .fillMaxSize() + .graphicsLayer { + alpha = compareProgressState.floatValue / 100f + } + ) + } + Box( + modifier = Modifier.matchParentSize() + ) { + CompareLabel( + uri = bitmapPair.first?.uri, + alignment = Alignment.TopStart, + enabled = isLabelsEnabled, + shape = ShapeDefaults.small.only( + CornerSides.BottomEnd + ) + ) + } + Box( + modifier = Modifier.matchParentSize() + ) { + CompareLabel( + uri = bitmapPair.second?.uri, + modifier = Modifier.graphicsLayer { + alpha = compareProgressState.floatValue / 100f + }, + alignment = Alignment.BottomEnd, + enabled = isLabelsEnabled, + shape = ShapeDefaults.small.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.fillMaxSize() + ) { + if (first != null) { + var transformations: List by remember { + mutableStateOf(emptyList()) + } + + LaunchedEffect( + first, + second, + pixelByPixelCompareState + ) { + snapshotFlow { compareProgressState.floatValue }.collectLatest { + delay(300) + transformations = listOf( + createPixelByPixelTransformation() + ) + } + } + + Picture( + model = first, + transformations = transformations, + onSuccess = { + isLoading = false + }, + onError = { + isLoading = false + }, + onLoading = { + isLoading = true + }, + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier.fillMaxSize() + ) + } + } + + AnimatedVisibility( + visible = isLoading && first != null, + enter = fadeIn(), + exit = fadeOut() + ) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + EnhancedLoadingIndicator() + } + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareScreenTopAppBar.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareScreenTopAppBar.kt new file mode 100644 index 0000000..c5bbd9e --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareScreenTopAppBar.kt @@ -0,0 +1,146 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.Label +import com.t8rin.imagetoolbox.core.resources.icons.RotateLeft +import com.t8rin.imagetoolbox.core.resources.icons.RotateRight +import com.t8rin.imagetoolbox.core.resources.icons.SwapHoriz +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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.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.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.Rounded.RotateLeft + else Icons.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.Rounded.Label, + contentDescription = "Label" + ) + } + } + }, + title = { + AnimatedContent( + targetState = titleWhenBitmapsPicked, + modifier = Modifier.marquee() + ) { text -> + Text(text) + } + } + ) + } +} \ No newline at end of file diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareSelectionButtons.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareSelectionButtons.kt new file mode 100644 index 0000000..68cbb21 --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareSelectionButtons.kt @@ -0,0 +1,99 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.resources.utils.animation.animateColorAsState +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() + } + } +} \ No newline at end of file diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareShareSheet.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareShareSheet.kt new file mode 100644 index 0000000..bc0cdb0 --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareShareSheet.kt @@ -0,0 +1,189 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.ContentCopy +import com.t8rin.imagetoolbox.core.resources.icons.IosShare +import com.t8rin.imagetoolbox.core.resources.icons.Save +import com.t8rin.imagetoolbox.core.resources.icons.Share +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 + ) +} \ No newline at end of file diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareSheet.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareSheet.kt new file mode 100644 index 0000000..37387bd --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareSheet.kt @@ -0,0 +1,119 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.foundation.layout.aspectRatio +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.graphics.asImageBitmap +import androidx.compose.ui.res.stringResource +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Compare +import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.derivative.EnhancedZoomableModalBottomSheet +import com.t8rin.imagetoolbox.core.ui.widget.image.Picture +import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem +import com.t8rin.imagetoolbox.feature.compare.presentation.components.beforeafter.BeforeAfterLayout + +@Composable +fun CompareSheet( + data: Pair?, + visible: Boolean, + onDismiss: () -> Unit +) { + var progress by rememberSaveable(visible) { mutableFloatStateOf(50f) } + + if (data != null) { + EnhancedZoomableModalBottomSheet( + visible = visible, + onDismiss = onDismiss, + title = { + TitleItem( + text = stringResource(R.string.compare), + icon = Icons.Outlined.Compare + ) + } + ) { + data.let { (b, a) -> + val before = remember(data) { b?.asImageBitmap() } + val after = remember(data) { a?.asImageBitmap() } + if (before != null && after != null) { + BeforeAfterLayout( + progress = { progress }, + onProgressChange = { + progress = it + }, + beforeContent = { + Picture( + model = before, + modifier = Modifier.aspectRatio(before.safeAspectRatio) + ) + }, + afterContent = { + Picture( + model = after, + modifier = Modifier.aspectRatio(after.safeAspectRatio) + ) + }, + beforeLabel = { }, + afterLabel = { } + ) + } + } + } + } +} + +@Composable +fun CompareSheet( + beforeContent: @Composable () -> Unit, + afterContent: @Composable () -> Unit, + visible: Boolean, + onDismiss: () -> Unit +) { + var progress by rememberSaveable(visible) { mutableFloatStateOf(50f) } + + EnhancedZoomableModalBottomSheet( + visible = visible, + onDismiss = onDismiss, + title = { + TitleItem( + text = stringResource(R.string.compare), + icon = Icons.Outlined.Compare + ) + } + ) { + BeforeAfterLayout( + progress = { progress }, + onProgressChange = { + progress = it + }, + beforeContent = beforeContent, + afterContent = afterContent, + beforeLabel = { }, + afterLabel = { } + ) + } +} \ No newline at end of file diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareType.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareType.kt new file mode 100644 index 0000000..756efdb --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareType.kt @@ -0,0 +1,64 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.Tonality +import com.t8rin.imagetoolbox.core.resources.icons.TouchApp +import com.t8rin.imagetoolbox.core.resources.icons.ZoomIn + +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.Outlined.ZoomIn, + title = R.string.side_by_side + ) + + data object Tap : CompareType( + icon = Icons.Outlined.TouchApp, + title = R.string.toggle_tap + ) + + data object Transparency : CompareType( + icon = Icons.Outlined.Tonality, + 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) + } + } +} \ No newline at end of file diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/PixelByPixelCompareState.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/PixelByPixelCompareState.kt new file mode 100644 index 0000000..5f89575 --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/PixelByPixelCompareState.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/BeforeAfterLayout.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/BeforeAfterLayout.kt new file mode 100644 index 0000000..1a34e97 --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/BeforeAfterLayout.kt @@ -0,0 +1,221 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.layout.BoxScope +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableFloatState +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, + progress: () -> Float = { 50f }, + sharedProgress: MutableFloatState? = null, + 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, + sharedProgress = sharedProgress, + 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, + 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 + ) +} \ No newline at end of file diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/BeforeAfterLayoutImpl.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/BeforeAfterLayoutImpl.kt new file mode 100644 index 0000000..6010eb7 --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/BeforeAfterLayoutImpl.kt @@ -0,0 +1,225 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableFloatState +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.draw.clipToBounds +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.drawscope.clipRect +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.DpSize +import com.t8rin.gesture.detectMotionEvents + +@Composable +internal fun Layout( + modifier: Modifier = Modifier, + @FloatRange(from = 0.0, to = 100.0) progress: () -> Float = { 50f }, + sharedProgress: MutableFloatState? = null, + 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 -> + BeforeAfterSplitLayout( + contentSize = contentSize, + progress = progress, + sharedProgress = sharedProgress, + onProgressChange = onProgressChange, + enableProgressWithTouch = enableProgressWithTouch, + contentOrder = contentOrder, + beforeContent = beforeContent, + afterContent = afterContent, + beforeLabel = beforeLabel, + afterLabel = afterLabel, + overlay = overlay, + ) + } + ) +} + +@Composable +private fun BeforeAfterSplitLayout( + contentSize: Size, + progress: () -> Float, + sharedProgress: MutableFloatState?, + onProgressChange: ((Float) -> Unit)?, + enableProgressWithTouch: Boolean, + contentOrder: ContentOrder, + beforeContent: @Composable () -> Unit, + afterContent: @Composable () -> Unit, + beforeLabel: @Composable (BoxScope.() -> Unit)?, + afterLabel: @Composable (BoxScope.() -> Unit)?, + overlay: @Composable ((DpSize, Offset) -> Unit)?, +) { + val boxWidth = contentSize.width + val boxHeight = contentSize.height + + val boxWidthInDp = with(LocalDensity.current) { boxWidth.toDp() } + val boxHeightInDp = with(LocalDensity.current) { boxHeight.toDp() } + + fun scaleToUserValue(offset: Float) = + scale(0f, boxWidth, offset, 0f, 100f) + + fun scaleToOffset(userValue: Float) = + scale(0f, 100f, userValue, 0f, boxWidth) + + var rawOffset by remember(boxWidth, boxHeight) { + mutableStateOf( + Offset( + x = scaleToOffset(progress()), + y = boxHeight / 2f, + ) + ) + } + + var isHandleTouched by remember { mutableStateOf(false) } + + LaunchedEffect(boxWidth, boxHeight, progress, sharedProgress) { + if (sharedProgress != null) return@LaunchedEffect + snapshotFlow { + progress() to isHandleTouched + }.collect { (value, isTouched) -> + if (!isTouched) { + rawOffset = rawOffset.copy(x = scaleToOffset(value)) + } + } + } + + val clipX = if (sharedProgress != null && !isHandleTouched) { + scaleToOffset(sharedProgress.floatValue) + } else { + rawOffset.x + } + + val overlayOffset = if (isHandleTouched) { + rawOffset + } else { + Offset(x = clipX, y = rawOffset.y) + } + + val touchModifier = Modifier.pointerInput(boxWidth, boxHeight) { + detectMotionEvents( + onDown = { + val position = it.position + val xPos = position.x + val currentX = if (sharedProgress != null && !isHandleTouched) { + scaleToOffset(sharedProgress.floatValue) + } else { + rawOffset.x + } + + isHandleTouched = + ((currentX - xPos) * (currentX - xPos) < 5000) + + if (isHandleTouched) { + rawOffset = Offset( + x = currentX, + y = boxHeight / 2f, + ) + } + }, + onMove = { + if (isHandleTouched) { + rawOffset = it.position + sharedProgress?.floatValue = scaleToUserValue(rawOffset.x) + it.consume() + } + }, + onUp = { + if (isHandleTouched) { + onProgressChange?.invoke(scaleToUserValue(rawOffset.x)) + } + isHandleTouched = false + } + ) + } + + val parentModifier = Modifier + .size(boxWidthInDp, boxHeightInDp) + .clipToBounds() + .then(if (enableProgressWithTouch) touchModifier else Modifier) + + val beforeClipModifier = Modifier + .fillMaxSize() + .drawWithContent { + clipRect(left = 0f, top = 0f, right = clipX, bottom = size.height) { + this@drawWithContent.drawContent() + } + } + + val afterClipModifier = Modifier + .fillMaxSize() + .drawWithContent { + clipRect(left = clipX, top = 0f, right = size.width, bottom = size.height) { + this@drawWithContent.drawContent() + } + } + + Box(modifier = parentModifier) { + val before = @Composable { + Box(if (contentOrder == ContentOrder.BeforeAfter) beforeClipModifier else afterClipModifier) { + beforeContent() + beforeLabel?.invoke(this) + } + } + + val after = @Composable { + Box(if (contentOrder == ContentOrder.BeforeAfter) afterClipModifier else beforeClipModifier) { + afterContent() + afterLabel?.invoke(this) + } + } + + if (contentOrder == ContentOrder.BeforeAfter) { + before() + after() + } else { + after() + before() + } + } + + overlay?.invoke( + DpSize(boxWidthInDp, boxHeightInDp), overlayOffset + ) +} diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/ContentOrder.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/ContentOrder.kt new file mode 100644 index 0000000..0987551 --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/ContentOrder.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 +} diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/DefaultOverlay.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/DefaultOverlay.kt new file mode 100644 index 0000000..6af88c7 --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/DefaultOverlay.kt @@ -0,0 +1,202 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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 +import com.t8rin.imagetoolbox.core.resources.icons.DragHandle + +/** + * 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, +) diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/DimensionSubcomposeLayout.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/DimensionSubcomposeLayout.kt new file mode 100644 index 0000000..0f5ca3f --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/DimensionSubcomposeLayout.kt @@ -0,0 +1,95 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + } + } +} + diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/DimensionUtil.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/DimensionUtil.kt new file mode 100644 index 0000000..b4e2cf5 --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/DimensionUtil.kt @@ -0,0 +1,61 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/Label.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/Label.kt new file mode 100644 index 0000000..f0a19c0 --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/Label.kt @@ -0,0 +1,105 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + ) +} diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/SlotsEnum.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/SlotsEnum.kt new file mode 100644 index 0000000..e1dc482 --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/SlotsEnum.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 } diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/model/CompareData.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/model/CompareData.kt new file mode 100644 index 0000000..f12a06c --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/model/CompareData.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/screenLogic/CompareComponent.kt b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/screenLogic/CompareComponent.kt new file mode 100644 index 0000000..fb2dc25 --- /dev/null +++ b/feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/screenLogic/CompareComponent.kt @@ -0,0 +1,355 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 { + _isImageLoading.value = true + 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 + ) + } + _isImageLoading.value = false + } + } + + 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 + } + +} \ No newline at end of file diff --git a/feature/crop/.gitignore b/feature/crop/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/crop/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/crop/build.gradle.kts b/feature/crop/build.gradle.kts new file mode 100644 index 0000000..439f815 --- /dev/null +++ b/feature/crop/build.gradle.kts @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + implementation(projects.lib.cropper) + implementation(libs.toolbox.advancedCrop) +} \ No newline at end of file diff --git a/feature/crop/src/main/AndroidManifest.xml b/feature/crop/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/crop/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/CropContent.kt b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/CropContent.kt new file mode 100644 index 0000000..b409d94 --- /dev/null +++ b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/CropContent.kt @@ -0,0 +1,416 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.AspectRatio +import com.t8rin.cropper.model.OutlineType +import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormatGroup +import com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Tune +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.SaveExifWidget +import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.CropOverlayDraggableSelector +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 -> + component.currentUri?.let { imageUri -> + Cropper( + bitmap = bitmap, + imageUri = imageUri, + imageSize = component.imageSize, + crop = crop, + onImageCropStarted = component::imageCropStarted, + onImageCropFinished = { uri -> + component.imageCropFinished() + if (uri != null) { + component.updateImageUri(uri) + } + 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 || + component.cropProperties.aspectRatio == AspectRatio.Original, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + CropOverlayDraggableSelector( + modifier = Modifier.fillMaxWidth(), + shape = ShapeDefaults.extraLarge + ) + } + 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() + } + ) + } + } + SaveExifWidget( + modifier = Modifier.fillMaxWidth(), + checked = component.saveExif, + imageFormat = component.imageFormat, + onCheckedChange = component::setSaveExif + ) + 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() + }, + drawBothStrokes = true + ) + 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 + ) +} \ No newline at end of file diff --git a/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CoercePointsToImageBoundsToggle.kt b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CoercePointsToImageBoundsToggle.kt new file mode 100644 index 0000000..3f11190 --- /dev/null +++ b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CoercePointsToImageBoundsToggle.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Rectangle +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 + ) +} \ No newline at end of file diff --git a/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CropMaskSelection.kt b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CropMaskSelection.kt new file mode 100644 index 0000000..2955df3 --- /dev/null +++ b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CropMaskSelection.kt @@ -0,0 +1,259 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.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.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.resources.utils.animation.animateColorAsState +import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant +import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme +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 com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction +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 + val interactionSource = remember { MutableInteractionSource() } + + val cardShape = shapeByInteraction( + shape = ShapeDefaults.default, + pressedShape = ShapeDefaults.pressed, + interactionSource = interactionSource + ) + + 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 = takeColorFromScheme { + if (selected) onPrimaryContainer.copy(0.7f) + else outlineVariant() + }, + shape = cardShape + ) + .hapticsClickable( + interactionSource = interactionSource, + indication = LocalIndication.current + ) { + if (item.cropOutline is ImageMaskOutline) { + maskLauncher.pickImage() + } else { + onCropMaskChange(item) + } + cornerRadius = 20 + } + .padding(16.dp), + 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 + ) + } + } + } +} \ No newline at end of file diff --git a/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CropRotationSelector.kt b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CropRotationSelector.kt new file mode 100644 index 0000000..47ec03b --- /dev/null +++ b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CropRotationSelector.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ScreenRotationAlt +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem +import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults + +@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, + shape = ShapeDefaults.large + ) +} \ No newline at end of file diff --git a/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CropType.kt b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CropType.kt new file mode 100644 index 0000000..9b00e6f --- /dev/null +++ b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CropType.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/Cropper.kt b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/Cropper.kt new file mode 100644 index 0000000..f32b132 --- /dev/null +++ b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/Cropper.kt @@ -0,0 +1,256 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Box +import androidx.compose.foundation.layout.PaddingValues +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.plus +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.union +import androidx.compose.material3.MaterialTheme +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.rememberUpdatedState +import androidx.compose.runtime.setValue +import androidx.compose.runtime.withFrameNanos +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import com.t8rin.crop.advanced.compose.AdvancedCropper +import com.t8rin.crop.advanced.compose.HorizontalWheelSliderConfig +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.Icons +import com.t8rin.imagetoolbox.core.resources.icons.Flip +import com.t8rin.imagetoolbox.core.resources.icons.Rotate90Ccw +import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState +import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker +import com.t8rin.imagetoolbox.core.ui.widget.other.ZoomBadge +import com.t8rin.opencv_tools.free_corners_crop.compose.FreeCornersCropper +import kotlinx.coroutines.launch +import kotlin.math.roundToInt + +@Composable +fun Cropper( + modifier: Modifier = Modifier, + bitmap: Bitmap, + imageUri: Uri, + imageSize: IntSize, + crop: Boolean, + onImageCropStarted: () -> Unit, + onImageCropFinished: (Uri?) -> Unit, + cropType: CropType, + coercePointsToImageArea: Boolean, + rotationState: MutableFloatState, + cropProperties: CropProperties, + addVerticalInsets: Boolean +) { + LaunchedEffect(crop) { + if (crop) onImageCropStarted() + } + + val settingsState = LocalSettingsState.current + + AnimatedContent( + targetState = cropType, + transitionSpec = { fadeIn() togetherWith fadeOut() } + ) { type -> + when (type) { + CropType.Default -> { + Box { + var zoomLevel by remember { + mutableFloatStateOf(1f) + } + + val scope = rememberCoroutineScope() + val currentCrop by rememberUpdatedState(crop) + val currentOnImageCropFinished by rememberUpdatedState(onImageCropFinished) + val onLoadingStateChange: (Boolean) -> Unit = remember { + { isLoading -> + if (!isLoading) { + scope.launch { + withFrameNanos { } + if (currentCrop) { + currentOnImageCropFinished(null) + } + } + } + } + } + + AdvancedCropper( + imageModel = imageUri, + aspectRatio = if (cropProperties.aspectRatio != AspectRatio.Original) { + cropProperties.aspectRatio.value + } else null, + modifier = Modifier.transparencyChecker(), + containerModifier = Modifier.fillMaxSize(), + croppingTrigger = crop, + onCropped = onImageCropFinished, + sliderConfig = remember { + HorizontalWheelSliderConfig( + mirrorIcon = Icons.Outlined.Flip, + rotate90Icon = Icons.Outlined.Rotate90Ccw + ) + }, + isOverlayDraggable = settingsState.cropOverlayDraggable, + rotationAngleState = rotationState, + onLoadingStateChange = onLoadingStateChange, + onZoomChange = { newZoom -> + zoomLevel = newZoom + }, + contentPadding = WindowInsets.systemBars.union(WindowInsets.displayCutout) + .let { + if (addVerticalInsets) it.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom) + else it.only(WindowInsetsSides.Horizontal) + }.asPaddingValues() + PaddingValues(top = 16.dp), + gridColor = MaterialTheme.colorScheme.primaryFixed.copy(0.5f), + handlesColor = MaterialTheme.colorScheme.primaryFixed + ) + ZoomBadge( + zoomLevel = zoomLevel, + modifier = Modifier.align(Alignment.TopStart), + ) + } + } + + 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) { + (cropProperties.handleSize * 3).roundToInt().let { + IntSize(it, it) + } + } + ImageCropper( + backgroundModifier = Modifier.transparencyChecker(), + imageBitmap = bmp, + sourceImageUri = imageUri, + sourceImageSize = imageSize, + contentDescription = null, + cropProperties = cropProperties.copy( + fixedAspectRatio = fixedAspectRatio, + minDimension = minDimension, + maxZoom = 20f + ), + onCropStart = onImageCropStarted, + crop = crop, + cropStyle = CropDefaults.style( + overlayColor = MaterialTheme.colorScheme.primaryFixed.copy(0.5f), + handleColor = MaterialTheme.colorScheme.primaryFixed + ), + onZoomChange = { newZoom -> + zoomLevel = newZoom + }, + onCropSuccess = onImageCropFinished, + isOverlayDraggable = settingsState.cropOverlayDraggable + ) + ZoomBadge( + zoomLevel = zoomLevel, + modifier = Modifier.align(Alignment.TopStart), + ) + } + } + } + + CropType.FreeCorners -> { + AnimatedContent( + targetState = bitmap, + transitionSpec = { fadeIn() togetherWith fadeOut() }, + modifier = modifier.fillMaxSize() + ) { bitmap -> + Box { + var zoomLevel by remember { + mutableFloatStateOf(1f) + } + FreeCornersCropper( + bitmap = bitmap, + sourceImageUri = imageUri, + sourceImageSize = imageSize, + croppingTrigger = crop, + onCropped = onImageCropFinished, + 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 = 32.dp, + right = 16.dp, + bottom = 32.dp + ) + ) + .asPaddingValues(), + onZoomChange = { newZoom -> + zoomLevel = newZoom + }, + showMagnifier = settingsState.magnifierEnabled, + isOverlayDraggable = settingsState.cropOverlayDraggable, + gridColor = MaterialTheme.colorScheme.primaryFixed.copy(0.5f), + handlesColor = MaterialTheme.colorScheme.primaryFixed + ) + ZoomBadge( + zoomLevel = zoomLevel, + modifier = Modifier.align(Alignment.TopStart), + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/DefaultOutlineProperties.kt b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/DefaultOutlineProperties.kt new file mode 100644 index 0000000..1c2a7e1 --- /dev/null +++ b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/DefaultOutlineProperties.kt @@ -0,0 +1,293 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + ) + ) + ) +} \ No newline at end of file diff --git a/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/FreeCornersCropToggle.kt b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/FreeCornersCropToggle.kt new file mode 100644 index 0000000..8508f07 --- /dev/null +++ b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/FreeCornersCropToggle.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() + } + ) +} \ No newline at end of file diff --git a/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/screenLogic/CropComponent.kt b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/screenLogic/CropComponent.kt new file mode 100644 index 0000000..f69519a --- /dev/null +++ b/feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/screenLogic/CropComponent.kt @@ -0,0 +1,358 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.compose.ui.unit.IntSize +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 + ) + ) + val cropProperties by _cropProperties + + private val _cropType: MutableState = mutableStateOf(CropType.Default) + val cropType: CropType by _cropType + + private val _uri = mutableStateOf(Uri.EMPTY) + private val _currentUri = mutableStateOf(Uri.EMPTY) + val currentUri: Uri? + get() = _currentUri.value.takeIf { it != Uri.EMPTY } + + private val _originalImageSize = mutableStateOf(IntSize.Zero) + + private val _imageSize = mutableStateOf(IntSize.Zero) + val imageSize: IntSize by _imageSize + + private var internalBitmap = mutableStateOf(null) + + private val _bitmap: MutableState = mutableStateOf(null) + val bitmap: Bitmap? by _bitmap + + val isBitmapChanged get() = _currentUri.value != _uri.value + + private val _imageFormat = 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 isBitmapLoading = false + private var isCropperLoading = false + + private fun updateImageLoadingState() { + _isImageLoading.value = isBitmapLoading || isCropperLoading + } + + fun updateBitmap( + bitmap: Bitmap?, + newBitmap: Boolean = false + ) { + componentScope.launch { + isBitmapLoading = true + updateImageLoadingState() + try { + val bmp = imageScaler.scaleUntilCanShow(bitmap) + if (newBitmap) { + internalBitmap.value = bmp + } + _bitmap.value = bmp + } finally { + isBitmapLoading = false + updateImageLoadingState() + } + } + } + + fun setImageFormat(imageFormat: ImageFormat) { + _imageFormat.value = imageFormat + } + + fun setSaveExif(saveExif: Boolean) { + _saveExif.value = saveExif + } + + private var savingJob: Job? by smartJob { + _isSaving.update { false } + } + + fun saveBitmap( + oneTimeSaveLocationUri: String? + ) { + savingJob = trackProgress { + _isSaving.value = true + currentUri?.let { uri -> + imageGetter.getImage( + uri = uri.toString(), + originalSize = true + )?.image?.let { localBitmap -> + val imageInfo = ImageInfo( + originalUri = _uri.value.toString(), + imageFormat = imageFormat, + width = localBitmap.width, + height = localBitmap.height + ) + val byteArray = imageCompressor.compressAndTransform( + image = localBitmap, + imageInfo = imageInfo + ) + + parseSaveResult( + fileController.save( + saveTarget = ImageSaveTarget( + imageInfo = imageInfo, + originalUri = _uri.value.toString(), + sequenceNumber = null, + data = byteArray + ), + keepOriginalMetadata = saveExif, + 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() { + _currentUri.value = _uri.value + _imageSize.value = _originalImageSize.value + _bitmap.value = internalBitmap.value + } + + fun imageCropStarted() { + isCropperLoading = true + updateImageLoadingState() + } + + fun imageCropFinished() { + isCropperLoading = false + updateImageLoadingState() + } + + fun setUri( + uri: Uri + ) { + _uri.value = uri + _currentUri.value = uri + imageGetter.getImageAsync( + uri = uri.toString(), + originalSize = true, + onGetImage = { + val size = IntSize(it.imageInfo.width, it.imageInfo.height) + _originalImageSize.value = size + _imageSize.value = size + updateBitmap(it.image, true) + setImageFormat(it.imageInfo.imageFormat) + }, + onFailure = AppToastHost::showFailureToast + ) + } + + fun updateImageUri(uri: Uri) { + _currentUri.value = uri + imageGetter.getImageAsync( + uri = uri.toString(), + originalSize = true, + onGetImage = { + _imageSize.value = IntSize(it.imageInfo.width, it.imageInfo.height) + updateBitmap(it.image) + }, + onFailure = AppToastHost::showFailureToast + ) + } + + fun shareBitmap() { + savingJob = trackProgress { + _isSaving.value = true + currentUri?.let { uri -> + imageGetter.getImage( + uri = uri.toString(), + originalSize = true + )?.image?.let { image -> + shareProvider.shareImage( + imageInfo = ImageInfo( + originalUri = _uri.value.toString(), + imageFormat = imageFormat, + width = image.width, + height = image.height + ), + image = image, + 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 + currentUri?.let { uri -> + imageGetter.getImage( + uri = uri.toString(), + originalSize = true + )?.image?.let { image -> + shareProvider.cacheImage( + image = image, + imageInfo = ImageInfo( + originalUri = _uri.value.toString(), + imageFormat = imageFormat, + width = image.width, + height = image.height + ) + )?.let { cachedUri -> + onComplete(cachedUri.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 + } +} diff --git a/feature/crop/src/main/res/values/dimens.xml b/feature/crop/src/main/res/values/dimens.xml new file mode 100644 index 0000000..033c8e2 --- /dev/null +++ b/feature/crop/src/main/res/values/dimens.xml @@ -0,0 +1,20 @@ + + + + 50dp + \ No newline at end of file diff --git a/feature/delete-exif/.gitignore b/feature/delete-exif/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/delete-exif/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/delete-exif/build.gradle.kts b/feature/delete-exif/build.gradle.kts new file mode 100644 index 0000000..8e4e58f --- /dev/null +++ b/feature/delete-exif/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/delete-exif/src/main/AndroidManifest.xml b/feature/delete-exif/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/delete-exif/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/delete-exif/src/main/java/com/t8rin/imagetoolbox/feature/delete_exif/presentation/DeleteExifContent.kt b/feature/delete-exif/src/main/java/com/t8rin/imagetoolbox/feature/delete_exif/presentation/DeleteExifContent.kt new file mode 100644 index 0000000..3801a79 --- /dev/null +++ b/feature/delete-exif/src/main/java/com/t8rin/imagetoolbox/feature/delete_exif/presentation/DeleteExifContent.kt @@ -0,0 +1,284 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/delete-exif/src/main/java/com/t8rin/imagetoolbox/feature/delete_exif/presentation/screenLogic/DeleteExifComponent.kt b/feature/delete-exif/src/main/java/com/t8rin/imagetoolbox/feature/delete_exif/presentation/screenLogic/DeleteExifComponent.kt new file mode 100644 index 0000000..33f922d --- /dev/null +++ b/feature/delete-exif/src/main/java/com/t8rin/imagetoolbox/feature/delete_exif/presentation/screenLogic/DeleteExifComponent.kt @@ -0,0 +1,318 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} \ No newline at end of file diff --git a/feature/document-scanner/.gitignore b/feature/document-scanner/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/document-scanner/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/document-scanner/build.gradle.kts b/feature/document-scanner/build.gradle.kts new file mode 100644 index 0000000..ecb705b --- /dev/null +++ b/feature/document-scanner/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/document-scanner/src/main/AndroidManifest.xml b/feature/document-scanner/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/document-scanner/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/document-scanner/src/main/java/com/t8rin/imagetoolbox/feature/document_scanner/presentation/DocumentScannerContent.kt b/feature/document-scanner/src/main/java/com/t8rin/imagetoolbox/feature/document_scanner/presentation/DocumentScannerContent.kt new file mode 100644 index 0000000..631a54b --- /dev/null +++ b/feature/document-scanner/src/main/java/com/t8rin/imagetoolbox/feature/document_scanner/presentation/DocumentScannerContent.kt @@ -0,0 +1,338 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.domain.utils.Flavor +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt +import com.t8rin.imagetoolbox.core.resources.icons.DocumentScanner +import com.t8rin.imagetoolbox.core.resources.icons.Pdf +import com.t8rin.imagetoolbox.core.resources.icons.Share +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.rememberDocumentScanner +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.ScanResult +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.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.enhanced.EnhancedFloatingActionButton +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.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 savePdfLauncher = rememberFileCreator( + mimeType = MimeType.Pdf, + onSuccess = component::savePdfTo + ) + + val documentScanner = rememberDocumentScanner { + if (Flavor.isFoss()) { + component.addScanResult(it) + } else { + 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 + ) + } + + val addImagesPicker = rememberImagePicker { uris: List -> + component.addScanResult( + ScanResult( + imageUris = uris + ) + ) + } + + var selectedUriForPreview by remember { + mutableStateOf(null) + } + + val previewBlock = @Composable { + UrisPreview( + modifier = Modifier.urisPreview(isPortrait = isPortrait), + uris = component.uris, + isPortrait = true, + onRemoveUri = component::removeImageUri, + onAddUris = additionalDocumentScanner::scan, + isAddUrisVisible = !Flavor.isFoss(), + onClickUri = { uri -> + selectedUriForPreview = uri + } + ) + } + + AdaptiveLayoutScreen( + shouldDisableBackHandler = false, + title = { + Text( + text = stringResource(R.string.document_scanner), + modifier = Modifier.marquee() + ) + }, + topAppBarPersistentActions = { + TopAppBarEmoji() + }, + onGoBack = { + showExitDialog = true + }, + actions = {}, + imagePreview = { + if (!isPortrait) previewBlock() + }, + showImagePreviewAsStickyHeader = false, + addHorizontalCutoutPaddingIfNoPreview = false, + noDataControls = { + FileNotPickedWidget( + onPickFile = documentScanner::scan, + text = stringResource(R.string.click_to_start_scanning) + ) + }, + controls = { + if (isPortrait) { + Spacer(modifier = Modifier.height(24.dp)) + previewBlock() + 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) + } + var showOneTimeImagePickingDialog 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 + ) + }, + showMiddleFabInRow = true, + middleFab = if (Flavor.isFoss()) { + { + EnhancedFloatingActionButton( + onClick = addImagesPicker::pickImage, + onLongClick = { + showOneTimeImagePickingDialog = true + }, + containerColor = takeColorFromScheme { + if (component.uris.isEmpty()) tertiaryContainer + else secondaryContainer + } + ) { + Icon( + imageVector = Icons.Rounded.AddPhotoAlt, + contentDescription = null + ) + } + } + } else null + ) + OneTimeSaveLocationSelectionDialog( + visible = showFolderSelectionDialog, + onDismiss = { showFolderSelectionDialog = false }, + onSaveRequest = saveBitmaps, + formatForFilenameSelection = component.getFormatForFilenameSelection() + ) + OneTimeImagePickingDialog( + onDismiss = { showOneTimeImagePickingDialog = false }, + picker = Picker.Multiple, + imagePicker = addImagesPicker, + visible = showOneTimeImagePickingDialog + ) + }, + canShowScreenData = component.uris.isNotEmpty() + ) + + ImagePager( + visible = selectedUriForPreview != null, + selectedUri = selectedUriForPreview, + uris = component.uris, + onNavigate = { + selectedUriForPreview = null + component.onNavigate(it) + }, + onUriSelected = { selectedUriForPreview = it }, + onShare = component::shareUri, + onDismiss = { selectedUriForPreview = null } + ) + + ExitWithoutSavingDialog( + onExit = component.onGoBack, + onDismiss = { showExitDialog = false }, + visible = showExitDialog + ) + + LoadingDialog( + visible = component.isSaving, + done = component.done, + left = component.left, + onCancelLoading = component::cancelSaving + ) + +} \ No newline at end of file diff --git a/feature/document-scanner/src/main/java/com/t8rin/imagetoolbox/feature/document_scanner/presentation/screenLogic/DocumentScannerComponent.kt b/feature/document-scanner/src/main/java/com/t8rin/imagetoolbox/feature/document_scanner/presentation/screenLogic/DocumentScannerComponent.kt new file mode 100644 index 0000000..649d8c8 --- /dev/null +++ b/feature/document-scanner/src/main/java/com/t8rin/imagetoolbox/feature/document_scanner/presentation/screenLogic/DocumentScannerComponent.kt @@ -0,0 +1,289 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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) { + componentScope.launch { + shareProvider.shareUri( + uri = uri.toString(), + onComplete = {} + ) + } + } + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): DocumentScannerComponent + } + +} \ No newline at end of file diff --git a/feature/draw/.gitignore b/feature/draw/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/draw/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/draw/build.gradle.kts b/feature/draw/build.gradle.kts new file mode 100644 index 0000000..57d62c5 --- /dev/null +++ b/feature/draw/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/draw/src/main/AndroidManifest.xml b/feature/draw/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/draw/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/data/AndroidImageDrawApplier.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/data/AndroidImageDrawApplier.kt new file mode 100644 index 0000000..086ca45 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/data/AndroidImageDrawApplier.kt @@ -0,0 +1,574 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.coerceAtLeast(1), + height = drawBehavior.height.coerceAtLeast(1) + ) + } + + 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() + } + +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/data/utils/DrawRepeatedPath.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/data/utils/DrawRepeatedPath.kt new file mode 100644 index 0000000..e0a32e2 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/data/utils/DrawRepeatedPath.kt @@ -0,0 +1,97 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/di/DrawModule.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/di/DrawModule.kt new file mode 100644 index 0000000..c2fb93d --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/di/DrawModule.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawBehavior.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawBehavior.kt new file mode 100644 index 0000000..69fe029 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawBehavior.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawLineStyle.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawLineStyle.kt new file mode 100644 index 0000000..f35352d --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawLineStyle.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawMode.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawMode.kt new file mode 100644 index 0000000..1a1ba74 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawMode.kt @@ -0,0 +1,84 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + ) + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawOnBackgroundParams.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawOnBackgroundParams.kt new file mode 100644 index 0000000..a981cec --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawOnBackgroundParams.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawPathMode.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawPathMode.kt new file mode 100644 index 0000000..aeb2cba --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawPathMode.kt @@ -0,0 +1,212 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() +) \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/ImageDrawApplier.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/ImageDrawApplier.kt new file mode 100644 index 0000000..b6bcb0e --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/ImageDrawApplier.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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? + +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/PathPaint.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/PathPaint.kt new file mode 100644 index 0000000..17b4db3 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/PathPaint.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/Warp.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/Warp.kt new file mode 100644 index 0000000..94317bb --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/Warp.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/DrawContent.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/DrawContent.kt new file mode 100644 index 0000000..d570f31 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/DrawContent.kt @@ -0,0 +1,383 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.material3.Icon +import androidx.compose.material3.SheetValue +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.Modifier +import androidx.compose.ui.graphics.ImageBitmap +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.Tune +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.imageBitmap) + + 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) } + + var showLineAngle 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 imageBitmap = + component.imageBitmap ?: (component.drawBehavior as? DrawBehavior.Background)?.run { + remember(width, height) { ImageBitmap(width, height) } + } ?: remember { + ImageBitmap( + screenSize.widthPx, + screenSize.heightPx + ) + } + + 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 = imageBitmap, + 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, + showLineAngle = showLineAngle, + spotHealCache = component.spotHealCache, + onCacheSpotHealPathResult = component::cacheSpotHealPathResult, + onRemovePath = component::removePath + ) + } + }, + 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 }, + showLineAngle = showLineAngle, + onShowLineAngleChange = { showLineAngle = 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, + drawBothStrokes = 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() +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/BitmapDrawer.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/BitmapDrawer.kt new file mode 100644 index 0000000..7605ef7 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/BitmapDrawer.kt @@ -0,0 +1,720 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.DisposableEffect +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.produceState +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.element.LineAngleIndicator +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.Dispatchers +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +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() }, + showLineAngle: Boolean = false, + spotHealCache: Map = emptyMap(), + onCacheSpotHealPathResult: (Int, Bitmap) -> Unit = { _, _ -> }, + onRemovePath: (UiPathPaint) -> Unit = {}, +) { + 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 produceState( + initialValue = null, + imageBitmap, + imageWidth, + imageHeight, + backgroundColor + ) { + value = null + value = withContext(Dispatchers.Default) { + 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 needsDrawPathBitmap = !isEraserOn && ( + drawMode is DrawMode.PathEffect + || drawMode is DrawMode.SpotHeal + || drawMode is DrawMode.Warp + ) + + val drawPathBitmap: ImageBitmap? by remember( + imageWidth, + imageHeight, + invalidations, + needsDrawPathBitmap + ) { + derivedStateOf { + if (needsDrawPathBitmap) { + createBitmap(imageWidth, imageHeight).asImageBitmap() + } else null + } + } + + LaunchedEffect( + paths, + drawMode, + backgroundColor, + drawPathMode, + imageWidth, + imageHeight + ) { + invalidations++ + } + + val outputImage by remember(invalidations) { + derivedStateOf { + drawImageBitmap?.overlay(drawBitmap) ?: imageBitmap + } + } + + val canvas: Canvas = remember(drawBitmap, imageHeight, imageWidth) { + Canvas(drawBitmap) + } + + val drawPathCanvas = remember(drawPathBitmap, imageWidth, imageHeight) { + drawPathBitmap?.let(::Canvas) + } + + 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 ?: imageBitmap, + drawBitmap = drawBitmap, + onClearDrawPath = { + drawPath = Path() + warpClearTrigger++ + warpRuntimeStrokes = emptyList() + }, + onClearWarpDrawPath = { token -> + if (token == pendingWarpCommitToken) { + drawPath = Path() + warpClearTrigger++ + warpRuntimeStrokes = emptyList() + pendingWarpCommitToken = -1L + } + }, + onRequestFiltering = onRequestFiltering, + spotHealCache = spotHealCache, + onCacheSpotHealPathResult = onCacheSpotHealPathResult, + onCancel = onRemovePath, + canvasSize = canvasSize, + scope = scope + ) + } + + 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) { + drawPathCanvas?.nativeCanvas?.let { + with(it) { + 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 && drawPathCanvas != null) { + 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(null) + } + + LaunchedEffect(warpClearTrigger, drawMode) { + if (drawMode is DrawMode.Warp && !isEraserOn) { + warpEngine?.release() + warpEngine = WarpEngine( + src = outputImage.asAndroidBitmap() + ) + } else { + warpEngine?.release() + warpEngine = null + } + } + + DisposableEffect(Unit) { + onDispose { + warpEngine?.release() + warpEngine = null + } + } + + LaunchedEffect(warpEngine) { + snapshotFlow { warpRuntimeStrokes.lastOrNull() } + .filterNotNull() + .collect { + val engine = warpEngine ?: return@collect + val warpMode = drawMode as? DrawMode.Warp ?: return@collect + + engine.applyStroke( + fromX = it.fromX, + fromY = it.fromY, + toX = it.toX, + toY = it.toY, + brush = WarpBrush( + radius = strokeWidth.toPx(canvasSize), + strength = warpMode.strength, + hardness = warpMode.hardness + ), + mode = WarpMode.valueOf(warpMode.warpMode.name) + ) + invalidations++ + } + } + + val warpedImage by remember(invalidations, warpEngine) { + derivedStateOf { + warpEngine?.takeIf { warpRuntimeStrokes.isNotEmpty() }?.let { engine -> + engine.render().asImageBitmap().also { + it.prepareToDraw() + } + } ?: drawPathBitmap?.let(outputImage::overlay) ?: outputImage + } + } + + val previewBitmap by remember(invalidations) { + derivedStateOf { + if (drawMode is DrawMode.Warp) { + warpedImage + } else { + drawPathBitmap?.let(outputImage::overlay) ?: outputImage + } + } + } + + 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 && drawImageBitmap != null, + helperGridParams = helperGridParams, + drawBitmapBorder = settingsState.drawBitmapBorder + ) + + if (showLineAngle && drawPathMode.canShowLineAngle() && drawDownPosition.isSpecified && currentDrawPosition.isSpecified) { + LineAngleIndicator( + drawDownPosition = drawDownPosition, + currentDrawPosition = currentDrawPosition, + imageWidth = imageWidth, + imageHeight = imageHeight, + isMagnifierEnabled = magnifierEnabled + ) + } + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/BrushSoftnessSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/BrushSoftnessSelector.kt new file mode 100644 index 0000000..de651e2 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/BrushSoftnessSelector.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawColorSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawColorSelector.kt new file mode 100644 index 0000000..dfe5c41 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawColorSelector.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawLineAngle.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawLineAngle.kt new file mode 100644 index 0000000..c1be3ea --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawLineAngle.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Line +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 + +@Composable +fun DrawLineAngleSelector( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + shape: androidx.compose.ui.graphics.Shape = ShapeDefaults.extraLarge, + startIcon: ImageVector? = Icons.Rounded.Line +) { + PreferenceRowSwitch( + modifier = modifier, + shape = shape, + title = stringResource(R.string.show_line_angle), + subtitle = stringResource(R.string.show_line_angle_sub), + checked = checked, + onClick = onCheckedChange, + startIcon = startIcon + ) +} + +fun DrawPathMode.canShowLineAngle(): Boolean { + return this is DrawPathMode.Line || + this is DrawPathMode.LinePointingArrow || + this is DrawPathMode.DoubleLinePointingArrow +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawLineStyleSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawLineStyleSelector.kt new file mode 100644 index 0000000..42c4a56 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawLineStyleSelector.kt @@ -0,0 +1,461 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +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.resources.utils.animation.animateColorAsState +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 +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawModeSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawModeSelector.kt new file mode 100644 index 0000000..21f161d --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawModeSelector.kt @@ -0,0 +1,261 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AutoFixHigh +import com.t8rin.imagetoolbox.core.resources.icons.BlurCircular +import com.t8rin.imagetoolbox.core.resources.icons.Cube +import com.t8rin.imagetoolbox.core.resources.icons.Healing +import com.t8rin.imagetoolbox.core.resources.icons.Highlighter +import com.t8rin.imagetoolbox.core.resources.icons.Image +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.resources.icons.TextFormat +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.Outlined.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 +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawPathModeSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawPathModeSelector.kt new file mode 100644 index 0000000..c2ccb08 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawPathModeSelector.kt @@ -0,0 +1,174 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/LineWidthSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/LineWidthSelector.kt new file mode 100644 index 0000000..952b505 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/LineWidthSelector.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.LineWeight +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 + ) +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/OpenColorPickerCard.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/OpenColorPickerCard.kt new file mode 100644 index 0000000..b9f1856 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/OpenColorPickerCard.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/PixelSizeSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/PixelSizeSelector.kt new file mode 100644 index 0000000..87e8c0b --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/PixelSizeSelector.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/UiPathPaint.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/UiPathPaint.kt new file mode 100644 index 0000000..9f1b75d --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/UiPathPaint.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/UiPathPaintCanvasAction.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/UiPathPaintCanvasAction.kt new file mode 100644 index 0000000..607b9e5 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/UiPathPaintCanvasAction.kt @@ -0,0 +1,395 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.IntSize +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.CoroutineScope +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?, + spotHealCache: Map, + onCacheSpotHealPathResult: (Int, Bitmap) -> Unit, + onCancel: (UiPathPaint) -> Unit, + scope: CoroutineScope +) = 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 cacheKey = uiPathPaint.hashCode() + + 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(cacheKey, backgroundColor) { + mutableStateOf(spotHealCache[cacheKey]?.asImageBitmap()) + } + LaunchedEffect(shaderSource, invalidations) { + withContext(Dispatchers.Default) { + if (shaderSource == null) { + 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 { + onCacheSpotHealPathResult(cacheKey, it.asAndroidBitmap()) + it.prepareToDraw() + onInvalidate() + } + } + isLoading = false + job.cancel() + progress = 0f + } + } + } + if (shaderSource != null) { + LaunchedEffect(shaderSource) { + onClearDrawPath() + onInvalidate() + } + val imagePaint = remember { Paint() } + drawImageRect( + image = shaderSource!!, + dstSize = IntSize(canvasSize.width, canvasSize.height), + paint = imagePaint + ) + } + + LoadingDialog( + visible = isLoading, + onCancelLoading = { + scope.launch { + isLoading = false + delay(400) + onClearDrawPath() + onCancel(uiPathPaint) + onInvalidate() + } + }, + canCancel = true, + 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) + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/controls/DrawContentControls.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/controls/DrawContentControls.kt new file mode 100644 index 0000000..60f0d61 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/controls/DrawContentControls.kt @@ -0,0 +1,310 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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.DrawLineAngleSelector +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.canShowLineAngle +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, + showLineAngle: Boolean, + onShowLineAngleChange: (Boolean) -> 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 = drawPathMode.canShowLineAngle() && drawMode !is DrawMode.Warp, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically(), + modifier = Modifier.fillMaxWidth() + ) { + DrawLineAngleSelector( + modifier = Modifier.fillMaxWidth(), + checked = showLineAngle, + onCheckedChange = onShowLineAngleChange + ) + } + 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 + ) +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/controls/DrawContentNoDataControls.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/controls/DrawContentNoDataControls.kt new file mode 100644 index 0000000..3a3dd48 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/controls/DrawContentNoDataControls.kt @@ -0,0 +1,246 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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 > 0 } ?: screenWidth) + } + var height by remember( + showBackgroundDrawingSetup, + screenHeight, + drawOnBackgroundParams + ) { + mutableIntStateOf(drawOnBackgroundParams.height.takeIf { it > 0 } ?: 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 + } + ) +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/controls/DrawContentSecondaryControls.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/controls/DrawContentSecondaryControls.kt new file mode 100644 index 0000000..376d1e5 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/controls/DrawContentSecondaryControls.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import com.t8rin.imagetoolbox.core.resources.icons.Redo +import com.t8rin.imagetoolbox.core.resources.icons.Undo +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.Rounded.Undo, + contentDescription = "Undo" + ) + } + EnhancedIconButton( + onClick = component::redo, + enabled = component.undonePaths.isNotEmpty() + ) { + Icon( + imageVector = Icons.Rounded.Redo, + contentDescription = "Redo" + ) + } + EraseModeButton( + selected = isEraserOn, + enabled = !panEnabled, + onClick = onToggleIsEraserOn + ) +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/ArrowParamsSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/ArrowParamsSelector.kt new file mode 100644 index 0000000..03c314c --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/ArrowParamsSelector.kt @@ -0,0 +1,96 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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)) + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/CustomPathEffectParamsSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/CustomPathEffectParamsSelector.kt new file mode 100644 index 0000000..10f6060 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/CustomPathEffectParamsSelector.kt @@ -0,0 +1,158 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AutoFixHigh +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.AutoFixHigh, + 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) + ) + } + ) + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/DrawPathModeInfoSheet.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/DrawPathModeInfoSheet.kt new file mode 100644 index 0000000..ecf4c59 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/DrawPathModeInfoSheet.kt @@ -0,0 +1,102 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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)) + } + } + ) +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/FloodFillParamsSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/FloodFillParamsSelector.kt new file mode 100644 index 0000000..e3c97c7 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/FloodFillParamsSelector.kt @@ -0,0 +1,77 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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)) + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/ImageParamsSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/ImageParamsSelector.kt new file mode 100644 index 0000000..ac96f1c --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/ImageParamsSelector.kt @@ -0,0 +1,111 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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)) + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/LineAngleIndicator.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/LineAngleIndicator.kt new file mode 100644 index 0000000..ab7242a --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/LineAngleIndicator.kt @@ -0,0 +1,143 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.Row +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +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.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.RotateRight +import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults +import kotlin.math.atan2 +import kotlin.math.roundToInt + +@Composable +internal fun LineAngleIndicator( + drawDownPosition: Offset, + currentDrawPosition: Offset, + imageWidth: Int, + imageHeight: Int, + isMagnifierEnabled: Boolean, + modifier: Modifier = Modifier +) { + val density = LocalDensity.current + var size by remember { + mutableStateOf( + with(density) { + IntSize( + width = 96.dp.roundToPx(), + height = 40.dp.roundToPx() + ) + } + ) + } + val (indicatorWidth, indicatorHeight) = size + + val offset by remember( + density, + indicatorWidth, + indicatorHeight, + currentDrawPosition, + imageWidth + ) { + derivedStateOf { + with(density) { + val verticalGap = 108.dp.roundToPx() + val horizontalGap = -indicatorWidth / 2 + val verticalGapSafe = if (isMagnifierEnabled) { + verticalGap - indicatorHeight + } else { + -verticalGap + } + + IntOffset( + x = (currentDrawPosition.x.roundToInt() + horizontalGap) + .coerceIn(0, (imageWidth - indicatorWidth).coerceAtLeast(0)), + y = (currentDrawPosition.y.roundToInt() + verticalGapSafe) + .coerceIn(0, (imageHeight - indicatorHeight).coerceAtLeast(0)) + ) + } + } + } + + Surface( + modifier = modifier + .offset { offset } + .defaultMinSize(minHeight = 40.dp) + .onSizeChanged { size = it }, + shape = ShapeDefaults.extraLarge, + color = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + tonalElevation = 6.dp, + shadowElevation = 2.dp + ) { + Row( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Rounded.RotateRight, + contentDescription = null, + modifier = Modifier.padding(end = 6.dp) + ) + val degrees by remember(drawDownPosition, currentDrawPosition) { + derivedStateOf { + drawDownPosition.angleDegreesTo(currentDrawPosition) + } + } + Text( + text = "${degrees}°", + style = MaterialTheme.typography.labelLarge, + fontWeight = FontWeight.SemiBold + ) + } + } +} + +private fun Offset.angleDegreesTo(other: Offset): Int { + val degrees = Math.toDegrees( + atan2( + y = (other.y - y).toDouble(), + x = (other.x - x).toDouble() + ) + ).roundToInt() + + return (degrees % FULL_CIRCLE_DEGREES + FULL_CIRCLE_DEGREES) % FULL_CIRCLE_DEGREES +} + +private const val FULL_CIRCLE_DEGREES = 360 \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/OutlinedFillColorSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/OutlinedFillColorSelector.kt new file mode 100644 index 0000000..346d0c0 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/OutlinedFillColorSelector.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.FormatColorFill +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.Rounded.FormatColorFill, + allowAlpha = true, + modifier = modifier + .fillMaxWidth() + .container( + color = containerColor, + shape = shape + ) + ) +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/OvalParamsSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/OvalParamsSelector.kt new file mode 100644 index 0000000..0676316 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/OvalParamsSelector.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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)) + } + } +} diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/PixelationParamsSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/PixelationParamsSelector.kt new file mode 100644 index 0000000..ae6f3d9 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/PixelationParamsSelector.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/PolygonParamsSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/PolygonParamsSelector.kt new file mode 100644 index 0000000..75a56ea --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/PolygonParamsSelector.kt @@ -0,0 +1,139 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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)) + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/PrivacyBlurParamsSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/PrivacyBlurParamsSelector.kt new file mode 100644 index 0000000..d1d83b3 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/PrivacyBlurParamsSelector.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/RectParamsSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/RectParamsSelector.kt new file mode 100644 index 0000000..95cb713 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/RectParamsSelector.kt @@ -0,0 +1,116 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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)) + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/SpotHealParamsSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/SpotHealParamsSelector.kt new file mode 100644 index 0000000..b6b4acc --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/SpotHealParamsSelector.kt @@ -0,0 +1,188 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/SprayParamsSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/SprayParamsSelector.kt new file mode 100644 index 0000000..cc8eff3 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/SprayParamsSelector.kt @@ -0,0 +1,119 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Square +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.Square + ) + Spacer(modifier = Modifier.height(8.dp)) + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/StarParamsSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/StarParamsSelector.kt new file mode 100644 index 0000000..c7aadbb --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/StarParamsSelector.kt @@ -0,0 +1,160 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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)) + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/TextParamsSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/TextParamsSelector.kt new file mode 100644 index 0000000..e86e07a --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/TextParamsSelector.kt @@ -0,0 +1,154 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/TriangleParamsSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/TriangleParamsSelector.kt new file mode 100644 index 0000000..f0285ea --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/TriangleParamsSelector.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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)) + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/WarpParamsSelector.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/WarpParamsSelector.kt new file mode 100644 index 0000000..4d69cbe --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/WarpParamsSelector.kt @@ -0,0 +1,163 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 = {} + ) + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/BitmapDrawerPreview.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/BitmapDrawerPreview.kt new file mode 100644 index 0000000..44edb25 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/BitmapDrawerPreview.kt @@ -0,0 +1,79 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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, + drawBitmapBorder: Boolean, + 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) + .then( + if (drawBitmapBorder) { + Modifier.border( + width = 1.dp, + color = MaterialTheme.colorScheme.outlineVariant(), + shape = ShapeDefaults.extremeSmall + ) + } else Modifier + ), + contentDescription = null, + contentScale = ContentScale.FillBounds + ) +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawPathEffectPreview.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawPathEffectPreview.kt new file mode 100644 index 0000000..baa92ac --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawPathEffectPreview.kt @@ -0,0 +1,103 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + } + } +} diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawPathModeUtils.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawPathModeUtils.kt new file mode 100644 index 0000000..45c3139 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawPathModeUtils.kt @@ -0,0 +1,523 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.CheckBoxOutlineBlank +import com.t8rin.imagetoolbox.core.resources.icons.Circle +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.Star +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.Outlined.Circle + 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.Outlined.Star + is DrawPathMode.Star -> Icons.Rounded.Star + is DrawPathMode.FloodFill -> Icons.Rounded.FloodFill + is DrawPathMode.Spray -> Icons.Outlined.Spray +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawRepeatedPath.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawRepeatedPath.kt new file mode 100644 index 0000000..9caa240 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawRepeatedPath.kt @@ -0,0 +1,96 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawUtils.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawUtils.kt new file mode 100644 index 0000000..00a3c23 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawUtils.kt @@ -0,0 +1,521 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/FloodFill.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/FloodFill.kt new file mode 100644 index 0000000..ed75411 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/FloodFill.kt @@ -0,0 +1,199 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.utils.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() \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/MotionEvent.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/MotionEvent.kt new file mode 100644 index 0000000..3b9bf27 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/MotionEvent.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/PathHelper.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/PathHelper.kt new file mode 100644 index 0000000..4c3af5a --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/PathHelper.kt @@ -0,0 +1,537 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/PointerDraw.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/PointerDraw.kt new file mode 100644 index 0000000..a612f74 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/PointerDraw.kt @@ -0,0 +1,130 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/screenLogic/DrawComponent.kt b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/screenLogic/DrawComponent.kt new file mode 100644 index 0000000..b07cb84 --- /dev/null +++ b/feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/screenLogic/DrawComponent.kt @@ -0,0 +1,489 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.mutableStateMapOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.asImageBitmap +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 _imageBitmap: MutableState = mutableStateOf(null) + val imageBitmap: ImageBitmap? by _imageBitmap + + 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 + + private val _spotHealCache = mutableStateMapOf() + val spotHealCache: Map = _spotHealCache + + 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 fun calculateScreenOrientationBasedOnBitmap(bitmap: Bitmap): Int { + val imageRatio = bitmap.width / bitmap.height.toFloat() + 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 + val scaledBitmap = imageScaler.scaleUntilCanShow(bitmap) + val scaledImageBitmap = scaledBitmap?.let { + withContext(defaultDispatcher) { + it.copy(Bitmap.Config.ARGB_8888, true).asImageBitmap() + } + } + _imageBitmap.value = scaledImageBitmap + _isImageLoading.value = false + } + } + + fun setUri( + uri: Uri + ) { + componentScope.launch { + _paths.value = listOf() + _lastPaths.value = listOf() + _undonePaths.value = listOf() + _spotHealCache.clear() + _imageBitmap.value = null + _isImageLoading.value = true + + _uri.value = uri + imageGetter.getImageData( + uri = uri.toString(), + size = 2500, + onFailure = AppToastHost::showFailureToast + )?.let { data -> + if (drawBehavior !is DrawBehavior.Background) { + _drawBehavior.update { + DrawBehavior.Image(calculateScreenOrientationBasedOnBitmap(data.image)) + } + } + updateBitmap(data.image) + _imageFormat.update { data.imageInfo.imageFormat } + } ?: run { + _isImageLoading.value = false + } + } + } + + 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() + _spotHealCache.clear() + _imageBitmap.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 + _spotHealCache.clear() + + 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 + _spotHealCache.clear() + registerChanges() + } + + fun clearDrawing() { + if (paths.isNotEmpty()) { + _lastPaths.value = paths + _paths.value = listOf() + _undonePaths.value = listOf() + _spotHealCache.clear() + 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 removePath(pathPaint: UiPathPaint) { + _paths.update { it - pathPaint } + _spotHealCache.remove(pathPaint.hashCode()) + registerChanges() + } + + fun cacheSpotHealPathResult( + key: Int, + bitmap: Bitmap + ) { + _spotHealCache[key] = bitmap + } + + 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 + } +} diff --git a/feature/duplicate-finder/.gitignore b/feature/duplicate-finder/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/duplicate-finder/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/duplicate-finder/build.gradle.kts b/feature/duplicate-finder/build.gradle.kts new file mode 100644 index 0000000..f6a9ba0 --- /dev/null +++ b/feature/duplicate-finder/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.duplicate_finder" + +dependencies { + testImplementation(libs.junit) +} \ No newline at end of file diff --git a/feature/duplicate-finder/src/main/AndroidManifest.xml b/feature/duplicate-finder/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3ac54d4 --- /dev/null +++ b/feature/duplicate-finder/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/data/AndroidDuplicateFinder.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/data/AndroidDuplicateFinder.kt new file mode 100644 index 0000000..2987a37 --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/data/AndroidDuplicateFinder.kt @@ -0,0 +1,191 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.data + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.net.Uri +import androidx.core.graphics.createBitmap +import androidx.core.net.toUri +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.image.ImageGetter +import com.t8rin.imagetoolbox.core.domain.model.HashingType +import com.t8rin.imagetoolbox.core.utils.distinctUris +import com.t8rin.imagetoolbox.core.utils.extension +import com.t8rin.imagetoolbox.core.utils.fileSize +import com.t8rin.imagetoolbox.core.utils.filename +import com.t8rin.imagetoolbox.core.utils.imageSize +import com.t8rin.imagetoolbox.core.utils.lastModified +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.DuplicateFinder +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.helper.DHash +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.helper.DuplicateGrouping +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateAnalysisError +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateAnalysisPhase +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateAnalysisProgress +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateAnalysisResult +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateItem +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.withContext +import kotlinx.coroutines.yield +import javax.inject.Inject + +internal class AndroidDuplicateFinder @Inject constructor( + @ApplicationContext private val context: Context, + private val imageGetter: ImageGetter, + dispatchersHolder: DispatchersHolder +) : DuplicateFinder, DispatchersHolder by dispatchersHolder { + + override suspend fun findDuplicates( + uris: List, + sensitivity: Int, + onProgress: (DuplicateAnalysisProgress) -> Unit + ): DuplicateAnalysisResult = withContext(defaultDispatcher) { + val targetUris = uris + .map(String::toUri) + .distinctUris() + .map(Uri::toString) + val items = mutableListOf() + val errors = mutableListOf() + var processed = 0 + + fun report(phase: DuplicateAnalysisPhase) { + onProgress( + DuplicateAnalysisProgress( + phase = phase, + processed = processed, + total = targetUris.size + ) + ) + } + + report(DuplicateAnalysisPhase.Reading) + targetUris.forEachIndexed { sourceIndex, uri -> + ensureActive() + try { + val item = readImage( + uri = uri.toUri(), + sourceIndex = sourceIndex + ) + + require(item.sha256.isNotBlank()) { "SHA-256 must not be empty" } + + items += item.copy( + uri = uri, + sourceIndex = sourceIndex, + distance = null + ) + } catch (cancellation: CancellationException) { + throw cancellation + } catch (throwable: Throwable) { + ensureActive() + errors += DuplicateAnalysisError( + uri = uri, + message = throwable.message + ?: throwable::class.simpleName + ?: "Failed to read URI" + ) + } + processed++ + report(DuplicateAnalysisPhase.Reading) + yield() + } + + report(DuplicateAnalysisPhase.GroupingExact) + yield() + report(DuplicateAnalysisPhase.GroupingSimilar) + val groups = DuplicateGrouping.regroup( + items = items, + sensitivity = sensitivity + ) + ensureActive() + report(DuplicateAnalysisPhase.Completed) + + DuplicateAnalysisResult( + items = items, + groups = groups, + errors = errors, + requestedCount = targetUris.size + ) + } + + private suspend fun readImage( + uri: Uri, + sourceIndex: Int + ): DuplicateItem = withContext(ioDispatcher) { + val sha256 = HashingType.SHA_256.computeFromReadable(UriReadable(uri, context)) + + val thumbnail = imageGetter.getImage( + data = uri, + size = 128 + ) ?: error("Unable to decode $uri") + val originalSize = uri.imageSize() + val normalized = createBitmap(DHash.WIDTH, DHash.HEIGHT).apply { + Canvas(this).drawBitmap( + thumbnail, + null, + Rect(0, 0, DHash.WIDTH, DHash.HEIGHT), + Paint(Paint.FILTER_BITMAP_FLAG) + ) + } + val pixels = IntArray(DHash.PIXEL_COUNT) + try { + normalized.getPixels( + pixels, + 0, + DHash.WIDTH, + 0, + 0, + DHash.WIDTH, + DHash.HEIGHT + ) + } finally { + normalized.recycle() + } + + DuplicateItem( + uri = uri.toString(), + name = uri.filename(context) + ?.takeIf(String::isNotBlank) + ?: uri.lastPathSegment?.substringAfterLast('/').orEmpty() + .ifBlank { "image_${sourceIndex + 1}" }, + width = originalSize?.width ?: thumbnail.width, + height = originalSize?.height ?: thumbnail.height, + sizeBytes = uri.fileSize() ?: 0L, + format = uri.extension(context) + ?.uppercase() + ?: context.contentResolver.getType(uri) + ?.substringAfterLast('/') + ?.uppercase() + .orEmpty() + .ifBlank { "IMAGE" }, + lastModified = uri.lastModified(), + sourceIndex = sourceIndex, + sha256 = sha256, + dHash = DHash.calculateFromArgb(pixels), + isCorrectSize = originalSize != null + ) + } + +} diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/di/DuplicateFinderModule.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/di/DuplicateFinderModule.kt new file mode 100644 index 0000000..2173f84 --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/di/DuplicateFinderModule.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.di + +import com.t8rin.imagetoolbox.feature.duplicate_finder.data.AndroidDuplicateFinder +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.DuplicateFinder +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 DuplicateFinderModule { + + @Binds + @Singleton + fun duplicateFinder( + impl: AndroidDuplicateFinder + ): DuplicateFinder +} diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/DuplicateFinder.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/DuplicateFinder.kt new file mode 100644 index 0000000..afea4a9 --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/DuplicateFinder.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.domain + +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.helper.DuplicateGrouping +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateAnalysisProgress +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateAnalysisResult + + +interface DuplicateFinder { + suspend fun findDuplicates( + uris: List, + sensitivity: Int = DuplicateGrouping.DEFAULT_SENSITIVITY, + onProgress: (DuplicateAnalysisProgress) -> Unit = {} + ): DuplicateAnalysisResult +} diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/helper/DHash.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/helper/DHash.kt new file mode 100644 index 0000000..e4b82e0 --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/helper/DHash.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.domain.helper + +data object DHash { + const val WIDTH: Int = 9 + const val HEIGHT: Int = 8 + const val PIXEL_COUNT: Int = WIDTH * HEIGHT + + fun calculate(grayscalePixels: IntArray): Long { + require(grayscalePixels.size == PIXEL_COUNT) { + "dHash requires a ${WIDTH}x$HEIGHT grayscale image" + } + + var hash = 0L + for (y in 0 until HEIGHT) { + val rowOffset = y * WIDTH + for (x in 0 until WIDTH - 1) { + hash = hash shl 1 + if (grayscalePixels[rowOffset + x] > grayscalePixels[rowOffset + x + 1]) { + hash = hash or 1L + } + } + } + return hash + } + + fun calculateFromArgb(argbPixels: IntArray): Long { + require(argbPixels.size == PIXEL_COUNT) { + "dHash requires a ${WIDTH}x$HEIGHT ARGB image" + } + return calculate( + grayscalePixels = IntArray(PIXEL_COUNT) { index -> + val color = argbPixels[index] + val red = color shr 16 and 0xFF + val green = color shr 8 and 0xFF + val blue = color and 0xFF + (red * 299 + green * 587 + blue * 114) / 1000 + } + ) + } + + fun hammingDistance(first: Long, second: Long): Int = + java.lang.Long.bitCount(first xor second) +} diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/helper/DuplicateGrouping.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/helper/DuplicateGrouping.kt new file mode 100644 index 0000000..ee3a801 --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/helper/DuplicateGrouping.kt @@ -0,0 +1,138 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.domain.helper + +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateGroup +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateItem +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateType +import kotlin.math.abs +import kotlin.math.max + +data object DuplicateGrouping { + const val MIN_SENSITIVITY: Int = 0 + const val MAX_SENSITIVITY: Int = 16 + const val DEFAULT_SENSITIVITY: Int = 6 + + private const val RATIO_TOLERANCE = 0.08 + + val RecommendedItemComparator: Comparator = + compareByDescending { it.pixelCount } + .thenByDescending { it.sizeBytes } + .thenByDescending { it.lastModified ?: Long.MIN_VALUE } + .thenBy { it.sourceIndex } + + fun normalizeSensitivity(sensitivity: Int): Int = + sensitivity.coerceIn(MIN_SENSITIVITY, MAX_SENSITIVITY) + + fun regroup( + items: List, + sensitivity: Int = DEFAULT_SENSITIVITY + ): List { + val orderedItems = items.sortedWith( + compareBy { it.sourceIndex }.thenBy { it.uri } + ) + val exactGroups = orderedItems + .groupBy(DuplicateItem::sha256) + .values + .filter { it.size > 1 } + .map { createGroup(DuplicateType.Exact, it) } + val exactUris = exactGroups + .flatMapTo(mutableSetOf()) { group -> group.items.map(DuplicateItem::uri) } + val similarGroups = groupSimilar( + items = orderedItems.filterNot { it.uri in exactUris }, + sensitivity = normalizeSensitivity(sensitivity) + ) + + return exactGroups + similarGroups + } + + fun recommendedItem(items: List): DuplicateItem = + requireNotNull(items.minWithOrNull(RecommendedItemComparator)) { + "Cannot recommend an item from an empty list" + } + + fun selectAllDuplicates(groups: List): Set = buildSet { + groups.forEach { group -> + group.items.forEach { item -> + if (item.uri != group.recommendedUri) add(item.uri) + } + } + } + + fun selectAllExceptRecommended(groups: List): Set = + selectAllDuplicates(groups) + + fun clearSelection(): Set = emptySet() + + private fun groupSimilar( + items: List, + sensitivity: Int + ): List { + val clusters = mutableListOf>() + + items.forEach { candidate -> + val target = clusters.firstOrNull { cluster -> + cluster.all { existing -> + aspectRatiosAreCompatible(candidate, existing) && + DHash.hammingDistance(candidate.dHash, existing.dHash) <= sensitivity + } + } + if (target == null) { + clusters += mutableListOf(candidate) + } else { + target += candidate + } + } + + return clusters + .filter { it.size > 1 } + .map { createGroup(DuplicateType.Similar, it) } + } + + private fun createGroup( + type: DuplicateType, + items: List + ): DuplicateGroup { + val recommended = recommendedItem(items) + val itemsWithDistance = items.map { item -> + item.copy( + distance = if (type == DuplicateType.Exact) { + 0 + } else { + DHash.hammingDistance(item.dHash, recommended.dHash) + } + ) + } + return DuplicateGroup( + type = type, + items = itemsWithDistance, + recommendedUri = recommended.uri + ) + } + + private fun aspectRatiosAreCompatible( + first: DuplicateItem, + second: DuplicateItem + ): Boolean { + val firstRatio = first.aspectRatio + val secondRatio = second.aspectRatio + if (firstRatio <= 0.0 || secondRatio <= 0.0) return true + + return abs(firstRatio - secondRatio) / max(firstRatio, secondRatio) <= RATIO_TOLERANCE + } +} diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/model/DuplicateAnalysisProgress.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/model/DuplicateAnalysisProgress.kt new file mode 100644 index 0000000..487ce4a --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/model/DuplicateAnalysisProgress.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model + +enum class DuplicateAnalysisPhase { + Reading, + GroupingExact, + GroupingSimilar, + Completed +} + +data class DuplicateAnalysisProgress( + val phase: DuplicateAnalysisPhase, + val processed: Int, + val total: Int +) { + val fraction: Float + get() = if (total == 0) 0f else (processed.toFloat() / total).coerceIn(0f, 1f) +} diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/model/DuplicateAnalysisResult.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/model/DuplicateAnalysisResult.kt new file mode 100644 index 0000000..7e7ba01 --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/model/DuplicateAnalysisResult.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model + +data class DuplicateAnalysisError( + val uri: String, + val message: String +) + +data class DuplicateAnalysisResult( + val items: List, + val groups: List, + val errors: List, + val requestedCount: Int +) { + val processedCount: Int + get() = items.size + errors.size + + val reclaimableBytes: Long + get() = groups.sumOf(DuplicateGroup::reclaimableBytes) +} diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/model/DuplicateGroup.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/model/DuplicateGroup.kt new file mode 100644 index 0000000..c1acc10 --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/model/DuplicateGroup.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model + +data class DuplicateGroup( + val type: DuplicateType, + val items: List, + val recommendedUri: String +) { + init { + require(items.size > 1) { "A duplicate group must contain at least two items" } + require(items.any { it.uri == recommendedUri }) { + "The recommended item must belong to the group" + } + } + + val sortedItems by lazy { + items.sortedWith( + compareBy( + { it.uri != recommendedUri }, + { it.distance } + ) + ) + } + + val recommendedItem: DuplicateItem + get() = items.first { it.uri == recommendedUri } + + val reclaimableBytes: Long + get() = items.sumOf { item -> + item.sizeBytes.takeIf { item.uri != recommendedUri } ?: 0L + } +} diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/model/DuplicateItem.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/model/DuplicateItem.kt new file mode 100644 index 0000000..330edab --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/model/DuplicateItem.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model + +data class DuplicateItem( + val uri: String, + val name: String, + val width: Int, + val height: Int, + val sizeBytes: Long, + val format: String, + val lastModified: Long?, + val sourceIndex: Int, + val sha256: String, + val dHash: Long, + val distance: Int? = null, + val isCorrectSize: Boolean = true +) { + val pixelCount: Long + get() = width.toLong() * height.toLong() + + val aspectRatio: Double + get() = if (width > 0 && height > 0) { + maxOf(width, height).toDouble() / minOf(width, height) + } else { + 0.0 + } +} diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/model/DuplicateType.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/model/DuplicateType.kt new file mode 100644 index 0000000..69871c4 --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/model/DuplicateType.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model + +enum class DuplicateType { + Exact, + Similar +} diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/DuplicateFinderContent.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/DuplicateFinderContent.kt new file mode 100644 index 0000000..90f0826 --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/DuplicateFinderContent.kt @@ -0,0 +1,280 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.presentation + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +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.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.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.core.net.toUri +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.Refresh +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.ContextUtils.shareUris +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.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.dialogs.OneTimeImagePickingDialog +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.image.AutoFilePicker +import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget +import com.t8rin.imagetoolbox.core.ui.widget.image.ImagePager +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.duplicate_finder.domain.model.DuplicateType +import com.t8rin.imagetoolbox.feature.duplicate_finder.presentation.components.DuplicateFinderControls +import com.t8rin.imagetoolbox.feature.duplicate_finder.presentation.components.DuplicateSelectionActions +import com.t8rin.imagetoolbox.feature.duplicate_finder.presentation.screenLogic.DuplicateFinderComponent + +@Composable +fun DuplicateFinderContent( + component: DuplicateFinderComponent +) { + val imagePicker = rememberImagePicker(onSuccess = component::addUris) + val pickImages = imagePicker::pickImage + val isPortrait by isPortraitOrientationAsState() + val context = LocalContext.current + + AutoFilePicker( + onAutoPick = pickImages, + isPickedAlready = !component.initialUris.isNullOrEmpty() + ) + + var showDeleteConfirmation by rememberSaveable { mutableStateOf(false) } + var previewUri by rememberSaveable { mutableStateOf(null) } + var showExitDialog by rememberSaveable { mutableStateOf(false) } + + val refreshButton = @Composable { + if (component.sourceUris.isNotEmpty()) { + EnhancedIconButton( + onClick = component::analyze, + enabled = !component.isAnalyzing && !component.isDeleting + ) { + Icon( + imageVector = Icons.Rounded.Refresh, + contentDescription = stringResource(R.string.refresh) + ) + } + } + } + + AdaptiveLayoutScreen( + shouldDisableBackHandler = component.sourceUris.isEmpty(), + onGoBack = { + if (component.sourceUris.isEmpty()) component.onGoBack() + else showExitDialog = true + }, + title = { + Text( + text = stringResource(R.string.duplicate_finder), + modifier = Modifier.marquee() + ) + }, + actions = {}, + topAppBarPersistentActions = { + if (!isPortrait) { + refreshButton() + } + + if (component.groups.isNotEmpty()) { + DuplicateSelectionActions( + selectedCount = component.selectedUris.size, + canSelectExact = remember(component.groups, component.selectedUris) { + component.groups.any { it.type == DuplicateType.Exact } && component.groups.any { group -> + group.type == DuplicateType.Exact && group.items.any { it.uri != group.recommendedUri && it.uri !in component.selectedUris } + } + }, + onSelectExact = component::selectExactDuplicates, + onSelectAllExceptRecommended = component::selectAllExceptRecommended, + onClearSelection = component::clearSelection, + canSelectAll = remember(component.groups, component.selectedUris) { + component.groups.any { group -> + group.items.any { it.uri != group.recommendedUri && it.uri !in component.selectedUris } + } + } + ) + } else if (component.sourceUris.isEmpty()) { + TopAppBarEmoji() + } + }, + imagePreview = {}, + placeImagePreview = false, + showImagePreviewAsStickyHeader = false, + addHorizontalCutoutPaddingIfNoPreview = component.sourceUris.isNotEmpty(), + placeControlsSeparately = true, + controls = { + DuplicateFinderControls( + component = component, + isPortrait = isPortrait, + onPickImages = pickImages, + onPreview = { previewUri = it } + ) + }, + noDataControls = { + ImageNotPickedWidget( + onPickImage = pickImages, + text = stringResource(R.string.pick_images_for_duplicates) + ) + }, + buttons = { + var showOneTimeImagePickingDialog by rememberSaveable { + mutableStateOf(false) + } + + BottomButtonsBlock( + isNoData = component.sourceUris.isEmpty(), + onSecondaryButtonClick = pickImages, + onPrimaryButtonClick = { showDeleteConfirmation = true }, + primaryButtonIcon = Icons.Outlined.Delete, + isPrimaryButtonEnabled = component.selectedUris.isNotEmpty() && !component.isDeleting, + actions = { + AnimatedVisibility(component.selectedUris.isNotEmpty()) { + EnhancedChip( + selected = true, + onClick = null, + selectedColor = MaterialTheme.colorScheme.surface, + modifier = Modifier.padding(8.dp), + contentPadding = PaddingValues( + vertical = 6.dp, + horizontal = 8.dp + ) + ) { + Text( + stringResource( + R.string.duplicate_selected_summary, + component.selectedUris.size, + rememberHumanFileSize(component.selectedSizeBytes) + ) + ) + } + } + if (isPortrait) { + refreshButton() + } + }, + onSecondaryButtonLongClick = { + showOneTimeImagePickingDialog = true + } + ) + + OneTimeImagePickingDialog( + onDismiss = { showOneTimeImagePickingDialog = false }, + picker = Picker.Multiple, + imagePicker = imagePicker, + visible = showOneTimeImagePickingDialog + ) + }, + canShowScreenData = component.sourceUris.isNotEmpty() + ) + + val progress = component.progress + LoadingDialog( + visible = component.isAnalyzing, + progress = { progress?.fraction ?: 1f }, + loaderSize = 72.dp, + canCancel = true, + onCancelLoading = component::cancelAnalysis, + additionalContent = { size -> + AutoSizeText( + text = "${progress?.processed ?: 0} / ${progress?.total ?: component.sourceUris.size}", + maxLines = 1, + fontWeight = FontWeight.Medium, + textAlign = TextAlign.Center, + modifier = Modifier.width(size * 0.7f), + ) + } + ) + LoadingDialog( + visible = component.isDeleting, + canCancel = false + ) + + EnhancedAlertDialog( + visible = showDeleteConfirmation, + onDismissRequest = { showDeleteConfirmation = false }, + icon = { + Icon( + imageVector = Icons.Outlined.Delete, + contentDescription = null + ) + }, + title = { Text(stringResource(R.string.delete)) }, + text = { + Text(stringResource(R.string.confirm_duplicate_deletion_text)) + }, + confirmButton = { + EnhancedButton( + onClick = { + showDeleteConfirmation = false + component.deleteSelected() + } + ) { + Text(stringResource(R.string.delete)) + } + }, + dismissButton = { + EnhancedButton( + onClick = { showDeleteConfirmation = false }, + containerColor = MaterialTheme.colorScheme.secondaryContainer + ) { + Text(stringResource(R.string.cancel)) + } + } + ) + + ImagePager( + visible = previewUri != null, + selectedUri = previewUri?.toUri(), + uris = component.previewUris, + onNavigate = component.onNavigate, + onUriSelected = { previewUri = it?.toString() }, + onShare = { context.shareUris(listOf(it)) }, + onDismiss = { previewUri = null } + ) + + ExitWithoutSavingDialog( + onExit = component.onGoBack, + onDismiss = { showExitDialog = false }, + visible = showExitDialog + ) +} \ No newline at end of file diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/components/DuplicateFinderControls.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/components/DuplicateFinderControls.kt new file mode 100644 index 0000000..e1b23ea --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/components/DuplicateFinderControls.kt @@ -0,0 +1,246 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.presentation.components + +import androidx.compose.foundation.layout.Arrangement +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.fillMaxSize +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.safeDrawing +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.itemsIndexed +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.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FolderMatch +import com.t8rin.imagetoolbox.core.resources.icons.ImageSearch +import com.t8rin.imagetoolbox.core.resources.icons.WarningAmber +import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme +import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberHumanFileSize +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedCheckbox +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.preferences.PreferenceItem +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.helper.DuplicateGrouping +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateGroup +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateType +import com.t8rin.imagetoolbox.feature.duplicate_finder.presentation.screenLogic.DuplicateFinderComponent +import kotlin.math.roundToInt + +@Composable +internal fun DuplicateFinderControls( + component: DuplicateFinderComponent, + isPortrait: Boolean, + onPickImages: () -> Unit, + onPreview: (String) -> Unit +) { + val exactGroups = remember(component.groups) { + component.groups.filter { it.type == DuplicateType.Exact } + } + val similarGroups = remember(component.groups) { + component.groups.filter { it.type == DuplicateType.Similar } + } + + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues( + start = 20.dp, + top = 20.dp, + end = 20.dp, + bottom = if (isPortrait) 128.dp else 20.dp + ) + WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal).asPaddingValues(), + verticalArrangement = Arrangement.spacedBy(4.dp), + horizontalAlignment = Alignment.CenterHorizontally, + flingBehavior = enhancedFlingBehavior() + ) { + item(key = "sensitivity") { + EnhancedSliderItem( + value = component.sensitivity, + title = stringResource(R.string.duplicate_finder_sensitivity), + valueRange = DuplicateGrouping.MIN_SENSITIVITY.toFloat().. + DuplicateGrouping.MAX_SENSITIVITY.toFloat(), + steps = DuplicateGrouping.MAX_SENSITIVITY - DuplicateGrouping.MIN_SENSITIVITY - 1, + onValueChange = { component.updateSensitivity(it.roundToInt()) }, + modifier = Modifier.fillMaxWidth() + ) + } + + component.analysisResult?.let { result -> + item(key = "result-summary") { + ResultSummary( + exactGroupCount = exactGroups.size, + similarGroupCount = similarGroups.size, + reclaimableBytes = component.groups.sumOf(DuplicateGroup::reclaimableBytes), + modifier = Modifier.fillMaxWidth() + ) + } + + if (result.errors.isNotEmpty()) { + item(key = "errors") { + PreferenceItem( + title = stringResource( + R.string.duplicate_analysis_errors, + result.errors.size + ), + startIcon = Icons.Outlined.WarningAmber, + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer, + modifier = Modifier.fillMaxWidth() + ) + } + } + + if (component.groups.isEmpty()) { + item(key = "empty-results") { + PreferenceItem( + title = stringResource(R.string.no_duplicates_found), + subtitle = stringResource(R.string.no_duplicates_found_sub), + startIcon = Icons.Outlined.ImageSearch, + onClick = onPickImages, + modifier = Modifier.fillMaxWidth() + ) + } + } else { + duplicateGroups( + type = DuplicateType.Exact, + groups = exactGroups, + selectedUris = component.selectedUris, + onToggleGroupSelection = component::toggleGroupSelection, + onToggleSelection = component::toggleSelection, + onPreview = onPreview + ) + duplicateGroups( + type = DuplicateType.Similar, + groups = similarGroups, + selectedUris = component.selectedUris, + onToggleGroupSelection = component::toggleGroupSelection, + onToggleSelection = component::toggleSelection, + onPreview = onPreview + ) + } + } + } +} + +private fun LazyListScope.duplicateGroups( + type: DuplicateType, + groups: List, + selectedUris: Set, + onToggleGroupSelection: (DuplicateGroup) -> Unit, + onToggleSelection: (String) -> Unit, + onPreview: (String) -> Unit +) { + groups.forEachIndexed { groupIndex, group -> + item(key = "group-title-${type.name}-$groupIndex-${group.recommendedUri}") { + val selectableUris = remember(group) { + group.items + .map { it.uri } + .filterNot { it == group.recommendedUri } + } + val isGroupSelected = remember(selectableUris, selectedUris) { + selectableUris.isNotEmpty() && selectableUris.all { + it in selectedUris + } + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 4.dp, start = 8.dp, end = 8.dp, bottom = 4.dp) + .container( + shape = ShapeDefaults.circle, + resultPadding = 0.dp, + color = takeColorFromScheme { + if (isGroupSelected) secondaryContainer.copy(0.5f) else surfaceContainer + } + ) + .padding( + horizontal = 10.dp, + vertical = 6.dp + ), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + val imagesType = stringResource( + if (type == DuplicateType.Exact) R.string.exact_copies + else R.string.similar_images + ) + + Spacer(Modifier.width(8.dp)) + Icon( + imageVector = if (type == DuplicateType.Exact) { + Icons.Rounded.FolderMatch + } else { + Icons.Rounded.ImageSearch + }, + contentDescription = null + ) + Spacer(Modifier.width(8.dp)) + Text( + text = stringResource( + R.string.duplicate_group_summary, + "${group.items.size} $imagesType", + rememberHumanFileSize(group.reclaimableBytes) + ), + style = PreferenceItemDefaults.TitleFontStyle, + modifier = Modifier.weight(1f, false) + ) + EnhancedCheckbox( + checked = isGroupSelected, + onCheckedChange = { onToggleGroupSelection(group) } + ) + } + } + + itemsIndexed( + items = group.sortedItems, + key = { _, item -> "${type.name}-$groupIndex-${item.uri}" } + ) { index, item -> + DuplicateItemRow( + item = item, + isRecommended = item.uri == group.recommendedUri, + isSelected = item.uri in selectedUris, + shape = ShapeDefaults.byIndex(index, group.sortedItems.size), + onToggleSelection = { onToggleSelection(item.uri) }, + onPreview = { onPreview(item.uri) }, + modifier = Modifier.fillMaxWidth() + ) + } + } +} diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/components/DuplicateItemRow.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/components/DuplicateItemRow.kt new file mode 100644 index 0000000..4f29da3 --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/components/DuplicateItemRow.kt @@ -0,0 +1,145 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.presentation.components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +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.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.clip +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.core.net.toUri +import com.t8rin.imagetoolbox.core.domain.utils.humanFileSize +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Visibility +import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge +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.image.Picture +import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRow +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateItem + +@Composable +internal fun DuplicateItemRow( + item: DuplicateItem, + isRecommended: Boolean, + isSelected: Boolean, + shape: Shape, + onToggleSelection: () -> Unit, + onPreview: () -> Unit, + modifier: Modifier = Modifier +) { + PreferenceRow( + title = item.name, + subtitle = remember( + item.width, + item.height, + item.sizeBytes, + item.format, + item.distance, + item.isCorrectSize + ) { + listOfNotNull( + "${item.width} × ${item.height}".takeIf { item.isCorrectSize }, + humanFileSize(item.sizeBytes), + item.format, + if (!isRecommended) "Δ = ${item.distance}" else null + ).joinToString(separator = " • ") + }, + shape = shape, + containerColor = takeColorFromScheme { + if (isSelected) { + secondaryContainer + } else { + surfaceContainer + } + }, + onClick = onToggleSelection, + applyHorizontalPadding = false, + startContent = { + Box( + modifier = Modifier + .padding(end = 12.dp) + .size(56.dp) + .clip(ShapeDefaults.small) + .hapticsClickable(onClick = onPreview) + ) { + Picture( + model = item.uri.toUri(), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + shape = RectangleShape, + size = 128 + ) + + Box( + modifier = Modifier + .matchParentSize() + .background( + MaterialTheme.colorScheme + .surfaceContainer + .copy(0.6f) + ), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Outlined.Visibility, + contentDescription = null + ) + } + } + }, + endContent = { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.offset(x = 4.dp) + ) { + AnimatedVisibility(visible = isRecommended) { + EnhancedBadge( + containerColor = MaterialTheme.colorScheme.tertiary + ) { + Text(stringResource(R.string.best)) + } + } + EnhancedCheckbox( + checked = isSelected, + onCheckedChange = { onToggleSelection() } + ) + } + }, + resultModifier = Modifier.padding(12.dp), + modifier = modifier + ) +} \ No newline at end of file diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/components/DuplicateSelectionActions.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/components/DuplicateSelectionActions.kt new file mode 100644 index 0000000..c8394a4 --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/components/DuplicateSelectionActions.kt @@ -0,0 +1,117 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.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.Spacer +import androidx.compose.foundation.layout.padding +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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.FolderMatch +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 + +@Composable +internal fun DuplicateSelectionActions( + selectedCount: Int, + canSelectExact: Boolean, + canSelectAll: Boolean, + onSelectExact: () -> Unit, + onSelectAllExceptRecommended: () -> Unit, + onClearSelection: () -> Unit +) { + AnimatedVisibility( + visible = canSelectExact, + enter = fadeIn() + scaleIn() + expandHorizontally(), + exit = fadeOut() + scaleOut() + shrinkHorizontally() + ) { + EnhancedIconButton(onClick = onSelectExact) { + Icon( + imageVector = Icons.Outlined.FolderMatch, + contentDescription = stringResource(R.string.select_all_duplicates) + ) + } + } + + AnimatedVisibility( + visible = canSelectAll, + enter = fadeIn() + scaleIn() + expandHorizontally(), + exit = fadeOut() + scaleOut() + shrinkHorizontally() + ) { + EnhancedIconButton(onClick = onSelectAllExceptRecommended) { + Icon( + imageVector = Icons.Outlined.SelectAll, + contentDescription = stringResource(R.string.select_all_except_best) + ) + } + } + + AnimatedVisibility( + visible = selectedCount > 0, + enter = fadeIn() + scaleIn() + expandHorizontally(), + exit = fadeOut() + scaleOut() + shrinkHorizontally(), + modifier = Modifier + .padding(8.dp) + .container( + shape = ShapeDefaults.circle, + color = MaterialTheme.colorScheme.surfaceContainerHighest, + resultPadding = 0.dp + ) + ) { + Row( + modifier = Modifier.padding(start = 12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Spacer(Modifier.width(8.dp)) + Text( + text = selectedCount.toString(), + fontSize = 20.sp, + fontWeight = FontWeight.Medium + ) + EnhancedIconButton(onClick = onClearSelection) { + Icon( + imageVector = Icons.Rounded.Close, + contentDescription = stringResource(R.string.clear_selection) + ) + } + } + } +} diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/components/ResultSummary.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/components/ResultSummary.kt new file mode 100644 index 0000000..e6dbba6 --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/components/ResultSummary.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.presentation.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FolderMatch +import com.t8rin.imagetoolbox.core.resources.icons.ImageSearch +import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberHumanFileSize +import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem + +@Composable +internal fun ResultSummary( + exactGroupCount: Int, + similarGroupCount: Int, + reclaimableBytes: Long, + modifier: Modifier = Modifier +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + PreferenceItem( + title = stringResource(R.string.exact_copies), + subtitle = exactGroupCount.takeIf { it > 0 }?.toString() + ?: stringResource(R.string.not_found), + startIcon = Icons.Outlined.FolderMatch, + shape = ShapeDefaults.top, + modifier = Modifier.fillMaxWidth() + ) + PreferenceItem( + title = stringResource(R.string.similar_images), + subtitle = stringResource( + R.string.duplicate_groups_summary, + similarGroupCount + exactGroupCount, + rememberHumanFileSize(reclaimableBytes) + ), + startIcon = Icons.Outlined.ImageSearch, + shape = ShapeDefaults.bottom, + modifier = Modifier.fillMaxWidth() + ) + } +} \ No newline at end of file diff --git a/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/screenLogic/DuplicateFinderComponent.kt b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/screenLogic/DuplicateFinderComponent.kt new file mode 100644 index 0000000..643fb4f --- /dev/null +++ b/feature/duplicate-finder/src/main/java/com/t8rin/imagetoolbox/feature/duplicate_finder/presentation/screenLogic/DuplicateFinderComponent.kt @@ -0,0 +1,263 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.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.data.saving.FileControllerEventEmitter +import com.t8rin.imagetoolbox.core.data.saving.FileDeletionResult +import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder +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.core.utils.distinctUris +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.DuplicateFinder +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.helper.DuplicateGrouping +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateAnalysisProgress +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateAnalysisResult +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateGroup +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateType +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Job + +class DuplicateFinderComponent @AssistedInject internal constructor( + @Assisted componentContext: ComponentContext, + @Assisted val initialUris: List?, + @Assisted val onGoBack: () -> Unit, + @Assisted val onNavigate: (Screen) -> Unit, + private val duplicateFinder: DuplicateFinder, + private val fileControllerEventEmitter: FileControllerEventEmitter, + dispatchersHolder: DispatchersHolder +) : BaseComponent(dispatchersHolder, componentContext) { + + private val _sourceUris: MutableState> = mutableStateOf(emptyList()) + val sourceUris by _sourceUris + + private val _analysisResult: MutableState = mutableStateOf(null) + val analysisResult by _analysisResult + + private val _groups: MutableState> = mutableStateOf(emptyList()) + val groups by _groups + + private val _sensitivity = mutableIntStateOf(DuplicateGrouping.DEFAULT_SENSITIVITY) + val sensitivity by _sensitivity + + private val _selectedUris: MutableState> = mutableStateOf(emptySet()) + val selectedUris by _selectedUris + + private val _progress: MutableState = mutableStateOf(null) + val progress by _progress + + private val _isAnalyzing: MutableState = mutableStateOf(false) + val isAnalyzing by _isAnalyzing + + private val _isDeleting: MutableState = mutableStateOf(false) + val isDeleting by _isDeleting + + private var analysisJob: Job? by smartJob { + _isAnalyzing.value = false + } + + val selectedSizeBytes: Long + get() = analysisResult?.items + ?.asSequence() + ?.filter { it.uri in selectedUris } + ?.sumOf { it.sizeBytes } + ?: 0L + + val previewUris: List + get() = groups + .flatMap(DuplicateGroup::sortedItems) + .distinctBy { it.uri } + .map { it.uri.toUri() } + + init { + initialUris + ?.takeIf { it.isNotEmpty() } + ?.let(::setUris) + } + + fun setUris(uris: List) { + componentScope.launch { + _isAnalyzing.value = true + val distinctUris = uris.distinctUris() + _sourceUris.value = distinctUris + _selectedUris.value = emptySet() + if (distinctUris.isEmpty()) { + cancelAnalysis() + _analysisResult.value = null + _groups.value = emptyList() + } else { + analyze() + } + } + } + + fun addUris(uris: List) { + setUris(sourceUris + uris) + } + + fun analyze() { + if (sourceUris.isEmpty()) return + + val requestedUris = sourceUris.map(Uri::toString) + analysisJob = trackProgress { + _isAnalyzing.value = true + _progress.value = null + try { + val result = duplicateFinder.findDuplicates( + uris = requestedUris, + sensitivity = sensitivity, + onProgress = { + updateProgress( + done = it.processed, + total = it.total + ) + _progress.value = it + } + ) + _analysisResult.value = result + updateGroups(result.groups) + } catch (cancellation: CancellationException) { + throw cancellation + } catch (throwable: Throwable) { + AppToastHost.showFailureToast(throwable) + } finally { + _isAnalyzing.value = false + } + } + } + + fun cancelAnalysis() { + analysisJob = null + _progress.value = null + } + + fun updateSensitivity(value: Int) { + val normalized = DuplicateGrouping.normalizeSensitivity(value) + if (_sensitivity.intValue == normalized) return + + _sensitivity.intValue = normalized + analysisResult?.let { result -> + val regrouped = DuplicateGrouping.regroup( + items = result.items, + sensitivity = normalized + ) + _analysisResult.value = result.copy(groups = regrouped) + updateGroups(regrouped) + } + } + + fun toggleSelection(uri: String) { + _selectedUris.update { selected -> + if (uri in selected) selected - uri else selected + uri + } + } + + fun toggleGroupSelection(group: DuplicateGroup) { + val groupUris = group.items.mapTo(mutableSetOf()) { it.uri } + val duplicateUris = groupUris - group.recommendedUri + + _selectedUris.update { selected -> + val isGroupSelected = + duplicateUris.isNotEmpty() && duplicateUris.all(selected::contains) + if (isGroupSelected) { + selected - groupUris + } else { + (selected - groupUris) + duplicateUris + } + } + } + + fun selectExactDuplicates() { + _selectedUris.value = DuplicateGrouping.selectAllDuplicates( + groups.filter { it.type == DuplicateType.Exact } + ) + } + + fun selectAllExceptRecommended() { + _selectedUris.value = DuplicateGrouping.selectAllExceptRecommended(groups) + } + + fun clearSelection() { + _selectedUris.value = DuplicateGrouping.clearSelection() + } + + fun deleteSelected() { + if (selectedUris.isEmpty() || isDeleting) return + + val uris = selectedUris.toList() + _isDeleting.value = true + componentScope.launch { + fileControllerEventEmitter.deleteFiles(uris) { result -> + handleDeletionResult(result) + } + } + } + + private fun handleDeletionResult(result: FileDeletionResult) { + val deletedUris = result.deletedUris.toSet() + if (deletedUris.isNotEmpty()) { + _sourceUris.update { uris -> + uris.filterNot { it.toString() in deletedUris } + } + analysisResult?.let { current -> + val remainingItems = current.items.filterNot { it.uri in deletedUris } + val regrouped = DuplicateGrouping.regroup(remainingItems, sensitivity) + _analysisResult.value = current.copy( + items = remainingItems, + groups = regrouped + ) + updateGroups(regrouped) + } + } + + val availableUris = groups + .flatMapTo(mutableSetOf()) { group -> group.items.map { it.uri } } + _selectedUris.value = result.failedUris.filterTo(mutableSetOf()) { it in availableUris } + _isDeleting.value = false + } + + private fun updateGroups(groups: List) { + _groups.value = groups + val availableUris = groups + .flatMapTo(mutableSetOf()) { group -> group.items.map { it.uri } } + _selectedUris.update { it.intersect(availableUris) } + } + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUris: List?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit + ): DuplicateFinderComponent + } +} diff --git a/feature/duplicate-finder/src/test/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/DuplicateFinderDomainTest.kt b/feature/duplicate-finder/src/test/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/DuplicateFinderDomainTest.kt new file mode 100644 index 0000000..8da6381 --- /dev/null +++ b/feature/duplicate-finder/src/test/java/com/t8rin/imagetoolbox/feature/duplicate_finder/domain/DuplicateFinderDomainTest.kt @@ -0,0 +1,201 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.duplicate_finder.domain + +import com.t8rin.imagetoolbox.core.data.saving.io.ByteArrayReadable +import com.t8rin.imagetoolbox.core.data.saving.io.StreamReadable +import com.t8rin.imagetoolbox.core.data.utils.computeFromReadable +import com.t8rin.imagetoolbox.core.domain.model.HashingType +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.helper.DHash +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.helper.DuplicateGrouping +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateItem +import com.t8rin.imagetoolbox.feature.duplicate_finder.domain.model.DuplicateType +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import java.io.ByteArrayInputStream + +class DuplicateFinderDomainTest { + + @Test + fun identicalImagesHaveIdenticalDHash() { + val pixels = ascendingPixels() + + assertEquals(DHash.calculate(pixels), DHash.calculate(pixels.copyOf())) + } + + @Test + fun smallChangeHasSmallHammingDistance() { + val original = ascendingPixels() + val changed = original.copyOf().apply { + this[0] = 2 + this[1] = 1 + } + + assertEquals(1, DHash.hammingDistance(DHash.calculate(original), DHash.calculate(changed))) + } + + @Test + fun completelyDifferentImagesAreNotGroupedAtDefaultSensitivity() { + val first = item(uri = "first", sha256 = "a", dHash = 0L) + val second = item(uri = "second", sha256 = "b", dHash = -1L) + + assertTrue(DuplicateGrouping.regroup(listOf(first, second)).isEmpty()) + } + + @Test + fun exactCopiesAreDeterminedBySha256() = runBlocking { + val bytes = "same image bytes".encodeToByteArray() + val firstHash = HashingType.SHA_256.computeFromReadable(ByteArrayReadable(bytes)) + val secondHash = HashingType.SHA_256.computeFromReadable(ByteArrayReadable(bytes.copyOf())) + val differentHash = + HashingType.SHA_256.computeFromReadable(StreamReadable(ByteArrayInputStream("different".encodeToByteArray()))) + + assertEquals(firstHash, secondHash) + assertNotEquals(firstHash, differentHash) + val groups = DuplicateGrouping.regroup( + listOf( + item(uri = "first", sha256 = firstHash, dHash = 0L), + item(uri = "second", sha256 = secondHash, dHash = -1L) + ) + ) + assertEquals(DuplicateType.Exact, groups.single().type) + } + + @Test + fun recommendedItemIsDeterministic() { + val older = item( + uri = "older", + sha256 = "a", + dHash = 0L, + width = 200, + height = 200, + sizeBytes = 2_000, + lastModified = 100, + sourceIndex = 0 + ) + val newer = older.copy(uri = "newer", sha256 = "b", lastModified = 200, sourceIndex = 1) + val sameAsNewerButLater = newer.copy(uri = "later", sha256 = "c", sourceIndex = 2) + + assertEquals( + "newer", + DuplicateGrouping.recommendedItem(listOf(sameAsNewerButLater, older, newer)).uri + ) + } + + @Test + fun selectAllNeverSelectsRecommendedItem() { + val group = DuplicateGrouping.regroup( + listOf( + item(uri = "best", sha256 = "same", dHash = 0L, sourceIndex = 0), + item(uri = "copy", sha256 = "same", dHash = 0L, sourceIndex = 1) + ) + ).single() + + val selection = DuplicateGrouping.selectAllDuplicates(listOf(group)) + + assertFalse(group.recommendedUri in selection) + assertEquals(setOf("copy"), selection) + } + + @Test + fun sensitivityIsClampedAndInclusiveAtBoundary() { + assertEquals(0, DuplicateGrouping.normalizeSensitivity(-1)) + assertEquals( + DuplicateGrouping.MAX_SENSITIVITY, + DuplicateGrouping.normalizeSensitivity(99) + ) + + val first = item(uri = "first", sha256 = "a", dHash = 0L) + val second = item( + uri = "second", + sha256 = "b", + dHash = (1L shl DuplicateGrouping.MAX_SENSITIVITY) - 1 + ) + assertTrue( + DuplicateGrouping.regroup( + listOf(first, second), + sensitivity = DuplicateGrouping.MAX_SENSITIVITY - 1 + ).isEmpty() + ) + assertEquals( + DuplicateType.Similar, + DuplicateGrouping.regroup( + listOf(first, second), + sensitivity = DuplicateGrouping.MAX_SENSITIVITY + ).single().type + ) + } + + @Test + fun sensitivityZeroDoesNotTurnVisualMatchIntoExactCopy() { + val groups = DuplicateGrouping.regroup( + listOf( + item(uri = "first", sha256 = "a", dHash = 42L), + item(uri = "second", sha256 = "b", dHash = 42L) + ), + sensitivity = 0 + ) + + assertEquals(DuplicateType.Similar, groups.single().type) + } + + @Test + fun completeLinkGroupingPreventsTransitiveMerge() { + val first = item(uri = "a", sha256 = "a", dHash = 0L) + val bridge = item(uri = "b", sha256 = "b", dHash = 0b11_1111L) + val distant = item(uri = "c", sha256 = "c", dHash = 0b11_1111_1111_11L) + + val groups = DuplicateGrouping.regroup( + items = listOf(first, bridge, distant), + sensitivity = DuplicateGrouping.DEFAULT_SENSITIVITY + ) + + assertEquals(setOf("a", "b"), groups.single().items.mapTo(mutableSetOf()) { it.uri }) + assertFalse(groups.single().items.any { it.uri == "c" }) + } + + private fun ascendingPixels(): IntArray = IntArray(DHash.PIXEL_COUNT) { index -> + index % DHash.WIDTH + } + + private fun item( + uri: String, + sha256: String, + dHash: Long, + width: Int = 100, + height: Int = 100, + sizeBytes: Long = 1_000, + lastModified: Long? = 100, + sourceIndex: Int = 0 + ) = DuplicateItem( + uri = uri, + name = "$uri.png", + width = width, + height = height, + sizeBytes = sizeBytes, + format = "PNG", + lastModified = lastModified, + sourceIndex = sourceIndex, + sha256 = sha256, + dHash = dHash + ) +} diff --git a/feature/easter-egg/.gitignore b/feature/easter-egg/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/easter-egg/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/easter-egg/build.gradle.kts b/feature/easter-egg/build.gradle.kts new file mode 100644 index 0000000..5f702ef --- /dev/null +++ b/feature/easter-egg/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/easter-egg/src/main/AndroidManifest.xml b/feature/easter-egg/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/easter-egg/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/easter-egg/src/main/java/com/t8rin/imagetoolbox/feature/easter_egg/presentation/EasterEggContent.kt b/feature/easter-egg/src/main/java/com/t8rin/imagetoolbox/feature/easter_egg/presentation/EasterEggContent.kt new file mode 100644 index 0000000..c8c5a33 --- /dev/null +++ b/feature/easter-egg/src/main/java/com/t8rin/imagetoolbox/feature/easter_egg/presentation/EasterEggContent.kt @@ -0,0 +1,326 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.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 androidx.core.net.toUri +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.emoji.Emoji +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +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, + animatedEmoji = remember(emoji) { + Emoji.animatedIconFor(emoji.toUri()) + }?.toString() + ) + } + } + }, + navigationIcon = { + EnhancedIconButton( + onClick = component.onGoBack + ) { + Icon( + imageVector = Icons.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 + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/easter-egg/src/main/java/com/t8rin/imagetoolbox/feature/easter_egg/presentation/screenLogic/EasterEggComponent.kt b/feature/easter-egg/src/main/java/com/t8rin/imagetoolbox/feature/easter_egg/presentation/screenLogic/EasterEggComponent.kt new file mode 100644 index 0000000..2b1d5f1 --- /dev/null +++ b/feature/easter-egg/src/main/java/com/t8rin/imagetoolbox/feature/easter_egg/presentation/screenLogic/EasterEggComponent.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + +} \ No newline at end of file diff --git a/feature/edit-exif/.gitignore b/feature/edit-exif/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/edit-exif/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/edit-exif/build.gradle.kts b/feature/edit-exif/build.gradle.kts new file mode 100644 index 0000000..416500c --- /dev/null +++ b/feature/edit-exif/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/edit-exif/src/main/AndroidManifest.xml b/feature/edit-exif/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d5fcf6a --- /dev/null +++ b/feature/edit-exif/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/edit-exif/src/main/java/com/t8rin/imagetoolbox/feature/edit_exif/presentation/EditExifContent.kt b/feature/edit-exif/src/main/java/com/t8rin/imagetoolbox/feature/edit_exif/presentation/EditExifContent.kt new file mode 100644 index 0000000..fb6d9d6 --- /dev/null +++ b/feature/edit-exif/src/main/java/com/t8rin/imagetoolbox/feature/edit_exif/presentation/EditExifContent.kt @@ -0,0 +1,270 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/edit-exif/src/main/java/com/t8rin/imagetoolbox/feature/edit_exif/presentation/screenLogic/EditExifComponent.kt b/feature/edit-exif/src/main/java/com/t8rin/imagetoolbox/feature/edit_exif/presentation/screenLogic/EditExifComponent.kt new file mode 100644 index 0000000..d1ed3ec --- /dev/null +++ b/feature/edit-exif/src/main/java/com/t8rin/imagetoolbox/feature/edit_exif/presentation/screenLogic/EditExifComponent.kt @@ -0,0 +1,198 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} diff --git a/feature/erase-background/.gitignore b/feature/erase-background/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/erase-background/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/erase-background/build.gradle.kts b/feature/erase-background/build.gradle.kts new file mode 100644 index 0000000..6c659b6 --- /dev/null +++ b/feature/erase-background/build.gradle.kts @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/erase-background/src/foss/java/com/t8rin/imagetoolbox/feature/erase_background/data/backend/MlKitBackgroundRemoverBackend.kt b/feature/erase-background/src/foss/java/com/t8rin/imagetoolbox/feature/erase_background/data/backend/MlKitBackgroundRemoverBackend.kt new file mode 100644 index 0000000..a4e5d1b --- /dev/null +++ b/feature/erase-background/src/foss/java/com/t8rin/imagetoolbox/feature/erase_background/data/backend/MlKitBackgroundRemoverBackend.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/feature/erase-background/src/main/AndroidManifest.xml b/feature/erase-background/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/erase-background/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/data/AndroidAutoBackgroundRemover.kt b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/data/AndroidAutoBackgroundRemover.kt new file mode 100644 index 0000000..988e443 --- /dev/null +++ b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/data/AndroidAutoBackgroundRemover.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.core.utils.makeLog +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.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() + +} \ No newline at end of file diff --git a/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/data/AndroidAutoBackgroundRemoverBackendFactory.kt b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/data/AndroidAutoBackgroundRemoverBackendFactory.kt new file mode 100644 index 0000000..ba6ad04 --- /dev/null +++ b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/data/AndroidAutoBackgroundRemoverBackendFactory.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + +} \ No newline at end of file diff --git a/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/data/backend/GenericBackgroundRemoverBackend.kt b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/data/backend/GenericBackgroundRemoverBackend.kt new file mode 100644 index 0000000..fa51142 --- /dev/null +++ b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/data/backend/GenericBackgroundRemoverBackend.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 = 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 + BgModelType.YOLO -> BgRemover.Type.YOLO + BgModelType.MODNet -> BgRemover.Type.MODNet + } + ) + +} \ No newline at end of file diff --git a/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/di/EraseBackgroundModule.kt b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/di/EraseBackgroundModule.kt new file mode 100644 index 0000000..9d9f512 --- /dev/null +++ b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/di/EraseBackgroundModule.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/AutoBackgroundRemover.kt b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/AutoBackgroundRemover.kt new file mode 100644 index 0000000..b0d92f4 --- /dev/null +++ b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/AutoBackgroundRemover.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + +} \ No newline at end of file diff --git a/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/AutoBackgroundRemoverBackend.kt b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/AutoBackgroundRemoverBackend.kt new file mode 100644 index 0000000..b0c11d0 --- /dev/null +++ b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/AutoBackgroundRemoverBackend.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/AutoBackgroundRemoverBackendFactory.kt b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/AutoBackgroundRemoverBackendFactory.kt new file mode 100644 index 0000000..086051a --- /dev/null +++ b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/AutoBackgroundRemoverBackendFactory.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/model/BgModelType.kt b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/model/BgModelType.kt new file mode 100644 index 0000000..5684d7c --- /dev/null +++ b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/model/BgModelType.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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"), + YOLO("YOLO"), + MODNet("MODNet"); + + companion object { + val Default = if (Flavor.isFoss()) U2NetP else MlKit + } +} \ No newline at end of file diff --git a/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/EraseBackgroundContent.kt b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/EraseBackgroundContent.kt new file mode 100644 index 0000000..d1a3165 --- /dev/null +++ b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/EraseBackgroundContent.kt @@ -0,0 +1,480 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.Redo +import com.t8rin.imagetoolbox.core.resources.icons.Tune +import com.t8rin.imagetoolbox.core.resources.icons.Undo +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.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.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() + }, + drawBothStrokes = true + ) + 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() +} \ No newline at end of file diff --git a/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/AutoEraseBackgroundCard.kt b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/AutoEraseBackgroundCard.kt new file mode 100644 index 0000000..b468b2f --- /dev/null +++ b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/AutoEraseBackgroundCard.kt @@ -0,0 +1,306 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AutoFixHigh +import com.t8rin.imagetoolbox.core.resources.icons.DownloadForOffline +import com.t8rin.imagetoolbox.core.resources.icons.SettingsBackupRestore +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 + BgModelType.YOLO -> BgRemover.Type.YOLO + BgModelType.MODNet -> BgRemover.Type.MODNet +} + +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 + BgRemover.Type.YOLO -> BgModelType.YOLO + BgRemover.Type.MODNet -> BgModelType.MODNet +} \ No newline at end of file diff --git a/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/BitmapEraser.kt b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/BitmapEraser.kt new file mode 100644 index 0000000..a2887ff --- /dev/null +++ b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/BitmapEraser.kt @@ -0,0 +1,412 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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, + drawBitmapBorder = settingsState.drawBitmapBorder, + beforeHelperGridModifier = Modifier.drawBehind { + if (originalImagePreviewAlpha != 0f) { + drawContext.canvas.apply { + drawImageRect( + image = imageBitmapForShader ?: drawImageBitmap, + dstSize = IntSize(canvasSize.width, canvasSize.height), + paint = Paint().apply { + alpha = originalImagePreviewAlpha + } + ) + } + } + } + ) + } + } +} \ No newline at end of file diff --git a/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/OriginalImagePreviewAlphaSelector.kt b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/OriginalImagePreviewAlphaSelector.kt new file mode 100644 index 0000000..668697b --- /dev/null +++ b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/OriginalImagePreviewAlphaSelector.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ImageSearch +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 + ) +} \ No newline at end of file diff --git a/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/RecoverModeCard.kt b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/RecoverModeCard.kt new file mode 100644 index 0000000..0ca0dba --- /dev/null +++ b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/RecoverModeCard.kt @@ -0,0 +1,89 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.foundation.layout.padding +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Healing +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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" + ) + } +} \ No newline at end of file diff --git a/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/TrimImageToggle.kt b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/TrimImageToggle.kt new file mode 100644 index 0000000..9922969 --- /dev/null +++ b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/TrimImageToggle.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/screenLogic/EraseBackgroundComponent.kt b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/screenLogic/EraseBackgroundComponent.kt new file mode 100644 index 0000000..673f91f --- /dev/null +++ b/feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/screenLogic/EraseBackgroundComponent.kt @@ -0,0 +1,385 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + +} \ No newline at end of file diff --git a/feature/erase-background/src/market/java/com/t8rin/imagetoolbox/feature/erase_background/data/backend/MlKitBackgroundRemoverBackend.kt b/feature/erase-background/src/market/java/com/t8rin/imagetoolbox/feature/erase_background/data/backend/MlKitBackgroundRemoverBackend.kt new file mode 100644 index 0000000..d9b14fc --- /dev/null +++ b/feature/erase-background/src/market/java/com/t8rin/imagetoolbox/feature/erase_background/data/backend/MlKitBackgroundRemoverBackend.kt @@ -0,0 +1,187 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 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)) + } + } + + private fun autoRemove( + type: ApiType = ApiType.New, + 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; + } + +} + +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 + } + +} + +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. + **/ + 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)) + } + } + +} \ No newline at end of file diff --git a/feature/filters/.gitignore b/feature/filters/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/filters/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/filters/build.gradle.kts b/feature/filters/build.gradle.kts new file mode 100644 index 0000000..d59290b --- /dev/null +++ b/feature/filters/build.gradle.kts @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/AndroidManifest.xml b/feature/filters/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/filters/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/AndroidFilterMaskApplier.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/AndroidFilterMaskApplier.kt new file mode 100644 index 0000000..b99acb5 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/AndroidFilterMaskApplier.kt @@ -0,0 +1,210 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } + } + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/AndroidFilterParamsInteractor.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/AndroidFilterParamsInteractor.kt new file mode 100644 index 0000000..ad69182 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/AndroidFilterParamsInteractor.kt @@ -0,0 +1,223 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.REAL_PACKAGE +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 getRecentFilters(): Flow>> = dataStore.data.map { prefs -> + prefs[RECENT_FILTERS]?.toFiltersList(false) ?: emptyList() + } + + override suspend fun addRecentFilter(filter: Filter<*>) { + dataStore.edit { prefs -> + val currentFilters = prefs[RECENT_FILTERS]?.toFiltersList(false) ?: emptyList() + val newList = listOf(filter) + currentFilters.filter { + !it::class.java.isInstance(filter) + } + prefs[RECENT_FILTERS] = newList.take(10).toDatastoreString() + } + } + + override fun getFavoriteFilters(): Flow>> = dataStore.data.map { prefs -> + prefs[FAVORITE_FILTERS]?.toFiltersList(false) ?: 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() + } + } else { + dataStore.edit { prefs -> + prefs[FAVORITE_FILTERS] = currentFilters + .filter { + !it::class.java.isInstance(filter) + } + .toDatastoreString() + } + } + } + + override fun getTemplateFilters(): Flow> = + dataStore.data.map { prefs -> + prefs[TEMPLATE_FILTERS]?.takeIf { it.isNotEmpty() }?.toTemplateFiltersList() + ?: 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().firstOrNull() + }.getOrNull()?.let { + addTemplateFilter(it) + onSuccess(it.name, it.filters.size) + } ?: onFailure() + } else onFailure() + }.onFailure { onFailure() } + } + + override fun isValidTemplateFilter( + string: String, + ): Boolean = + (REAL_PACKAGE 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() + } + } + + 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() + } + } + + override suspend fun convertTemplateFilterToString( + templateFilter: TemplateFilter, + ): String = "$LINK_HEADER${listOf(templateFilter).toDatastoreString()}" + + 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() + } + } + + 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 RECENT_FILTERS = stringPreferencesKey("RECENT_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") \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/AndroidFilterProvider.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/AndroidFilterProvider.kt new file mode 100644 index 0000000..f231577 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/AndroidFilterProvider.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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() + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/AndroidShaderPresetRepository.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/AndroidShaderPresetRepository.kt new file mode 100644 index 0000000..09e8878 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/AndroidShaderPresetRepository.kt @@ -0,0 +1,115 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringSetPreferencesKey +import com.t8rin.imagetoolbox.core.domain.json.JsonParser +import com.t8rin.imagetoolbox.core.filters.domain.ShaderPresetRepository +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderParser +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderPreset +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.toItShaderJson +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +internal class AndroidShaderPresetRepository @Inject constructor( + private val dataStore: DataStore, + jsonParser: JsonParser +) : ShaderPresetRepository { + + private val parser = ShaderParser(jsonParser) + + override fun getPresets(): Flow> = dataStore.data.map { prefs -> + val rawPresets = prefs[SHADER_PRESETS].orEmpty() + val parsedPresets = rawPresets.mapNotNull { json -> + parsePresetOrNull(json)?.let { preset -> json to preset } + } + + ParsedShaderPresets( + rawPresets = rawPresets, + validRawPresets = parsedPresets.mapTo(mutableSetOf()) { it.first }, + presets = parsedPresets.map { it.second } + ) + }.onEach { result -> + if (result.rawPresets.size != result.validRawPresets.size) { + dataStore.edit { prefs -> + if (result.validRawPresets.isEmpty()) { + prefs.remove(SHADER_PRESETS) + } else { + prefs[SHADER_PRESETS] = result.validRawPresets + } + } + } + }.map { result -> + result.presets.distinctBy { it.name }.sortedBy { it.name.lowercase() } + }.flowOn(Dispatchers.Default) + + override suspend fun savePreset( + preset: ShaderPreset, + replacingName: String? + ): Result = runCatching { + val currentPresets = getPresets().first() + val nextPresets = currentPresets + .filterNot { it.name == (replacingName ?: preset.name) || it.name == preset.name } + .plus(preset) + + dataStore.edit { prefs -> + prefs[SHADER_PRESETS] = nextPresets.mapTo(mutableSetOf(), ShaderPreset::toItShaderJson) + } + } + + override suspend fun importPreset(json: String): Result = + parser.parse(json).onSuccess { savePreset(it).getOrThrow() } + + override suspend fun deletePreset(preset: ShaderPreset) { + val nextPresets = getPresets().first().filterNot { it.name == preset.name } + dataStore.edit { prefs -> + prefs[SHADER_PRESETS] = nextPresets.mapTo(mutableSetOf(), ShaderPreset::toItShaderJson) + } + } + + override fun exportPreset(preset: ShaderPreset): String = preset.toItShaderJson() + + private fun parsePresetOrNull(json: String): ShaderPreset? = + json.takeIf(String::isLikelyShaderPresetJson) + ?.let { parser.parse(it).getOrNull() } + +} + +private data class ParsedShaderPresets( + val rawPresets: Set, + val validRawPresets: Set, + val presets: List +) + +private fun String.isLikelyShaderPresetJson(): Boolean { + val json = trim() + return json.startsWith("{") && + json.endsWith("}") && + REQUIRED_SHADER_PRESET_FIELDS.all { field -> json.contains("\"$field\"") } +} + +private val SHADER_PRESETS = stringSetPreferencesKey("SHADER_PRESETS") +private val REQUIRED_SHADER_PRESET_FIELDS = listOf("version", "name", "shader") diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AcesFilmicToneMappingFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AcesFilmicToneMappingFilter.kt new file mode 100644 index 0000000..28aed98 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AcesFilmicToneMappingFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AcesHillToneMappingFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AcesHillToneMappingFilter.kt new file mode 100644 index 0000000..5782b7e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AcesHillToneMappingFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AchromatomalyFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AchromatomalyFilter.kt new file mode 100644 index 0000000..917ed5a --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AchromatomalyFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AchromatopsiaFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AchromatopsiaFilter.kt new file mode 100644 index 0000000..72060a8 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AchromatopsiaFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AdaptiveBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AdaptiveBlurFilter.kt new file mode 100644 index 0000000..5753f52 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AdaptiveBlurFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 com.t8rin.imagetoolbox.feature.filters.data.model + +import com.jhlabs.JhFilter +import com.jhlabs.SmartBlurFilter +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 AdaptiveBlurFilter( + override val value: Pair = 5f to 10f +) : JhFilterTransformation(), Filter.AdaptiveBlur { + + override val cacheKey: String + get() = value.hashCode().toString() + + override fun createFilter(): JhFilter = SmartBlurFilter().apply { + radius = value.first.roundToInt() + threshold = value.second.roundToInt() + } + +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AldridgeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AldridgeFilter.kt new file mode 100644 index 0000000..d3018d6 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AldridgeFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AmatorkaFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AmatorkaFilter.kt new file mode 100644 index 0000000..ad53be4 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AmatorkaFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AnaglyphFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AnaglyphFilter.kt new file mode 100644 index 0000000..901a44c --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AnaglyphFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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()) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AnisotropicDiffusionFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AnisotropicDiffusionFilter.kt new file mode 100644 index 0000000..49bf0eb --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AnisotropicDiffusionFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ArcFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ArcFilter.kt new file mode 100644 index 0000000..06f5df7 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ArcFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AsciiFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AsciiFilter.kt new file mode 100644 index 0000000..151197b --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AsciiFilter.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AtkinsonDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AtkinsonDitheringFilter.kt new file mode 100644 index 0000000..106d795 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AtkinsonDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutoCropFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutoCropFilter.kt new file mode 100644 index 0000000..5794f7f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutoCropFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutoPerspectiveFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutoPerspectiveFilter.kt new file mode 100644 index 0000000..8a7a477 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutoPerspectiveFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutoRemoveRedEyesFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutoRemoveRedEyesFilter.kt new file mode 100644 index 0000000..7f09fa4 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutoRemoveRedEyesFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutoWhiteBalanceFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutoWhiteBalanceFilter.kt new file mode 100644 index 0000000..d846ecf --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutoWhiteBalanceFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 AutoWhiteBalanceFilter( + override val value: Pair = 1f to 0.05f +) : Transformation, Filter.AutoWhiteBalance { + + override val cacheKey: String + get() = value.hashCode().toString() + + override suspend fun transform( + input: Bitmap, + size: IntegerSize + ): Bitmap = Trickle.autoWhiteBalance( + input = input, + strength = value.first, + clipPercent = value.second + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutumnTonesFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutumnTonesFilter.kt new file mode 100644 index 0000000..40ded1b --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutumnTonesFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AverageDistanceFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AverageDistanceFilter.kt new file mode 100644 index 0000000..f00dce6 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AverageDistanceFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerEightDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerEightDitheringFilter.kt new file mode 100644 index 0000000..6a916c8 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerEightDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerFourDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerFourDitheringFilter.kt new file mode 100644 index 0000000..f05faf8 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerFourDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerThreeDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerThreeDitheringFilter.kt new file mode 100644 index 0000000..055f108 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerThreeDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerTwoDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerTwoDitheringFilter.kt new file mode 100644 index 0000000..59418ea --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerTwoDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BilaterialBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BilaterialBlurFilter.kt new file mode 100644 index 0000000..81ec140 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BilaterialBlurFilter.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BlackAndWhiteFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BlackAndWhiteFilter.kt new file mode 100644 index 0000000..b5df7a6 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BlackAndWhiteFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BlackHatFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BlackHatFilter.kt new file mode 100644 index 0000000..fc87ba6 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BlackHatFilter.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BleachBypassFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BleachBypassFilter.kt new file mode 100644 index 0000000..fa065b7 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BleachBypassFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BlockGlitchFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BlockGlitchFilter.kt new file mode 100644 index 0000000..fca4e42 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BlockGlitchFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BloomFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BloomFilter.kt new file mode 100644 index 0000000..5b49cce --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BloomFilter.kt @@ -0,0 +1,49 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BokehFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BokehFilter.kt new file mode 100644 index 0000000..1cc3125 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BokehFilter.kt @@ -0,0 +1,59 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BorderFrameFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BorderFrameFilter.kt new file mode 100644 index 0000000..2414ac1 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BorderFrameFilter.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + } + } +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BoxBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BoxBlurFilter.kt new file mode 100644 index 0000000..ba3b351 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BoxBlurFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BrightnessFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BrightnessFilter.kt new file mode 100644 index 0000000..428bbab --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BrightnessFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BrowniFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BrowniFilter.kt new file mode 100644 index 0000000..bb92594 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BrowniFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BulgeDistortionFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BulgeDistortionFilter.kt new file mode 100644 index 0000000..0948c54 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BulgeDistortionFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BurkesDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BurkesDitheringFilter.kt new file mode 100644 index 0000000..32de91e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BurkesDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CGAColorSpaceFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CGAColorSpaceFilter.kt new file mode 100644 index 0000000..2b4042b --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CGAColorSpaceFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CandlelightFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CandlelightFilter.kt new file mode 100644 index 0000000..8552d67 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CandlelightFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CannyFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CannyFilter.kt new file mode 100644 index 0000000..9598a90 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CannyFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CaramelDarknessFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CaramelDarknessFilter.kt new file mode 100644 index 0000000..6c655b0 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CaramelDarknessFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CelluloidFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CelluloidFilter.kt new file mode 100644 index 0000000..3f13a3c --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CelluloidFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ChannelMixFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ChannelMixFilter.kt new file mode 100644 index 0000000..47a3e8a --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ChannelMixFilter.kt @@ -0,0 +1,27 @@ +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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ChromeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ChromeFilter.kt new file mode 100644 index 0000000..ce8aa63 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ChromeFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 com.t8rin.imagetoolbox.feature.filters.data.model + +import com.jhlabs.ChromeFilter +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 ChromeFilter( + override val value: Pair = 0.5f to 1f +) : JhFilterTransformation(), Filter.Chrome { + + override val cacheKey: String + get() = value.hashCode().toString() + + override fun createFilter(): JhFilter = ChromeFilter().apply { + amount = value.first + exposure = value.second + } + +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CircleBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CircleBlurFilter.kt new file mode 100644 index 0000000..97f4de8 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CircleBlurFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CirclePixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CirclePixelationFilter.kt new file mode 100644 index 0000000..2192686 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CirclePixelationFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) } + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheFilter.kt new file mode 100644 index 0000000..11f0c77 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheHSLFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheHSLFilter.kt new file mode 100644 index 0000000..7826d4e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheHSLFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheHSVFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheHSVFilter.kt new file mode 100644 index 0000000..5457687 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheHSVFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheJzazbzFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheJzazbzFilter.kt new file mode 100644 index 0000000..effb475 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheJzazbzFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheLABFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheLABFilter.kt new file mode 100644 index 0000000..5a0054b --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheLABFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheLUVFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheLUVFilter.kt new file mode 100644 index 0000000..b6c8129 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheLUVFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheOklabFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheOklabFilter.kt new file mode 100644 index 0000000..48764e1 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheOklabFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheOklchFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheOklchFilter.kt new file mode 100644 index 0000000..4dc3e2c --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheOklchFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClosingFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClosingFilter.kt new file mode 100644 index 0000000..63e3736 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClosingFilter.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Clustered2x2DitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Clustered2x2DitheringFilter.kt new file mode 100644 index 0000000..9d8ee08 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Clustered2x2DitheringFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Clustered4x4DitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Clustered4x4DitheringFilter.kt new file mode 100644 index 0000000..a900b65 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Clustered4x4DitheringFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Clustered8x8DitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Clustered8x8DitheringFilter.kt new file mode 100644 index 0000000..efd7f9a --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Clustered8x8DitheringFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CodaChromeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CodaChromeFilter.kt new file mode 100644 index 0000000..08ab1ac --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CodaChromeFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CoffeeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CoffeeFilter.kt new file mode 100644 index 0000000..5640d21 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CoffeeFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorAnomalyFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorAnomalyFilter.kt new file mode 100644 index 0000000..b2e9225 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorAnomalyFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorBalanceFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorBalanceFilter.kt new file mode 100644 index 0000000..c8c04df --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorBalanceFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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()) + } +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorExplosionFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorExplosionFilter.kt new file mode 100644 index 0000000..c0f2156 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorExplosionFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorHalftoneFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorHalftoneFilter.kt new file mode 100644 index 0000000..704b1e5 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorHalftoneFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorMapFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorMapFilter.kt new file mode 100644 index 0000000..9f07219 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorMapFilter.kt @@ -0,0 +1,133 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorMaskFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorMaskFilter.kt new file mode 100644 index 0000000..23b7dff --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorMaskFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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.JhFilter +import com.jhlabs.MaskFilter +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.JhFilterTransformation + +@FilterInject +internal class ColorMaskFilter( + override val value: FilterValueWrapper = Color.Cyan.toModel().wrap() +) : JhFilterTransformation(), Filter.ColorMask { + + override val cacheKey: String + get() = value.hashCode().toString() + + override fun createFilter(): JhFilter = MaskFilter(value.wrapped.colorInt) +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorMatrix3x3Filter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorMatrix3x3Filter.kt new file mode 100644 index 0000000..8d42c38 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorMatrix3x3Filter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorMatrix4x4Filter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorMatrix4x4Filter.kt new file mode 100644 index 0000000..5f79e0f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorMatrix4x4Filter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorOverlayFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorOverlayFilter.kt new file mode 100644 index 0000000..6e5cf75 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorOverlayFilter.kt @@ -0,0 +1,49 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorPosterFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorPosterFilter.kt new file mode 100644 index 0000000..0bc45c6 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorPosterFilter.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorfulSwirlFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorfulSwirlFilter.kt new file mode 100644 index 0000000..e02ef50 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorfulSwirlFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ContourFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ContourFilter.kt new file mode 100644 index 0000000..1d1d9c2 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ContourFilter.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ContrastFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ContrastFilter.kt new file mode 100644 index 0000000..e5b068d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ContrastFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ConvexFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ConvexFilter.kt new file mode 100644 index 0000000..4824138 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ConvexFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Convolution3x3Filter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Convolution3x3Filter.kt new file mode 100644 index 0000000..041e28f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Convolution3x3Filter.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CoolFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CoolFilter.kt new file mode 100644 index 0000000..da1e748 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CoolFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CopyMoveDetectionFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CopyMoveDetectionFilter.kt new file mode 100644 index 0000000..e728ca6 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CopyMoveDetectionFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CropOrPerspectiveFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CropOrPerspectiveFilter.kt new file mode 100644 index 0000000..25113e8 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CropOrPerspectiveFilter.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + ) + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CropToContentFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CropToContentFilter.kt new file mode 100644 index 0000000..82cbf99 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CropToContentFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrossBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrossBlurFilter.kt new file mode 100644 index 0000000..52511bb --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrossBlurFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrossPixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrossPixelationFilter.kt new file mode 100644 index 0000000..78fb542 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrossPixelationFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) } + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrosshatchFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrosshatchFilter.kt new file mode 100644 index 0000000..ab5f65d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrosshatchFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrtCurvatureFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrtCurvatureFilter.kt new file mode 100644 index 0000000..c2091ad --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrtCurvatureFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrystallizeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrystallizeFilter.kt new file mode 100644 index 0000000..6b5ce4b --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrystallizeFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CubeLutFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CubeLutFilter.kt new file mode 100644 index 0000000..5cb56c4 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CubeLutFilter.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CyberpunkFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CyberpunkFilter.kt new file mode 100644 index 0000000..5937c8d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CyberpunkFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeepPurpleFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeepPurpleFilter.kt new file mode 100644 index 0000000..aedffbf --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeepPurpleFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DehazeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DehazeFilter.kt new file mode 100644 index 0000000..c6f5659 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DehazeFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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, + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeskewFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeskewFilter.kt new file mode 100644 index 0000000..27e2008 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeskewFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DespeckleFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DespeckleFilter.kt new file mode 100644 index 0000000..31b162a --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DespeckleFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeutaromalyFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeutaromalyFilter.kt new file mode 100644 index 0000000..ed38f50 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeutaromalyFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeuteranopiaFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeuteranopiaFilter.kt new file mode 100644 index 0000000..b9d21db --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeuteranopiaFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 DeuteranopiaFilter( + override val value: Unit = Unit +) : Transformation, Filter.Deuteranopia { + + override val cacheKey: String + get() = value.hashCode().toString() + + override suspend fun transform( + input: Bitmap, + size: IntegerSize + ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.Assistance.DEUTARONOPIA).transform(input, size) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DiamondPixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DiamondPixelationFilter.kt new file mode 100644 index 0000000..3933cb8 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DiamondPixelationFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) } + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DiffuseFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DiffuseFilter.kt new file mode 100644 index 0000000..265367d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DiffuseFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DigitalCodeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DigitalCodeFilter.kt new file mode 100644 index 0000000..b687be7 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DigitalCodeFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DilationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DilationFilter.kt new file mode 100644 index 0000000..73a7ba1 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DilationFilter.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DissolveFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DissolveFilter.kt new file mode 100644 index 0000000..6263d87 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DissolveFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 com.t8rin.imagetoolbox.feature.filters.data.model + +import com.jhlabs.DissolveFilter +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 DissolveFilter( + override val value: Pair = 0.75f to 0.1f +) : JhFilterTransformation(), Filter.Dissolve { + + override val cacheKey: String + get() = value.hashCode().toString() + + override fun createFilter(): JhFilter = DissolveFilter().apply { + density = value.first + softness = value.second + } + +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DistortPerspectiveFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DistortPerspectiveFilter.kt new file mode 100644 index 0000000..653f826 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DistortPerspectiveFilter.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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.PerspectiveFilter +import com.t8rin.imagetoolbox.core.filters.domain.model.Filter +import com.t8rin.imagetoolbox.core.filters.domain.model.params.DistortPerspectiveParams +import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject +import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation + +@FilterInject +internal class DistortPerspectiveFilter( + override val value: DistortPerspectiveParams = DistortPerspectiveParams.Default +) : JhFilterTransformation(), Filter.DistortPerspective { + + override val cacheKey: String + get() = value.hashCode().toString() + + override fun createFilter(): JhFilter = PerspectiveFilter() + + override fun createFilter(image: Bitmap): JhFilter = PerspectiveFilter().apply { + setCorners( + value.topLeft.first * image.width, + value.topLeft.second * image.height, + value.topRight.first * image.width, + value.topRight.second * image.height, + value.bottomRight.first * image.width, + value.bottomRight.second * image.height, + value.bottomLeft.first * image.width, + value.bottomLeft.second * image.height + ) + clip = value.clip + } +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DistortionFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DistortionFilter.kt new file mode 100644 index 0000000..8182906 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DistortionFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 DistortionFilter( + override val value: Float = 50f +) : Transformation, Filter.Distortion { + + override val cacheKey: String + get() = value.toString() + + override suspend fun transform( + input: Bitmap, + size: IntegerSize + ): Bitmap = SeamCarver.distort( + bitmap = input, + distortionPercent = value + ) + +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DoGFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DoGFilter.kt new file mode 100644 index 0000000..678daaf --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DoGFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DragoFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DragoFilter.kt new file mode 100644 index 0000000..61efba1 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DragoFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DropBluesFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DropBluesFilter.kt new file mode 100644 index 0000000..00381e9 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DropBluesFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DropShadowFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DropShadowFilter.kt new file mode 100644 index 0000000..5122603 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DropShadowFilter.kt @@ -0,0 +1,131 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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.BlurMaskFilter +import android.graphics.Paint +import android.graphics.PorterDuff +import androidx.core.graphics.applyCanvas +import androidx.core.graphics.createBitmap +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.DropShadowParams +import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject +import kotlin.math.ceil + +@FilterInject +internal class DropShadowFilter( + override val value: DropShadowParams = DropShadowParams.Default +) : Transformation, Filter.DropShadow { + + override val cacheKey: String + get() = value.hashCode().toString() + + override suspend fun transform( + input: Bitmap, + size: IntegerSize + ): Bitmap { + val padding = calculateShadowPadding(value) + val output = createBitmap( + width = input.width + padding.left + padding.right, + height = input.height + padding.top + padding.bottom + ) + + return output.applyCanvas { + buildShadowBitmap(input, value).let { shadow -> + drawBitmap( + shadow.bitmap, + padding.left + shadow.left, + padding.top + shadow.top, + null + ) + shadow.bitmap.recycle() + } + drawBitmap(input, padding.left.toFloat(), padding.top.toFloat(), null) + } + } + + private fun calculateShadowPadding( + shadow: DropShadowParams + ): ShadowPadding { + val blurRadius = shadow.blurRadius.coerceAtLeast(0f) + + return ShadowPadding( + left = ceil((blurRadius - shadow.offsetX).coerceAtLeast(0f)).toInt(), + top = ceil((blurRadius - shadow.offsetY).coerceAtLeast(0f)).toInt(), + right = ceil((blurRadius + shadow.offsetX).coerceAtLeast(0f)).toInt(), + bottom = ceil((blurRadius + shadow.offsetY).coerceAtLeast(0f)).toInt() + ) + } + + private fun buildShadowBitmap( + sourceBitmap: Bitmap, + shadow: DropShadowParams + ): ShadowBitmap { + val blurRadius = shadow.blurRadius.coerceAtLeast(0f) + 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.colorInt, PorterDuff.Mode.SRC_IN) + } + + alphaBitmap.recycle() + + return ShadowBitmap( + bitmap = tintedBitmap, + left = offset[0].toFloat() + shadow.offsetX, + top = offset[1].toFloat() + shadow.offsetY + ) + } + + private data class ShadowPadding( + val left: Int, + val top: Int, + val right: Int, + val bottom: Int + ) + + private data class ShadowBitmap( + val bitmap: Bitmap, + val left: Float, + val top: Float + ) +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EdgyAmberFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EdgyAmberFilter.kt new file mode 100644 index 0000000..96df971 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EdgyAmberFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ElectricGradientFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ElectricGradientFilter.kt new file mode 100644 index 0000000..4342f20 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ElectricGradientFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EmbossFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EmbossFilter.kt new file mode 100644 index 0000000..30f4530 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EmbossFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedCirclePixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedCirclePixelationFilter.kt new file mode 100644 index 0000000..50fe2b5 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedCirclePixelationFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) } + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedDiamondPixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedDiamondPixelationFilter.kt new file mode 100644 index 0000000..837e706 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedDiamondPixelationFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) } + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedGlitchFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedGlitchFilter.kt new file mode 100644 index 0000000..7aea370 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedGlitchFilter.kt @@ -0,0 +1,49 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedOilFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedOilFilter.kt new file mode 100644 index 0000000..31a8b3f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedOilFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedPixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedPixelationFilter.kt new file mode 100644 index 0000000..b26ef2c --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedPixelationFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) } + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedZoomBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedZoomBlurFilter.kt new file mode 100644 index 0000000..5e2c934 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedZoomBlurFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeFilter.kt new file mode 100644 index 0000000..5a05430 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveFilter.kt new file mode 100644 index 0000000..6821681 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveHSLFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveHSLFilter.kt new file mode 100644 index 0000000..1990824 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveHSLFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveHSVFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveHSVFilter.kt new file mode 100644 index 0000000..f99b95f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveHSVFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveLABFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveLABFilter.kt new file mode 100644 index 0000000..ab29b54 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveLABFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveLUVFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveLUVFilter.kt new file mode 100644 index 0000000..df75350 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveLUVFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramFilter.kt new file mode 100644 index 0000000..a9d2d5d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramHSVFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramHSVFilter.kt new file mode 100644 index 0000000..173f148 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramHSVFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramPixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramPixelationFilter.kt new file mode 100644 index 0000000..5667b2b --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramPixelationFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ErodeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ErodeFilter.kt new file mode 100644 index 0000000..92654e0 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ErodeFilter.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ErrorLevelAnalysisFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ErrorLevelAnalysisFilter.kt new file mode 100644 index 0000000..91dfd07 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ErrorLevelAnalysisFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ExpandImageFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ExpandImageFilter.kt new file mode 100644 index 0000000..f7b4fa2 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ExpandImageFilter.kt @@ -0,0 +1,49 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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.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.opencv_tools.expand.ImageExpander +import kotlin.math.roundToInt + +@FilterInject +internal class ExpandImageFilter( + override val value: Quad = 64f to 64f qto (64f to 64f) +) : Transformation, Filter.ExpandImage { + + override val cacheKey: String + get() = value.hashCode().toString() + + override suspend fun transform( + input: Bitmap, + size: IntegerSize + ): Bitmap = ImageExpander.expand( + bitmap = input, + top = value.first.roundToInt(), + left = value.second.roundToInt(), + right = value.third.roundToInt(), + bottom = value.fourth.roundToInt(), + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ExposureFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ExposureFilter.kt new file mode 100644 index 0000000..372086a --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ExposureFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FallColorsFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FallColorsFilter.kt new file mode 100644 index 0000000..90b5cef --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FallColorsFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FalseColorFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FalseColorFilter.kt new file mode 100644 index 0000000..74cfebe --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FalseColorFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FalseFloydSteinbergDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FalseFloydSteinbergDitheringFilter.kt new file mode 100644 index 0000000..146e8d7 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FalseFloydSteinbergDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FantasyLandscapeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FantasyLandscapeFilter.kt new file mode 100644 index 0000000..7ee97ea --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FantasyLandscapeFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastBilaterialBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastBilaterialBlurFilter.kt new file mode 100644 index 0000000..5979602 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastBilaterialBlurFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastBlurFilter.kt new file mode 100644 index 0000000..fc8eb3f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastBlurFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastGaussianBlur2DFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastGaussianBlur2DFilter.kt new file mode 100644 index 0000000..5c9d99e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastGaussianBlur2DFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastGaussianBlur3DFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastGaussianBlur3DFilter.kt new file mode 100644 index 0000000..3dac0bc --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastGaussianBlur3DFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastGaussianBlur4DFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastGaussianBlur4DFilter.kt new file mode 100644 index 0000000..85c2fee --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastGaussianBlur4DFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FeedbackFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FeedbackFilter.kt new file mode 100644 index 0000000..9a834a8 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FeedbackFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 com.t8rin.imagetoolbox.feature.filters.data.model + +import com.jhlabs.FeedbackFilter +import com.jhlabs.JhFilter +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 FeedbackFilter( + override val value: Quad = 10f to 0f qto (3f to 0.05f) +) : JhFilterTransformation(), Filter.Feedback { + + override val cacheKey: String + get() = value.hashCode().toString() + + override fun createFilter(): JhFilter = FeedbackFilter().apply { + distance = value.first + angle = Math.toRadians(value.second.toDouble()).toFloat() + rotation = Math.toRadians(value.third.toDouble()).toFloat() + zoom = value.fourth + } + +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FilmStock50Filter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FilmStock50Filter.kt new file mode 100644 index 0000000..c4e8c3e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FilmStock50Filter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FlareFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FlareFilter.kt new file mode 100644 index 0000000..8857673 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FlareFilter.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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.FlareFilter +import com.jhlabs.JhFilter +import com.t8rin.imagetoolbox.core.filters.domain.model.Filter +import com.t8rin.imagetoolbox.core.filters.domain.model.params.FlareParams +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 FlareFilter( + override val value: FlareParams = FlareParams.Default +) : JhFilterTransformation(), Filter.Flare { + + override val cacheKey: String + get() = value.hashCode().toString() + + override fun createFilter(): JhFilter = FlareFilter() + + override fun createFilter(image: Bitmap): JhFilter = FlareFilter( + (value.centreX * image.width).roundToInt(), + (value.centreY * image.height).roundToInt() + ).apply { + radius = (value.radius * (max(image.width, image.height) / 2f)).roundToInt() + baseAmount = value.baseAmount + ringAmount = value.ringAmount + rayAmount = value.rayAmount + ringWidth = value.ringWidth + color = value.color.colorInt + } +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FloydSteinbergDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FloydSteinbergDitheringFilter.kt new file mode 100644 index 0000000..de8a391 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FloydSteinbergDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FoggyNightFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FoggyNightFilter.kt new file mode 100644 index 0000000..d508cf0 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FoggyNightFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FractalGlassFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FractalGlassFilter.kt new file mode 100644 index 0000000..66e0a87 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FractalGlassFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FuturisticGradientFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FuturisticGradientFilter.kt new file mode 100644 index 0000000..60dc301 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FuturisticGradientFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GammaFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GammaFilter.kt new file mode 100644 index 0000000..a84ee18 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GammaFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GaussianBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GaussianBlurFilter.kt new file mode 100644 index 0000000..2a9f4e5 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GaussianBlurFilter.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GaussianBoxBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GaussianBoxBlurFilter.kt new file mode 100644 index 0000000..d1a799a --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GaussianBoxBlurFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlassSphereRefractionFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlassSphereRefractionFilter.kt new file mode 100644 index 0000000..9ebd972 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlassSphereRefractionFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlitchFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlitchFilter.kt new file mode 100644 index 0000000..45444e0 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlitchFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlitchVariantFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlitchVariantFilter.kt new file mode 100644 index 0000000..ae4d673 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlitchVariantFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlowFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlowFilter.kt new file mode 100644 index 0000000..7c3e073 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlowFilter.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GoldenForestFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GoldenForestFilter.kt new file mode 100644 index 0000000..a596bf3 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GoldenForestFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GoldenHourFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GoldenHourFilter.kt new file mode 100644 index 0000000..590bad0 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GoldenHourFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GothamFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GothamFilter.kt new file mode 100644 index 0000000..fef8114 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GothamFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GrainFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GrainFilter.kt new file mode 100644 index 0000000..6796efa --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GrainFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GrayscaleFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GrayscaleFilter.kt new file mode 100644 index 0000000..7cbe593 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GrayscaleFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GreenSunFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GreenSunFilter.kt new file mode 100644 index 0000000..f11e81f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GreenSunFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GreenishFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GreenishFilter.kt new file mode 100644 index 0000000..4a20119 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GreenishFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HDRFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HDRFilter.kt new file mode 100644 index 0000000..854a102 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HDRFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HableFilmicToneMappingFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HableFilmicToneMappingFilter.kt new file mode 100644 index 0000000..e923dad --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HableFilmicToneMappingFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HalftoneFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HalftoneFilter.kt new file mode 100644 index 0000000..4a476ef --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HalftoneFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HazeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HazeFilter.kt new file mode 100644 index 0000000..9cb635d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HazeFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HejlBurgessToneMappingFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HejlBurgessToneMappingFilter.kt new file mode 100644 index 0000000..3fab93b --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HejlBurgessToneMappingFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HighPassFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HighPassFilter.kt new file mode 100644 index 0000000..a9c27c6 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HighPassFilter.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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.HighPassFilter +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 HighPassFilter( + override val value: Float = 10f +) : JhFilterTransformation(), Filter.HighPass { + + override val cacheKey: String + get() = value.toString() + + override fun createFilter(): JhFilter = HighPassFilter().apply { + radius = value + } +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HighlightsAndShadowsFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HighlightsAndShadowsFilter.kt new file mode 100644 index 0000000..7d32c7f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HighlightsAndShadowsFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HorizontalWindStaggerFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HorizontalWindStaggerFilter.kt new file mode 100644 index 0000000..fa78114 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HorizontalWindStaggerFilter.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HotSummerFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HotSummerFilter.kt new file mode 100644 index 0000000..8b59155 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HotSummerFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HueFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HueFilter.kt new file mode 100644 index 0000000..fea794a --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HueFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/JarvisJudiceNinkeDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/JarvisJudiceNinkeDitheringFilter.kt new file mode 100644 index 0000000..a9fb51d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/JarvisJudiceNinkeDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/JavaLookAndFeelFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/JavaLookAndFeelFilter.kt new file mode 100644 index 0000000..552d264 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/JavaLookAndFeelFilter.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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.JavaLnFFilter +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 JavaLookAndFeelFilter( + override val value: Unit = Unit +) : JhFilterTransformation(), Filter.JavaLookAndFeel { + + override val cacheKey: String = "JavaLookAndFeelFilter" + + override fun createFilter(): JhFilter = JavaLnFFilter() +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/KaleidoscopeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/KaleidoscopeFilter.kt new file mode 100644 index 0000000..592a3ad --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/KaleidoscopeFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/KodakFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/KodakFilter.kt new file mode 100644 index 0000000..745df04 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/KodakFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/KuwaharaFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/KuwaharaFilter.kt new file mode 100644 index 0000000..82c2352 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/KuwaharaFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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()) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LUT512x512Filter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LUT512x512Filter.kt new file mode 100644 index 0000000..eda6368 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LUT512x512Filter.kt @@ -0,0 +1,58 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LaplacianFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LaplacianFilter.kt new file mode 100644 index 0000000..eb1cf88 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LaplacianFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LaplacianSimpleFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LaplacianSimpleFilter.kt new file mode 100644 index 0000000..1775131 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LaplacianSimpleFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LavenderDreamFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LavenderDreamFilter.kt new file mode 100644 index 0000000..49c47b2 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LavenderDreamFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LeftToRightDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LeftToRightDitheringFilter.kt new file mode 100644 index 0000000..8460aeb --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LeftToRightDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LemonadeLightFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LemonadeLightFilter.kt new file mode 100644 index 0000000..596e447 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LemonadeLightFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LensBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LensBlurFilter.kt new file mode 100644 index 0000000..fece15a --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LensBlurFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 com.t8rin.imagetoolbox.feature.filters.data.model + +import com.jhlabs.JhFilter +import com.jhlabs.LensBlurFilter +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.roundToInt + +@FilterInject +internal class LensBlurFilter( + override val value: Quad = 10f to 5f qto (2f to 255f) +) : JhFilterTransformation(), Filter.LensBlur { + + override val cacheKey: String + get() = value.hashCode().toString() + + override fun createFilter(): JhFilter = LensBlurFilter().apply { + radius = value.first + sides = value.second.roundToInt() + bloom = value.third + bloomThreshold = value.fourth + } + +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LensCorrectionFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LensCorrectionFilter.kt new file mode 100644 index 0000000..342a846 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LensCorrectionFilter.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + } + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LevelsFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LevelsFilter.kt new file mode 100644 index 0000000..251003f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LevelsFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 com.t8rin.imagetoolbox.feature.filters.data.model + +import com.jhlabs.JhFilter +import com.jhlabs.LevelsFilter +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 LevelsFilter( + override val value: Quad = 0f to 1f qto (0f to 1f) +) : JhFilterTransformation(), Filter.Levels { + + override val cacheKey: String + get() = value.hashCode().toString() + + override fun createFilter(): JhFilter = LevelsFilter().apply { + lowLevel = value.first + highLevel = value.second + lowOutputLevel = value.third + highOutputLevel = value.fourth + } + +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LightEffectsFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LightEffectsFilter.kt new file mode 100644 index 0000000..19e0d75 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LightEffectsFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 com.t8rin.imagetoolbox.feature.filters.data.model + +import com.jhlabs.JhFilter +import com.jhlabs.LightFilter +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 LightEffectsFilter( + override val value: Pair = 1f to 5f +) : JhFilterTransformation(), Filter.LightEffects { + + override val cacheKey: String + get() = value.hashCode().toString() + + override fun createFilter(): JhFilter = LightFilter().apply { + bumpHeight = value.first + bumpSoftness = value.second + } + +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearBoxBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearBoxBlurFilter.kt new file mode 100644 index 0000000..ff9bb7d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearBoxBlurFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearFastGaussianBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearFastGaussianBlurFilter.kt new file mode 100644 index 0000000..e5cedf2 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearFastGaussianBlurFilter.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearFastGaussianBlurNextFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearFastGaussianBlurNextFilter.kt new file mode 100644 index 0000000..6c4a570 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearFastGaussianBlurNextFilter.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearGaussianBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearGaussianBlurFilter.kt new file mode 100644 index 0000000..c3b9a38 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearGaussianBlurFilter.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearGaussianBoxBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearGaussianBoxBlurFilter.kt new file mode 100644 index 0000000..6c5163b --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearGaussianBoxBlurFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearStackBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearStackBlurFilter.kt new file mode 100644 index 0000000..c13d842 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearStackBlurFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearTentBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearTentBlurFilter.kt new file mode 100644 index 0000000..518311f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearTentBlurFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearTiltShiftFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearTiltShiftFilter.kt new file mode 100644 index 0000000..13293e0 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearTiltShiftFilter.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LogarithmicToneMappingFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LogarithmicToneMappingFilter.kt new file mode 100644 index 0000000..e5ca6e5 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LogarithmicToneMappingFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LookupFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LookupFilter.kt new file mode 100644 index 0000000..423635a --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LookupFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LowPolyFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LowPolyFilter.kt new file mode 100644 index 0000000..e62cf9e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LowPolyFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LuminanceGradientFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LuminanceGradientFilter.kt new file mode 100644 index 0000000..3706e56 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LuminanceGradientFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MarbleFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MarbleFilter.kt new file mode 100644 index 0000000..07e3c87 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MarbleFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MedianBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MedianBlurFilter.kt new file mode 100644 index 0000000..dbad1f7 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MedianBlurFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MicroMacroPixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MicroMacroPixelationFilter.kt new file mode 100644 index 0000000..610ecfb --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MicroMacroPixelationFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) } + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MirrorFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MirrorFilter.kt new file mode 100644 index 0000000..8f9860f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MirrorFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MissEtikateFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MissEtikateFilter.kt new file mode 100644 index 0000000..4d6ec3f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MissEtikateFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MobiusFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MobiusFilter.kt new file mode 100644 index 0000000..475a385 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MobiusFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MoireFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MoireFilter.kt new file mode 100644 index 0000000..9a352bc --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MoireFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MonochromeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MonochromeFilter.kt new file mode 100644 index 0000000..17129ed --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MonochromeFilter.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MorphologicalGradientFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MorphologicalGradientFilter.kt new file mode 100644 index 0000000..73370e1 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MorphologicalGradientFilter.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MotionBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MotionBlurFilter.kt new file mode 100644 index 0000000..f479612 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MotionBlurFilter.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NativeStackBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NativeStackBlurFilter.kt new file mode 100644 index 0000000..20f664d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NativeStackBlurFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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().coerceAtLeast(1), + horizontalRadius = value.toInt().coerceAtLeast(1) + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NegativeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NegativeFilter.kt new file mode 100644 index 0000000..1d4c2de --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NegativeFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NeonFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NeonFilter.kt new file mode 100644 index 0000000..2127a80 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NeonFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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()) + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NightMagicFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NightMagicFilter.kt new file mode 100644 index 0000000..88e109e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NightMagicFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NightVisionFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NightVisionFilter.kt new file mode 100644 index 0000000..cc5d1f0 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NightVisionFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NoiseFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NoiseFilter.kt new file mode 100644 index 0000000..57d7c71 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NoiseFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NonMaximumSuppressionFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NonMaximumSuppressionFilter.kt new file mode 100644 index 0000000..29c6349 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NonMaximumSuppressionFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NucleusPixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NucleusPixelationFilter.kt new file mode 100644 index 0000000..8d9d04e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NucleusPixelationFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) } + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OffsetFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OffsetFilter.kt new file mode 100644 index 0000000..a490356 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OffsetFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OilFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OilFilter.kt new file mode 100644 index 0000000..3e3809e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OilFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OldTvFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OldTvFilter.kt new file mode 100644 index 0000000..5d06f0c --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OldTvFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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()) + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OpacityFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OpacityFilter.kt new file mode 100644 index 0000000..a44e6dc --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OpacityFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OpeningFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OpeningFilter.kt new file mode 100644 index 0000000..0803fd7 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OpeningFilter.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OrangeHazeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OrangeHazeFilter.kt new file mode 100644 index 0000000..5b6cf5c --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OrangeHazeFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OrbitalPixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OrbitalPixelationFilter.kt new file mode 100644 index 0000000..5fbe924 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OrbitalPixelationFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) } + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PaletteTransferFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PaletteTransferFilter.kt new file mode 100644 index 0000000..0f83190 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PaletteTransferFilter.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PaletteTransferVariantFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PaletteTransferVariantFilter.kt new file mode 100644 index 0000000..d9c6a1b --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PaletteTransferVariantFilter.kt @@ -0,0 +1,58 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PastelFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PastelFilter.kt new file mode 100644 index 0000000..5193441 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PastelFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PerlinDistortionFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PerlinDistortionFilter.kt new file mode 100644 index 0000000..97fff3c --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PerlinDistortionFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PinchFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PinchFilter.kt new file mode 100644 index 0000000..9265631 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PinchFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PinkDreamFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PinkDreamFilter.kt new file mode 100644 index 0000000..399815d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PinkDreamFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PixelMeltFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PixelMeltFilter.kt new file mode 100644 index 0000000..17fe9a2 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PixelMeltFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PixelationFilter.kt new file mode 100644 index 0000000..7a58399 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PixelationFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) } + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PointillizeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PointillizeFilter.kt new file mode 100644 index 0000000..058d4dd --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PointillizeFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PoissonBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PoissonBlurFilter.kt new file mode 100644 index 0000000..5ca8396 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PoissonBlurFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PolarCoordinatesFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PolarCoordinatesFilter.kt new file mode 100644 index 0000000..babea98 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PolarCoordinatesFilter.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PolaroidFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PolaroidFilter.kt new file mode 100644 index 0000000..1fba502 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PolaroidFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PolkaDotFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PolkaDotFilter.kt new file mode 100644 index 0000000..8ba0284 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PolkaDotFilter.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PopArtFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PopArtFilter.kt new file mode 100644 index 0000000..a42b086 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PopArtFilter.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PosterizeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PosterizeFilter.kt new file mode 100644 index 0000000..090074a --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PosterizeFilter.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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()) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ProtanopiaFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ProtanopiaFilter.kt new file mode 100644 index 0000000..4fcd97a --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ProtanopiaFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ProtonomalyFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ProtonomalyFilter.kt new file mode 100644 index 0000000..fdf695d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ProtonomalyFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PulseGridPixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PulseGridPixelationFilter.kt new file mode 100644 index 0000000..0bcc646 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PulseGridPixelationFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) } + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PurpleMistFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PurpleMistFilter.kt new file mode 100644 index 0000000..05e6e02 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PurpleMistFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/QuantizierFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/QuantizierFilter.kt new file mode 100644 index 0000000..0ff9cb6 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/QuantizierFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RGBFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RGBFilter.kt new file mode 100644 index 0000000..04ad9b0 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RGBFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RadialTiltShiftFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RadialTiltShiftFilter.kt new file mode 100644 index 0000000..78e09e7 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RadialTiltShiftFilter.kt @@ -0,0 +1,49 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RadialWeavePixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RadialWeavePixelationFilter.kt new file mode 100644 index 0000000..e382a32 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RadialWeavePixelationFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) } + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RainbowWorldFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RainbowWorldFilter.kt new file mode 100644 index 0000000..3bda5ca --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RainbowWorldFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RandomDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RandomDitheringFilter.kt new file mode 100644 index 0000000..52a8344 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RandomDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RaysFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RaysFilter.kt new file mode 100644 index 0000000..f533e80 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RaysFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 com.t8rin.imagetoolbox.feature.filters.data.model + +import com.jhlabs.JhFilter +import com.jhlabs.RaysFilter +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 RaysFilter( + override val value: Triple = Triple(1f, 0.25f, 1f) +) : JhFilterTransformation(), Filter.Rays { + + override val cacheKey: String + get() = value.hashCode().toString() + + override fun createFilter(): JhFilter = RaysFilter().apply { + opacity = value.first + threshold = value.second + strength = value.third + } + +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RedSwirlFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RedSwirlFilter.kt new file mode 100644 index 0000000..6dce814 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RedSwirlFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ReduceNoiseFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ReduceNoiseFilter.kt new file mode 100644 index 0000000..4ab2d65 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ReduceNoiseFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RemoveColorFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RemoveColorFilter.kt new file mode 100644 index 0000000..d0b9090 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RemoveColorFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ReplaceColorFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ReplaceColorFilter.kt new file mode 100644 index 0000000..b7bb460 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ReplaceColorFilter.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RetroYellowFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RetroYellowFilter.kt new file mode 100644 index 0000000..75dccee --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RetroYellowFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RingBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RingBlurFilter.kt new file mode 100644 index 0000000..c218d92 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RingBlurFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RippleFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RippleFilter.kt new file mode 100644 index 0000000..c1f0371 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RippleFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * 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 com.t8rin.imagetoolbox.feature.filters.data.model + +import com.jhlabs.JhFilter +import com.jhlabs.RippleFilter +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 RippleFilter( + override val value: Quad = 5f to 16f qto (5f to 16f) +) : JhFilterTransformation(), Filter.Ripple { + + override val cacheKey: String + get() = value.hashCode().toString() + + override fun createFilter(): JhFilter = RippleFilter().apply { + xAmplitude = value.first + xWavelength = value.second + yAmplitude = value.third + yWavelength = value.fourth + } + +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RubberStampFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RubberStampFilter.kt new file mode 100644 index 0000000..0ea4949 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RubberStampFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + } +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SandPaintingFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SandPaintingFilter.kt new file mode 100644 index 0000000..f610b23 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SandPaintingFilter.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SaturationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SaturationFilter.kt new file mode 100644 index 0000000..4d551c8 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SaturationFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SeamCarvingFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SeamCarvingFilter.kt new file mode 100644 index 0000000..f82f513 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SeamCarvingFilter.kt @@ -0,0 +1,69 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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.safeConfig +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.SeamCarvingParams +import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject +import com.t8rin.imagetoolbox.feature.filters.data.utils.image.loadBitmap +import com.t8rin.opencv_tools.seam_carving.SeamCarver + +@FilterInject +internal class SeamCarvingFilter( + override val value: SeamCarvingParams = SeamCarvingParams() +) : Transformation, Filter.SeamCarving { + + override val cacheKey: String + get() = value.hashCode().toString() + + override suspend fun transform( + input: Bitmap, + size: IntegerSize + ): Bitmap = if (value.size.isZero()) { + input + } else { + val mask = value.maskFile.uri + .takeIf { it.isNotEmpty() } + ?.loadBitmap() + ?.let { bitmap -> + if (bitmap.width == input.width && bitmap.height == input.height) { + bitmap + } else { + bitmap.scale( + width = input.width, + height = input.height + ) + }.copy(input.safeConfig, false) + } + + SeamCarver.carve( + bitmap = input, + desiredWidth = value.size.width, + desiredHeight = value.size.height, + protectionMask = mask, + useBackwardEnergy = value.useBackwardEnergy, + useMaskAsRemoval = value.useMaskAsRemoval, + ) + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SepiaFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SepiaFilter.kt new file mode 100644 index 0000000..df9eb43 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SepiaFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ShaderFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ShaderFilter.kt new file mode 100644 index 0000000..7c2e48e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ShaderFilter.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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.filters.domain.model.Filter +import com.t8rin.imagetoolbox.core.filters.domain.model.params.ShaderParams +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderPreset +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderValidationError +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderValidator +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.ShaderGpuImageFilter +import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter + +@FilterInject +internal class ShaderFilter( + override val value: ShaderParams = ShaderParams() +) : GPUFilterTransformation(), Filter.Shader { + + override val cacheKey: String + get() = value.hashCode().toString() + + override fun createFilter(): GPUImageFilter { + val preset = value.preset + if (preset == null || !preset.isValidShaderPreset()) return GPUImageFilter() + + return ShaderGpuImageFilter( + preset = preset, + values = value.resolvedValues + ) + } + + override suspend fun transform( + input: Bitmap, + size: IntegerSize + ): Bitmap { + val preset = value.preset ?: return input + if (!preset.isValidShaderPreset()) return input + + return runCatching { super.transform(input, size) }.getOrElse { input } + } + + private fun ShaderPreset.isValidShaderPreset(): Boolean = + ShaderValidator.validate(this).none { + it !is ShaderValidationError.BlankName + } + +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SharpenFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SharpenFilter.kt new file mode 100644 index 0000000..2230666 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SharpenFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ShearFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ShearFilter.kt new file mode 100644 index 0000000..7ad28ed --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ShearFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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.ShearFilter +import com.t8rin.imagetoolbox.core.filters.domain.model.Filter +import com.t8rin.imagetoolbox.core.filters.domain.model.params.ShearParams +import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject +import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation + +@FilterInject +internal class ShearFilter( + override val value: ShearParams = ShearParams() +) : JhFilterTransformation(), Filter.Shear { + + override val cacheKey: String + get() = value.hashCode().toString() + + override fun createFilter(): JhFilter = ShearFilter().apply { + xAngle = Math.toRadians(value.xAngle.toDouble()).toFloat() + yAngle = Math.toRadians(value.yAngle.toDouble()).toFloat() + isResize = value.resize + } +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ShuffleBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ShuffleBlurFilter.kt new file mode 100644 index 0000000..895515f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ShuffleBlurFilter.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SideFadeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SideFadeFilter.kt new file mode 100644 index 0000000..56b1615 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SideFadeFilter.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + ) + } + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SierraDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SierraDitheringFilter.kt new file mode 100644 index 0000000..1e03928 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SierraDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SierraLiteDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SierraLiteDitheringFilter.kt new file mode 100644 index 0000000..b8f1bb2 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SierraLiteDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleOldTvFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleOldTvFilter.kt new file mode 100644 index 0000000..66e8b91 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleOldTvFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleSketchFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleSketchFilter.kt new file mode 100644 index 0000000..ae638c7 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleSketchFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleSolarizeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleSolarizeFilter.kt new file mode 100644 index 0000000..92e4ebf --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleSolarizeFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleThresholdDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleThresholdDitheringFilter.kt new file mode 100644 index 0000000..6be2e11 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleThresholdDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleWeavePixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleWeavePixelationFilter.kt new file mode 100644 index 0000000..0b9a10e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleWeavePixelationFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) } + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SketchFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SketchFilter.kt new file mode 100644 index 0000000..e500b8e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SketchFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SmearFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SmearFilter.kt new file mode 100644 index 0000000..6b3958b --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SmearFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SmoothToonFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SmoothToonFilter.kt new file mode 100644 index 0000000..d5a857d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SmoothToonFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + } +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SobelEdgeDetectionFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SobelEdgeDetectionFilter.kt new file mode 100644 index 0000000..ea4c1ca --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SobelEdgeDetectionFilter.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + } +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SobelSimpleFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SobelSimpleFilter.kt new file mode 100644 index 0000000..088c076 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SobelSimpleFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SoftEleganceFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SoftEleganceFilter.kt new file mode 100644 index 0000000..e0a0088 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SoftEleganceFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SoftEleganceVariantFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SoftEleganceVariantFilter.kt new file mode 100644 index 0000000..534d6ef --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SoftEleganceVariantFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SoftSpringLightFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SoftSpringLightFilter.kt new file mode 100644 index 0000000..645a74d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SoftSpringLightFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SolarizeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SolarizeFilter.kt new file mode 100644 index 0000000..4150380 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SolarizeFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SpacePortalFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SpacePortalFilter.kt new file mode 100644 index 0000000..d9659a0 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SpacePortalFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SparkleFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SparkleFilter.kt new file mode 100644 index 0000000..e45b57e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SparkleFilter.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SpectralFireFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SpectralFireFilter.kt new file mode 100644 index 0000000..c017145 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SpectralFireFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SphereLensDistortionFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SphereLensDistortionFilter.kt new file mode 100644 index 0000000..cedc2f8 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SphereLensDistortionFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SphereRefractionFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SphereRefractionFilter.kt new file mode 100644 index 0000000..bd155a1 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SphereRefractionFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SpotHealFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SpotHealFilter.kt new file mode 100644 index 0000000..e586eef --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SpotHealFilter.kt @@ -0,0 +1,80 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StackBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StackBlurFilter.kt new file mode 100644 index 0000000..cdd1cfb --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StackBlurFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StaggeredPixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StaggeredPixelationFilter.kt new file mode 100644 index 0000000..9464d98 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StaggeredPixelationFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) } + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StarBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StarBlurFilter.kt new file mode 100644 index 0000000..f2ca2da --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StarBlurFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StrokePixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StrokePixelationFilter.kt new file mode 100644 index 0000000..fd03fd9 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StrokePixelationFilter.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + } +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StuckiDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StuckiDitheringFilter.kt new file mode 100644 index 0000000..94d17d8 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StuckiDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SunriseFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SunriseFilter.kt new file mode 100644 index 0000000..49fdfd0 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SunriseFilter.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SwirlDistortionFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SwirlDistortionFilter.kt new file mode 100644 index 0000000..213f0c1 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SwirlDistortionFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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)) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TentBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TentBlurFilter.kt new file mode 100644 index 0000000..9b534e5 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TentBlurFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ThresholdFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ThresholdFilter.kt new file mode 100644 index 0000000..4af4b47 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ThresholdFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ToneCurvesFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ToneCurvesFilter.kt new file mode 100644 index 0000000..907e9e5 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ToneCurvesFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ToonFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ToonFilter.kt new file mode 100644 index 0000000..be1cf26 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ToonFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TopHatFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TopHatFilter.kt new file mode 100644 index 0000000..6572a22 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TopHatFilter.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TornEdgeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TornEdgeFilter.kt new file mode 100644 index 0000000..666c218 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TornEdgeFilter.kt @@ -0,0 +1,190 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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.Paint +import android.graphics.Path +import android.graphics.PorterDuff +import android.graphics.PorterDuffXfermode +import androidx.core.graphics.applyCanvas +import androidx.core.graphics.createBitmap +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.TornEdgeParams +import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject +import java.util.Random +import kotlin.math.ceil +import kotlin.math.min + +@FilterInject +internal class TornEdgeFilter( + override val value: TornEdgeParams = TornEdgeParams.Default +) : Transformation, Filter.TornEdge { + + override val cacheKey: String + get() = value.hashCode().toString() + + override suspend fun transform( + input: Bitmap, + size: IntegerSize + ): Bitmap { + val paths = createTornPaths(input.width, input.height, value) + + return createBitmap( + width = input.width, + height = input.height + ).apply { + setHasAlpha(true) + }.applyCanvas { + val layer = saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null) + drawBitmap( + input, + 0f, + 0f, + Paint(Paint.ANTI_ALIAS_FLAG).apply { + isFilterBitmap = true + } + ) + paths.forEach { path -> + drawPath( + path, + Paint(Paint.ANTI_ALIAS_FLAG).apply { + xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) + } + ) + } + restoreToCount(layer) + } + } + + private fun createTornPaths( + width: Int, + height: Int, + params: TornEdgeParams + ): List { + val random = Random((31L * width + height) * 31L + params.hashCode()) + val toothHeight = params.toothHeight.coerceIn(1, min(width, height).coerceAtLeast(1)) + val horizontalToothRange = params.horizontalToothRange.coerceAtLeast(2) + val verticalToothRange = params.verticalToothRange.coerceAtLeast(2) + val horizontalRegions = ceil(width.toFloat() / horizontalToothRange) + .toInt() + .coerceAtLeast(2) + val verticalRegions = ceil(height.toFloat() / verticalToothRange) + .toInt() + .coerceAtLeast(2) + val paths = mutableListOf() + + if (params.top) { + paths += Path().apply { + moveTo(0f, 0f) + lineTo(width.toFloat(), 0f) + lineTo(width.toFloat(), random.tooth(toothHeight)) + + for (i in horizontalRegions - 1 downTo 1) { + lineTo( + width * i / horizontalRegions.toFloat(), + random.tooth(toothHeight) + ) + } + lineTo( + 0f, + random.tooth(toothHeight) + ) + + close() + } + } + + if (params.right) { + paths += Path().apply { + moveTo(width.toFloat(), 0f) + lineTo(width.toFloat(), height.toFloat()) + lineTo( + width - random.tooth(toothHeight), + height.toFloat() + ) + + for (i in verticalRegions - 1 downTo 1) { + lineTo( + width - random.tooth(toothHeight), + height * i / verticalRegions.toFloat() + ) + } + lineTo( + width - random.tooth(toothHeight), + 0f + ) + + close() + } + } + + if (params.bottom) { + paths += Path().apply { + moveTo(width.toFloat(), height.toFloat()) + lineTo(0f, height.toFloat()) + lineTo( + 0f, + height - random.tooth(toothHeight) + ) + + for (i in 1 until horizontalRegions) { + lineTo( + width * i / horizontalRegions.toFloat(), + height - random.tooth(toothHeight) + ) + } + lineTo( + width.toFloat(), + height - random.tooth(toothHeight) + ) + + close() + } + } + + if (params.left) { + paths += Path().apply { + moveTo(0f, height.toFloat()) + lineTo(0f, 0f) + lineTo(random.tooth(toothHeight), 0f) + + for (i in 1 until verticalRegions) { + lineTo( + random.tooth(toothHeight), + height * i / verticalRegions.toFloat() + ) + } + lineTo( + random.tooth(toothHeight), + height.toFloat() + ) + + close() + } + } + + return paths + } + + private fun Random.tooth( + toothHeight: Int + ): Float = (nextInt(toothHeight) + 1).toFloat() +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TriToneFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TriToneFilter.kt new file mode 100644 index 0000000..a226a6f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TriToneFilter.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TritanopiaFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TritanopiaFilter.kt new file mode 100644 index 0000000..a099cf8 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TritanopiaFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TritonomalyFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TritonomalyFilter.kt new file mode 100644 index 0000000..8441a51 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TritonomalyFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TwirlFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TwirlFilter.kt new file mode 100644 index 0000000..4d24f21 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TwirlFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TwoRowSierraDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TwoRowSierraDitheringFilter.kt new file mode 100644 index 0000000..3d9cd90 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TwoRowSierraDitheringFilter.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/UchimuraFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/UchimuraFilter.kt new file mode 100644 index 0000000..bff341e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/UchimuraFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/UnsharpFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/UnsharpFilter.kt new file mode 100644 index 0000000..805a405 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/UnsharpFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VHSFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VHSFilter.kt new file mode 100644 index 0000000..718b198 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VHSFilter.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VHSNtscFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VHSNtscFilter.kt new file mode 100644 index 0000000..29f516f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VHSNtscFilter.kt @@ -0,0 +1,143 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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.NtscParams +import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject +import com.t8rin.trickle.NtscSettings +import com.t8rin.trickle.Trickle +import kotlin.math.roundToInt + +@FilterInject +internal class VHSNtscFilter( + override val value: NtscParams = NtscParams() +) : Transformation, Filter.VHSNtsc { + + override val cacheKey: String + get() = value.hashCode().toString() + + override suspend fun transform( + input: Bitmap, + size: IntegerSize + ): Bitmap { + if (value.amount <= 0f) return input + + return Trickle.ntsc( + src = input, + settings = value.toNtscSettings(), + scaleFactorX = value.scaleFactorX, + scaleFactorY = value.scaleFactorY + ) + } + +} + +private fun NtscParams.toNtscSettings(): NtscSettings { + val amountFactor = amount + val chromaBleedAmount = chromaBleed * amountFactor + val tapeWearAmount = tapeWear * amountFactor + val noiseAmount = noise * amountFactor + val trackingAmount = tracking * amountFactor + val snowAmount = snow * amountFactor + val ringingAmount = ringing * amountFactor + val selectedTapeSpeed = NtscSettings.VHSTapeSpeed.entries[vhsTapeSpeed] + + return NtscSettings( + randomSeed = seed, + useField = NtscSettings.UseField.entries[useField], + filterType = NtscSettings.FilterType.entries[filterType], + inputLumaFilter = NtscSettings.LumaLowpass.entries[inputLumaFilter], + chromaLowpassIn = NtscSettings.ChromaLowpass.entries[chromaLowpassIn], + chromaDemodulation = NtscSettings.ChromaDemodulationFilter.entries[chromaDemodulation], + lumaSmear = lumaSmear * amountFactor, + compositeSharpening = compositeSharpening * amountFactor, + videoScanlinePhaseShift = NtscSettings.PhaseShift.entries[videoScanlinePhaseShift], + videoScanlinePhaseShiftOffset = videoScanlinePhaseShiftOffset, + headSwitching = if (headSwitchingEnabled) NtscSettings.HeadSwitching( + height = (headSwitchingHeight * maxOf(trackingAmount, tapeWearAmount * 0.7f)) + .roundToInt(), + offset = (headSwitchingOffset * trackingAmount).roundToInt(), + horizontalShift = headSwitchingHorizontalShift * + maxOf(trackingAmount, tapeWearAmount * 0.7f), + midLine = NtscSettings.HeadSwitchingMidLine( + position = headSwitchingMidLinePosition, + jitter = headSwitchingMidLineJitter * trackingAmount + ) + ) else null, + trackingNoise = if (trackingNoiseEnabled) NtscSettings.TrackingNoise( + height = (trackingNoiseHeight * trackingAmount).roundToInt(), + waveIntensity = trackingNoiseWaveIntensity * trackingAmount, + snowIntensity = trackingNoiseSnowIntensity * trackingAmount, + snowAnisotropy = trackingNoiseSnowAnisotropy, + noiseIntensity = trackingNoiseNoiseIntensity * trackingAmount + ) else null, + compositeNoise = if (compositeNoiseEnabled) NtscSettings.FbmNoise( + frequency = compositeNoiseFrequency, + intensity = compositeNoiseIntensity * noiseAmount, + detail = compositeNoiseDetail + ) else null, + ringing = if (ringingEnabled) NtscSettings.Ringing( + frequency = ringingFrequency, + power = ringingPower, + intensity = ringingAmount + ) else null, + lumaNoise = if (lumaNoiseEnabled) NtscSettings.FbmNoise( + frequency = lumaNoiseFrequency, + intensity = lumaNoiseIntensity * noiseAmount, + detail = lumaNoiseDetail + ) else null, + chromaNoise = if (chromaNoiseEnabled) NtscSettings.FbmNoise( + frequency = chromaNoiseFrequency, + intensity = chromaNoiseIntensity * chromaBleedAmount, + detail = chromaNoiseDetail + ) else null, + snowIntensity = snowIntensity * snowAmount, + snowAnisotropy = snowAnisotropy, + chromaPhaseNoiseIntensity = chromaPhaseNoiseIntensity * chromaBleedAmount, + chromaPhaseError = chromaPhaseError * chromaBleedAmount, + chromaDelayHorizontal = chromaDelayHorizontal * chromaBleedAmount, + chromaDelayVertical = (chromaDelayVertical * chromaBleedAmount).roundToInt(), + vhs = if (vhsEnabled) NtscSettings.VHS( + tapeSpeed = if (tapeWearAmount > 0.03f) { + selectedTapeSpeed + } else { + NtscSettings.VHSTapeSpeed.NONE + }, + chromaLoss = vhsChromaLoss * tapeWearAmount, + sharpen = NtscSettings.VHSSharpen( + intensity = vhsSharpenIntensity * amountFactor, + frequency = vhsSharpenFrequency + ), + edgeWave = NtscSettings.VHSEdgeWave( + intensity = vhsEdgeWaveIntensity * tapeWearAmount, + speed = vhsEdgeWaveSpeed, + frequency = vhsEdgeWaveFrequency, + detail = vhsEdgeWaveDetail + ) + ) else null, + chromaLowpassOut = NtscSettings.ChromaLowpass.entries[chromaLowpassOut], + scale = if (scaleEnabled) NtscSettings.Scale( + horizontal = scaleHorizontal, + vertical = scaleVertical + ) else null + ) +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VibranceFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VibranceFilter.kt new file mode 100644 index 0000000..a4f8a92 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VibranceFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VignetteFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VignetteFilter.kt new file mode 100644 index 0000000..bc042ce --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VignetteFilter.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VintageFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VintageFilter.kt new file mode 100644 index 0000000..f834f9d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VintageFilter.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VoronoiCrystallizeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VoronoiCrystallizeFilter.kt new file mode 100644 index 0000000..3538f9d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VoronoiCrystallizeFilter.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VortexPixelationFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VortexPixelationFilter.kt new file mode 100644 index 0000000..97391de --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VortexPixelationFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) } + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WarmFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WarmFilter.kt new file mode 100644 index 0000000..0764a42 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WarmFilter.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WaterDropFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WaterDropFilter.kt new file mode 100644 index 0000000..886e7f5 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WaterDropFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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.WaterFilter +import com.t8rin.imagetoolbox.core.filters.domain.model.Filter +import com.t8rin.imagetoolbox.core.filters.domain.model.params.WaterDropParams +import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject +import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation +import kotlin.math.max + +@FilterInject +internal class WaterDropFilter( + override val value: WaterDropParams = WaterDropParams() +) : JhFilterTransformation(), Filter.WaterDrop { + + override val cacheKey: String + get() = value.hashCode().toString() + + override fun createFilter(): JhFilter = WaterFilter() + + override fun createFilter(image: Bitmap): JhFilter = WaterFilter().apply { + val imageRadius = max(image.width, image.height) / 2f + wavelength = value.wavelength * imageRadius + amplitude = value.amplitude * imageRadius + phase = Math.toRadians(value.phase.toDouble()).toFloat() + centreX = value.centreX + centreY = value.centreY + radius = value.radius * imageRadius + } +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WaterEffectFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WaterEffectFilter.kt new file mode 100644 index 0000000..c57d50b --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WaterEffectFilter.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WeakPixelFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WeakPixelFilter.kt new file mode 100644 index 0000000..c3f9a3b --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WeakPixelFilter.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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() +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WeaveFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WeaveFilter.kt new file mode 100644 index 0000000..956049f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WeaveFilter.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WhiteBalanceFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WhiteBalanceFilter.kt new file mode 100644 index 0000000..510f1d7 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WhiteBalanceFilter.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/YililomaDitheringFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/YililomaDitheringFilter.kt new file mode 100644 index 0000000..37ebf8d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/YililomaDitheringFilter.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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 + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ZoomBlurFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ZoomBlurFilter.kt new file mode 100644 index 0000000..5f7cdc2 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ZoomBlurFilter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/transformation/ColorMapTransformation.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/transformation/ColorMapTransformation.kt new file mode 100644 index 0000000..d5e2de6 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/transformation/ColorMapTransformation.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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) + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/transformation/GPUFilterTransformation.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/transformation/GPUFilterTransformation.kt new file mode 100644 index 0000000..9b1815f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/transformation/GPUFilterTransformation.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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()) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/transformation/JhFilterTransformation.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/transformation/JhFilterTransformation.kt new file mode 100644 index 0000000..98ba5d9 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/transformation/JhFilterTransformation.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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()) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/EnumMappings.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/EnumMappings.kt new file mode 100644 index 0000000..ae6deee --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/EnumMappings.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/TransformationUtils.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/TransformationUtils.kt new file mode 100644 index 0000000..d3b5872 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/TransformationUtils.kt @@ -0,0 +1,135 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/convolution/AireConvolution.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/convolution/AireConvolution.kt new file mode 100644 index 0000000..9ddb3a6 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/convolution/AireConvolution.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 +) \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/glitch/GlitchTool.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/glitch/GlitchTool.kt new file mode 100644 index 0000000..cc58a3e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/glitch/GlitchTool.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/glitch/tools/Anaglyph.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/glitch/tools/Anaglyph.kt new file mode 100644 index 0000000..6b28167 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/glitch/tools/Anaglyph.kt @@ -0,0 +1,135 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + ) + } +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/glitch/tools/JpegGlitch.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/glitch/tools/JpegGlitch.kt new file mode 100644 index 0000000..fdb1a16 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/glitch/tools/JpegGlitch.kt @@ -0,0 +1,88 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 + } + } +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/gpu/GPUImageHighlightShadowWideRangeFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/gpu/GPUImageHighlightShadowWideRangeFilter.kt new file mode 100644 index 0000000..b8707cb --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/gpu/GPUImageHighlightShadowWideRangeFilter.kt @@ -0,0 +1,85 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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" + + " }" + } +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/gpu/ShaderGpuImageFilter.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/gpu/ShaderGpuImageFilter.kt new file mode 100644 index 0000000..062996c --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/gpu/ShaderGpuImageFilter.kt @@ -0,0 +1,88 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderParam +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderPreset +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderValue +import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter + +internal class ShaderGpuImageFilter( + private val preset: ShaderPreset, + values: Map +) : GPUImageFilter( + NO_FILTER_VERTEX_SHADER, + preset.shader +) { + private val resolvedValues: Map = preset.params.associate { param -> + param.name to param.resolveValue(values[param.name]) + } + private val uniformLocations: MutableMap = mutableMapOf() + + override fun onInit() { + super.onInit() + preset.params.forEach { param -> + uniformLocations[param.name] = GLES20.glGetUniformLocation(program, param.name) + } + } + + override fun onDrawArraysPre() { + super.onDrawArraysPre() + applyUniformValues() + } + + private fun applyUniformValues() { + preset.params.forEach { param -> + val location = uniformLocations[param.name] ?: return@forEach + if (location < 0) return@forEach + + when (val value = resolvedValues[param.name] ?: param.defaultValue) { + is ShaderValue.FloatValue -> GLES20.glUniform1f(location, value.value) + is ShaderValue.IntValue -> GLES20.glUniform1i(location, value.value) + is ShaderValue.BoolValue -> GLES20.glUniform1i( + location, + if (value.value) TRUE_INT else FALSE_INT + ) + + is ShaderValue.ColorValue -> GLES20.glUniform4f( + location, + value.red.normalizedChannel(), + value.green.normalizedChannel(), + value.blue.normalizedChannel(), + value.alpha.normalizedChannel() + ) + + is ShaderValue.Vec2Value -> GLES20.glUniform2f(location, value.x, value.y) + } + } + } + + private fun ShaderParam.resolveValue(value: ShaderValue?): ShaderValue = + value?.takeIf { it.type == type } ?: defaultValue + + private fun Int.normalizedChannel(): Float = + coerceIn(MIN_COLOR_CHANNEL, MAX_COLOR_CHANNEL) / MAX_COLOR_CHANNEL.toFloat() + + private companion object { + const val TRUE_INT = 1 + const val FALSE_INT = 0 + const val MIN_COLOR_CHANNEL = 0 + const val MAX_COLOR_CHANNEL = 255 + } +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/image/ImageLoader.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/image/ImageLoader.kt new file mode 100644 index 0000000..7c5387b --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/image/ImageLoader.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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() \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/pixelation/PixelationTool.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/pixelation/PixelationTool.kt new file mode 100644 index 0000000..fd1a42e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/pixelation/PixelationTool.kt @@ -0,0 +1,189 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/pixelation/tool/PixelationCommands.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/pixelation/tool/PixelationCommands.kt new file mode 100644 index 0000000..397ee14 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/pixelation/tool/PixelationCommands.kt @@ -0,0 +1,303 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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() + ) + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/pixelation/tool/PixelationLayer.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/pixelation/tool/PixelationLayer.kt new file mode 100644 index 0000000..5da872f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/pixelation/tool/PixelationLayer.kt @@ -0,0 +1,77 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + } +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/serialization/FilterSerializationUtils.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/serialization/FilterSerializationUtils.kt new file mode 100644 index 0000000..9502885 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/serialization/FilterSerializationUtils.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.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 +): String = joinToString(separator = FILTERS_SEPARATOR) { filter -> + filter::class.qualifiedName!!.replace( + REAL_PACKAGE, + 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 +): 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, + REAL_PACKAGE + ) + ) 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(): List = + split(TEMPLATES_SEPARATOR).map { + val splitData = it.split(TEMPLATE_CONTENT_SEPARATOR) + val name = splitData[0] + val filters = splitData[1].toFiltersList(true) + + TemplateFilter( + name = name, + filters = filters + ) + } + +internal fun List.toDatastoreString(): String = + joinToString(separator = TEMPLATES_SEPARATOR) { + it.name + TEMPLATE_CONTENT_SEPARATOR + it.filters.toDatastoreString(true) + } + +private const val FILTERS_SEPARATOR = "," +private const val TEMPLATES_SEPARATOR = "\\" +private const val TEMPLATE_CONTENT_SEPARATOR = "+" +private const val VALUE_SEPARATOR = ":" + +internal const val REAL_PACKAGE = "com.t8rin.imagetoolbox" +internal const val PACKAGE_ALIAS = "^^" \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/serialization/Mappings.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/serialization/Mappings.kt new file mode 100644 index 0000000..a5e6856 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/serialization/Mappings.kt @@ -0,0 +1,1061 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.DistortPerspectiveParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.DropShadowParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.EnhancedZoomBlurParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.FlareParams +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.NtscParams +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.ShearParams +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.TornEdgeParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.VoronoiCrystallizeParams +import com.t8rin.imagetoolbox.core.filters.domain.model.params.WaterDropParams +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 FlareParams -> { + FlareParams::class.simpleName() to listOf( + radius, + baseAmount, + ringAmount, + rayAmount, + ringWidth, + centreX, + centreY, + color.colorInt + ).joinToString(PROPERTIES_SEPARATOR) + } + + is DistortPerspectiveParams -> { + DistortPerspectiveParams::class.simpleName() to listOf( + topLeft.first, + topLeft.second, + topRight.first, + topRight.second, + bottomLeft.first, + bottomLeft.second, + bottomRight.first, + bottomRight.second, + clip + ).joinToString(PROPERTIES_SEPARATOR) + } + + is ShearParams -> { + ShearParams::class.simpleName() to listOf( + xAngle, + yAngle, + resize + ).joinToString(PROPERTIES_SEPARATOR) + } + + is WaterDropParams -> { + WaterDropParams::class.simpleName() to listOf( + wavelength, + amplitude, + phase, + centreX, + centreY, + radius + ).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 DropShadowParams -> { + DropShadowParams::class.simpleName!! to listOf( + color.colorInt, + offsetX, + offsetY, + blurRadius + ).joinToString(PROPERTIES_SEPARATOR) + } + + is TornEdgeParams -> { + TornEdgeParams::class.simpleName!! to listOf( + toothHeight, + horizontalToothRange, + verticalToothRange, + top, + right, + bottom, + left + ).joinToString(PROPERTIES_SEPARATOR) + } + + is NtscParams -> { + NtscParams::class.simpleName!! to listOf( + amount, + chromaBleed, + tapeWear, + noise, + tracking, + seed, + lumaSmear, + compositeSharpening, + ringing, + snow, + useField, + filterType, + inputLumaFilter, + chromaLowpassIn, + chromaDemodulation, + videoScanlinePhaseShift, + videoScanlinePhaseShiftOffset, + headSwitchingHeight, + headSwitchingOffset, + headSwitchingHorizontalShift, + headSwitchingMidLinePosition, + headSwitchingMidLineJitter, + trackingNoiseHeight, + trackingNoiseWaveIntensity, + trackingNoiseSnowIntensity, + trackingNoiseSnowAnisotropy, + trackingNoiseNoiseIntensity, + compositeNoiseFrequency, + compositeNoiseIntensity, + compositeNoiseDetail, + ringingFrequency, + ringingPower, + lumaNoiseFrequency, + lumaNoiseIntensity, + lumaNoiseDetail, + chromaNoiseFrequency, + chromaNoiseIntensity, + chromaNoiseDetail, + snowIntensity, + snowAnisotropy, + chromaPhaseNoiseIntensity, + chromaPhaseError, + chromaDelayHorizontal, + chromaDelayVertical, + vhsTapeSpeed, + vhsChromaLoss, + vhsSharpenIntensity, + vhsSharpenFrequency, + vhsEdgeWaveIntensity, + vhsEdgeWaveSpeed, + vhsEdgeWaveFrequency, + vhsEdgeWaveDetail, + chromaLowpassOut, + scaleHorizontal, + scaleVertical, + scaleFactorX, + scaleFactorY, + headSwitchingEnabled, + trackingNoiseEnabled, + compositeNoiseEnabled, + ringingEnabled, + lumaNoiseEnabled, + chromaNoiseEnabled, + vhsEnabled, + scaleEnabled + ).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 == FlareParams::class.simpleName -> { + val (radius, baseAmount, ringAmount, rayAmount, ringWidth, centreX, centreY, color) = value.split( + PROPERTIES_SEPARATOR + ) + FlareParams( + radius = radius.toFloat(), + baseAmount = baseAmount.toFloat(), + ringAmount = ringAmount.toFloat(), + rayAmount = rayAmount.toFloat(), + ringWidth = ringWidth.toFloat(), + centreX = centreX.toFloat(), + centreY = centreY.toFloat(), + color = color.toInt().toColorModel() + ) + } + + name == DistortPerspectiveParams::class.simpleName -> { + val ( + topLeftX, + topLeftY, + topRightX, + topRightY, + bottomLeftX, + bottomLeftY, + bottomRightX, + bottomRightY, + clip + ) = value.split(PROPERTIES_SEPARATOR) + DistortPerspectiveParams( + topLeft = topLeftX.toFloat() to topLeftY.toFloat(), + topRight = topRightX.toFloat() to topRightY.toFloat(), + bottomLeft = bottomLeftX.toFloat() to bottomLeftY.toFloat(), + bottomRight = bottomRightX.toFloat() to bottomRightY.toFloat(), + clip = clip.toBoolean() + ) + } + + name == ShearParams::class.simpleName -> { + val (xAngle, yAngle, resize) = value.split(PROPERTIES_SEPARATOR) + ShearParams( + xAngle = xAngle.toFloat(), + yAngle = yAngle.toFloat(), + resize = resize.toBoolean() + ) + } + + name == WaterDropParams::class.simpleName -> { + val (wavelength, amplitude, phase, centreX, centreY, radius) = value.split( + PROPERTIES_SEPARATOR + ) + WaterDropParams( + wavelength = wavelength.toFloat(), + amplitude = amplitude.toFloat(), + phase = phase.toFloat(), + centreX = centreX.toFloat(), + centreY = centreY.toFloat(), + radius = radius.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 == DropShadowParams::class.simpleName -> { + val (color, offsetX, offsetY, blurRadius) = value.split( + PROPERTIES_SEPARATOR + ) + + DropShadowParams( + color = color.toInt().toColorModel(), + offsetX = offsetX.toFloat(), + offsetY = offsetY.toFloat(), + blurRadius = blurRadius.toFloat() + ) + } + + name == TornEdgeParams::class.simpleName -> { + val ( + toothHeight, + horizontalToothRange, + verticalToothRange, + top, + right, + bottom, + left + ) = value.split(PROPERTIES_SEPARATOR) + + TornEdgeParams( + toothHeight = toothHeight.toInt(), + horizontalToothRange = horizontalToothRange.toInt(), + verticalToothRange = verticalToothRange.toInt(), + top = top.toBoolean(), + right = right.toBoolean(), + bottom = bottom.toBoolean(), + left = left.toBoolean() + ) + } + + name == NtscParams::class.simpleName -> { + val parts = value.split(PROPERTIES_SEPARATOR) + val default = NtscParams() + + fun floatAt(index: Int, fallback: Float): Float { + return parts.getOrNull(index)?.toFloatOrNull() ?: fallback + } + + fun intAt(index: Int, fallback: Int): Int { + return parts.getOrNull(index)?.toIntOrNull() ?: fallback + } + + fun booleanAt(index: Int, fallback: Boolean): Boolean { + return when (parts.getOrNull(index)) { + "true" -> true + "false" -> false + else -> fallback + } + } + + NtscParams( + amount = floatAt(0, default.amount), + chromaBleed = floatAt(1, default.chromaBleed), + tapeWear = floatAt(2, default.tapeWear), + noise = floatAt(3, default.noise), + tracking = floatAt(4, default.tracking), + seed = intAt(5, default.seed), + lumaSmear = floatAt(6, default.lumaSmear), + compositeSharpening = floatAt(7, default.compositeSharpening), + ringing = floatAt(8, default.ringing), + snow = floatAt(9, default.snow), + useField = intAt(10, default.useField), + filterType = intAt(11, default.filterType), + inputLumaFilter = intAt(12, default.inputLumaFilter), + chromaLowpassIn = intAt(13, default.chromaLowpassIn), + chromaDemodulation = intAt(14, default.chromaDemodulation), + videoScanlinePhaseShift = intAt(15, default.videoScanlinePhaseShift), + videoScanlinePhaseShiftOffset = intAt(16, default.videoScanlinePhaseShiftOffset), + headSwitchingEnabled = booleanAt(57, default.headSwitchingEnabled), + headSwitchingHeight = intAt(17, default.headSwitchingHeight), + headSwitchingOffset = intAt(18, default.headSwitchingOffset), + headSwitchingHorizontalShift = floatAt(19, default.headSwitchingHorizontalShift), + headSwitchingMidLinePosition = floatAt(20, default.headSwitchingMidLinePosition), + headSwitchingMidLineJitter = floatAt(21, default.headSwitchingMidLineJitter), + trackingNoiseEnabled = booleanAt(58, default.trackingNoiseEnabled), + trackingNoiseHeight = intAt(22, default.trackingNoiseHeight), + trackingNoiseWaveIntensity = floatAt(23, default.trackingNoiseWaveIntensity), + trackingNoiseSnowIntensity = floatAt(24, default.trackingNoiseSnowIntensity), + trackingNoiseSnowAnisotropy = floatAt(25, default.trackingNoiseSnowAnisotropy), + trackingNoiseNoiseIntensity = floatAt(26, default.trackingNoiseNoiseIntensity), + compositeNoiseEnabled = booleanAt(59, default.compositeNoiseEnabled), + compositeNoiseFrequency = floatAt(27, default.compositeNoiseFrequency), + compositeNoiseIntensity = floatAt(28, default.compositeNoiseIntensity), + compositeNoiseDetail = intAt(29, default.compositeNoiseDetail), + ringingEnabled = booleanAt(60, default.ringingEnabled), + ringingFrequency = floatAt(30, default.ringingFrequency), + ringingPower = floatAt(31, default.ringingPower), + lumaNoiseEnabled = booleanAt(61, default.lumaNoiseEnabled), + lumaNoiseFrequency = floatAt(32, default.lumaNoiseFrequency), + lumaNoiseIntensity = floatAt(33, default.lumaNoiseIntensity), + lumaNoiseDetail = intAt(34, default.lumaNoiseDetail), + chromaNoiseEnabled = booleanAt(62, default.chromaNoiseEnabled), + chromaNoiseFrequency = floatAt(35, default.chromaNoiseFrequency), + chromaNoiseIntensity = floatAt(36, default.chromaNoiseIntensity), + chromaNoiseDetail = intAt(37, default.chromaNoiseDetail), + snowIntensity = floatAt(38, default.snowIntensity), + snowAnisotropy = floatAt(39, default.snowAnisotropy), + chromaPhaseNoiseIntensity = floatAt(40, default.chromaPhaseNoiseIntensity), + chromaPhaseError = floatAt(41, default.chromaPhaseError), + chromaDelayHorizontal = floatAt(42, default.chromaDelayHorizontal), + chromaDelayVertical = intAt(43, default.chromaDelayVertical), + vhsEnabled = booleanAt(63, default.vhsEnabled), + vhsTapeSpeed = intAt(44, default.vhsTapeSpeed), + vhsChromaLoss = floatAt(45, default.vhsChromaLoss), + vhsSharpenIntensity = floatAt(46, default.vhsSharpenIntensity), + vhsSharpenFrequency = floatAt(47, default.vhsSharpenFrequency), + vhsEdgeWaveIntensity = floatAt(48, default.vhsEdgeWaveIntensity), + vhsEdgeWaveSpeed = floatAt(49, default.vhsEdgeWaveSpeed), + vhsEdgeWaveFrequency = floatAt(50, default.vhsEdgeWaveFrequency), + vhsEdgeWaveDetail = intAt(51, default.vhsEdgeWaveDetail), + chromaLowpassOut = intAt(52, default.chromaLowpassOut), + scaleEnabled = booleanAt(64, default.scaleEnabled), + scaleHorizontal = floatAt(53, default.scaleHorizontal), + scaleVertical = floatAt(54, default.scaleVertical), + scaleFactorX = floatAt(55, default.scaleFactorX), + scaleFactorY = floatAt(56, default.scaleFactorY) + ) + } + + 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 = "*" diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/di/FilterModule.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/di/FilterModule.kt new file mode 100644 index 0000000..acbaa7d --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/di/FilterModule.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.core.filters.domain.ShaderPresetRepository +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.data.AndroidShaderPresetRepository +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 + + @Singleton + @Binds + fun shaderPresetRepository( + repository: AndroidShaderPresetRepository + ): ShaderPresetRepository + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/domain/FilterMask.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/domain/FilterMask.kt new file mode 100644 index 0000000..255ca14 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/domain/FilterMask.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/domain/FilterMaskApplier.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/domain/FilterMaskApplier.kt new file mode 100644 index 0000000..5559127 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/domain/FilterMaskApplier.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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? + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/FiltersContent.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/FiltersContent.kt new file mode 100644 index 0000000..962c356 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/FiltersContent.kt @@ -0,0 +1,320 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.foundation.rememberScrollState +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Tune +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.controls.UndoRedoButtons +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.enhanced.enhancedHorizontalScroll +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.fadingEdges +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 = { + val state = rememberScrollState() + Row( + modifier = Modifier + .fadingEdges(state) + .enhancedHorizontalScroll(state), + verticalAlignment = Alignment.CenterVertically + ) { + 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) + ) + } + } + if (isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + } + } + + 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 + ) +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/BasicFilterPreference.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/BasicFilterPreference.kt new file mode 100644 index 0000000..93369b0 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/BasicFilterPreference.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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.AutoFixHigh +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 + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/BasicFilterState.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/BasicFilterState.kt new file mode 100644 index 0000000..6885441 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/BasicFilterState.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 +) \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentActionButtons.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentActionButtons.kt new file mode 100644 index 0000000..002b318 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentActionButtons.kt @@ -0,0 +1,142 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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 com.t8rin.imagetoolbox.core.resources.icons.AutoFixHigh +import com.t8rin.imagetoolbox.core.resources.icons.Texture +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.Outlined.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 + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentControls.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentControls.kt new file mode 100644 index 0000000..c1d9405 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentControls.kt @@ -0,0 +1,391 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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 androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.t8rin.imagetoolbox.core.filters.domain.model.TemplateFilter +import com.t8rin.imagetoolbox.core.filters.domain.model.params.SeamCarvingParams +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Texture +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) + } + var seamMaskFilterIndex by rememberSaveable(filterType) { + mutableStateOf(null) + } + val shaderPresets by component.addFiltersSheetComponent.shaderPresets.collectAsStateWithLifecycle() + + 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, + shaderPresets = shaderPresets, + onImportShaderPreset = component.addFiltersSheetComponent::importShaderPreset, + onRemove = { + component.removeFilterAtIndex(index) + }, + onDuplicate = { + component.duplicateFilterAtIndex(index) + }, + onCreateTemplate = { + showTemplateCreationSheet = true + component.filterTemplateCreationSheetComponent.setInitialTemplateFilter( + TemplateFilter( + name = getString(filter.title), + filters = listOf(filter) + ) + ) + }, + additionalContent = (filter.value as? SeamCarvingParams)?.let { params -> + { + SeamCarvingMaskItem( + maskUri = params.maskFile.uri, + useMaskAsRemoval = params.useMaskAsRemoval, + onUseMaskAsRemovalChange = { useMaskAsRemoval -> + component.updateFilter( + value = params.copy( + useMaskAsRemoval = useMaskAsRemoval + ), + index = index + ) + }, + onAddMask = { + seamMaskFilterIndex = index + }, + onRemoveMask = { + component.removeSeamCarvingMask(index) + } + ) + } + } + ) + } + 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.Outlined.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.Outlined.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 + } + + seamMaskFilterIndex?.let { index -> + val params = component.basicFilterState.filters + .getOrNull(index) + ?.value as? SeamCarvingParams + + if (params?.maskFile?.uri?.isEmpty() == true) { + SeamCarvingMaskSheet( + visible = true, + bitmap = component.bitmap, + onDismiss = { seamMaskFilterIndex = null }, + onSave = { paths -> + component.updateSeamCarvingMask( + filterIndex = index, + paths = paths, + onComplete = {} + ) + } + ) + } + } + + FilterTemplateCreationSheet( + component = component.filterTemplateCreationSheetComponent, + visible = showTemplateCreationSheet, + onDismiss = { showTemplateCreationSheet = false } + ) +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentNoData.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentNoData.kt new file mode 100644 index 0000000..b0225ce --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentNoData.kt @@ -0,0 +1,162 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.FileOpen +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 + ) + } + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentSheets.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentSheets.kt new file mode 100644 index 0000000..75b050e --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentSheets.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentTopAppBarActions.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentTopAppBarActions.kt new file mode 100644 index 0000000..70e4302 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentTopAppBarActions.kt @@ -0,0 +1,139 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AutoFixHigh +import com.t8rin.imagetoolbox.core.resources.icons.Eyedropper +import com.t8rin.imagetoolbox.core.resources.icons.Texture +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.controls.UndoRedoButtons +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) { + if (!isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo + ) + } + + 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.Outlined.Texture, + contentDescription = stringResource(R.string.add_mask) + ) + } + } + + null -> Unit + } + } else { + actions() + } + } +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskFilterPreference.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskFilterPreference.kt new file mode 100644 index 0000000..311f2de --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskFilterPreference.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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.Texture +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.Outlined.Texture, + title = stringResource(R.string.mask_filter), + subtitle = stringResource(R.string.mask_filter_sub), + containerColor = color, + modifier = modifier + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskItem.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskItem.kt new file mode 100644 index 0000000..941a79c --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskItem.kt @@ -0,0 +1,331 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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 androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderPreset +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.DragHandle +import com.t8rin.imagetoolbox.core.resources.icons.EditAlt +import com.t8rin.imagetoolbox.core.resources.icons.RemoveCircle +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 shaderPresets by addMaskSheetComponent + ?.addFiltersSheetComponent + ?.shaderPresets + ?.collectAsStateWithLifecycle() + ?: remember { mutableStateOf(emptyList()) } + val importShaderPreset: (suspend (Uri) -> ShaderPreset?)? = addMaskSheetComponent + ?.addFiltersSheetComponent + ?.let { component -> + { uri: Uri -> component.importShaderPreset(uri) } + } + 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.Outlined.RemoveCircle, + 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, + shaderPresets = shaderPresets, + onImportShaderPreset = importShaderPreset, + 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 + ) + } +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskReorderSheet.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskReorderSheet.kt new file mode 100644 index 0000000..5197a59 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskReorderSheet.kt @@ -0,0 +1,143 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Reorder +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)) + } + }, + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskingFilterState.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskingFilterState.kt new file mode 100644 index 0000000..3569bd4 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskingFilterState.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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() +) \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/PathPaintPreview.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/PathPaintPreview.kt new file mode 100644 index 0000000..f1774b7 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/PathPaintPreview.kt @@ -0,0 +1,158 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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) + ) + } + ) + } +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/PreviewState.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/PreviewState.kt new file mode 100644 index 0000000..936941f --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/PreviewState.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo +import com.t8rin.imagetoolbox.core.filters.presentation.model.FilterPreviewKey +import com.t8rin.imagetoolbox.feature.draw.domain.PathPaint + +internal data class PreviewRequest( + val bitmapId: Int, + val imageInfo: ImageInfo, + val filterType: String?, + val basicState: BasicPreviewState?, + val maskingState: MaskingPreviewState? +) + +internal data class BasicPreviewState( + val uris: List?, + val selectedUri: Uri?, + val filters: List +) + +internal data class MaskingPreviewState( + val uri: Uri?, + val masks: List +) + +internal data class MaskPreviewState( + val filters: List, + val maskPaints: List>, + val isInverseFillType: Boolean +) \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/SeamCarvingMaskItem.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/SeamCarvingMaskItem.kt new file mode 100644 index 0000000..6099d98 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/SeamCarvingMaskItem.kt @@ -0,0 +1,213 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.border +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.foundation.layout.sizeIn +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.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.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.RemoveCircle +import com.t8rin.imagetoolbox.core.resources.icons.Texture +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.ShapeDefaults +import com.t8rin.imagetoolbox.core.ui.widget.modifier.container +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch + +@Composable +internal fun SeamCarvingMaskItem( + maskUri: String, + useMaskAsRemoval: Boolean, + onUseMaskAsRemovalChange: (Boolean) -> Unit, + onAddMask: () -> Unit, + onRemoveMask: () -> Unit +) { + if (maskUri.isEmpty()) { + EnhancedButton( + containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(0.5f), + contentColor = MaterialTheme.colorScheme.onErrorContainer, + onClick = onAddMask, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + .padding(bottom = 8.dp) + ) { + Icon( + imageVector = Icons.Outlined.Texture, + contentDescription = stringResource(R.string.add_mask) + ) + Spacer(Modifier.width(8.dp)) + Text(stringResource(id = R.string.add_mask)) + } + } else { + var showMaskRemoveDialog by rememberSaveable { mutableStateOf(false) } + + PreferenceRowSwitch( + title = stringResource(R.string.seam_carving_mask_as_removal), + subtitle = stringResource(R.string.seam_carving_mask_as_removal_sub), + checked = useMaskAsRemoval, + onClick = onUseMaskAsRemovalChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + .padding(bottom = 8.dp), + applyHorizontalPadding = false, + startContent = {}, + resultModifier = Modifier.padding(16.dp), + containerColor = Color.Unspecified, + shape = ShapeDefaults.extraLarge + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + .padding(bottom = 8.dp) + .container( + color = MaterialTheme.colorScheme.secondaryContainer.copy(0.5f), + shape = ShapeDefaults.extraLarge + ) + .padding(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Spacer(Modifier.width(4.dp)) + SeamCarvingMaskPreview( + maskUri = maskUri, + modifier = Modifier.size(42.dp) + ) + Text( + text = stringResource(R.string.mask), + fontWeight = FontWeight.SemiBold, + modifier = Modifier.padding(start = 16.dp) + ) + Spacer(Modifier.weight(1f)) + EnhancedIconButton( + onClick = { showMaskRemoveDialog = true } + ) { + Icon( + imageVector = Icons.Outlined.RemoveCircle, + contentDescription = stringResource(R.string.remove) + ) + } + } + + 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 + onRemoveMask() + } + ) { + 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 + ) { + SeamCarvingMaskPreview( + maskUri = maskUri, + modifier = Modifier.sizeIn( + maxHeight = 80.dp, + maxWidth = 80.dp + ) + ) + Spacer(modifier = Modifier.height(8.dp)) + Text(stringResource(R.string.delete_mask_warn)) + } + } + ) + } +} + +@Composable +private fun SeamCarvingMaskPreview( + maskUri: String, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .aspectRatio(1f) + .border( + width = 1.dp, + color = MaterialTheme.colorScheme.outlineVariant(), + shape = ShapeDefaults.extraSmall + ) + .clip(ShapeDefaults.extraSmall) + ) { + AsyncImage( + model = maskUri, + contentDescription = stringResource(R.string.mask), + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Fit + ) + } +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/SeamCarvingMaskSheet.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/SeamCarvingMaskSheet.kt new file mode 100644 index 0000000..b051f09 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/SeamCarvingMaskSheet.kt @@ -0,0 +1,433 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.Bitmap +import androidx.activity.compose.BackHandler +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.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.aspectRatio +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.foundation.lazy.LazyColumn +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.graphics.asImageBitmap +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.domain.model.pt +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.Redo +import com.t8rin.imagetoolbox.core.resources.icons.Texture +import com.t8rin.imagetoolbox.core.resources.icons.Undo +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.buttons.EraseModeButton +import com.t8rin.imagetoolbox.core.ui.widget.buttons.PanModeButton +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.EnhancedLoadingIndicator +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.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.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.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.DrawPathModeSelector +import com.t8rin.imagetoolbox.feature.draw.presentation.components.LineWidthSelector +import com.t8rin.imagetoolbox.feature.draw.presentation.components.UiPathPaint +import net.engawapg.lib.zoomable.rememberZoomState + +@Composable +internal fun SeamCarvingMaskSheet( + visible: Boolean, + bitmap: Bitmap?, + onDismiss: () -> Unit, + onSave: (List) -> Unit +) { + val settingsState = LocalSettingsState.current + val isPortrait by isPortraitOrientationAsState() + + var paths by remember(visible) { mutableStateOf(emptyList()) } + var undonePaths by remember(visible) { mutableStateOf(emptyList()) } + var strokeWidth by rememberSaveable(visible, stateSaver = PtSaver) { + mutableStateOf(settingsState.defaultDrawLineWidth.pt) + } + var brushSoftness by rememberSaveable(visible, stateSaver = PtSaver) { + mutableStateOf(20.pt) + } + var panEnabled by rememberSaveable(visible) { mutableStateOf(false) } + var isEraserOn by rememberSaveable(visible) { mutableStateOf(false) } + var drawPathMode by remember(visible) { mutableStateOf(DrawPathMode.Free) } + var showExitDialog by remember { mutableStateOf(false) } + var imageState by remember { mutableStateOf(ImageHeaderState(2)) } + + EnhancedModalBottomSheet( + visible = visible, + onDismiss = { + if (paths.isEmpty()) onDismiss() + else showExitDialog = true + }, + cancelable = false, + title = { + TitleItem( + text = stringResource(id = R.string.add_mask), + icon = Icons.Outlined.Texture + ) + }, + confirmButton = { + EnhancedButton( + enabled = paths.isNotEmpty(), + containerColor = MaterialTheme.colorScheme.secondaryContainer, + onClick = { + onSave(paths) + 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 (paths.isEmpty()) onDismiss() + else showExitDialog = true + } + ) { + Icon( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = stringResource(R.string.exit) + ) + } + } + }, + enableBackHandler = paths.isEmpty() + ) { + if (visible) { + BackHandler( + enabled = paths.isNotEmpty() + ) { + showExitDialog = true + } + } + + val drawPreview: @Composable () -> Unit = { + SeamCarvingMaskBitmapPreview( + bitmap = bitmap, + imageState = imageState, + paths = paths, + strokeWidth = strokeWidth, + brushSoftness = brushSoftness, + isEraserOn = isEraserOn, + panEnabled = panEnabled, + drawPathMode = drawPathMode, + onAddPath = { + paths = paths + it + undonePaths = emptyList() + } + ) + } + + 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 { + SeamCarvingMaskSheetControls( + pathsEmpty = paths.isEmpty(), + undonePathsEmpty = undonePaths.isEmpty(), + strokeWidth = strokeWidth, + onStrokeWidthChange = { strokeWidth = it.pt }, + brushSoftness = brushSoftness, + onBrushSoftnessChange = { brushSoftness = it.pt }, + panEnabled = panEnabled, + onTogglePanEnabled = { panEnabled = !panEnabled }, + isEraserOn = isEraserOn, + onToggleIsEraserOn = { isEraserOn = !isEraserOn }, + drawPathMode = drawPathMode, + onDrawPathModeChange = { drawPathMode = it }, + onUndo = { + paths.lastOrNull()?.let { + paths = paths - it + undonePaths = undonePaths + it + } + }, + onRedo = { + undonePaths.lastOrNull()?.let { + paths = paths + it + undonePaths = undonePaths - it + } + } + ) + } + } + } + } + + ExitWithoutSavingDialog( + onExit = onDismiss, + onDismiss = { showExitDialog = false }, + visible = showExitDialog, + placeAboveAll = true + ) +} + +@Composable +private fun SeamCarvingMaskBitmapPreview( + bitmap: Bitmap?, + imageState: ImageHeaderState, + paths: List, + strokeWidth: Pt, + brushSoftness: Pt, + isEraserOn: Boolean, + panEnabled: Boolean, + drawPathMode: DrawPathMode, + onAddPath: (UiPathPaint) -> Unit +) { + val zoomState = rememberZoomState(maxScale = 30f, key = imageState) + val isPortrait by isPortraitOrientationAsState() + + AnimatedContent( + targetState = bitmap, + 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) + ) + ) { image -> + if (image == null) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + contentAlignment = Alignment.Center + ) { + EnhancedLoadingIndicator() + } + } else { + BitmapDrawer( + zoomState = zoomState, + imageBitmap = image.asImageBitmap(), + paths = paths, + strokeWidth = strokeWidth, + brushSoftness = brushSoftness, + drawColor = Color.Red.copy(alpha = 0.55f), + onAddPath = onAddPath, + isEraserOn = isEraserOn, + drawMode = DrawMode.Pen, + modifier = Modifier + .padding(16.dp) + .aspectRatio(image.width / image.height.toFloat(), isPortrait) + .fillMaxSize(), + panEnabled = panEnabled, + onRequestFiltering = { source, _ -> source }, + drawPathMode = drawPathMode, + backgroundColor = Color.Transparent + ) + } + } +} + +@Composable +private fun SeamCarvingMaskSheetControls( + pathsEmpty: Boolean, + undonePathsEmpty: Boolean, + strokeWidth: Pt, + onStrokeWidthChange: (Float) -> Unit, + brushSoftness: Pt, + onBrushSoftnessChange: (Float) -> Unit, + panEnabled: Boolean, + onTogglePanEnabled: () -> Unit, + isEraserOn: Boolean, + onToggleIsEraserOn: () -> Unit, + drawPathMode: DrawPathMode, + onDrawPathModeChange: (DrawPathMode) -> Unit, + onUndo: () -> Unit, + onRedo: () -> Unit +) { + Row( + modifier = 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 = onUndo, + enabled = !pathsEmpty + ) { + Icon( + imageVector = Icons.Rounded.Undo, + contentDescription = "Undo" + ) + } + EnhancedIconButton( + containerColor = Color.Transparent, + borderColor = MaterialTheme.colorScheme.outlineVariant(luminance = 0.1f), + onClick = onRedo, + enabled = !undonePathsEmpty + ) { + Icon( + imageVector = Icons.Rounded.Redo, + contentDescription = "Redo" + ) + } + EraseModeButton( + selected = isEraserOn, + enabled = !panEnabled, + onClick = onToggleIsEraserOn + ) + } + + 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 = drawPathMode, + onValueChange = onDrawPathModeChange, + drawMode = DrawMode.Pen + ) + AnimatedVisibility( + visible = drawPathMode.canChangeStrokeWidth, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically(), + modifier = Modifier.fillMaxWidth() + ) { + LineWidthSelector( + modifier = Modifier.padding( + start = 16.dp, + end = 16.dp, + top = 8.dp + ), + color = Color.Unspecified, + value = strokeWidth.value, + onValueChange = onStrokeWidthChange + ) + } + BrushSoftnessSelector( + modifier = Modifier.padding( + start = 16.dp, + end = 16.dp, + top = 8.dp, + bottom = 16.dp + ), + color = Color.Unspecified, + value = brushSoftness.value, + onValueChange = onBrushSoftnessChange + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/UiFilterMask.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/UiFilterMask.kt new file mode 100644 index 0000000..fe0fb40 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/UiFilterMask.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddEditMaskSheet.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddEditMaskSheet.kt new file mode 100644 index 0000000..b67ff8c --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddEditMaskSheet.kt @@ -0,0 +1,220 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.Texture +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.Outlined.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.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 + ) +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddEditMaskSheetControls.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddEditMaskSheetControls.kt new file mode 100644 index 0000000..ce42f85 --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddEditMaskSheetControls.kt @@ -0,0 +1,406 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.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.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 androidx.lifecycle.compose.collectAsStateWithLifecycle +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Preview +import com.t8rin.imagetoolbox.core.resources.icons.Redo +import com.t8rin.imagetoolbox.core.resources.icons.Undo +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 shaderPresets by component.addFiltersSheetComponent.shaderPresets.collectAsStateWithLifecycle() + + 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.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.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 + ) + AnimatedVisibility( + visible = component.drawPathMode.canChangeStrokeWidth, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically(), + modifier = Modifier.fillMaxWidth() + ) { + 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, + shaderPresets = shaderPresets, + onImportShaderPreset = component.addFiltersSheetComponent::importShaderPreset, + 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 + ) +} diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddMaskSheetBitmapPreview.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddMaskSheetBitmapPreview.kt new file mode 100644 index 0000000..bcc162b --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddMaskSheetBitmapPreview.kt @@ -0,0 +1,131 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + ) + } + } +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddMaskSheetComponent.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddMaskSheetComponent.kt new file mode 100644 index 0000000..9d4a01c --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddMaskSheetComponent.kt @@ -0,0 +1,305 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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 + } + +} \ No newline at end of file diff --git a/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/screenLogic/FiltersComponent.kt b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/screenLogic/FiltersComponent.kt new file mode 100644 index 0000000..2333d4a --- /dev/null +++ b/feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/screenLogic/FiltersComponent.kt @@ -0,0 +1,1191 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the 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.graphics.BlurMaskFilter +import android.graphics.PorterDuff +import android.graphics.PorterDuffXfermode +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.compose.ui.graphics.asAndroidPath +import androidx.compose.ui.graphics.toArgb +import androidx.core.graphics.applyCanvas +import androidx.core.graphics.createBitmap +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.model.ColorModel +import com.t8rin.imagetoolbox.core.domain.model.FileModel +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.filters.domain.FilterProvider +import com.t8rin.imagetoolbox.core.filters.domain.model.params.SeamCarvingParams +import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter +import com.t8rin.imagetoolbox.core.filters.presentation.model.hasSameState +import com.t8rin.imagetoolbox.core.filters.presentation.model.hasSameValue +import com.t8rin.imagetoolbox.core.filters.presentation.model.previewKey +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.SettingsManager +import com.t8rin.imagetoolbox.core.ui.transformation.ImageInfoTransformation +import com.t8rin.imagetoolbox.core.ui.utils.BaseHistoryComponent +import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost +import com.t8rin.imagetoolbox.core.ui.utils.helper.scaleToFitCanvas +import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen +import com.t8rin.imagetoolbox.core.ui.utils.state.update +import com.t8rin.imagetoolbox.feature.draw.presentation.components.UiPathPaint +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.BasicPreviewState +import com.t8rin.imagetoolbox.feature.filters.presentation.components.MaskPreviewState +import com.t8rin.imagetoolbox.feature.filters.presentation.components.MaskingFilterState +import com.t8rin.imagetoolbox.feature.filters.presentation.components.MaskingPreviewState +import com.t8rin.imagetoolbox.feature.filters.presentation.components.PreviewRequest +import com.t8rin.imagetoolbox.feature.filters.presentation.components.UiFilterMask +import com.t8rin.imagetoolbox.feature.filters.presentation.components.addEditMaskSheet.AddMaskSheetComponent +import com.t8rin.imagetoolbox.feature.filters.presentation.screenLogic.FiltersComponent.HistorySnapshot +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext +import android.graphics.Color as NativeColor +import android.graphics.Paint as NativePaint + +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, + private val settingsManager: SettingsManager, + dispatchersHolder: DispatchersHolder, + addFiltersSheetComponentFactory: AddFiltersSheetComponent.Factory, + filterTemplateCreationSheetComponent: FilterTemplateCreationSheetComponent.Factory, + addMaskSheetComponentFactory: AddMaskSheetComponent.Factory, +) : BaseHistoryComponent( + dispatchersHolder = dispatchersHolder, + componentContext = 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) { + if (_imageInfo.value.imageFormat == imageFormat) return + if (pendingHistoryMode != PendingHistoryMode.FormatChange) { + finalizePendingHistoryTransaction() + } + beginPendingHistoryTransaction( + mode = PendingHistoryMode.FormatChange, + commitDelayMillis = formatHistoryTransactionDebounce + ) + _imageInfo.value = _imageInfo.value.copy( + imageFormat = imageFormat, + quality = imageInfo.quality.coerceIn(imageFormat) + ) + updatePreview() + registerChanges() + schedulePendingHistoryCommit() + } + + fun setBasicFilter(uris: List?) { + clearHistory() + registerChangesCleared() + _filterType.update { + it as? Screen.Filter.Type.Basic ?: Screen.Filter.Type.Basic(uris) + } + val selectedUri = uris?.firstOrNull() + _basicFilterState.update { + it.copy( + uris = uris, + selectedUri = selectedUri + ) + } + selectedUri?.let { + updateSelectedUriInternal( + uri = it, + resetHistoryAfterLoad = true + ) + } + } + + 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) { + if (_keepExif.value == boolean) return + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _keepExif.value = boolean + commitHistoryFrom(beforeSnapshot) + } + + 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 = {} + ) = updateSelectedUriInternal( + uri = uri, + onFailure = onFailure + ) + + private fun updateSelectedUriInternal( + uri: Uri, + onFailure: (Throwable) -> Unit = {}, + resetHistoryAfterLoad: Boolean = false + ) { + 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 + ) + } + _basicFilterState.update { + it.copy(selectedUri = uri) + } + updatePreview() + if (resetHistoryAfterLoad) { + resetHistory() + registerChangesCleared() + } + _isImageLoading.update { false } + } + }.onFailure(onFailure) + } + + private fun updateCanSave() { + _canSave.value = calculateCanSave() + registerChanges() + } + + private fun calculateCanSave(): Boolean = + _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() + ) + + private var filterJob: Job? by smartJob() + + private var lastPreviewRequest: PreviewRequest? = null + + fun updateFilter( + value: T, + index: Int + ) { + val list = _basicFilterState.value.filters.toMutableList() + runCatching { + val current = list[index] + val previewRequest = currentPreviewRequest() + + if (current.hasSameValue(value) && previewRequest == lastPreviewRequest) { + return@updateFilter + } + + val new = current.copy(value) + + if (current.hasSameState(new) && previewRequest == lastPreviewRequest) { + return@updateFilter + } + + beginPendingHistoryTransaction() + list[index] = new + _basicFilterState.update { + it.copy(filters = list) + } + }.onFailure { throwable -> + AppToastHost.showFailureToast(throwable) + beginPendingHistoryTransaction() + list[index] = list[index].newInstance() + _basicFilterState.update { + it.copy(filters = list) + } + } + updateCanSave() + updatePreview() + schedulePendingHistoryCommit() + } + + fun updateSeamCarvingMask( + filterIndex: Int, + paths: List, + onComplete: () -> Unit + ) { + val bitmap = bitmap ?: return + val filter = basicFilterState.filters.getOrNull(filterIndex) ?: return + val params = filter.value as? SeamCarvingParams ?: return + + componentScope.launch { + val uri = withContext(defaultDispatcher) { + val mask = renderSeamCarvingMask( + paths = paths, + width = bitmap.width, + height = bitmap.height + ) + shareProvider.cacheImage( + image = mask, + imageInfo = ImageInfo( + width = mask.width, + height = mask.height, + imageFormat = ImageFormat.Png.Lossless + ), + filename = "seam_carving_mask.png" + ) + } ?: return@launch + + updateFilter( + value = params.copy(maskFile = FileModel(uri)), + index = filterIndex + ) + onComplete() + } + } + + fun removeSeamCarvingMask(filterIndex: Int) { + val filter = basicFilterState.filters.getOrNull(filterIndex) ?: return + val params = filter.value as? SeamCarvingParams ?: return + + updateFilter( + value = params.copy( + maskFile = FileModel(""), + useMaskAsRemoval = false, + ), + index = filterIndex + ) + } + + private fun renderSeamCarvingMask( + paths: List, + width: Int, + height: Int + ): Bitmap = createBitmap(width, height).applyCanvas { + val canvasSize = IntegerSize(width, height) + drawColor(NativeColor.BLACK) + + paths.forEach { pathPaint -> + val path = pathPaint.path.scaleToFitCanvas( + currentSize = canvasSize, + oldSize = pathPaint.canvasSize + ).asAndroidPath() + val drawPathMode = pathPaint.drawPathMode + + drawPath( + path, + NativePaint(NativePaint.ANTI_ALIAS_FLAG).apply { + color = Color.White.toArgb() + style = if (!pathPaint.isErasing && drawPathMode.isFilled) { + NativePaint.Style.FILL + } else { + NativePaint.Style.STROKE + } + strokeWidth = pathPaint.strokeWidth.toPx(canvasSize) + strokeCap = if (drawPathMode.isSharpEdge && !pathPaint.isErasing) { + NativePaint.Cap.SQUARE + } else { + NativePaint.Cap.ROUND + } + strokeJoin = NativePaint.Join.ROUND + + if (pathPaint.isErasing) { + xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) + } + if (pathPaint.brushSoftness.value > 0f) { + maskFilter = BlurMaskFilter( + pathPaint.brushSoftness.toPx(canvasSize), + BlurMaskFilter.Blur.NORMAL + ) + } + } + ) + } + } + + fun updateFiltersOrder(value: List>) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _basicFilterState.update { + it.copy(filters = value) + } + filterJob = null + updateCanSave() + updatePreview() + commitHistoryFrom(beforeSnapshot) + } + + fun addFilterNewInstance(filter: UiFilter<*>) { + addFilter(filter.newInstance()) + } + + fun addFilter(filter: UiFilter<*>) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _basicFilterState.update { + it.copy(filters = it.filters + filter) + } + updateCanSave() + filterJob = null + updatePreview() + commitHistoryFrom(beforeSnapshot) + } + + fun removeFilterAtIndex(index: Int) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _basicFilterState.update { + it.copy( + filters = it.filters.toMutableList().apply { + removeAt(index) + } + ) + } + updateCanSave() + filterJob = null + updatePreview() + commitHistoryFrom(beforeSnapshot) + } + + fun duplicateFilterAtIndex(index: Int) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _basicFilterState.update { + it.copy( + filters = it.filters.toMutableList().apply { + val filter = get(index) + add( + index = index + 1, + element = filter.copy(filter.value).apply { + isVisible = filter.isVisible + } + ) + } + ) + } + updateCanSave() + filterJob = null + updatePreview() + commitHistoryFrom(beforeSnapshot) + } + + 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 { imageData -> + imageData.image to imageData.imageInfo.copy( + imageFormat = imageInfo.imageFormat, + quality = imageInfo.quality + ) + } + }, + 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) { + val coercedQuality = quality.coerceIn(imageInfo.imageFormat) + if (_imageInfo.value.quality == coercedQuality) return + beginPendingHistoryTransaction() + _imageInfo.update(onValueChanged = ::updatePreview) { + it.copy(quality = coercedQuality) + } + registerChanges() + schedulePendingHistoryCommit() + } + + private fun updatePreview() { + _bitmap.value?.let { bitmap -> + val previewRequest = currentPreviewRequest(bitmap) + if (_previewBitmap.value != null && previewRequest == lastPreviewRequest) return + + lastPreviewRequest = previewRequest + 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?) { + clearHistory() + registerChangesCleared() + _filterType.update { + it as? Screen.Filter.Type.Masking ?: Screen.Filter.Type.Masking(uri) + } + uri?.let { + updateSelectedUriInternal( + uri = it, + resetHistoryAfterLoad = true + ) + } + _maskingFilterState.value = MaskingFilterState(uri) + updatePreview() + updateCanSave() + } + + fun clearType() { + clearHistory() + _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) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _maskingFilterState.update { + it.copy(masks = uiFilterMasks) + } + updatePreview() + updateCanSave() + commitHistoryFrom(beforeSnapshot) + } + + fun updateMask( + value: UiFilterMask, + index: Int + ) { + runCatching { + beginPendingHistoryTransaction() + _maskingFilterState.update { + it.copy( + masks = it.masks.toMutableList().apply { + this[index] = value + } + ) + } + updatePreview() + updateCanSave() + schedulePendingHistoryCommit() + }.onFailure { + cancelPendingHistoryTransaction() + AppToastHost.showFailureToast(it) + } + } + + fun removeMaskAtIndex(index: Int) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _maskingFilterState.update { + it.copy( + masks = it.masks.toMutableList().apply { + removeAt(index) + } + ) + } + updatePreview() + updateCanSave() + commitHistoryFrom(beforeSnapshot) + } + + fun addMask(value: UiFilterMask) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _maskingFilterState.update { + it.copy( + masks = it.masks + value + ) + } + updatePreview() + updateCanSave() + commitHistoryFrom(beforeSnapshot) + } + + 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, sourceImageInfo) -> + shareProvider.cacheImage( + image = image, + imageInfo = sourceImageInfo.copy( + imageFormat = imageInfo.imageFormat, + quality = imageInfo.quality + ) + )?.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, sourceImageInfo) -> + shareProvider.cacheImage( + image = image, + imageInfo = sourceImageInfo.copy( + imageFormat = imageInfo.imageFormat, + quality = imageInfo.quality + ) + )?.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 + } + + private fun currentPreviewRequest(): PreviewRequest? = + _bitmap.value?.let(::currentPreviewRequest) + + private fun currentPreviewRequest(bitmap: Bitmap): PreviewRequest = PreviewRequest( + bitmapId = System.identityHashCode(bitmap), + imageInfo = imageInfo.copy(sizeInBytes = 0), + filterType = filterType?.let { it::class.simpleName }, + basicState = (filterType as? Screen.Filter.Type.Basic)?.let { + BasicPreviewState( + uris = basicFilterState.uris, + selectedUri = basicFilterState.selectedUri, + filters = basicFilterState.filters.map { filter -> + filter.previewKey() + } + ) + }, + maskingState = (filterType as? Screen.Filter.Type.Masking)?.let { + MaskingPreviewState( + uri = maskingFilterState.uri, + masks = maskingFilterState.masks.map { mask -> + mask.previewKey() + } + ) + } + ) + + private fun UiFilterMask.previewKey(): MaskPreviewState = MaskPreviewState( + filters = filters.map { filter -> + filter.previewKey() + }, + maskPaints = maskPaints, + isInverseFillType = isInverseFillType + ) + + override fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( + imageInfo = imageInfo.asHistoryImageInfo(), + keepExif = keepExif, + filterType = filterType, + basicFilterState = basicFilterState, + maskingFilterState = maskingFilterState, + backgroundColorForNoAlphaFormats = settingsManager + .settingsState + .value + .backgroundForNoAlphaImageFormats + ) + + override fun applyHistorySnapshot(snapshot: HistorySnapshot) { + _imageInfo.update { + it.copy( + imageFormat = snapshot.imageInfo.imageFormat, + quality = snapshot.imageInfo.quality + ) + } + val selectedUri = basicFilterState.selectedUri + _keepExif.value = snapshot.keepExif + _filterType.value = snapshot.filterType + _basicFilterState.value = snapshot.basicFilterState.copy( + selectedUri = selectedUri + ?.takeIf { it in snapshot.basicFilterState.uris.orEmpty() } + ?: snapshot.basicFilterState.selectedUri + ) + _maskingFilterState.value = snapshot.maskingFilterState + restoreBackgroundColorForNoAlphaFormats( + settingsManager = settingsManager, + backgroundColorForNoAlphaFormats = snapshot.backgroundColorForNoAlphaFormats + ) + lastPreviewRequest = null + filterJob = null + _canSave.value = calculateCanSave() + updatePreview() + } + + override fun hasSameUndoState( + first: HistorySnapshot, + second: HistorySnapshot + ): Boolean = first.normalized() == second.normalized() + + private fun ImageInfo.asHistoryImageInfo(): ImageInfo = ImageInfo( + imageFormat = imageFormat, + quality = quality + ) + + private fun HistorySnapshot.normalized(): HistorySnapshot = copy( + imageInfo = imageInfo.asHistoryImageInfo(), + basicFilterState = basicFilterState.copy(selectedUri = null) + ) + + data class HistorySnapshot( + val imageInfo: ImageInfo = ImageInfo(), + val keepExif: Boolean = false, + val filterType: Screen.Filter.Type? = null, + val basicFilterState: BasicFilterState = BasicFilterState(), + val maskingFilterState: MaskingFilterState = MaskingFilterState(), + val backgroundColorForNoAlphaFormats: ColorModel = ColorModel(-0x1000000) + ) + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialType: Screen.Filter.Type?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): FiltersComponent + } +} \ No newline at end of file diff --git a/feature/format-conversion/.gitignore b/feature/format-conversion/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/format-conversion/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/format-conversion/build.gradle.kts b/feature/format-conversion/build.gradle.kts new file mode 100644 index 0000000..050fdcf --- /dev/null +++ b/feature/format-conversion/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/format-conversion/src/main/AndroidManifest.xml b/feature/format-conversion/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/format-conversion/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/format-conversion/src/main/java/com/t8rin/imagetoolbox/feature/format_conversion/presentation/FormatConversionContent.kt b/feature/format-conversion/src/main/java/com/t8rin/imagetoolbox/feature/format_conversion/presentation/FormatConversionContent.kt new file mode 100644 index 0000000..9a5016a --- /dev/null +++ b/feature/format-conversion/src/main/java/com/t8rin/imagetoolbox/feature/format_conversion/presentation/FormatConversionContent.kt @@ -0,0 +1,322 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.rememberScrollState +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.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.UndoRedoButtons +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.enhancedHorizontalScroll +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.modifier.fadingEdges +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 = { + val state = rememberScrollState() + Row( + modifier = Modifier + .fadingEdges(state) + .enhancedHorizontalScroll(state), + verticalAlignment = Alignment.CenterVertically + ) { + if (!isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + 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 + ) + if (isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + } + }, + 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.height(8.dp)) + SaveExifWidget( + imageFormat = component.imageInfo.imageFormat, + checked = component.keepExif, + onCheckedChange = component::setKeepExif + ) + Spacer(Modifier.height(16.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 + ) +} diff --git a/feature/format-conversion/src/main/java/com/t8rin/imagetoolbox/feature/format_conversion/presentation/screenLogic/FormatConversionComponent.kt b/feature/format-conversion/src/main/java/com/t8rin/imagetoolbox/feature/format_conversion/presentation/screenLogic/FormatConversionComponent.kt new file mode 100644 index 0000000..885b2ec --- /dev/null +++ b/feature/format-conversion/src/main/java/com/t8rin/imagetoolbox/feature/format_conversion/presentation/screenLogic/FormatConversionComponent.kt @@ -0,0 +1,552 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.ColorModel +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.SettingsManager +import com.t8rin.imagetoolbox.core.ui.transformation.ImageInfoTransformation +import com.t8rin.imagetoolbox.core.ui.utils.BaseHistoryComponent +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.format_conversion.presentation.screenLogic.FormatConversionComponent.HistorySnapshot +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, + private val settingsManager: SettingsManager, + dispatchersHolder: DispatchersHolder +) : BaseHistoryComponent( + dispatchersHolder = dispatchersHolder, + componentContext = 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? + ) { + clearHistory() + registerChangesCleared() + _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( + clearPreview: Boolean = true + ) { + _bitmap.value?.let { bmp -> + val preview = updatePreview(bmp) + if (clearPreview) { + _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() + resetHistory() + registerChangesCleared() + _isImageLoading.update { false } + } + } + + fun setQuality(quality: Quality) { + val coercedQuality = quality.coerceIn(imageInfo.imageFormat) + if (_imageInfo.value.quality != coercedQuality) { + beginPendingHistoryTransaction() + _imageInfo.value = _imageInfo.value.copy(quality = coercedQuality) + debouncedImageCalculation { + checkBitmapAndUpdate() + } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setImageFormat(imageFormat: ImageFormat) { + if (_imageInfo.value.imageFormat != imageFormat) { + if (pendingHistoryMode != PendingHistoryMode.FormatChange) { + finalizePendingHistoryTransaction() + } + beginPendingHistoryTransaction( + mode = PendingHistoryMode.FormatChange, + commitDelayMillis = formatHistoryTransactionDebounce + ) + _imageInfo.value = _imageInfo.value.copy( + imageFormat = imageFormat, + quality = imageInfo.quality.coerceIn(imageFormat) + ) + debouncedImageCalculation { + checkBitmapAndUpdate() + } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setKeepExif(boolean: Boolean) { + if (_keepExif.value == boolean) return + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _keepExif.value = boolean + commitHistoryFrom(beforeSnapshot) + } + + 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 + ) + ) + + override fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( + imageInfo = imageInfo.asHistoryImageInfo(), + keepExif = keepExif, + backgroundColorForNoAlphaFormats = settingsManager + .settingsState + .value + .backgroundForNoAlphaImageFormats + ) + + override fun applyHistorySnapshot(snapshot: HistorySnapshot) { + _imageInfo.update { + it.copy( + imageFormat = snapshot.imageInfo.imageFormat, + quality = snapshot.imageInfo.quality + ) + } + _keepExif.value = snapshot.keepExif + restoreBackgroundColorForNoAlphaFormats( + settingsManager = settingsManager, + backgroundColorForNoAlphaFormats = snapshot.backgroundColorForNoAlphaFormats + ) + debouncedImageCalculation { + bitmap?.let { + checkBitmapAndUpdate(clearPreview = false) + } + } + } + + override fun hasSameUndoState( + first: HistorySnapshot, + second: HistorySnapshot + ): Boolean = first.normalized() == second.normalized() + + private fun ImageInfo.asHistoryImageInfo(): ImageInfo = ImageInfo( + imageFormat = imageFormat, + quality = quality + ) + + private fun HistorySnapshot.normalized(): HistorySnapshot = copy( + imageInfo = imageInfo.asHistoryImageInfo() + ) + + data class HistorySnapshot( + val imageInfo: ImageInfo = ImageInfo(), + val keepExif: Boolean = false, + val backgroundColorForNoAlphaFormats: ColorModel = ColorModel(-0x1000000) + ) + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUris: List?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): FormatConversionComponent + } +} \ No newline at end of file diff --git a/feature/gif-tools/.gitignore b/feature/gif-tools/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/gif-tools/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/gif-tools/build.gradle.kts b/feature/gif-tools/build.gradle.kts new file mode 100644 index 0000000..e949afa --- /dev/null +++ b/feature/gif-tools/build.gradle.kts @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/gif-tools/src/main/AndroidManifest.xml b/feature/gif-tools/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/gif-tools/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/data/AndroidGifConverter.kt b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/data/AndroidGifConverter.kt new file mode 100644 index 0000000..d30a87b --- /dev/null +++ b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/data/AndroidGifConverter.kt @@ -0,0 +1,371 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.Canvas +import android.graphics.Paint +import android.graphics.PorterDuff +import android.graphics.RectF +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.outputStream +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.saving.FailureNotifier +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.GifMergeItem +import com.t8rin.imagetoolbox.feature.gif_tools.domain.GifMergeParams +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.ensureActive +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.isActive +import kotlinx.coroutines.withContext +import javax.inject.Inject + + +internal class AndroidGifConverter @Inject constructor( + private val imageGetter: ImageGetter, + private val imageShareProvider: ImageShareProvider, + @ApplicationContext private val context: Context, + private val failureNotifier: FailureNotifier, + 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 + ): String? = withContext(defaultDispatcher) { + params.size?.let { size -> + if (size.width <= 0 || size.height <= 0) { + onFailure(IllegalArgumentException("Width and height must be > 0")) + return@withContext null + } + } + + runSuspendCatching { + imageShareProvider.cacheDataOrThrow( + filename = "temp_gif.gif" + ) { writeable -> + val encoder = GifEncoder() + .setRepeat(params.repeatCount) + .setQuality(params.quality.qualityValue) + .setFrameRate(params.fps.toFloat()) + .setDispose(if (params.dontStack) 2 else 0) + .setTransparent(Color.Transparent.toArgb()) + .apply { + params.size?.let { size -> + setSize( + width = size.width, + height = size.height + ) + } + start(writeable.outputStream()) + } + 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() + } + }.onFailure { + onFailure(it) + }.getOrNull() + } + + private fun Bitmap.overlay(overlay: Bitmap): Bitmap { + return createBitmap(width, height, safeConfig.toSoftware()).applyCanvas { + drawBitmap(this@overlay) + drawBitmap(overlay.toSoftware()) + } + } + + override suspend fun mergeGifs( + items: List, + params: GifMergeParams, + onFailure: (Throwable) -> Unit, + onProgress: () -> Unit + ): String? = withContext(defaultDispatcher) { + runSuspendCatching { + require(items.size >= 2) + val outputSize = items.map { item -> + val decoder = GifDecoder().apply { + read(requireNotNull(item.uri.bytes)) + } + require(decoder.frameCount > 0) + decoder.advance() + requireNotNull(decoder.nextFrame).let { it.width to it.height } + } + val outputWidth = outputSize.maxOf { it.first } + val outputHeight = outputSize.maxOf { it.second } + + imageShareProvider.cacheDataOrThrow(filename = "merged_gif.gif") { writeable -> + val encoder = GifEncoder() + .setRepeat(params.repeatCount) + .setQuality(params.quality.coerceIn(1, 100)) + .setDispose(0) + .setTransparent(Color.Transparent.toArgb()) + .setSize(outputWidth, outputHeight) + encoder.start(writeable.outputStream()) + + items.forEachIndexed { clipIndex, item -> + currentCoroutineContext().ensureActive() + val decoder = GifDecoder().apply { + read(requireNotNull(item.uri.bytes)) + } + val frames = buildList { + repeat(decoder.frameCount) { + decoder.advance() + decoder.nextFrame?.let { frame -> + add( + GifFrame( + frame.toSoftware(), + decoder.nextDelay.coerceAtLeast(10) + ) + ) + } + } + }.transform(item) + frames.forEachIndexed { frameIndex, frame -> + currentCoroutineContext().ensureActive() + val isClipEnd = + frameIndex == frames.lastIndex && clipIndex < items.lastIndex + encoder + .setDelay( + frame.durationMillis + if (isClipEnd) { + params.transitionDelayMillis.coerceAtLeast(0) + } else 0 + ) + .addFrame( + frame.bitmap.placeOnCanvas( + width = outputWidth, + height = outputHeight, + scaleToFit = params.normalizeFrameSizes + ) + ) + } + onProgress() + } + encoder.finish() + } + }.onFailure(onFailure).getOrNull() + } + + private data class GifFrame( + val bitmap: Bitmap, + val durationMillis: Int + ) + + private fun List.transform(item: GifMergeItem): List { + val transformed = if (item.reverse) reversed() else this + return if (item.boomerang && transformed.size > 1) { + transformed + transformed.dropLast(1).reversed() + } else transformed + } + + private fun Bitmap.placeOnCanvas( + width: Int, + height: Int, + scaleToFit: Boolean + ): Bitmap { + if (this.width == width && this.height == height) return this + val scale = if (scaleToFit) { + minOf(width.toFloat() / this.width, height.toFloat() / this.height) + } else 1f + val targetWidth = this.width * scale + val targetHeight = this.height * scale + val left = (width - targetWidth) / 2f + val top = (height - targetHeight) / 2f + return createBitmap(width, height, Bitmap.Config.ARGB_8888).also { output -> + Canvas(output).drawBitmap( + this, + null, + RectF(left, top, left + targetWidth, top + targetHeight), + Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG) + ) + } + } + + override suspend fun convertGifToJxl( + gifUris: List, + quality: Quality.Jxl, + onProgress: suspend (String, ByteArray) -> Unit + ) = withContext(defaultDispatcher) { + gifUris.forEach { uri -> + runSuspendCatching { + uri.bytes?.let { gifData -> + 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) } + } + }.catch { + failureNotifier.send(it) + } + + private val String.bytes: ByteArray? + get() = context + .contentResolver + .openInputStream(toUri()) + ?.use { + it.readBytes() + } + +} diff --git a/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/di/GifToolsModule.kt b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/di/GifToolsModule.kt new file mode 100644 index 0000000..4de5ac3 --- /dev/null +++ b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/di/GifToolsModule.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/domain/GifConverter.kt b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/domain/GifConverter.kt new file mode 100644 index 0000000..a37f104 --- /dev/null +++ b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/domain/GifConverter.kt @@ -0,0 +1,61 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ): String? + + suspend fun mergeGifs( + items: List, + params: GifMergeParams, + onFailure: (Throwable) -> Unit, + onProgress: () -> Unit + ): String? + + 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 + ) + +} diff --git a/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/domain/GifMergeParams.kt b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/domain/GifMergeParams.kt new file mode 100644 index 0000000..ed039a4 --- /dev/null +++ b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/domain/GifMergeParams.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +data class GifMergeParams( + val transitionDelayMillis: Int = 0, + val repeatCount: Int = 0, + val quality: Int = 50, + val normalizeFrameSizes: Boolean = true +) + +data class GifMergeItem( + val uri: String, + val reverse: Boolean = false, + val boomerang: Boolean = false +) diff --git a/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/domain/GifParams.kt b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/domain/GifParams.kt new file mode 100644 index 0000000..0ebde6f --- /dev/null +++ b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/domain/GifParams.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } +} \ No newline at end of file diff --git a/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/GifToolsContent.kt b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/GifToolsContent.kt new file mode 100644 index 0000000..9508bfb --- /dev/null +++ b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/GifToolsContent.kt @@ -0,0 +1,336 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Gif +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 pickMultipleGifToMergeLauncher = rememberFilePicker( + mimeType = MimeType.Gif, + onSuccess = { list: List -> + list.filter(Uri::isGif).let { uris -> + if (uris.isEmpty()) { + AppToastHost.showToast( + message = getString(R.string.select_gif_image_to_start), + icon = Icons.Rounded.Gif + ) + } else { + component.setType(Screen.GifTools.Type.MergeGif(uris.distinct())) + } + } + } + ) + + 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.Rounded.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.Rounded.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.Rounded.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.Rounded.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.canSave, + onShare = component::performSharing + ) + }, + imagePreview = { + GifToolsImagePreview( + component = component, + onAddGifsToJxl = addGifsToJxlLauncher::pickFile, + onAddGifsToWebp = addGifsToWebpLauncher::pickFile + ) + }, + placeImagePreview = component.type !is Screen.GifTools.Type.ImageToGif && + component.type !is Screen.GifTools.Type.MergeGif, + 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 = { + if (component.type is Screen.GifTools.Type.ImageToGif || + component.type is Screen.GifTools.Type.MergeGif + ) { + saveGifLauncher.make(component.createTargetFilename()) + } else { + component.saveBitmaps(oneTimeSaveLocationUri = it) + } + } + 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.MergeGif -> pickMultipleGifToMergeLauncher.pickFile() + 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 || + component.type is Screen.GifTools.Type.MergeGif + ) { + 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.MergeGif -> pickMultipleGifToMergeLauncher.pickFile() + 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() = when (val type = type) { + is Screen.GifTools.Type.MergeGif -> type.gifUris.orEmpty().size >= 2 + is Screen.GifTools.Type.GifToImage -> gifFrames == ImageFrames.All || + (gifFrames as? ImageFrames.ManualSelection)?.framePositions?.isNotEmpty() == true + + null -> false + else -> true + } diff --git a/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifMergeControls.kt b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifMergeControls.kt new file mode 100644 index 0000000..fceafec --- /dev/null +++ b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifMergeControls.kt @@ -0,0 +1,288 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 android.net.Uri +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.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.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.rotate +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.unit.dp +import androidx.compose.ui.unit.sp +import coil3.request.ImageRequest +import com.t8rin.imagetoolbox.core.data.image.utils.static +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Build +import com.t8rin.imagetoolbox.core.resources.icons.FitScreen +import com.t8rin.imagetoolbox.core.resources.icons.KeyboardArrowDown +import com.t8rin.imagetoolbox.core.resources.icons.Repeat +import com.t8rin.imagetoolbox.core.resources.icons.RepeatOne +import com.t8rin.imagetoolbox.core.resources.icons.RotateLeft +import com.t8rin.imagetoolbox.core.resources.icons.Timelapse +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.navigation.Screen +import com.t8rin.imagetoolbox.core.ui.widget.controls.ImageReorderCarousel +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.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.PreferenceRowSwitch +import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem +import com.t8rin.imagetoolbox.core.utils.appContext +import com.t8rin.imagetoolbox.core.utils.isGif +import com.t8rin.imagetoolbox.feature.gif_tools.presentation.screenLogic.GifToolsComponent +import kotlinx.collections.immutable.persistentMapOf +import kotlin.math.roundToInt + +@Composable +internal fun GifMergeControls( + component: GifToolsComponent, + type: Screen.GifTools.Type.MergeGif +) { + val addPicker = rememberFilePicker( + mimeType = MimeType.Gif, + onSuccess = { uris: List -> + component.addMergeUris(uris.filter(Uri::isGif)) + } + ) + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(16.dp)) + ImageReorderCarousel( + images = type.gifUris, + onReorder = component::reorderMergeUris, + onNeedToAddImage = addPicker::pickFile, + onNeedToRemoveImageAt = component::removeMergeUriAt, + onNavigate = component.onNavigate, + title = stringResource(R.string.gif_clips_order) + ) + Spacer(modifier = Modifier.height(8.dp)) + Column( + modifier = Modifier.container( + shape = ShapeDefaults.large, + resultPadding = 12.dp + ), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + TitleItem( + text = stringResource(R.string.params), + icon = Icons.Rounded.Build, + modifier = Modifier.padding(bottom = 8.dp) + ) + + type.gifUris.orEmpty().forEachIndexed { index, uri -> + var expanded by rememberSaveable { mutableStateOf(false) } + val rotation by animateFloatAsState(if (expanded) 180f else 0f) + + Column( + modifier = Modifier + .fillMaxWidth() + .container( + shape = ShapeDefaults.large, + color = MaterialTheme.colorScheme.surface + ) + .padding(8.dp) + ) { + val item = component.mergeItem(uri) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Box( + modifier = Modifier + .size(42.dp) + .container( + shape = MaterialTheme.shapes.medium, + color = Color.Transparent, + resultPadding = 0.dp + ) + ) { + Picture( + model = remember(uri) { + ImageRequest.Builder(appContext) + .data(uri) + .static() + .build() + }, + contentDescription = null, + contentScale = ContentScale.Crop, + shape = RectangleShape, + modifier = Modifier.fillMaxSize() + ) + Box( + modifier = Modifier + .matchParentSize() + .background( + MaterialTheme.colorScheme + .surfaceContainer + .copy(0.6f) + ), + contentAlignment = Alignment.Center + ) { + Text( + text = "${index + 1}", + color = MaterialTheme.colorScheme.onSurface, + fontSize = 14.sp, + fontWeight = FontWeight.Bold + ) + } + } + Text( + text = rememberFilename(uri) ?: uri.lastPathSegment.orEmpty(), + style = MaterialTheme.typography.labelLarge, + modifier = Modifier.weight(1f) + ) + EnhancedIconButton( + containerColor = Color.Transparent, + onClick = { expanded = !expanded } + ) { + Icon( + imageVector = Icons.Rounded.KeyboardArrowDown, + contentDescription = "Expand", + modifier = Modifier.rotate(rotation) + ) + } + } + + AnimatedVisibility( + visible = expanded, + modifier = Modifier.fillMaxWidth() + ) { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier + .fillMaxWidth() + .padding(top = 12.dp) + ) { + PreferenceRowSwitch( + title = stringResource(R.string.reverse), + subtitle = stringResource(R.string.reverse_sub), + checked = item.reverse, + onClick = { component.updateMergeItem(uri, reverse = it) }, + startIcon = Icons.Rounded.RotateLeft, + shape = ShapeDefaults.top, + resultModifier = Modifier.padding(12.dp), + modifier = Modifier.fillMaxWidth() + ) + PreferenceRowSwitch( + title = stringResource(R.string.boomerang), + subtitle = stringResource(R.string.boomerang_sub), + checked = item.boomerang, + onClick = { component.updateMergeItem(uri, boomerang = it) }, + startIcon = Icons.Rounded.Repeat, + shape = ShapeDefaults.bottom, + resultModifier = Modifier.padding(12.dp), + modifier = Modifier.fillMaxWidth() + ) + } + } + } + } + } + Spacer(modifier = Modifier.height(8.dp)) + PreferenceRowSwitch( + title = stringResource(R.string.normalize_gif_frames), + subtitle = stringResource(R.string.normalize_gif_frames_sub), + checked = component.mergeParams.normalizeFrameSizes, + onClick = { + component.updateMergeParams( + component.mergeParams.copy(normalizeFrameSizes = it) + ) + }, + startIcon = Icons.Outlined.FitScreen, + containerColor = Color.Unspecified, + shape = ShapeDefaults.extraLarge, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(8.dp)) + EnhancedSliderItem( + value = component.mergeParams.transitionDelayMillis, + icon = Icons.Outlined.Timelapse, + title = stringResource(R.string.delay_between_clips), + valueRange = 0f..10_000f, + internalStateTransformation = { it.roundToInt() }, + onValueChange = { + component.updateMergeParams( + component.mergeParams.copy(transitionDelayMillis = it.roundToInt()) + ) + }, + shape = ShapeDefaults.extraLarge + ) + Spacer(modifier = Modifier.height(8.dp)) + QualitySelector( + imageFormat = ImageFormat.Jpeg, + quality = Quality.Base(component.mergeParams.quality), + onQualityChange = { + component.updateMergeParams( + component.mergeParams.copy(quality = it.qualityValue) + ) + } + ) + Spacer(modifier = Modifier.height(8.dp)) + EnhancedSliderItem( + value = component.mergeParams.repeatCount, + icon = Icons.Rounded.RepeatOne, + title = stringResource(R.string.repeat_count), + valueRange = 0f..10f, + steps = 10, + valuesPreviewMapping = remember { persistentMapOf(0f to "∞") }, + internalStateTransformation = { it.roundToInt() }, + onValueChange = { + component.updateMergeParams( + component.mergeParams.copy(repeatCount = it.roundToInt()) + ) + }, + shape = ShapeDefaults.extraLarge + ) + } +} diff --git a/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifParamsSelector.kt b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifParamsSelector.kt new file mode 100644 index 0000000..3ecdca2 --- /dev/null +++ b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifParamsSelector.kt @@ -0,0 +1,197 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.FilterFrames +import com.t8rin.imagetoolbox.core.resources.icons.Opacity +import com.t8rin.imagetoolbox.core.resources.icons.PhotoSizeSelectLarge +import com.t8rin.imagetoolbox.core.resources.icons.RepeatOne +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.Rounded.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.Rounded.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) + ) + } + } +} \ No newline at end of file diff --git a/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsControls.kt b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsControls.kt new file mode 100644 index 0000000..a0bda2b --- /dev/null +++ b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsControls.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.MergeGif -> { + GifMergeControls( + component = component, + type = 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 + ) + } + + 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 + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsImagePreview.kt b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsImagePreview.kt new file mode 100644 index 0000000..b82a2a4 --- /dev/null +++ b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsImagePreview.kt @@ -0,0 +1,102 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.MergeGif -> Unit + + 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(isPortrait = isPortrait), + 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(isPortrait = isPortrait), + 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 + } + } + } + } +} \ No newline at end of file diff --git a/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsNoDataControls.kt b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsNoDataControls.kt new file mode 100644 index 0000000..ea2c467 --- /dev/null +++ b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsNoDataControls.kt @@ -0,0 +1,143 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.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]) + } + ) + } + val preference5 = @Composable { + PreferenceItem( + title = stringResource(types[4].title), + subtitle = stringResource(types[4].subtitle), + startIcon = types[4].icon, + modifier = Modifier.fillMaxWidth(), + onClick = { + onClickType(types[4]) + } + ) + } + 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 { + val cutout = WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal).asPaddingValues() + + Column( + Modifier.padding(cutout) + ) { + 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)) + } + Spacer(modifier = Modifier.height(8.dp)) + Row { + preference5.withModifier(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.weight(1f)) + } + } + } +} \ No newline at end of file diff --git a/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsTopAppBarActions.kt b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsTopAppBarActions.kt new file mode 100644 index 0000000..f3b18d5 --- /dev/null +++ b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsTopAppBarActions.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.Close +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) + ) + } + } + } +} \ No newline at end of file diff --git a/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/screenLogic/GifToolsComponent.kt b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/screenLogic/GifToolsComponent.kt new file mode 100644 index 0000000..5fcf3b1 --- /dev/null +++ b/feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/screenLogic/GifToolsComponent.kt @@ -0,0 +1,705 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.GifMergeItem +import com.t8rin.imagetoolbox.feature.gif_tools.domain.GifMergeParams +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 _mergeParams: MutableState = mutableStateOf(GifMergeParams()) + val mergeParams by _mergeParams + + private val _mergeItems: MutableState> = mutableStateOf(emptyMap()) + + 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 + + fun setType(type: Screen.GifTools.Type) { + when (type) { + is Screen.GifTools.Type.MergeGif -> { + _type.update { type } + _mergeItems.update { current -> + type.gifUris.orEmpty().associate { uri -> + uri.toString() to (current[uri.toString()] ?: GifMergeItem(uri.toString())) + } + } + } + + 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() } + _mergeItems.update { emptyMap() } + _mergeParams.update { GifMergeParams() } + 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 createTargetFilename(): String = "GIF_${timestamp()}.gif" + + fun saveGifTo(uri: Uri) { + savingJob = trackProgress { + _isSaving.value = true + _done.value = 0 + val progress = { + _done.update { it + 1 } + updateProgress(done = done, total = left) + } + val failure: (Throwable) -> Unit = { + parseSaveResults(listOf(SaveResult.Error.Exception(it))) + } + val gifUri = when (val type = type) { + is Screen.GifTools.Type.ImageToGif -> { + val imageUris = type.imageUris.orEmpty().map(Uri::toString) + _left.value = imageUris.size + gifConverter.createGifFromImageUris( + imageUris = imageUris, + params = params, + onProgress = progress, + onFailure = failure + ) + } + + is Screen.GifTools.Type.MergeGif -> { + val items = type.mergeItems() + _left.value = items.size + gifConverter.mergeGifs( + items = items, + params = mergeParams, + onProgress = progress, + onFailure = failure + ) + } + + else -> null + } + gifUri?.let { + fileController.transferBytes( + fromUri = it, + toUri = uri.toString() + ).also(::parseFileSaveResult).onSuccess(::registerSave) + } + _isSaving.value = false + } + } + + fun saveBitmaps( + oneTimeSaveLocationUri: String? + ) { + savingJob = trackProgress { + _isSaving.value = true + _left.value = 1 + _done.value = 0 + when (val type = _type.value) { + is Screen.GifTools.Type.MergeGif -> Unit + + 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 -> Unit + + 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 reorderMergeUris(uris: List) { + if (type is Screen.GifTools.Type.MergeGif) { + _type.update { Screen.GifTools.Type.MergeGif(uris) } + registerChanges() + } + } + + fun addMergeUris(uris: List) { + val type = type as? Screen.GifTools.Type.MergeGif ?: return + setType( + Screen.GifTools.Type.MergeGif( + type.gifUris.orEmpty().plus(uris).distinct() + ) + ) + registerChanges() + } + + fun removeMergeUriAt(index: Int) { + val type = type as? Screen.GifTools.Type.MergeGif ?: return + setType( + Screen.GifTools.Type.MergeGif( + type.gifUris.orEmpty().toMutableList().apply { removeAt(index) } + ) + ) + registerChanges() + } + + fun updateMergeItem(uri: Uri, reverse: Boolean? = null, boomerang: Boolean? = null) { + _mergeItems.update { items -> + val key = uri.toString() + val item = items[key] ?: GifMergeItem(key) + items + (key to item.copy( + reverse = reverse ?: item.reverse, + boomerang = boomerang ?: item.boomerang + )) + } + registerChanges() + } + + fun mergeItem(uri: Uri): GifMergeItem = + _mergeItems.value[uri.toString()] ?: GifMergeItem(uri.toString()) + + fun updateMergeParams(params: GifMergeParams) { + _mergeParams.update { params } + 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.MergeGif -> { + val items = type.mergeItems() + _left.value = items.size + gifConverter.mergeGifs( + items = items, + params = mergeParams, + onProgress = { + _done.update { it + 1 } + updateProgress(done = done, total = left) + }, + onFailure = AppToastHost::showFailureToast + )?.also { uri -> + shareProvider.shareUri( + uri = uri, + type = ImageFormat.Gif.mimeType, + onComplete = AppToastHost::showConfetti + ) + } + } + + 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 = AppToastHost::showFailureToast + )?.also { uri -> + shareProvider.shareUri( + uri = uri, + type = ImageFormat.Gif.mimeType, + 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() + } + + private fun Screen.GifTools.Type.MergeGif.mergeItems(): List = + gifUris.orEmpty().map { mergeItem(it) } + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialType: Screen.GifTools.Type?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): GifToolsComponent + } + +} \ No newline at end of file diff --git a/feature/gradient-maker/.gitignore b/feature/gradient-maker/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/gradient-maker/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/gradient-maker/build.gradle.kts b/feature/gradient-maker/build.gradle.kts new file mode 100644 index 0000000..7cceda1 --- /dev/null +++ b/feature/gradient-maker/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + implementation(projects.feature.pickColor) +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/AndroidManifest.xml b/feature/gradient-maker/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/gradient-maker/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/data/AndroidGradientMaker.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/data/AndroidGradientMaker.kt new file mode 100644 index 0000000..268ff58 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/data/AndroidGradientMaker.kt @@ -0,0 +1,403 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/di/GradientMakerModule.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/di/GradientMakerModule.kt new file mode 100644 index 0000000..4575282 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/di/GradientMakerModule.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/GradientMaker.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/GradientMaker.kt new file mode 100644 index 0000000..ae407b9 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/GradientMaker.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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? + +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/GradientState.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/GradientState.kt new file mode 100644 index 0000000..33074a3 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/GradientState.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/GradientType.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/GradientType.kt new file mode 100644 index 0000000..8e99570 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/GradientType.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/MeshGradientState.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/MeshGradientState.kt new file mode 100644 index 0000000..a551a82 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/MeshGradientState.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/GradientMakerContent.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/GradientMakerContent.kt new file mode 100644 index 0000000..af9103f --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/GradientMakerContent.kt @@ -0,0 +1,174 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/ColorStopSelection.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/ColorStopSelection.kt new file mode 100644 index 0000000..e1aacc4 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/ColorStopSelection.kt @@ -0,0 +1,289 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.graphics.Bitmap +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.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.Icons +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.resources.icons.Palette +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.ColorPickerSheet +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton +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.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.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, + colorPickerBitmap: Bitmap? +) { + 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, + colorPickerBitmap = colorPickerBitmap + ) + } + 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) + } + ColorPickerSheet( + visible = showColorPicker, + onDismiss = { showColorPicker = false }, + color = color, + onColorSelected = { + color = it + onAddColorStop(1f to it) + }, + allowAlpha = true, + additionalContent = { onColorChange -> + GradientMakerColorPickerAdditionalContent( + bitmap = colorPickerBitmap, + onColorChange = onColorChange + ) + } + ) +} + +@Composable +private fun ColorStopSelectionItem( + value: Float, + color: Color, + onRemoveClick: () -> Unit, + onValueChange: (Pair) -> Unit, + canDelete: Boolean, + colorPickerBitmap: Bitmap? +) { + 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) + } + val initialValue = rememberSaveable { value } + ValueText( + modifier = Modifier, + value = (value * 100).toInt(), + onClick = { + showValueDialog = true + }, + onLongClick = { + onValueChange(initialValue to color) + } + ) + 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 + } + ) + } + } + } + ) + + ColorPickerSheet( + visible = showColorPicker, + onDismiss = { showColorPicker = false }, + color = color, + onColorSelected = { + onValueChange(value to it) + }, + allowAlpha = true, + additionalContent = { onColorChange -> + GradientMakerColorPickerAdditionalContent( + bitmap = colorPickerBitmap, + onColorChange = onColorChange + ) + } + ) +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerAppColorSchemeHandler.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerAppColorSchemeHandler.kt new file mode 100644 index 0000000..288e6e7 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerAppColorSchemeHandler.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } + } +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerBottomButtons.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerBottomButtons.kt new file mode 100644 index 0000000..050e793 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerBottomButtons.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerColorPickerAdditionalContent.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerColorPickerAdditionalContent.kt new file mode 100644 index 0000000..5f8b268 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerColorPickerAdditionalContent.kt @@ -0,0 +1,97 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.graphics.Bitmap +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +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.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.Icons +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 +import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver +import com.t8rin.imagetoolbox.feature.pick_color.presentation.components.PickColorFromImageSheet + +@Composable +internal fun GradientMakerColorPickerAdditionalContent( + bitmap: Bitmap?, + onColorChange: (Color) -> Unit +) { + bitmap ?: return + + var showPickColorSheet by rememberSaveable { mutableStateOf(false) } + var pipetteColor by rememberSaveable(stateSaver = ColorSaver) { + mutableStateOf(Color.Black) + } + + Row( + modifier = Modifier + .padding(bottom = 16.dp) + .container( + resultPadding = 0.dp, + color = MaterialTheme.colorScheme.mixedContainer.copy(0.7f), + shape = ShapeDefaults.extraLarge + ) + .hapticsClickable { + showPickColorSheet = true + } + .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 + ) + } + + PickColorFromImageSheet( + visible = showPickColorSheet, + onDismiss = { + showPickColorSheet = false + }, + bitmap = bitmap, + onColorChange = { + pipetteColor = it + onColorChange(it) + }, + color = pipetteColor + ) +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerCompareButton.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerCompareButton.kt new file mode 100644 index 0000000..cfc0237 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerCompareButton.kt @@ -0,0 +1,100 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 androidx.compose.ui.graphics.RectangleShape +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, + shape = RectangleShape + ) + } 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, + shape = RectangleShape + ) + } + }, + visible = showCompareSheet, + onDismiss = { + showCompareSheet = false + } + ) +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerControls.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerControls.kt new file mode 100644 index 0000000..6c8e801 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerControls.kt @@ -0,0 +1,216 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Build +import com.t8rin.imagetoolbox.core.resources.icons.DensitySmall +import com.t8rin.imagetoolbox.core.resources.icons.GridOn +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), + colorPickerBitmap = component.colorPickerBitmap + ) + } + Spacer(Modifier.height(8.dp)) + EnhancedSliderItem( + value = component.meshGradientState.gridSize, + title = stringResource(R.string.grid_size), + icon = Icons.Outlined.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, + colorPickerBitmap = component.colorPickerBitmap + ) + 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, + ) +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerImagePreview.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerImagePreview.kt new file mode 100644 index 0000000..a5c0935 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerImagePreview.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerNoDataControls.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerNoDataControls.kt new file mode 100644 index 0000000..b359f07 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerNoDataControls.kt @@ -0,0 +1,170 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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)) + } + } +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientPreview.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientPreview.kt new file mode 100644 index 0000000..f6cabc2 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientPreview.kt @@ -0,0 +1,114 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.Shape +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, + shape: Shape = MaterialTheme.shapes.medium +) { + 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(shape) + .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 = shape + ) + } + } + } +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientPropertiesSelector.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientPropertiesSelector.kt new file mode 100644 index 0000000..c044dfe --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientPropertiesSelector.kt @@ -0,0 +1,118 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientSizeSelector.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientSizeSelector.kt new file mode 100644 index 0000000..4c5068a --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientSizeSelector.kt @@ -0,0 +1,102 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + ) + } +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientTypeSelector.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientTypeSelector.kt new file mode 100644 index 0000000..6d6fa13 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientTypeSelector.kt @@ -0,0 +1,92 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Tune +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.Rounded.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) + } diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/MeshGradientEditor.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/MeshGradientEditor.kt new file mode 100644 index 0000000..9780068 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/MeshGradientEditor.kt @@ -0,0 +1,272 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.graphics.Bitmap +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.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 +import com.t8rin.imagetoolbox.core.resources.icons.MiniEditLarge +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, + colorPickerBitmap: Bitmap? = null +) { + 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.Outlined.MiniEditLarge) + + 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) + selectedPoint.value = offset to newColor + } + }, + allowAlpha = true, + additionalContent = { onColorChange -> + GradientMakerColorPickerAdditionalContent( + bitmap = colorPickerBitmap, + onColorChange = onColorChange + ) + } + ) + } +} + +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 + ) + } + } \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/MeshGradientPreview.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/MeshGradientPreview.kt new file mode 100644 index 0000000..0d01a57 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/MeshGradientPreview.kt @@ -0,0 +1,88 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.graphics.Shape +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, + shape: Shape = MaterialTheme.shapes.medium +) { + 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(shape) + .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 = shape, + size = 1500 + ) + } + } + } +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/TileModeSelector.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/TileModeSelector.kt new file mode 100644 index 0000000..9ec04ed --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/TileModeSelector.kt @@ -0,0 +1,77 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/UiGradientState.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/UiGradientState.kt new file mode 100644 index 0000000..19d7613 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/UiGradientState.kt @@ -0,0 +1,173 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/model/GradientMakerType.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/model/GradientMakerType.kt new file mode 100644 index 0000000..057ed15 --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/model/GradientMakerType.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/screenLogic/GradientMakerComponent.kt b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/screenLogic/GradientMakerComponent.kt new file mode 100644 index 0000000..8591ced --- /dev/null +++ b/feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/screenLogic/GradientMakerComponent.kt @@ -0,0 +1,615 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 _colorPickerBitmap: MutableState = mutableStateOf(null) + val colorPickerBitmap: Bitmap? by _colorPickerBitmap + + 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 = "", + 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 + _colorPickerBitmap.value = null + _isImageLoading.value = true + imageGetter.getImageData( + uri = uri.toString(), + size = 2000, + onFailure = { + _isImageLoading.value = false + AppToastHost.showFailureToast(it) + } + )?.let { imageData -> + _colorPickerBitmap.value = imageData.image + _imageAspectRatio.update { + imageData.image.safeAspectRatio + } + _isImageLoading.value = false + setImageFormat(imageData.imageInfo.imageFormat) + } + } + } + + fun updateGradientAlpha(value: Float) { + _gradientAlpha.update { value } + registerChanges() + } + + override fun resetState() { + _selectedUri.update { Uri.EMPTY } + _colorPickerBitmap.value = null + _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) + val replacementUri = if (index == 0) { + uris.getOrNull(1) + } else { + uris.getOrNull(index - 1) + } + if (replacementUri != null) { + updateSelectedUri(replacementUri) + } else { + _selectedUri.value = Uri.EMPTY + _colorPickerBitmap.value = null + } + } + _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 + } +} \ No newline at end of file diff --git a/feature/help/.gitignore b/feature/help/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/help/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/help/build.gradle.kts b/feature/help/build.gradle.kts new file mode 100644 index 0000000..1abd774 --- /dev/null +++ b/feature/help/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.help" \ No newline at end of file diff --git a/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/data/HelpRepository.kt b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/data/HelpRepository.kt new file mode 100644 index 0000000..98e2107 --- /dev/null +++ b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/data/HelpRepository.kt @@ -0,0 +1,1935 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.feature.help.data + +import androidx.annotation.StringRes +import androidx.compose.ui.graphics.vector.ImageVector +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Album +import com.t8rin.imagetoolbox.core.resources.icons.Animation +import com.t8rin.imagetoolbox.core.resources.icons.AspectRatio +import com.t8rin.imagetoolbox.core.resources.icons.AutoFixHigh +import com.t8rin.imagetoolbox.core.resources.icons.BackgroundColor +import com.t8rin.imagetoolbox.core.resources.icons.Base64 +import com.t8rin.imagetoolbox.core.resources.icons.Bookmark +import com.t8rin.imagetoolbox.core.resources.icons.BubbleDelete +import com.t8rin.imagetoolbox.core.resources.icons.Collage +import com.t8rin.imagetoolbox.core.resources.icons.Compare +import com.t8rin.imagetoolbox.core.resources.icons.ContentPaste +import com.t8rin.imagetoolbox.core.resources.icons.CropSmall +import com.t8rin.imagetoolbox.core.resources.icons.Description +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.EvShadow +import com.t8rin.imagetoolbox.core.resources.icons.ExifEdit +import com.t8rin.imagetoolbox.core.resources.icons.Eyedropper +import com.t8rin.imagetoolbox.core.resources.icons.File +import com.t8rin.imagetoolbox.core.resources.icons.FileReplace +import com.t8rin.imagetoolbox.core.resources.icons.FolderZip +import com.t8rin.imagetoolbox.core.resources.icons.FormatPaintVariant +import com.t8rin.imagetoolbox.core.resources.icons.Gradient +import com.t8rin.imagetoolbox.core.resources.icons.HardDrive +import com.t8rin.imagetoolbox.core.resources.icons.HashTag +import com.t8rin.imagetoolbox.core.resources.icons.Help +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.ImageResize +import com.t8rin.imagetoolbox.core.resources.icons.ImageSearch +import com.t8rin.imagetoolbox.core.resources.icons.ImageWeight +import com.t8rin.imagetoolbox.core.resources.icons.LabelPercent +import com.t8rin.imagetoolbox.core.resources.icons.Lightbulb +import com.t8rin.imagetoolbox.core.resources.icons.MeshGradient +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.Palette +import com.t8rin.imagetoolbox.core.resources.icons.PaletteSwatch +import com.t8rin.imagetoolbox.core.resources.icons.Pdf +import com.t8rin.imagetoolbox.core.resources.icons.PhotoSizeSelectLarge +import com.t8rin.imagetoolbox.core.resources.icons.PhotoSizeSelectSmall +import com.t8rin.imagetoolbox.core.resources.icons.Preview +import com.t8rin.imagetoolbox.core.resources.icons.QrCode +import com.t8rin.imagetoolbox.core.resources.icons.QualityHigh +import com.t8rin.imagetoolbox.core.resources.icons.Save +import com.t8rin.imagetoolbox.core.resources.icons.SaveAs +import com.t8rin.imagetoolbox.core.resources.icons.SaveConfirm +import com.t8rin.imagetoolbox.core.resources.icons.Scanner +import com.t8rin.imagetoolbox.core.resources.icons.Scissors +import com.t8rin.imagetoolbox.core.resources.icons.Share +import com.t8rin.imagetoolbox.core.resources.icons.ShieldLock +import com.t8rin.imagetoolbox.core.resources.icons.SplitAlt +import com.t8rin.imagetoolbox.core.resources.icons.SquareFoot +import com.t8rin.imagetoolbox.core.resources.icons.StackSticky +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.Watermark +import com.t8rin.imagetoolbox.core.settings.presentation.model.Setting +import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen +import com.t8rin.imagetoolbox.feature.help.domain.HelpRepository +import com.t8rin.imagetoolbox.feature.help.domain.model.HelpCategory +import com.t8rin.imagetoolbox.feature.help.domain.model.HelpPage +import com.t8rin.imagetoolbox.feature.help.domain.model.HelpTip +import javax.inject.Inject + +internal class HelpRepositoryImpl @Inject constructor() : HelpRepository { + + private object TipIds { + const val ChooseTool = "choose-tool" + const val ImportSaveShare = "import-save-share" + const val BatchWorkflow = "batch-workflow" + const val SingleEdit = "single-edit" + const val PresetsSettings = "presets-settings" + const val DefaultValues = "default-values" + const val PickerLauncher = "picker-launcher" + const val ImageSource = "image-source" + const val SkipFilePicking = "skip-file-picking" + const val OpenEditDirectly = "open-edit-directly" + const val FavoritesShareTools = "favorites-share-tools" + const val BackupRestoreSettings = "backup-restore-settings" + const val PresetTextEntry = "preset-text-entry" + const val LoadNetImage = "load-net-image" + const val ResizeConvert = "resize-convert" + const val ResizeByFileSize = "resize-by-file-size" + const val LimitResize = "limit-resize" + const val ResizeType = "resize-type" + const val ImageScaleMode = "image-scale-mode" + const val ScaleColorSpace = "scale-color-space" + const val LimitResizeModes = "limit-resize-modes" + const val AspectRatioTargets = "aspect-ratio-targets" + const val UpscaleVsResize = "upscale-vs-resize" + const val CropStraighten = "crop-straighten" + const val Filters = "filters" + const val FilterOrder = "filter-order" + const val PreviewBeforeEditing = "preview-before-editing" + const val BackgroundRemover = "background-remover" + const val BackgroundEdges = "background-edges" + const val DrawWatermark = "draw-watermark" + const val WatermarkReadability = "watermark-readability" + const val DrawDefaults = "draw-defaults" + const val CollageStitchStack = "collage-stitch-stack" + const val ImageSplitting = "image-splitting" + const val ImageCutting = "image-cutting" + const val FormatConversion = "format-conversion" + const val FormatChoice = "format-choice" + const val AudioCoverExtractor = "audio-cover-extractor" + const val ExifPrivacy = "exif-privacy" + const val MetadataDatesLocation = "metadata-dates-location" + const val AutomaticExifCleanup = "automatic-exif-cleanup" + const val Filenames = "filenames" + const val FilenameCollisionSafety = "filename-collision-safety" + const val OverwriteFiles = "overwrite-files" + const val FilenamePatterns = "filename-patterns" + const val SaveLocation = "save-location" + const val SaveVsShare = "save-vs-share" + const val SaveOriginalFolder = "save-original-folder" + const val OneTimeSaveLocation = "one-time-save-location" + const val SkipLargerOutputs = "skip-larger-outputs" + const val QualityVsSize = "quality-vs-size" + const val AnimatedFormats = "animated-formats" + const val ComparePreview = "compare-preview" + const val PdfHub = "pdf-hub" + const val ImagesToPdf = "images-to-pdf" + const val PdfPageSizeMargins = "pdf-page-size-margins" + const val DocumentScanner = "document-scanner" + const val ScanCleanup = "scan-cleanup" + const val PdfProtection = "pdf-protection" + const val PdfPageOrganization = "pdf-page-organization" + const val PdfAnnotations = "pdf-annotations" + const val PdfOptimizeRepair = "pdf-optimize-repair" + const val PdfExtractImages = "pdf-extract-images" + const val PdfMetadataFlattenPrint = "pdf-metadata-flatten-print" + const val Ocr = "ocr" + const val OcrPreprocessing = "ocr-preprocessing" + const val OcrLanguageData = "ocr-language-data" + const val SearchablePdf = "searchable-pdf" + const val QrCode = "qr-code" + const val QrContentSafety = "qr-content-safety" + const val QrGeneration = "qr-generation" + const val Base64Checksum = "base64-checksum" + const val CipherZip = "cipher-zip" + const val ChecksumsForNames = "checksums-for-names" + const val ChecksumToolTabs = "checksum-tool-tabs" + const val ClipboardAndLinks = "clipboard-and-links" + const val ClipboardPrivacy = "clipboard-privacy" + const val PickColor = "pick-color" + const val ColorFormats = "color-formats" + const val PaletteLibrary = "palette-library" + const val Gradients = "gradients" + const val ColorLibrarySearch = "color-library-search" + const val MeshGradientsCollection = "mesh-gradients-collection" + const val ColorAccessibility = "color-accessibility" + const val AlphaBackgroundColor = "alpha-background-color" + const val AiModels = "ai-models" + const val AiParameters = "ai-parameters" + const val AiStability = "ai-stability" + const val ShaderStudio = "shader-studio" + const val SvgAscii = "svg-ascii" + const val NoiseGeneration = "noise-generation" + const val GeneratedAssetResolution = "generated-asset-resolution" + const val WallpaperExport = "wallpaper-export" + const val MarkupProjects = "markup-projects" + const val EncryptionWorkflow = "encryption-workflow" + const val ToolArrangement = "tool-arrangement" + const val LargeFiles = "large-files" + const val CacheAndPreviews = "cache-and-previews" + const val ScreenBehavior = "screen-behavior" + const val CompactControls = "compact-controls" + const val TransparentOutput = "transparent-output" + const val SharingImport = "sharing-import" + const val ExitConfirmation = "exit-confirmation" + const val LogsFeedback = "logs-feedback" + } + + private val gettingStarted = HelpCategory( + key = "getting-started", + title = R.string.help_category_getting_started, + subtitle = R.string.help_category_getting_started_sub, + icon = Icons.Outlined.Lightbulb + ) + + private val imageEditing = HelpCategory( + key = "image-editing", + title = R.string.help_category_image_editing, + subtitle = R.string.help_category_image_editing_sub, + icon = Icons.Outlined.MultipleImageEdit + ) + + private val filesAndMetadata = HelpCategory( + key = "files-metadata", + title = R.string.help_category_files_metadata, + subtitle = R.string.help_category_files_metadata_sub, + icon = Icons.Outlined.ExifEdit + ) + + private val pdfAndDocuments = HelpCategory( + key = "pdf-documents", + title = R.string.help_category_pdf_documents, + subtitle = R.string.help_category_pdf_documents_sub, + icon = Icons.Outlined.Pdf + ) + + private val textAndData = HelpCategory( + key = "text-data", + title = R.string.help_category_text_data, + subtitle = R.string.help_category_text_data_sub, + icon = Icons.Outlined.TextSearch + ) + + private val colorTools = HelpCategory( + key = "color-tools", + title = R.string.help_category_color_tools, + subtitle = R.string.help_category_color_tools_sub, + icon = Icons.Outlined.Palette + ) + + private val creativeTools = HelpCategory( + key = "creative-tools", + title = R.string.help_category_creative_tools, + subtitle = R.string.help_category_creative_tools_sub, + icon = Icons.Outlined.ImageConvert + ) + + private val troubleshooting = HelpCategory( + key = "troubleshooting", + title = R.string.help_category_troubleshooting, + subtitle = R.string.help_category_troubleshooting_sub, + icon = Icons.Outlined.Help + ) + + override val categories: List = listOf( + gettingStarted, + imageEditing, + filesAndMetadata, + pdfAndDocuments, + textAndData, + colorTools, + creativeTools, + troubleshooting + ) + + override val tips: List = listOf( + tip( + id = TipIds.ChooseTool, + title = R.string.help_tip_choose_tool_title, + subtitle = R.string.help_tip_choose_tool_subtitle, + icon = Icons.Outlined.Lightbulb, + category = gettingStarted, + deepLink = Screen.Main, + pageTitle = R.string.help_tip_choose_tool_page_title, + description = R.string.help_tip_choose_tool_description, + steps = listOf( + R.string.help_tip_choose_tool_step_1, + R.string.help_tip_choose_tool_step_2, + R.string.help_tip_choose_tool_step_3 + ) + ), + tip( + id = TipIds.ImportSaveShare, + title = R.string.help_tip_import_save_share_title, + subtitle = R.string.help_tip_import_save_share_subtitle, + icon = Icons.Outlined.MultipleImageEdit, + category = gettingStarted, + pageTitle = R.string.help_tip_import_save_share_page_title, + description = R.string.help_tip_import_save_share_description, + steps = listOf( + R.string.help_tip_import_save_share_step_1, + R.string.help_tip_import_save_share_step_2, + R.string.help_tip_import_save_share_step_3 + ) + ), + tip( + id = TipIds.BatchWorkflow, + title = R.string.help_tip_batch_workflow_title, + subtitle = R.string.help_tip_batch_workflow_subtitle, + icon = Icons.Outlined.MultipleImageEdit, + category = gettingStarted, + deepLink = Screen.ResizeAndConvert(), + pageTitle = R.string.help_tip_batch_workflow_page_title, + description = R.string.help_tip_batch_workflow_description, + steps = listOf( + R.string.help_tip_batch_workflow_step_1, + R.string.help_tip_batch_workflow_step_2, + R.string.help_tip_batch_workflow_step_3 + ) + ), + tip( + id = TipIds.SingleEdit, + title = R.string.help_tip_single_edit_title, + subtitle = R.string.help_tip_single_edit_subtitle, + icon = Icons.Outlined.ImageEdit, + category = gettingStarted, + deepLink = Screen.SingleEdit(), + pageTitle = R.string.help_tip_single_edit_page_title, + description = R.string.help_tip_single_edit_description, + steps = listOf( + R.string.help_tip_single_edit_step_1, + R.string.help_tip_single_edit_step_2, + R.string.help_tip_single_edit_step_3 + ) + ), + tip( + id = TipIds.PresetsSettings, + title = R.string.help_tip_presets_settings_title, + subtitle = R.string.help_tip_presets_settings_subtitle, + icon = Icons.Outlined.LabelPercent, + category = gettingStarted, + pageTitle = R.string.help_tip_presets_settings_page_title, + description = R.string.help_tip_presets_settings_description, + steps = listOf( + R.string.help_tip_presets_settings_step_1, + R.string.help_tip_presets_settings_step_2, + R.string.help_tip_presets_settings_step_3 + ) + ), + tip( + id = TipIds.DefaultValues, + title = R.string.help_tip_default_values_title, + subtitle = R.string.help_tip_default_values_subtitle, + icon = Icons.Rounded.SquareFoot, + category = gettingStarted, + deepLink = Screen.Settings(targetSetting = Setting.DefaultImageFormat), + pageTitle = R.string.help_tip_default_values_page_title, + description = R.string.help_tip_default_values_description, + steps = listOf( + R.string.help_tip_default_values_step_1, + R.string.help_tip_default_values_step_2, + R.string.help_tip_default_values_step_3 + ) + ), + tip( + id = TipIds.PickerLauncher, + title = R.string.help_tip_picker_launcher_title, + subtitle = R.string.help_tip_picker_launcher_subtitle, + icon = Icons.Outlined.Lightbulb, + category = gettingStarted, + deepLink = Screen.Settings(targetSetting = Setting.EnableLauncherMode), + pageTitle = R.string.help_tip_picker_launcher_page_title, + description = R.string.help_tip_picker_launcher_description, + steps = listOf( + R.string.help_tip_picker_launcher_step_1, + R.string.help_tip_picker_launcher_step_2, + R.string.help_tip_picker_launcher_step_3 + ) + ), + tip( + id = TipIds.ImageSource, + title = R.string.help_tip_image_source_title, + subtitle = R.string.help_tip_image_source_subtitle, + icon = Icons.Outlined.ImageSearch, + category = gettingStarted, + deepLink = Screen.Settings(targetSetting = Setting.ImagePickerMode), + pageTitle = R.string.help_tip_image_source_page_title, + description = R.string.help_tip_image_source_description, + steps = listOf( + R.string.help_tip_image_source_step_1, + R.string.help_tip_image_source_step_2, + R.string.help_tip_image_source_step_3 + ) + ), + tip( + id = TipIds.SkipFilePicking, + title = R.string.help_tip_skip_file_picking_title, + subtitle = R.string.help_tip_skip_file_picking_subtitle, + icon = Icons.Outlined.ImageSearch, + category = gettingStarted, + deepLink = Screen.Settings(targetSetting = Setting.SkipFilePicking), + pageTitle = R.string.help_tip_skip_file_picking_page_title, + description = R.string.help_tip_skip_file_picking_description, + steps = listOf( + R.string.help_tip_skip_file_picking_step_1, + R.string.help_tip_skip_file_picking_step_2, + R.string.help_tip_skip_file_picking_step_3 + ) + ), + tip( + id = TipIds.OpenEditDirectly, + title = R.string.help_tip_open_edit_directly_title, + subtitle = R.string.help_tip_open_edit_directly_subtitle, + icon = Icons.Outlined.ImageEdit, + category = gettingStarted, + deepLink = Screen.Settings(targetSetting = Setting.OpenEditInsteadOfPreview), + pageTitle = R.string.help_tip_open_edit_directly_page_title, + description = R.string.help_tip_open_edit_directly_description, + steps = listOf( + R.string.help_tip_open_edit_directly_step_1, + R.string.help_tip_open_edit_directly_step_2, + R.string.help_tip_open_edit_directly_step_3 + ) + ), + tip( + id = TipIds.FavoritesShareTools, + title = R.string.help_tip_favorites_share_tools_title, + subtitle = R.string.help_tip_favorites_share_tools_subtitle, + icon = Icons.Outlined.Bookmark, + category = gettingStarted, + deepLink = Screen.Settings(targetSetting = Setting.ToolsHiddenForShare), + pageTitle = R.string.help_tip_favorites_share_tools_page_title, + description = R.string.help_tip_favorites_share_tools_description, + steps = listOf( + R.string.help_tip_favorites_share_tools_step_1, + R.string.help_tip_favorites_share_tools_step_2, + R.string.help_tip_favorites_share_tools_step_3 + ) + ), + tip( + id = TipIds.ToolArrangement, + title = R.string.help_tip_tool_arrangement_title, + subtitle = R.string.help_tip_tool_arrangement_subtitle, + icon = Icons.Outlined.Lightbulb, + category = gettingStarted, + deepLink = Screen.Settings(targetSetting = Setting.ScreenOrder), + pageTitle = R.string.help_tip_tool_arrangement_page_title, + description = R.string.help_tip_tool_arrangement_description, + steps = listOf( + R.string.help_tip_tool_arrangement_step_1, + R.string.help_tip_tool_arrangement_step_2, + R.string.help_tip_tool_arrangement_step_3 + ) + ), + tip( + id = TipIds.BackupRestoreSettings, + title = R.string.help_tip_backup_restore_settings_title, + subtitle = R.string.help_tip_backup_restore_settings_subtitle, + icon = Icons.Outlined.HardDrive, + category = gettingStarted, + deepLink = Screen.Settings(targetSetting = Setting.Backup), + pageTitle = R.string.help_tip_backup_restore_settings_page_title, + description = R.string.help_tip_backup_restore_settings_description, + steps = listOf( + R.string.help_tip_backup_restore_settings_step_1, + R.string.help_tip_backup_restore_settings_step_2, + R.string.help_tip_backup_restore_settings_step_3 + ) + ), + tip( + id = TipIds.PresetTextEntry, + title = R.string.help_tip_preset_text_entry_title, + subtitle = R.string.help_tip_preset_text_entry_subtitle, + icon = Icons.Outlined.LabelPercent, + category = gettingStarted, + deepLink = Screen.Settings(targetSetting = Setting.CanEnterPresetsByTextField), + pageTitle = R.string.help_tip_preset_text_entry_page_title, + description = R.string.help_tip_preset_text_entry_description, + steps = listOf( + R.string.help_tip_preset_text_entry_step_1, + R.string.help_tip_preset_text_entry_step_2, + R.string.help_tip_preset_text_entry_step_3 + ) + ), + tip( + id = TipIds.LoadNetImage, + title = R.string.help_tip_load_net_image_title, + subtitle = R.string.help_tip_load_net_image_subtitle, + icon = Icons.Outlined.ImageDownload, + category = gettingStarted, + deepLink = Screen.LoadNetImage(), + pageTitle = R.string.help_tip_load_net_image_page_title, + description = R.string.help_tip_load_net_image_description, + steps = listOf( + R.string.help_tip_load_net_image_step_1, + R.string.help_tip_load_net_image_step_2, + R.string.help_tip_load_net_image_step_3 + ) + ), + tip( + id = TipIds.ResizeConvert, + title = R.string.help_tip_resize_convert_title, + subtitle = R.string.help_tip_resize_convert_subtitle, + icon = Icons.Outlined.MultipleImageEdit, + category = imageEditing, + deepLink = Screen.ResizeAndConvert(), + pageTitle = R.string.help_tip_resize_convert_page_title, + description = R.string.help_tip_resize_convert_description, + steps = listOf( + R.string.help_tip_resize_convert_step_1, + R.string.help_tip_resize_convert_step_2, + R.string.help_tip_resize_convert_step_3 + ) + ), + tip( + id = TipIds.ResizeByFileSize, + title = R.string.help_tip_resize_by_file_size_title, + subtitle = R.string.help_tip_resize_by_file_size_subtitle, + icon = Icons.Outlined.ImageWeight, + category = imageEditing, + deepLink = Screen.WeightResize(), + pageTitle = R.string.help_tip_resize_by_file_size_page_title, + description = R.string.help_tip_resize_by_file_size_description, + steps = listOf( + R.string.help_tip_resize_by_file_size_step_1, + R.string.help_tip_resize_by_file_size_step_2, + R.string.help_tip_resize_by_file_size_step_3 + ) + ), + tip( + id = TipIds.LimitResize, + title = R.string.help_tip_limit_resize_title, + subtitle = R.string.help_tip_limit_resize_subtitle, + icon = Icons.Outlined.ImageResize, + category = imageEditing, + deepLink = Screen.LimitResize(), + pageTitle = R.string.help_tip_limit_resize_page_title, + description = R.string.help_tip_limit_resize_description, + steps = listOf( + R.string.help_tip_limit_resize_step_1, + R.string.help_tip_limit_resize_step_2, + R.string.help_tip_limit_resize_step_3 + ) + ), + tip( + id = TipIds.ResizeType, + title = R.string.help_tip_resize_type_title, + subtitle = R.string.help_tip_resize_type_subtitle, + icon = Icons.Outlined.PhotoSizeSelectLarge, + category = imageEditing, + deepLink = Screen.Settings(targetSetting = Setting.DefaultResizeType), + pageTitle = R.string.help_tip_resize_type_page_title, + description = R.string.help_tip_resize_type_description, + steps = listOf( + R.string.help_tip_resize_type_step_1, + R.string.help_tip_resize_type_step_2, + R.string.help_tip_resize_type_step_3 + ) + ), + tip( + id = TipIds.ImageScaleMode, + title = R.string.help_tip_image_scale_mode_title, + subtitle = R.string.help_tip_image_scale_mode_subtitle, + icon = Icons.Outlined.QualityHigh, + category = imageEditing, + deepLink = Screen.Settings(targetSetting = Setting.DefaultScaleMode), + pageTitle = R.string.help_tip_image_scale_mode_page_title, + description = R.string.help_tip_image_scale_mode_description, + steps = listOf( + R.string.help_tip_image_scale_mode_step_1, + R.string.help_tip_image_scale_mode_step_2, + R.string.help_tip_image_scale_mode_step_3 + ) + ), + tip( + id = TipIds.ScaleColorSpace, + title = R.string.help_tip_scale_color_space_title, + subtitle = R.string.help_tip_scale_color_space_subtitle, + icon = Icons.Outlined.Palette, + category = imageEditing, + deepLink = Screen.Settings(targetSetting = Setting.DefaultColorSpace), + pageTitle = R.string.help_tip_scale_color_space_page_title, + description = R.string.help_tip_scale_color_space_description, + steps = listOf( + R.string.help_tip_scale_color_space_step_1, + R.string.help_tip_scale_color_space_step_2, + R.string.help_tip_scale_color_space_step_3 + ) + ), + tip( + id = TipIds.LimitResizeModes, + title = R.string.help_tip_limit_resize_modes_title, + subtitle = R.string.help_tip_limit_resize_modes_subtitle, + icon = Icons.Outlined.ImageWeight, + category = imageEditing, + deepLink = Screen.LimitResize(), + pageTitle = R.string.help_tip_limit_resize_modes_page_title, + description = R.string.help_tip_limit_resize_modes_description, + steps = listOf( + R.string.help_tip_limit_resize_modes_step_1, + R.string.help_tip_limit_resize_modes_step_2, + R.string.help_tip_limit_resize_modes_step_3 + ) + ), + tip( + id = TipIds.AspectRatioTargets, + title = R.string.help_tip_aspect_ratio_targets_title, + subtitle = R.string.help_tip_aspect_ratio_targets_subtitle, + icon = Icons.Outlined.AspectRatio, + category = imageEditing, + deepLink = Screen.ResizeAndConvert(), + pageTitle = R.string.help_tip_aspect_ratio_targets_page_title, + description = R.string.help_tip_aspect_ratio_targets_description, + steps = listOf( + R.string.help_tip_aspect_ratio_targets_step_1, + R.string.help_tip_aspect_ratio_targets_step_2, + R.string.help_tip_aspect_ratio_targets_step_3 + ) + ), + tip( + id = TipIds.UpscaleVsResize, + title = R.string.help_tip_upscale_vs_resize_title, + subtitle = R.string.help_tip_upscale_vs_resize_subtitle, + icon = Icons.Outlined.Neurology, + category = imageEditing, + deepLink = Screen.ResizeAndConvert(), + pageTitle = R.string.help_tip_upscale_vs_resize_page_title, + description = R.string.help_tip_upscale_vs_resize_description, + steps = listOf( + R.string.help_tip_upscale_vs_resize_step_1, + R.string.help_tip_upscale_vs_resize_step_2, + R.string.help_tip_upscale_vs_resize_step_3 + ) + ), + tip( + id = TipIds.CropStraighten, + title = R.string.help_tip_crop_straighten_title, + subtitle = R.string.help_tip_crop_straighten_subtitle, + icon = Icons.Rounded.CropSmall, + category = imageEditing, + deepLink = Screen.Crop(), + pageTitle = R.string.help_tip_crop_straighten_page_title, + description = R.string.help_tip_crop_straighten_description, + steps = listOf( + R.string.help_tip_crop_straighten_step_1, + R.string.help_tip_crop_straighten_step_2, + R.string.help_tip_crop_straighten_step_3 + ) + ), + tip( + id = TipIds.Filters, + title = R.string.help_tip_filters_title, + subtitle = R.string.help_tip_filters_subtitle, + icon = Icons.Outlined.AutoFixHigh, + category = imageEditing, + deepLink = Screen.Filter(), + pageTitle = R.string.help_tip_filters_page_title, + description = R.string.help_tip_filters_description, + steps = listOf( + R.string.help_tip_filters_step_1, + R.string.help_tip_filters_step_2, + R.string.help_tip_filters_step_3 + ) + ), + tip( + id = TipIds.FilterOrder, + title = R.string.help_tip_filter_order_title, + subtitle = R.string.help_tip_filter_order_subtitle, + icon = Icons.Outlined.AutoFixHigh, + category = imageEditing, + deepLink = Screen.Filter(), + pageTitle = R.string.help_tip_filter_order_page_title, + description = R.string.help_tip_filter_order_description, + steps = listOf( + R.string.help_tip_filter_order_step_1, + R.string.help_tip_filter_order_step_2, + R.string.help_tip_filter_order_step_3 + ) + ), + tip( + id = TipIds.PreviewBeforeEditing, + title = R.string.help_tip_preview_before_editing_title, + subtitle = R.string.help_tip_preview_before_editing_subtitle, + icon = Icons.Outlined.Preview, + category = imageEditing, + deepLink = Screen.ImagePreview(), + pageTitle = R.string.help_tip_preview_before_editing_page_title, + description = R.string.help_tip_preview_before_editing_description, + steps = listOf( + R.string.help_tip_preview_before_editing_step_1, + R.string.help_tip_preview_before_editing_step_2, + R.string.help_tip_preview_before_editing_step_3 + ) + ), + tip( + id = TipIds.BackgroundRemover, + title = R.string.help_tip_background_remover_title, + subtitle = R.string.help_tip_background_remover_subtitle, + icon = Icons.Rounded.Eraser, + category = imageEditing, + deepLink = Screen.EraseBackground(), + pageTitle = R.string.help_tip_background_remover_page_title, + description = R.string.help_tip_background_remover_description, + steps = listOf( + R.string.help_tip_background_remover_step_1, + R.string.help_tip_background_remover_step_2, + R.string.help_tip_background_remover_step_3 + ) + ), + tip( + id = TipIds.BackgroundEdges, + title = R.string.help_tip_background_edges_title, + subtitle = R.string.help_tip_background_edges_subtitle, + icon = Icons.Rounded.Eraser, + category = imageEditing, + deepLink = Screen.EraseBackground(), + pageTitle = R.string.help_tip_background_edges_page_title, + description = R.string.help_tip_background_edges_description, + steps = listOf( + R.string.help_tip_background_edges_step_1, + R.string.help_tip_background_edges_step_2, + R.string.help_tip_background_edges_step_3 + ) + ), + tip( + id = TipIds.DrawWatermark, + title = R.string.help_tip_draw_watermark_title, + subtitle = R.string.help_tip_draw_watermark_subtitle, + icon = Icons.Outlined.Draw, + category = imageEditing, + deepLink = Screen.Watermarking(), + pageTitle = R.string.help_tip_draw_watermark_page_title, + description = R.string.help_tip_draw_watermark_description, + steps = listOf( + R.string.help_tip_draw_watermark_step_1, + R.string.help_tip_draw_watermark_step_2, + R.string.help_tip_draw_watermark_step_3 + ) + ), + tip( + id = TipIds.WatermarkReadability, + title = R.string.help_tip_watermark_readability_title, + subtitle = R.string.help_tip_watermark_readability_subtitle, + icon = Icons.Outlined.Watermark, + category = imageEditing, + deepLink = Screen.Watermarking(), + pageTitle = R.string.help_tip_watermark_readability_page_title, + description = R.string.help_tip_watermark_readability_description, + steps = listOf( + R.string.help_tip_watermark_readability_step_1, + R.string.help_tip_watermark_readability_step_2, + R.string.help_tip_watermark_readability_step_3 + ) + ), + tip( + id = TipIds.DrawDefaults, + title = R.string.help_tip_draw_defaults_title, + subtitle = R.string.help_tip_draw_defaults_subtitle, + icon = Icons.Outlined.AutoFixHigh, + category = imageEditing, + deepLink = Screen.Settings(targetSetting = Setting.DefaultDrawLineWidth), + pageTitle = R.string.help_tip_draw_defaults_page_title, + description = R.string.help_tip_draw_defaults_description, + steps = listOf( + R.string.help_tip_draw_defaults_step_1, + R.string.help_tip_draw_defaults_step_2, + R.string.help_tip_draw_defaults_step_3 + ) + ), + tip( + id = TipIds.CollageStitchStack, + title = R.string.help_tip_collage_stitch_stack_title, + subtitle = R.string.help_tip_collage_stitch_stack_subtitle, + icon = Icons.Outlined.Collage, + category = imageEditing, + deepLink = Screen.CollageMaker(), + pageTitle = R.string.help_tip_collage_stitch_stack_page_title, + description = R.string.help_tip_collage_stitch_stack_description, + steps = listOf( + R.string.help_tip_collage_stitch_stack_step_1, + R.string.help_tip_collage_stitch_stack_step_2, + R.string.help_tip_collage_stitch_stack_step_3 + ) + ), + tip( + id = TipIds.ImageSplitting, + title = R.string.help_tip_image_splitting_title, + subtitle = R.string.help_tip_image_splitting_subtitle, + icon = Icons.Outlined.SplitAlt, + category = imageEditing, + deepLink = Screen.ImageSplitting(), + pageTitle = R.string.help_tip_image_splitting_page_title, + description = R.string.help_tip_image_splitting_description, + steps = listOf( + R.string.help_tip_image_splitting_step_1, + R.string.help_tip_image_splitting_step_2, + R.string.help_tip_image_splitting_step_3 + ) + ), + tip( + id = TipIds.ImageCutting, + title = R.string.help_tip_image_cutting_title, + subtitle = R.string.help_tip_image_cutting_subtitle, + icon = Icons.Rounded.Scissors, + category = imageEditing, + deepLink = Screen.ImageCutter(), + pageTitle = R.string.help_tip_image_cutting_page_title, + description = R.string.help_tip_image_cutting_description, + steps = listOf( + R.string.help_tip_image_cutting_step_1, + R.string.help_tip_image_cutting_step_2, + R.string.help_tip_image_cutting_step_3 + ) + ), + tip( + id = TipIds.FormatConversion, + title = R.string.help_tip_format_conversion_title, + subtitle = R.string.help_tip_format_conversion_subtitle, + icon = Icons.Outlined.ImageConvert, + category = filesAndMetadata, + deepLink = Screen.FormatConversion(), + pageTitle = R.string.help_tip_format_conversion_page_title, + description = R.string.help_tip_format_conversion_description, + steps = listOf( + R.string.help_tip_format_conversion_step_1, + R.string.help_tip_format_conversion_step_2, + R.string.help_tip_format_conversion_step_3 + ) + ), + tip( + id = TipIds.FormatChoice, + title = R.string.help_tip_format_choice_title, + subtitle = R.string.help_tip_format_choice_subtitle, + icon = Icons.Outlined.ImageConvert, + category = filesAndMetadata, + deepLink = Screen.FormatConversion(), + pageTitle = R.string.help_tip_format_choice_page_title, + description = R.string.help_tip_format_choice_description, + steps = listOf( + R.string.help_tip_format_choice_step_1, + R.string.help_tip_format_choice_step_2, + R.string.help_tip_format_choice_step_3 + ) + ), + tip( + id = TipIds.AudioCoverExtractor, + title = R.string.help_tip_audio_cover_extractor_title, + subtitle = R.string.help_tip_audio_cover_extractor_subtitle, + icon = Icons.Outlined.Album, + category = filesAndMetadata, + deepLink = Screen.AudioCoverExtractor(), + pageTitle = R.string.help_tip_audio_cover_extractor_page_title, + description = R.string.help_tip_audio_cover_extractor_description, + steps = listOf( + R.string.help_tip_audio_cover_extractor_step_1, + R.string.help_tip_audio_cover_extractor_step_2, + R.string.help_tip_audio_cover_extractor_step_3 + ) + ), + tip( + id = TipIds.ExifPrivacy, + title = R.string.help_tip_exif_privacy_title, + subtitle = R.string.help_tip_exif_privacy_subtitle, + icon = Icons.Outlined.ExifEdit, + category = filesAndMetadata, + deepLink = Screen.EditExif(), + pageTitle = R.string.help_tip_exif_privacy_page_title, + description = R.string.help_tip_exif_privacy_description, + steps = listOf( + R.string.help_tip_exif_privacy_step_1, + R.string.help_tip_exif_privacy_step_2, + R.string.help_tip_exif_privacy_step_3 + ) + ), + tip( + id = TipIds.MetadataDatesLocation, + title = R.string.help_tip_metadata_dates_location_title, + subtitle = R.string.help_tip_metadata_dates_location_subtitle, + icon = Icons.Outlined.ExifEdit, + category = filesAndMetadata, + deepLink = Screen.EditExif(), + pageTitle = R.string.help_tip_metadata_dates_location_page_title, + description = R.string.help_tip_metadata_dates_location_description, + steps = listOf( + R.string.help_tip_metadata_dates_location_step_1, + R.string.help_tip_metadata_dates_location_step_2, + R.string.help_tip_metadata_dates_location_step_3 + ) + ), + tip( + id = TipIds.AutomaticExifCleanup, + title = R.string.help_tip_automatic_exif_cleanup_title, + subtitle = R.string.help_tip_automatic_exif_cleanup_subtitle, + icon = Icons.Outlined.ExifEdit, + category = filesAndMetadata, + deepLink = Screen.Settings(targetSetting = Setting.AlwaysClearExif), + pageTitle = R.string.help_tip_automatic_exif_cleanup_page_title, + description = R.string.help_tip_automatic_exif_cleanup_description, + steps = listOf( + R.string.help_tip_automatic_exif_cleanup_step_1, + R.string.help_tip_automatic_exif_cleanup_step_2, + R.string.help_tip_automatic_exif_cleanup_step_3 + ) + ), + tip( + id = TipIds.Filenames, + title = R.string.help_tip_filenames_title, + subtitle = R.string.help_tip_filenames_subtitle, + icon = Icons.Outlined.File, + category = filesAndMetadata, + pageTitle = R.string.help_tip_filenames_page_title, + description = R.string.help_tip_filenames_description, + steps = listOf( + R.string.help_tip_filenames_step_1, + R.string.help_tip_filenames_step_2, + R.string.help_tip_filenames_step_3 + ) + ), + tip( + id = TipIds.FilenameCollisionSafety, + title = R.string.help_tip_filename_collision_safety_title, + subtitle = R.string.help_tip_filename_collision_safety_subtitle, + icon = Icons.Outlined.File, + category = filesAndMetadata, + deepLink = Screen.Settings(targetSetting = Setting.FilenamePattern), + pageTitle = R.string.help_tip_filename_collision_safety_page_title, + description = R.string.help_tip_filename_collision_safety_description, + steps = listOf( + R.string.help_tip_filename_collision_safety_step_1, + R.string.help_tip_filename_collision_safety_step_2, + R.string.help_tip_filename_collision_safety_step_3 + ) + ), + tip( + id = TipIds.OverwriteFiles, + title = R.string.help_tip_overwrite_files_title, + subtitle = R.string.help_tip_overwrite_files_subtitle, + icon = Icons.Outlined.FileReplace, + category = filesAndMetadata, + deepLink = Screen.Settings(targetSetting = Setting.OverwriteFiles), + pageTitle = R.string.help_tip_overwrite_files_page_title, + description = R.string.help_tip_overwrite_files_description, + steps = listOf( + R.string.help_tip_overwrite_files_step_1, + R.string.help_tip_overwrite_files_step_2, + R.string.help_tip_overwrite_files_step_3 + ) + ), + tip( + id = TipIds.FilenamePatterns, + title = R.string.help_tip_filename_patterns_title, + subtitle = R.string.help_tip_filename_patterns_subtitle, + icon = Icons.Outlined.Description, + category = filesAndMetadata, + deepLink = Screen.Settings(targetSetting = Setting.FilenamePattern), + pageTitle = R.string.help_tip_filename_patterns_page_title, + description = R.string.help_tip_filename_patterns_description, + steps = listOf( + R.string.help_tip_filename_patterns_step_1, + R.string.help_tip_filename_patterns_step_2, + R.string.help_tip_filename_patterns_step_3 + ) + ), + tip( + id = TipIds.SaveLocation, + title = R.string.help_tip_save_location_title, + subtitle = R.string.help_tip_save_location_subtitle, + icon = Icons.Outlined.SaveAs, + category = filesAndMetadata, + deepLink = Screen.Settings(targetSetting = Setting.SavingFolder), + pageTitle = R.string.help_tip_save_location_page_title, + description = R.string.help_tip_save_location_description, + steps = listOf( + R.string.help_tip_save_location_step_1, + R.string.help_tip_save_location_step_2, + R.string.help_tip_save_location_step_3 + ) + ), + tip( + id = TipIds.SaveVsShare, + title = R.string.help_tip_save_vs_share_title, + subtitle = R.string.help_tip_save_vs_share_subtitle, + icon = Icons.Outlined.Share, + category = filesAndMetadata, + pageTitle = R.string.help_tip_save_vs_share_page_title, + description = R.string.help_tip_save_vs_share_description, + steps = listOf( + R.string.help_tip_save_vs_share_step_1, + R.string.help_tip_save_vs_share_step_2, + R.string.help_tip_save_vs_share_step_3 + ) + ), + tip( + id = TipIds.SaveOriginalFolder, + title = R.string.help_tip_save_original_folder_title, + subtitle = R.string.help_tip_save_original_folder_subtitle, + icon = Icons.Outlined.SaveAs, + category = filesAndMetadata, + deepLink = Screen.Settings(targetSetting = Setting.SaveToOriginalFolder), + pageTitle = R.string.help_tip_save_original_folder_page_title, + description = R.string.help_tip_save_original_folder_description, + steps = listOf( + R.string.help_tip_save_original_folder_step_1, + R.string.help_tip_save_original_folder_step_2, + R.string.help_tip_save_original_folder_step_3 + ) + ), + tip( + id = TipIds.OneTimeSaveLocation, + title = R.string.help_tip_one_time_save_location_title, + subtitle = R.string.help_tip_one_time_save_location_subtitle, + icon = Icons.Outlined.Save, + category = filesAndMetadata, + deepLink = Screen.Settings(targetSetting = Setting.OneTimeSaveLocation), + pageTitle = R.string.help_tip_one_time_save_location_page_title, + description = R.string.help_tip_one_time_save_location_description, + steps = listOf( + R.string.help_tip_one_time_save_location_step_1, + R.string.help_tip_one_time_save_location_step_2, + R.string.help_tip_one_time_save_location_step_3 + ) + ), + tip( + id = TipIds.SkipLargerOutputs, + title = R.string.help_tip_skip_larger_outputs_title, + subtitle = R.string.help_tip_skip_larger_outputs_subtitle, + icon = Icons.Outlined.ImageWeight, + category = filesAndMetadata, + deepLink = Screen.Settings(targetSetting = Setting.AllowSkipIfLarger), + pageTitle = R.string.help_tip_skip_larger_outputs_page_title, + description = R.string.help_tip_skip_larger_outputs_description, + steps = listOf( + R.string.help_tip_skip_larger_outputs_step_1, + R.string.help_tip_skip_larger_outputs_step_2, + R.string.help_tip_skip_larger_outputs_step_3 + ) + ), + tip( + id = TipIds.QualityVsSize, + title = R.string.help_tip_quality_vs_size_title, + subtitle = R.string.help_tip_quality_vs_size_subtitle, + icon = Icons.Outlined.QualityHigh, + category = filesAndMetadata, + deepLink = Screen.ResizeAndConvert(), + pageTitle = R.string.help_tip_quality_vs_size_page_title, + description = R.string.help_tip_quality_vs_size_description, + steps = listOf( + R.string.help_tip_quality_vs_size_step_1, + R.string.help_tip_quality_vs_size_step_2, + R.string.help_tip_quality_vs_size_step_3 + ) + ), + tip( + id = TipIds.AnimatedFormats, + title = R.string.help_tip_animated_formats_title, + subtitle = R.string.help_tip_animated_formats_subtitle, + icon = Icons.Rounded.Animation, + category = filesAndMetadata, + deepLink = Screen.WebpTools(), + pageTitle = R.string.help_tip_animated_formats_page_title, + description = R.string.help_tip_animated_formats_description, + steps = listOf( + R.string.help_tip_animated_formats_step_1, + R.string.help_tip_animated_formats_step_2, + R.string.help_tip_animated_formats_step_3 + ) + ), + tip( + id = TipIds.ComparePreview, + title = R.string.help_tip_compare_preview_title, + subtitle = R.string.help_tip_compare_preview_subtitle, + icon = Icons.Outlined.Compare, + category = filesAndMetadata, + deepLink = Screen.Compare(), + pageTitle = R.string.help_tip_compare_preview_page_title, + description = R.string.help_tip_compare_preview_description, + steps = listOf( + R.string.help_tip_compare_preview_step_1, + R.string.help_tip_compare_preview_step_2, + R.string.help_tip_compare_preview_step_3 + ) + ), + tip( + id = TipIds.PdfHub, + title = R.string.help_tip_pdf_hub_title, + subtitle = R.string.help_tip_pdf_hub_subtitle, + icon = Icons.Outlined.Pdf, + category = pdfAndDocuments, + deepLink = Screen.PdfTools, + pageTitle = R.string.help_tip_pdf_hub_page_title, + description = R.string.help_tip_pdf_hub_description, + steps = listOf( + R.string.help_tip_pdf_hub_step_1, + R.string.help_tip_pdf_hub_step_2, + R.string.help_tip_pdf_hub_step_3 + ) + ), + tip( + id = TipIds.ImagesToPdf, + title = R.string.help_tip_images_to_pdf_title, + subtitle = R.string.help_tip_images_to_pdf_subtitle, + icon = Icons.Outlined.Scanner, + category = pdfAndDocuments, + deepLink = Screen.PdfTools.ImagesToPdf(), + pageTitle = R.string.help_tip_images_to_pdf_page_title, + description = R.string.help_tip_images_to_pdf_description, + steps = listOf( + R.string.help_tip_images_to_pdf_step_1, + R.string.help_tip_images_to_pdf_step_2, + R.string.help_tip_images_to_pdf_step_3 + ) + ), + tip( + id = TipIds.PdfPageSizeMargins, + title = R.string.help_tip_pdf_page_size_margins_title, + subtitle = R.string.help_tip_pdf_page_size_margins_subtitle, + icon = Icons.Outlined.Pdf, + category = pdfAndDocuments, + deepLink = Screen.PdfTools, + pageTitle = R.string.help_tip_pdf_page_size_margins_page_title, + description = R.string.help_tip_pdf_page_size_margins_description, + steps = listOf( + R.string.help_tip_pdf_page_size_margins_step_1, + R.string.help_tip_pdf_page_size_margins_step_2, + R.string.help_tip_pdf_page_size_margins_step_3 + ) + ), + tip( + id = TipIds.DocumentScanner, + title = R.string.help_tip_document_scanner_title, + subtitle = R.string.help_tip_document_scanner_subtitle, + icon = Icons.Outlined.DocumentScanner, + category = pdfAndDocuments, + deepLink = Screen.DocumentScanner, + pageTitle = R.string.help_tip_document_scanner_page_title, + description = R.string.help_tip_document_scanner_description, + steps = listOf( + R.string.help_tip_document_scanner_step_1, + R.string.help_tip_document_scanner_step_2, + R.string.help_tip_document_scanner_step_3 + ) + ), + tip( + id = TipIds.ScanCleanup, + title = R.string.help_tip_scan_cleanup_title, + subtitle = R.string.help_tip_scan_cleanup_subtitle, + icon = Icons.Outlined.DocumentScanner, + category = pdfAndDocuments, + deepLink = Screen.DocumentScanner, + pageTitle = R.string.help_tip_scan_cleanup_page_title, + description = R.string.help_tip_scan_cleanup_description, + steps = listOf( + R.string.help_tip_scan_cleanup_step_1, + R.string.help_tip_scan_cleanup_step_2, + R.string.help_tip_scan_cleanup_step_3 + ) + ), + tip( + id = TipIds.PdfProtection, + title = R.string.help_tip_pdf_protection_title, + subtitle = R.string.help_tip_pdf_protection_subtitle, + icon = Icons.Outlined.ShieldLock, + category = pdfAndDocuments, + deepLink = Screen.PdfTools, + pageTitle = R.string.help_tip_pdf_protection_page_title, + description = R.string.help_tip_pdf_protection_description, + steps = listOf( + R.string.help_tip_pdf_protection_step_1, + R.string.help_tip_pdf_protection_step_2, + R.string.help_tip_pdf_protection_step_3 + ) + ), + tip( + id = TipIds.PdfPageOrganization, + title = R.string.help_tip_pdf_page_organization_title, + subtitle = R.string.help_tip_pdf_page_organization_subtitle, + icon = Icons.Outlined.SwapVerticalCircle, + category = pdfAndDocuments, + deepLink = Screen.PdfTools, + pageTitle = R.string.help_tip_pdf_page_organization_page_title, + description = R.string.help_tip_pdf_page_organization_description, + steps = listOf( + R.string.help_tip_pdf_page_organization_step_1, + R.string.help_tip_pdf_page_organization_step_2, + R.string.help_tip_pdf_page_organization_step_3 + ) + ), + tip( + id = TipIds.PdfAnnotations, + title = R.string.help_tip_pdf_annotations_title, + subtitle = R.string.help_tip_pdf_annotations_subtitle, + icon = Icons.Outlined.BubbleDelete, + category = pdfAndDocuments, + deepLink = Screen.PdfTools, + pageTitle = R.string.help_tip_pdf_annotations_page_title, + description = R.string.help_tip_pdf_annotations_description, + steps = listOf( + R.string.help_tip_pdf_annotations_step_1, + R.string.help_tip_pdf_annotations_step_2, + R.string.help_tip_pdf_annotations_step_3 + ) + ), + tip( + id = TipIds.PdfOptimizeRepair, + title = R.string.help_tip_pdf_optimize_repair_title, + subtitle = R.string.help_tip_pdf_optimize_repair_subtitle, + icon = Icons.Outlined.Pdf, + category = pdfAndDocuments, + deepLink = Screen.PdfTools, + pageTitle = R.string.help_tip_pdf_optimize_repair_page_title, + description = R.string.help_tip_pdf_optimize_repair_description, + steps = listOf( + R.string.help_tip_pdf_optimize_repair_step_1, + R.string.help_tip_pdf_optimize_repair_step_2, + R.string.help_tip_pdf_optimize_repair_step_3 + ) + ), + tip( + id = TipIds.PdfExtractImages, + title = R.string.help_tip_pdf_extract_images_title, + subtitle = R.string.help_tip_pdf_extract_images_subtitle, + icon = Icons.Outlined.Unarchive, + category = pdfAndDocuments, + deepLink = Screen.PdfTools.ExtractImages(), + pageTitle = R.string.help_tip_pdf_extract_images_page_title, + description = R.string.help_tip_pdf_extract_images_description, + steps = listOf( + R.string.help_tip_pdf_extract_images_step_1, + R.string.help_tip_pdf_extract_images_step_2, + R.string.help_tip_pdf_extract_images_step_3 + ) + ), + tip( + id = TipIds.PdfMetadataFlattenPrint, + title = R.string.help_tip_pdf_metadata_flatten_print_title, + subtitle = R.string.help_tip_pdf_metadata_flatten_print_subtitle, + icon = Icons.Outlined.TagText, + category = pdfAndDocuments, + deepLink = Screen.PdfTools.Metadata(), + pageTitle = R.string.help_tip_pdf_metadata_flatten_print_page_title, + description = R.string.help_tip_pdf_metadata_flatten_print_description, + steps = listOf( + R.string.help_tip_pdf_metadata_flatten_print_step_1, + R.string.help_tip_pdf_metadata_flatten_print_step_2, + R.string.help_tip_pdf_metadata_flatten_print_step_3 + ) + ), + tip( + id = TipIds.Ocr, + title = R.string.help_tip_ocr_title, + subtitle = R.string.help_tip_ocr_subtitle, + icon = Icons.Outlined.TextSearch, + category = textAndData, + deepLink = Screen.RecognizeText(), + pageTitle = R.string.help_tip_ocr_page_title, + description = R.string.help_tip_ocr_description, + steps = listOf( + R.string.help_tip_ocr_step_1, + R.string.help_tip_ocr_step_2, + R.string.help_tip_ocr_step_3 + ) + ), + tip( + id = TipIds.OcrPreprocessing, + title = R.string.help_tip_ocr_preprocessing_title, + subtitle = R.string.help_tip_ocr_preprocessing_subtitle, + icon = Icons.Outlined.TextSearch, + category = textAndData, + deepLink = Screen.RecognizeText(), + pageTitle = R.string.help_tip_ocr_preprocessing_page_title, + description = R.string.help_tip_ocr_preprocessing_description, + steps = listOf( + R.string.help_tip_ocr_preprocessing_step_1, + R.string.help_tip_ocr_preprocessing_step_2, + R.string.help_tip_ocr_preprocessing_step_3 + ) + ), + tip( + id = TipIds.OcrLanguageData, + title = R.string.help_tip_ocr_language_data_title, + subtitle = R.string.help_tip_ocr_language_data_subtitle, + icon = Icons.Outlined.TextSearch, + category = textAndData, + deepLink = Screen.RecognizeText(), + pageTitle = R.string.help_tip_ocr_language_data_page_title, + description = R.string.help_tip_ocr_language_data_description, + steps = listOf( + R.string.help_tip_ocr_language_data_step_1, + R.string.help_tip_ocr_language_data_step_2, + R.string.help_tip_ocr_language_data_step_3 + ) + ), + tip( + id = TipIds.SearchablePdf, + title = R.string.help_tip_searchable_pdf_title, + subtitle = R.string.help_tip_searchable_pdf_subtitle, + icon = Icons.Outlined.Pdf, + category = textAndData, + deepLink = Screen.RecognizeText(), + pageTitle = R.string.help_tip_searchable_pdf_page_title, + description = R.string.help_tip_searchable_pdf_description, + steps = listOf( + R.string.help_tip_searchable_pdf_step_1, + R.string.help_tip_searchable_pdf_step_2, + R.string.help_tip_searchable_pdf_step_3 + ) + ), + tip( + id = TipIds.QrCode, + title = R.string.help_tip_qr_code_title, + subtitle = R.string.help_tip_qr_code_subtitle, + icon = Icons.Outlined.QrCode, + category = textAndData, + deepLink = Screen.ScanQrCode(), + pageTitle = R.string.help_tip_qr_code_page_title, + description = R.string.help_tip_qr_code_description, + steps = listOf( + R.string.help_tip_qr_code_step_1, + R.string.help_tip_qr_code_step_2, + R.string.help_tip_qr_code_step_3 + ) + ), + tip( + id = TipIds.QrContentSafety, + title = R.string.help_tip_qr_content_safety_title, + subtitle = R.string.help_tip_qr_content_safety_subtitle, + icon = Icons.Outlined.QrCode, + category = textAndData, + deepLink = Screen.ScanQrCode(), + pageTitle = R.string.help_tip_qr_content_safety_page_title, + description = R.string.help_tip_qr_content_safety_description, + steps = listOf( + R.string.help_tip_qr_content_safety_step_1, + R.string.help_tip_qr_content_safety_step_2, + R.string.help_tip_qr_content_safety_step_3 + ) + ), + tip( + id = TipIds.QrGeneration, + title = R.string.help_tip_qr_generation_title, + subtitle = R.string.help_tip_qr_generation_subtitle, + icon = Icons.Outlined.QrCode, + category = textAndData, + deepLink = Screen.ScanQrCode(), + pageTitle = R.string.help_tip_qr_generation_page_title, + description = R.string.help_tip_qr_generation_description, + steps = listOf( + R.string.help_tip_qr_generation_step_1, + R.string.help_tip_qr_generation_step_2, + R.string.help_tip_qr_generation_step_3 + ) + ), + tip( + id = TipIds.Base64Checksum, + title = R.string.help_tip_base64_checksum_title, + subtitle = R.string.help_tip_base64_checksum_subtitle, + icon = Icons.Outlined.Base64, + category = textAndData, + deepLink = Screen.Base64Tools(), + pageTitle = R.string.help_tip_base64_checksum_page_title, + description = R.string.help_tip_base64_checksum_description, + steps = listOf( + R.string.help_tip_base64_checksum_step_1, + R.string.help_tip_base64_checksum_step_2, + R.string.help_tip_base64_checksum_step_3 + ) + ), + tip( + id = TipIds.CipherZip, + title = R.string.help_tip_cipher_zip_title, + subtitle = R.string.help_tip_cipher_zip_subtitle, + icon = Icons.Outlined.FolderZip, + category = textAndData, + deepLink = Screen.Zip(), + pageTitle = R.string.help_tip_cipher_zip_page_title, + description = R.string.help_tip_cipher_zip_description, + steps = listOf( + R.string.help_tip_cipher_zip_step_1, + R.string.help_tip_cipher_zip_step_2, + R.string.help_tip_cipher_zip_step_3 + ) + ), + tip( + id = TipIds.ChecksumsForNames, + title = R.string.help_tip_checksums_for_names_title, + subtitle = R.string.help_tip_checksums_for_names_subtitle, + icon = Icons.Outlined.Base64, + category = textAndData, + deepLink = Screen.ChecksumTools(), + pageTitle = R.string.help_tip_checksums_for_names_page_title, + description = R.string.help_tip_checksums_for_names_description, + steps = listOf( + R.string.help_tip_checksums_for_names_step_1, + R.string.help_tip_checksums_for_names_step_2, + R.string.help_tip_checksums_for_names_step_3 + ) + ), + tip( + id = TipIds.ChecksumToolTabs, + title = R.string.help_tip_checksum_tool_tabs_title, + subtitle = R.string.help_tip_checksum_tool_tabs_subtitle, + icon = Icons.Rounded.HashTag, + category = textAndData, + deepLink = Screen.ChecksumTools(), + pageTitle = R.string.help_tip_checksum_tool_tabs_page_title, + description = R.string.help_tip_checksum_tool_tabs_description, + steps = listOf( + R.string.help_tip_checksum_tool_tabs_step_1, + R.string.help_tip_checksum_tool_tabs_step_2, + R.string.help_tip_checksum_tool_tabs_step_3 + ) + ), + tip( + id = TipIds.ClipboardAndLinks, + title = R.string.help_tip_clipboard_and_links_title, + subtitle = R.string.help_tip_clipboard_and_links_subtitle, + icon = Icons.Outlined.Share, + category = textAndData, + deepLink = Screen.Settings(targetSetting = Setting.AllowAutoClipboardPaste), + pageTitle = R.string.help_tip_clipboard_and_links_page_title, + description = R.string.help_tip_clipboard_and_links_description, + steps = listOf( + R.string.help_tip_clipboard_and_links_step_1, + R.string.help_tip_clipboard_and_links_step_2, + R.string.help_tip_clipboard_and_links_step_3 + ) + ), + tip( + id = TipIds.ClipboardPrivacy, + title = R.string.help_tip_clipboard_privacy_title, + subtitle = R.string.help_tip_clipboard_privacy_subtitle, + icon = Icons.Rounded.ContentPaste, + category = textAndData, + deepLink = Screen.Settings(targetSetting = Setting.AutoPinClipboard), + pageTitle = R.string.help_tip_clipboard_privacy_page_title, + description = R.string.help_tip_clipboard_privacy_description, + steps = listOf( + R.string.help_tip_clipboard_privacy_step_1, + R.string.help_tip_clipboard_privacy_step_2, + R.string.help_tip_clipboard_privacy_step_3 + ) + ), + tip( + id = TipIds.PickColor, + title = R.string.help_tip_pick_color_title, + subtitle = R.string.help_tip_pick_color_subtitle, + icon = Icons.Outlined.Eyedropper, + category = colorTools, + deepLink = Screen.PickColorFromImage(), + pageTitle = R.string.help_tip_pick_color_page_title, + description = R.string.help_tip_pick_color_description, + steps = listOf( + R.string.help_tip_pick_color_step_1, + R.string.help_tip_pick_color_step_2, + R.string.help_tip_pick_color_step_3 + ) + ), + tip( + id = TipIds.ColorFormats, + title = R.string.help_tip_color_formats_title, + subtitle = R.string.help_tip_color_formats_subtitle, + icon = Icons.Outlined.Eyedropper, + category = colorTools, + deepLink = Screen.PickColorFromImage(), + pageTitle = R.string.help_tip_color_formats_page_title, + description = R.string.help_tip_color_formats_description, + steps = listOf( + R.string.help_tip_color_formats_step_1, + R.string.help_tip_color_formats_step_2, + R.string.help_tip_color_formats_step_3 + ) + ), + tip( + id = TipIds.PaletteLibrary, + title = R.string.help_tip_palette_library_title, + subtitle = R.string.help_tip_palette_library_subtitle, + icon = Icons.Outlined.PaletteSwatch, + category = colorTools, + deepLink = Screen.PaletteTools(), + pageTitle = R.string.help_tip_palette_library_page_title, + description = R.string.help_tip_palette_library_description, + steps = listOf( + R.string.help_tip_palette_library_step_1, + R.string.help_tip_palette_library_step_2, + R.string.help_tip_palette_library_step_3 + ) + ), + tip( + id = TipIds.ColorLibrarySearch, + title = R.string.help_tip_color_library_search_title, + subtitle = R.string.help_tip_color_library_search_subtitle, + icon = Icons.Outlined.FormatPaintVariant, + category = colorTools, + deepLink = Screen.ColorLibrary, + pageTitle = R.string.help_tip_color_library_search_page_title, + description = R.string.help_tip_color_library_search_description, + steps = listOf( + R.string.help_tip_color_library_search_step_1, + R.string.help_tip_color_library_search_step_2, + R.string.help_tip_color_library_search_step_3 + ) + ), + tip( + id = TipIds.Gradients, + title = R.string.help_tip_gradients_title, + subtitle = R.string.help_tip_gradients_subtitle, + icon = Icons.Outlined.Gradient, + category = colorTools, + deepLink = Screen.GradientMaker(), + pageTitle = R.string.help_tip_gradients_page_title, + description = R.string.help_tip_gradients_description, + steps = listOf( + R.string.help_tip_gradients_step_1, + R.string.help_tip_gradients_step_2, + R.string.help_tip_gradients_step_3 + ) + ), + tip( + id = TipIds.MeshGradientsCollection, + title = R.string.help_tip_mesh_gradients_collection_title, + subtitle = R.string.help_tip_mesh_gradients_collection_subtitle, + icon = Icons.Outlined.MeshGradient, + category = colorTools, + deepLink = Screen.MeshGradients, + pageTitle = R.string.help_tip_mesh_gradients_collection_page_title, + description = R.string.help_tip_mesh_gradients_collection_description, + steps = listOf( + R.string.help_tip_mesh_gradients_collection_step_1, + R.string.help_tip_mesh_gradients_collection_step_2, + R.string.help_tip_mesh_gradients_collection_step_3 + ) + ), + tip( + id = TipIds.ColorAccessibility, + title = R.string.help_tip_color_accessibility_title, + subtitle = R.string.help_tip_color_accessibility_subtitle, + icon = Icons.Outlined.Palette, + category = colorTools, + deepLink = Screen.Settings(targetSetting = Setting.ColorBlindScheme), + pageTitle = R.string.help_tip_color_accessibility_page_title, + description = R.string.help_tip_color_accessibility_description, + steps = listOf( + R.string.help_tip_color_accessibility_step_1, + R.string.help_tip_color_accessibility_step_2, + R.string.help_tip_color_accessibility_step_3 + ) + ), + tip( + id = TipIds.AlphaBackgroundColor, + title = R.string.help_tip_alpha_background_color_title, + subtitle = R.string.help_tip_alpha_background_color_subtitle, + icon = Icons.Outlined.BackgroundColor, + category = colorTools, + deepLink = Screen.Settings(targetSetting = Setting.EnableBackgroundColorForAlphaFormats), + pageTitle = R.string.help_tip_alpha_background_color_page_title, + description = R.string.help_tip_alpha_background_color_description, + steps = listOf( + R.string.help_tip_alpha_background_color_step_1, + R.string.help_tip_alpha_background_color_step_2, + R.string.help_tip_alpha_background_color_step_3 + ) + ), + pagesTip( + id = TipIds.AiModels, + title = R.string.help_tip_ai_models_title, + subtitle = R.string.help_tip_ai_models_subtitle, + icon = Icons.Outlined.Neurology, + category = creativeTools, + deepLink = Screen.AiTools(), + pages = listOf( + page( + title = R.string.help_tip_ai_models_page_title, + description = R.string.help_tip_ai_models_description, + steps = listOf( + R.string.help_tip_ai_models_step_1, + R.string.help_tip_ai_models_step_2, + R.string.help_tip_ai_models_step_3 + ) + ), + page( + title = R.string.help_tip_ai_models_page_2_title, + description = R.string.help_tip_ai_models_page_2_description, + steps = listOf( + R.string.help_tip_ai_models_page_2_step_1, + R.string.help_tip_ai_models_page_2_step_2, + R.string.help_tip_ai_models_page_2_step_3 + ) + ) + ) + ), + pagesTip( + id = TipIds.AiParameters, + title = R.string.help_tip_ai_parameters_title, + subtitle = R.string.help_tip_ai_parameters_subtitle, + icon = Icons.Outlined.Neurology, + category = creativeTools, + deepLink = Screen.AiTools(), + pages = listOf( + page( + title = R.string.help_tip_ai_parameters_page_title, + description = R.string.help_tip_ai_parameters_description, + steps = listOf( + R.string.help_tip_ai_parameters_step_1, + R.string.help_tip_ai_parameters_step_2, + R.string.help_tip_ai_parameters_step_3 + ) + ), + page( + title = R.string.help_tip_ai_parameters_page_2_title, + description = R.string.help_tip_ai_parameters_page_2_description, + steps = listOf( + R.string.help_tip_ai_parameters_page_2_step_1, + R.string.help_tip_ai_parameters_page_2_step_2, + R.string.help_tip_ai_parameters_page_2_step_3 + ) + ) + ) + ), + pagesTip( + id = TipIds.AiStability, + title = R.string.help_tip_ai_stability_title, + subtitle = R.string.help_tip_ai_stability_subtitle, + icon = Icons.Outlined.Neurology, + category = troubleshooting, + deepLink = Screen.AiTools(), + pages = listOf( + page( + title = R.string.help_tip_ai_stability_page_title, + description = R.string.help_tip_ai_stability_description, + steps = listOf( + R.string.help_tip_ai_stability_step_1, + R.string.help_tip_ai_stability_step_2, + R.string.help_tip_ai_stability_step_3 + ) + ), + page( + title = R.string.help_tip_ai_stability_page_2_title, + description = R.string.help_tip_ai_stability_page_2_description, + steps = listOf( + R.string.help_tip_ai_stability_page_2_step_1, + R.string.help_tip_ai_stability_page_2_step_2, + R.string.help_tip_ai_stability_page_2_step_3 + ) + ) + ) + ), + tip( + id = TipIds.ShaderStudio, + title = R.string.help_tip_shader_studio_title, + subtitle = R.string.help_tip_shader_studio_subtitle, + icon = Icons.Rounded.EvShadow, + category = creativeTools, + deepLink = Screen.ShaderStudio, + pageTitle = R.string.help_tip_shader_studio_page_title, + description = R.string.help_tip_shader_studio_description, + steps = listOf( + R.string.help_tip_shader_studio_step_1, + R.string.help_tip_shader_studio_step_2, + R.string.help_tip_shader_studio_step_3 + ) + ), + tip( + id = TipIds.SvgAscii, + title = R.string.help_tip_svg_ascii_title, + subtitle = R.string.help_tip_svg_ascii_subtitle, + icon = Icons.Outlined.VectorPolyline, + category = creativeTools, + deepLink = Screen.SvgMaker(), + pageTitle = R.string.help_tip_svg_ascii_page_title, + description = R.string.help_tip_svg_ascii_description, + steps = listOf( + R.string.help_tip_svg_ascii_step_1, + R.string.help_tip_svg_ascii_step_2, + R.string.help_tip_svg_ascii_step_3 + ) + ), + tip( + id = TipIds.NoiseGeneration, + title = R.string.help_tip_noise_generation_title, + subtitle = R.string.help_tip_noise_generation_subtitle, + icon = Icons.Outlined.NoiseAlt, + category = creativeTools, + deepLink = Screen.NoiseGeneration, + pageTitle = R.string.help_tip_noise_generation_page_title, + description = R.string.help_tip_noise_generation_description, + steps = listOf( + R.string.help_tip_noise_generation_step_1, + R.string.help_tip_noise_generation_step_2, + R.string.help_tip_noise_generation_step_3 + ) + ), + tip( + id = TipIds.GeneratedAssetResolution, + title = R.string.help_tip_generated_asset_resolution_title, + subtitle = R.string.help_tip_generated_asset_resolution_subtitle, + icon = Icons.Outlined.PhotoSizeSelectSmall, + category = creativeTools, + pageTitle = R.string.help_tip_generated_asset_resolution_page_title, + description = R.string.help_tip_generated_asset_resolution_description, + steps = listOf( + R.string.help_tip_generated_asset_resolution_step_1, + R.string.help_tip_generated_asset_resolution_step_2, + R.string.help_tip_generated_asset_resolution_step_3 + ) + ), + tip( + id = TipIds.WallpaperExport, + title = R.string.help_tip_wallpaper_export_title, + subtitle = R.string.help_tip_wallpaper_export_subtitle, + icon = Icons.Outlined.WallpaperAlt, + category = creativeTools, + deepLink = Screen.WallpapersExport, + pageTitle = R.string.help_tip_wallpaper_export_page_title, + description = R.string.help_tip_wallpaper_export_description, + steps = listOf( + R.string.help_tip_wallpaper_export_step_1, + R.string.help_tip_wallpaper_export_step_2, + R.string.help_tip_wallpaper_export_step_3 + ) + ), + tip( + id = TipIds.MarkupProjects, + title = R.string.help_tip_markup_projects_title, + subtitle = R.string.help_tip_markup_projects_subtitle, + icon = Icons.Outlined.StackSticky, + category = creativeTools, + deepLink = Screen.MarkupLayers(), + pageTitle = R.string.help_tip_markup_projects_page_title, + description = R.string.help_tip_markup_projects_description, + steps = listOf( + R.string.help_tip_markup_projects_step_1, + R.string.help_tip_markup_projects_step_2, + R.string.help_tip_markup_projects_step_3 + ) + ), + tip( + id = TipIds.EncryptionWorkflow, + title = R.string.help_tip_encryption_workflow_title, + subtitle = R.string.help_tip_encryption_workflow_subtitle, + icon = Icons.Outlined.Encrypted, + category = textAndData, + deepLink = Screen.Cipher(), + pageTitle = R.string.help_tip_encryption_workflow_page_title, + description = R.string.help_tip_encryption_workflow_description, + steps = listOf( + R.string.help_tip_encryption_workflow_step_1, + R.string.help_tip_encryption_workflow_step_2, + R.string.help_tip_encryption_workflow_step_3 + ) + ), + tip( + id = TipIds.LargeFiles, + title = R.string.help_tip_large_files_title, + subtitle = R.string.help_tip_large_files_subtitle, + icon = Icons.Outlined.Help, + category = troubleshooting, + pageTitle = R.string.help_tip_large_files_page_title, + description = R.string.help_tip_large_files_description, + steps = listOf( + R.string.help_tip_large_files_step_1, + R.string.help_tip_large_files_step_2, + R.string.help_tip_large_files_step_3 + ) + ), + tip( + id = TipIds.CacheAndPreviews, + title = R.string.help_tip_cache_and_previews_title, + subtitle = R.string.help_tip_cache_and_previews_subtitle, + icon = Icons.Outlined.Help, + category = troubleshooting, + deepLink = Screen.Settings(targetSetting = Setting.GeneratePreviews), + pageTitle = R.string.help_tip_cache_and_previews_page_title, + description = R.string.help_tip_cache_and_previews_description, + steps = listOf( + R.string.help_tip_cache_and_previews_step_1, + R.string.help_tip_cache_and_previews_step_2, + R.string.help_tip_cache_and_previews_step_3 + ) + ), + tip( + id = TipIds.ScreenBehavior, + title = R.string.help_tip_screen_behavior_title, + subtitle = R.string.help_tip_screen_behavior_subtitle, + icon = Icons.Outlined.Help, + category = troubleshooting, + deepLink = Screen.Settings(targetSetting = Setting.SecureMode), + pageTitle = R.string.help_tip_screen_behavior_page_title, + description = R.string.help_tip_screen_behavior_description, + steps = listOf( + R.string.help_tip_screen_behavior_step_1, + R.string.help_tip_screen_behavior_step_2, + R.string.help_tip_screen_behavior_step_3 + ) + ), + tip( + id = TipIds.CompactControls, + title = R.string.help_tip_compact_controls_title, + subtitle = R.string.help_tip_compact_controls_subtitle, + icon = Icons.Outlined.Help, + category = troubleshooting, + deepLink = Screen.Settings(targetSetting = Setting.UseCompactSelectors), + pageTitle = R.string.help_tip_compact_controls_page_title, + description = R.string.help_tip_compact_controls_description, + steps = listOf( + R.string.help_tip_compact_controls_step_1, + R.string.help_tip_compact_controls_step_2, + R.string.help_tip_compact_controls_step_3 + ) + ), + tip( + id = TipIds.TransparentOutput, + title = R.string.help_tip_transparent_output_title, + subtitle = R.string.help_tip_transparent_output_subtitle, + icon = Icons.Rounded.Eraser, + category = troubleshooting, + deepLink = Screen.EraseBackground(), + pageTitle = R.string.help_tip_transparent_output_page_title, + description = R.string.help_tip_transparent_output_description, + steps = listOf( + R.string.help_tip_transparent_output_step_1, + R.string.help_tip_transparent_output_step_2, + R.string.help_tip_transparent_output_step_3 + ) + ), + tip( + id = TipIds.SharingImport, + title = R.string.help_tip_sharing_import_title, + subtitle = R.string.help_tip_sharing_import_subtitle, + icon = Icons.Outlined.Share, + category = troubleshooting, + pageTitle = R.string.help_tip_sharing_import_page_title, + description = R.string.help_tip_sharing_import_description, + steps = listOf( + R.string.help_tip_sharing_import_step_1, + R.string.help_tip_sharing_import_step_2, + R.string.help_tip_sharing_import_step_3 + ) + ), + tip( + id = TipIds.ExitConfirmation, + title = R.string.help_tip_exit_confirmation_title, + subtitle = R.string.help_tip_exit_confirmation_subtitle, + icon = Icons.Outlined.SaveConfirm, + category = troubleshooting, + deepLink = Screen.Settings(targetSetting = Setting.EnableToolExitConfirmation), + pageTitle = R.string.help_tip_exit_confirmation_page_title, + description = R.string.help_tip_exit_confirmation_description, + steps = listOf( + R.string.help_tip_exit_confirmation_step_1, + R.string.help_tip_exit_confirmation_step_2, + R.string.help_tip_exit_confirmation_step_3 + ) + ), + tip( + id = TipIds.LogsFeedback, + title = R.string.help_tip_logs_feedback_title, + subtitle = R.string.help_tip_logs_feedback_subtitle, + icon = Icons.Outlined.Help, + category = troubleshooting, + deepLink = Screen.AppLogs, + pageTitle = R.string.help_tip_logs_feedback_page_title, + description = R.string.help_tip_logs_feedback_description, + steps = listOf( + R.string.help_tip_logs_feedback_step_1, + R.string.help_tip_logs_feedback_step_2, + R.string.help_tip_logs_feedback_step_3 + ) + ) + ) + + override fun getTipsForCategory(category: HelpCategory): List = tips.filter { + it.category == category + } + + override fun getTip(id: String): HelpTip? = tips.firstOrNull { + it.id == id + } + + override fun getCategory(categoryKey: String): HelpCategory? = categories.firstOrNull { + it.key == categoryKey + } + + private fun tip( + id: String, + @StringRes title: Int, + @StringRes subtitle: Int, + icon: ImageVector, + category: HelpCategory, + @StringRes pageTitle: Int, + @StringRes description: Int, + steps: List, + deepLink: Screen? = null + ) = HelpTip( + id = id, + title = title, + subtitle = subtitle, + icon = icon, + category = category, + pages = listOf( + HelpPage( + title = pageTitle, + description = description, + steps = steps + ) + ), + deepLink = deepLink + ) + + private fun pagesTip( + id: String, + @StringRes title: Int, + @StringRes subtitle: Int, + icon: ImageVector, + category: HelpCategory, + pages: List, + deepLink: Screen? = null + ) = HelpTip( + id = id, + title = title, + subtitle = subtitle, + icon = icon, + category = category, + pages = pages, + deepLink = deepLink + ) + + private fun page( + @StringRes title: Int, + @StringRes description: Int, + steps: List + ) = HelpPage( + title = title, + description = description, + steps = steps + ) +} diff --git a/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/di/HelpModule.kt b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/di/HelpModule.kt new file mode 100644 index 0000000..3b8f66b --- /dev/null +++ b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/di/HelpModule.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.help.di + +import com.t8rin.imagetoolbox.feature.help.data.HelpRepositoryImpl +import com.t8rin.imagetoolbox.feature.help.domain.HelpRepository +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 HelpModule { + + @Binds + @Singleton + fun repository( + impl: HelpRepositoryImpl + ): HelpRepository + +} \ No newline at end of file diff --git a/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/domain/HelpRepository.kt b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/domain/HelpRepository.kt new file mode 100644 index 0000000..f394ff7 --- /dev/null +++ b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/domain/HelpRepository.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.help.domain + +import com.t8rin.imagetoolbox.feature.help.domain.model.HelpCategory +import com.t8rin.imagetoolbox.feature.help.domain.model.HelpTip + +interface HelpRepository { + + val categories: List + + val tips: List + + fun getTipsForCategory(category: HelpCategory): List + + fun getTip(id: String): HelpTip? + + fun getCategory(categoryKey: String): HelpCategory? + +} \ No newline at end of file diff --git a/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/domain/model/HelpCategory.kt b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/domain/model/HelpCategory.kt new file mode 100644 index 0000000..d9c1a7d --- /dev/null +++ b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/domain/model/HelpCategory.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.help.domain.model + +data class HelpCategory( + val key: String, + val title: Int, + val subtitle: Int, + val icon: Any +) \ No newline at end of file diff --git a/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/domain/model/HelpPage.kt b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/domain/model/HelpPage.kt new file mode 100644 index 0000000..603e04c --- /dev/null +++ b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/domain/model/HelpPage.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.help.domain.model + +data class HelpPage( + val title: Int, + val description: Int, + val steps: List +) \ No newline at end of file diff --git a/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/domain/model/HelpTip.kt b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/domain/model/HelpTip.kt new file mode 100644 index 0000000..9a2fb1e --- /dev/null +++ b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/domain/model/HelpTip.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.help.domain.model + +import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen + +data class HelpTip( + val id: String, + val title: Int, + val subtitle: Int, + val icon: Any, + val category: HelpCategory, + val pages: List, + val deepLink: Screen? = null +) \ No newline at end of file diff --git a/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/HelpContent.kt b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/HelpContent.kt new file mode 100644 index 0000000..9f215ef --- /dev/null +++ b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/HelpContent.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.help.presentation + +import androidx.compose.runtime.Composable +import com.t8rin.imagetoolbox.feature.help.presentation.components.HelpListContent +import com.t8rin.imagetoolbox.feature.help.presentation.components.HelpState +import com.t8rin.imagetoolbox.feature.help.presentation.components.TutorialCategoryContent +import com.t8rin.imagetoolbox.feature.help.presentation.components.TutorialDetailContent +import com.t8rin.imagetoolbox.feature.help.presentation.screenLogic.HelpComponent + +@Composable +fun HelpContent( + component: HelpComponent +) { + when (val state = component.state) { + is HelpState.Categories -> { + HelpListContent( + categories = state.categories, + filteredTips = component.filteredTips, + isSearching = component.isSearching, + searchKeyword = component.searchKeyword, + onOpenCategory = component::openCategory, + onOpenTip = component::openTip, + onSearchingChange = component::updateIsSearching, + onSearchKeywordChange = component::updateSearch, + onGoBack = component.onGoBack + ) + } + + is HelpState.TutorialCategory -> { + TutorialCategoryContent( + category = state.category, + filteredTips = component.filteredTips, + isSearching = component.isSearching, + searchKeyword = component.searchKeyword, + onOpenTip = component::openTip, + onSearchingChange = component::updateIsSearching, + onSearchKeywordChange = component::updateSearch, + onGoBack = component.onGoBack + ) + } + + is HelpState.TutorialDetails -> { + TutorialDetailContent( + tip = state.tip, + onGoBack = component.onGoBack, + onNavigate = component.onNavigate + ) + } + } +} \ No newline at end of file diff --git a/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/HelpListContent.kt b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/HelpListContent.kt new file mode 100644 index 0000000..8544c7f --- /dev/null +++ b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/HelpListContent.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.help.presentation.components + +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.runtime.Composable +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.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.help.domain.model.HelpCategory +import com.t8rin.imagetoolbox.feature.help.domain.model.HelpTip + +@Composable +internal fun HelpListContent( + categories: List, + filteredTips: List, + isSearching: Boolean, + searchKeyword: String, + onOpenCategory: (HelpCategory) -> Unit, + onOpenTip: (HelpTip) -> Unit, + onSearchingChange: (Boolean) -> Unit, + onSearchKeywordChange: (String) -> Unit, + onGoBack: () -> Unit +) { + HelpScaffold( + title = stringResource(R.string.help_tips), + onGoBack = onGoBack, + isSearching = isSearching, + searchKeyword = searchKeyword, + onSearchingChange = onSearchingChange, + onSearchKeywordChange = onSearchKeywordChange + ) { contentPadding -> + AnimatedContent( + targetState = searchKeyword.isBlank() to filteredTips.isNotEmpty(), + modifier = Modifier.fillMaxSize() + ) { (isSearchEmpty, haveData) -> + if (isSearchEmpty) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = contentPadding, + verticalArrangement = Arrangement.spacedBy(4.dp), + flingBehavior = enhancedFlingBehavior() + ) { + itemsIndexed( + items = categories, + key = { _, category -> category.key } + ) { index, category -> + PreferenceItem( + title = stringResource(category.title), + subtitle = stringResource(category.subtitle), + startIcon = category.icon as? ImageVector, + shape = ShapeDefaults.byIndex(index, categories.size), + onClick = { onOpenCategory(category) }, + modifier = Modifier + .fillMaxWidth() + .animateItem() + ) + } + } + } else if (haveData) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = contentPadding, + verticalArrangement = Arrangement.spacedBy(4.dp), + flingBehavior = enhancedFlingBehavior() + ) { + itemsIndexed( + items = filteredTips, + key = { _, tip -> tip.id } + ) { index, tip -> + PreferenceItem( + title = stringResource(tip.title), + subtitle = stringResource(tip.subtitle), + startIcon = tip.icon as? ImageVector, + shape = ShapeDefaults.byIndex(index, filteredTips.size), + onClick = { onOpenTip(tip) }, + modifier = Modifier + .fillMaxWidth() + .animateItem() + ) + } + } + } else { + HelpSearchEmptyContent(contentPadding) + } + } + } +} \ No newline at end of file diff --git a/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/HelpScaffold.kt b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/HelpScaffold.kt new file mode 100644 index 0000000..4ac583e --- /dev/null +++ b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/HelpScaffold.kt @@ -0,0 +1,245 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.help.presentation.components + +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.text.KeyboardOptions +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.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.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.Search +import com.t8rin.imagetoolbox.core.resources.icons.SearchOff +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.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.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.TopAppBarEmoji +import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField +import com.t8rin.imagetoolbox.core.ui.widget.text.marquee + +@Composable +internal fun HelpScaffold( + title: String, + onGoBack: () -> Unit, + isSearching: Boolean = false, + searchKeyword: String = "", + onSearchingChange: ((Boolean) -> Unit)? = null, + onSearchKeywordChange: (String) -> Unit = {}, + content: @Composable (PaddingValues) -> Unit +) { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + val focus = LocalFocusManager.current + val settingsState = LocalSettingsState.current + + Scaffold( + modifier = Modifier + .fillMaxSize() + .clearFocusOnTap() + .nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + EnhancedTopAppBar( + type = EnhancedTopAppBarType.Large, + title = { + Text( + text = title, + modifier = Modifier.marquee() + ) + }, + scrollBehavior = scrollBehavior, + navigationIcon = { + EnhancedIconButton( + onClick = onGoBack, + containerColor = Color.Transparent + ) { + Icon( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = stringResource(R.string.exit) + ) + } + }, + actions = { + TopAppBarEmoji() + } + ) + }, + bottomBar = { + if (onSearchingChange != null) { + 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 = onSearchKeywordChange, + endIcon = { + EnhancedIconButton( + onClick = { + if (searchKeyword.isNotBlank()) { + onSearchKeywordChange("") + } else { + onSearchingChange(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 = { onSearchingChange(true) }, + modifier = Modifier.align( + settingsState.fabAlignment.takeIf { it != Alignment.BottomCenter } + ?: Alignment.BottomEnd + ) + ) { + Icon( + imageVector = Icons.Outlined.Search, + contentDescription = null + ) + } + } + } + } + } + } + ) { contentPadding -> + content(contentPadding + PaddingValues(16.dp)) + } +} + +@Composable +internal fun HelpSearchEmptyContent( + contentPadding: PaddingValues +) { + 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.Outlined.SearchOff, + contentDescription = null, + modifier = Modifier + .weight(2f) + .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) + .fillMaxSize() + ) + Spacer(Modifier.weight(1f)) + } +} \ No newline at end of file diff --git a/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/HelpState.kt b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/HelpState.kt new file mode 100644 index 0000000..019a994 --- /dev/null +++ b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/HelpState.kt @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.help.presentation.components + +import com.t8rin.imagetoolbox.feature.help.domain.model.HelpCategory +import com.t8rin.imagetoolbox.feature.help.domain.model.HelpTip + +sealed interface HelpState { + data class Categories(val categories: List) : HelpState + + data class TutorialCategory(val category: HelpCategory) : HelpState + + data class TutorialDetails(val tip: HelpTip) : HelpState +} \ No newline at end of file diff --git a/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/TutorialCategoryContent.kt b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/TutorialCategoryContent.kt new file mode 100644 index 0000000..93604c9 --- /dev/null +++ b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/TutorialCategoryContent.kt @@ -0,0 +1,88 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.help.presentation.components + +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.runtime.Composable +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.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.help.domain.model.HelpCategory +import com.t8rin.imagetoolbox.feature.help.domain.model.HelpTip + +@Composable +internal fun TutorialCategoryContent( + category: HelpCategory, + filteredTips: List, + isSearching: Boolean, + searchKeyword: String, + onOpenTip: (HelpTip) -> Unit, + onSearchingChange: (Boolean) -> Unit, + onSearchKeywordChange: (String) -> Unit, + onGoBack: () -> Unit +) { + HelpScaffold( + title = stringResource(category.title), + onGoBack = onGoBack, + isSearching = isSearching, + searchKeyword = searchKeyword, + onSearchingChange = onSearchingChange, + onSearchKeywordChange = onSearchKeywordChange + ) { contentPadding -> + AnimatedContent( + targetState = filteredTips.isNotEmpty(), + modifier = Modifier.fillMaxSize() + ) { haveData -> + if (haveData) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = contentPadding, + verticalArrangement = Arrangement.spacedBy(4.dp), + flingBehavior = enhancedFlingBehavior() + ) { + itemsIndexed( + items = filteredTips, + key = { _, tip -> tip.id } + ) { index, tip -> + PreferenceItem( + title = stringResource(tip.title), + subtitle = stringResource(tip.subtitle), + startIcon = tip.icon as? ImageVector, + shape = ShapeDefaults.byIndex(index, filteredTips.size), + onClick = { onOpenTip(tip) }, + modifier = Modifier + .fillMaxWidth() + .animateItem() + ) + } + } + } else { + HelpSearchEmptyContent(contentPadding) + } + } + } +} \ No newline at end of file diff --git a/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/TutorialDetailContent.kt b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/TutorialDetailContent.kt new file mode 100644 index 0000000..01c3e1f --- /dev/null +++ b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/TutorialDetailContent.kt @@ -0,0 +1,83 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.help.presentation.components + +import androidx.compose.foundation.layout.Arrangement +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.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Icon +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.OpenInNew +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.enhancedFlingBehavior +import com.t8rin.imagetoolbox.feature.help.domain.model.HelpTip + +@Composable +internal fun TutorialDetailContent( + tip: HelpTip, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit +) { + HelpScaffold( + title = stringResource(tip.title), + onGoBack = onGoBack + ) { contentPadding -> + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = contentPadding, + verticalArrangement = Arrangement.spacedBy(12.dp), + flingBehavior = enhancedFlingBehavior() + ) { + items( + items = tip.pages, + key = { it.title } + ) { page -> + TutorialPageItem(page) + } + tip.deepLink?.let { screen -> + item { + EnhancedButton( + onClick = { onNavigate(screen) }, + modifier = Modifier + .fillMaxWidth() + .padding(top = 12.dp) + ) { + Icon( + imageVector = Icons.Rounded.OpenInNew, + contentDescription = null + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(text = stringResource(R.string.open_tool)) + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/TutorialPageItem.kt b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/TutorialPageItem.kt new file mode 100644 index 0000000..7a05900 --- /dev/null +++ b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/components/TutorialPageItem.kt @@ -0,0 +1,121 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.help.presentation.components + +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.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +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.clip +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.ui.widget.modifier.ShapeDefaults +import com.t8rin.imagetoolbox.core.ui.widget.modifier.container +import com.t8rin.imagetoolbox.feature.help.domain.model.HelpPage + +@Composable +internal fun TutorialPageItem( + page: HelpPage +) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = stringResource(page.title), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold + ) + Text( + text = stringResource(page.description), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + if (page.steps.isNotEmpty()) { + Column( + modifier = Modifier.padding(top = 12.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + page.steps.forEachIndexed { index, stepId -> + StepItem( + stepNumber = index + 1, + text = stringResource(stepId), + shape = ShapeDefaults.byIndex(index, page.steps.size) + ) + } + } + } + } +} + +@Composable +private fun StepItem( + stepNumber: Int, + text: String, + shape: Shape, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier + .fillMaxWidth() + .container( + shape = shape, + color = MaterialTheme.colorScheme.surfaceContainerLowest + ) + .padding(8.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.Top + ) { + Box( + modifier = Modifier + .size(28.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.primaryContainer), + contentAlignment = Alignment.Center + ) { + Text( + text = "$stepNumber", + style = MaterialTheme.typography.labelMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + Text( + text = text, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Start, + modifier = Modifier + .weight(1f) + .padding(top = 4.dp) + ) + } +} \ No newline at end of file diff --git a/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/screenLogic/HelpComponent.kt b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/screenLogic/HelpComponent.kt new file mode 100644 index 0000000..2f89971 --- /dev/null +++ b/feature/help/src/main/java/com/t8rin/imagetoolbox/feature/help/presentation/screenLogic/HelpComponent.kt @@ -0,0 +1,170 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.help.presentation.screenLogic + +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.resource.ResourceManager +import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent +import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen +import com.t8rin.imagetoolbox.feature.help.domain.HelpRepository +import com.t8rin.imagetoolbox.feature.help.domain.model.HelpCategory +import com.t8rin.imagetoolbox.feature.help.domain.model.HelpPage +import com.t8rin.imagetoolbox.feature.help.domain.model.HelpTip +import com.t8rin.imagetoolbox.feature.help.presentation.components.HelpState +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.util.Locale + +class HelpComponent @AssistedInject internal constructor( + @Assisted componentContext: ComponentContext, + @Assisted("categoryName") initialCategory: String?, + @Assisted("tipId") initialTipId: String?, + @Assisted val onGoBack: () -> Unit, + @Assisted val onNavigate: (Screen) -> Unit, + helpRepository: HelpRepository, + resourceManager: ResourceManager, + dispatchersHolder: DispatchersHolder +) : BaseComponent(dispatchersHolder, componentContext), ResourceManager by resourceManager { + + private val baseTips: List = initialCategory + ?.let(helpRepository::getCategory) + ?.let(helpRepository::getTipsForCategory) + ?: helpRepository.tips + + private val _isSearching: MutableState = mutableStateOf(false) + val isSearching by _isSearching + + private val _searchKeyword: MutableState = mutableStateOf("") + val searchKeyword by _searchKeyword + + private val _filteredTips: MutableState> = mutableStateOf(baseTips) + val filteredTips by _filteredTips + + val state: HelpState = run { + initialTipId?.let(helpRepository::getTip)?.let { + return@run HelpState.TutorialDetails(it) + } + + initialCategory?.let(helpRepository::getCategory)?.let { + return@run HelpState.TutorialCategory(it) + } + + HelpState.Categories(helpRepository.categories) + } + + fun openCategory(category: HelpCategory) { + onNavigate( + Screen.Help( + categoryName = category.key + ) + ) + } + + fun openTip(tip: HelpTip) { + onNavigate( + Screen.Help( + categoryName = tip.category.key, + tipId = tip.id + ) + ) + } + + fun updateIsSearching(isSearching: Boolean) { + _isSearching.value = isSearching + if (!isSearching) { + clearSearch() + } + } + + fun updateSearch(keyword: String) { + _searchKeyword.value = keyword + + if (keyword.isBlank()) { + updateFilteredTips() + return + } + + debouncedImageCalculation( + delay = 350, + action = { updateFilteredTips() } + ) + } + + private fun clearSearch() { + updateSearch("") + } + + private fun updateFilteredTips() { + val keyword = searchKeyword + + _filteredTips.value = if (keyword.isBlank()) { + baseTips + } else { + baseTips.filter { it.matchesSearch(keyword) } + } + } + + private fun HelpTip.matchesSearch(keyword: String): Boolean { + return title.matchesSearch(keyword) || + subtitle.matchesSearch(keyword) || + category.matchesSearch(keyword) || + pages.any { it.matchesSearch(keyword) } + } + + private fun HelpCategory.matchesSearch(keyword: String): Boolean { + return title.matchesSearch(keyword) || + subtitle.matchesSearch(keyword) + } + + private fun HelpPage.matchesSearch(keyword: String): Boolean { + return title.matchesSearch(keyword) || + description.matchesSearch(keyword) || + steps.any { it.matchesSearch(keyword) } + } + + private fun Int.matchesSearch(keyword: String): Boolean { + return getString(this).contains( + other = keyword, + ignoreCase = true + ) || getStringLocalized( + resId = this, + language = Locale.ENGLISH.language + ).contains( + other = keyword, + ignoreCase = true + ) + } + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + @Assisted("categoryName") + initialCategory: String?, + @Assisted("tipId") + initialTipId: String?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit + ): HelpComponent + } +} diff --git a/feature/image-cutting/.gitignore b/feature/image-cutting/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/image-cutting/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/image-cutting/build.gradle.kts b/feature/image-cutting/build.gradle.kts new file mode 100644 index 0000000..92bcf62 --- /dev/null +++ b/feature/image-cutting/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/image-cutting/src/main/AndroidManifest.xml b/feature/image-cutting/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d5fcf6a --- /dev/null +++ b/feature/image-cutting/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/data/AndroidImageCutter.kt b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/data/AndroidImageCutter.kt new file mode 100644 index 0000000..a5c339b --- /dev/null +++ b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/data/AndroidImageCutter.kt @@ -0,0 +1,277 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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() + } + } + } + +} diff --git a/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/di/ImageCutterModule.kt b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/di/ImageCutterModule.kt new file mode 100644 index 0000000..ce12544 --- /dev/null +++ b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/di/ImageCutterModule.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 + +} \ No newline at end of file diff --git a/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/domain/CutParams.kt b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/domain/CutParams.kt new file mode 100644 index 0000000..22627eb --- /dev/null +++ b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/domain/CutParams.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 + } +} \ No newline at end of file diff --git a/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/domain/ImageCutter.kt b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/domain/ImageCutter.kt new file mode 100644 index 0000000..ed77915 --- /dev/null +++ b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/domain/ImageCutter.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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? + +} \ No newline at end of file diff --git a/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/ImageCutterContent.kt b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/ImageCutterContent.kt new file mode 100644 index 0000000..dcb0120 --- /dev/null +++ b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/ImageCutterContent.kt @@ -0,0 +1,339 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.UndoRedoButtons +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()) + } + if (!isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + 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 + ) + if (isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + }, + 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 + ) +} \ No newline at end of file diff --git a/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/components/CutParamsSelector.kt b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/components/CutParamsSelector.kt new file mode 100644 index 0000000..b32493f --- /dev/null +++ b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/components/CutParamsSelector.kt @@ -0,0 +1,140 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 com.t8rin.imagetoolbox.core.resources.Icons +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.BorderHorizontal +import com.t8rin.imagetoolbox.core.resources.icons.BorderVertical +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 + ) + ) + } + ) + } +} \ No newline at end of file diff --git a/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/components/CutPreview.kt b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/components/CutPreview.kt new file mode 100644 index 0000000..5681a26 --- /dev/null +++ b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/components/CutPreview.kt @@ -0,0 +1,286 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 + ) + ) +} diff --git a/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/components/Utils.kt b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/components/Utils.kt new file mode 100644 index 0000000..352436b --- /dev/null +++ b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/components/Utils.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 +} \ No newline at end of file diff --git a/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/screenLogic/ImageCutterComponent.kt b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/screenLogic/ImageCutterComponent.kt new file mode 100644 index 0000000..e3e560e --- /dev/null +++ b/feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/screenLogic/ImageCutterComponent.kt @@ -0,0 +1,421 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.model.ColorModel +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.settings.domain.SettingsManager +import com.t8rin.imagetoolbox.core.ui.utils.BaseHistoryComponent +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 com.t8rin.imagetoolbox.image_cutting.presentation.screenLogic.ImageCutterComponent.HistorySnapshot +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, + private val settingsManager: SettingsManager, + dispatchersHolder: DispatchersHolder +) : BaseHistoryComponent( + dispatchersHolder = dispatchersHolder, + componentContext = 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) { + if (_params.value != cutParams) { + beginPendingHistoryTransaction() + _params.update { cutParams } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun updateUris(uris: List?) { + clearHistory() + registerChangesCleared() + _uris.value = null + _uris.value = uris + _selectedUri.value = uris?.firstOrNull() + if (!uris.isNullOrEmpty()) { + resetHistory() + } + } + + 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) { + beginPendingHistoryTransaction() + _quality.value = quality + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setImageFormat(imageFormat: ImageFormat) { + if (_imageFormat.value != imageFormat) { + if (pendingHistoryMode != PendingHistoryMode.FormatChange) { + finalizePendingHistoryTransaction() + } + beginPendingHistoryTransaction( + mode = PendingHistoryMode.FormatChange, + commitDelayMillis = formatHistoryTransactionDebounce + ) + _imageFormat.value = imageFormat + registerChanges() + schedulePendingHistoryCommit() + } + } + + 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() + ) + + override fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( + params = params, + imageFormat = imageFormat, + quality = quality, + backgroundColorForNoAlphaFormats = settingsManager + .settingsState + .value + .backgroundForNoAlphaImageFormats + ) + + override fun applyHistorySnapshot(snapshot: HistorySnapshot) { + _params.update { snapshot.params } + _imageFormat.update { snapshot.imageFormat } + _quality.update { snapshot.quality } + restoreBackgroundColorForNoAlphaFormats( + settingsManager = settingsManager, + backgroundColorForNoAlphaFormats = snapshot.backgroundColorForNoAlphaFormats + ) + } + + data class HistorySnapshot( + val params: CutParams = CutParams.Default, + val imageFormat: ImageFormat = ImageFormat.Default, + val quality: Quality = Quality.Base(100), + val backgroundColorForNoAlphaFormats: ColorModel = ColorModel(-0x1000000) + ) + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUris: List?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): ImageCutterComponent + } +} \ No newline at end of file diff --git a/feature/image-preview/.gitignore b/feature/image-preview/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/image-preview/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/image-preview/build.gradle.kts b/feature/image-preview/build.gradle.kts new file mode 100644 index 0000000..e0be105 --- /dev/null +++ b/feature/image-preview/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/image-preview/src/main/AndroidManifest.xml b/feature/image-preview/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/image-preview/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/image-preview/src/main/java/com/t8rin/imagetoolbox/feature/image_preview/presentation/ImagePreviewContent.kt b/feature/image-preview/src/main/java/com/t8rin/imagetoolbox/feature/image_preview/presentation/ImagePreviewContent.kt new file mode 100644 index 0000000..56a6707 --- /dev/null +++ b/feature/image-preview/src/main/java/com/t8rin/imagetoolbox/feature/image_preview/presentation/ImagePreviewContent.kt @@ -0,0 +1,468 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.FolderOpen +import com.t8rin.imagetoolbox.core.resources.icons.ImageEdit +import com.t8rin.imagetoolbox.core.resources.icons.ImageSearch +import com.t8rin.imagetoolbox.core.resources.icons.SelectAll +import com.t8rin.imagetoolbox.core.resources.icons.Share +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.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, + isSelectable = true + ) + } + } + } 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.FolderOpen, + 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 + ) +} \ No newline at end of file diff --git a/feature/image-preview/src/main/java/com/t8rin/imagetoolbox/feature/image_preview/presentation/screenLogic/ImagePreviewComponent.kt b/feature/image-preview/src/main/java/com/t8rin/imagetoolbox/feature/image_preview/presentation/screenLogic/ImagePreviewComponent.kt new file mode 100644 index 0000000..2cc810f --- /dev/null +++ b/feature/image-preview/src/main/java/com/t8rin/imagetoolbox/feature/image_preview/presentation/screenLogic/ImagePreviewComponent.kt @@ -0,0 +1,166 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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" +) \ No newline at end of file diff --git a/feature/image-splitting/.gitignore b/feature/image-splitting/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/image-splitting/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/image-splitting/build.gradle.kts b/feature/image-splitting/build.gradle.kts new file mode 100644 index 0000000..1051b85 --- /dev/null +++ b/feature/image-splitting/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/image-splitting/src/main/AndroidManifest.xml b/feature/image-splitting/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d5fcf6a --- /dev/null +++ b/feature/image-splitting/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/data/AndroidImageSplitter.kt b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/data/AndroidImageSplitter.kt new file mode 100644 index 0000000..d09b1aa --- /dev/null +++ b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/data/AndroidImageSplitter.kt @@ -0,0 +1,220 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 + } + } + } + +} \ No newline at end of file diff --git a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/di/ImageSplitterModule.kt b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/di/ImageSplitterModule.kt new file mode 100644 index 0000000..5c8424a --- /dev/null +++ b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/di/ImageSplitterModule.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 + +} \ No newline at end of file diff --git a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/ImageSplitter.kt b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/ImageSplitter.kt new file mode 100644 index 0000000..25bb1aa --- /dev/null +++ b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/ImageSplitter.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 + +} \ No newline at end of file diff --git a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/SavableSplitParams.kt b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/SavableSplitParams.kt new file mode 100644 index 0000000..7c841c4 --- /dev/null +++ b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/SavableSplitParams.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 + +data class SavableSplitParams( + val rowsCount: Int = 2, + val columnsCount: Int = 2, +) \ No newline at end of file diff --git a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/SplitParams.kt b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/SplitParams.kt new file mode 100644 index 0000000..9bd235b --- /dev/null +++ b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/SplitParams.kt @@ -0,0 +1,83 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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() + ) + } + } +} \ No newline at end of file diff --git a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/ImageSplitterContent.kt b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/ImageSplitterContent.kt new file mode 100644 index 0000000..08b233b --- /dev/null +++ b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/ImageSplitterContent.kt @@ -0,0 +1,263 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.controls.UndoRedoButtons +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.image.urisPreview +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.urisPreview(isPortrait = isPortrait), + 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()) + } + if (!isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + 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 + ) + if (isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + }, + 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 + ) +} \ No newline at end of file diff --git a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/components/SplitParamsSelector.kt b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/components/SplitParamsSelector.kt new file mode 100644 index 0000000..109d08f --- /dev/null +++ b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/components/SplitParamsSelector.kt @@ -0,0 +1,324 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.CheckCircle +import com.t8rin.imagetoolbox.core.resources.icons.Percent +import com.t8rin.imagetoolbox.core.resources.icons.TableRows +import com.t8rin.imagetoolbox.core.resources.icons.ViewColumn +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) + ) +} \ No newline at end of file diff --git a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/screenLogic/ImageSplitterComponent.kt b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/screenLogic/ImageSplitterComponent.kt new file mode 100644 index 0000000..13df4ed --- /dev/null +++ b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/screenLogic/ImageSplitterComponent.kt @@ -0,0 +1,244 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.model.ColorModel +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.settings.domain.SettingsManager +import com.t8rin.imagetoolbox.core.ui.utils.BaseHistoryComponent +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.image_splitting.domain.ImageSplitter +import com.t8rin.imagetoolbox.image_splitting.domain.SavableSplitParams +import com.t8rin.imagetoolbox.image_splitting.domain.SplitParams +import com.t8rin.imagetoolbox.image_splitting.presentation.screenLogic.ImageSplitterComponent.HistorySnapshot +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, + private val settingsManager: SettingsManager, + dispatchersHolder: DispatchersHolder +) : BaseHistoryComponent( + dispatchersHolder = dispatchersHolder, + componentContext = componentContext +) { + private var savableParams by fileController.savable( + scope = componentScope, + initial = SavableSplitParams() + ) + + init { + debounce { + initialUri?.let(::updateUri) + _params.update { + it.copy( + rowsCount = savableParams.rowsCount, + columnsCount = savableParams.columnsCount + ) + } + } + } + + 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?) { + clearHistory() + registerChangesCleared() + _uri.value = null + _uri.value = uri + if (uri != null) { + resetHistory() + } + updateUris() + } + + fun updateParams(params: SplitParams) { + if (params != this.params) { + savableParams = SavableSplitParams( + rowsCount = params.rowsCount, + columnsCount = params.columnsCount + ) + beginPendingHistoryTransaction() + _params.update { params } + registerChanges() + updateUris() + schedulePendingHistoryCommit() + } + } + + 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 + } + } + + override fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( + uri = uri, + params = params, + backgroundColorForNoAlphaFormats = settingsManager + .settingsState + .value + .backgroundForNoAlphaImageFormats + ) + + override fun applyHistorySnapshot(snapshot: HistorySnapshot) { + _uri.update { snapshot.uri } + _params.update { snapshot.params } + restoreBackgroundColorForNoAlphaFormats( + settingsManager = settingsManager, + backgroundColorForNoAlphaFormats = snapshot.backgroundColorForNoAlphaFormats + ) + updateUris() + } + + data class HistorySnapshot( + val uri: Uri? = null, + val params: SplitParams = SplitParams.Default, + val backgroundColorForNoAlphaFormats: ColorModel = ColorModel(-0x1000000) + ) + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUris: Uri?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): ImageSplitterComponent + } + +} \ No newline at end of file diff --git a/feature/image-stacking/.gitignore b/feature/image-stacking/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/image-stacking/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/image-stacking/build.gradle.kts b/feature/image-stacking/build.gradle.kts new file mode 100644 index 0000000..65e40e8 --- /dev/null +++ b/feature/image-stacking/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/image-stacking/src/main/AndroidManifest.xml b/feature/image-stacking/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/image-stacking/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/data/AndroidImageStacker.kt b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/data/AndroidImageStacker.kt new file mode 100644 index 0000000..8b1fd63 --- /dev/null +++ b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/data/AndroidImageStacker.kt @@ -0,0 +1,153 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 { + val paint = stackImage.blendingMode.toPaint().apply { + alpha = (stackImage.alpha * 255).toInt() + } + + stackImage.position?.let { position -> + drawBitmap( + bitmap = it, + position = position, + paint = paint + ) + } ?: drawBitmap( + bitmap = it, + left = (width - it.width) * stackImage.positionX.coerceIn(0f, 1f), + top = (height - it.height) * stackImage.positionY.coerceIn(0f, 1f), + paint = paint + ) + } + + 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 + ) + } + } + +} \ No newline at end of file diff --git a/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/di/ImageStackingModule.kt b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/di/ImageStackingModule.kt new file mode 100644 index 0000000..4d8482d --- /dev/null +++ b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/di/ImageStackingModule.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/domain/ImageStacker.kt b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/domain/ImageStacker.kt new file mode 100644 index 0000000..60a681d --- /dev/null +++ b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/domain/ImageStacker.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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? + +} \ No newline at end of file diff --git a/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/domain/StackImage.kt b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/domain/StackImage.kt new file mode 100644 index 0000000..509dd8e --- /dev/null +++ b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/domain/StackImage.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 positionX: Float = 0f, + val positionY: Float = 0f, + 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 +) \ No newline at end of file diff --git a/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/domain/StackingParams.kt b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/domain/StackingParams.kt new file mode 100644 index 0000000..b38aa6e --- /dev/null +++ b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/domain/StackingParams.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + } + +} \ No newline at end of file diff --git a/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/ImageStackingContent.kt b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/ImageStackingContent.kt new file mode 100644 index 0000000..e78599c --- /dev/null +++ b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/ImageStackingContent.kt @@ -0,0 +1,337 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AddCircle +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.UndoRedoButtons +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()) + } + if (!isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + 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 + ) + if (isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + }, + imagePreview = { + ImageContainer( + imageInside = isPortrait, + showOriginal = false, + previewBitmap = component.previewBitmap, + originalBitmap = null, + isLoading = component.isImageLoading, + shouldShowPreview = true + ) + }, + topAppBarPersistentActions = { + if (component.stackImages.isEmpty()) { + TopAppBarEmoji() + } + + ZoomButton( + onClick = { showZoomSheet = true }, + visible = component.previewBitmap != null, + ) + }, + 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.Outlined.AddCircle, + 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 + ) +} \ No newline at end of file diff --git a/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/components/StackImageItem.kt b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/components/StackImageItem.kt new file mode 100644 index 0000000..4d05ac3 --- /dev/null +++ b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/components/StackImageItem.kt @@ -0,0 +1,307 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.colors.util.roundToTwoDigits +import com.t8rin.imagetoolbox.core.domain.model.Position +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.KeyboardArrowDown +import com.t8rin.imagetoolbox.core.resources.icons.Percent +import com.t8rin.imagetoolbox.core.resources.icons.PhotoSizeSelectLarge +import com.t8rin.imagetoolbox.core.resources.icons.Place +import com.t8rin.imagetoolbox.core.resources.icons.RemoveCircle +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.translatedName +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.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.Outlined.RemoveCircle, + 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)) + DataSelector( + value = stackImage.positionOption, + onValueChange = { positionOption -> + onStackImageChange( + when (positionOption) { + StackPosition.Custom -> stackImage.copy(position = null) + is StackPosition.Preset -> stackImage.copy( + position = positionOption.position + ) + } + ) + }, + entries = rememberStackPositions(), + spanCount = 2, + title = stringResource(R.string.position), + titleIcon = Icons.Outlined.Place, + itemContentText = { + when (it) { + StackPosition.Custom -> stringResource(R.string.custom) + is StackPosition.Preset -> it.position.translatedName + } + }, + containerColor = Color.Unspecified, + shape = ShapeDefaults.center + ) + Spacer(modifier = Modifier.height(4.dp)) + AnimatedVisibility(visible = stackImage.position == null) { + Column { + EnhancedSliderItem( + value = stackImage.positionX, + title = stringResource(R.string.offset_x), + icon = Icons.Rounded.Percent, + valueRange = 0f..1f, + internalStateTransformation = { + it.roundToTwoDigits() + }, + onValueChange = { + onStackImageChange( + stackImage.copy( + positionX = it.roundToTwoDigits() + ) + ) + }, + containerColor = Color.Unspecified, + shape = ShapeDefaults.center + ) + Spacer(modifier = Modifier.height(4.dp)) + EnhancedSliderItem( + value = stackImage.positionY, + title = stringResource(R.string.offset_y), + icon = Icons.Rounded.Percent, + valueRange = 0f..1f, + internalStateTransformation = { + it.roundToTwoDigits() + }, + onValueChange = { + onStackImageChange( + stackImage.copy( + positionY = it.roundToTwoDigits() + ) + ) + }, + containerColor = 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 + ) + } + } + } + } +} + +private val StackImage.positionOption: StackPosition + get() = position?.let(StackPosition::Preset) ?: StackPosition.Custom + +@Composable +private fun rememberStackPositions(): List = remember { + Position.entries.map(StackPosition::Preset) + StackPosition.Custom +} + +private sealed class StackPosition { + data class Preset(val position: Position) : StackPosition() + + data object Custom : StackPosition() +} + +@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) +} \ No newline at end of file diff --git a/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/components/StackingParamsSelector.kt b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/components/StackingParamsSelector.kt new file mode 100644 index 0000000..2b8bd26 --- /dev/null +++ b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/components/StackingParamsSelector.kt @@ -0,0 +1,83 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.PhotoSizeSelectLarge +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 + ) + } +} \ No newline at end of file diff --git a/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/screenLogic/ImageStackingComponent.kt b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/screenLogic/ImageStackingComponent.kt new file mode 100644 index 0000000..58f3b31 --- /dev/null +++ b/feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/screenLogic/ImageStackingComponent.kt @@ -0,0 +1,414 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.model.ColorModel +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.settings.domain.SettingsManager +import com.t8rin.imagetoolbox.core.ui.utils.BaseHistoryComponent +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 com.t8rin.imagetoolbox.feature.image_stacking.presentation.screenLogic.ImageStackingComponent.HistorySnapshot +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, + private val settingsManager: SettingsManager, + dispatchersHolder: DispatchersHolder +) : BaseHistoryComponent( + dispatchersHolder = dispatchersHolder, + componentContext = 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) { + clearHistory() + registerChangesCleared() + _stackImages.value = uris.map { it.toString().toStackImage() } + if (uris.isNotEmpty()) { + resetHistory() + } + 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) { + if (_imageInfo.value.imageFormat != imageFormat) { + if (pendingHistoryMode != PendingHistoryMode.FormatChange) { + finalizePendingHistoryTransaction() + } + beginPendingHistoryTransaction( + mode = PendingHistoryMode.FormatChange, + commitDelayMillis = formatHistoryTransactionDebounce + ) + _imageInfo.value = _imageInfo.value.copy(imageFormat = imageFormat) + registerChanges() + calculatePreview() + schedulePendingHistoryCommit() + } + } + + 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 = "", + 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) { + if (_imageInfo.value.quality != quality) { + beginPendingHistoryTransaction() + _imageInfo.value = _imageInfo.value.copy(quality = quality) + registerChanges() + calculatePreview() + schedulePendingHistoryCommit() + } + } + + fun cancelSaving() { + savingJob?.cancel() + savingJob = null + _isSaving.value = false + } + + fun addUrisToEnd(uris: List) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _stackImages.update { list -> + list + uris.map { it.toString().toStackImage() }.filter { it !in list } + } + commitHistoryFrom(beforeSnapshot) + calculatePreview() + } + + fun removeImageAt(index: Int) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _stackImages.update { list -> + list.toMutableList().apply { + removeAt(index) + }.takeIf { it.size >= 2 }.also { + if (it == null) _previewBitmap.value = null + } ?: emptyList() + } + commitHistoryFrom(beforeSnapshot) + 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 + ) { + if (_stackingParams.value != newParams) { + beginPendingHistoryTransaction() + _stackingParams.update { newParams } + registerChanges() + calculatePreview() + schedulePendingHistoryCommit() + } + } + + fun updateStackImage( + value: StackImage, + index: Int + ) { + beginPendingHistoryTransaction() + val list = stackImages.toMutableList() + runCatching { + list[index] = value + _stackImages.update { list } + }.onFailure(AppToastHost::showFailureToast) + + registerChanges() + calculatePreview() + schedulePendingHistoryCommit() + } + + fun reorderUris(uris: List) { + if (stackImages.map { it.uri } != uris) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _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() + } + commitHistoryFrom(beforeSnapshot) + calculatePreview() + } + } + + fun getFormatForFilenameSelection(): ImageFormat = imageInfo.imageFormat + + override fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( + stackImages = stackImages, + stackingParams = stackingParams, + imageInfo = imageInfo.asHistoryImageInfo(), + backgroundColorForNoAlphaFormats = settingsManager + .settingsState + .value + .backgroundForNoAlphaImageFormats + ) + + override fun applyHistorySnapshot(snapshot: HistorySnapshot) { + _stackImages.update { snapshot.stackImages } + _stackingParams.update { snapshot.stackingParams } + _imageInfo.update { current -> + current.copy( + imageFormat = snapshot.imageInfo.imageFormat, + quality = snapshot.imageInfo.quality + ) + } + restoreBackgroundColorForNoAlphaFormats( + settingsManager = settingsManager, + backgroundColorForNoAlphaFormats = snapshot.backgroundColorForNoAlphaFormats + ) + calculatePreview() + } + + override fun hasSameUndoState( + first: HistorySnapshot, + second: HistorySnapshot + ): Boolean = first.normalized() == second.normalized() + + private fun ImageInfo.asHistoryImageInfo(): ImageInfo = ImageInfo( + imageFormat = imageFormat, + quality = quality + ) + + private fun HistorySnapshot.normalized(): HistorySnapshot = copy( + imageInfo = imageInfo.asHistoryImageInfo() + ) + + data class HistorySnapshot( + val stackImages: List = emptyList(), + val stackingParams: StackingParams = StackingParams.Default, + val imageInfo: ImageInfo = ImageInfo(), + val backgroundColorForNoAlphaFormats: ColorModel = ColorModel(-0x1000000) + ) + + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUris: List?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): ImageStackingComponent + } + +} \ No newline at end of file diff --git a/feature/image-stitch/.gitignore b/feature/image-stitch/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/image-stitch/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/image-stitch/build.gradle.kts b/feature/image-stitch/build.gradle.kts new file mode 100644 index 0000000..5dab96f --- /dev/null +++ b/feature/image-stitch/build.gradle.kts @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/AndroidManifest.xml b/feature/image-stitch/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/image-stitch/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/data/AndroidImageCombiner.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/data/AndroidImageCombiner.kt new file mode 100644 index 0000000..46eab73 --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/data/AndroidImageCombiner.kt @@ -0,0 +1,512 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 imageSpacing = combiningParams.spacingFor(isHorizontal) + val (size, images) = calculateCombinedImageDimensionsAndBitmaps( + imageUris = imagesUris, + isHorizontal = isHorizontal, + scaleSmallImagesToLarge = combiningParams.scaleSmallImagesToLarge, + imageSpacing = imageSpacing, + 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 = imageSpacing.absoluteValue + val halfSpace = fullSpace / 2 + val strength = combiningParams.fadeStrength + + for (i in imagesUris.indices) { + var bmp = bitmaps[i] + + imageSpacing.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 + imageSpacing).coerceAtLeast(1) + } else (bmp.height + imageSpacing).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 + }, + outputScale = 1f + ), + onProgress = onProgress + ) + } else { + getImageData( + imagesUris = imageUris, + isHorizontal = combiningParams.stitchMode.isHorizontal() + ) + } + } + + override suspend fun calculateCombinedImageDimensions( + imageUris: List, + combiningParams: CombiningParams + ): IntegerSize { + val isHorizontal = combiningParams.stitchMode.isHorizontal() + return if (combiningParams.stitchMode.gridCellsCount() + .let { it == 0 || it > imageUris.size } + ) { + calculateCombinedImageDimensionsAndBitmaps( + imageUris = imageUris, + isHorizontal = isHorizontal, + scaleSmallImagesToLarge = combiningParams.scaleSmallImagesToLarge, + imageSpacing = combiningParams.spacingFor(isHorizontal), + imageScale = combiningParams.outputScale + ).first + } else { + val outerIsHorizontal = !isHorizontal + val outerSpacing = combiningParams.spacingFor(outerIsHorizontal) + var size = IntegerSize(0, 0) + val gridImages = distributeImages( + images = imageUris, + cellCount = combiningParams.stitchMode.gridCellsCount() + ) + gridImages.forEachIndexed { index, images -> + calculateCombinedImageDimensionsAndBitmaps( + imageUris = images, + isHorizontal = isHorizontal, + scaleSmallImagesToLarge = combiningParams.scaleSmallImagesToLarge, + imageSpacing = combiningParams.spacingFor(isHorizontal), + imageScale = combiningParams.outputScale + ).first.let { newSize -> + val spacing = if (index != gridImages.lastIndex) outerSpacing else 0 + size = if (outerIsHorizontal) { + size.copy( + width = size.width + (newSize.width + spacing).coerceAtLeast(1), + height = max(newSize.height, size.height) + ) + } else { + size.copy( + width = max(newSize.width, size.width), + height = size.height + (newSize.height + spacing).coerceAtLeast(1) + ) + } + } + } + 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 + ) + +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/data/CvStitchHelper.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/data/CvStitchHelper.kt new file mode 100644 index 0000000..7b70588 --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/data/CvStitchHelper.kt @@ -0,0 +1,288 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.features.FlannBasedMatcher +import org.opencv.features.SIFT +import org.opencv.geometry.Geometry +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 = Geometry.findHomography( + MatOfPoint2f(*trainPoints.toTypedArray()), + MatOfPoint2f(*queryPoints.toTypedArray()), + Geometry.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 + ) +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/di/ImageStitchModule.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/di/ImageStitchModule.kt new file mode 100644 index 0000000..859e505 --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/di/ImageStitchModule.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/CombiningParams.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/CombiningParams.kt new file mode 100644 index 0000000..28bc41f --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/CombiningParams.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 horizontalSpacing: Int = 0, + val verticalSpacing: 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 +) { + fun spacingFor( + isHorizontal: Boolean + ): Int = if (isHorizontal) horizontalSpacing else verticalSpacing + + fun hasNegativeSpacing(): Boolean = horizontalSpacing < 0 || verticalSpacing < 0 +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/ImageCombiner.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/ImageCombiner.kt new file mode 100644 index 0000000..11149fb --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/ImageCombiner.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/SavableCombiningParams.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/SavableCombiningParams.kt new file mode 100644 index 0000000..7876e2a --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/SavableCombiningParams.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 horizontalSpacing: Int, + val verticalSpacing: 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 = "_") + }", + horizontalSpacing = horizontalSpacing, + verticalSpacing = verticalSpacing, + 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 + } + } + }, + horizontalSpacing = horizontalSpacing, + verticalSpacing = verticalSpacing, + scaleSmallImagesToLarge = scaleSmallImagesToLarge, + backgroundColor = backgroundColor, + fadingEdgesMode = fadingEdgesMode, + alignment = alignment, + outputScale = outputScale, + blendingMode = BlendingMode.entries.find { it.value == blendingMode } ?: BlendingMode.SrcOver, + fadeStrength = fadeStrength +) \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/StitchAlignment.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/StitchAlignment.kt new file mode 100644 index 0000000..aea9347 --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/StitchAlignment.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/StitchFadeSide.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/StitchFadeSide.kt new file mode 100644 index 0000000..32df7b3 --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/StitchFadeSide.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/StitchMode.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/StitchMode.kt new file mode 100644 index 0000000..31a137d --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/StitchMode.kt @@ -0,0 +1,80 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + ) + } + } +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/ImageStitchingContent.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/ImageStitchingContent.kt new file mode 100644 index 0000000..7053621 --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/ImageStitchingContent.kt @@ -0,0 +1,385 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.UndoRedoButtons +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.GridSpacingSelector +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()) + } + if (!isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + 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, + ) + if (isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + }, + imagePreview = { + ImageContainer( + imageInside = isPortrait, + showOriginal = false, + previewBitmap = component.previewBitmap, + originalBitmap = null, + isLoading = component.isImageLoading, + shouldShowPreview = true + ) + }, + topAppBarPersistentActions = { + if (component.uris.isNullOrEmpty()) { + TopAppBarEmoji() + } + }, + controls = { + val combiningParams = component.combiningParams + val stitchMode = combiningParams.stitchMode + val isGridMode = stitchMode is StitchMode.Grid + val hasNegativeSpacing = if (isGridMode) { + combiningParams.hasNegativeSpacing() + } else { + combiningParams.spacingFor(stitchMode.isHorizontal()) < 0 + } + + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + ImageReorderCarousel( + images = component.uris, + onReorder = component::reorderUris, + onNeedToAddImage = addImages, + onNeedToRemoveImageAt = component::removeImageAt, + onNavigate = component.onNavigate + ) + ImageScaleSelector( + modifier = Modifier.padding(top = 8.dp), + value = combiningParams.outputScale, + onValueChange = component::updateImageScale, + approximateImageSize = component.imageSize + ) + StitchModeSelector( + value = stitchMode, + onValueChange = component::setStitchMode + ) + AnimatedVisibility( + visible = stitchMode !is StitchMode.Auto, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + if (isGridMode) { + GridSpacingSelector( + horizontalValue = combiningParams.horizontalSpacing, + verticalValue = combiningParams.verticalSpacing, + onHorizontalValueChange = component::updateHorizontalImageSpacing, + onVerticalValueChange = component::updateVerticalImageSpacing + ) + } else { + SpacingSelector( + value = combiningParams.spacingFor(stitchMode.isHorizontal()), + onValueChange = component::updateImageSpacing + ) + } + } + AnimatedVisibility( + visible = hasNegativeSpacing && stitchMode !is StitchMode.Auto, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + ImageFadingEdgesSelector( + value = combiningParams.fadingEdgesMode, + onValueChange = component::setFadingEdgesMode + ) + } + AnimatedVisibility( + visible = hasNegativeSpacing && + combiningParams.fadingEdgesMode != StitchFadeSide.None && + stitchMode !is StitchMode.Auto, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + FadeStrengthSelector( + value = combiningParams.fadeStrength, + onValueChange = component::setFadeStrength + ) + } + AnimatedVisibility( + visible = stitchMode !is StitchMode.Auto, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + ScaleSmallImagesToLargeToggle( + checked = combiningParams.scaleSmallImagesToLarge, + onCheckedChange = component::toggleScaleSmallImagesToLarge + ) + } + AnimatedVisibility( + visible = hasNegativeSpacing && stitchMode !is StitchMode.Auto, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + BlendingModeSelector( + value = combiningParams.blendingMode, + onValueChange = component::setBlendingMode, + color = Color.Unspecified + ) + } + AnimatedVisibility( + visible = !combiningParams.scaleSmallImagesToLarge && stitchMode !is StitchMode.Auto, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + StitchAlignmentSelector( + value = combiningParams.alignment, + onValueChange = component::setStitchAlignment + ) + } + ColorRowSelector( + value = Color(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 + ) +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/FadeStrengthSelector.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/FadeStrengthSelector.kt new file mode 100644 index 0000000..0cd986c --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/FadeStrengthSelector.kt @@ -0,0 +1,59 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/ImageFadingEdgesSelector.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/ImageFadingEdgesSelector.kt new file mode 100644 index 0000000..427bde0 --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/ImageFadingEdgesSelector.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/ImageScaleSelector.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/ImageScaleSelector.kt new file mode 100644 index 0000000..8304527 --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/ImageScaleSelector.kt @@ -0,0 +1,174 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.PhotoSizeSelectSmall +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.Outlined.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 + ) + } + } + } +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/SpacingSelector.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/SpacingSelector.kt new file mode 100644 index 0000000..d6bf7ca --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/SpacingSelector.kt @@ -0,0 +1,91 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FormatLineSpacing +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, + title: String? = null, + shape: Shape? = null +) { + EnhancedSliderItem( + modifier = modifier, + value = value, + title = 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 = shape ?: ShapeDefaults.extraLarge + ) +} + +@Composable +fun GridSpacingSelector( + modifier: Modifier = Modifier, + horizontalValue: Int, + verticalValue: Int, + onHorizontalValueChange: (Int) -> Unit, + onVerticalValueChange: (Int) -> Unit +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + SpacingSelector( + value = horizontalValue, + onValueChange = onHorizontalValueChange, + title = stringResource(R.string.horizontal_spacing), + shape = ShapeDefaults.top + ) + SpacingSelector( + value = verticalValue, + onValueChange = onVerticalValueChange, + title = stringResource(R.string.vertical_spacing), + shape = ShapeDefaults.bottom + ) + } +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/StitchAlignmentSelector.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/StitchAlignmentSelector.kt new file mode 100644 index 0000000..a5b75f6 --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/StitchAlignmentSelector.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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)) + } + ) + } +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/StitchModeSelector.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/StitchModeSelector.kt new file mode 100644 index 0000000..0a48358 --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/StitchModeSelector.kt @@ -0,0 +1,267 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.BorderBottom +import com.t8rin.imagetoolbox.core.resources.icons.BorderLeft +import com.t8rin.imagetoolbox.core.resources.icons.BorderRight +import com.t8rin.imagetoolbox.core.resources.icons.BorderTop +import com.t8rin.imagetoolbox.core.resources.icons.TableRows +import com.t8rin.imagetoolbox.core.resources.icons.ViewColumn +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) +} \ No newline at end of file diff --git a/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/screenLogic/ImageStitchingComponent.kt b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/screenLogic/ImageStitchingComponent.kt new file mode 100644 index 0000000..1599b9d --- /dev/null +++ b/feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/screenLogic/ImageStitchingComponent.kt @@ -0,0 +1,476 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.ColorModel +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.settings.domain.SettingsManager +import com.t8rin.imagetoolbox.core.ui.utils.BaseHistoryComponent +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 com.t8rin.imagetoolbox.feature.image_stitch.presentation.screenLogic.ImageStitchingComponent.HistorySnapshot +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, + private val settingsManager: SettingsManager, + dispatchersHolder: DispatchersHolder +) : BaseHistoryComponent( + dispatchersHolder = dispatchersHolder, + componentContext = 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) { + if (_imageInfo.value.imageFormat != imageFormat) { + if (pendingHistoryMode != PendingHistoryMode.FormatChange) { + finalizePendingHistoryTransaction() + } + beginPendingHistoryTransaction( + mode = PendingHistoryMode.FormatChange, + commitDelayMillis = formatHistoryTransactionDebounce + ) + _imageInfo.update { it.copy(imageFormat = imageFormat) } + registerChanges() + calculatePreview(true) + schedulePendingHistoryCommit() + } + } + + fun updateUris(uris: List?) { + if (uris != _uris.value) { + clearHistory() + registerChangesCleared() + _uris.value = uris + if (!uris.isNullOrEmpty()) { + resetHistory() + } + calculatePreview(true) + } + } + + fun reorderUris(uris: List?) { + if (uris != _uris.value) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _uris.value = uris + commitHistoryFrom(beforeSnapshot) + 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 = "", + 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) { + if (_imageInfo.value.quality != quality) { + beginPendingHistoryTransaction() + _imageInfo.update { it.copy(quality = quality) } + registerChanges() + calculatePreview(true) + schedulePendingHistoryCommit() + } + } + + fun cancelSaving() { + savingJob?.cancel() + savingJob = null + _isSaving.value = false + } + + fun updateImageScale(newScale: Float) { + updateCombiningParams(combiningParams.copy(outputScale = newScale)) + } + + 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( + horizontalSpacing = spacing, + verticalSpacing = spacing + ) + ) + calculatePreview() + } + + fun updateHorizontalImageSpacing(spacing: Int) { + updateCombiningParams( + combiningParams.copy(horizontalSpacing = spacing) + ) + calculatePreview() + } + + fun updateVerticalImageSpacing(spacing: Int) { + updateCombiningParams( + combiningParams.copy(verticalSpacing = 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) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _uris.update { list -> + list?.plus( + uris.filter { it !in list } + ) + } + commitHistoryFrom(beforeSnapshot) + calculatePreview(true) + } + + fun removeImageAt(index: Int) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _uris.update { list -> + list?.toMutableList()?.apply { + removeAt(index) + }?.takeIf { it.size >= 2 }.also { + if (it == null) _previewBitmap.value = null + } + } + commitHistoryFrom(beforeSnapshot) + 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) { + if (combiningParams != params) { + beginPendingHistoryTransaction() + _combiningParams.update { params.toSavable() } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun getFormatForFilenameSelection(): ImageFormat = imageInfo.imageFormat + + override fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( + uris = uris, + imageInfo = imageInfo.asHistoryImageInfo(), + combiningParams = combiningParams, + backgroundColorForNoAlphaFormats = settingsManager + .settingsState + .value + .backgroundForNoAlphaImageFormats + ) + + override fun applyHistorySnapshot(snapshot: HistorySnapshot) { + _uris.update { snapshot.uris } + _imageInfo.update { current -> + current.copy( + imageFormat = snapshot.imageInfo.imageFormat, + quality = snapshot.imageInfo.quality + ) + } + _combiningParams.update { snapshot.combiningParams.toSavable() } + restoreBackgroundColorForNoAlphaFormats( + settingsManager = settingsManager, + backgroundColorForNoAlphaFormats = snapshot.backgroundColorForNoAlphaFormats + ) + calculatePreview(true) + } + + override fun hasSameUndoState( + first: HistorySnapshot, + second: HistorySnapshot + ): Boolean = first.normalized() == second.normalized() + + private fun ImageInfo.asHistoryImageInfo(): ImageInfo = ImageInfo( + imageFormat = imageFormat, + quality = quality + ) + + private fun HistorySnapshot.normalized(): HistorySnapshot = copy( + imageInfo = imageInfo.asHistoryImageInfo() + ) + + data class HistorySnapshot( + val uris: List? = null, + val imageInfo: ImageInfo = ImageInfo(), + val combiningParams: CombiningParams = CombiningParams(), + val backgroundColorForNoAlphaFormats: ColorModel = ColorModel(-0x1000000) + ) + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUris: List?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): ImageStitchingComponent + } + +} \ No newline at end of file diff --git a/feature/jxl-tools/.gitignore b/feature/jxl-tools/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/jxl-tools/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/jxl-tools/build.gradle.kts b/feature/jxl-tools/build.gradle.kts new file mode 100644 index 0000000..8bc35a0 --- /dev/null +++ b/feature/jxl-tools/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/jxl-tools/src/main/AndroidManifest.xml b/feature/jxl-tools/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/jxl-tools/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/data/AndroidJxlConverter.kt b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/data/AndroidJxlConverter.kt new file mode 100644 index 0000000..c0f345f --- /dev/null +++ b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/data/AndroidJxlConverter.kt @@ -0,0 +1,212 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + } + +} \ No newline at end of file diff --git a/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/di/JxlToolsModule.kt b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/di/JxlToolsModule.kt new file mode 100644 index 0000000..c48c859 --- /dev/null +++ b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/di/JxlToolsModule.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/domain/AnimatedJxlParams.kt b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/domain/AnimatedJxlParams.kt new file mode 100644 index 0000000..8c1d5eb --- /dev/null +++ b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/domain/AnimatedJxlParams.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } +} \ No newline at end of file diff --git a/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/domain/JxlConverter.kt b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/domain/JxlConverter.kt new file mode 100644 index 0000000..fe79723 --- /dev/null +++ b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/domain/JxlConverter.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/JxlToolsContent.kt b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/JxlToolsContent.kt new file mode 100644 index 0000000..d630da1 --- /dev/null +++ b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/JxlToolsContent.kt @@ -0,0 +1,239 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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 == null + || 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 + ) +} \ No newline at end of file diff --git a/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/AnimatedJxlParamsSelector.kt b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/AnimatedJxlParamsSelector.kt new file mode 100644 index 0000000..9ae2403 --- /dev/null +++ b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/AnimatedJxlParamsSelector.kt @@ -0,0 +1,154 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.EnergySavingsLeaf +import com.t8rin.imagetoolbox.core.resources.icons.PhotoSizeSelectLarge +import com.t8rin.imagetoolbox.core.resources.icons.RepeatOne +import com.t8rin.imagetoolbox.core.resources.icons.Timelapse +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.Rounded.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..10_000f, + internalStateTransformation = { it.roundToInt() }, + onValueChange = { + onValueChange( + value.copy( + delay = it.roundToInt() + ) + ) + }, + shape = ShapeDefaults.extraLarge + ) + } +} \ No newline at end of file diff --git a/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsBitmapPreview.kt b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsBitmapPreview.kt new file mode 100644 index 0000000..be4a518 --- /dev/null +++ b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsBitmapPreview.kt @@ -0,0 +1,117 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + } + } + } +} \ No newline at end of file diff --git a/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsButtons.kt b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsButtons.kt new file mode 100644 index 0000000..1bfdb1e --- /dev/null +++ b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsButtons.kt @@ -0,0 +1,108 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) +} \ No newline at end of file diff --git a/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsControls.kt b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsControls.kt new file mode 100644 index 0000000..01b13bb --- /dev/null +++ b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsControls.kt @@ -0,0 +1,95 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} \ No newline at end of file diff --git a/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsNoDataControls.kt b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsNoDataControls.kt new file mode 100644 index 0000000..ae30f39 --- /dev/null +++ b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsNoDataControls.kt @@ -0,0 +1,124 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.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 { + Column( + modifier = Modifier.windowInsetsPadding( + WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal) + ) + ) { + 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)) + } + } + } +} \ No newline at end of file diff --git a/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsTopAppBarActions.kt b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsTopAppBarActions.kt new file mode 100644 index 0000000..ab86f2a --- /dev/null +++ b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsTopAppBarActions.kt @@ -0,0 +1,122 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.Close +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) + ) + } + } + } +} \ No newline at end of file diff --git a/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/screenLogic/JxlToolsComponent.kt b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/screenLogic/JxlToolsComponent.kt new file mode 100644 index 0000000..556a968 --- /dev/null +++ b/feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/screenLogic/JxlToolsComponent.kt @@ -0,0 +1,570 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + } + +} \ No newline at end of file diff --git a/feature/libraries-info/.gitignore b/feature/libraries-info/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/libraries-info/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/libraries-info/build.gradle.kts b/feature/libraries-info/build.gradle.kts new file mode 100644 index 0000000..4e1789f --- /dev/null +++ b/feature/libraries-info/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/libraries-info/src/main/AndroidManifest.xml b/feature/libraries-info/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d5fcf6a --- /dev/null +++ b/feature/libraries-info/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/LibrariesInfoContent.kt b/feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/LibrariesInfoContent.kt new file mode 100644 index 0000000..8558dea --- /dev/null +++ b/feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/LibrariesInfoContent.kt @@ -0,0 +1,154 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.author +import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent +import com.mikepenz.aboutlibraries.util.withContext +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +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.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.author.contains("T8RIN") + }, + { 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 + ) + ), + padding = LibraryDefaults.libraryPadding( + versionPadding = LibraryDefaults.chipPadding( + containerPadding = PaddingValues( + start = 16.dp + ), + contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp) + ), + ), + textStyles = LibraryDefaults.libraryTextStyles( + versionTextStyle = MaterialTheme.typography.labelMedium + ), + 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) + } + } + ) + } +} \ No newline at end of file diff --git a/feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/components/LibrariesContainer.kt b/feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/components/LibrariesContainer.kt new file mode 100644 index 0000000..d5760f7 --- /dev/null +++ b/feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/components/LibrariesContainer.kt @@ -0,0 +1,456 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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, + modifier = Modifier.padding(top = 8.dp) + ) + } + +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 + ) + } + } +} \ No newline at end of file diff --git a/feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/components/LibraryLink.kt b/feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/components/LibraryLink.kt new file mode 100644 index 0000000..720e9e6 --- /dev/null +++ b/feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/components/LibraryLink.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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://", "") \ No newline at end of file diff --git a/feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/screenLogic/LibrariesInfoComponent.kt b/feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/screenLogic/LibrariesInfoComponent.kt new file mode 100644 index 0000000..0d218a3 --- /dev/null +++ b/feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/screenLogic/LibrariesInfoComponent.kt @@ -0,0 +1,61 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + +} \ No newline at end of file diff --git a/feature/library-details/.gitignore b/feature/library-details/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/library-details/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/library-details/build.gradle.kts b/feature/library-details/build.gradle.kts new file mode 100644 index 0000000..8e961b5 --- /dev/null +++ b/feature/library-details/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/library-details/src/main/AndroidManifest.xml b/feature/library-details/src/main/AndroidManifest.xml new file mode 100644 index 0000000..44008a4 --- /dev/null +++ b/feature/library-details/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/feature/library-details/src/main/java/com/t8rin/imagetoolbox/library_details/presentation/LibraryDetailsContent.kt b/feature/library-details/src/main/java/com/t8rin/imagetoolbox/library_details/presentation/LibraryDetailsContent.kt new file mode 100644 index 0000000..3ce6951 --- /dev/null +++ b/feature/library-details/src/main/java/com/t8rin/imagetoolbox/library_details/presentation/LibraryDetailsContent.kt @@ -0,0 +1,109 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.OpenInNew +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.Rounded.ArrowBack, + contentDescription = null + ) + } + }, + actions = { + if (component.libraryLink.isNullOrBlank()) { + TopAppBarEmoji() + } else { + EnhancedIconButton( + onClick = { linkHandler.openUri(component.libraryLink) } + ) { + Icon( + imageVector = Icons.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 + ) + } + } +} \ No newline at end of file diff --git a/feature/library-details/src/main/java/com/t8rin/imagetoolbox/library_details/presentation/screenLogic/LibraryDetailsComponent.kt b/feature/library-details/src/main/java/com/t8rin/imagetoolbox/library_details/presentation/screenLogic/LibraryDetailsComponent.kt new file mode 100644 index 0000000..5376b1a --- /dev/null +++ b/feature/library-details/src/main/java/com/t8rin/imagetoolbox/library_details/presentation/screenLogic/LibraryDetailsComponent.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 + } + +} \ No newline at end of file diff --git a/feature/limits-resize/.gitignore b/feature/limits-resize/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/limits-resize/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/limits-resize/build.gradle.kts b/feature/limits-resize/build.gradle.kts new file mode 100644 index 0000000..44c3117 --- /dev/null +++ b/feature/limits-resize/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/limits-resize/src/main/AndroidManifest.xml b/feature/limits-resize/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/limits-resize/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/data/AndroidLimitsImageScaler.kt b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/data/AndroidLimitsImageScaler.kt new file mode 100644 index 0000000..dca44db --- /dev/null +++ b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/data/AndroidLimitsImageScaler.kt @@ -0,0 +1,152 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + } + } + +} \ No newline at end of file diff --git a/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/di/LimitsResizeModule.kt b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/di/LimitsResizeModule.kt new file mode 100644 index 0000000..5165d4d --- /dev/null +++ b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/di/LimitsResizeModule.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/domain/LimitsImageScaler.kt b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/domain/LimitsImageScaler.kt new file mode 100644 index 0000000..36a3644 --- /dev/null +++ b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/domain/LimitsImageScaler.kt @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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? + +} \ No newline at end of file diff --git a/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/domain/LimitsResizeType.kt b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/domain/LimitsResizeType.kt new file mode 100644 index 0000000..e1364c2 --- /dev/null +++ b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/domain/LimitsResizeType.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) +} \ No newline at end of file diff --git a/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/LimitsResizeContent.kt b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/LimitsResizeContent.kt new file mode 100644 index 0000000..d7711c2 --- /dev/null +++ b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/LimitsResizeContent.kt @@ -0,0 +1,325 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.rememberScrollState +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.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.UndoRedoButtons +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.enhanced.enhancedHorizontalScroll +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.modifier.fadingEdges +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 = { + val state = rememberScrollState() + Row( + modifier = Modifier + .fadingEdges(state) + .enhancedHorizontalScroll(state), + verticalAlignment = Alignment.CenterVertically + ) { + if (!isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + var editSheetData by remember { + mutableStateOf(listOf()) + } + if (component.previewBitmap != null) { + 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 + ) + } + if (isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + } + }, + 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() + } + ZoomButton( + onClick = { showZoomSheet = true }, + visible = component.bitmap != null, + ) + }, + 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 + ) +} diff --git a/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/components/AutoRotateLimitBoxToggle.kt b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/components/AutoRotateLimitBoxToggle.kt new file mode 100644 index 0000000..1f53e95 --- /dev/null +++ b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/components/AutoRotateLimitBoxToggle.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.MotionPhotosAuto +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 + ) +} \ No newline at end of file diff --git a/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/components/LimitResizeGroup.kt b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/components/LimitResizeGroup.kt new file mode 100644 index 0000000..f7f728a --- /dev/null +++ b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/components/LimitResizeGroup.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + ) + } + ) +} \ No newline at end of file diff --git a/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/screenLogic/LimitsResizeComponent.kt b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/screenLogic/LimitsResizeComponent.kt new file mode 100644 index 0000000..f81cd09 --- /dev/null +++ b/feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/screenLogic/LimitsResizeComponent.kt @@ -0,0 +1,548 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.ColorModel +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.SettingsManager +import com.t8rin.imagetoolbox.core.ui.utils.BaseHistoryComponent +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 com.t8rin.imagetoolbox.feature.limits_resize.presentation.screenLogic.LimitsResizeComponent.HistorySnapshot +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, + private val settingsManager: SettingsManager, + dispatchersHolder: DispatchersHolder +) : BaseHistoryComponent( + dispatchersHolder = dispatchersHolder, + componentContext = 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) { + if (_imageInfo.value.imageFormat != imageFormat) { + if (pendingHistoryMode != PendingHistoryMode.FormatChange) { + finalizePendingHistoryTransaction() + } + beginPendingHistoryTransaction( + mode = PendingHistoryMode.FormatChange, + commitDelayMillis = formatHistoryTransactionDebounce + ) + _imageInfo.value = _imageInfo.value.copy( + imageFormat = imageFormat, + quality = imageInfo.quality.coerceIn(imageFormat) + ) + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setUris( + uris: List? + ) { + clearHistory() + registerChangesCleared() + _uris.value = null + _uris.value = uris + _selectedUri.value = uris?.firstOrNull() + if (uris != null) { + componentScope.launch { + imageGetter.getImageAsync( + uri = uris[0].toString(), + originalSize = true, + onGetImage = { + _imageInfo.value = _imageInfo.value.copy( + imageFormat = it.imageInfo.imageFormat, + quality = imageInfo.quality.coerceIn(it.imageInfo.imageFormat) + ) + updateBitmap(it.image) + resetHistory() + registerChangesCleared() + }, + 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) { + if (_keepExif.value == boolean) return + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _keepExif.value = boolean + commitHistoryFrom(beforeSnapshot) + } + + private var savingJob: Job? by smartJob { + _isSaving.update { false } + } + + fun saveBitmaps( + oneTimeSaveLocationUri: String? + ) { + finalizePendingHistoryTransaction() + 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( + register: Boolean = true + ) { + _canSave.update { + _bitmap.value != null && (_imageInfo.value.height != 0 || _imageInfo.value.width != 0) + } + + if (register) { + registerChanges() + } + } + + fun updateWidth(i: Int) { + if (_imageInfo.value.width != i) { + beginPendingHistoryTransaction() + _imageInfo.value = _imageInfo.value.copy(width = i) + updateCanSave() + schedulePendingHistoryCommit() + } + } + + fun updateHeight(i: Int) { + if (_imageInfo.value.height != i) { + beginPendingHistoryTransaction() + _imageInfo.value = _imageInfo.value.copy(height = i) + updateCanSave() + schedulePendingHistoryCommit() + } + } + + 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) { + val coercedQuality = quality.coerceIn(imageInfo.imageFormat) + if (_imageInfo.value.quality != coercedQuality) { + beginPendingHistoryTransaction() + _imageInfo.value = _imageInfo.value.copy(quality = coercedQuality) + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setResizeType(resizeType: LimitsResizeType) { + if (!_resizeType.value.isSameType(resizeType)) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _resizeType.value = resizeType + commitHistoryFrom(beforeSnapshot) + } + } + + fun cancelSaving() { + savingJob?.cancel() + savingJob = null + _isSaving.value = false + } + + fun toggleAutoRotateLimitBox() { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _resizeType.update { it.copy(!it.autoRotateLimitBox) } + commitHistoryFrom(beforeSnapshot) + } + + fun setImageScaleMode(imageScaleMode: ImageScaleMode) { + if (_imageInfo.value.imageScaleMode != imageScaleMode) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + it.copy( + imageScaleMode = imageScaleMode + ) + } + commitHistoryFrom(beforeSnapshot) + } + } + + 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 + + override fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( + imageInfo = imageInfo.asHistoryImageInfo(), + resizeType = resizeType, + keepExif = keepExif, + backgroundColorForNoAlphaFormats = settingsManager + .settingsState + .value + .backgroundForNoAlphaImageFormats + ) + + override fun applyHistorySnapshot(snapshot: HistorySnapshot) { + _imageInfo.value = snapshot.imageInfo + _resizeType.value = snapshot.resizeType + _keepExif.value = snapshot.keepExif + restoreBackgroundColorForNoAlphaFormats( + settingsManager = settingsManager, + backgroundColorForNoAlphaFormats = snapshot.backgroundColorForNoAlphaFormats + ) + updateCanSave(register = false) + } + + override fun hasSameUndoState( + first: HistorySnapshot, + second: HistorySnapshot + ): Boolean = first.imageInfo == second.imageInfo && + first.keepExif == second.keepExif && + first.backgroundColorForNoAlphaFormats == second.backgroundColorForNoAlphaFormats && + first.resizeType.isSameType(second.resizeType) + + private fun ImageInfo.asHistoryImageInfo(): ImageInfo = copy( + sizeInBytes = 0, + originalUri = selectedUri?.toString() + ) + + private fun LimitsResizeType.isSameType( + other: LimitsResizeType + ): Boolean = this::class == other::class && + autoRotateLimitBox == other.autoRotateLimitBox + + data class HistorySnapshot( + val imageInfo: ImageInfo = ImageInfo(), + val resizeType: LimitsResizeType = LimitsResizeType.Recode(), + val keepExif: Boolean = false, + val backgroundColorForNoAlphaFormats: ColorModel = ColorModel(-0x1000000) + ) + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUris: List?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): LimitsResizeComponent + } +} diff --git a/feature/load-net-image/.gitignore b/feature/load-net-image/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/load-net-image/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/load-net-image/build.gradle.kts b/feature/load-net-image/build.gradle.kts new file mode 100644 index 0000000..9d8a4da --- /dev/null +++ b/feature/load-net-image/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/load-net-image/src/main/AndroidManifest.xml b/feature/load-net-image/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/load-net-image/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/data/AndroidHtmlImageParser.kt b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/data/AndroidHtmlImageParser.kt new file mode 100644 index 0000000..c74bf2e --- /dev/null +++ b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/data/AndroidHtmlImageParser.kt @@ -0,0 +1,111 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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://")) + +} \ No newline at end of file diff --git a/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/di/LoadNetImageModule.kt b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/di/LoadNetImageModule.kt new file mode 100644 index 0000000..7e61b90 --- /dev/null +++ b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/di/LoadNetImageModule.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/domain/HtmlImageParser.kt b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/domain/HtmlImageParser.kt new file mode 100644 index 0000000..f1b2497 --- /dev/null +++ b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/domain/HtmlImageParser.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/LoadNetImageContent.kt b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/LoadNetImageContent.kt new file mode 100644 index 0000000..2f5bbfc --- /dev/null +++ b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/LoadNetImageContent.kt @@ -0,0 +1,115 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) +} \ No newline at end of file diff --git a/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageActionButtons.kt b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageActionButtons.kt new file mode 100644 index 0000000..0afce5a --- /dev/null +++ b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageActionButtons.kt @@ -0,0 +1,102 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.res.stringResource +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ContentPaste +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 + ) +} \ No newline at end of file diff --git a/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageAdaptiveActions.kt b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageAdaptiveActions.kt new file mode 100644 index 0000000..4404fa9 --- /dev/null +++ b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageAdaptiveActions.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + ) +} \ No newline at end of file diff --git a/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageTopAppBarActions.kt b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageTopAppBarActions.kt new file mode 100644 index 0000000..5f2ac13 --- /dev/null +++ b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageTopAppBarActions.kt @@ -0,0 +1,122 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.Close +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) + ) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageUrlTextField.kt b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageUrlTextField.kt new file mode 100644 index 0000000..4974a96 --- /dev/null +++ b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageUrlTextField.kt @@ -0,0 +1,69 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Cancel +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, + 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) + ) + } + } + } + ) +} \ No newline at end of file diff --git a/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/ParsedImagePreview.kt b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/ParsedImagePreview.kt new file mode 100644 index 0000000..4fc2372 --- /dev/null +++ b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/ParsedImagePreview.kt @@ -0,0 +1,98 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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, + contentScale = ContentScale.FillBounds + ) + } else { + Column( + modifier = Modifier + .fillMaxSize() + .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)) + } + } + } + ) +} \ No newline at end of file diff --git a/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/ParsedImagesSelection.kt b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/ParsedImagesSelection.kt new file mode 100644 index 0000000..6703305 --- /dev/null +++ b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/ParsedImagesSelection.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } +} \ No newline at end of file diff --git a/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/screenLogic/LoadNetImageComponent.kt b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/screenLogic/LoadNetImageComponent.kt new file mode 100644 index 0000000..198238f --- /dev/null +++ b/feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/screenLogic/LoadNetImageComponent.kt @@ -0,0 +1,263 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.WifiTetheringError +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) { + _targetUrl.update( + onValueChanged = { + debouncedImageCalculation { + val newImages = htmlImageParser.parseImagesSrc( + url = newUrl, + onFailure = { + if (newUrl.isNotBlank()) { + AppToastHost.showToast( + message = it, + icon = Icons.Rounded.WifiTetheringError + ) + } + } + ) + + 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 + } +} \ No newline at end of file diff --git a/feature/main/.gitignore b/feature/main/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/main/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/main/build.gradle.kts b/feature/main/build.gradle.kts new file mode 100644 index 0000000..7e7eb59 --- /dev/null +++ b/feature/main/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/main/src/main/AndroidManifest.xml b/feature/main/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/main/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/MainContent.kt b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/MainContent.kt new file mode 100644 index 0000000..8f39308 --- /dev/null +++ b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/MainContent.kt @@ -0,0 +1,256 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.MenuOpen +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.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.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 + + val lastUsedTools by component.lastUsedTools.collectAsStateWithLifecycle() + + 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.Rounded.ArrowBack, + contentDescription = stringResource(R.string.exit) + ) + } + } else if (expanded) { + EnhancedIconButton( + onClick = { + sheetExpanded = !sheetExpanded + } + ) { + Icon( + imageVector = Icons.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 + ) + }, + isUpdateAvailable = isUpdateAvailable, + onNavigate = component.onNavigate, + onToggleFavorite = component::toggleFavoriteScreen, + lastUsedTools = lastUsedTools + ) + } + } + + 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 } + } + } + } +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/FilteredScreenListFor.kt b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/FilteredScreenListFor.kt new file mode 100644 index 0000000..9899a71 --- /dev/null +++ b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/FilteredScreenListFor.kt @@ -0,0 +1,104 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.navigation.Screen +import com.t8rin.imagetoolbox.core.ui.utils.navigation.matchesSearchQuery + +@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.showFavoriteToolsInGroupedMode, + settingsState.showFavoriteAsLast, + settingsState.favoriteScreenList, + screenSearchKeyword, + screenList, + selectedNavigationItem, + showScreenSearch + ) { + derivedStateOf { + when { + settingsState.groupOptionsByTypes && (screenSearchKeyword.isEmpty() && !showScreenSearch) -> { + val favoriteIndex = if (settingsState.showFavoriteAsLast) { + Screen.typedEntries.size + } else { + 0 + } + val screenGroupIndex = if ( + settingsState.showFavoriteToolsInGroupedMode && + !settingsState.showFavoriteAsLast + ) { + selectedNavigationItem - 1 + } else { + selectedNavigationItem + } + + if ( + settingsState.showFavoriteToolsInGroupedMode && + selectedNavigationItem == favoriteIndex + ) { + screenList.filter { + it.id in settingsState.favoriteScreenList + } + } else { + Screen.typedEntries.getOrNull(screenGroupIndex)?.entries.orEmpty() + } + } + + !settingsState.groupOptionsByTypes && (screenSearchKeyword.isEmpty() && !showScreenSearch) -> { + val favoriteIndex = if (settingsState.showFavoriteAsLast) 1 else 0 + if (selectedNavigationItem == favoriteIndex) { + screenList.filter { + it.id in settingsState.favoriteScreenList + } + } else screenList + } + + else -> screenList + }.let { screens -> + if (screenSearchKeyword.isNotEmpty() && canSearchScreens) { + screens.filter { + it.matchesSearchQuery(screenSearchKeyword) + } + } else screens + } + } + } +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/LastUsedToolsCard.kt b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/LastUsedToolsCard.kt new file mode 100644 index 0000000..65e9fc6 --- /dev/null +++ b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/LastUsedToolsCard.kt @@ -0,0 +1,271 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.foundation.Canvas +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.key +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.PathEffect +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.FinanceMode +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.takeColorFromScheme +import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen +import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor +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.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.fadingEdges +import com.t8rin.imagetoolbox.core.ui.widget.modifier.scaleOnTap +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlin.random.Random +import kotlin.time.Duration.Companion.milliseconds + +@Composable +internal fun LastUsedToolsCard( + tools: List, + onNavigate: (Screen) -> Unit, + modifier: Modifier = Modifier +) { + val lazyListState = key(tools) { + rememberLazyListState() + } + val scope = rememberCoroutineScope() + + Box( + modifier = modifier, + contentAlignment = Alignment.Center + ) { + Row( + modifier = Modifier + .container( + shape = ShapeDefaults.large, + resultPadding = 0.dp + ), + verticalAlignment = Alignment.CenterVertically + ) { + Spacer(Modifier.width(11.dp)) + IconShapeContainer( + containerColor = MaterialTheme.colorScheme.tertiaryContainer, + contentColor = MaterialTheme.colorScheme.onTertiaryContainer, + modifier = Modifier.scaleOnTap( + onRelease = { + scope.launch { + delay(200.milliseconds) + onNavigate(Screen.UsageStatistics) + } + } + ) + ) { + Icon( + imageVector = Icons.Outlined.FinanceMode, + contentDescription = null + ) + } + Spacer(Modifier.width(4.dp)) + + val containerColor = takeColorFromScheme { isNightMode -> + if (isNightMode) { + secondaryContainer.copy(0.4f) + } else { + secondaryContainer.copy(0.6f) + } + } + + val contentColor = takeColorFromScheme { + onSecondaryContainer.blend( + color = onTertiaryContainer, + fraction = 0.5f + ) + }.copy(0.9f) + + LazyRow( + state = lazyListState, + modifier = Modifier + .weight( + weight = 1f, + fill = false + ) + .fadingEdges( + scrollableState = lazyListState, + color = SafeLocalContainerColor, + length = 32.dp + ), + flingBehavior = enhancedFlingBehavior(), + contentPadding = PaddingValues( + start = 6.dp, + end = 6.dp, + top = 8.dp, + bottom = 8.dp + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(tools) { tool -> + val icon = tool.screen.twoToneIcon ?: return@items + + EnhancedChip( + selected = false, + onClick = { + onNavigate(tool.screen) + }, + unselectedColor = containerColor, + selectedColor = Color.Transparent, + contentPadding = PaddingValues( + start = 6.dp, + end = 8.dp, + top = 4.dp, + bottom = 4.dp + ) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = contentColor + ) + Spacer(Modifier.width(4.dp)) + Text( + text = stringResource(tool.screen.title), + style = MaterialTheme.typography.labelMedium, + color = contentColor + ) + } + } + } + if (tools.size < 5) { + items(5 - tools.size) { + val shape = ShapeDefaults.small + val density = LocalDensity.current + val colorScheme = MaterialTheme.colorScheme + val stroke = 2.dp + + val emptyColor = takeColorFromScheme { isNightMode -> + if (isNightMode) { + surfaceContainerLowest.copy(0.5f).blend(contentColor.copy(0.2f)) + } else { + surfaceContainerLowest + } + } + + Canvas( + modifier = Modifier + .height(36.dp) + .width(128.dp) + .padding(stroke / 2) + ) { + val outline = shape.createOutline( + size = size, + layoutDirection = layoutDirection, + density = density + ) + drawOutline( + outline = outline, + color = emptyColor + ) + drawOutline( + outline = outline, + color = colorScheme.surfaceVariant, + style = Stroke( + width = stroke.toPx(), + pathEffect = PathEffect.dashPathEffect( + intervals = floatArrayOf(6.dp.toPx(), 6.dp.toPx()), + phase = 0f + ) + ) + ) + } + } + } + } + Spacer(Modifier.width(2.dp)) + } + } +} + +@Composable +private fun PreviewContent() { + Button( + onClick = {}, + modifier = Modifier.alpha(0f) + ) { } + + CompositionLocalProvider( + LocalSettingsState provides LocalSettingsState.current.copy( + drawContainerShadows = false + ) + ) { + LastUsedToolsCard( + tools = Screen.entries.take(2).map { screen -> + UiLastUsedTool( + screen = screen, + openCount = Random.nextInt(1, 100), + ) + }, + onNavigate = {} + ) + } +} + +@Preview +@Composable +private fun Preview() = ImageToolboxThemeForPreview(true, Color.Green) { + PreviewContent() +} + +@Preview +@Composable +private fun Preview1() = ImageToolboxThemeForPreview(false, Color.Green) { + PreviewContent() +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/LauncherScreenSelector.kt b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/LauncherScreenSelector.kt new file mode 100644 index 0000000..d2589ec --- /dev/null +++ b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/LauncherScreenSelector.kt @@ -0,0 +1,293 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +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 +import com.t8rin.imagetoolbox.core.resources.icons.Bookmark +import com.t8rin.imagetoolbox.core.resources.icons.BookmarkRemove +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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, + lastUsedTools: List +) { + val settingsState = LocalSettingsState.current + val showFavoriteControls = + !settingsState.groupOptionsByTypes || settingsState.showFavoriteToolsInGroupedMode + + LazyVerticalGrid( + columns = GridCells.Adaptive(80.dp), + contentPadding = contentPadding, + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + flingBehavior = enhancedFlingBehavior() + ) { + if (lastUsedTools.isNotEmpty()) { + item( + key = "lastUsedTools", + span = { GridItemSpan(maxLineSpan) } + ) { + LastUsedToolsCard( + tools = lastUsedTools, + onNavigate = onNavigateToScreenWithPopUpTo, + modifier = Modifier.animateItem() + ) + } + } + 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 = showFavoriteControls, + 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.Outlined.Bookmark + } + } + 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) + ) + } + } + } + } +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainContentImpl.kt b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainContentImpl.kt new file mode 100644 index 0000000..6e04194 --- /dev/null +++ b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainContentImpl.kt @@ -0,0 +1,289 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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, + lastUsedTools: List +) { + val settingsState = LocalSettingsState.current + + var selectedNavigationItem by rememberSaveable { mutableIntStateOf(0) } + val showFavoriteTabInGroupedMode = + settingsState.groupOptionsByTypes && settingsState.showFavoriteToolsInGroupedMode + 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 + ) + + LaunchedEffect( + settingsState.groupOptionsByTypes, + showFavoriteTabInGroupedMode, + settingsState.showFavoriteAsLast, + selectedNavigationItem + ) { + val lastNavigationIndex = when { + showFavoriteTabInGroupedMode -> Screen.typedEntries.size + settingsState.groupOptionsByTypes -> Screen.typedEntries.lastIndex + else -> 1 + } + + if (selectedNavigationItem > lastNavigationIndex) { + selectedNavigationItem = lastNavigationIndex + } + } + + 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( + color = MaterialTheme.colorScheme.primary, + 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 = Triple( + first = settingsState.groupOptionsByTypes, + second = showFavoriteTabInGroupedMode, + third = Pair( + settingsState.showFavoriteAsLast, + showScreenSearch && canSearchScreens + ) + ), + transitionSpec = { fadeIn() togetherWith fadeOut() } + ) { (groupOptionsByTypes, showFavorite, favoriteState) -> + val (showFavoriteAsLast, searching) = favoriteState + + if (groupOptionsByTypes && !searching) { + MainNavigationBar( + selectedIndex = selectedNavigationItem, + showFavorite = showFavorite, + showFavoriteAsLast = showFavoriteAsLast, + onValueChange = { selectedNavigationItem = it } + ) + } else if (!searching) { + MainNavigationBarForFavorites( + selectedIndex = selectedNavigationItem, + showFavoriteAsLast = showFavoriteAsLast, + 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() + ) { + AnimatedContent( + targetState = Triple( + first = settingsState.groupOptionsByTypes, + second = showFavoriteTabInGroupedMode, + third = settingsState.showFavoriteAsLast + ), + transitionSpec = { fadeIn() togetherWith fadeOut() } + ) { (groupOptionsByTypes, showFavorite, showFavoriteAsLast) -> + if (groupOptionsByTypes) { + MainNavigationRail( + selectedIndex = selectedNavigationItem, + showFavorite = showFavorite, + showFavoriteAsLast = showFavoriteAsLast, + onValueChange = { + selectedNavigationItem = it + } + ) + } else { + MainNavigationRailForFavorites( + selectedIndex = selectedNavigationItem, + showFavoriteAsLast = showFavoriteAsLast, + 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, + lastUsedTools = lastUsedTools + ) + } + } + } +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainDrawerContent.kt b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainDrawerContent.kt new file mode 100644 index 0000000..2dfe880 --- /dev/null +++ b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainDrawerContent.kt @@ -0,0 +1,174 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 + ) + + 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 = 0.dp + ) + } else Modifier + ), + drawerContainerColor = MaterialTheme.colorScheme.surfaceContainerLowest, + drawerShape = shape, + windowInsets = WindowInsets(0) + ) { + LocalLayoutDirection.ProvidesValue(layoutDirection) { + settingsBlock() + } + } +} + +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) + } +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationBar.kt b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationBar.kt new file mode 100644 index 0000000..d5d58dc --- /dev/null +++ b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationBar.kt @@ -0,0 +1,140 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.layout.RowScope +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Bookmark +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, + showFavorite: Boolean = false, + showFavoriteAsLast: Boolean = false, + onValueChange: (Int) -> Unit +) { + NavigationBar( + modifier = Modifier.drawHorizontalStroke(top = true) + ) { + if (showFavorite && !showFavoriteAsLast) { + FavoriteNavigationBarItem( + selected = selectedIndex == 0, + favoriteIndex = 0, + onValueChange = onValueChange + ) + } + + Screen.typedEntries.forEachIndexed { index, group -> + val navigationIndex = if (showFavorite && !showFavoriteAsLast) index + 1 else index + val selected = navigationIndex == selectedIndex + val haptics = LocalHapticFeedback.current + EnhancedNavigationBarItem( + modifier = Modifier.weight(1f), + selected = selected, + onClick = { + onValueChange(navigationIndex) + 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() + ) + } + ) + } + if (showFavorite && showFavoriteAsLast) { + val favoriteIndex = Screen.typedEntries.size + FavoriteNavigationBarItem( + selected = favoriteIndex == selectedIndex, + favoriteIndex = favoriteIndex, + onValueChange = onValueChange + ) + } + } +} + +@Composable +private fun RowScope.FavoriteNavigationBarItem( + selected: Boolean, + favoriteIndex: Int, + onValueChange: (Int) -> Unit +) { + val haptics = LocalHapticFeedback.current + EnhancedNavigationBarItem( + modifier = Modifier.weight(1f), + selected = selected, + onClick = { + onValueChange(favoriteIndex) + haptics.longPress() + }, + icon = { + AnimatedContent( + targetState = selected, + transitionSpec = { + fadeIn() togetherWith fadeOut() + } + ) { selected -> + Icon( + imageVector = if (selected) { + Icons.Rounded.Bookmark + } else { + Icons.Outlined.Bookmark + }, + contentDescription = stringResource(R.string.favorite) + ) + } + }, + label = { + Text( + text = stringResource(R.string.favorite), + modifier = Modifier.marquee() + ) + } + ) +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationBarForFavorites.kt b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationBarForFavorites.kt new file mode 100644 index 0000000..43b1faf --- /dev/null +++ b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationBarForFavorites.kt @@ -0,0 +1,151 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.layout.RowScope +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Bookmark +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, + showFavoriteAsLast: Boolean = false, + onValueChange: (Int) -> Unit +) { + NavigationBar( + modifier = Modifier.drawHorizontalStroke(top = true) + ) { + val favoriteIndex = if (showFavoriteAsLast) 1 else 0 + val toolsIndex = if (showFavoriteAsLast) 0 else 1 + + if (!showFavoriteAsLast) { + FavoriteNavigationBarItem( + selected = selectedIndex == favoriteIndex, + favoriteIndex = favoriteIndex, + onValueChange = onValueChange + ) + } + + ToolsNavigationBarItem( + selected = selectedIndex == toolsIndex, + toolsIndex = toolsIndex, + onValueChange = onValueChange + ) + + if (showFavoriteAsLast) { + FavoriteNavigationBarItem( + selected = selectedIndex == favoriteIndex, + favoriteIndex = favoriteIndex, + onValueChange = onValueChange + ) + } + } +} + +@Composable +private fun RowScope.FavoriteNavigationBarItem( + selected: Boolean, + favoriteIndex: Int, + onValueChange: (Int) -> Unit +) { + val haptics = LocalHapticFeedback.current + EnhancedNavigationBarItem( + modifier = Modifier.weight(1f), + selected = selected, + onClick = { + onValueChange(favoriteIndex) + haptics.longPress() + }, + icon = { + AnimatedContent( + targetState = selected, + transitionSpec = { + fadeIn() togetherWith fadeOut() + } + ) { selected -> + Icon( + imageVector = if (selected) Icons.Rounded.Bookmark else Icons.Outlined.Bookmark, + contentDescription = null + ) + } + }, + label = { + Text( + text = stringResource(R.string.favorite), + modifier = Modifier.marquee() + ) + } + ) +} + +@Composable +private fun RowScope.ToolsNavigationBarItem( + selected: Boolean, + toolsIndex: Int, + onValueChange: (Int) -> Unit +) { + val haptics = LocalHapticFeedback.current + EnhancedNavigationBarItem( + modifier = Modifier.weight(1f), + selected = selected, + onClick = { + onValueChange(toolsIndex) + haptics.longPress() + }, + icon = { + AnimatedContent( + targetState = selected, + 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() + ) + } + ) +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationRail.kt b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationRail.kt new file mode 100644 index 0000000..8076a4d --- /dev/null +++ b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationRail.kt @@ -0,0 +1,208 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Bookmark +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, + showFavorite: Boolean = false, + showFavoriteAsLast: Boolean = false, + 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)) + if (showFavorite && !showFavoriteAsLast) { + FavoriteNavigationRailItem( + selected = selectedIndex == 0, + favoriteIndex = 0, + onValueChange = onValueChange + ) + } + + Screen.typedEntries.forEachIndexed { index, group -> + val navigationIndex = + if (showFavorite && !showFavoriteAsLast) index + 1 else index + val selected = navigationIndex == selectedIndex + val haptics = LocalHapticFeedback.current + EnhancedNavigationRailItem( + modifier = Modifier.width(100.dp), + selected = selected, + onClick = { + onValueChange(navigationIndex) + 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)) + } + ) + } + if (showFavorite && showFavoriteAsLast) { + val favoriteIndex = Screen.typedEntries.size + FavoriteNavigationRailItem( + selected = favoriteIndex == selectedIndex, + favoriteIndex = favoriteIndex, + onValueChange = onValueChange + ) + } + Spacer(Modifier.height(8.dp)) + } + } + Spacer( + Modifier + .fillMaxHeight() + .width(settingsState.borderWidth) + .background( + MaterialTheme.colorScheme.outlineVariant( + 0.3f, + DrawerDefaults.standardContainerColor + ) + ) + ) + } +} + +@Composable +private fun FavoriteNavigationRailItem( + selected: Boolean, + favoriteIndex: Int, + onValueChange: (Int) -> Unit +) { + val haptics = LocalHapticFeedback.current + EnhancedNavigationRailItem( + modifier = Modifier.width(100.dp), + selected = selected, + onClick = { + onValueChange(favoriteIndex) + haptics.longPress() + }, + icon = { + AnimatedContent( + targetState = selected, + transitionSpec = { + fadeIn() togetherWith fadeOut() + } + ) { selected -> + Icon( + imageVector = if (selected) { + Icons.Rounded.Bookmark + } else { + Icons.Outlined.Bookmark + }, + contentDescription = stringResource(R.string.favorite) + ) + } + }, + label = { + Text(stringResource(R.string.favorite)) + } + ) +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationRailForFavorites.kt b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationRailForFavorites.kt new file mode 100644 index 0000000..7181a19 --- /dev/null +++ b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationRailForFavorites.kt @@ -0,0 +1,229 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Bookmark +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, + showFavoriteAsLast: Boolean = false, + 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 favoriteIndex = if (showFavoriteAsLast) 1 else 0 + val toolsIndex = if (showFavoriteAsLast) 0 else 1 + + if (!showFavoriteAsLast) { + FavoriteNavigationRailItem( + selected = selectedIndex == favoriteIndex, + favoriteIndex = favoriteIndex, + onValueChange = onValueChange + ) + } + + ToolsNavigationRailItem( + selected = selectedIndex == toolsIndex, + toolsIndex = toolsIndex, + onValueChange = onValueChange + ) + + if (showFavoriteAsLast) { + FavoriteNavigationRailItem( + selected = selectedIndex == favoriteIndex, + favoriteIndex = favoriteIndex, + onValueChange = onValueChange + ) + } + Spacer(Modifier.height(8.dp)) + } + } + Box( + Modifier + .fillMaxHeight() + .width(settingsState.borderWidth) + .background( + MaterialTheme.colorScheme.outlineVariant( + 0.3f, + DrawerDefaults.standardContainerColor + ) + ) + ) + } +} + +@Composable +private fun FavoriteNavigationRailItem( + selected: Boolean, + favoriteIndex: Int, + onValueChange: (Int) -> Unit +) { + val haptics = LocalHapticFeedback.current + NavigationRailItem( + modifier = Modifier + .height(height = 56.dp) + .width(100.dp), + selected = selected, + onClick = { + onValueChange(favoriteIndex) + haptics.longPress() + }, + icon = { + AnimatedContent( + targetState = selected, + transitionSpec = { + fadeIn() togetherWith fadeOut() + } + ) { selected -> + Icon( + imageVector = if (selected) Icons.Rounded.Bookmark else Icons.Outlined.Bookmark, + contentDescription = null + ) + } + }, + label = { + Text( + text = stringResource(R.string.favorite), + modifier = Modifier.marquee() + ) + } + ) +} + +@Composable +private fun ToolsNavigationRailItem( + selected: Boolean, + toolsIndex: Int, + onValueChange: (Int) -> Unit +) { + val haptics = LocalHapticFeedback.current + NavigationRailItem( + modifier = Modifier + .height(height = 56.dp) + .width(100.dp), + selected = selected, + onClick = { + onValueChange(toolsIndex) + haptics.longPress() + }, + icon = { + AnimatedContent( + targetState = selected, + 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() + ) + } + ) +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainTopAppBar.kt b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainTopAppBar.kt new file mode 100644 index 0000000..eb06c93 --- /dev/null +++ b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainTopAppBar.kt @@ -0,0 +1,456 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 com.t8rin.imagetoolbox.core.resources.Icons +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.BugReport +import com.t8rin.imagetoolbox.core.resources.icons.MobileArrowUpRight +import com.t8rin.imagetoolbox.core.resources.icons.PhotoPrints +import com.t8rin.imagetoolbox.core.resources.icons.PushPin +import com.t8rin.imagetoolbox.core.resources.icons.Settings +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() + } + } +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/ScreenPreferenceSelection.kt b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/ScreenPreferenceSelection.kt new file mode 100644 index 0000000..15ff1a9 --- /dev/null +++ b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/ScreenPreferenceSelection.kt @@ -0,0 +1,493 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.StaggeredGridItemSpan +import androidx.compose.foundation.lazy.staggeredgrid.items +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Bookmark +import com.t8rin.imagetoolbox.core.resources.icons.BookmarkOff +import com.t8rin.imagetoolbox.core.resources.icons.BookmarkRemove +import com.t8rin.imagetoolbox.core.resources.icons.ContentPaste +import com.t8rin.imagetoolbox.core.resources.icons.ContentPasteOff +import com.t8rin.imagetoolbox.core.resources.icons.LayersSearchOutline +import com.t8rin.imagetoolbox.core.resources.icons.SearchOff +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, + lastUsedTools: List +) { + val settingsState = LocalSettingsState.current + val cutout = WindowInsets.displayCutout.asPaddingValues() + val canSearchScreens = settingsState.screensSearchEnabled + val isSearching = + showScreenSearch && screenSearchKeyword.isNotEmpty() && canSearchScreens + val isScreenSelectionLauncherMode = settingsState.isScreenSelectionLauncherMode + val showFavoriteControls = + !settingsState.groupOptionsByTypes || settingsState.showFavoriteToolsInGroupedMode + + 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, + lastUsedTools = lastUsedTools + ) + } 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 = { + if (lastUsedTools.isNotEmpty()) { + item( + key = "lastUsedTools", + span = StaggeredGridItemSpan.FullLine + ) { + LastUsedToolsCard( + tools = lastUsedTools, + onNavigate = onNavigateToScreenWithPopUpTo, + modifier = Modifier.animateItem() + ) + } + } + 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 (showFavoriteControls) { + 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.Outlined.Bookmark + } + } + 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.Rounded.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( + when { + settingsState.groupOptionsByTypes && !settingsState.showFavoriteAsLast -> 1 + settingsState.groupOptionsByTypes -> 0 + settingsState.showFavoriteAsLast -> 0 + else -> 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.Outlined.SearchOff, + contentDescription = null, + modifier = Modifier + .weight(2f) + .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) + .fillMaxSize() + ) + Spacer(Modifier.weight(1f)) + } + } + } + } +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/SearchableBottomBar.kt b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/SearchableBottomBar.kt new file mode 100644 index 0000000..29d9349 --- /dev/null +++ b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/SearchableBottomBar.kt @@ -0,0 +1,200 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.Close +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.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) + ) + } + } + ) + } + } + ) +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/UiLastUsedTool.kt b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/UiLastUsedTool.kt new file mode 100644 index 0000000..8040755 --- /dev/null +++ b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/UiLastUsedTool.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen + +data class UiLastUsedTool( + val screen: Screen, + val openCount: Int +) \ No newline at end of file diff --git a/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/screenLogic/MainComponent.kt b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/screenLogic/MainComponent.kt new file mode 100644 index 0000000..2fc4045 --- /dev/null +++ b/feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/screenLogic/MainComponent.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.domain.history.AppHistoryRepository +import com.t8rin.imagetoolbox.core.domain.remote.AnalyticsManager +import com.t8rin.imagetoolbox.core.domain.utils.onEachDebounced +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.main.presentation.components.UiLastUsedTool +import com.t8rin.imagetoolbox.feature.settings.presentation.screenLogic.SettingsComponent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlin.time.Duration.Companion.minutes + +class MainComponent @AssistedInject internal constructor( + @Assisted componentContext: ComponentContext, + @Assisted private val onTryGetUpdate: (Boolean) -> Unit, + @Assisted private val onGetClipList: (List) -> Unit, + @Assisted val onNavigate: (Screen) -> Unit, + @Assisted val isUpdateAvailable: Value, + private val analyticsManager: AnalyticsManager, + appHistoryRepository: AppHistoryRepository, + dispatchersHolder: DispatchersHolder, + private val settingsManager: SettingsManager, + settingsComponentFactory: SettingsComponent.Factory +) : BaseComponent(dispatchersHolder, componentContext) { + + val lastUsedTools: StateFlow> = appHistoryRepository + .lastUsedTools() + .map { lastUsedTools -> + lastUsedTools.mapNotNull { tool -> + Screen.entries.find { it.id == tool.screenId }?.let { screen -> + UiLastUsedTool( + screen = screen, + openCount = tool.openCount + ) + } + } + } + .onEachDebounced(5.minutes) { mapped -> + analyticsManager.pushMetric( + tag = "AppHistory", + metric = mapped.joinToString { + "[${it.screen.simpleName} - ${it.openCount}]" + } + ) + } + .stateIn( + scope = componentScope, + started = SharingStarted.Eagerly, + initialValue = emptyList() + ) + + val settingsComponent = settingsComponentFactory( + componentContext = childContext("mainSettings"), + onTryGetUpdate = onTryGetUpdate, + onNavigate = onNavigate, + isUpdateAvailable = isUpdateAvailable, + onGoBack = null, + initialSearchQuery = "" + ) + + fun tryGetUpdate(isNewRequest: Boolean = false) = onTryGetUpdate(isNewRequest) + + 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, + onGetClipList: (List) -> Unit, + onNavigate: (Screen) -> Unit, + isUpdateAvailable: Value, + ): MainComponent + } + +} \ No newline at end of file diff --git a/feature/markup-layers/.gitignore b/feature/markup-layers/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/markup-layers/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/markup-layers/build.gradle.kts b/feature/markup-layers/build.gradle.kts new file mode 100644 index 0000000..ee10fb6 --- /dev/null +++ b/feature/markup-layers/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" diff --git a/feature/markup-layers/src/main/AndroidManifest.xml b/feature/markup-layers/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d5fcf6a --- /dev/null +++ b/feature/markup-layers/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/AndroidMarkupLayersApplier.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/AndroidMarkupLayersApplier.kt new file mode 100644 index 0000000..2831a3d --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/AndroidMarkupLayersApplier.kt @@ -0,0 +1,238 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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, + fontScale: Float? + ): Bitmap = withContext(defaultDispatcher) { + renderer.render( + backgroundImage = image, + layers = layers, + fontScale = fontScale + ) + } + + 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()) + } + +} \ No newline at end of file diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/AssetRegistry.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/AssetRegistry.kt new file mode 100644 index 0000000..6a598df --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/AssetRegistry.kt @@ -0,0 +1,69 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/Mapping.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/Mapping.kt new file mode 100644 index 0000000..e432679 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/Mapping.kt @@ -0,0 +1,608 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.core.utils.makeLog +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 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) + +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/MarkupProjectConstants.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/MarkupProjectConstants.kt new file mode 100644 index 0000000..c82bfc6 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/MarkupProjectConstants.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 \ No newline at end of file diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/MarkupProjectExtensions.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/MarkupProjectExtensions.kt new file mode 100644 index 0000000..e8f7ba3 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/MarkupProjectExtensions.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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") +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/MarkupProjectFile.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/MarkupProjectFile.kt new file mode 100644 index 0000000..2ee3a85 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/MarkupProjectFile.kt @@ -0,0 +1,148 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/ProjectFileLoadResult.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/ProjectFileLoadResult.kt new file mode 100644 index 0000000..0eda14e --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/ProjectFileLoadResult.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/LayersRenderer.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/LayersRenderer.kt new file mode 100644 index 0000000..ca9aa45 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/LayersRenderer.kt @@ -0,0 +1,912 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.drawscope.CanvasDrawScope +import androidx.compose.ui.graphics.drawscope.withTransform +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextMeasurer +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.drawText +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontSynthesis +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.font.createFontFamilyResolver +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.Constraints +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.sp +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.settings.presentation.model.asUi +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.ceil +import kotlin.math.min +import kotlin.math.roundToInt +import androidx.compose.ui.graphics.Canvas as ComposeCanvas +import androidx.compose.ui.graphics.Color as ComposeColor +import androidx.compose.ui.graphics.drawscope.Stroke as ComposeStroke +import androidx.compose.ui.text.style.TextGeometricTransform as ComposeTextGeometricTransform + +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, + fontScale: Float? = null + ): 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 textDensity = Density( + density = context.resources.displayMetrics.density, + fontScale = fontScale + ?.takeIf { it > 0f } + ?: context.resources.configuration.fontScale.takeIf { it > 0f } + ?: 1f + ) + val textMeasurer = TextMeasurer( + defaultFontFamilyResolver = createFontFamilyResolver(context), + defaultDensity = textDensity, + defaultLayoutDirection = LayoutDirection.Ltr, + cacheSize = 0 + ) + + 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 = resolveLayerShadowRasterScale( + 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 = resolveLayerShadowRasterScale( + 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, + density = textDensity, + textMeasurer = textMeasurer + ) + } + 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 = resolveLayerShadowRasterScale( + 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, + density: Density, + textMeasurer: TextMeasurer + ): TextLayerRenderData { + val textMetrics = context.calculateTextLayerMetrics( + type = type, + textFullSize = textFullSize, + fontScale = density.fontScale + ) + val layoutText = type.text.ifEmpty { " " } + val resolvedContentSize = contentSize.takeIf { it.width > 0 && it.height > 0 } + val maxLayoutWidth = ( + (resolvedContentSize?.width?.toFloat() ?: fallbackMaxTextBoxWidth) - + textMetrics.padding.horizontalPx + ).roundToInt().coerceAtLeast(1) + val fillStyle = type.composeTextStyle( + textMetrics = textMetrics, + density = density + ) + val fillLayout = textMeasurer.measure( + text = layoutText, + style = fillStyle, + overflow = TextOverflow.Clip, + maxLines = maxLines ?: Int.MAX_VALUE, + constraints = Constraints( + maxWidth = maxLayoutWidth + ), + density = density, + layoutDirection = LayoutDirection.Ltr + ) + + val outlineLayout = type.outline?.takeIf { it.width > 0f }?.let { outline -> + textMeasurer.measure( + text = layoutText, + style = fillStyle.copy( + color = ComposeColor(outline.color), + textDecoration = null, + drawStyle = ComposeStroke( + width = outline.width, + cap = androidx.compose.ui.graphics.StrokeCap.Round, + join = androidx.compose.ui.graphics.StrokeJoin.Round + ) + ), + overflow = TextOverflow.Clip, + maxLines = maxLines ?: Int.MAX_VALUE, + constraints = Constraints( + maxWidth = maxLayoutWidth + ), + density = density, + layoutDirection = LayoutDirection.Ltr + ) + } + + val shadowRenderData = buildTextShadowRenderData( + type = type, + textMetrics = textMetrics, + textLayoutResult = fillLayout, + rasterScale = shadowRasterScale + ) + + val requiredBitmapWidth = ceil( + fillLayout.size.width + + textMetrics.padding.horizontalPx + ).toInt().coerceAtLeast(1) + val requiredBitmapHeight = ceil( + fillLayout.size.height + + textMetrics.padding.verticalPx + ).toInt().coerceAtLeast(1) + val bitmapWidth = resolvedContentSize?.width ?: requiredBitmapWidth + val bitmapHeight = resolvedContentSize?.height ?: requiredBitmapHeight + + 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 = textMetrics.padding.leftPx, + textTop = textMetrics.padding.topPx, + shadowRenderData = shadowRenderData, + outlineLayout = outlineLayout, + density = density, + 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 + } + ) + } + } + + CanvasDrawScope().draw( + density = data.density, + layoutDirection = LayoutDirection.Ltr, + canvas = ComposeCanvas(this), + size = Size(data.width, data.height) + ) { + withTransform({ + translate(data.textLeft, data.textTop) + }) { + data.outlineLayout?.let(::drawText) + drawText(data.fillLayout) + } + } + } + } + + 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 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 fun LayerType.Text.composeTextStyle( + textMetrics: TextLayerMetrics, + density: Density +): TextStyle = TextStyle( + color = ComposeColor(color), + fontSize = with(density) { textMetrics.fontSizePx.toSp() }, + lineHeight = with(density) { textMetrics.lineHeightPx.toSp() }, + fontFamily = font.asUi().fontFamily, + letterSpacing = 0.5.sp, + fontSynthesis = FontSynthesis.All, + textDecoration = TextDecoration.combine( + decorations.mapNotNull { + when (it) { + LayerType.Text.Decoration.LineThrough -> TextDecoration.LineThrough + LayerType.Text.Decoration.Underline -> TextDecoration.Underline + else -> null + } + } + ), + fontWeight = if (decorations.any { it == LayerType.Text.Decoration.Bold }) { + FontWeight.Bold + } else { + null + }, + fontStyle = if (decorations.any { it == LayerType.Text.Decoration.Italic }) { + FontStyle.Italic + } else { + FontStyle.Normal + }, + textGeometricTransform = geometricTransform?.let { + ComposeTextGeometricTransform( + scaleX = it.scaleX, + skewX = it.skewX + ) + }, + textAlign = when (alignment) { + LayerType.Text.Alignment.Start -> TextAlign.Start + LayerType.Text.Alignment.Center -> TextAlign.Center + LayerType.Text.Alignment.End -> TextAlign.End + } +) + +private data class TextLayerRenderData( + val width: Float, + val height: Float, + val backgroundPaint: Paint?, + val textLeft: Float, + val textTop: Float, + val shadowRenderData: TextShadowRenderData?, + val outlineLayout: TextLayoutResult?, + val density: Density, + val fillLayout: TextLayoutResult +) + +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? +) diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/PictureLayerShadowRenderer.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/PictureLayerShadowRenderer.kt new file mode 100644 index 0000000..befa4d4 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/PictureLayerShadowRenderer.kt @@ -0,0 +1,154 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.absoluteValue +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 resolveLayerShadowRasterScale( + layerScale: Float +): Float = layerScale + .absoluteValue + .coerceAtLeast(1f) + .coerceAtMost(MAX_SHADOW_RASTER_SCALE) + +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 const val MAX_SHADOW_RASTER_SCALE = 4f + +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) +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/ShapeLayerRenderer.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/ShapeLayerRenderer.kt new file mode 100644 index 0000000..23fd8bb --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/ShapeLayerRenderer.kt @@ -0,0 +1,682 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/TextLayerMetrics.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/TextLayerMetrics.kt new file mode 100644 index 0000000..5e90d14 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/TextLayerMetrics.kt @@ -0,0 +1,143 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) { + val horizontalPx: Float + get() = leftPx + rightPx + + val verticalPx: Float + get() = topPx + bottomPx + + companion object { + val Zero = TextLayerPadding( + leftPx = 0f, + topPx = 0f, + rightPx = 0f, + bottomPx = 0f + ) + } +} + +internal data class TextLayerMetrics( + val fontSizePx: Float, + val lineHeightPx: Float, + val basePadding: TextLayerPadding, + val padding: TextLayerPadding, + val typeface: Typeface +) + +internal fun Context.calculateTextLayerMetrics( + type: LayerType.Text, + textFullSize: Int, + fontScale: Float? = null +): TextLayerMetrics { + val displayMetrics = resources.displayMetrics + val baseTextSize = textFullSize.coerceAtLeast(1) * type.size + val fontSizeSp = baseTextSize / 12.5f + val fontSizePx = ( + fontScale + ?.takeIf { it > 0f } + ?.let { fontSizeSp * displayMetrics.density * it } + ?: TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_SP, + fontSizeSp, + 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) + + val basePadding = TextLayerPadding( + leftPx = baseHorizontalPaddingPx + geometricTransformPaddingPx, + topPx = baseVerticalPaddingPx, + rightPx = baseHorizontalPaddingPx + geometricTransformPaddingPx, + bottomPx = baseVerticalPaddingPx + ) + + return TextLayerMetrics( + fontSizePx = fontSizePx, + lineHeightPx = lineHeightPx, + basePadding = basePadding, + padding = TextLayerPadding( + leftPx = basePadding.leftPx + shadowPadding.leftPx, + topPx = basePadding.topPx + shadowPadding.topPx, + rightPx = basePadding.rightPx + shadowPadding.rightPx, + bottomPx = basePadding.bottomPx + 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) + ) +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/TextLayerShadowRenderer.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/TextLayerShadowRenderer.kt new file mode 100644 index 0000000..d46970c --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/TextLayerShadowRenderer.kt @@ -0,0 +1,135 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.drawscope.CanvasDrawScope +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.drawText +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection +import androidx.core.graphics.applyCanvas +import androidx.core.graphics.createBitmap +import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType +import kotlin.math.ceil +import androidx.compose.ui.graphics.Canvas as ComposeCanvas +import androidx.compose.ui.graphics.Color as ComposeColor + +internal data class TextShadowRenderData( + val bitmap: Bitmap, + val left: Float, + val top: Float, + val rasterScale: Float +) + +internal fun buildTextShadowRenderData( + type: LayerType.Text, + textMetrics: TextLayerMetrics, + textLayoutResult: TextLayoutResult, + rasterScale: Float = 1f +): TextShadowRenderData? { + val shadow = type.shadow ?: return null + val safeRasterScale = rasterScale.coerceAtLeast(1f) + val sourcePadding = shadowSourcePadding( + textMetrics = textMetrics, + rasterScale = safeRasterScale + ) + val sourceWidth = ceil( + textLayoutResult.size.width * safeRasterScale + + sourcePadding.leftPx + + sourcePadding.rightPx + ).toInt().coerceAtLeast(1) + val sourceHeight = ceil( + textLayoutResult.size.height * safeRasterScale + + sourcePadding.topPx + + sourcePadding.bottomPx + ).toInt().coerceAtLeast(1) + val sourceBitmap = createBitmap( + width = sourceWidth, + height = sourceHeight + ).applyCanvas { + CanvasDrawScope().draw( + density = Density(1f), + layoutDirection = LayoutDirection.Ltr, + canvas = ComposeCanvas(this), + size = Size(sourceWidth.toFloat(), sourceHeight.toFloat()) + ) { + drawContext.canvas.save() + drawContext.canvas.translate(sourcePadding.leftPx, sourcePadding.topPx) + drawContext.canvas.scale(safeRasterScale, safeRasterScale) + drawText( + textLayoutResult = textLayoutResult, + color = ComposeColor.Black + ) + drawContext.canvas.restore() + } + } + + 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) +) \ No newline at end of file diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/di/MarkupLayersModule.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/di/MarkupLayersModule.kt new file mode 100644 index 0000000..6a2c4ce --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/di/MarkupLayersModule.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupLayer.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupLayer.kt new file mode 100644 index 0000000..b80d18f --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupLayer.kt @@ -0,0 +1,164 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupLayersApplier.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupLayersApplier.kt new file mode 100644 index 0000000..755c7b4 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupLayersApplier.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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, + fontScale: Float? = null + ): I + + suspend fun saveProject( + destination: Writeable, + project: MarkupProject + ) + + suspend fun openProject( + uri: String + ): MarkupProjectResult + + fun clearProjectCache() + +} \ No newline at end of file diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupLayersParams.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupLayersParams.kt new file mode 100644 index 0000000..cc4f3ad --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupLayersParams.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 MarkupLayersParams( + val sideMenuScale: Float = 1f, + val sideMenuAlpha: Float = 0.9f, + val sideMenuTranslationX: Float = 0f, + val sideMenuTranslationY: Float = 0f, + val isOptionsExpanded: Boolean = false +) \ No newline at end of file diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupProject.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupProject.kt new file mode 100644 index 0000000..e05c5e3 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupProject.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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, +) diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupProjectResult.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupProjectResult.kt new file mode 100644 index 0000000..54b298d --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupProjectResult.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/ProjectBackground.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/ProjectBackground.kt new file mode 100644 index 0000000..09b391f --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/ProjectBackground.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/ShapeLayerModeExt.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/ShapeLayerModeExt.kt new file mode 100644 index 0000000..2f4bd50 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/ShapeLayerModeExt.kt @@ -0,0 +1,547 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/MarkupLayersContent.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/MarkupLayersContent.kt new file mode 100644 index 0000000..dde3e5f --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/MarkupLayersContent.kt @@ -0,0 +1,514 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.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.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.graphicsLayer +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalDensity +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 com.t8rin.colors.util.roundToTwoDigits +import com.t8rin.dynamic.theme.LocalDynamicThemeState +import com.t8rin.imagetoolbox.core.domain.model.MimeType +import com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.ImageResize +import com.t8rin.imagetoolbox.core.resources.icons.Opacity +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.enhanced.EnhancedSliderItem +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.shimmer +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.BackgroundCanvasSizeControls +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() + val density = LocalDensity.current + + 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, + fontScale = density.fontScale + ) + } + 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 colorBackground = component.backgroundBehavior as? BackgroundBehavior.Color + val imageBitmap = component.bitmap + val canvasAspectRatio = remember( + imageBitmap, + colorBackground, + screenSize.widthPx, + screenSize.heightPx + ) { + when { + imageBitmap != null -> imageBitmap.width / imageBitmap.height.toFloat() + colorBackground != null -> colorBackground.width / colorBackground.height.toFloat() + else -> screenSize.widthPx / screenSize.heightPx.toFloat() + } + } + + 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 direction = LocalLayoutDirection.current + 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(canvasAspectRatio, isPortrait) + .fillMaxSize() + .clip(ShapeDefaults.extremeSmall) + .border( + width = 1.dp, + color = MaterialTheme.colorScheme.outlineVariant(), + shape = ShapeDefaults.extremeSmall + ) + .background(MaterialTheme.colorScheme.surfaceContainerLow) + .shimmer(component.isImageLoading), + 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 + ) { + if (colorBackground != null) { + Box( + modifier = Modifier + .matchParentSize() + .clipToBounds() + .background(colorBackground.color.toColor()) + ) + } else if (imageBitmap != null) { + 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) + .navigationBarsPadding(), + 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 + ) + ) + BackgroundCanvasSizeControls( + behavior = behavior, + imageFormat = component.imageFormat, + onApply = component::resizeBackgroundCanvas + ) + } + 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, + ) + Column( + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + EnhancedSliderItem( + value = component.sideMenuScale, + title = stringResource(R.string.markup_layers_side_menu_scale), + valueRange = 0.5f..1f, + internalStateTransformation = { it.roundToTwoDigits() }, + onValueChange = component::updateSideMenuScale, + shape = ShapeDefaults.top, + icon = Icons.Outlined.ImageResize, + modifier = Modifier.fillMaxWidth() + ) + EnhancedSliderItem( + value = component.sideMenuAlpha, + title = stringResource(R.string.markup_layers_side_menu_alpha), + valueRange = 0.5f..1f, + internalStateTransformation = { it.roundToTwoDigits() }, + onValueChange = component::updateSideMenuAlpha, + shape = ShapeDefaults.bottom, + icon = Icons.Rounded.Opacity, + modifier = Modifier.fillMaxWidth() + ) + } + ImageFormatSelector( + 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, + drawBothStrokes = 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 + ) +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/ActiveLayerGestureModifier.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/ActiveLayerGestureModifier.kt new file mode 100644 index 0000000..8cc0178 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/ActiveLayerGestureModifier.kt @@ -0,0 +1,147 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + } + } +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/AddShapeLayerDialog.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/AddShapeLayerDialog.kt new file mode 100644 index 0000000..1a6d335 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/AddShapeLayerDialog.kt @@ -0,0 +1,141 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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.enhancedVerticalScroll +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() + .enhancedVerticalScroll( + state = state + ) + .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)) + } + } + ) +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/AddTextLayerDialog.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/AddTextLayerDialog.kt new file mode 100644 index 0000000..0f4656a --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/AddTextLayerDialog.kt @@ -0,0 +1,110 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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)) + } + } + ) +} \ No newline at end of file diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/BackgroundCanvasSizeControls.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/BackgroundCanvasSizeControls.kt new file mode 100644 index 0000000..1c0527c --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/BackgroundCanvasSizeControls.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.animation.core.animateDpAsState +import androidx.compose.foundation.layout.Column +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.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.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.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.enhanced.EnhancedButton +import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape +import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.BackgroundBehavior + +@Composable +internal fun BackgroundCanvasSizeControls( + behavior: BackgroundBehavior.Color, + imageFormat: ImageFormat, + onApply: (width: Int, height: Int) -> Unit +) { + var width by rememberSaveable(behavior.width, behavior.height) { + mutableIntStateOf(behavior.width) + } + var height by rememberSaveable(behavior.width, behavior.height) { + mutableIntStateOf(behavior.height) + } + + val canApply by remember(width, height, behavior.width, behavior.height) { + derivedStateOf { + width > 0 && + height > 0 && + (width != behavior.width || height != behavior.height) + } + } + val bottomCorner by animateDpAsState( + if (canApply) 4.dp else 24.dp + ) + + Column { + ResizeImageField( + imageInfo = ImageInfo( + width = width, + height = height, + imageFormat = imageFormat + ), + originalSize = IntegerSize( + width = behavior.width, + height = behavior.height + ), + onWidthChange = { width = it.coerceIn(0, 8192) }, + onHeightChange = { height = it.coerceIn(0, 8192) }, + modifier = Modifier.fillMaxWidth(), + shape = AutoCornersShape( + topStart = 24.dp, + topEnd = 24.dp, + bottomStart = bottomCorner, + bottomEnd = bottomCorner + ) + ) + AnimatedVisibility( + visible = canApply, + modifier = Modifier.fillMaxWidth() + ) { + EnhancedButton( + onClick = { onApply(width, height) }, + enabled = canApply, + containerColor = MaterialTheme.colorScheme.secondaryContainer, + modifier = Modifier + .fillMaxWidth() + .padding(top = 4.dp), + shape = AutoCornersShape( + topStart = 4.dp, + topEnd = 4.dp, + bottomStart = 24.dp, + bottomEnd = 24.dp + ) + ) { + Text(stringResource(R.string.apply)) + } + } + } +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/ClickableTile.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/ClickableTile.kt new file mode 100644 index 0000000..7b29d1e --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/ClickableTile.kt @@ -0,0 +1,213 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.LocalIndication +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.waitForUpOrCancellation +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.PressInteraction +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.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.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 +import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction + +@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, + shape: Shape = ShapeDefaults.extraSmall, + modifier: Modifier = Modifier, + content: @Composable ColumnScope.() -> Unit +) { + val haptics = LocalHapticFeedback.current + val interactionSource = remember { MutableInteractionSource() } + + val interactionModifier = if (!enabled) { + Modifier + } else if (onHoldStep != null) { + Modifier.pointerInput(onClick, onHoldStep) { + awaitEachGesture { + val down = awaitFirstDown(requireUnconsumed = false) + down.consume() + + val press = PressInteraction.Press(down.position) + interactionSource.tryEmit(press) + + haptics.longPress() + onClick() + + var repeatDelayMs = 140L + val minRepeatDelayMs = 45L + + var up = withTimeoutOrNull(240L) { + waitForUpOrCancellation() + } + if (up != null) { + up.consume() + interactionSource.tryEmit(PressInteraction.Release(press)) + 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() + interactionSource.tryEmit(PressInteraction.Release(press)) + } + } + } else { + Modifier.hapticsClickable( + interactionSource = interactionSource, + indication = LocalIndication.current, + onClick = onClick + ) + } + + val animatedShape = shapeByInteraction( + shape = shape, + pressedShape = ShapeDefaults.pressed, + interactionSource = interactionSource + ) + + Column( + modifier = Modifier + .then( + if (modifier == Modifier) { + Modifier.size( + width = 100.dp, + height = 72.dp + ) + } else { + modifier + } + ) + .container( + shape = animatedShape, + 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 + ) +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/DropShadowSection.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/DropShadowSection.kt new file mode 100644 index 0000000..3f2866a --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/DropShadowSection.kt @@ -0,0 +1,156 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) + } + } + } + ) +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/EditBox.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/EditBox.kt new file mode 100644 index 0000000..463faf1 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/EditBox.kt @@ -0,0 +1,297 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.onGloballyPositioned +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, + animateOnTapWhenInactive: Boolean = false, + showSelectionBackground: Boolean = true, + adjustScaleOnCanvasResize: Boolean = true, + preserveBoundsOnCanvasResize: Boolean = false, + 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, + animateOnTapWhenInactive = animateOnTapWhenInactive, + showSelectionBackground = showSelectionBackground, + adjustScaleOnCanvasResize = adjustScaleOnCanvasResize, + preserveBoundsOnCanvasResize = preserveBoundsOnCanvasResize, + 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, + animateOnTapWhenInactive: Boolean = false, + showSelectionBackground: Boolean = true, + adjustScaleOnCanvasResize: Boolean = true, + preserveBoundsOnCanvasResize: Boolean = false, + content: @Composable BoxScope.() -> Unit +) { + if (!state.isVisible) return + + var measuredContentGeometry by remember { + mutableStateOf(null) + } + val contentSize = measuredContentGeometry?.contentSize ?: IntSize.Zero + + SideEffect { + if ( + measuredContentGeometry?.canvasSize == parentSize && + contentSize != IntSize.Zero + ) { + state.syncCanvasGeometry( + canvasSize = parentSize, + contentSize = contentSize, + cornerRadiusPercent = cornerRadiusPercent, + adjustScale = adjustScaleOnCanvasResize, + preserveBoundsPosition = preserveBoundsOnCanvasResize + ) + } + } + + var lastBoundsRecalculationGeometry by remember { + mutableStateOf(null) + } + var skipNextContentBoundsRecalculation by remember { + mutableStateOf(false) + } + + LaunchedEffect( + state.coerceToBounds, + measuredContentGeometry, + cornerRadiusPercent + ) { + val geometry = measuredContentGeometry + if (!state.coerceToBounds || geometry == null || contentSize == IntSize.Zero) return@LaunchedEffect + + val previousGeometry = lastBoundsRecalculationGeometry + val isCanvasResize = previousGeometry != null && + previousGeometry.canvasSize != geometry.canvasSize + val isContentResize = previousGeometry != null && + previousGeometry.contentSize != geometry.contentSize + val shouldRecalculateBounds = when { + previousGeometry == null -> true + isCanvasResize -> false + isContentResize && skipNextContentBoundsRecalculation -> false + isContentResize -> true + else -> false + } + + if (shouldRecalculateBounds) { + state.applyChanges( + parentMaxWidth = geometry.canvasSize.width, + parentMaxHeight = geometry.canvasSize.height, + contentSize = geometry.contentSize, + cornerRadiusPercent = cornerRadiusPercent, + zoomChange = 1f, + offsetChange = Offset.Zero, + rotationChange = 0f + ) + } + skipNextContentBoundsRecalculation = isCanvasResize + lastBoundsRecalculationGeometry = geometry + } + + 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 || animateOnTapWhenInactive) animateTap() + } + } + } else { + Modifier + } + + Box( + modifier = modifier + .onGloballyPositioned { + measuredContentGeometry = MeasuredContentGeometry( + canvasSize = parentSize, + contentSize = it.size + ) + } + .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 data class MeasuredContentGeometry( + val canvasSize: IntegerSize, + val contentSize: IntSize +) + +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() + } + } +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/EditBoxState.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/EditBoxState.kt new file mode 100644 index 0000000..ada6791 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/EditBoxState.kt @@ -0,0 +1,647 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ).also { copied -> + copied.preserveGeometryOnNextCanvasResize = preserveGeometryOnNextCanvasResize + } + + 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) + private var preserveGeometryOnNextCanvasResize = false + + init { + adjustByCanvasSize(canvasSize) + } + + var canvasSize: IntegerSize + get() = _canvasSize.value + set(value) { + adjustByCanvasSize(value) + } + + internal fun syncCanvasSize( + value: IntegerSize, + forceScaleAdjustment: Boolean = false, + adjustScale: Boolean = true, + preserveBoundsPosition: Boolean = false + ) { + syncCanvasGeometry( + canvasSize = value, + contentSize = contentSize, + forceScaleAdjustment = forceScaleAdjustment, + adjustScale = adjustScale, + preserveBoundsPosition = preserveBoundsPosition + ) + } + + internal fun syncCanvasGeometry( + canvasSize: IntegerSize, + contentSize: IntSize, + forceScaleAdjustment: Boolean = false, + cornerRadiusPercent: Int = 0, + adjustScale: Boolean = true, + preserveBoundsPosition: Boolean = false + ) { + adjustByCanvasGeometry( + canvasSize = canvasSize, + contentSize = contentSize, + forceScaleAdjustment = forceScaleAdjustment, + cornerRadiusPercent = cornerRadiusPercent, + adjustScale = adjustScale, + preserveBoundsPosition = preserveBoundsPosition + ) + } + + internal fun markPreserveGeometryOnNextCanvasResize() { + preserveGeometryOnNextCanvasResize = true + } + + private fun adjustByCanvasSize( + value: IntegerSize + ) { + adjustByCanvasGeometry( + canvasSize = value, + contentSize = contentSize + ) + } + + private fun adjustByCanvasGeometry( + canvasSize: IntegerSize, + contentSize: IntSize, + forceScaleAdjustment: Boolean = false, + cornerRadiusPercent: Int = 0, + adjustScale: Boolean = true, + preserveBoundsPosition: Boolean = false + ) { + val previousCanvasSize = _canvasSize.value + val previousContentSize = this.contentSize + if (previousCanvasSize == canvasSize && previousContentSize == contentSize) { + _canvasSize.value = canvasSize + this.contentSize = contentSize + preserveGeometryOnNextCanvasResize = false + return + } + if (preserveGeometryOnNextCanvasResize) { + _canvasSize.value = canvasSize + this.contentSize = contentSize + preserveGeometryOnNextCanvasResize = false + return + } + if (previousCanvasSize != IntegerSize.Zero && previousCanvasSize != canvasSize) { + if ( + previousCanvasSize.width <= 0 || + previousCanvasSize.height <= 0 || + canvasSize.width <= 0 || + canvasSize.height <= 0 + ) { + _canvasSize.value = canvasSize + this.contentSize = contentSize + return + } + + val normalizedBoundsPosition = if (preserveBoundsPosition) { + calculateNormalizedPosition( + canvasSize = previousCanvasSize, + contentSize = previousContentSize, + offset = offset, + scale = scale, + rotation = rotation, + cornerRadiusPercent = cornerRadiusPercent + ) + } else null + val sx = canvasSize.width.toFloat() / previousCanvasSize.width + val sy = canvasSize.height.toFloat() / previousCanvasSize.height + val referenceScale = min(canvasSize.width, canvasSize.height).toFloat() / + min(previousCanvasSize.width, previousCanvasSize.height) + + val contentReferenceScale = previousContentSize.referenceScaleTo(contentSize) + if ( + adjustScale && + contentReferenceScale > 0f && + previousContentSize.isSpecified() && + contentSize.isSpecified() && + (forceScaleAdjustment || !previousContentSize.isBoundedByCanvas(previousCanvasSize)) + ) { + scale = (scale * referenceScale / contentReferenceScale).fastCoerceIn( + minimumValue = SCALE_RANGE.start, + maximumValue = SCALE_RANGE.endInclusive + ) + } + + offset = normalizedBoundsPosition?.let { + calculateOffsetForNormalizedPosition( + normalizedPosition = it, + canvasSize = canvasSize, + contentSize = contentSize, + scale = scale, + rotation = rotation, + cornerRadiusPercent = cornerRadiusPercent + ) + } ?: Offset( + x = offset.x * sx, + y = offset.y * sy + ) + } + _canvasSize.value = canvasSize + this.contentSize = contentSize + } + + internal fun normalizedPosition( + cornerRadiusPercent: Int + ): Offset? = calculateNormalizedPosition( + canvasSize = canvasSize, + contentSize = contentSize, + offset = offset, + scale = scale, + rotation = rotation, + cornerRadiusPercent = cornerRadiusPercent + ) + + 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 calculateNormalizedPosition( + canvasSize: IntegerSize, + contentSize: IntSize, + offset: Offset, + scale: Float, + rotation: Float, + cornerRadiusPercent: Int +): Offset? { + 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 + ) + ) +} + +private fun calculateOffsetForNormalizedPosition( + normalizedPosition: Offset, + canvasSize: IntegerSize, + contentSize: IntSize, + scale: Float, + rotation: Float, + cornerRadiusPercent: Int +): Offset? { + 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 targetLeft = normalizedPosition.x.denormalizeEdgeAware( + axisSize = canvasWidth.toFloat(), + layerSize = layerWidth + ) + val targetTop = normalizedPosition.y.denormalizeEdgeAware( + axisSize = canvasHeight.toFloat(), + layerSize = layerHeight + ) + + return Offset( + x = targetLeft - canvasWidth / 2f + halfWidth, + y = targetTop - canvasHeight / 2f + halfHeight + ) +} + +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.referenceScaleTo( + value: IntSize +): Float { + if (!isSpecified() || !value.isSpecified()) return 0f + + return min(value.width, value.height).toFloat() / min(width, height) +} + +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 diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/EditLayerSheet.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/EditLayerSheet.kt new file mode 100644 index 0000000..8a0e3dc --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/EditLayerSheet.kt @@ -0,0 +1,743 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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.BorderColor +import com.t8rin.imagetoolbox.core.resources.icons.BorderStyle +import com.t8rin.imagetoolbox.core.resources.icons.FormatAlignCenter +import com.t8rin.imagetoolbox.core.resources.icons.FormatAlignLeft +import com.t8rin.imagetoolbox.core.resources.icons.FormatAlignRight +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.icons.MiniEditLarge +import com.t8rin.imagetoolbox.core.resources.icons.Percent +import com.t8rin.imagetoolbox.core.resources.icons.Rectangle +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.Rounded.FormatAlignLeft + Alignment.Center -> Icons.Rounded.FormatAlignCenter + Alignment.End -> Icons.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.Rounded.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 + ) + + EmojiSelectionSheet( + selectedEmojiIndex = null, + onEmojiPicked = { + updateLayerWithHistory( + layer.copy( + type = type.copy( + imageData = Emoji.allIcons[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 = ShapeDefaults.top, + modifier = Modifier.fillMaxWidth(), + containerColor = MaterialTheme.colorScheme.surface + ) + 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 = if (layer.isGroup) ShapeDefaults.bottom else ShapeDefaults.center + ) + if (!layer.isGroup) { + 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.Rounded.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 + ) + } + } + } + } +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/Layer.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/Layer.kt new file mode 100644 index 0000000..078b9be --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/Layer.kt @@ -0,0 +1,299 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +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.combinedBounds +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 +import kotlinx.coroutines.launch + +@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() + }, + preserveBoundsOnCanvasResize = type is LayerType.Shape && onUpdateLayer != null, + 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 || type is LayerType.Shape -> Modifier.sizeIn( + maxWidth = this@Layer.maxWidth, + maxHeight = this@Layer.maxHeight + ) + + else -> { + Modifier.sizeIn( + maxWidth = this@Layer.maxWidth / 2, + maxHeight = this@Layer.maxHeight / 2 + ) + } + } + + val renderContentSize = if (type is LayerType.Shape && onUpdateLayer != null) { + IntSize.Zero + } else { + layer.state.contentSize + } + + LayerContent( + modifier = contentModifier, + type = type, + groupedLayers = layer.groupedLayers, + textFullSize = referenceSizeOverride + ?: this@Layer.constraints.run { minOf(maxWidth, maxHeight) }, + contentSize = renderContentSize, + layerScale = layer.state.scale, + 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 scope = rememberCoroutineScope() + val tapScale = remember { Animatable(1f) } + val parentCanvasSize = IntegerSize( + width = constraints.maxWidth, + height = constraints.maxHeight + ) + val animateGroupTap = { + scope.launch { + tapScale.stop() + tapScale.snapTo(1f) + tapScale.animateTo(0.98f) + tapScale.animateTo(1.02f) + tapScale.animateTo(1f) + } + } + 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) + val groupCenter = leafLayers.combinedBounds()?.center ?: Offset.Zero + + leafLayers.forEach { child -> + val renderedChild = child.renderCopy().let { renderCopy -> + val displayCopy = renderCopy.withTransientGroupTapScale( + scaleMultiplier = tapScale.value, + groupCenter = groupCenter + ) + + if (layer.state.isActive) { + displayCopy.copy( + state = displayCopy.state.copy( + isActive = true + ) + ) + } else displayCopy + } + 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, + animateOnTapWhenInactive = true, + showSelectionBackground = false, + onTap = { + animateGroupTap() + activateGroup?.invoke() + }, + onLongTap = { + animateGroupTap() + 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.syncCanvasGeometry( + canvasSize = parentCanvasSize, + contentSize = measuredContentSize, + forceScaleAdjustment = true + ) + } + + 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 + +private fun UiMarkupLayer.withTransientGroupTapScale( + scaleMultiplier: Float, + groupCenter: Offset +): UiMarkupLayer { + if (scaleMultiplier == 1f || isGroup) return this + + return copy( + state = state.copy( + scale = state.scale * scaleMultiplier, + offset = groupCenter + (state.offset - groupCenter) * scaleMultiplier + ) + ) +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/LayerAlignmentSelector.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/LayerAlignmentSelector.kt new file mode 100644 index 0000000..0a22a82 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/LayerAlignmentSelector.kt @@ -0,0 +1,276 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +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.Shape +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.domain.model.Position +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Place +import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview +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.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 kotlin.math.abs + +@Composable +internal fun LayerAlignmentSupportingButton( + normalizedPositionX: Float?, + normalizedPositionY: Float?, + enabled: Boolean, + onClick: () -> Unit +) { + val selected = normalizedPositionX.isAlignmentValue() && + normalizedPositionY.isAlignmentValue() + + SupportingButton( + icon = Icons.Outlined.Place, + onClick = { + if (enabled) onClick() + }, + containerColor = takeColorFromScheme { + if (selected) secondary else secondaryContainer + }, + contentColor = takeColorFromScheme { + if (selected) onSecondary else onSecondaryContainer + }, + shape = ShapeDefaults.circle, + modifier = Modifier + .padding(4.dp) + .alpha(if (enabled) 1f else 0.5f) + ) +} + +@Composable +internal fun LayerAlignmentDialog( + visible: Boolean, + normalizedPositionX: Float?, + normalizedPositionY: Float?, + onAlignLayer: (Float, Float) -> Unit, + onDismiss: () -> Unit +) { + EnhancedAlertDialog( + visible = visible, + onDismissRequest = onDismiss, + icon = { + Icon( + imageVector = Icons.Outlined.Place, + contentDescription = null + ) + }, + title = { + Text(stringResource(R.string.position)) + }, + text = { + val state = rememberScrollState() + + Row( + modifier = Modifier + .fillMaxWidth() + .fadingEdges( + scrollableState = state, + isVertical = true + ) + .enhancedVerticalScroll(state), + horizontalArrangement = Arrangement.Center + ) { + CompositionLocalProvider( + LocalLayoutDirection provides LayoutDirection.Ltr + ) { + LayerAlignmentSelector( + normalizedPositionX = normalizedPositionX, + normalizedPositionY = normalizedPositionY, + onAlignLayer = onAlignLayer + ) + } + } + }, + confirmButton = { + EnhancedButton( + onClick = onDismiss, + containerColor = MaterialTheme.colorScheme.secondaryContainer + ) { + Text(stringResource(R.string.close)) + } + } + ) +} + +@Composable +internal fun LayerAlignmentSelector( + normalizedPositionX: Float?, + normalizedPositionY: Float?, + onAlignLayer: (Float, Float) -> Unit +) { + val positions = Position.entriesSorted + val entries = remember { + positions.chunked(ALIGNMENT_GRID_COLUMN_COUNT) + } + + Column( + modifier = Modifier + .widthIn(max = 400.dp) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + entries.forEachIndexed { rowIndex, rowPositions -> + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + rowPositions.forEachIndexed { columnIndex, position -> + val x = normalizedAlignmentValues[columnIndex] + val y = normalizedAlignmentValues[rowIndex] + AlignmentTile( + position = position, + selected = normalizedPositionX?.isNear(x) == true && + normalizedPositionY?.isNear(y) == true, + onClick = { onAlignLayer(x, y) }, + shape = ShapeDefaults.byGridIndex( + index = rowIndex * ALIGNMENT_GRID_COLUMN_COUNT + columnIndex, + size = positions.size, + crossAxisCount = ALIGNMENT_GRID_COLUMN_COUNT + ) + ) + } + } + } + } +} + +@Composable +private fun RowScope.AlignmentTile( + position: Position, + selected: Boolean, + onClick: () -> Unit, + shape: Shape +) { + val contentColor = takeColorFromScheme { + if (selected) { + onSecondary + } else { + onSurface + } + } + + ClickableTile( + onClick = onClick, + containerColor = takeColorFromScheme { + if (selected) { + secondary + } else { + surfaceContainerLow + } + }, + shape = shape, + modifier = Modifier.weight(1f) + ) { + Box( + contentAlignment = position.contentAlignment, + modifier = Modifier + .padding(12.dp) + .size(32.dp) + .border( + width = 2.dp, + color = contentColor.copy(alpha = 0.6f), + shape = ShapeDefaults.pressed + ) + .padding(4.dp) + ) { + Box( + modifier = Modifier + .size(12.dp) + .background( + color = contentColor, + shape = ShapeDefaults.extremeSmall + ) + ) + } + } +} + +private val normalizedAlignmentValues = listOf(0f, NORMALIZED_CENTER, 1f) + +private val Position.contentAlignment: Alignment + get() = when (this) { + 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 + } + +private fun Float.isNear(value: Float): Boolean = abs(this - value) < ALIGNMENT_TOLERANCE + +private fun Float?.isAlignmentValue(): Boolean = this != null && + normalizedAlignmentValues.any(::isNear) + +private const val ALIGNMENT_GRID_COLUMN_COUNT = 3 +private const val NORMALIZED_CENTER = 0.5f +private const val ALIGNMENT_TOLERANCE = 0.0001 + +@Preview +@Composable +private fun Preview() = ImageToolboxThemeForPreview(true) { + Button({}) { } + Box( + modifier = Modifier + .background(MaterialTheme.colorScheme.surface) + .padding(24.dp) + ) { + LayerAlignmentSelector( + normalizedPositionX = 0.5f, + normalizedPositionY = 0.5f, + onAlignLayer = { _, _ -> } + ) + } +} \ No newline at end of file diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/LayerContent.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/LayerContent.kt new file mode 100644 index 0000000..1b741a3 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/LayerContent.kt @@ -0,0 +1,481 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.IntSize +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.domain.model.IntegerSize +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.SubcomposePicture +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.resolveLayerShadowRasterScale +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 androidx.compose.ui.text.style.TextGeometricTransform as ComposeTextGeometricTransform + +@Composable +internal fun LayerContent( + modifier: Modifier = Modifier, + type: LayerType, + groupedLayers: List = emptyList(), + textFullSize: Int, + contentSize: IntSize = IntSize.Zero, + layerScale: Float = 1f, + 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, + layerScale = layerScale + ) + + is LayerType.Text -> TextLayerContent( + modifier = modifier, + type = type, + textFullSize = textFullSize, + layerScale = layerScale, + maxLines = maxLines, + onTextLayout = onTextLayout + ) + + is LayerType.Shape -> ShapeLayerContent( + modifier = modifier, + type = type, + textFullSize = textFullSize, + contentSize = contentSize, + layerScale = layerScale + ) + } + } +} + +@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, + contentSize: IntSize, + layerScale: Float +) { + val density = LocalDensity.current + val shapeContentInsetPx = with(density) { 4.dp.toPx() } + val shadowRasterScale = remember(layerScale) { + resolveLayerShadowRasterScale(layerScale) + } + BoxWithConstraints( + modifier = modifier, + contentAlignment = Alignment.Center + ) { + val renderData = remember( + type, + textFullSize, + contentSize, + constraints.maxWidth, + constraints.maxHeight, + shapeContentInsetPx + ) { + resolveShapeLayerRenderData( + type = type, + referenceSize = textFullSize.toFloat(), + contentSize = contentSize.toIntegerSize(), + 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, + rasterScale = shadowRasterScale + ) + + onDrawWithContent { + shadow?.let { shadowData -> + drawContext.canvas.nativeCanvas.apply { + withSave { + val rasterScale = shadowData.rasterScale.coerceAtLeast(1f) + scale(1f / rasterScale, 1f / rasterScale) + drawBitmap( + shadowData.bitmap, + renderData.contentLeft * rasterScale + shadowData.left, + renderData.contentTop * rasterScale + 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, + layerScale: Float +) { + val density = LocalDensity.current + var previewBitmap by remember(type.imageData) { + mutableStateOf(null) + } + val shadowPadding = remember(type.shadow) { + calculateShadowPadding(type.shadow) + } + val shadowRasterScale = remember(layerScale) { + resolveLayerShadowRasterScale(layerScale) + } + + 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, + rasterScale = shadowRasterScale + ) + } + + onDrawWithContent { + shadow?.let { shadowData -> + drawContext.canvas.nativeCanvas.apply { + withSave { + val rasterScale = shadowData.rasterScale.coerceAtLeast(1f) + scale(1f / rasterScale, 1f / rasterScale) + drawBitmap( + shadowData.bitmap, + shadowPadding.leftPx * rasterScale + shadowData.left, + shadowPadding.topPx * rasterScale + shadowData.top, + null + ) + } + } + } + drawContent() + } + }, + contentAlignment = Alignment.Center + ) { + SubcomposePicture( + 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, + layerScale: Float, + 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 + ) + ) + } + } + val shadowRasterScale = remember(layerScale) { + resolveLayerShadowRasterScale(layerScale) + } + var textLayoutResult by remember( + type, + textFullSize, + maxLines, + density + ) { + mutableStateOf(null) + } + + Box( + modifier = modifier, + contentAlignment = Alignment.Center + ) { + OutlinedText( + text = type.text, + style = mergedStyle, + outlineParams = outlineParams, + maxLines = maxLines, + onTextLayout = { result -> + textLayoutResult = result + onTextLayout?.invoke(result) + }, + modifier = Modifier + .drawWithCache { + val textLeft = textMetrics.padding.leftPx + val textTop = textMetrics.padding.topPx + val shadow = textLayoutResult?.let { + buildTextShadowRenderData( + type = type, + textMetrics = textMetrics, + textLayoutResult = it, + rasterScale = shadowRasterScale + ) + } + + 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() } + ) + ) + } +} + +private fun IntSize.toIntegerSize(): IntegerSize = IntegerSize( + width = width.coerceAtLeast(0), + height = height.coerceAtLeast(0) +) diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersActions.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersActions.kt new file mode 100644 index 0000000..682b642 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersActions.kt @@ -0,0 +1,279 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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.Redo +import com.t8rin.imagetoolbox.core.resources.icons.StarSticky +import com.t8rin.imagetoolbox.core.resources.icons.TextSticky +import com.t8rin.imagetoolbox.core.resources.icons.Undo +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)) + } + } + + EmojiSelectionSheet( + selectedEmojiIndex = null, + onEmojiPicked = { + component.addLayer( + UiMarkupLayer( + type = LayerType.Picture.Sticker(Emoji.allIcons[it]) + ) + ) + showEmojiPicker = false + }, + visible = showEmojiPicker, + onDismiss = { + showEmojiPicker = false + }, + icon = Icons.Outlined.EmojiSticky, + useAnimatedEmojis = false + ) + + 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.Rounded.Undo, + contentDescription = "Undo" + ) + } + EnhancedIconButton( + onClick = component::redo, + enabled = component.canRedo + ) { + Icon( + imageVector = Icons.Rounded.Redo, + contentDescription = "Redo" + ) + } + } +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersContextActions.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersContextActions.kt new file mode 100644 index 0000000..bfac32a --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersContextActions.kt @@ -0,0 +1,612 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowDropDown +import com.t8rin.imagetoolbox.core.resources.icons.ArrowDropUp +import com.t8rin.imagetoolbox.core.resources.icons.ArrowLeft +import com.t8rin.imagetoolbox.core.resources.icons.ArrowRight +import com.t8rin.imagetoolbox.core.resources.icons.CenterFocusStrong +import com.t8rin.imagetoolbox.core.resources.icons.ContentCopy +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.Deselect +import com.t8rin.imagetoolbox.core.resources.icons.FitScreen +import com.t8rin.imagetoolbox.core.resources.icons.Flip +import com.t8rin.imagetoolbox.core.resources.icons.FlipVertical +import com.t8rin.imagetoolbox.core.resources.icons.Lock +import com.t8rin.imagetoolbox.core.resources.icons.LockOpen +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.icons.ScreenRotationAlt +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, + onAlignLayer: (Float, Float) -> 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 +) { + val transformActionsEnabled = !isLayerLocked && !isGroupingSelectionMode + + var expandedAdjustableAction by rememberSaveable { + mutableStateOf(AdjustableActionType.None) + } + var valueDialogType by rememberSaveable { + mutableStateOf(ValueDialogType.None) + } + var showAlignmentDialog by rememberSaveable(transformActionsEnabled) { + mutableStateOf(false) + } + + 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.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.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 + ) + ) + } + LayerAlignmentSupportingButton( + normalizedPositionX = normalizedPositionX, + normalizedPositionY = normalizedPositionY, + enabled = transformActionsEnabled, + onClick = { + onDismiss() + showAlignmentDialog = true + } + ) + 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 + } + } + ) + + LayerAlignmentDialog( + visible = showAlignmentDialog, + normalizedPositionX = normalizedPositionX, + normalizedPositionY = normalizedPositionY, + onAlignLayer = onAlignLayer, + onDismiss = { showAlignmentDialog = false } + ) +} + +@Composable +private fun AdjustableActionCard( + title: String, + value: Float, + valueRange: ClosedFloatingPointRange, + enabled: Boolean, + sliderEnabled: Boolean, + onValueClick: () -> Unit, + onValueChange: (Float) -> Unit, + onValueChangeFinished: () -> Unit, + modifier: Modifier = Modifier +) { + val initialValue = rememberSaveable { value } + + 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, + onLongClick = if (enabled) { + { + onValueChange(initialValue) + onValueChangeFinished() + } + } 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 +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersNoDataControls.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersNoDataControls.kt new file mode 100644 index 0000000..50145c3 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersNoDataControls.kt @@ -0,0 +1,250 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + } + ) +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersSideMenu.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersSideMenu.kt new file mode 100644 index 0000000..c559b06 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersSideMenu.kt @@ -0,0 +1,521 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.core.animateFloatAsState +import androidx.compose.animation.expandHorizontally +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkHorizontally +import androidx.compose.foundation.gestures.detectDragGestures +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.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.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.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.domain.utils.roundTo +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.Build +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 { + var parentSize by remember { + mutableStateOf(IntSize.Zero) + } + var sideMenuSize by remember { + mutableStateOf(IntSize.Zero) + } + + AnimatedVisibility( + visible = visible, + enter = fadeIn() + expandHorizontally(), + exit = fadeOut() + shrinkHorizontally(), + modifier = Modifier.fillMaxSize(), + ) { + BoxWithConstraints( + modifier = Modifier + .fillMaxSize() + .padding( + WindowInsets.systemBars + .union(WindowInsets.displayCutout) + .asPaddingValues() + ) + .onSizeChanged { + parentSize = it + }, + contentAlignment = Alignment.CenterEnd + ) { + if (visible) { + BackHandler(onBack = onDismiss) + + Box( + Modifier + .fillMaxSize() + .tappable { onDismiss() } + ) + } + + val maxHeightFull = this.maxHeight + val sideMenuScale by animateFloatAsState(component.sideMenuScale) + val sideMenuAlpha by animateFloatAsState(component.sideMenuAlpha) + + LaunchedEffect(parentSize, sideMenuSize, sideMenuScale) { + component.updateSideMenuTranslation( + coerceSideMenuTranslation( + currentTranslation = Offset( + component.sideMenuTranslationX, + component.sideMenuTranslationY + ), + dragAmount = Offset.Zero, + parentSize = parentSize, + menuSize = sideMenuSize, + scale = sideMenuScale + ) + ) + } + + Surface( + color = Color.Transparent, + modifier = Modifier + .onSizeChanged { + sideMenuSize = it + } + .graphicsLayer { + scaleX = sideMenuScale + scaleY = sideMenuScale + translationX = component.sideMenuTranslationX + translationY = component.sideMenuTranslationY + } + ) { + Column( + modifier = Modifier + .padding(8.dp) + .height( + minOf(maxHeightFull, 480.dp) + ) + .width(168.dp) + .container( + color = MaterialTheme.colorScheme.surfaceContainer.copy( + alpha = sideMenuAlpha + ), + 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.copy( + alpha = sideMenuAlpha * 0.9f + ), + composeColorOnTopOfBackground = false + ) + .pointerInput(parentSize, sideMenuSize, sideMenuScale) { + detectDragGestures { change, dragAmount -> + change.consume() + + component.updateSideMenuTranslation( + coerceSideMenuTranslation( + currentTranslation = Offset( + component.sideMenuTranslationX, + component.sideMenuTranslationY + ), + dragAmount = dragAmount, + parentSize = parentSize, + menuSize = sideMenuSize, + scale = sideMenuScale + ) + ) + } + } + ) { + val showContextActions = + isContextOptionsVisible && (activeLayer != null || component.isGroupingSelectionMode) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + EnhancedIconButton( + onClick = { + activeLayer?.let(component::removeLayer) + }, + enabled = activeLayer != null && !component.isGroupingSelectionMode && !showContextActions + ) { + 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, + enabled = !showContextActions + ) { + Icon( + imageVector = Icons.Outlined.StackSticky, + contentDescription = null + ) + } + } else if (canUngroupLayer) { + EnhancedIconButton( + onClick = { + activeLayer?.let(component::ungroupLayer) + }, + enabled = !showContextActions + ) { + 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 = showContextActions, + 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 + } + } + }, + onAlignLayer = { x, y -> + activeLayer?.let { layer -> + component.setLayerNormalizedPosition( + layer = layer, + x = x, + y = y + ) + } + }, + 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) + ) + } + }, + containerColor = Color.Transparent, + 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 coerceSideMenuTranslation( + currentTranslation: Offset, + dragAmount: Offset, + parentSize: IntSize, + menuSize: IntSize, + scale: Float +): Offset { + if ( + parentSize.width <= 0 || + parentSize.height <= 0 || + menuSize.width <= 0 || + menuSize.height <= 0 + ) { + return currentTranslation + dragAmount * scale + } + + val parentWidth = parentSize.width.toFloat() + val parentHeight = parentSize.height.toFloat() + + val menuWidth = menuSize.width.toFloat() + val menuHeight = menuSize.height.toFloat() + + val scaledHalfWidth = menuWidth * scale / 2f + val scaledHalfHeight = menuHeight * scale / 2f + + val initialCenterX = parentWidth - menuWidth / 2f + val initialCenterY = parentHeight / 2f + + val maxCenterX = parentWidth - scaledHalfWidth + + val maxCenterY = parentHeight - scaledHalfHeight + + val targetTranslation = currentTranslation + dragAmount * scale + + val targetCenterX = initialCenterX + targetTranslation.x + val targetCenterY = initialCenterY + targetTranslation.y + + val coercedCenterX = targetCenterX.coerceInSafe(scaledHalfWidth, maxCenterX) + val coercedCenterY = targetCenterY.coerceInSafe(scaledHalfHeight, maxCenterY) + + return Offset( + x = coercedCenterX - initialCenterX, + y = coercedCenterY - initialCenterY + ) +} + +private fun Float.coerceInSafe( + minimumValue: Float, + maximumValue: Float +): Float { + return if (minimumValue <= maximumValue) { + coerceIn(minimumValue, maximumValue) + } else { + (minimumValue + maximumValue) / 2f + } +} + +private fun Float.normalizeForUi(): Float { + val normalized = this % 360f + + return when { + normalized < 0f -> normalized + 360f + normalized == 0f && this > 0f -> 360f + else -> normalized + } +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersSideMenuColumn.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersSideMenuColumn.kt new file mode 100644 index 0000000..f609862 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersSideMenuColumn.kt @@ -0,0 +1,383 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 com.t8rin.imagetoolbox.core.resources.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.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.DragHandle +import com.t8rin.imagetoolbox.core.resources.icons.EmojiSticky +import com.t8rin.imagetoolbox.core.resources.icons.ImageSticky +import com.t8rin.imagetoolbox.core.resources.icons.Lock +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.resources.icons.Visibility +import com.t8rin.imagetoolbox.core.resources.icons.VisibilityOff +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 diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersTopAppBarActions.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersTopAppBarActions.kt new file mode 100644 index 0000000..7a7c36a --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersTopAppBarActions.kt @@ -0,0 +1,119 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.DeleteSweep +import com.t8rin.imagetoolbox.core.resources.icons.Tune +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() + val density = LocalDensity.current + + 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.DeleteSweep, + contentDescription = stringResource(R.string.clear) + ) + } + + var editSheetData by remember { + mutableStateOf(listOf()) + } + ShareButton( + enabled = component.backgroundBehavior !is BackgroundBehavior.None, + onShare = { + component.shareBitmap( + fontScale = density.fontScale + ) + }, + onCopy = { + component.cacheCurrentImage( + fontScale = density.fontScale, + onComplete = Clipboard::copy + ) + }, + onEdit = { + component.cacheCurrentImage( + fontScale = density.fontScale + ) { uri -> + editSheetData = listOf(uri) + } + } + ) + ProcessImagesPreferenceSheet( + uris = editSheetData, + visible = editSheetData.isNotEmpty(), + onDismiss = { + editSheetData = emptyList() + }, + onNavigate = component.onNavigate + ) + } +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/ShapeLayerParamsSelector.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/ShapeLayerParamsSelector.kt new file mode 100644 index 0000000..ed70231 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/ShapeLayerParamsSelector.kt @@ -0,0 +1,563 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.FormatColorFill +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.Rounded.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 + } +} diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/BackgroundBehavior.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/BackgroundBehavior.kt new file mode 100644 index 0000000..9ecd3b5 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/BackgroundBehavior.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() +} \ No newline at end of file diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/ShapeLayerModeUiExt.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/ShapeLayerModeUiExt.kt new file mode 100644 index 0000000..bbb8b35 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/ShapeLayerModeUiExt.kt @@ -0,0 +1,72 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +import androidx.compose.ui.graphics.vector.ImageVector +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.CheckBoxOutlineBlank +import com.t8rin.imagetoolbox.core.resources.icons.Circle +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.Star +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.Outlined.Circle + 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.Outlined.Star + } diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/UiMarkupLayer.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/UiMarkupLayer.kt new file mode 100644 index 0000000..2217308 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/UiMarkupLayer.kt @@ -0,0 +1,147 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.FormatBold +import com.t8rin.imagetoolbox.core.resources.icons.FormatItalic +import com.t8rin.imagetoolbox.core.resources.icons.FormatStrikethrough +import com.t8rin.imagetoolbox.core.resources.icons.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 + } diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/UiMarkupLayerGrouping.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/UiMarkupLayerGrouping.kt new file mode 100644 index 0000000..fb80884 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/UiMarkupLayerGrouping.kt @@ -0,0 +1,644 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 { + if (!isGroup) return listOf(asDomain()) + + return canvasLeafLayers( + canvasSize = state.canvasSize + ).map(UiMarkupLayer::asDomain) +} + +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 +) + +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 +) + +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 +) + +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 diff --git a/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/screenLogic/MarkupLayersComponent.kt b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/screenLogic/MarkupLayersComponent.kt new file mode 100644 index 0000000..75ed0b5 --- /dev/null +++ b/feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/screenLogic/MarkupLayersComponent.kt @@ -0,0 +1,1063 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.core.utils.makeLog +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.MarkupLayersParams +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 dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext +import kotlin.time.Duration.Companion.seconds + + +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 var _params by fileController.savable( + scope = componentScope, + initial = MarkupLayersParams(), + key = "markupLayersParams" + ) + val isOptionsExpanded: Boolean get() = _params.isOptionsExpanded + val sideMenuScale: Float get() = _params.sideMenuScale + val sideMenuAlpha: Float get() = _params.sideMenuAlpha + val sideMenuTranslationX: Float get() = _params.sideMenuTranslationX + val sideMenuTranslationY: Float get() = _params.sideMenuTranslationY + + 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() { + _params = _params.copy(isOptionsExpanded = !_params.isOptionsExpanded) + } + + fun updateSideMenuScale(scale: Float) { + _params = _params.copy(sideMenuScale = scale) + } + + fun updateSideMenuAlpha(alpha: Float) { + _params = _params.copy(sideMenuAlpha = alpha) + } + + fun updateSideMenuTranslation(offset: Offset) { + _params = _params.copy( + sideMenuTranslationX = offset.x, + sideMenuTranslationY = offset.y + ) + } + + 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?, + fontScale: Float? + ) { + savingJob = trackProgress { + _isSaving.value = true + renderLayers(fontScale)?.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 { + _isImageLoading.value = true + _bitmap.value = imageScaler.scaleUntilCanShow(bitmap) + _isImageLoading.value = false + } + } + + private suspend fun updateBitmapSync(bitmap: Bitmap?) { + _bitmap.value = imageScaler.scaleUntilCanShow(bitmap) + } + + 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 + ) + delay(2.seconds) + registerChangesCleared() + } + + is MarkupProjectResult.Error -> { + AppToastHost.showFailureToast(result.message) + } + } + _isImageLoading.value = false + } + } + + private suspend fun renderLayers( + fontScale: Float? + ): 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), + fontScale = fontScale + ) + }.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 + + _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( + fontScale: Float? + ) { + savingJob = trackProgress { + _isSaving.value = true + renderLayers(fontScale)?.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( + fontScale: Float?, + onComplete: (Uri) -> Unit + ) { + savingJob = trackProgress { + _isSaving.value = true + renderLayers(fontScale)?.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 + } + } + } + + fun resizeBackgroundCanvas( + reqWidth: Int, + reqHeight: Int + ) { + val behavior = backgroundBehavior as? BackgroundBehavior.Color ?: return + val width = reqWidth.takeIf { it > 0 } ?: behavior.width + val height = reqHeight.takeIf { it > 0 } ?: behavior.height + + if (width == behavior.width && height == behavior.height) return + + runEditorChange { + _backgroundBehavior.value = behavior.copy( + width = width, + height = height + ) + layers.forEach { layer -> + layer.state.markPreserveGeometryOnNextCanvasResize() + } + } + } + + 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 +} diff --git a/feature/media-picker/.gitignore b/feature/media-picker/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/media-picker/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/media-picker/build.gradle.kts b/feature/media-picker/build.gradle.kts new file mode 100644 index 0000000..f5973dc --- /dev/null +++ b/feature/media-picker/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} diff --git a/feature/media-picker/src/main/AndroidManifest.xml b/feature/media-picker/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6889f04 --- /dev/null +++ b/feature/media-picker/src/main/AndroidManifest.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/AndroidMediaRetriever.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/AndroidMediaRetriever.kt new file mode 100644 index 0000000..8b15131 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/AndroidMediaRetriever.kt @@ -0,0 +1,190 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/DateExt.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/DateExt.kt new file mode 100644 index 0000000..11bb421 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/DateExt.kt @@ -0,0 +1,68 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.icu.text.SimpleDateFormat +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.utils.getString +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.util.Calendar +import java.util.Locale + +fun Long.getDate( + format: String = DEFAULT_DATE_FORMAT, + weeklyFormat: String = WEEKLY_DATE_FORMAT, + extendedFormat: String = EXTENDED_DATE_FORMAT, + stringToday: String = getString(R.string.header_today), + stringYesterday: String = getString(R.string.header_yesterday) +): String { + val currentDate = Calendar.getInstance(Locale.getDefault()).apply { + timeInMillis = System.currentTimeMillis() + } + + val mediaDate = Calendar.getInstance(Locale.getDefault()).apply { + timeInMillis = this@getDate * 1000L + } + + val daysDifference = + (System.currentTimeMillis() - mediaDate.timeInMillis) / (1000 * 60 * 60 * 24) + + 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) { + SimpleDateFormat(weeklyFormat, Locale.getDefault()) + } else { + if (currentDate.get(Calendar.YEAR) > mediaDate.get(Calendar.YEAR)) { + SimpleDateFormat(extendedFormat, Locale.getDefault()) + } else { + SimpleDateFormat(format, Locale.getDefault()) + } + }.format(mediaDate.time) + } + }.replaceFirstChar { it.titlecase(Locale.getDefault()) } +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/MediaObserver.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/MediaObserver.kt new file mode 100644 index 0000000..9037987 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/MediaObserver.kt @@ -0,0 +1,230 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 expiryTimestamp: Long? = try { + getLong(getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_EXPIRES)) + } catch (_: Throwable) { + null + } + val width: Int? = getPositiveIntOrNull(MediaStore.MediaColumns.WIDTH) + val height: Int? = getPositiveIntOrNull(MediaStore.MediaColumns.HEIGHT) + + 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) + + return Media( + id = id, + label = title, + uri = uri.toString(), + path = path, + relativePath = relativePath, + albumID = albumID, + albumLabel = albumLabel, + timestamp = modifiedTimestamp, + takenTimestamp = takenTimestamp, + expiryTimestamp = expiryTimestamp, + mimeType = mimeType, + width = width, + height = height + ) +} + +private fun Cursor.getPositiveIntOrNull(column: String): Int? = runCatching { + val index = getColumnIndex(column) + if (index != -1 && !isNull(index)) getInt(index).takeIf { it > 0 } else null +}.getOrNull() + +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 + ) + ) + ) + } +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/MediaQuery.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/MediaQuery.kt new file mode 100644 index 0000000..7687517 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/MediaQuery.kt @@ -0,0 +1,285 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.core.utils.makeLog +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 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, + MediaStore.MediaColumns.WIDTH, + MediaStore.MediaColumns.HEIGHT + ).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, + MediaStore.MediaColumns.WIDTH, + MediaStore.MediaColumns.HEIGHT + ).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, + MediaStore.MediaColumns.WIDTH, + MediaStore.MediaColumns.HEIGHT + ).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, + MediaStore.MediaColumns.WIDTH, + MediaStore.MediaColumns.HEIGHT + ).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() + } +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/SupportedFiles.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/SupportedFiles.kt new file mode 100644 index 0000000..8b65410 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/SupportedFiles.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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" } \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/di/MediaPickerModule.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/di/MediaPickerModule.kt new file mode 100644 index 0000000..0c8dabf --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/di/MediaPickerModule.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/MediaRetriever.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/MediaRetriever.kt new file mode 100644 index 0000000..6f2ce88 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/MediaRetriever.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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>> + +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/Album.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/Album.kt new file mode 100644 index 0000000..782dd2a --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/Album.kt @@ -0,0 +1,49 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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, + ) + } +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/AllowedMedia.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/AllowedMedia.kt new file mode 100644 index 0000000..8ad8fc4 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/AllowedMedia.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/Media.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/Media.kt new file mode 100644 index 0000000..7f93660 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/Media.kt @@ -0,0 +1,104 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.model.IntegerSize +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 mimeType: String, + val width: Int? = null, + val height: Int? = null, +) { + val fileSize: Long by lazy { uri.toUri().fileSize() ?: 0 } + + val humanFileSize: String by lazy { humanFileSize(fileSize) } + + val imageSize: IntegerSize? = IntegerSize(width ?: 0, height ?: 0) + .takeIf { it.width > 0 && it.height > 0 } + + 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, d MMMM" +const val EXTENDED_DATE_FORMAT = "EEE, d MMMM yyyy" \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/MediaItem.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/MediaItem.kt new file mode 100644 index 0000000..ce7be7f --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/MediaItem.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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" \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/MediaOrder.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/MediaOrder.kt new file mode 100644 index 0000000..43fda82 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/MediaOrder.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + } + } + } +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/MediaState.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/MediaState.kt new file mode 100644 index 0000000..c25d446 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/MediaState.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 error: String = "", + val isLoading: Boolean = true +) + +data class AlbumState( + val albums: List = emptyList(), + val error: String = "" +) \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/OrderType.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/OrderType.kt new file mode 100644 index 0000000..5aa58a4 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/OrderType.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() +} diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/MediaPickerActivity.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/MediaPickerActivity.kt new file mode 100644 index 0000000..5f45f1d --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/MediaPickerActivity.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/ManageExternalStorageWarning.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/ManageExternalStorageWarning.kt new file mode 100644 index 0000000..675226f --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/ManageExternalStorageWarning.kt @@ -0,0 +1,98 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.WarningAmber +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)) + } + } +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaExtensionHeader.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaExtensionHeader.kt new file mode 100644 index 0000000..a9cf097 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaExtensionHeader.kt @@ -0,0 +1,58 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaImage.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaImage.kt new file mode 100644 index 0000000..087eaee --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaImage.kt @@ -0,0 +1,241 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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 +import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt +import com.t8rin.imagetoolbox.core.resources.icons.CheckCircle +import com.t8rin.imagetoolbox.core.resources.icons.Error +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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 + .fillMaxSize() + .background( + takeColorFromScheme { isNightMode -> + errorContainer.copy( + if (isNightMode) 0.25f + else 1f + ).compositeOverSafe(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) + ) { + 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.Rounded.Error + } else { + Icons.Rounded.CheckCircle + }, + selectionIndex = selectionIndex, + modifier = Modifier + .clip(ShapeDefaults.circle) + .background( + transition.animateColor { + if (it) MaterialTheme.colorScheme.surfaceContainer + else Color.Transparent + }.value + ) + ) + } + } + } +} diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaImagePager.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaImagePager.kt new file mode 100644 index 0000000..39f3732 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaImagePager.kt @@ -0,0 +1,464 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.RectangleShape +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt +import com.t8rin.imagetoolbox.core.resources.icons.CheckCircle +import com.t8rin.imagetoolbox.core.resources.icons.Error +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +import com.t8rin.imagetoolbox.core.ui.theme.White +import com.t8rin.imagetoolbox.core.ui.theme.blend +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 + .fillMaxSize() + .background( + takeColorFromScheme { isNightMode -> + errorContainer.copy( + if (isNightMode) 0.25f + else 1f + ).compositeOverSafe(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.Rounded.Error + } else { + Icons.Rounded.CheckCircle + }, + addContainer = isChecked + ) + } + }, + navigationIcon = { + AnimatedVisibility(media.isNotEmpty()) { + EnhancedIconButton( + onClick = onDismiss + ) { + Icon( + imageVector = Icons.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 }, + imageSize = { currentMedia?.takeIf { it.isImage }?.imageSize } + ) + } + Spacer(Modifier.width(16.dp)) + HistogramChart( + model = currentMedia?.uri ?: Uri.EMPTY, + modifier = Modifier + .height(50.dp) + .width(90.dp), + bordersColor = MaterialTheme.colorScheme.primaryFixed.blend(White, 0.5f) + ) + } + } + } + + PredictiveBackObserver( + onProgress = { + predictiveBackProgress = it / 6f + }, + onClean = { isCompleted -> + if (isCompleted) { + onDismiss() + delay(400) + } + predictiveBackProgress = 0f + }, + enabled = visible + ) + } + } +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerGrid.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerGrid.kt new file mode 100644 index 0000000..ac7b9a5 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerGrid.kt @@ -0,0 +1,252 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.unit.dp +import com.t8rin.imagetoolbox.core.domain.utils.safeCast +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 gridState = rememberLazyGridState() + 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 = item.text, + 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) + } + } + } else null + ) + } + + 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 + ) +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerGridWithOverlays.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerGridWithOverlays.kt new file mode 100644 index 0000000..a8ce309 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerGridWithOverlays.kt @@ -0,0 +1,448 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.Deselect +import com.t8rin.imagetoolbox.core.resources.icons.Search +import com.t8rin.imagetoolbox.core.resources.icons.SearchOff +import com.t8rin.imagetoolbox.core.resources.icons.Verified +import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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.modifier.animateContentSizeNoClip +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.AlbumState +import com.t8rin.imagetoolbox.feature.media_picker.domain.model.AllowedMedia +import com.t8rin.imagetoolbox.feature.media_picker.domain.model.MediaState +import com.t8rin.imagetoolbox.feature.media_picker.presentation.screenLogic.MediaPickerComponent + +@Composable +internal fun MediaPickerGridWithOverlays( + component: MediaPickerComponent, + mediaState: MediaState, + albumsState: AlbumState, + isSearching: Boolean, + allowedMedia: AllowedMedia, + allowMultiple: Boolean, + onRequestManagePermission: () -> Unit, + isManagePermissionAllowed: Boolean, + selectedAlbumIndex: Long, + onSearchingChange: (Boolean) -> Unit, + onPicked: (List) -> Unit, + modifier: Modifier = Modifier +) { + 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.Outlined.Deselect, + contentDescription = stringResource(R.string.close) + ) + }, + modifier = Modifier.padding(bottom = 8.dp) + ) + } + BadgedBox( + badge = { + BoxAnimatedVisibility(selectedMedia.isNotEmpty() && allowMultiple) { + EnhancedBadge( + containerColor = MaterialTheme.colorScheme.primary + ) { + AnimatedContent( + targetState = selectedMedia.size, + transitionSpec = { + fadeIn() + scaleIn(initialScale = 0.85f) togetherWith fadeOut() + scaleOut( + targetScale = 0.85f + ) + }, + modifier = Modifier.animateContentSizeNoClip() + ) { size -> + Text( + text = size.toString(), + fontWeight = FontWeight.Bold + ) + } + } + } + } + ) { + EnhancedFloatingActionButton( + content = { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(horizontal = 16.dp) + ) { + Icon( + imageVector = Icons.Outlined.Verified, + 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 && mediaState.media.isEmpty()) || 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.Outlined.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.Rounded.BrokenImageAlt, + 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.Outlined.Search, + contentDescription = null + ) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerHavePermissions.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerHavePermissions.kt new file mode 100644 index 0000000..ad8263b --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerHavePermissions.kt @@ -0,0 +1,303 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.material3.Icon +import androidx.compose.material3.LinearWavyProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.KeyboardArrowDown +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.AlbumState +import com.t8rin.imagetoolbox.feature.media_picker.domain.model.AllowedMedia +import com.t8rin.imagetoolbox.feature.media_picker.domain.model.MediaState +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, + mediaState: MediaState, + albumsState: AlbumState +) { + var selectedAlbumIndex by rememberSaveable { mutableLongStateOf(-1) } + + var isSearching by rememberSaveable { + mutableStateOf(false) + } + + BackHandler(selectedAlbumIndex != -1L) { + selectedAlbumIndex = -1L + component.getAlbum(selectedAlbumIndex) + } + + BackHandler(isSearching) { + isSearching = false + } + + Scaffold( + topBar = { + val hasAlbums = albumsState.albums.size > 1 + val isRefreshing = mediaState.isLoading && mediaState.media.isNotEmpty() + + Column( + Modifier + .drawHorizontalStroke( + enabled = hasAlbums || isRefreshing + ) + .background(MaterialTheme.colorScheme.surfaceContainer) + ) { + AnimatedVisibility( + modifier = Modifier.fillMaxWidth(), + visible = hasAlbums + ) { + val layoutDirection = LocalLayoutDirection.current + var showAlbumThumbnail by rememberSaveable { + mutableStateOf(false) + } + val listState = rememberLazyListState() + Row { + 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) + ) + } + } + } + AnimatedVisibility( + visible = isRefreshing, + modifier = Modifier.fillMaxWidth() + ) { + LinearWavyProgressIndicator( + modifier = Modifier.padding(12.dp) + ) + } + } + }, + contentWindowInsets = WindowInsets() + ) { contentPadding -> + MediaPickerGridWithOverlays( + component = component, + isSearching = isSearching, + allowedMedia = allowedMedia, + allowMultiple = allowMultiple, + onRequestManagePermission = onRequestManagePermission, + isManagePermissionAllowed = isManagePermissionAllowed, + selectedAlbumIndex = selectedAlbumIndex, + onSearchingChange = { isSearching = it }, + onPicked = onPicked, + mediaState = mediaState, + albumsState = albumsState, + modifier = Modifier.padding(contentPadding) + ) + } +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerRootContent.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerRootContent.kt new file mode 100644 index 0000000..022ec99 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerRootContent.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerRootContentEmbeddable.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerRootContentEmbeddable.kt new file mode 100644 index 0000000..6fc0584 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerRootContentEmbeddable.kt @@ -0,0 +1,247 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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 androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.t8rin.imagetoolbox.core.domain.utils.tryAll +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +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 albumsState by component.albumsState.collectAsStateWithLifecycle() + val mediaState by component.mediaState.collectAsStateWithLifecycle() + + 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, + albumsState = albumsState, + mediaState = mediaState + ) + 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.Rounded.ArrowBack, + contentDescription = stringResource(R.string.close) + ) + } + }, + actions = { + TopAppBarEmoji() + }, + drawHorizontalStroke = albumsState.albums.size <= 1 && !(mediaState.isLoading && mediaState.media.isNotEmpty()) + ) + }, + content = content + ) + } + } +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaSizeFooter.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaSizeFooter.kt new file mode 100644 index 0000000..0f671f1 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaSizeFooter.kt @@ -0,0 +1,58 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaStickyHeader.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaStickyHeader.kt new file mode 100644 index 0000000..bda05fc --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaStickyHeader.kt @@ -0,0 +1,120 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.LocalIndication +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.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview +import com.t8rin.imagetoolbox.core.ui.theme.blend +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.modifier.ShapeDefaults +import com.t8rin.imagetoolbox.core.ui.widget.modifier.container +import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction + +@Composable +fun MediaStickyHeader( + modifier: Modifier = Modifier, + date: String, + isChecked: Boolean, + onChecked: (() -> Unit)? = null +) { + Row( + modifier = modifier + .padding( + start = 8.dp, + end = 12.dp, + top = 14.dp, + bottom = 14.dp + ) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + val interactionSource = remember { MutableInteractionSource() } + val shape = shapeByInteraction( + shape = ShapeDefaults.circle, + pressedShape = ShapeDefaults.pressed, + interactionSource = interactionSource + ) + + Text( + text = date, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .container( + shape = shape, + resultPadding = 0.dp, + color = MaterialTheme.colorScheme.surfaceContainer + .blend(MaterialTheme.colorScheme.primary, 0.1f) + .copy(0.5f) + ) + .hapticsCombinedClickable( + interactionSource = interactionSource, + indication = LocalIndication.current, + onLongClick = onChecked, + onClick = { + onChecked?.invoke() + } + ) + .padding( + vertical = 6.dp, + horizontal = 12.dp + ) + ) + AnimatedVisibility( + visible = onChecked != null, + enter = fadeIn(tween(150)), + exit = fadeOut(tween(150)) + ) { + MediaCheckBox( + isChecked = isChecked, + onCheck = { onChecked?.invoke() }, + modifier = Modifier.size(22.dp) + ) + } + } +} + +@Preview +@Composable +private fun Preview() = ImageToolboxThemeForPreview(true) { + MediaStickyHeader( + date = "Today", + isChecked = true, + onChecked = {} + ) +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/ObserveColorSchemeExtra.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/ObserveColorSchemeExtra.kt new file mode 100644 index 0000000..b627aab --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/ObserveColorSchemeExtra.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + } + } + } +} diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/SendMediaAsResult.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/SendMediaAsResult.kt new file mode 100644 index 0000000..c7ab811 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/SendMediaAsResult.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() +} \ No newline at end of file diff --git a/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/screenLogic/MediaPickerComponent.kt b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/screenLogic/MediaPickerComponent.kt new file mode 100644 index 0000000..362f3c1 --- /dev/null +++ b/feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/screenLogic/MediaPickerComponent.kt @@ -0,0 +1,233 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.getDate +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) + _filteredMediaState.emit(mediaState.value) + } + } + } + + private suspend fun MutableStateFlow.collectMedia( + data: List, + error: String + ) { + val mappedData = mutableListOf() + withContext(defaultDispatcher) { + val groupedData = data.groupBy { + it.timestamp.getDate() + } + groupedData.forEach { (date, data) -> + val dateHeader = MediaItem.Header("header_$date", date, data) + val groupedMedia = data.map { + MediaItem.MediaViewItem("media_${it.id}_${it.label}", it) + } + mappedData.add(dateHeader) + mappedData.addAll(groupedMedia) + } + } + withContext(uiDispatcher) { + tryEmit( + MediaState( + isLoading = false, + error = error, + media = data, + mappedMedia = mappedData, + ) + ) + } + } + + 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 + ) + } + } + } + + init { + runBlocking { + _settingsState.value = settingsManager.getSettingsState() + } + settingsManager.settingsState.onEach { + _settingsState.value = it + }.launchIn(componentScope) + } + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext + ): MediaPickerComponent + } + +} \ No newline at end of file diff --git a/feature/mesh-gradients/.gitignore b/feature/mesh-gradients/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/mesh-gradients/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/mesh-gradients/build.gradle.kts b/feature/mesh-gradients/build.gradle.kts new file mode 100644 index 0000000..0b268fb --- /dev/null +++ b/feature/mesh-gradients/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/mesh-gradients/src/main/AndroidManifest.xml b/feature/mesh-gradients/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d5fcf6a --- /dev/null +++ b/feature/mesh-gradients/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/mesh-gradients/src/main/java/com/t8rin/imagetoolbox/feature/mesh_gradients/presentation/MeshGradientsContent.kt b/feature/mesh-gradients/src/main/java/com/t8rin/imagetoolbox/feature/mesh_gradients/presentation/MeshGradientsContent.kt new file mode 100644 index 0000000..47e69f7 --- /dev/null +++ b/feature/mesh-gradients/src/main/java/com/t8rin/imagetoolbox/feature/mesh_gradients/presentation/MeshGradientsContent.kt @@ -0,0 +1,150 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.ArrowBack +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.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() + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/mesh-gradients/src/main/java/com/t8rin/imagetoolbox/feature/mesh_gradients/presentation/screenLogic/MeshGradientsComponent.kt b/feature/mesh-gradients/src/main/java/com/t8rin/imagetoolbox/feature/mesh_gradients/presentation/screenLogic/MeshGradientsComponent.kt new file mode 100644 index 0000000..59c4c7a --- /dev/null +++ b/feature/mesh-gradients/src/main/java/com/t8rin/imagetoolbox/feature/mesh_gradients/presentation/screenLogic/MeshGradientsComponent.kt @@ -0,0 +1,104 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + +} \ No newline at end of file diff --git a/feature/noise-generation/.gitignore b/feature/noise-generation/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/noise-generation/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/noise-generation/build.gradle.kts b/feature/noise-generation/build.gradle.kts new file mode 100644 index 0000000..937130a --- /dev/null +++ b/feature/noise-generation/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/AndroidManifest.xml b/feature/noise-generation/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1d2906e --- /dev/null +++ b/feature/noise-generation/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/data/AndroidNoiseGenerator.kt b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/data/AndroidNoiseGenerator.kt new file mode 100644 index 0000000..f90d81b --- /dev/null +++ b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/data/AndroidNoiseGenerator.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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() + } + +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/di/NoiseGenerationModule.kt b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/di/NoiseGenerationModule.kt new file mode 100644 index 0000000..2c4d20c --- /dev/null +++ b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/di/NoiseGenerationModule.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 + +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/NoiseGenerator.kt b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/NoiseGenerator.kt new file mode 100644 index 0000000..e1b73b8 --- /dev/null +++ b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/NoiseGenerator.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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? + +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/CellularDistanceFunction.kt b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/CellularDistanceFunction.kt new file mode 100644 index 0000000..613ccbb --- /dev/null +++ b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/CellularDistanceFunction.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/CellularReturnType.kt b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/CellularReturnType.kt new file mode 100644 index 0000000..309594d --- /dev/null +++ b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/CellularReturnType.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/DomainWarpType.kt b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/DomainWarpType.kt new file mode 100644 index 0000000..5a3dd9b --- /dev/null +++ b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/DomainWarpType.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/FractalType.kt b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/FractalType.kt new file mode 100644 index 0000000..b3fb11b --- /dev/null +++ b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/FractalType.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/NoiseParams.kt b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/NoiseParams.kt new file mode 100644 index 0000000..0ea1088 --- /dev/null +++ b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/NoiseParams.kt @@ -0,0 +1,58 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 + ) + } + } +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/NoiseType.kt b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/NoiseType.kt new file mode 100644 index 0000000..0f4511f --- /dev/null +++ b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/NoiseType.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/RotationType3D.kt b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/RotationType3D.kt new file mode 100644 index 0000000..39dacef --- /dev/null +++ b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/RotationType3D.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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 +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/presentation/NoiseGenerationContent.kt b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/presentation/NoiseGenerationContent.kt new file mode 100644 index 0000000..15d86b8 --- /dev/null +++ b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/presentation/NoiseGenerationContent.kt @@ -0,0 +1,225 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.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.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.helper.isPortraitOrientationAsState +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.UndoRedoButtons +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.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 isPortrait by isPortraitOrientationAsState() + + 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 + ) + } + + var showExitDialog by rememberSaveable { mutableStateOf(false) } + + AdaptiveLayoutScreen( + shouldDisableBackHandler = false, + title = { + Text( + text = stringResource(R.string.noise_generation), + textAlign = TextAlign.Center, + modifier = Modifier.marquee() + ) + }, + onGoBack = { + showExitDialog = true + }, + actions = { + if (!isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + shareButton() + if (isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + }, + 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 = { actions -> + var showFolderSelectionDialog by rememberSaveable { + mutableStateOf(false) + } + BottomButtonsBlock( + isNoData = false, + isSecondaryButtonVisible = false, + onSecondaryButtonClick = {}, + onPrimaryButtonClick = { + saveBitmap(null) + }, + onPrimaryButtonLongClick = { + showFolderSelectionDialog = true + }, + actions = { + if (isPortrait) actions() + } + ) + OneTimeSaveLocationSelectionDialog( + visible = showFolderSelectionDialog, + onDismiss = { showFolderSelectionDialog = false }, + onSaveRequest = saveBitmap, + formatForFilenameSelection = component.getFormatForFilenameSelection() + ) + }, + canShowScreenData = true + ) + + ExitWithoutSavingDialog( + onExit = component.onGoBack, + onDismiss = { showExitDialog = false }, + visible = showExitDialog + ) + + LoadingDialog( + visible = component.isSaving, + onCancelLoading = component::cancelSaving + ) +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/presentation/components/NoiseParamsSelection.kt b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/presentation/components/NoiseParamsSelection.kt new file mode 100644 index 0000000..ccad799 --- /dev/null +++ b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/presentation/components/NoiseParamsSelection.kt @@ -0,0 +1,279 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.padding +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.colors.util.roundToTwoDigits +import com.t8rin.imagetoolbox.core.domain.utils.roundTo +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Build +import com.t8rin.imagetoolbox.core.resources.icons.RampLeft +import com.t8rin.imagetoolbox.core.resources.icons.SettingsEthernet +import com.t8rin.imagetoolbox.core.resources.icons.Waves +import com.t8rin.imagetoolbox.core.ui.utils.provider.ProvideContainerDefaults +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.core.ui.widget.modifier.container +import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem +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( + modifier = Modifier.container( + shape = ShapeDefaults.large, + resultPadding = 12.dp + ), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + TitleItem( + text = stringResource(R.string.params), + icon = Icons.Rounded.Build, + modifier = Modifier.padding(bottom = 8.dp) + ) + ProvideContainerDefaults( + color = MaterialTheme.colorScheme.surface + ) { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + EnhancedSliderItem( + value = value.seed, + icon = Icons.Rounded.SettingsEthernet, + title = stringResource(R.string.seed), + valueRange = -10000f..10000f, + internalStateTransformation = { + it.roundToInt() + }, + onValueChange = { + onValueChange(value.copy(seed = it.toInt())) + }, + shape = ShapeDefaults.top + ) + EnhancedSliderItem( + value = value.frequency, + icon = Icons.Rounded.Waves, + title = stringResource(R.string.frequency), + valueRange = -0.5f..0.5f, + internalStateTransformation = { + it.roundTo(3) + }, + onValueChange = { + onValueChange(value.copy(frequency = it)) + }, + shape = ShapeDefaults.center + ) + 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, + shape = ShapeDefaults.center + ) + 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, + shape = ShapeDefaults.center + ) + AnimatedVisibility(value.fractalType != FractalType.None) { + Column( + verticalArrangement = Arrangement.spacedBy(4.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.center + ) + EnhancedSliderItem( + value = value.fractalLacunarity, + title = stringResource(R.string.lacunarity), + valueRange = -50f..50f, + internalStateTransformation = { + it.roundToTwoDigits() + }, + onValueChange = { + onValueChange(value.copy(fractalLacunarity = it)) + }, + shape = ShapeDefaults.center + ) + EnhancedSliderItem( + value = value.fractalGain, + title = stringResource(R.string.gain), + valueRange = -10f..10f, + internalStateTransformation = { + it.roundToTwoDigits() + }, + onValueChange = { + onValueChange(value.copy(fractalGain = it)) + }, + shape = ShapeDefaults.center + ) + EnhancedSliderItem( + value = value.fractalWeightedStrength, + title = stringResource(R.string.weighted_strength), + valueRange = -3f..3f, + internalStateTransformation = { + it.roundToTwoDigits() + }, + onValueChange = { + onValueChange(value.copy(fractalWeightedStrength = it)) + }, + shape = ShapeDefaults.center + ) + 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.center + ) + } + AnimatedVisibility(value.noiseType == NoiseType.Cellular) { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + 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 = 1, + containerColor = Color.Unspecified, + shape = ShapeDefaults.center + ) + 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, + shape = ShapeDefaults.center + ) + EnhancedSliderItem( + value = value.cellularJitter, + title = stringResource(R.string.jitter), + valueRange = -10f..10f, + internalStateTransformation = { + it.roundToTwoDigits() + }, + onValueChange = { + onValueChange(value.copy(cellularJitter = it)) + }, + shape = ShapeDefaults.center + ) + } + } + } + } + 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 = 1, + containerColor = Color.Unspecified, + shape = ShapeDefaults.center + ) + EnhancedSliderItem( + value = value.domainWarpAmp, + icon = Icons.Rounded.RampLeft, + title = stringResource(R.string.amplitude), + valueRange = -2000f..2000f, + internalStateTransformation = { + it.roundToTwoDigits() + }, + onValueChange = { + onValueChange(value.copy(domainWarpAmp = it)) + }, + shape = ShapeDefaults.bottom + ) + } + } + } +} \ No newline at end of file diff --git a/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/presentation/screenLogic/NoiseGenerationComponent.kt b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/presentation/screenLogic/NoiseGenerationComponent.kt new file mode 100644 index 0000000..2a4ad26 --- /dev/null +++ b/feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/presentation/screenLogic/NoiseGenerationComponent.kt @@ -0,0 +1,309 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations 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.ColorModel +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.settings.domain.SettingsManager +import com.t8rin.imagetoolbox.core.ui.utils.BaseHistoryComponent +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 com.t8rin.imagetoolbox.noise_generation.presentation.screenLogic.NoiseGenerationComponent.HistorySnapshot +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, + private val settingsManager: SettingsManager, +) : BaseHistoryComponent( + dispatchersHolder = dispatchersHolder, + componentContext = componentContext +) { + + 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 + + init { + resetHistory() + updatePreview() + } + + 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) { + if (_imageFormat.value != imageFormat) { + if (pendingHistoryMode != PendingHistoryMode.FormatChange) { + finalizePendingHistoryTransaction() + } + beginPendingHistoryTransaction( + mode = PendingHistoryMode.FormatChange, + commitDelayMillis = formatHistoryTransactionDebounce + ) + _imageFormat.update { imageFormat } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setQuality(quality: Quality) { + if (_quality.value != quality) { + beginPendingHistoryTransaction() + _quality.update { quality } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun updateParams(params: NoiseParams) { + if (_noiseParams.value != params) { + beginPendingHistoryTransaction() + _noiseParams.update( + onValueChanged = ::updatePreview, + transform = { params } + ) + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setNoiseWidth(width: Int) { + val coercedWidth = width.coerceAtMost(8192) + if (_noiseSize.value.width != coercedWidth) { + beginPendingHistoryTransaction() + _noiseSize.update( + onValueChanged = ::updatePreview, + transform = { it.copy(width = coercedWidth) } + ) + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setNoiseHeight(height: Int) { + val coercedHeight = height.coerceAtMost(8192) + if (_noiseSize.value.height != coercedHeight) { + beginPendingHistoryTransaction() + _noiseSize.update( + onValueChanged = ::updatePreview, + transform = { it.copy(height = coercedHeight) } + ) + registerChanges() + schedulePendingHistoryCommit() + } + } + + 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 + + override fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( + noiseParams = noiseParams, + noiseSize = noiseSize, + imageFormat = imageFormat, + quality = quality, + backgroundColorForNoAlphaFormats = settingsManager + .settingsState + .value + .backgroundForNoAlphaImageFormats + ) + + override fun applyHistorySnapshot(snapshot: HistorySnapshot) { + _noiseParams.update { snapshot.noiseParams } + _noiseSize.update { snapshot.noiseSize } + _imageFormat.update { snapshot.imageFormat } + _quality.update { snapshot.quality } + restoreBackgroundColorForNoAlphaFormats( + settingsManager = settingsManager, + backgroundColorForNoAlphaFormats = snapshot.backgroundColorForNoAlphaFormats + ) + updatePreview() + } + + data class HistorySnapshot( + val noiseParams: NoiseParams = NoiseParams.Default, + val noiseSize: IntegerSize = IntegerSize(1000, 1000), + val imageFormat: ImageFormat = ImageFormat.Default, + val quality: Quality = Quality.Base(100), + val backgroundColorForNoAlphaFormats: ColorModel = ColorModel(-0x1000000) + ) + + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): NoiseGenerationComponent + } + +} \ No newline at end of file diff --git a/feature/palette-tools/.gitignore b/feature/palette-tools/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/palette-tools/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/palette-tools/build.gradle.kts b/feature/palette-tools/build.gradle.kts new file mode 100644 index 0000000..1f86ef6 --- /dev/null +++ b/feature/palette-tools/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/palette-tools/src/main/AndroidManifest.xml b/feature/palette-tools/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/palette-tools/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/PaletteToolsContent.kt b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/PaletteToolsContent.kt new file mode 100644 index 0000000..e387a6a --- /dev/null +++ b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/PaletteToolsContent.kt @@ -0,0 +1,399 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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.FileOpen +import com.t8rin.imagetoolbox.core.resources.icons.Palette +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) { + !component.palette.isNotEmpty() + } 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 + ) +} \ No newline at end of file diff --git a/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/DefaultPaletteControls.kt b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/DefaultPaletteControls.kt new file mode 100644 index 0000000..4ea62a9 --- /dev/null +++ b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/DefaultPaletteControls.kt @@ -0,0 +1,111 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.material3.MaterialTheme +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.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.colors.parser.ColorWithName +import com.t8rin.colors.rememberImageColorPaletteState +import com.t8rin.imagetoolbox.core.resources.Icons +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.widget.dialogs.ColorCopyFormatSelectionDialog +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) } + var colorCopyTarget by remember { + mutableStateOf(null) + } + + 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.Rounded.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 = { + colorCopyTarget = ColorWithName( + color = it.color, + name = it.name + ) + } + ) + + ColorCopyFormatSelectionDialog( + target = colorCopyTarget, + onDismiss = { colorCopyTarget = null } + ) +} \ No newline at end of file diff --git a/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/EditPaletteControls.kt b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/EditPaletteControls.kt new file mode 100644 index 0000000..f147c85 --- /dev/null +++ b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/EditPaletteControls.kt @@ -0,0 +1,304 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.AddCircle +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.File +import com.t8rin.imagetoolbox.core.resources.icons.Palette +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.Rounded.File, + 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.AddCircle, + 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)) +} diff --git a/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/ImageColorPalette.kt b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/ImageColorPalette.kt new file mode 100644 index 0000000..2245d2f --- /dev/null +++ b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/ImageColorPalette.kt @@ -0,0 +1,134 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPalette.kt b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPalette.kt new file mode 100644 index 0000000..f154d78 --- /dev/null +++ b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPalette.kt @@ -0,0 +1,191 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.colors.parser.ColorWithName +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.Icons +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.dialogs.ColorCopyFormatSelectionDialog +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 + var colorCopyTarget by remember { + mutableStateOf(null) + } + + val themeState = LocalDynamicThemeState.current + LaunchedEffect(colorScheme.primary) { + delay(200L) + themeState.updateColorTuple( + ColorTuple( + primary = colorScheme.primary, + secondary = colorScheme.secondary, + tertiary = colorScheme.tertiary, + surface = colorScheme.surface, + neutralVariant = colorScheme.surfaceVariant, + error = colorScheme.error + ) + ) + } + + MaterialYouPaletteGroup( + colorScheme = colorScheme, + onCopy = { color, name -> + colorCopyTarget = ColorWithName( + color = color, + name = name + ) + } + ) + + 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)) + } + + ColorCopyFormatSelectionDialog( + visible = colorCopyTarget != null, + onDismiss = { colorCopyTarget = null }, + color = colorCopyTarget?.color ?: Color.Transparent, + colorName = colorCopyTarget?.name.orEmpty() + ) +} + +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()})" \ No newline at end of file diff --git a/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPaletteControls.kt b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPaletteControls.kt new file mode 100644 index 0000000..6e06e45 --- /dev/null +++ b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPaletteControls.kt @@ -0,0 +1,273 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.Contrast +import com.t8rin.imagetoolbox.core.resources.icons.DarkMode +import com.t8rin.imagetoolbox.core.resources.icons.InvertColors +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.icons.Palette +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)) + } + } + ) +} \ No newline at end of file diff --git a/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPaletteGroup.kt b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPaletteGroup.kt new file mode 100644 index 0000000..3a1b1a8 --- /dev/null +++ b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPaletteGroup.kt @@ -0,0 +1,318 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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, String) -> 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) + ) + } + } +} \ No newline at end of file diff --git a/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPaletteItem.kt b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPaletteItem.kt new file mode 100644 index 0000000..d180d8e --- /dev/null +++ b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPaletteItem.kt @@ -0,0 +1,136 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.resources.utils.animation.animateColorAsState +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, String) -> 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, name) + } + .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 +} \ No newline at end of file diff --git a/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteColorNameField.kt b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteColorNameField.kt new file mode 100644 index 0000000..5c6e74f --- /dev/null +++ b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteColorNameField.kt @@ -0,0 +1,115 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } + } +} \ No newline at end of file diff --git a/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteColorsCountSelector.kt b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteColorsCountSelector.kt new file mode 100644 index 0000000..c2d9b51 --- /dev/null +++ b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteColorsCountSelector.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Palette +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 + ) +} \ No newline at end of file diff --git a/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteToolsScreenControls.kt b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteToolsScreenControls.kt new file mode 100644 index 0000000..4fe4e57 --- /dev/null +++ b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteToolsScreenControls.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } + } + } +} \ No newline at end of file diff --git a/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteType.kt b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteType.kt new file mode 100644 index 0000000..e2d2907 --- /dev/null +++ b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteType.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/model/NamedColor.kt b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/model/NamedColor.kt new file mode 100644 index 0000000..fc176d7 --- /dev/null +++ b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/model/NamedColor.kt @@ -0,0 +1,84 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/model/PaletteFormatHelper.kt b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/model/PaletteFormatHelper.kt new file mode 100644 index 0000000..b4f3bbc --- /dev/null +++ b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/model/PaletteFormatHelper.kt @@ -0,0 +1,42 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } +} \ No newline at end of file diff --git a/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/screenLogic/PaletteToolsComponent.kt b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/screenLogic/PaletteToolsComponent.kt new file mode 100644 index 0000000..78a0237 --- /dev/null +++ b/feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/screenLogic/PaletteToolsComponent.kt @@ -0,0 +1,217 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.appContext +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.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 entries = + PaletteFormatHelper.entriesFor(uri.filename() ?: uri.toString()) + + for (format in entries) { + format.getCoder().use { + decode( + uri = uri, + context = appContext + ) + }.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 = { + format.getCoder().encode( + palette = palette.toPalette(), + output = it.outputStream() + ) + } + ).also(::parseFileSaveResult).onSuccess(::registerSave) + _isSaving.value = false + } + } + + fun sharePalette() { + val format = paletteFormat ?: PaletteFormatHelper.entries.first() + + savingJob = trackProgress { + _isSaving.value = true + + val uri = shareProvider.cacheData( + writeData = { + format.getCoder().encode( + palette = palette.toPalette(), + output = it.outputStream() + ) + }, + filename = createPaletteFilename() + ) + + if (uri == null) { + _isSaving.value = false + return@trackProgress + } + + shareProvider.shareUri( + uri = uri, + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/.gitignore b/feature/pdf-tools/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/pdf-tools/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/pdf-tools/build.gradle.kts b/feature/pdf-tools/build.gradle.kts new file mode 100644 index 0000000..532ade6 --- /dev/null +++ b/feature/pdf-tools/build.gradle.kts @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/AndroidManifest.xml b/feature/pdf-tools/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/pdf-tools/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/AndroidPdfHelper.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/AndroidPdfHelper.kt new file mode 100644 index 0000000..7648143 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/AndroidPdfHelper.kt @@ -0,0 +1,469 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 android.graphics.Color.WHITE +import androidx.core.graphics.createBitmap +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.resources.R +import com.t8rin.imagetoolbox.core.utils.filename +import com.t8rin.imagetoolbox.core.utils.getString +import com.t8rin.imagetoolbox.core.utils.makeLog +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.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.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.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 com.tom_roush.pdfbox.pdmodel.font.PDFont +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.io.deleteRecursively +import kotlin.io.outputStream +import kotlin.math.max +import kotlin.math.roundToInt +import kotlin.random.Random +import kotlin.use +import android.graphics.Matrix as AndroidMatrix +import android.graphics.pdf.PdfRenderer as AndroidPdfRenderer + +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 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") + } + + 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 + ) + + inline fun useAndroidPdfRenderer( + uri: String, + action: (AndroidPdfRenderer) -> T + ): T = context.contentResolver.openFileDescriptor( + uri.toUri(), + "r" + )?.use { fileDescriptor -> + AndroidPdfRenderer(fileDescriptor).use(action) + } ?: error(getString(R.string.something_went_wrong)) + + fun AndroidPdfRenderer.safeRenderDpi( + pageIndex: Int, + dpi: Float + ): Bitmap { + val scale = dpi / 72f + + return try { + renderPage(pageIndex, scale) + } catch (t1: Throwable) { + t1.makeLog("safeRenderDpi") + System.gc() + try { + renderPage(pageIndex, 1f) + } catch (t2: Throwable) { + t2.makeLog("safeRenderDpi") + System.gc() + renderPage(pageIndex, 0.5f) + } + } finally { + System.gc() + } + } + + private fun AndroidPdfRenderer.renderPage( + pageIndex: Int, + scale: Float + ): Bitmap = openPage(pageIndex).use { page -> + createBitmap( + (page.width * scale).toInt().coerceAtLeast(1), + (page.height * scale).toInt().coerceAtLeast(1) + ).apply { + eraseColor(WHITE) + page.render( + this, + null, + AndroidMatrix().apply { setScale(scale, scale) }, + AndroidPdfRenderer.Page.RENDER_MODE_FOR_DISPLAY + ) + } + } + + val AndroidPdfRenderer.pageIndices: List + get() = List(pageCount) { it } + + fun List?.orAll(renderer: AndroidPdfRenderer) = orEmpty().ifEmpty { renderer.pageIndices } + + 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(font: PDFont): String { + return replace(PDF_CONTROL_REGEX, "") + .take(2000) + .mapNotNull { char -> + char.takeIf { + font.hasGlyph(it) + } + } + .joinToString("") + } + + private fun PDFont.hasGlyph(char: Char): Boolean { + return runCatching { + encode(char.toString()) + }.isSuccess + } + + 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") + } + } + +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/AndroidPdfManager.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/AndroidPdfManager.kt new file mode 100644 index 0000000..1d38380 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/AndroidPdfManager.kt @@ -0,0 +1,1018 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.makeLog +import com.t8rin.imagetoolbox.core.utils.putEntry +import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.BaseMemoryConfig +import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.HocrWord +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.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 { + useAndroidPdfRenderer(uri) { renderer -> + params.pages.orAll(renderer).also { + send(ExtractPagesAction.PagesCount(it.size)) + }.forEach { pageIndex -> + send( + ExtractPagesAction.Progress( + index = pageIndex, + image = renderer.safeRenderDpi( + pageIndex = pageIndex, + dpi = dpi + ) + ) + ) + } + } + } + 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 font = document.defaultFont + + 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(font) + 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 = (font.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(font, 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(BaseMemoryConfig) + } + } + } + + 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 fontDescriptor = font.fontDescriptor + 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 glyphWidthEm = (font.getStringWidth(text) / 1000f).coerceAtLeast(0.001f) + val fontSize = pageWidth * params.fontSize / 100f / glyphWidthEm + val textWidth = glyphWidthEm * fontSize + val textAscent = fontDescriptor.ascent / 1000f * fontSize + val textDescent = fontDescriptor.descent / 1000f * fontSize + val textVerticalCenter = + (fontDescriptor.ascent + fontDescriptor.descent) / 2000f * fontSize + + 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.TopLeft, + Position.TopCenter, + Position.TopRight -> baseY - textAscent + + Position.CenterLeft, + Position.Center, + Position.CenterRight -> baseY - textVerticalCenter + + Position.BottomLeft, + Position.BottomCenter, + Position.BottomRight -> baseY - textDescent + } + + val adjustedXWithOrigin = adjustedX + originX + val adjustedYWithOrigin = adjustedY + originY + + document.writePage(page) { + beginText() + setFont(font, fontSize) + 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 + val fontDescriptor = font.fontDescriptor + + params.pages.orAll(document).forEach { pageIndex -> + val page = document.getPageSafe(pageIndex) + val text = params.text + + if (text.isBlank()) return@forEach + + val radians = Math.toRadians(-params.rotation.toDouble()) + val cropBox = page.cropBox + val glyphWidthEm = (font.getStringWidth(text) / 1000f).coerceAtLeast(0.001f) + val fontSize = cropBox.width * params.fontSize / 100f / glyphWidthEm + + val textWidth = + glyphWidthEm * fontSize + val textVerticalCenter = + (fontDescriptor.ascent + fontDescriptor.descent) / 2000f * fontSize + + 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, fontSize) + setColor(color.copy(params.opacity)) + setTextMatrix(matrix) + newLineAtOffset(-textWidth / 2f, -textVerticalCenter) + showText(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 * 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) { + saveGraphicsState() + setAlpha(params.opacity) + drawImage(signatureImage, x, y, targetWidth, targetHeight) + 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 { + useAndroidPdfRenderer(uri) { renderer -> + renderer.pageIndices.mapNotNull { pageIndex -> + val bitmap = renderer.safeRenderDpi( + pageIndex = pageIndex, + dpi = 72f + ) + + shareProvider.cacheImage( + image = bitmap, + imageInfo = ImageInfo( + width = bitmap.width, + height = bitmap.height, + imageFormat = ImageFormat.Png.Lossless + ) + ) + } + } + } + + 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 -> + useAndroidPdfRenderer(uri) { renderer -> + createPdf { newDoc -> + document.pages.forEachIndexed { index, page -> + val cropBox = page.cropBox + + val pdImage = renderer + .safeRenderDpi(index, dpi) + .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 -> + useAndroidPdfRenderer(uri) { renderer -> + 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 = renderer + .safeRenderDpi(pageIndex, dpi) + .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 + } + } + +} diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/Hocr.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/Hocr.kt new file mode 100644 index 0000000..da98a5f --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/Hocr.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/PdfContentStreamEditor.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/PdfContentStreamEditor.kt new file mode 100644 index 0000000..a480cca --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/PdfContentStreamEditor.kt @@ -0,0 +1,228 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/PdfRenderer.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/PdfRenderer.kt new file mode 100644 index 0000000..449a12f --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/PdfRenderer.kt @@ -0,0 +1,271 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.graphics.Color.WHITE +import android.graphics.pdf.LoadParams +import android.graphics.pdf.PdfRendererPreV +import android.graphics.pdf.RenderParams +import android.os.Build +import android.os.ext.SdkExtensions +import androidx.annotation.ChecksSdkIntAtLeast +import androidx.annotation.RequiresExtension +import androidx.core.graphics.createBitmap +import androidx.core.net.toUri +import com.t8rin.imagetoolbox.core.domain.model.IntegerSize +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.utils.appContext +import com.t8rin.imagetoolbox.core.utils.getString +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 +import android.graphics.Matrix as AndroidMatrix +import android.graphics.pdf.PdfRenderer as AndroidPdfRenderer + +class PdfRenderer private constructor( + private val backend: Backend +) : AutoCloseable { + + val pageCount: Int get() = backend.pageCount + + fun openPage(index: Int): Page = backend.openPage(index) + + fun renderImage( + pageIndex: Int, + scale: Float + ): Bitmap = backend.renderImage(pageIndex, scale) + + override fun close() = backend.close() + + class Page( + val width: Int, + val height: Int + ) { + val size = IntegerSize(width, height) + } + + private sealed interface Backend : AutoCloseable { + val pageCount: Int + + fun openPage(index: Int): Page + + fun renderImage( + pageIndex: Int, + scale: Float + ): Bitmap + } + + private class AndroidBackend( + private val renderer: AndroidPdfRenderer + ) : Backend { + override val pageCount: Int get() = renderer.pageCount + + override fun openPage(index: Int): Page = renderer.openPage(index).use { page -> + Page( + width = page.width, + height = page.height + ) + } + + override fun renderImage( + pageIndex: Int, + scale: Float + ): Bitmap = renderer.openPage(pageIndex).use { page -> + createBitmap( + (page.width * scale).toInt().coerceAtLeast(1), + (page.height * scale).toInt().coerceAtLeast(1) + ).apply { + eraseColor(WHITE) + page.render( + this, + null, + AndroidMatrix().apply { setScale(scale, scale) }, + AndroidPdfRenderer.Page.RENDER_MODE_FOR_DISPLAY + ) + } + } + + override fun close() = renderer.close() + } + + @RequiresExtension(extension = Build.VERSION_CODES.S, version = 13) + private class AndroidPreVBackend( + private val renderer: PdfRendererPreV + ) : Backend { + private val renderParams = RenderParams.Builder(RenderParams.RENDER_MODE_FOR_DISPLAY) + .build() + + override val pageCount: Int get() = renderer.pageCount + + override fun openPage(index: Int): Page = renderer.openPage(index).use { page -> + Page( + width = page.width, + height = page.height + ) + } + + override fun renderImage( + pageIndex: Int, + scale: Float + ): Bitmap = renderer.openPage(pageIndex).use { page -> + createBitmap( + (page.width * scale).toInt().coerceAtLeast(1), + (page.height * scale).toInt().coerceAtLeast(1) + ).apply { + eraseColor(WHITE) + page.render( + this, + null, + AndroidMatrix().apply { setScale(scale, scale) }, + renderParams + ) + } + } + + override fun close() = renderer.close() + } + + private class ApacheBackend( + val document: PDDocument + ) : Backend { + private val renderer = PDFRenderer(document) + + override val pageCount: Int get() = document.numberOfPages + + override fun openPage(index: Int): Page = document.getPage(index).let { page -> + page.cropBox.run { + Page( + width = width.roundToInt(), + height = height.roundToInt() + ) + } + } + + override fun renderImage( + pageIndex: Int, + scale: Float + ): Bitmap = renderer.renderImage(pageIndex, scale) + + override fun close() = document.close() + } + + companion object { + fun create( + uri: String, + password: String? + ): PdfRenderer = runCatching { + createAndroidRenderer(uri, password) + }.getOrElse { + PdfRenderer( + ApacheBackend( + safeOpenPdf( + uri = uri, + password = password + ) + ) + ) + } + + private fun createAndroidRenderer( + uri: String, + password: String? + ): PdfRenderer { + val passwordOrNull = password?.takeIf { it.isNotEmpty() } + + return if (passwordOrNull == null) { + createAndroidRenderer(uri) + } else if (canUseNewPdfFully()) { + createAndroidPreVRenderer(uri, passwordOrNull) + } else { + error(getString(R.string.something_went_wrong)) + } + } + + private fun createAndroidRenderer(uri: String): PdfRenderer { + if (!canUseNewPdf()) error(getString(R.string.something_went_wrong)) + + val descriptor = appContext.contentResolver.openFileDescriptor( + uri.toUri(), + "r" + ) ?: error(getString(R.string.something_went_wrong)) + + return try { + PdfRenderer(AndroidBackend(AndroidPdfRenderer(descriptor))) + } catch (throwable: Throwable) { + descriptor.close() + throw throwable + } + } + + @RequiresExtension(extension = Build.VERSION_CODES.S, version = 13) + private fun createAndroidPreVRenderer( + uri: String, + password: String + ): PdfRenderer { + val descriptor = appContext.contentResolver.openFileDescriptor( + uri.toUri(), + "r" + ) ?: error(getString(R.string.something_went_wrong)) + + return try { + PdfRenderer( + AndroidPreVBackend( + PdfRendererPreV( + descriptor, + LoadParams.Builder() + .setPassword(password) + .build() + ) + ) + ) + } catch (throwable: Throwable) { + descriptor.close() + throw throwable + } + } + } +} + +fun PdfRenderer( + uri: String, + password: String? = null, + onFailure: (Throwable) -> Unit = {}, + onPasswordRequest: (() -> Unit)? = null +): PdfRenderer? = runCatching { + PdfRenderer.create( + uri = uri, + password = password + ) +}.onFailure { throwable -> + when (throwable) { + is InvalidPasswordException -> onPasswordRequest?.invoke() ?: onFailure(throwable) + else -> onFailure(throwable) + } +}.getOrNull() + + +@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.P) +fun canUseNewPdf(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P + +@ChecksSdkIntAtLeast(api = 13, extension = Build.VERSION_CODES.S) +fun canUseNewPdfFully(): 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 \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/PdfUtils.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/PdfUtils.kt new file mode 100644 index 0000000..0c23672 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/PdfUtils.kt @@ -0,0 +1,290 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.core.utils.makeLog +import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfMetadata +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 + +private const val PDF_MAIN_MEMORY_LIMIT_BYTES = 16L * 1024L * 1024L + +internal val BaseMemoryConfig = MemoryUsageSetting.setupMixed(PDF_MAIN_MEMORY_LIMIT_BYTES) + +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(), + BaseMemoryConfig, + ) + } 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(BaseMemoryConfig).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 +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/di/PdfToolsModule.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/di/PdfToolsModule.kt new file mode 100644 index 0000000..221f38c --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/di/PdfToolsModule.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/PdfHelper.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/PdfHelper.kt new file mode 100644 index 0000000..90b110e --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/PdfHelper.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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 + +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/PdfManager.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/PdfManager.kt new file mode 100644 index 0000000..c1963b7 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/PdfManager.kt @@ -0,0 +1,162 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/ExtractPagesAction.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/ExtractPagesAction.kt new file mode 100644 index 0000000..5461f70 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/ExtractPagesAction.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PageOrientation.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PageOrientation.kt new file mode 100644 index 0000000..a191de2 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PageOrientation.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PageSize.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PageSize.kt new file mode 100644 index 0000000..8c906c0 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PageSize.kt @@ -0,0 +1,144 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfAnnotationType.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfAnnotationType.kt new file mode 100644 index 0000000..1c08f46 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfAnnotationType.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + } + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfCheckResult.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfCheckResult.kt new file mode 100644 index 0000000..300b918 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfCheckResult.kt @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfCreationParams.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfCreationParams.kt new file mode 100644 index 0000000..2eb8b38 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfCreationParams.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfCropParams.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfCropParams.kt new file mode 100644 index 0000000..0a5ee56 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfCropParams.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) +) \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfExtractPagesParams.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfExtractPagesParams.kt new file mode 100644 index 0000000..c436fe0 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfExtractPagesParams.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfMetadata.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfMetadata.kt new file mode 100644 index 0000000..bdfad19 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfMetadata.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfPageNumbersParams.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfPageNumbersParams.kt new file mode 100644 index 0000000..0b7f68a --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfPageNumbersParams.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 fontSize: Float = 32f, + val color: Int = -7829368 +) \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfRemoveAnnotationParams.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfRemoveAnnotationParams.kt new file mode 100644 index 0000000..aaabb49 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfRemoveAnnotationParams.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) +) \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfSignatureParams.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfSignatureParams.kt new file mode 100644 index 0000000..a411355 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfSignatureParams.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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" +) \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfWatermarkParams.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfWatermarkParams.kt new file mode 100644 index 0000000..8f90dd6 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfWatermarkParams.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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", +) \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PrintPdfParams.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PrintPdfParams.kt new file mode 100644 index 0000000..a55617c --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PrintPdfParams.kt @@ -0,0 +1,64 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() } + } + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/SearchablePdfPage.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/SearchablePdfPage.kt new file mode 100644 index 0000000..ec64e7f --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/SearchablePdfPage.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/BasePdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/BasePdfToolComponent.kt new file mode 100644 index 0000000..34746a0 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/BasePdfToolComponent.kt @@ -0,0 +1,173 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.core.utils.makeLog +import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager +import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfCheckResult +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 + } + } + +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/BasePdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/BasePdfToolContent.kt new file mode 100644 index 0000000..8a8d1d6 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/BasePdfToolContent.kt @@ -0,0 +1,252 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.FileOpen +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() + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/PageSwitcher.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/PageSwitcher.kt new file mode 100644 index 0000000..388e03b --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/PageSwitcher.kt @@ -0,0 +1,133 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.VisibilityOff +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) + ) + } + } + } + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/PdfPreviewItem.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/PdfPreviewItem.kt new file mode 100644 index 0000000..b6a68c2 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/PdfPreviewItem.kt @@ -0,0 +1,122 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) + } + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/PdfTextStyle.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/PdfTextStyle.kt new file mode 100644 index 0000000..d7d60a2 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/PdfTextStyle.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import com.t8rin.imagetoolbox.core.resources.R + +val PdfTextStyle = TextStyle( + fontFamily = FontFamily( + Font(R.raw.roboto_bold) + ), + fontWeight = FontWeight.Bold +) \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/compress/CompressPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/compress/CompressPdfToolContent.kt new file mode 100644 index 0000000..155377e --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/compress/CompressPdfToolContent.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/compress/screenLogic/CompressPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/compress/screenLogic/CompressPdfToolComponent.kt new file mode 100644 index 0000000..72ef346 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/compress/screenLogic/CompressPdfToolComponent.kt @@ -0,0 +1,150 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/crop/CropPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/crop/CropPdfToolContent.kt new file mode 100644 index 0000000..341aad3 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/crop/CropPdfToolContent.kt @@ -0,0 +1,133 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.BorderHorizontal +import com.t8rin.imagetoolbox.core.resources.icons.BorderVertical +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) + } + ) +} diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/crop/components/CropPreview.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/crop/components/CropPreview.kt new file mode 100644 index 0000000..faf7361 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/crop/components/CropPreview.kt @@ -0,0 +1,216 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ), + ) + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/crop/screenLogic/CropPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/crop/screenLogic/CropPdfToolComponent.kt new file mode 100644 index 0000000..fa96f8c --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/crop/screenLogic/CropPdfToolComponent.kt @@ -0,0 +1,173 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_images/ExtractImagesPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_images/ExtractImagesPdfToolContent.kt new file mode 100644 index 0000000..ec6e602 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_images/ExtractImagesPdfToolContent.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + ) +} diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_images/screenLogic/ExtractImagesPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_images/screenLogic/ExtractImagesPdfToolComponent.kt new file mode 100644 index 0000000..e8d4b57 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_images/screenLogic/ExtractImagesPdfToolComponent.kt @@ -0,0 +1,152 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_pages/ExtractPagesPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_pages/ExtractPagesPdfToolContent.kt new file mode 100644 index 0000000..905c7b4 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_pages/ExtractPagesPdfToolContent.kt @@ -0,0 +1,251 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.Close +import com.t8rin.imagetoolbox.core.resources.icons.Image +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 + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_pages/screenLogic/ExtractPagesPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_pages/screenLogic/ExtractPagesPdfToolComponent.kt new file mode 100644 index 0000000..731cac3 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_pages/screenLogic/ExtractPagesPdfToolComponent.kt @@ -0,0 +1,292 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/flatten/FlattenPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/flatten/FlattenPdfToolContent.kt new file mode 100644 index 0000000..89aad7c --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/flatten/FlattenPdfToolContent.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/flatten/screenLogic/FlattenPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/flatten/screenLogic/FlattenPdfToolComponent.kt new file mode 100644 index 0000000..3e5a4b1 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/flatten/screenLogic/FlattenPdfToolComponent.kt @@ -0,0 +1,150 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/grayscale/GrayscalePdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/grayscale/GrayscalePdfToolContent.kt new file mode 100644 index 0000000..34e7ce1 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/grayscale/GrayscalePdfToolContent.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/grayscale/screenLogic/GrayscalePdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/grayscale/screenLogic/GrayscalePdfToolComponent.kt new file mode 100644 index 0000000..22ce804 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/grayscale/screenLogic/GrayscalePdfToolComponent.kt @@ -0,0 +1,139 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/images_to_pdf/ImagesToPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/images_to_pdf/ImagesToPdfToolContent.kt new file mode 100644 index 0000000..f01950a --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/images_to_pdf/ImagesToPdfToolContent.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() + } + ) + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/images_to_pdf/screenLogic/ImagesToPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/images_to_pdf/screenLogic/ImagesToPdfToolComponent.kt new file mode 100644 index 0000000..69d4c10 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/images_to_pdf/screenLogic/ImagesToPdfToolComponent.kt @@ -0,0 +1,176 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/merge/MergePdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/merge/MergePdfToolContent.kt new file mode 100644 index 0000000..48523f0 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/merge/MergePdfToolContent.kt @@ -0,0 +1,56 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/merge/screenLogic/MergePdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/merge/screenLogic/MergePdfToolComponent.kt new file mode 100644 index 0000000..6661c31 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/merge/screenLogic/MergePdfToolComponent.kt @@ -0,0 +1,122 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/metadata/MetadataPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/metadata/MetadataPdfToolContent.kt new file mode 100644 index 0000000..f1265bb --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/metadata/MetadataPdfToolContent.kt @@ -0,0 +1,70 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + ) +} diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/metadata/components/MetadataEditor.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/metadata/components/MetadataEditor.kt new file mode 100644 index 0000000..435eecb --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/metadata/components/MetadataEditor.kt @@ -0,0 +1,257 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.Cancel +import com.t8rin.imagetoolbox.core.resources.icons.Mop +import com.t8rin.imagetoolbox.core.resources.icons.Restore +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) + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/metadata/screenLogic/MetadataPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/metadata/screenLogic/MetadataPdfToolComponent.kt new file mode 100644 index 0000000..2caae5a --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/metadata/screenLogic/MetadataPdfToolComponent.kt @@ -0,0 +1,181 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkSelectedPdf(initialUri) + } + + override fun getKey(): Pair = "metadata" to uri + + fun setUri(uri: Uri?) { + if (uri == null) { + registerChangesCleared() + } else { + registerChanges() + } + _uri.update { uri } + checkSelectedPdf(uri) + } + + private fun checkSelectedPdf(uri: 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/ocr/OCRPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/ocr/OCRPdfToolContent.kt new file mode 100644 index 0000000..bdd7592 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/ocr/OCRPdfToolContent.kt @@ -0,0 +1,73 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.model.MimeType +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FilePresent +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) + } + ) +} diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/ocr/screenLogic/OCRPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/ocr/screenLogic/OCRPdfToolComponent.kt new file mode 100644 index 0000000..1690cbc --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/ocr/screenLogic/OCRPdfToolComponent.kt @@ -0,0 +1,176 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/page_numbers/PageNumbersPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/page_numbers/PageNumbersPdfToolContent.kt new file mode 100644 index 0000000..8239aa3 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/page_numbers/PageNumbersPdfToolContent.kt @@ -0,0 +1,146 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.selection.ColorRowSelector +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.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)) + EnhancedSliderItem( + value = params.fontSize, + title = stringResource(R.string.just_size), + internalStateTransformation = { + it.roundToTwoDigits() + }, + onValueChange = { + component.updateParams( + params.copy( + fontSize = it + ) + ) + }, + valueRange = 5f..100f, + shape = ShapeDefaults.large + ) + 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) + } + ) +} diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/page_numbers/components/PageNumbersPreview.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/page_numbers/components/PageNumbersPreview.kt new file mode 100644 index 0000000..d0ec427 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/page_numbers/components/PageNumbersPreview.kt @@ -0,0 +1,210 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.foundation.layout.requiredWidth +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.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.graphics.graphicsLayer +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.rememberTextMeasurer +import androidx.compose.ui.text.style.TextAlign +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.feature.pdf_tools.domain.model.PdfPageNumbersParams +import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PageSwitcher +import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfTextStyle +import kotlin.math.max +import kotlin.math.min + +@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) + } + var pageWidth by rememberSaveable { + mutableIntStateOf(1) + } + + 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 + pageWidth = it.result.image.width + }, + shape = MaterialTheme.shapes.medium + ) + + BoxWithConstraints( + modifier = Modifier.matchParentSize() + ) { + val density = LocalDensity.current + val textMeasurer = rememberTextMeasurer() + val pageScale = maxWidth.value / pageWidth.coerceAtLeast(1) + val horizontalPadding = 10f * pageScale + val verticalPadding = 20f * pageScale + val targetWidth = maxWidth * params.fontSize / 100f + val textLayoutWidth = targetWidth + 8.dp + val scaledFontSize = remember( + density, + params.fontSize, + previewText, + targetWidth + ) { + val baseFontSize = 100.sp + val textWidth = textMeasurer.measure( + text = previewText, + style = PdfTextStyle.copy(fontSize = baseFontSize), + maxLines = 1, + softWrap = false + ).size.width.coerceAtLeast(1) + + with(density) { + baseFontSize * (targetWidth.toPx() / textWidth) + } + } + val textVerticalOffsetPx = remember( + params.position, + previewText, + scaledFontSize + ) { + val textLayout = textMeasurer.measure( + text = previewText, + style = PdfTextStyle.copy(fontSize = scaledFontSize), + maxLines = 1, + softWrap = false + ) + var top = Float.POSITIVE_INFINITY + var bottom = Float.NEGATIVE_INFINITY + + previewText.indices.forEach { index -> + val bounds = textLayout.getBoundingBox(index) + top = min(top, bounds.top) + bottom = max(bottom, bounds.bottom) + } + + if (!top.isFinite() || !bottom.isFinite()) { + 0f + } else { + val visualCenter = (top + bottom) / 2f + val layoutHeight = textLayout.size.height.toFloat() + + when (params.position) { + Position.TopLeft, + Position.TopCenter, + Position.TopRight -> -top + + Position.CenterLeft, + Position.Center, + Position.CenterRight -> layoutHeight / 2f - visualCenter + + Position.BottomLeft, + Position.BottomCenter, + Position.BottomRight -> layoutHeight - bottom + } + } + } + + Text( + text = previewText, + modifier = Modifier + .align(previewAlignment) + .requiredWidth(textLayoutWidth) + .padding( + horizontal = horizontalPadding.dp, + vertical = verticalPadding.dp + ) + .graphicsLayer { + translationY = textVerticalOffsetPx + }, + color = Color(params.color), + maxLines = 1, + softWrap = false, + textAlign = TextAlign.Center, + style = PdfTextStyle.copy(fontSize = scaledFontSize) + ) + } + } + } + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/page_numbers/screenLogic/PageNumbersPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/page_numbers/screenLogic/PageNumbersPdfToolComponent.kt new file mode 100644 index 0000000..53f00cc --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/page_numbers/screenLogic/PageNumbersPdfToolComponent.kt @@ -0,0 +1,150 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/preview/PreviewPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/preview/PreviewPdfToolContent.kt new file mode 100644 index 0000000..8bbe18b --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/preview/PreviewPdfToolContent.kt @@ -0,0 +1,103 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Search +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.canUseNewPdfFully +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 && canUseNewPdfFully()) { + 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) + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/preview/screenLogic/PreviewPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/preview/screenLogic/PreviewPdfToolComponent.kt new file mode 100644 index 0000000..dc55f71 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/preview/screenLogic/PreviewPdfToolComponent.kt @@ -0,0 +1,127 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/print/PrintPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/print/PrintPdfToolContent.kt new file mode 100644 index 0000000..c7f5c99 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/print/PrintPdfToolContent.kt @@ -0,0 +1,152 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Receipt +import com.t8rin.imagetoolbox.core.resources.icons.ViewWeek +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) + } + ) +} diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/print/screenLogic/PrintPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/print/screenLogic/PrintPdfToolComponent.kt new file mode 100644 index 0000000..8268b17 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/print/screenLogic/PrintPdfToolComponent.kt @@ -0,0 +1,151 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/protect/ProtectPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/protect/ProtectPdfToolContent.kt new file mode 100644 index 0000000..2fa3d05 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/protect/ProtectPdfToolContent.kt @@ -0,0 +1,115 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.Cancel +import com.t8rin.imagetoolbox.core.resources.icons.Shuffle +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) + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/protect/screenLogic/ProtectPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/protect/screenLogic/ProtectPdfToolComponent.kt new file mode 100644 index 0000000..af80f71 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/protect/screenLogic/ProtectPdfToolComponent.kt @@ -0,0 +1,153 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rearrange/RearrangePdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rearrange/RearrangePdfToolContent.kt new file mode 100644 index 0000000..d345e55 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rearrange/RearrangePdfToolContent.kt @@ -0,0 +1,89 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rearrange/components/PdfPagesRearrangeGrid.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rearrange/components/PdfPagesRearrangeGrid.kt new file mode 100644 index 0000000..f6ff986 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rearrange/components/PdfPagesRearrangeGrid.kt @@ -0,0 +1,280 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.Restore +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") + } + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rearrange/screenLogic/RearrangePdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rearrange/screenLogic/RearrangePdfToolComponent.kt new file mode 100644 index 0000000..c4b5dd8 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rearrange/screenLogic/RearrangePdfToolComponent.kt @@ -0,0 +1,150 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/RemoveAnnotationsPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/RemoveAnnotationsPdfToolContent.kt new file mode 100644 index 0000000..5bbe29c --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/RemoveAnnotationsPdfToolContent.kt @@ -0,0 +1,100 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/components/PdfAnnotationTypeSelector.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/components/PdfAnnotationTypeSelector.kt new file mode 100644 index 0000000..f511b3c --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/components/PdfAnnotationTypeSelector.kt @@ -0,0 +1,124 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/components/RemoveAnnotationsPreview.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/components/RemoveAnnotationsPreview.kt new file mode 100644 index 0000000..ecbc433 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/components/RemoveAnnotationsPreview.kt @@ -0,0 +1,90 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } + } +} diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/screenLogic/RemoveAnnotationsPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/screenLogic/RemoveAnnotationsPdfToolComponent.kt new file mode 100644 index 0000000..1070258 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/screenLogic/RemoveAnnotationsPdfToolComponent.kt @@ -0,0 +1,162 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_pages/RemovePagesPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_pages/RemovePagesPdfToolContent.kt new file mode 100644 index 0000000..e7c342f --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_pages/RemovePagesPdfToolContent.kt @@ -0,0 +1,91 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_pages/components/PdfPagesRemoveGrid.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_pages/components/PdfPagesRemoveGrid.kt new file mode 100644 index 0000000..01780d5 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_pages/components/PdfPagesRemoveGrid.kt @@ -0,0 +1,357 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.CheckCircle +import com.t8rin.imagetoolbox.core.resources.icons.Pages +import com.t8rin.imagetoolbox.core.resources.icons.Restore +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.Rounded.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") + } + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_pages/screenLogic/RemovePagesPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_pages/screenLogic/RemovePagesPdfToolComponent.kt new file mode 100644 index 0000000..08fe4a2 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_pages/screenLogic/RemovePagesPdfToolComponent.kt @@ -0,0 +1,149 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/repair/RepairPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/repair/RepairPdfToolContent.kt new file mode 100644 index 0000000..bd47e6b --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/repair/RepairPdfToolContent.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/repair/screenLogic/RepairPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/repair/screenLogic/RepairPdfToolComponent.kt new file mode 100644 index 0000000..b81f0e2 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/repair/screenLogic/RepairPdfToolComponent.kt @@ -0,0 +1,139 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/RootPdfToolsContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/RootPdfToolsContent.kt new file mode 100644 index 0000000..47c280e --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/RootPdfToolsContent.kt @@ -0,0 +1,224 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.FileOpen +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 + ) + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/PdfViewer.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/PdfViewer.kt new file mode 100644 index 0000000..9f3303a --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/PdfViewer.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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() + } + } + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/viewer/LegacyPdfViewer.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/viewer/LegacyPdfViewer.kt new file mode 100644 index 0000000..8554ed4 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/viewer/LegacyPdfViewer.kt @@ -0,0 +1,331 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.core.utils.makeLog +import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.PdfRenderer +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 + ) + } + +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/viewer/ModernPdfViewer.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/viewer/ModernPdfViewer.kt new file mode 100644 index 0000000..d914aa7 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/viewer/ModernPdfViewer.kt @@ -0,0 +1,84 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.RequiresApi +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 + +@RequiresApi(Build.VERSION_CODES.P) +@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() + } + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/viewer/ModernPdfViewerDelegate.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/viewer/ModernPdfViewerDelegate.kt new file mode 100644 index 0000000..ddf3c9e --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/viewer/ModernPdfViewerDelegate.kt @@ -0,0 +1,276 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.RequiresApi +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.imagetoolbox.core.utils.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") +@RequiresApi(Build.VERSION_CODES.P) +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 +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/screenLogic/RootPdfToolsComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/screenLogic/RootPdfToolsComponent.kt new file mode 100644 index 0000000..78ddfa0 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/screenLogic/RootPdfToolsComponent.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rotate/RotatePdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rotate/RotatePdfToolContent.kt new file mode 100644 index 0000000..59c514b --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rotate/RotatePdfToolContent.kt @@ -0,0 +1,103 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rotate/components/PdfPagesRotationGrid.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rotate/components/PdfPagesRotationGrid.kt new file mode 100644 index 0000000..9b8e810 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rotate/components/PdfPagesRotationGrid.kt @@ -0,0 +1,325 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.AutoMode +import com.t8rin.imagetoolbox.core.resources.icons.Restore +import com.t8rin.imagetoolbox.core.resources.icons.RotateRight +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.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") + } + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rotate/screenLogic/RotatePdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rotate/screenLogic/RotatePdfToolComponent.kt new file mode 100644 index 0000000..6b36def --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rotate/screenLogic/RotatePdfToolComponent.kt @@ -0,0 +1,162 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/SignaturePdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/SignaturePdfToolContent.kt new file mode 100644 index 0000000..6fa1dbc --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/SignaturePdfToolContent.kt @@ -0,0 +1,165 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + ) +} diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/components/SignatureDialog.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/components/SignatureDialog.kt new file mode 100644 index 0000000..a347727 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/components/SignatureDialog.kt @@ -0,0 +1,405 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.LineWeight +import com.t8rin.imagetoolbox.core.resources.icons.Signature +import com.t8rin.imagetoolbox.core.resources.icons.Tune +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)) + } + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/components/SignaturePreview.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/components/SignaturePreview.kt new file mode 100644 index 0000000..5c6241b --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/components/SignaturePreview.kt @@ -0,0 +1,139 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/components/SignatureSelector.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/components/SignatureSelector.kt new file mode 100644 index 0000000..deaaef1 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/components/SignatureSelector.kt @@ -0,0 +1,127 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.AddCircle +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.Outlined.AddCircle, + 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 = {} + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/screenLogic/SignaturePdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/screenLogic/SignaturePdfToolComponent.kt new file mode 100644 index 0000000..1bccbec --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/screenLogic/SignaturePdfToolComponent.kt @@ -0,0 +1,183 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/split/SplitPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/split/SplitPdfToolContent.kt new file mode 100644 index 0000000..59730a9 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/split/SplitPdfToolContent.kt @@ -0,0 +1,199 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.Close +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) + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/split/screenLogic/SplitPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/split/screenLogic/SplitPdfToolComponent.kt new file mode 100644 index 0000000..d460a13 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/split/screenLogic/SplitPdfToolComponent.kt @@ -0,0 +1,161 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/unlock/UnlockPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/unlock/UnlockPdfToolContent.kt new file mode 100644 index 0000000..e8cf18d --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/unlock/UnlockPdfToolContent.kt @@ -0,0 +1,106 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.CheckCircle +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) + } + ) +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/unlock/screenLogic/UnlockPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/unlock/screenLogic/UnlockPdfToolComponent.kt new file mode 100644 index 0000000..60cb2b7 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/unlock/screenLogic/UnlockPdfToolComponent.kt @@ -0,0 +1,150 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/watermark/WatermarkPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/watermark/WatermarkPdfToolContent.kt new file mode 100644 index 0000000..1255e89 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/watermark/WatermarkPdfToolContent.kt @@ -0,0 +1,167 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.TextRotationAngleup +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.watermark_pdf), + 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.Rounded.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) + } + ) +} diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/watermark/components/WatermarkPreview.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/watermark/components/WatermarkPreview.kt new file mode 100644 index 0000000..8ce8f92 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/watermark/components/WatermarkPreview.kt @@ -0,0 +1,144 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.foundation.layout.requiredWidth +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.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.platform.LocalDensity +import androidx.compose.ui.text.rememberTextMeasurer +import androidx.compose.ui.text.style.TextAlign +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.feature.pdf_tools.domain.model.PdfWatermarkParams +import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PageSwitcher +import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfTextStyle + +@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 density = LocalDensity.current + val textMeasurer = rememberTextMeasurer() + val watermarkTextStyle = PdfTextStyle + + val targetWidth = maxWidth * params.fontSize / 100f + val textLayoutWidth = targetWidth + 8.dp + val scaledFontSize = remember( + density, + params.fontSize, + params.text, + targetWidth, + watermarkTextStyle + ) { + val baseFontSize = 100.sp + val textWidth = textMeasurer.measure( + text = params.text, + style = watermarkTextStyle.copy(fontSize = baseFontSize), + maxLines = 1, + softWrap = false + ).size.width.coerceAtLeast(1) + + with(density) { + baseFontSize * (targetWidth.toPx() / textWidth) + } + } + + Text( + text = params.text, + modifier = Modifier + .align(Alignment.Center) + .requiredWidth(textLayoutWidth) + .graphicsLayer { + rotationZ = params.rotation + alpha = params.opacity + }, + color = Color(params.color), + maxLines = 1, + softWrap = false, + textAlign = TextAlign.Center, + style = watermarkTextStyle.copy(fontSize = scaledFontSize) + ) + } + } + } + } + } +} diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/watermark/screenLogic/WatermarkPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/watermark/screenLogic/WatermarkPdfToolComponent.kt new file mode 100644 index 0000000..6352043 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/watermark/screenLogic/WatermarkPdfToolComponent.kt @@ -0,0 +1,165 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/zip_convert/ZipConvertPdfToolContent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/zip_convert/ZipConvertPdfToolContent.kt new file mode 100644 index 0000000..a67c325 --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/zip_convert/ZipConvertPdfToolContent.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + ) +} diff --git a/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/zip_convert/screenLogic/ZipConvertPdfToolComponent.kt b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/zip_convert/screenLogic/ZipConvertPdfToolComponent.kt new file mode 100644 index 0000000..10a8d1f --- /dev/null +++ b/feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/zip_convert/screenLogic/ZipConvertPdfToolComponent.kt @@ -0,0 +1,157 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + init { + checkPdf( + uri = initialUri, + onDecrypted = { _uri.value = it } + ) + } + + 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 + } +} \ No newline at end of file diff --git a/feature/pick-color/.gitignore b/feature/pick-color/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/pick-color/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/pick-color/build.gradle.kts b/feature/pick-color/build.gradle.kts new file mode 100644 index 0000000..6c4cb0a --- /dev/null +++ b/feature/pick-color/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/pick-color/src/main/AndroidManifest.xml b/feature/pick-color/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/pick-color/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/PickColorFromImageContent.kt b/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/PickColorFromImageContent.kt new file mode 100644 index 0000000..79d6c85 --- /dev/null +++ b/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/PickColorFromImageContent.kt @@ -0,0 +1,232 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.takeOrElse +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.colors.parser.ColorNameParser +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt +import com.t8rin.imagetoolbox.core.resources.icons.ZoomIn +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.ColorCopyFormatSelectionDialog +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 displayColor = component.color.takeOrElse { Color.Transparent } + val displayColorName = remember(displayColor) { + ColorNameParser.parseColorName(displayColor) + } + + 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) + } + var showColorCopyDialog 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 = displayColor, + onCopyColorRequest = { + showColorCopyDialog = true + } + ) + }, + bottomBar = { + PickColorFromImageBottomAppBar( + bitmap = component.bitmap, + isPortrait = isPortrait, + switch = switch, + color = displayColor, + 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 = displayColor, + 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 + ) + + ColorCopyFormatSelectionDialog( + visible = showColorCopyDialog, + onDismiss = { showColorCopyDialog = false }, + color = displayColor, + colorName = displayColorName + ) +} \ No newline at end of file diff --git a/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageBottomAppBar.kt b/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageBottomAppBar.kt new file mode 100644 index 0000000..f53204d --- /dev/null +++ b/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageBottomAppBar.kt @@ -0,0 +1,95 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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) + ) + } + } + ) + } +} \ No newline at end of file diff --git a/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageContentImpl.kt b/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageContentImpl.kt new file mode 100644 index 0000000..9b8e58f --- /dev/null +++ b/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageContentImpl.kt @@ -0,0 +1,199 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() + ) + } + } +} \ No newline at end of file diff --git a/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageSheet.kt b/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageSheet.kt new file mode 100644 index 0000000..fa25fb6 --- /dev/null +++ b/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageSheet.kt @@ -0,0 +1,217 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.foundation.LocalIndication +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.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.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.graphics.takeOrElse +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.ImageColorDetector +import com.t8rin.colors.parser.ColorNameParser +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ContentCopy +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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.toHex +import com.t8rin.imagetoolbox.core.ui.widget.buttons.PanModeButton +import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ColorCopyFormatSelectionDialog +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.derivative.EnhancedZoomableModalBottomSheet +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.modifier.shimmer + +@Composable +fun PickColorFromImageSheet( + visible: Boolean, + onDismiss: () -> Unit, + bitmap: Bitmap?, + onColorChange: (Color) -> Unit, + color: Color +) { + val settingsState = LocalSettingsState.current + var panEnabled by rememberSaveable { mutableStateOf(false) } + var showColorCopyDialog by rememberSaveable(visible) { mutableStateOf(false) } + val displayColor = color.takeOrElse { Color.Transparent } + val displayColorName = remember(displayColor) { + ColorNameParser.parseColorName(displayColor) + } + + EnhancedZoomableModalBottomSheet( + visible = visible, + onDismiss = onDismiss, + confirmButton = { + PanModeButton( + selected = panEnabled, + onClick = { panEnabled = !panEnabled } + ) + }, + title = { + val interactionSource = remember { MutableInteractionSource() } + + val shape = shapeByInteraction( + shape = ShapeDefaults.default, + pressedShape = ShapeDefaults.pressed, + interactionSource = interactionSource + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .padding(start = 8.dp, end = 16.dp) + .container( + color = MaterialTheme.colorScheme.surfaceContainerLow, + shape = shape, + resultPadding = 0.dp + ) + .hapticsClickable( + interactionSource = interactionSource, + indication = LocalIndication.current, + onClick = { showColorCopyDialog = true } + ) + .padding(8.dp) + .height(IntrinsicSize.Max) + ) { + Box( + modifier = Modifier + .fillMaxHeight() + .background( + color = animateColorAsState(displayColor).value, + shape = ShapeDefaults.small + ) + .size(40.dp) + .border( + width = settingsState.borderWidth, + color = MaterialTheme.colorScheme.outlineVariant( + onTopOf = animateColorAsState(displayColor).value + ), + shape = ShapeDefaults.small + ) + .clip(ShapeDefaults.small), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Rounded.ContentCopy, + contentDescription = stringResource(R.string.copy), + tint = animateColorAsState( + displayColor.inverse( + fraction = { cond -> + if (cond) 0.8f + else 0.5f + }, + darkMode = displayColor.luminance() < 0.3f + ) + ).value, + modifier = Modifier.size(20.dp) + ) + } + Spacer(Modifier.width(8.dp)) + Column( + modifier = Modifier.fillMaxHeight() + ) { + Text( + modifier = Modifier + .clip(ShapeDefaults.mini) + .background(MaterialTheme.colorScheme.secondaryContainer) + .border( + width = settingsState.borderWidth, + color = MaterialTheme.colorScheme.outlineVariant( + onTopOf = MaterialTheme.colorScheme.secondaryContainer + ), + shape = ShapeDefaults.mini + ) + .padding(horizontal = 8.dp), + text = displayColor.toHex(), + style = LocalTextStyle.current.copy( + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSecondaryContainer + ) + ) + Spacer(Modifier.height(4.dp)) + Text( + text = displayColorName, + style = LocalTextStyle.current.copy( + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + lineHeight = 14.sp + ) + ) + } + } + } + ) { + remember(bitmap) { bitmap?.asImageBitmap() }?.let { + ImageColorDetector( + panEnabled = panEnabled, + color = displayColor, + imageBitmap = it, + onColorChange = onColorChange, + isMagnifierEnabled = settingsState.magnifierEnabled, + boxModifier = Modifier.fillMaxSize() + ) + } ?: Box( + modifier = Modifier + .fillMaxSize() + .shimmer(true) + ) + } + + ColorCopyFormatSelectionDialog( + visible = showColorCopyDialog, + onDismiss = { showColorCopyDialog = false }, + color = displayColor, + colorName = displayColorName + ) +} \ No newline at end of file diff --git a/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageTopAppBar.kt b/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageTopAppBar.kt new file mode 100644 index 0000000..b9a585c --- /dev/null +++ b/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageTopAppBar.kt @@ -0,0 +1,304 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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.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, + onCopyColorRequest: () -> Unit, +) { + 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.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.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(onClick = onCopyColorRequest) + .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(onClick = onCopyColorRequest) + ) + } + } + } + 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(onClick = onCopyColorRequest) + .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(onClick = onCopyColorRequest) + ) + } + } + } else { + Spacer(modifier = Modifier.height(8.dp)) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/screenLogic/PickColorFromImageComponent.kt b/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/screenLogic/PickColorFromImageComponent.kt new file mode 100644 index 0000000..09e1c64 --- /dev/null +++ b/feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/screenLogic/PickColorFromImageComponent.kt @@ -0,0 +1,85 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 androidx.compose.ui.graphics.takeOrElse +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.takeOrElse { _color.value } + } + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUri: Uri?, + onGoBack: () -> Unit, + ): PickColorFromImageComponent + } +} \ No newline at end of file diff --git a/feature/quick-tiles/.gitignore b/feature/quick-tiles/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/quick-tiles/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/quick-tiles/build.gradle.kts b/feature/quick-tiles/build.gradle.kts new file mode 100644 index 0000000..30ff534 --- /dev/null +++ b/feature/quick-tiles/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/quick-tiles/src/main/AndroidManifest.xml b/feature/quick-tiles/src/main/AndroidManifest.xml new file mode 100644 index 0000000..221041a --- /dev/null +++ b/feature/quick-tiles/src/main/AndroidManifest.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/Contants.kt b/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/Contants.kt new file mode 100644 index 0000000..5e8e7ec --- /dev/null +++ b/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/Contants.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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" \ No newline at end of file diff --git a/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/ScreenshotLauncher.kt b/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/ScreenshotLauncher.kt new file mode 100644 index 0000000..d772f93 --- /dev/null +++ b/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/ScreenshotLauncher.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val projectionManager = getSystemService() + val captureIntent = projectionManager?.createScreenCaptureIntent() + + if (captureIntent == null) { + onFailure(NullPointerException("No projection manager")) + return + } + + 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() + } + +} \ No newline at end of file diff --git a/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/ScreenshotMaker.kt b/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/ScreenshotMaker.kt new file mode 100644 index 0000000..457a50d --- /dev/null +++ b/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/ScreenshotMaker.kt @@ -0,0 +1,126 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.imagetoolbox.core.utils.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() + virtualDisplay = null + mediaProjection.stop() + imageReader?.close() + imageReader = null + } + +} \ No newline at end of file diff --git a/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/ScreenshotService.kt b/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/ScreenshotService.kt new file mode 100644 index 0000000..4abf3f2 --- /dev/null +++ b/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/ScreenshotService.kt @@ -0,0 +1,281 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.core.utils.initAppContext +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 + +@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 onCreate() { + super.onCreate() + initAppContext() + } + + 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 = "", + 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 + ) + +} \ No newline at end of file diff --git a/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/tiles/QuickTile.kt b/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/tiles/QuickTile.kt new file mode 100644 index 0000000..f0db55d --- /dev/null +++ b/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/tiles/QuickTile.kt @@ -0,0 +1,45 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.app.PendingIntent +import android.service.quicksettings.TileService +import androidx.core.service.quicksettings.PendingIntentActivityWrapper +import androidx.core.service.quicksettings.TileServiceCompat + +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 + ) + ) + } + } + +} \ No newline at end of file diff --git a/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/tiles/TileAction.kt b/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/tiles/TileAction.kt new file mode 100644 index 0000000..a08e540 --- /dev/null +++ b/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/tiles/TileAction.kt @@ -0,0 +1,59 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 -> flags = 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 + } +} \ No newline at end of file diff --git a/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/tiles/Tiles.kt b/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/tiles/Tiles.kt new file mode 100644 index 0000000..dae372d --- /dev/null +++ b/feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/tiles/Tiles.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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()) +) \ No newline at end of file diff --git a/feature/recognize-text/.gitignore b/feature/recognize-text/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/recognize-text/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/recognize-text/build.gradle.kts b/feature/recognize-text/build.gradle.kts new file mode 100644 index 0000000..6d64af9 --- /dev/null +++ b/feature/recognize-text/build.gradle.kts @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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(projects.lib.neuralTools) + implementation(libs.tesseract) + implementation(projects.lib.cropper) +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/AndroidManifest.xml b/feature/recognize-text/src/main/AndroidManifest.xml new file mode 100644 index 0000000..426cb9f --- /dev/null +++ b/feature/recognize-text/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/data/AndroidImageTextReader.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/data/AndroidImageTextReader.kt new file mode 100644 index 0000000..399e271 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/data/AndroidImageTextReader.kt @@ -0,0 +1,439 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.data.utils.outputStream +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.PaddleOCRModel +import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionData +import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionEngine +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 com.t8rin.neural_tools.ocr.PaddleOCR +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +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, + recognitionEngine: RecognitionEngine, + paddleOCRModel: PaddleOCRModel, + 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) + + if (recognitionEngine == RecognitionEngine.PaddleOCRv5 || recognitionEngine == RecognitionEngine.PaddleOCRv6) { + return@withContext runCatching { + PaddleOCR.recognize( + image = image, + model = recognitionEngine.toPaddleModel(paddleOCRModel) + )?.let { result -> + TextRecognitionResult.Success( + RecognitionData( + text = result.text, + accuracy = result.accuracy, + hocr = result.hocr + ) + ) + } ?: TextRecognitionResult.NoPaddleData + }.getOrElse { + TextRecognitionResult.Error(it) + } + } + + 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()!!) + } + } + } + + override fun isPaddleOCRModelDownloaded( + model: PaddleOCRModel + ): Boolean = PaddleOCR.isModelDownloaded(model.toNeural()) + + override fun downloadPaddleOCRModel( + model: PaddleOCRModel + ) = PaddleOCR.startDownload(model.toNeural()).map { + DownloadProgress( + currentPercent = it.currentPercent, + currentTotalSize = it.currentTotalSize + ) + } + + override fun deletePaddleOCRModel( + model: PaddleOCRModel + ) = PaddleOCR.deleteModel(model.toNeural()) + + 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) { + shareProvider.cacheData( + filename = "exported_languages.zip", + writeData = { writeable -> + writeable.outputStream().createZip { zip -> + RecognitionType.entries.forEach { type -> + File(getPathFromMode(type), "tessdata").listFiles()?.forEach { file -> + zip.putEntry( + name = "${type.displayName}/tessdata/${file.name}", + input = FileInputStream(file) + ) + } + } + } + } + ) + } + + 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() + } + } + } + } + } + } +} + +private fun PaddleOCRModel.toNeural(): PaddleOCR.Model = when (this) { + PaddleOCRModel.CJK -> PaddleOCR.Model.CJK + PaddleOCRModel.Korean -> PaddleOCR.Model.Korean + PaddleOCRModel.Latin -> PaddleOCR.Model.Latin + PaddleOCRModel.EastSlavic -> PaddleOCR.Model.EastSlavic + PaddleOCRModel.Thai -> PaddleOCR.Model.Thai + PaddleOCRModel.Greek -> PaddleOCR.Model.Greek + PaddleOCRModel.English -> PaddleOCR.Model.English + PaddleOCRModel.Cyrillic -> PaddleOCR.Model.Cyrillic + PaddleOCRModel.Arabic -> PaddleOCR.Model.Arabic + PaddleOCRModel.Devanagari -> PaddleOCR.Model.Devanagari + PaddleOCRModel.Tamil -> PaddleOCR.Model.Tamil + PaddleOCRModel.Telugu -> PaddleOCR.Model.Telugu + PaddleOCRModel.UniversalV6 -> PaddleOCR.Model.UniversalV6 +} + +private fun RecognitionEngine.toPaddleModel(paddleOCRModel: PaddleOCRModel): PaddleOCR.Model = + when (this) { + RecognitionEngine.PaddleOCRv6 -> PaddleOCR.Model.UniversalV6 + else -> paddleOCRModel.toNeural() + } \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/di/RecognizeTextModule.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/di/RecognizeTextModule.kt new file mode 100644 index 0000000..fec06ec --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/di/RecognizeTextModule.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/DownloadData.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/DownloadData.kt new file mode 100644 index 0000000..6410f79 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/DownloadData.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/ImageTextReader.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/ImageTextReader.kt new file mode 100644 index 0000000..4f8344b --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/ImageTextReader.kt @@ -0,0 +1,79 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +import kotlinx.coroutines.flow.Flow + +interface ImageTextReader { + + suspend fun getTextFromImage( + type: RecognitionType, + languageCode: String, + recognitionEngine: RecognitionEngine, + paddleOCRModel: PaddleOCRModel, + 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 + ) + + fun isPaddleOCRModelDownloaded( + model: PaddleOCRModel + ): Boolean + + fun downloadPaddleOCRModel( + model: PaddleOCRModel + ): Flow + + fun deletePaddleOCRModel( + model: PaddleOCRModel + ) + + suspend fun exportLanguagesToZip(): String? + + suspend fun importLanguagesFromUri( + zipUri: String + ): Result + +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/OCRLanguage.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/OCRLanguage.kt new file mode 100644 index 0000000..abdd23a --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/OCRLanguage.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + ) + } + } +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/OcrEngineMode.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/OcrEngineMode.kt new file mode 100644 index 0000000..c9cf637 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/OcrEngineMode.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/PaddleOCRModel.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/PaddleOCRModel.kt new file mode 100644 index 0000000..469d840 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/PaddleOCRModel.kt @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 PaddleOCRModel( + val englishName: String +) { + CJK("Universal"), + Korean("Korean"), + Latin("Latin"), + EastSlavic("East Slavic"), + Thai("Thai"), + Greek("Greek"), + English("English"), + Cyrillic("Cyrillic"), + Arabic("Arabic"), + Devanagari("Devanagari"), + Tamil("Tamil"), + Telugu("Telugu"), + UniversalV6("PaddleOCRv6") +} diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/RecognitionData.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/RecognitionData.kt new file mode 100644 index 0000000..d72bb06 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/RecognitionData.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 = "" + ) + } +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/RecognitionEngine.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/RecognitionEngine.kt new file mode 100644 index 0000000..1185dec --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/RecognitionEngine.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 RecognitionEngine { + Tesseract, + PaddleOCRv5, + PaddleOCRv6 +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/RecognitionType.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/RecognitionType.kt new file mode 100644 index 0000000..0951bfa --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/RecognitionType.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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") +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/SegmentationMode.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/SegmentationMode.kt new file mode 100644 index 0000000..3428ce4 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/SegmentationMode.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/TessConstants.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/TessConstants.kt new file mode 100644 index 0000000..e7a387f --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/TessConstants.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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" +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/TessParams.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/TessParams.kt new file mode 100644 index 0000000..3db8180 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/TessParams.kt @@ -0,0 +1,121 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + + } +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/TextRecognitionResult.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/TextRecognitionResult.kt new file mode 100644 index 0000000..074625a --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/TextRecognitionResult.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + data object NoPaddleData : TextRecognitionResult + +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/RecognizeTextContent.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/RecognizeTextContent.kt new file mode 100644 index 0000000..8236c5e --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/RecognizeTextContent.kt @@ -0,0 +1,318 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.IntSize +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.domain.model.MimeType +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Save +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(isPortrait = isPortrait), + 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, + imageUri = component.currentImageUri, + imageSize = component.currentImageSize.run { IntSize(width, height) }, + onGetImageUri = component::updateImageUriAfterEditing, + cropProperties = component.cropProperties, + setCropAspectRatio = component::setCropAspectRatio, + setCropMask = component::setCropMask, + selectedAspectRatio = component.selectedAspectRatio, + loadImage = component::loadImage, + loadImagePreview = component::loadImagePreview + ) +} diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/DeleteLanguageDialog.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/DeleteLanguageDialog.kt new file mode 100644 index 0000000..fcea434 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/DeleteLanguageDialog.kt @@ -0,0 +1,86 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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)) + } + } + ) +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/DeletePaddleOCRModelDialog.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/DeletePaddleOCRModelDialog.kt new file mode 100644 index 0000000..5726db7 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/DeletePaddleOCRModelDialog.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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.PaddleOCRModel + +@Composable +internal fun DeletePaddleOCRModelDialog( + modelToDelete: PaddleOCRModel?, + onDismiss: () -> Unit, + onDeleteModel: (PaddleOCRModel) -> Unit +) { + EnhancedAlertDialog( + visible = modelToDelete != 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, + modelToDelete?.englishName ?: "" + ) + ) + }, + onDismissRequest = onDismiss, + confirmButton = { + EnhancedButton( + containerColor = MaterialTheme.colorScheme.error, + onClick = { + modelToDelete?.let(onDeleteModel) + onDismiss() + } + ) { + Text(stringResource(R.string.delete)) + } + }, + dismissButton = { + EnhancedButton( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + onClick = onDismiss + ) { + Text(stringResource(R.string.cancel)) + } + } + ) +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/DownloadLanguageDialog.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/DownloadLanguageDialog.kt new file mode 100644 index 0000000..eab089f --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/DownloadLanguageDialog.kt @@ -0,0 +1,153 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Download +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 +) { + DownloadLanguageDialog( + title = R.string.no_data, + description = R.string.download_description, + onDownloadRequest = { + onDownloadRequest(downloadDialogData) + }, + downloadProgress = downloadProgress, + dataRemaining = dataRemaining, + onNoConnection = onNoConnection, + onDismiss = onDismiss, + descriptionArgs = arrayOf( + downloadDialogData.firstOrNull()?.type?.displayName ?: "", + downloadDialogData.joinToString(separator = ", ") { it.localizedName } + ) + ) +} + +@Composable +fun DownloadLanguageDialog( + title: Int, + description: Int, + onDownloadRequest: () -> Unit, + downloadProgress: Float, + dataRemaining: String, + onNoConnection: () -> Unit, + onDismiss: () -> Unit, + descriptionArgs: Array = emptyArray() +) { + val context = LocalContext.current + var downloadStarted by rememberSaveable(title, description) { + mutableStateOf(false) + } + + EnhancedAlertDialog( + visible = !downloadStarted, + icon = { + Icon( + imageVector = Icons.Rounded.Download, + contentDescription = null + ) + }, + title = { Text(stringResource(id = title)) }, + text = { + Text( + if (descriptionArgs.isEmpty()) { + stringResource(id = description) + } else { + stringResource(id = description, *descriptionArgs) + } + ) + }, + onDismissRequest = {}, + confirmButton = { + EnhancedButton( + onClick = { + if (context.isNetworkAvailable()) { + onDownloadRequest() + 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 + ) + ) + } + } +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/DownloadedLanguageItem.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/DownloadedLanguageItem.kt new file mode 100644 index 0000000..fa45901 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/DownloadedLanguageItem.kt @@ -0,0 +1,280 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Cancel +import com.t8rin.imagetoolbox.core.resources.icons.CheckCircle +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 + ) +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/FillableButton.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/FillableButton.kt new file mode 100644 index 0000000..e1502b9 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/FillableButton.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + } + } +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/FilterSelectionBar.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/FilterSelectionBar.kt new file mode 100644 index 0000000..4893ebb --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/FilterSelectionBar.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/ModelTypeSelector.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/ModelTypeSelector.kt new file mode 100644 index 0000000..854460e --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/ModelTypeSelector.kt @@ -0,0 +1,135 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.icons.Segment +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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.Rounded.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.Rounded.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 + } diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OCRLanguageColumnForSearch.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OCRLanguageColumnForSearch.kt new file mode 100644 index 0000000..0c4919b --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OCRLanguageColumnForSearch.kt @@ -0,0 +1,108 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.resources.utils.animation.animateColorAsState +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() + ) + } + } +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OCRLanguagesColumn.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OCRLanguagesColumn.kt new file mode 100644 index 0000000..da30d9c --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OCRLanguagesColumn.kt @@ -0,0 +1,280 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Download +import com.t8rin.imagetoolbox.core.resources.icons.DownloadDone +import com.t8rin.imagetoolbox.core.resources.icons.DownloadFile +import com.t8rin.imagetoolbox.core.resources.icons.MultipleStop +import com.t8rin.imagetoolbox.core.resources.icons.UploadFile +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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() + ) + } + } +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OCRTextPreviewItem.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OCRTextPreviewItem.kt new file mode 100644 index 0000000..59847e6 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OCRTextPreviewItem.kt @@ -0,0 +1,178 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.KeyboardArrowDown +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 + ) + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OcrEngineModeSelector.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OcrEngineModeSelector.kt new file mode 100644 index 0000000..d5836e0 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OcrEngineModeSelector.kt @@ -0,0 +1,122 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Engineering +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 + } diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/PaddleOCRModelSelector.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/PaddleOCRModelSelector.kt new file mode 100644 index 0000000..7fc020e --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/PaddleOCRModelSelector.kt @@ -0,0 +1,219 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.Language +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.icons.Search +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.PaddleOCRModel + +@Composable +fun PaddleOCRModelSelector( + value: PaddleOCRModel, + updateKey: Int, + isDownloaded: (PaddleOCRModel) -> Boolean, + onValueChange: (PaddleOCRModel) -> Unit, + onDeleteModel: (PaddleOCRModel) -> Unit +) { + var showSelectionSheet by rememberSaveable { + mutableStateOf(false) + } + + PreferenceItem( + modifier = Modifier.fillMaxWidth(), + title = stringResource(id = R.string.language), + subtitle = stringResource(id = value.localizedName).replaceFirstChar { it.titlecase() }, + onClick = { + showSelectionSheet = true + }, + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + shape = ShapeDefaults.extraLarge, + startIcon = Icons.Rounded.Language, + endIcon = Icons.Rounded.MiniEdit + ) + + var isSearching by rememberSaveable { + mutableStateOf(false) + } + var searchKeyword by rememberSaveable { + mutableStateOf("") + } + + EnhancedModalBottomSheet( + visible = showSelectionSheet, + onDismiss = { + showSelectionSheet = 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.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.Rounded.Language + ) + Spacer(modifier = Modifier.weight(1f)) + EnhancedIconButton( + onClick = { isSearching = true }, + containerColor = MaterialTheme.colorScheme.tertiaryContainer + ) { + Icon( + imageVector = Icons.Outlined.Search, + contentDescription = stringResource(R.string.search_here) + ) + } + EnhancedButton( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + onClick = { + showSelectionSheet = false + } + ) { + Text(stringResource(R.string.close)) + } + } + } + } + }, + sheetContent = { + PaddleOCRModelSelectorSheetContent( + value = value, + updateKey = updateKey, + isDownloaded = isDownloaded, + searchKeyword = searchKeyword, + isSearching = isSearching, + onValueChange = onValueChange, + onDeleteModel = onDeleteModel + ) + } + ) +} + +internal val PaddleOCRModel.localizedName: Int + get() = when (this) { + PaddleOCRModel.CJK -> R.string.paddle_ocr_model_universal + PaddleOCRModel.Korean -> R.string.paddle_ocr_model_korean + PaddleOCRModel.Latin -> R.string.paddle_ocr_model_latin + PaddleOCRModel.EastSlavic -> R.string.paddle_ocr_model_east_slavic + PaddleOCRModel.Thai -> R.string.paddle_ocr_model_thai + PaddleOCRModel.Greek -> R.string.paddle_ocr_model_greek + PaddleOCRModel.English -> R.string.paddle_ocr_model_english + PaddleOCRModel.Cyrillic -> R.string.paddle_ocr_model_cyrillic + PaddleOCRModel.Arabic -> R.string.paddle_ocr_model_arabic + PaddleOCRModel.Devanagari -> R.string.paddle_ocr_model_devanagari + PaddleOCRModel.Tamil -> R.string.paddle_ocr_model_tamil + PaddleOCRModel.Telugu -> R.string.paddle_ocr_model_telugu + PaddleOCRModel.UniversalV6 -> R.string.paddle_ocr_v6 + } \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/PaddleOCRModelSelectorSheetContent.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/PaddleOCRModelSelectorSheetContent.kt new file mode 100644 index 0000000..4e0c2ed --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/PaddleOCRModelSelectorSheetContent.kt @@ -0,0 +1,289 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.LazyItemScope +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.Download +import com.t8rin.imagetoolbox.core.resources.icons.DownloadDone +import com.t8rin.imagetoolbox.core.resources.icons.SearchOff +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer +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.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.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.text.TitleItem +import com.t8rin.imagetoolbox.core.utils.getString +import com.t8rin.imagetoolbox.feature.recognize.text.domain.PaddleOCRModel +import kotlinx.coroutines.delay + +@Composable +internal fun PaddleOCRModelSelectorSheetContent( + value: PaddleOCRModel, + updateKey: Int, + isDownloaded: (PaddleOCRModel) -> Boolean, + searchKeyword: String, + isSearching: Boolean, + onValueChange: (PaddleOCRModel) -> Unit, + onDeleteModel: (PaddleOCRModel) -> Unit +) { + val models = remember { + PaddleOCRModel.entries.filterNot { it == PaddleOCRModel.UniversalV6 } + } + val downloadedModels by remember(updateKey) { + derivedStateOf { + models.filter(isDownloaded) + } + } + val notDownloadedModels by remember(updateKey) { + derivedStateOf { + models.filterNot(isDownloaded) + } + } + + var modelsForSearch by remember { + mutableStateOf(downloadedModels + notDownloadedModels) + } + + LaunchedEffect(searchKeyword, updateKey) { + delay(400L) + val allModels = downloadedModels + notDownloadedModels + modelsForSearch = if (searchKeyword.isEmpty()) { + allModels + } else { + allModels.filter { + it.englishName.contains(searchKeyword, ignoreCase = true) || + getString(it.localizedName).contains(searchKeyword, ignoreCase = true) + }.sortedBy(PaddleOCRModel::englishName) + } + } + + AnimatedContent(targetState = isSearching to modelsForSearch.isNotEmpty()) { (searching, haveData) -> + if (searching && !haveData) { + 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(24.dp) + ) + Icon( + imageVector = Icons.Outlined.SearchOff, + contentDescription = null, + modifier = Modifier + .weight(2f) + .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) + .fillMaxSize() + ) + Spacer(Modifier.weight(1f)) + } + } else { + val listState = rememberLazyListState() + LazyColumn( + state = listState, + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + flingBehavior = enhancedFlingBehavior() + ) { + val shownDownloaded = if (searching) { + modelsForSearch.filter(isDownloaded) + } else { + downloadedModels + } + val shownAvailable = if (searching) { + modelsForSearch.filterNot(isDownloaded) + } else { + notDownloadedModels + } + + if (shownDownloaded.isNotEmpty()) { + item { + TitleItem( + icon = Icons.Rounded.DownloadDone, + text = stringResource(id = R.string.downloaded_models) + ) + } + } + itemsIndexed( + items = shownDownloaded, + key = { _, model -> model.name } + ) { index, model -> + PaddleOCRModelItem( + model = model, + selected = value == model, + downloaded = true, + index = index, + size = shownDownloaded.size, + onValueChange = onValueChange, + onDeleteModel = onDeleteModel + ) + } + if (shownAvailable.isNotEmpty()) { + item { + TitleItem( + icon = Icons.Rounded.Download, + text = stringResource(id = R.string.available_models) + ) + } + } + itemsIndexed( + items = shownAvailable, + key = { _, model -> model.name } + ) { index, model -> + PaddleOCRModelItem( + model = model, + selected = value == model, + downloaded = false, + index = index, + size = shownAvailable.size, + onValueChange = onValueChange, + onDeleteModel = onDeleteModel + ) + } + } + } + } +} + +@Composable +private fun LazyItemScope.PaddleOCRModelItem( + model: PaddleOCRModel, + selected: Boolean, + downloaded: Boolean, + index: Int, + size: Int, + onValueChange: (PaddleOCRModel) -> Unit, + onDeleteModel: (PaddleOCRModel) -> Unit +) { + var deleteDialogData by remember { + mutableStateOf(null) + } + val shape = ShapeDefaults.byIndex(index = index, size = size) + val item = @Composable { + PreferenceItem( + title = model.englishName, + subtitle = stringResource(id = model.localizedName) + .replaceFirstChar { it.titlecase() } + .takeIf { it != model.englishName }, + onClick = { + onValueChange(model) + }, + containerColor = animateColorAsState( + if (selected) { + MaterialTheme.colorScheme.mixedContainer.copy(0.8f) + } else { + EnhancedBottomSheetDefaults.contentContainerColor + } + ).value, + shape = shape, + modifier = Modifier + .animateItem() + .fillMaxWidth() + ) + } + + DeletePaddleOCRModelDialog( + modelToDelete = deleteDialogData, + onDismiss = { deleteDialogData = null }, + onDeleteModel = onDeleteModel + ) + + if (downloaded) { + val state = rememberRevealState() + SwipeToReveal( + state = state, + modifier = Modifier.animateItem(), + revealedContentEnd = { + Box( + Modifier + .fillMaxSize() + .container( + color = MaterialTheme.colorScheme.errorContainer, + shape = shape, + autoShadowElevation = 0.dp, + resultPadding = 0.dp + ) + .hapticsClickable { + 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 = { + item() + } + ) + } else { + item() + } +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognitionEngineSelector.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognitionEngineSelector.kt new file mode 100644 index 0000000..dc53897 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognitionEngineSelector.kt @@ -0,0 +1,56 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.fillMaxWidth +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.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.recognize.text.domain.RecognitionEngine + +@Composable +fun RecognitionEngineSelector( + value: RecognitionEngine, + onValueChange: (RecognitionEngine) -> Unit +) { + EnhancedButtonGroup( + modifier = Modifier + .fillMaxWidth() + .container(shape = ShapeDefaults.extraLarge), + items = RecognitionEngine.entries.map { stringResource(it.title) }, + selectedIndex = RecognitionEngine.entries.indexOf(value), + title = stringResource(id = R.string.provider), + onIndexChange = { + onValueChange(RecognitionEngine.entries[it]) + }, + activeButtonColor = MaterialTheme.colorScheme.tertiaryContainer, + isScrollable = false + ) +} + +private inline val RecognitionEngine.title: Int + get() = when (this) { + RecognitionEngine.Tesseract -> R.string.tesseract + RecognitionEngine.PaddleOCRv5 -> R.string.paddle_ocr_v5 + RecognitionEngine.PaddleOCRv6 -> R.string.paddle_ocr_v6 + } \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognitionTypeSelector.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognitionTypeSelector.kt new file mode 100644 index 0000000..681f6dd --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognitionTypeSelector.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.recognize.text.domain.RecognitionType + +@Composable +fun RecognitionTypeSelector( + value: RecognitionType, + onValueChange: (RecognitionType) -> Unit, + modifier: Modifier = Modifier +) { + EnhancedButtonGroup( + modifier = modifier + .container(shape = ShapeDefaults.extraLarge), + items = RecognitionType.entries.map { it.translatedName }, + selectedIndex = RecognitionType.entries.indexOf(value), + title = stringResource(id = R.string.recognition_type), + isScrollable = false, + 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) + } \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeLanguageSelector.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeLanguageSelector.kt new file mode 100644 index 0000000..3cd9f11 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeLanguageSelector.kt @@ -0,0 +1,215 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.Language +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.icons.Search +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.Rounded.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.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.Rounded.Language + ) + Spacer(modifier = Modifier.weight(1f)) + EnhancedIconButton( + onClick = { isSearching = true }, + containerColor = MaterialTheme.colorScheme.tertiaryContainer + ) { + Icon( + imageVector = Icons.Outlined.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 + } + ) + } + ) +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeLanguageSelectorSheetContent.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeLanguageSelectorSheetContent.kt new file mode 100644 index 0000000..29871ad --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeLanguageSelectorSheetContent.kt @@ -0,0 +1,194 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.SearchOff +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.Outlined.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 + ) + } + } + } + } +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextButtons.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextButtons.kt new file mode 100644 index 0000000..16d3b36 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextButtons.kt @@ -0,0 +1,116 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 com.t8rin.imagetoolbox.core.domain.model.MimeType +import com.t8rin.imagetoolbox.core.resources.icons.CopyAll +import com.t8rin.imagetoolbox.core.resources.icons.Save +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.Outlined.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 + ) +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextControls.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextControls.kt new file mode 100644 index 0000000..e7a7d11 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextControls.kt @@ -0,0 +1,232 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.CameraAlt +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.domain.RecognitionEngine +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)) + RecognitionEngineSelector( + value = component.recognitionEngine, + onValueChange = component::setRecognitionEngine + ) + AnimatedVisibility( + visible = component.recognitionEngine == RecognitionEngine.PaddleOCRv5, + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier.fillMaxWidth() + ) { + Spacer(modifier = Modifier.height(8.dp)) + PaddleOCRModelSelector( + value = component.paddleOCRModel, + updateKey = component.paddleOCRModelsUpdateKey, + isDownloaded = component::isPaddleOCRModelDownloaded, + onValueChange = component::setPaddleOCRModel, + onDeleteModel = component::deletePaddleOCRModel + ) + } + } + AnimatedVisibility( + visible = component.recognitionEngine == RecognitionEngine.Tesseract, + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier.fillMaxWidth() + ) { + Spacer(modifier = Modifier.height(8.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)) + AnimatedVisibility( + visible = component.recognitionEngine == RecognitionEngine.Tesseract, + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier.fillMaxWidth() + ) { + 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() + ) + } + } +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextDownloadDataDialog.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextDownloadDataDialog.kt new file mode 100644 index 0000000..fb03a9f --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextDownloadDataDialog.kt @@ -0,0 +1,115 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.WifiTetheringError +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 (component.showPaddleDownloadDialog) { + var progress by rememberSaveable(Unit) { + mutableFloatStateOf(0f) + } + var dataRemaining by rememberSaveable(Unit) { + mutableStateOf("") + } + DownloadLanguageDialog( + title = R.string.paddle_ocr, + description = R.string.download_paddle_ocr_description, + descriptionArgs = arrayOf(component.paddleOCRModel.englishName), + onDownloadRequest = { + component.downloadPaddleData( + onProgress = { (p, size) -> + dataRemaining = humanFileSize(size) + progress = p + }, + onComplete = { + AppToastHost.showConfetti() + component.startRecognition() + } + ) + }, + downloadProgress = progress, + dataRemaining = dataRemaining, + onNoConnection = { + component.clearPaddleDownloadDialog() + AppToastHost.showToast( + message = getString(R.string.no_connection), + icon = Icons.Rounded.WifiTetheringError, + duration = ToastDuration.Long + ) + }, + onDismiss = component::clearPaddleDownloadDialog + ) + } + + 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.Rounded.WifiTetheringError, + duration = ToastDuration.Long + ) + }, + onDismiss = component::clearDownloadDialogData + ) + } +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextNoDataControls.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextNoDataControls.kt new file mode 100644 index 0000000..5ff3502 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextNoDataControls.kt @@ -0,0 +1,233 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.FileOpen +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 + ) + } + ) +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/TessParamsSelector.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/TessParamsSelector.kt new file mode 100644 index 0000000..74f3de3 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/TessParamsSelector.kt @@ -0,0 +1,175 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.Done +import com.t8rin.imagetoolbox.core.resources.icons.Tune +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.Rounded.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 + ) +} \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/UiDownloadData.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/UiDownloadData.kt new file mode 100644 index 0000000..7bab1e5 --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/UiDownloadData.kt @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) \ No newline at end of file diff --git a/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/screenLogic/RecognizeTextComponent.kt b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/screenLogic/RecognizeTextComponent.kt new file mode 100644 index 0000000..40427da --- /dev/null +++ b/feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/screenLogic/RecognizeTextComponent.kt @@ -0,0 +1,1170 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.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.IntegerSize +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Language +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.core.utils.imageSize +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.PaddleOCRModel +import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionData +import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionEngine +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 kotlin.math.roundToInt +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 _recognitionEngine: MutableState = + mutableStateOf(RecognitionEngine.Tesseract) + val recognitionEngine by _recognitionEngine + + private val _paddleOCRModel: MutableState = + mutableStateOf(PaddleOCRModel.CJK) + + val paddleOCRModel: PaddleOCRModel + get() = if (recognitionEngine == RecognitionEngine.PaddleOCRv6) { + PaddleOCRModel.UniversalV6 + } else { + _paddleOCRModel.value + } + + private val _paddleOCRModelsUpdateKey = mutableIntStateOf(0) + val paddleOCRModelsUpdateKey by _paddleOCRModelsUpdateKey + + 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 _currentImageSize = mutableStateOf(IntegerSize.Zero) + val currentImageSize by _currentImageSize + + val currentImageUri: Uri? + get() = (type as? Screen.RecognizeText.Type.Extraction)?.uri + + 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 + ) + ) + ) + ) + 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 + + private val _showPaddleDownloadDialog = mutableStateOf(false) + val showPaddleDownloadDialog by _showPaddleDownloadDialog + + fun clearDownloadDialogData() { + _downloadDialogData.update { emptyList() } + } + + fun clearPaddleDownloadDialog() { + _showPaddleDownloadDialog.update { false } + } + + 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 { + _recognitionEngine.update { + RecognitionEngine.entries.getOrElse( + index = settingsManager.getSettingsState().initialOcrEngine + ) { + RecognitionEngine.Tesseract + } + } + _paddleOCRModel.update { + PaddleOCRModel.entries.getOrElse( + index = settingsManager.getSettingsState().initialPaddleOcrModel + ) { + PaddleOCRModel.CJK + } + } + 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) { + _currentImageSize.update { + type.uri?.imageSize() ?: IntegerSize.Zero + } + type.uri?.let { uri -> + loadImagePreview(uri)?.let { + updateBitmap( + bitmap = it, + onComplete = ::startRecognition + ) + } + } + } else { + _currentImageSize.update { IntegerSize.Zero } + } + _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 -> + if (!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.NoPaddleData -> { + _showPaddleDownloadDialog.update { true } + 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 + updateBitmapInternal(bitmap) + _isImageLoading.value = false + onComplete() + } + } + + private suspend fun updateBitmapInternal(bitmap: Bitmap) { + _previewBitmap.value = imageScaler.scaleUntilCanShow(bitmap) + internalBitmap.update { previewBitmap } + } + + fun updateImageUriAfterEditing(uri: Uri) { + componentScope.launch { + _isImageLoading.value = true + val imageSize = uri.imageSize() + val preview = loadImagePreview(uri) + + _type.update { Screen.RecognizeText.Type.Extraction(uri) } + _currentImageSize.update { size -> + imageSize ?: preview?.let { IntegerSize(it.width, it.height) } ?: size + } + preview?.let { updateBitmapInternal(it) } + _isImageLoading.value = false + startRecognition() + } + } + + 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.NoPaddleData -> { + _showPaddleDownloadDialog.update { true } + } + + 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() + } + + fun setRecognitionEngine(recognitionEngine: RecognitionEngine) { + if (_recognitionEngine.value == recognitionEngine) return + + _recognitionEngine.update { recognitionEngine } + componentScope.launch { + settingsManager.setInitialOcrEngine(recognitionEngine.ordinal) + } + _recognitionData.update { null } + _editedText.update { null } + startRecognition() + } + + fun setPaddleOCRModel(model: PaddleOCRModel) { + _paddleOCRModel.update { model } + componentScope.launch { + settingsManager.setInitialPaddleOcrModel(model.ordinal) + } + _recognitionData.update { null } + _editedText.update { null } + startRecognition() + } + + fun isPaddleOCRModelDownloaded(model: PaddleOCRModel): Boolean = + imageTextReader.isPaddleOCRModelDownloaded(model) + + fun deletePaddleOCRModel(model: PaddleOCRModel) { + imageTextReader.deletePaddleOCRModel(model) + _paddleOCRModelsUpdateKey.update { it + 1 } + if (model == paddleOCRModel) { + _recognitionData.update { null } + _editedText.update { null } + } + } + + fun downloadPaddleData( + onProgress: (DownloadProgress) -> Unit, + onComplete: () -> Unit + ) { + componentScope.launch { + imageTextReader.downloadPaddleOCRModel(paddleOCRModel).collect { progress -> + onProgress( + DownloadProgress( + currentPercent = progress.currentPercent, + currentTotalSize = progress.currentTotalSize + ) + ) + } + _paddleOCRModelsUpdateKey.update { it + 1 } + clearPaddleDownloadDialog() + onComplete() + } + } + + 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) + + suspend fun loadImagePreview(uri: Uri): Bitmap? { + val targetSize = uri.imageSize()?.safePreviewSize() + val preview = if (targetSize != null) { + imageGetter.getImage( + data = uri, + size = targetSize + ) + } else { + imageGetter.getImage( + uri = uri.toString(), + originalSize = false, + onFailure = AppToastHost::showFailureToast + )?.image + } + + return imageScaler.scaleUntilCanShow(preview) + } + + private fun IntegerSize.safePreviewSize(): IntegerSize { + var targetWidth = width + var targetHeight = height + + while (targetWidth * targetHeight * 4L >= MAX_PREVIEW_SIZE) { + targetWidth = (targetWidth * 0.85f).roundToInt().coerceAtLeast(1) + targetHeight = (targetHeight * 0.85f).roundToInt().coerceAtLeast(1) + } + + return IntegerSize( + width = targetWidth, + height = targetHeight + ) + } + + 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 -> + if (!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.NoPaddleData -> { + _showPaddleDownloadDialog.update { true } + 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 + ): Boolean { + when (this) { + is TextRecognitionResult.Error -> { + builder.apply { + append("${done + 1} - ") + append("[${filenameCreator.getFilename(uri.toString())}]") + append("\n\n") + append(throwable.message) + append("\n\n") + } + return true + } + + is TextRecognitionResult.NoData -> { + onRequestDownload(data) + return false + } + + is TextRecognitionResult.NoPaddleData -> { + _showPaddleDownloadDialog.update { true } + return false + } + + 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") + } + return true + } + } + } + + fun exportLanguagesTo(uri: Uri) { + languagesJob = componentScope.launch { + _isExporting.value = true + imageTextReader.exportLanguagesToZip()?.let { zipUri -> + fileController.transferBytes( + fromUri = zipUri, + toUri = uri.toString() + ).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.Rounded.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.NoPaddleData -> { + _showPaddleDownloadDialog.update { true } + 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 }, + recognitionEngine = recognitionEngine, + paddleOCRModel = paddleOCRModel, + 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 } + } + } + + private companion object { + const val MAX_PREVIEW_SIZE = 3096L * 3096L * 3L + } + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialType: Screen.RecognizeText.Type?, + onGoBack: () -> Unit, + ): RecognizeTextComponent + } + +} \ No newline at end of file diff --git a/feature/resize-convert/.gitignore b/feature/resize-convert/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/resize-convert/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/resize-convert/build.gradle.kts b/feature/resize-convert/build.gradle.kts new file mode 100644 index 0000000..0f705a6 --- /dev/null +++ b/feature/resize-convert/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/resize-convert/src/main/AndroidManifest.xml b/feature/resize-convert/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/resize-convert/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/resize-convert/src/main/java/com/t8rin/imagetoolbox/feature/resize_convert/presentation/ResizeAndConvertContent.kt b/feature/resize-convert/src/main/java/com/t8rin/imagetoolbox/feature/resize_convert/presentation/ResizeAndConvertContent.kt new file mode 100644 index 0000000..cda8375 --- /dev/null +++ b/feature/resize-convert/src/main/java/com/t8rin/imagetoolbox/feature/resize_convert/presentation/ResizeAndConvertContent.kt @@ -0,0 +1,457 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.rememberScrollState +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.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.Preset +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.History +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.UndoRedoButtons +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.enhanced.enhancedHorizontalScroll +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.modifier.fadingEdges +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 = { + val state = rememberScrollState() + Row( + modifier = Modifier + .fadingEdges(state) + .enhancedHorizontalScroll(state), + verticalAlignment = Alignment.CenterVertically + ) { + var editSheetData by remember { + mutableStateOf(listOf()) + } + if (!isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + 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) + ) + } + } + if (isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + } + }, + 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::updateProfile, + imageInfo = imageInfo, + imageExportProfilesHolder = component + ) + 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 + ) + +} diff --git a/feature/resize-convert/src/main/java/com/t8rin/imagetoolbox/feature/resize_convert/presentation/screenLogic/ResizeAndConvertComponent.kt b/feature/resize-convert/src/main/java/com/t8rin/imagetoolbox/feature/resize_convert/presentation/screenLogic/ResizeAndConvertComponent.kt new file mode 100644 index 0000000..c7cc2fe --- /dev/null +++ b/feature/resize-convert/src/main/java/com/t8rin/imagetoolbox/feature/resize_convert/presentation/screenLogic/ResizeAndConvertComponent.kt @@ -0,0 +1,806 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.ImageExportProfilesUseCase +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.ImageExportProfile +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.ColorModel +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.SettingsManager +import com.t8rin.imagetoolbox.core.ui.transformation.ImageInfoTransformation +import com.t8rin.imagetoolbox.core.ui.utils.BaseHistoryComponent +import com.t8rin.imagetoolbox.core.ui.utils.ImageExportProfilesHolder +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.navigation.coroutineScope +import com.t8rin.imagetoolbox.core.ui.utils.state.update +import com.t8rin.imagetoolbox.feature.resize_convert.presentation.screenLogic.ResizeAndConvertComponent.HistorySnapshot +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, + private val settingsManager: SettingsManager, + private val imageExportProfilesUseCase: ImageExportProfilesUseCase, + dispatchersHolder: DispatchersHolder +) : BaseHistoryComponent( + dispatchersHolder = dispatchersHolder, + componentContext = componentContext +), ImageExportProfilesHolder by ImageExportProfilesHolder( + imageExportProfilesUseCase = imageExportProfilesUseCase, + componentScope = componentContext.coroutineScope +) { + + 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 + + private val isAlwaysClearExif: Boolean get() = settingsManager.settingsState.value.isAlwaysClearExif + + override val currentProfileKeepExif: Boolean + get() = keepExif + + init { + componentScope.launch { + val settingsState = settingsManager.getSettingsState() + _imageInfo.update { + it.copy(resizeType = settingsState.defaultResizeType) + } + } + } + + private var job: Job? by smartJob { + _isImageLoading.update { false } + } + + fun setUris( + uris: List? + ) { + clearHistory() + registerChangesCleared() + _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, + clearPreview: Boolean = true, + previewImageInfo: ImageInfo = imageInfo + ) { + if (resetPreset) { + _presetSelected.update { Preset.None } + } + _bitmap.value?.let { bmp -> + val preview = updatePreview( + bitmap = bmp, + previewImageInfo = previewImageInfo + ) + if (clearPreview) { + _previewBitmap.update { null } + } + _shouldShowPreview.update { imagePreviewCreator.canShow(preview) } + if (shouldShowPreview) _previewBitmap.update { preview } + } + } + + private suspend fun updatePreview( + bitmap: Bitmap, + previewImageInfo: ImageInfo = imageInfo + ): Bitmap? = withContext(defaultDispatcher) { + return@withContext previewImageInfo.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(previewImageInfo = newInfo) + } + } + } + + fun resetValues( + saveFormat: Boolean = false, + resetPreset: Boolean = true + ) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _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 + ) + } + commitHistoryFrom(beforeSnapshot) + } + + private fun setImageData(imageData: ImageData) { + job = componentScope.launch { + _isImageLoading.update { true } + _exif.update { imageData.metadata.takeIf { !isAlwaysClearExif } } + 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 + ) + resetHistory() + registerChangesCleared() + _isImageLoading.update { false } + } + } + + fun rotateLeft() { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + it.copy( + rotationDegrees = it.rotationDegrees - 90f, + height = it.width, + width = it.height + ) + } + debouncedImageCalculation { + checkBitmapAndUpdate() + } + commitHistoryFrom(beforeSnapshot) + } + + fun rotateRight() { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + it.copy( + rotationDegrees = it.rotationDegrees + 90f, + height = it.width, + width = it.height + ) + } + debouncedImageCalculation { + checkBitmapAndUpdate() + } + commitHistoryFrom(beforeSnapshot) + } + + fun flip() { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + it.copy(isFlipped = !it.isFlipped) + } + debouncedImageCalculation { + checkBitmapAndUpdate() + } + commitHistoryFrom(beforeSnapshot) + } + + fun updateWidth(width: Int) { + if (imageInfo.width != width) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + it.copy(width = width) + } + debouncedImageCalculation { + checkBitmapAndUpdate(true) + } + commitHistoryFrom(beforeSnapshot) + } + } + + fun updateHeight(height: Int) { + if (imageInfo.height != height) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + it.copy(height = height) + } + debouncedImageCalculation { + checkBitmapAndUpdate(true) + } + commitHistoryFrom(beforeSnapshot) + } + } + + fun setQuality(quality: Quality) { + val currentQuality = imageInfo.quality + val coercedQuality = quality.coerceIn(imageInfo.imageFormat) + if ( + quality::class != coercedQuality::class && + currentQuality::class == coercedQuality::class + ) return + + if (currentQuality != coercedQuality) { + beginPendingHistoryTransaction() + _imageInfo.update { + it.copy(quality = coercedQuality) + } + debouncedImageCalculation { + checkBitmapAndUpdate() + } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setImageFormat(imageFormat: ImageFormat) { + if (imageInfo.imageFormat != imageFormat) { + if (pendingHistoryMode != PendingHistoryMode.FormatChange) { + finalizePendingHistoryTransaction() + } + beginPendingHistoryTransaction( + mode = PendingHistoryMode.FormatChange, + commitDelayMillis = formatHistoryTransactionDebounce + ) + _imageInfo.update { + it.copy( + imageFormat = imageFormat, + quality = it.quality.coerceIn(imageFormat) + ) + } + debouncedImageCalculation { + checkBitmapAndUpdate( + resetPreset = _presetSelected.value == Preset.Telegram && imageFormat != ImageFormat.Png.Lossless + ) + } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setResizeType(type: ResizeType) { + if (imageInfo.resizeType != type) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + it.copy( + resizeType = type.withOriginalSizeIfCrop(originalSize) + ) + } + debouncedImageCalculation { + checkBitmapAndUpdate() + } + commitHistoryFrom(beforeSnapshot) + } + } + + fun setKeepExif(boolean: Boolean) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _keepExif.update { boolean } + commitHistoryFrom(beforeSnapshot) + } + + private var savingJob: Job? by smartJob { + _isSaving.update { false } + } + + fun saveBitmaps( + oneTimeSaveLocationUri: String? + ) { + finalizePendingHistoryTransaction() + 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) + } + + override fun updateProfile(profile: Preset) { + componentScope.launch { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + if (profile is Preset.AspectRatio && profile.ratio != 1f) { + _imageInfo.update { it.copy(rotationDegrees = 0f) } + } + setBitmapInfo( + imageTransformer.applyPresetBy( + image = bitmap, + preset = profile, + currentInfo = imageInfo.copy( + originalUri = selectedUri?.toString() + ) + ) + ) + _presetSelected.update { profile } + commitHistoryFrom(beforeSnapshot) + } + } + + override fun saveProfile(name: String) { + componentScope.launch { + imageExportProfilesUseCase.upsert( + ImageExportProfile.from( + name = name, + imageInfo = imageInfo, + preset = presetSelected, + keepExif = keepExif, + backgroundColorForNoAlphaFormats = settingsManager + .settingsState + .value + .backgroundForNoAlphaImageFormats + ) + ) + } + } + + override fun applyProfile(profile: ImageExportProfile) { + componentScope.launch { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + restoreProfileBackgroundColor(profile) + val restoredInfo = profile.toImageInfo(imageInfo).copy( + originalUri = selectedUri?.toString() + ) + setBitmapInfo( + profile.applyExportSettingsTo( + imageTransformer.applyPresetBy( + image = bitmap, + preset = profile.preset, + currentInfo = restoredInfo + ) + ) + ) + _presetSelected.update { profile.preset } + profile.keepExif?.let { keepExif -> + _keepExif.update { keepExif } + } + commitHistoryFrom(beforeSnapshot) + } + } + + private suspend fun restoreProfileBackgroundColor(profile: ImageExportProfile) { + val color = profile.backgroundColorModel() ?: return + if (settingsManager.settingsState.value.backgroundForNoAlphaImageFormats != color) { + settingsManager.setBackgroundColorForNoAlphaFormats(color) + } + } + + 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 } + } + + 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) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + it.copy( + imageScaleMode = imageScaleMode + ) + } + debouncedImageCalculation { + checkBitmapAndUpdate() + } + commitHistoryFrom(beforeSnapshot) + } + + 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 + ) + ) + + override fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( + imageInfo = imageInfo.asHistoryImageInfo(), + preset = presetSelected, + keepExif = keepExif, + backgroundColorForNoAlphaFormats = settingsManager + .settingsState + .value + .backgroundForNoAlphaImageFormats + ) + + override fun applyHistorySnapshot(snapshot: HistorySnapshot) { + _imageInfo.value = snapshot.imageInfo + _presetSelected.value = snapshot.preset + _keepExif.value = snapshot.keepExif + restoreBackgroundColorForNoAlphaFormats( + settingsManager = settingsManager, + backgroundColorForNoAlphaFormats = snapshot.backgroundColorForNoAlphaFormats + ) + debouncedImageCalculation { + bitmap?.let { + checkBitmapAndUpdate(clearPreview = false) + } + } + } + + private fun ImageInfo.asHistoryImageInfo(): ImageInfo = copy(sizeInBytes = 0) + + override fun hasSameUndoState( + first: HistorySnapshot, + second: HistorySnapshot + ): Boolean = first.normalized() == second.normalized() + + private fun HistorySnapshot.normalized(): HistorySnapshot = copy( + imageInfo = imageInfo.asHistoryImageInfo() + ) + + data class HistorySnapshot( + val imageInfo: ImageInfo = ImageInfo(), + val preset: Preset = Preset.None, + val keepExif: Boolean = false, + val backgroundColorForNoAlphaFormats: ColorModel = ColorModel(-0x1000000) + ) + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUris: List?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): ResizeAndConvertComponent + } +} diff --git a/feature/root/.gitignore b/feature/root/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/root/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/root/build.gradle.kts b/feature/root/build.gradle.kts new file mode 100644 index 0000000..f962289 --- /dev/null +++ b/feature/root/build.gradle.kts @@ -0,0 +1,85 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.textureGeneration) + 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) + implementation(projects.feature.appLogs) + implementation(projects.feature.shaderStudio) + implementation(projects.feature.help) + implementation(projects.feature.usageStatistics) + implementation(projects.feature.batchRename) + implementation(projects.feature.duplicateFinder) +} diff --git a/feature/root/src/main/AndroidManifest.xml b/feature/root/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/root/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/RootContent.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/RootContent.kt new file mode 100644 index 0000000..2a9a915 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/RootContent.kt @@ -0,0 +1,49 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.FileControllerEventsHandler +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, + onNavigate = component::navigateTo + ) { + ScreenSelector(component) + + RootDialogs(component) + + FileControllerEventsHandler(component) + } +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/FileControllerEventsHandler.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/FileControllerEventsHandler.kt new file mode 100644 index 0000000..690b4a5 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/FileControllerEventsHandler.kt @@ -0,0 +1,122 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 android.app.Activity +import android.content.IntentSender +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.IntentSenderRequest +import androidx.activity.result.contract.ActivityResultContracts +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.listSaver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import com.t8rin.imagetoolbox.core.data.saving.FileControllerEvent +import com.t8rin.imagetoolbox.core.data.saving.FileControllerEventEmitter +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost +import com.t8rin.imagetoolbox.core.utils.appContext + +@Composable +internal fun FileControllerEventsHandler( + eventEmitter: FileControllerEventEmitter +) { + var activeRequestId by rememberSaveable { mutableStateOf(null) } + var pendingRequests by rememberSaveable(stateSaver = deleteRequestsSaver) { + mutableStateOf(emptyList()) + } + val launcher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartIntentSenderForResult() + ) { result -> + activeRequestId?.let { requestId -> + eventEmitter.onDeleteOriginalsPermissionResult( + requestId = requestId, + granted = result.resultCode == Activity.RESULT_OK + ) + } + activeRequestId = null + } + + LaunchedEffect(eventEmitter) { + eventEmitter.events.collect { event -> + when (event) { + is FileControllerEvent.RequestDeleteOriginalsPermission -> { + pendingRequests = pendingRequests + event + } + + is FileControllerEvent.OriginalFilesDeleteResult -> { + val message = when { + event.failed == 0 -> appContext.getString( + R.string.original_files_deleted, + event.deleted + ) + + event.deleted == 0 -> appContext.getString( + R.string.original_files_delete_failed, + event.failed + ) + + else -> appContext.getString( + R.string.original_files_delete_result, + event.deleted, + event.failed + ) + } + AppToastHost.showToast( + message = message, + icon = Icons.Outlined.Delete + ) + } + } + } + } + + LaunchedEffect(activeRequestId, pendingRequests, launcher) { + if (activeRequestId == null) { + pendingRequests.firstOrNull()?.let { event -> + pendingRequests = pendingRequests.drop(1) + activeRequestId = event.requestId + launcher.launch( + IntentSenderRequest.Builder(event.intentSender).build() + ) + } + } + } +} + +private val deleteRequestsSaver = listSaver( + save = { requests -> + requests.flatMap { request -> + listOf(request.requestId, request.intentSender, request.count) + } + }, + restore = { values -> + values.chunked(3).map { request -> + FileControllerEvent.RequestDeleteOriginalsPermission( + requestId = request[0] as Long, + intentSender = request[1] as IntentSender, + count = request[2] as Int + ) + } + } +) \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/RootDialogs.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/RootDialogs.kt new file mode 100644 index 0000000..f8177a3 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/RootDialogs.kt @@ -0,0 +1,96 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/ScreenSelector.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/ScreenSelector.kt new file mode 100644 index 0000000..0092e5b --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/ScreenSelector.kt @@ -0,0 +1,64 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/SettingsBackdropWrapper.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/SettingsBackdropWrapper.kt new file mode 100644 index 0000000..9f9c828 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/SettingsBackdropWrapper.kt @@ -0,0 +1,208 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState +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 + val gridState = rememberLazyStaggeredGridState() + + 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, + gridState = gridState + ) + } + } + }, + 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 + ) +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/SettingsOpenButton.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/SettingsOpenButton.kt new file mode 100644 index 0000000..a0da655 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/SettingsOpenButton.kt @@ -0,0 +1,247 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Settings +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 + ) + } + } + } + } +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/AppExitDialog.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/AppExitDialog.kt new file mode 100644 index 0000000..ae0d817 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/AppExitDialog.kt @@ -0,0 +1,102 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.DoorBack +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 + ) + } + ) +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/EditPresetsSheet.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/EditPresetsSheet.kt new file mode 100644 index 0000000..87d9895 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/EditPresetsSheet.kt @@ -0,0 +1,204 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.AddCircle +import com.t8rin.imagetoolbox.core.resources.icons.LabelPercent +import com.t8rin.imagetoolbox.core.resources.icons.PhotoSizeSelectSmall +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.Outlined.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)) + } + } + ) +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/FirstLaunchSetupDialog.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/FirstLaunchSetupDialog.kt new file mode 100644 index 0000000..b319073 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/FirstLaunchSetupDialog.kt @@ -0,0 +1,150 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Beta +import com.t8rin.imagetoolbox.core.resources.icons.MobileArrowDown +import com.t8rin.imagetoolbox.core.resources.icons.ReleaseAlert +import com.t8rin.imagetoolbox.core.resources.icons.Webhook +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) + adjustPerformance(appContext.performanceClass) + } + + EnhancedAlertDialog( + visible = updateOnFirstOpen, + onDismissRequest = {}, + icon = { + Icon( + imageVector = Icons.Rounded.MobileArrowDown, + 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.ReleaseAlert + ) + 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)) + } + } + ) +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/GithubReviewDialog.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/GithubReviewDialog.kt new file mode 100644 index 0000000..ec6aa99 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/GithubReviewDialog.kt @@ -0,0 +1,78 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.Star +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.Star, + contentDescription = null + ) + }, + text = { + Text(stringResource(R.string.rate_app_sub)) + } + ) +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/PermissionDialog.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/PermissionDialog.kt new file mode 100644 index 0000000..28b4640 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/PermissionDialog.kt @@ -0,0 +1,116 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.Storage +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)) + } + } + ) +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/TelegramGroupDialog.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/TelegramGroupDialog.kt new file mode 100644 index 0000000..2444bf0 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/TelegramGroupDialog.kt @@ -0,0 +1,92 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.Cancel +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 + ) +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/navigation/ChildProvider.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/navigation/ChildProvider.kt new file mode 100644 index 0000000..8e9fff1 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/navigation/ChildProvider.kt @@ -0,0 +1,985 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.batchrename.presentation.screenLogic.BatchRenameComponent +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.duplicate_finder.presentation.screenLogic.DuplicateFinderComponent +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.help.presentation.screenLogic.HelpComponent +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.AppLogs +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.BatchRename +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.DuplicateFinder +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.Help +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.ShaderStudio +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.TextureGeneration +import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.UnlockPdfTool +import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.UsageStatistics +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.shader_studio.presentation.screenLogic.ShaderStudioComponent +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.usage_statistics.presentation.screenLogic.UsageStatisticsComponent +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 com.t8rin.imagetoolbox.presentation.app_logs.screenLogic.AppLogsComponent +import com.t8rin.imagetoolbox.texture_generation.presentation.screenLogic.TextureGenerationComponent +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 duplicateFinderComponentFactory: DuplicateFinderComponent.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 textureGenerationComponentFactory: TextureGenerationComponent.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 shaderStudioComponentFactory: ShaderStudioComponent.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 appLogsComponentFactory: AppLogsComponent.Factory, + private val usageStatisticsComponentFactory: UsageStatisticsComponent.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, + private val helpComponentFactory: HelpComponent.Factory, + private val batchRenameComponentFactory: BatchRenameComponent.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 + ) + ) + + is Screen.DuplicateFinder -> DuplicateFinder( + duplicateFinderComponentFactory( + 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, + ) + ) + + Screen.TextureGeneration -> TextureGeneration( + textureGenerationComponentFactory( + 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, + targetSetting = config.targetSetting + ) + ) + + Screen.ShaderStudio -> ShaderStudio( + shaderStudioComponentFactory( + componentContext = componentContext, + onGoBack = ::navigateBack + ) + ) + + is Screen.Help -> Help( + helpComponentFactory( + componentContext = componentContext, + initialCategory = config.categoryName, + initialTipId = config.tipId, + onGoBack = ::navigateBack, + onNavigate = ::navigateTo + ) + ) + + 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 + ) + ) + + Screen.AppLogs -> AppLogs( + appLogsComponentFactory( + componentContext = componentContext, + onGoBack = ::navigateBack + ) + ) + + Screen.UsageStatistics -> UsageStatistics( + usageStatisticsComponentFactory( + 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 + ) + ) + + is Screen.BatchRename -> BatchRename( + batchRenameComponentFactory( + componentContext = componentContext, + initialUris = config.uris, + onGoBack = ::navigateBack + ) + ) + + } +} diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/navigation/NavigationChild.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/navigation/NavigationChild.kt new file mode 100644 index 0000000..8892339 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/navigation/NavigationChild.kt @@ -0,0 +1,600 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.batchrename.presentation.BatchRenameContent +import com.t8rin.imagetoolbox.feature.batchrename.presentation.screenLogic.BatchRenameComponent +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.duplicate_finder.presentation.DuplicateFinderContent +import com.t8rin.imagetoolbox.feature.duplicate_finder.presentation.screenLogic.DuplicateFinderComponent +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.help.presentation.HelpContent +import com.t8rin.imagetoolbox.feature.help.presentation.screenLogic.HelpComponent +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.shader_studio.presentation.ShaderStudioContent +import com.t8rin.imagetoolbox.feature.shader_studio.presentation.screenLogic.ShaderStudioComponent +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.usage_statistics.presentation.UsageStatisticsContent +import com.t8rin.imagetoolbox.feature.usage_statistics.presentation.screenLogic.UsageStatisticsComponent +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 +import com.t8rin.imagetoolbox.presentation.app_logs.AppLogsContent +import com.t8rin.imagetoolbox.presentation.app_logs.screenLogic.AppLogsComponent +import com.t8rin.imagetoolbox.texture_generation.presentation.TextureGenerationContent +import com.t8rin.imagetoolbox.texture_generation.presentation.screenLogic.TextureGenerationComponent + + +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 BatchRename(private val component: BatchRenameComponent) : NavigationChild { + @Composable + override fun Content() = BatchRenameContent(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 DuplicateFinder(private val component: DuplicateFinderComponent) : NavigationChild { + @Composable + override fun Content() = DuplicateFinderContent(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 TextureGeneration(private val component: TextureGenerationComponent) : NavigationChild { + @Composable + override fun Content() = TextureGenerationContent(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 ShaderStudio(private val component: ShaderStudioComponent) : NavigationChild { + @Composable + override fun Content() = ShaderStudioContent(component) + } + + class Help(private val component: HelpComponent) : NavigationChild { + @Composable + override fun Content() = HelpContent(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 AppLogs(private val component: AppLogsComponent) : NavigationChild { + @Composable + override fun Content() = AppLogsContent(component) + } + + class UsageStatistics(private val component: UsageStatisticsComponent) : NavigationChild { + @Composable + override fun Content() = UsageStatisticsContent(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) + } +} diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/BackEventObserver.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/BackEventObserver.kt new file mode 100644 index 0000000..709cd24 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/BackEventObserver.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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?) +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/ResetThemeOnGoBack.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/ResetThemeOnGoBack.kt new file mode 100644 index 0000000..437ac1f --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/ResetThemeOnGoBack.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + } +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/ScreenBasedMaxBrightnessEnforcement.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/ScreenBasedMaxBrightnessEnforcement.kt new file mode 100644 index 0000000..cf13adc --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/ScreenBasedMaxBrightnessEnforcement.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } + } + } +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/SettingsUtils.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/SettingsUtils.kt new file mode 100644 index 0000000..af3d6a9 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/SettingsUtils.kt @@ -0,0 +1,68 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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( + onNoUpdates = null + ) + } + } +} + + +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 +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/SuccessRestoreBackupToastHandler.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/SuccessRestoreBackupToastHandler.kt new file mode 100644 index 0000000..a8a9ef2 --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/SuccessRestoreBackupToastHandler.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.resources.Icons +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Save +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 + ) + } + } + } +} \ No newline at end of file diff --git a/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/screenLogic/RootComponent.kt b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/screenLogic/RootComponent.kt new file mode 100644 index 0000000..5ff855a --- /dev/null +++ b/feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/screenLogic/RootComponent.kt @@ -0,0 +1,498 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.data.saving.FileControllerEventEmitter +import com.t8rin.imagetoolbox.core.domain.APP_CHANGELOG +import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder +import com.t8rin.imagetoolbox.core.domain.history.AppHistoryRepository +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AutoFixHigh +import com.t8rin.imagetoolbox.core.resources.icons.DownloadOff +import com.t8rin.imagetoolbox.core.resources.icons.Error +import com.t8rin.imagetoolbox.core.settings.domain.AutoCacheCleanupUseCase +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.makeLog +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 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, + private val filterParamsInteractor: FilterParamsInteractor, + private val fileController: FileController, + private val appHistoryRepository: AppHistoryRepository, + private val autoCacheCleanupUseCase: AutoCacheCleanupUseCase, + dispatchersHolder: DispatchersHolder, + settingsComponentFactory: SettingsComponent.Factory, + resourceManager: ResourceManager, + fileControllerEventEmitter: FileControllerEventEmitter +) : BaseComponent(dispatchersHolder, componentContext), + ResourceManager by resourceManager, + FileControllerEventEmitter by fileControllerEventEmitter { + + 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().also { + autoCacheCleanupUseCase.clearCacheIfNeeded(it) + } + } + 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)? = { + AppToastHost.showToast( + icon = Icons.Rounded.DownloadOff, + message = getString(R.string.no_updates) + ) + } + ) { + 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.Outlined.Error, + duration = ToastDuration.Long + ) + _backupRestoredEvents.trySend(false) + } + ) + } + return + } + + if (type is ExtraDataType.Template) { + componentScope.launch { + val content = fileController.readBytes(type.uri).toString(Charsets.UTF_8) + + if (filterParamsInteractor.isValidTemplateFilter(content)) { + filterParamsInteractor.addTemplateFilterFromString( + string = content, + onSuccess = { filterName, filtersCount -> + AppToastHost.showToast( + message = getString( + R.string.added_filter_template, + filterName, + filtersCount + ), + icon = Icons.Outlined.AutoFixHigh + ) + }, + onFailure = {} + ) + } + } + 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.saveToHistory().simpleName.makeLog("Navigator") + .also(analyticsManager::registerScreenOpen) + navController.pushNew(screen) + hideSelectDialog() + } + } + + fun replaceTo(screen: Screen) { + componentScope.launch { + delay(100) + screen.saveToHistory().simpleName.makeLog("Navigator") + .also(analyticsManager::registerScreenOpen) + navController.navigate( + transformer = { stack -> + stack.dropLastWhile { it !is Screen.PdfTools && it !is Screen.Main } + screen + } + ) + hideSelectDialog() + } + } + + fun navigateToNew(screen: Screen) { + if (childStack.items.lastOrNull()?.configuration != Screen.Main) { + navigateBack() + } + screen.saveToHistory().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 + ) + } + + private fun Screen.saveToHistory() = apply { + if (id < 0) return@apply + + componentScope.launch { + appHistoryRepository.pushLastTool(id) + } + } + + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext + ): RootComponent + } + +} \ No newline at end of file diff --git a/feature/scan-qr-code/.gitignore b/feature/scan-qr-code/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/scan-qr-code/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/scan-qr-code/build.gradle.kts b/feature/scan-qr-code/build.gradle.kts new file mode 100644 index 0000000..5582509 --- /dev/null +++ b/feature/scan-qr-code/build.gradle.kts @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/AndroidManifest.xml b/feature/scan-qr-code/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/scan-qr-code/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/data/AndroidImageBarcodeReader.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/data/AndroidImageBarcodeReader.kt new file mode 100644 index 0000000..260916f --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/data/AndroidImageBarcodeReader.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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)) + } + ) + } + } + +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/di/ScanQrCodeModule.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/di/ScanQrCodeModule.kt new file mode 100644 index 0000000..8f86967 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/di/ScanQrCodeModule.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + + +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/domain/ImageBarcodeReader.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/domain/ImageBarcodeReader.kt new file mode 100644 index 0000000..6667634 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/domain/ImageBarcodeReader.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/ScanQrCodeContent.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/ScanQrCodeContent.kt new file mode 100644 index 0000000..0c5abba --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/ScanQrCodeContent.kt @@ -0,0 +1,274 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.BarcodeScanner +import com.t8rin.imagetoolbox.core.resources.icons.ImageSearch +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.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 + ) + ) + component.processFilterTemplateFromQrContent() + } + + val isNotScannable = params.content.raw.isNotEmpty() && component.mayBeNotScannable + val isSaveEnabled = params.content.raw.isNotEmpty() && component.isSaveEnabled + + val analyzerImagePicker = rememberImagePicker { uri: Uri -> + component.readBarcodeFromImage(uri) + } + + 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 + ) + ) + } + + 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 + ) + +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrCodePreview.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrCodePreview.kt new file mode 100644 index 0000000..ff01a25 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrCodePreview.kt @@ -0,0 +1,178 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + ) + } + } + } + } +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrInfo.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrInfo.kt new file mode 100644 index 0000000..8f3729f --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrInfo.kt @@ -0,0 +1,345 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.AlternateEmail +import com.t8rin.imagetoolbox.core.resources.icons.Badge +import com.t8rin.imagetoolbox.core.resources.icons.Business +import com.t8rin.imagetoolbox.core.resources.icons.Description +import com.t8rin.imagetoolbox.core.resources.icons.Event +import com.t8rin.imagetoolbox.core.resources.icons.Flag +import com.t8rin.imagetoolbox.core.resources.icons.HashTag +import com.t8rin.imagetoolbox.core.resources.icons.Home +import com.t8rin.imagetoolbox.core.resources.icons.Info +import com.t8rin.imagetoolbox.core.resources.icons.Latitude +import com.t8rin.imagetoolbox.core.resources.icons.Link +import com.t8rin.imagetoolbox.core.resources.icons.Longitude +import com.t8rin.imagetoolbox.core.resources.icons.NoteSticky +import com.t8rin.imagetoolbox.core.resources.icons.Password +import com.t8rin.imagetoolbox.core.resources.icons.Person +import com.t8rin.imagetoolbox.core.resources.icons.Phone +import com.t8rin.imagetoolbox.core.resources.icons.Place +import com.t8rin.imagetoolbox.core.resources.icons.Public +import com.t8rin.imagetoolbox.core.resources.icons.RecordVoiceOver +import com.t8rin.imagetoolbox.core.resources.icons.Security +import com.t8rin.imagetoolbox.core.resources.icons.ShortText +import com.t8rin.imagetoolbox.core.resources.icons.Start +import com.t8rin.imagetoolbox.core.resources.icons.TextFields +import com.t8rin.imagetoolbox.core.resources.icons.Topic +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.Rounded.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.Rounded.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.Rounded.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.Outlined.NoteSticky, + 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.HashTag, + 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.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() + ) + ) + } + } +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrInfoBuilder.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrInfoBuilder.kt new file mode 100644 index 0000000..650d50c --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrInfoBuilder.kt @@ -0,0 +1,86 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrParamsSelector.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrParamsSelector.kt new file mode 100644 index 0000000..65e3ba3 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrParamsSelector.kt @@ -0,0 +1,589 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.Circle +import com.t8rin.imagetoolbox.core.resources.icons.Code +import com.t8rin.imagetoolbox.core.resources.icons.DarkMode +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.LightMode +import com.t8rin.imagetoolbox.core.resources.icons.Padding +import com.t8rin.imagetoolbox.core.resources.icons.PhotoSizeSelectLarge +import com.t8rin.imagetoolbox.core.resources.icons.RoundedCorner +import com.t8rin.imagetoolbox.core.resources.icons.Shuffle +import com.t8rin.imagetoolbox.core.resources.icons.Square +import com.t8rin.imagetoolbox.core.resources.icons.TableRows +import com.t8rin.imagetoolbox.core.resources.icons.TopLeft +import com.t8rin.imagetoolbox.core.resources.icons.ViewColumn +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.Rounded.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.Rounded.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.Rounded.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.Rounded.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, + ) + ) + } + } +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrPreviewParams.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrPreviewParams.kt new file mode 100644 index 0000000..bf90957 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrPreviewParams.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + ) + } + } +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrTypeEditSheet.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrTypeEditSheet.kt new file mode 100644 index 0000000..1d926a7 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrTypeEditSheet.kt @@ -0,0 +1,114 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.QrCode +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.Outlined.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) + ) + } + } +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrTypeInfoItem.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrTypeInfoItem.kt new file mode 100644 index 0000000..425d2bc --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrTypeInfoItem.kt @@ -0,0 +1,191 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.ContentCopy +import com.t8rin.imagetoolbox.core.resources.icons.OpenInNew +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.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) + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrTypeUtils.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrTypeUtils.kt new file mode 100644 index 0000000..40b4d7f --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrTypeUtils.kt @@ -0,0 +1,420 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.CalendarMonth +import com.t8rin.imagetoolbox.core.resources.icons.Contacts +import com.t8rin.imagetoolbox.core.resources.icons.Email +import com.t8rin.imagetoolbox.core.resources.icons.Link +import com.t8rin.imagetoolbox.core.resources.icons.LocationOn +import com.t8rin.imagetoolbox.core.resources.icons.Phone +import com.t8rin.imagetoolbox.core.resources.icons.Sms +import com.t8rin.imagetoolbox.core.resources.icons.TextFields +import com.t8rin.imagetoolbox.core.resources.icons.Wifi +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 +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/ScanQrCodeControls.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/ScanQrCodeControls.kt new file mode 100644 index 0000000..ce28e30 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/ScanQrCodeControls.kt @@ -0,0 +1,395 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.QrCode2 +import com.t8rin.imagetoolbox.core.resources.icons.RoundedCorner +import com.t8rin.imagetoolbox.core.resources.icons.WarningAmber +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.Rounded.RoundedCorner, + steps = 22 + ) + Spacer(modifier = Modifier.height(8.dp)) + } + } +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrCalendarEditField.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrCalendarEditField.kt new file mode 100644 index 0000000..8cdb0ac --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrCalendarEditField.kt @@ -0,0 +1,347 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.DateRange +import com.t8rin.imagetoolbox.core.resources.icons.Description +import com.t8rin.imagetoolbox.core.resources.icons.Event +import com.t8rin.imagetoolbox.core.resources.icons.Flag +import com.t8rin.imagetoolbox.core.resources.icons.Info +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.icons.Person +import com.t8rin.imagetoolbox.core.resources.icons.Place +import com.t8rin.imagetoolbox.core.resources.icons.Start +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.Rounded.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.Rounded.MiniEdit, + 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.Rounded.MiniEdit, + 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) + ) + ) + } + } + } +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrContactEditField.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrContactEditField.kt new file mode 100644 index 0000000..3d1d400 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrContactEditField.kt @@ -0,0 +1,403 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 com.t8rin.imagetoolbox.core.resources.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.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.Add +import com.t8rin.imagetoolbox.core.resources.icons.AlternateEmail +import com.t8rin.imagetoolbox.core.resources.icons.Badge +import com.t8rin.imagetoolbox.core.resources.icons.Business +import com.t8rin.imagetoolbox.core.resources.icons.Call +import com.t8rin.imagetoolbox.core.resources.icons.Email +import com.t8rin.imagetoolbox.core.resources.icons.HashTag +import com.t8rin.imagetoolbox.core.resources.icons.Home +import com.t8rin.imagetoolbox.core.resources.icons.Link +import com.t8rin.imagetoolbox.core.resources.icons.Person +import com.t8rin.imagetoolbox.core.resources.icons.Place +import com.t8rin.imagetoolbox.core.resources.icons.Prefix +import com.t8rin.imagetoolbox.core.resources.icons.Public +import com.t8rin.imagetoolbox.core.resources.icons.RecordVoiceOver +import com.t8rin.imagetoolbox.core.resources.icons.RemoveCircle +import com.t8rin.imagetoolbox.core.resources.icons.Suffix +import com.t8rin.imagetoolbox.core.resources.icons.SupervisedUserCircle +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.Rounded.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.Rounded.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.Rounded.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.Rounded.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.HashTag, + 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.RemoveCircle, + 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( + imageVector = Icons.Rounded.Add, + contentDescription = null + ) + Spacer(Modifier.width(4.dp)) + Text(stringResource(title)) + } +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrEditField.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrEditField.kt new file mode 100644 index 0000000..26dcf50 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrEditField.kt @@ -0,0 +1,80 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + } + } +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrEmailEditField.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrEmailEditField.kt new file mode 100644 index 0000000..ed45e37 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrEmailEditField.kt @@ -0,0 +1,84 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.AlternateEmail +import com.t8rin.imagetoolbox.core.resources.icons.ShortText +import com.t8rin.imagetoolbox.core.resources.icons.Topic +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.Rounded.ShortText, + contentDescription = null + ) + }, + singleLine = false + ) + } +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrGeoEditField.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrGeoEditField.kt new file mode 100644 index 0000000..b1a4a04 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrGeoEditField.kt @@ -0,0 +1,101 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrPhoneEditField.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrPhoneEditField.kt new file mode 100644 index 0000000..6ccfc0d --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrPhoneEditField.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.HashTag +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.HashTag, + contentDescription = null + ) + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Phone + ) + ) + + ContactPickerButton( + onPicked = { onValueChange(value.copy(number = it.phones.firstOrNull()?.number.orEmpty())) } + ) + } +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrSmsEditField.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrSmsEditField.kt new file mode 100644 index 0000000..9859d1b --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrSmsEditField.kt @@ -0,0 +1,76 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.HashTag +import com.t8rin.imagetoolbox.core.resources.icons.NoteSticky +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.HashTag, + 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.Outlined.NoteSticky, + contentDescription = null + ) + }, + singleLine = false + ) + + ContactPickerButton( + onPicked = { onValueChange(value.copy(phoneNumber = it.phones.firstOrNull()?.number.orEmpty())) } + ) + } +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrWifiEditField.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrWifiEditField.kt new file mode 100644 index 0000000..a90b005 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrWifiEditField.kt @@ -0,0 +1,109 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.CheckCircle +import com.t8rin.imagetoolbox.core.resources.icons.Password +import com.t8rin.imagetoolbox.core.resources.icons.Security +import com.t8rin.imagetoolbox.core.resources.icons.TextFields +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.Outlined.CheckCircle else null }, + spanCount = 1, + behaveAsContainer = false, + titlePadding = PaddingValues(top = 8.dp, bottom = 16.dp), + contentPadding = PaddingValues(), + canExpand = false, + selectedItemColor = MaterialTheme.colorScheme.secondaryContainer + ) + } +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/screenLogic/ScanQrCodeComponent.kt b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/screenLogic/ScanQrCodeComponent.kt new file mode 100644 index 0000000..3ab1a85 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/screenLogic/ScanQrCodeComponent.kt @@ -0,0 +1,264 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AutoFixHigh +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.core.utils.getString +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() { + componentScope.launch { + if (filterParamsInteractor.isValidTemplateFilter(params.content.raw)) { + filterParamsInteractor.addTemplateFilterFromString( + string = params.content.raw, + onSuccess = { filterName, filtersCount -> + AppToastHost.showToast( + message = getString( + R.string.added_filter_template, + filterName, + filtersCount + ), + icon = Icons.Outlined.AutoFixHigh + ) + }, + onFailure = {} + ) + } + } + } + + fun getFormatForFilenameSelection(): ImageFormat = ImageFormat.Png.Lossless + + fun updateParams(params: QrPreviewParams) { + _params.update { params } + } + + fun readBarcodeFromImage(image: Any) { + componentScope.launch { + syncReadBarcodeFromImage(image).onFailure { + AppToastHost.showFailureToast( + Throwable(getString(R.string.no_barcode_found), it) + ) + } + processFilterTemplateFromQrContent() + } + } + + 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 + } + +} \ No newline at end of file diff --git a/feature/settings/.gitignore b/feature/settings/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/settings/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts new file mode 100644 index 0000000..0171174 --- /dev/null +++ b/feature/settings/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/settings/src/main/AndroidManifest.xml b/feature/settings/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/settings/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/AndroidSettingsManager.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/AndroidSettingsManager.kt new file mode 100644 index 0000000..44ebbf3 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/AndroidSettingsManager.kt @@ -0,0 +1,1112 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.CacheAutoClearInterval +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.Logger +import com.t8rin.imagetoolbox.core.utils.createZip +import com.t8rin.imagetoolbox.core.utils.filename +import com.t8rin.imagetoolbox.core.utils.makeLog +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.ALWAYS_CLEAR_EXIF +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.CACHE_AUTO_CLEAR_INTERVAL +import com.t8rin.imagetoolbox.feature.settings.data.keys.CACHE_AUTO_CLEAR_LIMIT_BYTES +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.CROP_OVERLAY_DRAGGABLE +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.DELETE_ORIGINALS_AFTER_SAVE +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_BITMAP_BORDER +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_SHEET_GESTURES +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_ENGINE +import com.t8rin.imagetoolbox.feature.settings.data.keys.INITIAL_OCR_MODE +import com.t8rin.imagetoolbox.feature.settings.data.keys.INITIAL_PADDLE_OCR_MODEL +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.LAST_CACHE_AUTO_CLEAR_TIMESTAMP_MILLIS +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.MOTION_DURATION_SCALE +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.PERFORMANCE_VERSION +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.SAVE_TO_ORIGINAL_FOLDER +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.SHAPE_BY_INTERACTION_THROTTLE +import com.t8rin.imagetoolbox.feature.settings.data.keys.SHOW_FAVORITE_AS_LAST +import com.t8rin.imagetoolbox.feature.settings.data.keys.SHOW_FAVORITE_TOOLS_IN_GROUPED_MODE +import com.t8rin.imagetoolbox.feature.settings.data.keys.SHOW_SETTINGS_IN_LANDSCAPE +import com.t8rin.imagetoolbox.feature.settings.data.keys.SHOW_TOOLS_HISTORY +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_ANIMATED_EMOJIS +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 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 setCacheAutoClearLimitBytes(bytes: Long) = edit { + it[CACHE_AUTO_CLEAR_LIMIT_BYTES] = bytes.coerceAtLeast(0L) + } + + override suspend fun setCacheAutoClearInterval(interval: CacheAutoClearInterval) = edit { + it[CACHE_AUTO_CLEAR_INTERVAL] = interval.key + } + + override suspend fun setLastCacheAutoClearTimestampMillis(timestampMillis: Long) = edit { + it[LAST_CACHE_AUTO_CLEAR_TIMESTAMP_MILLIS] = timestampMillis.coerceAtLeast(0L) + } + + override suspend fun toggleGroupOptionsByTypes() = toggle( + key = GROUP_OPTIONS_BY_TYPE, + defaultValue = default.groupOptionsByTypes + ) + + override suspend fun toggleShowFavoriteToolsInGroupedMode() = toggle( + key = SHOW_FAVORITE_TOOLS_IN_GROUPED_MODE, + defaultValue = default.showFavoriteToolsInGroupedMode + ) + + override suspend fun toggleShowFavoriteAsLast() = toggle( + key = SHOW_FAVORITE_AS_LAST, + defaultValue = default.showFavoriteAsLast + ) + + 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 toggleSaveToOriginalFolder() = toggle( + key = SAVE_TO_ORIGINAL_FOLDER, + defaultValue = default.saveToOriginalFolder, + predicate = { it.filenameBehavior !is FilenameBehavior.Overwrite } + ) + + override suspend fun toggleDeleteOriginalsAfterSave() = toggle( + key = DELETE_ORIGINALS_AFTER_SAVE, + defaultValue = default.deleteOriginalsAfterSave, + predicate = { it.filenameBehavior !is 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 toggleCropOverlayDraggable() = toggle( + key = CROP_OVERLAY_DRAGGABLE, + defaultValue = default.cropOverlayDraggable + ) + + override suspend fun toggleDrawBitmapBorder() = toggle( + key = DRAW_BITMAP_BORDER, + defaultValue = default.drawBitmapBorder + ) + + 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 toggleUseAnimatedEmojis() = toggle( + key = USE_ANIMATED_EMOJIS, + defaultValue = default.useAnimatedEmojis + ) + + 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 toggleEnableSheetGestures() = toggle( + key = ENABLE_SHEET_GESTURES, + defaultValue = default.enableSheetGestures + ) + + 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 { + performanceClass.makeLog("adjustPerformance") + + val performanceVersion = it[PERFORMANCE_VERSION] ?: 0 + + if (performanceVersion >= TARGET_PERFORMANCE_VERSION) return@edit + + it[PERFORMANCE_VERSION] = TARGET_PERFORMANCE_VERSION + + when (performanceClass) { + PerformanceClass.Low -> { + it[USE_ANIMATED_EMOJIS] = false + 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[USE_ANIMATED_EMOJIS] = true + 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[USE_ANIMATED_EMOJIS] = true + 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 setInitialOcrEngine(engine: Int) = edit { + it[INITIAL_OCR_ENGINE] = engine + } + + override suspend fun setInitialPaddleOcrModel(model: Int) = edit { + it[INITIAL_PADDLE_OCR_MODEL] = model + } + + 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 setShapeByInteractionThrottle(delay: Long) = edit { + it[SHAPE_BY_INTERACTION_THROTTLE] = delay.coerceIn(0, 500) + } + + 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 toggleAlwaysClearExif() = toggle( + key = ALWAYS_CLEAR_EXIF, + defaultValue = default.isAlwaysClearExif + ) + + override suspend fun toggleEnableBackgroundColorForAlphaFormats() = toggle( + key = ENABLE_BACKGROUND_COLOR_FOR_ALPHA_FORMATS, + defaultValue = default.enableBackgroundColorForAlphaFormats + ) + + override suspend fun toggleShowToolsHistory() = toggle( + key = SHOW_TOOLS_HISTORY, + defaultValue = default.showToolsHistory + ) + + override suspend fun setMotionDurationScale(scale: Float) = edit { + it[MOTION_DURATION_SCALE] = scale.coerceIn(0f, 5f) + } + + private suspend fun toggleFilenameBehavior( + behavior: FilenameBehavior + ) = edit { + if (behavior is FilenameBehavior.Overwrite) { + val canOverwrite = + !currentSettings.deleteOriginalsAfterSave && !currentSettings.saveToOriginalFolder && + (currentSettings.filenameBehavior is FilenameBehavior.Overwrite || currentSettings.filenameBehavior is FilenameBehavior.None) + + if (!canOverwrite) return@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, + predicate: (SettingsState) -> Boolean = { true } + ) = edit { + if (!predicate(currentSettings)) return@edit + + it.toggle( + key = key, + defaultValue = defaultValue + ) + } + + private suspend fun edit( + transform: suspend (MutablePreferences) -> Unit + ) { + dataStore.edit(transform) + } + +} + +private const val TARGET_PERFORMANCE_VERSION = 1 \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/AutoCacheCleanupUseCaseImpl.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/AutoCacheCleanupUseCaseImpl.kt new file mode 100644 index 0000000..355afbb --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/AutoCacheCleanupUseCaseImpl.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.domain.coroutines.AppScope +import com.t8rin.imagetoolbox.core.domain.saving.FileController +import com.t8rin.imagetoolbox.core.settings.domain.AutoCacheCleanupUseCase +import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager +import com.t8rin.imagetoolbox.core.settings.domain.model.CacheAutoClearInterval +import com.t8rin.imagetoolbox.core.settings.domain.model.SettingsState +import kotlinx.coroutines.launch +import javax.inject.Inject + +internal class AutoCacheCleanupUseCaseImpl @Inject constructor( + private val fileController: FileController, + private val settingsManager: SettingsManager, + private val appScope: AppScope +) : AutoCacheCleanupUseCase { + override fun clearCacheIfNeeded(settings: SettingsState) { + if (!settings.clearCacheOnLaunch) return + if (fileController.getCacheSize() <= settings.cacheAutoClearLimitBytes) return + + val now = System.currentTimeMillis() + val intervalElapsed = settings.cacheAutoClearInterval == + CacheAutoClearInterval.OnAppLaunch || + settings.lastCacheAutoClearTimestampMillis <= 0L || + now - settings.lastCacheAutoClearTimestampMillis >= + settings.cacheAutoClearInterval.duration.inWholeMilliseconds + + if (!intervalElapsed) return + + fileController.clearCache { + appScope.launch { + settingsManager.setLastCacheAutoClearTimestampMillis(now) + } + } + } +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/ContextUtils.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/ContextUtils.kt new file mode 100644 index 0000000..1d15418 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/ContextUtils.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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() + } +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/keys/MapToSettingsState.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/keys/MapToSettingsState.kt new file mode 100644 index 0000000..7a91658 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/keys/MapToSettingsState.kt @@ -0,0 +1,303 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.CacheAutoClearInterval +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, + cacheAutoClearLimitBytes = this[CACHE_AUTO_CLEAR_LIMIT_BYTES] + ?: default.cacheAutoClearLimitBytes, + cacheAutoClearInterval = CacheAutoClearInterval.fromKey(this[CACHE_AUTO_CLEAR_INTERVAL]) + ?: default.cacheAutoClearInterval, + lastCacheAutoClearTimestampMillis = this[LAST_CACHE_AUTO_CLEAR_TIMESTAMP_MILLIS] + ?: default.lastCacheAutoClearTimestampMillis, + groupOptionsByTypes = this[GROUP_OPTIONS_BY_TYPE] ?: default.groupOptionsByTypes, + showFavoriteToolsInGroupedMode = this[SHOW_FAVORITE_TOOLS_IN_GROUPED_MODE] + ?: default.showFavoriteToolsInGroupedMode, + showFavoriteAsLast = this[SHOW_FAVORITE_AS_LAST] ?: default.showFavoriteAsLast, + addSequenceNumber = this[ADD_SEQ_NUM_TO_FILENAME] ?: default.addSequenceNumber, + saveFolderUri = this[SAVE_FOLDER_URI], + saveToOriginalFolder = this[SAVE_TO_ORIGINAL_FOLDER] ?: default.saveToOriginalFolder, + deleteOriginalsAfterSave = this[DELETE_ORIGINALS_AFTER_SAVE] + ?: default.deleteOriginalsAfterSave, + 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, + cropOverlayDraggable = this[CROP_OVERLAY_DRAGGABLE] ?: default.cropOverlayDraggable, + drawBitmapBorder = this[DRAW_BITMAP_BORDER] ?: default.drawBitmapBorder, + 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, + useAnimatedEmojis = this[USE_ANIMATED_EMOJIS] ?: default.useAnimatedEmojis, + 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, + enableSheetGestures = this[ENABLE_SHEET_GESTURES] + ?: default.enableSheetGestures, + 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, + initialOcrEngine = this[INITIAL_OCR_ENGINE] ?: default.initialOcrEngine, + initialPaddleOcrModel = this[INITIAL_PADDLE_OCR_MODEL] ?: default.initialPaddleOcrModel, + 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.fromTitle(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, + shapeByInteractionThrottle = (this[SHAPE_BY_INTERACTION_THROTTLE] + ?: default.shapeByInteractionThrottle).coerceIn(0, 500), + 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, + isAlwaysClearExif = this[ALWAYS_CLEAR_EXIF] ?: default.isAlwaysClearExif, + enableBackgroundColorForAlphaFormats = this[ENABLE_BACKGROUND_COLOR_FOR_ALPHA_FORMATS] + ?: default.enableBackgroundColorForAlphaFormats, + showToolsHistory = this[SHOW_TOOLS_HISTORY] ?: default.showToolsHistory, + motionDurationScale = (this[MOTION_DURATION_SCALE] ?: default.motionDurationScale) + .coerceIn(0f, 5f) +) + +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() diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/keys/SettingKeys.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/keys/SettingKeys.kt new file mode 100644 index 0000000..dfbc12b --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/keys/SettingKeys.kt @@ -0,0 +1,160 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.longPreferencesKey +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.core.stringSetPreferencesKey + +internal val SAVE_FOLDER_URI = stringPreferencesKey("saveFolder") +internal val SAVE_TO_ORIGINAL_FOLDER = booleanPreferencesKey("SAVE_TO_ORIGINAL_FOLDER") +internal val DELETE_ORIGINALS_AFTER_SAVE = booleanPreferencesKey("DELETE_ORIGINALS_AFTER_SAVE") +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 CACHE_AUTO_CLEAR_LIMIT_BYTES = longPreferencesKey("CACHE_AUTO_CLEAR_LIMIT_BYTES") +internal val CACHE_AUTO_CLEAR_INTERVAL = stringPreferencesKey("CACHE_AUTO_CLEAR_INTERVAL") +internal val LAST_CACHE_AUTO_CLEAR_TIMESTAMP_MILLIS = + longPreferencesKey("LAST_CACHE_AUTO_CLEAR_TIMESTAMP_MILLIS") +internal val GROUP_OPTIONS_BY_TYPE = booleanPreferencesKey("group_options") +internal val SHOW_FAVORITE_TOOLS_IN_GROUPED_MODE = + booleanPreferencesKey("SHOW_FAVORITE_TOOLS_IN_GROUPED_MODE") +internal val SHOW_FAVORITE_AS_LAST = booleanPreferencesKey("SHOW_FAVORITE_AS_LAST") +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 CROP_OVERLAY_DRAGGABLE = booleanPreferencesKey("CROP_OVERLAY_DRAGGABLE") +internal val DRAW_BITMAP_BORDER = booleanPreferencesKey("DRAW_BITMAP_BORDER") +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 USE_ANIMATED_EMOJIS = booleanPreferencesKey("USE_ANIMATED_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 ENABLE_SHEET_GESTURES = booleanPreferencesKey("ENABLE_SHEET_GESTURES") +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 INITIAL_OCR_ENGINE = intPreferencesKey("INITIAL_OCR_ENGINE") +internal val INITIAL_PADDLE_OCR_MODEL = intPreferencesKey("INITIAL_PADDLE_OCR_MODEL") +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 SHAPE_BY_INTERACTION_THROTTLE = longPreferencesKey("SHAPE_BY_INTERACTION_THROTTLE") +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 ALWAYS_CLEAR_EXIF = + booleanPreferencesKey("ALWAYS_CLEAR_EXIF") +internal val ENABLE_BACKGROUND_COLOR_FOR_ALPHA_FORMATS = + booleanPreferencesKey("ENABLE_BACKGROUND_COLOR_FOR_ALPHA_FORMATS") +internal val PERFORMANCE_VERSION = intPreferencesKey("PERFORMANCE_VERSION") +internal val SHOW_TOOLS_HISTORY = booleanPreferencesKey("SHOW_TOOLS_HISTORY") +internal val MOTION_DURATION_SCALE = floatPreferencesKey("MOTION_DURATION_SCALE") diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/di/SettingsModule.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/di/SettingsModule.kt new file mode 100644 index 0000000..ee17028 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/di/SettingsModule.kt @@ -0,0 +1,61 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.AutoCacheCleanupUseCase +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 com.t8rin.imagetoolbox.feature.settings.data.AutoCacheCleanupUseCaseImpl +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 + + @Singleton + @Binds + fun useCase( + impl: AutoCacheCleanupUseCaseImpl + ): AutoCacheCleanupUseCase + +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/SettingsContent.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/SettingsContent.kt new file mode 100644 index 0000000..edbb7bd --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/SettingsContent.kt @@ -0,0 +1,527 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.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.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.LazyStaggeredGridState +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.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState +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.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.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.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.Search +import com.t8rin.imagetoolbox.core.resources.icons.SearchOff +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.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 +import kotlinx.coroutines.delay +import kotlin.time.Duration.Companion.seconds + + +@Composable +fun SettingsContent( + component: SettingsComponent, + disableBottomInsets: Boolean = false, + gridState: LazyStaggeredGridState = rememberLazyStaggeredGridState(), + appBarNavigationIcon: (@Composable (Boolean, () -> Unit) -> Unit)? = null +) { + val isStandaloneScreen = appBarNavigationIcon == null + 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 targetSetting = component.targetSetting + + var showTargetHighlight by rememberSaveable { + mutableStateOf(false) + } + var isHighlightShown by rememberSaveable(targetSetting) { + mutableStateOf(false) + } + + val highlightedContainerColor = if (showTargetHighlight) { + val highlightTransition = rememberInfiniteTransition() + val fraction by highlightTransition.animateFloat( + initialValue = 0f, + targetValue = 0.75f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 500), + repeatMode = RepeatMode.Reverse + ) + ) + + MaterialTheme.colorScheme.surfaceContainerLow.blend( + color = MaterialTheme.colorScheme.primaryContainer, + fraction = fraction + ) + } else { + MaterialTheme.colorScheme.surfaceContainerLow + } + + 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() + + LaunchedEffect(targetSetting, settings) { + targetSetting ?: return@LaunchedEffect + val index = settings?.indexOfFirst { (_, setting) -> + setting == targetSetting + } ?: initialSettingGroups.indexOfFirst { group -> + group.settingsList.contains(targetSetting) + } + + if (index >= 0) { + gridState.scrollToItem(index) + } + } + + LaunchedEffect(targetSetting) { + if (targetSetting != null && !showTargetHighlight && !isHighlightShown) { + showTargetHighlight = true + delay(3.seconds) + showTargetHighlight = false + isHighlightShown = true + } + } + + DisposableEffect(Unit) { + onDispose { showTargetHighlight = false } + } + + 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.Outlined.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.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( + state = gridState, + 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, + containerColor = if (showTargetHighlight && setting == targetSetting) { + highlightedContainerColor + } else { + MaterialTheme.colorScheme.surfaceContainerLow + } + ) + } + } + } + } else { + SettingGroupItem( + groupKey = group.id, + icon = group.icon, + text = stringResource(group.titleId), + initialState = group.initialState, + forceExpanded = targetSetting in group.settingsList + ) { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + group.settingsList.forEach { setting -> + SettingItem( + setting = setting, + component = component, + containerColor = if (showTargetHighlight && setting == targetSetting) { + highlightedContainerColor + } else { + MaterialTheme.colorScheme.surface + } + ) + } + } + } + } + } + } + } + } else if (settingsAnimated.isNotEmpty()) { + LazyVerticalStaggeredGrid( + state = gridState, + 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 + ) + } + } + } 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.Outlined.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() + } + } + } + } +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddFileSizeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddFileSizeSettingItem.kt new file mode 100644 index 0000000..0d80424 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddFileSizeSettingItem.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddImageScaleModeToFilenameSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddImageScaleModeToFilenameSettingItem.kt new file mode 100644 index 0000000..42a7e96 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddImageScaleModeToFilenameSettingItem.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FitScreen +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.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddOriginalFilenameSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddOriginalFilenameSettingItem.kt new file mode 100644 index 0000000..3d31d42 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddOriginalFilenameSettingItem.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Difference +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddPresetToFilenameSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddPresetToFilenameSettingItem.kt new file mode 100644 index 0000000..54a23d1 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddPresetToFilenameSettingItem.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddTimestampToFilenameSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddTimestampToFilenameSettingItem.kt new file mode 100644 index 0000000..384538e --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddTimestampToFilenameSettingItem.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Timer +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowAutoClipboardPasteSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowAutoClipboardPasteSettingItem.kt new file mode 100644 index 0000000..cd0394e --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowAutoClipboardPasteSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ContentPasteGo +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.Rounded.ContentPasteGo + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowBetasSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowBetasSettingItem.kt new file mode 100644 index 0000000..f5e45f8 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowBetasSettingItem.kt @@ -0,0 +1,49 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowImageMonetSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowImageMonetSettingItem.kt new file mode 100644 index 0000000..77e867f --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowImageMonetSettingItem.kt @@ -0,0 +1,61 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.WaterDrop +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowSkipIfLargerSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowSkipIfLargerSettingItem.kt new file mode 100644 index 0000000..398a526 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowSkipIfLargerSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AlwaysClearExifSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AlwaysClearExifSettingItem.kt new file mode 100644 index 0000000..9ed7926 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AlwaysClearExifSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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.ui.widget.modifier.ShapeDefaults +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch + +@Composable +fun AlwaysClearExifSettingItem( + 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.always_clear_exif), + subtitle = stringResource(R.string.always_clear_exif_sub), + checked = settingsState.isAlwaysClearExif, + onClick = { + onClick() + }, + startIcon = Icons.Outlined.DeleteSweep + ) +} diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AmoledModeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AmoledModeSettingItem.kt new file mode 100644 index 0000000..42c2209 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AmoledModeSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Brightness4 +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() + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AnalyticsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AnalyticsSettingItem.kt new file mode 100644 index 0000000..974d0f1 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AnalyticsSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AppBarShadowsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AppBarShadowsSettingItem.kt new file mode 100644 index 0000000..d89716b --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AppBarShadowsSettingItem.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.HorizontalSplit +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.Rounded.HorizontalSplit + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AppLogsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AppLogsSettingItem.kt new file mode 100644 index 0000000..51fccb7 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AppLogsSettingItem.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.material3.Icon +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.Book2 +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload + +@Composable +fun AppLogsSettingItem( + onClick: () -> Unit, + modifier: Modifier = Modifier.padding(horizontal = 8.dp), + shape: Shape = ShapeDefaults.center, +) { + PreferenceItemOverload( + shape = shape, + onClick = onClick, + startIcon = { + Icon( + imageVector = Icons.Outlined.Book2, + contentDescription = null + ) + }, + title = stringResource(R.string.app_logs), + subtitle = stringResource(R.string.app_logs_sub), + modifier = modifier + ) +} diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AppUsageStatisticsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AppUsageStatisticsSettingItem.kt new file mode 100644 index 0000000..cd5cd15 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AppUsageStatisticsSettingItem.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FinanceMode +import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem + +@Composable +fun AppUsageStatisticsSettingItem( + onClick: () -> Unit, + shape: Shape = ShapeDefaults.center, + modifier: Modifier = Modifier.padding(horizontal = 8.dp) +) { + PreferenceItem( + modifier = modifier, + shape = shape, + title = stringResource(R.string.usage_statistics), + subtitle = stringResource(R.string.usage_statistics_sub), + startIcon = Icons.Outlined.FinanceMode, + onClick = onClick + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AuthorSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AuthorSettingItem.kt new file mode 100644 index 0000000..966649e --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AuthorSettingItem.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoCacheClearSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoCacheClearSettingItem.kt new file mode 100644 index 0000000..bc33a37 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoCacheClearSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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.center, + 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() + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoCheckUpdatesSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoCheckUpdatesSettingItem.kt new file mode 100644 index 0000000..25a1a7a --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoCheckUpdatesSettingItem.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ReleaseAlert +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.ReleaseAlert + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoPinClipboardOnlyClipSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoPinClipboardOnlyClipSettingItem.kt new file mode 100644 index 0000000..e730830 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoPinClipboardOnlyClipSettingItem.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.SaveAs +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoPinClipboardSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoPinClipboardSettingItem.kt new file mode 100644 index 0000000..c0189c7 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoPinClipboardSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.PushPin +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/BackupSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/BackupSettingItem.kt new file mode 100644 index 0000000..76639f8 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/BackupSettingItem.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.UploadFile +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/BorderThicknessSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/BorderThicknessSettingItem.kt new file mode 100644 index 0000000..0da79e8 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/BorderThicknessSettingItem.kt @@ -0,0 +1,71 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.BorderStyle +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.Rounded.BorderStyle, + onValueChange = { + value = (it * 10).roundToInt() / 10f + }, + internalStateTransformation = { + (it * 10).roundToInt() / 10f + }, + valueRange = 0f..1.5f, + steps = 14 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/BrightnessEnforcementSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/BrightnessEnforcementSettingItem.kt new file mode 100644 index 0000000..22f5953 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/BrightnessEnforcementSettingItem.kt @@ -0,0 +1,165 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.BrightnessHigh +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) + } + ) + } + } + } + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ButtonShadowsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ButtonShadowsSettingItem.kt new file mode 100644 index 0000000..d89870e --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ButtonShadowsSettingItem.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Gamepad +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CacheAutoClearIntervalSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CacheAutoClearIntervalSettingItem.kt new file mode 100644 index 0000000..4af3b57 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CacheAutoClearIntervalSettingItem.kt @@ -0,0 +1,152 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AvTimer +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonChecked +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.icons.Schedule +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +import com.t8rin.imagetoolbox.core.settings.domain.model.CacheAutoClearInterval +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 CacheAutoClearIntervalSettingItem( + onValueChange: (CacheAutoClearInterval) -> Unit, + shape: Shape = ShapeDefaults.bottom, + modifier: Modifier = Modifier.padding(horizontal = 8.dp) +) { + val settingsState = LocalSettingsState.current + var showSheet by rememberSaveable { + mutableStateOf(false) + } + + PreferenceItem( + modifier = modifier, + title = stringResource(R.string.cache_auto_clear_interval), + startIcon = Icons.Outlined.AvTimer, + subtitle = settingsState.cacheAutoClearInterval.title(), + onClick = { + showSheet = true + }, + shape = shape, + endIcon = Icons.Rounded.MiniEdit, + enabled = settingsState.clearCacheOnLaunch + ) + + EnhancedModalBottomSheet( + visible = showSheet, + onDismiss = { showSheet = it }, + title = { + TitleItem( + text = stringResource(R.string.cache_auto_clear_interval), + icon = Icons.Outlined.Schedule + ) + }, + 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) + ) { + CacheAutoClearInterval.entries.forEachIndexed { index, interval -> + val selected = interval == settingsState.cacheAutoClearInterval + val itemShape = ShapeDefaults.byIndex( + index = index, + size = CacheAutoClearInterval.entries.size + ) + + PreferenceItem( + onClick = { onValueChange(interval) }, + title = interval.title(), + containerColor = takeColorFromScheme { + if (selected) secondaryContainer + else SafeLocalContainerColor + }, + shape = itemShape, + modifier = Modifier + .fillMaxWidth() + .border( + width = settingsState.borderWidth, + color = animateColorAsState( + if (selected) { + MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.5f) + } else Color.Transparent + ).value, + shape = itemShape + ), + endIcon = if (selected) { + Icons.Rounded.RadioButtonChecked + } else { + Icons.Rounded.RadioButtonUnchecked + } + ) + } + } + } +} + +@Composable +private fun CacheAutoClearInterval.title(): String = stringResource( + when (this) { + CacheAutoClearInterval.OnAppLaunch -> R.string.cache_auto_clear_on_app_launch + CacheAutoClearInterval.Day -> R.string.cache_auto_clear_after_day + CacheAutoClearInterval.Week -> R.string.cache_auto_clear_after_week + CacheAutoClearInterval.Month -> R.string.cache_auto_clear_after_month + } +) \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CacheAutoClearLimitSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CacheAutoClearLimitSettingItem.kt new file mode 100644 index 0000000..4cfffc6 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CacheAutoClearLimitSettingItem.kt @@ -0,0 +1,94 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.runtime.Composable +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.graphics.Shape +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.domain.utils.humanFileSize +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Speed +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.collections.immutable.toPersistentMap +import kotlin.math.abs +import kotlin.math.roundToInt + +@Composable +fun CacheAutoClearLimitSettingItem( + onValueChange: (Long) -> Unit, + shape: Shape = ShapeDefaults.center, + modifier: Modifier = Modifier.padding(horizontal = 8.dp) +) { + val settingsState = LocalSettingsState.current + var selectedIndex by remember(settingsState.cacheAutoClearLimitBytes) { + mutableIntStateOf(settingsState.cacheAutoClearLimitBytes.closestCacheLimitIndex()) + } + + val valuesPreviewMapping = remember { + CacheLimits.mapIndexed { index, bytes -> + index.toFloat() to humanFileSize(bytes) + }.toMap().toPersistentMap() + } + + EnhancedSliderItem( + value = selectedIndex, + title = stringResource(R.string.cache_auto_clear_limit), + modifier = modifier, + icon = Icons.Outlined.Speed, + valueRange = 0f..CacheLimits.lastIndex.toFloat(), + onValueChange = { + selectedIndex = it.toCacheLimitIndex() + }, + onValueChangeFinished = { + onValueChange(CacheLimits[selectedIndex]) + }, + steps = CacheLimits.size - 2, + internalStateTransformation = Float::toCacheLimitIndex, + shape = shape, + valueTextTapEnabled = false, + valuesPreviewMapping = valuesPreviewMapping, + enabled = settingsState.clearCacheOnLaunch, + canInputValue = false + ) +} + +private fun Long.closestCacheLimitIndex(): Int = CacheLimits.indices.minByOrNull { index -> + abs(CacheLimits[index] - this) +} ?: 0 + +private fun Float.toCacheLimitIndex(): Int = roundToInt().coerceIn(CacheLimits.indices) + +private val CacheLimits = listOf( + 20L * 1024 * 1024, + 50L * 1024 * 1024, + 100L * 1024 * 1024, + 250L * 1024 * 1024, + 500L * 1024 * 1024, + 1024L * 1024 * 1024, + 2L * 1024 * 1024 * 1024 +) \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CanEnterPresetsByTextFieldSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CanEnterPresetsByTextFieldSettingItem.kt new file mode 100644 index 0000000..9438a83 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CanEnterPresetsByTextFieldSettingItem.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.WrapText +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.Rounded.WrapText + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CenterAlignDialogButtonsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CenterAlignDialogButtonsSettingItem.kt new file mode 100644 index 0000000..669903e --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CenterAlignDialogButtonsSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ChangeFontSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ChangeFontSettingItem.kt new file mode 100644 index 0000000..234095f --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ChangeFontSettingItem.kt @@ -0,0 +1,85 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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") + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ChangeLanguageSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ChangeLanguageSettingItem.kt new file mode 100644 index 0000000..8cc030c --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ChangeLanguageSettingItem.kt @@ -0,0 +1,236 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Language +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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.imagetoolbox.core.utils.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.Rounded.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 ?: Resources.getSystem().configuration.locales[0] + 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() + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CheckUpdatesButtonSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CheckUpdatesButtonSettingItem.kt new file mode 100644 index 0000000..4ab4653 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CheckUpdatesButtonSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ChecksumAsFilenameSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ChecksumAsFilenameSettingItem.kt new file mode 100644 index 0000000..4b87be0 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ChecksumAsFilenameSettingItem.kt @@ -0,0 +1,111 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.HashTag +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.HashTag, + 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 + } + ) + } + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ClearCacheSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ClearCacheSettingItem.kt new file mode 100644 index 0000000..2d8f034 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ClearCacheSettingItem.kt @@ -0,0 +1,59 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.Memory +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ColorBlindSchemeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ColorBlindSchemeSettingItem.kt new file mode 100644 index 0000000..2720098 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ColorBlindSchemeSettingItem.kt @@ -0,0 +1,186 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonChecked +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.icons.Visibility +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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.Visibility, + endIcon = Icons.Rounded.MiniEdit, + modifier = modifier + ) + + EnhancedModalBottomSheet( + visible = isShowSheet, + onDismiss = { + isShowSheet = false + }, + title = { + TitleItem( + text = stringResource(R.string.color_blind_scheme), + icon = Icons.Rounded.Visibility + ) + }, + 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.deuteranopia + 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 + } + ) diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ColorSchemeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ColorSchemeSettingItem.kt new file mode 100644 index 0000000..d3a8d5e --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ColorSchemeSettingItem.kt @@ -0,0 +1,183 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.background +import androidx.compose.foundation.layout.Box +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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.icons.Palette +import com.t8rin.imagetoolbox.core.resources.icons.PaletteBox +import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiHarmonizationColorSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiHarmonizationColorSettingItem.kt new file mode 100644 index 0000000..08cdd9e --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiHarmonizationColorSettingItem.kt @@ -0,0 +1,250 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Palette +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.Palette + ) + + 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 -> ((value as? ColorHarmonizer.Custom) + ?.color + ?.toColor() + ?: colorScheme.primary) + .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.Palette + ) + }, + 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) + } \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiHarmonizationLevelSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiHarmonizationLevelSettingItem.kt new file mode 100644 index 0000000..9eb987a --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiHarmonizationLevelSettingItem.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiSettingItem.kt new file mode 100644 index 0000000..9e711a0 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiSettingItem.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Celebration +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiTypeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiTypeSettingItem.kt new file mode 100644 index 0000000..98b840b --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiTypeSettingItem.kt @@ -0,0 +1,153 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Emergency +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.Rounded.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) + } \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ContainerShadowsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ContainerShadowsSettingItem.kt new file mode 100644 index 0000000..d9b6111 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ContainerShadowsSettingItem.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ViewComfy +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CornersSizeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CornersSizeSettingItem.kt new file mode 100644 index 0000000..e43cc24 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CornersSizeSettingItem.kt @@ -0,0 +1,100 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Percent +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.Rounded.Percent, + onValueChange = { + value = it.roundToTwoDigits() + }, + onValueChangeFinished = { + trigger++ + }, + internalStateTransformation = { + it.roundToTwoDigits() + }, + valueRange = 0f..2f + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CrashlyticsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CrashlyticsSettingItem.kt new file mode 100644 index 0000000..3ead199 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CrashlyticsSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CurrentVersionCodeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CurrentVersionCodeSettingItem.kt new file mode 100644 index 0000000..73c9991 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CurrentVersionCodeSettingItem.kt @@ -0,0 +1,105 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.foundation.layout.padding +import androidx.compose.foundation.layout.size +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Verified +import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DebugMenuSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DebugMenuSettingItem.kt new file mode 100644 index 0000000..d0f450a --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DebugMenuSettingItem.kt @@ -0,0 +1,153 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.BugReport +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.utils.helper.EnPreview +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 +import com.t8rin.imagetoolbox.feature.settings.presentation.components.additional.AnimatedGradientBox +import com.t8rin.imagetoolbox.feature.settings.presentation.components.additional.FullscreenDebugMenu + +@Composable +fun DebugMenuSettingItem( + shape: Shape = ShapeDefaults.default, + modifier: Modifier = Modifier.padding(horizontal = 8.dp) +) { + val showMenu = rememberSaveable { + mutableStateOf(false) + } + val isNightMode = LocalSettingsState.current.isNightMode + + AnimatedGradientBox( + colors = { + if (isNightMode) { + listOf( + errorContainer.blend( + color = error, + fraction = 0.6f + ), + primary.blend( + color = error, + fraction = 0.4f + ), + error, + ) + } else { + listOf( + error.blend( + color = errorContainer, + fraction = 0.6f + ), + primaryContainer.blend( + color = errorContainer, + fraction = 0.4f + ), + errorContainer, + ) + } + } + ) { + PreferenceItem( + onClick = { + showMenu.value = true + }, + shape = shape, + modifier = modifier, + overrideIconShapeContentColor = true, + containerColor = Color.Transparent, + contentColor = if (isNightMode) { + MaterialTheme.colorScheme.onError + } else { + MaterialTheme.colorScheme.onErrorContainer + }, + title = stringResource(R.string.debug_menu), + subtitle = stringResource(R.string.debug_menu_sub), + startIcon = Icons.TwoTone.BugReport + ) + } + + FullscreenDebugMenu( + showMenuState = showMenu + ) { + EnhancedButton( + onClick = { + throw OutOfMemoryError("TEST") + }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Trigger OutOfMemory crash") + } + EnhancedButton( + onClick = { + throw Throwable("ForegroundServiceDidNotStartInTimeException") + }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Trigger ForegroundServiceDidNotStartInTimeException") + } + EnhancedButton( + onClick = { + throw Throwable("TEST") + }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Trigger regular crash") + } + } +} + +@Composable +private fun PreviewContent(isDarkTheme: Boolean) = ImageToolboxThemeForPreview(isDarkTheme) { + Box(Modifier.padding(16.dp)) { + EnhancedButton( + onClick = {}, + modifier = Modifier.alpha(0f) + ) { } + + DebugMenuSettingItem() + } +} + +@Composable +@EnPreview +private fun PreviewNight() = PreviewContent(true) + +@Composable +@EnPreview +private fun PreviewDay() = PreviewContent(false) \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultColorSpaceSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultColorSpaceSettingItem.kt new file mode 100644 index 0000000..fd54c8c --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultColorSpaceSettingItem.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Palette +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.Palette, + itemContentText = { it.title }, + containerColor = Color.Unspecified, + shape = shape, + modifier = modifier, + selectedItemColor = MaterialTheme.colorScheme.secondary + ) + } +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultDrawColorSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultDrawColorSettingItem.kt new file mode 100644 index 0000000..8d9e915 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultDrawColorSettingItem.kt @@ -0,0 +1,56 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultDrawLineWidthSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultDrawLineWidthSettingItem.kt new file mode 100644 index 0000000..faefa32 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultDrawLineWidthSettingItem.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.LineWeight +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultDrawPathModeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultDrawPathModeSettingItem.kt new file mode 100644 index 0000000..0d7403d --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultDrawPathModeSettingItem.kt @@ -0,0 +1,137 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.CheckBoxOutlineBlank +import com.t8rin.imagetoolbox.core.resources.icons.Circle +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.HourglassEmpty +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.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.icons.Spray +import com.t8rin.imagetoolbox.core.resources.icons.Square +import com.t8rin.imagetoolbox.core.resources.icons.Star +import com.t8rin.imagetoolbox.core.resources.icons.TouchApp +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.Outlined.Star + 15 -> Icons.Rounded.Star + 17 -> Icons.Rounded.FloodFill + 18 -> Icons.Outlined.Spray + else -> Icons.Rounded.HourglassEmpty +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultImageFormatSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultImageFormatSettingItem.kt new file mode 100644 index 0000000..8b02350 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultImageFormatSettingItem.kt @@ -0,0 +1,86 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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) + ) + } + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultQualitySettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultQualitySettingItem.kt new file mode 100644 index 0000000..58123a7 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultQualitySettingItem.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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, + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultResizeTypeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultResizeTypeSettingItem.kt new file mode 100644 index 0000000..08732dd --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultResizeTypeSettingItem.kt @@ -0,0 +1,85 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultScaleModeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultScaleModeSettingItem.kt new file mode 100644 index 0000000..d89fbdc --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultScaleModeSettingItem.kt @@ -0,0 +1,80 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.FitScreen +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) + ) + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DeleteOriginalsAfterSaveSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DeleteOriginalsAfterSaveSettingItem.kt new file mode 100644 index 0000000..08971a7 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DeleteOriginalsAfterSaveSettingItem.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Delete +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 DeleteOriginalsAfterSaveSettingItem( + onClick: () -> Unit, + shape: Shape = ShapeDefaults.default, + modifier: Modifier = Modifier.padding(horizontal = 8.dp) +) { + val settingsState = LocalSettingsState.current + + PreferenceRowSwitch( + shape = shape, + modifier = modifier, + onClick = { onClick() }, + enabled = settingsState.filenameBehavior !is FilenameBehavior.Overwrite, + title = stringResource(R.string.delete_originals_after_save), + subtitle = stringResource(R.string.delete_originals_after_save_sub), + checked = settingsState.deleteOriginalsAfterSave, + startIcon = Icons.Outlined.Delete + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DonateSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DonateSettingItem.kt new file mode 100644 index 0000000..91003fb --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DonateSettingItem.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DragHandleWidthSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DragHandleWidthSettingItem.kt new file mode 100644 index 0000000..9a2254f --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DragHandleWidthSettingItem.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.DragHandle +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DrawBitmapBorderSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DrawBitmapBorderSettingItem.kt new file mode 100644 index 0000000..f0d9ab7 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DrawBitmapBorderSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FilterFrames +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 DrawBitmapBorderSettingItem( + 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.draw_bitmap_border), + subtitle = stringResource(R.string.draw_bitmap_border_sub), + checked = settingsState.drawBitmapBorder, + onClick = { + onClick() + }, + startIcon = Icons.Outlined.FilterFrames + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DynamicColorsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DynamicColorsSettingItem.kt new file mode 100644 index 0000000..260e340 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DynamicColorsSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.FormatColorFill +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.Rounded.FormatColorFill, + title = stringResource(R.string.dynamic_colors), + subtitle = stringResource(R.string.dynamic_colors_sub), + checked = settingsState.isDynamicColors, + onClick = { + onClick() + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EmojiSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EmojiSettingItem.kt new file mode 100644 index 0000000..dd9ab23 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EmojiSettingItem.kt @@ -0,0 +1,200 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.emoji.Emoji +import com.t8rin.imagetoolbox.core.resources.icons.Block +import com.t8rin.imagetoolbox.core.resources.icons.Casino +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 + val settings = LocalSettingsState.current + 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(), + animatedEmoji = emoji + ?.takeIf { settings.useAnimatedEmojis } + ?.let(Emoji::animatedIconFor) + ?.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 + ), + fontSize = MaterialTheme.typography.headlineLarge.fontSize, + onNoEmoji = { + Icon( + imageVector = Icons.Rounded.Block, + contentDescription = null, + modifier = Modifier.size(32.dp) + ) + } + ) + } + } + ) + EmojiSelectionSheet( + selectedEmojiIndex = selectedEmojiIndex, + onEmojiPicked = onUpdateEmoji, + visible = showEmojiDialog, + onDismiss = { + showEmojiDialog = false + } + ) + + EnhancedAlertDialog( + visible = showShoeDescriptionDialog.isNotEmpty(), + icon = { + EmojiItem( + emoji = showShoeDescriptionDialog, + 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, + 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 = "" + } + ) +} diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EmojisCountSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EmojisCountSettingItem.kt new file mode 100644 index 0000000..5df410b --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EmojisCountSettingItem.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.EmojiEmotions +import com.t8rin.imagetoolbox.core.resources.icons.EmojiMultiple +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.Outlined.EmojiEmotions + ) + } + } 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()) + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableBackgroundColorForAlphaFormatsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableBackgroundColorForAlphaFormatsSettingItem.kt new file mode 100644 index 0000000..16831f6 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableBackgroundColorForAlphaFormatsSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Gradient +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.Outlined.Gradient + ) +} diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableLauncherModeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableLauncherModeSettingItem.kt new file mode 100644 index 0000000..c6a9ea5 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableLauncherModeSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableLinksPreviewSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableLinksPreviewSettingItem.kt new file mode 100644 index 0000000..dbb923f --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableLinksPreviewSettingItem.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Link +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.Rounded.Link + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableSheetGesturesSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableSheetGesturesSettingItem.kt new file mode 100644 index 0000000..409f6a6 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableSheetGesturesSettingItem.kt @@ -0,0 +1,49 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.SwipeDown +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 EnableSheetGesturesSettingItem( + 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.sheet_gestures), + subtitle = stringResource(R.string.sheet_gestures_sub), + checked = settingsState.enableSheetGestures, + startIcon = Icons.Outlined.SwipeDown + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableToolExitConfirmationSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableToolExitConfirmationSettingItem.kt new file mode 100644 index 0000000..8d83e49 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableToolExitConfirmationSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ExifWidgetInitialStateSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ExifWidgetInitialStateSettingItem.kt new file mode 100644 index 0000000..8f299ae --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ExifWidgetInitialStateSettingItem.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.DataSaverOff +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.center, + 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, + enabled = !settingsState.isAlwaysClearExif, + onClick = { + onClick() + }, + startIcon = Icons.Rounded.DataSaverOff + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FabAlignmentSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FabAlignmentSettingItem.kt new file mode 100644 index 0000000..4216e1b --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FabAlignmentSettingItem.kt @@ -0,0 +1,126 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.AlignVerticalCenter +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() + ) + } +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FabShadowsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FabShadowsSettingItem.kt new file mode 100644 index 0000000..b72483c --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FabShadowsSettingItem.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FastSettingsSideSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FastSettingsSideSettingItem.kt new file mode 100644 index 0000000..a285fc4 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FastSettingsSideSettingItem.kt @@ -0,0 +1,136 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.RadioButtonChecked +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +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 + }, + ) + } + } + } + } + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FavoriteToolsInGroupedModeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FavoriteToolsInGroupedModeSettingItem.kt new file mode 100644 index 0000000..252d6e5 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FavoriteToolsInGroupedModeSettingItem.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Bookmark +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 FavoriteToolsInGroupedModeSettingItem( + onClick: () -> Unit, + shape: Shape = ShapeDefaults.center, + modifier: Modifier = Modifier.padding(horizontal = 8.dp) +) { + val settingsState = LocalSettingsState.current + PreferenceRowSwitch( + shape = shape, + modifier = modifier, + enabled = settingsState.groupOptionsByTypes, + startIcon = Icons.Outlined.Bookmark, + title = stringResource(R.string.favorite_tools_in_grouped_mode), + subtitle = stringResource(R.string.favorite_tools_in_grouped_mode_sub), + checked = settingsState.showFavoriteToolsInGroupedMode, + onClick = { + onClick() + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FilenamePatternSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FilenamePatternSettingItem.kt new file mode 100644 index 0000000..660554f --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FilenamePatternSettingItem.kt @@ -0,0 +1,152 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Surface +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.tooling.preview.Preview +import androidx.compose.ui.unit.dp +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.ImageSaveTarget +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Description +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.widget.modifier.ShapeDefaults +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem +import com.t8rin.imagetoolbox.core.ui.widget.text.FilenamePatternEditSheet + +@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() + ) + + FilenamePatternEditSheet( + visible = showEditDialog, + onDismiss = { showEditDialog = false }, + value = value, + onValueChange = { value = it }, + onValueChangeFinished = onValueChange, + exampleFilename = { + PreferenceItem( + title = stringResource(R.string.filename), + subtitle = exampleFilename, + 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" + } + ) + } +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FilenamePrefixSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FilenamePrefixSettingItem.kt new file mode 100644 index 0000000..6e18951 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FilenamePrefixSettingItem.kt @@ -0,0 +1,136 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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)) + } + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FilenameSuffixSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FilenameSuffixSettingItem.kt new file mode 100644 index 0000000..74847ea --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FilenameSuffixSettingItem.kt @@ -0,0 +1,129 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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)) + } + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FlingTypeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FlingTypeSettingItem.kt new file mode 100644 index 0000000..c9967f4 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FlingTypeSettingItem.kt @@ -0,0 +1,172 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +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.resources.icons.RadioButtonChecked +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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.Rounded.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) + } \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FontScaleSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FontScaleSettingItem.kt new file mode 100644 index 0000000..3df2893 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FontScaleSettingItem.kt @@ -0,0 +1,97 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Rounded.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) + ) + } + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FreeSoftwarePartnerSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FreeSoftwarePartnerSettingItem.kt new file mode 100644 index 0000000..586b5e2 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FreeSoftwarePartnerSettingItem.kt @@ -0,0 +1,55 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.PARTNER_FREE_SOFTWARE +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.HandshakeAlt +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRow + +@Composable +fun FreeSoftwarePartnerSettingItem( + shape: Shape = ShapeDefaults.center, + modifier: Modifier = Modifier.padding(horizontal = 8.dp) +) { + 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) + .compositeOverSafe(MaterialTheme.colorScheme.surface), + contentColor = MaterialTheme.colorScheme.onSecondaryContainer.copy(0.9f), + modifier = modifier + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/GeneratePreviewsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/GeneratePreviewsSettingItem.kt new file mode 100644 index 0000000..ec287b2 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/GeneratePreviewsSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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.center, + 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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/GroupOptionsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/GroupOptionsSettingItem.kt new file mode 100644 index 0000000..e8d9cd9 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/GroupOptionsSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.center, + 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() + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/HelpTipsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/HelpTipsSettingItem.kt new file mode 100644 index 0000000..8d53ab9 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/HelpTipsSettingItem.kt @@ -0,0 +1,124 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.Column +import androidx.compose.foundation.layout.padding +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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Lightbulb +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.utils.helper.EnPreview +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.PreferenceItemOverload +import com.t8rin.imagetoolbox.feature.settings.presentation.components.additional.AnimatedGradientBox + +@Composable +fun HelpTipsSettingItem( + onClick: () -> Unit, + modifier: Modifier = Modifier.padding(horizontal = 8.dp), + shape: Shape = ShapeDefaults.default, +) { + val isNightMode = LocalSettingsState.current.isNightMode + + AnimatedGradientBox( + colors = { + if (isNightMode) { + listOf( + primaryContainer.blend( + color = primary, + fraction = 0.8f + ), + secondaryContainer.blend( + color = primary, + fraction = 0.65f + ), + tertiary, + ) + } else { + listOf( + primary.blend( + color = primaryContainer, + fraction = 0.8f + ), + secondary.blend( + color = primaryContainer, + fraction = 0.65f + ), + tertiaryContainer, + ) + } + } + ) { + PreferenceItemOverload( + shape = shape, + onClick = onClick, + startIcon = { + Icon( + imageVector = Icons.TwoTone.Lightbulb, + contentDescription = null + ) + }, + overrideIconShapeContentColor = true, + containerColor = Color.Transparent, + contentColor = if (isNightMode) { + MaterialTheme.colorScheme.onTertiary + } else { + MaterialTheme.colorScheme.onTertiaryContainer + }, + title = stringResource(R.string.help_tips), + subtitle = stringResource(R.string.help_tips_settings_sub), + modifier = modifier + ) + } +} + +@Composable +private fun PreviewContent(isDarkTheme: Boolean) = ImageToolboxThemeForPreview( + isDarkTheme = isDarkTheme, + keyColor = Color.Green +) { + Box(Modifier.padding(16.dp)) { + EnhancedButton( + onClick = {}, + modifier = Modifier.alpha(0f) + ) { } + + HelpTipsSettingItem({}) + } +} + +@Composable +@EnPreview +private fun Preview() = Column { + PreviewContent(true) + PreviewContent(false) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/HelpTranslateSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/HelpTranslateSettingItem.kt new file mode 100644 index 0000000..dbe8871 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/HelpTranslateSettingItem.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.Translate +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) + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/IconShapeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/IconShapeSettingItem.kt new file mode 100644 index 0000000..4bf7693 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/IconShapeSettingItem.kt @@ -0,0 +1,249 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Block +import com.t8rin.imagetoolbox.core.resources.icons.FormatShapes +import com.t8rin.imagetoolbox.core.resources.icons.Shuffle +import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 + ) + ) + } + } +} diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ImagePickerModeSettingItemGroup.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ImagePickerModeSettingItemGroup.kt new file mode 100644 index 0000000..979734c --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ImagePickerModeSettingItemGroup.kt @@ -0,0 +1,98 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonChecked +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 + ) + ) + } + } +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/IssueTrackerSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/IssueTrackerSettingItem.kt new file mode 100644 index 0000000..13bbf0d --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/IssueTrackerSettingItem.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.resources.icons.BugReport +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) + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/KeepDateTimeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/KeepDateTimeSettingItem.kt new file mode 100644 index 0000000..ddbcde6 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/KeepDateTimeSettingItem.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AccessTime +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, + enabled = !settingsState.isAlwaysClearExif, + onClick = { + onClick() + }, + startIcon = Icons.Outlined.AccessTime + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/LockDrawOrientationSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/LockDrawOrientationSettingItem.kt new file mode 100644 index 0000000..f1a4828 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/LockDrawOrientationSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/MagnifierSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/MagnifierSettingItem.kt new file mode 100644 index 0000000..fb6c762 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/MagnifierSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ZoomIn +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.center, + 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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/MainScreenTitleSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/MainScreenTitleSettingItem.kt new file mode 100644 index 0000000..741a0a6 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/MainScreenTitleSettingItem.kt @@ -0,0 +1,133 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Title +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)) + } + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/MotionDurationScaleSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/MotionDurationScaleSettingItem.kt new file mode 100644 index 0000000..120d8df --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/MotionDurationScaleSettingItem.kt @@ -0,0 +1,70 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.MotionPlay +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 MotionDurationScaleSettingItem( + onValueChange: (Float) -> Unit, + shape: Shape = ShapeDefaults.center, + modifier: Modifier = Modifier.padding(horizontal = 8.dp) +) { + val settingsState = LocalSettingsState.current + var value by remember(settingsState.motionDurationScale) { + mutableFloatStateOf(settingsState.motionDurationScale.roundToStep()) + } + + EnhancedSliderItem( + modifier = modifier, + shape = shape, + value = value, + title = stringResource(R.string.motion_duration_scale), + icon = Icons.Outlined.MotionPlay, + valueSuffix = "x", + onValueChange = { + value = it.roundToStep() + }, + onValueChangeFinished = { + onValueChange(value) + }, + internalStateTransformation = { + it.roundToStep() + }, + valueRange = 0f..5f, + steps = 19 + ) +} + +private fun Float.roundToStep(): Float = (this * 4f).roundToInt() / 4f \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/NightModeSettingItemGroup.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/NightModeSettingItemGroup.kt new file mode 100644 index 0000000..07d12ba --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/NightModeSettingItemGroup.kt @@ -0,0 +1,104 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.DarkMode +import com.t8rin.imagetoolbox.core.resources.icons.LightMode +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonChecked +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.icons.SettingsSuggest +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 + ) + } + } +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OneTimeSaveLocationSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OneTimeSaveLocationSettingItem.kt new file mode 100644 index 0000000..4a4055d --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OneTimeSaveLocationSettingItem.kt @@ -0,0 +1,59 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.LocationSearching +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OpenEditInsteadOfPreviewSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OpenEditInsteadOfPreviewSettingItem.kt new file mode 100644 index 0000000..b621783 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OpenEditInsteadOfPreviewSettingItem.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OpenSourceLicensesSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OpenSourceLicensesSettingItem.kt new file mode 100644 index 0000000..58d54a0 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OpenSourceLicensesSettingItem.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OverwriteFilesSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OverwriteFilesSettingItem.kt new file mode 100644 index 0000000..1aaf8bb --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OverwriteFilesSettingItem.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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.deleteOriginalsAfterSave && !settingsState.saveToOriginalFolder && + (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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/PresetsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/PresetsSettingItem.kt new file mode 100644 index 0000000..d0c50b6 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/PresetsSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.HashTag +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.Rounded.HashTag, + endIcon = Icons.Rounded.MiniEdit, + modifier = modifier + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/RandomizeFilenameSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/RandomizeFilenameSettingItem.kt new file mode 100644 index 0000000..5d7db86 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/RandomizeFilenameSettingItem.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ReplaceSequenceNumberSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ReplaceSequenceNumberSettingItem.kt new file mode 100644 index 0000000..30d23a3 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ReplaceSequenceNumberSettingItem.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ResetSettingsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ResetSettingsSettingItem.kt new file mode 100644 index 0000000..5ef9ab3 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ResetSettingsSettingItem.kt @@ -0,0 +1,82 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.RestartAlt +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/RestoreSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/RestoreSettingItem.kt new file mode 100644 index 0000000..bb0b1a7 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/RestoreSettingItem.kt @@ -0,0 +1,50 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SaveToOriginalFolderSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SaveToOriginalFolderSettingItem.kt new file mode 100644 index 0000000..34e98c3 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SaveToOriginalFolderSettingItem.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FolderOpen +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 SaveToOriginalFolderSettingItem( + onClick: () -> Unit, + shape: Shape = ShapeDefaults.default, + modifier: Modifier = Modifier.padding(horizontal = 8.dp) +) { + val settingsState = LocalSettingsState.current + PreferenceRowSwitch( + shape = shape, + modifier = modifier, + onClick = { + onClick() + }, + enabled = settingsState.filenameBehavior !is FilenameBehavior.Overwrite, + title = stringResource(R.string.save_to_original_folder), + subtitle = stringResource(R.string.save_to_original_folder_sub), + checked = settingsState.saveToOriginalFolder, + startIcon = Icons.Outlined.FolderOpen + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SavingFolderSettingItemGroup.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SavingFolderSettingItemGroup.kt new file mode 100644 index 0000000..e170c5f --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SavingFolderSettingItemGroup.kt @@ -0,0 +1,113 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FolderShared +import com.t8rin.imagetoolbox.core.resources.icons.FolderSpecial +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 enabled = !settingsState.saveToOriginalFolder + val launcher = rememberFolderPicker( + onSuccess = onValueChange + ) + + PreferenceItem( + shape = ShapeDefaults.top, + onClick = { onValueChange(null) }, + title = stringResource(R.string.def), + subtitle = stringResource(R.string.default_folder), + enabled = enabled, + 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) + ), + enabled = enabled, + 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 + ) + ) + } +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ScreenOrderSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ScreenOrderSettingItem.kt new file mode 100644 index 0000000..446ed21 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ScreenOrderSettingItem.kt @@ -0,0 +1,192 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.DragHandle +import com.t8rin.imagetoolbox.core.resources.icons.FormatLineSpacing +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.Rounded.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 + ) + ) + } + } + } + } + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ScreenSearchSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ScreenSearchSettingItem.kt new file mode 100644 index 0000000..c5a57f7 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ScreenSearchSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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() + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SearchableSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SearchableSettingItem.kt new file mode 100644 index 0000000..452f776 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SearchableSettingItem.kt @@ -0,0 +1,103 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) { + 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 + ) + } + Spacer(Modifier.height(8.dp)) + } +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SecureModeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SecureModeSettingItem.kt new file mode 100644 index 0000000..4a46c6d --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SecureModeSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.Security +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.Rounded.Security + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SendLogsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SendLogsSettingItem.kt new file mode 100644 index 0000000..12e65ab --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SendLogsSettingItem.kt @@ -0,0 +1,96 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.material3.Icon +import androidx.compose.material3.MaterialTheme +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.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.Icons +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: () -> Unit, + isSendingLogs: Boolean, + modifier: Modifier = Modifier.padding(horizontal = 8.dp), + shape: Shape = ShapeDefaults.center, + color: Color = MaterialTheme.colorScheme.mixedContainer.copy(0.9f), + contentColor: Color = MaterialTheme.colorScheme.onMixedContainer +) { + val progressAnimatable = remember { Animatable(if (isSendingLogs) 1f else 0f) } + val progress = progressAnimatable.value + + LaunchedEffect(isSendingLogs) { + delay(400) + if (isSendingLogs) { + progressAnimatable.animateTo( + targetValue = 1f, + animationSpec = springySpec() + ) + } else { + progressAnimatable.animateTo( + targetValue = 0f, + animationSpec = tween(200) + ) + } + } + + PreferenceItemOverload( + contentColor = contentColor, + shape = shape, + onClick = onClick, + 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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SettingGroupItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SettingGroupItem.kt new file mode 100644 index 0000000..bc75d66 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SettingGroupItem.kt @@ -0,0 +1,98 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Settings +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, + forceExpanded: Boolean = false, + modifier: Modifier = Modifier + .fillMaxWidth() + .padding(2.dp), + content: @Composable ColumnScope.(Boolean) -> Unit +) { + val settingsState = LocalSettingsState.current + + val initialState = + forceExpanded || (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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SettingItem.kt new file mode 100644 index 0000000..0d1ec08 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SettingItem.kt @@ -0,0 +1,712 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.arkivanov.decompose.extensions.compose.subscribeAsState +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Save +import com.t8rin.imagetoolbox.core.resources.icons.TextFields +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.navigation.Screen +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, + containerColor: Color = MaterialTheme.colorScheme.surface, +) { + val isUpdateAvailable by component.isUpdateAvailable.subscribeAsState() + + 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.AppLogs -> { + AppLogsSettingItem( + onClick = { + component.onNavigate(Screen.AppLogs) + } + ) + } + + Setting.AppUsageStatistics -> { + AppUsageStatisticsSettingItem( + onClick = { + component.onNavigate(Screen.UsageStatistics) + } + ) + } + + 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) { + component.onNavigate(Screen.EasterEgg) + clicks = 0 + } + + delay(500L) //debounce + + if (clicks == 0) return@LaunchedEffect + + AppToastHost.dismissToasts() + if (clicks == 1) { + component.tryGetUpdate(true) + } + } + + 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.FavoriteToolsInGroupedMode -> { + FavoriteToolsInGroupedModeSettingItem( + onClick = component::toggleFavoriteToolsInGroupedMode + ) + } + + Setting.ShowFavoriteAsLast -> { + ShowFavoriteAsLastSettingItem(onClick = component::toggleFavoriteAsLast) + } + + Setting.HelpTranslate -> { + HelpTranslateSettingItem() + } + + Setting.HelpTips -> { + HelpTipsSettingItem( + onClick = { + component.onNavigate(Screen.Help()) + } + ) + } + + 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.SaveToOriginalFolder -> { + SaveToOriginalFolderSettingItem(onClick = component::toggleSaveToOriginalFolder) + } + + Setting.DeleteOriginalsAfterSave -> { + DeleteOriginalsAfterSaveSettingItem( + onClick = component::toggleDeleteOriginalsAfterSave + ) + } + + 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) + } + ) + } + + 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.DrawBitmapBorder -> { + DrawBitmapBorderSettingItem(onClick = component::toggleDrawBitmapBorder) + } + + 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.UseAnimatedEmojis -> { + UseAnimatedEmojisSettingItem(onClick = component::toggleUseAnimatedEmojis) + } + + Setting.IconShape -> { + IconShapeSettingItem( + value = component.settingsState.iconShape, + onValueChange = component::setIconShape + ) + } + + Setting.DragHandleWidth -> { + DragHandleWidthSettingItem(onValueChange = component::setDragHandleWidth) + } + + Setting.ShapeByInteractionThrottle -> { + ShapeByInteractionThrottleSettingItem( + onValueChange = component::setShapeByInteractionThrottle + ) + } + + 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.EnableSheetGestures -> { + EnableSheetGesturesSettingItem(onClick = component::toggleEnableSheetGestures) + } + + Setting.SkipFilePicking -> { + SkipImagePickingSettingItem(onClick = component::toggleSkipImagePicking) + } + + Setting.ShowSettingsInLandscape -> { + ShowSettingsInLandscapeSettingItem(onClick = component::toggleShowSettingsInLandscape) + } + + Setting.UseFullscreenSettings -> { + UseFullscreenSettingsSettingItem( + onClick = component::toggleUseFullscreenSettings, + onNavigateToSettings = { + component.onNavigate(Screen.Settings()) + } + ) + } + + 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 = { + component.onNavigate(Screen.LibrariesInfo) + } + ) + } + + Setting.FastSettingsSide -> { + FastSettingsSideSettingItem(onValueChange = component::setFastSettingsSide) + } + + Setting.ChecksumAsFilename -> { + ChecksumAsFilenameSettingItem(onValueChange = component::setChecksumTypeForFilename) + } + + Setting.EnableToolExitConfirmation -> { + EnableToolExitConfirmationSettingItem(onClick = component::toggleEnableToolExitConfirmation) + } + + Setting.SendLogs -> { + SendLogsSettingItem( + onClick = component::shareLogs, + isSendingLogs = component.isSendingLogs + ) + } + + 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.MotionDurationScale -> { + MotionDurationScaleSettingItem(onValueChange = component::setMotionDurationScale) + } + + Setting.ToolsHiddenForShare -> { + ToolsHiddenForShareSettingItem(onValueChange = component::setHiddenForShareScreens) + } + + Setting.KeepDateTime -> { + KeepDateTimeSettingItem(onClick = component::toggleKeepDateTime) + } + + Setting.AlwaysClearExif -> { + AlwaysClearExifSettingItem(onClick = component::toggleAlwaysClearExif) + } + + Setting.EnableBackgroundColorForAlphaFormats -> { + EnableBackgroundColorForAlphaFormatsSettingItem(onClick = component::toggleEnableBackgroundColorForAlphaFormats) + } + + Setting.DebugMenu -> { + DebugMenuSettingItem() + } + + Setting.ShowToolsHistory -> { + ShowToolsHistorySettingItem(onClick = component::toggleShowToolsHistory) + } + + Setting.CacheAutoClearLimit -> { + CacheAutoClearLimitSettingItem( + onValueChange = component::setCacheAutoClearLimitBytes + ) + } + + Setting.CacheAutoClearInterval -> { + CacheAutoClearIntervalSettingItem( + onValueChange = component::setCacheAutoClearInterval + ) + } + } + } +} diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShapeByInteractionThrottleSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShapeByInteractionThrottleSettingItem.kt new file mode 100644 index 0000000..66bd0c3 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShapeByInteractionThrottleSettingItem.kt @@ -0,0 +1,68 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Timelapse +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 +import kotlin.math.roundToLong + +@Composable +fun ShapeByInteractionThrottleSettingItem( + onValueChange: (Long) -> Unit, + shape: Shape = ShapeDefaults.center, + modifier: Modifier = Modifier.padding(horizontal = 8.dp) +) { + val settingsState = LocalSettingsState.current + var value by remember(settingsState.shapeByInteractionThrottle) { + mutableFloatStateOf(settingsState.shapeByInteractionThrottle.toFloat()) + } + + EnhancedSliderItem( + modifier = modifier, + shape = shape, + valueSuffix = " ms", + value = value, + title = stringResource(R.string.shape_by_interaction_throttle), + icon = Icons.Outlined.Timelapse, + onValueChange = { + value = it.roundToInt().toFloat() + }, + onValueChangeFinished = { + onValueChange(value.roundToLong()) + }, + internalStateTransformation = { + it.roundToInt() + }, + valueRange = 0f..500f + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShapeTypeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShapeTypeSettingItem.kt new file mode 100644 index 0000000..1fa61f8 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShapeTypeSettingItem.kt @@ -0,0 +1,191 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonChecked +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.icons.RoundedCorner +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 = remember(type) { + AutoCornersShape( + size = when (type) { + is ShapeType.Smooth -> 8.dp + is ShapeType.Squircle -> 24.dp + is ShapeType.Notch -> 4.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 + is ShapeType.Wavy -> R.string.wavy + is ShapeType.Scoop -> R.string.scoop + is ShapeType.Notch -> R.string.notch +} + +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 + is ShapeType.Wavy -> R.string.wavy_shapes_sub + is ShapeType.Scoop -> R.string.scoop_shapes_sub + is ShapeType.Notch -> R.string.notch_shapes_sub +} diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShowFavoriteAsLastSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShowFavoriteAsLastSettingItem.kt new file mode 100644 index 0000000..0ab1f4c --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShowFavoriteAsLastSettingItem.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.LastPage +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 ShowFavoriteAsLastSettingItem( + onClick: () -> Unit, + shape: Shape = ShapeDefaults.bottom, + modifier: Modifier = Modifier.padding(horizontal = 8.dp) +) { + val settingsState = LocalSettingsState.current + PreferenceRowSwitch( + shape = shape, + modifier = modifier, + enabled = settingsState.showFavoriteToolsInGroupedMode || !settingsState.groupOptionsByTypes, + startIcon = Icons.Outlined.LastPage, + title = stringResource(R.string.show_favorite_as_last), + subtitle = stringResource(R.string.show_favorite_as_last_sub), + checked = settingsState.showFavoriteAsLast, + onClick = { + onClick() + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShowSettingsInLandscapeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShowSettingsInLandscapeSettingItem.kt new file mode 100644 index 0000000..8e8754c --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShowSettingsInLandscapeSettingItem.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShowSystemBarsBySwipeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShowSystemBarsBySwipeSettingItem.kt new file mode 100644 index 0000000..2a8ea83 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShowSystemBarsBySwipeSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.SwipeVertical +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShowToolsHistorySettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShowToolsHistorySettingItem.kt new file mode 100644 index 0000000..dee19c3 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShowToolsHistorySettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.History +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 ShowToolsHistorySettingItem( + 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.show_recent_tools), + subtitle = stringResource(R.string.show_recent_tools_sub), + checked = settingsState.showToolsHistory, + onClick = { + onClick() + }, + startIcon = Icons.Rounded.History + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SkipImagePickingSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SkipImagePickingSettingItem.kt new file mode 100644 index 0000000..fe4edf7 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SkipImagePickingSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.SkipNext +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SliderShadowsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SliderShadowsSettingItem.kt new file mode 100644 index 0000000..4d17dac --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SliderShadowsSettingItem.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SliderTypeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SliderTypeSettingItem.kt new file mode 100644 index 0000000..b1cdf54 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SliderTypeSettingItem.kt @@ -0,0 +1,174 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Android +import com.t8rin.imagetoolbox.core.resources.icons.AutoAwesome +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.RadioButtonChecked +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.icons.Slider +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 + } \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SnowfallModeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SnowfallModeSettingItem.kt new file mode 100644 index 0000000..218542f --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SnowfallModeSettingItem.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SourceCodeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SourceCodeSettingItem.kt new file mode 100644 index 0000000..4c76d3c --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SourceCodeSettingItem.kt @@ -0,0 +1,69 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) + } +} + diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SwitchShadowsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SwitchShadowsSettingItem.kt new file mode 100644 index 0000000..3bfa4d4 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SwitchShadowsSettingItem.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.ToggleOff +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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SwitchTypeSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SwitchTypeSettingItem.kt new file mode 100644 index 0000000..b1d3269 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SwitchTypeSettingItem.kt @@ -0,0 +1,191 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Android +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.RadioButtonChecked +import com.t8rin.imagetoolbox.core.resources.icons.RadioButtonUnchecked +import com.t8rin.imagetoolbox.core.resources.icons.SamsungLetter +import com.t8rin.imagetoolbox.core.resources.icons.ToggleOff +import com.t8rin.imagetoolbox.core.resources.icons.ToggleOn +import com.t8rin.imagetoolbox.core.resources.icons.Water +import com.t8rin.imagetoolbox.core.resources.icons.Windows +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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 + } \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SystemBarsVisibilitySettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SystemBarsVisibilitySettingItem.kt new file mode 100644 index 0000000..16175bd --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SystemBarsVisibilitySettingItem.kt @@ -0,0 +1,115 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + } + ) \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/TelegramChannelSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/TelegramChannelSettingItem.kt new file mode 100644 index 0000000..69dd91d --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/TelegramChannelSettingItem.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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_CHANNEL_LINK +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.RssFeed +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.Rounded.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/TelegramGroupSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/TelegramGroupSettingItem.kt new file mode 100644 index 0000000..bf4c9e7 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/TelegramGroupSettingItem.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ToolsHiddenForShareSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ToolsHiddenForShareSettingItem.kt new file mode 100644 index 0000000..8e24cde --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ToolsHiddenForShareSettingItem.kt @@ -0,0 +1,164 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +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.hidden_for_share), + 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) + } + ) + } + } + } + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseAnimatedEmojisSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseAnimatedEmojisSettingItem.kt new file mode 100644 index 0000000..75422bd --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseAnimatedEmojisSettingItem.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.MotionMode +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 UseAnimatedEmojisSettingItem( + 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.animated_emojis), + subtitle = stringResource(R.string.animated_emojis_sub), + checked = settingsState.useAnimatedEmojis, + enabled = settingsState.selectedEmoji != null, + onClick = { + onClick() + }, + startIcon = Icons.Outlined.MotionMode + ) +} diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseCompactSelectorsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseCompactSelectorsSettingItem.kt new file mode 100644 index 0000000..7cd0d93 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseCompactSelectorsSettingItem.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseFormattedFilenameTimestampSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseFormattedFilenameTimestampSettingItem.kt new file mode 100644 index 0000000..78cce8a --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseFormattedFilenameTimestampSettingItem.kt @@ -0,0 +1,63 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.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.resources.icons.FormatParagraph +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.Rounded.FormatParagraph + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseFullscreenSettingsSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseFullscreenSettingsSettingItem.kt new file mode 100644 index 0000000..2f5dfba --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseFullscreenSettingsSettingItem.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.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.Rounded.Fullscreen + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseRandomEmojisSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseRandomEmojisSettingItem.kt new file mode 100644 index 0000000..979a140 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseRandomEmojisSettingItem.kt @@ -0,0 +1,61 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/VibrationStrengthSettingItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/VibrationStrengthSettingItem.kt new file mode 100644 index 0000000..fa7d46e --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/VibrationStrengthSettingItem.kt @@ -0,0 +1,66 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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 + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/AnimatedGradientBox.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/AnimatedGradientBox.kt new file mode 100644 index 0000000..9b134cf --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/AnimatedGradientBox.kt @@ -0,0 +1,217 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.Animatable +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.layout.Box +import androidx.compose.material3.ColorScheme +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.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged +import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState +import com.t8rin.imagetoolbox.core.ui.utils.animation.AlphaEasing +import com.t8rin.imagetoolbox.core.ui.utils.animation.SoftEasing +import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalContainerShape +import com.t8rin.imagetoolbox.core.ui.utils.provider.ProvideContainerDefaults +import kotlinx.coroutines.isActive +import kotlin.math.hypot +import kotlin.random.Random + +@Composable +internal fun AnimatedGradientBox( + colors: ColorScheme.() -> List, + content: @Composable () -> Unit = {} +) { + var size by remember { mutableStateOf(Size.Zero) } + + val progress = remember { Animatable(1f) } + + var from by remember { mutableStateOf(Offset.Zero) } + var to by remember { mutableStateOf(Offset.Zero) } + var control by remember { mutableStateOf(Offset.Zero) } + val motionDurationScale = LocalSettingsState.current.motionDurationScale + + LaunchedEffect(size, motionDurationScale) { + if (size == Size.Zero || motionDurationScale <= 0f) return@LaunchedEffect + + from = Offset(size.width / 2f, size.height / 2f) + to = randomOffset(size, from) + control = arcControlPoint(from, to, size) + + while (isActive) { + progress.snapTo(0f) + + val distance = (to - from).getDistance() + + progress.animateTo( + targetValue = 1f, + animationSpec = tween( + durationMillis = distance + .coerceIn(500f, 1800f) + .toInt() + 1200, + easing = SoftEasing + ) + ) + + from = to + to = randomOffset(size, from) + control = arcControlPoint(from, to, size) + } + } + + val transition = rememberInfiniteTransition(label = "gradient-radius") + + val radius by transition.animateFloat( + initialValue = 400f, + targetValue = 700f, + animationSpec = infiniteRepeatable( + animation = tween( + durationMillis = 3600, + easing = AlphaEasing + ), + repeatMode = RepeatMode.Reverse + ), + label = "radius" + ) + + val center = quadraticBezier( + from = from, + control = control, + to = to, + t = progress.value + ) + + Box( + modifier = Modifier.onSizeChanged { + size = Size( + width = it.width.toFloat(), + height = it.height.toFloat() + ) + } + ) { + val colorScheme = MaterialTheme.colorScheme + + ProvideContainerDefaults( + brush = Brush.radialGradient( + colors = remember(colorScheme) { + colors(colorScheme) + }, + center = center, + radius = radius + ), + shape = LocalContainerShape.current, + content = content + ) + } +} + +private fun randomOffset( + size: Size, + from: Offset, + minDistanceFactor: Float = 0.45f +): Offset { + val paddingX = size.width * 0.15f + val paddingY = size.height * 0.15f + + val minDistance = minOf(size.width, size.height) * minDistanceFactor + + repeat(12) { + val offset = Offset( + x = Random.nextFloat() * (size.width + paddingX * 2f) - paddingX, + y = Random.nextFloat() * (size.height + paddingY * 2f) - paddingY + ) + + if ((offset - from).getDistance() >= minDistance) { + return offset + } + } + + return Offset( + x = size.width - from.x + Random.nextFloat() * paddingX - paddingX / 2f, + y = size.height - from.y + Random.nextFloat() * paddingY - paddingY / 2f + ) +} + +private fun arcControlPoint( + from: Offset, + to: Offset, + size: Size +): Offset { + val mid = Offset( + x = (from.x + to.x) / 2f, + y = (from.y + to.y) / 2f + ) + + val dx = to.x - from.x + val dy = to.y - from.y + + val distance = hypot(dx, dy).coerceAtLeast(1f) + val maxArcPower = minOf(size.width, size.height) * 0.45f + val arcPower = (distance * Random.nextFloatIn(0.35f, 0.75f)) + .coerceAtMost(maxArcPower) + + val direction = if (Random.nextBoolean()) 1f else -1f + + val normalX = -dy / distance + val normalY = dx / distance + + return Offset( + x = mid.x + normalX * arcPower * direction, + y = mid.y + normalY * arcPower * direction + ) +} + +private fun Random.nextFloatIn( + from: Float, + to: Float +): Float { + return from + nextFloat() * (to - from) +} + +private fun quadraticBezier( + from: Offset, + control: Offset, + to: Offset, + t: Float +): Offset { + val oneMinusT = 1f - t + + return Offset( + x = oneMinusT * oneMinusT * from.x + + 2f * oneMinusT * t * control.x + + t * t * to.x, + y = oneMinusT * oneMinusT * from.y + + 2f * oneMinusT * t * control.y + + t * t * to.y + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/AuthorLinksSheet.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/AuthorLinksSheet.kt new file mode 100644 index 0000000..f99ac41 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/AuthorLinksSheet.kt @@ -0,0 +1,145 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.rememberScrollState +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_EMAIL +import com.t8rin.imagetoolbox.core.domain.AUTHOR_GITHUB +import com.t8rin.imagetoolbox.core.domain.AUTHOR_TELEGRAM +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AlternateEmail +import com.t8rin.imagetoolbox.core.resources.icons.Forum +import com.t8rin.imagetoolbox.core.resources.icons.Github +import com.t8rin.imagetoolbox.core.resources.icons.Link +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 +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 + +@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 + + 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 = ShapeDefaults.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 = { + runCatching { + activity!!.startActivity( + Intent(Intent.ACTION_SENDTO).apply { + data = "mailto:$AUTHOR_EMAIL".toUri() + } + ) + }.onFailure { + appContext.shareText(AUTHOR_EMAIL) + } + }, + shape = ShapeDefaults.center, + endIcon = Icons.Rounded.Link, + title = stringResource(R.string.email), + startIcon = Icons.Rounded.AlternateEmail, + subtitle = AUTHOR_EMAIL + ) + Spacer(Modifier.height(4.dp)) + PreferenceItem( + containerColor = MaterialTheme.colorScheme.primaryContainer, + onClick = { + linkHandler.openUri(AUTHOR_GITHUB) + }, + endIcon = Icons.Rounded.Link, + shape = ShapeDefaults.bottom, + title = stringResource(R.string.github), + startIcon = Icons.Rounded.Github, + subtitle = stringResource(R.string.app_developer_nick) + ) + Spacer(Modifier.height(16.dp)) + } + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/DonateContainerContent.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/DonateContainerContent.kt new file mode 100644 index 0000000..0a754e3 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/DonateContainerContent.kt @@ -0,0 +1,216 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.ContentCopy +import com.t8rin.imagetoolbox.core.resources.icons.Link +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 + ) + ) + } + } + } +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/DonateDialog.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/DonateDialog.kt new file mode 100644 index 0000000..17879b6 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/DonateDialog.kt @@ -0,0 +1,124 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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)) + } + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/DonateSheet.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/DonateSheet.kt new file mode 100644 index 0000000..2f87caa --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/DonateSheet.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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()) + ) + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/FabPreview.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/FabPreview.kt new file mode 100644 index 0000000..e6faed5 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/FabPreview.kt @@ -0,0 +1,161 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt +import com.t8rin.imagetoolbox.core.resources.icons.Image +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 + +@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) + .autoElevatedBorder( + 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)) + } + } + } +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/FontItem.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/FontItem.kt new file mode 100644 index 0000000..a6cf130 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/FontItem.kt @@ -0,0 +1,126 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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) + } + ) + } +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/FullscreenDebugMenu.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/FullscreenDebugMenu.kt new file mode 100644 index 0000000..77448c6 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/FullscreenDebugMenu.kt @@ -0,0 +1,180 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.ColumnScope +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +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.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.produceState +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.graphicsLayer +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.ui.utils.helper.PredictiveBackObserver +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.enhancedVerticalScroll +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.other.TopAppBarEmoji +import com.t8rin.modalsheet.FullscreenPopup +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collectLatest + +@Composable +internal fun FullscreenDebugMenu( + showMenuState: MutableState, + content: @Composable ColumnScope.() -> Unit +) { + var showMenu by showMenuState + val onDismiss = { showMenu = false } + + val dialogVisible by produceState(showMenu) { + snapshotFlow { showMenu } + .collectLatest { + if (!it) delay(600) + value = it + } + } + val visible by produceState(showMenu) { + snapshotFlow { showMenu } + .collectLatest { + if (it) delay(100) + value = it + } + } + + var predictiveBackProgress by remember { + mutableFloatStateOf(0f) + } + + LaunchedEffect(predictiveBackProgress, visible) { + if (!visible && predictiveBackProgress != 0f) { + delay(400) + predictiveBackProgress = 0f + } + } + + if (dialogVisible) { + FullscreenPopup { + AnimatedVisibility( + visible = visible, + modifier = Modifier.fillMaxSize(), + enter = fadeIn(tween(400)), + exit = fadeOut(tween(400)) + ) { + 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 + this.shape = corners.toShape(animatedPredictiveBackProgress) + clip = true + } + } + ) { + Scaffold( + topBar = { + EnhancedTopAppBar( + title = { + Text(stringResource(R.string.debug_menu)) + }, + navigationIcon = { + EnhancedIconButton( + onClick = onDismiss + ) { + Icon( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = stringResource(R.string.close) + ) + } + }, + actions = { + TopAppBarEmoji() + } + ) + }, + contentWindowInsets = WindowInsets() + ) { contentPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .enhancedVerticalScroll(rememberScrollState()) + .padding(contentPadding) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + content = content + ) + } + } + if (visible) { + PredictiveBackObserver( + onProgress = { + predictiveBackProgress = it / 6f + }, + onClean = { isCompleted -> + if (isCompleted) { + onDismiss() + delay(400) + } + predictiveBackProgress = 0f + } + ) + } + } + } + } +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/PickFontFamilySheet.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/PickFontFamilySheet.kt new file mode 100644 index 0000000..9554ba9 --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/PickFontFamilySheet.kt @@ -0,0 +1,198 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.Extension +import com.t8rin.imagetoolbox.core.resources.icons.FileExport +import com.t8rin.imagetoolbox.core.resources.icons.FileImport +import com.t8rin.imagetoolbox.core.resources.icons.FontDownload +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), + ) + } + ) +} \ No newline at end of file diff --git a/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/screenLogic/SettingsComponent.kt b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/screenLogic/SettingsComponent.kt new file mode 100644 index 0000000..3a9f49b --- /dev/null +++ b/feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/screenLogic/SettingsComponent.kt @@ -0,0 +1,579 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.runSuspendCatching +import com.t8rin.imagetoolbox.core.domain.utils.smartJob +import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager +import com.t8rin.imagetoolbox.core.settings.domain.model.CacheAutoClearInterval +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, + @Assisted val onNavigate: (Screen) -> Unit, + @Assisted val isUpdateAvailable: Value, + @Assisted val onGoBack: (() -> Unit)?, + @Assisted("search") initialSearchQuery: String, + @Assisted("setting") val targetSetting: Setting?, + 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 val _isSendingLogs = mutableStateOf(false) + val isSendingLogs by _isSendingLogs + + 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) = onTryGetUpdate(isNewRequest) + + 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()}*${neutralVariant?.toArgb()}*${error?.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()}/${it.neutralVariant?.toArgb()}/${it.error?.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 setCacheAutoClearLimitBytes(bytes: Long) = settingsScope { + setCacheAutoClearLimitBytes(bytes) + } + + fun setCacheAutoClearInterval(interval: CacheAutoClearInterval) = settingsScope { + setCacheAutoClearInterval(interval) + } + + fun toggleGroupOptionsByType() = settingsScope { toggleGroupOptionsByTypes() } + + fun toggleFavoriteToolsInGroupedMode() = + settingsScope { toggleShowFavoriteToolsInGroupedMode() } + + fun toggleFavoriteAsLast() = settingsScope { toggleShowFavoriteAsLast() } + + 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), + neutralVariant = Color(0xFF2F3F4F), + error = Color(0xFFFFB4AB) + ) + 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 toggleSaveToOriginalFolder() = settingsScope { toggleSaveToOriginalFolder() } + + fun toggleDeleteOriginalsAfterSave() = settingsScope { toggleDeleteOriginalsAfterSave() } + + fun setDefaultImageScaleMode(imageScaleMode: ImageScaleMode) = + settingsScope { setDefaultImageScaleMode(imageScaleMode) } + + fun setSwitchType(type: SwitchType) = settingsScope { setSwitchType(type) } + + fun toggleMagnifierEnabled() = settingsScope { toggleMagnifierEnabled() } + + fun toggleDrawBitmapBorder() = settingsScope { toggleDrawBitmapBorder() } + + 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 toggleUseAnimatedEmojis() = settingsScope { toggleUseAnimatedEmojis() } + + 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 toggleEnableSheetGestures() = settingsScope { toggleEnableSheetGestures() } + + 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() = settingsScope { + _isSendingLogs.update { true } + runSuspendCatching { + shareProvider.shareUri( + uri = settingsManager.createLogsExport(), + onComplete = {} + ) + } + _isSendingLogs.update { false } + } + + 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 setShapeByInteractionThrottle(delay: Long) = + settingsScope { setShapeByInteractionThrottle(delay) } + + fun setFilenamePattern(value: String) = settingsScope { setFilenamePattern(value) } + + fun setFlingType(type: FlingType) = settingsScope { setFlingType(type) } + + fun setMotionDurationScale(scale: Float) = settingsScope { setMotionDurationScale(scale) } + + fun setHiddenForShareScreens(screen: Screen) = settingsScope { + val screens = + this@SettingsComponent.settingsState.hiddenForShareScreens + .toggle(screen.id) + + setHiddenForShareScreens(screens) + } + + fun toggleKeepDateTime() = + settingsScope { toggleKeepDateTime() } + + fun toggleAlwaysClearExif() = + settingsScope { toggleAlwaysClearExif() } + + fun toggleEnableBackgroundColorForAlphaFormats() = + settingsScope { toggleEnableBackgroundColorForAlphaFormats() } + + fun toggleShowToolsHistory() = + settingsScope { toggleShowToolsHistory() } + + private inline fun settingsScope( + crossinline action: suspend SettingsManager.() -> Unit + ) { + componentScope.launch { + settingsManager.action() + } + } + + @AssistedFactory + interface Factory { + operator fun invoke( + componentContext: ComponentContext, + onTryGetUpdate: (Boolean) -> Unit, + onNavigate: (Screen) -> Unit, + isUpdateAvailable: Value, + onGoBack: (() -> Unit)?, + @Assisted("search") initialSearchQuery: String, + @Assisted("setting") targetSetting: Setting? = null + ): SettingsComponent + } +} diff --git a/feature/shader-studio/.gitignore b/feature/shader-studio/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/shader-studio/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/shader-studio/build.gradle.kts b/feature/shader-studio/build.gradle.kts new file mode 100644 index 0000000..d6efc73 --- /dev/null +++ b/feature/shader-studio/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.shader_studio" + +dependencies { + implementation(projects.core.filters) + implementation(libs.compose.highlight) +} diff --git a/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/ShaderStudioContent.kt b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/ShaderStudioContent.kt new file mode 100644 index 0000000..52acc81 --- /dev/null +++ b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/ShaderStudioContent.kt @@ -0,0 +1,151 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.shader_studio.presentation + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +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.style.TextAlign +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.domain.model.MimeType +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ImageReset +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.dialogs.ExitWithoutSavingDialog +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.other.TopAppBarEmoji +import com.t8rin.imagetoolbox.core.ui.widget.text.marquee +import com.t8rin.imagetoolbox.feature.shader_studio.presentation.components.SavedShadersItem +import com.t8rin.imagetoolbox.feature.shader_studio.presentation.components.ShaderLibrarySheet +import com.t8rin.imagetoolbox.feature.shader_studio.presentation.components.ShaderParamsEditor +import com.t8rin.imagetoolbox.feature.shader_studio.presentation.components.ShaderPresetEditor +import com.t8rin.imagetoolbox.feature.shader_studio.presentation.components.ShaderPreview +import com.t8rin.imagetoolbox.feature.shader_studio.presentation.components.ShaderPreviewSourceSelector +import com.t8rin.imagetoolbox.feature.shader_studio.presentation.components.ShaderStudioButtons +import com.t8rin.imagetoolbox.feature.shader_studio.presentation.screenLogic.ShaderStudioComponent +import dev.hossain.highlight.ui.HighlightThemeProvider +import dev.hossain.highlight.ui.rememberTomorrowLightTheme +import dev.hossain.highlight.ui.rememberTomorrowNightTheme + +@Composable +fun ShaderStudioContent( + component: ShaderStudioComponent +) { + var showShaderLibrary by rememberSaveable { mutableStateOf(false) } + var showResetDialog by rememberSaveable { mutableStateOf(false) } + var showExitDialog by rememberSaveable { mutableStateOf(false) } + + val importPicker = rememberFilePicker( + mimeType = MimeType.All, + onSuccess = component::importPreset + ) + + HighlightThemeProvider( + lightHighlightTheme = rememberTomorrowLightTheme(), + darkHighlightTheme = rememberTomorrowNightTheme() + ) { + AdaptiveLayoutScreen( + shouldDisableBackHandler = !component.haveChanges, + title = { + Text( + text = stringResource(R.string.shader_studio), + textAlign = TextAlign.Center, + modifier = Modifier.marquee() + ) + }, + onGoBack = { + if (component.haveChanges) { + showExitDialog = true + } else { + component.onGoBack() + } + }, + actions = { + EnhancedIconButton( + onClick = { showResetDialog = true } + ) { + Icon( + imageVector = Icons.Rounded.ImageReset, + contentDescription = stringResource(R.string.reset) + ) + } + }, + topAppBarPersistentActions = { + TopAppBarEmoji() + }, + imagePreview = { + ShaderPreview(component) + }, + controls = { + Column( + verticalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.fillMaxWidth() + ) { + ShaderPreviewSourceSelector(component) + ShaderPresetEditor(component) + ShaderParamsEditor(component) + SavedShadersItem( + component = component, + onClick = { showShaderLibrary = true } + ) + } + }, + buttons = { actions -> + ShaderStudioButtons( + actions = actions, + onImport = importPicker::pickFile, + onSave = component::savePreset, + canSave = component.canSave + ) + }, + canShowScreenData = true + ) + } + + ShaderLibrarySheet( + visible = showShaderLibrary, + component = component, + onDismiss = { showShaderLibrary = false } + ) + + ResetDialog( + visible = showResetDialog, + onDismiss = { showResetDialog = false }, + onReset = component::createPreset, + title = stringResource(R.string.reset_shader), + text = stringResource(R.string.reset_shader_sub) + ) + + ExitWithoutSavingDialog( + onExit = component.onGoBack, + onDismiss = { showExitDialog = false }, + visible = showExitDialog + ) +} diff --git a/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/components/ShaderLibrary.kt b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/components/ShaderLibrary.kt new file mode 100644 index 0000000..4b74760 --- /dev/null +++ b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/components/ShaderLibrary.kt @@ -0,0 +1,273 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.shader_studio.presentation.components + +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.IntrinsicSize +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.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.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 androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.t8rin.imagetoolbox.core.domain.SHADER_EXT +import com.t8rin.imagetoolbox.core.domain.model.MimeType +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderPreset +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Code +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.DownloadFile +import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit +import com.t8rin.imagetoolbox.core.resources.icons.MoreVert +import com.t8rin.imagetoolbox.core.resources.icons.Share +import com.t8rin.imagetoolbox.core.ui.theme.blend +import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme +import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator +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.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.PreferenceItemOverload +import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem +import com.t8rin.imagetoolbox.feature.shader_studio.presentation.screenLogic.ShaderStudioComponent + +@Composable +internal fun SavedShadersItem( + component: ShaderStudioComponent, + onClick: () -> Unit +) { + val presets by component.presets.collectAsStateWithLifecycle() + + if (presets.isNotEmpty()) { + PreferenceItemOverload( + title = stringResource(R.string.saved_shaders), + subtitle = stringResource(R.string.shader_library_sub), + startIcon = { + Icon( + imageVector = Icons.Rounded.Code, + contentDescription = null + ) + }, + endIcon = { + Icon( + imageVector = Icons.Rounded.MiniEdit, + contentDescription = null + ) + }, + onClick = onClick, + shape = ShapeDefaults.large, + modifier = Modifier.fillMaxWidth() + ) + } +} + +@Composable +internal fun ShaderLibrarySheet( + visible: Boolean, + component: ShaderStudioComponent, + onDismiss: () -> Unit +) { + val presets by component.presets.collectAsStateWithLifecycle() + + LaunchedEffect(presets) { + if (presets.isEmpty()) onDismiss() + } + + EnhancedModalBottomSheet( + visible = visible, + onDismiss = { if (!it) onDismiss() }, + confirmButton = { + EnhancedButton( + onClick = onDismiss, + containerColor = MaterialTheme.colorScheme.secondaryContainer + ) { + Text(stringResource(R.string.close)) + } + }, + title = { + TitleItem( + text = stringResource(R.string.saved_shaders), + icon = Icons.Rounded.Code + ) + } + ) { + ShaderLibrary( + presets = presets, + onEdit = { + component.editPreset(it) + onDismiss() + }, + onDelete = component::deletePreset, + onShare = component::sharePreset, + onExport = component::exportPreset + ) + } +} + +@Composable +private fun ShaderLibrary( + presets: List, + onEdit: (ShaderPreset) -> Unit, + onDelete: (ShaderPreset) -> Unit, + onShare: (ShaderPreset) -> Unit, + onExport: (ShaderPreset, Uri) -> Unit +) { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier + .fillMaxWidth() + .enhancedVerticalScroll(rememberScrollState()) + .padding(16.dp) + ) { + presets.forEachIndexed { index, preset -> + var showMenu by rememberSaveable(preset.name) { mutableStateOf(false) } + + val exportPicker = rememberFileCreator( + mimeType = MimeType.All, + onSuccess = { uri: Uri -> + onExport(preset, uri) + } + ) + + PreferenceItemOverload( + title = preset.name, + subtitle = stringResource(R.string.shader_params_count, preset.params.size), + endIcon = { + Box { + EnhancedIconButton(onClick = { showMenu = true }) { + Icon( + imageVector = Icons.Rounded.MoreVert, + contentDescription = null + ) + } + EnhancedDropdownMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false }, + shape = ShapeDefaults.large + ) { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier + .width(IntrinsicSize.Max) + .padding(horizontal = 8.dp) + ) { + ShaderMenuAction( + title = R.string.edit, + icon = Icons.Rounded.MiniEdit, + shape = ShapeDefaults.top, + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + onClick = { + showMenu = false + onEdit(preset) + } + ) + ShaderMenuAction( + title = R.string.export, + icon = Icons.Outlined.DownloadFile, + shape = ShapeDefaults.center, + containerColor = takeColorFromScheme { + secondary.blend(primary) + }, + contentColor = takeColorFromScheme { + onSecondary.blend(onPrimary) + }, + onClick = { + showMenu = false + exportPicker.make("${preset.name}.$SHADER_EXT") + } + ) + ShaderMenuAction( + title = R.string.share, + icon = Icons.Outlined.Share, + shape = ShapeDefaults.center, + containerColor = MaterialTheme.colorScheme.tertiary, + contentColor = MaterialTheme.colorScheme.onTertiary, + onClick = { + showMenu = false + onShare(preset) + } + ) + ShaderMenuAction( + title = R.string.delete, + icon = Icons.Rounded.Delete, + shape = ShapeDefaults.bottom, + containerColor = MaterialTheme.colorScheme.error, + contentColor = MaterialTheme.colorScheme.onError, + onClick = { + showMenu = false + onDelete(preset) + } + ) + } + } + } + }, + onClick = { onEdit(preset) }, + shape = ShapeDefaults.byIndex(index, presets.size), + modifier = Modifier.fillMaxWidth() + ) + } + } +} + +@Composable +private fun ShaderMenuAction( + title: Int, + icon: ImageVector, + shape: Shape, + containerColor: Color, + contentColor: Color, + onClick: () -> Unit +) { + EnhancedButton( + modifier = Modifier.fillMaxWidth(), + onClick = onClick, + shape = shape, + containerColor = containerColor, + contentColor = contentColor + ) { + Icon( + imageVector = icon, + contentDescription = null + ) + Spacer(Modifier.width(8.dp)) + Text(stringResource(title)) + } +} diff --git a/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/components/ShaderParamsEditor.kt b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/components/ShaderParamsEditor.kt new file mode 100644 index 0000000..00d3111 --- /dev/null +++ b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/components/ShaderParamsEditor.kt @@ -0,0 +1,436 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.shader_studio.presentation.components + +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.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +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.Shape +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderParam +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderParamType +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderValue +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AddCircle +import com.t8rin.imagetoolbox.core.resources.icons.Delete +import com.t8rin.imagetoolbox.core.resources.icons.HashTag +import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRow +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.animateContentSizeNoClip +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.PreferenceItemOverload +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch +import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField +import com.t8rin.imagetoolbox.feature.shader_studio.presentation.screenLogic.ShaderStudioComponent + +@Composable +internal fun ShaderParamsEditor(component: ShaderStudioComponent) { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.fillMaxWidth() + ) { + PreferenceItemOverload( + title = stringResource(R.string.params), + subtitle = if (component.params.isEmpty()) { + stringResource(R.string.shader_no_parameters) + } else { + stringResource(R.string.shader_params_count, component.params.size) + }, + startIcon = { + Icon( + imageVector = Icons.Rounded.HashTag, + contentDescription = null + ) + }, + endIcon = { + EnhancedIconButton( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + onClick = { component.addParam() } + ) { + Icon( + imageVector = Icons.Outlined.AddCircle, + contentDescription = stringResource(R.string.add) + ) + } + }, + shape = if (component.params.isEmpty()) { + ShapeDefaults.large + } else { + ShapeDefaults.top + }, + onClick = { component.addParam() }, + modifier = Modifier.fillMaxWidth() + ) + component.params.forEachIndexed { index, param -> + ShaderParamEditor( + param = param, + shape = ShapeDefaults.byIndex(index + 1, component.params.size + 1), + onParamChange = { component.updateParam(index, it) }, + onRemove = { component.removeParam(index) } + ) + } + } +} + +@Composable +private fun ShaderParamEditor( + param: ShaderParam, + shape: Shape, + onParamChange: (ShaderParam) -> Unit, + onRemove: () -> Unit +) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .fillMaxWidth() + .container(shape) + .padding(12.dp) + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + RoundedTextField( + value = param.name, + onValueChange = { onParamChange(param.copy(name = it)) }, + label = stringResource(R.string.name), + shape = ShapeDefaults.large, + modifier = Modifier.weight(1f) + ) + EnhancedIconButton( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer, + onClick = onRemove + ) { + Icon( + imageVector = Icons.Rounded.Delete, + contentDescription = stringResource(R.string.delete) + ) + } + } + EnhancedButtonGroup( + items = ShaderParamType.entries.map { it.serialName }, + selectedIndex = ShaderParamType.entries.indexOf(param.type), + onIndexChange = { index -> + val type = ShaderParamType.entries[index] + onParamChange( + param.copy( + type = type, + defaultValue = type.defaultValue(), + minValue = null, + maxValue = null + ) + ) + }, + isScrollable = false, + modifier = Modifier.fillMaxWidth(), + contentPadding = PaddingValues(), + inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainerHigh + ) + + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.animateContentSizeNoClip() + ) { + val hasBounds = param.type.supportsBounds() + val settingsCount = if (hasBounds) 3 else 1 + + ShaderValueEditor( + title = stringResource(R.string.default_value), + value = param.defaultValue, + shape = ShapeDefaults.byIndex(0, settingsCount), + onValueChange = { onParamChange(param.copy(defaultValue = it)) } + ) + if (hasBounds) { + ShaderOptionalBoundsEditor( + param = param, + totalSize = settingsCount, + onParamChange = onParamChange + ) + } + } + } +} + +@Composable +private fun ShaderOptionalBoundsEditor( + param: ShaderParam, + totalSize: Int, + onParamChange: (ShaderParam) -> Unit +) { + ShaderValueEditor( + title = stringResource(R.string.min), + value = param.minValue ?: param.type.defaultValue(), + enabled = param.minValue != null, + shape = ShapeDefaults.byIndex(1, totalSize), + onEnabledChange = { + onParamChange(param.copy(minValue = if (it) param.type.defaultValue() else null)) + }, + onValueChange = { onParamChange(param.copy(minValue = it)) } + ) + ShaderValueEditor( + title = stringResource(R.string.max), + value = param.maxValue ?: param.type.defaultValue(), + enabled = param.maxValue != null, + shape = ShapeDefaults.byIndex(2, totalSize), + onEnabledChange = { + onParamChange(param.copy(maxValue = if (it) param.type.defaultValue() else null)) + }, + onValueChange = { onParamChange(param.copy(maxValue = it)) } + ) +} + +@Composable +private fun ShaderValueEditor( + title: String, + value: ShaderValue, + enabled: Boolean = true, + shape: Shape, + onEnabledChange: ((Boolean) -> Unit)? = null, + onValueChange: (ShaderValue) -> Unit +) { + if (onEnabledChange != null) { + PreferenceRowSwitch( + title = title, + checked = enabled, + shape = shape, + containerColor = MaterialTheme.colorScheme.surface, + applyHorizontalPadding = false, + modifier = Modifier.fillMaxWidth(), + onClick = onEnabledChange, + resultModifier = Modifier.padding(16.dp), + additionalContent = { + AnimatedVisibility( + visible = enabled, + modifier = Modifier.fillMaxWidth() + ) { + ShaderValueContent( + value = value, + onValueChange = onValueChange, + modifier = Modifier.padding(top = 12.dp) + ) + } + } + ) + } else if (value is ShaderValue.BoolValue) { + PreferenceRowSwitch( + title = title, + checked = value.value, + shape = shape, + containerColor = MaterialTheme.colorScheme.surface, + applyHorizontalPadding = false, + modifier = Modifier.fillMaxWidth(), + onClick = { onValueChange(ShaderValue.BoolValue(it)) } + ) + } else { + Column( + verticalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier + .fillMaxWidth() + .container( + shape = shape, + color = MaterialTheme.colorScheme.surface, + autoShadowElevation = 0.dp, + isStandaloneContainer = false + ) + .then( + if (value is ShaderValue.ColorValue) { + Modifier.padding( + top = 12.dp, + start = 12.dp, + end = 12.dp, + bottom = 8.dp + ) + } else { + Modifier.padding(12.dp) + } + ) + ) { + Text( + text = title, + style = PreferenceItemDefaults.TitleFontStyle + ) + ShaderValueContent( + value = value, + onValueChange = onValueChange + ) + } + } +} + +@Composable +private fun ShaderValueContent( + value: ShaderValue, + modifier: Modifier = Modifier, + onValueChange: (ShaderValue) -> Unit +) { + AnimatedContent( + targetState = value, + contentKey = { it::class.simpleName }, + modifier = Modifier.fillMaxWidth() + ) { state -> + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + when (state) { + is ShaderValue.FloatValue -> FloatValueField( + value = state, + onValueChange = onValueChange + ) + + is ShaderValue.IntValue -> IntValueField( + value = state, + onValueChange = onValueChange + ) + + is ShaderValue.ColorValue -> ColorSelectionRow( + value = state.toComposeColor(), + allowAlpha = true, + contentPadding = PaddingValues(0.dp), + onValueChange = { onValueChange(it.toShaderColorValue()) } + ) + + is ShaderValue.Vec2Value -> Vec2ValueFields( + value = state, + onValueChange = onValueChange + ) + + is ShaderValue.BoolValue -> Unit + } + } + } +} + +@Composable +private fun FloatValueField( + value: ShaderValue.FloatValue, + onValueChange: (ShaderValue) -> Unit +) { + var text by remember(value.value) { mutableStateOf(value.value.toString()) } + RoundedTextField( + value = text, + onValueChange = { + text = it + it.toFloatOrNull()?.let { parsed -> onValueChange(ShaderValue.FloatValue(parsed)) } + }, + label = "", + shape = ShapeDefaults.large, + modifier = Modifier.fillMaxWidth() + ) +} + +@Composable +private fun IntValueField( + value: ShaderValue.IntValue, + onValueChange: (ShaderValue) -> Unit +) { + var text by remember(value.value) { mutableStateOf(value.value.toString()) } + RoundedTextField( + value = text, + onValueChange = { + text = it + it.toIntOrNull()?.let { parsed -> onValueChange(ShaderValue.IntValue(parsed)) } + }, + label = "", + shape = ShapeDefaults.large, + modifier = Modifier.fillMaxWidth() + ) +} + +@Composable +private fun Vec2ValueFields( + value: ShaderValue.Vec2Value, + onValueChange: (ShaderValue) -> Unit +) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.fillMaxWidth() + ) { + var x by remember(value.x) { mutableStateOf(value.x.toString()) } + var y by remember(value.y) { mutableStateOf(value.y.toString()) } + RoundedTextField( + value = x, + onValueChange = { + x = it + it.toFloatOrNull() + ?.let { parsed -> onValueChange(ShaderValue.Vec2Value(parsed, value.y)) } + }, + label = "X", + shape = ShapeDefaults.large, + modifier = Modifier.weight(1f) + ) + RoundedTextField( + value = y, + onValueChange = { + y = it + it.toFloatOrNull() + ?.let { parsed -> onValueChange(ShaderValue.Vec2Value(value.x, parsed)) } + }, + label = "Y", + shape = ShapeDefaults.large, + modifier = Modifier.weight(1f) + ) + } +} + +private fun ShaderParamType.defaultValue(): ShaderValue = when (this) { + ShaderParamType.Float -> ShaderValue.FloatValue(0f) + ShaderParamType.Int -> ShaderValue.IntValue(0) + ShaderParamType.Bool -> ShaderValue.BoolValue(false) + ShaderParamType.Color -> ShaderValue.ColorValue(255, 255, 255, 255) + ShaderParamType.Vec2 -> ShaderValue.Vec2Value(0f, 0f) +} + +private fun ShaderParamType.supportsBounds(): Boolean = + this != ShaderParamType.Bool && this != ShaderParamType.Color + +private fun ShaderValue.ColorValue.toComposeColor(): Color = + Color(red = red, green = green, blue = blue, alpha = alpha) + +private fun Color.toShaderColorValue(): ShaderValue.ColorValue { + val argb = toArgb() + return ShaderValue.ColorValue( + red = (argb shr 16) and 0xFF, + green = (argb shr 8) and 0xFF, + blue = argb and 0xFF, + alpha = (argb shr 24) and 0xFF + ) +} diff --git a/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/components/ShaderPresetEditor.kt b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/components/ShaderPresetEditor.kt new file mode 100644 index 0000000..cbf9f06 --- /dev/null +++ b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/components/ShaderPresetEditor.kt @@ -0,0 +1,206 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.shader_studio.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.text.KeyboardOptions +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.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization +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.unit.dp +import com.t8rin.imagetoolbox.core.filters.presentation.utils.localizedMessage +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.EvShadow +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.container +import com.t8rin.imagetoolbox.core.ui.widget.other.ExpandableItem +import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField +import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem +import com.t8rin.imagetoolbox.feature.shader_studio.presentation.screenLogic.ShaderStudioComponent +import dev.hossain.highlight.engine.HighlightTheme +import dev.hossain.highlight.ui.rememberHighlightedCode + +@Composable +internal fun ShaderPresetEditor(component: ShaderStudioComponent) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .fillMaxWidth() + .container(MaterialTheme.shapes.extraLarge) + .padding(16.dp) + ) { + TitleItem( + text = stringResource(R.string.shader), + icon = Icons.Rounded.EvShadow, + modifier = Modifier.padding(bottom = 8.dp) + ) + RoundedTextField( + value = component.name, + onValueChange = component::updateName, + label = stringResource(R.string.name), + shape = ShapeDefaults.large, + modifier = Modifier.fillMaxWidth() + ) + GlslCodeField( + value = component.shaderSource, + onValueChange = component::updateShaderSource, + hint = stringResource(R.string.shader_main_body_info), + modifier = Modifier.fillMaxWidth(), + supportingText = component.validationErrors.takeIf { it.isNotEmpty() }?.let { + { + Text( + text = remember(component.validationErrors) { + component.validationErrors.joinToString("\n") { it.localizedMessage() } + }, + color = MaterialTheme.colorScheme.error + ) + } + } + ) + ExpandableItem( + initialState = component.helperSource.isNotBlank(), + visibleContent = { + TitleItem(text = stringResource(R.string.shader_helper_code)) + }, + expandableContent = { + GlslCodeField( + value = component.helperSource, + onValueChange = component::updateHelperSource, + hint = stringResource(R.string.shader_helper_code_info), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + ) + }, + color = MaterialTheme.colorScheme.surface + ) + } +} + +@Composable +private fun GlslCodeField( + value: String, + onValueChange: (String) -> Unit, + hint: String, + modifier: Modifier = Modifier, + supportingText: (@Composable (Boolean) -> Unit)? = null +) { + val isNightMode = LocalSettingsState.current.isNightMode + + val highlightedCode by rememberHighlightedCode( + code = value, + theme = remember(isNightMode) { + if (isNightMode) { + HighlightTheme.tomorrowNight() + } else { + HighlightTheme.tomorrow() + } + }, + language = GLSL_LANGUAGE + ) + var cachedHighlightedCode by remember(isNightMode) { + mutableStateOf(null) + } + LaunchedEffect(highlightedCode, value) { + if (highlightedCode?.text == value) { + cachedHighlightedCode = highlightedCode + } + } + + val displayHighlightedCode = remember(value, cachedHighlightedCode) { + cachedHighlightedCode + ?.takeIf { it.text == value } + ?: cachedHighlightedCode?.applySpanStylesTo(value) + } + val visualTransformation = remember(value, displayHighlightedCode) { + GlslHighlightVisualTransformation( + highlightedCode = displayHighlightedCode + ) + } + + RoundedTextField( + value = value, + onValueChange = onValueChange, + hint = hint, + shape = ShapeDefaults.large, + singleLine = false, + textStyle = TextStyle( + fontFamily = FontFamily.Monospace, + color = MaterialTheme.colorScheme.onSurface + ), + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.None, + autoCorrectEnabled = false, + imeAction = ImeAction.Default + ), + visualTransformation = visualTransformation, + modifier = modifier, + supportingText = supportingText + ) +} + +private class GlslHighlightVisualTransformation( + private val highlightedCode: AnnotatedString? +) : VisualTransformation { + override fun filter(text: AnnotatedString): TransformedText { + val transformedText = highlightedCode + ?.takeIf { it.text == text.text } + ?: text + + return TransformedText( + text = transformedText, + offsetMapping = OffsetMapping.Identity + ) + } +} + +private fun AnnotatedString.applySpanStylesTo(text: String): AnnotatedString { + if (text.isEmpty()) return AnnotatedString(text) + + return AnnotatedString.Builder(text).apply { + spanStyles.forEach { range -> + val start = range.start.coerceAtMost(text.length) + val end = range.end.coerceAtMost(text.length) + if (start < end) { + addStyle(range.item, start, end) + } + } + }.toAnnotatedString() +} + +private const val GLSL_LANGUAGE = "glsl" diff --git a/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/components/ShaderPreview.kt b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/components/ShaderPreview.kt new file mode 100644 index 0000000..0ddf4fd --- /dev/null +++ b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/components/ShaderPreview.kt @@ -0,0 +1,152 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.shader_studio.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.IntrinsicSize +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.contentColorFor +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.clip +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 coil3.toBitmap +import com.t8rin.imagetoolbox.core.data.utils.safeAspectRatio +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme +import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageSelector +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator +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.text.AutoSizeText +import com.t8rin.imagetoolbox.feature.shader_studio.presentation.screenLogic.ShaderStudioComponent + +@Composable +internal fun ShaderPreview(component: ShaderStudioComponent) { + Box( + contentAlignment = Alignment.Center + ) { + var aspectRatio by remember { + mutableFloatStateOf(1f) + } + + Picture( + model = component.previewModel.data, + 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 = remember( + component.shaderSource, + component.helperSource, + component.params + ) { + component.getPreviewTransformations() + } + ) + if (component.isImageLoading) EnhancedLoadingIndicator() + } +} + +@Composable +internal fun ShaderPreviewSourceSelector(component: ShaderStudioComponent) { + val previewModel = component.previewModel + + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier + .fillMaxWidth() + .height(intrinsicSize = IntrinsicSize.Max) + ) { + 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 + ) + 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 (previewModel.data) { + R.drawable.filter_preview_source if index == 0 -> secondary + R.drawable.filter_preview_source_3 if 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) + ) + } + } + } + } +} diff --git a/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/components/ShaderStudioButtons.kt b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/components/ShaderStudioButtons.kt new file mode 100644 index 0000000..7b2765f --- /dev/null +++ b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/components/ShaderStudioButtons.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.shader_studio.presentation.components + +import androidx.compose.foundation.layout.RowScope +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.res.stringResource +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FileOpen +import com.t8rin.imagetoolbox.core.resources.icons.Save +import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState +import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock + +@Composable +internal fun ShaderStudioButtons( + actions: @Composable RowScope.() -> Unit, + onImport: () -> Unit, + onSave: () -> Unit, + canSave: Boolean +) { + val isPortrait by isPortraitOrientationAsState() + + BottomButtonsBlock( + isNoData = false, + onSecondaryButtonClick = onImport, + secondaryButtonIcon = Icons.Rounded.FileOpen, + secondaryButtonText = stringResource(R.string.import_word), + onPrimaryButtonClick = onSave, + primaryButtonIcon = Icons.Rounded.Save, + isPrimaryButtonVisible = canSave, + actions = { + if (isPortrait) actions() + } + ) +} diff --git a/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/screenLogic/ShaderDraftSnapshot.kt b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/screenLogic/ShaderDraftSnapshot.kt new file mode 100644 index 0000000..941c87b --- /dev/null +++ b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/screenLogic/ShaderDraftSnapshot.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.shader_studio.presentation.screenLogic + +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderParam + +internal data class ShaderDraftSnapshot( + val name: String, + val shaderSource: String, + val helperSource: String, + val params: List +) diff --git a/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/screenLogic/ShaderStudioComponent.kt b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/screenLogic/ShaderStudioComponent.kt new file mode 100644 index 0000000..7a6682b --- /dev/null +++ b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/screenLogic/ShaderStudioComponent.kt @@ -0,0 +1,331 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.shader_studio.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.SHADER_EXT +import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder +import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider +import com.t8rin.imagetoolbox.core.domain.model.ImageModel +import com.t8rin.imagetoolbox.core.domain.saving.FileController +import com.t8rin.imagetoolbox.core.domain.saving.io.Writeable +import com.t8rin.imagetoolbox.core.filters.domain.FilterParamsInteractor +import com.t8rin.imagetoolbox.core.filters.domain.FilterProvider +import com.t8rin.imagetoolbox.core.filters.domain.ShaderPresetRepository +import com.t8rin.imagetoolbox.core.filters.domain.model.params.ShaderParams +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderParam +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderParamType +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderParseException +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderPreset +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderValidationError +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderValidator +import com.t8rin.imagetoolbox.core.filters.presentation.model.UiShaderFilter +import com.t8rin.imagetoolbox.core.filters.presentation.utils.localizedMessage +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.toCoil +import com.t8rin.imagetoolbox.core.ui.utils.state.update +import com.t8rin.imagetoolbox.core.utils.toImageModel +import dagger.Lazy +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +class ShaderStudioComponent @AssistedInject internal constructor( + @Assisted componentContext: ComponentContext, + @Assisted val onGoBack: () -> Unit, + private val shaderPresetRepository: ShaderPresetRepository, + private val filterProvider: Lazy>, + private val filterParamsInteractor: Lazy, + private val fileController: FileController, + private val shareProvider: ImageShareProvider, + dispatchersHolder: DispatchersHolder +) : BaseComponent(dispatchersHolder, componentContext) { + + val presets: StateFlow> = shaderPresetRepository.getPresets() + .stateIn( + scope = componentScope, + started = SharingStarted.Lazily, + initialValue = emptyList() + ) + + private val _name = mutableStateOf("") + val name: String by _name + + private val _shaderSource = mutableStateOf("") + val shaderSource: String by _shaderSource + + private val _helperSource = mutableStateOf("") + val helperSource: String by _helperSource + + private val _params = mutableStateOf(emptyList()) + val params: List by _params + + private val _validationErrors = mutableStateOf>( + listOf(ShaderValidationError.BlankSource) + ) + val validationErrors: List by _validationErrors + + private val _previewModel: MutableState = mutableStateOf( + R.drawable.filter_preview_source.toImageModel() + ) + val previewModel: ImageModel by _previewModel + + val canSave: Boolean + get() = validationErrors.isEmpty() + + fun createPreset() { + _name.update { "" } + _shaderSource.update { "" } + _helperSource.update { "" } + _params.update { emptyList() } + registerChanges() + updateBasicValidationErrors() + cancelImageLoading() + } + + fun editPreset(preset: ShaderPreset) { + val draft = preset.toDraftSnapshot() + _name.update { preset.name } + _shaderSource.update { draft.shaderSource } + _helperSource.update { draft.helperSource } + _params.update { preset.params } + registerChangesCleared() + updateBasicValidationErrors() + } + + fun updateName(value: String) { + _name.update { value } + registerChanges() + updateBasicValidationErrors() + } + + fun updateShaderSource(value: String) { + _shaderSource.update { value } + registerChanges() + updateBasicValidationErrors() + } + + fun updateHelperSource(value: String) { + _helperSource.update { value } + registerChanges() + updateBasicValidationErrors() + } + + fun addParam(type: ShaderParamType = ShaderParamType.Float) { + _params.update { + it + ShaderParam( + name = it.nextParamName(), + type = type, + defaultValue = type.defaultValue() + ) + } + registerChanges() + updateBasicValidationErrors() + } + + fun removeParam(index: Int) { + _params.update { it.filterIndexed { paramIndex, _ -> paramIndex != index } } + registerChanges() + updateBasicValidationErrors() + } + + fun updateParam( + index: Int, + param: ShaderParam + ) { + _params.update { + it.toMutableList().apply { + if (index in indices) this[index] = param + } + } + registerChanges() + updateBasicValidationErrors() + } + + fun savePreset() { + componentScope.launch { + val snapshot = buildDraftSnapshot() + val errors = validateSnapshot(snapshot) + + _validationErrors.update { errors } + if (errors.isNotEmpty()) return@launch + + val preset = snapshot.toPreset().withUniqueName() + + shaderPresetRepository.savePreset( + preset = preset, + replacingName = null + ).onSuccess { + _name.update { preset.name } + registerChangesCleared() + AppToastHost.showConfetti() + }.onFailure(::showError) + } + } + + fun setFilterPreviewModel(value: String) { + _previewModel.update { value.toPreviewImageModel() } + + componentScope.launch { + val interactor = filterParamsInteractor.get() + interactor.setFilterPreviewModel(value) + interactor.setCanSetDynamicFilterPreview(false) + } + } + + fun importPreset(uri: Uri) { + componentScope.launch { + val result = runCatching { + val json = fileController.readBytes(uri.toString()).decodeToString() + shaderPresetRepository.importPreset(json).getOrThrow() + } + + result.onSuccess { preset -> + editPreset(preset) + AppToastHost.showConfetti() + }.onFailure(::showError) + } + } + + fun exportPreset( + preset: ShaderPreset, + uri: Uri + ) { + componentScope.launch { + fileController.writeBytes(uri.toString()) { writeable -> + writeable.writeBytes( + shaderPresetRepository.exportPreset(preset).encodeToByteArray() + ) + }.also(::parseFileSaveResult).onSuccess(::registerSave) + } + } + + fun sharePreset(preset: ShaderPreset) { + componentScope.launch { + shareProvider.shareData( + writeData = { writeable: Writeable -> + writeable.writeBytes( + shaderPresetRepository.exportPreset(preset).encodeToByteArray() + ) + }, + filename = "${preset.name.sanitizedFileName()}.$SHADER_EXT", + onComplete = AppToastHost::showConfetti + ) + } + } + + fun deletePreset(preset: ShaderPreset) { + componentScope.launch { + shaderPresetRepository.deletePreset(preset) + } + } + + fun getPreviewTransformations(): List { + val snapshot = buildDraftSnapshot() + val previewPreset = snapshot.toPreset().copy( + name = snapshot.name + ) + + val haveErrors = validationErrors.any { + it !is ShaderValidationError.BlankName + } + + if (haveErrors || snapshot.shaderSource.isBlank()) return emptyList() + + return listOf( + UiShaderFilter( + ShaderParams( + preset = previewPreset, + values = previewPreset.defaultValues + ) + ) + ).map { + filterProvider.get().filterToTransformation(it).toCoil() + } + } + + private fun buildDraftSnapshot(): ShaderDraftSnapshot = + ShaderDraftSnapshot( + name = name, + shaderSource = shaderSource, + helperSource = helperSource, + params = params + ) + + private fun updateBasicValidationErrors() { + _validationErrors.update { + validateSnapshot(buildDraftSnapshot()) + } + } + + private fun validateSnapshot( + snapshot: ShaderDraftSnapshot + ): List { + val preset = snapshot.toPreset() + + return buildList { + if (snapshot.shaderSource.isBlank()) { + add(ShaderValidationError.BlankSource) + } + addAll(ShaderValidator.validate(preset)) + }.distinct() + } + + private fun ShaderPreset.withUniqueName(): ShaderPreset { + val existingNames = presets.value.mapTo(mutableSetOf()) { it.name } + if (name !in existingNames) return this + + var index = 1 + var candidate = "$name Copy" + while (candidate in existingNames) { + index += 1 + candidate = "$name Copy $index" + } + + return copy(name = candidate) + } + + private fun showError(throwable: Throwable) { + _isImageLoading.update { false } + if (throwable is ShaderParseException) { + AppToastHost.showFailureToast(throwable.localizedMessage()) + } else { + AppToastHost.showFailureToast(throwable) + } + } + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + onGoBack: () -> Unit + ): ShaderStudioComponent + } + +} diff --git a/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/screenLogic/ShaderStudioMapper.kt b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/screenLogic/ShaderStudioMapper.kt new file mode 100644 index 0000000..95a3e4c --- /dev/null +++ b/feature/shader-studio/src/main/java/com/t8rin/imagetoolbox/feature/shader_studio/presentation/screenLogic/ShaderStudioMapper.kt @@ -0,0 +1,189 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.shader_studio.presentation.screenLogic + +import com.t8rin.imagetoolbox.core.domain.model.ImageModel +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderParam +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderParamType +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderPreset +import com.t8rin.imagetoolbox.core.filters.domain.model.shader.ShaderValue +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.utils.toImageModel + +internal fun ShaderDraftSnapshot.toPreset(): ShaderPreset = + params.sanitizeForShaderPreset().let { params -> + ShaderPreset( + version = SUPPORTED_SHADER_VERSION, + name = name.trim(), + params = params, + shader = toFragmentShader(params) + ) + } + +internal fun ShaderPreset.toDraftSnapshot(): ShaderDraftSnapshot { + val parts = shader.toShaderSourceParts(params) + return ShaderDraftSnapshot( + name = name, + shaderSource = parts.mainBody, + helperSource = parts.helperSource, + params = params + ) +} + +internal fun ShaderParamType.defaultValue(): ShaderValue = when (this) { + ShaderParamType.Float -> ShaderValue.FloatValue(0f) + ShaderParamType.Int -> ShaderValue.IntValue(0) + ShaderParamType.Bool -> ShaderValue.BoolValue(false) + ShaderParamType.Color -> ShaderValue.ColorValue(255, 255, 255, 255) + ShaderParamType.Vec2 -> ShaderValue.Vec2Value(0f, 0f) +} + +internal fun List.nextParamName(): String { + val names = mapTo(mutableSetOf()) { it.name } + var index = size + 1 + var candidate = "uParam$index" + while (candidate in names) { + index += 1 + candidate = "uParam$index" + } + return candidate +} + +private fun List.sanitizeForShaderPreset(): List = + map { param -> + if (param.type == ShaderParamType.Bool || param.type == ShaderParamType.Color) { + param.copy(minValue = null, maxValue = null) + } else { + param + } + } + +private fun ShaderDraftSnapshot.toFragmentShader(params: List): String = buildString { + appendLine("precision mediump float;") + appendLine("varying highp vec2 textureCoordinate;") + appendLine("uniform sampler2D inputImageTexture;") + params.forEach { param -> + appendLine("uniform ${param.type.glslType()} ${param.name};") + } + appendLine() + helperSource.trim().takeIf(String::isNotEmpty)?.let { helpers -> + appendLine(helpers) + appendLine() + } + appendLine("void main() {") + shaderSource.lineSequence() + .map { it.trimEnd() } + .forEach { line -> + append(" ") + appendLine(line) + } + append("}") +} + +private fun String.toShaderSourceParts(params: List): ShaderSourceParts { + val mainMatch = MAIN_PATTERN.find(this) ?: return ShaderSourceParts( + mainBody = this, + helperSource = "" + ) + val bodyStart = indexOf('{', startIndex = mainMatch.range.last + 1) + .takeIf { it >= 0 } + ?: return ShaderSourceParts(mainBody = this, helperSource = "") + var depth = 0 + + for (index in bodyStart until length) { + when (this[index]) { + '{' -> depth += 1 + '}' -> { + depth -= 1 + if (depth == 0) { + val prefix = substring(0, mainMatch.range.first) + val body = substring(bodyStart + 1, index) + val suffix = substring(index + 1) + return ShaderSourceParts( + mainBody = body.toEditableMainBody(), + helperSource = listOf(prefix, suffix) + .joinToString("\n") + .withoutStudioWrapper(params) + ) + } + } + } + } + + return ShaderSourceParts(mainBody = this, helperSource = "") +} + +private fun String.toEditableMainBody(): String = + trim('\n', '\r') + .lines() + .joinToString("\n") { it.removePrefix(" ") } + +private fun String.withoutStudioWrapper(params: List): String { + val paramUniforms = params.associate { param -> + param.name to param.type.glslType() + } + return lineSequence() + .filterNot { line -> line.isStudioWrapperDeclaration(paramUniforms) } + .joinToString("\n") + .trim() +} + +private fun String.isStudioWrapperDeclaration(paramUniforms: Map): Boolean { + val trimmed = trim() + if (trimmed.isEmpty()) return false + if (PRECISION_DECLARATION.matches(trimmed)) return true + if (TEXTURE_COORDINATE_DECLARATION.matches(trimmed)) return true + if (INPUT_TEXTURE_DECLARATION.matches(trimmed)) return true + + val uniform = UNIFORM_DECLARATION.matchEntire(trimmed) ?: return false + val type = uniform.groupValues[1] + val name = uniform.groupValues[2] + return paramUniforms[name] == type +} + +internal fun String.sanitizedFileName(): String = + replace(Regex("""[\\/:*?"<>|]"""), "_").ifBlank { "shader" } + +internal fun String.toPreviewImageModel(): ImageModel = when (this) { + "0" -> R.drawable.filter_preview_source + "1" -> R.drawable.filter_preview_source_3 + else -> this +}.toImageModel() + +private fun ShaderParamType.glslType(): String = when (this) { + ShaderParamType.Float -> "float" + ShaderParamType.Int -> "int" + ShaderParamType.Bool -> "bool" + ShaderParamType.Color -> "vec4" + ShaderParamType.Vec2 -> "vec2" +} + +private data class ShaderSourceParts( + val mainBody: String, + val helperSource: String +) + +private const val SUPPORTED_SHADER_VERSION = 1 +private val MAIN_PATTERN = Regex("""void\s+main\s*\(\s*\)""") +private val PRECISION_DECLARATION = Regex("""precision\s+\w+\s+\w+\s*;""") +private val TEXTURE_COORDINATE_DECLARATION = + Regex("""varying\s+(?:lowp|mediump|highp)?\s*vec2\s+textureCoordinate\s*;""") +private val INPUT_TEXTURE_DECLARATION = + Regex("""uniform\s+(?:lowp|mediump|highp)?\s*sampler2D\s+inputImageTexture\s*;""") +private val UNIFORM_DECLARATION = + Regex("""uniform\s+(?:lowp|mediump|highp)?\s*(\w+)\s+([A-Za-z_][A-Za-z0-9_]*)\s*;""") diff --git a/feature/single-edit/.gitignore b/feature/single-edit/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/single-edit/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/single-edit/build.gradle.kts b/feature/single-edit/build.gradle.kts new file mode 100644 index 0000000..858844f --- /dev/null +++ b/feature/single-edit/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + implementation(projects.lib.cropper) +} \ No newline at end of file diff --git a/feature/single-edit/src/main/AndroidManifest.xml b/feature/single-edit/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/single-edit/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/SingleEditContent.kt b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/SingleEditContent.kt new file mode 100644 index 0000000..1cf78f6 --- /dev/null +++ b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/SingleEditContent.kt @@ -0,0 +1,511 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.rememberScrollState +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.res.stringResource +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.domain.image.model.Preset +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.History +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.UndoRedoButtons +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.enhanced.enhancedHorizontalScroll +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.fadingEdges +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 = { + val state = rememberScrollState() + Row( + modifier = Modifier + .fadingEdges(state) + .enhancedHorizontalScroll(state), + verticalAlignment = Alignment.CenterVertically + ) { + var editSheetData by remember { + mutableStateOf(listOf()) + } + if (!isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + 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) + ) + } + } + if (isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + } + }, + 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::updateProfile, + imageInfo = imageInfo, + imageExportProfilesHolder = component + ) + 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, + imageUri = component.currentImageUri, + imageSize = component.currentImageSize.run { IntSize(width, height) }, + onGetImageUri = component::updateImageUriAfterEditing, + cropProperties = component.cropProperties, + setCropAspectRatio = component::setCropAspectRatio, + setCropMask = component::setCropMask, + selectedAspectRatio = component.selectedAspectRatio, + loadImage = component::loadImage, + loadImagePreview = component::loadImagePreview + ) + + 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, + spotHealCache = component.drawSpotHealCache, + onCacheSpotHealPathResult = component::cacheDrawSpotHealPathResult, + drawMode = component.drawMode, + onUpdateDrawMode = component::updateDrawMode, + drawPathMode = component.drawPathMode, + onUpdateDrawPathMode = component::updateDrawPathMode, + drawLineStyle = component.drawLineStyle, + onUpdateDrawLineStyle = component::updateDrawLineStyle, + helperGridParams = component.helperGridParams, + onUpdateHelperGridParams = component::updateHelperGridParams, + onRemovePath = component::removePath + ) + + 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) + } + ) +} diff --git a/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/CropEditOption.kt b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/CropEditOption.kt new file mode 100644 index 0000000..1d24c05 --- /dev/null +++ b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/CropEditOption.kt @@ -0,0 +1,337 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.IntSize +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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.CropSmall +import com.t8rin.imagetoolbox.core.resources.icons.Done +import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.CropOverlayDraggableSelector +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.core.utils.imageSize +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?, + imageUri: Uri?, + imageSize: IntSize, + onGetImageUri: (Uri) -> Unit, + cropProperties: CropProperties, + selectedAspectRatio: DomainAspectRatio, + setCropAspectRatio: (DomainAspectRatio, AspectRatio) -> Unit, + setCropMask: (CropOutlineProperty) -> Unit, + loadImage: suspend (Uri) -> Bitmap?, + loadImagePreview: 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() + if (bitmap != null && imageUri != null) { + var crop by remember(visible) { mutableStateOf(false) } + var stateBitmap by remember(bitmap, imageUri, visible) { mutableStateOf(bitmap) } + var stateUri by remember(imageUri, visible) { mutableStateOf(imageUri) } + var stateImageSize by remember(imageSize, imageUri, visible) { mutableStateOf(imageSize) } + + FullscreenEditOption( + canGoBack = stateUri == imageUri, + 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 || + cropProperties.aspectRatio == AspectRatio.Original, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + CropOverlayDraggableSelector( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp) + .padding(horizontal = 16.dp), + shape = ShapeDefaults.extraLarge + ) + } + 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 = stateUri != imageUri) { + EnhancedIconButton( + containerColor = MaterialTheme.colorScheme.tertiaryContainer, + onClick = { + onGetImageUri(stateUri) + 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, + imageUri = stateUri, + imageSize = stateImageSize, + crop = crop, + onImageCropStarted = { loading = true }, + onImageCropFinished = { uri -> + crop = false + if (uri != null) { + stateUri = uri + stateImageSize = uri.imageSize() + ?.let { IntSize(it.width, it.height) } + ?: stateImageSize + scope.launch { + loading = true + loadImagePreview(uri)?.let { preview -> + stateBitmap = preview + } + delay(500) + loading = false + } + } else { + loading = 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() + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/DrawEditOption.kt b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/DrawEditOption.kt new file mode 100644 index 0000000..10b4e22 --- /dev/null +++ b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/DrawEditOption.kt @@ -0,0 +1,514 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Done +import com.t8rin.imagetoolbox.core.resources.icons.Redo +import com.t8rin.imagetoolbox.core.resources.icons.Undo +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.DrawLineAngleSelector +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.draw.presentation.components.canShowLineAngle +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, + spotHealCache: Map, + onCacheSpotHealPathResult: (Int, Bitmap) -> Unit, + helperGridParams: HelperGridParams, + onUpdateHelperGridParams: (HelperGridParams) -> Unit, + addFiltersSheetComponent: AddFiltersSheetComponent, + filterTemplateCreationSheetComponent: FilterTemplateCreationSheetComponent, + onRemovePath: (UiPathPaint) -> Unit +) { + 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 showLineAngle by rememberSaveable { mutableStateOf(false) } + 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.Rounded.Undo, + contentDescription = "Undo" + ) + } + EnhancedIconButton( + containerColor = Color.Transparent, + borderColor = MaterialTheme.colorScheme.outlineVariant( + luminance = 0.1f + ), + onClick = redo, + enabled = undonePaths.isNotEmpty() + ) { + Icon( + imageVector = Icons.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 = drawPathMode.canShowLineAngle() && drawMode !is DrawMode.Warp, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically(), + modifier = Modifier.fillMaxWidth() + ) { + DrawLineAngleSelector( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + checked = showLineAngle, + onCheckedChange = { showLineAngle = it } + ) + } + 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, + showLineAngle = showLineAngle, + spotHealCache = spotHealCache, + onCacheSpotHealPathResult = onCacheSpotHealPathResult, + onRemovePath = onRemovePath + ) + } + } + } + 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() + } + } +} \ No newline at end of file diff --git a/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/EraseBackgroundEditOption.kt b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/EraseBackgroundEditOption.kt new file mode 100644 index 0000000..dbccabd --- /dev/null +++ b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/EraseBackgroundEditOption.kt @@ -0,0 +1,430 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.resources.icons.Done +import com.t8rin.imagetoolbox.core.resources.icons.Redo +import com.t8rin.imagetoolbox.core.resources.icons.Undo +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.Rounded.Undo, + contentDescription = "Undo" + ) + } + EnhancedIconButton( + containerColor = Color.Transparent, + borderColor = MaterialTheme.colorScheme.outlineVariant( + luminance = 0.1f + ), + onClick = redo, + enabled = undonePaths.isNotEmpty() + ) { + Icon( + imageVector = Icons.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() + } + } +} \ No newline at end of file diff --git a/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/FilterEditOption.kt b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/FilterEditOption.kt new file mode 100644 index 0000000..90c101a --- /dev/null +++ b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/FilterEditOption.kt @@ -0,0 +1,366 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.AutoFixHigh +import com.t8rin.imagetoolbox.core.resources.icons.Done +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 + ) + } +} \ No newline at end of file diff --git a/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/FullscreenEditOption.kt b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/FullscreenEditOption.kt new file mode 100644 index 0000000..1d6d6e2 --- /dev/null +++ b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/FullscreenEditOption.kt @@ -0,0 +1,448 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.material3.rememberBottomSheetState +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.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.platform.LocalLayoutDirection +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Close +import com.t8rin.imagetoolbox.core.resources.icons.Tune +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.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.AutoCornersShape +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( + bottomSheetState = rememberBottomSheetState( + initialValue = SheetValue.PartiallyExpanded, + enabledValues = setOf( + SheetValue.PartiallyExpanded, + if (showControls) SheetValue.Expanded else SheetValue.PartiallyExpanded + ), + confirmValueChange = { + when (it) { + SheetValue.Hidden -> false + SheetValue.Expanded -> showControls + else -> true + } + } + ) + ), + content: @Composable () -> Unit +) { + val settingsState = LocalSettingsState.current + + var predictiveBackProgress by remember { + mutableFloatStateOf(0f) + } + + LaunchedEffect(predictiveBackProgress, visible) { + if (!visible && predictiveBackProgress != 0f) { + delay(400) + predictiveBackProgress = 0f + } + } + + AnimatedVisibility( + visible = visible, + modifier = Modifier.fillMaxSize(), + enter = fadeIn(tween(400)), + exit = fadeOut(tween(400)) + ) { + var showExitDialog by remember(visible) { mutableStateOf(false) } + val internalOnDismiss = { + if (!canGoBack) showExitDialog = true + else onDismiss() + } + val direction = LocalLayoutDirection.current + val bottomSheetScope = rememberCoroutineScope() + val isExpanded by remember { + derivedStateOf { + scaffoldState.bottomSheetState.currentValue == SheetValue.Expanded || + scaffoldState.bottomSheetState.targetValue == SheetValue.Expanded + } + } + 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 = + settingsState.enableSheetGestures || + scaffoldState.bottomSheetState.currentValue == SheetValue.PartiallyExpanded + && !scaffoldState.bottomSheetState.isAnimationRunning && showControls + + var bottomSheetPredictiveBackProgress by remember { + mutableFloatStateOf(0f) + } + val animatedBottomSheetPredictiveBackProgress by animateFloatAsState( + bottomSheetPredictiveBackProgress + ) + + val clean = { + bottomSheetPredictiveBackProgress = 0f + } + + PredictiveBackObserver( + onProgress = { progress -> + bottomSheetPredictiveBackProgress = progress / 6f + }, + onClean = { isCompleted -> + if (isCompleted) { + bottomSheetScope.launch { + delay(150) + clean() + } + scaffoldState.bottomSheetState.partialExpand() + } + clean() + }, + enabled = isExpanded + ) + + 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, + containerColor = Color.Transparent, + sheetContainerColor = Color.Transparent, + sheetShadowElevation = 0.dp, + sheetSwipeEnabled = sheetSwipeEnabled, + sheetContent = { + val animatedShape = AutoCornersShape( + (32.dp * (animatedBottomSheetPredictiveBackProgress * 10f)) + .coerceIn(0.dp, 32.dp) + ) + + Scaffold( + modifier = modifier + .heightIn(max = screenHeight * 0.7f) + .graphicsLayer { + val progress = animatedBottomSheetPredictiveBackProgress + scaleX = (1f - progress).coerceAtLeast(0.85f) + scaleY = (1f - progress).coerceAtLeast(0.85f) + transformOrigin = TransformOrigin( + pivotFractionX = 0.5f, + pivotFractionY = 1f + ) + shape = animatedShape + clip = progress > 0f + } + .clearFocusOnTap(), + topBar = { + val scope = rememberCoroutineScope() + Box( + modifier = Modifier.onSwipeDown(!sheetSwipeEnabled) { + scope.launch { + scaffoldState.bottomSheetState.partialExpand() + } + } + ) { + BottomAppBar( + modifier = Modifier + .drawHorizontalStroke(true) + .drawHorizontalStroke( + top = false, + enabled = showControls + ), + 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 + }, + enabled = !useScaffold || !isExpanded + ) + } else { + ExitBackHandler( + enabled = !useScaffold || !isExpanded, + onBack = internalOnDismiss + ) + } + + ExitWithoutSavingDialog( + onExit = onDismiss, + onDismiss = { showExitDialog = false }, + visible = showExitDialog + ) + } + } +} \ No newline at end of file diff --git a/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/ToneCurvesEditOption.kt b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/ToneCurvesEditOption.kt new file mode 100644 index 0000000..22592e0 --- /dev/null +++ b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/ToneCurvesEditOption.kt @@ -0,0 +1,220 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +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.Done +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 + ) + } +} diff --git a/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/screenLogic/SingleEditComponent.kt b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/screenLogic/SingleEditComponent.kt new file mode 100644 index 0000000..d327fc2 --- /dev/null +++ b/feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/screenLogic/SingleEditComponent.kt @@ -0,0 +1,1090 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.mutableStateMapOf +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.ImageExportProfilesUseCase +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.ImageExportProfile +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.ColorModel +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.SettingsManager +import com.t8rin.imagetoolbox.core.ui.utils.BaseHistoryComponent +import com.t8rin.imagetoolbox.core.ui.utils.ImageExportProfilesHolder +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.navigation.coroutineScope +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.core.utils.imageSize +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 com.t8rin.imagetoolbox.feature.single_edit.presentation.screenLogic.SingleEditComponent.HistorySnapshot +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext +import kotlin.math.roundToInt + +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 settingsManager: SettingsManager, + private val imageExportProfilesUseCase: ImageExportProfilesUseCase, + dispatchersHolder: DispatchersHolder, + addFiltersSheetComponentFactory: AddFiltersSheetComponent.Factory, + filterTemplateCreationSheetComponentFactory: FilterTemplateCreationSheetComponent.Factory, +) : BaseHistoryComponent( + dispatchersHolder = dispatchersHolder, + componentContext = componentContext +), ImageExportProfilesHolder by ImageExportProfilesHolder( + imageExportProfilesUseCase = imageExportProfilesUseCase, + componentScope = componentContext.coroutineScope +) { + + 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 _drawSpotHealCache = mutableStateMapOf() + val drawSpotHealCache: Map = _drawSpotHealCache + + 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 + ) + ) + ) + ) + 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 + + val currentImageUri: Uri? + get() = currentImageUriString()?.toUri() + + val currentImageSize: IntegerSize + get() = IntegerSize( + width = imageInfo.width, + height = imageInfo.height + ) + + private var currentCachedBitmapUri: String? = null + + 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 + + private val isAlwaysClearExif: Boolean get() = settingsManager.settingsState.value.isAlwaysClearExif + + init { + componentScope.launch { + val settingsState = settingsManager.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, + clearPreview: Boolean = true, + previewImageInfo: ImageInfo = imageInfo + ) { + if (resetPreset) { + _presetSelected.update { Preset.None } + } + _bitmap.value?.let { bmp -> + val preview = updatePreview( + bitmap = bmp, + previewImageInfo = previewImageInfo + ) + if (clearPreview) { + _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? + ) { + finalizePendingHistoryTransaction() + savingJob = trackProgress { + _isSaving.update { true } + getCurrentImageForExport()?.let { image -> + parseSaveResult( + fileController.save( + saveTarget = ImageSaveTarget( + imageInfo = imageInfo, + metadata = exif, + originalUri = uri.toString(), + sequenceNumber = null, + data = imageCompressor.compressAndTransform( + image = image, + imageInfo = imageInfo.copy( + originalUri = uri.toString() + ) + ), + presetInfo = presetSelected + ), + keepOriginalMetadata = true, + oneTimeSaveLocationUri = oneTimeSaveLocationUri + ).onSuccess(::registerSave) + ) + } + _isSaving.update { false } + } + } + + private suspend fun updatePreview( + bitmap: Bitmap, + previewImageInfo: ImageInfo = imageInfo + ): Bitmap? = withContext(defaultDispatcher) { + return@withContext previewImageInfo.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(previewImageInfo = newInfo) + } + } + } + + fun resetValues( + newBitmapComes: Boolean = false, + commitToHistory: Boolean = true + ) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + ImageInfo( + width = _originalSize.value?.width ?: 0, + height = _originalSize.value?.height ?: 0, + imageFormat = it.imageFormat, + originalUri = uri.toString() + ) + } + if (newBitmapComes) { + currentCachedBitmapUri = null + _bitmap.update { + _internalBitmap.value + } + } + debouncedImageCalculation { + checkBitmapAndUpdate( + resetPreset = true + ) + } + if (commitToHistory) { + commitHistoryFrom(beforeSnapshot) + } + } + + fun updateBitmapAfterEditing( + bitmap: Bitmap?, + saveOriginalSize: Boolean = false, + ) { + componentScope.launch { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + val cachedUri = bitmap?.let { + cacheEditedBitmap(it) + } + + if (!saveOriginalSize) { + val size = bitmap?.let { it.width to it.height } + _originalSize.update { + size?.run { IntegerSize(width = first, height = second) } + } + } + _drawSpotHealCache.clear() + _bitmap.update { + imageScaler.scaleUntilCanShow(bitmap) + } + currentCachedBitmapUri = cachedUri + _imageInfo.update { + it.copy( + rotationDegrees = 0f + ) + } + if (!saveOriginalSize) { + _imageInfo.update { + it.copy( + width = bitmap?.width ?: 0, + height = bitmap?.height ?: 0 + ) + } + } + debouncedImageCalculation { + checkBitmapAndUpdate( + resetPreset = true + ) + } + commitHistoryFrom(beforeSnapshot) + } + } + + fun updateImageUriAfterEditing(uri: Uri) { + componentScope.launch { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + val imageSize = uri.imageSize() + val preview = loadImagePreview(uri) + + currentCachedBitmapUri = uri.toString() + imageSize?.let { size -> + _originalSize.update { size } + } + _drawSpotHealCache.clear() + _bitmap.update { preview } + _imageInfo.update { info -> + info.copy( + rotationDegrees = 0f, + width = imageSize?.width ?: preview?.width ?: info.width, + height = imageSize?.height ?: preview?.height ?: info.height, + originalUri = uri.toString() + ) + } + debouncedImageCalculation { + checkBitmapAndUpdate( + resetPreset = true + ) + } + commitHistoryFrom(beforeSnapshot) + } + } + + fun rotateBitmapLeft() { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + it.copy( + rotationDegrees = it.rotationDegrees - 90f, + height = it.width, + width = it.height + ) + } + debouncedImageCalculation { + checkBitmapAndUpdate() + } + commitHistoryFrom(beforeSnapshot) + } + + fun rotateBitmapRight() { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + it.copy( + rotationDegrees = it.rotationDegrees + 90f, + height = it.width, + width = it.height + ) + } + debouncedImageCalculation { + checkBitmapAndUpdate() + } + commitHistoryFrom(beforeSnapshot) + } + + fun flipImage() { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + it.copy(isFlipped = !it.isFlipped) + } + debouncedImageCalculation { + checkBitmapAndUpdate() + } + commitHistoryFrom(beforeSnapshot) + } + + fun updateWidth(width: Int) { + if (imageInfo.width != width) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + it.copy(width = width) + } + debouncedImageCalculation { + checkBitmapAndUpdate( + resetPreset = true + ) + } + commitHistoryFrom(beforeSnapshot) + } + } + + fun updateHeight(height: Int) { + if (imageInfo.height != height) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + it.copy(height = height) + } + debouncedImageCalculation { + checkBitmapAndUpdate( + resetPreset = true + ) + } + commitHistoryFrom(beforeSnapshot) + } + } + + fun setQuality(quality: Quality) { + val currentQuality = imageInfo.quality + val coercedQuality = quality.coerceIn(imageInfo.imageFormat) + if ( + quality::class != coercedQuality::class && + currentQuality::class == coercedQuality::class + ) return + + if (currentQuality != coercedQuality) { + beginPendingHistoryTransaction() + _imageInfo.update { + it.copy(quality = coercedQuality) + } + debouncedImageCalculation { + checkBitmapAndUpdate() + } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setImageFormat(imageFormat: ImageFormat) { + if (imageInfo.imageFormat != imageFormat) { + if (pendingHistoryMode != PendingHistoryMode.FormatChange) { + finalizePendingHistoryTransaction() + } + beginPendingHistoryTransaction( + mode = PendingHistoryMode.FormatChange, + commitDelayMillis = formatHistoryTransactionDebounce + ) + _imageInfo.update { + it.copy( + imageFormat = imageFormat, + quality = it.quality.coerceIn(imageFormat) + ) + } + debouncedImageCalculation { + checkBitmapAndUpdate( + resetPreset = _presetSelected.value == Preset.Telegram && imageFormat != ImageFormat.Png.Lossless + ) + } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setResizeType(type: ResizeType) { + if (imageInfo.resizeType != type) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + it.copy( + resizeType = type.withOriginalSizeIfCrop(originalSize) + ) + } + debouncedImageCalculation { + checkBitmapAndUpdate( + resetPreset = false + ) + } + commitHistoryFrom(beforeSnapshot) + } + } + + fun setUri(uri: Uri) { + clearHistory() + currentCachedBitmapUri = null + registerChangesCleared() + _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 } + AppToastHost.showFailureToast(it) + } + ) + } + + private fun setImageData(imageData: ImageData) { + job = componentScope.launch { + _isImageLoading.update { true } + _exif.update { imageData.metadata.takeIf { !isAlwaysClearExif } } + val bitmap = imageData.image + val size = bitmap.width to bitmap.height + _originalSize.update { + size.run { IntegerSize(width = first, height = second) } + } + _drawSpotHealCache.clear() + _bitmap.update { + _internalBitmap.update { + imageScaler.scaleUntilCanShow(bitmap) + } + } + resetValues( + newBitmapComes = true, + commitToHistory = false + ) + _imageInfo.update { + imageData.imageInfo.copy( + width = size.first, + height = size.second + ) + } + checkBitmapAndUpdate( + resetPreset = _presetSelected.value == Preset.Telegram && imageData.imageInfo.imageFormat != ImageFormat.Png.Lossless + ) + resetHistory() + registerChangesCleared() + _isImageLoading.update { false } + } + } + + fun shareBitmap() { + savingJob = trackProgress { + _isSaving.update { true } + getCurrentImageForExport()?.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 } + getCurrentImageForExport()?.let { image -> + shareProvider.cacheImage( + image = image, + imageInfo = imageInfo.copy(originalUri = uri.toString()) + )?.let { uri -> + onComplete(uri.toUri()) + } + } + _isSaving.update { false } + } + } + + private suspend fun cacheEditedBitmap(bitmap: Bitmap): String? = shareProvider.cacheImage( + image = bitmap, + imageInfo = ImageInfo( + originalUri = uri.toString(), + imageFormat = ImageFormat.Png.Lossless, + width = bitmap.width, + height = bitmap.height + ) + ) + + fun canShow(): Boolean = bitmap?.let { imagePreviewCreator.canShow(it) } == true + + override fun updateProfile(profile: Preset) { + componentScope.launch { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + if (profile is Preset.AspectRatio && profile.ratio != 1f) { + _imageInfo.update { it.copy(rotationDegrees = 0f) } + } + setBitmapInfo( + imageTransformer.applyPresetBy( + image = bitmap, + preset = profile, + currentInfo = imageInfo.copy( + originalUri = uri.toString() + ) + ) + ) + _presetSelected.update { profile } + commitHistoryFrom(beforeSnapshot) + } + } + + override fun saveProfile(name: String) { + componentScope.launch { + imageExportProfilesUseCase.upsert( + ImageExportProfile.from( + name = name, + imageInfo = imageInfo, + preset = presetSelected, + backgroundColorForNoAlphaFormats = settingsManager + .settingsState + .value + .backgroundForNoAlphaImageFormats + ) + ) + } + } + + override fun applyProfile(profile: ImageExportProfile) { + componentScope.launch { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + restoreProfileBackgroundColor(profile) + val restoredInfo = profile.toImageInfo(imageInfo).copy( + originalUri = uri.toString() + ) + setBitmapInfo( + profile.applyExportSettingsTo( + imageTransformer.applyPresetBy( + image = bitmap, + preset = profile.preset, + currentInfo = restoredInfo + ) + ) + ) + _presetSelected.update { profile.preset } + commitHistoryFrom(beforeSnapshot) + } + } + + private suspend fun restoreProfileBackgroundColor(profile: ImageExportProfile) { + val color = profile.backgroundColorModel() ?: return + if (settingsManager.settingsState.value.backgroundForNoAlphaImageFormats != color) { + settingsManager.setBackgroundColorForNoAlphaFormats(color) + } + } + + fun clearExif() { + updateExif(_exif.value?.clearAllAttributes()) + } + + private fun updateExif(metadata: Metadata?) { + _exif.update { metadata } + } + + 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) + + suspend fun loadImagePreview(uri: Uri): Bitmap? { + val targetSize = uri.imageSize()?.safePreviewSize() + val preview = if (targetSize != null) { + imageGetter.getImage( + data = uri, + size = targetSize + ) + } else { + imageGetter.getImage( + uri = uri.toString(), + originalSize = false, + onFailure = AppToastHost::showFailureToast + )?.image + } + + return imageScaler.scaleUntilCanShow(preview) + } + + private suspend fun getCurrentImageForExport(): Bitmap? { + return currentImageUriString()?.let { uri -> + imageGetter.getImage( + uri = uri, + originalSize = true, + onFailure = AppToastHost::showFailureToast + )?.image + } ?: bitmap + } + + private fun currentImageUriString(): String? = currentCachedBitmapUri + ?: uri.takeIf { it != Uri.EMPTY }?.toString() + + private fun IntegerSize.safePreviewSize(): IntegerSize { + var targetWidth = width + var targetHeight = height + + while (targetWidth * targetHeight * 4L >= PreviewMaxBytes) { + targetWidth = (targetWidth * 0.85f).roundToInt().coerceAtLeast(1) + targetHeight = (targetHeight * 0.85f).roundToInt().coerceAtLeast(1) + } + + return IntegerSize( + width = targetWidth, + height = targetHeight + ) + } + + 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() } + _drawSpotHealCache.clear() + _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 cacheDrawSpotHealPathResult( + key: Int, + bitmap: Bitmap + ) { + _drawSpotHealCache[key] = bitmap + } + + fun removePath(pathPaint: UiPathPaint) { + _drawPaths.update { it - pathPaint } + _drawSpotHealCache.remove(pathPaint.hashCode()) + registerChanges() + } + + 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) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageInfo.update { + it.copy(imageScaleMode = imageScaleMode) + } + debouncedImageCalculation { + checkBitmapAndUpdate() + } + commitHistoryFrom(beforeSnapshot) + } + + 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 } + } + + override fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( + cachedUri = currentCachedBitmapUri, + originalSize = originalSize, + imageInfo = imageInfo.asHistoryImageInfo(), + preset = presetSelected, + backgroundColorForNoAlphaFormats = settingsManager + .settingsState + .value + .backgroundForNoAlphaImageFormats + ) + + override fun applyHistorySnapshot(snapshot: HistorySnapshot) { + currentCachedBitmapUri = snapshot.cachedUri + _originalSize.value = snapshot.originalSize + _imageInfo.value = snapshot.imageInfo + _presetSelected.value = snapshot.preset + restoreBackgroundColorForNoAlphaFormats( + settingsManager = settingsManager, + backgroundColorForNoAlphaFormats = snapshot.backgroundColorForNoAlphaFormats + ) + + job = componentScope.launch { + _isImageLoading.update { true } + _bitmap.value = snapshot.cachedUri + ?.toUri() + ?.let { loadImagePreview(it) } + ?: _internalBitmap.value + checkBitmapAndUpdate(clearPreview = false) + _isImageLoading.update { false } + } + } + + private fun ImageInfo.asHistoryImageInfo(): ImageInfo = copy(sizeInBytes = 0) + + override fun hasSameUndoState( + first: HistorySnapshot, + second: HistorySnapshot + ): Boolean = first.normalized() == second.normalized() + + private fun HistorySnapshot.normalized(): HistorySnapshot = copy( + imageInfo = imageInfo.asHistoryImageInfo() + ) + + data class HistorySnapshot( + val cachedUri: String? = null, + val originalSize: IntegerSize? = null, + val imageInfo: ImageInfo = ImageInfo(), + val preset: Preset = Preset.None, + val backgroundColorForNoAlphaFormats: ColorModel = ColorModel(-0x1000000) + ) + + private companion object { + const val PreviewMaxBytes = 3096L * 3096L * 3L + } + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUri: Uri?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): SingleEditComponent + } +} diff --git a/feature/svg-maker/.gitignore b/feature/svg-maker/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/svg-maker/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/svg-maker/build.gradle.kts b/feature/svg-maker/build.gradle.kts new file mode 100644 index 0000000..781cdb0 --- /dev/null +++ b/feature/svg-maker/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/svg-maker/src/main/AndroidManifest.xml b/feature/svg-maker/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/svg-maker/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/data/AndroidSvgManager.kt b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/data/AndroidSvgManager.kt new file mode 100644 index 0000000..4a48c0f --- /dev/null +++ b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/data/AndroidSvgManager.kt @@ -0,0 +1,106 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.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.data.tracer.ImageTracer +import com.t8rin.imagetoolbox.feature.svg_maker.data.tracer.ImageTracer.Options +import com.t8rin.imagetoolbox.feature.svg_maker.data.tracer.ImageTracer.SvgListener +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 -> + ImageTracer.imageToSVG( + bitmap = imageGetter.getImage( + data = uri, + size = if (params.isImageSampled) { + IntegerSize(1000, 1000) + } else null + )!!, + options = params.toOptions(), + palette = null, + listener = SvgTracer(writer::write) + ) + } + } + + 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(): Options = Options( + numberOfColors = colorsCount.toFloat(), + colorQuantCycles = quantizationCyclesCount.toFloat(), + colorSampling = if (isPaletteSampled) 1f else 0f, + blurRadius = blurRadius.toFloat(), + blurDelta = blurDelta.toFloat(), + pathOmit = pathOmit.toFloat(), + lineThreshold = linesThreshold, + quadraticThreshold = quadraticThreshold, + roundCoords = coordinatesRoundingAmount.toFloat(), + minColorRatio = minColorRatio, + scale = svgPathsScale, + seed = seed.toFloat() + ) + +} \ No newline at end of file diff --git a/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/data/tracer/ImageTracer.kt b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/data/tracer/ImageTracer.kt new file mode 100644 index 0000000..9f6a7e9 --- /dev/null +++ b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/data/tracer/ImageTracer.kt @@ -0,0 +1,1320 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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") + +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.data.tracer + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import com.t8rin.imagetoolbox.core.ui.utils.helper.AppVersion +import java.io.File +import java.nio.IntBuffer +import java.util.TreeMap +import kotlin.math.abs +import kotlin.math.floor +import kotlin.math.pow +import kotlin.math.roundToInt +import kotlin.random.Random + +internal data object ImageTracer { + + private val SIGNATURE = "desc=\"Created with ImageToolbox version $AppVersion\" " + + data class Options( + val lineThreshold: Float = 1f, + val quadraticThreshold: Float = 1f, + val pathOmit: Float = 8f, + val colorSampling: Float = 1f, + val numberOfColors: Float = 16f, + val minColorRatio: Float = 0.02f, + val colorQuantCycles: Float = 3f, + val scale: Float = 1f, + val roundCoords: Float = 1f, + val lineControlPointRadius: Float = 0f, + val quadraticControlPointRadius: Float = 0f, + val description: Float = 1f, + val viewBox: Float = 0f, + val blurRadius: Float = 0f, + val blurDelta: Float = 20f, + val seed: Float = 0f + ) + + private val pathsDirLookup = byteArrayOf( + 0, 0, 3, 0, + 1, 0, 3, 0, + 0, 3, 3, 1, + 0, 3, 0, 0 + ) + + private val pathsHolepathLookup = booleanArrayOf( + false, false, false, false, + false, false, false, true, + false, false, false, true, + false, true, true, false + ) + + private val pathsCombinedLookup = arrayOf( + arrayOf( + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(-1, -1, -1, -1) + ), + arrayOf( + byteArrayOf(0, 1, 0, -1), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(0, 2, -1, 0) + ), + arrayOf( + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(0, 1, 0, -1), + byteArrayOf(0, 0, 1, 0) + ), + arrayOf( + byteArrayOf(0, 0, 1, 0), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(0, 2, -1, 0), + byteArrayOf(-1, -1, -1, -1) + ), + arrayOf( + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(0, 0, 1, 0), + byteArrayOf(0, 3, 0, 1), + byteArrayOf(-1, -1, -1, -1) + ), + arrayOf( + byteArrayOf(13, 3, 0, 1), + byteArrayOf(13, 2, -1, 0), + byteArrayOf(7, 1, 0, -1), + byteArrayOf(7, 0, 1, 0) + ), + arrayOf( + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(0, 1, 0, -1), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(0, 3, 0, 1) + ), + arrayOf( + byteArrayOf(0, 3, 0, 1), + byteArrayOf(0, 2, -1, 0), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(-1, -1, -1, -1) + ), + arrayOf( + byteArrayOf(0, 3, 0, 1), + byteArrayOf(0, 2, -1, 0), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(-1, -1, -1, -1) + ), + arrayOf( + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(0, 1, 0, -1), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(0, 3, 0, 1) + ), + arrayOf( + byteArrayOf(11, 1, 0, -1), + byteArrayOf(14, 0, 1, 0), + byteArrayOf(14, 3, 0, 1), + byteArrayOf(11, 2, -1, 0) + ), + arrayOf( + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(0, 0, 1, 0), + byteArrayOf(0, 3, 0, 1), + byteArrayOf(-1, -1, -1, -1) + ), + arrayOf( + byteArrayOf(0, 0, 1, 0), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(0, 2, -1, 0), + byteArrayOf(-1, -1, -1, -1) + ), + arrayOf( + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(0, 1, 0, -1), + byteArrayOf(0, 0, 1, 0) + ), + arrayOf( + byteArrayOf(0, 1, 0, -1), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(0, 2, -1, 0) + ), + arrayOf( + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(-1, -1, -1, -1), + byteArrayOf(-1, -1, -1, -1) + ) + ) + + private val gaussianKernels = arrayOf( + doubleArrayOf(0.27901, 0.44198, 0.27901), + doubleArrayOf(0.135336, 0.228569, 0.272192, 0.228569, 0.135336), + doubleArrayOf(0.086776, 0.136394, 0.178908, 0.195843, 0.178908, 0.136394, 0.086776), + doubleArrayOf( + 0.063327, + 0.093095, + 0.122589, + 0.144599, + 0.152781, + 0.144599, + 0.122589, + 0.093095, + 0.063327 + ), + doubleArrayOf( + 0.049692, + 0.069304, + 0.089767, + 0.107988, + 0.120651, + 0.125194, + 0.120651, + 0.107988, + 0.089767, + 0.069304, + 0.049692 + ) + ) + + fun saveString(filename: String, str: String) { + val file = File(filename) + if (!file.exists()) { + file.createNewFile() + } + file.bufferedWriter().use { writer -> + writer.write(str) + } + } + + fun loadImageData(filename: String): ImageData { + val image = BitmapFactory.decodeFile(File(filename).absolutePath) + return loadImageData(image) + } + + fun loadImageData(image: Bitmap): ImageData { + val width = image.width + val height = image.height + val intBuffer = IntBuffer.allocate(width * height) + image.copyPixelsToBuffer(intBuffer) + val rawData = intBuffer.array() + val data = ByteArray(rawData.size * 4) + + for (i in rawData.indices) { + data[(i * 4) + 3] = byteTransfer((rawData[i] ushr 24).toByte()) + data[(i * 4) + 2] = byteTransfer((rawData[i] ushr 16).toByte()) + data[(i * 4) + 1] = byteTransfer((rawData[i] ushr 8).toByte()) + data[i * 4] = byteTransfer(rawData[i].toByte()) + } + + return ImageData(width, height, data) + } + + private fun byteTransfer(b: Byte): Byte = if (b < 0) { + (b + 128).toByte() + } else { + (b - 128).toByte() + } + + fun imageToSVG( + filename: String, + options: Options?, + palette: Array? + ): String { + val checkedOptions = checkOptions(options) + val imageData = loadImageData(filename) + return imageDataToSVG(imageData, checkedOptions, palette) + } + + fun imageToSVG( + bitmap: Bitmap, + options: Options?, + palette: Array? + ): String { + val checkedOptions = checkOptions(options) + val imageData = loadImageData(bitmap) + return imageDataToSVG(imageData, checkedOptions, palette) + } + + fun imageDataToSVG( + imageData: ImageData, + options: Options?, + palette: Array? + ): String { + val checkedOptions = checkOptions(options) + val indexedImage = imageDataToIndexedImage(imageData, checkedOptions, palette) + return getsvgstring(indexedImage, checkedOptions) + } + + fun imageToSVG( + bitmap: Bitmap, + options: Options?, + palette: Array?, + listener: SvgListener + ) { + val checkedOptions = checkOptions(options) + val imageData = loadImageData(bitmap) + imageDataToSVG(imageData, checkedOptions, palette, listener) + } + + fun imageDataToSVG( + imageData: ImageData, + options: Options?, + palette: Array?, + listener: SvgListener + ) { + val checkedOptions = checkOptions(options) + val indexedImage = imageDataToIndexedImage(imageData, checkedOptions, palette) + getsvgstring(indexedImage, checkedOptions, listener) + } + + fun imageDataToIndexedImage( + imageData: ImageData, + options: Options?, + palette: Array? + ): IndexedImage { + val checkedOptions = checkOptions(options) + val indexedImage = colorquantization(imageData, palette, checkedOptions) + val rawLayers = layering(indexedImage) + val batchPathScan = batchpathscan( + layers = rawLayers, + pathomit = floor(checkedOptions.pathOmit.toDouble()).toFloat() + ) + indexedImage.layers.clear() + indexedImage.layers.addAll( + batchtracelayers( + binternodes = batchinternodes(batchPathScan), + ltres = checkedOptions.lineThreshold, + qtres = checkedOptions.quadraticThreshold + ) + ) + return indexedImage + } + + private fun checkOptions(options: Options?): Options = options ?: Options() + + private fun colorquantization( + imgd: ImageData, + palette: Array?, + options: Options + ): IndexedImage { + val numberOfColors = floor(options.numberOfColors.toDouble()).toInt() + val minRatio = options.minColorRatio + val cycles = floor(options.colorQuantCycles.toDouble()).toInt() + val random = Random(options.seed.toLong()) + val arr = Array(imgd.height + 2) { IntArray(imgd.width + 2) } + + for (j in 0 until imgd.height + 2) { + arr[j][0] = -1 + arr[j][imgd.width + 1] = -1 + } + for (i in 0 until imgd.width + 2) { + arr[0][i] = -1 + arr[imgd.height + 1][i] = -1 + } + + var workingPalette = palette?.copyPalette() + if (workingPalette == null) { + workingPalette = if (options.colorSampling != 0f) { + samplepalette(numberOfColors, imgd, random) + } else { + createPalette(numberOfColors, random) + } + } + + var workingImageData = imgd + if (options.blurRadius > 0f) { + workingImageData = blur( + imgd = workingImageData, + rad = options.blurRadius, + del = options.blurDelta + ) + } + + val paletteAccumulator = Array(workingPalette.size) { LongArray(5) } + + for (cnt in 0 until cycles) { + if (cnt > 0) { + for (k in workingPalette.indices) { + if (paletteAccumulator[k][3] > 0) { + workingPalette[k][0] = + (-128 + (paletteAccumulator[k][0] / paletteAccumulator[k][4])).toByte() + workingPalette[k][1] = + (-128 + (paletteAccumulator[k][1] / paletteAccumulator[k][4])).toByte() + workingPalette[k][2] = + (-128 + (paletteAccumulator[k][2] / paletteAccumulator[k][4])).toByte() + workingPalette[k][3] = + (-128 + (paletteAccumulator[k][3] / paletteAccumulator[k][4])).toByte() + } + + val ratio = + paletteAccumulator[k][4].toDouble() / + (workingImageData.width * workingImageData.height).toDouble() + if (ratio < minRatio && cnt < cycles - 1) { + workingPalette[k][0] = randomPaletteByte(random) + workingPalette[k][1] = randomPaletteByte(random) + workingPalette[k][2] = randomPaletteByte(random) + workingPalette[k][3] = randomPaletteByte(random) + } + } + } + + for (i in workingPalette.indices) { + paletteAccumulator[i][0] = 0 + paletteAccumulator[i][1] = 0 + paletteAccumulator[i][2] = 0 + paletteAccumulator[i][3] = 0 + paletteAccumulator[i][4] = 0 + } + + for (j in 0 until workingImageData.height) { + for (i in 0 until workingImageData.width) { + val idx = ((j * workingImageData.width) + i) * 4 + var closestDistance = 256 + 256 + 256 + 256 + var closestIndex = 0 + + for (k in workingPalette.indices) { + val c1 = + abs(workingPalette[k][0].toInt() - workingImageData.data[idx].toInt()) + val c2 = + abs(workingPalette[k][1].toInt() - workingImageData.data[idx + 1].toInt()) + val c3 = + abs(workingPalette[k][2].toInt() - workingImageData.data[idx + 2].toInt()) + val c4 = + abs(workingPalette[k][3].toInt() - workingImageData.data[idx + 3].toInt()) + val colorDistance = c1 + c2 + c3 + (c4 * 4) + + if (colorDistance < closestDistance) { + closestDistance = colorDistance + closestIndex = k + } + } + + paletteAccumulator[closestIndex][0] += (128 + workingImageData.data[idx]).toLong() + paletteAccumulator[closestIndex][1] += (128 + workingImageData.data[idx + 1]).toLong() + paletteAccumulator[closestIndex][2] += (128 + workingImageData.data[idx + 2]).toLong() + paletteAccumulator[closestIndex][3] += (128 + workingImageData.data[idx + 3]).toLong() + paletteAccumulator[closestIndex][4]++ + + arr[j + 1][i + 1] = closestIndex + } + } + } + + return IndexedImage(arr, workingPalette) + } + + private fun createPalette(colorNumber: Int, random: Random): Array { + val palette = Array(colorNumber) { ByteArray(4) } + + if (colorNumber < 8) { + val grayStep = 255.0 / (colorNumber - 1) + for (colorCount in 0 until colorNumber) { + val gray = (colorCount * grayStep).roundToInt() + palette[colorCount][0] = (-128 + gray).toByte() + palette[colorCount][1] = (-128 + gray).toByte() + palette[colorCount][2] = (-128 + gray).toByte() + palette[colorCount][3] = 127.toByte() + } + } else { + val colorQNum = floor(colorNumber.toDouble().pow(1.0 / 3.0)).toInt() + val colorStep = floor(255.0 / (colorQNum - 1)).toInt() + var colorCount = 0 + + for (redCount in 0 until colorQNum) { + for (greenCount in 0 until colorQNum) { + for (blueCount in 0 until colorQNum) { + palette[colorCount][0] = (-128 + (redCount * colorStep)).toByte() + palette[colorCount][1] = (-128 + (greenCount * colorStep)).toByte() + palette[colorCount][2] = (-128 + (blueCount * colorStep)).toByte() + palette[colorCount][3] = 127.toByte() + colorCount++ + } + } + } + + for (randomCount in colorCount until colorNumber) { + palette[randomCount][0] = randomPaletteByte(random) + palette[randomCount][1] = randomPaletteByte(random) + palette[randomCount][2] = randomPaletteByte(random) + palette[randomCount][3] = randomPaletteByte(random) + } + } + + return palette + } + + fun samplepalette( + numberofcolors: Int, + imgd: ImageData, + random: Random = Random(0) + ): Array { + val palette = Array(numberofcolors) { ByteArray(4) } + + for (i in 0 until numberofcolors) { + val idx = random.nextInt(imgd.data.size / 4) * 4 + palette[i][0] = imgd.data[idx] + palette[i][1] = imgd.data[idx + 1] + palette[i][2] = imgd.data[idx + 2] + palette[i][3] = imgd.data[idx + 3] + } + + return palette + } + + fun layering(ii: IndexedImage): Array> { + val arrayWidth = ii.array[0].size + val arrayHeight = ii.array.size + val layers = Array(ii.palette.size) { Array(arrayHeight) { IntArray(arrayWidth) } } + + for (j in 1 until arrayHeight - 1) { + for (i in 1 until arrayWidth - 1) { + val value = ii.array[j][i] + val n1 = if (ii.array[j - 1][i - 1] == value) 1 else 0 + val n2 = if (ii.array[j - 1][i] == value) 1 else 0 + val n3 = if (ii.array[j - 1][i + 1] == value) 1 else 0 + val n4 = if (ii.array[j][i - 1] == value) 1 else 0 + val n5 = if (ii.array[j][i + 1] == value) 1 else 0 + val n6 = if (ii.array[j + 1][i - 1] == value) 1 else 0 + val n7 = if (ii.array[j + 1][i] == value) 1 else 0 + val n8 = if (ii.array[j + 1][i + 1] == value) 1 else 0 + + layers[value][j + 1][i + 1] = 1 + (n5 * 2) + (n8 * 4) + (n7 * 8) + if (n4 == 0) { + layers[value][j + 1][i] = 2 + (n7 * 4) + (n6 * 8) + } + if (n2 == 0) { + layers[value][j][i + 1] = (n3 * 2) + (n5 * 4) + 8 + } + if (n1 == 0) { + layers[value][j][i] = (n2 * 2) + 4 + (n4 * 8) + } + } + } + + return layers + } + + fun pathscan(arr: Array, pathomit: Float): ArrayList>> { + val paths = ArrayList>>() + + for (j in arr.indices) { + for (i in arr[j].indices) { + if (arr[j][i] != 0 && arr[j][i] != 15) { + var px = i + var py = j + val thisPath = ArrayList>() + paths.add(thisPath) + + var dir = pathsDirLookup[arr[py][px]].toInt() + val holePath = pathsHolepathLookup[arr[py][px]] + var pathFinished = false + + while (!pathFinished) { + thisPath.add(arrayOf(px - 1, py - 1, arr[py][px])) + + val lookupRow = pathsCombinedLookup[arr[py][px]][dir] + arr[py][px] = lookupRow[0].toInt() + dir = lookupRow[1].toInt() + px += lookupRow[2].toInt() + py += lookupRow[3].toInt() + + if ((px - 1) == thisPath[0][0] && (py - 1) == thisPath[0][1]) { + pathFinished = true + if (holePath || thisPath.size.toFloat() < pathomit) { + paths.remove(thisPath) + } + } + } + } + } + } + + return paths + } + + fun batchpathscan( + layers: Array>, + pathomit: Float + ): ArrayList>>> { + val batchPaths = ArrayList>>>() + for (layer in layers) { + batchPaths.add(pathscan(layer, pathomit)) + } + return batchPaths + } + + fun internodes(paths: ArrayList>>): ArrayList>> { + val internodes = ArrayList>>() + + for (path in paths) { + val thisInternodePath = ArrayList>() + internodes.add(thisInternodePath) + val pathLength = path.size + + for (pointIndex in 0 until pathLength) { + val nextIndex = (pointIndex + 1) % pathLength + val nextNextIndex = (pointIndex + 2) % pathLength + val currentPoint = path[pointIndex] + val nextPoint = path[nextIndex] + val nextNextPoint = path[nextNextIndex] + + val x = (currentPoint[0] + nextPoint[0]) / 2.0 + val y = (currentPoint[1] + nextPoint[1]) / 2.0 + val nextX = (nextPoint[0] + nextNextPoint[0]) / 2.0 + val nextY = (nextPoint[1] + nextNextPoint[1]) / 2.0 + + val direction = when { + x < nextX && y < nextY -> 1.0 + x < nextX && y > nextY -> 7.0 + x < nextX -> 0.0 + x > nextX && y < nextY -> 3.0 + x > nextX && y > nextY -> 5.0 + x > nextX -> 4.0 + y < nextY -> 2.0 + y > nextY -> 6.0 + else -> 8.0 + } + + thisInternodePath.add(arrayOf(x, y, direction)) + } + } + + return internodes + } + + private fun batchinternodes( + batchPaths: ArrayList>>> + ): ArrayList>>> { + val batchInternodes = ArrayList>>>() + for (paths in batchPaths) { + batchInternodes.add(internodes(paths)) + } + return batchInternodes + } + + fun tracepath( + path: ArrayList>, + ltreshold: Float, + qtreshold: Float + ): ArrayList> { + var pointIndex = 0 + val smp = ArrayList>() + val pathLength = path.size + + while (pointIndex < pathLength) { + val segmentType1 = path[pointIndex][2] + var segmentType2 = -1.0 + var sequenceEnd = pointIndex + 1 + + while ( + sequenceEnd < pathLength - 1 && + ( + path[sequenceEnd][2] == segmentType1 || + path[sequenceEnd][2] == segmentType2 || + segmentType2 == -1.0 + ) + ) { + if (path[sequenceEnd][2] != segmentType1 && segmentType2 == -1.0) { + segmentType2 = path[sequenceEnd][2] + } + sequenceEnd++ + } + + if (sequenceEnd == pathLength - 1) { + sequenceEnd = 0 + } + + smp.addAll(fitSequence(path, ltreshold, qtreshold, pointIndex, sequenceEnd)) + + pointIndex = if (sequenceEnd > 0) { + sequenceEnd + } else { + pathLength + } + } + + return smp + } + + private fun fitSequence( + path: ArrayList>, + ltreshold: Float, + qtreshold: Float, + seqstart: Int, + seqend: Int + ): ArrayList> { + var segment = ArrayList>() + val pathLength = path.size + + if (seqend !in 0..pathLength) { + return segment + } + + var errorPoint = seqstart + var curvePass = true + var errorValue = 0.0 + var totalLength = (seqend - seqstart).toDouble() + if (totalLength < 0) { + totalLength += pathLength.toDouble() + } + + val vx = (path[seqend][0] - path[seqstart][0]) / totalLength + val vy = (path[seqend][1] - path[seqstart][1]) / totalLength + + var pointIndex = (seqstart + 1) % pathLength + while (pointIndex != seqend) { + var pointLength = (pointIndex - seqstart).toDouble() + if (pointLength < 0) { + pointLength += pathLength.toDouble() + } + + val px = path[seqstart][0] + (vx * pointLength) + val py = path[seqstart][1] + (vy * pointLength) + val distance = squaredDistance(path[pointIndex][0], path[pointIndex][1], px, py) + + if (distance > ltreshold) { + curvePass = false + } + if (distance > errorValue) { + errorPoint = pointIndex + errorValue = distance + } + + pointIndex = (pointIndex + 1) % pathLength + } + + if (curvePass) { + segment.add( + arrayOf( + 1.0, + path[seqstart][0], + path[seqstart][1], + path[seqend][0], + path[seqend][1], + 0.0, + 0.0 + ) + ) + return segment + } + + val fitPoint = errorPoint + curvePass = true + errorValue = 0.0 + + var fitLength = (fitPoint - seqstart).toDouble() + if (fitLength < 0) { + fitLength += pathLength.toDouble() + } + + var t = fitLength / totalLength + var t1 = (1.0 - t) * (1.0 - t) + var t2 = 2.0 * (1.0 - t) * t + var t3 = t * t + val cpx = + (((t1 * path[seqstart][0]) + (t3 * path[seqend][0])) - path[fitPoint][0]) / -t2 + val cpy = + (((t1 * path[seqstart][1]) + (t3 * path[seqend][1])) - path[fitPoint][1]) / -t2 + + pointIndex = (seqstart + 1) % pathLength + while (pointIndex != seqend) { + var pointLength = (pointIndex - seqstart).toDouble() + if (pointLength < 0) { + pointLength += pathLength.toDouble() + } + + t = pointLength / totalLength + t1 = (1.0 - t) * (1.0 - t) + t2 = 2.0 * (1.0 - t) * t + t3 = t * t + + val px = (t1 * path[seqstart][0]) + (t2 * cpx) + (t3 * path[seqend][0]) + val py = (t1 * path[seqstart][1]) + (t2 * cpy) + (t3 * path[seqend][1]) + val distance = squaredDistance(path[pointIndex][0], path[pointIndex][1], px, py) + + if (distance > qtreshold) { + curvePass = false + } + if (distance > errorValue) { + errorPoint = pointIndex + errorValue = distance + } + + pointIndex = (pointIndex + 1) % pathLength + } + + if (curvePass) { + segment.add( + arrayOf( + 2.0, + path[seqstart][0], + path[seqstart][1], + cpx, + cpy, + path[seqend][0], + path[seqend][1] + ) + ) + return segment + } + + val splitPoint = (fitPoint + errorPoint) / 2 + segment = fitSequence(path, ltreshold, qtreshold, seqstart, splitPoint) + segment.addAll(fitSequence(path, ltreshold, qtreshold, splitPoint, seqend)) + return segment + } + + fun batchtracepaths( + internodepaths: ArrayList>>, + ltres: Float, + qtres: Float + ): ArrayList>> { + val batchTracedPaths = ArrayList>>() + for (internodePath in internodepaths) { + batchTracedPaths.add(tracepath(internodePath, ltres, qtres)) + } + return batchTracedPaths + } + + fun batchtracelayers( + binternodes: ArrayList>>>, + ltres: Float, + qtres: Float + ): ArrayList>>> { + val batchTracedLayers = ArrayList>>>() + for (internodePaths in binternodes) { + batchTracedLayers.add(batchtracepaths(internodePaths, ltres, qtres)) + } + return batchTracedLayers + } + + private fun roundToDecimal(value: Float, places: Float): Float { + val multiplier = 10.0.pow(places.toDouble()) + return ((value * multiplier).roundToInt() / multiplier).toFloat() + } + + private fun trace( + sb: StringBuilder, + desc: String, + segments: ArrayList>, + color: String, + options: Options + ) { + val scale = options.scale.toDouble() + val lcpr = options.lineControlPointRadius.toDouble() + val qcpr = options.quadraticControlPointRadius.toDouble() + val roundcoords = floor(options.roundCoords.toDouble()).toFloat() + + sb.append("") + + for (segment in segments) { + if (lcpr > 0 && segment[0] == 1.0) { + sb.append("") + } + if (qcpr > 0 && segment[0] == 2.0) { + appendQuadraticControlPoints(sb, segment, scale, qcpr) + } + } + } + + fun trace( + listener: SvgListener, + desc: String, + segments: ArrayList>, + colorstr: String, + options: Options + ) { + val scale = options.scale.toDouble() + val lcpr = options.lineControlPointRadius.toDouble() + val qcpr = options.quadraticControlPointRadius.toDouble() + val roundcoords = floor(options.roundCoords.toDouble()).toFloat() + + listener.onProgress("") + + for (segment in segments) { + if (lcpr > 0 && segment[0] == 1.0) { + listener.onProgress("") + } + if (qcpr > 0 && segment[0] == 2.0) { + appendQuadraticControlPoints(listener, segment, scale, qcpr) + } + } + } + + fun getsvgstring(ii: IndexedImage, options: Options?): String { + val checkedOptions = checkOptions(options) + val w = (ii.width * checkedOptions.scale).toInt() + val h = (ii.height * checkedOptions.scale).toInt() + val viewBoxOrViewport = if (checkedOptions.viewBox != 0f) { + "viewBox=\"0 0 $w $h\" " + } else { + "width=\"$w\" height=\"$h\" " + } + val svgString = StringBuilder( + "") + + val zindex = TreeMap>() + for (layerIndex in ii.layers.indices) { + for (pathIndex in ii.layers[layerIndex].indices) { + val label = + (ii.layers[layerIndex][pathIndex][0][2] * w) + ii.layers[layerIndex][pathIndex][0][1] + zindex[label] = arrayOf(layerIndex, pathIndex) + } + } + + for (entry in zindex.entries) { + val layerIndex = entry.value[0] + val pathIndex = entry.value[1] + val pathDescription = if (checkedOptions.description != 0f) { + "desc=\"l $layerIndex p $pathIndex\" " + } else { + "" + } + trace( + sb = svgString, + desc = pathDescription, + segments = ii.layers[layerIndex][pathIndex], + color = tosvgcolorstr(ii.palette[layerIndex]), + options = checkedOptions + ) + } + + svgString.append("") + return svgString.toString() + } + + fun getsvgstring( + ii: IndexedImage, + options: Options?, + listener: SvgListener + ) { + val checkedOptions = checkOptions(options) + val w = (ii.width * checkedOptions.scale).toInt() + val h = (ii.height * checkedOptions.scale).toInt() + val viewBoxOrViewport = if (checkedOptions.viewBox != 0f) { + "viewBox=\"0 0 $w $h\" " + } else { + "width=\"$w\" height=\"$h\" " + } + + listener.onProgress("") + + val zindex = TreeMap>() + for (layerIndex in ii.layers.indices) { + for (pathIndex in ii.layers[layerIndex].indices) { + val label = + (ii.layers[layerIndex][pathIndex][0][2] * w) + ii.layers[layerIndex][pathIndex][0][1] + zindex[label] = arrayOf(layerIndex, pathIndex) + } + } + + for (entry in zindex.entries) { + val layerIndex = entry.value[0] + val pathIndex = entry.value[1] + val pathDescription = if (checkedOptions.description != 0f) { + "desc=\"l $layerIndex p $pathIndex\" " + } else { + "" + } + trace( + listener = listener, + desc = pathDescription, + segments = ii.layers[layerIndex][pathIndex], + colorstr = tosvgcolorstr(ii.palette[layerIndex]), + options = checkedOptions + ) + } + + listener.onProgress("") + } + + private fun tosvgcolorstr(c: ByteArray): String { + val red = c[0] + 128 + val green = c[1] + 128 + val blue = c[2] + 128 + return "fill=\"rgb($red,$green,$blue)\" stroke=\"rgb($red,$green,$blue)\" " + + "stroke-width=\"1\" opacity=\"${(c[3] + 128) / 255.0}\" " + } + + private fun blur(imgd: ImageData, rad: Float, del: Float): ImageData { + var radius = floor(rad.toDouble()).toInt() + if (radius < 1) { + return imgd + } + if (radius > 5) { + radius = 5 + } + + var delta = abs(del.toInt()) + if (delta > 1024) { + delta = 1024 + } + + val imageData2 = + ImageData(imgd.width, imgd.height, ByteArray(imgd.width * imgd.height * 4)) + val kernel = gaussianKernels[radius - 1] + + for (j in 0 until imgd.height) { + for (i in 0 until imgd.width) { + var redAccumulator = 0.0 + var greenAccumulator = 0.0 + var blueAccumulator = 0.0 + var alphaAccumulator = 0.0 + var weightAccumulator = 0.0 + + for (k in -radius until radius + 1) { + if (i + k > 0 && i + k < imgd.width) { + val idx = ((j * imgd.width) + i + k) * 4 + val weight = kernel[k + radius] + redAccumulator += imgd.data[idx].toDouble() * weight + greenAccumulator += imgd.data[idx + 1].toDouble() * weight + blueAccumulator += imgd.data[idx + 2].toDouble() * weight + alphaAccumulator += imgd.data[idx + 3].toDouble() * weight + weightAccumulator += weight + } + } + + val idx = ((j * imgd.width) + i) * 4 + imageData2.data[idx] = + floor(redAccumulator / weightAccumulator).toInt().toByte() + imageData2.data[idx + 1] = + floor(greenAccumulator / weightAccumulator).toInt().toByte() + imageData2.data[idx + 2] = + floor(blueAccumulator / weightAccumulator).toInt().toByte() + imageData2.data[idx + 3] = + floor(alphaAccumulator / weightAccumulator).toInt().toByte() + } + } + + val horizontalImageData = imageData2.data.clone() + + for (j in 0 until imgd.height) { + for (i in 0 until imgd.width) { + var redAccumulator = 0.0 + var greenAccumulator = 0.0 + var blueAccumulator = 0.0 + var alphaAccumulator = 0.0 + var weightAccumulator = 0.0 + + for (k in -radius until radius + 1) { + if (j + k > 0 && j + k < imgd.height) { + val idx = (((j + k) * imgd.width) + i) * 4 + val weight = kernel[k + radius] + redAccumulator += horizontalImageData[idx].toDouble() * weight + greenAccumulator += horizontalImageData[idx + 1].toDouble() * weight + blueAccumulator += horizontalImageData[idx + 2].toDouble() * weight + alphaAccumulator += horizontalImageData[idx + 3].toDouble() * weight + weightAccumulator += weight + } + } + + val idx = ((j * imgd.width) + i) * 4 + imageData2.data[idx] = + floor(redAccumulator / weightAccumulator).toInt().toByte() + imageData2.data[idx + 1] = + floor(greenAccumulator / weightAccumulator).toInt().toByte() + imageData2.data[idx + 2] = + floor(blueAccumulator / weightAccumulator).toInt().toByte() + imageData2.data[idx + 3] = + floor(alphaAccumulator / weightAccumulator).toInt().toByte() + } + } + + for (j in 0 until imgd.height) { + for (i in 0 until imgd.width) { + val idx = ((j * imgd.width) + i) * 4 + val difference = + abs(imageData2.data[idx].toInt() - imgd.data[idx].toInt()) + + abs(imageData2.data[idx + 1].toInt() - imgd.data[idx + 1].toInt()) + + abs(imageData2.data[idx + 2].toInt() - imgd.data[idx + 2].toInt()) + + abs(imageData2.data[idx + 3].toInt() - imgd.data[idx + 3].toInt()) + + if (difference > delta) { + imageData2.data[idx] = imgd.data[idx] + imageData2.data[idx + 1] = imgd.data[idx + 1] + imageData2.data[idx + 2] = imgd.data[idx + 2] + imageData2.data[idx + 3] = imgd.data[idx + 3] + } + } + } + + return imageData2 + } + + private fun appendPathSegments( + sb: StringBuilder, + segments: ArrayList>, + scale: Double, + roundcoords: Float + ) { + for (segment in segments) { + if (segment[0] == 1.0) { + sb.append("L ") + appendCoordinate(sb, segment[3], scale, roundcoords) + sb.append(" ") + appendCoordinate(sb, segment[4], scale, roundcoords) + sb.append(" ") + } else { + sb.append("Q ") + appendCoordinate(sb, segment[3], scale, roundcoords) + sb.append(" ") + appendCoordinate(sb, segment[4], scale, roundcoords) + sb.append(" ") + appendCoordinate(sb, segment[5], scale, roundcoords) + sb.append(" ") + appendCoordinate(sb, segment[6], scale, roundcoords) + sb.append(" ") + } + } + } + + private fun appendPathSegments( + listener: SvgListener, + segments: ArrayList>, + scale: Double, + roundcoords: Float + ) { + for (segment in segments) { + if (segment[0] == 1.0) { + listener.onProgress("L ") + appendCoordinate(listener, segment[3], scale, roundcoords) + listener.onProgress(" ") + appendCoordinate(listener, segment[4], scale, roundcoords) + listener.onProgress(" ") + } else { + listener.onProgress("Q ") + appendCoordinate(listener, segment[3], scale, roundcoords) + listener.onProgress(" ") + appendCoordinate(listener, segment[4], scale, roundcoords) + listener.onProgress(" ") + appendCoordinate(listener, segment[5], scale, roundcoords) + listener.onProgress(" ") + appendCoordinate(listener, segment[6], scale, roundcoords) + listener.onProgress(" ") + } + } + } + + private fun appendCoordinate( + sb: StringBuilder, + coordinate: Double, + scale: Double, + roundcoords: Float + ) { + if (roundcoords == -1f) { + sb.append(coordinate * scale) + } else { + sb.append(roundToDecimal((coordinate * scale).toFloat(), roundcoords)) + } + } + + private fun appendCoordinate( + listener: SvgListener, + coordinate: Double, + scale: Double, + roundcoords: Float + ) { + if (roundcoords == -1f) { + listener.onProgress(coordinate * scale) + } else { + listener.onProgress( + roundToDecimal( + (coordinate * scale).toFloat(), + roundcoords + ).toDouble() + ) + } + } + + private fun appendQuadraticControlPoints( + sb: StringBuilder, + segment: Array, + scale: Double, + qcpr: Double + ) { + sb.append("") + sb.append("") + sb.append("") + sb.append("") + } + + private fun appendQuadraticControlPoints( + listener: SvgListener, + segment: Array, + scale: Double, + qcpr: Double + ) { + listener.onProgress("") + listener.onProgress("") + listener.onProgress("") + listener.onProgress("") + } + + private fun randomPaletteByte(random: Random): Byte = + (-128 + random.nextInt(255)).toByte() + + private fun Array.copyPalette(): Array = + Array(size) { index -> this[index].copyOf() } + + private fun squaredDistance(x1: Double, y1: Double, x2: Double, y2: Double): Double = + ((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)) + + fun imageToIndexedImage( + filename: String, + options: Options?, + palette: Array? + ): IndexedImage { + val checkedOptions = checkOptions(options) + val imageData = loadImageData(filename) + return imageDataToIndexedImage(imageData, checkedOptions, palette) + } + + fun imageToIndexedImage( + bitmap: Bitmap, + options: Options?, + palette: Array? + ): IndexedImage { + val checkedOptions = checkOptions(options) + val imageData = loadImageData(bitmap) + return imageDataToIndexedImage(imageData, checkedOptions, palette) + } + + class IndexedImage( + val array: Array, + val palette: Array + ) { + val width: Int = array[0].size - 2 + + val height: Int = array.size - 2 + + val layers: ArrayList>>> = ArrayList() + } + + class ImageData( + val width: Int, + val height: Int, + val data: ByteArray + ) + + interface SvgListener { + fun onProgress(part: String): SvgListener + + fun onProgress(part: Double): SvgListener + } +} diff --git a/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/di/SvgMakerModule.kt b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/di/SvgMakerModule.kt new file mode 100644 index 0000000..79bd135 --- /dev/null +++ b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/di/SvgMakerModule.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/domain/SvgManager.kt b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/domain/SvgManager.kt new file mode 100644 index 0000000..edcb5c6 --- /dev/null +++ b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/domain/SvgManager.kt @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + +} \ No newline at end of file diff --git a/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/domain/SvgParams.kt b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/domain/SvgParams.kt new file mode 100644 index 0000000..276fb4d --- /dev/null +++ b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/domain/SvgParams.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 seed: 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, + seed = 0, + 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) + } + } +} \ No newline at end of file diff --git a/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/presentation/SvgMakerContent.kt b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/presentation/SvgMakerContent.kt new file mode 100644 index 0000000..909eb39 --- /dev/null +++ b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/presentation/SvgMakerContent.kt @@ -0,0 +1,215 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.foundation.layout.padding +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.unit.dp +import com.t8rin.imagetoolbox.core.resources.Icons +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.controls.UndoRedoButtons +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 = { + if (!isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + 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) + ) + } + if (isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + }, + imagePreview = { + UrisPreview( + modifier = Modifier.urisPreview(isPortrait = isPortrait), + 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 + ) + +} \ No newline at end of file diff --git a/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/presentation/components/SvgParamsSelector.kt b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/presentation/components/SvgParamsSelector.kt new file mode 100644 index 0000000..8db14cc --- /dev/null +++ b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/presentation/components/SvgParamsSelector.kt @@ -0,0 +1,398 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.BlurCircular +import com.t8rin.imagetoolbox.core.resources.icons.Calculate +import com.t8rin.imagetoolbox.core.resources.icons.ChangeHistory +import com.t8rin.imagetoolbox.core.resources.icons.Eyedropper +import com.t8rin.imagetoolbox.core.resources.icons.FormatColorFill +import com.t8rin.imagetoolbox.core.resources.icons.FreeDraw +import com.t8rin.imagetoolbox.core.resources.icons.Line +import com.t8rin.imagetoolbox.core.resources.icons.LinearScale +import com.t8rin.imagetoolbox.core.resources.icons.Palette +import com.t8rin.imagetoolbox.core.resources.icons.PhotoSizeSelectSmall +import com.t8rin.imagetoolbox.core.resources.icons.RepeatOne +import com.t8rin.imagetoolbox.core.resources.icons.SettingsEthernet +import com.t8rin.imagetoolbox.core.resources.icons.Upcoming +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.Palette, + 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.Rounded.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)) + EnhancedSliderItem( + value = value.seed, + icon = Icons.Rounded.SettingsEthernet, + title = stringResource(id = R.string.seed), + valueRange = -10000f..10000f, + internalStateTransformation = { it.roundToInt() }, + onValueChange = { + onValueChange( + value.copy( + seed = 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.Rounded.FormatColorFill, + modifier = Modifier.fillMaxWidth(), + shape = ShapeDefaults.extraLarge + ) + Spacer(modifier = Modifier.height(8.dp)) + EnhancedSliderItem( + value = value.svgPathsScale, + icon = Icons.Rounded.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.Outlined.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 -> "" + } \ No newline at end of file diff --git a/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/presentation/screenLogic/SvgMakerComponent.kt b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/presentation/screenLogic/SvgMakerComponent.kt new file mode 100644 index 0000000..4a6ce33 --- /dev/null +++ b/feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/presentation/screenLogic/SvgMakerComponent.kt @@ -0,0 +1,251 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.BaseHistoryComponent +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 com.t8rin.imagetoolbox.feature.svg_maker.presentation.screenLogic.SvgMakerComponent.HistorySnapshot +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 +) : BaseHistoryComponent( + dispatchersHolder = dispatchersHolder, + componentContext = 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) { + clearHistory() + registerChangesCleared() + _uris.update { newUris.distinct() } + if (_uris.value.isNotEmpty()) { + resetHistory() + } + } + + fun removeUri(uri: Uri) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _uris.update { it - uri } + commitHistoryFrom(beforeSnapshot) + } + + fun addUris(list: List) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _uris.update { (it + list).distinct() } + commitHistoryFrom(beforeSnapshot) + } + + fun updateParams(newParams: SvgParams) { + if (_params.value != newParams) { + beginPendingHistoryTransaction() + _params.update { newParams } + registerChanges() + schedulePendingHistoryCommit() + } + } + + override fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( + uris = uris, + params = params + ) + + override fun applyHistorySnapshot(snapshot: HistorySnapshot) { + _uris.update { snapshot.uris } + _params.update { snapshot.params } + } + + data class HistorySnapshot( + val uris: List = emptyList(), + val params: SvgParams = SvgParams.Default + ) + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUris: List?, + onGoBack: () -> Unit, + ): SvgMakerComponent + } + +} \ No newline at end of file diff --git a/feature/texture-generation/.gitignore b/feature/texture-generation/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/texture-generation/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/texture-generation/build.gradle.kts b/feature/texture-generation/build.gradle.kts new file mode 100644 index 0000000..5173917 --- /dev/null +++ b/feature/texture-generation/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.texture_generation" + +dependencies { + implementation(libs.toolbox.fastNoise) + implementation(libs.toolbox.jhlabs) +} diff --git a/feature/texture-generation/src/main/AndroidManifest.xml b/feature/texture-generation/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3ac54d4 --- /dev/null +++ b/feature/texture-generation/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/data/AndroidTextureGenerator.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/data/AndroidTextureGenerator.kt new file mode 100644 index 0000000..07b09d1 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/data/AndroidTextureGenerator.kt @@ -0,0 +1,1199 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.data + +import android.graphics.Bitmap +import android.graphics.Color +import androidx.core.graphics.createBitmap +import com.jhlabs.BrushedMetalFilter +import com.jhlabs.CausticsFilter +import com.jhlabs.CellularFilter +import com.jhlabs.CheckFilter +import com.jhlabs.FBMFilter +import com.jhlabs.JhFilter +import com.jhlabs.MarbleTexFilter +import com.jhlabs.PlasmaFilter +import com.jhlabs.QuiltFilter +import com.jhlabs.WoodFilter +import com.t8rin.fast_noise.texture.AsphaltTextureGenerator +import com.t8rin.fast_noise.texture.AsphaltTextureParameters +import com.t8rin.fast_noise.texture.AuroraTextureGenerator +import com.t8rin.fast_noise.texture.AuroraTextureParameters +import com.t8rin.fast_noise.texture.BioluminescenceTextureGenerator +import com.t8rin.fast_noise.texture.BioluminescenceTextureParameters +import com.t8rin.fast_noise.texture.BrickTextureGenerator +import com.t8rin.fast_noise.texture.BrickTextureParameters +import com.t8rin.fast_noise.texture.CamouflageTextureGenerator +import com.t8rin.fast_noise.texture.CamouflageTextureParameters +import com.t8rin.fast_noise.texture.CellTextureGenerator +import com.t8rin.fast_noise.texture.CellTextureParameters +import com.t8rin.fast_noise.texture.ChromaticTunnelTextureGenerator +import com.t8rin.fast_noise.texture.ChromaticTunnelTextureParameters +import com.t8rin.fast_noise.texture.CloudTextureGenerator +import com.t8rin.fast_noise.texture.CloudTextureParameters +import com.t8rin.fast_noise.texture.ConcreteTextureGenerator +import com.t8rin.fast_noise.texture.ConcreteTextureParameters +import com.t8rin.fast_noise.texture.CosmicVortexTextureGenerator +import com.t8rin.fast_noise.texture.CosmicVortexTextureParameters +import com.t8rin.fast_noise.texture.CrackTextureGenerator +import com.t8rin.fast_noise.texture.CrackTextureParameters +import com.t8rin.fast_noise.texture.DamascusTextureGenerator +import com.t8rin.fast_noise.texture.DamascusTextureParameters +import com.t8rin.fast_noise.texture.DirtTextureGenerator +import com.t8rin.fast_noise.texture.DirtTextureParameters +import com.t8rin.fast_noise.texture.EclipseCoronaTextureGenerator +import com.t8rin.fast_noise.texture.EclipseCoronaTextureParameters +import com.t8rin.fast_noise.texture.EventHorizonTextureGenerator +import com.t8rin.fast_noise.texture.EventHorizonTextureParameters +import com.t8rin.fast_noise.texture.FabricTextureGenerator +import com.t8rin.fast_noise.texture.FabricTextureParameters +import com.t8rin.fast_noise.texture.FerrofluidCrownTextureGenerator +import com.t8rin.fast_noise.texture.FerrofluidCrownTextureParameters +import com.t8rin.fast_noise.texture.FireTextureGenerator +import com.t8rin.fast_noise.texture.FireTextureParameters +import com.t8rin.fast_noise.texture.FlowTextureGenerator +import com.t8rin.fast_noise.texture.FlowTextureParameters +import com.t8rin.fast_noise.texture.FoliageTextureGenerator +import com.t8rin.fast_noise.texture.FoliageTextureParameters +import com.t8rin.fast_noise.texture.FractalBloomTextureGenerator +import com.t8rin.fast_noise.texture.FractalBloomTextureParameters +import com.t8rin.fast_noise.texture.GrassTextureGenerator +import com.t8rin.fast_noise.texture.GrassTextureParameters +import com.t8rin.fast_noise.texture.HolographicTextureGenerator +import com.t8rin.fast_noise.texture.HolographicTextureParameters +import com.t8rin.fast_noise.texture.HoneycombTextureGenerator +import com.t8rin.fast_noise.texture.HoneycombTextureParameters +import com.t8rin.fast_noise.texture.IceTextureGenerator +import com.t8rin.fast_noise.texture.IceTextureParameters +import com.t8rin.fast_noise.texture.InkMarblingTextureGenerator +import com.t8rin.fast_noise.texture.InkMarblingTextureParameters +import com.t8rin.fast_noise.texture.IrisTextureGenerator +import com.t8rin.fast_noise.texture.IrisTextureParameters +import com.t8rin.fast_noise.texture.LavaLampTextureGenerator +import com.t8rin.fast_noise.texture.LavaLampTextureParameters +import com.t8rin.fast_noise.texture.LavaTextureGenerator +import com.t8rin.fast_noise.texture.LavaTextureParameters +import com.t8rin.fast_noise.texture.LeatherTextureGenerator +import com.t8rin.fast_noise.texture.LeatherTextureParameters +import com.t8rin.fast_noise.texture.LightningTextureGenerator +import com.t8rin.fast_noise.texture.LightningTextureParameters +import com.t8rin.fast_noise.texture.MossTextureGenerator +import com.t8rin.fast_noise.texture.MossTextureParameters +import com.t8rin.fast_noise.texture.NautilusShellTextureGenerator +import com.t8rin.fast_noise.texture.NautilusShellTextureParameters +import com.t8rin.fast_noise.texture.NebulaTextureGenerator +import com.t8rin.fast_noise.texture.NebulaTextureParameters +import com.t8rin.fast_noise.texture.OilSlickTextureGenerator +import com.t8rin.fast_noise.texture.OilSlickTextureParameters +import com.t8rin.fast_noise.texture.OpalTextureGenerator +import com.t8rin.fast_noise.texture.OpalTextureParameters +import com.t8rin.fast_noise.texture.PaperTextureGenerator +import com.t8rin.fast_noise.texture.PaperTextureParameters +import com.t8rin.fast_noise.texture.PeacockFeatherTextureGenerator +import com.t8rin.fast_noise.texture.PeacockFeatherTextureParameters +import com.t8rin.fast_noise.texture.RingedPlanetTextureGenerator +import com.t8rin.fast_noise.texture.RingedPlanetTextureParameters +import com.t8rin.fast_noise.texture.RustTextureGenerator +import com.t8rin.fast_noise.texture.RustTextureParameters +import com.t8rin.fast_noise.texture.SandTextureGenerator +import com.t8rin.fast_noise.texture.SandTextureParameters +import com.t8rin.fast_noise.texture.SmokeTextureGenerator +import com.t8rin.fast_noise.texture.SmokeTextureParameters +import com.t8rin.fast_noise.texture.StoneTextureGenerator +import com.t8rin.fast_noise.texture.StoneTextureParameters +import com.t8rin.fast_noise.texture.StrangeAttractorTextureGenerator +import com.t8rin.fast_noise.texture.StrangeAttractorTextureParameters +import com.t8rin.fast_noise.texture.SupernovaTextureGenerator +import com.t8rin.fast_noise.texture.SupernovaTextureParameters +import com.t8rin.fast_noise.texture.TerrainTextureGenerator +import com.t8rin.fast_noise.texture.TerrainTextureParameters +import com.t8rin.fast_noise.texture.TopographyTextureGenerator +import com.t8rin.fast_noise.texture.TopographyTextureParameters +import com.t8rin.fast_noise.texture.VelvetTextureGenerator +import com.t8rin.fast_noise.texture.VelvetTextureParameters +import com.t8rin.fast_noise.texture.WaterRippleTextureGenerator +import com.t8rin.fast_noise.texture.WaterRippleTextureParameters +import com.t8rin.fast_noise.texture.WatercolorTextureGenerator +import com.t8rin.fast_noise.texture.WatercolorTextureParameters +import com.t8rin.fast_noise.texture.WoodTextureGenerator +import com.t8rin.fast_noise.texture.WoodTextureParameters +import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder +import com.t8rin.imagetoolbox.texture_generation.domain.TextureGenerator +import com.t8rin.imagetoolbox.texture_generation.domain.model.FastNoiseTextureParams +import com.t8rin.imagetoolbox.texture_generation.domain.model.TextureFilterType +import com.t8rin.imagetoolbox.texture_generation.domain.model.TextureParams +import kotlinx.coroutines.withContext +import javax.inject.Inject + +internal class AndroidTextureGenerator @Inject constructor( + dispatchersHolder: DispatchersHolder +) : TextureGenerator, DispatchersHolder by dispatchersHolder { + + override suspend fun generateTexture( + width: Int, + height: Int, + textureParams: TextureParams, + onFailure: (Throwable) -> Unit + ): Bitmap? = withContext(defaultDispatcher) { + runCatching { + if (textureParams.textureFilterType.isFastNoise) { + createFastNoiseTexture( + width = width, + height = height, + type = textureParams.textureFilterType, + params = requireNotNull(textureParams.fastNoiseParams) + ) + } else { + createFilter(textureParams).filter( + createBitmap(width, height).apply { + val sourceColor = when (textureParams.textureFilterType) { + TextureFilterType.Cellular, + TextureFilterType.Plasma -> textureParams.color.colorInt + + else -> Color.TRANSPARENT + } + eraseColor(sourceColor) + } + ) + } + }.onFailure(onFailure).getOrNull() + } + + private fun createFilter( + textureParams: TextureParams + ): JhFilter = when (textureParams.textureFilterType) { + TextureFilterType.BrushedMetal -> BrushedMetalFilter().apply { + color = textureParams.color.colorInt + radius = textureParams.radius + amount = textureParams.amount + monochrome = textureParams.monochrome + shine = textureParams.shine + } + + TextureFilterType.Caustics -> CausticsFilter().apply { + scale = textureParams.scale + brightness = textureParams.brightness + amount = textureParams.amount + turbulence = textureParams.turbulence + dispersion = textureParams.dispersion + time = textureParams.time + samples = textureParams.samples + bgColor = textureParams.backgroundColor.colorInt + } + + TextureFilterType.Cellular -> CellularFilter().apply { + setScale(textureParams.scale) + setStretch(textureParams.stretch) + setAngle(textureParams.angle) + setAngleCoefficient(textureParams.angleCoefficient) + gradientCoefficient = textureParams.gradientCoefficient + f1 = textureParams.f1 + f2 = textureParams.f2 + f3 = textureParams.f3 + f4 = textureParams.f4 + setRandomness(textureParams.randomness) + setGridType(textureParams.gridType.value) + setDistancePower(textureParams.distancePower) + setTurbulence(textureParams.turbulence) + setAmount(textureParams.amount) + gain = textureParams.gain + bias = textureParams.bias + useColor = true + } + + TextureFilterType.Check -> CheckFilter().apply { + foreground = textureParams.foregroundColor.colorInt + background = textureParams.backgroundColor.colorInt + xScale = textureParams.xScale + yScale = textureParams.yScale + fuzziness = textureParams.fuzziness + angle = textureParams.angle + } + + TextureFilterType.FBM -> FBMFilter().apply { + amount = textureParams.amount + scale = textureParams.scale + stretch = textureParams.stretch + angle = textureParams.angle + octaves = textureParams.octaves + h = textureParams.h + lacunarity = textureParams.lacunarity + gain = textureParams.gain + bias = textureParams.bias + } + + TextureFilterType.Marble -> MarbleTexFilter().apply { + scale = textureParams.scale + stretch = textureParams.stretch + angle = textureParams.angle + turbulence = textureParams.turbulence + turbulenceFactor = textureParams.turbulenceFactor + } + + TextureFilterType.Plasma -> PlasmaFilter().apply { + turbulence = textureParams.turbulence + scaling = textureParams.scaling + useImageColors = true + } + + TextureFilterType.Quilt -> QuiltFilter().apply { + iterations = textureParams.iterations + a = textureParams.a + b = textureParams.b + c = textureParams.c + d = textureParams.d + k = textureParams.k + } + + TextureFilterType.Wood -> WoodFilter().apply { + rings = textureParams.rings + turbulence = textureParams.turbulence + gain = textureParams.gain + bias = textureParams.bias + scale = textureParams.scale + stretch = textureParams.stretch + angle = textureParams.angle + } + + else -> error("Unsupported JH Labs texture type: ${textureParams.textureFilterType}") + } + + private fun createFastNoiseTexture( + width: Int, + height: Int, + type: TextureFilterType, + params: FastNoiseTextureParams + ): Bitmap? { + val values = params.values + val colors = params.colors.map { it.colorInt } + + return when (type) { + TextureFilterType.Brick -> BrickTextureGenerator().generate( + width = width, + height = height, + parameters = BrickTextureParameters( + seed = params.seed, + scale = params.scale, + aspectRatio = values[0], + mortarWidth = values[1], + irregularity = values[2], + roughness = values[3], + bevel = values[4], + mortarColor = colors[0], + darkBrickColor = colors[1], + brickColor = colors[2], + highlightColor = colors[3] + ) + ) + + TextureFilterType.Camouflage -> CamouflageTextureGenerator().generate( + width = width, + height = height, + parameters = CamouflageTextureParameters( + seed = params.seed, + scale = params.scale, + firstThreshold = values[0], + secondThreshold = values[1], + thirdThreshold = values[2], + distortion = values[3], + edgeSoftness = values[4], + darkColor = colors[0], + forestColor = colors[1], + earthColor = colors[2], + sandColor = colors[3] + ) + ) + + TextureFilterType.Cell -> CellTextureGenerator().generate( + width = width, + height = height, + parameters = CellTextureParameters( + seed = params.seed, + scale = params.scale, + jitter = values[0], + borderWidth = values[1], + glow = values[2], + distortion = values[3], + variation = values[4], + backgroundColor = colors[0], + cellColor = colors[1], + edgeColor = colors[2], + highlightColor = colors[3] + ) + ) + + TextureFilterType.Cloud -> CloudTextureGenerator().generate( + width = width, + height = height, + parameters = CloudTextureParameters( + seed = params.seed, + scale = params.scale, + coverage = values[0], + softness = values[1], + detail = values[2], + distortion = values[3], + density = values[4], + skyColor = colors[0], + shadowColor = colors[1], + lightColor = colors[2] + ) + ) + + TextureFilterType.Crack -> CrackTextureGenerator().generate( + width = width, + height = height, + parameters = CrackTextureParameters( + seed = params.seed, + scale = params.scale, + width = values[0], + density = values[1], + distortion = values[2], + depth = values[3], + branching = values[4], + surfaceColor = colors[0], + variationColor = colors[1], + crackColor = colors[2], + edgeColor = colors[3] + ) + ) + + TextureFilterType.Fabric -> FabricTextureGenerator().generate( + width = width, + height = height, + parameters = FabricTextureParameters( + seed = params.seed, + scale = params.scale, + horizontalThreads = values[0], + verticalThreads = values[1], + irregularity = values[2], + depth = values[3], + fuzz = values[4], + warpColor = colors[0], + weftColor = colors[1], + shadowColor = colors[2], + highlightColor = colors[3] + ) + ) + + TextureFilterType.Foliage -> FoliageTextureGenerator().generate( + width = width, + height = height, + parameters = FoliageTextureParameters( + seed = params.seed, + scale = params.scale, + density = values[0], + edgeSoftness = values[1], + veins = values[2], + lighting = values[3], + variation = values[4], + shadowColor = colors[0], + darkLeafColor = colors[1], + leafColor = colors[2], + highlightColor = colors[3] + ) + ) + + TextureFilterType.Honeycomb -> HoneycombTextureGenerator().generate( + width = width, + height = height, + parameters = HoneycombTextureParameters( + seed = params.seed, + scale = params.scale, + borderWidth = values[0], + bevel = values[1], + irregularity = values[2], + fill = values[3], + glow = values[4], + backgroundColor = colors[0], + borderColor = colors[1], + honeyColor = colors[2], + highlightColor = colors[3] + ) + ) + + TextureFilterType.Ice -> IceTextureGenerator().generate( + width = width, + height = height, + parameters = IceTextureParameters( + seed = params.seed, + scale = params.scale, + crackWidth = values[0], + frost = values[1], + depth = values[2], + distortion = values[3], + sparkle = values[4], + deepColor = colors[0], + iceColor = colors[1], + frostColor = colors[2], + crackColor = colors[3] + ) + ) + + TextureFilterType.Lava -> LavaTextureGenerator().generate( + width = width, + height = height, + parameters = LavaTextureParameters( + seed = params.seed, + scale = params.scale, + distortion = values[0], + flow = values[1], + detail = values[2], + crust = values[3], + glow = values[4], + crustColor = colors[0], + lavaColor = colors[1], + glowColor = colors[2] + ) + ) + + TextureFilterType.Nebula -> NebulaTextureGenerator().generate( + width = width, + height = height, + parameters = NebulaTextureParameters( + seed = params.seed, + scale = params.scale, + turbulence = values[0], + cloudDensity = values[1], + stars = values[2], + glow = values[3], + contrast = values[4], + spaceColor = colors[0], + violetColor = colors[1], + blueColor = colors[2], + glowColor = colors[3] + ) + ) + + TextureFilterType.Paper -> PaperTextureGenerator().generate( + width = width, + height = height, + parameters = PaperTextureParameters( + seed = params.seed, + scale = params.scale, + fiberDensity = values[0], + fiberStrength = values[1], + grain = values[2], + stains = values[3], + roughness = values[4], + baseColor = colors[0], + lightColor = colors[1], + fiberColor = colors[2], + stainColor = colors[3] + ) + ) + + TextureFilterType.Rust -> RustTextureGenerator().generate( + width = width, + height = height, + parameters = RustTextureParameters( + seed = params.seed, + scale = params.scale, + corrosion = values[0], + pitting = values[1], + flakes = values[2], + distortion = values[3], + contrast = values[4], + metalColor = colors[0], + darkRustColor = colors[1], + rustColor = colors[2], + orangeColor = colors[3] + ) + ) + + TextureFilterType.Sand -> SandTextureGenerator().generate( + width = width, + height = height, + parameters = SandTextureParameters( + seed = params.seed, + scale = params.scale, + duneFrequency = values[0], + windAngle = values[1], + ripples = values[2], + grain = values[3], + contrast = values[4], + shadowColor = colors[0], + sandColor = colors[1], + lightColor = colors[2] + ) + ) + + TextureFilterType.Smoke -> SmokeTextureGenerator().generate( + width = width, + height = height, + parameters = SmokeTextureParameters( + seed = params.seed, + scale = params.scale, + turbulence = values[0], + density = values[1], + wisps = values[2], + contrast = values[3], + detail = values[4], + backgroundColor = colors[0], + shadowColor = colors[1], + smokeColor = colors[2] + ) + ) + + TextureFilterType.Stone -> StoneTextureGenerator().generate( + width = width, + height = height, + parameters = StoneTextureParameters( + seed = params.seed, + scale = params.scale, + grain = values[0], + veins = values[1], + veinScale = values[2], + distortion = values[3], + contrast = values[4], + darkColor = colors[0], + lightColor = colors[1], + veinColor = colors[2] + ) + ) + + TextureFilterType.Terrain -> TerrainTextureGenerator().generate( + width = width, + height = height, + parameters = TerrainTextureParameters( + seed = params.seed, + scale = params.scale, + waterLevel = values[0], + mountainLevel = values[1], + erosion = values[2], + detail = values[3], + snowLevel = values[4], + waterColor = colors[0], + lowlandColor = colors[1], + rockColor = colors[2], + snowColor = colors[3] + ) + ) + + TextureFilterType.Topography -> TopographyTextureGenerator().generate( + width = width, + height = height, + parameters = TopographyTextureParameters( + seed = params.seed, + scale = params.scale, + lineCount = values[0], + lineThickness = values[1], + shading = values[2], + distortion = values[3], + contrast = values[4], + lowColor = colors[0], + highColor = colors[1], + lineColor = colors[2] + ) + ) + + TextureFilterType.WaterRipple -> WaterRippleTextureGenerator().generate( + width = width, + height = height, + parameters = WaterRippleTextureParameters( + seed = params.seed, + scale = params.scale, + frequency = values[0], + distortion = values[1], + caustics = values[2], + depth = values[3], + highlights = values[4], + deepColor = colors[0], + shallowColor = colors[1], + highlightColor = colors[2] + ) + ) + + TextureFilterType.AdvancedWood -> WoodTextureGenerator().generate( + width = width, + height = height, + parameters = WoodTextureParameters( + seed = params.seed, + scale = params.scale, + rings = values[0], + grain = values[1], + distortion = values[2], + stretch = values[3], + contrast = values[4], + darkColor = colors[0], + lightColor = colors[1], + poreColor = colors[2] + ) + ) + + TextureFilterType.Grass -> GrassTextureGenerator().generate( + width = width, + height = height, + parameters = GrassTextureParameters( + seed = params.seed, + scale = params.scale, + bladeDensity = values[0], + bladeLength = values[1], + wind = values[2], + patchiness = values[3], + highlights = values[4], + dirtColor = colors[0], + darkGrassColor = colors[1], + grassColor = colors[2], + tipColor = colors[3] + ) + ) + + TextureFilterType.Dirt -> DirtTextureGenerator().generate( + width = width, + height = height, + parameters = DirtTextureParameters( + seed = params.seed, + scale = params.scale, + clumps = values[0], + moisture = values[1], + pebbles = values[2], + roughness = values[3], + variation = values[4], + darkEarthColor = colors[0], + earthColor = colors[1], + dryColor = colors[2], + pebbleColor = colors[3] + ) + ) + + TextureFilterType.Leather -> LeatherTextureGenerator().generate( + width = width, + height = height, + parameters = LeatherTextureParameters( + seed = params.seed, + scale = params.scale, + wrinkles = values[0], + pores = values[1], + grain = values[2], + softness = values[3], + shine = values[4], + shadowColor = colors[0], + leatherColor = colors[1], + lightColor = colors[2], + poreColor = colors[3] + ) + ) + + TextureFilterType.Concrete -> ConcreteTextureGenerator().generate( + width = width, + height = height, + parameters = ConcreteTextureParameters( + seed = params.seed, + scale = params.scale, + aggregate = values[0], + stains = values[1], + roughness = values[2], + cracks = values[3], + contrast = values[4], + darkColor = colors[0], + concreteColor = colors[1], + lightColor = colors[2], + crackColor = colors[3] + ) + ) + + TextureFilterType.Asphalt -> AsphaltTextureGenerator().generate( + width = width, + height = height, + parameters = AsphaltTextureParameters( + seed = params.seed, + scale = params.scale, + aggregate = values[0], + tar = values[1], + wear = values[2], + speckles = values[3], + contrast = values[4], + tarColor = colors[0], + asphaltColor = colors[1], + stoneColor = colors[2], + dustColor = colors[3] + ) + ) + + TextureFilterType.Moss -> MossTextureGenerator().generate( + width = width, + height = height, + parameters = MossTextureParameters( + seed = params.seed, + scale = params.scale, + density = values[0], + fibers = values[1], + moisture = values[2], + variation = values[3], + clumps = values[4], + soilColor = colors[0], + darkMossColor = colors[1], + mossColor = colors[2], + tipColor = colors[3] + ) + ) + + TextureFilterType.Fire -> FireTextureGenerator().generate( + width = width, + height = height, + parameters = FireTextureParameters( + seed = params.seed, + scale = params.scale, + flameFrequency = values[0], + turbulence = values[1], + intensity = values[2], + smoke = values[3], + detail = values[4], + backgroundColor = colors[0], + redColor = colors[1], + orangeColor = colors[2], + coreColor = colors[3] + ) + ) + + TextureFilterType.Aurora -> AuroraTextureGenerator().generate( + width = width, + height = height, + parameters = AuroraTextureParameters( + seed = params.seed, + scale = params.scale, + ribbons = values[0], + distortion = values[1], + glow = values[2], + stars = values[3], + contrast = values[4], + skyColor = colors[0], + greenColor = colors[1], + cyanColor = colors[2], + violetColor = colors[3] + ) + ) + + TextureFilterType.OilSlick -> OilSlickTextureGenerator().generate( + width = width, + height = height, + parameters = OilSlickTextureParameters( + seed = params.seed, + scale = params.scale, + bands = values[0], + distortion = values[1], + iridescence = values[2], + darkness = values[3], + contrast = values[4], + darkColor = colors[0], + magentaColor = colors[1], + cyanColor = colors[2], + goldColor = colors[3] + ) + ) + + TextureFilterType.Watercolor -> WatercolorTextureGenerator().generate( + width = width, + height = height, + parameters = WatercolorTextureParameters( + seed = params.seed, + scale = params.scale, + blooms = values[0], + pigment = values[1], + edges = values[2], + paper = values[3], + diffusion = values[4], + paperColor = colors[0], + pigmentColor = colors[1], + secondaryColor = colors[2], + edgeColor = colors[3] + ) + ) + + TextureFilterType.AbstractFlow -> FlowTextureGenerator().generate( + width = width, + height = height, + parameters = FlowTextureParameters( + seed = params.seed, + scale = params.scale, + frequency = values[0], + distortion = values[1], + symmetry = values[2], + sharpness = values[3], + glow = values[4], + backgroundColor = colors[0], + firstColor = colors[1], + secondColor = colors[2], + glowColor = colors[3] + ) + ) + + TextureFilterType.Opal -> OpalTextureGenerator().generate( + width = width, + height = height, + parameters = OpalTextureParameters( + seed = params.seed, + scale = params.scale, + colorPlay = values[0], + milkiness = values[1], + bands = values[2], + distortion = values[3], + glow = values[4], + baseColor = colors[0], + cyanColor = colors[1], + pinkColor = colors[2], + goldColor = colors[3] + ) + ) + + TextureFilterType.DamascusSteel -> DamascusTextureGenerator().generate( + width = width, + height = height, + parameters = DamascusTextureParameters( + seed = params.seed, + scale = params.scale, + layers = values[0], + folding = values[1], + distortion = values[2], + polish = values[3], + contrast = values[4], + darkSteelColor = colors[0], + steelColor = colors[1], + lightSteelColor = colors[2], + oxideColor = colors[3] + ) + ) + + TextureFilterType.Lightning -> LightningTextureGenerator().generate( + width = width, + height = height, + parameters = LightningTextureParameters( + seed = params.seed, + scale = params.scale, + branches = values[0], + turbulence = values[1], + width = values[2], + glow = values[3], + intensity = values[4], + backgroundColor = colors[0], + haloColor = colors[1], + boltColor = colors[2], + coreColor = colors[3] + ) + ) + + TextureFilterType.Velvet -> VelvetTextureGenerator().generate( + width = width, + height = height, + parameters = VelvetTextureParameters( + seed = params.seed, + scale = params.scale, + fibers = values[0], + direction = values[1], + softness = values[2], + sheen = values[3], + folds = values[4], + shadowColor = colors[0], + velvetColor = colors[1], + sheenColor = colors[2], + highlightColor = colors[3] + ) + ) + + TextureFilterType.InkMarbling -> InkMarblingTextureGenerator().generate( + width = width, + height = height, + parameters = InkMarblingTextureParameters( + seed = params.seed, + scale = params.scale, + ribbons = values[0], + turbulence = values[1], + feathering = values[2], + inkBalance = values[3], + contrast = values[4], + paperColor = colors[0], + blueInkColor = colors[1], + redInkColor = colors[2], + darkInkColor = colors[3] + ) + ) + + TextureFilterType.HolographicFoil -> HolographicTextureGenerator().generate( + width = width, + height = height, + parameters = HolographicTextureParameters( + seed = params.seed, + scale = params.scale, + spectrum = values[0], + crinkles = values[1], + diffraction = values[2], + angle = values[3], + shine = values[4], + silverColor = colors[0], + cyanColor = colors[1], + magentaColor = colors[2], + yellowColor = colors[3] + ) + ) + + TextureFilterType.Bioluminescence -> BioluminescenceTextureGenerator().generate( + width = width, + height = height, + parameters = BioluminescenceTextureParameters( + seed = params.seed, + scale = params.scale, + veins = values[0], + branching = values[1], + turbulence = values[2], + glow = values[3], + depth = values[4], + backgroundColor = colors[0], + tissueColor = colors[1], + glowColor = colors[2], + coreColor = colors[3] + ) + ) + + TextureFilterType.CosmicVortex -> CosmicVortexTextureGenerator().generate( + width = width, + height = height, + parameters = CosmicVortexTextureParameters( + seed = params.seed, + scale = params.scale, + arms = values[0], + twist = values[1], + turbulence = values[2], + stars = values[3], + coreGlow = values[4], + spaceColor = colors[0], + blueColor = colors[1], + violetColor = colors[2], + coreColor = colors[3] + ) + ) + + TextureFilterType.LavaLamp -> LavaLampTextureGenerator().generate( + width = width, + height = height, + parameters = LavaLampTextureParameters( + seed = params.seed, + scale = params.scale, + blobs = values[0], + softness = values[1], + distortion = values[2], + glow = values[3], + contrast = values[4], + backgroundColor = colors[0], + firstColor = colors[1], + secondColor = colors[2], + glowColor = colors[3] + ) + ) + + TextureFilterType.EventHorizon -> EventHorizonTextureGenerator().generate( + width = width, + height = height, + parameters = EventHorizonTextureParameters( + seed = params.seed, + scale = params.scale, + diskTilt = values[0], + horizonSize = values[1], + diskWidth = values[2], + lensing = values[3], + stars = values[4], + spaceColor = colors[0], + diskColor = colors[1], + hotColor = colors[2], + lensColor = colors[3] + ) + ) + + TextureFilterType.FractalBloom -> FractalBloomTextureGenerator().generate( + width = width, + height = height, + parameters = FractalBloomTextureParameters( + seed = params.seed, + scale = params.scale, + petals = values[0], + layers = values[1], + curl = values[2], + filigree = values[3], + glow = values[4], + backgroundColor = colors[0], + outerColor = colors[1], + innerColor = colors[2], + coreColor = colors[3] + ) + ) + + TextureFilterType.ChromaticTunnel -> ChromaticTunnelTextureGenerator().generate( + width = width, + height = height, + parameters = ChromaticTunnelTextureParameters( + seed = params.seed, + scale = params.scale, + depth = values[0], + twist = values[1], + facets = values[2], + curvature = values[3], + glow = values[4], + deepColor = colors[0], + cyanColor = colors[1], + magentaColor = colors[2], + lightColor = colors[3] + ) + ) + + TextureFilterType.EclipseCorona -> EclipseCoronaTextureGenerator().generate( + width = width, + height = height, + parameters = EclipseCoronaTextureParameters( + seed = params.seed, + scale = params.scale, + moonSize = values[0], + coronaSize = values[1], + rays = values[2], + turbulence = values[3], + diamondRing = values[4], + spaceColor = colors[0], + coronaColor = colors[1], + hotColor = colors[2], + lightColor = colors[3] + ) + ) + + TextureFilterType.StrangeAttractor -> StrangeAttractorTextureGenerator().generate( + width = width, + height = height, + parameters = StrangeAttractorTextureParameters( + seed = params.seed, + scale = params.scale, + lobes = values[0], + orbitDensity = values[1], + curvature = values[2], + thickness = values[3], + glow = values[4], + backgroundColor = colors[0], + coldColor = colors[1], + warmColor = colors[2], + coreColor = colors[3] + ) + ) + + TextureFilterType.FerrofluidCrown -> FerrofluidCrownTextureGenerator().generate( + width = width, + height = height, + parameters = FerrofluidCrownTextureParameters( + seed = params.seed, + scale = params.scale, + spikes = values[0], + spikeLength = values[1], + bodySize = values[2], + metallic = values[3], + distortion = values[4], + backgroundColor = colors[0], + shadowColor = colors[1], + metalColor = colors[2], + highlightColor = colors[3] + ) + ) + + TextureFilterType.Supernova -> SupernovaTextureGenerator().generate( + width = width, + height = height, + parameters = SupernovaTextureParameters( + seed = params.seed, + scale = params.scale, + shockRadius = values[0], + shellWidth = values[1], + ejecta = values[2], + turbulence = values[3], + stars = values[4], + spaceColor = colors[0], + cloudColor = colors[1], + flameColor = colors[2], + coreColor = colors[3] + ) + ) + + TextureFilterType.Iris -> IrisTextureGenerator().generate( + width = width, + height = height, + parameters = IrisTextureParameters( + seed = params.seed, + scale = params.scale, + pupilSize = values[0], + irisSize = values[1], + fibers = values[2], + colorVariation = values[3], + catchlight = values[4], + backgroundColor = colors[0], + outerColor = colors[1], + innerColor = colors[2], + goldColor = colors[3] + ) + ) + + TextureFilterType.PeacockFeather -> PeacockFeatherTextureGenerator().generate( + width = width, + height = height, + parameters = PeacockFeatherTextureParameters( + seed = params.seed, + scale = params.scale, + eyeSize = values[0], + barbDensity = values[1], + curvature = values[2], + iridescence = values[3], + softness = values[4], + backgroundColor = colors[0], + featherColor = colors[1], + blueColor = colors[2], + goldColor = colors[3] + ) + ) + + TextureFilterType.NautilusShell -> NautilusShellTextureGenerator().generate( + width = width, + height = height, + parameters = NautilusShellTextureParameters( + seed = params.seed, + scale = params.scale, + turns = values[0], + chambers = values[1], + opening = values[2], + ridges = values[3], + pearlescence = values[4], + backgroundColor = colors[0], + shadowColor = colors[1], + shellColor = colors[2], + pearlColor = colors[3] + ) + ) + + TextureFilterType.RingedPlanet -> RingedPlanetTextureGenerator().generate( + width = width, + height = height, + parameters = RingedPlanetTextureParameters( + seed = params.seed, + scale = params.scale, + planetSize = values[0], + ringTilt = values[1], + ringWidth = values[2], + atmosphere = values[3], + stars = values[4], + spaceColor = colors[0], + shadowColor = colors[1], + planetColor = colors[2], + ringColor = colors[3] + ) + ) + + else -> error("Unsupported fast-noise texture type: $type") + } + } +} diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/di/TextureGenerationModule.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/di/TextureGenerationModule.kt new file mode 100644 index 0000000..7eb7ac7 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/di/TextureGenerationModule.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.di + +import android.graphics.Bitmap +import com.t8rin.imagetoolbox.texture_generation.data.AndroidTextureGenerator +import com.t8rin.imagetoolbox.texture_generation.domain.TextureGenerator +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 TextureGenerationModule { + + @Binds + @Singleton + fun provideGenerator( + impl: AndroidTextureGenerator + ): TextureGenerator + +} diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/domain/TextureGenerator.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/domain/TextureGenerator.kt new file mode 100644 index 0000000..5f8c822 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/domain/TextureGenerator.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.domain + +import com.t8rin.imagetoolbox.texture_generation.domain.model.TextureParams + +interface TextureGenerator { + + suspend fun generateTexture( + width: Int, + height: Int, + textureParams: TextureParams, + onFailure: (Throwable) -> Unit = {} + ): Image? + +} diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/domain/model/FastNoiseTextureParams.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/domain/model/FastNoiseTextureParams.kt new file mode 100644 index 0000000..9acf447 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/domain/model/FastNoiseTextureParams.kt @@ -0,0 +1,303 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.domain.model + +import com.t8rin.imagetoolbox.core.domain.model.ColorModel + +data class FastNoiseTextureParams( + val seed: Int, + val scale: Float, + val values: List, + val colors: List +) { + companion object { + fun defaultFor(type: TextureFilterType): FastNoiseTextureParams = when (type) { + TextureFilterType.Brick -> create( + 0.02f, 2.15f, 0.09f, 0.18f, 0.42f, 0.55f, + -4674151, -9493480, -4830158, -2655659 + ) + + TextureFilterType.Camouflage -> create( + 0.007f, 0.36f, 0.58f, 0.76f, 28f, 0.035f, + -14670055, -11574988, -8887484, -4938633 + ) + + TextureFilterType.Cell -> create( + 0.018f, 0.92f, 0.12f, 0.28f, 6f, 0.42f, + -15721948, -14586248, -10825272, -3538956 + ) + + TextureFilterType.Cloud -> create( + 0.0045f, 0.48f, 0.22f, 0.62f, 24f, 0.92f, + -9459236, -6378052, -459777 + ) + + TextureFilterType.Crack -> create( + 0.019f, 0.065f, 0.72f, 9f, 0.72f, 0.45f, + -7502988, -4147547, -15330286, -11186619 + ) + + TextureFilterType.Fabric -> create( + 0.018f, 28f, 28f, 0.14f, 0.48f, 0.1f, + -11965566, -8874585, -14271413, -4797740 + ) + + TextureFilterType.Foliage -> create( + 0.022f, 0.72f, 0.16f, 0.38f, 0.62f, 0.58f, + -15717869, -14394331, -11625923, -5976475 + ) + + TextureFilterType.Honeycomb -> create( + 0.018f, 0.095f, 0.5f, 0.12f, 0.72f, 0.35f, + -14413051, -9487864, -1729774, -11172 + ) + + TextureFilterType.Ice -> create( + 0.014f, 0.075f, 0.48f, 0.64f, 8f, 0.32f, + -15377799, -8795940, -2558731, -655361 + ) + + TextureFilterType.Lava -> create( + 0.008f, 32f, 1.35f, 0.55f, 0.52f, 0.72f, + -15594742, -2807030, -11174 + ) + + TextureFilterType.Nebula -> create( + 0.004f, 44f, 0.64f, 0.38f, 0.72f, 1.45f, + -16447725, -11262601, -14456926, -875297 + ) + + TextureFilterType.Paper -> create( + 0.012f, 72f, 0.24f, 0.16f, 0.14f, 0.35f, + -1780562, -661047, -4875152, -7706559 + ) + + TextureFilterType.Rust -> create( + 0.009f, 0.55f, 0.0f, 0.42f, 16f, 1.25f, + -10919835, -12970229, -6603245, -2066398 + ) + + TextureFilterType.Sand -> create( + 0.005f, 12f, 0.32f, 0.62f, 0.22f, 1.18f, + -6659282, -2709414, -862835 + ) + + TextureFilterType.Smoke -> create( + 0.006f, 34f, 0.56f, 2.2f, 1.35f, 0.5f, + -15658216, -12301742, -2762275 + ) + + TextureFilterType.Stone -> create( + 0.011f, 0.3f, 0.58f, 0.024f, 18f, 1.12f, + -13355722, -5593185, -1909037 + ) + + TextureFilterType.Terrain -> create( + 0.0045f, 0.34f, 0.7f, 0.46f, 0.58f, 0.86f, + -15118989, -10516673, -9147299, -1446168 + ) + + TextureFilterType.Topography -> create( + 0.0055f, 16f, 0.11f, 0.42f, 12f, 1.1f, + -15254469, -4863606, -858442 + ) + + TextureFilterType.WaterRipple -> create( + 0.0075f, 22f, 22f, 0.58f, 0.55f, 0.62f, + -16305066, -15298898, -4589582 + ) + + TextureFilterType.AdvancedWood -> create( + 0.0039f, 11.4f, 0.3f, 15f, 7.42f, 0.17f, + -12969717, -4624846, -14873337 + ) + + TextureFilterType.Grass -> create( + 0.012f, 1f, 50f, 1f, 0.35f, 0.45f, + -12965096, -14857953, -11695566, -6568870 + ) + + TextureFilterType.Dirt -> create( + 0.009f, 0.62f, 0.35f, 0.22f, 0.68f, 0.52f, + -14018290, -9879002, -6195635, -5135732 + ) + + TextureFilterType.Leather -> create( + 0.011f, 0.58f, 0.42f, 0.5f, 0.38f, 0.035f, + -13625841, -8700635, -5018813, -14939128 + ) + + TextureFilterType.Concrete -> create( + 0.014f, 0.45f, 0.28f, 0.65f, 0.14f, 1.08f, + -10856107, -6711666, -3685447, -13290190 + ) + + TextureFilterType.Asphalt -> create( + 0.02f, 0.72f, 0.44f, 0.28f, 0.48f, 1.3f, + -15263462, -12697022, -7763067, -5199467 + ) + + TextureFilterType.Moss -> create( + 0.018f, 0.74f, 0.58f, 0.38f, 0.62f, 0.46f, + -14080746, -14072548, -10190780, -5128855 + ) + + TextureFilterType.Fire -> create( + 0.006f, 8f, 36f, 0.78f, 0.18f, 0.62f, + -15726585, -4908280, -34294, -5750 + ) + + TextureFilterType.Aurora -> create( + 0.004f, 7f, 34f, 0.72f, 0.2f, 1.32f, + -16379353, -12396128, -10099480, -5935646 + ) + + TextureFilterType.OilSlick -> create( + 0.008f, 13f, 32f, 0.82f, 0.3f, 1.2f, + -15724518, -2805093, -14297904, -998596 + ) + + TextureFilterType.Watercolor -> create( + 0.006f, 0.72f, 0.65f, 0.38f, 0.22f, 0.62f, + -726056, -13402189, -2597511, -11976842 + ) + + TextureFilterType.AbstractFlow -> create( + 0.006f, 12f, 46f, 0.3f, 1.25f, 0.62f, + -15658712, -11446823, -2208845, -8457497 + ) + + TextureFilterType.Opal -> create( + 0.007f, 0.82f, 0.48f, 8f, 28f, 0.62f, + -2234145, -11545135, -1019210, -14249 + ) + + TextureFilterType.DamascusSteel -> create( + 0.006f, 22f, 0.72f, 34f, 0.58f, 1.4f, + -15262688, -10063238, -3616558, -13611945 + ) + + TextureFilterType.Lightning -> create( + 0.006f, 7f, 42f, 0.055f, 0.82f, 0.9f, + -16579054, -14334296, -9909761, -852737 + ) + + TextureFilterType.Velvet -> create( + 0.014f, 0.78f, 0.18f, 0.7f, 0.62f, 0.34f, + -15268578, -10610838, -4110152, -1005864 + ) + + TextureFilterType.InkMarbling -> create( + 0.006f, 14f, 48f, 0.52f, 0.5f, 1.25f, + -792109, -15316104, -6477495, -14936015 + ) + + TextureFilterType.HolographicFoil -> create( + 0.008f, 12f, 0.7f, 0.82f, 0.3f, 0.72f, + -3287842, -11672094, -1484088, -529806 + ) + + TextureFilterType.Bioluminescence -> create( + 0.009f, 0.72f, 0.58f, 30f, 0.86f, 0.62f, + -16573926, -16035507, -14620990, -3866639 + ) + + TextureFilterType.CosmicVortex -> create( + 0.008f, 5f, 12f, 0.52f, 0.28f, 0.82f, + -16645365, -14202200, -7520574, -8800 + ) + + TextureFilterType.LavaLamp -> create( + 0.007f, 6f, 0.24f, 32f, 0.58f, 1.3f, + -14939863, -49791, -30172, -11172 + ) + + TextureFilterType.EventHorizon -> create( + 0.008f, 0.72f, 0.15f, 0.065f, 0.82f, 0.22f, + -16711417, -42472, -8029, -8632065 + ) + + TextureFilterType.FractalBloom -> create( + 0.01f, 7f, 5f, 4.2f, 0.7f, 0.72f, + -16185829, -10927914, -49517, -6006 + ) + + TextureFilterType.ChromaticTunnel -> create( + 0.009f, 18f, 5.5f, 7f, 0.48f, 0.78f, + -16579306, -16722433, -54116, -3656 + ) + + TextureFilterType.EclipseCorona -> create( + 0.008f, 0.23f, 0.2f, 34f, 0.62f, 0.8f, + -16645366, -9020417, -25787, -1 + ) + + TextureFilterType.StrangeAttractor -> create( + 0.01f, 3f, 18f, 6f, 0.035f, 0.8f, + -16579311, -15410747, -50567, -3659 + ) + + TextureFilterType.FerrofluidCrown -> create( + 0.012f, 19f, 0.14f, 0.22f, 0.86f, 0.42f, + -1515052, -16316148, -13288125, -2492417 + ) + + TextureFilterType.Supernova -> create( + 0.01f, 0.27f, 0.075f, 0.72f, 0.68f, 0.24f, + -16645365, -9886793, -45790, -3387 + ) + + TextureFilterType.Iris -> create( + 0.014f, 0.12f, 0.38f, 46f, 0.72f, 0.82f, + -16250355, -15583674, -13125979, -1657264 + ) + + TextureFilterType.PeacockFeather -> create( + 0.012f, 0.24f, 54f, 0.58f, 0.8f, 0.42f, + -16378609, -15303339, -15578948, -1786811 + ) + + TextureFilterType.NautilusShell -> create( + 0.011f, 3.4f, 19f, 0.13f, 0.68f, 0.46f, + -15392216, -11915998, -2511495, -3887 + ) + + TextureFilterType.RingedPlanet -> create( + 0.009f, 0.24f, 0.72f, 0.16f, 0.62f, 0.25f, + -16645107, -15391923, -1733534, -7770 + ) + + else -> error("Unsupported fast-noise texture type: $type") + } + + private fun create( + scale: Float, + value1: Float, + value2: Float, + value3: Float, + value4: Float, + value5: Float, + vararg colors: Int + ) = FastNoiseTextureParams( + seed = 1337, + scale = scale, + values = listOf(value1, value2, value3, value4, value5), + colors = colors.map(::ColorModel) + ) + } +} \ No newline at end of file diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/domain/model/TextureEnums.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/domain/model/TextureEnums.kt new file mode 100644 index 0000000..af0a1f0 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/domain/model/TextureEnums.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.domain.model + +enum class CellularGridType( + val value: Int +) { + Random(0), + Square(1), + Hexagonal(2), + Octagonal(3), + Triangular(4) +} \ No newline at end of file diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/domain/model/TextureFilterType.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/domain/model/TextureFilterType.kt new file mode 100644 index 0000000..a46a0ac --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/domain/model/TextureFilterType.kt @@ -0,0 +1,81 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.domain.model + +enum class TextureFilterType(val isFastNoise: Boolean = false) { + BrushedMetal, + Brick(true), + Camouflage(true), + Cell(true), + Cloud(true), + Crack(true), + Fabric(true), + Foliage(true), + Honeycomb(true), + Ice(true), + Lava(true), + Nebula(true), + Paper(true), + Rust(true), + Sand(true), + Smoke(true), + Stone(true), + Terrain(true), + Topography(true), + WaterRipple(true), + AdvancedWood(true), + Grass(true), + Dirt(true), + Leather(true), + Concrete(true), + Asphalt(true), + Moss(true), + Fire(true), + Aurora(true), + OilSlick(true), + Watercolor(true), + AbstractFlow(true), + Opal(true), + DamascusSteel(true), + Lightning(true), + Velvet(true), + InkMarbling(true), + HolographicFoil(true), + Bioluminescence(true), + CosmicVortex(true), + LavaLamp(true), + EventHorizon(true), + FractalBloom(true), + ChromaticTunnel(true), + EclipseCorona(true), + StrangeAttractor(true), + FerrofluidCrown(true), + Supernova(true), + Iris(true), + PeacockFeather(true), + NautilusShell(true), + RingedPlanet(true), + Caustics, + Cellular, + Check, + FBM, + Marble, + Plasma, + Quilt, + Wood, +} diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/domain/model/TextureParams.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/domain/model/TextureParams.kt new file mode 100644 index 0000000..e2c87ba --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/domain/model/TextureParams.kt @@ -0,0 +1,236 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.domain.model + +import com.t8rin.imagetoolbox.core.domain.model.ColorModel + +data class TextureParams( + val textureFilterType: TextureFilterType, + val color: ColorModel, + val radius: Int, + val monochrome: Boolean, + val shine: Float, + val scale: Float, + val brightness: Int, + val amount: Float, + val turbulence: Float, + val dispersion: Float, + val time: Float, + val samples: Int, + val backgroundColor: ColorModel, + val foregroundColor: ColorModel, + val xScale: Int, + val yScale: Int, + val fuzziness: Int, + val stretch: Float, + val angle: Float, + val angleCoefficient: Float, + val gradientCoefficient: Float, + val f1: Float, + val f2: Float, + val f3: Float, + val f4: Float, + val randomness: Float, + val gridType: CellularGridType, + val distancePower: Float, + val octaves: Float, + val h: Float, + val lacunarity: Float, + val gain: Float, + val bias: Float, + val turbulenceFactor: Float, + val scaling: Float, + val iterations: Int, + val a: Float, + val b: Float, + val c: Float, + val d: Float, + val k: Int, + val rings: Float, + val fastNoiseParams: FastNoiseTextureParams? = null +) { + companion object { + val Default by lazy { + TextureParams( + textureFilterType = TextureFilterType.BrushedMetal, + color = ColorModel(-7829368), + radius = 10, + monochrome = true, + shine = 0.1f, + scale = 32f, + brightness = 10, + amount = 1f, + turbulence = 1f, + dispersion = 0f, + time = 0f, + samples = 2, + backgroundColor = ColorModel(-16777216), + foregroundColor = ColorModel(-1), + xScale = 8, + yScale = 8, + fuzziness = 0, + stretch = 1f, + angle = 0f, + angleCoefficient = 0f, + gradientCoefficient = 0f, + f1 = 1f, + f2 = 0f, + f3 = 0f, + f4 = 0f, + randomness = 0f, + gridType = CellularGridType.Hexagonal, + distancePower = 2f, + octaves = 4f, + h = 1f, + lacunarity = 2f, + gain = 0.5f, + bias = 0.5f, + turbulenceFactor = 0.4f, + scaling = 0f, + iterations = 25000, + a = -0.59f, + b = 0.2f, + c = 0.1f, + d = 0f, + k = 0, + rings = 0.5f + ) + } + } +} + +fun TextureParams.withDefaultsFor(textureFilterType: TextureFilterType): TextureParams = + if (textureFilterType.isFastNoise) { + copy( + textureFilterType = textureFilterType, + fastNoiseParams = FastNoiseTextureParams.defaultFor(textureFilterType) + ) + } else when (textureFilterType) { + TextureFilterType.BrushedMetal -> copy( + textureFilterType = textureFilterType, + color = ColorModel(-7829368), + radius = 10, + amount = 0.1f, + monochrome = true, + shine = 0.1f, + fastNoiseParams = null + ) + + TextureFilterType.Caustics -> copy( + textureFilterType = textureFilterType, + scale = 32f, + brightness = 10, + amount = 1f, + turbulence = 1f, + dispersion = 0f, + time = 0f, + samples = 2, + backgroundColor = ColorModel(-8806401), + fastNoiseParams = null + ) + + TextureFilterType.Cellular -> copy( + textureFilterType = textureFilterType, + color = ColorModel(-1), + scale = 32f, + stretch = 1f, + angle = 0f, + angleCoefficient = 0f, + gradientCoefficient = 0f, + f1 = 1f, + f2 = 0f, + f3 = 0f, + f4 = 0f, + randomness = 0f, + gridType = CellularGridType.Hexagonal, + distancePower = 2f, + turbulence = 1f, + amount = 1f, + gain = 0.5f, + bias = 0.5f, + fastNoiseParams = null + ) + + TextureFilterType.Check -> copy( + textureFilterType = textureFilterType, + foregroundColor = ColorModel(-1), + backgroundColor = ColorModel(-16777216), + xScale = 8, + yScale = 8, + fuzziness = 0, + angle = 0f, + fastNoiseParams = null + ) + + TextureFilterType.FBM -> copy( + textureFilterType = textureFilterType, + amount = 1f, + scale = 32f, + stretch = 1f, + angle = 0f, + octaves = 4f, + h = 1f, + lacunarity = 2f, + gain = 0.5f, + bias = 0.5f, + fastNoiseParams = null + ) + + TextureFilterType.Marble -> copy( + textureFilterType = textureFilterType, + scale = 32f, + stretch = 1f, + angle = 0f, + turbulence = 1f, + turbulenceFactor = 0.4f, + fastNoiseParams = null + ) + + TextureFilterType.Plasma -> copy( + textureFilterType = textureFilterType, + color = ColorModel(-16777216), + turbulence = 1f, + scaling = 0f, + fastNoiseParams = null + ) + + TextureFilterType.Quilt -> copy( + textureFilterType = textureFilterType, + iterations = 25000, + a = -0.59f, + b = 0.2f, + c = 0.1f, + d = 0f, + k = 0, + fastNoiseParams = null + ) + + TextureFilterType.Wood -> copy( + textureFilterType = textureFilterType, + rings = 2f, + turbulence = 1f, + gain = 0.5f, + bias = 0.5f, + scale = 32f, + stretch = 1f, + angle = 0f, + fastNoiseParams = null + ) + + else -> error("Unsupported JH Labs texture type: $textureFilterType") + } \ No newline at end of file diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/TextureGenerationContent.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/TextureGenerationContent.kt new file mode 100644 index 0000000..54dc4b0 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/TextureGenerationContent.kt @@ -0,0 +1,231 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_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.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.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.helper.isPortraitOrientationAsState +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.UndoRedoButtons +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.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.texture_generation.presentation.components.TextureParamsSelection +import com.t8rin.imagetoolbox.texture_generation.presentation.screenLogic.TextureGenerationComponent + +@Composable +fun TextureGenerationContent( + component: TextureGenerationComponent +) { + val isPortrait by isPortraitOrientationAsState() + + val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = { + component.saveTexture( + oneTimeSaveLocationUri = it + ) + } + + val shareButton: @Composable () -> Unit = { + var editSheetData by remember { + mutableStateOf(listOf()) + } + ShareButton( + onShare = component::shareTexture, + onCopy = { + component.cacheCurrentTexture(Clipboard::copy) + }, + onEdit = { + component.cacheCurrentTexture( + onComplete = { + editSheetData = listOf(it) + } + ) + } + ) + ProcessImagesPreferenceSheet( + uris = editSheetData, + visible = editSheetData.isNotEmpty(), + onDismiss = { + editSheetData = emptyList() + }, + onNavigate = component.onNavigate + ) + } + + var showExitDialog by rememberSaveable { mutableStateOf(false) } + + AdaptiveLayoutScreen( + shouldDisableBackHandler = false, + title = { + Text( + text = stringResource(R.string.texture_generation), + textAlign = TextAlign.Center, + modifier = Modifier.marquee() + ) + }, + onGoBack = { + showExitDialog = true + }, + actions = { + if (!isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + shareButton() + if (isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + }, + topAppBarPersistentActions = { + TopAppBarEmoji() + }, + imagePreview = { + Box( + contentAlignment = Alignment.Center + ) { + Picture( + model = component.previewBitmap, + modifier = Modifier + .container(MaterialTheme.shapes.medium) + .aspectRatio(component.textureSize.safeAspectRatio.animate()), + shape = MaterialTheme.shapes.medium, + contentScale = ContentScale.FillBounds + ) + if (component.isImageLoading) EnhancedLoadingIndicator() + } + }, + controls = { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + ResizeImageField( + imageInfo = derivedValueOf( + keys = arrayOf(component.textureSize), + calculation = { + ImageInfo(component.textureSize.width, component.textureSize.height) + } + ), + originalSize = null, + onWidthChange = component::setTextureWidth, + onHeightChange = component::setTextureHeight + ) + TextureParamsSelection( + value = component.textureParams, + onValueChange = component::updateParams, + previewProvider = component::getTextureTransformation + ) + 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 = { actions -> + var showFolderSelectionDialog by rememberSaveable { + mutableStateOf(false) + } + BottomButtonsBlock( + isNoData = false, + isSecondaryButtonVisible = false, + onSecondaryButtonClick = {}, + onPrimaryButtonClick = { + saveBitmap(null) + }, + onPrimaryButtonLongClick = { + showFolderSelectionDialog = true + }, + actions = { + if (isPortrait) actions() + } + ) + OneTimeSaveLocationSelectionDialog( + visible = showFolderSelectionDialog, + onDismiss = { showFolderSelectionDialog = false }, + onSaveRequest = saveBitmap, + formatForFilenameSelection = component.getFormatForFilenameSelection() + ) + }, + canShowScreenData = true + ) + + ExitWithoutSavingDialog( + onExit = component.onGoBack, + onDismiss = { showExitDialog = false }, + visible = showExitDialog + ) + + LoadingDialog( + visible = component.isSaving, + onCancelLoading = component::cancelSaving + ) +} diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/BrushedMetalParams.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/BrushedMetalParams.kt new file mode 100644 index 0000000..e0e2153 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/BrushedMetalParams.kt @@ -0,0 +1,77 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.presentation.components + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +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.modifier.ShapeDefaults +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch +import com.t8rin.imagetoolbox.texture_generation.domain.model.TextureParams + +@Composable +internal fun BrushedMetalParams( + value: TextureParams, + onValueChange: (TextureParams) -> Unit +) { + ParamColumn { + ColorParam( + title = stringResource(R.string.color), + value = value.color.toColor(), + onValueChange = { + onValueChange(value.copy(color = it.toModel())) + }, + shape = ShapeDefaults.top + ) + IntParam( + value = value.radius, + title = stringResource(R.string.radius), + range = 0f..50f, + onValueChange = { + onValueChange(value.copy(radius = it)) + } + ) + FloatParam( + value = value.amount, + title = stringResource(R.string.amount), + range = 0f..1f, + onValueChange = { + onValueChange(value.copy(amount = it)) + } + ) + FloatParam( + value = value.shine, + title = stringResource(R.string.shine), + range = 0f..1f, + onValueChange = { + onValueChange(value.copy(shine = it)) + } + ) + PreferenceRowSwitch( + title = stringResource(R.string.monochrome), + checked = value.monochrome, + onClick = { + onValueChange(value.copy(monochrome = it)) + }, + applyHorizontalPadding = false, + shape = ShapeDefaults.bottom + ) + } +} \ No newline at end of file diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/CausticsParams.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/CausticsParams.kt new file mode 100644 index 0000000..2d8e7e8 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/CausticsParams.kt @@ -0,0 +1,80 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.presentation.components + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +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.modifier.ShapeDefaults +import com.t8rin.imagetoolbox.texture_generation.domain.model.TextureParams + +@Composable +internal fun CausticsParams( + value: TextureParams, + onValueChange: (TextureParams) -> Unit +) { + ParamColumn { + ColorParam( + title = stringResource(R.string.color), + value = value.backgroundColor.toColor(), + onValueChange = { + onValueChange(value.copy(backgroundColor = it.toModel())) + }, + shape = ShapeDefaults.top + ) + FloatParam( + value = value.scale, + title = stringResource(R.string.scale), + range = 1f..512f, + onValueChange = { onValueChange(value.copy(scale = it)) } + ) + IntParam( + value = value.brightness, + title = stringResource(R.string.brightness), + range = 0f..100f, + onValueChange = { onValueChange(value.copy(brightness = it)) } + ) + FloatParam( + value = value.amount, + title = stringResource(R.string.amount), + range = 0f..10f, + onValueChange = { onValueChange(value.copy(amount = it)) } + ) + FloatParam( + value = value.turbulence, + title = stringResource(R.string.turbulence), + range = 0f..10f, + onValueChange = { onValueChange(value.copy(turbulence = it)) } + ) + FloatParam( + value = value.dispersion, + title = stringResource(R.string.dispersion), + range = 0f..10f, + onValueChange = { onValueChange(value.copy(dispersion = it)) } + ) + FloatParam( + value = value.time, + title = stringResource(R.string.time), + range = 0f..10f, + onValueChange = { onValueChange(value.copy(time = it)) }, + shape = ShapeDefaults.bottom + ) + } +} \ No newline at end of file diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/CellularParams.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/CellularParams.kt new file mode 100644 index 0000000..544895c --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/CellularParams.kt @@ -0,0 +1,138 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.presentation.components + +import androidx.compose.runtime.Composable +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.utils.helper.toColor +import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel +import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.DataSelector +import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults +import com.t8rin.imagetoolbox.texture_generation.domain.model.CellularGridType +import com.t8rin.imagetoolbox.texture_generation.domain.model.TextureParams + +@Composable +internal fun CellularParams( + value: TextureParams, + onValueChange: (TextureParams) -> Unit +) { + ParamColumn { + ColorParam( + title = stringResource(R.string.color), + value = value.color.toColor(), + onValueChange = { onValueChange(value.copy(color = it.toModel())) }, + shape = ShapeDefaults.top + ) + FloatParam( + value = value.scale, + title = stringResource(R.string.scale), + range = 1f..512f, + onValueChange = { onValueChange(value.copy(scale = it)) } + ) + FloatParam( + value = value.stretch, + title = stringResource(R.string.stretch), + range = 0.1f..20f, + onValueChange = { onValueChange(value.copy(stretch = it)) } + ) + FloatParam( + value = value.angle, + title = stringResource(R.string.angle), + range = -360f..360f, + onValueChange = { onValueChange(value.copy(angle = it)) } + ) + FloatParam( + value = value.angleCoefficient, + title = stringResource(R.string.angle_coefficient), + range = -10f..10f, + onValueChange = { onValueChange(value.copy(angleCoefficient = it)) } + ) + FloatParam( + value = value.gradientCoefficient, + title = stringResource(R.string.gradient_coefficient), + range = -10f..10f, + onValueChange = { onValueChange(value.copy(gradientCoefficient = it)) } + ) + FloatParam( + value = value.f1, + title = "F1", + range = -5f..5f, + onValueChange = { onValueChange(value.copy(f1 = it)) } + ) + FloatParam( + value = value.f2, + title = "F2", + range = -5f..5f, + onValueChange = { onValueChange(value.copy(f2 = it)) } + ) + FloatParam( + value = value.randomness, + title = stringResource(R.string.randomness), + range = 0f..1f, + onValueChange = { onValueChange(value.copy(randomness = it)) } + ) + DataSelector( + value = value.gridType, + onValueChange = { + onValueChange(value.copy(gridType = it)) + }, + entries = CellularGridType.entries, + title = stringResource(R.string.grid_type), + titleIcon = null, + itemContentText = { + stringResource(it.titleRes()) + }, + spanCount = 1, + containerColor = Color.Unspecified, + shape = ShapeDefaults.center + ) + FloatParam( + value = value.distancePower, + title = stringResource(R.string.distance_power), + range = 0.1f..10f, + onValueChange = { onValueChange(value.copy(distancePower = it)) } + ) + FloatParam( + value = value.turbulence, + title = stringResource(R.string.turbulence), + range = 0f..10f, + onValueChange = { onValueChange(value.copy(turbulence = it)) } + ) + FloatParam( + value = value.amount, + title = stringResource(R.string.amount), + range = -10f..10f, + onValueChange = { onValueChange(value.copy(amount = it)) } + ) + FloatParam( + value = value.gain, + title = stringResource(R.string.gain), + range = 0f..1f, + onValueChange = { onValueChange(value.copy(gain = it)) } + ) + FloatParam( + value = value.bias, + title = stringResource(R.string.bias), + range = 0f..1f, + onValueChange = { onValueChange(value.copy(bias = it)) }, + shape = ShapeDefaults.bottom + ) + } +} \ No newline at end of file diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/CheckParams.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/CheckParams.kt new file mode 100644 index 0000000..3792ae0 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/CheckParams.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.presentation.components + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +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.modifier.ShapeDefaults +import com.t8rin.imagetoolbox.texture_generation.domain.model.TextureParams + +@Composable +internal fun CheckParams( + value: TextureParams, + onValueChange: (TextureParams) -> Unit +) { + ParamColumn { + ColorParam( + title = stringResource(R.string.light_color), + value = value.foregroundColor.toColor(), + onValueChange = { + onValueChange(value.copy(foregroundColor = it.toModel())) + }, + shape = ShapeDefaults.top + ) + ColorParam( + title = stringResource(R.string.dark_color), + value = value.backgroundColor.toColor(), + onValueChange = { + onValueChange(value.copy(backgroundColor = it.toModel())) + } + ) + IntParam( + value = value.xScale, + title = stringResource(R.string.scale_x), + range = 1f..128f, + onValueChange = { onValueChange(value.copy(xScale = it)) } + ) + IntParam( + value = value.yScale, + title = stringResource(R.string.scale_y), + range = 1f..128f, + onValueChange = { onValueChange(value.copy(yScale = it)) } + ) + IntParam( + value = value.fuzziness, + title = stringResource(R.string.fuzziness), + range = 0f..128f, + onValueChange = { onValueChange(value.copy(fuzziness = it)) } + ) + FloatParam( + value = value.angle, + title = stringResource(R.string.angle), + range = -360f..360f, + onValueChange = { onValueChange(value.copy(angle = it)) }, + shape = ShapeDefaults.bottom + ) + } +} \ No newline at end of file diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/FastNoiseParams.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/FastNoiseParams.kt new file mode 100644 index 0000000..05cb9aa --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/FastNoiseParams.kt @@ -0,0 +1,844 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.presentation.components + +import androidx.annotation.StringRes +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +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.modifier.ShapeDefaults +import com.t8rin.imagetoolbox.texture_generation.domain.model.TextureFilterType +import com.t8rin.imagetoolbox.texture_generation.domain.model.TextureParams + +@Composable +internal fun FastNoiseParams( + value: TextureParams, + onValueChange: (TextureParams) -> Unit +) { + val params = value.fastNoiseParams ?: return + val config = value.textureFilterType.fastNoiseConfig() + + ParamColumn { + val showScale = value.textureFilterType != TextureFilterType.RingedPlanet + + if (showScale) { + FloatParam( + value = params.scale, + title = stringResource(R.string.scale), + range = 0.001f..0.05f, + roundTo = 4, + onValueChange = { scale -> + onValueChange(value.copy(fastNoiseParams = params.copy(scale = scale))) + }, + shape = ShapeDefaults.top + ) + } + config.values.forEachIndexed { index, info -> + FloatParam( + value = params.values[index], + title = stringResource(info.title), + range = info.range, + roundTo = info.roundTo, + onValueChange = { newValue -> + onValueChange( + value.copy( + fastNoiseParams = params.copy( + values = params.values.updated(index, newValue) + ) + ) + ) + }, + shape = if (!showScale && index == 0) ShapeDefaults.top else ShapeDefaults.center + ) + } + config.colors.forEachIndexed { index, title -> + ColorParam( + title = stringResource(title), + value = params.colors[index].toColor(), + onValueChange = { color -> + onValueChange( + value.copy( + fastNoiseParams = params.copy( + colors = params.colors.updated(index, color.toModel()) + ) + ) + ) + }, + shape = ShapeDefaults.center + ) + } + IntParam( + value = params.seed, + title = stringResource(R.string.seed), + range = -10000f..10000f, + onValueChange = { seed -> + onValueChange(value.copy(fastNoiseParams = params.copy(seed = seed))) + }, + shape = ShapeDefaults.bottom + ) + } +} + +private data class FastNoiseConfig( + val values: List, + val colors: List +) + +private data class ParamInfo( + @StringRes val title: Int, + val range: ClosedFloatingPointRange, + val roundTo: Int = 2 +) + +private fun TextureFilterType.fastNoiseConfig(): FastNoiseConfig = when (this) { + TextureFilterType.Brick -> config( + param(R.string.aspect_ratio, 0.5f..4f), + unit(R.string.texture_mortar_width), + unit(R.string.texture_irregularity), + unit(R.string.texture_roughness), + unit(R.string.texture_bevel), + colors = intArrayOf( + R.string.texture_mortar_color, + R.string.texture_dark_brick_color, + R.string.texture_brick_color, + R.string.texture_highlight_color + ) + ) + + TextureFilterType.Camouflage -> config( + unit(R.string.texture_first_threshold), + unit(R.string.texture_second_threshold), + unit(R.string.texture_third_threshold), + distortion(), + unit(R.string.texture_edge_softness, 3), + colors = intArrayOf( + R.string.texture_dark_color, + R.string.texture_forest_color, + R.string.texture_earth_color, + R.string.texture_sand_color + ) + ) + + TextureFilterType.Cell -> config( + unit(R.string.jitter), + unit(R.string.texture_border_width), + unit(R.string.glow), + distortion(), + unit(R.string.variation), + colors = intArrayOf( + R.string.texture_background_color, + R.string.texture_cell_color, + R.string.texture_edge_color, + R.string.texture_highlight_color + ) + ) + + TextureFilterType.Cloud -> config( + unit(R.string.texture_coverage), + unit(R.string.softness), + unit(R.string.texture_detail), + distortion(), + unit(R.string.density), + colors = intArrayOf( + R.string.texture_sky_color, + R.string.texture_shadow_color, + R.string.texture_light_color + ) + ) + + TextureFilterType.Crack -> config( + unit(R.string.width, 3), + unit(R.string.density), + distortion(), + unit(R.string.texture_depth), + unit(R.string.texture_branching), + colors = intArrayOf( + R.string.texture_surface_color, + R.string.texture_variation_color, + R.string.texture_crack_color, + R.string.texture_edge_color + ) + ) + + TextureFilterType.Fabric -> config( + param(R.string.texture_horizontal_threads, 1f..64f), + param(R.string.texture_vertical_threads, 1f..64f), + unit(R.string.texture_irregularity), + unit(R.string.texture_depth), + unit(R.string.texture_fuzz), + colors = intArrayOf( + R.string.texture_warp_color, + R.string.texture_weft_color, + R.string.texture_shadow_color, + R.string.texture_highlight_color + ) + ) + + TextureFilterType.Foliage -> config( + unit(R.string.density), + unit(R.string.texture_edge_softness), + unit(R.string.texture_veins), + unit(R.string.texture_lighting), + unit(R.string.variation), + colors = intArrayOf( + R.string.texture_shadow_color, + R.string.texture_dark_leaf_color, + R.string.texture_leaf_color, + R.string.texture_highlight_color + ) + ) + + TextureFilterType.Honeycomb -> config( + unit(R.string.texture_border_width, 3), + unit(R.string.texture_bevel), + unit(R.string.texture_irregularity), + unit(R.string.fill), + unit(R.string.glow), + colors = intArrayOf( + R.string.texture_background_color, + R.string.texture_border_color, + R.string.texture_honey_color, + R.string.texture_highlight_color + ) + ) + + TextureFilterType.Ice -> config( + unit(R.string.texture_crack_width, 3), + unit(R.string.texture_frost), + unit(R.string.texture_depth), + distortion(), + unit(R.string.sparkle), + colors = intArrayOf( + R.string.texture_deep_color, + R.string.texture_ice_color, + R.string.texture_frost_color, + R.string.texture_crack_color + ) + ) + + TextureFilterType.Lava -> config( + distortion(), + param(R.string.texture_flow, 0f..4f), + unit(R.string.texture_detail), + unit(R.string.texture_crust), + unit(R.string.glow), + colors = intArrayOf( + R.string.texture_crust_color, + R.string.texture_lava_color, + R.string.texture_glow_color + ) + ) + + TextureFilterType.Nebula -> config( + param(R.string.turbulence, 0f..64f), + unit(R.string.texture_cloud_density), + unit(R.string.texture_stars), + unit(R.string.glow), + contrast(), + colors = intArrayOf( + R.string.texture_space_color, + R.string.texture_violet_color, + R.string.texture_blue_color, + R.string.texture_glow_color + ) + ) + + TextureFilterType.Paper -> config( + param(R.string.texture_fiber_density, 1f..128f), + unit(R.string.texture_fiber_strength), + unit(R.string.grain), + unit(R.string.texture_stains), + unit(R.string.texture_roughness), + colors = intArrayOf( + R.string.texture_base_color, + R.string.texture_light_color, + R.string.texture_fiber_color, + R.string.texture_stain_color + ) + ) + + TextureFilterType.Rust -> config( + unit(R.string.texture_corrosion), + unit(R.string.texture_pitting), + unit(R.string.texture_flakes), + distortion(), + contrast(), + colors = intArrayOf( + R.string.texture_metal_color, + R.string.texture_dark_rust_color, + R.string.texture_rust_color, + R.string.texture_orange_color + ) + ) + + TextureFilterType.Sand -> config( + param(R.string.texture_dune_frequency, 1f..32f), + param(R.string.texture_wind_angle, 0f..6.28f), + unit(R.string.texture_ripples), + unit(R.string.grain), + contrast(), + colors = intArrayOf( + R.string.texture_shadow_color, + R.string.texture_sand_color, + R.string.texture_light_color + ) + ) + + TextureFilterType.Smoke -> config( + param(R.string.turbulence, 0f..64f), + unit(R.string.density), + param(R.string.texture_wisps, 0f..5f), + contrast(), + unit(R.string.texture_detail), + colors = intArrayOf( + R.string.texture_background_color, + R.string.texture_shadow_color, + R.string.texture_smoke_color + ) + ) + + TextureFilterType.Stone -> config( + unit(R.string.grain), + unit(R.string.texture_veins), + param(R.string.texture_vein_scale, 0.001f..0.1f, 3), + distortion(), + contrast(), + colors = intArrayOf( + R.string.texture_dark_color, + R.string.texture_light_color, + R.string.texture_vein_color + ) + ) + + TextureFilterType.Terrain -> config( + unit(R.string.texture_water_level), + unit(R.string.texture_mountain_level), + unit(R.string.texture_erosion), + unit(R.string.texture_detail), + unit(R.string.texture_snow_level), + colors = intArrayOf( + R.string.texture_water_color, + R.string.texture_lowland_color, + R.string.texture_rock_color, + R.string.texture_snow_color + ) + ) + + TextureFilterType.Topography -> config( + param(R.string.texture_line_count, 1f..32f), + unit(R.string.texture_line_thickness), + unit(R.string.texture_shading), + distortion(), + contrast(), + colors = intArrayOf( + R.string.texture_low_color, + R.string.texture_high_color, + R.string.texture_line_color + ) + ) + + TextureFilterType.WaterRipple -> config( + param(R.string.frequency, 1f..48f), + distortion(), + unit(R.string.texture_caustics), + unit(R.string.texture_depth), + unit(R.string.highlights), + colors = intArrayOf( + R.string.texture_deep_color, + R.string.texture_shallow_color, + R.string.texture_highlight_color + ) + ) + + TextureFilterType.AdvancedWood -> config( + param(R.string.rings, 1f..32f), + unit(R.string.grain), + distortion(), + param(R.string.stretch, 1f..16f), + unit(R.string.contrast), + colors = intArrayOf( + R.string.texture_dark_color, + R.string.texture_light_color, + R.string.texture_pore_color + ) + ) + + TextureFilterType.Grass -> config( + param(R.string.texture_blade_density, 0f..2f), + param(R.string.texture_blade_length, 1f..100f), + param(R.string.texture_wind, 0f..2f), + unit(R.string.texture_patchiness), + unit(R.string.highlights), + colors = intArrayOf( + R.string.texture_dirt_color, + R.string.texture_dark_grass_color, + R.string.texture_grass_color, + R.string.texture_tip_color + ) + ) + + TextureFilterType.Dirt -> config( + unit(R.string.texture_clumps), + unit(R.string.texture_moisture), + unit(R.string.texture_pebbles), + unit(R.string.texture_roughness), + unit(R.string.variation), + colors = intArrayOf( + R.string.texture_dark_earth_color, + R.string.texture_earth_color, + R.string.texture_dry_color, + R.string.texture_pebble_color + ) + ) + + TextureFilterType.Leather -> config( + unit(R.string.texture_wrinkles), + unit(R.string.texture_pores), + unit(R.string.grain), + unit(R.string.softness), + unit(R.string.shine, 3), + colors = intArrayOf( + R.string.texture_shadow_color, + R.string.texture_leather_color, + R.string.texture_light_color, + R.string.texture_pore_color + ) + ) + + TextureFilterType.Concrete -> config( + unit(R.string.texture_aggregate), + unit(R.string.texture_stains), + unit(R.string.texture_roughness), + unit(R.string.texture_cracks), + contrast(), + colors = intArrayOf( + R.string.texture_dark_color, + R.string.texture_concrete_color, + R.string.texture_light_color, + R.string.texture_crack_color + ) + ) + + TextureFilterType.Asphalt -> config( + unit(R.string.texture_aggregate), + unit(R.string.texture_tar), + unit(R.string.texture_wear), + unit(R.string.texture_speckles), + contrast(), + colors = intArrayOf( + R.string.texture_tar_color, + R.string.texture_asphalt_color, + R.string.texture_stone_color, + R.string.texture_dust_color + ) + ) + + TextureFilterType.Moss -> config( + unit(R.string.density), + unit(R.string.texture_fibers), + unit(R.string.texture_moisture), + unit(R.string.variation), + unit(R.string.texture_clumps), + colors = intArrayOf( + R.string.texture_soil_color, + R.string.texture_dark_moss_color, + R.string.texture_moss_color, + R.string.texture_tip_color + ) + ) + + TextureFilterType.Fire -> config( + param(R.string.texture_flame_frequency, 1f..32f), + param(R.string.turbulence, 0f..64f), + unit(R.string.texture_intensity), + unit(R.string.texture_smoke_amount), + unit(R.string.texture_detail), + colors = intArrayOf( + R.string.texture_background_color, + R.string.texture_red_color, + R.string.texture_orange_color, + R.string.texture_core_color + ) + ) + + TextureFilterType.Aurora -> config( + param(R.string.texture_ribbons, 1f..32f), + distortion(), + unit(R.string.glow), + unit(R.string.texture_stars), + contrast(), + colors = intArrayOf( + R.string.texture_sky_color, + R.string.texture_green_color, + R.string.texture_cyan_color, + R.string.texture_violet_color + ) + ) + + TextureFilterType.OilSlick -> config( + param(R.string.texture_bands, 1f..32f), + distortion(), + unit(R.string.texture_iridescence), + unit(R.string.texture_darkness), + contrast(), + colors = intArrayOf( + R.string.texture_dark_color, + R.string.texture_magenta_color, + R.string.texture_cyan_color, + R.string.texture_gold_color + ) + ) + + TextureFilterType.Watercolor -> config( + unit(R.string.texture_blooms), + unit(R.string.texture_pigment), + unit(R.string.texture_edges), + unit(R.string.texture_paper_amount), + unit(R.string.texture_diffusion), + colors = intArrayOf( + R.string.texture_paper_color, + R.string.texture_pigment_color, + R.string.texture_secondary_color, + R.string.texture_edge_color + ) + ) + + TextureFilterType.AbstractFlow -> config( + param(R.string.frequency, 1f..32f), + distortion(), + unit(R.string.texture_symmetry), + param(R.string.texture_sharpness, 0f..3f), + unit(R.string.glow), + colors = intArrayOf( + R.string.texture_background_color, + R.string.texture_first_color, + R.string.texture_second_color, + R.string.texture_glow_color + ) + ) + + TextureFilterType.Opal -> config( + unit(R.string.texture_color_play), + unit(R.string.texture_milkiness), + param(R.string.texture_bands, 1f..32f), + distortion(), + unit(R.string.glow), + colors = intArrayOf( + R.string.texture_base_color, + R.string.texture_cyan_color, + R.string.texture_pink_color, + R.string.texture_gold_color + ) + ) + + TextureFilterType.DamascusSteel -> config( + param(R.string.texture_layers, 1f..64f), + unit(R.string.texture_folding), + distortion(), + unit(R.string.texture_polish), + contrast(), + colors = intArrayOf( + R.string.texture_dark_steel_color, + R.string.texture_steel_color, + R.string.texture_light_steel_color, + R.string.texture_oxide_color + ) + ) + + TextureFilterType.Lightning -> config( + param(R.string.texture_branches, 1f..32f), + param(R.string.turbulence, 0f..64f), + unit(R.string.width, 3), + unit(R.string.glow), + unit(R.string.texture_intensity), + colors = intArrayOf( + R.string.texture_background_color, + R.string.texture_halo_color, + R.string.texture_bolt_color, + R.string.texture_core_color + ) + ) + + TextureFilterType.Velvet -> config( + unit(R.string.texture_fibers), + param(R.string.texture_direction, 0f..6.28f), + unit(R.string.softness), + unit(R.string.texture_sheen), + unit(R.string.texture_folds), + colors = intArrayOf( + R.string.texture_shadow_color, + R.string.texture_velvet_color, + R.string.texture_sheen_color, + R.string.texture_highlight_color + ) + ) + + TextureFilterType.InkMarbling -> config( + param(R.string.texture_ribbons, 1f..32f), + param(R.string.turbulence, 0f..64f), + unit(R.string.texture_feathering), + unit(R.string.texture_ink_balance), + contrast(), + colors = intArrayOf( + R.string.texture_paper_color, + R.string.texture_blue_ink_color, + R.string.texture_red_ink_color, + R.string.texture_dark_ink_color + ) + ) + + TextureFilterType.HolographicFoil -> config( + param(R.string.texture_spectrum, 1f..32f), + unit(R.string.texture_crinkles), + unit(R.string.texture_diffraction), + param(R.string.angle, 0f..6.28f), + unit(R.string.shine), + colors = intArrayOf( + R.string.texture_silver_color, + R.string.texture_cyan_color, + R.string.texture_magenta_color, + R.string.texture_yellow_color + ) + ) + + TextureFilterType.Bioluminescence -> config( + unit(R.string.texture_veins), + unit(R.string.texture_branching), + param(R.string.turbulence, 0f..64f), + unit(R.string.glow), + unit(R.string.texture_depth), + colors = intArrayOf( + R.string.texture_background_color, + R.string.texture_tissue_color, + R.string.texture_glow_color, + R.string.texture_core_color + ) + ) + + TextureFilterType.CosmicVortex -> config( + param(R.string.texture_arms, 1f..16f), + param(R.string.texture_twist, 0f..32f), + unit(R.string.turbulence), + unit(R.string.texture_stars), + unit(R.string.texture_core_glow), + colors = intArrayOf( + R.string.texture_space_color, + R.string.texture_blue_color, + R.string.texture_violet_color, + R.string.texture_core_color + ) + ) + + TextureFilterType.LavaLamp -> config( + param(R.string.texture_blobs, 1f..16f), + unit(R.string.softness), + distortion(), + unit(R.string.glow), + contrast(), + colors = intArrayOf( + R.string.texture_background_color, + R.string.texture_first_color, + R.string.texture_second_color, + R.string.texture_glow_color + ) + ) + + TextureFilterType.EventHorizon -> config( + unit(R.string.texture_disk_tilt), + unit(R.string.texture_horizon_size), + unit(R.string.texture_disk_width, 3), + unit(R.string.texture_lensing), + unit(R.string.texture_stars), + colors = intArrayOf( + R.string.texture_space_color, + R.string.texture_disk_color, + R.string.texture_hot_color, + R.string.texture_lens_color + ) + ) + + TextureFilterType.FractalBloom -> config( + param(R.string.texture_petals, 1f..16f), + param(R.string.texture_layers, 1f..16f), + param(R.string.texture_curl, 0f..12f), + unit(R.string.texture_filigree), + unit(R.string.glow), + colors = intArrayOf( + R.string.texture_background_color, + R.string.texture_outer_color, + R.string.texture_inner_color, + R.string.texture_core_color + ) + ) + + TextureFilterType.ChromaticTunnel -> config( + param(R.string.texture_depth, 1f..32f), + param(R.string.texture_twist, 0f..16f), + param(R.string.texture_facets, 1f..16f), + unit(R.string.texture_curvature), + unit(R.string.glow), + colors = intArrayOf( + R.string.texture_deep_color, + R.string.texture_cyan_color, + R.string.texture_magenta_color, + R.string.texture_light_color + ) + ) + + TextureFilterType.EclipseCorona -> config( + unit(R.string.texture_moon_size), + unit(R.string.texture_corona_size), + param(R.string.texture_rays, 1f..64f), + unit(R.string.turbulence), + unit(R.string.texture_diamond_ring), + colors = intArrayOf( + R.string.texture_space_color, + R.string.texture_corona_color, + R.string.texture_hot_color, + R.string.texture_light_color + ) + ) + + TextureFilterType.StrangeAttractor -> config( + param(R.string.texture_lobes, 1f..8f), + param(R.string.texture_orbit_density, 1f..64f), + param(R.string.texture_curvature, 0f..12f), + unit(R.string.texture_thickness, 3), + unit(R.string.glow), + colors = intArrayOf( + R.string.texture_background_color, + R.string.texture_cold_color, + R.string.texture_warm_color, + R.string.texture_core_color + ) + ) + + TextureFilterType.FerrofluidCrown -> config( + param(R.string.texture_spikes, 1f..32f), + unit(R.string.texture_spike_length), + unit(R.string.texture_body_size), + unit(R.string.texture_metallic), + unit(R.string.distortion), + colors = intArrayOf( + R.string.texture_background_color, + R.string.texture_shadow_color, + R.string.texture_metal_color, + R.string.texture_highlight_color + ) + ) + + TextureFilterType.Supernova -> config( + unit(R.string.texture_shock_radius), + unit(R.string.texture_shell_width, 3), + unit(R.string.texture_ejecta), + unit(R.string.turbulence), + unit(R.string.texture_stars), + colors = intArrayOf( + R.string.texture_space_color, + R.string.texture_cloud_color, + R.string.texture_flame_color, + R.string.texture_core_color + ) + ) + + TextureFilterType.Iris -> config( + unit(R.string.texture_pupil_size), + unit(R.string.texture_iris_size), + param(R.string.texture_fibers, 1f..96f), + unit(R.string.texture_color_variation), + unit(R.string.texture_catchlight), + colors = intArrayOf( + R.string.texture_background_color, + R.string.texture_outer_color, + R.string.texture_inner_color, + R.string.texture_gold_color + ) + ) + + TextureFilterType.PeacockFeather -> config( + unit(R.string.texture_eye_size), + param(R.string.texture_barb_density, 1f..96f), + unit(R.string.texture_curvature), + unit(R.string.texture_iridescence), + unit(R.string.softness), + colors = intArrayOf( + R.string.texture_background_color, + R.string.texture_feather_color, + R.string.texture_blue_color, + R.string.texture_gold_color + ) + ) + + TextureFilterType.NautilusShell -> config( + param(R.string.texture_turns, 1f..8f), + param(R.string.texture_chambers, 1f..32f), + unit(R.string.texture_opening), + unit(R.string.texture_ridges), + unit(R.string.texture_pearlescence), + colors = intArrayOf( + R.string.texture_background_color, + R.string.texture_shadow_color, + R.string.texture_shell_color, + R.string.texture_pearl_color + ) + ) + + TextureFilterType.RingedPlanet -> config( + unit(R.string.texture_planet_size), + unit(R.string.texture_ring_tilt), + unit(R.string.texture_ring_width), + unit(R.string.texture_atmosphere), + unit(R.string.texture_stars), + colors = intArrayOf( + R.string.texture_space_color, + R.string.texture_shadow_color, + R.string.texture_planet_color, + R.string.texture_ring_color + ) + ) + + else -> error("Unsupported fast-noise texture type: $this") +} + +private fun config( + vararg values: ParamInfo, + colors: IntArray +) = FastNoiseConfig( + values = values.toList(), + colors = colors.toList() +) + +private fun unit( + @StringRes title: Int, + roundTo: Int = 2 +) = param(title, 0f..1f, roundTo) + +private fun distortion() = param(R.string.distortion, 0f..64f) + +private fun contrast() = param(R.string.contrast, 0f..3f) + +private fun param( + @StringRes title: Int, + range: ClosedFloatingPointRange, + roundTo: Int = 2 +) = ParamInfo(title, range, roundTo) + +private fun List.updated(index: Int, value: T): List = + toMutableList().apply { this[index] = value } \ No newline at end of file diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/FbmParams.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/FbmParams.kt new file mode 100644 index 0000000..75e71e9 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/FbmParams.kt @@ -0,0 +1,59 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.presentation.components + +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.texture_generation.domain.model.TextureParams + +@Composable +internal fun FbmParams( + value: TextureParams, + onValueChange: (TextureParams) -> Unit +) { + ParamColumn { + FloatParam( + value = value.amount, + title = stringResource(R.string.amount), + range = 0.01f..10f, + onValueChange = { onValueChange(value.copy(amount = it)) }, + shape = ShapeDefaults.top + ) + FloatParam( + value = value.scale, + title = stringResource(R.string.scale), + range = 1f..512f, + onValueChange = { onValueChange(value.copy(scale = it)) } + ) + FloatParam( + value = value.stretch, + title = stringResource(R.string.stretch), + range = 0.1f..20f, + onValueChange = { onValueChange(value.copy(stretch = it)) } + ) + FloatParam( + value = value.angle, + title = stringResource(R.string.angle), + range = -360f..360f, + onValueChange = { onValueChange(value.copy(angle = it)) }, + shape = ShapeDefaults.bottom + ) + } +} \ No newline at end of file diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/MarbleParams.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/MarbleParams.kt new file mode 100644 index 0000000..a265b70 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/MarbleParams.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.presentation.components + +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.texture_generation.domain.model.TextureParams + +@Composable +internal fun MarbleParams( + value: TextureParams, + onValueChange: (TextureParams) -> Unit +) { + ParamColumn { + FloatParam( + value = value.scale, + title = stringResource(R.string.scale), + range = 1f..512f, + onValueChange = { onValueChange(value.copy(scale = it)) }, + shape = ShapeDefaults.top + ) + FloatParam( + value = value.stretch, + title = stringResource(R.string.stretch), + range = 0.1f..20f, + onValueChange = { onValueChange(value.copy(stretch = it)) } + ) + FloatParam( + value = value.angle, + title = stringResource(R.string.angle), + range = -360f..360f, + onValueChange = { onValueChange(value.copy(angle = it)) } + ) + FloatParam( + value = value.turbulence, + title = stringResource(R.string.turbulence), + range = 0f..10f, + onValueChange = { onValueChange(value.copy(turbulence = it)) } + ) + FloatParam( + value = value.turbulenceFactor, + title = stringResource(R.string.turbulence_factor), + range = 0f..5f, + onValueChange = { onValueChange(value.copy(turbulenceFactor = it)) }, + shape = ShapeDefaults.bottom + ) + } +} \ No newline at end of file diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/PlasmaParams.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/PlasmaParams.kt new file mode 100644 index 0000000..61e01b4 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/PlasmaParams.kt @@ -0,0 +1,54 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.presentation.components + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +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.modifier.ShapeDefaults +import com.t8rin.imagetoolbox.texture_generation.domain.model.TextureParams + +@Composable +internal fun PlasmaParams( + value: TextureParams, + onValueChange: (TextureParams) -> Unit +) { + ParamColumn { + ColorParam( + title = stringResource(R.string.color), + value = value.color.toColor(), + onValueChange = { onValueChange(value.copy(color = it.toModel())) }, + shape = ShapeDefaults.top + ) + FloatParam( + value = value.turbulence, + title = stringResource(R.string.turbulence), + range = 0f..10f, + onValueChange = { onValueChange(value.copy(turbulence = it)) } + ) + FloatParam( + value = value.scaling, + title = stringResource(R.string.scaling), + range = 0f..1f, + onValueChange = { onValueChange(value.copy(scaling = it)) }, + shape = ShapeDefaults.bottom + ) + } +} \ No newline at end of file diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/QuiltParams.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/QuiltParams.kt new file mode 100644 index 0000000..5c82693 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/QuiltParams.kt @@ -0,0 +1,71 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.presentation.components + +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.texture_generation.domain.model.TextureParams + +@Composable +internal fun QuiltParams( + value: TextureParams, + onValueChange: (TextureParams) -> Unit +) { + ParamColumn { + IntParam( + value = value.iterations, + title = stringResource(R.string.iterations), + range = 1f..50000f, + onValueChange = { onValueChange(value.copy(iterations = it)) }, + shape = ShapeDefaults.top + ) + FloatParam( + value = value.a, + title = "A", + range = -2f..2f, + onValueChange = { onValueChange(value.copy(a = it)) } + ) + FloatParam( + value = value.b, + title = "B", + range = -2f..2f, + onValueChange = { onValueChange(value.copy(b = it)) } + ) + FloatParam( + value = value.c, + title = "C", + range = -2f..2f, + onValueChange = { onValueChange(value.copy(c = it)) } + ) + FloatParam( + value = value.d, + title = "D", + range = -2f..2f, + onValueChange = { onValueChange(value.copy(d = it)) } + ) + IntParam( + value = value.k, + title = "K", + range = 0f..20f, + onValueChange = { onValueChange(value.copy(k = it)) }, + shape = ShapeDefaults.bottom + ) + } +} \ No newline at end of file diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/TextureCommonParams.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/TextureCommonParams.kt new file mode 100644 index 0000000..6780dbe --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/TextureCommonParams.kt @@ -0,0 +1,183 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.presentation.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +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 com.t8rin.imagetoolbox.core.domain.utils.roundTo +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.ui.utils.provider.ProvideContainerDefaults +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.texture_generation.domain.model.CellularGridType +import com.t8rin.imagetoolbox.texture_generation.domain.model.TextureFilterType +import kotlin.math.roundToInt + +@Composable +internal fun ParamColumn( + content: @Composable () -> Unit +) { + ProvideContainerDefaults( + color = MaterialTheme.colorScheme.surface + ) { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + content() + } + } +} + +@Composable +internal fun FloatParam( + value: Float, + title: String, + range: ClosedFloatingPointRange, + onValueChange: (Float) -> Unit, + roundTo: Int = 2, + shape: Shape = ShapeDefaults.center +) { + EnhancedSliderItem( + value = value, + title = title, + valueRange = range, + internalStateTransformation = { + it.roundTo(roundTo) + }, + onValueChange = onValueChange, + shape = shape + ) +} + +@Composable +internal fun IntParam( + value: Int, + title: String, + range: ClosedFloatingPointRange, + onValueChange: (Int) -> Unit, + shape: Shape = ShapeDefaults.center +) { + EnhancedSliderItem( + value = value, + title = title, + valueRange = range, + internalStateTransformation = { + it.roundToInt() + }, + onValueChange = { + onValueChange(it.roundToInt()) + }, + shape = shape + ) +} + +@Composable +internal fun ColorParam( + title: String, + value: Color, + onValueChange: (Color) -> Unit, + shape: Shape = ShapeDefaults.center +) { + ColorRowSelector( + value = value, + onValueChange = onValueChange, + title = title, + modifier = Modifier.container( + shape = shape + ) + ) +} + +internal fun TextureFilterType.titleRes(): Int = when (this) { + TextureFilterType.BrushedMetal -> R.string.texture_brushed_metal + TextureFilterType.Caustics -> R.string.texture_caustics + TextureFilterType.Cellular -> R.string.texture_cellular + TextureFilterType.Check -> R.string.texture_checkerboard + TextureFilterType.FBM -> R.string.texture_fbm + TextureFilterType.Marble -> R.string.texture_marble + TextureFilterType.Plasma -> R.string.texture_plasma + TextureFilterType.Quilt -> R.string.texture_quilt + TextureFilterType.Wood -> R.string.texture_wood + TextureFilterType.Brick -> R.string.texture_brick + TextureFilterType.Camouflage -> R.string.texture_camouflage + TextureFilterType.Cell -> R.string.texture_cell + TextureFilterType.Cloud -> R.string.texture_cloud + TextureFilterType.Crack -> R.string.texture_crack + TextureFilterType.Fabric -> R.string.texture_fabric + TextureFilterType.Foliage -> R.string.texture_foliage + TextureFilterType.Honeycomb -> R.string.texture_honeycomb + TextureFilterType.Ice -> R.string.texture_ice + TextureFilterType.Lava -> R.string.texture_lava + TextureFilterType.Nebula -> R.string.texture_nebula + TextureFilterType.Paper -> R.string.texture_paper + TextureFilterType.Rust -> R.string.texture_rust + TextureFilterType.Sand -> R.string.texture_sand + TextureFilterType.Smoke -> R.string.texture_smoke + TextureFilterType.Stone -> R.string.texture_stone + TextureFilterType.Terrain -> R.string.texture_terrain + TextureFilterType.Topography -> R.string.texture_topography + TextureFilterType.WaterRipple -> R.string.texture_water_ripple + TextureFilterType.AdvancedWood -> R.string.texture_advanced_wood + TextureFilterType.Grass -> R.string.texture_grass + TextureFilterType.Dirt -> R.string.texture_dirt + TextureFilterType.Leather -> R.string.texture_leather + TextureFilterType.Concrete -> R.string.texture_concrete + TextureFilterType.Asphalt -> R.string.texture_asphalt + TextureFilterType.Moss -> R.string.texture_moss + TextureFilterType.Fire -> R.string.texture_fire + TextureFilterType.Aurora -> R.string.texture_aurora + TextureFilterType.OilSlick -> R.string.texture_oil_slick + TextureFilterType.Watercolor -> R.string.texture_watercolor + TextureFilterType.AbstractFlow -> R.string.texture_abstract_flow + TextureFilterType.Opal -> R.string.texture_opal + TextureFilterType.DamascusSteel -> R.string.texture_damascus_steel + TextureFilterType.Lightning -> R.string.texture_lightning + TextureFilterType.Velvet -> R.string.texture_velvet + TextureFilterType.InkMarbling -> R.string.texture_ink_marbling + TextureFilterType.HolographicFoil -> R.string.texture_holographic_foil + TextureFilterType.Bioluminescence -> R.string.texture_bioluminescence + TextureFilterType.CosmicVortex -> R.string.texture_cosmic_vortex + TextureFilterType.LavaLamp -> R.string.texture_lava_lamp + TextureFilterType.EventHorizon -> R.string.texture_event_horizon + TextureFilterType.FractalBloom -> R.string.texture_fractal_bloom + TextureFilterType.ChromaticTunnel -> R.string.texture_chromatic_tunnel + TextureFilterType.EclipseCorona -> R.string.texture_eclipse_corona + TextureFilterType.StrangeAttractor -> R.string.texture_strange_attractor + TextureFilterType.FerrofluidCrown -> R.string.texture_ferrofluid_crown + TextureFilterType.Supernova -> R.string.texture_supernova + TextureFilterType.Iris -> R.string.texture_iris + TextureFilterType.PeacockFeather -> R.string.texture_peacock_feather + TextureFilterType.NautilusShell -> R.string.texture_nautilus_shell + TextureFilterType.RingedPlanet -> R.string.texture_ringed_planet +} + +internal fun CellularGridType.titleRes(): Int = when (this) { + CellularGridType.Random -> R.string.grid_random + CellularGridType.Square -> R.string.grid_square + CellularGridType.Hexagonal -> R.string.grid_hexagonal + CellularGridType.Octagonal -> R.string.grid_octagonal + CellularGridType.Triangular -> R.string.grid_triangular +} \ No newline at end of file diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/TextureParamsSelection.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/TextureParamsSelection.kt new file mode 100644 index 0000000..f189695 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/TextureParamsSelection.kt @@ -0,0 +1,200 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.presentation.components + +import androidx.compose.animation.AnimatedContent +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.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.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.core.graphics.createBitmap +import coil3.request.ImageRequest +import coil3.request.transformations +import coil3.transform.Transformation +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Brick +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.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.text.TitleItem +import com.t8rin.imagetoolbox.core.utils.appContext +import com.t8rin.imagetoolbox.texture_generation.domain.model.TextureFilterType +import com.t8rin.imagetoolbox.texture_generation.domain.model.TextureParams +import com.t8rin.imagetoolbox.texture_generation.domain.model.withDefaultsFor + +@Composable +fun TextureParamsSelection( + value: TextureParams, + onValueChange: (TextureParams) -> Unit, + previewProvider: (TextureParams) -> Transformation +) { + Column( + modifier = Modifier.container( + shape = ShapeDefaults.large, + resultPadding = 12.dp + ), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + TitleItem( + text = stringResource(R.string.params), + icon = Icons.Rounded.Build, + modifier = Modifier.padding(bottom = 8.dp) + ) + Column( + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + val empty = remember { createBitmap(1, 1) } + + DataSelector( + value = value.textureFilterType, + onValueChange = { + onValueChange(value.withDefaultsFor(it)) + }, + entries = TextureFilterType.entries, + title = stringResource(R.string.texture_type), + titleIcon = Icons.TwoTone.Brick, + badgeContent = { + Text(TextureFilterType.entries.size.toString()) + }, + itemContentText = { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Picture( + model = remember(empty, it) { + ImageRequest.Builder(appContext) + .data(empty) + .memoryCacheKey(it.name) + .diskCacheKey(it.name) + .size(128, 128) + .transformations(listOf(previewProvider(value.withDefaultsFor(it)))) + .build() + }, + modifier = Modifier + .size(28.dp) + .offset(x = (-2).dp), + shape = ShapeDefaults.mini + ) + Spacer(Modifier.width(4.dp)) + Text(stringResource(it.titleRes())) + } + + null + }, + chipHeight = 46.dp, + minSpanCount = 2, + spanCount = 4, + containerColor = MaterialTheme.colorScheme.surface, + shape = ShapeDefaults.default + ) + + AnimatedContent( + targetState = value.textureFilterType, + modifier = Modifier.fillMaxWidth() + ) { textureFilterType -> + when (textureFilterType) { + TextureFilterType.BrushedMetal -> { + BrushedMetalParams( + value = value, + onValueChange = onValueChange + ) + } + + TextureFilterType.Caustics -> { + CausticsParams( + value = value, + onValueChange = onValueChange + ) + } + + TextureFilterType.Cellular -> { + CellularParams( + value = value, + onValueChange = onValueChange + ) + } + + TextureFilterType.Check -> { + CheckParams( + value = value, + onValueChange = onValueChange + ) + } + + TextureFilterType.FBM -> { + FbmParams( + value = value, + onValueChange = onValueChange + ) + } + + TextureFilterType.Marble -> { + MarbleParams( + value = value, + onValueChange = onValueChange + ) + } + + TextureFilterType.Plasma -> { + PlasmaParams( + value = value, + onValueChange = onValueChange + ) + } + + TextureFilterType.Quilt -> { + QuiltParams( + value = value, + onValueChange = onValueChange + ) + } + + TextureFilterType.Wood -> { + WoodParams( + value = value, + onValueChange = onValueChange + ) + } + + else -> { + FastNoiseParams( + value = value, + onValueChange = onValueChange + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/WoodParams.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/WoodParams.kt new file mode 100644 index 0000000..d30ad25 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/components/WoodParams.kt @@ -0,0 +1,77 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_generation.presentation.components + +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.texture_generation.domain.model.TextureParams + +@Composable +internal fun WoodParams( + value: TextureParams, + onValueChange: (TextureParams) -> Unit +) { + ParamColumn { + FloatParam( + value = value.rings, + title = stringResource(R.string.rings), + range = 0f..5f, + onValueChange = { onValueChange(value.copy(rings = it)) }, + shape = ShapeDefaults.top + ) + FloatParam( + value = value.turbulence, + title = stringResource(R.string.turbulence), + range = 0f..10f, + onValueChange = { onValueChange(value.copy(turbulence = it)) } + ) + FloatParam( + value = value.gain, + title = stringResource(R.string.gain), + range = 0f..1f, + onValueChange = { onValueChange(value.copy(gain = it)) } + ) + FloatParam( + value = value.bias, + title = stringResource(R.string.bias), + range = 0f..1f, + onValueChange = { onValueChange(value.copy(bias = it)) } + ) + FloatParam( + value = value.scale, + title = stringResource(R.string.scale), + range = 1f..512f, + onValueChange = { onValueChange(value.copy(scale = it)) } + ) + FloatParam( + value = value.stretch, + title = stringResource(R.string.stretch), + range = 0.1f..20f, + onValueChange = { onValueChange(value.copy(stretch = it)) } + ) + FloatParam( + value = value.angle, + title = stringResource(R.string.angle), + range = -360f..360f, + onValueChange = { onValueChange(value.copy(angle = it)) }, + shape = ShapeDefaults.bottom + ) + } +} \ No newline at end of file diff --git a/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/screenLogic/TextureGenerationComponent.kt b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/screenLogic/TextureGenerationComponent.kt new file mode 100644 index 0000000..bddf4f9 --- /dev/null +++ b/feature/texture-generation/src/main/java/com/t8rin/imagetoolbox/texture_generation/presentation/screenLogic/TextureGenerationComponent.kt @@ -0,0 +1,330 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.texture_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 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.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.ColorModel +import com.t8rin.imagetoolbox.core.domain.model.IntegerSize +import com.t8rin.imagetoolbox.core.domain.model.flexibleResize +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.transformation.GenericTransformation +import com.t8rin.imagetoolbox.core.domain.utils.smartJob +import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager +import com.t8rin.imagetoolbox.core.ui.utils.BaseHistoryComponent +import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost +import com.t8rin.imagetoolbox.core.ui.utils.helper.toCoil +import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen +import com.t8rin.imagetoolbox.core.ui.utils.state.update +import com.t8rin.imagetoolbox.texture_generation.domain.TextureGenerator +import com.t8rin.imagetoolbox.texture_generation.domain.model.TextureParams +import com.t8rin.imagetoolbox.texture_generation.presentation.screenLogic.TextureGenerationComponent.HistorySnapshot +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.Job + +class TextureGenerationComponent @AssistedInject internal constructor( + @Assisted componentContext: ComponentContext, + @Assisted val onGoBack: () -> Unit, + @Assisted val onNavigate: (Screen) -> Unit, + dispatchersHolder: DispatchersHolder, + private val textureGenerator: TextureGenerator, + private val fileController: FileController, + private val shareProvider: ImageShareProvider, + private val imageCompressor: ImageCompressor, + private val imageScaler: ImageScaler, + private val settingsManager: SettingsManager, +) : BaseHistoryComponent( + dispatchersHolder = dispatchersHolder, + componentContext = componentContext +) { + + private val _previewBitmap: MutableState = mutableStateOf(null) + val previewBitmap: Bitmap? by _previewBitmap + + private val _textureParams: MutableState = mutableStateOf(TextureParams.Default) + val textureParams: TextureParams by _textureParams + + private val _textureSize: MutableState = mutableStateOf(IntegerSize(1000, 1000)) + val textureSize: IntegerSize by _textureSize + + private val _imageFormat: MutableState = mutableStateOf(ImageFormat.Png.Lossless) + 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 + + init { + resetHistory() + updatePreview() + } + + private var savingJob: Job? by smartJob { + _isSaving.update { false } + } + + fun saveTexture( + oneTimeSaveLocationUri: String? + ) { + savingJob = trackProgress { + _isSaving.update { true } + textureGenerator.generateTexture( + width = textureSize.width, + height = textureSize.height, + textureParams = textureParams, + 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 cacheCurrentTexture(onComplete: (Uri) -> Unit) { + savingJob = trackProgress { + _isSaving.update { true } + textureGenerator.generateTexture( + width = textureSize.width, + height = textureSize.height, + textureParams = textureParams + )?.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 shareTexture() { + cacheCurrentTexture( + onComplete = { uri -> + componentScope.launch { + shareProvider.shareUri( + uri = uri.toString(), + onComplete = AppToastHost::showConfetti + ) + } + } + ) + } + + fun cancelSaving() { + savingJob?.cancel() + savingJob = null + _isSaving.update { false } + } + + fun setImageFormat(imageFormat: ImageFormat) { + if (_imageFormat.value != imageFormat) { + if (pendingHistoryMode != PendingHistoryMode.FormatChange) { + finalizePendingHistoryTransaction() + } + beginPendingHistoryTransaction( + mode = PendingHistoryMode.FormatChange, + commitDelayMillis = formatHistoryTransactionDebounce + ) + _imageFormat.update { imageFormat } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setQuality(quality: Quality) { + if (_quality.value != quality) { + beginPendingHistoryTransaction() + _quality.update { quality } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun updateParams(params: TextureParams) { + if (_textureParams.value != params) { + beginPendingHistoryTransaction() + _textureParams.update( + onValueChanged = ::updatePreview, + transform = { params } + ) + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setTextureWidth(width: Int) { + val coercedWidth = width.coerceAtMost(8192) + if (_textureSize.value.width != coercedWidth) { + beginPendingHistoryTransaction() + _textureSize.update( + onValueChanged = ::updatePreview, + transform = { it.copy(width = coercedWidth) } + ) + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setTextureHeight(height: Int) { + val coercedHeight = height.coerceAtMost(8192) + if (_textureSize.value.height != coercedHeight) { + beginPendingHistoryTransaction() + _textureSize.update( + onValueChanged = ::updatePreview, + transform = { it.copy(height = coercedHeight) } + ) + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun getTextureTransformation(targetParams: TextureParams): Transformation = + GenericTransformation { _, size -> + textureGenerator.generateTexture( + width = size.width, + height = size.height, + textureParams = targetParams, + onFailure = {} + )!! + }.toCoil() + + private fun updatePreview() { + componentScope.launch { + _isImageLoading.update { true } + _previewBitmap.update { null } + debouncedImageCalculation { + val previewSize = if (textureSize.width > 512 || textureSize.height > 512) { + textureSize.flexibleResize(512, 512) + } else { + textureSize + } + + textureGenerator.generateTexture( + width = previewSize.width, + height = previewSize.height, + textureParams = textureParams + )?.let { + imageScaler.scaleImage( + image = it, + width = 512, + height = 512, + resizeType = ResizeType.Flexible + ) + }.also { bitmap -> + _previewBitmap.update { bitmap } + _isImageLoading.update { false } + } + } + } + } + + fun getFormatForFilenameSelection(): ImageFormat = imageFormat + + override fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( + textureParams = textureParams, + textureSize = textureSize, + imageFormat = imageFormat, + quality = quality, + backgroundColorForNoAlphaFormats = settingsManager + .settingsState + .value + .backgroundForNoAlphaImageFormats + ) + + override fun applyHistorySnapshot(snapshot: HistorySnapshot) { + _textureParams.update { snapshot.textureParams } + _textureSize.update { snapshot.textureSize } + _imageFormat.update { snapshot.imageFormat } + _quality.update { snapshot.quality } + restoreBackgroundColorForNoAlphaFormats( + settingsManager = settingsManager, + backgroundColorForNoAlphaFormats = snapshot.backgroundColorForNoAlphaFormats + ) + updatePreview() + } + + data class HistorySnapshot( + val textureParams: TextureParams = TextureParams.Default, + val textureSize: IntegerSize = IntegerSize(1000, 1000), + val imageFormat: ImageFormat = ImageFormat.Default, + val quality: Quality = Quality.Base(100), + val backgroundColorForNoAlphaFormats: ColorModel = ColorModel(-0x1000000) + ) + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): TextureGenerationComponent + } + +} diff --git a/feature/usage-statistics/.gitignore b/feature/usage-statistics/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/usage-statistics/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/usage-statistics/build.gradle.kts b/feature/usage-statistics/build.gradle.kts new file mode 100644 index 0000000..f21512b --- /dev/null +++ b/feature/usage-statistics/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.usage_statistics" \ No newline at end of file diff --git a/feature/usage-statistics/src/main/AndroidManifest.xml b/feature/usage-statistics/src/main/AndroidManifest.xml new file mode 100644 index 0000000..063ac92 --- /dev/null +++ b/feature/usage-statistics/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + \ No newline at end of file diff --git a/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/UsageStatisticsContent.kt b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/UsageStatisticsContent.kt new file mode 100644 index 0000000..192e7d5 --- /dev/null +++ b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/UsageStatisticsContent.kt @@ -0,0 +1,132 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.usage_statistics.presentation + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.plus +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.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 androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowBack +import com.t8rin.imagetoolbox.core.resources.icons.DeleteSweep +import com.t8rin.imagetoolbox.core.resources.icons.Info +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.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.usage_statistics.presentation.components.UsageStatisticsContentImpl +import com.t8rin.imagetoolbox.feature.usage_statistics.presentation.components.UsageStatisticsInfoDialog +import com.t8rin.imagetoolbox.feature.usage_statistics.presentation.screenLogic.UsageStatisticsComponent + +@Composable +fun UsageStatisticsContent( + component: UsageStatisticsComponent +) { + val state by component.state.collectAsStateWithLifecycle() + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + var showInfoDialog by rememberSaveable { + mutableStateOf(false) + } + var showResetDialog by rememberSaveable { + mutableStateOf(false) + } + + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { + EnhancedTopAppBar( + title = { + Text( + text = stringResource(R.string.usage_statistics), + modifier = Modifier.marquee() + ) + }, + navigationIcon = { + EnhancedIconButton(onClick = component.onGoBack) { + Icon( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = null + ) + } + }, + type = EnhancedTopAppBarType.Large, + scrollBehavior = scrollBehavior, + actions = { + TopAppBarEmoji() + } + ) + }, + floatingActionButton = { + EnhancedFloatingActionButton( + onClick = { + showInfoDialog = true + } + ) { + Icon( + imageVector = Icons.Outlined.Info, + contentDescription = null + ) + } + } + ) { contentPadding -> + UsageStatisticsContentImpl( + state = state, + contentPadding = contentPadding + PaddingValues(bottom = 80.dp), + onNavigate = component.onNavigate, + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection) + ) + } + + UsageStatisticsInfoDialog( + visible = showInfoDialog, + onDismiss = { showInfoDialog = false }, + onWantReset = { + showResetDialog = true + } + ) + + ResetDialog( + visible = showResetDialog, + onDismiss = { showResetDialog = false }, + onReset = { + showInfoDialog = false + showResetDialog = false + component.resetUsageStatistics() + }, + title = stringResource(R.string.reset_usage_statistics), + text = stringResource(R.string.reset_usage_statistics_sub), + icon = Icons.Outlined.DeleteSweep + ) +} \ No newline at end of file diff --git a/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/ToolUsageStatisticItem.kt b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/ToolUsageStatisticItem.kt new file mode 100644 index 0000000..439ffc4 --- /dev/null +++ b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/ToolUsageStatisticItem.kt @@ -0,0 +1,144 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.usage_statistics.presentation.components + +import android.icu.text.SimpleDateFormat +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.LinearWavyProgressIndicator +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.platform.LocalLocale +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.EnhancedBadge +import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload +import java.util.Calendar + +@Composable +internal fun ToolUsageStatisticItem( + item: UiToolUsageStatistic, + progress: Float, + shape: Shape, + onClick: () -> Unit +) { + PreferenceItemOverload( + modifier = Modifier.fillMaxWidth(), + shape = shape, + title = stringResource(item.screen.title), + subtitle = stringResource( + R.string.tool_usage_subtitle, + item.openCount, + item.lastOpenedTimestamp.asReadableDate() + ), + onClick = onClick, + startIcon = { + item.screen.icon?.let { + Icon( + imageVector = it, + contentDescription = null + ) + } + }, + badgeAlignment = Alignment.CenterVertically, + badge = { + EnhancedBadge( + modifier = Modifier.padding(start = 8.dp), + containerColor = MaterialTheme.colorScheme.secondary + ) { + Text( + text = "${(progress * 100).toInt()}%" + ) + } + }, + placeBottomContentInside = true, + bottomContent = { + LinearWavyProgressIndicator( + progress = { progress }, + modifier = Modifier + .padding( + top = if (progress >= 1f) 8.dp else 12.dp + ) + .fillMaxWidth(), + amplitude = { + if (it >= 0.05f) 0.8f else 0f + } + ) + } + ) +} + +@Composable +internal fun Long?.asReadableDate(): String { + val millis = this ?: return stringResource(R.string.no_data) + + val today = stringResource(R.string.header_today) + val yesterday = stringResource(R.string.header_yesterday) + + val locale = LocalLocale.current.platformLocale + + val date = remember(millis) { + Calendar.getInstance().apply { + timeInMillis = millis + } + } + + val now = remember { + Calendar.getInstance() + } + + val yesterdayCalendar = remember { + Calendar.getInstance().apply { + add(Calendar.DAY_OF_YEAR, -1) + } + } + + fun Calendar.isSameDay(other: Calendar): Boolean { + return get(Calendar.YEAR) == other.get(Calendar.YEAR) && + get(Calendar.DAY_OF_YEAR) == other.get(Calendar.DAY_OF_YEAR) + } + + val time = remember(millis, locale) { + SimpleDateFormat("HH:mm", locale).format(date.time) + } + + return when { + date.isSameDay(now) -> "$today $time" + + date.isSameDay(yesterdayCalendar) -> "$yesterday $time" + + date.get(Calendar.YEAR) == now.get(Calendar.YEAR) -> { + remember(millis, locale) { + SimpleDateFormat("d MMMM, HH:mm", locale).format(date.time) + } + } + + else -> { + remember(millis, locale) { + SimpleDateFormat("d MMMM yyyy, HH:mm", locale).format(date.time) + } + } + } +} \ No newline at end of file diff --git a/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/UiToolUsageStatistic.kt b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/UiToolUsageStatistic.kt new file mode 100644 index 0000000..1c4a5e6 --- /dev/null +++ b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/UiToolUsageStatistic.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.usage_statistics.presentation.components + +import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen + +data class UiToolUsageStatistic( + val screen: Screen, + val openCount: Int, + val lastOpenedTimestamp: Long +) \ No newline at end of file diff --git a/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/UsageStatisticSummaryItem.kt b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/UsageStatisticSummaryItem.kt new file mode 100644 index 0000000..84347c3 --- /dev/null +++ b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/UsageStatisticSummaryItem.kt @@ -0,0 +1,109 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.usage_statistics.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.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +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.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Save +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.ImageToolboxThemeForPreview +import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape +import com.t8rin.imagetoolbox.core.ui.widget.modifier.container + +@Composable +internal fun UsageStatisticSummaryItem( + icon: ImageVector, + title: String, + value: String, + modifier: Modifier +) { + val strength = LocalSettingsState.current.shapesType.strength + Row( + modifier = modifier + .widthIn(min = 172.dp) + .container( + shape = remember(strength) { + AutoCornersShape( + size = 20.dp, + shapesType = ShapeType.Wavy(strength) + ) + }, + resultPadding = 12.dp, + color = MaterialTheme.colorScheme.tertiaryContainer.copy(0.75f) + ), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.onTertiaryContainer + ) + Spacer(Modifier.width(10.dp)) + Column( + modifier = Modifier.weight(1f, false) + ) { + Text( + text = if (value.isBlank() || value == "0" || value == "0.0") { + stringResource(R.string.not_specified) + } else { + value + }, + maxLines = 1, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onTertiaryContainer + ) + Text( + text = title, + maxLines = 1, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onTertiaryContainer.copy(0.65f) + ) + } + } +} + +@Composable +@Preview +private fun Preview() = ImageToolboxThemeForPreview(true) { + UsageStatisticSummaryItem( + icon = Icons.Rounded.Save, + title = "Test card", + value = "1231", + modifier = Modifier.padding(20.dp) + ) +} \ No newline at end of file diff --git a/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/UsageStatisticsContentImpl.kt b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/UsageStatisticsContentImpl.kt new file mode 100644 index 0000000..5aba43f --- /dev/null +++ b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/UsageStatisticsContentImpl.kt @@ -0,0 +1,275 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.usage_statistics.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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.plus +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.runtime.Composable +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.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Crown +import com.t8rin.imagetoolbox.core.resources.icons.Database +import com.t8rin.imagetoolbox.core.resources.icons.DateRange +import com.t8rin.imagetoolbox.core.resources.icons.FinanceMode +import com.t8rin.imagetoolbox.core.resources.icons.Interests +import com.t8rin.imagetoolbox.core.resources.icons.LocalFireDepartment +import com.t8rin.imagetoolbox.core.resources.icons.Save +import com.t8rin.imagetoolbox.core.resources.icons.ServiceToolbox +import com.t8rin.imagetoolbox.core.resources.icons.TouchApp +import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview +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.utils.navigation.Screen +import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalWindowSizeClass +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 java.util.Calendar +import kotlin.random.Random +import androidx.compose.foundation.lazy.staggeredgrid.itemsIndexed as staggeredItemsIndexed + +@Composable +internal fun UsageStatisticsContentImpl( + state: UsageStatisticsState, + onNavigate: (Screen) -> Unit, + contentPadding: PaddingValues = PaddingValues(), + modifier: Modifier = Modifier +) { + val statistics = state.tools + val widthSizeClass = LocalWindowSizeClass.current.widthSizeClass + + val isPortrait by isPortraitOrientationAsState() + val useGrid = widthSizeClass >= WindowWidthSizeClass.Medium || !isPortrait + val padding = contentPadding + PaddingValues(12.dp) + + val statsCard = @Composable { + FlowRow( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 4.dp), + horizontalArrangement = Arrangement.spacedBy(3.dp), + verticalArrangement = Arrangement.spacedBy(3.dp) + ) { + UsageStatisticSummaryItem( + icon = Icons.Rounded.TouchApp, + title = stringResource(R.string.app_opens), + value = state.appOpenCount.toString(), + modifier = Modifier.weight(1f) + ) + UsageStatisticSummaryItem( + icon = Icons.Rounded.Interests, + title = stringResource(R.string.tool_opens), + value = state.totalToolOpens.toString(), + modifier = Modifier.weight(1f) + ) + UsageStatisticSummaryItem( + icon = Icons.Rounded.ServiceToolbox, + title = stringResource(R.string.tools_used), + value = statistics.size.toString(), + modifier = Modifier.weight(1f) + ) + UsageStatisticSummaryItem( + icon = Icons.Rounded.Save, + title = stringResource(R.string.successful_saves), + value = state.successfulSavesCount.toString(), + modifier = Modifier.weight(1f) + ) + UsageStatisticSummaryItem( + icon = Icons.Rounded.LocalFireDepartment, + title = stringResource(R.string.activity_streak), + value = state.activityStreak.toString(), + modifier = Modifier.weight(1f) + ) + UsageStatisticSummaryItem( + icon = Icons.Rounded.Database, + title = stringResource(R.string.data_saved), + value = rememberHumanFileSize(state.savedBytes), + modifier = Modifier.weight(1f) + ) + UsageStatisticSummaryItem( + icon = Icons.Rounded.Crown, + title = stringResource(R.string.top_format), + value = state.topSavedFormat, + modifier = Modifier.weight(1f) + ) + UsageStatisticSummaryItem( + icon = Icons.Rounded.DateRange, + title = stringResource(R.string.last_tool_opened), + value = state.lastOpened.asReadableDate(), + modifier = Modifier.weight(1f) + ) + } + } + + if (useGrid) { + LazyVerticalStaggeredGrid( + modifier = modifier.fillMaxSize(), + contentPadding = padding, + columns = StaggeredGridCells.Adaptive(400.dp), + verticalItemSpacing = 4.dp, + horizontalArrangement = Arrangement.spacedBy(4.dp), + flingBehavior = enhancedFlingBehavior() + ) { + item( + span = StaggeredGridItemSpan.FullLine + ) { + statsCard() + } + + if (statistics.isEmpty()) { + item( + span = StaggeredGridItemSpan.FullLine + ) { + PreferenceItem( + modifier = Modifier.fillMaxWidth(), + shape = ShapeDefaults.default, + title = stringResource(R.string.no_usage_statistics), + subtitle = stringResource(R.string.no_usage_statistics_sub), + startIcon = Icons.Outlined.FinanceMode, + ) + } + } else { + staggeredItemsIndexed( + items = statistics, + key = { _, item -> item.screen.id } + ) { _, item -> + ToolUsageStatisticItem( + item = item, + progress = item.openCount / state.maxOpenCount.toFloat(), + shape = ShapeDefaults.default, + onClick = { onNavigate(item.screen) } + ) + } + } + } + } else { + LazyColumn( + modifier = modifier.fillMaxSize(), + contentPadding = padding, + verticalArrangement = Arrangement.spacedBy(4.dp), + flingBehavior = enhancedFlingBehavior() + ) { + item { + statsCard() + } + + if (statistics.isEmpty()) { + item { + PreferenceItem( + modifier = Modifier.fillMaxWidth(), + shape = ShapeDefaults.default, + title = stringResource(R.string.no_usage_statistics), + subtitle = stringResource(R.string.no_usage_statistics_sub), + startIcon = Icons.Outlined.FinanceMode, + ) + } + } else { + itemsIndexed( + items = statistics, + key = { _, item -> item.screen.id } + ) { index, item -> + ToolUsageStatisticItem( + item = item, + progress = item.openCount / state.maxOpenCount.toFloat(), + shape = ShapeDefaults.byIndex(index, statistics.size), + onClick = { onNavigate(item.screen) } + ) + } + } + } + } +} + +@Preview +@Composable +private fun Preview() = ImageToolboxThemeForPreview(true, keyColor = Color.Green) { + UsageStatisticsContentImpl( + onNavigate = {}, + state = remember { + UsageStatisticsState( + appOpenCount = 840, + successfulSavesCount = 320, + activityStreak = 12, + savedBytes = 128_400_000, + topSavedFormat = "JPG", + tools = Screen.entries.map { screen -> + UiToolUsageStatistic( + screen = screen, + openCount = Random.nextInt(1, 100), + lastOpenedTimestamp = 0 + ) + }.sortedByDescending { it.openCount }.mapIndexed { index, item -> + item.copy( + lastOpenedTimestamp = when (index % 5) { + 1 -> Calendar.getInstance().apply { + timeInMillis = System.currentTimeMillis() + add(Calendar.DAY_OF_YEAR, -1) + }.timeInMillis + + 2 -> Calendar.getInstance().apply { + timeInMillis = System.currentTimeMillis() + add(Calendar.DAY_OF_YEAR, -7) + }.timeInMillis + + 3 -> Calendar.getInstance().apply { + timeInMillis = System.currentTimeMillis() + set(Calendar.YEAR, get(Calendar.YEAR) - 1) + set(Calendar.MONTH, Calendar.MAY) + set(Calendar.DAY_OF_MONTH, 4) + }.timeInMillis + + else -> System.currentTimeMillis() + } + ) + } + ) + } + ) +} + +@Preview +@Composable +private fun Preview1() = ImageToolboxThemeForPreview(true) { + UsageStatisticsContentImpl( + state = UsageStatisticsState( + appOpenCount = 840, + activityStreak = 3, + savedBytes = 12_000_000, + tools = emptyList() + ), + onNavigate = {} + ) +} \ No newline at end of file diff --git a/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/UsageStatisticsInfoDialog.kt b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/UsageStatisticsInfoDialog.kt new file mode 100644 index 0000000..cd44ec7 --- /dev/null +++ b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/UsageStatisticsInfoDialog.kt @@ -0,0 +1,77 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.usage_statistics.presentation.components + +import androidx.compose.foundation.layout.Row +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.res.stringResource +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.DeleteSweep +import com.t8rin.imagetoolbox.core.resources.icons.FinanceMode +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 UsageStatisticsInfoDialog( + visible: Boolean, + onDismiss: () -> Unit, + onWantReset: () -> Unit +) { + EnhancedAlertDialog( + visible = visible, + onDismissRequest = onDismiss, + confirmButton = { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + EnhancedIconButton( + onClick = onWantReset, + containerColor = MaterialTheme.colorScheme.errorContainer + ) { + Icon( + imageVector = Icons.Outlined.DeleteSweep, + contentDescription = stringResource(R.string.reset_usage_statistics) + ) + } + EnhancedButton( + onClick = onDismiss + ) { + Text(stringResource(R.string.close)) + } + } + }, + icon = { + Icon( + imageVector = Icons.Outlined.FinanceMode, + contentDescription = null + ) + }, + title = { + Text(stringResource(R.string.usage_statistics)) + }, + text = { + Text(stringResource(R.string.usage_statistics_info)) + } + ) +} \ No newline at end of file diff --git a/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/UsageStatisticsState.kt b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/UsageStatisticsState.kt new file mode 100644 index 0000000..773f0d7 --- /dev/null +++ b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/components/UsageStatisticsState.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.usage_statistics.presentation.components + +data class UsageStatisticsState( + val appOpenCount: Int = 0, + val successfulSavesCount: Int = 0, + val activityStreak: Int = 0, + val savedBytes: Long = 0, + val topSavedFormat: String = "", + val tools: List = emptyList() +) { + val totalToolOpens = tools.sumOf { it.openCount } + val lastOpened = tools.maxByOrNull { it.lastOpenedTimestamp }?.lastOpenedTimestamp + val maxOpenCount = tools.maxOfOrNull { it.openCount } ?: 0 +} \ No newline at end of file diff --git a/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/screenLogic/UsageStatisticsComponent.kt b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/screenLogic/UsageStatisticsComponent.kt new file mode 100644 index 0000000..08a1ae5 --- /dev/null +++ b/feature/usage-statistics/src/main/java/com/t8rin/imagetoolbox/feature/usage_statistics/presentation/screenLogic/UsageStatisticsComponent.kt @@ -0,0 +1,100 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package com.t8rin.imagetoolbox.feature.usage_statistics.presentation.screenLogic + +import com.arkivanov.decompose.ComponentContext +import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder +import com.t8rin.imagetoolbox.core.domain.history.AppHistoryRepository +import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat +import com.t8rin.imagetoolbox.core.settings.domain.SettingsProvider +import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent +import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen +import com.t8rin.imagetoolbox.feature.usage_statistics.presentation.components.UiToolUsageStatistic +import com.t8rin.imagetoolbox.feature.usage_statistics.presentation.components.UsageStatisticsState +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn + +class UsageStatisticsComponent @AssistedInject constructor( + @Assisted componentContext: ComponentContext, + @Assisted val onGoBack: () -> Unit, + @Assisted val onNavigate: (Screen) -> Unit, + private val appHistoryRepository: AppHistoryRepository, + settingsProvider: SettingsProvider, + dispatchersHolder: DispatchersHolder +) : BaseComponent(dispatchersHolder, componentContext) { + + val state: StateFlow = combine( + settingsProvider.settingsState, + appHistoryRepository.toolUsageStatistics(), + appHistoryRepository.appUsageStatistics() + ) { settings, history, usageStatistics -> + UsageStatisticsState( + appOpenCount = settings.appOpenCount, + successfulSavesCount = usageStatistics.successfulSavesCount, + activityStreak = usageStatistics.currentActivityStreak, + savedBytes = usageStatistics.savedBytes, + topSavedFormat = usageStatistics.savedFormatCounts + .maxWithOrNull(compareBy> { it.value }.thenBy { it.key }) + ?.key + ?.asSavedFormatLabel().orEmpty(), + tools = history.mapNotNull { item -> + Screen.entries.find { it.id == item.screenId }?.let { screen -> + UiToolUsageStatistic( + screen = screen, + openCount = item.openCount, + lastOpenedTimestamp = item.lastOpenedTimestamp + ) + } + } + ) + }.stateIn( + scope = componentScope, + started = SharingStarted.Eagerly, + initialValue = UsageStatisticsState() + ) + + private fun String.asSavedFormatLabel(): String { + val format = ImageFormat.entries.find { + it.title.equals(this, ignoreCase = true) || + it.extension.equals(this, ignoreCase = true) || + it.mimeType.entry.equals(this, ignoreCase = true) + } + + return format?.title ?: uppercase() + } + + fun resetUsageStatistics() { + componentScope.launch { + appHistoryRepository.resetUsageStatistics() + } + } + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): UsageStatisticsComponent + } +} \ No newline at end of file diff --git a/feature/wallpapers-export/.gitignore b/feature/wallpapers-export/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/wallpapers-export/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/wallpapers-export/build.gradle.kts b/feature/wallpapers-export/build.gradle.kts new file mode 100644 index 0000000..7cad227 --- /dev/null +++ b/feature/wallpapers-export/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/wallpapers-export/src/main/AndroidManifest.xml b/feature/wallpapers-export/src/main/AndroidManifest.xml new file mode 100644 index 0000000..44008a4 --- /dev/null +++ b/feature/wallpapers-export/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/data/AndroidWallpapersProvider.kt b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/data/AndroidWallpapersProvider.kt new file mode 100644 index 0000000..c3d9077 --- /dev/null +++ b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/data/AndroidWallpapersProvider.kt @@ -0,0 +1,131 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.core.utils.makeLog +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 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 = 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) } + } + + 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 + } + +} \ No newline at end of file diff --git a/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/di/WallpapersExportModule.kt b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/di/WallpapersExportModule.kt new file mode 100644 index 0000000..25d2ce6 --- /dev/null +++ b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/di/WallpapersExportModule.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/WallpapersProvider.kt b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/WallpapersProvider.kt new file mode 100644 index 0000000..e34e450 --- /dev/null +++ b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/WallpapersProvider.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/model/Permission.kt b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/model/Permission.kt new file mode 100644 index 0000000..2df66b9 --- /dev/null +++ b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/model/Permission.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/model/Wallpaper.kt b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/model/Wallpaper.kt new file mode 100644 index 0000000..45b43ea --- /dev/null +++ b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/model/Wallpaper.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +) \ No newline at end of file diff --git a/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/model/WallpapersResult.kt b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/model/WallpapersResult.kt new file mode 100644 index 0000000..42200bb --- /dev/null +++ b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/model/WallpapersResult.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} \ No newline at end of file diff --git a/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/WallpapersExportContent.kt b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/WallpapersExportContent.kt new file mode 100644 index 0000000..4865723 --- /dev/null +++ b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/WallpapersExportContent.kt @@ -0,0 +1,121 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) +} \ No newline at end of file diff --git a/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/components/WallpapersActionButtons.kt b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/components/WallpapersActionButtons.kt new file mode 100644 index 0000000..d3c22ee --- /dev/null +++ b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/components/WallpapersActionButtons.kt @@ -0,0 +1,105 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.icons.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 + ) +} \ No newline at end of file diff --git a/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/components/WallpapersControls.kt b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/components/WallpapersControls.kt new file mode 100644 index 0000000..31cc6cc --- /dev/null +++ b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/components/WallpapersControls.kt @@ -0,0 +1,187 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.ArrowCircleRight +import com.t8rin.imagetoolbox.core.resources.icons.FolderOpen +import com.t8rin.imagetoolbox.core.resources.icons.ImageSearch +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.FolderOpen + 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" + } \ No newline at end of file diff --git a/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/components/WallpapersPreview.kt b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/components/WallpapersPreview.kt new file mode 100644 index 0000000..b3f8392 --- /dev/null +++ b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/components/WallpapersPreview.kt @@ -0,0 +1,235 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.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 +import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt +import com.t8rin.imagetoolbox.core.resources.icons.CheckCircle +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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.Rounded.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)) + } + } +} \ No newline at end of file diff --git a/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/screenLogic/WallpapersExportComponent.kt b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/screenLogic/WallpapersExportComponent.kt new file mode 100644 index 0000000..5a5d487 --- /dev/null +++ b/feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/screenLogic/WallpapersExportComponent.kt @@ -0,0 +1,244 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} \ No newline at end of file diff --git a/feature/watermarking/.gitignore b/feature/watermarking/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/watermarking/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/watermarking/build.gradle.kts b/feature/watermarking/build.gradle.kts new file mode 100644 index 0000000..b0a8bef --- /dev/null +++ b/feature/watermarking/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/watermarking/src/main/AndroidManifest.xml b/feature/watermarking/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/watermarking/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/data/AndroidWatermarkApplier.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/data/AndroidWatermarkApplier.kt new file mode 100644 index 0000000..97ce24c --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/data/AndroidWatermarkApplier.kt @@ -0,0 +1,303 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.ImageShareProvider +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.ImageFormat +import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo +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.makeLog +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, + private val shareProvider: ImageShareProvider, + 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 { + val returnValue = suspendCancellableCoroutine { continuation -> + WatermarkDetector + .create(image) + .detect( + object : DetectFinishListener { + override fun onSuccess(returnValue: DetectionReturnValue) = + continuation.resume(returnValue) + + override fun onFailure(message: String?) = continuation.resume(null) + } + ) + } ?: return@runSuspendCatching null + + returnValue.watermarkBitmap?.let { watermark -> + shareProvider.cacheImage( + image = watermark, + imageInfo = ImageInfo( + width = watermark.width, + height = watermark.height, + imageFormat = ImageFormat.Png.Lossless + ) + )?.let(HiddenWatermark::Image) + } ?: returnValue.watermarkString + ?.takeIf { it.isNotEmpty() } + ?.let(HiddenWatermark::Text) + }.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) = (reason to params) + .makeLog("WatermarkBuilder.generateImage") + .run { 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 + ) + } + } + +} \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/di/WatermarkingModule.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/di/WatermarkingModule.kt new file mode 100644 index 0000000..976a704 --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/di/WatermarkingModule.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/domain/HiddenWatermark.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/domain/HiddenWatermark.kt new file mode 100644 index 0000000..aa11919 --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/domain/HiddenWatermark.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 uri: String) : HiddenWatermark + data class Text(val text: String) : HiddenWatermark +} \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/domain/WatermarkApplier.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/domain/WatermarkApplier.kt new file mode 100644 index 0000000..501a7cc --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/domain/WatermarkApplier.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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? + +} \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/domain/WatermarkParams.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/domain/WatermarkParams.kt new file mode 100644 index 0000000..1353cbb --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/domain/WatermarkParams.kt @@ -0,0 +1,215 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 +} \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/WatermarkingContent.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/WatermarkingContent.kt new file mode 100644 index 0000000..db9eda0 --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/WatermarkingContent.kt @@ -0,0 +1,388 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +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 androidx.core.net.toUri +import com.t8rin.imagetoolbox.core.resources.Icons +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.UndoRedoButtons +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.enhanced.enhancedHorizontalScroll +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.image.ImagePager +import com.t8rin.imagetoolbox.core.ui.widget.modifier.detectSwipes +import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges +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) } + var hiddenWatermarkPreviewUri by remember { mutableStateOf(null) } + + 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 = { + val state = rememberScrollState() + Row( + modifier = Modifier + .fadingEdges(state) + .enhancedHorizontalScroll(state), + verticalAlignment = Alignment.CenterVertically + ) { + var editSheetData by remember { + mutableStateOf(listOf()) + } + if (!isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + 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 + } + ) + } + if (isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + } + }, + 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, + isLoading = component.isCheckingHiddenWatermark, + onImageClick = { hiddenWatermarkPreviewUri = it.uri.toUri() } + ) + 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 + } + ) + + ImagePager( + visible = hiddenWatermarkPreviewUri != null, + selectedUri = hiddenWatermarkPreviewUri, + uris = listOfNotNull(hiddenWatermarkPreviewUri), + onNavigate = { + hiddenWatermarkPreviewUri = null + component.onNavigate(it) + }, + onUriSelected = { hiddenWatermarkPreviewUri = it }, + onShare = component::shareUri, + onDismiss = { hiddenWatermarkPreviewUri = null } + ) + + LoadingDialog( + visible = component.isSaving, + done = component.done, + left = component.left, + onCancelLoading = component::cancelSaving + ) + + ExitWithoutSavingDialog( + onExit = component.onGoBack, + onDismiss = { showExitDialog = false }, + visible = showExitDialog + ) +} \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/HiddenWatermarkInfo.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/HiddenWatermarkInfo.kt new file mode 100644 index 0000000..1af8d17 --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/HiddenWatermarkInfo.kt @@ -0,0 +1,181 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.togetherWith +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.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.res.stringResource +import androidx.compose.ui.unit.dp +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt +import com.t8rin.imagetoolbox.core.resources.icons.ContentCopy +import com.t8rin.imagetoolbox.core.resources.icons.Info +import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard +import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedCircularProgressIndicator +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.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?, + isLoading: Boolean, + onImageClick: (HiddenWatermark.Image) -> Unit +) { + AnimatedContent( + targetState = isLoading to hiddenWatermark, + transitionSpec = { fadeIn() togetherWith fadeOut() }, + modifier = Modifier + .fillMaxWidth() + .animateContentSizeNoClip() + ) { (loading, hidden) -> + if (loading) { + HiddenWatermarkLoadingCard() + } else { + hidden?.let { + HiddenWatermarkResultCard( + hiddenWatermark = it, + onImageClick = onImageClick + ) + } + } + } +} + +@Composable +private fun HiddenWatermarkLoadingCard() { + HiddenWatermarkCard( + title = stringResource(R.string.loading), + subtitle = stringResource(R.string.checking_for_hidden_watermarks), + onClick = null, + endIcon = { + EnhancedCircularProgressIndicator( + modifier = Modifier.size(24.dp), + trackColor = MaterialTheme.colorScheme.primary.copy(0.2f), + strokeWidth = 3.dp + ) + } + ) +} + +@Composable +private fun HiddenWatermarkResultCard( + hiddenWatermark: HiddenWatermark, + onImageClick: (HiddenWatermark.Image) -> Unit +) { + HiddenWatermarkCard( + title = stringResource( + when (hiddenWatermark) { + is HiddenWatermark.Text -> R.string.hidden_watermark_text_detected + is HiddenWatermark.Image -> R.string.hidden_watermark_image_detected + } + ), + subtitle = when (hiddenWatermark) { + is HiddenWatermark.Text -> hiddenWatermark.text + is HiddenWatermark.Image -> stringResource(R.string.this_image_was_hidden) + }, + onClick = { + when (hiddenWatermark) { + is HiddenWatermark.Text -> Clipboard.copy(hiddenWatermark.text) + is HiddenWatermark.Image -> onImageClick(hiddenWatermark) + } + }, + endIcon = { + HiddenWatermarkEndIcon(hiddenWatermark) + } + ) +} + +@Composable +private fun HiddenWatermarkCard( + title: String, + subtitle: String, + onClick: (() -> Unit)?, + endIcon: @Composable () -> Unit +) { + PreferenceItemOverload( + title = title, + subtitle = subtitle, + onClick = onClick, + contentColor = MaterialTheme.colorScheme.onSecondaryContainer, + containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.35f), + titleFontStyle = PreferenceItemDefaults.TitleFontStyleSmall, + startIcon = { + Icon( + imageVector = Icons.Outlined.Info, + contentDescription = null + ) + }, + endIcon = endIcon, + modifier = Modifier.fillMaxWidth(), + shape = ShapeDefaults.large + ) +} + +@Composable +private fun HiddenWatermarkEndIcon(hiddenWatermark: HiddenWatermark) { + when (hiddenWatermark) { + is HiddenWatermark.Text -> { + Icon( + imageVector = Icons.Rounded.ContentCopy, + contentDescription = null + ) + } + + is HiddenWatermark.Image -> { + Picture( + model = hiddenWatermark.uri, + 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) + .compositeOverSafe(MaterialTheme.colorScheme.surfaceContainer) + ) + .padding(8.dp) + ) + } + ) + } + } +} \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/WatermarkDataSelector.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/WatermarkDataSelector.kt new file mode 100644 index 0000000..672a537 --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/WatermarkDataSelector.kt @@ -0,0 +1,173 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Info +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)) + }, + endIcon = { + WatermarkPlaceholderInfoButton() + } + ) + } + + 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)) + }, + endIcon = { + WatermarkPlaceholderInfoButton() + } + ) + } + + 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 + ) + } + } + ) + } + } + } + } +} \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/WatermarkParamsSelectionGroup.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/WatermarkParamsSelectionGroup.kt new file mode 100644 index 0000000..515a3ac --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/WatermarkParamsSelectionGroup.kt @@ -0,0 +1,99 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Tune +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.Rounded.Tune + ) + }, + expandableContent = { + Column( + modifier = Modifier.padding( + start = 12.dp, + end = 12.dp, + bottom = 4.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 + ) +} \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/WatermarkPlaceholderInfoButton.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/WatermarkPlaceholderInfoButton.kt new file mode 100644 index 0000000..a237b39 --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/WatermarkPlaceholderInfoButton.kt @@ -0,0 +1,75 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.res.stringResource +import com.t8rin.imagetoolbox.core.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Info +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 +internal fun WatermarkPlaceholderInfoButton() { + var showInfoDialog by rememberSaveable { + mutableStateOf(false) + } + + EnhancedIconButton( + onClick = { + showInfoDialog = true + } + ) { + Icon( + imageVector = Icons.Outlined.Info, + contentDescription = null + ) + } + + EnhancedAlertDialog( + visible = showInfoDialog, + onDismissRequest = { showInfoDialog = false }, + confirmButton = { + EnhancedButton( + onClick = { showInfoDialog = false } + ) { + Text(stringResource(R.string.close)) + } + }, + title = { + Text(stringResource(R.string.watermark_filename_placeholder)) + }, + icon = { + Icon( + imageVector = Icons.Outlined.Info, + contentDescription = null + ) + }, + text = { + Text(stringResource(R.string.watermark_filename_placeholder_sub)) + } + ) +} \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/WatermarkingTypeSelector.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/WatermarkingTypeSelector.kt new file mode 100644 index 0000000..59f0c45 --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/WatermarkingTypeSelector.kt @@ -0,0 +1,72 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + } \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/CommonParamsContent.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/CommonParamsContent.kt new file mode 100644 index 0000000..2aad33b --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/CommonParamsContent.kt @@ -0,0 +1,169 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.Repeat +import com.t8rin.imagetoolbox.core.resources.icons.TextRotationAngleup +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.top, + 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.center, + 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.Rounded.TextRotationAngleup, + title = stringResource(id = R.string.angle), + valueRange = 0f..360f, + internalStateTransformation = Float::roundToInt, + onValueChange = { + onValueChange(params.copy(rotation = it.roundToInt())) + }, + shape = if (params.isRepeated) { + ShapeDefaults.top + } else { + ShapeDefaults.center + }, + 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 = if (params.watermarkingType.isStamp()) { + ShapeDefaults.top + } else { + ShapeDefaults.center + }, + 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.center, + containerColor = MaterialTheme.colorScheme.surface + ) + Spacer(Modifier.height(4.dp)) + BlendingModeSelector( + value = params.overlayMode, + onValueChange = { + onValueChange( + params.copy(overlayMode = it) + ) + }, + shape = ShapeDefaults.center + ) + } + } +} \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/DigitalParamsContent.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/DigitalParamsContent.kt new file mode 100644 index 0000000..6bf06f2 --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/DigitalParamsContent.kt @@ -0,0 +1,100 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.resources.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.DisabledVisible +import com.t8rin.imagetoolbox.core.resources.icons.GraphicEq +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 = if (isInvisible) { + ShapeDefaults.default + } else { + ShapeDefaults.bottom + }, + 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.Outlined.GraphicEq, + onClick = { + onValueChange( + params.copy( + digitalParams = digitalParams?.copy( + isLSB = !digitalParams.isLSB + ) + ) + ) + }, + shape = ShapeDefaults.large, + containerColor = MaterialTheme.colorScheme.surface, + modifier = Modifier.padding(top = 4.dp) + ) + } + } + } +} \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/ImageParamsContent.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/ImageParamsContent.kt new file mode 100644 index 0000000..bf15234 --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/ImageParamsContent.kt @@ -0,0 +1,70 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.center, + containerColor = MaterialTheme.colorScheme.surface + ) + } +} \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/StampParamsContent.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/StampParamsContent.kt new file mode 100644 index 0000000..66e7de3 --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/StampParamsContent.kt @@ -0,0 +1,177 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + ) + ) + ) + ) + }, + shape = ShapeDefaults.center + ) + Spacer(modifier = Modifier.height(4.dp)) + PositionSelector( + value = type.position, + onValueChange = { + onValueChange( + params.copy( + watermarkingType = type.copy( + position = it + ) + ) + ) + }, + selectedItemColor = MaterialTheme.colorScheme.primary, + shape = ShapeDefaults.center + ) + 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.center, + 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.center, + 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.center, + 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.bottom, + color = MaterialTheme.colorScheme.surface + ) + ) + } + } +} \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/TextParamsContent.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/TextParamsContent.kt new file mode 100644 index 0000000..dc0db5a --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/TextParamsContent.kt @@ -0,0 +1,135 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + ) + ) + ) + }, + shape = ShapeDefaults.center + ) + 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.center, + 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.center, + 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.center, + color = MaterialTheme.colorScheme.surface + ) + ) + } + } +} \ No newline at end of file diff --git a/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/screenLogic/WatermarkingComponent.kt b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/screenLogic/WatermarkingComponent.kt new file mode 100644 index 0000000..dc10fd4 --- /dev/null +++ b/feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/screenLogic/WatermarkingComponent.kt @@ -0,0 +1,557 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.model.ColorModel +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.settings.domain.SettingsManager +import com.t8rin.imagetoolbox.core.ui.utils.BaseHistoryComponent +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.feature.watermarking.domain.HiddenWatermark +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.t8rin.imagetoolbox.feature.watermarking.presentation.screenLogic.WatermarkingComponent.HistorySnapshot +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, + private val settingsManager: SettingsManager, + dispatchersHolder: DispatchersHolder +) : BaseHistoryComponent( + dispatchersHolder = dispatchersHolder, + componentContext = 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 val _isCheckingHiddenWatermark = mutableStateOf(false) + val isCheckingHiddenWatermark by _isCheckingHiddenWatermark + + private var hiddenWatermarkJob: Job? by smartJob() + + private fun updateBitmap( + bitmap: Bitmap, + onComplete: () -> Unit = {} + ) { + _currentHiddenWatermark.value = null + _isCheckingHiddenWatermark.value = true + hiddenWatermarkJob = componentScope.launch { + _currentHiddenWatermark.value = watermarkApplier.checkHiddenWatermark(bitmap) + _isCheckingHiddenWatermark.value = false + } + 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.resolveFilenamePlaceholder(data) + ) + } + } + + private fun WatermarkParams.resolveFilenamePlaceholder(data: Any): WatermarkParams { + val text = when (val type = watermarkingType) { + is WatermarkingType.Text -> type.text + is WatermarkingType.Stamp.Text -> type.text + else -> return this + } + if (FILENAME_PLACEHOLDER !in text) return this + + val sourceUri = when (data) { + is Uri -> data + is String -> data.toUri() + else -> selectedUri + } + val filename = sourceUri.filename() + ?.substringBeforeLast('.') + ?.takeIf(String::isNotEmpty) + ?: return this + + return when (val type = watermarkingType) { + is WatermarkingType.Text -> copy( + watermarkingType = type.copy( + text = type.text.replace(FILENAME_PLACEHOLDER, filename) + ) + ) + + is WatermarkingType.Stamp.Text -> copy( + watermarkingType = type.copy( + text = type.text.replace(FILENAME_PLACEHOLDER, filename) + ) + ) + + else -> this + } + } + + fun shareBitmaps() { + _left.value = -1 + savingJob = trackProgress { + _isSaving.value = true + _done.value = 0 + _left.value = uris.size + shareProvider.shareImages( + uris = 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) { + if (_quality.value != quality) { + beginPendingHistoryTransaction() + _quality.update { quality } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setImageFormat(imageFormat: ImageFormat) { + if (_imageFormat.value != imageFormat) { + if (pendingHistoryMode != PendingHistoryMode.FormatChange) { + finalizePendingHistoryTransaction() + } + beginPendingHistoryTransaction( + mode = PendingHistoryMode.FormatChange, + commitDelayMillis = formatHistoryTransactionDebounce + ) + _imageFormat.update { imageFormat } + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun updateWatermarkParams(watermarkParams: WatermarkParams) { + if (_watermarkParams.value != watermarkParams) { + beginPendingHistoryTransaction() + _watermarkParams.update { watermarkParams } + registerChanges() + checkBitmapAndUpdate() + schedulePendingHistoryCommit() + } + } + + 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 + _imageFormat.update { 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, + ) { + clearHistory() + registerChangesCleared() + _uris.update { uris } + uris.firstOrNull()?.let(::updateSelectedUri) + if (uris.isNotEmpty()) { + resetHistory() + } + } + + fun toggleKeepExif(value: Boolean) { + if (_keepExif.value == value) return + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _keepExif.update { value } + commitHistoryFrom(beforeSnapshot) + } + + 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 shareUri(uri: Uri) { + componentScope.launch { + shareProvider.shareUri( + uri = uri.toString(), + onComplete = {} + ) + } + } + + 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 + + override fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( + watermarkParams = watermarkParams, + imageFormat = imageFormat, + quality = quality, + keepExif = keepExif, + backgroundColorForNoAlphaFormats = settingsManager + .settingsState + .value + .backgroundForNoAlphaImageFormats + ) + + override fun applyHistorySnapshot(snapshot: HistorySnapshot) { + _watermarkParams.update { snapshot.watermarkParams } + _imageFormat.update { snapshot.imageFormat } + _quality.update { snapshot.quality } + _keepExif.update { snapshot.keepExif } + restoreBackgroundColorForNoAlphaFormats( + settingsManager = settingsManager, + backgroundColorForNoAlphaFormats = snapshot.backgroundColorForNoAlphaFormats + ) + checkBitmapAndUpdate() + } + + data class HistorySnapshot( + val watermarkParams: WatermarkParams = WatermarkParams.Default, + val imageFormat: ImageFormat = ImageFormat.Default, + val quality: Quality = Quality.Base(), + val keepExif: Boolean = false, + val backgroundColorForNoAlphaFormats: ColorModel = ColorModel(-0x1000000) + ) + + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUris: List?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): WatermarkingComponent + } + + private companion object { + const val FILENAME_PLACEHOLDER = "{filename}" + } +} \ No newline at end of file diff --git a/feature/webp-tools/.gitignore b/feature/webp-tools/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/webp-tools/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/webp-tools/build.gradle.kts b/feature/webp-tools/build.gradle.kts new file mode 100644 index 0000000..bf7024c --- /dev/null +++ b/feature/webp-tools/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/feature/webp-tools/src/main/AndroidManifest.xml b/feature/webp-tools/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/webp-tools/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/data/AndroidWebpConverter.kt b/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/data/AndroidWebpConverter.kt new file mode 100644 index 0000000..a9f8fd6 --- /dev/null +++ b/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/data/AndroidWebpConverter.kt @@ -0,0 +1,144 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + + +} \ No newline at end of file diff --git a/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/di/WebpToolsModule.kt b/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/di/WebpToolsModule.kt new file mode 100644 index 0000000..717041a --- /dev/null +++ b/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/di/WebpToolsModule.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/domain/WebpConverter.kt b/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/domain/WebpConverter.kt new file mode 100644 index 0000000..34e8810 --- /dev/null +++ b/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/domain/WebpConverter.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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? + +} \ No newline at end of file diff --git a/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/domain/WebpParams.kt b/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/domain/WebpParams.kt new file mode 100644 index 0000000..26c7c7c --- /dev/null +++ b/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/domain/WebpParams.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + ) + } + } +} \ No newline at end of file diff --git a/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/presentation/WebpToolsContent.kt b/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/presentation/WebpToolsContent.kt new file mode 100644 index 0000000..9d0846e --- /dev/null +++ b/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/presentation/WebpToolsContent.kt @@ -0,0 +1,424 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 com.t8rin.imagetoolbox.core.resources.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.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.Close +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 + ) +} \ No newline at end of file diff --git a/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/presentation/components/WebpParamsSelector.kt b/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/presentation/components/WebpParamsSelector.kt new file mode 100644 index 0000000..0e9e2b9 --- /dev/null +++ b/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/presentation/components/WebpParamsSelector.kt @@ -0,0 +1,135 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.PhotoSizeSelectLarge +import com.t8rin.imagetoolbox.core.resources.icons.RepeatOne +import com.t8rin.imagetoolbox.core.resources.icons.Timelapse +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.Rounded.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..10_000f, + internalStateTransformation = { it.roundToInt() }, + onValueChange = { + onValueChange( + value.copy( + delay = it.roundToInt() + ) + ) + }, + shape = ShapeDefaults.extraLarge + ) + } +} \ No newline at end of file diff --git a/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/presentation/screenLogic/WebpToolsComponent.kt b/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/presentation/screenLogic/WebpToolsComponent.kt new file mode 100644 index 0000000..70149fa --- /dev/null +++ b/feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/presentation/screenLogic/WebpToolsComponent.kt @@ -0,0 +1,416 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } + +} \ No newline at end of file diff --git a/feature/weight-resize/.gitignore b/feature/weight-resize/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/weight-resize/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/weight-resize/build.gradle.kts b/feature/weight-resize/build.gradle.kts new file mode 100644 index 0000000..80ce63b --- /dev/null +++ b/feature/weight-resize/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/weight-resize/src/main/AndroidManifest.xml b/feature/weight-resize/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/weight-resize/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/data/AndroidWeightImageScaler.kt b/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/data/AndroidWeightImageScaler.kt new file mode 100644 index 0000000..2cba61c --- /dev/null +++ b/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/data/AndroidWeightImageScaler.kt @@ -0,0 +1,119 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) +} \ No newline at end of file diff --git a/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/di/WeightResizeModule.kt b/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/di/WeightResizeModule.kt new file mode 100644 index 0000000..444a948 --- /dev/null +++ b/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/di/WeightResizeModule.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/domain/WeightImageScaler.kt b/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/domain/WeightImageScaler.kt new file mode 100644 index 0000000..43451df --- /dev/null +++ b/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/domain/WeightImageScaler.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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? + +} \ No newline at end of file diff --git a/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/presentation/WeightResizeContent.kt b/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/presentation/WeightResizeContent.kt new file mode 100644 index 0000000..d29bef2 --- /dev/null +++ b/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/presentation/WeightResizeContent.kt @@ -0,0 +1,384 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.rememberScrollState +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.Alignment +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.UndoRedoButtons +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.enhanced.enhancedHorizontalScroll +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.modifier.fadingEdges +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 = { + val state = rememberScrollState() + Row( + modifier = Modifier + .fadingEdges(state) + .enhancedHorizontalScroll(state), + verticalAlignment = Alignment.CenterVertically + ) { + if (!isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + 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 + ) + } + if (isPortrait) { + UndoRedoButtons( + canUndo = component.canUndo, + canRedo = component.canRedo, + onUndo = component::undo, + onRedo = component::redo, + modifier = Modifier.padding(2.dp) + ) + } + } + }, + topAppBarPersistentActions = { + if (component.bitmap == null) { + TopAppBarEmoji() + } + 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 + } + ) + 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 = { 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 + }, + isPrimaryButtonVisible = component.canSave, + actions = { + PanModeButton( + selected = component.handMode, + onClick = component::updateHandMode + ) + 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 + ) + }, + 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 + ) +} diff --git a/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/presentation/components/ImageFormatAlert.kt b/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/presentation/components/ImageFormatAlert.kt new file mode 100644 index 0000000..bb9c96c --- /dev/null +++ b/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/presentation/components/ImageFormatAlert.kt @@ -0,0 +1,68 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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) + ) + } + } +} \ No newline at end of file diff --git a/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/presentation/screenLogic/WeightResizeComponent.kt b/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/presentation/screenLogic/WeightResizeComponent.kt new file mode 100644 index 0000000..eda3a92 --- /dev/null +++ b/feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/presentation/screenLogic/WeightResizeComponent.kt @@ -0,0 +1,602 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.model.ColorModel +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.settings.domain.SettingsManager +import com.t8rin.imagetoolbox.core.ui.utils.BaseHistoryComponent +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 com.t8rin.imagetoolbox.feature.weight_resize.presentation.screenLogic.WeightResizeComponent.HistorySnapshot +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, + private val settingsManager: SettingsManager, + dispatchersHolder: DispatchersHolder +) : BaseHistoryComponent( + dispatchersHolder = dispatchersHolder, + componentContext = 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) { + if (pendingHistoryMode != PendingHistoryMode.FormatChange) { + finalizePendingHistoryTransaction() + } + beginPendingHistoryTransaction( + mode = PendingHistoryMode.FormatChange, + commitDelayMillis = formatHistoryTransactionDebounce + ) + _imageFormat.value = imageFormat + updateImageSize() + registerChanges() + schedulePendingHistoryCommit() + } + } + + fun setUris( + uris: List? + ) { + clearHistory() + registerChangesCleared() + _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) { + if (_keepExif.value == boolean) return + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _keepExif.value = boolean + commitHistoryFrom(beforeSnapshot) + } + + private var savingJob: Job? by smartJob { + _isSaving.update { false } + } + + fun saveBitmaps( + oneTimeSaveLocationUri: String? + ) { + finalizePendingHistoryTransaction() + savingJob = trackProgress { + _isSaving.value = true + val results = mutableListOf() + _done.value = 0 + uris?.forEach { uri -> + runSuspendCatching { + checkNotNull(imageGetter.getImage(uri.toString())) + }.onFailure { + results.add( + SaveResult.Error.Exception(it) + ) + }.onSuccess { (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 + ) + ) + } + } + + _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( + register: Boolean = true + ) { + _canSave.value = + _bitmap.value != null && (_maxBytes.value != 0L && _handMode.value || !_handMode.value && _presetSelected.value != -1) + + if (register) { + registerChanges() + } + } + + fun updateMaxBytes(newBytes: String) { + val b = newBytes.toLongOrNull() ?: 0 + val maxBytes = b * 1024 + if (_maxBytes.value != maxBytes) { + beginPendingHistoryTransaction() + _maxBytes.value = maxBytes + updateCanSave() + schedulePendingHistoryCommit() + } + } + + fun selectPreset(preset: Preset) { + preset.value()?.let { + if (_presetSelected.value != it) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _presetSelected.value = it + updateCanSave(register = false) + commitHistoryFrom(beforeSnapshot) + } + } + } + + fun updateHandMode() { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _handMode.value = !_handMode.value + updateCanSave(register = false) + commitHistoryFrom(beforeSnapshot) + } + + 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 + ) + ) + } + resetHistory() + registerChangesCleared() + _isImageLoading.value = false + } + } + + fun cancelSaving() { + savingJob?.cancel() + savingJob = null + _isSaving.value = false + } + + fun setImageScaleMode(imageScaleMode: ImageScaleMode) { + if (_imageScaleMode.value != imageScaleMode) { + finalizePendingHistoryTransaction() + val beforeSnapshot = currentHistorySnapshot() + _imageScaleMode.update { imageScaleMode } + updateCanSave(register = false) + commitHistoryFrom(beforeSnapshot) + } + } + + 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 + + override fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( + imageScaleMode = imageScaleMode, + presetSelected = presetSelected, + handMode = handMode, + maxBytes = maxBytes, + imageFormat = imageFormat, + keepExif = keepExif, + backgroundColorForNoAlphaFormats = settingsManager + .settingsState + .value + .backgroundForNoAlphaImageFormats + ) + + override fun applyHistorySnapshot(snapshot: HistorySnapshot) { + _imageScaleMode.value = snapshot.imageScaleMode + _presetSelected.value = snapshot.presetSelected + _handMode.value = snapshot.handMode + _maxBytes.value = snapshot.maxBytes + _imageFormat.value = snapshot.imageFormat + _keepExif.value = snapshot.keepExif + restoreBackgroundColorForNoAlphaFormats( + settingsManager = settingsManager, + backgroundColorForNoAlphaFormats = snapshot.backgroundColorForNoAlphaFormats + ) + updateCanSave(register = false) + updateImageSize() + } + + private fun updateImageSize() { + componentScope.launch { + _bitmap.value?.let { + _isImageLoading.value = true + _imageSize.value = imageCompressor.calculateImageSize( + image = it, + imageInfo = ImageInfo(imageFormat = imageFormat) + ) + _isImageLoading.value = false + } + } + } + + data class HistorySnapshot( + val imageScaleMode: ImageScaleMode = ImageScaleMode.Default, + val presetSelected: Int = -1, + val handMode: Boolean = true, + val maxBytes: Long = 0L, + val imageFormat: ImageFormat = ImageFormat.Default, + val keepExif: Boolean = false, + val backgroundColorForNoAlphaFormats: ColorModel = ColorModel(-0x1000000) + ) + + @AssistedFactory + fun interface Factory { + operator fun invoke( + componentContext: ComponentContext, + initialUris: List?, + onGoBack: () -> Unit, + onNavigate: (Screen) -> Unit, + ): WeightResizeComponent + } +} diff --git a/feature/zip/.gitignore b/feature/zip/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/zip/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/zip/build.gradle.kts b/feature/zip/build.gradle.kts new file mode 100644 index 0000000..8b9b02a --- /dev/null +++ b/feature/zip/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/feature/zip/src/main/AndroidManifest.xml b/feature/zip/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0862d94 --- /dev/null +++ b/feature/zip/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/data/AndroidZipManager.kt b/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/data/AndroidZipManager.kt new file mode 100644 index 0000000..4565961 --- /dev/null +++ b/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/data/AndroidZipManager.kt @@ -0,0 +1,60 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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") + } + +} \ No newline at end of file diff --git a/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/di/ZipModule.kt b/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/di/ZipModule.kt new file mode 100644 index 0000000..695960f --- /dev/null +++ b/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/di/ZipModule.kt @@ -0,0 +1,39 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/domain/ZipManager.kt b/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/domain/ZipManager.kt new file mode 100644 index 0000000..6e9ad04 --- /dev/null +++ b/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/domain/ZipManager.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + +} \ No newline at end of file diff --git a/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/presentation/ZipContent.kt b/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/presentation/ZipContent.kt new file mode 100644 index 0000000..fd9fbfe --- /dev/null +++ b/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/presentation/ZipContent.kt @@ -0,0 +1,129 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.FileOpen +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 = component.uris.isNotEmpty(), + 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 + ) + +} \ No newline at end of file diff --git a/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/presentation/components/ZipControls.kt b/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/presentation/components/ZipControls.kt new file mode 100644 index 0000000..ff31804 --- /dev/null +++ b/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/presentation/components/ZipControls.kt @@ -0,0 +1,231 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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.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.Icons +import com.t8rin.imagetoolbox.core.resources.R +import com.t8rin.imagetoolbox.core.resources.icons.CheckCircle +import com.t8rin.imagetoolbox.core.resources.icons.Download +import com.t8rin.imagetoolbox.core.resources.icons.NoteAdd +import com.t8rin.imagetoolbox.core.resources.icons.Share +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.Download, + 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, + addUrisContent = { width -> + Icon( + imageVector = Icons.Rounded.NoteAdd, + contentDescription = stringResource(R.string.add), + modifier = Modifier.size(width / 3f) + ) + }, + ) +} \ No newline at end of file diff --git a/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/presentation/screenLogic/ZipComponent.kt b/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/presentation/screenLogic/ZipComponent.kt new file mode 100644 index 0000000..06895e5 --- /dev/null +++ b/feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/presentation/screenLogic/ZipComponent.kt @@ -0,0 +1,161 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under 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 + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..7b49fb5 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,28 @@ +# +# ImageToolbox is an image editor for android +# Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language 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 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..0abad18 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,223 @@ +[versions] +androidMinSdk = "24" +androidTargetSdk = "37" +androidCompileSdk = "37" +androidCompileSdkExtension = "-" + +versionName = "4.2.0-beta01" +versionCode = "254" + +jvmTarget = "21" + +imageToolboxLibs = "7.3.5" +trickle = "1.9.0" +evaluator = "1.0.0" +quickie = "1.18.1" +fadingEdges = "1.0.4" +composeHighlight = "0.32.0" + +avifCoder = "3.0.0-alpha06" +aire = "0.18.1" +jxlCoder = "2.6.1" +jxlCoderCoil = "2.6.1" +jpegliCoder = "1.0.2" + +tesseract = "4.9.0" +material3 = "1.5.0-alpha23" +composeVersion = "1.12.0-beta02" +dataStore = "1.3.0-alpha09" +appUpdateKtx = "2.1.0" +appUpdate = "2.1.0" +shadowGadgets = "2.5.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.6.2" +decompose = "3.5.0" + +kotlin = "2.4.0" +agp = "9.2.1" +hilt = "2.60.1" +gms = "4.5.0" + +ktor = "3.5.1" +coil = "3.5.0" +dotlottieAndroid = "0.15.0" +appCompat = "1.8.0-alpha01" +androidxCore = "1.19.0" +desugaring = "2.1.5" +activityCompose = "1.13.0" +kotlinxCollectionsImmutable = "0.5.1" +scrollbar = "2.2.0" +reorderable = "3.1.0" +reviewKtx = "2.0.2" +splashScreen = "1.2.0" + +espresso = "3.7.0" +ksp = "2.3.10" + +androidx-test-ext-junit = "1.3.0" +documentfile = "1.1.0" +uiautomator = "2.4.0" +androidxMacroBenchmark = "1.5.0-alpha07" + +material = "1.14.0" +jsoup = "1.22.2" +backdrop = "2.0.0" +capsule = "2.1.3" +squircle-shape = "5.3.0" + +mlkitDocumentScanner = "16.0.0" +moshi = "1.15.2" +bouncycastle = "1.84" +pdfviewer = "1.0.0-alpha19" +fragmentCompose = "1.9.0-alpha02" +aboutlibraries = "15.0.3" +flinger = "2.1.0" +zxingCore = "3.5.4" +pdfbox = "2.0.27.0" + +onnx = "1.27.0" +opencv = "5.0.0.1" + +tiffdecoder = "1.0.5" +materialKolor = "5.0.0-alpha07" +paletteKtx = "1.0.0" +kotlinxSerializationJson = "1.11.0" + +firebaseBom = "34.16.0" +firebaseCrashlyticsGradle = "3.0.7" +junit = "4.13.2" +coilResvg = "1.0.0" + +[libraries] +coil-resvg = { module = "com.hashsequence:coil-resvg", version.ref = "coilResvg" } +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" } +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", 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" } +backdrop = { module = "io.github.kyant0:backdrop", version.ref = "backdrop" } +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" } + +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-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-advancedCrop = { module = "com.github.T8RIN.ImageToolboxLibs:advanced-crop", 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" } +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" } +compose-highlight = { module = "dev.hossain:compose-highlight", version.ref = "composeHighlight" } +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-okhttp", 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" } +dotlottie-android = { module = "com.github.LottieFiles:dotlottie-android", version.ref = "dotlottieAndroid" } + +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" } +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" } +compose-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "composeVersion" } + +desugaring = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugaring" } +kotlin-metadata-jvm = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", version.ref = "kotlin" } + +[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" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..b1b8ef5 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..6c2f86e --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,22 @@ +# +# ImageToolbox is an image editor for android +# Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# You should have received a copy of the Apache License +# along with this program. If not, see . +# + +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..b9bb139 --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..aa5f10b --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,82 @@ +@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 +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +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% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/lib/ascii/.gitignore b/lib/ascii/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lib/ascii/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/ascii/build.gradle.kts b/lib/ascii/build.gradle.kts new file mode 100644 index 0000000..d187f57 --- /dev/null +++ b/lib/ascii/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/lib/ascii/src/main/AndroidManifest.xml b/lib/ascii/src/main/AndroidManifest.xml new file mode 100644 index 0000000..205decf --- /dev/null +++ b/lib/ascii/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/lib/ascii/src/main/java/com/t8rin/ascii/ASCIIConverter.kt b/lib/ascii/src/main/java/com/t8rin/ascii/ASCIIConverter.kt new file mode 100644 index 0000000..c9c39e1 --- /dev/null +++ b/lib/ascii/src/main/java/com/t8rin/ascii/ASCIIConverter.kt @@ -0,0 +1,240 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.setSrgbColor( + color = Color.GRAY, + alpha = (luma * 255.0f).toInt() + ) + } else { + paint.setSrgbColor(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() + } + + private fun Paint.setSrgbColor( + color: Int, + alpha: Int = color.alpha + ) { + setARGB(alpha, color.red, color.green, color.blue) + } +} + +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") + + val entries by lazy { + listOf( + NORMAL, + NORMAL2, + ARROWS, + OLD, + EXTENDED_HIGH, + MINIMAL, + MATH, + NUMERICAL + ) + } + } +} + +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> diff --git a/lib/collages/.gitignore b/lib/collages/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lib/collages/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/collages/build.gradle.kts b/lib/collages/build.gradle.kts new file mode 100644 index 0000000..f48a471 --- /dev/null +++ b/lib/collages/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/lib/collages/src/main/AndroidManifest.xml b/lib/collages/src/main/AndroidManifest.xml new file mode 100644 index 0000000..205decf --- /dev/null +++ b/lib/collages/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/lib/collages/src/main/assets/frame/collage_10_0.webp b/lib/collages/src/main/assets/frame/collage_10_0.webp new file mode 100644 index 0000000..f3a702b Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_10_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_10_1.webp b/lib/collages/src/main/assets/frame/collage_10_1.webp new file mode 100644 index 0000000..2a27368 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_10_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_10_10.webp b/lib/collages/src/main/assets/frame/collage_10_10.webp new file mode 100644 index 0000000..3c1dcc8 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_10_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_10_2.webp b/lib/collages/src/main/assets/frame/collage_10_2.webp new file mode 100644 index 0000000..6c818bf Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_10_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_10_3.webp b/lib/collages/src/main/assets/frame/collage_10_3.webp new file mode 100644 index 0000000..e7bbadc Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_10_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_10_4.webp b/lib/collages/src/main/assets/frame/collage_10_4.webp new file mode 100644 index 0000000..1cd8855 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_10_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_10_5.webp b/lib/collages/src/main/assets/frame/collage_10_5.webp new file mode 100644 index 0000000..cd0a2f2 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_10_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_10_6.webp b/lib/collages/src/main/assets/frame/collage_10_6.webp new file mode 100644 index 0000000..0aebef2 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_10_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_10_7.webp b/lib/collages/src/main/assets/frame/collage_10_7.webp new file mode 100644 index 0000000..4dc524e Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_10_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_10_8.webp b/lib/collages/src/main/assets/frame/collage_10_8.webp new file mode 100644 index 0000000..6bdf582 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_10_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_10_9.webp b/lib/collages/src/main/assets/frame/collage_10_9.webp new file mode 100644 index 0000000..cadfdd9 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_10_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_11_0.webp b/lib/collages/src/main/assets/frame/collage_11_0.webp new file mode 100644 index 0000000..3499e25 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_11_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_11_1.webp b/lib/collages/src/main/assets/frame/collage_11_1.webp new file mode 100644 index 0000000..6c81ea1 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_11_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_11_10.webp b/lib/collages/src/main/assets/frame/collage_11_10.webp new file mode 100644 index 0000000..9a93681 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_11_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_11_2.webp b/lib/collages/src/main/assets/frame/collage_11_2.webp new file mode 100644 index 0000000..b079b7d Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_11_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_11_3.webp b/lib/collages/src/main/assets/frame/collage_11_3.webp new file mode 100644 index 0000000..b8185e1 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_11_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_11_4.webp b/lib/collages/src/main/assets/frame/collage_11_4.webp new file mode 100644 index 0000000..bb13622 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_11_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_11_5.webp b/lib/collages/src/main/assets/frame/collage_11_5.webp new file mode 100644 index 0000000..7ac0f96 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_11_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_11_6.webp b/lib/collages/src/main/assets/frame/collage_11_6.webp new file mode 100644 index 0000000..6590b0c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_11_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_11_7.webp b/lib/collages/src/main/assets/frame/collage_11_7.webp new file mode 100644 index 0000000..e8a6437 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_11_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_11_8.webp b/lib/collages/src/main/assets/frame/collage_11_8.webp new file mode 100644 index 0000000..1796392 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_11_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_11_9.webp b/lib/collages/src/main/assets/frame/collage_11_9.webp new file mode 100644 index 0000000..a5e01cc Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_11_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_12_0.webp b/lib/collages/src/main/assets/frame/collage_12_0.webp new file mode 100644 index 0000000..3b816e1 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_12_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_12_1.webp b/lib/collages/src/main/assets/frame/collage_12_1.webp new file mode 100644 index 0000000..bfa44f0 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_12_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_12_10.webp b/lib/collages/src/main/assets/frame/collage_12_10.webp new file mode 100644 index 0000000..e0ec111 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_12_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_12_2.webp b/lib/collages/src/main/assets/frame/collage_12_2.webp new file mode 100644 index 0000000..9dff907 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_12_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_12_3.webp b/lib/collages/src/main/assets/frame/collage_12_3.webp new file mode 100644 index 0000000..dc76970 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_12_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_12_4.webp b/lib/collages/src/main/assets/frame/collage_12_4.webp new file mode 100644 index 0000000..5c93517 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_12_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_12_5.webp b/lib/collages/src/main/assets/frame/collage_12_5.webp new file mode 100644 index 0000000..527598e Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_12_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_12_6.webp b/lib/collages/src/main/assets/frame/collage_12_6.webp new file mode 100644 index 0000000..0d55b8a Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_12_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_12_7.webp b/lib/collages/src/main/assets/frame/collage_12_7.webp new file mode 100644 index 0000000..918a8c7 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_12_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_12_8.webp b/lib/collages/src/main/assets/frame/collage_12_8.webp new file mode 100644 index 0000000..dae6801 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_12_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_12_9.webp b/lib/collages/src/main/assets/frame/collage_12_9.webp new file mode 100644 index 0000000..de908f2 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_12_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_13_0.webp b/lib/collages/src/main/assets/frame/collage_13_0.webp new file mode 100644 index 0000000..ccf8900 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_13_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_13_1.webp b/lib/collages/src/main/assets/frame/collage_13_1.webp new file mode 100644 index 0000000..23aa2ea Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_13_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_13_10.webp b/lib/collages/src/main/assets/frame/collage_13_10.webp new file mode 100644 index 0000000..ad60b8f Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_13_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_13_2.webp b/lib/collages/src/main/assets/frame/collage_13_2.webp new file mode 100644 index 0000000..065b558 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_13_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_13_3.webp b/lib/collages/src/main/assets/frame/collage_13_3.webp new file mode 100644 index 0000000..6557e52 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_13_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_13_4.webp b/lib/collages/src/main/assets/frame/collage_13_4.webp new file mode 100644 index 0000000..fc9de10 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_13_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_13_5.webp b/lib/collages/src/main/assets/frame/collage_13_5.webp new file mode 100644 index 0000000..80ba18b Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_13_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_13_6.webp b/lib/collages/src/main/assets/frame/collage_13_6.webp new file mode 100644 index 0000000..0895230 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_13_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_13_7.webp b/lib/collages/src/main/assets/frame/collage_13_7.webp new file mode 100644 index 0000000..f88c135 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_13_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_13_8.webp b/lib/collages/src/main/assets/frame/collage_13_8.webp new file mode 100644 index 0000000..aba2da5 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_13_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_13_9.webp b/lib/collages/src/main/assets/frame/collage_13_9.webp new file mode 100644 index 0000000..72f156f Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_13_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_14_0.webp b/lib/collages/src/main/assets/frame/collage_14_0.webp new file mode 100644 index 0000000..3cae516 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_14_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_14_1.webp b/lib/collages/src/main/assets/frame/collage_14_1.webp new file mode 100644 index 0000000..11f4c51 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_14_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_14_10.webp b/lib/collages/src/main/assets/frame/collage_14_10.webp new file mode 100644 index 0000000..744c6fc Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_14_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_14_2.webp b/lib/collages/src/main/assets/frame/collage_14_2.webp new file mode 100644 index 0000000..b1e0641 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_14_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_14_3.webp b/lib/collages/src/main/assets/frame/collage_14_3.webp new file mode 100644 index 0000000..b224319 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_14_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_14_4.webp b/lib/collages/src/main/assets/frame/collage_14_4.webp new file mode 100644 index 0000000..cf8c489 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_14_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_14_5.webp b/lib/collages/src/main/assets/frame/collage_14_5.webp new file mode 100644 index 0000000..ac5fda1 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_14_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_14_6.webp b/lib/collages/src/main/assets/frame/collage_14_6.webp new file mode 100644 index 0000000..774d1eb Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_14_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_14_7.webp b/lib/collages/src/main/assets/frame/collage_14_7.webp new file mode 100644 index 0000000..936bb53 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_14_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_14_8.webp b/lib/collages/src/main/assets/frame/collage_14_8.webp new file mode 100644 index 0000000..949bad6 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_14_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_14_9.webp b/lib/collages/src/main/assets/frame/collage_14_9.webp new file mode 100644 index 0000000..b5fda8f Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_14_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_15_0.webp b/lib/collages/src/main/assets/frame/collage_15_0.webp new file mode 100644 index 0000000..86cf88d Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_15_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_15_1.webp b/lib/collages/src/main/assets/frame/collage_15_1.webp new file mode 100644 index 0000000..900bb84 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_15_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_15_10.webp b/lib/collages/src/main/assets/frame/collage_15_10.webp new file mode 100644 index 0000000..25dbcba Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_15_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_15_2.webp b/lib/collages/src/main/assets/frame/collage_15_2.webp new file mode 100644 index 0000000..1275fe0 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_15_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_15_3.webp b/lib/collages/src/main/assets/frame/collage_15_3.webp new file mode 100644 index 0000000..b1b273b Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_15_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_15_4.webp b/lib/collages/src/main/assets/frame/collage_15_4.webp new file mode 100644 index 0000000..fc9fdad Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_15_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_15_5.webp b/lib/collages/src/main/assets/frame/collage_15_5.webp new file mode 100644 index 0000000..e58769c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_15_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_15_6.webp b/lib/collages/src/main/assets/frame/collage_15_6.webp new file mode 100644 index 0000000..c67df58 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_15_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_15_7.webp b/lib/collages/src/main/assets/frame/collage_15_7.webp new file mode 100644 index 0000000..110cbe2 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_15_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_15_8.webp b/lib/collages/src/main/assets/frame/collage_15_8.webp new file mode 100644 index 0000000..f275558 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_15_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_15_9.webp b/lib/collages/src/main/assets/frame/collage_15_9.webp new file mode 100644 index 0000000..71304c5 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_15_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_16_0.webp b/lib/collages/src/main/assets/frame/collage_16_0.webp new file mode 100644 index 0000000..7025eac Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_16_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_16_1.webp b/lib/collages/src/main/assets/frame/collage_16_1.webp new file mode 100644 index 0000000..d24b98b Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_16_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_16_10.webp b/lib/collages/src/main/assets/frame/collage_16_10.webp new file mode 100644 index 0000000..f6e8244 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_16_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_16_2.webp b/lib/collages/src/main/assets/frame/collage_16_2.webp new file mode 100644 index 0000000..8696609 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_16_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_16_3.webp b/lib/collages/src/main/assets/frame/collage_16_3.webp new file mode 100644 index 0000000..afc5744 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_16_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_16_4.webp b/lib/collages/src/main/assets/frame/collage_16_4.webp new file mode 100644 index 0000000..6b7efc7 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_16_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_16_5.webp b/lib/collages/src/main/assets/frame/collage_16_5.webp new file mode 100644 index 0000000..ba8df01 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_16_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_16_6.webp b/lib/collages/src/main/assets/frame/collage_16_6.webp new file mode 100644 index 0000000..627336c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_16_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_16_7.webp b/lib/collages/src/main/assets/frame/collage_16_7.webp new file mode 100644 index 0000000..b9ea725 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_16_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_16_8.webp b/lib/collages/src/main/assets/frame/collage_16_8.webp new file mode 100644 index 0000000..a343fe7 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_16_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_16_9.webp b/lib/collages/src/main/assets/frame/collage_16_9.webp new file mode 100644 index 0000000..bef1f3c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_16_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_17_0.webp b/lib/collages/src/main/assets/frame/collage_17_0.webp new file mode 100644 index 0000000..4521a3e Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_17_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_17_1.webp b/lib/collages/src/main/assets/frame/collage_17_1.webp new file mode 100644 index 0000000..e3331dc Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_17_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_17_10.webp b/lib/collages/src/main/assets/frame/collage_17_10.webp new file mode 100644 index 0000000..18908ba Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_17_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_17_2.webp b/lib/collages/src/main/assets/frame/collage_17_2.webp new file mode 100644 index 0000000..aeb6a1c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_17_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_17_3.webp b/lib/collages/src/main/assets/frame/collage_17_3.webp new file mode 100644 index 0000000..3fabe82 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_17_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_17_4.webp b/lib/collages/src/main/assets/frame/collage_17_4.webp new file mode 100644 index 0000000..c2be239 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_17_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_17_5.webp b/lib/collages/src/main/assets/frame/collage_17_5.webp new file mode 100644 index 0000000..3d74c69 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_17_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_17_6.webp b/lib/collages/src/main/assets/frame/collage_17_6.webp new file mode 100644 index 0000000..b1e5f39 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_17_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_17_7.webp b/lib/collages/src/main/assets/frame/collage_17_7.webp new file mode 100644 index 0000000..26b8647 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_17_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_17_8.webp b/lib/collages/src/main/assets/frame/collage_17_8.webp new file mode 100644 index 0000000..5ffe82d Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_17_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_17_9.webp b/lib/collages/src/main/assets/frame/collage_17_9.webp new file mode 100644 index 0000000..0faaace Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_17_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_18_0.webp b/lib/collages/src/main/assets/frame/collage_18_0.webp new file mode 100644 index 0000000..5e3982c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_18_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_18_1.webp b/lib/collages/src/main/assets/frame/collage_18_1.webp new file mode 100644 index 0000000..ecf91d3 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_18_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_18_10.webp b/lib/collages/src/main/assets/frame/collage_18_10.webp new file mode 100644 index 0000000..55570df Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_18_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_18_2.webp b/lib/collages/src/main/assets/frame/collage_18_2.webp new file mode 100644 index 0000000..5e3982c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_18_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_18_3.webp b/lib/collages/src/main/assets/frame/collage_18_3.webp new file mode 100644 index 0000000..fd8ce41 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_18_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_18_4.webp b/lib/collages/src/main/assets/frame/collage_18_4.webp new file mode 100644 index 0000000..2a6e42b Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_18_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_18_5.webp b/lib/collages/src/main/assets/frame/collage_18_5.webp new file mode 100644 index 0000000..66f2b7b Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_18_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_18_6.webp b/lib/collages/src/main/assets/frame/collage_18_6.webp new file mode 100644 index 0000000..0854713 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_18_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_18_7.webp b/lib/collages/src/main/assets/frame/collage_18_7.webp new file mode 100644 index 0000000..a1caa6a Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_18_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_18_8.webp b/lib/collages/src/main/assets/frame/collage_18_8.webp new file mode 100644 index 0000000..99c0ad7 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_18_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_18_9.webp b/lib/collages/src/main/assets/frame/collage_18_9.webp new file mode 100644 index 0000000..09157d4 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_18_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_19_0.webp b/lib/collages/src/main/assets/frame/collage_19_0.webp new file mode 100644 index 0000000..35a6e83 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_19_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_19_1.webp b/lib/collages/src/main/assets/frame/collage_19_1.webp new file mode 100644 index 0000000..804f1ea Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_19_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_19_10.webp b/lib/collages/src/main/assets/frame/collage_19_10.webp new file mode 100644 index 0000000..0121423 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_19_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_19_2.webp b/lib/collages/src/main/assets/frame/collage_19_2.webp new file mode 100644 index 0000000..07079f1 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_19_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_19_3.webp b/lib/collages/src/main/assets/frame/collage_19_3.webp new file mode 100644 index 0000000..82dc495 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_19_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_19_4.webp b/lib/collages/src/main/assets/frame/collage_19_4.webp new file mode 100644 index 0000000..533d7d6 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_19_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_19_5.webp b/lib/collages/src/main/assets/frame/collage_19_5.webp new file mode 100644 index 0000000..a3e50de Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_19_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_19_6.webp b/lib/collages/src/main/assets/frame/collage_19_6.webp new file mode 100644 index 0000000..1d304f8 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_19_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_19_7.webp b/lib/collages/src/main/assets/frame/collage_19_7.webp new file mode 100644 index 0000000..2ce56a0 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_19_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_19_8.webp b/lib/collages/src/main/assets/frame/collage_19_8.webp new file mode 100644 index 0000000..ef5edc7 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_19_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_19_9.webp b/lib/collages/src/main/assets/frame/collage_19_9.webp new file mode 100644 index 0000000..9baf10f Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_19_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_1_0.webp b/lib/collages/src/main/assets/frame/collage_1_0.webp new file mode 100644 index 0000000..daece8c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_1_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_20_0.webp b/lib/collages/src/main/assets/frame/collage_20_0.webp new file mode 100644 index 0000000..952ce53 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_20_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_20_1.webp b/lib/collages/src/main/assets/frame/collage_20_1.webp new file mode 100644 index 0000000..89bd92b Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_20_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_20_10.webp b/lib/collages/src/main/assets/frame/collage_20_10.webp new file mode 100644 index 0000000..6a913ef Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_20_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_20_2.webp b/lib/collages/src/main/assets/frame/collage_20_2.webp new file mode 100644 index 0000000..5f19b4c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_20_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_20_3.webp b/lib/collages/src/main/assets/frame/collage_20_3.webp new file mode 100644 index 0000000..a35eaa5 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_20_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_20_4.webp b/lib/collages/src/main/assets/frame/collage_20_4.webp new file mode 100644 index 0000000..21896f2 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_20_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_20_5.webp b/lib/collages/src/main/assets/frame/collage_20_5.webp new file mode 100644 index 0000000..0ac6fe2 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_20_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_20_6.webp b/lib/collages/src/main/assets/frame/collage_20_6.webp new file mode 100644 index 0000000..7efa1c7 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_20_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_20_7.webp b/lib/collages/src/main/assets/frame/collage_20_7.webp new file mode 100644 index 0000000..b81db79 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_20_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_20_8.webp b/lib/collages/src/main/assets/frame/collage_20_8.webp new file mode 100644 index 0000000..cb6f5a0 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_20_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_20_9.webp b/lib/collages/src/main/assets/frame/collage_20_9.webp new file mode 100644 index 0000000..a1c22c9 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_20_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_2_0.webp b/lib/collages/src/main/assets/frame/collage_2_0.webp new file mode 100644 index 0000000..836092f Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_2_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_2_1.webp b/lib/collages/src/main/assets/frame/collage_2_1.webp new file mode 100644 index 0000000..890aa56 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_2_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_2_10.webp b/lib/collages/src/main/assets/frame/collage_2_10.webp new file mode 100644 index 0000000..6f5a95d Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_2_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_2_11.webp b/lib/collages/src/main/assets/frame/collage_2_11.webp new file mode 100644 index 0000000..4b85b42 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_2_11.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_2_12.webp b/lib/collages/src/main/assets/frame/collage_2_12.webp new file mode 100644 index 0000000..15a5d0d Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_2_12.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_2_13.webp b/lib/collages/src/main/assets/frame/collage_2_13.webp new file mode 100644 index 0000000..33b5379 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_2_13.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_2_2.webp b/lib/collages/src/main/assets/frame/collage_2_2.webp new file mode 100644 index 0000000..8829915 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_2_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_2_3.webp b/lib/collages/src/main/assets/frame/collage_2_3.webp new file mode 100644 index 0000000..8ebbcf5 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_2_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_2_4.webp b/lib/collages/src/main/assets/frame/collage_2_4.webp new file mode 100644 index 0000000..2ca9d30 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_2_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_2_5.webp b/lib/collages/src/main/assets/frame/collage_2_5.webp new file mode 100644 index 0000000..d72f1c3 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_2_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_2_6.webp b/lib/collages/src/main/assets/frame/collage_2_6.webp new file mode 100644 index 0000000..304a5ec Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_2_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_2_7.webp b/lib/collages/src/main/assets/frame/collage_2_7.webp new file mode 100644 index 0000000..681afcd Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_2_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_2_8.webp b/lib/collages/src/main/assets/frame/collage_2_8.webp new file mode 100644 index 0000000..5ee327a Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_2_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_2_9.webp b/lib/collages/src/main/assets/frame/collage_2_9.webp new file mode 100644 index 0000000..97c1de6 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_2_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_0.webp b/lib/collages/src/main/assets/frame/collage_3_0.webp new file mode 100644 index 0000000..79a6426 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_1.webp b/lib/collages/src/main/assets/frame/collage_3_1.webp new file mode 100644 index 0000000..7430201 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_10.webp b/lib/collages/src/main/assets/frame/collage_3_10.webp new file mode 100644 index 0000000..db99b5c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_11.webp b/lib/collages/src/main/assets/frame/collage_3_11.webp new file mode 100644 index 0000000..a386065 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_11.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_12.webp b/lib/collages/src/main/assets/frame/collage_3_12.webp new file mode 100644 index 0000000..f1e4545 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_12.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_13.webp b/lib/collages/src/main/assets/frame/collage_3_13.webp new file mode 100644 index 0000000..a2c6985 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_13.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_14.webp b/lib/collages/src/main/assets/frame/collage_3_14.webp new file mode 100644 index 0000000..177ae5b Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_14.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_15.webp b/lib/collages/src/main/assets/frame/collage_3_15.webp new file mode 100644 index 0000000..387fdfb Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_15.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_16.webp b/lib/collages/src/main/assets/frame/collage_3_16.webp new file mode 100644 index 0000000..1c21744 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_16.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_17.webp b/lib/collages/src/main/assets/frame/collage_3_17.webp new file mode 100644 index 0000000..1511755 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_17.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_18.webp b/lib/collages/src/main/assets/frame/collage_3_18.webp new file mode 100644 index 0000000..a7ee24f Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_18.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_19.webp b/lib/collages/src/main/assets/frame/collage_3_19.webp new file mode 100644 index 0000000..ab9c2f4 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_19.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_20.webp b/lib/collages/src/main/assets/frame/collage_3_20.webp new file mode 100644 index 0000000..9193e10 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_20.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_21.webp b/lib/collages/src/main/assets/frame/collage_3_21.webp new file mode 100644 index 0000000..84b8263 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_21.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_22.webp b/lib/collages/src/main/assets/frame/collage_3_22.webp new file mode 100644 index 0000000..2a97e7c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_22.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_23.webp b/lib/collages/src/main/assets/frame/collage_3_23.webp new file mode 100644 index 0000000..8e24792 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_23.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_24.webp b/lib/collages/src/main/assets/frame/collage_3_24.webp new file mode 100644 index 0000000..9430be0 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_24.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_25.webp b/lib/collages/src/main/assets/frame/collage_3_25.webp new file mode 100644 index 0000000..02300f7 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_25.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_26.webp b/lib/collages/src/main/assets/frame/collage_3_26.webp new file mode 100644 index 0000000..8476716 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_26.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_27.webp b/lib/collages/src/main/assets/frame/collage_3_27.webp new file mode 100644 index 0000000..b198dc4 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_27.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_28.webp b/lib/collages/src/main/assets/frame/collage_3_28.webp new file mode 100644 index 0000000..a5f9537 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_28.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_29.webp b/lib/collages/src/main/assets/frame/collage_3_29.webp new file mode 100644 index 0000000..7b938f3 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_29.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_3.webp b/lib/collages/src/main/assets/frame/collage_3_3.webp new file mode 100644 index 0000000..7c8d229 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_30.webp b/lib/collages/src/main/assets/frame/collage_3_30.webp new file mode 100644 index 0000000..bed6b39 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_30.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_31.webp b/lib/collages/src/main/assets/frame/collage_3_31.webp new file mode 100644 index 0000000..d290683 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_31.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_32.webp b/lib/collages/src/main/assets/frame/collage_3_32.webp new file mode 100644 index 0000000..9e95edb Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_32.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_33.webp b/lib/collages/src/main/assets/frame/collage_3_33.webp new file mode 100644 index 0000000..67893e3 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_33.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_34.webp b/lib/collages/src/main/assets/frame/collage_3_34.webp new file mode 100644 index 0000000..4cb5b19 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_34.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_35.webp b/lib/collages/src/main/assets/frame/collage_3_35.webp new file mode 100644 index 0000000..a75ba5b Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_35.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_36.webp b/lib/collages/src/main/assets/frame/collage_3_36.webp new file mode 100644 index 0000000..bd734ca Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_36.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_37.webp b/lib/collages/src/main/assets/frame/collage_3_37.webp new file mode 100644 index 0000000..428fab2 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_37.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_38.webp b/lib/collages/src/main/assets/frame/collage_3_38.webp new file mode 100644 index 0000000..f3a4745 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_38.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_39.webp b/lib/collages/src/main/assets/frame/collage_3_39.webp new file mode 100644 index 0000000..161cf96 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_39.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_4.webp b/lib/collages/src/main/assets/frame/collage_3_4.webp new file mode 100644 index 0000000..f14dbc5 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_40.webp b/lib/collages/src/main/assets/frame/collage_3_40.webp new file mode 100644 index 0000000..8234e32 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_40.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_41.webp b/lib/collages/src/main/assets/frame/collage_3_41.webp new file mode 100644 index 0000000..6bba298 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_41.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_42.webp b/lib/collages/src/main/assets/frame/collage_3_42.webp new file mode 100644 index 0000000..01ca8f5 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_42.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_43.webp b/lib/collages/src/main/assets/frame/collage_3_43.webp new file mode 100644 index 0000000..d7032ac Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_43.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_44.webp b/lib/collages/src/main/assets/frame/collage_3_44.webp new file mode 100644 index 0000000..d5c5352 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_44.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_45.webp b/lib/collages/src/main/assets/frame/collage_3_45.webp new file mode 100644 index 0000000..a191cac Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_45.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_46.webp b/lib/collages/src/main/assets/frame/collage_3_46.webp new file mode 100644 index 0000000..6e24d0e Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_46.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_47.webp b/lib/collages/src/main/assets/frame/collage_3_47.webp new file mode 100644 index 0000000..c19b501 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_47.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_48.webp b/lib/collages/src/main/assets/frame/collage_3_48.webp new file mode 100644 index 0000000..f26cb2f Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_48.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_5.webp b/lib/collages/src/main/assets/frame/collage_3_5.webp new file mode 100644 index 0000000..48fc6d7 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_6.webp b/lib/collages/src/main/assets/frame/collage_3_6.webp new file mode 100644 index 0000000..173fc62 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_7.webp b/lib/collages/src/main/assets/frame/collage_3_7.webp new file mode 100644 index 0000000..bd23e5a Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_8.webp b/lib/collages/src/main/assets/frame/collage_3_8.webp new file mode 100644 index 0000000..f610338 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_3_9.webp b/lib/collages/src/main/assets/frame/collage_3_9.webp new file mode 100644 index 0000000..b65185a Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_3_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_0.webp b/lib/collages/src/main/assets/frame/collage_4_0.webp new file mode 100644 index 0000000..500f9fd Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_1.webp b/lib/collages/src/main/assets/frame/collage_4_1.webp new file mode 100644 index 0000000..54d7423 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_10.webp b/lib/collages/src/main/assets/frame/collage_4_10.webp new file mode 100644 index 0000000..593e503 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_11.webp b/lib/collages/src/main/assets/frame/collage_4_11.webp new file mode 100644 index 0000000..797f676 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_11.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_12.webp b/lib/collages/src/main/assets/frame/collage_4_12.webp new file mode 100644 index 0000000..4371496 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_12.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_13.webp b/lib/collages/src/main/assets/frame/collage_4_13.webp new file mode 100644 index 0000000..607a69d Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_13.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_14.webp b/lib/collages/src/main/assets/frame/collage_4_14.webp new file mode 100644 index 0000000..7541c38 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_14.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_15.webp b/lib/collages/src/main/assets/frame/collage_4_15.webp new file mode 100644 index 0000000..b3322a5 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_15.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_16.webp b/lib/collages/src/main/assets/frame/collage_4_16.webp new file mode 100644 index 0000000..73f1b14 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_16.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_17.webp b/lib/collages/src/main/assets/frame/collage_4_17.webp new file mode 100644 index 0000000..0b53c86 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_17.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_18.webp b/lib/collages/src/main/assets/frame/collage_4_18.webp new file mode 100644 index 0000000..590b520 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_18.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_19.webp b/lib/collages/src/main/assets/frame/collage_4_19.webp new file mode 100644 index 0000000..0097c53 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_19.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_1_1.webp b/lib/collages/src/main/assets/frame/collage_4_1_1.webp new file mode 100644 index 0000000..6dad86b Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_1_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_2.webp b/lib/collages/src/main/assets/frame/collage_4_2.webp new file mode 100644 index 0000000..919155c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_20.webp b/lib/collages/src/main/assets/frame/collage_4_20.webp new file mode 100644 index 0000000..24a6e69 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_20.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_21.webp b/lib/collages/src/main/assets/frame/collage_4_21.webp new file mode 100644 index 0000000..065b665 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_21.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_22.webp b/lib/collages/src/main/assets/frame/collage_4_22.webp new file mode 100644 index 0000000..7bd0bcf Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_22.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_23.webp b/lib/collages/src/main/assets/frame/collage_4_23.webp new file mode 100644 index 0000000..8128044 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_23.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_24.webp b/lib/collages/src/main/assets/frame/collage_4_24.webp new file mode 100644 index 0000000..400f3ba Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_24.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_25.webp b/lib/collages/src/main/assets/frame/collage_4_25.webp new file mode 100644 index 0000000..22a4d39 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_25.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_26.webp b/lib/collages/src/main/assets/frame/collage_4_26.webp new file mode 100644 index 0000000..8c34f6a Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_26.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_27.webp b/lib/collages/src/main/assets/frame/collage_4_27.webp new file mode 100644 index 0000000..7ec03c0 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_27.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_4.webp b/lib/collages/src/main/assets/frame/collage_4_4.webp new file mode 100644 index 0000000..753565d Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_5.webp b/lib/collages/src/main/assets/frame/collage_4_5.webp new file mode 100644 index 0000000..5962c22 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_6.webp b/lib/collages/src/main/assets/frame/collage_4_6.webp new file mode 100644 index 0000000..55abfcc Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_7.webp b/lib/collages/src/main/assets/frame/collage_4_7.webp new file mode 100644 index 0000000..32ac3a6 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_8.webp b/lib/collages/src/main/assets/frame/collage_4_8.webp new file mode 100644 index 0000000..3b77b53 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_4_9.webp b/lib/collages/src/main/assets/frame/collage_4_9.webp new file mode 100644 index 0000000..9bade01 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_4_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_0.webp b/lib/collages/src/main/assets/frame/collage_5_0.webp new file mode 100644 index 0000000..9ba3a42 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_1.webp b/lib/collages/src/main/assets/frame/collage_5_1.webp new file mode 100644 index 0000000..99003c0 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_10.webp b/lib/collages/src/main/assets/frame/collage_5_10.webp new file mode 100644 index 0000000..19c1979 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_11.webp b/lib/collages/src/main/assets/frame/collage_5_11.webp new file mode 100644 index 0000000..28c40e1 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_11.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_12.webp b/lib/collages/src/main/assets/frame/collage_5_12.webp new file mode 100644 index 0000000..4e71b70 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_12.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_13.webp b/lib/collages/src/main/assets/frame/collage_5_13.webp new file mode 100644 index 0000000..814cbb2 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_13.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_14.webp b/lib/collages/src/main/assets/frame/collage_5_14.webp new file mode 100644 index 0000000..ee2a0cd Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_14.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_15.webp b/lib/collages/src/main/assets/frame/collage_5_15.webp new file mode 100644 index 0000000..90fa657 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_15.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_16.webp b/lib/collages/src/main/assets/frame/collage_5_16.webp new file mode 100644 index 0000000..92c910c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_16.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_17.webp b/lib/collages/src/main/assets/frame/collage_5_17.webp new file mode 100644 index 0000000..f82e332 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_17.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_18.webp b/lib/collages/src/main/assets/frame/collage_5_18.webp new file mode 100644 index 0000000..7515c5c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_18.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_19.webp b/lib/collages/src/main/assets/frame/collage_5_19.webp new file mode 100644 index 0000000..0c5270b Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_19.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_1_1.webp b/lib/collages/src/main/assets/frame/collage_5_1_1.webp new file mode 100644 index 0000000..55119ac Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_1_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_1_2.webp b/lib/collages/src/main/assets/frame/collage_5_1_2.webp new file mode 100644 index 0000000..5c955aa Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_1_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_2.webp b/lib/collages/src/main/assets/frame/collage_5_2.webp new file mode 100644 index 0000000..6370648 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_20.webp b/lib/collages/src/main/assets/frame/collage_5_20.webp new file mode 100644 index 0000000..09dc1f5 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_20.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_21.webp b/lib/collages/src/main/assets/frame/collage_5_21.webp new file mode 100644 index 0000000..17d28ef Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_21.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_22.webp b/lib/collages/src/main/assets/frame/collage_5_22.webp new file mode 100644 index 0000000..566e740 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_22.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_23.webp b/lib/collages/src/main/assets/frame/collage_5_23.webp new file mode 100644 index 0000000..b0dc523 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_23.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_24.webp b/lib/collages/src/main/assets/frame/collage_5_24.webp new file mode 100644 index 0000000..bd2e55e Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_24.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_25.webp b/lib/collages/src/main/assets/frame/collage_5_25.webp new file mode 100644 index 0000000..09990d3 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_25.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_26.webp b/lib/collages/src/main/assets/frame/collage_5_26.webp new file mode 100644 index 0000000..9e231d0 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_26.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_27.webp b/lib/collages/src/main/assets/frame/collage_5_27.webp new file mode 100644 index 0000000..7f92b79 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_27.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_28.webp b/lib/collages/src/main/assets/frame/collage_5_28.webp new file mode 100644 index 0000000..ef0c626 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_28.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_29.webp b/lib/collages/src/main/assets/frame/collage_5_29.webp new file mode 100644 index 0000000..58af71c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_29.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_3.webp b/lib/collages/src/main/assets/frame/collage_5_3.webp new file mode 100644 index 0000000..5332b3a Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_30.webp b/lib/collages/src/main/assets/frame/collage_5_30.webp new file mode 100644 index 0000000..4180edf Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_30.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_31.webp b/lib/collages/src/main/assets/frame/collage_5_31.webp new file mode 100644 index 0000000..dfbd0e7 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_31.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_32.webp b/lib/collages/src/main/assets/frame/collage_5_32.webp new file mode 100644 index 0000000..bacad2a Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_32.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_33.webp b/lib/collages/src/main/assets/frame/collage_5_33.webp new file mode 100644 index 0000000..13f12cb Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_33.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_4.webp b/lib/collages/src/main/assets/frame/collage_5_4.webp new file mode 100644 index 0000000..7bc76b2 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_5.webp b/lib/collages/src/main/assets/frame/collage_5_5.webp new file mode 100644 index 0000000..697cdb2 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_6.webp b/lib/collages/src/main/assets/frame/collage_5_6.webp new file mode 100644 index 0000000..d1b0926 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_7.webp b/lib/collages/src/main/assets/frame/collage_5_7.webp new file mode 100644 index 0000000..76790b6 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_8.webp b/lib/collages/src/main/assets/frame/collage_5_8.webp new file mode 100644 index 0000000..f20343d Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_5_9.webp b/lib/collages/src/main/assets/frame/collage_5_9.webp new file mode 100644 index 0000000..edff0cb Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_5_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_0.webp b/lib/collages/src/main/assets/frame/collage_6_0.webp new file mode 100644 index 0000000..d9d9cc3 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_1.webp b/lib/collages/src/main/assets/frame/collage_6_1.webp new file mode 100644 index 0000000..f89bc6c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_10.webp b/lib/collages/src/main/assets/frame/collage_6_10.webp new file mode 100644 index 0000000..4f4bb78 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_11.webp b/lib/collages/src/main/assets/frame/collage_6_11.webp new file mode 100644 index 0000000..ed9930c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_11.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_12.webp b/lib/collages/src/main/assets/frame/collage_6_12.webp new file mode 100644 index 0000000..c8f32af Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_12.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_13.webp b/lib/collages/src/main/assets/frame/collage_6_13.webp new file mode 100644 index 0000000..c616fa9 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_13.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_14.webp b/lib/collages/src/main/assets/frame/collage_6_14.webp new file mode 100644 index 0000000..d730fa0 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_14.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_15.webp b/lib/collages/src/main/assets/frame/collage_6_15.webp new file mode 100644 index 0000000..908f4f6 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_15.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_16.webp b/lib/collages/src/main/assets/frame/collage_6_16.webp new file mode 100644 index 0000000..3a769c7 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_16.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_17.webp b/lib/collages/src/main/assets/frame/collage_6_17.webp new file mode 100644 index 0000000..cd807bd Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_17.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_1_1.webp b/lib/collages/src/main/assets/frame/collage_6_1_1.webp new file mode 100644 index 0000000..63b722f Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_1_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_1_2.webp b/lib/collages/src/main/assets/frame/collage_6_1_2.webp new file mode 100644 index 0000000..a916c81 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_1_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_2.webp b/lib/collages/src/main/assets/frame/collage_6_2.webp new file mode 100644 index 0000000..dc6e0e0 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_3.webp b/lib/collages/src/main/assets/frame/collage_6_3.webp new file mode 100644 index 0000000..546c2b5 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_4.webp b/lib/collages/src/main/assets/frame/collage_6_4.webp new file mode 100644 index 0000000..62509be Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_5.webp b/lib/collages/src/main/assets/frame/collage_6_5.webp new file mode 100644 index 0000000..c3f5660 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_6.webp b/lib/collages/src/main/assets/frame/collage_6_6.webp new file mode 100644 index 0000000..ae8dd0c Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_7.webp b/lib/collages/src/main/assets/frame/collage_6_7.webp new file mode 100644 index 0000000..847a6f0 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_8.webp b/lib/collages/src/main/assets/frame/collage_6_8.webp new file mode 100644 index 0000000..1a39f91 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_6_9.webp b/lib/collages/src/main/assets/frame/collage_6_9.webp new file mode 100644 index 0000000..6fc726d Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_6_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_7_0.webp b/lib/collages/src/main/assets/frame/collage_7_0.webp new file mode 100644 index 0000000..2e9ecd9 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_7_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_7_1.webp b/lib/collages/src/main/assets/frame/collage_7_1.webp new file mode 100644 index 0000000..e558a2a Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_7_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_7_10.webp b/lib/collages/src/main/assets/frame/collage_7_10.webp new file mode 100644 index 0000000..bf122a5 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_7_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_7_11.webp b/lib/collages/src/main/assets/frame/collage_7_11.webp new file mode 100644 index 0000000..11894cc Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_7_11.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_7_12.webp b/lib/collages/src/main/assets/frame/collage_7_12.webp new file mode 100644 index 0000000..f3fa70d Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_7_12.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_7_1_1.webp b/lib/collages/src/main/assets/frame/collage_7_1_1.webp new file mode 100644 index 0000000..539ab58 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_7_1_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_7_1_2.webp b/lib/collages/src/main/assets/frame/collage_7_1_2.webp new file mode 100644 index 0000000..3a265b1 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_7_1_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_7_2.webp b/lib/collages/src/main/assets/frame/collage_7_2.webp new file mode 100644 index 0000000..4fa622e Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_7_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_7_3.webp b/lib/collages/src/main/assets/frame/collage_7_3.webp new file mode 100644 index 0000000..4189ab5 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_7_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_7_4.webp b/lib/collages/src/main/assets/frame/collage_7_4.webp new file mode 100644 index 0000000..6a3b086 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_7_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_7_5.webp b/lib/collages/src/main/assets/frame/collage_7_5.webp new file mode 100644 index 0000000..1da91e1 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_7_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_7_6.webp b/lib/collages/src/main/assets/frame/collage_7_6.webp new file mode 100644 index 0000000..537912e Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_7_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_7_7.webp b/lib/collages/src/main/assets/frame/collage_7_7.webp new file mode 100644 index 0000000..04fa9c3 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_7_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_7_8.webp b/lib/collages/src/main/assets/frame/collage_7_8.webp new file mode 100644 index 0000000..26913d7 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_7_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_7_9.webp b/lib/collages/src/main/assets/frame/collage_7_9.webp new file mode 100644 index 0000000..45f8acb Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_7_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_0.webp b/lib/collages/src/main/assets/frame/collage_8_0.webp new file mode 100644 index 0000000..cf28f1d Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_1.webp b/lib/collages/src/main/assets/frame/collage_8_1.webp new file mode 100644 index 0000000..a5ed0da Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_10.webp b/lib/collages/src/main/assets/frame/collage_8_10.webp new file mode 100644 index 0000000..51f56c9 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_11.webp b/lib/collages/src/main/assets/frame/collage_8_11.webp new file mode 100644 index 0000000..49e0fea Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_11.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_12.webp b/lib/collages/src/main/assets/frame/collage_8_12.webp new file mode 100644 index 0000000..d46bfbe Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_12.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_13.webp b/lib/collages/src/main/assets/frame/collage_8_13.webp new file mode 100644 index 0000000..f742f5e Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_13.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_14.webp b/lib/collages/src/main/assets/frame/collage_8_14.webp new file mode 100644 index 0000000..15c06c8 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_14.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_15.webp b/lib/collages/src/main/assets/frame/collage_8_15.webp new file mode 100644 index 0000000..3c20300 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_15.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_16.webp b/lib/collages/src/main/assets/frame/collage_8_16.webp new file mode 100644 index 0000000..5d23eff Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_16.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_17.webp b/lib/collages/src/main/assets/frame/collage_8_17.webp new file mode 100644 index 0000000..d2a757e Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_17.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_18.webp b/lib/collages/src/main/assets/frame/collage_8_18.webp new file mode 100644 index 0000000..51f56c9 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_18.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_2.webp b/lib/collages/src/main/assets/frame/collage_8_2.webp new file mode 100644 index 0000000..3750fee Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_3.webp b/lib/collages/src/main/assets/frame/collage_8_3.webp new file mode 100644 index 0000000..085688b Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_4.webp b/lib/collages/src/main/assets/frame/collage_8_4.webp new file mode 100644 index 0000000..4d8dcf1 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_5.webp b/lib/collages/src/main/assets/frame/collage_8_5.webp new file mode 100644 index 0000000..1fa49b3 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_6.webp b/lib/collages/src/main/assets/frame/collage_8_6.webp new file mode 100644 index 0000000..996c08b Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_7.webp b/lib/collages/src/main/assets/frame/collage_8_7.webp new file mode 100644 index 0000000..f429645 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_8.webp b/lib/collages/src/main/assets/frame/collage_8_8.webp new file mode 100644 index 0000000..288b766 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_8_9.webp b/lib/collages/src/main/assets/frame/collage_8_9.webp new file mode 100644 index 0000000..ad275a5 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_8_9.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_9_0.webp b/lib/collages/src/main/assets/frame/collage_9_0.webp new file mode 100644 index 0000000..ab3d26e Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_9_0.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_9_1.webp b/lib/collages/src/main/assets/frame/collage_9_1.webp new file mode 100644 index 0000000..6d7e8f4 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_9_1.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_9_10.webp b/lib/collages/src/main/assets/frame/collage_9_10.webp new file mode 100644 index 0000000..5104854 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_9_10.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_9_11.webp b/lib/collages/src/main/assets/frame/collage_9_11.webp new file mode 100644 index 0000000..e4dfb05 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_9_11.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_9_12.webp b/lib/collages/src/main/assets/frame/collage_9_12.webp new file mode 100644 index 0000000..929f726 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_9_12.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_9_13.webp b/lib/collages/src/main/assets/frame/collage_9_13.webp new file mode 100644 index 0000000..2e09ce0 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_9_13.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_9_2.webp b/lib/collages/src/main/assets/frame/collage_9_2.webp new file mode 100644 index 0000000..87534a1 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_9_2.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_9_3.webp b/lib/collages/src/main/assets/frame/collage_9_3.webp new file mode 100644 index 0000000..8307232 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_9_3.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_9_4.webp b/lib/collages/src/main/assets/frame/collage_9_4.webp new file mode 100644 index 0000000..1068e18 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_9_4.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_9_5.webp b/lib/collages/src/main/assets/frame/collage_9_5.webp new file mode 100644 index 0000000..f362d03 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_9_5.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_9_6.webp b/lib/collages/src/main/assets/frame/collage_9_6.webp new file mode 100644 index 0000000..d73c73b Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_9_6.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_9_7.webp b/lib/collages/src/main/assets/frame/collage_9_7.webp new file mode 100644 index 0000000..5ba88e5 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_9_7.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_9_8.webp b/lib/collages/src/main/assets/frame/collage_9_8.webp new file mode 100644 index 0000000..938b26a Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_9_8.webp differ diff --git a/lib/collages/src/main/assets/frame/collage_9_9.webp b/lib/collages/src/main/assets/frame/collage_9_9.webp new file mode 100644 index 0000000..eccea78 Binary files /dev/null and b/lib/collages/src/main/assets/frame/collage_9_9.webp differ diff --git a/lib/collages/src/main/java/com/t8rin/collages/Collage.kt b/lib/collages/src/main/java/com/t8rin/collages/Collage.kt new file mode 100644 index 0000000..6a95cfe --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/Collage.kt @@ -0,0 +1,240 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + } +} \ No newline at end of file diff --git a/lib/collages/src/main/java/com/t8rin/collages/CollageType.kt b/lib/collages/src/main/java/com/t8rin/collages/CollageType.kt new file mode 100644 index 0000000..f85931a --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/CollageType.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + ) + } + } +} \ No newline at end of file diff --git a/lib/collages/src/main/java/com/t8rin/collages/CollageTypeSelection.kt b/lib/collages/src/main/java/com/t8rin/collages/CollageTypeSelection.kt new file mode 100644 index 0000000..2e476a4 --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/CollageTypeSelection.kt @@ -0,0 +1,129 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + ) + } + } + } + +} \ No newline at end of file diff --git a/lib/collages/src/main/java/com/t8rin/collages/frames/EightFrameImage.kt b/lib/collages/src/main/java/com/t8rin/collages/frames/EightFrameImage.kt new file mode 100644 index 0000000..07a0a5a --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/frames/EightFrameImage.kt @@ -0,0 +1,956 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) } + ) + } + } +} \ No newline at end of file diff --git a/lib/collages/src/main/java/com/t8rin/collages/frames/ExtendedFrameImage.kt b/lib/collages/src/main/java/com/t8rin/collages/frames/ExtendedFrameImage.kt new file mode 100644 index 0000000..5224e9d --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/frames/ExtendedFrameImage.kt @@ -0,0 +1,1094 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + } + } + +} diff --git a/lib/collages/src/main/java/com/t8rin/collages/frames/FiveFrameImage.kt b/lib/collages/src/main/java/com/t8rin/collages/frames/FiveFrameImage.kt new file mode 100644 index 0000000..320eee4 --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/frames/FiveFrameImage.kt @@ -0,0 +1,1811 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + ) + } +} \ No newline at end of file diff --git a/lib/collages/src/main/java/com/t8rin/collages/frames/FourFrameImage.kt b/lib/collages/src/main/java/com/t8rin/collages/frames/FourFrameImage.kt new file mode 100644 index 0000000..5608333 --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/frames/FourFrameImage.kt @@ -0,0 +1,1222 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + } + ) + } + } +} \ No newline at end of file diff --git a/lib/collages/src/main/java/com/t8rin/collages/frames/NineFrameImage.kt b/lib/collages/src/main/java/com/t8rin/collages/frames/NineFrameImage.kt new file mode 100644 index 0000000..6e7325a --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/frames/NineFrameImage.kt @@ -0,0 +1,1012 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) } + ) + } + } +} \ No newline at end of file diff --git a/lib/collages/src/main/java/com/t8rin/collages/frames/SevenFrameImage.kt b/lib/collages/src/main/java/com/t8rin/collages/frames/SevenFrameImage.kt new file mode 100644 index 0000000..5735654 --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/frames/SevenFrameImage.kt @@ -0,0 +1,574 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + ) + } +} \ No newline at end of file diff --git a/lib/collages/src/main/java/com/t8rin/collages/frames/SixFrameImage.kt b/lib/collages/src/main/java/com/t8rin/collages/frames/SixFrameImage.kt new file mode 100644 index 0000000..efe0229 --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/frames/SixFrameImage.kt @@ -0,0 +1,676 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + ) + } +} \ No newline at end of file diff --git a/lib/collages/src/main/java/com/t8rin/collages/frames/TenFrameImage.kt b/lib/collages/src/main/java/com/t8rin/collages/frames/TenFrameImage.kt new file mode 100644 index 0000000..33d35d9 --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/frames/TenFrameImage.kt @@ -0,0 +1,582 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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]) } + ) + } + } +} \ No newline at end of file diff --git a/lib/collages/src/main/java/com/t8rin/collages/frames/ThreeFrameImage.kt b/lib/collages/src/main/java/com/t8rin/collages/frames/ThreeFrameImage.kt new file mode 100644 index 0000000..ea01b8c --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/frames/ThreeFrameImage.kt @@ -0,0 +1,1865 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + ) + } +} \ No newline at end of file diff --git a/lib/collages/src/main/java/com/t8rin/collages/frames/TwoFrameImage.kt b/lib/collages/src/main/java/com/t8rin/collages/frames/TwoFrameImage.kt new file mode 100644 index 0000000..eed1f96 --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/frames/TwoFrameImage.kt @@ -0,0 +1,245 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + } +} diff --git a/lib/collages/src/main/java/com/t8rin/collages/model/CollageLayout.kt b/lib/collages/src/main/java/com/t8rin/collages/model/CollageLayout.kt new file mode 100644 index 0000000..f353fac --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/model/CollageLayout.kt @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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() +) \ No newline at end of file diff --git a/lib/collages/src/main/java/com/t8rin/collages/public/CollageConstants.kt b/lib/collages/src/main/java/com/t8rin/collages/public/CollageConstants.kt new file mode 100644 index 0000000..5609e93 --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/public/CollageConstants.kt @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + } +} \ No newline at end of file diff --git a/lib/collages/src/main/java/com/t8rin/collages/utils/CollageLayoutFactory.kt b/lib/collages/src/main/java/com/t8rin/collages/utils/CollageLayoutFactory.kt new file mode 100644 index 0000000..c7290dd --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/utils/CollageLayoutFactory.kt @@ -0,0 +1,471 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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() }, + ) + +} diff --git a/lib/collages/src/main/java/com/t8rin/collages/utils/GeometryUtils.kt b/lib/collages/src/main/java/com/t8rin/collages/utils/GeometryUtils.kt new file mode 100644 index 0000000..3b39121 --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/utils/GeometryUtils.kt @@ -0,0 +1,861 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 +} diff --git a/lib/collages/src/main/java/com/t8rin/collages/utils/ImageDecoder.kt b/lib/collages/src/main/java/com/t8rin/collages/utils/ImageDecoder.kt new file mode 100644 index 0000000..37b5179 --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/utils/ImageDecoder.kt @@ -0,0 +1,58 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) + } + } + } + +} diff --git a/lib/collages/src/main/java/com/t8rin/collages/utils/ParamsManager.kt b/lib/collages/src/main/java/com/t8rin/collages/utils/ParamsManager.kt new file mode 100644 index 0000000..cb33203 --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/utils/ParamsManager.kt @@ -0,0 +1,98 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) + } +} \ No newline at end of file diff --git a/lib/collages/src/main/java/com/t8rin/collages/utils/ParamsManagerBuilder.kt b/lib/collages/src/main/java/com/t8rin/collages/utils/ParamsManagerBuilder.kt new file mode 100644 index 0000000..32d33c3 --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/utils/ParamsManagerBuilder.kt @@ -0,0 +1,152 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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() + } +} diff --git a/lib/collages/src/main/java/com/t8rin/collages/utils/PreviewCollageGeneration.kt b/lib/collages/src/main/java/com/t8rin/collages/utils/PreviewCollageGeneration.kt new file mode 100644 index 0000000..24d57f2 --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/utils/PreviewCollageGeneration.kt @@ -0,0 +1,196 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + ) + } + } + } + } +} \ No newline at end of file diff --git a/lib/collages/src/main/java/com/t8rin/collages/view/FrameImageView.kt b/lib/collages/src/main/java/com/t8rin/collages/view/FrameImageView.kt new file mode 100644 index 0000000..d365fb3 --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/view/FrameImageView.kt @@ -0,0 +1,937 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + } +} diff --git a/lib/collages/src/main/java/com/t8rin/collages/view/FramePhotoLayout.kt b/lib/collages/src/main/java/com/t8rin/collages/view/FramePhotoLayout.kt new file mode 100644 index 0000000..760e900 --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/view/FramePhotoLayout.kt @@ -0,0 +1,489 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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.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) + view.startDragAndDrop(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) + } + +} diff --git a/lib/collages/src/main/java/com/t8rin/collages/view/MultiTouchHandler.kt b/lib/collages/src/main/java/com/t8rin/collages/view/MultiTouchHandler.kt new file mode 100644 index 0000000..ebe825f --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/view/MultiTouchHandler.kt @@ -0,0 +1,278 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + } + } +} diff --git a/lib/collages/src/main/java/com/t8rin/collages/view/PhotoItem.kt b/lib/collages/src/main/java/com/t8rin/collages/view/PhotoItem.kt new file mode 100644 index 0000000..cc06e6d --- /dev/null +++ b/lib/collages/src/main/java/com/t8rin/collages/view/PhotoItem.kt @@ -0,0 +1,69 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + } +} diff --git a/lib/colors/.gitignore b/lib/colors/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lib/colors/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/colors/build.gradle.kts b/lib/colors/build.gradle.kts new file mode 100644 index 0000000..b43f104 --- /dev/null +++ b/lib/colors/build.gradle.kts @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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(projects.core.resources) + + implementation(libs.androidx.palette.ktx) + + implementation(projects.lib.gesture) + implementation(projects.lib.image) + implementation(projects.lib.zoomable) +} \ No newline at end of file diff --git a/lib/colors/src/main/AndroidManifest.xml b/lib/colors/src/main/AndroidManifest.xml new file mode 100644 index 0000000..205decf --- /dev/null +++ b/lib/colors/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/lib/colors/src/main/assets/color_names.json b/lib/colors/src/main/assets/color_names.json new file mode 100644 index 0000000..c955414 --- /dev/null +++ b/lib/colors/src/main/assets/color_names.json @@ -0,0 +1 @@ +{"#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"} \ No newline at end of file diff --git a/lib/colors/src/main/java/com/t8rin/colors/ImageColorDetector.kt b/lib/colors/src/main/java/com/t8rin/colors/ImageColorDetector.kt new file mode 100644 index 0000000..2c45280 --- /dev/null +++ b/lib/colors/src/main/java/com/t8rin/colors/ImageColorDetector.kt @@ -0,0 +1,551 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 android.graphics.Matrix +import androidx.compose.animation.Animatable +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.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +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.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.draw.scale +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.Outline +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.graphics.asAndroidPath +import androidx.compose.ui.graphics.asComposePath +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.DrawStyle +import androidx.compose.ui.graphics.drawscope.Fill +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.drawscope.withTransform +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.input.pointer.PointerInputChange +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.IntRect +import androidx.compose.ui.unit.LayoutDirection +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 com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +import kotlinx.coroutines.delay +import net.engawapg.lib.zoomable.ZoomState +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, + zoomState: ZoomState? = rememberZoomState(maxScale = 20f) +) { + var pickerOffset by remember { + mutableStateOf(Offset.Unspecified) + } + + val magnifierEnabled by remember(zoomState?.scale, isMagnifierEnabled, panEnabled) { + derivedStateOf { + (zoomState?.scale ?: 1f) <= 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 ?: 1f), + cornerRadius = 50.dp, + elevation = 2.dp + ) + } else Modifier + ) + .then( + zoomState?.let { + Modifier.zoomable( + zoomState = zoomState, + zoomEnabled = (globalTouchPointersCount >= 2 || panEnabled), + enableOneFingerZoom = panEnabled, + onDoubleTap = { pos -> + if (panEnabled) zoomState.defaultZoomOnDoubleTap(pos) + } + ) + } ?: Modifier + ), + 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 ?: 1f, + offset = pickerOffset + ) + } + } +} + +@Composable +internal fun ColorSelectionDrawing( + modifier: Modifier, + selectedColor: Color = Color.Black, + zoom: Float = 1f, + offset: Offset, +) { + val isDark = selectedColor.luminance() <= 0.3f + + val darkOuter = MaterialTheme.colorScheme.primaryFixedDim + val darkInner = MaterialTheme.colorScheme.onPrimaryFixed + + val outerColor by animateColorAsState( + targetValue = if (isDark) { + darkOuter + } else { + darkInner + }, + label = "outerColor" + ) + + val innerColor by animateColorAsState( + targetValue = if (isDark) { + darkInner + } else { + darkOuter + }, + label = "innerColor" + ) + + + val safeZoom = zoom.coerceAtLeast(0.01f) + + val density = LocalDensity.current + val dash = with(density) { 7.8.dp.toPx() } + val gap = with(density) { 8.dp.toPx() } + val phase = (dash + gap) * 3 + + val pathEffect = rememberAnimatedBorder( + intervals = floatArrayOf(dash / safeZoom, gap / safeZoom), + phase = phase / safeZoom, + repeatDuration = 4000 + ) + + Canvas(modifier = modifier.fillMaxSize()) { + if (!offset.isSpecified) return@Canvas + + val radius = 9.dp.toPx() / safeZoom + + fun drawStarOutline( + starSize: Float, + color: Color, + style: DrawStyle = Fill, + ) { + val outline = MaterialStarShape.createOutline( + size = Size(starSize, starSize), + layoutDirection = layoutDirection, + density = this + ) + + withTransform( + transformBlock = { + translate( + left = offset.x - starSize / 2f, + top = offset.y - starSize / 2f + ) + } + ) { + drawOutline( + outline = outline, + color = color, + style = style + ) + } + } + + drawStarOutline( + starSize = radius * 3.25f, + color = outerColor, + style = Stroke( + width = radius * 0.4f + ) + ) + + drawStarOutline( + starSize = radius * 3.25f, + color = innerColor, + style = Stroke( + width = radius * 0.3f, + pathEffect = pathEffect + ) + ) + + drawStarOutline( + starSize = radius * 1f, + color = outerColor + ) + + drawStarOutline( + starSize = radius * 0.9f, + color = innerColor + ) + + drawStarOutline( + starSize = radius * 0.5f, + color = outerColor + ) + } +} + +/** + * 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) + +@Suppress("SameParameterValue") +@Composable +private 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 +private 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 +} + +private 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() + ) + } +} + +@Preview +@Composable +private fun Preview() = MaterialTheme { + val selected = remember { Animatable(Color.Black) } + + LaunchedEffect(Unit) { + while (true) { + selected.animateTo(Color.Black) + delay(3000) + selected.animateTo(Color.White) + delay(3000) + } + } + BoxWithConstraints(modifier = Modifier.background(selected.value)) { + ColorSelectionDrawing( + modifier = Modifier + .fillMaxWidth() + .scale(10f), + selectedColor = selected.value, + offset = Offset( + x = constraints.maxWidth / 2f, + y = constraints.maxHeight / 2f + ) + ) + } +} \ No newline at end of file diff --git a/lib/colors/src/main/java/com/t8rin/colors/ImageColorPaletteState.kt b/lib/colors/src/main/java/com/t8rin/colors/ImageColorPaletteState.kt new file mode 100644 index 0000000..49e6cde --- /dev/null +++ b/lib/colors/src/main/java/com/t8rin/colors/ImageColorPaletteState.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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] }, + ) + ) + } +} \ No newline at end of file diff --git a/lib/colors/src/main/java/com/t8rin/colors/model/ColorData.kt b/lib/colors/src/main/java/com/t8rin/colors/model/ColorData.kt new file mode 100644 index 0000000..e6e39de --- /dev/null +++ b/lib/colors/src/main/java/com/t8rin/colors/model/ColorData.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) +} \ No newline at end of file diff --git a/lib/colors/src/main/java/com/t8rin/colors/parser/ColorNameParser.kt b/lib/colors/src/main/java/com/t8rin/colors/parser/ColorNameParser.kt new file mode 100644 index 0000000..5c135e2 --- /dev/null +++ b/lib/colors/src/main/java/com/t8rin/colors/parser/ColorNameParser.kt @@ -0,0 +1,138 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} \ No newline at end of file diff --git a/lib/colors/src/main/java/com/t8rin/colors/util/ColorUtil.kt b/lib/colors/src/main/java/com/t8rin/colors/util/ColorUtil.kt new file mode 100644 index 0000000..a0c3fb2 --- /dev/null +++ b/lib/colors/src/main/java/com/t8rin/colors/util/ColorUtil.kt @@ -0,0 +1,294 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) + } + + fun Color.hex() = if (alpha == 1f) { + colorToHex(this) + } else { + colorToHexAlpha(this) + }.uppercase() + + /** + * 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 + } +} \ No newline at end of file diff --git a/lib/colors/src/main/java/com/t8rin/colors/util/HexRegex.kt b/lib/colors/src/main/java/com/t8rin/colors/util/HexRegex.kt new file mode 100644 index 0000000..7fdddae --- /dev/null +++ b/lib/colors/src/main/java/com/t8rin/colors/util/HexRegex.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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() \ No newline at end of file diff --git a/lib/colors/src/main/java/com/t8rin/colors/util/HexUtil.kt b/lib/colors/src/main/java/com/t8rin/colors/util/HexUtil.kt new file mode 100644 index 0000000..b43d128 --- /dev/null +++ b/lib/colors/src/main/java/com/t8rin/colors/util/HexUtil.kt @@ -0,0 +1,62 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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)) + } + +} \ No newline at end of file diff --git a/lib/colors/src/main/java/com/t8rin/colors/util/HexVisualTransformation.kt b/lib/colors/src/main/java/com/t8rin/colors/util/HexVisualTransformation.kt new file mode 100644 index 0000000..c7455d4 --- /dev/null +++ b/lib/colors/src/main/java/com/t8rin/colors/util/HexVisualTransformation.kt @@ -0,0 +1,88 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + } + } +} diff --git a/lib/colors/src/main/java/com/t8rin/colors/util/RGBUtil.kt b/lib/colors/src/main/java/com/t8rin/colors/util/RGBUtil.kt new file mode 100644 index 0000000..abd3617 --- /dev/null +++ b/lib/colors/src/main/java/com/t8rin/colors/util/RGBUtil.kt @@ -0,0 +1,412 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + +} \ No newline at end of file diff --git a/lib/colors/src/main/java/com/t8rin/colors/util/RoundngUtil.kt b/lib/colors/src/main/java/com/t8rin/colors/util/RoundngUtil.kt new file mode 100644 index 0000000..8ac72aa --- /dev/null +++ b/lib/colors/src/main/java/com/t8rin/colors/util/RoundngUtil.kt @@ -0,0 +1,57 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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() diff --git a/lib/cropper/.gitignore b/lib/cropper/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lib/cropper/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/cropper/build.gradle.kts b/lib/cropper/build.gradle.kts new file mode 100644 index 0000000..0895d64 --- /dev/null +++ b/lib/cropper/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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(libs.coil) + implementation(libs.toolbox.exif) + implementation(projects.lib.gesture) + implementation(projects.core.resources) +} \ No newline at end of file diff --git a/lib/cropper/src/main/AndroidManifest.xml b/lib/cropper/src/main/AndroidManifest.xml new file mode 100644 index 0000000..44008a4 --- /dev/null +++ b/lib/cropper/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/CropModifier.kt b/lib/cropper/src/main/java/com/t8rin/cropper/CropModifier.kt new file mode 100644 index 0000000..5e56d4d --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/CropModifier.kt @@ -0,0 +1,336 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.calculateCentroid +import androidx.compose.foundation.gestures.calculatePan +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.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.platform.debugInspectorInfo +import androidx.compose.ui.util.fastAny +import androidx.compose.ui.util.fastForEach +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, + enableOneFingerZoom: Boolean = true, + 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, enableOneFingerZoom) { + fun onDoubleTap(offset: Offset) { + coroutineScope.launch { + zoomLevel = getNextZoomLevel(zoomLevel) + val newZoom = zoomOnDoubleTap(zoomLevel) + cropState.onDoubleTap( + offset = offset, + zoom = newZoom + ) { + onGestureEnd?.invoke(cropState.cropData) + } + } + } + + if (enableOneFingerZoom) { + detectOneFingerZoomGestures( + onDoubleTap = ::onDoubleTap, + onGestureStart = { + onGestureStart?.invoke(cropState.cropData) + }, + onGestureEnd = { + coroutineScope.launch { + cropState.onGestureEnd { + onGestureEnd?.invoke(cropState.cropData) + } + } + }, + onZoom = { centroid, zoom, mainPointer, pointerList -> + coroutineScope.launch { + cropState.onGesture( + centroid = centroid, + panChange = Offset.Zero, + zoomChange = zoom, + rotationChange = 0f, + mainPointer = mainPointer, + changes = pointerList + ) + } + onGesture?.invoke(cropState.cropData) + } + ) + } else { + detectTapGestures(onDoubleTap = ::onDoubleTap) + } + } + + 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 + properties["enableOneFingerZoom"] = enableOneFingerZoom + } +) + +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) + } + } + +private suspend fun PointerInputScope.detectOneFingerZoomGestures( + onDoubleTap: (Offset) -> Unit, + onGestureStart: () -> Unit, + onZoom: ( + centroid: Offset, + zoom: Float, + mainPointer: PointerInputChange, + changes: List + ) -> Unit, + onGestureEnd: () -> Unit +) = awaitEachGesture { + val firstDown = awaitFirstDown(requireUnconsumed = false) + var firstUp = firstDown + var hasMoved = false + var isMultiTouch = false + var isCanceled = false + + do { + val event = awaitPointerEvent() + if (event.changes.fastAny { it.isConsumed }) { + isCanceled = true + break + } + if (event.changes.fastAny { it.positionChanged() }) { + hasMoved = true + } + if (event.changes.size > 1) { + isMultiTouch = true + } + firstUp = event.changes.first() + } while (event.changes.fastAny { it.pressed }) + + val isTap = !hasMoved && + !isMultiTouch && + !isCanceled && + firstUp.uptimeMillis - firstDown.uptimeMillis <= viewConfiguration.longPressTimeoutMillis + + if (isTap) { + val secondDown = awaitSecondDown(firstUp) + if (secondDown != null) { + var secondUp: PointerInputChange = secondDown + var isZooming = false + var isSecondMultiTouch = false + var isSecondCanceled = false + + do { + val event = awaitPointerEvent(PointerEventPass.Initial) + if (event.changes.fastAny { it.isConsumed }) { + isSecondCanceled = true + break + } + + if (event.changes.size > 1) { + isSecondMultiTouch = true + } + + val panChange = event.calculatePan() + if (panChange != Offset.Zero) { + if (!isZooming) { + onGestureStart() + } + isZooming = true + val zoomChange = 1f + panChange.y * 0.004f + if (zoomChange != 1f) { + onZoom( + event.calculateCentroid(useCurrent = true), + zoomChange, + event.changes.first(), + event.changes + ) + event.changes.fastForEach { + if (it.positionChanged()) { + it.consume() + } + } + } + } + secondUp = event.changes.first() + } while (event.changes.fastAny { it.pressed }) + + if (isZooming && !isSecondMultiTouch && !isSecondCanceled) { + onGestureEnd() + } else if (!isSecondMultiTouch && + !isSecondCanceled && + secondUp.uptimeMillis - secondDown.uptimeMillis <= + viewConfiguration.longPressTimeoutMillis + ) { + onDoubleTap(secondUp.position) + } + } + } +} + +private suspend fun AwaitPointerEventScope.awaitSecondDown( + firstUp: PointerInputChange +): PointerInputChange? = + withTimeoutOrNull(viewConfiguration.doubleTapTimeoutMillis) { + val minUptime = firstUp.uptimeMillis + viewConfiguration.doubleTapMinTimeMillis + var change: PointerInputChange + do { + change = awaitFirstDown() + } while (change.uptimeMillis < minUptime) + change + } diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/ImageCropper.kt b/lib/cropper/src/main/java/com/t8rin/cropper/ImageCropper.kt new file mode 100644 index 0000000..422c8b5 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/ImageCropper.kt @@ -0,0 +1,440 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 android.net.Uri +import androidx.compose.animation.AnimatedVisibility +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.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntRect +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 com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +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, + sourceImageUri: Uri? = null, + sourceImageSize: IntSize = IntSize(imageBitmap.width, imageBitmap.height), + contentDescription: String? = null, + cropStyle: CropStyle = CropDefaults.style(), + cropProperties: CropProperties, + filterQuality: FilterQuality = DrawScope.DefaultFilterQuality, + crop: Boolean = false, + enableOneFingerZoom: Boolean = true, + isOverlayDraggable: Boolean = true, + onCropStart: () -> Unit, + onZoomChange: (Float) -> Unit, + onCropSuccess: (Uri?) -> 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, + isOverlayDraggable = isOverlayDraggable, + keys = resetKeys + ) + + LaunchedEffect(cropState, isOverlayDraggable) { + if (cropState is DynamicCropState) { + cropState.isOverlayDraggable = isOverlayDraggable + } + } + + 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 = crop, + scaledImageBitmap = scaledImageBitmap, + sourceImageUri = sourceImageUri, + sourceImageSize = sourceImageSize, + sourceImageVisibleRect = rect, + previewImageSize = IntSize(imageBitmap.width, imageBitmap.height), + cropRect = cropState.cropRect, + cropOutline = cropOutline, + onCropStart = onCropStart, + onCropSuccess = onCropSuccess + ) + + val imageModifier = Modifier + .size(containerWidth, containerHeight) + .crop( + keys = resetKeys, + cropState = cropState, + enableOneFingerZoom = enableOneFingerZoom + ) + + 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, + sourceImageUri: Uri?, + sourceImageSize: IntSize, + sourceImageVisibleRect: IntRect, + previewImageSize: IntSize, + cropRect: Rect, + cropOutline: CropOutline, + onCropStart: () -> Unit, + onCropSuccess: (Uri?) -> Unit +) { + + val context = LocalContext.current + val density = LocalDensity.current + val layoutDirection = LocalLayoutDirection.current + + // Crop Agent is responsible for cropping image + val cropAgent = remember { CropAgent() } + + LaunchedEffect(crop) { + if (crop) { + flow { + val previewSize = IntSize(scaledImageBitmap.width, scaledImageBitmap.height) + emit( + cropAgent.cropToCache( + context = context, + imageUri = sourceImageUri, + fallbackImageBitmap = scaledImageBitmap, + fallbackCropRect = cropRect, + sourceCropRect = cropRect.scaleTo( + sourceSize = sourceImageSize, + previewSize = previewSize, + sourceImageVisibleRect = sourceImageVisibleRect, + previewImageSize = previewImageSize + ), + cropOutline = cropOutline, + layoutDirection = layoutDirection, + density = density + ) + ) + } + .flowOn(Dispatchers.Default) + .onStart { + onCropStart() + delay(400) + } + .onEach { + onCropSuccess(it) + } + .launchIn(this) + } + } +} + +private fun Rect.scaleTo( + sourceSize: IntSize, + previewSize: IntSize, + sourceImageVisibleRect: IntRect, + previewImageSize: IntSize +): Rect { + if (sourceSize == IntSize.Zero || previewSize == IntSize.Zero) return this + + val widthScale = sourceSize.width.toFloat() / previewImageSize.width.coerceAtLeast(1) + val heightScale = sourceSize.height.toFloat() / previewImageSize.height.coerceAtLeast(1) + + return Rect( + left = (sourceImageVisibleRect.left + left) * widthScale, + top = (sourceImageVisibleRect.top + top) * heightScale, + right = (sourceImageVisibleRect.left + right) * widthScale, + bottom = (sourceImageVisibleRect.top + bottom) * heightScale + ) +} + +@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, + ) +} \ No newline at end of file diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/TouchRegion.kt b/lib/cropper/src/main/java/com/t8rin/cropper/TouchRegion.kt new file mode 100644 index 0000000..0b2eb3a --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/TouchRegion.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/crop/CropAgent.kt b/lib/cropper/src/main/java/com/t8rin/cropper/crop/CropAgent.kt new file mode 100644 index 0000000..76ecaa2 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/crop/CropAgent.kt @@ -0,0 +1,325 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.BitmapRegionDecoder +import android.net.Uri +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 androidx.core.net.toUri +import coil3.imageLoader +import coil3.request.ImageRequest +import coil3.request.allowHardware +import coil3.toBitmap +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.exif.ExifInterface +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.File +import java.io.FileOutputStream +import android.graphics.Rect as AndroidRect +import coil3.size.Size as CoilSize + + +/** + * 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 + } + + suspend fun cropToCache( + context: Context, + imageUri: Uri?, + fallbackImageBitmap: ImageBitmap, + fallbackCropRect: Rect, + sourceCropRect: Rect, + cropOutline: CropOutline, + layoutDirection: LayoutDirection, + density: Density, + ): Uri? = withContext(Dispatchers.Default) { + runCatching { + context.loadCroppedBitmap(imageUri, sourceCropRect)?.let { sourceBitmap -> + crop( + imageBitmap = sourceBitmap.asImageBitmap(), + cropRect = Rect( + left = 0f, + top = 0f, + right = sourceBitmap.width.toFloat(), + bottom = sourceBitmap.height.toFloat() + ), + cropOutline = cropOutline, + layoutDirection = layoutDirection, + density = density + ).asAndroidBitmap().cacheAsPng(context) + } ?: run { + val sourceBitmap = context.loadBitmap(imageUri)?.asImageBitmap() + crop( + imageBitmap = sourceBitmap ?: fallbackImageBitmap, + cropRect = if (sourceBitmap != null) { + sourceCropRect.coerceIn(sourceBitmap.width, sourceBitmap.height) + } else { + fallbackCropRect.coerceIn( + fallbackImageBitmap.width, + fallbackImageBitmap.height + ) + }, + cropOutline = cropOutline, + layoutDirection = layoutDirection, + density = density + ).asAndroidBitmap().cacheAsPng(context) + } + }.getOrNull() ?: runCatching { + crop( + imageBitmap = fallbackImageBitmap, + cropRect = fallbackCropRect.coerceIn( + fallbackImageBitmap.width, + fallbackImageBitmap.height + ), + cropOutline = cropOutline, + layoutDirection = layoutDirection, + density = density + ).asAndroidBitmap().cacheAsPng(context) + }.getOrNull() + } + + 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() + } + } + } + } +} + +@Suppress("DEPRECATION") +private fun Context.loadCroppedBitmap( + uri: Uri?, + cropRect: Rect +): Bitmap? { + uri ?: return null + if (!hasNormalExifOrientation(uri)) return null + + return runCatching { + contentResolver.openInputStream(uri)?.use { input -> + val decoder = BitmapRegionDecoder.newInstance(input, false) ?: return null + try { + decoder.decodeRegion( + cropRect.toAndroidRect(decoder.width, decoder.height), + BitmapFactory.Options().apply { + inPreferredConfig = Bitmap.Config.ARGB_8888 + } + ) + } finally { + decoder.recycle() + } + } + }.getOrNull() +} + +private fun Context.hasNormalExifOrientation(uri: Uri): Boolean { + return runCatching { + contentResolver.openInputStream(uri)?.use { input -> + ExifInterface(input).getAttributeInt( + ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL + ) + } == ExifInterface.ORIENTATION_NORMAL + }.getOrDefault(true) +} + +private suspend fun Context.loadBitmap(uri: Uri?): Bitmap? { + uri ?: return null + + return imageLoader.execute( + ImageRequest.Builder(this) + .data(uri) + .size(CoilSize.ORIGINAL) + .allowHardware(false) + .build() + ).image?.toBitmap() +} + +private fun Bitmap.cacheAsPng(context: Context): Uri { + val file = File( + File(context.cacheDir, "temp").apply(File::mkdirs), + "temp_crop_${System.currentTimeMillis()}.png" + ) + + FileOutputStream(file).use { output -> + check(compress(Bitmap.CompressFormat.PNG, 100, output)) + } + + return file.toUri() +} + +private fun Rect.coerceIn( + width: Int, + height: Int +): Rect { + val left = left.coerceIn(0f, (width - 1).coerceAtLeast(0).toFloat()) + val top = top.coerceIn(0f, (height - 1).coerceAtLeast(0).toFloat()) + val right = right.coerceIn(left + 1f, width.toFloat()) + val bottom = bottom.coerceIn(top + 1f, height.toFloat()) + + return Rect(left, top, right, bottom) +} + +private fun Rect.toAndroidRect( + width: Int, + height: Int +): AndroidRect { + val safeRect = coerceIn(width, height) + + return AndroidRect( + safeRect.left.toInt(), + safeRect.top.toInt(), + safeRect.right.toInt(), + safeRect.bottom.toInt() + ) +} diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/draw/ImageDrawCanvas.kt b/lib/cropper/src/main/java/com/t8rin/cropper/draw/ImageDrawCanvas.kt new file mode 100644 index 0000000..72f35d0 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/draw/ImageDrawCanvas.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + ) + ) + } +} diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/draw/Overlay.kt b/lib/cropper/src/main/java/com/t8rin/cropper/draw/Overlay.kt new file mode 100644 index 0000000..324f630 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/draw/Overlay.kt @@ -0,0 +1,427 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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.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 +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe + +/** + * 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).compositeOverSafe(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) + } +} \ No newline at end of file diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/image/ImageScope.kt b/lib/cropper/src/main/java/com/t8rin/cropper/image/ImageScope.kt new file mode 100644 index 0000000..658c7d3 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/image/ImageScope.kt @@ -0,0 +1,145 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} \ No newline at end of file diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/image/ImageWithConstraints.kt b/lib/cropper/src/main/java/com/t8rin/cropper/image/ImageWithConstraints.kt new file mode 100644 index 0000000..a156759 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/image/ImageWithConstraints.kt @@ -0,0 +1,250 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + ) + } + } +} diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/model/AspectRatios.kt b/lib/cropper/src/main/java/com/t8rin/cropper/model/AspectRatios.kt new file mode 100644 index 0000000..c166852 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/model/AspectRatios.kt @@ -0,0 +1,71 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) + ) +) \ No newline at end of file diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/model/CropAspectRatio.kt b/lib/cropper/src/main/java/com/t8rin/cropper/model/CropAspectRatio.kt new file mode 100644 index 0000000..b6d86f2 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/model/CropAspectRatio.kt @@ -0,0 +1,44 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) + } +} \ No newline at end of file diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/model/CropData.kt b/lib/cropper/src/main/java/com/t8rin/cropper/model/CropData.kt new file mode 100644 index 0000000..a391a08 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/model/CropData.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +) diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/model/CropOutline.kt b/lib/cropper/src/main/java/com/t8rin/cropper/model/CropOutline.kt new file mode 100644 index 0000000..a8db104 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/model/CropOutline.kt @@ -0,0 +1,151 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/model/CropOutlineContainer.kt b/lib/cropper/src/main/java/com/t8rin/cropper/model/CropOutlineContainer.kt new file mode 100644 index 0000000..a7668ab --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/model/CropOutlineContainer.kt @@ -0,0 +1,87 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/model/CropOutlineProperties.kt b/lib/cropper/src/main/java/com/t8rin/cropper/model/CropOutlineProperties.kt new file mode 100644 index 0000000..0c86eca --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/model/CropOutlineProperties.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +) diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/model/OutlineType.kt b/lib/cropper/src/main/java/com/t8rin/cropper/model/OutlineType.kt new file mode 100644 index 0000000..796057b --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/model/OutlineType.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/settings/CropDefaults.kt b/lib/cropper/src/main/java/com/t8rin/cropper/settings/CropDefaults.kt new file mode 100644 index 0000000..6429314 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/settings/CropDefaults.kt @@ -0,0 +1,158 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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.Black.copy(0.55f) +private val DefaultOverlayColor = Color.Gray +private val DefaultHandleColor = Color.White \ No newline at end of file diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/settings/CropType.kt b/lib/cropper/src/main/java/com/t8rin/cropper/settings/CropType.kt new file mode 100644 index 0000000..ec88aaf --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/settings/CropType.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} \ No newline at end of file diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/settings/Paths.kt b/lib/cropper/src/main/java/com/t8rin/cropper/settings/Paths.kt new file mode 100644 index 0000000..32bbcd5 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/settings/Paths.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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() + } +} + diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/state/CropState.kt b/lib/cropper/src/main/java/com/t8rin/cropper/state/CropState.kt new file mode 100644 index 0000000..aaca7c1 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/state/CropState.kt @@ -0,0 +1,100 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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, + isOverlayDraggable: Boolean = true, + 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, + isOverlayDraggable = isOverlayDraggable, + ) + } + } + } +} diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/state/CropStateImpl.kt b/lib/cropper/src/main/java/com/t8rin/cropper/state/CropStateImpl.kt new file mode 100644 index 0000000..c6be89b --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/state/CropStateImpl.kt @@ -0,0 +1,497 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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(250) + ) { + 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) + ) + } + + /** + * 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(250) + ) { + // 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) + ) + } +} diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/state/DynamicCropState.kt b/lib/cropper/src/main/java/com/t8rin/cropper/state/DynamicCropState.kt new file mode 100644 index 0000000..cc1850d --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/state/DynamicCropState.kt @@ -0,0 +1,826 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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, + internal var isOverlayDraggable: 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 + ).let { + if (it == TouchRegion.Inside && !isOverlayDraggable) TouchRegion.None else it + } + + // 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 + } + } +} diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/state/StaticCropState.kt b/lib/cropper/src/main/java/com/t8rin/cropper/state/StaticCropState.kt new file mode 100644 index 0000000..240101e --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/state/StaticCropState.kt @@ -0,0 +1,144 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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() + } +} diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/state/TransformState.kt b/lib/cropper/src/main/java/com/t8rin/cropper/state/TransformState.kt new file mode 100644 index 0000000..5df2c2b --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/state/TransformState.kt @@ -0,0 +1,272 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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(250) + ) = 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(250) + ) { + if (pannable && pan.x != panX) { + animatablePanX.animateTo(panX, animationSpec) + } + } + + internal suspend fun animatePanYto( + panY: Float, + animationSpec: AnimationSpec = tween(250) + ) { + if (pannable && pan.y != panY) { + animatablePanY.animateTo(panY, animationSpec) + } + } + + internal suspend fun animateZoomTo( + zoom: Float, + animationSpec: AnimationSpec = tween(250) + ) { + if (zoomable && this.zoom != zoom) { + val newZoom = zoom.coerceIn(zoomMin, zoomMax) + animatableZoom.animateTo(newZoom, animationSpec) + } + } + + suspend fun animateRotationTo( + rotation: Float, + animationSpec: AnimationSpec = tween(250) + ) { + 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() + } +} diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/util/DimensionUtil.kt b/lib/cropper/src/main/java/com/t8rin/cropper/util/DimensionUtil.kt new file mode 100644 index 0000000..180cd72 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/util/DimensionUtil.kt @@ -0,0 +1,61 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/util/DrawScopeUtils.kt b/lib/cropper/src/main/java/com/t8rin/cropper/util/DrawScopeUtils.kt new file mode 100644 index 0000000..6694ae9 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/util/DrawScopeUtils.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) + } +} \ No newline at end of file diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/util/ImageContentScaleUtil.kt b/lib/cropper/src/main/java/com/t8rin/cropper/util/ImageContentScaleUtil.kt new file mode 100644 index 0000000..b980173 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/util/ImageContentScaleUtil.kt @@ -0,0 +1,105 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) +} diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/util/OffsetUtil.kt b/lib/cropper/src/main/java/com/t8rin/cropper/util/OffsetUtil.kt new file mode 100644 index 0000000..a759ee2 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/util/OffsetUtil.kt @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) \ No newline at end of file diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/util/ShapeUtils.kt b/lib/cropper/src/main/java/com/t8rin/cropper/util/ShapeUtils.kt new file mode 100644 index 0000000..56c2b85 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/util/ShapeUtils.kt @@ -0,0 +1,189 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) +} \ No newline at end of file diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/util/ZoomLevel.kt b/lib/cropper/src/main/java/com/t8rin/cropper/util/ZoomLevel.kt new file mode 100644 index 0000000..f03606a --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/util/ZoomLevel.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/util/ZoomUtil.kt b/lib/cropper/src/main/java/com/t8rin/cropper/util/ZoomUtil.kt new file mode 100644 index 0000000..73bb099 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/util/ZoomUtil.kt @@ -0,0 +1,88 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/widget/AspectRatioSlectionCard.kt b/lib/cropper/src/main/java/com/t8rin/cropper/widget/AspectRatioSlectionCard.kt new file mode 100644 index 0000000..6764fd9 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/widget/AspectRatioSlectionCard.kt @@ -0,0 +1,109 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + ) + } + } + } +} diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/widget/CropFrameDisplayCard.kt b/lib/cropper/src/main/java/com/t8rin/cropper/widget/CropFrameDisplayCard.kt new file mode 100644 index 0000000..c6813ef --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/widget/CropFrameDisplayCard.kt @@ -0,0 +1,196 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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.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 com.t8rin.imagetoolbox.core.resources.Icons +import androidx.compose.material3.Icon +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.imagetoolbox.core.resources.icons.Favorite +import com.t8rin.imagetoolbox.core.resources.icons.Image +import com.t8rin.imagetoolbox.core.resources.icons.Star + +@Composable +fun CropFrameDisplayCard( + modifier: Modifier = Modifier, + scale: Float, + outlineColor: Color, + fontSize: TextUnit = 12.sp, + title: String, + cropOutline: CropOutline, +) { + 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, + content = {} + ) + + + 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.Star, + tint = color, + contentDescription = "Crop with Path" + ) + } else { + Icon( + modifier = Modifier + .matchParentSize() + .scale(1.3f), + imageVector = Icons.Outlined.Favorite, + 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() + } + } + } +} diff --git a/lib/cropper/src/main/java/com/t8rin/cropper/widget/GridImageLayout.kt b/lib/cropper/src/main/java/com/t8rin/cropper/widget/GridImageLayout.kt new file mode 100644 index 0000000..9591966 --- /dev/null +++ b/lib/cropper/src/main/java/com/t8rin/cropper/widget/GridImageLayout.kt @@ -0,0 +1,186 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + ) +} diff --git a/lib/curves/.gitignore b/lib/curves/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lib/curves/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/curves/build.gradle.kts b/lib/curves/build.gradle.kts new file mode 100644 index 0000000..8b49b5a --- /dev/null +++ b/lib/curves/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/lib/curves/src/main/AndroidManifest.xml b/lib/curves/src/main/AndroidManifest.xml new file mode 100644 index 0000000..205decf --- /dev/null +++ b/lib/curves/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditor.kt b/lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditor.kt new file mode 100644 index 0000000..4b61c90 --- /dev/null +++ b/lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditor.kt @@ -0,0 +1,458 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) + } + } +} \ No newline at end of file diff --git a/lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditorColors.kt b/lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditorColors.kt new file mode 100644 index 0000000..8cd928e --- /dev/null +++ b/lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditorColors.kt @@ -0,0 +1,29 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +) \ No newline at end of file diff --git a/lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditorDefaults.kt b/lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditorDefaults.kt new file mode 100644 index 0000000..c066632 --- /dev/null +++ b/lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditorDefaults.kt @@ -0,0 +1,47 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) +} \ No newline at end of file diff --git a/lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditorState.kt b/lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditorState.kt new file mode 100644 index 0000000..e40e263 --- /dev/null +++ b/lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditorState.kt @@ -0,0 +1,97 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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() \ No newline at end of file diff --git a/lib/curves/src/main/java/com/t8rin/curves/utils/Utils.kt b/lib/curves/src/main/java/com/t8rin/curves/utils/Utils.kt new file mode 100644 index 0000000..4e4e4bf --- /dev/null +++ b/lib/curves/src/main/java/com/t8rin/curves/utils/Utils.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) \ No newline at end of file diff --git a/lib/curves/src/main/java/com/t8rin/curves/view/PhotoFilterCurvesControl.kt b/lib/curves/src/main/java/com/t8rin/curves/view/PhotoFilterCurvesControl.kt new file mode 100644 index 0000000..e278dd6 --- /dev/null +++ b/lib/curves/src/main/java/com/t8rin/curves/view/PhotoFilterCurvesControl.kt @@ -0,0 +1,534 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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() + } + } +} \ No newline at end of file diff --git a/lib/curves/src/main/java/com/t8rin/curves/view/Rect.kt b/lib/curves/src/main/java/com/t8rin/curves/view/Rect.kt new file mode 100644 index 0000000..01136d5 --- /dev/null +++ b/lib/curves/src/main/java/com/t8rin/curves/view/Rect.kt @@ -0,0 +1,41 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + } +} \ No newline at end of file diff --git a/lib/documentscanner/.gitignore b/lib/documentscanner/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lib/documentscanner/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/documentscanner/build.gradle.kts b/lib/documentscanner/build.gradle.kts new file mode 100644 index 0000000..e39221a --- /dev/null +++ b/lib/documentscanner/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/lib/documentscanner/src/main/AndroidManifest.xml b/lib/documentscanner/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7ff5408 --- /dev/null +++ b/lib/documentscanner/src/main/AndroidManifest.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScanner.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScanner.kt new file mode 100644 index 0000000..4c02cc7 --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScanner.kt @@ -0,0 +1,211 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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 + * @param cropperHandleColor color of cropper corner handles + * @param cropperHandleOutlineColor color of cropper corner outer outline + * @param cropperFrameColor color of cropper frame lines + * @param cropperStrokeWidthDp width of cropper lines in dp + * @param buttonTintColor color of scanner button icons + * @param buttonContainerColor color of scanner button containers + * @param doneButtonTintColor color of finish button icon + * @param doneButtonContainerColor color of finish button inner circle + * @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, + private val cropperHandleColor: Int? = null, + private val cropperHandleOutlineColor: Int? = null, + private val cropperFrameColor: Int? = null, + private val cropperStrokeWidthDp: Float? = null, + private val buttonTintColor: Int? = null, + private val buttonContainerColor: Int? = null, + private val doneButtonTintColor: Int? = null, + private val doneButtonContainerColor: 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 + ) + cropperHandleColor?.let { + documentScanIntent.putExtra(DocumentScannerExtra.EXTRA_CROPPER_HANDLE_COLOR, it) + } + cropperHandleOutlineColor?.let { + documentScanIntent.putExtra( + DocumentScannerExtra.EXTRA_CROPPER_HANDLE_OUTLINE_COLOR, + it + ) + } + cropperFrameColor?.let { + documentScanIntent.putExtra(DocumentScannerExtra.EXTRA_CROPPER_FRAME_COLOR, it) + } + cropperStrokeWidthDp?.let { + documentScanIntent.putExtra(DocumentScannerExtra.EXTRA_CROPPER_STROKE_WIDTH_DP, it) + } + buttonTintColor?.let { + documentScanIntent.putExtra(DocumentScannerExtra.EXTRA_BUTTON_TINT_COLOR, it) + } + buttonContainerColor?.let { + documentScanIntent.putExtra(DocumentScannerExtra.EXTRA_BUTTON_CONTAINER_COLOR, it) + } + doneButtonTintColor?.let { + documentScanIntent.putExtra( + DocumentScannerExtra.EXTRA_DONE_BUTTON_TINT_COLOR, + it + ) + } + doneButtonContainerColor?.let { + documentScanIntent.putExtra( + DocumentScannerExtra.EXTRA_DONE_BUTTON_CONTAINER_COLOR, + it + ) + } + + 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 Throwable( + "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 Throwable("error - $error") + } + + // get an array with scanned document file paths + val croppedImageResults: ArrayList = + result.data?.getStringArrayListExtra( + "croppedImageResults" + ) ?: throw Throwable("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: Throwable) { + // 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()) + } +} diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScannerActivity.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScannerActivity.kt new file mode 100644 index 0000000..33e2725 --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScannerActivity.kt @@ -0,0 +1,591 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.graphics.Color +import android.net.Uri +import android.os.Bundle +import android.view.View +import android.view.WindowManager +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.FileProvider +import androidx.core.graphics.Insets +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +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.CircleButton +import com.websitebeaver.documentscanner.ui.DoneButton +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 + +/** + * 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 + + private var cropperHandleColor = Color.WHITE + private var cropperFrameColor = Color.WHITE + private var buttonTintColor = Color.WHITE + private var buttonContainerColor = Color.argb(128, 255, 255, 255) + private var cropperHandleOutlineColor = buttonContainerColor + private var cropperStrokeWidthDp = 1.2f + private var doneButtonTintColor = buttonTintColor + private var doneButtonContainerColor = buttonContainerColor + private var shouldApplyButtonColors = false + private var contentWidth = 0 + private var contentHeight = 0 + + /** + * 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: Throwable) { + finishIntentWithError( + "error starting OpenCV: ${exception.message}" + ) + } + + WindowCompat.setDecorFitsSystemWindows(window, false) + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + window.statusBarColor = Color.TRANSPARENT + window.navigationBarColor = Color.BLACK + WindowCompat.getInsetsController(window, window.decorView).apply { + isAppearanceLightStatusBars = false + isAppearanceLightNavigationBars = false + } + + // 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) + setupWindowInsets() + 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 Throwable( + "${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 Throwable( + "${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 Throwable( + "${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 Throwable( + "${DocumentScannerExtra.EXTRA_CROPPED_IMAGE_QUALITY} must be a number " + + "between 0 and 100" + ) + } + croppedImageQuality = it + } + + cropperHandleColor = intent.getIntExtra( + DocumentScannerExtra.EXTRA_CROPPER_HANDLE_COLOR, + cropperHandleColor + ) + cropperFrameColor = intent.getIntExtra( + DocumentScannerExtra.EXTRA_CROPPER_FRAME_COLOR, + cropperFrameColor + ) + intent.extras?.get(DocumentScannerExtra.EXTRA_CROPPER_STROKE_WIDTH_DP)?.let { + val strokeWidthDp = (it as? Number)?.toFloat() + ?: throw Throwable( + "${DocumentScannerExtra.EXTRA_CROPPER_STROKE_WIDTH_DP} must be a number" + ) + + if ( + strokeWidthDp <= 0f || + strokeWidthDp.isNaN() || + strokeWidthDp.isInfinite() + ) { + throw Throwable( + "${DocumentScannerExtra.EXTRA_CROPPER_STROKE_WIDTH_DP} must be greater than 0" + ) + } + + cropperStrokeWidthDp = strokeWidthDp + } + buttonTintColor = intent.getIntExtra( + DocumentScannerExtra.EXTRA_BUTTON_TINT_COLOR, + buttonTintColor + ) + buttonContainerColor = intent.getIntExtra( + DocumentScannerExtra.EXTRA_BUTTON_CONTAINER_COLOR, + buttonContainerColor + ) + cropperHandleOutlineColor = intent.getIntExtra( + DocumentScannerExtra.EXTRA_CROPPER_HANDLE_OUTLINE_COLOR, + buttonContainerColor + ) + doneButtonTintColor = intent.getIntExtra( + DocumentScannerExtra.EXTRA_DONE_BUTTON_TINT_COLOR, + buttonTintColor + ) + doneButtonContainerColor = intent.getIntExtra( + DocumentScannerExtra.EXTRA_DONE_BUTTON_CONTAINER_COLOR, + buttonContainerColor + ) + shouldApplyButtonColors = listOf( + DocumentScannerExtra.EXTRA_BUTTON_TINT_COLOR, + DocumentScannerExtra.EXTRA_BUTTON_CONTAINER_COLOR, + DocumentScannerExtra.EXTRA_DONE_BUTTON_TINT_COLOR, + DocumentScannerExtra.EXTRA_DONE_BUTTON_CONTAINER_COLOR + ).any(intent::hasExtra) + } catch (exception: Throwable) { + 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: CircleButton = findViewById(R.id.new_photo_button) + val completeDocumentScanButton: DoneButton = findViewById( + R.id.complete_document_scan_button + ) + val retakePhotoButton: CircleButton = findViewById(R.id.retake_photo_button) + + imageView.setCropperStyle( + handleColor = cropperHandleColor, + frameColor = cropperFrameColor, + handleOutlineColor = cropperHandleOutlineColor, + strokeWidthDp = cropperStrokeWidthDp + ) + if (shouldApplyButtonColors) { + retakePhotoButton.setColors( + containerColor = buttonContainerColor, + iconColor = buttonTintColor + ) + completeDocumentScanButton.setColors( + ringColor = buttonContainerColor, + circleColor = doneButtonContainerColor, + iconColor = doneButtonTintColor + ) + newPhotoButton.setColors( + containerColor = buttonContainerColor, + iconColor = buttonTintColor + ) + } + + newPhotoButton.onClick { onClickNew() } + completeDocumentScanButton.onClick { onClickDone() } + retakePhotoButton.onClick { onClickRetake() } + + // open camera, so user can snap document photo + try { + openCamera() + } catch (exception: Throwable) { + finishIntentWithError( + "error opening camera: ${exception.message}" + ) + } + } + + private fun setupWindowInsets() { + val root: View = findViewById(R.id.document_scanner_root) + val initialPaddingLeft = root.paddingLeft + val initialPaddingTop = root.paddingTop + val initialPaddingRight = root.paddingRight + val initialPaddingBottom = root.paddingBottom + + fun updateContentSize() { + contentWidth = (screenWidth - root.paddingLeft - root.paddingRight).coerceAtLeast(0) + contentHeight = (screenHeight - root.paddingTop - root.paddingBottom).coerceAtLeast(0) + } + + ViewCompat.setOnApplyWindowInsetsListener(root) { view, windowInsets -> + val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + val displayCutout = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) + val safeInsets = Insets.max(systemBars, displayCutout) + + view.setPadding( + initialPaddingLeft + safeInsets.left, + initialPaddingTop + safeInsets.top, + initialPaddingRight + safeInsets.right, + initialPaddingBottom + safeInsets.bottom + ) + updateContentSize() + windowInsets + } + + ViewCompat.requestApplyInsets(root) + updateContentSize() + } + + /** + * 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: CircleButton = 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: Throwable) { + 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, + contentWidth.takeIf { it > 0 } ?: screenWidth, + contentHeight.takeIf { it > 0 } ?: 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: Throwable) { + 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 + */ + 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: Throwable) { + 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: Throwable) { + 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() + } +} diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScannerFileProvider.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScannerFileProvider.kt new file mode 100644 index 0000000..5becf92 --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScannerFileProvider.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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() \ No newline at end of file diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DefaultSetting.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DefaultSetting.kt new file mode 100644 index 0000000..cca9bcc --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DefaultSetting.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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 + } +} \ No newline at end of file diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DocumentScannerExtra.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DocumentScannerExtra.kt new file mode 100644 index 0000000..a73855c --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DocumentScannerExtra.kt @@ -0,0 +1,37 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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" + const val EXTRA_CROPPER_HANDLE_COLOR = "cropperHandleColor" + const val EXTRA_CROPPER_HANDLE_OUTLINE_COLOR = "cropperHandleOutlineColor" + const val EXTRA_CROPPER_FRAME_COLOR = "cropperFrameColor" + const val EXTRA_CROPPER_STROKE_WIDTH_DP = "cropperStrokeWidthDp" + const val EXTRA_BUTTON_TINT_COLOR = "buttonTintColor" + const val EXTRA_BUTTON_CONTAINER_COLOR = "buttonContainerColor" + const val EXTRA_DONE_BUTTON_TINT_COLOR = "doneButtonTintColor" + const val EXTRA_DONE_BUTTON_CONTAINER_COLOR = "doneButtonContainerColor" + } +} diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/ResponseType.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/ResponseType.kt new file mode 100644 index 0000000..ad9afab --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/ResponseType.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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" + } +} \ No newline at end of file diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/enums/QuadCorner.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/enums/QuadCorner.kt new file mode 100644 index 0000000..a05841d --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/enums/QuadCorner.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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 +} \ No newline at end of file diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/AppCompatActivity.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/AppCompatActivity.kt new file mode 100644 index 0000000..a8cf461 --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/AppCompatActivity.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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() \ No newline at end of file diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/Bitmap.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/Bitmap.kt new file mode 100644 index 0000000..e3428e8 --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/Bitmap.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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 + ) +} \ No newline at end of file diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/Canvas.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/Canvas.kt new file mode 100644 index 0000000..c0164ad --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/Canvas.kt @@ -0,0 +1,146 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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 cropperLineStyle quad line style (color, thickness for example) + * @param cropperCornerStyle quad corner style (color, thickness for example) + * @param cropperCornerOutlineStyle quad corner outer outline style + * @param cropperSelectedCornerFillStyles style for selected corner + * @param selectedCornerProgress corner selection animation progress + */ +fun Canvas.drawQuad( + quad: Quad, + pointRadius: Float, + cropperLineStyle: Paint, + cropperCornerStyle: Paint, + cropperCornerOutlineStyle: Paint, + cropperSelectedCornerFillStyles: Paint, + selectedCornerProgress: Map, + imagePreviewBounds: RectF, + ratio: Float, + selectedCornerRadiusMagnification: Float, + selectedCornerBackgroundMagnification: Float, +) { + fun drawEdges() { + for (edge: Line in quad.edges) { + drawLine(edge.from.x, edge.from.y, edge.to.x, edge.to.y, cropperLineStyle) + } + } + + fun Float.lerpTo(target: Float, progress: Float): Float = this + (target - this) * progress + + // draw connecting lines under the corner handles + drawEdges() + + // draw selected corner magnifier fills first, then redraw edges so the frame is visible in zoom + for ((quadCorner: QuadCorner, cornerPoint: PointF) in quad.corners) { + val progress = selectedCornerProgress[quadCorner]?.coerceIn(0f, 1f) ?: 0f + if (progress <= 0f) continue + + val circleRadius = pointRadius.lerpTo( + selectedCornerRadiusMagnification * pointRadius, + progress + ) + val backgroundMagnification = 1f.lerpTo( + selectedCornerBackgroundMagnification, + progress + ) + val matrix = Matrix() + matrix.postScale(ratio, ratio, ratio / cornerPoint.x, ratio / cornerPoint.y) + matrix.postTranslate(imagePreviewBounds.left, imagePreviewBounds.top) + matrix.postScale( + backgroundMagnification, + backgroundMagnification, + 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) + } + + if (selectedCornerProgress.isNotEmpty()) { + drawEdges() + } + + // draw 4 corner points + for ((quadCorner: QuadCorner, cornerPoint: PointF) in quad.corners) { + val progress = selectedCornerProgress[quadCorner]?.coerceIn(0f, 1f) ?: 0f + val circleRadius = pointRadius.lerpTo( + selectedCornerRadiusMagnification * pointRadius, + progress + ) + + // draw outer ring around corner circles + drawCircle( + cornerPoint.x, + cornerPoint.y, + circleRadius + cropperCornerOutlineStyle.strokeWidth, + cropperCornerOutlineStyle + ) + + // draw corner circles + drawCircle( + cornerPoint.x, + cornerPoint.y, + circleRadius, + cropperCornerStyle + ) + } +} + +/** + * 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, + tintColor: Int? = null +) { + val mutate = drawable.constantState?.newDrawable()?.mutate() + tintColor?.let { mutate?.setTint(it) } + 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) +} diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/ImageButton.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/ImageButton.kt new file mode 100644 index 0000000..f043fbd --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/ImageButton.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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 + } +} \ No newline at end of file diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/Point.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/Point.kt new file mode 100644 index 0000000..979fe7f --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/Point.kt @@ -0,0 +1,92 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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)) +} \ No newline at end of file diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/models/Document.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/models/Document.kt new file mode 100644 index 0000000..65e70df --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/models/Document.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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 +) \ No newline at end of file diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/models/Line.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/models/Line.kt new file mode 100644 index 0000000..fcedb9f --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/models/Line.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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 +} \ No newline at end of file diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/models/Quad.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/models/Quad.kt new file mode 100644 index 0000000..482db58 --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/models/Quad.kt @@ -0,0 +1,159 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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) + ) + } +} \ No newline at end of file diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/ui/CircleButton.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/ui/CircleButton.kt new file mode 100644 index 0000000..0ec9042 --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/ui/CircleButton.kt @@ -0,0 +1,98 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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.content.res.ColorStateList +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.RectF +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatImageButton +import com.websitebeaver.documentscanner.R + +/** + * This class creates a rounded action 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) + + private val circle = Paint(Paint.ANTI_ALIAS_FLAG) + + private var drawCircleBackground = false + + init { + // set outer ring style + ring.color = Color.WHITE + ring.style = Paint.Style.STROKE + ring.strokeWidth = resources.getDimension(R.dimen.small_button_ring_thickness) + + circle.style = Paint.Style.FILL + } + + /** + * Set button colors from the app theme. + */ + fun setColors(containerColor: Int, iconColor: Int) { + ring.color = containerColor + circle.color = containerColor + drawCircleBackground = true + imageTintList = ColorStateList.valueOf(iconColor) + invalidate() + } + + /** + * This gets called repeatedly. We use it to draw the button + * + * @param canvas the image button canvas + */ + @SuppressLint("DrawAllocation") + override fun onDraw(canvas: Canvas) { + val rect = RectF( + ring.strokeWidth / 2, + ring.strokeWidth / 2, + width.toFloat() - ring.strokeWidth / 2, + height.toFloat() - ring.strokeWidth / 2 + ) + val cornerRadius = resources.getDimension(R.dimen.small_button_corner_radius) + + if (drawCircleBackground) { + canvas.drawRoundRect(rect, cornerRadius, cornerRadius, circle) + } + + super.onDraw(canvas) + + if (!drawCircleBackground) { + // draw outer ring + canvas.drawRoundRect(rect, cornerRadius, cornerRadius, ring) + } + } +} diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/ui/DoneButton.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/ui/DoneButton.kt new file mode 100644 index 0000000..aee90db --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/ui/DoneButton.kt @@ -0,0 +1,102 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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.content.res.ColorStateList +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) + + private var iconTintColor = Color.WHITE + + 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 + } + + /** + * Set button colors from the app theme. + */ + fun setColors(ringColor: Int, circleColor: Int, iconColor: Int) { + ring.color = ringColor + circle.color = circleColor + iconTintColor = iconColor + imageTintList = ColorStateList.valueOf(iconColor) + invalidate() + } + + /** + * 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, iconTintColor) + } +} diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/ui/ImageCropView.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/ui/ImageCropView.kt new file mode 100644 index 0000000..85695c5 --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/ui/ImageCropView.kt @@ -0,0 +1,524 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +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 android.view.animation.DecelerateInterpolator +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.distance +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 dragTarget corner or edge that should move on drag + */ + private var dragTarget: CropperDragTarget? = null + + /** + * @property selectedCornerProgress animated selected state for each cropper corner + */ + private val selectedCornerProgress = mutableMapOf() + + /** + * @property selectedCornerAnimators active corner selection animations + */ + private val selectedCornerAnimators = mutableMapOf() + + /** + * @property cropperLineStyle paint style for connecting lines + */ + private val cropperLineStyle = Paint(Paint.ANTI_ALIAS_FLAG) + + /** + * @property cropperCornerStyle paint style for 4 corners + */ + private val cropperCornerStyle = Paint(Paint.ANTI_ALIAS_FLAG) + + /** + * @property cropperCornerOutlineStyle paint style for the outer ring around corner handles + */ + private val cropperCornerOutlineStyle = 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 { + val defaultCropperStrokeWidth = DefaultCropperStrokeWidthDp.dpToPx() + + // set cropper style + cropperLineStyle.color = Color.WHITE + cropperLineStyle.style = Paint.Style.STROKE + cropperLineStyle.strokeWidth = defaultCropperStrokeWidth + + cropperCornerStyle.color = Color.WHITE + cropperCornerStyle.style = Paint.Style.STROKE + cropperCornerStyle.strokeWidth = defaultCropperStrokeWidth + + cropperCornerOutlineStyle.color = Color.argb(128, 255, 255, 255) + cropperCornerOutlineStyle.style = Paint.Style.STROKE + cropperCornerOutlineStyle.strokeWidth = defaultCropperStrokeWidth + } + + /** + * 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 + } + + /** + * Set cropper colors from the app theme. + */ + fun setCropperColors(handleColor: Int, frameColor: Int) { + cropperCornerStyle.color = handleColor + cropperLineStyle.color = frameColor + invalidate() + } + + /** + * Set cropper colors and line width from the app theme. + */ + fun setCropperStyle( + handleColor: Int, + frameColor: Int, + handleOutlineColor: Int, + strokeWidthDp: Float + ) { + val strokeWidth = strokeWidthDp.dpToPx() + + cropperCornerStyle.color = handleColor + cropperCornerOutlineStyle.color = handleOutlineColor + cropperLineStyle.color = frameColor + cropperCornerStyle.strokeWidth = strokeWidth + cropperCornerOutlineStyle.strokeWidth = strokeWidth + cropperLineStyle.strokeWidth = strokeWidth + invalidate() + } + + /** + * @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 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 + ) + } + + private fun findDragTarget(touchPoint: PointF): CropperDragTarget { + val touchRadius = resources.getDimension(R.dimen.cropper_corner_radius) * 2f + val touchedCorner = quad!!.corners + .mapNotNull { (corner, point) -> + val distance = point.distance(touchPoint) + if (distance <= touchRadius) { + corner to distance + } else null + } + .minByOrNull { it.second } + ?.first + + if (touchedCorner != null) return CropperDragTarget.Corner(touchedCorner) + + val touchedEdge = cropperEdges + .mapNotNull { (firstCorner, secondCorner) -> + val firstPoint = quad!!.corners[firstCorner] ?: return@mapNotNull null + val secondPoint = quad!!.corners[secondCorner] ?: return@mapNotNull null + val distanceSquared = touchPoint.distanceToSegmentSquared(firstPoint, secondPoint) + + if (distanceSquared <= touchRadius * touchRadius) { + CropperDragTarget.Edge(firstCorner, secondCorner) to distanceSquared + } else null + } + .minByOrNull { it.second } + ?.first + + return touchedEdge ?: CropperDragTarget.Corner(quad!!.getCornerClosestToPoint(touchPoint)) + } + + private fun PointF.coerceDragAmountFor(corners: Set): PointF { + val bounds = imagePreviewBounds + var minX = Float.NEGATIVE_INFINITY + var maxX = Float.POSITIVE_INFINITY + var minY = Float.NEGATIVE_INFINITY + var maxY = Float.POSITIVE_INFINITY + + corners.forEach { corner -> + val point = quad!!.corners[corner] ?: return@forEach + minX = minX.coerceAtLeast(bounds.left - point.x) + maxX = maxX.coerceAtMost(bounds.right - point.x) + minY = minY.coerceAtLeast(bounds.top - point.y) + maxY = maxY.coerceAtMost(bounds.bottom - point.y) + } + + return PointF( + x.coerceIn(minX, maxX), + y.coerceIn(minY, maxY) + ) + } + + /** + * 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), + cropperLineStyle, + cropperCornerStyle, + cropperCornerOutlineStyle, + cropperSelectedCornerFillStyles, + selectedCornerProgress, + 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 or edge to the touch point + prevTouchPoint = touchPoint + dragTarget = findDragTarget(touchPoint) + animateSelectedCorners(dragTarget?.corners ?: emptySet()) + } + + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + // when the user stops touching the screen reset these values + prevTouchPoint = null + dragTarget = null + animateSelectedCorners(emptySet()) + } + + MotionEvent.ACTION_MOVE -> { + // when the user drags their finger, update the selected corner or edge position + val target = dragTarget ?: return true + val touchMoveDistance = PointF( + touchPoint.x - prevTouchPoint!!.x, + touchPoint.y - prevTouchPoint!!.y + ).coerceDragAmountFor(target.corners) + + if (target.corners.isNotEmpty()) { + target.corners.forEach { corner -> + quad!!.moveCorner( + corner, + touchMoveDistance.x, + touchMoveDistance.y + ) + } + } + + // 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 + } + + override fun onDetachedFromWindow() { + val animators = selectedCornerAnimators.values.toList() + selectedCornerAnimators.clear() + selectedCornerProgress.clear() + animators.forEach { it.cancel() } + super.onDetachedFromWindow() + } + + private fun animateSelectedCorners(selectedCorners: Set) { + val cornersToUpdate = selectedCornerProgress.keys + selectedCorners + + cornersToUpdate.forEach { corner -> + animateCornerSelection( + corner = corner, + targetProgress = if (corner in selectedCorners) 1f else 0f + ) + } + } + + private fun animateCornerSelection(corner: QuadCorner, targetProgress: Float) { + val currentProgress = selectedCornerProgress[corner] ?: 0f + if (currentProgress == targetProgress) return + + selectedCornerAnimators.remove(corner)?.cancel() + + val animator = ValueAnimator.ofFloat(currentProgress, targetProgress).apply { + duration = CornerSelectionAnimationDurationMs + interpolator = DecelerateInterpolator() + addUpdateListener { animation -> + val progress = animation.animatedValue as Float + if (progress <= 0f) { + selectedCornerProgress.remove(corner) + } else { + selectedCornerProgress[corner] = progress + } + invalidate() + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + if (selectedCornerAnimators[corner] != animation) return + + selectedCornerAnimators.remove(corner) + if (targetProgress <= 0f) { + selectedCornerProgress.remove(corner) + } else { + selectedCornerProgress[corner] = targetProgress + } + invalidate() + } + }) + } + + selectedCornerAnimators[corner] = animator + animator.start() + } + + private fun Float.dpToPx(): Float = this * resources.displayMetrics.density + + private sealed class CropperDragTarget { + data class Corner(val corner: QuadCorner) : CropperDragTarget() + data class Edge( + val firstCorner: QuadCorner, + val secondCorner: QuadCorner + ) : CropperDragTarget() + + val corners: Set + get() = when (this) { + is Corner -> setOf(corner) + is Edge -> setOf(firstCorner, secondCorner) + } + } + + private fun PointF.distanceToSegmentSquared(start: PointF, end: PointF): Float { + val segmentX = end.x - start.x + val segmentY = end.y - start.y + val lengthSquared = segmentX * segmentX + segmentY * segmentY + + if (lengthSquared == 0f) { + val xDistance = x - start.x + val yDistance = y - start.y + return xDistance * xDistance + yDistance * yDistance + } + + val projection = (((x - start.x) * segmentX + (y - start.y) * segmentY) / lengthSquared) + .coerceIn(0f, 1f) + val closestX = start.x + projection * segmentX + val closestY = start.y + projection * segmentY + val xDistance = x - closestX + val yDistance = y - closestY + + return xDistance * xDistance + yDistance * yDistance + } + + private companion object { + private const val DefaultCropperStrokeWidthDp = 1.2f + private const val CornerSelectionAnimationDurationMs = 140L + + val cropperEdges = listOf( + QuadCorner.TOP_LEFT to QuadCorner.TOP_RIGHT, + QuadCorner.TOP_RIGHT to QuadCorner.BOTTOM_RIGHT, + QuadCorner.BOTTOM_RIGHT to QuadCorner.BOTTOM_LEFT, + QuadCorner.BOTTOM_LEFT to QuadCorner.TOP_LEFT + ) + } +} diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/FileUtil.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/FileUtil.kt new file mode 100644 index 0000000..4c0610a --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/FileUtil.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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.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 + */ + 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 + ) + } +} diff --git a/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/ImageUtil.kt b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/ImageUtil.kt new file mode 100644 index 0000000..14ecb39 --- /dev/null +++ b/lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/ImageUtil.kt @@ -0,0 +1,183 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions 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.geometry.Geometry +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 Throwable("File doesn't exist - $filePath") + } + + if (!File(filePath).canRead()) { + throw Throwable("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, + Geometry.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()) + ) + } +} \ No newline at end of file diff --git a/lib/documentscanner/src/main/res/animator/button_grow_animation.xml b/lib/documentscanner/src/main/res/animator/button_grow_animation.xml new file mode 100644 index 0000000..a245896 --- /dev/null +++ b/lib/documentscanner/src/main/res/animator/button_grow_animation.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/documentscanner/src/main/res/drawable/ic_baseline_add_24.xml b/lib/documentscanner/src/main/res/drawable/ic_baseline_add_24.xml new file mode 100644 index 0000000..53e89b6 --- /dev/null +++ b/lib/documentscanner/src/main/res/drawable/ic_baseline_add_24.xml @@ -0,0 +1,27 @@ + + + + + \ No newline at end of file diff --git a/lib/documentscanner/src/main/res/drawable/ic_baseline_arrow_back_24.xml b/lib/documentscanner/src/main/res/drawable/ic_baseline_arrow_back_24.xml new file mode 100644 index 0000000..d814a63 --- /dev/null +++ b/lib/documentscanner/src/main/res/drawable/ic_baseline_arrow_back_24.xml @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/lib/documentscanner/src/main/res/drawable/ic_baseline_check_24.xml b/lib/documentscanner/src/main/res/drawable/ic_baseline_check_24.xml new file mode 100644 index 0000000..663ca09 --- /dev/null +++ b/lib/documentscanner/src/main/res/drawable/ic_baseline_check_24.xml @@ -0,0 +1,27 @@ + + + + + \ No newline at end of file diff --git a/lib/documentscanner/src/main/res/layout/activity_image_crop.xml b/lib/documentscanner/src/main/res/layout/activity_image_crop.xml new file mode 100644 index 0000000..bce8134 --- /dev/null +++ b/lib/documentscanner/src/main/res/layout/activity_image_crop.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/documentscanner/src/main/res/values/colors.xml b/lib/documentscanner/src/main/res/values/colors.xml new file mode 100644 index 0000000..5a770ed --- /dev/null +++ b/lib/documentscanner/src/main/res/values/colors.xml @@ -0,0 +1,21 @@ + + + + #000000 + #D0E4FF + \ No newline at end of file diff --git a/lib/documentscanner/src/main/res/values/dimens.xml b/lib/documentscanner/src/main/res/values/dimens.xml new file mode 100644 index 0000000..b26d6f8 --- /dev/null +++ b/lib/documentscanner/src/main/res/values/dimens.xml @@ -0,0 +1,37 @@ + + + + 0dp + 1 + 0.1dp + 5dp + 200dp + 25px + 4px + 5px + 1dp + 1.07 + 0dp + 15.4dp + 84dp + 4dp + 8dp + 16dp + 52dp + 2dp + diff --git a/lib/documentscanner/src/main/res/values/integers.xml b/lib/documentscanner/src/main/res/values/integers.xml new file mode 100644 index 0000000..95384f7 --- /dev/null +++ b/lib/documentscanner/src/main/res/values/integers.xml @@ -0,0 +1,21 @@ + + + + 100 + 150 + \ No newline at end of file diff --git a/lib/documentscanner/src/main/res/values/strings.xml b/lib/documentscanner/src/main/res/values/strings.xml new file mode 100644 index 0000000..66e35ae --- /dev/null +++ b/lib/documentscanner/src/main/res/values/strings.xml @@ -0,0 +1,20 @@ + + + + Original Image With Cropper + \ No newline at end of file diff --git a/lib/documentscanner/src/main/res/values/themes.xml b/lib/documentscanner/src/main/res/values/themes.xml new file mode 100644 index 0000000..2d05478 --- /dev/null +++ b/lib/documentscanner/src/main/res/values/themes.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/lib/documentscanner/src/main/res/xml/file_paths.xml b/lib/documentscanner/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..2be340e --- /dev/null +++ b/lib/documentscanner/src/main/res/xml/file_paths.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/lib/dynamic-theme/.gitignore b/lib/dynamic-theme/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lib/dynamic-theme/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/dynamic-theme/build.gradle.kts b/lib/dynamic-theme/build.gradle.kts new file mode 100644 index 0000000..eedbb93 --- /dev/null +++ b/lib/dynamic-theme/build.gradle.kts @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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(projects.core.resources) + + implementation(libs.androidx.palette.ktx) + implementation(libs.materialKolor) +} \ No newline at end of file diff --git a/lib/dynamic-theme/src/main/AndroidManifest.xml b/lib/dynamic-theme/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7e0cab2 --- /dev/null +++ b/lib/dynamic-theme/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/ColorBlindUtils.kt b/lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/ColorBlindUtils.kt new file mode 100644 index 0000000..4fe2a52 --- /dev/null +++ b/lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/ColorBlindUtils.kt @@ -0,0 +1,171 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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), + ) +} \ No newline at end of file diff --git a/lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/DynamicTheme.kt b/lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/DynamicTheme.kt new file mode 100644 index 0000000..ac69f5b --- /dev/null +++ b/lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/DynamicTheme.kt @@ -0,0 +1,1065 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.core.AnimationSpec +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +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.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.MaterialExpressiveTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +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.isSpecified +import androidx.compose.ui.graphics.takeOrElse +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.tooling.preview.PreviewLightDark +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.hct.Hct +import com.materialkolor.ktx.blend +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 com.materialkolor.toColorScheme +import com.t8rin.imagetoolbox.core.resources.utils.animation.animateColorAsState +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe +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 = { + MaterialExpressiveTheme( + 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 { + Triple( + primary, + secondary ?: Color( + TonalPalette.fromInt(primary.calculateSecondaryColor()).tone(70) + ), + tertiary ?: Color( + TonalPalette.fromInt(primary.calculateTertiaryColor()).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()) + + return TonalPalette.fromHueAndChroma(hct.hue, hct.secondaryChroma()).tone(80) +} + +fun Color.calculateTertiaryColor(): Int { + val hct = Hct.fromInt(this.toArgb()) + + return TonalPalette.fromHueAndChroma(hct.tertiaryHue(), hct.tertiaryChroma()).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) +} + +fun Color.calculateNeutralVariantColor(): Int = calculateSurfaceColor() + +fun Color.calculateErrorColor(style: PaletteStyle = PaletteStyle.TonalSpot): Int { + val hct = Hct.fromInt(this.toArgb()) + + return TonalPalette.fromHueAndChroma( + hue = hct.dynamicErrorHue(), + chroma = style.dynamicErrorChroma() + ).tone(82) +} + +private fun Hct.secondaryChroma(): Double = (chroma / 3.0).coerceIn(14.0, 28.0) + +private fun Hct.tertiaryChroma(): Double = (chroma / 2.0).coerceIn(20.0, 42.0) + +private fun Hct.tertiaryHue(): Double = (hue + 60.0).mod(360.0) + +private fun PaletteStyle.dynamicErrorChroma(): Double = when (this) { + PaletteStyle.Neutral -> 56.0 + PaletteStyle.TonalSpot -> 72.0 + PaletteStyle.Expressive -> 76.0 + PaletteStyle.Vibrant -> 80.0 + else -> 72.0 +} + +private fun Hct.dynamicErrorHue(): Double { + val breakpoints = doubleArrayOf(0.0, 3.0, 13.0, 23.0, 33.0, 43.0, 153.0, 273.0, 360.0) + val hues = doubleArrayOf(12.0, 22.0, 32.0, 12.0, 22.0, 32.0, 22.0, 12.0) + + return hues.getOrElse(breakpoints.indexOfLast { hue >= it }.coerceAtMost(hues.lastIndex)) { + hues.last() + } +} + + +@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, + neutralVariant = surfaceVariant, + error = error + ) + } + } + + 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(), + primaryFixed = primaryFixed.animateColor(), + primaryFixedDim = primaryFixedDim.animateColor(), + onPrimaryFixed = onPrimaryFixed.animateColor(), + onPrimaryFixedVariant = onPrimaryFixedVariant.animateColor(), + secondaryFixed = secondaryFixed.animateColor(), + secondaryFixedDim = secondaryFixedDim.animateColor(), + onSecondaryFixed = onSecondaryFixed.animateColor(), + onSecondaryFixedVariant = onSecondaryFixedVariant.animateColor(), + tertiaryFixed = tertiaryFixed.animateColor(), + tertiaryFixedDim = tertiaryFixedDim.animateColor(), + onTertiaryFixed = onTertiaryFixed.animateColor(), + onTertiaryFixedVariant = onTertiaryFixedVariant.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 + } + ).takeOrElse { Color.Transparent } +} + +/** Class that represents App color scheme based on main palette colors + * @param primary primary color + * @param secondary secondary color + * @param tertiary tertiary color + * @param surface neutral color + * @param neutralVariant neutral variant color + * @param error error color + */ +data class ColorTuple( + val primary: Color, + val secondary: Color? = null, + val tertiary: Color? = null, + val surface: Color? = null, + val neutralVariant: Color? = null, + val error: Color? = null +) { + override fun toString(): String = + "ColorTuple(primary=${primary.toArgb()}, secondary=${secondary?.toArgb()}, tertiary=${tertiary?.toArgb()}, surface=${surface?.toArgb()}, neutralVariant=${neutralVariant?.toArgb()}, error=${error?.toArgb()})" +} + +private fun Color.asSrgb(): Color = if (isSpecified) Color(toArgb()) else Color.Transparent + +private fun ColorTuple.asSrgb(): ColorTuple = ColorTuple( + primary = primary.asSrgb(), + secondary = secondary?.asSrgb(), + tertiary = tertiary?.asSrgb(), + surface = surface?.asSrgb(), + neutralVariant = neutralVariant?.asSrgb(), + error = error?.asSrgb() +) + +/** + * 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, + neutralVariant = MaterialTheme.colorScheme.surfaceVariant, + error = MaterialTheme.colorScheme.error + ) +): 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, + colorTuple.neutralVariant?.toArgb() ?: -1, + colorTuple.error?.toArgb() ?: -1 + ) + + list + }, + restore = { ints: List -> + DynamicThemeState( + initialColorTuple = ColorTuple( + primary = Color(ints[0]), + secondary = ints.getOrNull(1)?.takeIf { it != -1 }?.let { Color(it) }, + tertiary = ints.getOrNull(2)?.takeIf { it != -1 }?.let { Color(it) }, + surface = ints.getOrNull(3)?.takeIf { it != -1 }?.let { Color(it) }, + neutralVariant = ints.getOrNull(4)?.takeIf { it != -1 }?.let { Color(it) }, + error = ints.getOrNull(5)?.takeIf { it != -1 }?.let { Color(it) }, + ) + ) + } +) + +@Stable +class DynamicThemeState( + initialColorTuple: ColorTuple +) { + private val _colorTuple: MutableState = mutableStateOf(initialColorTuple.asSrgb()) + val colorTuple: State = _colorTuple + + fun updateColor(color: Color) { + _colorTuple.value = ColorTuple(primary = color.asSrgb(), secondary = null, tertiary = null) + } + + fun updateColorTuple(newColorTuple: ColorTuple) { + _colorTuple.value = newColorTuple.asSrgb() + } + + 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.colorFilter = 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, hct.secondaryChroma()) + } + } + + val a3 = colorTuple.tertiary?.toArgb().let { + if (it != null) { + TonalPalette.fromInt(it) + } else { + TonalPalette.fromHueAndChroma(hct.tertiaryHue(), hct.tertiaryChroma()) + } + } + + 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 nv = colorTuple.neutralVariant?.toArgb().let { + if (it != null) { + TonalPalette.fromInt(it) + } else { + n2 + } + } + + val e = colorTuple.error?.toArgb()?.let(TonalPalette::fromInt) + + val scheme = when (style) { + PaletteStyle.TonalSpot -> DynamicScheme( + sourceColorHct = hct, + variant = Variant.TONAL_SPOT, + isDark = isDarkTheme, + contrastLevel = contrastLevel, + primaryPalette = a1, + secondaryPalette = a2, + tertiaryPalette = a3, + neutralPalette = n1, + neutralVariantPalette = nv, + errorPalette = e ?: TonalPalette.fromHueAndChroma(25.0, 84.0) + ) + + 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() + .withDynamicErrorColors(style, isDarkTheme, colorTuple.error) + .withExpressiveOnColors(isDarkTheme) + } + + + return colorScheme + .withContrastingSurfaces( + surfaceSeed = colorTuple.surface, + isDarkTheme = isDarkTheme + ) + .toAmoled(amoledMode && isDarkTheme) + .toColorBlind(colorBlindType) + .invertColors(isInvertColors && !dynamicColor) + .run { + copy( + outlineVariant = onSecondaryContainer + .copy(alpha = 0.2f) + .compositeOverSafe(surfaceColorAtElevation(6.dp)), + background = surface + ) + } +} + +private fun ColorScheme.withContrastingSurfaces( + surfaceSeed: Color?, + isDarkTheme: Boolean +): ColorScheme { + if (isDarkTheme) return this + + val seed = Hct.fromInt((surfaceSeed ?: surface).toArgb()) + + fun surfaceColor(tone: Int, chromaMultiplier: Double = 1.0): Color { + val chroma = (seed.chroma * chromaMultiplier).coerceAtMost(12.0) + return Color( + TonalPalette.fromHueAndChroma(seed.hue, chroma).tone(tone) + ) + } + + return copy( + surface = surfaceColor(97), + surfaceBright = surfaceColor(99), + surfaceDim = surfaceColor(82, 1.8), + surfaceContainerLowest = surfaceColor(100), + surfaceContainerLow = surfaceColor(94, 1.2), + surfaceContainer = surfaceColor(91, 1.4), + surfaceContainerHigh = surfaceColor(88, 1.6), + surfaceContainerHighest = surfaceColor(85, 1.8), + surfaceVariant = surfaceColor(85, 1.8) + ) +} + +private fun ColorScheme.withDynamicErrorColors( + style: PaletteStyle, + isDarkTheme: Boolean, + errorSeed: Color? +): ColorScheme { + val errorHue = errorSeed?.toArgb()?.let { Hct.fromInt(it).hue } + ?: Hct.fromInt(primary.toArgb()).dynamicErrorHue() + val palette = TonalPalette.fromHueAndChroma( + hue = errorHue, + chroma = style.dynamicErrorChroma() + ) + val errorTone = if (isDarkTheme) 70 else 30 + val onErrorTone = if (isDarkTheme) 22 else 84 + val errorContainerTone = if (isDarkTheme) 30 else 78 + val onErrorContainerTone = if (isDarkTheme) 84 else 22 + + return copy( + error = Color(palette.tone(errorTone)).blend(primary, 0.15f), + onError = Color(palette.tone(onErrorTone)), + errorContainer = Color(palette.tone(errorContainerTone)).blend(primaryContainer, 0.15f), + onErrorContainer = Color(palette.tone(onErrorContainerTone)) + ) +} + +private fun ColorScheme.withExpressiveOnColors(isDarkTheme: Boolean): ColorScheme = copy( + onPrimary = primary.expressiveOnColor(isDarkTheme) ?: onPrimary, + onSecondary = secondary.expressiveOnColor(isDarkTheme) ?: onSecondary, + onTertiary = tertiary.expressiveOnColor(isDarkTheme) ?: onTertiary, +) + +private fun Color.expressiveOnColor(isDarkTheme: Boolean): Color? { + val palette = TonalPalette.fromInt(toArgb()) + val candidateTones = if (isDarkTheme) { + listOf(22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10) + } else { + listOf(96, 97, 98) + } + + return candidateTones + .asSequence() + .map { Color(palette.tone(it)) } + .firstOrNull { candidate -> + ColorUtils.calculateContrast(candidate.toArgb(), toArgb()) >= 4.5 + } +} + +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 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") } + +@PreviewLightDark +@Composable +private fun PreviewLightDark() = MaterialTheme( + colorScheme = rememberColorScheme( + isDarkTheme = isSystemInDarkTheme(), + amoledMode = false, + colorTuple = ColorTuple( + primary = Color(0xFFBCE870) + ), + style = PaletteStyle.TonalSpot, + contrastLevel = 0.0, + dynamicColor = false, + isInvertColors = false, + ) +) { + Column { + Row { + Button( + onClick = {}, + colors = ButtonDefaults.textButtonColors( + containerColor = MaterialTheme.colorScheme.error, + contentColor = MaterialTheme.colorScheme.onError + ) + ) { + Text("error") + } + + Button( + onClick = {}, + colors = ButtonDefaults.textButtonColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer + ) + ) { + Text("errorContainer") + } + } + + Row { + Button( + onClick = {}, + colors = ButtonDefaults.textButtonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Text("primary") + } + + Button( + onClick = {}, + colors = ButtonDefaults.textButtonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer + ) + ) { + Text("primaryContainer") + } + } + + Row { + Button( + onClick = {}, + colors = ButtonDefaults.textButtonColors( + containerColor = MaterialTheme.colorScheme.secondary, + contentColor = MaterialTheme.colorScheme.onSecondary + ) + ) { + Text("secondary") + } + + Button( + onClick = {}, + colors = ButtonDefaults.textButtonColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + contentColor = MaterialTheme.colorScheme.onSecondaryContainer + ) + ) { + Text("secondaryContainer") + } + } + + Row { + Button( + onClick = {}, + colors = ButtonDefaults.textButtonColors( + containerColor = MaterialTheme.colorScheme.tertiary, + contentColor = MaterialTheme.colorScheme.onTertiary + ) + ) { + Text("tertiary") + } + + Button( + onClick = {}, + colors = ButtonDefaults.textButtonColors( + containerColor = MaterialTheme.colorScheme.tertiaryContainer, + contentColor = MaterialTheme.colorScheme.onTertiaryContainer + ) + ) { + Text("tertiaryContainer") + } + } + } +} \ No newline at end of file diff --git a/lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/PaletteStyle.kt b/lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/PaletteStyle.kt new file mode 100644 index 0000000..10664b1 --- /dev/null +++ b/lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/PaletteStyle.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} \ No newline at end of file diff --git a/lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/SystemUiController.kt b/lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/SystemUiController.kt new file mode 100644 index 0000000..e075199 --- /dev/null +++ b/lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/SystemUiController.kt @@ -0,0 +1,311 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.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 +import com.t8rin.imagetoolbox.core.resources.utils.compositeOverSafe + + +@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.compositeOverSafe(original) +} \ No newline at end of file diff --git a/lib/gesture/.gitignore b/lib/gesture/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lib/gesture/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/gesture/build.gradle.kts b/lib/gesture/build.gradle.kts new file mode 100644 index 0000000..b6a843a --- /dev/null +++ b/lib/gesture/build.gradle.kts @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/lib/gesture/src/main/AndroidManifest.xml b/lib/gesture/src/main/AndroidManifest.xml new file mode 100644 index 0000000..44008a4 --- /dev/null +++ b/lib/gesture/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/lib/gesture/src/main/java/com/t8rin/gesture/AwaitPointerMotionEvent.kt b/lib/gesture/src/main/java/com/t8rin/gesture/AwaitPointerMotionEvent.kt new file mode 100644 index 0000000..41be632 --- /dev/null +++ b/lib/gesture/src/main/java/com/t8rin/gesture/AwaitPointerMotionEvent.kt @@ -0,0 +1,239 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + } + } + } + } +} diff --git a/lib/gesture/src/main/java/com/t8rin/gesture/PointerMotionModifier.kt b/lib/gesture/src/main/java/com/t8rin/gesture/PointerMotionModifier.kt new file mode 100644 index 0000000..82bd441 --- /dev/null +++ b/lib/gesture/src/main/java/com/t8rin/gesture/PointerMotionModifier.kt @@ -0,0 +1,446 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + ) + } +) diff --git a/lib/gesture/src/main/java/com/t8rin/gesture/TransformGesture.kt b/lib/gesture/src/main/java/com/t8rin/gesture/TransformGesture.kt new file mode 100644 index 0000000..55952ac --- /dev/null +++ b/lib/gesture/src/main/java/com/t8rin/gesture/TransformGesture.kt @@ -0,0 +1,400 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 } \ No newline at end of file diff --git a/lib/image/.gitignore b/lib/image/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lib/image/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/image/build.gradle.kts b/lib/image/build.gradle.kts new file mode 100644 index 0000000..0e61aaf --- /dev/null +++ b/lib/image/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/lib/image/src/main/AndroidManifest.xml b/lib/image/src/main/AndroidManifest.xml new file mode 100644 index 0000000..44008a4 --- /dev/null +++ b/lib/image/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/lib/image/src/main/java/com/t8rin/image/ImageScope.kt b/lib/image/src/main/java/com/t8rin/image/ImageScope.kt new file mode 100644 index 0000000..8e2590f --- /dev/null +++ b/lib/image/src/main/java/com/t8rin/image/ImageScope.kt @@ -0,0 +1,110 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + } +} \ No newline at end of file diff --git a/lib/image/src/main/java/com/t8rin/image/ImageWithConstraints.kt b/lib/image/src/main/java/com/t8rin/image/ImageWithConstraints.kt new file mode 100644 index 0000000..6b812d4 --- /dev/null +++ b/lib/image/src/main/java/com/t8rin/image/ImageWithConstraints.kt @@ -0,0 +1,250 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + ) + } + } +} diff --git a/lib/image/src/main/java/com/t8rin/image/util/ImageContentScaleUtil.kt b/lib/image/src/main/java/com/t8rin/image/util/ImageContentScaleUtil.kt new file mode 100644 index 0000000..3112ae6 --- /dev/null +++ b/lib/image/src/main/java/com/t8rin/image/util/ImageContentScaleUtil.kt @@ -0,0 +1,105 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) +} diff --git a/lib/modalsheet/.gitignore b/lib/modalsheet/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lib/modalsheet/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/modalsheet/build.gradle.kts b/lib/modalsheet/build.gradle.kts new file mode 100644 index 0000000..d2dfdcd --- /dev/null +++ b/lib/modalsheet/build.gradle.kts @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/lib/modalsheet/src/main/AndroidManifest.xml b/lib/modalsheet/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b782a57 --- /dev/null +++ b/lib/modalsheet/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + diff --git a/lib/modalsheet/src/main/java/com/t8rin/modalsheet/FullscreenPopup.kt b/lib/modalsheet/src/main/java/com/t8rin/modalsheet/FullscreenPopup.kt new file mode 100644 index 0000000..8c3638f --- /dev/null +++ b/lib/modalsheet/src/main/java/com/t8rin/modalsheet/FullscreenPopup.kt @@ -0,0 +1,223 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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.setViewTreeLifecycleOwner +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 decorView = findOwner(composeView.context)?.window?.decorView as ViewGroup + + override val subCompositionView: AbstractComposeView get() = this + + init { + id = android.R.id.content + setViewTreeLifecycleOwner(composeView.findViewTreeLifecycleOwner()) + 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 +} + +private const val ABOVE_ALL_Z = Float.MAX_VALUE \ No newline at end of file diff --git a/lib/modalsheet/src/main/java/com/t8rin/modalsheet/ModalSheet.kt b/lib/modalsheet/src/main/java/com/t8rin/modalsheet/ModalSheet.kt new file mode 100644 index 0000000..63bc9e4 --- /dev/null +++ b/lib/modalsheet/src/main/java/com/t8rin/modalsheet/ModalSheet.kt @@ -0,0 +1,224 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 = {} + ) + } + } +} diff --git a/lib/modalsheet/src/main/java/com/t8rin/modalsheet/ModalSheetLayout.kt b/lib/modalsheet/src/main/java/com/t8rin/modalsheet/ModalSheetLayout.kt new file mode 100644 index 0000000..84a579f --- /dev/null +++ b/lib/modalsheet/src/main/java/com/t8rin/modalsheet/ModalSheetLayout.kt @@ -0,0 +1,569 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 \ No newline at end of file diff --git a/lib/modalsheet/src/main/java/com/t8rin/modalsheet/SwipeableV2.kt b/lib/modalsheet/src/main/java/com/t8rin/modalsheet/SwipeableV2.kt new file mode 100644 index 0000000..97bb089 --- /dev/null +++ b/lib/modalsheet/src/main/java/com/t8rin/modalsheet/SwipeableV2.kt @@ -0,0 +1,680 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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()}" +} diff --git a/lib/neural-tools/.gitignore b/lib/neural-tools/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lib/neural-tools/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/neural-tools/build.gradle.kts b/lib/neural-tools/build.gradle.kts new file mode 100644 index 0000000..9f3e848 --- /dev/null +++ b/lib/neural-tools/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/lib/neural-tools/src/main/AndroidManifest.xml b/lib/neural-tools/src/main/AndroidManifest.xml new file mode 100644 index 0000000..205decf --- /dev/null +++ b/lib/neural-tools/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/lib/neural-tools/src/main/assets/u2netp.onnx b/lib/neural-tools/src/main/assets/u2netp.onnx new file mode 100644 index 0000000..4f21f9a Binary files /dev/null and b/lib/neural-tools/src/main/assets/u2netp.onnx differ diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/DownloadProgress.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/DownloadProgress.kt new file mode 100644 index 0000000..092402f --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/DownloadProgress.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +) \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/NeuralTool.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/NeuralTool.kt new file mode 100644 index 0000000..233689b --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/NeuralTool.kt @@ -0,0 +1,53 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + } + } +} \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/BgRemover.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/BgRemover.kt new file mode 100644 index 0000000..5214f81 --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/BgRemover.kt @@ -0,0 +1,100 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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, + MODNet, + ISNet, + YOLO + } + + 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 + ): Result = runCatching { + getRemover(type).removeBackground(image) + } + + 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.MODNet -> MODNetBackgroundRemover + Type.ISNet -> ISNetBackgroundRemover + Type.YOLO -> YoloSegRemover + } + +} \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/BiRefNetBackgroundRemover.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/BiRefNetBackgroundRemover.kt new file mode 100644 index 0000000..8d1254b --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/BiRefNetBackgroundRemover.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +) \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/BiRefNetTinyBackgroundRemover.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/BiRefNetTinyBackgroundRemover.kt new file mode 100644 index 0000000..df9ca3b --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/BiRefNetTinyBackgroundRemover.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +) \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/GenericBackgroundRemover.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/GenericBackgroundRemover.kt new file mode 100644 index 0000000..ade6ca2 --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/GenericBackgroundRemover.kt @@ -0,0 +1,243 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.io.NotActiveException +import java.nio.FloatBuffer +import kotlin.math.roundToInt + +abstract class GenericBackgroundRemover( + path: String, + open 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() + throw NotActiveException("Model ${modelFile.name} is not downloaded") + } + + 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 + } + +} \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/ISNetBackgroundRemover.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/ISNetBackgroundRemover.kt new file mode 100644 index 0000000..24b9bd8 --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/ISNetBackgroundRemover.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +) \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/InSPyReNetBackgroundRemover.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/InSPyReNetBackgroundRemover.kt new file mode 100644 index 0000000..ba10c45 --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/InSPyReNetBackgroundRemover.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +) \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/MODNetBackgroundRemover.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/MODNetBackgroundRemover.kt new file mode 100644 index 0000000..220944c --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/MODNetBackgroundRemover.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 MODNetBackgroundRemover : GenericBackgroundRemover( + path = "modnet_portrait_matting.onnx", + trainedSize = 512 +) \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/RMBGBackgroundRemover.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/RMBGBackgroundRemover.kt new file mode 100644 index 0000000..7305139 --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/RMBGBackgroundRemover.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +) \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/U2NetFullBackgroundRemover.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/U2NetFullBackgroundRemover.kt new file mode 100644 index 0000000..d397d4d --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/U2NetFullBackgroundRemover.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +) \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/U2NetPortableBackgroundRemover.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/U2NetPortableBackgroundRemover.kt new file mode 100644 index 0000000..642d364 --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/U2NetPortableBackgroundRemover.kt @@ -0,0 +1,65 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 } + } +} diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/YoloSegRemover.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/YoloSegRemover.kt new file mode 100644 index 0000000..0da462a --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/YoloSegRemover.kt @@ -0,0 +1,356 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 ai.onnxruntime.OnnxTensor +import ai.onnxruntime.OrtSession +import android.graphics.Bitmap +import android.graphics.Color +import androidx.core.graphics.createBitmap +import androidx.core.graphics.scale +import java.nio.FloatBuffer +import kotlin.math.exp + +internal object YoloSegRemover : GenericBackgroundRemover( + path = "yolo11x-seg.onnx", + trainedSize = 1024 +) { + override fun removeBackground( + image: Bitmap, + modelPath: String, + trainedSize: Int? + ): Bitmap { + val session = session ?: env.createSession( + modelFile.absolutePath, + OrtSession.SessionOptions() + ).also { + this.session = it + } + + val resized = image.scale( + width = trainedSize!!, + height = trainedSize + ) + + val input = bitmapToFloatBuffer(resized) + + val tensor = OnnxTensor.createTensor( + env, + input, + longArrayOf( + 1, + 3, + trainedSize.toLong(), + trainedSize.toLong() + ) + ) + + val outputs = session.run( + mapOf(session.inputNames.first() to tensor) + ) + + val det = + outputs[0].value as Array> + + val proto = + outputs[1].value as Array>> + + val resultMask = processSegmentation( + det = det[0], + proto = proto[0], + width = image.width, + height = image.height + ) + + tensor.close() + outputs.close() + + return applyMask(image, resultMask) + } + + private data class Detection( + val cx: Float, + val cy: Float, + val w: Float, + val h: Float, + val score: Float, + val classId: Int, + val coeffs: FloatArray + ) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Detection + + if (cx != other.cx) return false + if (cy != other.cy) return false + if (w != other.w) return false + if (h != other.h) return false + if (score != other.score) return false + if (classId != other.classId) return false + if (!coeffs.contentEquals(other.coeffs)) return false + + return true + } + + override fun hashCode(): Int { + var result = cx.hashCode() + result = 31 * result + cy.hashCode() + result = 31 * result + w.hashCode() + result = 31 * result + h.hashCode() + result = 31 * result + score.hashCode() + result = 31 * result + classId + result = 31 * result + coeffs.contentHashCode() + return result + } + } + + private fun processSegmentation( + det: Array, + proto: Array>, + width: Int, + height: Int + ): Bitmap { + val protoChannels = 32 + val protoH = proto[0].size + val protoW = proto[0][0].size + + val candidates = mutableListOf() + val count = det[0].size + + for (i in 0 until count) { + var bestClass = -1 + var bestScore = 0f + + for (c in 4 until 84) { + val score = det[c][i] + if (score > bestScore) { + bestScore = score + bestClass = c - 4 + } + } + + if (bestScore < 0.45f) continue + + candidates += Detection( + cx = det[0][i], + cy = det[1][i], + w = det[2][i], + h = det[3][i], + score = bestScore, + classId = bestClass, + coeffs = FloatArray(32) { k -> det[84 + k][i] } + ) + } + + val selected = nms(candidates, 0.5f).take(8) + + val mask = FloatArray(protoW * protoH) + + selected.forEach { d -> + val left = ((d.cx - d.w / 2f) / trainedSize!! * protoW) + .toInt() + .coerceIn(0, protoW - 1) + + val top = ((d.cy - d.h / 2f) / trainedSize * protoH) + .toInt() + .coerceIn(0, protoH - 1) + + val right = ((d.cx + d.w / 2f) / trainedSize * protoW) + .toInt() + .coerceIn(0, protoW - 1) + + val bottom = ((d.cy + d.h / 2f) / trainedSize * protoH) + .toInt() + .coerceIn(0, protoH - 1) + + for (y in top..bottom) { + for (x in left..right) { + var sum = 0f + + for (k in 0 until protoChannels) { + sum += d.coeffs[k] * proto[k][y][x] + } + + val v = 1f / (1f + exp(-sum)) + val idx = y * protoW + x + + if (v > mask[idx]) mask[idx] = v + } + } + } + + val pixels = IntArray(protoW * protoH) + + for (i in pixels.indices) { + val alpha = when { + mask[i] >= 0.55f -> 255 + mask[i] <= 0.35f -> 0 + else -> ((mask[i] - 0.35f) / 0.2f * 255f).toInt() + }.coerceIn(0, 255) + + pixels[i] = Color.argb(alpha, 255, 255, 255) + } + + val maskBmp = createBitmap(protoW, protoH, Bitmap.Config.ARGB_8888) + + maskBmp.setPixels( + pixels, + 0, + protoW, + 0, + 0, + protoW, + protoH + ) + + return maskBmp.scale(width, height) + } + + private fun nms( + detections: List, + iouThreshold: Float + ): List { + val sorted = detections + .sortedByDescending { it.score } + .toMutableList() + + val result = mutableListOf() + + while (sorted.isNotEmpty()) { + val best = sorted.removeAt(0) + result += best + + sorted.removeAll { + it.classId == best.classId && iou(best, it) > iouThreshold + } + } + + return result + } + + private fun iou(a: Detection, b: Detection): Float { + val ax1 = a.cx - a.w / 2f + val ay1 = a.cy - a.h / 2f + val ax2 = a.cx + a.w / 2f + val ay2 = a.cy + a.h / 2f + + val bx1 = b.cx - b.w / 2f + val by1 = b.cy - b.h / 2f + val bx2 = b.cx + b.w / 2f + val by2 = b.cy + b.h / 2f + + val x1 = maxOf(ax1, bx1) + val y1 = maxOf(ay1, by1) + val x2 = minOf(ax2, bx2) + val y2 = minOf(ay2, by2) + + val inter = maxOf(0f, x2 - x1) * maxOf(0f, y2 - y1) + val areaA = maxOf(0f, ax2 - ax1) * maxOf(0f, ay2 - ay1) + val areaB = maxOf(0f, bx2 - bx1) * maxOf(0f, by2 - by1) + + return inter / (areaA + areaB - inter + 1e-6f) + } + + private fun applyMask( + original: Bitmap, + mask: Bitmap + ): Bitmap { + + val w = original.width + val h = original.height + + val src = IntArray(w * h) + val msk = IntArray(w * h) + + original.getPixels(src, 0, w, 0, 0, w, h) + mask.getPixels(msk, 0, w, 0, 0, w, h) + + for (i in src.indices) { + + val alpha = Color.alpha(msk[i]) + + val c = src[i] + + src[i] = Color.argb( + alpha, + Color.red(c), + Color.green(c), + Color.blue(c) + ) + } + + return Bitmap.createBitmap( + src, + w, + h, + Bitmap.Config.ARGB_8888 + ) + } + + private fun bitmapToFloatBuffer( + bitmap: Bitmap + ): FloatBuffer { + + val pixels = IntArray( + trainedSize!! * trainedSize + ) + + bitmap.getPixels( + pixels, + 0, + trainedSize, + 0, + 0, + trainedSize, + trainedSize + ) + + val buffer = FloatBuffer.allocate( + 3 * trainedSize * trainedSize + ) + + var r = 0 + var g = trainedSize * trainedSize + var b = trainedSize * trainedSize * 2 + + for (p in pixels) { + + buffer.put( + r++, + Color.red(p) / 255f + ) + + buffer.put( + g++, + Color.green(p) / 255f + ) + + buffer.put( + b++, + Color.blue(p) / 255f + ) + } + + return buffer + } + +} \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/inpaint/LaMaProcessor.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/inpaint/LaMaProcessor.kt new file mode 100644 index 0000000..4632317 --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/inpaint/LaMaProcessor.kt @@ -0,0 +1,274 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 = total + ) + ) + } + } + + 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) + } +} \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/inpaint/WatermarkRemoverProcessor.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/inpaint/WatermarkRemoverProcessor.kt new file mode 100644 index 0000000..a9fa2e3 --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/inpaint/WatermarkRemoverProcessor.kt @@ -0,0 +1,340 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import kotlinx.io.readByteArray +import java.io.File +import java.io.FileOutputStream +import java.nio.FloatBuffer + +object WatermarkRemoverProcessor : NeuralTool() { + + const val WATERMARK_MODEL_NAME = "watermark_mit_b5_sigmoid.onnx" + + private const val TAG = "WatermarkRemoverProcessor" + + private const val TRAINED_SIZE = 512 + + private const val THRESHOLD = 0.18f + + private val MODEL_DOWNLOAD_LINK + get() = baseUrl.replace( + oldValue = "*", + newValue = WATERMARK_MODEL_NAME + ) + + private val directory: File + get() = File(context.filesDir, "onnx").apply { + mkdirs() + } + + val modelFile: File + get() = File(directory, WATERMARK_MODEL_NAME) + + 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 = combine( + _isDownloaded, + LaMaProcessor.isDownloaded + ) { watermarkDownloaded, lamaDownloaded -> + watermarkDownloaded && lamaDownloaded + }.stateIn( + scope = CoroutineScope(Dispatchers.Default), + started = SharingStarted.Eagerly, + initialValue = modelFile.exists() && LaMaProcessor.isDownloaded.value + ) + + fun startDownload(): Flow = callbackFlow { + startWatermarkModelDownload().collect { progress -> + send( + progress.copy( + currentPercent = progress.currentPercent * if (LaMaProcessor.isDownloaded.value) 1f else 0.5f, + currentTotalSize = progress.currentTotalSize + ) + ) + } + + if (!LaMaProcessor.isDownloaded.value) { + LaMaProcessor.startDownload().collect { progress -> + send( + progress.copy( + currentPercent = 0.5f + progress.currentPercent * 0.5f, + currentTotalSize = progress.currentTotalSize + ) + ) + } + } + + send( + DownloadProgress( + currentPercent = 1f, + currentTotalSize = 0L + ) + ) + }.flowOn(Dispatchers.IO) + + private fun startWatermarkModelDownload(): 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 = total + ) + ) + } + } + + tmp.renameTo(modelFile) + _isDownloaded.update { modelFile.exists() } + + close() + } catch (e: Throwable) { + tmp.delete() + close(e) + } + } + } + }.flowOn(Dispatchers.IO) + + fun removeWatermark( + image: Bitmap, + onMaskFound: () -> Unit = {} + ): Bitmap? { + val mask = findWatermarkMask(image = image) + ?: return null + + onMaskFound() + + return LaMaProcessor.inpaint( + image = image, + mask = mask + ) + } + + fun findWatermarkMask( + image: 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 tensorImg = bitmapToWatermarkTensor(bitmap = inputImage) + + session.run( + mapOf("input" to tensorImg) + ).use { res -> + val outValue = res[0] + val outTensor = outValue as? OnnxTensor + ?: throw IllegalStateException("Model output is not OnnxTensor") + + val mask512 = outputTensorToBinaryMaskBitmap(outTensor) + + tensorImg.close() + + if (image.width != TRAINED_SIZE || image.height != TRAINED_SIZE) { + return Aire.scale( + bitmap = mask512, + dstWidth = image.width, + dstHeight = image.height, + scaleMode = ResizeFunction.Nearest, + colorSpace = ScaleColorSpace.SRGB + ) + } + + return mask512 + } + }.onFailure { + Log.e(TAG, "findWatermarkMask failure", it) + }.getOrNull() + + private fun bitmapToWatermarkTensor( + 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 channelSize = w * h + val data = FloatArray(3 * channelSize) + + /* + * ВАЖНО: + * ONNX экспортнут без встроенной нормализации. + * Поэтому тут делаем ту же нормализацию, что в Python preview. + */ + val meanR = 0.485f + val meanG = 0.456f + val meanB = 0.406f + + val stdR = 0.229f + val stdG = 0.224f + val stdB = 0.225f + + 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 - meanR) / stdR + data[channelSize + i] = (g - meanG) / stdG + data[2 * channelSize + i] = (b - meanB) / stdB + } + + return OnnxTensor.createTensor( + env, + FloatBuffer.wrap(data), + longArrayOf( + 1, + 3, + h.toLong(), + w.toLong() + ) + ) + } + + private fun outputTensorToBinaryMaskBitmap( + 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) + + /* + * model output: + * [1, 1, 512, 512] + * + * output уже sigmoid probability [0..1], + * поэтому просто threshold -> black/white. + */ + for (i in 0 until size) { + val v = data[i] + val c = if (v >= THRESHOLD) 255 else 0 + + pixels[i] = (0xFF shl 24) or + (c shl 16) or + (c shl 8) or + c + } + + return Bitmap.createBitmap( + pixels, + width, + height, + Bitmap.Config.ARGB_8888 + ) + } + + fun deleteDownloadedModels() { + modelFile.delete() + close() + _isDownloaded.update { false } + } + + fun close() { + sessionHolder?.close() + sessionHolder = null + } +} \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/ocr/PaddleOCR.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/ocr/PaddleOCR.kt new file mode 100644 index 0000000..136705c --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/ocr/PaddleOCR.kt @@ -0,0 +1,1186 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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("TooManyFunctions", "MagicNumber", "SameParameterValue") + +package com.t8rin.neural_tools.ocr + +import ai.onnxruntime.OnnxTensor +import ai.onnxruntime.OrtEnvironment +import ai.onnxruntime.OrtSession +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Matrix +import android.graphics.Paint +import android.graphics.PointF +import androidx.core.graphics.createBitmap +import androidx.core.graphics.scale +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 java.util.ArrayDeque +import java.util.zip.ZipInputStream +import kotlin.math.abs +import kotlin.math.atan2 +import kotlin.math.ceil +import kotlin.math.floor +import kotlin.math.max +import kotlin.math.min +import kotlin.math.roundToInt +import kotlin.math.sqrt + +object PaddleOCR : NeuralTool() { + + enum class Model( + val title: String, + val fileName: String + ) { + CJK("CJK", "paddleocr_v5.zip"), + Korean("Korean", "paddleocr_v5_korean.zip"), + Latin("Latin", "paddleocr_v5_latin.zip"), + EastSlavic("East Slavic", "paddleocr_v5_eslav.zip"), + Thai("Thai", "paddleocr_v5_th.zip"), + Greek("Greek", "paddleocr_v5_el.zip"), + English("English", "paddleocr_v5_en.zip"), + Cyrillic("Cyrillic", "paddleocr_v5_cyrillic.zip"), + Arabic("Arabic", "paddleocr_v5_arabic.zip"), + Devanagari("Devanagari", "paddleocr_v5_devanagari.zip"), + Tamil("Tamil", "paddleocr_v5_ta.zip"), + Telugu("Telugu", "paddleocr_v5_te.zip"), + UniversalV6("PaddleOCRv6", "paddleocr_v6_medium.zip") + } + + private val env: OrtEnvironment by lazy { OrtEnvironment.getEnvironment() } + private var processor: OcrProcessor? = null + private var processorModel: Model? = null + + private val directory: File + get() = File(context.filesDir, "ai_models/paddleocr").apply(File::mkdirs) + + private fun modelDirectory(model: Model): File = File(directory, model.name).apply(File::mkdirs) + + private fun modelUrl(model: Model): String = + "https://huggingface.co/T8RIN/imagetoolbox-models/resolve/main/paddleocr/${model.fileName}?download=true" + + private fun modelFiles(model: Model): ModelFiles? { + fun resolveFrom(folder: File): ModelFiles? { + val files = folder.walkTopDown().filter(File::isFile).toList() + val det = files.firstOrNull { + it.name == "det.onnx" || it.name.contains( + "det", + true + ) && it.extension == "onnx" + } + val rec = files.firstOrNull { + it.name == "rec.onnx" || it.name.contains( + "rec", + true + ) && it.extension == "onnx" + } + val cls = files.firstOrNull { + it.name == "cls.onnx" || it.name.contains( + "cls", + true + ) && it.extension == "onnx" + } + val dict = files.firstOrNull { + it.name == "ppocrv5_dict.txt" || it.name.contains( + "dict", + true + ) && it.extension == "txt" + } + + return if (det != null && rec != null && cls != null && dict != null) { + ModelFiles( + detectionModel = det, + recognitionModel = rec, + classificationModel = cls, + dictionaryFile = dict + ) + } else { + null + } + } + + return resolveFrom(modelDirectory(model)) + } + + private val _isDownloaded = MutableStateFlow(modelFiles(Model.CJK) != null) + val isDownloaded: StateFlow = _isDownloaded + + fun isModelDownloaded(model: Model = Model.CJK): Boolean = modelFiles(model) != null + + fun checkModel(model: Model = Model.CJK): Boolean = + (modelFiles(model) != null).also { downloaded -> + _isDownloaded.update { downloaded } + if (!downloaded) close() + } + + fun deleteModel(model: Model) { + if (processorModel == model) close() + modelDirectory(model).deleteRecursively() + _isDownloaded.update { isModelDownloaded(Model.CJK) } + } + + fun startDownload( + model: Model = Model.CJK, + forced: Boolean = false + ): Flow = callbackFlow { + if (!forced && checkModel(model)) { + trySend(DownloadProgress(currentPercent = 1f, currentTotalSize = 0L)) + close() + return@callbackFlow + } + + val targetDirectory = modelDirectory(model) + val zipFile = File(targetDirectory, "${model.fileName}.tmp") + httpClient.prepareGet(modelUrl(model)).execute { response -> + val total = response.contentLength() ?: -1L + val channel = response.bodyAsChannel() + var downloaded = 0L + + FileOutputStream(zipFile).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 + ) + ) + } + } + + unzipModels(zipFile, targetDirectory) + _isDownloaded.update { checkModel(model) } + close() + } catch (e: Throwable) { + close(e) + } finally { + zipFile.delete() + } + } + } + }.flowOn(Dispatchers.IO) + + fun recognize( + image: Bitmap, + model: Model = Model.CJK + ): PaddleOCRResult? { + if (!checkModel(model)) return null + + val files = modelFiles(model) ?: return null + val ocrProcessor = processor?.takeIf { processorModel == model } ?: OcrProcessor( + modelFiles = files, + ortEnv = env + ).also { + close() + processor = it + processorModel = model + } + + val result = ocrProcessor.processImage(image) + return PaddleOCRResult( + text = result.texts.joinToString(separator = "\n"), + accuracy = if (result.scores.isEmpty()) 0 else (result.scores.average() * 100).roundToInt(), + hocr = result.toHocr(image.width, image.height) + ) + } + + fun close() { + processor?.close() + processor = null + processorModel = null + } + + private fun unzipModels( + zipFile: File, + targetDirectory: File + ) { + ZipInputStream(zipFile.inputStream()).use { zip -> + generateSequence { zip.nextEntry }.forEach { entry -> + if (!entry.isDirectory) { + val outFile = File(targetDirectory, entry.name.substringAfterLast('/')) + outFile.parentFile?.mkdirs() + FileOutputStream(outFile).use { output -> + zip.copyTo(output) + } + } + zip.closeEntry() + } + } + } +} + +private data class ModelFiles( + val detectionModel: File, + val recognitionModel: File, + val classificationModel: File, + val dictionaryFile: File +) + +private data class OcrResult( + val boxes: List, + val texts: List, + val scores: List +) { + fun toHocr( + imageWidth: Int, + imageHeight: Int + ): String = buildString { + append("""
""") + append('\n') + boxes.forEachIndexed { index, box -> + val rect = box.boundingRect() + val text = texts.getOrNull(index).orEmpty().escapeHocr() + append( + """ """ + ) + append( + """$text""" + ) + append("") + append('\n') + } + append("
") + } +} + +private data class TextBox( + val points: List +) { + fun boundingRect(): BoundingRect { + val minX = points.minOfOrNull { it.x }?.floorToInt() ?: 0 + val minY = points.minOfOrNull { it.y }?.floorToInt() ?: 0 + val maxX = points.maxOfOrNull { it.x }?.ceilToInt() ?: 0 + val maxY = points.maxOfOrNull { it.y }?.ceilToInt() ?: 0 + return BoundingRect(minX, minY, maxX, maxY) + } +} + +private data class BoundingRect( + val left: Int, + val top: Int, + val right: Int, + val bottom: Int +) + +private fun Float.floorToInt(): Int = floor(toDouble()).toInt() + +private fun Float.ceilToInt(): Int = ceil(toDouble()).toInt() + +private fun String.escapeHocr(): String = buildString { + this@escapeHocr.forEach { char -> + append( + when (char) { + '&' -> "&" + '<' -> "<" + '>' -> ">" + '"' -> """ + '\'' -> "'" + else -> char + } + ) + } +} + +private class OcrProcessor( + modelFiles: ModelFiles, + private val ortEnv: OrtEnvironment +) { + + private val sessionOptions = OrtSession.SessionOptions().apply { + setOptimizationLevel(OrtSession.SessionOptions.OptLevel.BASIC_OPT) + } + + private val detectionSession = ortEnv.createSession( + modelFiles.detectionModel.absolutePath, + sessionOptions + ) + private val recognitionSession = ortEnv.createSession( + modelFiles.recognitionModel.absolutePath, + sessionOptions + ) + private val classificationSession = ortEnv.createSession( + modelFiles.classificationModel.absolutePath, + sessionOptions + ) + private val characterDict = modelFiles.dictionaryFile.inputStream().use { stream -> + stream.bufferedReader().readLines().toMutableList().apply { + add(" ") + }.let { listOf("blank") + it } + } + + fun processImage(bitmap: Bitmap): OcrResult { + val detectionResult = TextDetector(detectionSession, ortEnv).detect(bitmap) + if (detectionResult.isEmpty()) { + return OcrResult(emptyList(), emptyList(), emptyList()) + } + + val croppedImages = detectionResult.map { box -> + ImageUtils.cropTextRegion(bitmap, ImageUtils.orderPointsClockwise(box.points)) + }.toMutableList() + + val classificationMask = BooleanArray(croppedImages.size) + val rotationStates = BooleanArray(croppedImages.size) + val aspectCandidates = croppedImages.mapIndexedNotNull { index, image -> + val aspectRatio = image.width.toFloat() / image.height + if (aspectRatio < 0.5f) index else null + } + classifyAndRotateIndices( + croppedImages, + aspectCandidates, + classificationMask, + rotationStates + ) + + val recognitionResults = TextRecognizer( + session = recognitionSession, + ortEnv = ortEnv, + characterDict = characterDict + ).recognize(croppedImages).toMutableList() + + val lowConfidenceIndices = recognitionResults.mapIndexedNotNull { index, result -> + if (!classificationMask[index] && result.confidence < 0.65f) index else null + } + if (lowConfidenceIndices.isNotEmpty()) { + classifyAndRotateIndices( + croppedImages, + lowConfidenceIndices, + classificationMask, + rotationStates + ) + val refreshed = TextRecognizer(recognitionSession, ortEnv, characterDict).recognize( + lowConfidenceIndices.map { croppedImages[it] } + ) + lowConfidenceIndices.forEachIndexed { refreshedIndex, originalIndex -> + val current = recognitionResults[originalIndex] + val updated = refreshed[refreshedIndex] + recognitionResults[originalIndex] = + if (updated.confidence > current.confidence) updated else current + } + } + + val boxes = mutableListOf() + val texts = mutableListOf() + val scores = mutableListOf() + + recognitionResults.forEachIndexed { index, recognition -> + if (recognition.confidence >= 0.5f && recognition.text.isNotBlank()) { + boxes.add(detectionResult[index]) + texts.add(recognition.text) + scores.add(recognition.confidence) + } + } + + return OcrResult(boxes, texts, scores) + } + + private fun classifyAndRotateIndices( + images: MutableList, + indices: List, + classificationMask: BooleanArray, + rotationStates: BooleanArray + ) { + if (indices.isEmpty()) return + + val outputs = TextClassifier( + session = classificationSession, + ortEnv = ortEnv + ).classifyAndRotate(indices.map { images[it] }) + + indices.forEachIndexed { idx, imageIndex -> + classificationMask[imageIndex] = true + val output = outputs[idx] + val old = images[imageIndex] + if (output.rotated) { + rotationStates[imageIndex] = !rotationStates[imageIndex] + } + images[imageIndex] = output.bitmap + if (output.rotated && output.bitmap !== old && !old.isRecycled) { + old.recycle() + } + } + } + + fun close() { + detectionSession.close() + recognitionSession.close() + classificationSession.close() + } +} + +private class TextDetector( + private val session: OrtSession, + private val ortEnv: OrtEnvironment +) { + + fun detect(bitmap: Bitmap): List { + val boxes = mutableListOf() + runDetection(bitmap) { box, _ -> + boxes.add(box) + false + } + return sortBoxes(boxes) + } + + private fun runDetection( + bitmap: Bitmap, + handler: (TextBox, Float) -> Boolean + ) { + val originalWidth = bitmap.width + val originalHeight = bitmap.height + val (inputTensor, resizedWidth, resizedHeight) = preprocessImage(bitmap) + var output: OnnxTensor? = null + + try { + output = session.run(mapOf("x" to inputTensor))[0] as OnnxTensor + postprocessDetection( + output = output, + originalWidth = originalWidth, + originalHeight = originalHeight, + resizedWidth = resizedWidth, + resizedHeight = resizedHeight, + handler = handler + ) + } finally { + output?.close() + inputTensor.close() + } + } + + private fun preprocessImage(bitmap: Bitmap): Triple { + val (resizedWidth, resizedHeight) = calculateResizeDimensions(bitmap.width, bitmap.height) + val resizedBitmap = bitmap.scale(resizedWidth, resizedHeight) + val inputArray = FloatArray(3 * resizedHeight * resizedWidth) + val pixels = IntArray(resizedWidth * resizedHeight) + resizedBitmap.getPixels(pixels, 0, resizedWidth, 0, 0, resizedWidth, resizedHeight) + + val mean = floatArrayOf(0.485f, 0.456f, 0.406f) + val std = floatArrayOf(0.229f, 0.224f, 0.225f) + var pixelIndex = 0 + + for (y in 0 until resizedHeight) { + for (x in 0 until resizedWidth) { + val pixel = pixels[y * resizedWidth + x] + val b = (pixel and 0xFF) / 255f + val g = ((pixel shr 8) and 0xFF) / 255f + val r = ((pixel shr 16) and 0xFF) / 255f + + inputArray[pixelIndex] = (b - mean[0]) / std[0] + inputArray[pixelIndex + resizedHeight * resizedWidth] = (g - mean[1]) / std[1] + inputArray[pixelIndex + 2 * resizedHeight * resizedWidth] = (r - mean[2]) / std[2] + pixelIndex++ + } + } + + if (resizedBitmap !== bitmap && !resizedBitmap.isRecycled) { + resizedBitmap.recycle() + } + + return Triple( + OnnxTensor.createTensor( + ortEnv, + FloatBuffer.wrap(inputArray), + longArrayOf(1, 3, resizedHeight.toLong(), resizedWidth.toLong()) + ), + resizedWidth, + resizedHeight + ) + } + + private fun calculateResizeDimensions(width: Int, height: Int): Pair { + val ratio = if (max(width, height) > 960) { + 960f / max(width, height) + } else { + 1f + } + val resizedWidth = max((((width * ratio).roundToInt() + 31) / 32) * 32, 32) + val resizedHeight = max((((height * ratio).roundToInt() + 31) / 32) * 32, 32) + return resizedWidth to resizedHeight + } + + private fun postprocessDetection( + output: OnnxTensor, + originalWidth: Int, + originalHeight: Int, + resizedWidth: Int, + resizedHeight: Int, + handler: (TextBox, Float) -> Boolean + ) { + val outputArray = ImageUtils.toFloatArray(output.floatBuffer) + val probMap = Array(resizedHeight) { y -> + FloatArray(resizedWidth) { x -> outputArray[y * resizedWidth + x] } + } + val binaryMap = Array(resizedHeight) { y -> + BooleanArray(resizedWidth) { x -> probMap[y][x] > 0.3f } + } + val scaleX = originalWidth.toFloat() / resizedWidth + val scaleY = originalHeight.toFloat() / resizedHeight + + extractConnectedComponents(binaryMap) + .sortedByDescending { it.size } + .take(1000) + .forEach { component -> + if (component.size < 4) return@forEach + + val hull = convexHull(component) + if (hull.size < 3) return@forEach + + val rect = minimumAreaRectangle(hull, true) + if (rect.isEmpty()) return@forEach + + val score = calculateBoxScore(probMap, rect) + if (score < 0.6f) return@forEach + + val expanded = unclipBox(rect, 1.5f) + val expandedRect = minimumAreaRectangle(expanded, false) + if (expandedRect.isEmpty() || getMinSide(expandedRect) < 3f) return@forEach + + val points = ImageUtils.orderPointsClockwise( + ImageUtils.clipBoxToImageBounds(expandedRect, resizedWidth, resizedHeight) + .map { point -> PointF(point.x * scaleX, point.y * scaleY) } + ) + + if (handler(TextBox(points), score)) return + } + } + + private fun extractConnectedComponents(binaryMap: Array): List> { + val height = binaryMap.size + val width = binaryMap.firstOrNull()?.size ?: 0 + val visited = Array(height) { BooleanArray(width) } + val components = mutableListOf>() + val stack = ArrayDeque>() + + for (y in 0 until height) { + for (x in 0 until width) { + if (!binaryMap[y][x] || visited[y][x]) continue + + val points = mutableListOf() + stack.clear() + stack.add(x to y) + visited[y][x] = true + + while (stack.isNotEmpty()) { + val (cx, cy) = stack.removeLast() + points.add(PointF(cx.toFloat(), cy.toFloat())) + + for (dy in -1..1) { + for (dx in -1..1) { + val nx = cx + dx + val ny = cy + dy + if ((dx != 0 || dy != 0) && nx in 0 until width && ny in 0 until height && + binaryMap[ny][nx] && !visited[ny][nx] + ) { + visited[ny][nx] = true + stack.add(nx to ny) + } + } + } + } + components.add(points) + } + } + return components + } + + private fun calculateBoxScore(probMap: Array, polygon: List): Float { + var minX = floor(polygon.minOf { it.x.toDouble() }).toInt() + var maxX = ceil(polygon.maxOf { it.x.toDouble() }).toInt() + var minY = floor(polygon.minOf { it.y.toDouble() }).toInt() + var maxY = ceil(polygon.maxOf { it.y.toDouble() }).toInt() + + minX = min(max(minX, 0), probMap[0].size - 1) + maxX = min(max(maxX, 0), probMap[0].size - 1) + minY = min(max(minY, 0), probMap.size - 1) + maxY = min(max(maxY, 0), probMap.size - 1) + + var sum = 0f + var count = 0 + for (y in minY..maxY) { + for (x in minX..maxX) { + if (isPointInsideQuad(x + 0.5f, y + 0.5f, polygon)) { + sum += probMap[y][x] + count++ + } + } + } + return if (count > 0) sum / count else 0f + } + + private fun minimumAreaRectangle(points: List, pointsAreConvex: Boolean): List { + val hull = if (pointsAreConvex) points else convexHull(points) + if (hull.size < 3) return emptyList() + + var bestRect = emptyList() + var minArea = Float.MAX_VALUE + + for (i in hull.indices) { + val p1 = hull[i] + val p2 = hull[(i + 1) % hull.size] + val edgeVec = normalizeVector(p1, p2) ?: continue + val normal = PointF(-edgeVec.y, edgeVec.x) + var minProj = Float.MAX_VALUE + var maxProj = -Float.MAX_VALUE + var minOrth = Float.MAX_VALUE + var maxOrth = -Float.MAX_VALUE + + for (pt in hull) { + val relX = pt.x - p1.x + val relY = pt.y - p1.y + val projection = relX * edgeVec.x + relY * edgeVec.y + val orthProjection = relX * normal.x + relY * normal.y + minProj = min(minProj, projection) + maxProj = max(maxProj, projection) + minOrth = min(minOrth, orthProjection) + maxOrth = max(maxOrth, orthProjection) + } + + val area = (maxProj - minProj) * (maxOrth - minOrth) + if (area < minArea && maxProj - minProj > 1e-3f && maxOrth - minOrth > 1e-3f) { + minArea = area + bestRect = listOf( + PointF( + p1.x + edgeVec.x * minProj + normal.x * minOrth, + p1.y + edgeVec.y * minProj + normal.y * minOrth + ), + PointF( + p1.x + edgeVec.x * maxProj + normal.x * minOrth, + p1.y + edgeVec.y * maxProj + normal.y * minOrth + ), + PointF( + p1.x + edgeVec.x * maxProj + normal.x * maxOrth, + p1.y + edgeVec.y * maxProj + normal.y * maxOrth + ), + PointF( + p1.x + edgeVec.x * minProj + normal.x * maxOrth, + p1.y + edgeVec.y * minProj + normal.y * maxOrth + ) + ) + } + } + return bestRect.ifEmpty { axisAlignedBoundingBox(hull) } + } + + private fun normalizeVector(from: PointF, to: PointF): PointF? { + val dx = to.x - from.x + val dy = to.y - from.y + val length = sqrt(dx * dx + dy * dy) + return if (length < 1e-6f) null else PointF(dx / length, dy / length) + } + + private fun axisAlignedBoundingBox(points: List): List { + val minX = points.minOf { it.x } + val maxX = points.maxOf { it.x } + val minY = points.minOf { it.y } + val maxY = points.maxOf { it.y } + return listOf( + PointF(minX, minY), + PointF(maxX, minY), + PointF(maxX, maxY), + PointF(minX, maxY) + ) + } + + private fun isPointInsideQuad(x: Float, y: Float, quad: List): Boolean { + var hasPositive = false + var hasNegative = false + for (i in quad.indices) { + val p1 = quad[i] + val p2 = quad[(i + 1) % quad.size] + val cross = (p2.x - p1.x) * (y - p1.y) - (p2.y - p1.y) * (x - p1.x) + if (cross > 0) hasPositive = true else if (cross < 0) hasNegative = true + if (hasPositive && hasNegative) return false + } + return true + } + + private fun convexHull(points: List): List { + if (points.size < 3) return points + + val sorted = points.sortedWith(compareBy({ it.x }, { it.y })) + val lower = mutableListOf() + val upper = mutableListOf() + + sorted.forEach { point -> + while (lower.size >= 2 && crossProduct( + lower[lower.size - 2], + lower.last(), + point + ) <= 0 + ) { + lower.removeAt(lower.lastIndex) + } + lower.add(point) + } + sorted.asReversed().forEach { point -> + while (upper.size >= 2 && crossProduct( + upper[upper.size - 2], + upper.last(), + point + ) <= 0 + ) { + upper.removeAt(upper.lastIndex) + } + upper.add(point) + } + + lower.removeAt(lower.lastIndex) + upper.removeAt(upper.lastIndex) + return lower + upper + } + + private fun crossProduct(o: PointF, a: PointF, b: PointF): Float = + (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x) + + private fun distance(p1: PointF, p2: PointF): Float { + val dx = p2.x - p1.x + val dy = p2.y - p1.y + return sqrt(dx * dx + dy * dy) + } + + private fun sortBoxes(boxes: List): List { + val sortedByTop = boxes.sortedBy { box -> box.points.minOf { it.y } } + val ordered = mutableListOf() + var index = 0 + while (index < sortedByTop.size) { + val current = sortedByTop[index] + val referenceY = current.points.minOf { it.y } + val group = mutableListOf() + var j = index + while (j < sortedByTop.size && abs(sortedByTop[j].points.minOf { it.y } - referenceY) <= 10f) { + group.add(sortedByTop[j]) + j++ + } + group.sortBy { box -> box.points.minOf { it.x } } + ordered.addAll(group) + index = j + } + return ordered + } + + private fun unclipBox(box: List, unclipRatio: Float): List { + val perimeter = polygonPerimeter(box) + if (perimeter <= 1e-6f) return emptyList() + + val offset = abs(polygonSignedArea(box)) * unclipRatio / perimeter + return offsetPolygon(box, offset).takeIf { it.size >= 3 } ?: emptyList() + } + + private fun getMinSide(box: List): Float = + box.indices.minOf { i -> distance(box[i], box[(i + 1) % box.size]) } + + private fun polygonSignedArea(points: List): Float { + var area = 0f + for (i in points.indices) { + val j = (i + 1) % points.size + area += points[i].x * points[j].y - points[j].x * points[i].y + } + return area / 2f + } + + private fun polygonPerimeter(points: List): Float = + points.indices.sumOf { i -> distance(points[i], points[(i + 1) % points.size]).toDouble() } + .toFloat() + + private fun offsetPolygon(points: List, offset: Float): List { + if (points.size < 3) return emptyList() + + val isCounterClockwise = polygonSignedArea(points) > 0f + return points.indices.mapNotNull { i -> + val prev = points[(i - 1 + points.size) % points.size] + val curr = points[i] + val next = points[(i + 1) % points.size] + val dir1 = normalize(PointF(curr.x - prev.x, curr.y - prev.y)) ?: return@mapNotNull null + val dir2 = normalize(PointF(next.x - curr.x, next.y - curr.y)) ?: return@mapNotNull null + val normal1 = + if (isCounterClockwise) PointF(dir1.y, -dir1.x) else PointF(-dir1.y, dir1.x) + val normal2 = + if (isCounterClockwise) PointF(dir2.y, -dir2.x) else PointF(-dir2.y, dir2.x) + intersectLines( + PointF(curr.x + normal1.x * offset, curr.y + normal1.y * offset), + dir1, + PointF(curr.x + normal2.x * offset, curr.y + normal2.y * offset), + dir2 + ) ?: PointF(curr.x, curr.y) + } + } + + private fun normalize(vector: PointF): PointF? { + val length = sqrt(vector.x * vector.x + vector.y * vector.y) + return if (length < 1e-6f) null else PointF(vector.x / length, vector.y / length) + } + + private fun intersectLines( + point: PointF, + direction: PointF, + otherPoint: PointF, + otherDirection: PointF + ): PointF? { + val cross = direction.x * otherDirection.y - direction.y * otherDirection.x + if (abs(cross) < 1e-6f) return null + + val diffX = otherPoint.x - point.x + val diffY = otherPoint.y - point.y + val t = (diffX * otherDirection.y - diffY * otherDirection.x) / cross + return PointF(point.x + direction.x * t, point.y + direction.y * t) + } +} + +private data class ClassificationOutput( + val bitmap: Bitmap, + val rotated: Boolean +) + +private class TextClassifier( + private val session: OrtSession, + private val ortEnv: OrtEnvironment +) { + + fun classifyAndRotate(images: List): List = + images.chunked(6).flatMap { batch -> + val flags = classifyBatch(batch) + batch.indices.map { index -> + if (flags[index]) { + ClassificationOutput(rotateImage180(batch[index]), true) + } else { + ClassificationOutput(batch[index], false) + } + } + } + + private fun classifyBatch(batchImages: List): List { + val inputArray = FloatArray(batchImages.size * 3 * 48 * 192) + batchImages.forEachIndexed { index, image -> + preprocessImage(image, inputArray, index) + } + + val inputTensor = OnnxTensor.createTensor( + ortEnv, + FloatBuffer.wrap(inputArray), + longArrayOf(batchImages.size.toLong(), 3, 48, 192) + ) + val output = session.run(mapOf(session.inputNames.first() to inputTensor))[0] as OnnxTensor + val outputArray = ImageUtils.toFloatArray(output.floatBuffer) + val result = List(batchImages.size) { index -> + val baseOffset = index * 2 + outputArray[baseOffset + 1] > outputArray[baseOffset] && outputArray[baseOffset + 1] > 0.9f + } + + output.close() + inputTensor.close() + return result + } + + private fun preprocessImage(bitmap: Bitmap, outputArray: FloatArray, batchIndex: Int) { + val resizedWidth = + min(ceil(48f * bitmap.width / bitmap.height).toInt().coerceAtLeast(1), 192) + val resizedBitmap = bitmap.scale(resizedWidth, 48) + val pixels = IntArray(resizedWidth * 48) + resizedBitmap.getPixels(pixels, 0, resizedWidth, 0, 0, resizedWidth, 48) + if (resizedBitmap !== bitmap && !resizedBitmap.isRecycled) resizedBitmap.recycle() + + val baseOffset = batchIndex * 3 * 48 * 192 + val channelStride = 48 * 192 + for (y in 0 until 48) { + val rowOffset = y * 192 + val sourceRowOffset = y * resizedWidth + for (x in 0 until 192) { + val pixelIndex = rowOffset + x + if (x < resizedWidth) { + val pixel = pixels[sourceRowOffset + x] + outputArray[baseOffset + pixelIndex] = (((pixel and 0xFF) / 255f) - 0.5f) / 0.5f + outputArray[baseOffset + channelStride + pixelIndex] = + ((((pixel shr 8) and 0xFF) / 255f) - 0.5f) / 0.5f + outputArray[baseOffset + 2 * channelStride + pixelIndex] = + ((((pixel shr 16) and 0xFF) / 255f) - 0.5f) / 0.5f + } + } + } + } + + private fun rotateImage180(bitmap: Bitmap): Bitmap { + val matrix = Matrix().apply { postRotate(180f) } + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) + } +} + +private data class RecognitionResult( + val text: String, + val confidence: Float +) + +private class TextRecognizer( + private val session: OrtSession, + private val ortEnv: OrtEnvironment, + private val characterDict: List +) { + + fun recognize(images: List): List { + if (images.isEmpty()) return emptyList() + + val sortedIndices = + images.indices.sortedBy { images[it].width.toFloat() / images[it].height } + val orderedResults = MutableList(images.size) { RecognitionResult("", 0f) } + + sortedIndices.chunked(6).forEach { batchIndices -> + val batchResults = processBatch(batchIndices.map { images[it] }) + batchIndices.forEachIndexed { idx, originalIndex -> + orderedResults[originalIndex] = batchResults[idx] + } + } + return orderedResults + } + + private fun processBatch(batchImages: List): List { + var maxWhRatio = 320f / 48f + batchImages.forEach { image -> + maxWhRatio = max(maxWhRatio, image.width.toFloat() / image.height) + } + val targetWidth = ceil(48f * maxWhRatio).toInt().coerceAtLeast(1) + val inputArray = FloatArray(batchImages.size * 3 * 48 * targetWidth) + batchImages.forEachIndexed { index, image -> + preprocessImage(image, inputArray, index, targetWidth) + } + + val inputTensor = OnnxTensor.createTensor( + ortEnv, + FloatBuffer.wrap(inputArray), + longArrayOf(batchImages.size.toLong(), 3, 48, targetWidth.toLong()) + ) + val output = session.run(mapOf(session.inputNames.first() to inputTensor))[0] as OnnxTensor + val results = decodeOutput(output, batchImages.size) + + output.close() + inputTensor.close() + return results + } + + private fun preprocessImage( + bitmap: Bitmap, + outputArray: FloatArray, + batchIndex: Int, + targetWidth: Int + ) { + val resizedWidth = + min(ceil(48f * bitmap.width / bitmap.height).toInt().coerceAtLeast(1), targetWidth) + val resizedBitmap = bitmap.scale(resizedWidth, 48) + val pixels = IntArray(resizedWidth * 48) + resizedBitmap.getPixels(pixels, 0, resizedWidth, 0, 0, resizedWidth, 48) + if (resizedBitmap !== bitmap && !resizedBitmap.isRecycled) resizedBitmap.recycle() + + val baseOffset = batchIndex * 3 * 48 * targetWidth + val channelStride = 48 * targetWidth + for (y in 0 until 48) { + val rowOffset = y * targetWidth + val sourceRowOffset = y * resizedWidth + for (x in 0 until targetWidth) { + val pixelIndex = rowOffset + x + if (x < resizedWidth) { + val pixel = pixels[sourceRowOffset + x] + outputArray[baseOffset + pixelIndex] = (((pixel and 0xFF) / 255f) - 0.5f) / 0.5f + outputArray[baseOffset + channelStride + pixelIndex] = + ((((pixel shr 8) and 0xFF) / 255f) - 0.5f) / 0.5f + outputArray[baseOffset + 2 * channelStride + pixelIndex] = + ((((pixel shr 16) and 0xFF) / 255f) - 0.5f) / 0.5f + } + } + } + } + + private fun decodeOutput(output: OnnxTensor, batchSize: Int): List { + val outputArray = ImageUtils.toFloatArray(output.floatBuffer) + val shape = output.info.shape + val seqLen = shape[1].toInt() + val vocabSize = shape[2].toInt() + + return List(batchSize) { batch -> + val batchOffset = batch * seqLen * vocabSize + val charIndices = IntArray(seqLen) + val probs = FloatArray(seqLen) + + for (t in 0 until seqLen) { + val timeOffset = batchOffset + t * vocabSize + var maxProb = outputArray[timeOffset] + var maxIndex = 0 + for (c in 1 until vocabSize) { + val prob = outputArray[timeOffset + c] + if (prob > maxProb) { + maxProb = prob + maxIndex = c + } + } + charIndices[t] = maxIndex + probs[t] = maxProb + } + ctcDecode(charIndices, probs) + } + } + + private fun ctcDecode(charIndices: IntArray, probs: FloatArray): RecognitionResult { + val decodedChars = mutableListOf() + val decodedProbs = mutableListOf() + var t = 0 + + while (t < charIndices.size) { + val currentIndex = charIndices[t] + if (currentIndex == 0) { + t++ + continue + } + + var end = t + 1 + var probSum = probs[t] + var count = 1 + while (end < charIndices.size && charIndices[end] == currentIndex) { + probSum += probs[end] + end++ + count++ + } + + if (currentIndex < characterDict.size) { + decodedChars.add(characterDict[currentIndex]) + decodedProbs.add(probSum / count) + } + t = end + } + + return RecognitionResult( + text = decodedChars.joinToString(""), + confidence = decodedProbs.takeIf { it.isNotEmpty() }?.average()?.toFloat() ?: 0f + ) + } +} + +private object ImageUtils { + + fun toFloatArray(buffer: FloatBuffer): FloatArray { + val duplicate = buffer.duplicate() + duplicate.rewind() + return FloatArray(duplicate.remaining()).also(duplicate::get) + } + + fun cropTextRegion(bitmap: Bitmap, points: List): Bitmap { + val width = max(distance(points[0], points[1]), distance(points[2], points[3])) + .toInt() + .coerceAtLeast(1) + val height = max(distance(points[0], points[3]), distance(points[1], points[2])) + .toInt() + .coerceAtLeast(1) + + val matrix = Matrix().apply { + setPolyToPoly( + floatArrayOf( + points[0].x, + points[0].y, + points[1].x, + points[1].y, + points[2].x, + points[2].y, + points[3].x, + points[3].y + ), + 0, + floatArrayOf( + 0f, + 0f, + width.toFloat(), + 0f, + width.toFloat(), + height.toFloat(), + 0f, + height.toFloat() + ), + 0, + 4 + ) + } + val croppedBitmap = createBitmap(width, height) + Canvas(croppedBitmap).drawBitmap( + bitmap, + matrix, + Paint(Paint.ANTI_ALIAS_FLAG).apply { isFilterBitmap = true } + ) + + return if (height.toFloat() / width >= 1.5f) { + val rotationMatrix = Matrix().apply { postRotate(90f) } + Bitmap.createBitmap( + croppedBitmap, + 0, + 0, + croppedBitmap.width, + croppedBitmap.height, + rotationMatrix, + true + ) + } else { + croppedBitmap + } + } + + fun orderPointsClockwise(points: List): List { + if (points.size != 4) return points + + val centerX = points.sumOf { it.x.toDouble() }.toFloat() / 4 + val centerY = points.sumOf { it.y.toDouble() }.toFloat() / 4 + val sortedPoints = points.sortedBy { point -> + atan2((point.y - centerY).toDouble(), (point.x - centerX).toDouble()) + } + val topLeftIndex = sortedPoints.indices.minBy { + sortedPoints[it].x + sortedPoints[it].y + } + return List(4) { sortedPoints[(topLeftIndex + it) % 4] } + } + + fun clipBoxToImageBounds( + points: List, + imageWidth: Int, + imageHeight: Int + ): List = + points.map { point -> + PointF( + point.x.coerceIn(0f, imageWidth - 1f), + point.y.coerceIn(0f, imageHeight - 1f) + ) + } + + private fun distance(p1: PointF, p2: PointF): Float { + val dx = p2.x - p1.x + val dy = p2.y - p1.y + return sqrt(dx * dx + dy * dy) + } +} \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/ocr/PaddleOCRResult.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/ocr/PaddleOCRResult.kt new file mode 100644 index 0000000..6af9908 --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/ocr/PaddleOCRResult.kt @@ -0,0 +1,24 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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.ocr + +data class PaddleOCRResult( + val text: String, + val accuracy: Int, + val hocr: String +) \ No newline at end of file diff --git a/lib/neural-tools/src/main/java/com/t8rin/neural_tools/scans/UvDocUnwarper.kt b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/scans/UvDocUnwarper.kt new file mode 100644 index 0000000..7fa63f1 --- /dev/null +++ b/lib/neural-tools/src/main/java/com/t8rin/neural_tools/scans/UvDocUnwarper.kt @@ -0,0 +1,237 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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.scans + +import ai.onnxruntime.OnnxTensor +import ai.onnxruntime.OrtEnvironment +import ai.onnxruntime.OrtSession +import android.graphics.Bitmap +import android.graphics.Color +import android.util.Log +import androidx.core.graphics.scale +import com.t8rin.neural_tools.NeuralTool +import java.nio.FloatBuffer +import kotlin.math.floor + +object UvDocUnwarper : NeuralTool() { + + private const val TAG = "UvDocUnwarper" + private const val INPUT_WIDTH = 496 + private const val INPUT_HEIGHT = 720 + private const val CHANNELS = 3 + + fun unwarp( + image: Bitmap, + session: OrtSession + ): Bitmap? = runCatching { + val inputImage = image.scale(INPUT_WIDTH, INPUT_HEIGHT) + val inputTensor = bitmapToTensor(inputImage) + + try { + session.run(mapOf(session.inputNames.first() to inputTensor)).use { result -> + val outputTensor = result[0] as? OnnxTensor + ?: throw IllegalStateException("Model output is not OnnxTensor") + + applyGrid( + source = image, + grid = outputTensorToGrid(outputTensor) + ) + } + } finally { + inputTensor.close() + if (inputImage !== image) inputImage.recycle() + } + }.onFailure { + Log.e(TAG, "unwarp failure", it) + }.getOrNull() + + private fun bitmapToTensor( + bitmap: Bitmap + ): OnnxTensor { + val pixels = IntArray(INPUT_WIDTH * INPUT_HEIGHT) + bitmap.getPixels(pixels, 0, INPUT_WIDTH, 0, 0, INPUT_WIDTH, INPUT_HEIGHT) + + val planeSize = INPUT_WIDTH * INPUT_HEIGHT + val input = FloatArray(planeSize * CHANNELS) + + pixels.forEachIndexed { index, color -> + input[index] = Color.red(color) / 255f + input[planeSize + index] = Color.green(color) / 255f + input[planeSize * 2 + index] = Color.blue(color) / 255f + } + + return OnnxTensor.createTensor( + OrtEnvironment.getEnvironment(), + FloatBuffer.wrap(input), + longArrayOf(1, CHANNELS.toLong(), INPUT_HEIGHT.toLong(), INPUT_WIDTH.toLong()) + ) + } + + private fun outputTensorToGrid( + tensor: OnnxTensor + ): Grid { + val shape = tensor.info.shape + val height = shape[2].toInt() + val width = shape[3].toInt() + val planeSize = width * height + val data = FloatArray(tensor.floatBuffer.capacity()) + tensor.floatBuffer.get(data) + + return Grid( + x = Array(height) { row -> + FloatArray(width) { column -> + data[row * width + column] + } + }, + y = Array(height) { row -> + FloatArray(width) { column -> + data[planeSize + row * width + column] + } + } + ) + } + + private fun applyGrid( + source: Bitmap, + grid: Grid + ): Bitmap { + val width = source.width + val height = source.height + val sourcePixels = IntArray(width * height) + val resultPixels = IntArray(width * height) + source.getPixels(sourcePixels, 0, width, 0, 0, width, height) + + for (y in 0 until height) { + val gridRow = if (height > 1) { + y.toFloat() / (height - 1) * (grid.x.size - 1) + } else { + 0f + } + + for (x in 0 until width) { + val gridColumn = if (width > 1) { + x.toFloat() / (width - 1) * (grid.x[0].size - 1) + } else { + 0f + } + val normalizedX = sampleGrid(grid.x, gridColumn, gridRow) + val normalizedY = sampleGrid(grid.y, gridColumn, gridRow) + val sourceX = (normalizedX + 1f) * 0.5f * (width - 1) + val sourceY = (normalizedY + 1f) * 0.5f * (height - 1) + + resultPixels[y * width + x] = sampleSourcePixel( + pixels = sourcePixels, + width = width, + height = height, + x = sourceX, + y = sourceY + ) + } + } + + return Bitmap.createBitmap( + resultPixels, + width, + height, + Bitmap.Config.ARGB_8888 + ) + } + + private fun sampleGrid( + grid: Array, + x: Float, + y: Float + ): Float { + val x0 = floor(x).toInt().coerceIn(0, grid[0].lastIndex) + val y0 = floor(y).toInt().coerceIn(0, grid.lastIndex) + val x1 = (x0 + 1).coerceAtMost(grid[0].lastIndex) + val y1 = (y0 + 1).coerceAtMost(grid.lastIndex) + val tx = x - x0 + val ty = y - y0 + + val top = grid[y0][x0] * (1f - tx) + grid[y0][x1] * tx + val bottom = grid[y1][x0] * (1f - tx) + grid[y1][x1] * tx + return top * (1f - ty) + bottom * ty + } + + private fun sampleSourcePixel( + pixels: IntArray, + width: Int, + height: Int, + x: Float, + y: Float + ): Int { + val safeX = x.coerceIn(0f, (width - 1).toFloat()) + val safeY = y.coerceIn(0f, (height - 1).toFloat()) + val x0 = floor(safeX).toInt() + val y0 = floor(safeY).toInt() + val x1 = (x0 + 1).coerceAtMost(width - 1) + val y1 = (y0 + 1).coerceAtMost(height - 1) + val tx = safeX - x0 + val ty = safeY - y0 + + val topLeft = pixels[y0 * width + x0] + val topRight = pixels[y0 * width + x1] + val bottomLeft = pixels[y1 * width + x0] + val bottomRight = pixels[y1 * width + x1] + + return Color.argb( + bilinearChannel(topLeft, topRight, bottomLeft, bottomRight, tx, ty, Color::alpha), + bilinearChannel(topLeft, topRight, bottomLeft, bottomRight, tx, ty, Color::red), + bilinearChannel(topLeft, topRight, bottomLeft, bottomRight, tx, ty, Color::green), + bilinearChannel(topLeft, topRight, bottomLeft, bottomRight, tx, ty, Color::blue) + ) + } + + private fun bilinearChannel( + topLeft: Int, + topRight: Int, + bottomLeft: Int, + bottomRight: Int, + tx: Float, + ty: Float, + channel: (Int) -> Int + ): Int { + val top = channel(topLeft) * (1f - tx) + channel(topRight) * tx + val bottom = channel(bottomLeft) * (1f - tx) + channel(bottomRight) * tx + return (top * (1f - ty) + bottom * ty).toInt().coerceIn(0, 255) + } + + private data class Grid( + val x: Array, + val y: Array + ) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Grid + + if (!x.contentDeepEquals(other.x)) return false + if (!y.contentDeepEquals(other.y)) return false + + return true + } + + override fun hashCode(): Int { + var result = x.contentDeepHashCode() + result = 31 * result + y.contentDeepHashCode() + return result + } + } +} \ No newline at end of file diff --git a/lib/opencv-tools/.gitignore b/lib/opencv-tools/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lib/opencv-tools/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/opencv-tools/build.gradle.kts b/lib/opencv-tools/build.gradle.kts new file mode 100644 index 0000000..f6259b1 --- /dev/null +++ b/lib/opencv-tools/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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(libs.toolbox.exif) + implementation(projects.lib.image) + implementation(projects.lib.zoomable) + implementation(projects.lib.gesture) +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/AndroidManifest.xml b/lib/opencv-tools/src/main/AndroidManifest.xml new file mode 100644 index 0000000..205decf --- /dev/null +++ b/lib/opencv-tools/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/lib/opencv-tools/src/main/assets/face_detection_yunet_2026may.onnx b/lib/opencv-tools/src/main/assets/face_detection_yunet_2026may.onnx new file mode 100644 index 0000000..a939997 Binary files /dev/null and b/lib/opencv-tools/src/main/assets/face_detection_yunet_2026may.onnx differ diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/AutoStraighten.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/AutoStraighten.kt new file mode 100644 index 0000000..593e967 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/AutoStraighten.kt @@ -0,0 +1,285 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.ensureActive +import org.opencv.core.Core +import org.opencv.core.Mat +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.geometry.Geometry +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() { + + suspend 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 suspend fun autoDeskew( + input: Bitmap, + maxSkew: Int, + allowCrop: Boolean + ): Bitmap = coroutineScope { + ensureActive() + 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@coroutineScope input + + val angles = mutableListOf() + for (i in 0 until lines.rows()) { + ensureActive() + 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@coroutineScope 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 = Geometry.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() + ensureActive() + Imgproc.warpAffine( + rotated, + rotatedFull, + rotMat, + Size(newWidth.toDouble(), newHeight.toDouble()), + Imgproc.INTER_LINEAR, + Core.BORDER_REPLICATE, + ) + + if (allowCrop) { + val cropRect = getLargestRotatedRect( + width = rotated.width(), + height = rotated.height(), + angle = angleRad + ) + + Mat(rotatedFull, cropRect).clone() + } else { + rotatedFull + }.toBitmap() + } + + private suspend fun autoPerspective(input: Bitmap): Bitmap = coroutineScope { + ensureActive() + val corners = PerspectiveDetector.findCorners(input) + ?: return@coroutineScope input + ensureActive() + + val sorted = corners.clone() + 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() + if (maxWidth < 2 || maxHeight < 2) return@coroutineScope input + + val dst = MatOfPoint2f( + Point(0.0, 0.0), + Point(maxWidth.toDouble(), 0.0), + Point(maxWidth.toDouble(), maxHeight.toDouble()), + Point(0.0, maxHeight.toDouble()) + ) + + val srcMat = input.toMat() + val src = MatOfPoint2f(*sorted) + try { + val transform = Geometry.getPerspectiveTransform(src, dst) + try { + val out = Mat() + try { + ensureActive() + Imgproc.warpPerspective( + srcMat, + out, + transform, + Size(maxWidth.toDouble(), maxHeight.toDouble()), + Imgproc.INTER_LINEAR, + Core.BORDER_REPLICATE, + Scalar(0.0) + ) + + out.toBitmap() + } finally { + out.release() + } + } finally { + transform.release() + } + } finally { + srcMat.release() + src.release() + dst.release() + } + } + + private suspend fun perspectiveFromPoints( + input: Bitmap, + corners: Corners + ): Bitmap = coroutineScope { + ensureActive() + val width = input.width + val height = input.height + + val absPoints = if (corners.isAbsolute) { + corners.points.map { + ensureActive() + Offset( + x = it.x.toFloat(), + y = it.y.toFloat() + ) + } + } else { + corners.points.map { + ensureActive() + Offset( + x = (it.x * width).toFloat(), + y = (it.y * height).toFloat() + ) + } + } + + FreeCrop.crop( + bitmap = input, + points = absPoints + ) + } + + 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] + } + +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/PerspectiveDetector.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/PerspectiveDetector.kt new file mode 100644 index 0000000..026e24d --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/PerspectiveDetector.kt @@ -0,0 +1,384 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.opencv_tools.auto_straight + +import android.graphics.Bitmap +import com.t8rin.opencv_tools.document_detector.DocumentDetector +import com.t8rin.opencv_tools.utils.OpenCV +import com.t8rin.opencv_tools.utils.toMat +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.ensureActive +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.geometry.Geometry +import org.opencv.imgproc.Imgproc +import kotlin.math.abs +import kotlin.math.atan2 +import kotlin.math.max +import kotlin.math.min +import kotlin.math.sqrt + +internal object PerspectiveDetector : OpenCV() { + + private const val MAX_DETECTION_SIDE = 1200.0 + private const val MIN_AREA_FACTOR = 0.02 + private const val MAX_CORNER_COSINE = 0.82 + private val approximationFactors = doubleArrayOf(0.012, 0.02, 0.035, 0.05) + + private class Candidate( + val points: Array, + val score: Double + ) + + suspend fun findCorners(image: Bitmap): Array? = coroutineScope { + ensureActive() + val candidates = mutableListOf() + + DocumentDetector.findDocumentCorners(image)?.let { points -> + val sortedPoints = sortCorners(points.toTypedArray()) + addCandidate( + points = sortedPoints, + contourArea = polygonArea(sortedPoints), + imageWidth = image.width.toDouble(), + imageHeight = image.height.toDouble(), + scale = 1.0, + sourceBonus = 0.3, + candidates = candidates + ) + } + ensureActive() + + val source = image.toMat() + val resized = Mat() + val gray = Mat() + val blurred = Mat() + val kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, Size(5.0, 5.0)) + val masks = mutableListOf>() + + try { + val maxSide = max(image.width, image.height).toDouble() + val scale = max(1.0, maxSide / MAX_DETECTION_SIDE) + val width = image.width / scale + val height = image.height / scale + Imgproc.resize(source, resized, Size(width, height)) + + when (resized.channels()) { + 4 -> Imgproc.cvtColor(resized, gray, Imgproc.COLOR_RGBA2GRAY) + 3 -> Imgproc.cvtColor(resized, gray, Imgproc.COLOR_RGB2GRAY) + else -> resized.copyTo(gray) + } + Imgproc.GaussianBlur(gray, blurred, Size(5.0, 5.0), 0.0) + + val otsuThreshold = addOtsuMask( + blurred, + kernel, + Imgproc.THRESH_BINARY, + 0.1, + masks + ) + addOtsuMask(blurred, kernel, Imgproc.THRESH_BINARY_INV, 0.1, masks) + val dynamicLowerThreshold = (otsuThreshold * 0.45).coerceIn(20.0, 110.0) + val dynamicUpperThreshold = (otsuThreshold * 1.35) + .coerceIn(dynamicLowerThreshold + 30.0, 255.0) + addEdgeMask( + blurred, + kernel, + dynamicLowerThreshold, + dynamicUpperThreshold, + 0.25, + masks + ) + addEdgeMask(blurred, kernel, 30.0, 100.0, 0.2, masks) + + val adaptiveBlockSize = (min(width, height) / 12) + .toInt() + .coerceIn(15, 101) + .let { if (it % 2 == 0) it + 1 else it } + addAdaptiveMask( + image = blurred, + kernel = kernel, + thresholdType = Imgproc.THRESH_BINARY, + blockSize = adaptiveBlockSize, + sourceBonus = 0.15, + masks = masks + ) + addAdaptiveMask( + image = blurred, + kernel = kernel, + thresholdType = Imgproc.THRESH_BINARY_INV, + blockSize = adaptiveBlockSize, + sourceBonus = 0.15, + masks = masks + ) + + masks.forEach { (mask, sourceBonus) -> + ensureActive() + collectCandidates( + mask = mask, + imageWidth = width, + imageHeight = height, + scale = scale, + sourceBonus = sourceBonus, + candidates = candidates + ) + } + } finally { + source.release() + resized.release() + gray.release() + blurred.release() + kernel.release() + masks.forEach { (mask) -> mask.release() } + } + + candidates.maxByOrNull(Candidate::score)?.points + } + + private fun addEdgeMask( + image: Mat, + kernel: Mat, + lowerThreshold: Double, + upperThreshold: Double, + sourceBonus: Double, + masks: MutableList> + ) { + val mask = Mat() + masks += mask to sourceBonus + Imgproc.Canny(image, mask, lowerThreshold, upperThreshold) + Imgproc.morphologyEx(mask, mask, Imgproc.MORPH_CLOSE, kernel) + Imgproc.dilate(mask, mask, kernel) + } + + private fun addOtsuMask( + image: Mat, + kernel: Mat, + thresholdType: Int, + sourceBonus: Double, + masks: MutableList> + ): Double { + val mask = Mat() + masks += mask to sourceBonus + val threshold = Imgproc.threshold( + image, + mask, + 0.0, + 255.0, + thresholdType or Imgproc.THRESH_OTSU + ) + Imgproc.morphologyEx(mask, mask, Imgproc.MORPH_CLOSE, kernel) + return threshold + } + + private fun addAdaptiveMask( + image: Mat, + kernel: Mat, + thresholdType: Int, + blockSize: Int, + sourceBonus: Double, + masks: MutableList> + ) { + val mask = Mat() + masks += mask to sourceBonus + Imgproc.adaptiveThreshold( + image, + mask, + 255.0, + Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, + thresholdType, + blockSize, + 7.0 + ) + Imgproc.morphologyEx(mask, mask, Imgproc.MORPH_CLOSE, kernel) + } + + private suspend fun collectCandidates( + mask: Mat, + imageWidth: Double, + imageHeight: Double, + scale: Double, + sourceBonus: Double, + candidates: MutableList + ) { + val contours = mutableListOf() + val hierarchy = Mat() + try { + Imgproc.findContours( + mask, + contours, + hierarchy, + Imgproc.RETR_LIST, + Imgproc.CHAIN_APPROX_SIMPLE + ) + + val imageArea = imageWidth * imageHeight + for (contour in contours) { + currentCoroutineContext().ensureActive() + val contourArea = abs(Geometry.contourArea(contour)) + if (contourArea < imageArea * MIN_AREA_FACTOR) continue + + val contour2f = MatOfPoint2f(*contour.toArray()) + try { + val perimeter = Geometry.arcLength(contour2f, true) + if (perimeter <= 0.0) continue + + for (approximationFactor in approximationFactors) { + val approximation = MatOfPoint2f() + try { + Geometry.approxPolyDP( + contour2f, + approximation, + perimeter * approximationFactor, + true + ) + if (approximation.total() != 4L) continue + + val quad = MatOfPoint(*approximation.toArray()) + try { + if (!Geometry.isContourConvex(quad)) continue + + addCandidate( + points = approximation.toArray(), + contourArea = contourArea, + imageWidth = imageWidth, + imageHeight = imageHeight, + scale = scale, + sourceBonus = sourceBonus, + candidates = candidates + ) + } finally { + quad.release() + } + } finally { + approximation.release() + } + } + } finally { + contour2f.release() + } + } + } finally { + contours.forEach(Mat::release) + hierarchy.release() + } + } + + private fun addCandidate( + points: Array, + contourArea: Double, + imageWidth: Double, + imageHeight: Double, + scale: Double, + sourceBonus: Double, + candidates: MutableList + ) { + val sorted = sortCorners(points) + val quadArea = polygonArea(sorted) + val imageArea = imageWidth * imageHeight + val areaFactor = quadArea / imageArea + if (areaFactor !in MIN_AREA_FACTOR..1.02) return + + val maxCosine = sorted.maxCornerCosine() + if (maxCosine > MAX_CORNER_COSINE) return + + val sides = sorted.indices.map { index -> + distance(sorted[index], sorted[(index + 1) % sorted.size]) + } + if (sides.min() < min(imageWidth, imageHeight) * 0.04) return + + val contourFill = (contourArea / quadArea).coerceIn(0.0, 1.0) + if (contourFill < 0.45) return + + val borderMargin = min(imageWidth, imageHeight) * 0.012 + val borderPoints = sorted.count { point -> + point.x <= borderMargin || + point.y <= borderMargin || + point.x >= imageWidth - borderMargin || + point.y >= imageHeight - borderMargin + } + if (borderPoints == 4) return + + val borderPenalty = when (borderPoints) { + 3 -> 0.7 + else -> 0.0 + } + val score = areaFactor * 5.0 + + (1.0 - maxCosine) * 2.0 + + contourFill + + sourceBonus - + borderPenalty + + candidates += Candidate( + points = sorted.map { point -> + Point(point.x * scale, point.y * scale) + }.toTypedArray(), + score = score + ) + } + + private fun sortCorners(points: Array): Array { + val centerX = points.map(Point::x).average() + val centerY = points.map(Point::y).average() + val ordered = points.sortedBy { point -> + atan2(point.y - centerY, point.x - centerX) + } + val topLeftIndex = ordered.indices.minBy { index -> + ordered[index].x + ordered[index].y + } + + return Array(ordered.size) { offset -> + ordered[(topLeftIndex + offset) % ordered.size] + } + } + + private fun Array.maxCornerCosine(): Double = indices.maxOf { index -> + val previous = this[(index + size - 1) % size] + val current = this[index] + val next = this[(index + 1) % size] + val firstX = previous.x - current.x + val firstY = previous.y - current.y + val secondX = next.x - current.x + val secondY = next.y - current.y + val denominator = sqrt( + (firstX * firstX + firstY * firstY) * + (secondX * secondX + secondY * secondY) + ) + + if (denominator == 0.0) 1.0 + else abs((firstX * secondX + firstY * secondY) / denominator) + } + + private fun polygonArea(points: Array): Double = abs( + points.indices.sumOf { index -> + val current = points[index] + val next = points[(index + 1) % points.size] + current.x * next.y - next.x * current.y + } / 2.0 + ) + + private fun distance(first: Point, second: Point): Double { + val dx = first.x - second.x + val dy = first.y - second.y + return sqrt(dx * dx + dy * dy) + } +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/model/Corners.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/model/Corners.kt new file mode 100644 index 0000000..8e4b12b --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/model/Corners.kt @@ -0,0 +1,28 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/model/PointD.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/model/PointD.kt new file mode 100644 index 0000000..11d0694 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/model/PointD.kt @@ -0,0 +1,20 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/model/StraightenMode.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/model/StraightenMode.kt new file mode 100644 index 0000000..5d04ebd --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/model/StraightenMode.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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() +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/AutoCropper.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/AutoCropper.kt new file mode 100644 index 0000000..a9739c8 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/AutoCropper.kt @@ -0,0 +1,123 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.ensureActive +import org.opencv.core.CvType +import org.opencv.core.Mat +import org.opencv.imgproc.Imgproc + + +object AutoCropper : OpenCV() { + + suspend 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 suspend fun Bitmap.findEdges( + @CropSensitivity sensitivity: Int + ): CropParameters? = coroutineScope { + ensureActive() + val matRGBA = toMat() + getEdgeCandidates(matRGBA, sensitivity)?.let { + CropParameters( + edges = getMaxScoreCropEdges(candidates = it, matRGBA = matRGBA), + candidates = it + ) + } + } + + private suspend fun getEdgeCandidates( + matRGBA: Mat, + @CropSensitivity sensitivity: Int + ): List? = coroutineScope { + // 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@coroutineScope (0 until matCanny.rows()).filter { i -> + ensureActive() + matCanny.row(i).singleChannelMean() > threshold + }.run { + if (isEmpty()) null + else listOf(0) + this + listOf(matCanny.rows()) + } + } + + private suspend fun getMaxScoreCropEdges( + candidates: List, + matRGBA: Mat + ): CropEdges = coroutineScope { + 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 -> + ensureActive() + 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 + } + } + + maxScoreEdges!! + } + +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/model/CropEdges.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/model/CropEdges.kt new file mode 100644 index 0000000..cfd2680 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/model/CropEdges.kt @@ -0,0 +1,32 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/model/CropParameters.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/model/CropParameters.kt new file mode 100644 index 0000000..c51c66f --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/model/CropParameters.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +) \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/model/CropSensitivity.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/model/CropSensitivity.kt new file mode 100644 index 0000000..bf374ab --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/model/CropSensitivity.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/color_map/ColorMap.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/color_map/ColorMap.kt new file mode 100644 index 0000000..ad80670 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/color_map/ColorMap.kt @@ -0,0 +1,51 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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() + } + +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/color_map/model/ColorMapType.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/color_map/model/ColorMapType.kt new file mode 100644 index 0000000..1812377 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/color_map/model/ColorMapType.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/document_detector/DocumentDetector.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/document_detector/DocumentDetector.kt new file mode 100644 index 0000000..7af449c --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/document_detector/DocumentDetector.kt @@ -0,0 +1,327 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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.Mat +import org.opencv.core.MatOfPoint +import org.opencv.core.MatOfPoint2f +import org.opencv.core.Point +import org.opencv.core.Size +import org.opencv.geometry.Geometry +import org.opencv.imgproc.Imgproc +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.sqrt + +/** + * This class uses OpenCV to find document corners. + * + * @constructor creates document detector + */ +object DocumentDetector : OpenCV() { + + private const val RESIZE_THRESHOLD = 500.0 + private const val MIN_AREA_FACTOR = 0.04 + private const val MAX_AREA_FACTOR = 0.92 + private const val EXPECTED_MAX_COSINE = 0.4 + private const val EXPECTED_OPTIMAL_MAX_COSINE = 0.3 + private const val EXPECTED_AREA_FACTOR = 0.2 + private const val APPROX_EPSILON_FACTOR = 0.02 + private const val THRESHOLD_VALUE = 160.0 + private const val THRESHOLD_MAX_VALUE = 255.0 + + private data class DocumentCandidate( + val points: List, + val area: Double, + val maxCosine: Double, + val weight: Double + ) { + val score: Double = area + weight * (1 - maxCosine) + } + + /** + * 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 source = image.toMat() + val resized = Mat() + val rgbImage = Mat() + val blurredImage = Mat() + val imageSplitByColorChannel = mutableListOf() + val luvImage = Mat() + val imageSplitByLuvChannel = mutableListOf() + + try { + // shrink photo to make it easier to find document corners + val maxSide = max(image.width, image.height).toDouble() + val resizeScale = if (maxSide > RESIZE_THRESHOLD) { + maxSide / RESIZE_THRESHOLD + } else { + 1.0 + } + val scaledWidth = image.width / resizeScale + val scaledHeight = image.height / resizeScale + Imgproc.resize(source, resized, Size(scaledWidth, scaledHeight)) + + when (resized.channels()) { + 4 -> Imgproc.cvtColor(resized, rgbImage, Imgproc.COLOR_RGBA2RGB) + 1 -> Imgproc.cvtColor(resized, rgbImage, Imgproc.COLOR_GRAY2RGB) + else -> resized.copyTo(rgbImage) + } + + Imgproc.medianBlur(rgbImage, blurredImage, 9) + + val candidates = mutableListOf() + Core.split(blurredImage, imageSplitByColorChannel) + + Imgproc.cvtColor(blurredImage, luvImage, Imgproc.COLOR_RGB2Luv) + Core.split(luvImage, imageSplitByLuvChannel) + + var weight = 3_000_000.0 + (imageSplitByColorChannel + imageSplitByLuvChannel).forEach { channel -> + findCandidates( + image = channel, + imageWidth = scaledWidth, + imageHeight = scaledHeight, + candidates = candidates, + weight = weight + ) + weight -= 1.0 + } + + val documentCorners: List? = candidates + .maxByOrNull(DocumentCandidate::score) + ?.points + ?.map { point -> + Point( + point.x * resizeScale, + point.y * resizeScale + ) + } + + // sort points to force this order (top left, top right, bottom left, bottom right) + return documentCorners?.sortForCropper() + } finally { + source.release() + resized.release() + rgbImage.release() + blurredImage.release() + imageSplitByColorChannel.forEach(Mat::release) + luvImage.release() + imageSplitByLuvChannel.forEach(Mat::release) + } + } + + private fun findCandidates( + image: Mat, + imageWidth: Double, + imageHeight: Double, + candidates: MutableList, + weight: Double + ) { + val morphologyStruct = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, Size(4.0, 4.0)) + val dilateStruct = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, Size(3.0, 3.0)) + val thresholdImage = Mat() + val otsuImage = Mat() + + try { + Imgproc.threshold( + image, + thresholdImage, + THRESHOLD_VALUE, + THRESHOLD_MAX_VALUE, + Imgproc.THRESH_BINARY + ) + Imgproc.morphologyEx( + thresholdImage, + thresholdImage, + Imgproc.MORPH_CLOSE, + morphologyStruct + ) + Imgproc.dilate(thresholdImage, thresholdImage, dilateStruct) + collectCandidates(thresholdImage, imageWidth, imageHeight, candidates, weight) + + Imgproc.threshold( + image, + otsuImage, + 0.0, + THRESHOLD_MAX_VALUE, + Imgproc.THRESH_BINARY + Imgproc.THRESH_OTSU + ) + Imgproc.morphologyEx( + otsuImage, + otsuImage, + Imgproc.MORPH_CLOSE, + morphologyStruct + ) + Imgproc.dilate(otsuImage, otsuImage, dilateStruct) + collectCandidates(otsuImage, imageWidth, imageHeight, candidates, weight - 0.25) + + var threshold = 60 + while (threshold >= 10) { + val edgeImage = Mat() + try { + Imgproc.Canny( + image, + edgeImage, + threshold * 2.0, + threshold * 4.0 + ) + Imgproc.dilate(edgeImage, edgeImage, dilateStruct) + collectCandidates( + image = edgeImage, + imageWidth = imageWidth, + imageHeight = imageHeight, + candidates = candidates, + weight = weight - (60 - threshold + 1) + ) + } finally { + edgeImage.release() + } + + val bestCandidate = candidates.maxByOrNull(DocumentCandidate::score) + if ( + bestCandidate != null && + bestCandidate.maxCosine < EXPECTED_OPTIMAL_MAX_COSINE && + bestCandidate.area > imageWidth * imageHeight * EXPECTED_AREA_FACTOR + ) { + break + } + + threshold -= 10 + } + } finally { + morphologyStruct.release() + dilateStruct.release() + thresholdImage.release() + otsuImage.release() + } + } + + private fun collectCandidates( + image: Mat, + imageWidth: Double, + imageHeight: Double, + candidates: MutableList, + weight: Double + ) { + val contours: MutableList = mutableListOf() + val hierarchy = Mat() + + try { + Imgproc.findContours( + image, + contours, + hierarchy, + Imgproc.RETR_TREE, + Imgproc.CHAIN_APPROX_SIMPLE + ) + + val minArea = imageWidth * imageHeight * MIN_AREA_FACTOR + val maxArea = imageWidth * imageHeight * MAX_AREA_FACTOR + + contours.forEach { contour -> + val contourArea = Geometry.contourArea(contour) + if (contourArea !in minArea..= EXPECTED_MAX_COSINE) return@forEach + + candidates += DocumentCandidate( + points = points, + area = contourArea, + maxCosine = maxCosine, + weight = weight + ) + } finally { + approx.release() + } + } finally { + approxContour.release() + contour2f.release() + } + } + } finally { + contours.forEach(Mat::release) + hierarchy.release() + } + } + + private fun List.maxCornerCosine(): Double { + var maxCosine = 0.0 + for (i in 2 until 6) { + val cosine = abs( + angleCosine( + point1 = this[i % 4], + point2 = this[i - 2], + origin = this[(i - 1) % 4] + ) + ) + maxCosine = max(maxCosine, cosine) + } + return maxCosine + } + + private fun angleCosine( + point1: Point, + point2: Point, + origin: Point + ): Double { + val dx1 = point1.x - origin.x + val dy1 = point1.y - origin.y + val dx2 = point2.x - origin.x + val dy2 = point2.y - origin.y + return (dx1 * dx2 + dy1 * dy2) / + sqrt((dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2) + 1e-10) + } + + private fun List.sortForCropper(): List { + val sortedByY = sortedBy(Point::y) + val top = sortedByY.take(2).sortedBy(Point::x) + val bottom = sortedByY.takeLast(2).sortedBy(Point::x) + return top + bottom + } +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/expand/ImageExpander.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/expand/ImageExpander.kt new file mode 100644 index 0000000..8d5725c --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/expand/ImageExpander.kt @@ -0,0 +1,913 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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.opencv_tools.expand + +import android.graphics.Bitmap +import com.t8rin.opencv_tools.utils.OpenCV +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.withContext +import kotlin.math.max +import kotlin.math.min +import kotlin.random.Random + +object ImageExpander : OpenCV() { + + suspend fun expand( + bitmap: Bitmap, + left: Int, + top: Int, + right: Int, + bottom: Int + ): Bitmap = coroutineScope { + require(left >= 0 && top >= 0 && right >= 0 && bottom >= 0) + val outputWidth = bitmap.width.toLong() + left + right + val outputHeight = bitmap.height.toLong() + top + bottom + require(outputWidth in 1..Int.MAX_VALUE) + require(outputHeight in 1..Int.MAX_VALUE) + + withContext(Dispatchers.Default) { + if (left == 0 && top == 0 && right == 0 && bottom == 0) { + return@withContext bitmap.copy(Bitmap.Config.ARGB_8888, false) + } + + val source = bitmap.copy(Bitmap.Config.ARGB_8888, false) + val sourceWidth = source.width + val sourceHeight = source.height + val resultWidth = outputWidth.toInt() + val resultHeight = outputHeight.toInt() + val sourcePixels = IntArray(sourceWidth * sourceHeight) + val resultPixels = IntArray(resultWidth * resultHeight) + val knownPixels = BooleanArray(resultPixels.size) + + source.getPixels( + sourcePixels, + 0, + sourceWidth, + 0, + 0, + sourceWidth, + sourceHeight + ) + + copySourceToResult( + sourcePixels = sourcePixels, + resultPixels = resultPixels, + knownPixels = knownPixels, + sourceWidth = sourceWidth, + sourceHeight = sourceHeight, + resultWidth = resultWidth, + left = left, + top = top + ) + + if (sourceWidth == 1 && sourceHeight == 1) { + fillUnknownWithProjection( + sourcePixels = sourcePixels, + resultPixels = resultPixels, + knownPixels = knownPixels, + sourceWidth = sourceWidth, + sourceHeight = sourceHeight, + resultWidth = resultWidth, + left = left, + top = top + ) + } else { + val config = SynthesisConfig.forImage( + sourceWidth = sourceWidth, + sourceHeight = sourceHeight, + maxExpansion = max(max(left, right), max(top, bottom)) + ) + + synthesizeTexture( + sourcePixels = sourcePixels, + resultPixels = resultPixels, + knownPixels = knownPixels, + sourceWidth = sourceWidth, + sourceHeight = sourceHeight, + resultWidth = resultWidth, + resultHeight = resultHeight, + sourceLeft = left, + sourceTop = top, + config = config + ) + fillUnknownWithProjection( + sourcePixels = sourcePixels, + resultPixels = resultPixels, + knownPixels = knownPixels, + sourceWidth = sourceWidth, + sourceHeight = sourceHeight, + resultWidth = resultWidth, + left = left, + top = top + ) + } + + Bitmap.createBitmap( + resultPixels, + resultWidth, + resultHeight, + Bitmap.Config.ARGB_8888 + ) + } + } + + private suspend fun synthesizeTexture( + sourcePixels: IntArray, + resultPixels: IntArray, + knownPixels: BooleanArray, + sourceWidth: Int, + sourceHeight: Int, + resultWidth: Int, + resultHeight: Int, + sourceLeft: Int, + sourceTop: Int, + config: SynthesisConfig + ) = coroutineScope { + val random = Random( + sourceWidth * SeedSourceWidthFactor + + sourceHeight * SeedSourceHeightFactor + + resultWidth * SeedResultWidthFactor + + resultHeight * SeedResultHeightFactor + ) + val placements = buildPatchPlacements( + resultWidth = resultWidth, + resultHeight = resultHeight, + sourceLeft = sourceLeft, + sourceTop = sourceTop, + sourceWidth = sourceWidth, + sourceHeight = sourceHeight, + step = config.step, + patchSize = config.patchSize + ) + var remaining = knownPixels.count { !it } + var requiredOverlap = config.requiredOverlap + + repeat(config.maxPasses) { pass -> + ensureActive() + if (remaining == 0) return@coroutineScope + + var filledThisPass = 0 + val relaxedOverlap = if (pass >= config.maxPasses - RelaxedPassCount) { + 1 + } else { + requiredOverlap + } + + placements.forEach { placement -> + ensureActive() + val bounds = PatchBounds.fromCenter( + centerX = placement.centerX, + centerY = placement.centerY, + patchSize = config.patchSize, + imageWidth = resultWidth, + imageHeight = resultHeight + ) + + if (!bounds.hasUnknown(knownPixels, resultWidth)) return@forEach + + val overlap = bounds.knownCount(knownPixels, resultWidth, config.sampleStride) + if (overlap < relaxedOverlap) return@forEach + + val sourcePatch = findBestSourcePatch( + targetBounds = bounds, + sourcePixels = sourcePixels, + resultPixels = resultPixels, + knownPixels = knownPixels, + sourceWidth = sourceWidth, + sourceHeight = sourceHeight, + resultWidth = resultWidth, + sourceLeft = sourceLeft, + sourceTop = sourceTop, + config = config, + random = random + ) + val filled = copyUnknownPatch( + sourcePatch = sourcePatch, + targetBounds = bounds, + sourcePixels = sourcePixels, + resultPixels = resultPixels, + knownPixels = knownPixels, + sourceWidth = sourceWidth, + sourceHeight = sourceHeight, + resultWidth = resultWidth, + resultHeight = resultHeight, + sourceLeft = sourceLeft, + sourceTop = sourceTop, + blendWidth = config.blendWidth + ) + + filledThisPass += filled + remaining -= filled + } + + if (filledThisPass == 0) { + requiredOverlap = (requiredOverlap / 2).coerceAtLeast(1) + } + } + } + + private suspend fun copySourceToResult( + sourcePixels: IntArray, + resultPixels: IntArray, + knownPixels: BooleanArray, + sourceWidth: Int, + sourceHeight: Int, + resultWidth: Int, + left: Int, + top: Int + ) = coroutineScope { + for (y in 0 until sourceHeight) { + ensureActive() + val sourceIndex = y * sourceWidth + val resultIndex = (top + y) * resultWidth + left + sourcePixels.copyInto( + destination = resultPixels, + destinationOffset = resultIndex, + startIndex = sourceIndex, + endIndex = sourceIndex + sourceWidth + ) + + for (x in 0 until sourceWidth) { + ensureActive() + knownPixels[resultIndex + x] = true + } + } + } + + private suspend fun buildPatchPlacements( + resultWidth: Int, + resultHeight: Int, + sourceLeft: Int, + sourceTop: Int, + sourceWidth: Int, + sourceHeight: Int, + step: Int, + patchSize: Int + ): List { + val xs = steppedPositions(resultWidth, step) + val ys = steppedPositions(resultHeight, step) + val sourceRight = sourceLeft + sourceWidth - 1 + val sourceBottom = sourceTop + sourceHeight - 1 + + return ys.flatMap { y -> + xs.mapNotNull patch@{ x -> + val bounds = PatchBounds.fromCenter( + centerX = x, + centerY = y, + patchSize = patchSize, + imageWidth = resultWidth, + imageHeight = resultHeight + ) + + if (bounds.isInsideSource(sourceLeft, sourceTop, sourceWidth, sourceHeight)) { + return@patch null + } + + val dx = when { + x < sourceLeft -> sourceLeft - x + x > sourceRight -> x - sourceRight + else -> 0 + } + val dy = when { + y < sourceTop -> sourceTop - y + y > sourceBottom -> y - sourceBottom + else -> 0 + } + + PatchPlacement( + centerX = x, + centerY = y, + distance = dx * dx + dy * dy + ) + } + } + .sortedBy(PatchPlacement::distance) + } + + private suspend fun steppedPositions( + size: Int, + step: Int + ): List = coroutineScope { + buildSet { + var position = 0 + + while (position < size) { + ensureActive() + add(position) + position += step + } + + add(size - 1) + }.sorted() + } + + private suspend fun findBestSourcePatch( + targetBounds: PatchBounds, + sourcePixels: IntArray, + resultPixels: IntArray, + knownPixels: BooleanArray, + sourceWidth: Int, + sourceHeight: Int, + resultWidth: Int, + sourceLeft: Int, + sourceTop: Int, + config: SynthesisConfig, + random: Random + ): SourcePatch { + val allowed = SourceSearchArea.forTarget( + targetBounds = targetBounds, + sourceLeft = sourceLeft, + sourceTop = sourceTop, + sourceWidth = sourceWidth, + sourceHeight = sourceHeight, + bandSize = config.sourceBandSize + ) + val anchor = allowed.anchorPatch( + targetBounds = targetBounds, + sourceLeft = sourceLeft, + sourceTop = sourceTop, + sourceWidth = sourceWidth, + sourceHeight = sourceHeight + ) + + var bestPatch = anchor + var bestError = targetBounds.matchError( + sourcePatch = anchor, + sourcePixels = sourcePixels, + resultPixels = resultPixels, + knownPixels = knownPixels, + sourceWidth = sourceWidth, + resultWidth = resultWidth, + sampleStride = config.sampleStride + ) + + repeat(config.candidateCount) { + val candidate = allowed.randomPatch( + random = random + ) + val error = targetBounds.matchError( + sourcePatch = candidate, + sourcePixels = sourcePixels, + resultPixels = resultPixels, + knownPixels = knownPixels, + sourceWidth = sourceWidth, + resultWidth = resultWidth, + sampleStride = config.sampleStride + ) + + if (error < bestError) { + bestError = error + bestPatch = candidate + } + } + + return bestPatch + } + + private suspend fun copyUnknownPatch( + sourcePatch: SourcePatch, + targetBounds: PatchBounds, + sourcePixels: IntArray, + resultPixels: IntArray, + knownPixels: BooleanArray, + sourceWidth: Int, + sourceHeight: Int, + resultWidth: Int, + resultHeight: Int, + sourceLeft: Int, + sourceTop: Int, + blendWidth: Int + ): Int = coroutineScope { + var filled = 0 + val shouldBlendLeft = targetBounds.hasKnownColumn(knownPixels, resultWidth, 0) + val shouldBlendTop = targetBounds.hasKnownRow(knownPixels, resultWidth, 0) + val shouldBlendRight = targetBounds.hasKnownColumn( + knownPixels = knownPixels, + imageWidth = resultWidth, + localX = targetBounds.width - 1 + ) + val shouldBlendBottom = targetBounds.hasKnownRow( + knownPixels = knownPixels, + imageWidth = resultWidth, + localY = targetBounds.height - 1 + ) + + for (y in 0 until targetBounds.height) { + ensureActive() + val resultIndex = (targetBounds.top + y) * resultWidth + targetBounds.left + val sourceIndex = (sourcePatch.top + y) * sourceWidth + sourcePatch.left + + for (x in 0 until targetBounds.width) { + ensureActive() + val index = resultIndex + x + val resultX = targetBounds.left + x + val resultY = targetBounds.top + y + val incoming = sourcePixels[sourceIndex + x] + + if (!knownPixels[index]) { + resultPixels[index] = incoming + knownPixels[index] = true + filled++ + } else { + val insideOriginal = isInsideOriginalSource( + x = resultX, + y = resultY, + sourceLeft = sourceLeft, + sourceTop = sourceTop, + sourceWidth = sourceWidth, + sourceHeight = sourceHeight + ) + val blend = minOf( + if (shouldBlendLeft && x < blendWidth) { + smoothStep(x, blendWidth) + } else { + 1f + }, + if (shouldBlendTop && y < blendWidth) { + smoothStep(y, blendWidth) + } else { + 1f + }, + if (shouldBlendRight && targetBounds.width - x - 1 < blendWidth) { + smoothStep(targetBounds.width - x - 1, blendWidth) + } else { + 1f + }, + if (shouldBlendBottom && targetBounds.height - y - 1 < blendWidth) { + smoothStep(targetBounds.height - y - 1, blendWidth) + } else { + 1f + } + ) + + if (insideOriginal) { + val edgeBlend = originalEdgeBlendAmount( + x = resultX, + y = resultY, + sourceLeft = sourceLeft, + sourceTop = sourceTop, + sourceWidth = sourceWidth, + sourceHeight = sourceHeight, + resultWidth = resultWidth, + resultHeight = resultHeight, + blendWidth = blendWidth + ) + val amount = minOf(blend, edgeBlend) + + if (amount > 0f) { + resultPixels[index] = mixColors( + from = resultPixels[index], + to = incoming, + amount = amount + ) + } + } else if (blend < 1f) { + resultPixels[index] = mixColors( + from = resultPixels[index], + to = incoming, + amount = blend + ) + } + } + } + } + + filled + } + + private suspend fun fillUnknownWithProjection( + sourcePixels: IntArray, + resultPixels: IntArray, + knownPixels: BooleanArray, + sourceWidth: Int, + sourceHeight: Int, + resultWidth: Int, + left: Int, + top: Int + ) = coroutineScope { + for (index in knownPixels.indices) { + ensureActive() + if (knownPixels[index]) continue + + val x = index % resultWidth + val y = index / resultWidth + val sourceX = (x - left).coerceIn(0, sourceWidth - 1) + val sourceY = (y - top).coerceIn(0, sourceHeight - 1) + + resultPixels[index] = sourcePixels[sourceY * sourceWidth + sourceX] + knownPixels[index] = true + } + } + + private data class SynthesisConfig( + val patchSize: Int, + val step: Int, + val sampleStride: Int, + val requiredOverlap: Int, + val candidateCount: Int, + val sourceBandSize: Int, + val blendWidth: Int, + val maxPasses: Int + ) { + companion object { + fun forImage( + sourceWidth: Int, + sourceHeight: Int, + maxExpansion: Int + ): SynthesisConfig { + val minSide = min(sourceWidth, sourceHeight) + val patchSize = min(minSide, (minSide / PatchDivisor).coerceAtLeast(MinPatchSize)) + .coerceAtMost(MaxPatchSize) + .toOdd() + .coerceAtLeast(1) + val step = (patchSize / 2).coerceAtLeast(1) + val stride = when { + patchSize >= LargePatchSize -> 4 + patchSize >= MediumPatchSize -> 2 + else -> 1 + } + val sampledArea = ((patchSize + stride - 1) / stride).let { it * it } + val passes = + (maxExpansion / step + ExtraPassCount).coerceIn(MinPassCount, MaxPassCount) + + return SynthesisConfig( + patchSize = patchSize, + step = step, + sampleStride = stride, + requiredOverlap = (sampledArea / RequiredOverlapDivisor).coerceAtLeast(1), + candidateCount = when { + sourceWidth * sourceHeight < SmallImageArea -> SmallCandidateCount + patchSize >= LargePatchSize -> LargeCandidateCount + else -> DefaultCandidateCount + }, + sourceBandSize = (patchSize * SourceBandPatchMultiplier) + .coerceAtMost(max(sourceWidth, sourceHeight)), + blendWidth = (patchSize - step).coerceAtLeast(1), + maxPasses = passes + ) + } + } + } + + private data class PatchPlacement( + val centerX: Int, + val centerY: Int, + val distance: Int + ) + + private data class PatchBounds( + val left: Int, + val top: Int, + val right: Int, + val bottom: Int + ) { + val width: Int get() = right - left + val height: Int get() = bottom - top + + suspend fun hasUnknown( + knownPixels: BooleanArray, + imageWidth: Int + ): Boolean = coroutineScope { + for (y in top until bottom) { + ensureActive() + val index = y * imageWidth + left + + for (x in 0 until width) { + ensureActive() + if (!knownPixels[index + x]) return@coroutineScope true + } + } + + return@coroutineScope false + } + + suspend fun knownCount( + knownPixels: BooleanArray, + imageWidth: Int, + sampleStride: Int + ): Int = coroutineScope { + var count = 0 + + for (y in top until bottom step sampleStride) { + ensureActive() + val index = y * imageWidth + left + + for (x in 0 until width step sampleStride) { + ensureActive() + if (knownPixels[index + x]) count++ + } + } + + return@coroutineScope count + } + + suspend fun hasKnownColumn( + knownPixels: BooleanArray, + imageWidth: Int, + localX: Int + ): Boolean = coroutineScope { + for (y in top until bottom) { + ensureActive() + if (knownPixels[y * imageWidth + left + localX]) return@coroutineScope true + } + + return@coroutineScope false + } + + suspend fun hasKnownRow( + knownPixels: BooleanArray, + imageWidth: Int, + localY: Int + ): Boolean = coroutineScope { + val index = (top + localY) * imageWidth + left + + for (x in 0 until width) { + ensureActive() + if (knownPixels[index + x]) return@coroutineScope true + } + + return@coroutineScope false + } + + suspend fun matchError( + sourcePatch: SourcePatch, + sourcePixels: IntArray, + resultPixels: IntArray, + knownPixels: BooleanArray, + sourceWidth: Int, + resultWidth: Int, + sampleStride: Int + ): Long = coroutineScope { + var error = 0L + var samples = 0 + + for (y in 0 until height step sampleStride) { + ensureActive() + val resultIndex = (top + y) * resultWidth + left + val sourceIndex = (sourcePatch.top + y) * sourceWidth + sourcePatch.left + + for (x in 0 until width step sampleStride) { + ensureActive() + if (knownPixels[resultIndex + x]) { + error += colorDistance( + resultPixels[resultIndex + x], + sourcePixels[sourceIndex + x] + ) + samples++ + } + } + } + + return@coroutineScope if (samples == 0) Long.MAX_VALUE else error / samples + } + + fun isInsideSource( + sourceLeft: Int, + sourceTop: Int, + sourceWidth: Int, + sourceHeight: Int + ): Boolean = left >= sourceLeft && + top >= sourceTop && + right <= sourceLeft + sourceWidth && + bottom <= sourceTop + sourceHeight + + companion object { + fun fromCenter( + centerX: Int, + centerY: Int, + patchSize: Int, + imageWidth: Int, + imageHeight: Int + ): PatchBounds { + val radius = patchSize / 2 + val left = (centerX - radius).coerceAtLeast(0) + val top = (centerY - radius).coerceAtLeast(0) + + return PatchBounds( + left = left, + top = top, + right = (left + patchSize).coerceAtMost(imageWidth), + bottom = (top + patchSize).coerceAtMost(imageHeight) + ) + } + } + } + + private data class SourcePatch( + val left: Int, + val top: Int + ) + + private data class SourceSearchArea( + val minLeft: Int, + val maxLeft: Int, + val minTop: Int, + val maxTop: Int + ) { + fun anchorPatch( + targetBounds: PatchBounds, + sourceLeft: Int, + sourceTop: Int, + sourceWidth: Int, + sourceHeight: Int + ): SourcePatch { + val anchorX = (targetBounds.left + targetBounds.width / 2 - sourceLeft) + .coerceIn(0, sourceWidth - 1) + val anchorY = (targetBounds.top + targetBounds.height / 2 - sourceTop) + .coerceIn(0, sourceHeight - 1) + + return SourcePatch( + left = (anchorX - targetBounds.width / 2).coerceIn(minLeft, maxLeft), + top = (anchorY - targetBounds.height / 2).coerceIn(minTop, maxTop) + ) + } + + fun randomPatch(random: Random): SourcePatch = SourcePatch( + left = random.nextIntInclusive(minLeft, maxLeft), + top = random.nextIntInclusive(minTop, maxTop) + ) + + companion object { + fun forTarget( + targetBounds: PatchBounds, + sourceLeft: Int, + sourceTop: Int, + sourceWidth: Int, + sourceHeight: Int, + bandSize: Int + ): SourceSearchArea { + val sourceRight = sourceLeft + sourceWidth - 1 + val sourceBottom = sourceTop + sourceHeight - 1 + val maxPatchLeft = sourceWidth - targetBounds.width + val maxPatchTop = sourceHeight - targetBounds.height + val targetCenterX = targetBounds.left + targetBounds.width / 2 + val targetCenterY = targetBounds.top + targetBounds.height / 2 + val horizontalRange = when { + targetCenterX < sourceLeft -> 0..bandSize.coerceAtMost(maxPatchLeft) + targetCenterX > sourceRight -> (maxPatchLeft - bandSize) + .coerceAtLeast(0)..maxPatchLeft + + else -> 0..maxPatchLeft + } + val verticalRange = when { + targetCenterY < sourceTop -> 0..bandSize.coerceAtMost(maxPatchTop) + targetCenterY > sourceBottom -> (maxPatchTop - bandSize) + .coerceAtLeast(0)..maxPatchTop + + else -> 0..maxPatchTop + } + + return SourceSearchArea( + minLeft = horizontalRange.first, + maxLeft = horizontalRange.last, + minTop = verticalRange.first, + maxTop = verticalRange.last + ) + } + } + } + + private fun colorDistance( + first: Int, + second: Int + ): Long { + val alpha = (first ushr 24 and 0xFF) - (second ushr 24 and 0xFF) + val red = (first ushr 16 and 0xFF) - (second ushr 16 and 0xFF) + val green = (first ushr 8 and 0xFF) - (second ushr 8 and 0xFF) + val blue = (first and 0xFF) - (second and 0xFF) + + return (red * red * RedWeight + + green * green * GreenWeight + + blue * blue * BlueWeight + + alpha * alpha * AlphaWeight).toLong() + } + + private fun Int.toOdd(): Int = if (this % 2 == 0) this - 1 else this + + private fun Random.nextIntInclusive( + from: Int, + to: Int + ): Int = if (from >= to) from else nextInt(from, to + 1) + + private fun isInsideOriginalSource( + x: Int, + y: Int, + sourceLeft: Int, + sourceTop: Int, + sourceWidth: Int, + sourceHeight: Int + ): Boolean = x in sourceLeft until sourceLeft + sourceWidth && + y in sourceTop until sourceTop + sourceHeight + + private fun originalEdgeBlendAmount( + x: Int, + y: Int, + sourceLeft: Int, + sourceTop: Int, + sourceWidth: Int, + sourceHeight: Int, + resultWidth: Int, + resultHeight: Int, + blendWidth: Int + ): Float { + var amount = 0f + val sourceRight = sourceLeft + sourceWidth - 1 + val sourceBottom = sourceTop + sourceHeight - 1 + + if (sourceLeft > 0) { + val distance = x - sourceLeft + if (distance in 0 until blendWidth) { + amount = max(amount, 1f - smoothStep(distance, blendWidth)) + } + } + + if (sourceTop > 0) { + val distance = y - sourceTop + if (distance in 0 until blendWidth) { + amount = max(amount, 1f - smoothStep(distance, blendWidth)) + } + } + + if (sourceRight < resultWidth - 1) { + val distance = sourceRight - x + if (distance in 0 until blendWidth) { + amount = max(amount, 1f - smoothStep(distance, blendWidth)) + } + } + + if (sourceBottom < resultHeight - 1) { + val distance = sourceBottom - y + if (distance in 0 until blendWidth) { + amount = max(amount, 1f - smoothStep(distance, blendWidth)) + } + } + + return amount + } + + private fun smoothStep( + position: Int, + length: Int + ): Float { + val x = (position.toFloat() / (length - 1).coerceAtLeast(1)).coerceIn(0f, 1f) + return x * x * (3f - 2f * x) + } + + private fun mixColors( + from: Int, + to: Int, + amount: Float + ): Int { + val keep = 1f - amount + val alpha = (keep * (from ushr 24 and 0xFF) + amount * (to ushr 24 and 0xFF)).toInt() + val red = (keep * (from ushr 16 and 0xFF) + amount * (to ushr 16 and 0xFF)).toInt() + val green = (keep * (from ushr 8 and 0xFF) + amount * (to ushr 8 and 0xFF)).toInt() + val blue = (keep * (from and 0xFF) + amount * (to and 0xFF)).toInt() + + return (alpha shl 24) or (red shl 16) or (green shl 8) or blue + } + + private const val MinPatchSize = 7 + private const val MaxPatchSize = 120 + private const val MediumPatchSize = 15 + private const val LargePatchSize = 25 + private const val PatchDivisor = 8 + private const val RequiredOverlapDivisor = 5 + private const val ExtraPassCount = 6 + private const val MinPassCount = 8 + private const val MaxPassCount = 48 + private const val RelaxedPassCount = 2 + private const val SmallImageArea = 128 * 128 + private const val SmallCandidateCount = 32 + private const val DefaultCandidateCount = 64 + private const val LargeCandidateCount = 64 + private const val SourceBandPatchMultiplier = 3 + private const val RedWeight = 3 + private const val GreenWeight = 4 + private const val BlueWeight = 2 + private const val AlphaWeight = 2 + private const val SeedSourceWidthFactor = 73856093 + private const val SeedSourceHeightFactor = 19349663 + private const val SeedResultWidthFactor = 83492791 + private const val SeedResultHeightFactor = 265443576 + +} diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/forensics/ImageForensics.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/forensics/ImageForensics.kt new file mode 100644 index 0000000..a63bf3a --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/forensics/ImageForensics.kt @@ -0,0 +1,294 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.ensureActive +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() { + + suspend fun errorLevelAnalysis( + input: Bitmap, + quality: Int = 90 + ): Bitmap = coroutineScope { + ensureActive() + 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) + ensureActive() + + 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() + ensureActive() + + src.release() + resaved.release() + diff.release() + dst.release() + buf.release() + + outBmp + } + + suspend fun luminanceGradient(input: Bitmap): Bitmap = coroutineScope { + ensureActive() + 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) { + ensureActive() + 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) { + ensureActive() + 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) + ensureActive() + 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() + + outBmp + } + + suspend fun averageDistance(input: Bitmap): Bitmap = coroutineScope { + ensureActive() + 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) + ensureActive() + + 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() + ensureActive() + + src.release() + src32.release() + kernel.release() + filtered.release() + + diff.release() + + outBmp + } + + suspend fun detectCopyMove( + input: Bitmap, + retain: Int = 4, + qCoefficent: Double = 1.0 + ): Bitmap = coroutineScope { + ensureActive() + 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) { + ensureActive() + for (x in 0 until blocksWidth) { + ensureActive() + 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()) + } + } + + ensureActive() + val index = Array(totalBlocks) { it } + index.sortWith { a, b -> + ensureActive() + 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) { + ensureActive() + 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) { + ensureActive() + 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) { + ensureActive() + 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) + + dst.toBitmap() + } + +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/free_corners_crop/FreeCrop.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/free_corners_crop/FreeCrop.kt new file mode 100644 index 0000000..a162e30 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/free_corners_crop/FreeCrop.kt @@ -0,0 +1,236 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.BitmapRegionDecoder +import android.graphics.PointF +import android.net.Uri +import androidx.compose.ui.geometry.Offset +import androidx.core.net.toUri +import coil3.imageLoader +import coil3.request.ImageRequest +import coil3.request.allowHardware +import coil3.toBitmap +import com.t8rin.exif.ExifInterface +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 kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.withContext +import org.opencv.core.Mat +import org.opencv.core.MatOfPoint2f +import org.opencv.core.Point +import org.opencv.core.Size +import org.opencv.geometry.Geometry +import org.opencv.imgproc.Imgproc +import java.io.File +import java.io.FileOutputStream +import kotlin.math.ceil +import kotlin.math.floor +import kotlin.math.min +import android.graphics.Rect as AndroidRect +import coil3.size.Size as CoilSize + +object FreeCrop : OpenCV() { + + suspend fun cropToCache( + context: Context, + imageUri: Uri?, + fallbackBitmap: Bitmap, + fallbackPoints: List, + sourcePoints: List + ): Uri? = withContext(Dispatchers.Default) { + runCatching { + val region = context.loadRegionBitmap(imageUri, sourcePoints) + if (region != null) { + try { + crop( + bitmap = region.bitmap, + points = region.points + ).cacheAsPng(context) + } finally { + region.bitmap.recycleIfNeeded() + } + } else { + val sourceBitmap = context.loadBitmap(imageUri) + crop( + bitmap = sourceBitmap ?: fallbackBitmap, + points = if (sourceBitmap != null) sourcePoints else fallbackPoints + ).cacheAsPng(context) + } + }.getOrNull() ?: runCatching { + crop( + bitmap = fallbackBitmap, + points = fallbackPoints + ).cacheAsPng(context) + }.getOrNull() + } + + suspend fun crop( + bitmap: Bitmap, + points: List + ): Bitmap = coroutineScope { + 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, + Geometry.getPerspectiveTransform( + MatOfPoint2f(tLC, tRC, bRC, bLC), + croppedImage + ), + Size(width, height) + ) + + output.toBitmap() + } + +} + +private data class RegionBitmap( + val bitmap: Bitmap, + val points: List +) + +@Suppress("DEPRECATION") +private fun Context.loadRegionBitmap( + uri: Uri?, + points: List +): RegionBitmap? { + uri ?: return null + if (!hasNormalExifOrientation(uri)) return null + + return runCatching { + contentResolver.openInputStream(uri)?.use { input -> + val decoder = BitmapRegionDecoder.newInstance(input, false) ?: return null + try { + val rect = points.toBoundingRect(decoder.width, decoder.height) + ?: return null + val bitmap = decoder.decodeRegion( + rect, + BitmapFactory.Options().apply { + inPreferredConfig = Bitmap.Config.ARGB_8888 + } + ) ?: return null + + RegionBitmap( + bitmap = bitmap, + points = points.map { point -> + Offset( + x = point.x - rect.left, + y = point.y - rect.top + ) + } + ) + } finally { + decoder.recycle() + } + } + }.getOrNull() +} + +private fun Context.hasNormalExifOrientation(uri: Uri): Boolean { + return runCatching { + contentResolver.openInputStream(uri)?.use { input -> + ExifInterface(input).getAttributeInt( + ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL + ) + } == ExifInterface.ORIENTATION_NORMAL + }.getOrDefault(true) +} + +private suspend fun Context.loadBitmap(uri: Uri?): Bitmap? { + uri ?: return null + + return imageLoader.execute( + ImageRequest.Builder(this) + .data(uri) + .size(CoilSize.ORIGINAL) + .allowHardware(false) + .build() + ).image?.toBitmap() +} + +private fun List.toBoundingRect( + width: Int, + height: Int +): AndroidRect? { + if (isEmpty() || width <= 0 || height <= 0) return null + + val left = floor(minOf { it.x }).toInt() + .coerceIn(0, (width - 1).coerceAtLeast(0)) + val top = floor(minOf { it.y }).toInt() + .coerceIn(0, (height - 1).coerceAtLeast(0)) + val right = ceil(maxOf { it.x }).toInt() + .coerceIn(left + 1, width) + val bottom = ceil(maxOf { it.y }).toInt() + .coerceIn(top + 1, height) + + return AndroidRect(left, top, right, bottom) +} + +private fun Bitmap.recycleIfNeeded() { + if (!isRecycled) recycle() +} + +private fun Bitmap.cacheAsPng(context: Context): Uri { + val file = File( + File(context.cacheDir, "temp").apply(File::mkdirs), + "temp_free_crop_${System.currentTimeMillis()}.png" + ) + + FileOutputStream(file).use { output -> + check(compress(Bitmap.CompressFormat.PNG, 100, output)) + } + + return file.toUri() +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/free_corners_crop/compose/FreeCornersCropper.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/free_corners_crop/compose/FreeCornersCropper.kt new file mode 100644 index 0000000..1290d70 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/free_corners_crop/compose/FreeCornersCropper.kt @@ -0,0 +1,1204 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 android.net.Uri +import androidx.compose.animation.core.animate +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.spring +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.Image +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.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.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.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +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.StrokeCap +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.drawscope.clipPath +import androidx.compose.ui.graphics.graphicsLayer +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.positionChange +import androidx.compose.ui.input.pointer.positionChanged +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.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.toSize +import androidx.compose.ui.util.fastAny +import androidx.compose.ui.util.fastForEach +import com.t8rin.gesture.detectTransformGestures +import com.t8rin.image.ImageWithConstraints +import com.t8rin.opencv_tools.free_corners_crop.FreeCrop +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlin.math.exp +import kotlin.math.roundToInt + +@Composable +fun FreeCornersCropper( + bitmap: Bitmap, + sourceImageUri: Uri? = null, + sourceImageSize: IntSize = IntSize(bitmap.width, bitmap.height), + croppingTrigger: Boolean, + onCropped: (Uri?) -> Unit, + modifier: Modifier = Modifier, + showMagnifier: Boolean = true, + handlesSize: Dp = 8.dp, + frameStrokeWidth: Dp = 1.2.dp, + onZoomChange: (Float) -> Unit = {}, + coercePointsToImageArea: Boolean = true, + isOverlayDraggable: Boolean = true, + overlayColor: Color = Color.Black.copy(0.55f), + contentPadding: PaddingValues = PaddingValues(24.dp), + gridColor: Color, + handlesColor: Color +) { + val density = LocalDensity.current + val context = LocalContext.current + val scope = rememberCoroutineScope() + val imageKey = FreeCornersCropperImageKey( + bitmap = bitmap, + generationId = bitmap.generationId + ) + + val handleRadiusPx = with(density) { + handlesSize.toPx() + } + val strictCornerTouchRadiusPx = with(density) { + maxOf( + handleRadiusPx * STRICT_CORNER_TOUCH_RADIUS_MULTIPLIER, + MinStrictCornerTouchRadius.toPx() + ) + } + val cornerTouchRadiusPx = with(density) { + maxOf(handleRadiusPx * CORNER_TOUCH_RADIUS_MULTIPLIER, MinCornerTouchRadius.toPx()) + } + val edgeTouchRadiusPx = with(density) { + maxOf(handleRadiusPx * EDGE_TOUCH_RADIUS_MULTIPLIER, MinEdgeTouchRadius.toPx()) + } + val frameStrokeWidthPx = with(density) { + frameStrokeWidth.toPx() + } + val imageBitmap = remember(imageKey) { bitmap.asImageBitmap() } + + var dragTarget by remember(imageKey) { mutableStateOf(DragTarget.None) } + var magnifierCenter by remember(imageKey) { mutableStateOf(Offset.Unspecified) } + var baseImageBounds by remember(imageKey) { mutableStateOf(Rect.Zero) } + var drawPoints by remember(imageKey) { mutableStateOf(emptyList()) } + var imageScale by remember(imageKey) { mutableFloatStateOf(1f) } + var imageTranslation by remember(imageKey) { mutableStateOf(Offset.Zero) } + var transformAnimationJob by remember { mutableStateOf(null) } + + fun updateImageTransform(scale: Float, translation: Offset) { + imageScale = scale + imageTranslation = translation + } + + onZoomChange(imageScale) + + ImageWithConstraints( + modifier = modifier + .clipToBounds() + .pointerInput(imageKey, coercePointsToImageArea) { + detectOneFingerZoomGestures( + onDoubleTap = { position -> + transformAnimationJob?.cancel() + val minScale = if (coercePointsToImageArea) { + calculateMinimumScale( + cropBounds = drawPoints.boundingRect(), + imageBounds = baseImageBounds + ) + } else { + MINIMUM_CROP_ZOOM + } + val zoomedScale = DEFAULT_DOUBLE_TAP_SCALE.coerceIn(minScale, MAX_ZOOM) + val targetScale = if ( + imageScale >= zoomedScale - SCALE_COMPARISON_TOLERANCE + ) { + minScale + } else { + zoomedScale + } + val target = calculateTransformAfterZoom( + currentScale = imageScale, + currentTranslation = imageTranslation, + targetScale = targetScale, + centroid = position, + transformCenter = baseImageBounds.center + ) + transformAnimationJob = scope.launch { + animateTransformTo( + startScale = imageScale, + startTranslation = imageTranslation, + targetScale = targetScale, + targetTranslation = if (coercePointsToImageArea) { + coerceImageTranslation( + translation = target.translation, + scale = targetScale, + imageBounds = baseImageBounds, + cropBounds = drawPoints.boundingRect(), + coerceToCrop = true + ) + } else { + target.translation + }, + onUpdate = ::updateImageTransform + ) + } + }, + onZoomStart = { + transformAnimationJob?.cancel() + }, + onZoom = { centroid, zoom -> + val minScale = if (coercePointsToImageArea) { + calculateMinimumScale( + cropBounds = drawPoints.boundingRect(), + imageBounds = baseImageBounds + ) + } else { + MINIMUM_CROP_ZOOM + } + val targetScale = (imageScale * zoom).coerceIn( + minimumValue = minScale * MIN_SCALE_GESTURE_RESISTANCE, + maximumValue = MAX_ZOOM + ) + val target = calculateTransformAfterZoom( + currentScale = imageScale, + currentTranslation = imageTranslation, + targetScale = targetScale, + centroid = centroid, + transformCenter = baseImageBounds.center + ) + imageScale = targetScale + imageTranslation = if (coercePointsToImageArea) { + coerceImageTranslation( + translation = target.translation, + scale = targetScale, + imageBounds = baseImageBounds, + cropBounds = drawPoints.boundingRect(), + coerceToCrop = true + ) + } else { + target.translation + } + }, + onZoomEnd = { + if (!coercePointsToImageArea) { + return@detectOneFingerZoomGestures + } + + transformAnimationJob = scope.launch { + val minScale = calculateMinimumScale( + cropBounds = drawPoints.boundingRect(), + imageBounds = baseImageBounds + ) + val targetScale = imageScale.coerceIn(minScale, MAX_ZOOM) + val targetTranslation = coerceImageTranslation( + translation = imageTranslation, + scale = targetScale, + imageBounds = baseImageBounds, + cropBounds = drawPoints.boundingRect(), + coerceToCrop = coercePointsToImageArea + ) + animateTransformTo( + startScale = imageScale, + startTranslation = imageTranslation, + targetScale = targetScale, + targetTranslation = targetTranslation, + onUpdate = ::updateImageTransform + ) + } + } + ) + } + .pointerInput(imageKey, coercePointsToImageArea) { + var transformGestureActive = false + + detectTransformGestures( + consume = true, + onGestureStart = { + transformGestureActive = false + transformAnimationJob?.cancel() + }, + onGesture = { centroid, pan, zoom, _, _, _ -> + if (!transformGestureActive) { + transformGestureActive = true + transformAnimationJob?.cancel() + } + val minScale = if (coercePointsToImageArea) { + calculateMinimumScale( + cropBounds = drawPoints.boundingRect(), + imageBounds = baseImageBounds + ) + } else { + MINIMUM_CROP_ZOOM + } + val targetScale = (imageScale * zoom).coerceIn( + minimumValue = minScale * MIN_SCALE_GESTURE_RESISTANCE, + maximumValue = MAX_ZOOM + ) + val target = calculateTransformAfterZoom( + currentScale = imageScale, + currentTranslation = imageTranslation, + targetScale = targetScale, + centroid = centroid, + transformCenter = baseImageBounds.center + ) + imageScale = targetScale + imageTranslation = target.translation + pan + }, + onGestureEnd = { + if (!transformGestureActive || !coercePointsToImageArea) { + return@detectTransformGestures + } + + transformAnimationJob = scope.launch { + val minScale = calculateMinimumScale( + cropBounds = drawPoints.boundingRect(), + imageBounds = baseImageBounds + ) + val targetScale = imageScale.coerceIn(minScale, MAX_ZOOM) + val targetTranslation = coerceImageTranslation( + translation = imageTranslation, + scale = targetScale, + imageBounds = baseImageBounds, + cropBounds = drawPoints.boundingRect(), + coerceToCrop = coercePointsToImageArea + ) + animateTransformTo( + startScale = imageScale, + startTranslation = imageTranslation, + targetScale = targetScale, + targetTranslation = targetTranslation, + onUpdate = ::updateImageTransform + ) + } + } + ) + }, + imageBitmap = imageBitmap, + drawImage = false + ) { + val internalPaddingDp = 16.dp + val internalPadding = with(density) { internalPaddingDp.toPx() } + val containerWidthPx = with(density) { maxWidth.toPx() } + val containerHeightPx = with(density) { maxHeight.toPx() } + val horizontalPointInset = handleRadiusPx.coerceAtMost(containerWidthPx / 2f) + val verticalPointInset = handleRadiusPx.coerceAtMost(containerHeightPx / 2f) + val containerBounds = Rect( + left = horizontalPointInset, + top = verticalPointInset, + right = containerWidthPx - horizontalPointInset, + bottom = containerHeightPx - verticalPointInset + ) + val selectedCornerIndex = (dragTarget as? DragTarget.Corner)?.index + val selectedEdge = dragTarget as? DragTarget.Edge + val cropPointsInitialized = drawPoints.size == CROP_POINTS_COUNT + val pointScales = List(drawPoints.size) { + animateFloatAsState(if (it == selectedCornerIndex) 1.4f else 1f) + } + val edgeScales = edgeIndices.map { (firstIndex, secondIndex) -> + animateFloatAsState( + if (selectedEdge?.firstIndex == firstIndex && + selectedEdge.secondIndex == secondIndex + ) { + 1.4f + } else { + 1f + } + ) + } + + LaunchedEffect(croppingTrigger, baseImageBounds, cropPointsInitialized) { + if (croppingTrigger && cropPointsInitialized && baseImageBounds.hasPositiveSize()) { + fun scaledPointsFor(size: IntSize): List { + val widthScale = size.width.toFloat() / baseImageBounds.width.coerceAtLeast(1f) + val heightScale = + size.height.toFloat() / baseImageBounds.height.coerceAtLeast(1f) + + return drawPoints.map { + val untransformed = it.untransform( + center = baseImageBounds.center, + scale = imageScale, + translation = imageTranslation + ) + Offset( + x = ((untransformed.x - baseImageBounds.left) * widthScale).roundToInt() + .coerceIn(0, size.width).toFloat(), + y = ((untransformed.y - baseImageBounds.top) * heightScale).roundToInt() + .coerceIn(0, size.height).toFloat() + ) + } + } + + onCropped( + FreeCrop.cropToCache( + context = context, + imageUri = sourceImageUri, + fallbackBitmap = bitmap, + fallbackPoints = scaledPointsFor(IntSize(bitmap.width, bitmap.height)), + sourcePoints = scaledPointsFor( + sourceImageSize.takeIf { it != IntSize.Zero } + ?: IntSize(bitmap.width, bitmap.height) + ) + ) + ) + } + } + + Image( + bitmap = imageBitmap, + contentDescription = null, + modifier = Modifier + .padding(internalPaddingDp) + .padding(contentPadding) + .aspectRatio(bitmap.width / bitmap.height.toFloat()) + .onGloballyPositioned { + val position = it.positionInParent() + baseImageBounds = Rect( + offset = position, + size = it.size.toSize() + ) + } + .graphicsLayer { + scaleX = imageScale + scaleY = imageScale + translationX = imageTranslation.x + translationY = imageTranslation.y + }, + contentScale = ContentScale.FillBounds + ) + + LaunchedEffect(imageKey, baseImageBounds) { + if (baseImageBounds.hasPositiveSize()) { + transformAnimationJob?.cancel() + transformAnimationJob = null + val inset = internalPadding.coerceAtMost( + minOf(baseImageBounds.width, baseImageBounds.height) / 4f + ) + drawPoints = baseImageBounds.insetBy(inset).corners() + imageScale = 1f + imageTranslation = Offset.Zero + dragTarget = DragTarget.None + magnifierCenter = Offset.Unspecified + } + } + + fun currentPointBounds(): Rect = containerBounds + + fun Offset.coerceToPointBounds(): Offset = currentPointBounds().let { bounds -> + coerceIn( + horizontalRange = bounds.left..bounds.right, + verticalRange = bounds.top..bounds.bottom + ) + } + + fun Offset.coerceDragAmountFor(points: List): Offset { + val bounds = currentPointBounds() + val horizontalRange = bounds.left..bounds.right + val verticalRange = bounds.top..bounds.bottom + var minX = Float.NEGATIVE_INFINITY + var maxX = Float.POSITIVE_INFINITY + var minY = Float.NEGATIVE_INFINITY + var maxY = Float.POSITIVE_INFINITY + + points.forEach { point -> + minX = minX.coerceAtLeast(horizontalRange.start - point.x) + maxX = maxX.coerceAtMost(horizontalRange.endInclusive - point.x) + minY = minY.coerceAtLeast(verticalRange.start - point.y) + maxY = maxY.coerceAtMost(verticalRange.endInclusive - point.y) + } + + return Offset( + x = x.coerceIn(minX, maxX), + y = y.coerceIn(minY, maxY) + ) + } + + LaunchedEffect(coercePointsToImageArea, containerBounds, baseImageBounds) { + transformAnimationJob?.cancel() + if (drawPoints.size == CROP_POINTS_COUNT) { + drawPoints = drawPoints.map { + it.coerceToPointBounds() + } + } + } + + LaunchedEffect(drawPoints, coercePointsToImageArea, dragTarget) { + transformAnimationJob?.cancel() + if (!coercePointsToImageArea || + dragTarget != DragTarget.None || + drawPoints.size != CROP_POINTS_COUNT || + !baseImageBounds.hasPositiveSize() + ) { + return@LaunchedEffect + } + + val targetScale = imageScale.coerceAtLeast( + calculateMinimumScale( + cropBounds = drawPoints.boundingRect(), + imageBounds = baseImageBounds + ) + ) + val targetTranslation = coerceImageTranslation( + translation = imageTranslation, + scale = targetScale, + imageBounds = baseImageBounds, + cropBounds = drawPoints.boundingRect(), + coerceToCrop = true + ) + + if (targetScale != imageScale || targetTranslation != imageTranslation) { + transformAnimationJob = scope.launch { + animateTransformTo( + startScale = imageScale, + startTranslation = imageTranslation, + targetScale = targetScale, + targetTranslation = targetTranslation, + onUpdate = ::updateImageTransform + ) + } + } + } + + 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, + strictCornerTouchRadiusPx, + cornerTouchRadiusPx, + edgeTouchRadiusPx, + imageKey, + showMagnifier, + isOverlayDraggable, + containerBounds, + baseImageBounds + ) { + detectCropDragGestures( + hitTest = { offset -> + findDragTarget( + points = drawPoints, + touchPosition = offset, + touchRadii = TouchRadii( + strictCorner = strictCornerTouchRadiusPx, + corner = cornerTouchRadiusPx, + edge = edgeTouchRadiusPx + ), + isOverlayDraggable = isOverlayDraggable + ) + }, + onDragStart = { target -> + dragTarget = target + magnifierCenter = + if (showMagnifier && dragTarget != DragTarget.None) { + dragTarget.focusPoint(drawPoints) + } else { + Offset.Unspecified + } + }, + onDrag = { dragAmount -> + val selectedIndices = dragTarget.pointIndices + val selectedPoints = selectedIndices.mapNotNull { index -> + drawPoints.getOrNull(index) + } + val coercedDragAmount = dragAmount.coerceDragAmountFor(selectedPoints) + + if (selectedIndices.isNotEmpty()) { + drawPoints = drawPoints + .toMutableList() + .apply { + selectedIndices.forEach { index -> + this[index] = this[index] + .plus(coercedDragAmount) + .coerceToPointBounds() + } + } + + if (showMagnifier) { + magnifierCenter = dragTarget.focusPoint(drawPoints) + } + } + }, + onDragEnd = { + dragTarget = DragTarget.None + magnifierCenter = Offset.Unspecified + }, + onDragCancel = { + dragTarget = DragTarget.None + magnifierCenter = Offset.Unspecified + } + ) + } + ) { + if (drawPoints.size != CROP_POINTS_COUNT) return@Canvas + + val (x, y) = drawPoints[0] + val (x1, y1) = drawPoints[1] + val (x2, y2) = drawPoints[2] + val (x3, y3) = drawPoints[3] + + val framePath = Path().apply { + moveTo(x, y) + lineTo(x1, y1) + lineTo(x2, y2) + lineTo(x3, y3) + close() + } + + drawRect(overlayColor) + + drawPath( + path = framePath, + brush = SolidColor(Color.Transparent), + blendMode = BlendMode.Clear + ) + + clipPath(framePath) { + drawPerspectiveGrid( + points = drawPoints, + color = gridColor, + strokeWidth = frameStrokeWidthPx + ) + } + + drawPath( + path = framePath, + brush = SolidColor(gridColor), + style = Stroke(frameStrokeWidthPx) + ) + + edgeIndices.forEachIndexed { index, (firstIndex, secondIndex) -> + val firstPoint = drawPoints[firstIndex] + val secondPoint = drawPoints[secondIndex] + val edgeVector = secondPoint - firstPoint + val edgeLength = edgeVector.getDistance() + if (edgeLength > 0f) { + val center = (firstPoint + secondPoint) / 2f + val halfHandleLength = minOf( + handleRadiusPx * MIDDLE_HANDLE_LENGTH_MULTIPLIER * + edgeScales[index].value, + edgeLength / 2f + ) + val handleVector = edgeVector / edgeLength * halfHandleLength + + drawLine( + color = handlesColor, + start = center - handleVector, + end = center + handleVector, + strokeWidth = frameStrokeWidthPx * 4f * edgeScales[index].value, + cap = StrokeCap.Round + ) + } + } + + drawPoints.forEachIndexed { index, point -> + val scale = pointScales[index].value + + drawCircle( + color = handlesColor, + center = point, + radius = handleRadiusPx * scale + ) + } + } + } +} + +private sealed class DragTarget { + object None : DragTarget() + object Overlay : DragTarget() + data class Corner(val index: Int) : DragTarget() + data class Edge( + val firstIndex: Int, + val secondIndex: Int + ) : DragTarget() + + val pointIndices: List + get() = when (this) { + is Corner -> listOf(index) + is Edge -> listOf(firstIndex, secondIndex) + Overlay -> pointsIndices + None -> emptyList() + } + + private companion object { + val pointsIndices = listOf(0, 1, 2, 3) + } +} + +private suspend fun PointerInputScope.detectCropDragGestures( + hitTest: (Offset) -> DragTarget, + onDragStart: (DragTarget) -> Unit, + onDrag: (Offset) -> Unit, + onDragEnd: () -> Unit, + onDragCancel: () -> Unit +) { + var lastTapUpTime: Long? = null + var lastTapPosition = Offset.Unspecified + + awaitEachGesture { + val down = awaitFirstDown(requireUnconsumed = false) + val timeSinceLastTap = lastTapUpTime?.let { down.uptimeMillis - it } + val isSecondTap = lastTapPosition.isSpecified && + timeSinceLastTap != null && + timeSinceLastTap >= viewConfiguration.doubleTapMinTimeMillis && + timeSinceLastTap <= viewConfiguration.doubleTapTimeoutMillis && + (down.position - lastTapPosition).getDistance() <= + viewConfiguration.touchSlop * DOUBLE_TAP_POSITION_TOLERANCE_MULTIPLIER + val candidate = if (isSecondTap) DragTarget.None else hitTest(down.position) + + var accumulatedDrag = Offset.Zero + var dragStarted = false + var isCanceled = false + var isMultiTouch = false + var upTime = down.uptimeMillis + var upPosition = down.position + + do { + val event = awaitPointerEvent(PointerEventPass.Main) + if (event.changes.size > 1) { + isMultiTouch = true + isCanceled = dragStarted + } + + val change = event.changes.firstOrNull { it.id == down.id } + if (change == null) { + isCanceled = dragStarted + break + } + + upTime = change.uptimeMillis + upPosition = change.position + + if (change.isConsumed) { + isCanceled = dragStarted + break + } + + val dragAmount = change.positionChange() + if (!isMultiTouch && candidate != DragTarget.None && dragAmount != Offset.Zero) { + accumulatedDrag += dragAmount + if (dragStarted || accumulatedDrag.getDistance() > viewConfiguration.touchSlop) { + val appliedDrag = if (dragStarted) dragAmount else accumulatedDrag + if (!dragStarted) { + dragStarted = true + onDragStart(candidate) + } + change.consume() + onDrag(appliedDrag) + accumulatedDrag = Offset.Zero + } + } + } while (change.pressed) + + if (dragStarted) { + if (isCanceled) onDragCancel() else onDragEnd() + lastTapUpTime = null + lastTapPosition = Offset.Unspecified + } else { + if (isSecondTap || isMultiTouch || upTime - down.uptimeMillis > + viewConfiguration.longPressTimeoutMillis + ) { + lastTapUpTime = null + lastTapPosition = Offset.Unspecified + } else { + lastTapUpTime = upTime + lastTapPosition = upPosition + } + } + } +} + +private data class FreeCornersCropperImageKey( + val bitmap: Bitmap, + val generationId: Int +) + +private data class ImageTransform( + val translation: Offset +) + +private suspend fun PointerInputScope.detectOneFingerZoomGestures( + onDoubleTap: (Offset) -> Unit, + onZoomStart: () -> Unit, + onZoom: (centroid: Offset, zoom: Float) -> Unit, + onZoomEnd: () -> Unit +) = awaitEachGesture { + val firstDown = awaitFirstDown(requireUnconsumed = false) + var firstUp = firstDown + var hasMoved = false + var isMultiTouch = false + var isCanceled = false + + do { + val event = awaitPointerEvent() + if (event.changes.fastAny { it.isConsumed }) { + isCanceled = true + break + } + if (event.changes.fastAny { it.positionChanged() }) { + hasMoved = true + } + if (event.changes.size > 1) { + isMultiTouch = true + } + firstUp = event.changes.first() + } while (event.changes.fastAny { it.pressed }) + + val isTap = !hasMoved && + !isMultiTouch && + !isCanceled && + firstUp.uptimeMillis - firstDown.uptimeMillis <= + viewConfiguration.longPressTimeoutMillis + + if (isTap) { + val secondDown = awaitSecondDown(firstUp) + if (secondDown != null) { + var secondUp: PointerInputChange = secondDown + var isZooming = false + var isSecondMultiTouch = false + var isSecondCanceled = false + + do { + val event = awaitPointerEvent(PointerEventPass.Initial) + if (event.changes.fastAny { it.isConsumed }) { + isSecondCanceled = true + break + } + + if (event.changes.size > 1) { + isSecondMultiTouch = true + } + + val panChange = event.calculatePan() + if (!isSecondMultiTouch && panChange != Offset.Zero) { + if (!isZooming) { + onZoomStart() + } + isZooming = true + val zoomChange = exp(panChange.y * ONE_FINGER_ZOOM_SENSITIVITY) + if (zoomChange != 1f) { + onZoom( + event.calculateCentroid(useCurrent = true), + zoomChange + ) + } + event.changes.fastForEach { + if (it.positionChanged()) { + it.consume() + } + } + } + secondUp = event.changes.first() + } while (event.changes.fastAny { it.pressed }) + + if (isZooming && !isSecondMultiTouch && !isSecondCanceled) { + onZoomEnd() + } else if (!isSecondMultiTouch && + !isSecondCanceled && + secondUp.uptimeMillis - secondDown.uptimeMillis <= + viewConfiguration.longPressTimeoutMillis + ) { + onDoubleTap(secondUp.position) + } + } + } +} + +private suspend fun AwaitPointerEventScope.awaitSecondDown( + firstUp: PointerInputChange +): PointerInputChange? = withTimeoutOrNull(viewConfiguration.doubleTapTimeoutMillis) { + val minUptime = firstUp.uptimeMillis + viewConfiguration.doubleTapMinTimeMillis + var change: PointerInputChange + do { + change = awaitFirstDown() + } while (change.uptimeMillis < minUptime) + change +} + +private fun calculateMinimumScale( + cropBounds: Rect, + imageBounds: Rect +): Float { + if (cropBounds == Rect.Zero || !imageBounds.hasPositiveSize()) return 1f + + return maxOf( + cropBounds.width / imageBounds.width, + cropBounds.height / imageBounds.height + ).coerceIn(MINIMUM_CROP_ZOOM, MAX_ZOOM) +} + +private fun calculateTransformAfterZoom( + currentScale: Float, + currentTranslation: Offset, + targetScale: Float, + centroid: Offset, + transformCenter: Offset +): ImageTransform { + if (currentScale <= 0f || !centroid.isSpecified || !transformCenter.isSpecified) { + return ImageTransform(currentTranslation) + } + + val scaleChange = targetScale / currentScale + return ImageTransform( + translation = currentTranslation + + (centroid - transformCenter - currentTranslation) * (1f - scaleChange) + ) +} + +private fun coerceImageTranslation( + translation: Offset, + scale: Float, + imageBounds: Rect, + cropBounds: Rect, + coerceToCrop: Boolean +): Offset { + if (!imageBounds.hasPositiveSize()) return Offset.Zero + + val scaledImageBounds = imageBounds.transform( + center = imageBounds.center, + scale = scale, + translation = Offset.Zero + ) + + val horizontalRange: ClosedFloatingPointRange + val verticalRange: ClosedFloatingPointRange + + if (coerceToCrop && cropBounds != Rect.Zero) { + val minTranslationX = cropBounds.right - scaledImageBounds.right + val maxTranslationX = cropBounds.left - scaledImageBounds.left + val minTranslationY = cropBounds.bottom - scaledImageBounds.bottom + val maxTranslationY = cropBounds.top - scaledImageBounds.top + horizontalRange = minTranslationX..maxTranslationX + verticalRange = minTranslationY..maxTranslationY + } else { + val horizontalPan = ((scaledImageBounds.width - imageBounds.width) / 2f) + .coerceAtLeast(0f) + val verticalPan = ((scaledImageBounds.height - imageBounds.height) / 2f) + .coerceAtLeast(0f) + horizontalRange = -horizontalPan..horizontalPan + verticalRange = -verticalPan..verticalPan + } + + return Offset( + x = translation.x.coerceInOrCenter(horizontalRange), + y = translation.y.coerceInOrCenter(verticalRange) + ) +} + +private suspend fun animateTransformTo( + startScale: Float, + startTranslation: Offset, + targetScale: Float, + targetTranslation: Offset, + onUpdate: (scale: Float, translation: Offset) -> Unit +) { + animate( + initialValue = 0f, + targetValue = 1f, + animationSpec = spring() + ) { progress, _ -> + onUpdate( + startScale + (targetScale - startScale) * progress, + startTranslation + (targetTranslation - startTranslation) * progress + ) + } +} + +private fun findDragTarget( + points: List, + touchPosition: Offset, + touchRadii: TouchRadii, + isOverlayDraggable: Boolean +): DragTarget { + val touchedStrictCornerIndex = findTouchedCornerIndex( + points = points, + touchPosition = touchPosition, + distanceThresholdSquared = touchRadii.strictCorner.square() + ) + + if (touchedStrictCornerIndex != null) return DragTarget.Corner(touchedStrictCornerIndex) + + val touchedEdge = edgeIndices + .mapNotNull { (firstIndex, secondIndex) -> + val firstPoint = points.getOrNull(firstIndex) ?: return@mapNotNull null + val secondPoint = points.getOrNull(secondIndex) ?: return@mapNotNull null + val edgeTouch = touchPosition.edgeTouch(firstPoint, secondPoint) + + if (edgeTouch.projection in EdgeTouchProjectionRange && + edgeTouch.distanceSquared < touchRadii.edge.square() + ) { + DragTarget.Edge(firstIndex, secondIndex) to edgeTouch.distanceSquared + } else { + null + } + } + .minByOrNull { it.second } + ?.first + + if (touchedEdge != null) return touchedEdge + + val touchedCornerIndex = findTouchedCornerIndex( + points = points, + touchPosition = touchPosition, + distanceThresholdSquared = touchRadii.corner.square() + ) + + if (touchedCornerIndex != null) return DragTarget.Corner(touchedCornerIndex) + + return if (isOverlayDraggable && touchPosition.isInsidePolygon(points)) { + DragTarget.Overlay + } else { + DragTarget.None + } +} + +private fun DragTarget.focusPoint(points: List): Offset { + return when (this) { + is DragTarget.Corner -> points.getOrNull(index) ?: Offset.Unspecified + is DragTarget.Edge -> { + val firstPoint = points.getOrNull(firstIndex) ?: return Offset.Unspecified + val secondPoint = points.getOrNull(secondIndex) ?: return Offset.Unspecified + Offset( + x = (firstPoint.x + secondPoint.x) / 2f, + y = (firstPoint.y + secondPoint.y) / 2f + ) + } + + DragTarget.Overlay -> + points + .takeIf { it.isNotEmpty() } + ?.reduce(Offset::plus) + ?.div(points.size.toFloat()) + ?: Offset.Unspecified + + DragTarget.None -> Offset.Unspecified + } +} + +private fun findTouchedCornerIndex( + points: List, + touchPosition: Offset, + distanceThresholdSquared: Float +): Int? = points + .mapIndexedNotNull { index, point -> + val distanceSquared = point.minus(touchPosition).getDistanceSquared() + if (distanceSquared < distanceThresholdSquared) { + index to distanceSquared + } else { + null + } + } + .minByOrNull { it.second } + ?.first + +private fun Float.square(): Float = this * this + +private data class EdgeTouch( + val distanceSquared: Float, + val projection: Float +) + +private data class TouchRadii( + val strictCorner: Float, + val corner: Float, + val edge: Float +) + +private fun Offset.edgeTouch(start: Offset, end: Offset): EdgeTouch { + val segmentX = end.x - start.x + val segmentY = end.y - start.y + val lengthSquared = segmentX * segmentX + segmentY * segmentY + + if (lengthSquared == 0f) { + return EdgeTouch( + distanceSquared = minus(start).getDistanceSquared(), + projection = 0f + ) + } + + val projection = (((x - start.x) * segmentX + (y - start.y) * segmentY) / lengthSquared) + .coerceIn(0f, 1f) + val closestPoint = Offset( + x = start.x + projection * segmentX, + y = start.y + projection * segmentY + ) + + return EdgeTouch( + distanceSquared = minus(closestPoint).getDistanceSquared(), + projection = projection + ) +} + +private fun Offset.isInsidePolygon(points: List): Boolean { + if (points.size < 3) return false + + var isInside = false + var previous = points.last() + points.forEach { current -> + if ((current.y > y) != (previous.y > y)) { + val intersectionX = (previous.x - current.x) * (y - current.y) / + (previous.y - current.y) + current.x + if (x < intersectionX) isInside = !isInside + } + previous = current + } + return isInside +} + +private fun List.boundingRect(): Rect { + if (isEmpty()) return Rect.Zero + + return Rect( + left = minOf { it.x }, + top = minOf { it.y }, + right = maxOf { it.x }, + bottom = maxOf { it.y } + ) +} + +private fun Rect.hasPositiveSize(): Boolean = width > 0f && height > 0f + +private fun Rect.insetBy(value: Float): Rect = Rect( + left = left + value, + top = top + value, + right = right - value, + bottom = bottom - value +) + +private fun Rect.corners(): List = listOf( + topLeft, + topRight, + bottomRight, + bottomLeft +) + +private fun Rect.transform( + center: Offset, + scale: Float, + translation: Offset +): Rect = Rect( + topLeft = topLeft.transform(center, scale, translation), + bottomRight = bottomRight.transform(center, scale, translation) +) + +private fun Offset.transform( + center: Offset, + scale: Float, + translation: Offset +): Offset = center + (this - center) * scale + translation + +private fun Offset.untransform( + center: Offset, + scale: Float, + translation: Offset +): Offset = center + (this - center - translation) / scale + +private val edgeIndices = listOf( + 0 to 1, + 1 to 2, + 2 to 3, + 3 to 0 +) + +private val EdgeTouchProjectionRange = 0.20f..0.80f + +private val MinStrictCornerTouchRadius = 22.dp +private val MinCornerTouchRadius = 30.dp +private val MinEdgeTouchRadius = 22.dp + +private const val STRICT_CORNER_TOUCH_RADIUS_MULTIPLIER = 1.9f +private const val CORNER_TOUCH_RADIUS_MULTIPLIER = 3.2f +private const val EDGE_TOUCH_RADIUS_MULTIPLIER = 2.5f +private const val MIDDLE_HANDLE_LENGTH_MULTIPLIER = 1.5f +private const val DOUBLE_TAP_POSITION_TOLERANCE_MULTIPLIER = 4f +private const val MINIMUM_CROP_ZOOM = 0.05f +private const val CROP_POINTS_COUNT = 4 +private const val MAX_ZOOM = 20f +private const val DEFAULT_DOUBLE_TAP_SCALE = 3f +private const val MIN_SCALE_GESTURE_RESISTANCE = 0.85f +private const val SCALE_COMPARISON_TOLERANCE = 0.01f +private const val ONE_FINGER_ZOOM_SENSITIVITY = 0.004f + +private fun Offset.coerceIn( + horizontalRange: ClosedRange, + verticalRange: ClosedRange +) = Offset(this.x.coerceIn(horizontalRange), this.y.coerceIn(verticalRange)) + +private fun Float.coerceInOrCenter(range: ClosedFloatingPointRange): Float { + return if (range.start <= range.endInclusive) { + coerceIn(range) + } else { + (range.start + range.endInclusive) / 2f + } +} diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/free_corners_crop/compose/PerspectiveGrid.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/free_corners_crop/compose/PerspectiveGrid.kt new file mode 100644 index 0000000..bcf327d --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/free_corners_crop/compose/PerspectiveGrid.kt @@ -0,0 +1,152 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.DrawScope +import kotlin.math.abs + +internal fun DrawScope.drawPerspectiveGrid( + points: List, + color: Color, + strokeWidth: Float +) { + if (points.size != QUAD_POINTS_COUNT) return + + val transform = PerspectiveQuadTransform.from(points) + + fun pointAt(horizontalFraction: Float, verticalFraction: Float): Offset { + return transform + ?.map(horizontalFraction, verticalFraction) + ?: points.bilinearPoint(horizontalFraction, verticalFraction) + } + + for (index in 1 until GRID_DIVISIONS) { + val fraction = index.toFloat() / GRID_DIVISIONS + + drawLine( + color = color, + start = pointAt(fraction, 0f), + end = pointAt(fraction, 1f), + strokeWidth = strokeWidth + ) + drawLine( + color = color, + start = pointAt(0f, fraction), + end = pointAt(1f, fraction), + strokeWidth = strokeWidth + ) + } +} + +private data class PerspectiveQuadTransform( + val horizontalX: Float, + val verticalX: Float, + val originX: Float, + val horizontalY: Float, + val verticalY: Float, + val originY: Float, + val horizontalPerspective: Float, + val verticalPerspective: Float, + val fallbackPoints: List +) { + fun map(horizontalFraction: Float, verticalFraction: Float): Offset { + val denominator = horizontalPerspective * horizontalFraction + + verticalPerspective * verticalFraction + HOMOGENEOUS_ORIGIN + if (abs(denominator) <= TRANSFORM_EPSILON) { + return fallbackPoints.bilinearPoint(horizontalFraction, verticalFraction) + } + + val point = Offset( + x = (horizontalX * horizontalFraction + verticalX * verticalFraction + originX) / + denominator, + y = (horizontalY * horizontalFraction + verticalY * verticalFraction + originY) / + denominator + ) + return point.takeIf { it.x.isFinite() && it.y.isFinite() } + ?: fallbackPoints.bilinearPoint(horizontalFraction, verticalFraction) + } + + companion object { + fun from(points: List): PerspectiveQuadTransform? { + val topLeft = points[TOP_LEFT_INDEX] + val topRight = points[TOP_RIGHT_INDEX] + val bottomRight = points[BOTTOM_RIGHT_INDEX] + val bottomLeft = points[BOTTOM_LEFT_INDEX] + + val rightEdgeX = topRight.x - bottomRight.x + val rightEdgeY = topRight.y - bottomRight.y + val bottomEdgeX = bottomLeft.x - bottomRight.x + val bottomEdgeY = bottomLeft.y - bottomRight.y + val perspectiveX = topLeft.x - topRight.x + bottomRight.x - bottomLeft.x + val perspectiveY = topLeft.y - topRight.y + bottomRight.y - bottomLeft.y + val determinant = rightEdgeX * bottomEdgeY - bottomEdgeX * rightEdgeY + + if (abs(determinant) <= TRANSFORM_EPSILON) return null + + val horizontalPerspective = + (perspectiveX * bottomEdgeY - bottomEdgeX * perspectiveY) / determinant + val verticalPerspective = + (rightEdgeX * perspectiveY - perspectiveX * rightEdgeY) / determinant + val topRightDenominator = HOMOGENEOUS_ORIGIN + horizontalPerspective + val bottomLeftDenominator = HOMOGENEOUS_ORIGIN + verticalPerspective + val bottomRightDenominator = + HOMOGENEOUS_ORIGIN + horizontalPerspective + verticalPerspective + val minimumDenominator = minOf( + topRightDenominator, + bottomLeftDenominator, + bottomRightDenominator + ) + + if (minimumDenominator <= TRANSFORM_EPSILON) return null + + return PerspectiveQuadTransform( + horizontalX = topRight.x - topLeft.x + horizontalPerspective * topRight.x, + verticalX = bottomLeft.x - topLeft.x + verticalPerspective * bottomLeft.x, + originX = topLeft.x, + horizontalY = topRight.y - topLeft.y + horizontalPerspective * topRight.y, + verticalY = bottomLeft.y - topLeft.y + verticalPerspective * bottomLeft.y, + originY = topLeft.y, + horizontalPerspective = horizontalPerspective, + verticalPerspective = verticalPerspective, + fallbackPoints = points + ) + } + } +} + +private fun List.bilinearPoint( + horizontalFraction: Float, + verticalFraction: Float +): Offset { + val top = this[TOP_LEFT_INDEX] + + (this[TOP_RIGHT_INDEX] - this[TOP_LEFT_INDEX]) * horizontalFraction + val bottom = this[BOTTOM_LEFT_INDEX] + + (this[BOTTOM_RIGHT_INDEX] - this[BOTTOM_LEFT_INDEX]) * horizontalFraction + return top + (bottom - top) * verticalFraction +} + +private const val QUAD_POINTS_COUNT = 4 +private const val GRID_DIVISIONS = 3 +private const val TRANSFORM_EPSILON = 0.0001f +private const val HOMOGENEOUS_ORIGIN = 1f +private const val TOP_LEFT_INDEX = 0 +private const val TOP_RIGHT_INDEX = 1 +private const val BOTTOM_RIGHT_INDEX = 2 +private const val BOTTOM_LEFT_INDEX = 3 diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/free_corners_crop/model/Quad.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/free_corners_crop/model/Quad.kt new file mode 100644 index 0000000..91b8154 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/free_corners_crop/model/Quad.kt @@ -0,0 +1,38 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/image_comparison/ImageDiffTool.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/image_comparison/ImageDiffTool.kt new file mode 100644 index 0000000..80de2e6 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/image_comparison/ImageDiffTool.kt @@ -0,0 +1,133 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + } + +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/image_comparison/model/ComparisonType.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/image_comparison/model/ComparisonType.kt new file mode 100644 index 0000000..22a1314 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/image_comparison/model/ComparisonType.kt @@ -0,0 +1,20 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 } \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/image_processing/ImageProcessing.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/image_processing/ImageProcessing.kt new file mode 100644 index 0000000..9864984 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/image_processing/ImageProcessing.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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() + } + +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/LensCorrection.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/LensCorrection.kt new file mode 100644 index 0000000..22fd737 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/LensCorrection.kt @@ -0,0 +1,233 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.ensureActive +import org.json.JSONArray +import org.json.JSONObject +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() { + + suspend fun undistort( + bitmap: Bitmap, + lensDataUri: Uri, + intensity: Double = 1.0 + ): Bitmap = undistort( + bitmap = bitmap, + lensData = context.contentResolver.openInputStream(lensDataUri)!!, + intensity = intensity + ) + + suspend fun undistort( + bitmap: Bitmap, + lensData: InputStream, + intensity: Double = 1.0 + ): Bitmap = undistort( + bitmap = bitmap, + lensDataJson = lensData.bufferedReader().use(Reader::readText), + intensity = intensity + ) + + suspend fun undistort( + bitmap: Bitmap, + lensDataJson: String, + intensity: Double = 1.0 + ): Bitmap = undistort( + bitmap = bitmap, + lensProfile = LensProfile.fromJson(lensDataJson).withIntensity(intensity) + ) + + suspend fun undistort( + bitmap: Bitmap, + lensProfile: LensProfile + ): Bitmap = undistortImpl( + bitmap = bitmap, + cameraMatrix = lensProfile.cameraMatrix, + distortionCoeffs = lensProfile.distortionCoeffs, + calibWidth = lensProfile.calibWidth, + calibHeight = lensProfile.calibHeight + ) + + + private suspend fun undistortImpl( + bitmap: Bitmap, + cameraMatrix: List>, + distortionCoeffs: List, + calibWidth: Int, + calibHeight: Int + ): Bitmap = coroutineScope { + ensureActive() + 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 -> + ensureActive() + lcCheck( + value = row.size == 3, + message = InvalidMatrixSize() + ) + + row.forEachIndexed { j, value -> + ensureActive() + K.put(i, j, value) + } + } + + distortionCoeffs.forEachIndexed { i, v -> + ensureActive() + 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 + + Imgproc.fisheye_undistortImage(rgbaMat, undistorted, K, D, K, rgbaMat.size()) + ensureActive() + + Imgproc.cvtColor(undistorted, undistorted, Imgproc.COLOR_RGB2RGBA) + + 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() + +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/model/LCException.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/model/LCException.kt new file mode 100644 index 0000000..7ad9599 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/model/LCException.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + +sealed class LCException(message: String) : Throwable(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)") +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/model/LensProfile.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/model/LensProfile.kt new file mode 100644 index 0000000..823bb60 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/model/LensProfile.kt @@ -0,0 +1,33 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/model/Sample.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/model/Sample.kt new file mode 100644 index 0000000..ee469c4 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/model/Sample.kt @@ -0,0 +1,25 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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}" diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/moire/Moire.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/moire/Moire.kt new file mode 100644 index 0000000..aff3d5e --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/moire/Moire.kt @@ -0,0 +1,277 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.ensureActive +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() { + + suspend fun remove( + bitmap: Bitmap, + type: MoireType = MoireType.AUTO + ): Bitmap = coroutineScope { + ensureActive() + val rgba = bitmap.toMat() + + val channels = mutableListOf() + Core.split(rgba, channels) + + val filteredChannels = channels.take(3).map { channel -> + ensureActive() + 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) + + merged.toBitmap() + } + + private suspend fun detectNoiseType(dftMat: Mat): MoireType? = coroutineScope { + 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()) { + ensureActive() + 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@coroutineScope null + + val dataPts = Mat(peaks.size, 2, CvType.CV_64F) + for (i in peaks.indices) { + ensureActive() + 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") + + 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 suspend fun butterworthLP(size: Size, d0: Double, n: Int): Mat = coroutineScope { + 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) { + ensureActive() + for (j in 0 until cols) { + ensureActive() + 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) + } + } + + filter + } + + private suspend fun applyMask( + dftMat: Mat, + type: MoireType? + ) = coroutineScope { + if (type == null || type == MoireType.AUTO) return@coroutineScope + + 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) { + ensureActive() + for (x in -radius..radius) { + ensureActive() + 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) { + ensureActive() + for (x in 0 until cols) { + ensureActive() + 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) { + ensureActive() + 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) + } + +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/moire/model/MoireType.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/moire/model/MoireType.kt new file mode 100644 index 0000000..e828e78 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/moire/model/MoireType.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/qr_prepare/QrPrepareHelper.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/qr_prepare/QrPrepareHelper.kt new file mode 100644 index 0000000..bd42952 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/qr_prepare/QrPrepareHelper.kt @@ -0,0 +1,74 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + } + +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/red_eye/RedEyeRemover.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/red_eye/RedEyeRemover.kt new file mode 100644 index 0000000..4984812 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/red_eye/RedEyeRemover.kt @@ -0,0 +1,241 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.ensureActive +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 org.opencv.imgproc.Imgproc +import org.opencv.objdetect.FaceDetectorYN +import java.io.File +import java.io.FileOutputStream +import kotlin.math.max +import kotlin.math.min +import kotlin.math.roundToInt + +object RedEyeRemover : OpenCV() { + + suspend fun removeRedEyes( + bitmap: Bitmap, + minEyeSize: Double = 50.0, + redThreshold: Double = 150.0 + ): Bitmap = coroutineScope { + ensureActive() + val srcMat = bitmap.toMat() + + Imgproc.cvtColor(srcMat, srcMat, Imgproc.COLOR_RGBA2BGR) + val resultMat = srcMat.clone() + + val eyes = detectEyes(srcMat, minEyeSize) + + for (rect in eyes) { + ensureActive() + 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)) + + eyeMat.release() + bg.release() + maskRed.release() + maskCmp.release() + redEyeMask.release() + filledMask.release() + dilatedMask.release() + meanMat.release() + mean3.release() + eyeOut.release() + channels.forEach(Mat::release) + } + + Imgproc.cvtColor(resultMat, resultMat, Imgproc.COLOR_BGR2RGBA) + + val result = resultMat.toBitmap() + + srcMat.release() + resultMat.release() + + result + } + + private fun detectEyes( + srcMat: Mat, + minEyeSize: Double + ): List { + val detector = loadFaceDetector(srcMat.size()) + val faces = Mat() + + detector.detect(srcMat, faces) + + val result = buildList { + for (i in 0 until faces.rows()) { + val faceWidth = faces.value(i, 2) + val faceHeight = faces.value(i, 3) + + val eyeSize = max( + minEyeSize, + min(faceWidth, faceHeight) * 0.16 + ) + + createEyeRect( + imageWidth = srcMat.cols(), + imageHeight = srcMat.rows(), + center = Point( + faces.value(i, 4), + faces.value(i, 5) + ), + size = eyeSize + )?.let(::add) + + createEyeRect( + imageWidth = srcMat.cols(), + imageHeight = srcMat.rows(), + center = Point( + faces.value(i, 6), + faces.value(i, 7) + ), + size = eyeSize + )?.let(::add) + } + } + + faces.release() + + return result + } + + private fun createEyeRect( + imageWidth: Int, + imageHeight: Int, + center: Point, + size: Double + ): Rect? { + val left = (center.x - size / 2.0) + .roundToInt() + .coerceIn(0, imageWidth - 1) + + val top = (center.y - size / 2.0) + .roundToInt() + .coerceIn(0, imageHeight - 1) + + val right = (center.x + size / 2.0) + .roundToInt() + .coerceIn(0, imageWidth) + + val bottom = (center.y + size / 2.0) + .roundToInt() + .coerceIn(0, imageHeight) + + val rectWidth = right - left + val rectHeight = bottom - top + + if (rectWidth <= 0 || rectHeight <= 0) return null + + return Rect(left, top, rectWidth, rectHeight) + } + + 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) + + maskFloodfill.release() + floodfillMask.release() + maskInv.release() + + return filledMask + } + + private fun Mat.value(row: Int, col: Int): Double { + return get(row, col)?.firstOrNull() ?: 0.0 + } + + private fun loadFaceDetector(inputSize: Size): FaceDetectorYN { + return FaceDetectorYN.create( + copyAssetToCache(), + "", + inputSize, + 0.7f, + 0.3f, + 5000 + ) + } + + private fun copyAssetToCache(assetName: String = "face_detection_yunet_2026may.onnx"): String { + val file = File(context.cacheDir, assetName) + + if (!file.exists() || file.length() == 0L) { + context.assets.open(assetName).use { input -> + FileOutputStream(file).use { output -> + input.copyTo(output) + } + } + } + + return file.absolutePath + } +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/seam_carving/SeamCarver.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/seam_carving/SeamCarver.kt new file mode 100644 index 0000000..601769f --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/seam_carving/SeamCarver.kt @@ -0,0 +1,1039 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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.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 kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.ensureActive +import org.opencv.core.Core +import org.opencv.core.CvType +import org.opencv.core.Mat +import org.opencv.core.Scalar +import org.opencv.core.Size +import org.opencv.imgproc.Imgproc +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.roundToInt + +object SeamCarver : OpenCV() { + + private const val PROTECTED_MASK_ENERGY = 1_000_000_000.0 + private const val REMOVAL_MASK_ENERGY = -1_000_000_000.0 + private const val INSERTION_BATCH_RATIO = 0.15 + + suspend fun distort( + bitmap: Bitmap, + distortionPercent: Float, + maxInputSide: Int = 512 + ): Bitmap = coroutineScope { + ensureActive() + val amount = distortionPercent.coerceIn(0f, 95f) + if (amount <= 0f) return@coroutineScope bitmap + + val originalWidth = bitmap.width + val originalHeight = bitmap.height + val targetScale = 1f - amount / 100f + + val originalMat = bitmap.toMat() + var mat = originalMat.resizeToFit(maxInputSide) + if (mat !== originalMat) { + originalMat.release() + } + + val targetWidth = (mat.cols() * targetScale).roundToInt().coerceAtLeast(1) + val targetHeight = (mat.rows() * targetScale).roundToInt().coerceAtLeast(1) + + ensureActive() + mat = carveMat( + mat = mat, + desiredWidth = targetWidth, + desiredHeight = targetHeight + ) + + val restored = Mat() + Imgproc.resize( + mat, + restored, + Size(originalWidth.toDouble(), originalHeight.toDouble()), + 0.0, + 0.0, + Imgproc.INTER_CUBIC + ) + mat.release() + + restored.toBitmap() + } + + /** + * Main entry: + * input: bitmap, desiredWidth, desiredHeight + * returns: processed Bitmap. Smaller dimensions are reached by removing seams, larger dimensions + * by inserting seams. + */ + suspend fun carve( + bitmap: Bitmap, + desiredWidth: Int, + desiredHeight: Int, + protectionMask: Bitmap? = null, + useBackwardEnergy: Boolean = false, + useMaskAsRemoval: Boolean = false, + ): Bitmap = coroutineScope { + ensureActive() + val mat = bitmap.toMat() + val maskMat = protectionMask?.toMat()?.resizeTo( + width = mat.cols(), + height = mat.rows() + ) + + carveMat( + mat = mat, + desiredWidth = desiredWidth, + desiredHeight = desiredHeight, + protectionMask = maskMat, + useBackwardEnergy = useBackwardEnergy, + useMaskAsRemoval = useMaskAsRemoval, + ).toBitmap() + } + + private suspend fun carveMat( + mat: Mat, + desiredWidth: Int, + desiredHeight: Int, + protectionMask: Mat? = null, + useBackwardEnergy: Boolean = false, + useMaskAsRemoval: Boolean = false, + ): Mat = coroutineScope { + var current = mat + var currentMask = protectionMask + val targetW = desiredWidth.coerceAtLeast(1) + val targetH = desiredHeight.coerceAtLeast(1) + + if (useMaskAsRemoval) { + currentMask?.takeIf { hasMaskPixels(it) }?.let { + removeMaskedArea( + src = current, + mask = it, + useBackwardEnergy = useBackwardEnergy + ).let { result -> + current = result.image + result.mask.release() + currentMask = null + } + } + } + + resizeWidthWithSeams( + src = current, + targetWidth = targetW, + mask = currentMask, + useBackwardEnergy = useBackwardEnergy + ).let { + current = it.image + currentMask = it.mask + } + + var transposed = transposeMat(current) + current.release() + current = transposed + + currentMask = currentMask?.let { + val transposedMask = transposeMat(it) + it.release() + transposedMask + } + + resizeWidthWithSeams( + src = current, + targetWidth = targetH, + mask = currentMask, + useBackwardEnergy = useBackwardEnergy + ).let { + current = it.image + currentMask = it.mask + } + + transposed = transposeMat(current) + current.release() + currentMask?.release() + + transposed + } + + private suspend fun resizeWidthWithSeams( + src: Mat, + targetWidth: Int, + mask: Mat?, + useBackwardEnergy: Boolean + ): SeamResizeResult = coroutineScope { + var current = src + var currentMask = mask + + while (current.cols() > targetWidth) { + ensureActive() + val seam = findVerticalSeam( + src = current, + protectionMask = currentMask, + useBackwardEnergy = useBackwardEnergy, + useMaskAsRemoval = false + ) + + val resized = removeVerticalSeam(current, seam) + current.release() + current = resized + + currentMask = currentMask?.let { + val resizedMask = removeVerticalSeam(it, seam) + it.release() + resizedMask + } + } + + while (current.cols() < targetWidth) { + ensureActive() + val remaining = targetWidth - current.cols() + val insertCount = current.maxInsertionBatchSize() + .coerceAtMost(remaining) + val seams = findVerticalSeamsForInsertion( + src = current, + mask = currentMask, + count = insertCount, + useBackwardEnergy = useBackwardEnergy + ) + + val resized = insertVerticalSeams(current, seams) + current.release() + current = resized + + currentMask = currentMask?.let { + val resizedMask = insertVerticalSeams(it, seams) + it.release() + resizedMask + } + } + + SeamResizeResult( + image = current, + mask = currentMask + ) + } + + private fun Mat.maxInsertionBatchSize(): Int { + if (cols() <= 1) return 1 + + return (cols() * INSERTION_BATCH_RATIO) + .roundToInt() + .coerceIn(1, cols() - 1) + } + + private suspend fun findVerticalSeamsForInsertion( + src: Mat, + mask: Mat?, + count: Int, + useBackwardEnergy: Boolean + ): List = coroutineScope { + if (count <= 0) return@coroutineScope emptyList() + if (src.cols() <= 1) { + return@coroutineScope List(count) { IntArray(src.rows()) } + } + + var working = src.clone() + var workingMask = mask?.clone() + val columnIndexes = Array(src.rows()) { + MutableList(src.cols()) { column -> column } + } + val seams = mutableListOf() + + repeat(count.coerceAtMost(src.cols() - 1)) { + ensureActive() + val seam = findVerticalSeam( + src = working, + protectionMask = workingMask, + useBackwardEnergy = useBackwardEnergy, + useMaskAsRemoval = false + ) + seams += IntArray(src.rows()) { row -> + columnIndexes[row][seam[row]] + } + + for (row in 0 until src.rows()) { + ensureActive() + columnIndexes[row].removeAt(seam[row]) + } + + val resized = removeVerticalSeam(working, seam) + working.release() + working = resized + + workingMask = workingMask?.let { + val resizedMask = removeVerticalSeam(it, seam) + it.release() + resizedMask + } + } + + working.release() + workingMask?.release() + + seams + } + + private suspend fun removeMaskedArea( + src: Mat, + mask: Mat, + useBackwardEnergy: Boolean, + ): SeamMaskRemovalResult { + val bounds = findMaskBounds(mask) ?: return SeamMaskRemovalResult(src, mask) + val removeVertically = bounds.width <= bounds.height || src.rows() <= 1 + + if (removeVertically || src.cols() <= 1) { + return removeMaskWithVerticalSeams( + src = src, + mask = mask, + useBackwardEnergy = useBackwardEnergy + ) + } + + val transposedSrc = transposeMat(src) + src.release() + val transposedMask = transposeMat(mask) + mask.release() + + val result = removeMaskWithVerticalSeams( + src = transposedSrc, + mask = transposedMask, + useBackwardEnergy = useBackwardEnergy + ) + + val restoredImage = transposeMat(result.image) + result.image.release() + + val restoredMask = transposeMat(result.mask) + result.mask.release() + + return SeamMaskRemovalResult( + image = restoredImage, + mask = restoredMask + ) + } + + private suspend fun removeMaskWithVerticalSeams( + src: Mat, + mask: Mat, + useBackwardEnergy: Boolean, + ): SeamMaskRemovalResult = coroutineScope { + var current = src + var currentMask = mask + + while (current.cols() > 1 && hasMaskPixels(currentMask)) { + ensureActive() + val seam = findVerticalSeam( + src = current, + protectionMask = currentMask, + useBackwardEnergy = useBackwardEnergy, + useMaskAsRemoval = true, + ) + + if (!seamHitsMask(currentMask, seam)) break + + val resized = removeVerticalSeam(current, seam) + current.release() + current = resized + + val resizedMask = removeVerticalSeam(currentMask, seam) + currentMask.release() + currentMask = resizedMask + } + + SeamMaskRemovalResult( + image = current, + mask = currentMask + ) + } + + private fun Mat.resizeTo(width: Int, height: Int): Mat { + if (cols() == width && rows() == height) return this + + val resized = Mat() + Imgproc.resize( + this, + resized, + Size(width.toDouble(), height.toDouble()), + 0.0, + 0.0, + Imgproc.INTER_NEAREST + ) + release() + return resized + } + + private fun Mat.resizeToFit(maxSide: Int): Mat { + val currentMaxSide = max(cols(), rows()) + if (maxSide !in 1.. 0 + } + + var previous = DoubleArray(cols) + var current = DoubleArray(cols) + val rowEnergy = DoubleArray(cols) + val backtrack = Array(rows) { IntArray(cols) } + + energy.get(0, 0, rowEnergy) + for (col in 0 until cols) { + ensureActive() + previous[col] = if (isProtected(0, col)) { + Double.POSITIVE_INFINITY + } else { + rowEnergy[col] + } + } + + for (row in 1 until rows) { + ensureActive() + energy.get(row, 0, rowEnergy) + + for (col in 0 until cols) { + ensureActive() + if (isProtected(row, col)) { + current[col] = Double.POSITIVE_INFINITY + backtrack[row][col] = col + continue + } + + var minPrev = previous[col] + var bestColumn = col + + if (col > 0 && previous[col - 1] < minPrev) { + minPrev = previous[col - 1] + bestColumn = col - 1 + } + + if (col < cols - 1 && previous[col + 1] < minPrev) { + minPrev = previous[col + 1] + bestColumn = col + 1 + } + + current[col] = if (minPrev.isInfinite()) { + Double.POSITIVE_INFINITY + } else { + rowEnergy[col] + minPrev + } + + backtrack[row][col] = bestColumn + } + + val swap = previous + previous = current + current = swap + } + + var minCol = -1 + var minVal = Double.POSITIVE_INFINITY + + for (col in 0 until cols) { + ensureActive() + if (previous[col] < minVal) { + minVal = previous[col] + minCol = col + } + } + + if (minCol < 0 || minVal.isInfinite()) return@coroutineScope null + + val seam = IntArray(rows) + var column = minCol + + for (row in rows - 1 downTo 0) { + ensureActive() + seam[row] = column + if (row > 0) column = backtrack[row][column] + } + + seam + } + + /** + * Backward energy map using gradient magnitude (Sobel). + */ + private fun computeBackwardEnergy(src: Mat): Mat { + val gray = src.toGray() + 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) + + gray.release() + gradX.release() + gradY.release() + gradXSq.release() + gradYSq.release() + + return energy + } + + private suspend fun findForwardVerticalSeam( + src: Mat, + protectionMask: Mat?, + useMaskAsRemoval: Boolean, + ): IntArray { + val hardProtectedSeam = if (!useMaskAsRemoval && protectionMask != null) { + findForwardVerticalSeamInternal( + src = src, + protectionMask = protectionMask, + useMaskAsRemoval = false, + forbidProtectionMask = true + ) + } else { + null + } + + return hardProtectedSeam ?: findForwardVerticalSeamInternal( + src = src, + protectionMask = protectionMask, + useMaskAsRemoval = useMaskAsRemoval, + forbidProtectionMask = false + ) ?: IntArray(src.rows()) + } + + private suspend fun findForwardVerticalSeamInternal( + src: Mat, + protectionMask: Mat?, + useMaskAsRemoval: Boolean, + forbidProtectionMask: Boolean, + ): IntArray? = coroutineScope { + ensureActive() + val gray = src.toGray() + gray.convertTo(gray, CvType.CV_64F) + + val rows = gray.rows() + val cols = gray.cols() + val pixels = DoubleArray(rows * cols) + gray.get(0, 0, pixels) + gray.release() + + val maskEnergy = protectionMask?.createMaskEnergy(useMaskAsRemoval) + val protectedPixels = if (forbidProtectionMask) { + protectionMask?.toBinaryMaskPixels() + } else { + null + } + + fun pixelAt(row: Int, col: Int): Double { + val safeRow = row.coerceIn(0, rows - 1) + val safeCol = col.coerceIn(0, cols - 1) + return pixels[safeRow * cols + safeCol] + } + + fun isProtected(row: Int, col: Int): Boolean { + return protectedPixels?.let { + (it[row * cols + col].toInt() and 0xFF) > 0 + } ?: false + } + + var previous = DoubleArray(cols) { col -> + if (isProtected(0, col)) { + Double.POSITIVE_INFINITY + } else { + maskEnergy?.get(col) ?: 0.0 + } + } + + var current = DoubleArray(cols) + val backtrack = Array(rows) { IntArray(cols) } + + for (row in 1 until rows) { + ensureActive() + for (col in 0 until cols) { + ensureActive() + if (isProtected(row, col)) { + current[col] = Double.POSITIVE_INFINITY + backtrack[row][col] = col + continue + } + + val up = pixelAt(row - 1, col) + val left = pixelAt(row, col - 1) + val right = pixelAt(row, col + 1) + + val costUp = abs(right - left) + val costLeft = abs(up - left) + costUp + val costRight = abs(up - right) + costUp + + var best = previous[col] + costUp + var bestColumn = col + + if (col > 0) { + val leftTotal = previous[col - 1] + costLeft + if (leftTotal < best) { + best = leftTotal + bestColumn = col - 1 + } + } + + if (col < cols - 1) { + val rightTotal = previous[col + 1] + costRight + if (rightTotal < best) { + best = rightTotal + bestColumn = col + 1 + } + } + + current[col] = if (best.isInfinite()) { + Double.POSITIVE_INFINITY + } else { + best + (maskEnergy?.get(row * cols + col) ?: 0.0) + } + + backtrack[row][col] = bestColumn + } + + val swap = previous + previous = current + current = swap + } + + var minCol = -1 + var minVal = Double.POSITIVE_INFINITY + + for (col in 0 until cols) { + ensureActive() + if (previous[col] < minVal) { + minVal = previous[col] + minCol = col + } + } + + if (minCol < 0 || minVal.isInfinite()) return@coroutineScope null + + val seam = IntArray(rows) + var column = minCol + + for (row in rows - 1 downTo 0) { + ensureActive() + seam[row] = column + if (row > 0) column = backtrack[row][column] + } + + seam + } + + private suspend fun Mat.toBinaryMaskPixels(): ByteArray = coroutineScope { + val binaryMask = toBinaryMask(this@toBinaryMaskPixels) + val rows = binaryMask.rows() + val cols = binaryMask.cols() + val pixels = ByteArray(rows * cols) + val rowPixels = ByteArray(cols) + + for (row in 0 until rows) { + ensureActive() + binaryMask.get(row, 0, rowPixels) + System.arraycopy(rowPixels, 0, pixels, row * cols, cols) + } + + binaryMask.release() + pixels + } + + private fun addProtectionMaskEnergy(energy: Mat, protectionMask: Mat) { + val grayMask = toBinaryMask(protectionMask) + grayMask.convertTo(grayMask, CvType.CV_64F, PROTECTED_MASK_ENERGY / 255.0) + Core.add(energy, grayMask, energy) + grayMask.release() + } + + private fun applyRemovalMaskEnergy(energy: Mat, removalMask: Mat) { + val grayMask = toBinaryMask(removalMask) + val selector = Mat() + grayMask.convertTo(selector, CvType.CV_64F, 1.0 / 255.0) + + val removalValues = Mat() + selector.convertTo(removalValues, CvType.CV_64F, REMOVAL_MASK_ENERGY) + + val ones = Mat(energy.size(), CvType.CV_64F, Scalar(1.0)) + val inverseSelector = Mat() + Core.subtract(ones, selector, inverseSelector) + + val keptEnergy = Mat() + Core.multiply(energy, inverseSelector, keptEnergy) + Core.add(keptEnergy, removalValues, energy) + + grayMask.release() + selector.release() + removalValues.release() + ones.release() + inverseSelector.release() + keptEnergy.release() + } + + private fun toBinaryMask(mask: Mat): Mat { + val grayMask = mask.toGray() + Imgproc.threshold(grayMask, grayMask, 0.0, 255.0, Imgproc.THRESH_BINARY) + return grayMask + } + + private suspend fun Mat.createMaskEnergy( + useMaskAsRemoval: Boolean + ): DoubleArray = coroutineScope { + val grayMask = toBinaryMask(this@createMaskEnergy) + val rows = grayMask.rows() + val cols = grayMask.cols() + val rowPixels = ByteArray(cols) + val energy = DoubleArray(rows * cols) + val selectedEnergy = if (useMaskAsRemoval) { + REMOVAL_MASK_ENERGY + } else { + PROTECTED_MASK_ENERGY + } + + for (row in 0 until rows) { + ensureActive() + grayMask.get(row, 0, rowPixels) + for (col in 0 until cols) { + ensureActive() + if ((rowPixels[col].toInt() and 0xFF) > 0) { + energy[row * cols + col] = selectedEnergy + } + } + } + grayMask.release() + + energy + } + + private fun Mat.toGray(): Mat { + val gray = Mat() + when (channels()) { + 1 -> copyTo(gray) + 3 -> Imgproc.cvtColor(this, gray, Imgproc.COLOR_RGB2GRAY) + else -> Imgproc.cvtColor(this, gray, Imgproc.COLOR_RGBA2GRAY) + } + return gray + } + + private fun hasMaskPixels(mask: Mat): Boolean { + val grayMask = toBinaryMask(mask) + val hasPixels = Core.countNonZero(grayMask) > 0 + grayMask.release() + return hasPixels + } + + private suspend fun seamHitsMask(mask: Mat, seam: IntArray): Boolean = coroutineScope { + val grayMask = toBinaryMask(mask) + val value = ByteArray(1) + for (row in 0 until grayMask.rows()) { + ensureActive() + grayMask.get(row, seam[row], value) + if ((value[0].toInt() and 0xFF) > 0) { + grayMask.release() + return@coroutineScope true + } + } + grayMask.release() + + false + } + + private suspend fun findMaskBounds(mask: Mat): MaskBounds? = coroutineScope { + val grayMask = toBinaryMask(mask) + val cols = grayMask.cols() + val rowPixels = ByteArray(cols) + var left = cols + var right = -1 + var top = grayMask.rows() + var bottom = -1 + + for (row in 0 until grayMask.rows()) { + ensureActive() + grayMask.get(row, 0, rowPixels) + for (col in 0 until cols) { + ensureActive() + if ((rowPixels[col].toInt() and 0xFF) > 0) { + if (col < left) left = col + if (col > right) right = col + if (row < top) top = row + if (row > bottom) bottom = row + } + } + } + grayMask.release() + + if (right < left || bottom < top) return@coroutineScope null + + MaskBounds( + width = right - left + 1, + height = bottom - top + 1 + ) + } + + /** + * Find vertical seam with minimum cumulative energy. + */ + private suspend fun findLowestEnergyVerticalSeam(energy: Mat): IntArray = coroutineScope { + val rows = energy.rows() + val cols = energy.cols() + + var previous = DoubleArray(cols) + var current = DoubleArray(cols) + val rowEnergy = DoubleArray(cols) + val backtrack = Array(rows) { IntArray(cols) } + + energy.get(0, 0, previous) + + for (r in 1 until rows) { + ensureActive() + energy.get(r, 0, rowEnergy) + for (c in 0 until cols) { + ensureActive() + var minPrev = previous[c] + var idx = c + if (c > 0 && previous[c - 1] < minPrev) { + minPrev = previous[c - 1] + idx = c - 1 + } + if (c < cols - 1 && previous[c + 1] < minPrev) { + minPrev = previous[c + 1] + idx = c + 1 + } + current[c] = rowEnergy[c] + minPrev + backtrack[r][c] = idx + } + val swap = previous + previous = current + current = swap + } + + var minCol = 0 + var minVal = previous[0] + for (c in 1 until cols) { + ensureActive() + if (previous[c] < minVal) { + minVal = previous[c] + minCol = c + } + } + + val seam = IntArray(rows) + var cur = minCol + for (r in rows - 1 downTo 0) { + ensureActive() + seam[r] = cur + if (r > 0) cur = backtrack[r][cur] + } + + seam + } + + /** + * Remove vertical seam from src and return new Mat with cols-1. + */ + private suspend fun removeVerticalSeam(src: Mat, seam: IntArray): Mat = coroutineScope { + val rows = src.rows() + val cols = src.cols() + val dst = Mat(rows, cols - 1, src.type()) + + val rowBuf = ByteArray(cols * src.channels()) + val outBuf = ByteArray((cols - 1) * src.channels()) + + val channels = src.channels() + + for (r in 0 until rows) { + ensureActive() + src.get(r, 0, rowBuf) + val skipCol = seam[r] + var dstIdx = 0 + var srcIdx = 0 + for (c in 0 until cols) { + ensureActive() + if (c == skipCol) { + srcIdx += channels + continue + } + repeat(channels) { + ensureActive() + outBuf[dstIdx++] = rowBuf[srcIdx++] + } + } + dst.put(r, 0, outBuf) + } + + dst + } + + private suspend fun insertVerticalSeams( + src: Mat, + seams: List + ): Mat = coroutineScope { + val rows = src.rows() + val cols = src.cols() + val dst = Mat(rows, cols + seams.size, src.type()) + + val channels = src.channels() + val rowBuf = ByteArray(cols * channels) + val outBuf = ByteArray((cols + seams.size) * channels) + + for (row in 0 until rows) { + ensureActive() + val rowSeams = seams + .map { seam -> + ensureActive() + seam[row].coerceIn(0, cols - 1) + } + .sorted() + src.get(row, 0, rowBuf) + var dstIdx = 0 + var seamIndex = 0 + + for (col in 0 until cols) { + val srcIdx = col * channels + + repeat(channels) { ch -> + ensureActive() + outBuf[dstIdx++] = rowBuf[srcIdx + ch] + } + + while (seamIndex < rowSeams.size && rowSeams[seamIndex] == col) { + ensureActive() + val neighborCol = if (col < cols - 1) { + col + 1 + } else { + (col - 1).coerceAtLeast(0) + } + val neighborIdx = neighborCol * channels + + repeat(channels) { ch -> + ensureActive() + outBuf[dstIdx++] = averageByte( + first = rowBuf[srcIdx + ch], + second = rowBuf[neighborIdx + ch] + ) + } + seamIndex++ + } + } + + dst.put(row, 0, outBuf) + } + + dst + } + + private fun averageByte(first: Byte, second: Byte): Byte { + val firstValue = first.toInt() and 0xFF + val secondValue = second.toInt() and 0xFF + + return ((firstValue + secondValue) / 2).toByte() + } + + private data class SeamResizeResult( + val image: Mat, + val mask: Mat? + ) + + private data class SeamMaskRemovalResult( + val image: Mat, + val mask: Mat + ) + + private data class MaskBounds( + val width: Int, + val height: Int + ) + +} diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/spot_heal/SpotHealer.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/spot_heal/SpotHealer.kt new file mode 100644 index 0000000..94358de --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/spot_heal/SpotHealer.kt @@ -0,0 +1,68 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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() + } + + +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/spot_heal/model/HealType.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/spot_heal/model/HealType.kt new file mode 100644 index 0000000..eb153eb --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/spot_heal/model/HealType.kt @@ -0,0 +1,22 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} \ No newline at end of file diff --git a/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/utils/OpenCVUtils.kt b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/utils/OpenCVUtils.kt new file mode 100644 index 0000000..4028ed3 --- /dev/null +++ b/lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/utils/OpenCVUtils.kt @@ -0,0 +1,116 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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 + } + } +} \ No newline at end of file diff --git a/lib/palette/.gitignore b/lib/palette/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lib/palette/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/palette/build.gradle.kts b/lib/palette/build.gradle.kts new file mode 100644 index 0000000..f90627c --- /dev/null +++ b/lib/palette/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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) +} \ No newline at end of file diff --git a/lib/palette/src/main/AndroidManifest.xml b/lib/palette/src/main/AndroidManifest.xml new file mode 100644 index 0000000..205decf --- /dev/null +++ b/lib/palette/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/ColorByteFormat.kt b/lib/palette/src/main/java/com/t8rin/palette/ColorByteFormat.kt new file mode 100644 index 0000000..bb27495 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/ColorByteFormat.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/ColorGroup.kt b/lib/palette/src/main/java/com/t8rin/palette/ColorGroup.kt new file mode 100644 index 0000000..30c64f9 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/ColorGroup.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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() +) \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/ColorSpace.kt b/lib/palette/src/main/java/com/t8rin/palette/ColorSpace.kt new file mode 100644 index 0000000..8631a21 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/ColorSpace.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/ColorType.kt b/lib/palette/src/main/java/com/t8rin/palette/ColorType.kt new file mode 100644 index 0000000..dfeb29a --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/ColorType.kt @@ -0,0 +1,30 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/Palette.kt b/lib/palette/src/main/java/com/t8rin/palette/Palette.kt new file mode 100644 index 0000000..a6423e7 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/Palette.kt @@ -0,0 +1,293 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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() + ) +} \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/PaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/PaletteCoder.kt new file mode 100644 index 0000000..56f4e1e --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/PaletteCoder.kt @@ -0,0 +1,58 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) = + context.contentResolver.openInputStream(uri)!!.use { decode(it) } \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/PaletteCoderException.kt b/lib/palette/src/main/java/com/t8rin/palette/PaletteCoderException.kt new file mode 100644 index 0000000..75167d0 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/PaletteCoderException.kt @@ -0,0 +1,48 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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") + +} \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/PaletteColor.kt b/lib/palette/src/main/java/com/t8rin/palette/PaletteColor.kt new file mode 100644 index 0000000..7837936 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/PaletteColor.kt @@ -0,0 +1,694 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +) \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/PaletteFormat.kt b/lib/palette/src/main/java/com/t8rin/palette/PaletteFormat.kt new file mode 100644 index 0000000..4bc61f9 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/PaletteFormat.kt @@ -0,0 +1,249 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/ACBPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/ACBPaletteCoder.kt new file mode 100644 index 0000000..92010f7 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/ACBPaletteCoder.kt @@ -0,0 +1,333 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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()) + } + } +} \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/ACOPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/ACOPaletteCoder.kt new file mode 100644 index 0000000..ca8922e --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/ACOPaletteCoder.kt @@ -0,0 +1,229 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) + } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/ACTPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/ACTPaletteCoder.kt new file mode 100644 index 0000000..3bca8c0 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/ACTPaletteCoder.kt @@ -0,0 +1,130 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) + } + } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/AFPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/AFPaletteCoder.kt new file mode 100644 index 0000000..9f9619b --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/AFPaletteCoder.kt @@ -0,0 +1,321 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) + } + } +} \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/ASEPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/ASEPaletteCoder.kt new file mode 100644 index 0000000..53d440c --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/ASEPaletteCoder.kt @@ -0,0 +1,301 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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") + } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/AndroidColorsXMLCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/AndroidColorsXMLCoder.kt new file mode 100644 index 0000000..bd062ff --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/AndroidColorsXMLCoder.kt @@ -0,0 +1,128 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/AutodeskColorBookCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/AutodeskColorBookCoder.kt new file mode 100644 index 0000000..cdebe4d --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/AutodeskColorBookCoder.kt @@ -0,0 +1,187 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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} + +""" + } +} diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/BasicXMLCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/BasicXMLCoder.kt new file mode 100644 index 0000000..3acf212 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/BasicXMLCoder.kt @@ -0,0 +1,142 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/CPLPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/CPLPaletteCoder.kt new file mode 100644 index 0000000..d0556d2 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/CPLPaletteCoder.kt @@ -0,0 +1,409 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } + } + } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/CSVPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/CSVPaletteCoder.kt new file mode 100644 index 0000000..d101dff --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/CSVPaletteCoder.kt @@ -0,0 +1,108 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/CorelDraw3PaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/CorelDraw3PaletteCoder.kt new file mode 100644 index 0000000..9d40383 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/CorelDraw3PaletteCoder.kt @@ -0,0 +1,96 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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() + } +} + + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/CorelPainterCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/CorelPainterCoder.kt new file mode 100644 index 0000000..fda4dde --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/CorelPainterCoder.kt @@ -0,0 +1,110 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/CorelXMLPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/CorelXMLPaletteCoder.kt new file mode 100644 index 0000000..d48d3ff --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/CorelXMLPaletteCoder.kt @@ -0,0 +1,297 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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() + } +} \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/DCPPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/DCPPaletteCoder.kt new file mode 100644 index 0000000..a6e05af --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/DCPPaletteCoder.kt @@ -0,0 +1,241 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/GIMPPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/GIMPPaletteCoder.kt new file mode 100644 index 0000000..a75282f --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/GIMPPaletteCoder.kt @@ -0,0 +1,115 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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() +} diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/HEXPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/HEXPaletteCoder.kt new file mode 100644 index 0000000..2421ba1 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/HEXPaletteCoder.kt @@ -0,0 +1,114 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/HPLPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/HPLPaletteCoder.kt new file mode 100644 index 0000000..db3406e --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/HPLPaletteCoder.kt @@ -0,0 +1,105 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/ImagePaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/ImagePaletteCoder.kt new file mode 100644 index 0000000..4404b3b --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/ImagePaletteCoder.kt @@ -0,0 +1,170 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/JCWPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/JCWPaletteCoder.kt new file mode 100644 index 0000000..4b00b3e --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/JCWPaletteCoder.kt @@ -0,0 +1,177 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) + } + } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/JSONPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/JSONPaletteCoder.kt new file mode 100644 index 0000000..129f0d6 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/JSONPaletteCoder.kt @@ -0,0 +1,46 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) } + } +} + + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/KOfficePaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/KOfficePaletteCoder.kt new file mode 100644 index 0000000..76fbccd --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/KOfficePaletteCoder.kt @@ -0,0 +1,90 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/KRITAPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/KRITAPaletteCoder.kt new file mode 100644 index 0000000..5c63d46 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/KRITAPaletteCoder.kt @@ -0,0 +1,403 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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" + } +} diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/KotlinPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/KotlinPaletteCoder.kt new file mode 100644 index 0000000..ecd1955 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/KotlinPaletteCoder.kt @@ -0,0 +1,250 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/OpenOfficePaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/OpenOfficePaletteCoder.kt new file mode 100644 index 0000000..6363cf2 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/OpenOfficePaletteCoder.kt @@ -0,0 +1,107 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/PaintNETPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/PaintNETPaletteCoder.kt new file mode 100644 index 0000000..2c79dea --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/PaintNETPaletteCoder.kt @@ -0,0 +1,134 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/PaintShopProPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/PaintShopProPaletteCoder.kt new file mode 100644 index 0000000..cfeaef6 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/PaintShopProPaletteCoder.kt @@ -0,0 +1,115 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/PaletteFormatCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/PaletteFormatCoder.kt new file mode 100644 index 0000000..8fa1365 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/PaletteFormatCoder.kt @@ -0,0 +1,67 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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() +} \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/ProcreateSwatchesCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/ProcreateSwatchesCoder.kt new file mode 100644 index 0000000..46b5a52 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/ProcreateSwatchesCoder.kt @@ -0,0 +1,172 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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() + } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/RGBAPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/RGBAPaletteCoder.kt new file mode 100644 index 0000000..34f9561 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/RGBAPaletteCoder.kt @@ -0,0 +1,99 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/RGBPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/RGBPaletteCoder.kt new file mode 100644 index 0000000..70a3c68 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/RGBPaletteCoder.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/RIFFPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/RIFFPaletteCoder.kt new file mode 100644 index 0000000..e0af05e --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/RIFFPaletteCoder.kt @@ -0,0 +1,149 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) + } + } + } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/SKPPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/SKPPaletteCoder.kt new file mode 100644 index 0000000..5215547 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/SKPPaletteCoder.kt @@ -0,0 +1,107 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/SVGPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/SVGPaletteCoder.kt new file mode 100644 index 0000000..1e3567e --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/SVGPaletteCoder.kt @@ -0,0 +1,209 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/ScribusXMLPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/ScribusXMLPaletteCoder.kt new file mode 100644 index 0000000..0de728f --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/ScribusXMLPaletteCoder.kt @@ -0,0 +1,143 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/SkencilPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/SkencilPaletteCoder.kt new file mode 100644 index 0000000..7e3593d --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/SkencilPaletteCoder.kt @@ -0,0 +1,93 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/SketchPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/SketchPaletteCoder.kt new file mode 100644 index 0000000..d48bddc --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/SketchPaletteCoder.kt @@ -0,0 +1,123 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/SwatchbookerCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/SwatchbookerCoder.kt new file mode 100644 index 0000000..0e81e4d --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/SwatchbookerCoder.kt @@ -0,0 +1,292 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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(" ") + } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/SwiftPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/SwiftPaletteCoder.kt new file mode 100644 index 0000000..c6701e9 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/SwiftPaletteCoder.kt @@ -0,0 +1,159 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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)) + } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/VGA18BitPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/VGA18BitPaletteCoder.kt new file mode 100644 index 0000000..d69a14b --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/VGA18BitPaletteCoder.kt @@ -0,0 +1,109 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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() + } +} + + diff --git a/lib/palette/src/main/java/com/t8rin/palette/coders/VGA24BitPaletteCoder.kt b/lib/palette/src/main/java/com/t8rin/palette/coders/VGA24BitPaletteCoder.kt new file mode 100644 index 0000000..14ed81b --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/coders/VGA24BitPaletteCoder.kt @@ -0,0 +1,101 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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())) + } + } +} + + diff --git a/lib/palette/src/main/java/com/t8rin/palette/utils/BytesReader.kt b/lib/palette/src/main/java/com/t8rin/palette/utils/BytesReader.kt new file mode 100644 index 0000000..3d0ab61 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/utils/BytesReader.kt @@ -0,0 +1,329 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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() } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/utils/BytesWriter.kt b/lib/palette/src/main/java/com/t8rin/palette/utils/BytesWriter.kt new file mode 100644 index 0000000..b84c9b4 --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/utils/BytesWriter.kt @@ -0,0 +1,198 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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) } + } +} + diff --git a/lib/palette/src/main/java/com/t8rin/palette/utils/CSVParser.kt b/lib/palette/src/main/java/com/t8rin/palette/utils/CSVParser.kt new file mode 100644 index 0000000..3ba9f8f --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/utils/CSVParser.kt @@ -0,0 +1,43 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + } +} + + diff --git a/lib/palette/src/main/java/com/t8rin/palette/utils/HexUtils.kt b/lib/palette/src/main/java/com/t8rin/palette/utils/HexUtils.kt new file mode 100644 index 0000000..aa4612f --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/utils/HexUtils.kt @@ -0,0 +1,185 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + ) +} \ No newline at end of file diff --git a/lib/palette/src/main/java/com/t8rin/palette/utils/Xml.kt b/lib/palette/src/main/java/com/t8rin/palette/utils/Xml.kt new file mode 100644 index 0000000..ec6ae6f --- /dev/null +++ b/lib/palette/src/main/java/com/t8rin/palette/utils/Xml.kt @@ -0,0 +1,34 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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("'", "'") +} \ No newline at end of file diff --git a/lib/palette/src/test/java/com/t8rin/palette/coders/PaletteCoderCompatibilityTest.kt b/lib/palette/src/test/java/com/t8rin/palette/coders/PaletteCoderCompatibilityTest.kt new file mode 100644 index 0000000..1204b92 --- /dev/null +++ b/lib/palette/src/test/java/com/t8rin/palette/coders/PaletteCoderCompatibilityTest.kt @@ -0,0 +1,218 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + } +} diff --git a/lib/qrose/.gitignore b/lib/qrose/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lib/qrose/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/qrose/build.gradle.kts b/lib/qrose/build.gradle.kts new file mode 100644 index 0000000..d15c13f --- /dev/null +++ b/lib/qrose/build.gradle.kts @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/lib/qrose/src/main/AndroidManifest.xml b/lib/qrose/src/main/AndroidManifest.xml new file mode 100644 index 0000000..44008a4 --- /dev/null +++ b/lib/qrose/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/CachedPainter.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/CachedPainter.kt new file mode 100644 index 0000000..d409df4 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/CachedPainter.kt @@ -0,0 +1,135 @@ +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) + } +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/Converters.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/Converters.kt new file mode 100644 index 0000000..554828a --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/Converters.kt @@ -0,0 +1,52 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package io.github.alexzhirkevich.qrose + +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.drawscope.CanvasDrawScope +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection + +/** + * 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 +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/DelicateQRoseApi.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/DelicateQRoseApi.kt new file mode 100644 index 0000000..5b1dc76 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/DelicateQRoseApi.kt @@ -0,0 +1,7 @@ +package io.github.alexzhirkevich.qrose + +@RequiresOptIn( + message = "This API may negatively impact QR code functionality", + level = RequiresOptIn.Level.WARNING +) +annotation class DelicateQRoseApi \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/QrCodePainter.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/QrCodePainter.kt new file mode 100644 index 0000000..0500f7a --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/QrCodePainter.kt @@ -0,0 +1,711 @@ +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 +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/QrData.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/QrData.kt new file mode 100644 index 0000000..e6cde35 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/QrData.kt @@ -0,0 +1,226 @@ +@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("'", "\\'") +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/BarcodeEncoder.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/BarcodeEncoder.kt new file mode 100644 index 0000000..3d3d433 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/BarcodeEncoder.kt @@ -0,0 +1,14 @@ +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 +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/BarcodePainter.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/BarcodePainter.kt new file mode 100644 index 0000000..1d9c517 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/BarcodePainter.kt @@ -0,0 +1,128 @@ +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 + ) + ) + } + } +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/BarcodeType.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/BarcodeType.kt new file mode 100644 index 0000000..fdeb381 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/BarcodeType.kt @@ -0,0 +1,14 @@ +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) +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodabarEncoder.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodabarEncoder.kt new file mode 100644 index 0000000..3e6b221 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodabarEncoder.kt @@ -0,0 +1,110 @@ +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 + ) +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code128Encoder.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code128Encoder.kt new file mode 100644 index 0000000..525187d --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code128Encoder.kt @@ -0,0 +1,655 @@ +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) + ) +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt new file mode 100644 index 0000000..0ae7ba9 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt @@ -0,0 +1,70 @@ +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 +) diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code39Encoder.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code39Encoder.kt new file mode 100644 index 0000000..646005f --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code39Encoder.kt @@ -0,0 +1,118 @@ +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() + } +} + diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code93Encoder.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code93Encoder.kt new file mode 100644 index 0000000..672a5e5 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code93Encoder.kt @@ -0,0 +1,98 @@ +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() + } +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeEAN13Encoder.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeEAN13Encoder.kt new file mode 100644 index 0000000..a553aab --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeEAN13Encoder.kt @@ -0,0 +1,56 @@ +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 + } + +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeEAN8Encoder.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeEAN8Encoder.kt new file mode 100644 index 0000000..b6105e8 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeEAN8Encoder.kt @@ -0,0 +1,55 @@ +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 + } + +} + diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeITFEncoder.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeITFEncoder.kt new file mode 100644 index 0000000..0b7e397 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeITFEncoder.kt @@ -0,0 +1,57 @@ +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) + ) +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeUPCAEncoder.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeUPCAEncoder.kt new file mode 100644 index 0000000..80028ff --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeUPCAEncoder.kt @@ -0,0 +1,5 @@ +package io.github.alexzhirkevich.qrose.oned + +internal object CodeUPCAEncoder : BarcodeEncoder { + override fun encode(contents: String) = CodeEAN13Encoder.encode("0$contents") +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeUPCEEncoder.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeUPCEEncoder.kt new file mode 100644 index 0000000..de19325 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeUPCEEncoder.kt @@ -0,0 +1,48 @@ +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 + } + +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt new file mode 100644 index 0000000..4e2d32f --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt @@ -0,0 +1,147 @@ +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 +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/Neighbors.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/Neighbors.kt new file mode 100644 index 0000000..bb4dfc8 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/Neighbors.kt @@ -0,0 +1,67 @@ +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 + diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrBallShape.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrBallShape.kt new file mode 100644 index 0000000..ca69c96 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrBallShape.kt @@ -0,0 +1,63 @@ +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) + ) + } + } + } +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrBrush.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrBrush.kt new file mode 100644 index 0000000..81a3c05 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrBrush.kt @@ -0,0 +1,211 @@ +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) + } +} diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrCodeMatrix.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrCodeMatrix.kt new file mode 100644 index 0000000..9582614 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrCodeMatrix.kt @@ -0,0 +1,70 @@ +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) + ) +} + diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrCodeShape.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrCodeShape.kt new file mode 100644 index 0000000..a9d11b0 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrCodeShape.kt @@ -0,0 +1,201 @@ +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) + } + +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrColors.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrColors.kt new file mode 100644 index 0000000..d5ea7b1 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrColors.kt @@ -0,0 +1,21 @@ +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 +) \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrErrorCorrectionLevel.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrErrorCorrectionLevel.kt new file mode 100644 index 0000000..698ccfc --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrErrorCorrectionLevel.kt @@ -0,0 +1,43 @@ +@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) +} diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrFrameShape.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrFrameShape.kt new file mode 100644 index 0000000..4a49c08 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrFrameShape.kt @@ -0,0 +1,233 @@ +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) + } +} diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrLogo.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrLogo.kt new file mode 100644 index 0000000..257c5b1 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrLogo.kt @@ -0,0 +1,22 @@ +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, +) + diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrLogoPadding.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrLogoPadding.kt new file mode 100644 index 0000000..f1b823f --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrLogoPadding.kt @@ -0,0 +1,46 @@ +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 +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrLogoShape.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrLogoShape.kt new file mode 100644 index 0000000..e3d1bd3 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrLogoShape.kt @@ -0,0 +1,15 @@ +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) {} diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrOptions.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrOptions.kt new file mode 100644 index 0000000..68c6354 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrOptions.kt @@ -0,0 +1,53 @@ +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 + ) + } + +} + diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrPixelShape.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrPixelShape.kt new file mode 100644 index 0000000..87a94db --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrPixelShape.kt @@ -0,0 +1,26 @@ +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) {} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrShapeModifier.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrShapeModifier.kt new file mode 100644 index 0000000..e0fae78 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrShapeModifier.kt @@ -0,0 +1,21 @@ +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) +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrShapeModifiers.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrShapeModifiers.kt new file mode 100644 index 0000000..0b3e0e8 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrShapeModifiers.kt @@ -0,0 +1,125 @@ +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) + } + } +} diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrShapes.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrShapes.kt new file mode 100644 index 0000000..9b29889 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrShapes.kt @@ -0,0 +1,40 @@ +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 + ) +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/Util.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/Util.kt new file mode 100644 index 0000000..2130ee3 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/Util.kt @@ -0,0 +1,14 @@ +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() { + } + +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrColorsBuilderScope.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrColorsBuilderScope.kt new file mode 100644 index 0000000..0718041 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrColorsBuilderScope.kt @@ -0,0 +1,42 @@ +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 + ) + } +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrLogoBuilderScope.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrLogoBuilderScope.kt new file mode 100644 index 0000000..2395b10 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrLogoBuilderScope.kt @@ -0,0 +1,33 @@ +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) + } +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrOptionsBuilderScope.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrOptionsBuilderScope.kt new file mode 100644 index 0000000..107cf2c --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrOptionsBuilderScope.kt @@ -0,0 +1,42 @@ +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) + } +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrShapesBuilderScope.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrShapesBuilderScope.kt new file mode 100644 index 0000000..8df7a66 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrShapesBuilderScope.kt @@ -0,0 +1,61 @@ +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 + ) + } +} \ No newline at end of file diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrColorsBuilderScope.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrColorsBuilderScope.kt new file mode 100644 index 0000000..341778c --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrColorsBuilderScope.kt @@ -0,0 +1,18 @@ +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 +} diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrLogoBuilderScope.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrLogoBuilderScope.kt new file mode 100644 index 0000000..9aa6a21 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrLogoBuilderScope.kt @@ -0,0 +1,22 @@ +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 +} + diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrOptionsBuilderScope.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrOptionsBuilderScope.kt new file mode 100644 index 0000000..6cba101 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrOptionsBuilderScope.kt @@ -0,0 +1,39 @@ +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) +} + + + + diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrShapesBuilderScope.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrShapesBuilderScope.kt new file mode 100644 index 0000000..6c8543b --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrShapesBuilderScope.kt @@ -0,0 +1,24 @@ +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 +} + diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/QRCode.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/QRCode.kt new file mode 100644 index 0000000..4ddff33 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/QRCode.kt @@ -0,0 +1,327 @@ +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}" + + ")" +} + diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/QRCodeEnums.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/QRCodeEnums.kt new file mode 100644 index 0000000..b2efd20 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/QRCodeEnums.kt @@ -0,0 +1,72 @@ +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) +} diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/BitBuffer.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/BitBuffer.kt new file mode 100644 index 0000000..652398e --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/BitBuffer.kt @@ -0,0 +1,47 @@ +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() + } +} diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/ErrorMessage.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/ErrorMessage.kt new file mode 100644 index 0000000..2a69de8 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/ErrorMessage.kt @@ -0,0 +1,15 @@ +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" +} diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/Polynomial.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/Polynomial.kt new file mode 100644 index 0000000..a58e1f6 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/Polynomial.kt @@ -0,0 +1,56 @@ +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) + } +} diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRCodeSetup.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRCodeSetup.kt new file mode 100644 index 0000000..4e83c8c --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRCodeSetup.kt @@ -0,0 +1,303 @@ +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 + ) + } + } +} diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRCodeSquare.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRCodeSquare.kt new file mode 100644 index 0000000..a568dee --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRCodeSquare.kt @@ -0,0 +1,100 @@ +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 +} diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRData.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRData.kt new file mode 100644 index 0000000..c0c4b4c --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRData.kt @@ -0,0 +1,141 @@ +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 +} diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRMath.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRMath.kt new file mode 100644 index 0000000..a812eb9 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRMath.kt @@ -0,0 +1,44 @@ +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 + } + } +} diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRUtil.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRUtil.kt new file mode 100644 index 0000000..7c69f05 --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRUtil.kt @@ -0,0 +1,361 @@ +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 + } +} diff --git a/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/RSBlock.kt b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/RSBlock.kt new file mode 100644 index 0000000..c2474bc --- /dev/null +++ b/lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/RSBlock.kt @@ -0,0 +1,200 @@ +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 + } + } + } + } + } +} diff --git a/lib/snowfall/.gitignore b/lib/snowfall/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lib/snowfall/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/snowfall/build.gradle.kts b/lib/snowfall/build.gradle.kts new file mode 100644 index 0000000..55833b4 --- /dev/null +++ b/lib/snowfall/build.gradle.kts @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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" \ No newline at end of file diff --git a/lib/snowfall/src/main/AndroidManifest.xml b/lib/snowfall/src/main/AndroidManifest.xml new file mode 100644 index 0000000..063ac92 --- /dev/null +++ b/lib/snowfall/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + \ No newline at end of file diff --git a/lib/snowfall/src/main/java/com/t8rin/snowfall/Constants.kt b/lib/snowfall/src/main/java/com/t8rin/snowfall/Constants.kt new file mode 100644 index 0000000..e39c4bf --- /dev/null +++ b/lib/snowfall/src/main/java/com/t8rin/snowfall/Constants.kt @@ -0,0 +1,31 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} \ No newline at end of file diff --git a/lib/snowfall/src/main/java/com/t8rin/snowfall/SnowAnimState.kt b/lib/snowfall/src/main/java/com/t8rin/snowfall/SnowAnimState.kt new file mode 100644 index 0000000..4993e3e --- /dev/null +++ b/lib/snowfall/src/main/java/com/t8rin/snowfall/SnowAnimState.kt @@ -0,0 +1,162 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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, + ) + } + } + } +} \ No newline at end of file diff --git a/lib/snowfall/src/main/java/com/t8rin/snowfall/Snowfall.kt b/lib/snowfall/src/main/java/com/t8rin/snowfall/Snowfall.kt new file mode 100644 index 0000000..57bc997 --- /dev/null +++ b/lib/snowfall/src/main/java/com/t8rin/snowfall/Snowfall.kt @@ -0,0 +1,168 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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.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.layout.onSizeChanged +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, + 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, + 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, + 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, + 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 + } + 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) + } +} \ No newline at end of file diff --git a/lib/snowfall/src/main/java/com/t8rin/snowfall/Snowflake.kt b/lib/snowfall/src/main/java/com/t8rin/snowfall/Snowflake.kt new file mode 100644 index 0000000..513834e --- /dev/null +++ b/lib/snowfall/src/main/java/com/t8rin/snowfall/Snowflake.kt @@ -0,0 +1,145 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 + ) + ) + } + } + } + } +} \ No newline at end of file diff --git a/lib/snowfall/src/main/java/com/t8rin/snowfall/Utils.kt b/lib/snowfall/src/main/java/com/t8rin/snowfall/Utils.kt new file mode 100644 index 0000000..52d469b --- /dev/null +++ b/lib/snowfall/src/main/java/com/t8rin/snowfall/Utils.kt @@ -0,0 +1,35 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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()) \ No newline at end of file diff --git a/lib/snowfall/src/main/java/com/t8rin/snowfall/types/AnimType.kt b/lib/snowfall/src/main/java/com/t8rin/snowfall/types/AnimType.kt new file mode 100644 index 0000000..6bce9d8 --- /dev/null +++ b/lib/snowfall/src/main/java/com/t8rin/snowfall/types/AnimType.kt @@ -0,0 +1,23 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 +} \ No newline at end of file diff --git a/lib/snowfall/src/main/java/com/t8rin/snowfall/types/FlakeType.kt b/lib/snowfall/src/main/java/com/t8rin/snowfall/types/FlakeType.kt new file mode 100644 index 0000000..ea87000 --- /dev/null +++ b/lib/snowfall/src/main/java/com/t8rin/snowfall/types/FlakeType.kt @@ -0,0 +1,27 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * 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 { + class Custom(val data: List) : FlakeType +} \ No newline at end of file diff --git a/lib/zoomable/.gitignore b/lib/zoomable/.gitignore new file mode 100644 index 0000000..137870a --- /dev/null +++ b/lib/zoomable/.gitignore @@ -0,0 +1,3 @@ +/build +/.idea/ +/.idea \ No newline at end of file diff --git a/lib/zoomable/build.gradle.kts b/lib/zoomable/build.gradle.kts new file mode 100644 index 0000000..fff6391 --- /dev/null +++ b/lib/zoomable/build.gradle.kts @@ -0,0 +1,6 @@ +plugins { + alias(libs.plugins.image.toolbox.library) + alias(libs.plugins.image.toolbox.compose) +} + +android.namespace = "net.engawapg.lib.zoomable" \ No newline at end of file diff --git a/lib/zoomable/src/main/AndroidManifest.xml b/lib/zoomable/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0cff186 --- /dev/null +++ b/lib/zoomable/src/main/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/lib/zoomable/src/main/java/net/engawapg/lib/zoomable/ZoomState.kt b/lib/zoomable/src/main/java/net/engawapg/lib/zoomable/ZoomState.kt new file mode 100644 index 0000000..cc157af --- /dev/null +++ b/lib/zoomable/src/main/java/net/engawapg/lib/zoomable/ZoomState.kt @@ -0,0 +1,414 @@ +/* + * 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 + ) +} \ No newline at end of file diff --git a/lib/zoomable/src/main/java/net/engawapg/lib/zoomable/Zoomable.kt b/lib/zoomable/src/main/java/net/engawapg/lib/zoomable/Zoomable.kt new file mode 100644 index 0000000..b56cf75 --- /dev/null +++ b/lib/zoomable/src/main/java/net/engawapg/lib/zoomable/Zoomable.kt @@ -0,0 +1,511 @@ +/* + * 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) +} \ No newline at end of file diff --git a/lib/zoomable/src/main/java/net/engawapg/lib/zoomable/ZoomableDefaults.kt b/lib/zoomable/src/main/java/net/engawapg/lib/zoomable/ZoomableDefaults.kt new file mode 100644 index 0000000..a87721e --- /dev/null +++ b/lib/zoomable/src/main/java/net/engawapg/lib/zoomable/ZoomableDefaults.kt @@ -0,0 +1,26 @@ +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 } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..f961d2b --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,155 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language 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:texture-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:app-logs") +include(":feature:shader-studio") +include(":feature:help") +include(":feature:usage-statistics") +include(":feature:batch-rename") +include(":feature:duplicate-finder") + +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")